diff options
Diffstat (limited to 'src/dutil/cabcutil.cpp')
| -rw-r--r-- | src/dutil/cabcutil.cpp | 1532 |
1 files changed, 1532 insertions, 0 deletions
diff --git a/src/dutil/cabcutil.cpp b/src/dutil/cabcutil.cpp new file mode 100644 index 00000000..35fefaba --- /dev/null +++ b/src/dutil/cabcutil.cpp | |||
| @@ -0,0 +1,1532 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | #include "precomp.h" | ||
| 4 | |||
| 5 | static const WCHAR CABC_MAGIC_UNICODE_STRING_MARKER = '?'; | ||
| 6 | static const DWORD MAX_CABINET_HEADER_SIZE = 16 * 1024 * 1024; | ||
| 7 | |||
| 8 | // The minimum number of uncompressed bytes between FciFlushFolder() calls - if we call FciFlushFolder() | ||
| 9 | // too often (because of duplicates too close together) we theoretically ruin our compression ratio - | ||
| 10 | // left at zero to maximize install-time performance, because even a small minimum threshhold seems to | ||
| 11 | // have a high install-time performance cost for little or no size benefit. The value is left here for | ||
| 12 | // tweaking though - possible suggested values are 524288 for 512K, or 2097152 for 2MB. | ||
| 13 | static const DWORD MINFLUSHTHRESHHOLD = 0; | ||
| 14 | |||
| 15 | // structs | ||
| 16 | struct MS_CABINET_HEADER | ||
| 17 | { | ||
| 18 | DWORD sig; | ||
| 19 | DWORD csumHeader; | ||
| 20 | DWORD cbCabinet; | ||
| 21 | DWORD csumFolders; | ||
| 22 | DWORD coffFiles; | ||
| 23 | DWORD csumFiles; | ||
| 24 | WORD version; | ||
| 25 | WORD cFolders; | ||
| 26 | WORD cFiles; | ||
| 27 | WORD flags; | ||
| 28 | WORD setID; | ||
| 29 | WORD iCabinet; | ||
| 30 | }; | ||
| 31 | |||
| 32 | |||
| 33 | struct MS_CABINET_ITEM | ||
| 34 | { | ||
| 35 | DWORD cbFile; | ||
| 36 | DWORD uoffFolderStart; | ||
| 37 | WORD iFolder; | ||
| 38 | WORD date; | ||
| 39 | WORD time; | ||
| 40 | WORD attribs; | ||
| 41 | }; | ||
| 42 | |||
| 43 | struct CABC_INTERNAL_ADDFILEINFO | ||
| 44 | { | ||
| 45 | LPCWSTR wzSourcePath; | ||
| 46 | LPCWSTR wzEmptyPath; | ||
| 47 | }; | ||
| 48 | |||
| 49 | struct CABC_DUPLICATEFILE | ||
| 50 | { | ||
| 51 | DWORD dwFileArrayIndex; | ||
| 52 | DWORD dwDuplicateCabFileIndex; | ||
| 53 | LPWSTR pwzSourcePath; | ||
| 54 | LPWSTR pwzToken; | ||
| 55 | }; | ||
| 56 | |||
| 57 | |||
| 58 | struct CABC_FILE | ||
| 59 | { | ||
| 60 | DWORD dwCabFileIndex; | ||
| 61 | LPWSTR pwzSourcePath; | ||
| 62 | LPWSTR pwzToken; | ||
| 63 | PMSIFILEHASHINFO pmfHash; | ||
| 64 | LONGLONG llFileSize; | ||
| 65 | BOOL fHasDuplicates; | ||
| 66 | }; | ||
| 67 | |||
| 68 | |||
| 69 | struct CABC_DATA | ||
| 70 | { | ||
| 71 | LONGLONG llBytesSinceLastFlush; | ||
| 72 | LONGLONG llFlushThreshhold; | ||
| 73 | |||
| 74 | STRINGDICT_HANDLE shDictHandle; | ||
| 75 | |||
| 76 | WCHAR wzCabinetPath[MAX_PATH]; | ||
| 77 | WCHAR wzEmptyFile[MAX_PATH]; | ||
| 78 | HANDLE hEmptyFile; | ||
| 79 | DWORD dwLastFileIndex; | ||
| 80 | |||
| 81 | DWORD cFilePaths; | ||
| 82 | DWORD cMaxFilePaths; | ||
| 83 | CABC_FILE *prgFiles; | ||
| 84 | |||
| 85 | DWORD cDuplicates; | ||
| 86 | DWORD cMaxDuplicates; | ||
| 87 | CABC_DUPLICATEFILE *prgDuplicates; | ||
| 88 | |||
| 89 | HRESULT hrLastError; | ||
| 90 | BOOL fGoodCab; | ||
| 91 | |||
| 92 | HFCI hfci; | ||
| 93 | ERF erf; | ||
| 94 | CCAB ccab; | ||
| 95 | TCOMP tc; | ||
| 96 | |||
| 97 | // Below Field are used for Cabinet Splitting | ||
| 98 | BOOL fCabinetSplittingEnabled; | ||
| 99 | FileSplitCabNamesCallback fileSplitCabNamesCallback; | ||
| 100 | WCHAR wzFirstCabinetName[MAX_PATH]; // Stores Name of First Cabinet excluding ".cab" extention to help generate other names by Splitting | ||
| 101 | }; | ||
| 102 | |||
| 103 | const int CABC_HANDLE_BYTES = sizeof(CABC_DATA); | ||
| 104 | |||
| 105 | // | ||
| 106 | // prototypes | ||
| 107 | // | ||
| 108 | static void FreeCabCData( | ||
| 109 | __in CABC_DATA* pcd | ||
| 110 | ); | ||
| 111 | static HRESULT CheckForDuplicateFile( | ||
| 112 | __in CABC_DATA *pcd, | ||
| 113 | __out CABC_FILE **ppcf, | ||
| 114 | __in LPCWSTR wzFileName, | ||
| 115 | __in PMSIFILEHASHINFO *ppmfHash, | ||
| 116 | __in LONGLONG llFileSize | ||
| 117 | ); | ||
| 118 | static HRESULT AddDuplicateFile( | ||
| 119 | __in CABC_DATA *pcd, | ||
| 120 | __in DWORD dwFileArrayIndex, | ||
| 121 | __in_z LPCWSTR wzSourcePath, | ||
| 122 | __in_opt LPCWSTR wzToken, | ||
| 123 | __in DWORD dwDuplicateCabFileIndex | ||
| 124 | ); | ||
| 125 | static HRESULT AddNonDuplicateFile( | ||
| 126 | __in CABC_DATA *pcd, | ||
| 127 | __in LPCWSTR wzFile, | ||
| 128 | __in_opt LPCWSTR wzToken, | ||
| 129 | __in_opt const MSIFILEHASHINFO* pmfHash, | ||
| 130 | __in LONGLONG llFileSize, | ||
| 131 | __in DWORD dwCabFileIndex | ||
| 132 | ); | ||
| 133 | static HRESULT UpdateDuplicateFiles( | ||
| 134 | __in const CABC_DATA *pcd | ||
| 135 | ); | ||
| 136 | static HRESULT DuplicateFile( | ||
| 137 | __in MS_CABINET_HEADER *pHeader, | ||
| 138 | __in const CABC_DATA *pcd, | ||
| 139 | __in const CABC_DUPLICATEFILE *pDuplicate | ||
| 140 | ); | ||
| 141 | static HRESULT UtcFileTimeToLocalDosDateTime( | ||
| 142 | __in const FILETIME* pFileTime, | ||
| 143 | __out USHORT* pDate, | ||
| 144 | __out USHORT* pTime | ||
| 145 | ); | ||
| 146 | |||
| 147 | static __callback int DIAMONDAPI CabCFilePlaced(__in PCCAB pccab, __in_z PSTR szFile, __in long cbFile, __in BOOL fContinuation, __out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 148 | static __callback void * DIAMONDAPI CabCAlloc(__in ULONG cb); | ||
| 149 | static __callback void DIAMONDAPI CabCFree(__out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 150 | static __callback INT_PTR DIAMONDAPI CabCOpen(__in_z PSTR pszFile, __in int oflag, __in int pmode, __out int *err, __out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 151 | static __callback UINT FAR DIAMONDAPI CabCRead(__in INT_PTR hf, __out_bcount(cb) void FAR *memory, __in UINT cb, __out int *err, __out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 152 | static __callback UINT FAR DIAMONDAPI CabCWrite(__in INT_PTR hf, __in_bcount(cb) void FAR *memory, __in UINT cb, __out int *err, __out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 153 | static __callback long FAR DIAMONDAPI CabCSeek(__in INT_PTR hf, __in long dist, __in int seektype, __out int *err, __out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 154 | static __callback int FAR DIAMONDAPI CabCClose(__in INT_PTR hf, __out int *err, __out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 155 | static __callback int DIAMONDAPI CabCDelete(__in_z PSTR szFile, __out int *err, __out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 156 | __success(return != FALSE) static __callback BOOL DIAMONDAPI CabCGetTempFile(__out_bcount_z(cbFile) char *szFile, __in int cbFile, __out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 157 | __success(return != FALSE) static __callback BOOL DIAMONDAPI CabCGetNextCabinet(__in PCCAB pccab, __in ULONG ul, __out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 158 | static __callback INT_PTR DIAMONDAPI CabCGetOpenInfo(__in_z PSTR pszName, __out USHORT *pdate, __out USHORT *ptime, __out USHORT *pattribs, __out int *err, __out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 159 | static __callback long DIAMONDAPI CabCStatus(__in UINT uiTypeStatus, __in ULONG cb1, __in ULONG cb2, __out_bcount(CABC_HANDLE_BYTES) void *pv); | ||
| 160 | |||
| 161 | |||
| 162 | /******************************************************************** | ||
| 163 | CabcBegin - begins creating a cabinet | ||
| 164 | |||
| 165 | NOTE: phContext must be the same handle used in AddFile and Finish. | ||
| 166 | wzCabDir can be L"", but not NULL. | ||
| 167 | dwMaxSize and dwMaxThresh can be 0. A large default value will be used in that case. | ||
| 168 | |||
| 169 | ********************************************************************/ | ||
| 170 | extern "C" HRESULT DAPI CabCBegin( | ||
| 171 | __in_z LPCWSTR wzCab, | ||
| 172 | __in_z LPCWSTR wzCabDir, | ||
| 173 | __in DWORD dwMaxFiles, | ||
| 174 | __in DWORD dwMaxSize, | ||
| 175 | __in DWORD dwMaxThresh, | ||
| 176 | __in COMPRESSION_TYPE ct, | ||
| 177 | __out HANDLE *phContext | ||
| 178 | ) | ||
| 179 | { | ||
| 180 | Assert(wzCab && *wzCab && phContext); | ||
| 181 | |||
| 182 | HRESULT hr = S_OK; | ||
| 183 | CABC_DATA *pcd = NULL; | ||
| 184 | WCHAR wzTempPath[MAX_PATH] = { }; | ||
| 185 | |||
| 186 | C_ASSERT(sizeof(MSIFILEHASHINFO) == 20); | ||
| 187 | |||
| 188 | WCHAR wzPathBuffer [MAX_PATH] = L""; | ||
| 189 | size_t cchPathBuffer; | ||
| 190 | if (wzCabDir) | ||
| 191 | { | ||
| 192 | hr = ::StringCchLengthW(wzCabDir, MAX_PATH, &cchPathBuffer); | ||
| 193 | ExitOnFailure(hr, "Failed to get length of cab directory"); | ||
| 194 | |||
| 195 | // Need room to terminate with L'\\' and L'\0' | ||
| 196 | if((MAX_PATH - 1) <= cchPathBuffer || 0 == cchPathBuffer) | ||
| 197 | { | ||
| 198 | hr = E_INVALIDARG; | ||
| 199 | ExitOnFailure(hr, "Cab directory had invalid length: %u", cchPathBuffer); | ||
| 200 | } | ||
| 201 | |||
| 202 | hr = ::StringCchCopyW(wzPathBuffer, countof(wzPathBuffer), wzCabDir); | ||
| 203 | ExitOnFailure(hr, "Failed to copy cab directory to buffer"); | ||
| 204 | |||
| 205 | if (L'\\' != wzPathBuffer[cchPathBuffer - 1]) | ||
| 206 | { | ||
| 207 | hr = ::StringCchCatW(wzPathBuffer, countof(wzPathBuffer), L"\\"); | ||
| 208 | ExitOnFailure(hr, "Failed to cat \\ to end of buffer"); | ||
| 209 | ++cchPathBuffer; | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | pcd = static_cast<CABC_DATA*>(MemAlloc(sizeof(CABC_DATA), TRUE)); | ||
| 214 | ExitOnNull(pcd, hr, E_OUTOFMEMORY, "failed to allocate cab creation data structure"); | ||
| 215 | |||
| 216 | pcd->hrLastError = S_OK; | ||
| 217 | pcd->fGoodCab = TRUE; | ||
| 218 | pcd->llFlushThreshhold = MINFLUSHTHRESHHOLD; | ||
| 219 | |||
| 220 | pcd->hEmptyFile = INVALID_HANDLE_VALUE; | ||
| 221 | |||
| 222 | pcd->fileSplitCabNamesCallback = NULL; | ||
| 223 | |||
| 224 | if (NULL == dwMaxSize) | ||
| 225 | { | ||
| 226 | pcd->ccab.cb = CAB_MAX_SIZE; | ||
| 227 | pcd->fCabinetSplittingEnabled = FALSE; // If no max cab size is supplied, cabinet splitting is not desired | ||
| 228 | } | ||
| 229 | else | ||
| 230 | { | ||
| 231 | pcd->ccab.cb = dwMaxSize * 1024 * 1024; | ||
| 232 | pcd->fCabinetSplittingEnabled = TRUE; | ||
| 233 | } | ||
| 234 | |||
| 235 | if (0 == dwMaxThresh) | ||
| 236 | { | ||
| 237 | // Subtract 16 to magically make cabbing of uncompressed data larger than 2GB work. | ||
| 238 | pcd->ccab.cbFolderThresh = CAB_MAX_SIZE - 16; | ||
| 239 | } | ||
| 240 | else | ||
| 241 | { | ||
| 242 | pcd->ccab.cbFolderThresh = dwMaxThresh; | ||
| 243 | } | ||
| 244 | |||
| 245 | // Translate the compression type | ||
| 246 | if (COMPRESSION_TYPE_NONE == ct) | ||
| 247 | { | ||
| 248 | pcd->tc = tcompTYPE_NONE; | ||
| 249 | } | ||
| 250 | else if (COMPRESSION_TYPE_LOW == ct) | ||
| 251 | { | ||
| 252 | pcd->tc = tcompTYPE_LZX | tcompLZX_WINDOW_LO; | ||
| 253 | } | ||
| 254 | else if (COMPRESSION_TYPE_MEDIUM == ct) | ||
| 255 | { | ||
| 256 | pcd->tc = TCOMPfromLZXWindow(18); | ||
| 257 | } | ||
| 258 | else if (COMPRESSION_TYPE_HIGH == ct) | ||
| 259 | { | ||
| 260 | pcd->tc = tcompTYPE_LZX | tcompLZX_WINDOW_HI; | ||
| 261 | } | ||
| 262 | else if (COMPRESSION_TYPE_MSZIP == ct) | ||
| 263 | { | ||
| 264 | pcd->tc = tcompTYPE_MSZIP; | ||
| 265 | } | ||
| 266 | else | ||
| 267 | { | ||
| 268 | hr = E_INVALIDARG; | ||
| 269 | ExitOnFailure(hr, "Invalid compression type specified."); | ||
| 270 | } | ||
| 271 | |||
| 272 | if (0 == ::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, wzCab, -1, pcd->ccab.szCab, sizeof(pcd->ccab.szCab), NULL, NULL)) | ||
| 273 | { | ||
| 274 | ExitWithLastError(hr, "failed to convert cab name to multi-byte"); | ||
| 275 | } | ||
| 276 | |||
| 277 | if (0 == ::WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, wzPathBuffer, -1, pcd->ccab.szCabPath, sizeof(pcd->ccab.szCab), NULL, NULL)) | ||
| 278 | { | ||
| 279 | ExitWithLastError(hr, "failed to convert cab dir to multi-byte"); | ||
| 280 | } | ||
| 281 | |||
| 282 | // Remember the path to the cabinet. | ||
| 283 | hr= ::StringCchCopyW(pcd->wzCabinetPath, countof(pcd->wzCabinetPath), wzPathBuffer); | ||
| 284 | ExitOnFailure(hr, "Failed to copy cabinet path from path: %ls", wzPathBuffer); | ||
| 285 | |||
| 286 | hr = ::StringCchCatW(pcd->wzCabinetPath, countof(pcd->wzCabinetPath), wzCab); | ||
| 287 | ExitOnFailure(hr, "Failed to concat to cabinet path cabinet name: %ls", wzCab); | ||
| 288 | |||
| 289 | // Get the empty file to use as the blank marker for duplicates. | ||
| 290 | if (!::GetTempPathW(countof(wzTempPath), wzTempPath)) | ||
| 291 | { | ||
| 292 | ExitWithLastError(hr, "Failed to get temp path."); | ||
| 293 | } | ||
| 294 | |||
| 295 | if (!::GetTempFileNameW(wzTempPath, L"WSC", 0, pcd->wzEmptyFile)) | ||
| 296 | { | ||
| 297 | ExitWithLastError(hr, "Failed to create a temp file name."); | ||
| 298 | } | ||
| 299 | |||
| 300 | // Try to open the newly created empty file (remember, GetTempFileName() is kind enough to create a file for us) | ||
| 301 | // with a handle to automatically delete the file on close. Ignore any failure that might happen, since the worst | ||
| 302 | // case is we'll leave a zero byte file behind in the temp folder. | ||
| 303 | pcd->hEmptyFile = ::CreateFileW(pcd->wzEmptyFile, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); | ||
| 304 | |||
| 305 | hr = DictCreateWithEmbeddedKey(&pcd->shDictHandle, dwMaxFiles, reinterpret_cast<void **>(&pcd->prgFiles), offsetof(CABC_FILE, pwzSourcePath), DICT_FLAG_NONE); | ||
| 306 | ExitOnFailure(hr, "Failed to create dictionary to keep track of duplicate files"); | ||
| 307 | |||
| 308 | // Make sure to allocate at least some space, or we won't be able to realloc later if they "lied" about having zero files | ||
| 309 | if (1 > dwMaxFiles) | ||
| 310 | { | ||
| 311 | dwMaxFiles = 1; | ||
| 312 | } | ||
| 313 | |||
| 314 | pcd->cMaxFilePaths = dwMaxFiles; | ||
| 315 | size_t cbFileAllocSize = 0; | ||
| 316 | |||
| 317 | hr = ::SizeTMult(pcd->cMaxFilePaths, sizeof(CABC_FILE), &(cbFileAllocSize)); | ||
| 318 | ExitOnFailure(hr, "Maximum allocation exceeded on initialization."); | ||
| 319 | |||
| 320 | pcd->prgFiles = static_cast<CABC_FILE*>(MemAlloc(cbFileAllocSize, TRUE)); | ||
| 321 | ExitOnNull(pcd->prgFiles, hr, E_OUTOFMEMORY, "Failed to allocate memory for files."); | ||
| 322 | |||
| 323 | // Tell cabinet API about our configuration. | ||
| 324 | pcd->hfci = ::FCICreate(&(pcd->erf), CabCFilePlaced, CabCAlloc, CabCFree, CabCOpen, CabCRead, CabCWrite, CabCClose, CabCSeek, CabCDelete, CabCGetTempFile, &(pcd->ccab), pcd); | ||
| 325 | if (NULL == pcd->hfci || pcd->erf.fError) | ||
| 326 | { | ||
| 327 | // Prefer our recorded last error, then ::GetLastError(), finally fallback to the useless "E_FAIL" error | ||
| 328 | if (FAILED(pcd->hrLastError)) | ||
| 329 | { | ||
| 330 | hr = pcd->hrLastError; | ||
| 331 | } | ||
| 332 | else | ||
| 333 | { | ||
| 334 | ExitWithLastError(hr, "failed to create FCI object Oper: 0x%x Type: 0x%x", pcd->erf.erfOper, pcd->erf.erfType); | ||
| 335 | } | ||
| 336 | |||
| 337 | pcd->fGoodCab = FALSE; | ||
| 338 | |||
| 339 | ExitOnFailure(hr, "failed to create FCI object Oper: 0x%x Type: 0x%x", pcd->erf.erfOper, pcd->erf.erfType); // TODO: can these be converted to HRESULTS? | ||
| 340 | } | ||
| 341 | |||
| 342 | *phContext = pcd; | ||
| 343 | |||
| 344 | LExit: | ||
| 345 | if (FAILED(hr) && pcd && pcd->hfci) | ||
| 346 | { | ||
| 347 | ::FCIDestroy(pcd->hfci); | ||
| 348 | } | ||
| 349 | |||
| 350 | return hr; | ||
| 351 | } | ||
| 352 | |||
| 353 | |||
| 354 | /******************************************************************** | ||
| 355 | CabCNextCab - This will be useful when creating multiple cabs. | ||
| 356 | Haven't needed it yet. | ||
| 357 | ********************************************************************/ | ||
| 358 | extern "C" HRESULT DAPI CabCNextCab( | ||
| 359 | __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext | ||
| 360 | ) | ||
| 361 | { | ||
| 362 | UNREFERENCED_PARAMETER(hContext); | ||
| 363 | // TODO: Make the appropriate FCIFlushCabinet and FCIFlushFolder calls | ||
| 364 | return E_NOTIMPL; | ||
| 365 | } | ||
| 366 | |||
| 367 | |||
| 368 | /******************************************************************** | ||
| 369 | CabcAddFile - adds a file to a cabinet | ||
| 370 | |||
| 371 | NOTE: hContext must be the same used in Begin and Finish | ||
| 372 | if wzToken is null, the file's original name is used within the cab | ||
| 373 | ********************************************************************/ | ||
| 374 | extern "C" HRESULT DAPI CabCAddFile( | ||
| 375 | __in_z LPCWSTR wzFile, | ||
| 376 | __in_z_opt LPCWSTR wzToken, | ||
| 377 | __in_opt PMSIFILEHASHINFO pmfHash, | ||
| 378 | __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext | ||
| 379 | ) | ||
| 380 | { | ||
| 381 | Assert(wzFile && *wzFile && hContext); | ||
| 382 | |||
| 383 | HRESULT hr = S_OK; | ||
| 384 | CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(hContext); | ||
| 385 | CABC_FILE *pcfDuplicate = NULL; | ||
| 386 | LPWSTR sczUpperCaseFile = NULL; | ||
| 387 | LONGLONG llFileSize = 0; | ||
| 388 | PMSIFILEHASHINFO pmfLocalHash = pmfHash; | ||
| 389 | |||
| 390 | hr = StrAllocString(&sczUpperCaseFile, wzFile, 0); | ||
| 391 | ExitOnFailure(hr, "Failed to allocate new string for file %ls", wzFile); | ||
| 392 | |||
| 393 | // Modifies the string in-place | ||
| 394 | StrStringToUpper(sczUpperCaseFile); | ||
| 395 | |||
| 396 | // Use Smart Cabbing if there are duplicates and if Cabinet Splitting is not desired | ||
| 397 | // For Cabinet Spliting avoid hashing as Smart Cabbing is disabled | ||
| 398 | if(!pcd->fCabinetSplittingEnabled) | ||
| 399 | { | ||
| 400 | // Store file size, primarily used to determine which files to hash for duplicates | ||
| 401 | hr = FileSize(wzFile, &llFileSize); | ||
| 402 | ExitOnFailure(hr, "Failed to check size of file %ls", wzFile); | ||
| 403 | |||
| 404 | hr = CheckForDuplicateFile(pcd, &pcfDuplicate, sczUpperCaseFile, &pmfLocalHash, llFileSize); | ||
| 405 | ExitOnFailure(hr, "Failed while checking for duplicate of file: %ls", wzFile); | ||
| 406 | } | ||
| 407 | |||
| 408 | if (pcfDuplicate) // This will be null for smart cabbing case | ||
| 409 | { | ||
| 410 | DWORD index; | ||
| 411 | hr = ::PtrdiffTToDWord(pcfDuplicate - pcd->prgFiles, &index); | ||
| 412 | ExitOnFailure(hr, "Failed to calculate index of file name: %ls", pcfDuplicate->pwzSourcePath); | ||
| 413 | |||
| 414 | hr = AddDuplicateFile(pcd, index, sczUpperCaseFile, wzToken, pcd->dwLastFileIndex); | ||
| 415 | ExitOnFailure(hr, "Failed to add duplicate of file name: %ls", pcfDuplicate->pwzSourcePath); | ||
| 416 | } | ||
| 417 | else | ||
| 418 | { | ||
| 419 | hr = AddNonDuplicateFile(pcd, sczUpperCaseFile, wzToken, pmfLocalHash, llFileSize, pcd->dwLastFileIndex); | ||
| 420 | ExitOnFailure(hr, "Failed to add non-duplicated file: %ls", wzFile); | ||
| 421 | } | ||
| 422 | |||
| 423 | ++pcd->dwLastFileIndex; | ||
| 424 | |||
| 425 | LExit: | ||
| 426 | ReleaseStr(sczUpperCaseFile); | ||
| 427 | |||
| 428 | // If we allocated a hash struct ourselves, free it | ||
| 429 | if (pmfHash != pmfLocalHash) | ||
| 430 | { | ||
| 431 | ReleaseMem(pmfLocalHash); | ||
| 432 | } | ||
| 433 | |||
| 434 | return hr; | ||
| 435 | } | ||
| 436 | |||
| 437 | |||
| 438 | /******************************************************************** | ||
| 439 | CabcFinish - finishes making a cabinet | ||
| 440 | |||
| 441 | NOTE: hContext must be the same used in Begin and AddFile | ||
| 442 | *********************************************************************/ | ||
| 443 | extern "C" HRESULT DAPI CabCFinish( | ||
| 444 | __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext, | ||
| 445 | __in_opt FileSplitCabNamesCallback fileSplitCabNamesCallback | ||
| 446 | ) | ||
| 447 | { | ||
| 448 | Assert(hContext); | ||
| 449 | |||
| 450 | HRESULT hr = S_OK; | ||
| 451 | CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(hContext); | ||
| 452 | CABC_INTERNAL_ADDFILEINFO fileInfo = { }; | ||
| 453 | DWORD dwCabFileIndex; // Total file index, counts up to pcd->dwLastFileIndex | ||
| 454 | DWORD dwArrayFileIndex = 0; // Index into pcd->prgFiles[] array | ||
| 455 | DWORD dwDupeArrayFileIndex = 0; // Index into pcd->prgDuplicates[] array | ||
| 456 | LPSTR pszFileToken = NULL; | ||
| 457 | LONGLONG llFileSize = 0; | ||
| 458 | |||
| 459 | pcd->fileSplitCabNamesCallback = fileSplitCabNamesCallback; | ||
| 460 | |||
| 461 | // These are used to determine whether to call FciFlushFolder() before or after the next call to FciAddFile() | ||
| 462 | // doing so at appropriate times results in install-time performance benefits in the case of duplicate files. | ||
| 463 | // Basically, when MSI has to extract files out of order (as it does due to our smart cabbing), it can't just jump | ||
| 464 | // exactly to the out of order file, it must begin extracting all over again, starting from that file's CAB folder | ||
| 465 | // (this is not the same as a regular folder, and is a concept unique to CABs). | ||
| 466 | |||
| 467 | // This means MSI spends a lot of time extracting the same files twice, especially if the duplicate file has many files | ||
| 468 | // before it in the CAB folder. To avoid this, we want to make sure whenever MSI jumps to another file in the CAB, that | ||
| 469 | // file is at the beginning of its own folder, so no extra files need to be extracted. FciFlushFolder() causes the CAB | ||
| 470 | // to close the current folder, and create a new folder for the next file to be added. | ||
| 471 | |||
| 472 | // So to maximize our performance benefit, we must call FciFlushFolder() at every place MSI will jump "to" in the CAB sequence. | ||
| 473 | // So, we call FciFlushFolder() before adding the original version of a duplicated file (as this will be jumped "to") | ||
| 474 | // And we call FciFlushFolder() after adding the duplicate versions of files (as this will be jumped back "to" to get back in the regular sequence) | ||
| 475 | BOOL fFlushBefore = FALSE; | ||
| 476 | BOOL fFlushAfter = FALSE; | ||
| 477 | |||
| 478 | ReleaseDict(pcd->shDictHandle); | ||
| 479 | |||
| 480 | // We need to go through all the files, duplicates and non-duplicates, sequentially in the order they were added | ||
| 481 | for (dwCabFileIndex = 0; dwCabFileIndex < pcd->dwLastFileIndex; ++dwCabFileIndex) | ||
| 482 | { | ||
| 483 | if (dwArrayFileIndex < pcd->cMaxFilePaths && pcd->prgFiles[dwArrayFileIndex].dwCabFileIndex == dwCabFileIndex) // If it's a non-duplicate file | ||
| 484 | { | ||
| 485 | // Just a normal, non-duplicated file. We'll add it to the list for later checking of | ||
| 486 | // duplicates. | ||
| 487 | fileInfo.wzSourcePath = pcd->prgFiles[dwArrayFileIndex].pwzSourcePath; | ||
| 488 | fileInfo.wzEmptyPath = NULL; | ||
| 489 | |||
| 490 | // Use the provided token, otherwise default to the source file name. | ||
| 491 | if (pcd->prgFiles[dwArrayFileIndex].pwzToken) | ||
| 492 | { | ||
| 493 | LPCWSTR pwzTemp = pcd->prgFiles[dwArrayFileIndex].pwzToken; | ||
| 494 | hr = StrAnsiAllocString(&pszFileToken, pwzTemp, 0, CP_ACP); | ||
| 495 | ExitOnFailure(hr, "failed to convert file token to ANSI: %ls", pwzTemp); | ||
| 496 | } | ||
| 497 | else | ||
| 498 | { | ||
| 499 | LPCWSTR pwzTemp = FileFromPath(fileInfo.wzSourcePath); | ||
| 500 | hr = StrAnsiAllocString(&pszFileToken, pwzTemp, 0, CP_ACP); | ||
| 501 | ExitOnFailure(hr, "failed to convert file name to ANSI: %ls", pwzTemp); | ||
| 502 | } | ||
| 503 | |||
| 504 | if (pcd->prgFiles[dwArrayFileIndex].fHasDuplicates) | ||
| 505 | { | ||
| 506 | fFlushBefore = TRUE; | ||
| 507 | } | ||
| 508 | |||
| 509 | llFileSize = pcd->prgFiles[dwArrayFileIndex].llFileSize; | ||
| 510 | |||
| 511 | ++dwArrayFileIndex; // Increment into the non-duplicate array | ||
| 512 | } | ||
| 513 | else if (dwDupeArrayFileIndex < pcd->cMaxDuplicates && pcd->prgDuplicates[dwDupeArrayFileIndex].dwDuplicateCabFileIndex == dwCabFileIndex) // If it's a duplicate file | ||
| 514 | { | ||
| 515 | // For duplicate files, we point them at our empty (zero-byte) file so it takes up no space | ||
| 516 | // in the resultant cabinet. Later on (CabCFinish) we'll go through and change all the zero | ||
| 517 | // byte files to point at their duplicated file index. | ||
| 518 | // | ||
| 519 | // Notice that duplicate files are not added to the list of file paths because all duplicate | ||
| 520 | // files point at the same path (the empty file) so there is no point in tracking them with | ||
| 521 | // their path. | ||
| 522 | fileInfo.wzSourcePath = pcd->prgDuplicates[dwDupeArrayFileIndex].pwzSourcePath; | ||
| 523 | fileInfo.wzEmptyPath = pcd->wzEmptyFile; | ||
| 524 | |||
| 525 | // Use the provided token, otherwise default to the source file name. | ||
| 526 | if (pcd->prgDuplicates[dwDupeArrayFileIndex].pwzToken) | ||
| 527 | { | ||
| 528 | LPCWSTR pwzTemp = pcd->prgDuplicates[dwDupeArrayFileIndex].pwzToken; | ||
| 529 | hr = StrAnsiAllocString(&pszFileToken, pwzTemp, 0, CP_ACP); | ||
| 530 | ExitOnFailure(hr, "failed to convert duplicate file token to ANSI: %ls", pwzTemp); | ||
| 531 | } | ||
| 532 | else | ||
| 533 | { | ||
| 534 | LPCWSTR pwzTemp = FileFromPath(fileInfo.wzSourcePath); | ||
| 535 | hr = StrAnsiAllocString(&pszFileToken, pwzTemp, 0, CP_ACP); | ||
| 536 | ExitOnFailure(hr, "failed to convert duplicate file name to ANSI: %ls", pwzTemp); | ||
| 537 | } | ||
| 538 | |||
| 539 | // Flush afterward only if this isn't a duplicate of the previous file, and at least one non-duplicate file remains to be added to the cab | ||
| 540 | if (!(dwCabFileIndex - 1 == pcd->prgFiles[pcd->prgDuplicates[dwDupeArrayFileIndex].dwFileArrayIndex].dwCabFileIndex) && | ||
| 541 | !(dwDupeArrayFileIndex > 0 && dwCabFileIndex - 1 == pcd->prgDuplicates[dwDupeArrayFileIndex - 1].dwDuplicateCabFileIndex) && | ||
| 542 | dwArrayFileIndex < pcd->cFilePaths) | ||
| 543 | { | ||
| 544 | fFlushAfter = TRUE; | ||
| 545 | } | ||
| 546 | |||
| 547 | // We're just adding a 0-byte file, so set it appropriately | ||
| 548 | llFileSize = 0; | ||
| 549 | |||
| 550 | ++dwDupeArrayFileIndex; // Increment into the duplicate array | ||
| 551 | } | ||
| 552 | else // If it's neither duplicate nor non-duplicate, throw an error | ||
| 553 | { | ||
| 554 | hr = HRESULT_FROM_WIN32(ERROR_EA_LIST_INCONSISTENT); | ||
| 555 | ExitOnRootFailure(hr, "Internal inconsistency in data structures while creating CAB file - a non-standard, non-duplicate file was encountered"); | ||
| 556 | } | ||
| 557 | |||
| 558 | if (fFlushBefore && pcd->llBytesSinceLastFlush > pcd->llFlushThreshhold) | ||
| 559 | { | ||
| 560 | if (!::FCIFlushFolder(pcd->hfci, CabCGetNextCabinet, CabCStatus)) | ||
| 561 | { | ||
| 562 | ExitWithLastError(hr, "failed to flush FCI folder before adding file, Oper: 0x%x Type: 0x%x", pcd->erf.erfOper, pcd->erf.erfType); | ||
| 563 | } | ||
| 564 | pcd->llBytesSinceLastFlush = 0; | ||
| 565 | } | ||
| 566 | |||
| 567 | pcd->llBytesSinceLastFlush += llFileSize; | ||
| 568 | |||
| 569 | // Add the file to the cab. Notice that we are passing our CABC_INTERNAL_ADDFILEINFO struct | ||
| 570 | // through the pointer to an ANSI string. This is neccessary so we can smuggle through the | ||
| 571 | // path to the empty file (should this be a duplicate file). | ||
| 572 | #pragma prefast(push) | ||
| 573 | #pragma prefast(disable:6387) // OACR is silly, pszFileToken can't be false here | ||
| 574 | if (!::FCIAddFile(pcd->hfci, reinterpret_cast<LPSTR>(&fileInfo), pszFileToken, FALSE, CabCGetNextCabinet, CabCStatus, CabCGetOpenInfo, pcd->tc)) | ||
| 575 | #pragma prefast(pop) | ||
| 576 | { | ||
| 577 | pcd->fGoodCab = FALSE; | ||
| 578 | |||
| 579 | // Prefer our recorded last error, then ::GetLastError(), finally fallback to the useless "E_FAIL" error | ||
| 580 | if (FAILED(pcd->hrLastError)) | ||
| 581 | { | ||
| 582 | hr = pcd->hrLastError; | ||
| 583 | } | ||
| 584 | else | ||
| 585 | { | ||
| 586 | ExitWithLastError(hr, "failed to add file to FCI object Oper: 0x%x Type: 0x%x File: %ls", pcd->erf.erfOper, pcd->erf.erfType, fileInfo.wzSourcePath); | ||
| 587 | } | ||
| 588 | |||
| 589 | ExitOnFailure(hr, "failed to add file to FCI object Oper: 0x%x Type: 0x%x File: %ls", pcd->erf.erfOper, pcd->erf.erfType, fileInfo.wzSourcePath); // TODO: can these be converted to HRESULTS? | ||
| 590 | } | ||
| 591 | |||
| 592 | // For Cabinet Splitting case, check for pcd->hrLastError that may be set as result of Error in CabCGetNextCabinet | ||
| 593 | // This is required as returning False in CabCGetNextCabinet is not aborting cabinet creation and is reporting success instead | ||
| 594 | if (pcd->fCabinetSplittingEnabled && FAILED(pcd->hrLastError)) | ||
| 595 | { | ||
| 596 | hr = pcd->hrLastError; | ||
| 597 | ExitOnFailure(hr, "Failed to create next cabinet name while splitting cabinet."); | ||
| 598 | } | ||
| 599 | |||
| 600 | if (fFlushAfter && pcd->llBytesSinceLastFlush > pcd->llFlushThreshhold) | ||
| 601 | { | ||
| 602 | if (!::FCIFlushFolder(pcd->hfci, CabCGetNextCabinet, CabCStatus)) | ||
| 603 | { | ||
| 604 | ExitWithLastError(hr, "failed to flush FCI folder after adding file, Oper: 0x%x Type: 0x%x", pcd->erf.erfOper, pcd->erf.erfType); | ||
| 605 | } | ||
| 606 | pcd->llBytesSinceLastFlush = 0; | ||
| 607 | } | ||
| 608 | |||
| 609 | fFlushAfter = FALSE; | ||
| 610 | fFlushBefore = FALSE; | ||
| 611 | } | ||
| 612 | |||
| 613 | if (!pcd->fGoodCab) | ||
| 614 | { | ||
| 615 | // Prefer our recorded last error, then ::GetLastError(), finally fallback to the useless "E_FAIL" error | ||
| 616 | if (FAILED(pcd->hrLastError)) | ||
| 617 | { | ||
| 618 | hr = pcd->hrLastError; | ||
| 619 | } | ||
| 620 | else | ||
| 621 | { | ||
| 622 | ExitWithLastError(hr, "failed while creating CAB FCI object Oper: 0x%x Type: 0x%x File: %s", pcd->erf.erfOper, pcd->erf.erfType); | ||
| 623 | } | ||
| 624 | |||
| 625 | ExitOnFailure(hr, "failed while creating CAB FCI object Oper: 0x%x Type: 0x%x File: %s", pcd->erf.erfOper, pcd->erf.erfType); // TODO: can these be converted to HRESULTS? | ||
| 626 | } | ||
| 627 | |||
| 628 | // Only flush the cabinet if we actually succeeded in previous calls - otherwise we just waste time (a lot on big cabs) | ||
| 629 | if (!::FCIFlushCabinet(pcd->hfci, FALSE, CabCGetNextCabinet, CabCStatus)) | ||
| 630 | { | ||
| 631 | // If we have a last error, use that, otherwise return the useless error | ||
| 632 | hr = FAILED(pcd->hrLastError) ? pcd->hrLastError : E_FAIL; | ||
| 633 | ExitOnFailure(hr, "failed to flush FCI object Oper: 0x%x Type: 0x%x", pcd->erf.erfOper, pcd->erf.erfType); // TODO: can these be converted to HRESULTS? | ||
| 634 | } | ||
| 635 | |||
| 636 | if (pcd->fGoodCab && pcd->cDuplicates) | ||
| 637 | { | ||
| 638 | hr = UpdateDuplicateFiles(pcd); | ||
| 639 | ExitOnFailure(hr, "Failed to update duplicates in cabinet: %ls", pcd->wzCabinetPath); | ||
| 640 | } | ||
| 641 | |||
| 642 | LExit: | ||
| 643 | ::FCIDestroy(pcd->hfci); | ||
| 644 | FreeCabCData(pcd); | ||
| 645 | ReleaseNullStr(pszFileToken); | ||
| 646 | |||
| 647 | return hr; | ||
| 648 | } | ||
| 649 | |||
| 650 | |||
| 651 | /******************************************************************** | ||
| 652 | CabCCancel - cancels making a cabinet | ||
| 653 | |||
| 654 | NOTE: hContext must be the same used in Begin and AddFile | ||
| 655 | *********************************************************************/ | ||
| 656 | extern "C" void DAPI CabCCancel( | ||
| 657 | __in_bcount(CABC_HANDLE_BYTES) HANDLE hContext | ||
| 658 | ) | ||
| 659 | { | ||
| 660 | Assert(hContext); | ||
| 661 | |||
| 662 | CABC_DATA* pcd = reinterpret_cast<CABC_DATA*>(hContext); | ||
| 663 | ::FCIDestroy(pcd->hfci); | ||
| 664 | FreeCabCData(pcd); | ||
| 665 | } | ||
| 666 | |||
| 667 | |||
| 668 | // | ||
| 669 | // private | ||
| 670 | // | ||
| 671 | |||
| 672 | static void FreeCabCData( | ||
| 673 | __in CABC_DATA* pcd | ||
| 674 | ) | ||
| 675 | { | ||
| 676 | if (pcd) | ||
| 677 | { | ||
| 678 | ReleaseFileHandle(pcd->hEmptyFile); | ||
| 679 | |||
| 680 | for (DWORD i = 0; i < pcd->cFilePaths; ++i) | ||
| 681 | { | ||
| 682 | ReleaseStr(pcd->prgFiles[i].pwzSourcePath); | ||
| 683 | ReleaseMem(pcd->prgFiles[i].pmfHash); | ||
| 684 | } | ||
| 685 | ReleaseMem(pcd->prgFiles); | ||
| 686 | ReleaseMem(pcd->prgDuplicates); | ||
| 687 | |||
| 688 | ReleaseMem(pcd); | ||
| 689 | } | ||
| 690 | } | ||
| 691 | |||
| 692 | /******************************************************************** | ||
| 693 | SmartCab functions | ||
| 694 | |||
| 695 | ********************************************************************/ | ||
| 696 | |||
| 697 | static HRESULT CheckForDuplicateFile( | ||
| 698 | __in CABC_DATA *pcd, | ||
| 699 | __out CABC_FILE **ppcf, | ||
| 700 | __in LPCWSTR wzFileName, | ||
| 701 | __in PMSIFILEHASHINFO *ppmfHash, | ||
| 702 | __in LONGLONG llFileSize | ||
| 703 | ) | ||
| 704 | { | ||
| 705 | DWORD i; | ||
| 706 | HRESULT hr = S_OK; | ||
| 707 | UINT er = ERROR_SUCCESS; | ||
| 708 | |||
| 709 | ExitOnNull(ppcf, hr, E_INVALIDARG, "No file structure sent while checking for duplicate file"); | ||
| 710 | ExitOnNull(ppmfHash, hr, E_INVALIDARG, "No file hash structure pointer sent while checking for duplicate file"); | ||
| 711 | |||
| 712 | *ppcf = NULL; // By default, we'll set our output to NULL | ||
| 713 | |||
| 714 | hr = DictGetValue(pcd->shDictHandle, wzFileName, reinterpret_cast<void **>(ppcf)); | ||
| 715 | // If we found it in the hash of previously added source paths, return our match immediately | ||
| 716 | if (SUCCEEDED(hr)) | ||
| 717 | { | ||
| 718 | ExitFunction1(hr = S_OK); | ||
| 719 | } | ||
| 720 | else if (E_NOTFOUND == hr) | ||
| 721 | { | ||
| 722 | hr = S_OK; | ||
| 723 | } | ||
| 724 | ExitOnFailure(hr, "Failed while searching for file in dictionary of previously added files"); | ||
| 725 | |||
| 726 | for (i = 0; i < pcd->cFilePaths; ++i) | ||
| 727 | { | ||
| 728 | // If two files have the same size, use hashing to check if they're a match | ||
| 729 | if (llFileSize == pcd->prgFiles[i].llFileSize) | ||
| 730 | { | ||
| 731 | // If pcd->prgFiles[i], our potential match, hasn't been hashed yet, hash it | ||
| 732 | if (pcd->prgFiles[i].pmfHash == NULL) | ||
| 733 | { | ||
| 734 | pcd->prgFiles[i].pmfHash = (PMSIFILEHASHINFO)MemAlloc(sizeof(MSIFILEHASHINFO), FALSE); | ||
| 735 | ExitOnNull(pcd->prgFiles[i].pmfHash, hr, E_OUTOFMEMORY, "Failed to allocate memory for candidate duplicate file's MSI file hash"); | ||
| 736 | |||
| 737 | pcd->prgFiles[i].pmfHash->dwFileHashInfoSize = sizeof(MSIFILEHASHINFO); | ||
| 738 | er = ::MsiGetFileHashW(pcd->prgFiles[i].pwzSourcePath, 0, pcd->prgFiles[i].pmfHash); | ||
| 739 | ExitOnWin32Error(er, hr, "Failed while getting MSI file hash of candidate duplicate file: %ls", pcd->prgFiles[i].pwzSourcePath); | ||
| 740 | } | ||
| 741 | |||
| 742 | // If our own file hasn't yet been hashed, hash it | ||
| 743 | if (NULL == *ppmfHash) | ||
| 744 | { | ||
| 745 | *ppmfHash = (PMSIFILEHASHINFO)MemAlloc(sizeof(MSIFILEHASHINFO), FALSE); | ||
| 746 | ExitOnNull(*ppmfHash, hr, E_OUTOFMEMORY, "Failed to allocate memory for file's MSI file hash"); | ||
| 747 | |||
| 748 | (*ppmfHash)->dwFileHashInfoSize = sizeof(MSIFILEHASHINFO); | ||
| 749 | er = ::MsiGetFileHashW(wzFileName, 0, *ppmfHash); | ||
| 750 | ExitOnWin32Error(er, hr, "Failed while getting MSI file hash of file: %ls", pcd->prgFiles[i].pwzSourcePath); | ||
| 751 | } | ||
| 752 | |||
| 753 | // If the two file hashes are both of the expected size, and they match, we've got a match, so return it! | ||
| 754 | if (pcd->prgFiles[i].pmfHash->dwFileHashInfoSize == (*ppmfHash)->dwFileHashInfoSize && | ||
| 755 | sizeof(MSIFILEHASHINFO) == (*ppmfHash)->dwFileHashInfoSize && | ||
| 756 | pcd->prgFiles[i].pmfHash->dwData[0] == (*ppmfHash)->dwData[0] && | ||
| 757 | pcd->prgFiles[i].pmfHash->dwData[1] == (*ppmfHash)->dwData[1] && | ||
| 758 | pcd->prgFiles[i].pmfHash->dwData[2] == (*ppmfHash)->dwData[2] && | ||
| 759 | pcd->prgFiles[i].pmfHash->dwData[3] == (*ppmfHash)->dwData[3]) | ||
| 760 | { | ||
| 761 | *ppcf = pcd->prgFiles + i; | ||
| 762 | ExitFunction1(hr = S_OK); | ||
| 763 | } | ||
| 764 | } | ||
| 765 | } | ||
| 766 | |||
| 767 | LExit: | ||
| 768 | |||
| 769 | return hr; | ||
| 770 | } | ||
| 771 | |||
| 772 | |||
| 773 | static HRESULT AddDuplicateFile( | ||
| 774 | __in CABC_DATA *pcd, | ||
| 775 | __in DWORD dwFileArrayIndex, | ||
| 776 | __in_z LPCWSTR wzSourcePath, | ||
| 777 | __in_opt LPCWSTR wzToken, | ||
| 778 | __in DWORD dwDuplicateCabFileIndex | ||
| 779 | ) | ||
| 780 | { | ||
| 781 | HRESULT hr = S_OK; | ||
| 782 | LPVOID pv = NULL; | ||
| 783 | |||
| 784 | // Ensure there is enough memory to store this duplicate file index. | ||
| 785 | if (pcd->cDuplicates == pcd->cMaxDuplicates) | ||
| 786 | { | ||
| 787 | pcd->cMaxDuplicates += 20; // grow by a reasonable number (20 is reasonable, right?) | ||
| 788 | size_t cbDuplicates = 0; | ||
| 789 | |||
| 790 | hr = ::SizeTMult(pcd->cMaxDuplicates, sizeof(CABC_DUPLICATEFILE), &cbDuplicates); | ||
| 791 | ExitOnFailure(hr, "Maximum allocation exceeded."); | ||
| 792 | |||
| 793 | if (pcd->cDuplicates) | ||
| 794 | { | ||
| 795 | pv = MemReAlloc(pcd->prgDuplicates, cbDuplicates, FALSE); | ||
| 796 | ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to reallocate memory for duplicate file."); | ||
| 797 | } | ||
| 798 | else | ||
| 799 | { | ||
| 800 | pv = MemAlloc(cbDuplicates, FALSE); | ||
| 801 | ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to allocate memory for duplicate file."); | ||
| 802 | } | ||
| 803 | |||
| 804 | ZeroMemory(reinterpret_cast<BYTE*>(pv) + (pcd->cDuplicates * sizeof(CABC_DUPLICATEFILE)), (pcd->cMaxDuplicates - pcd->cDuplicates) * sizeof(CABC_DUPLICATEFILE)); | ||
| 805 | |||
| 806 | pcd->prgDuplicates = static_cast<CABC_DUPLICATEFILE*>(pv); | ||
| 807 | pv = NULL; | ||
| 808 | } | ||
| 809 | |||
| 810 | // Store the duplicate file index. | ||
| 811 | pcd->prgDuplicates[pcd->cDuplicates].dwFileArrayIndex = dwFileArrayIndex; | ||
| 812 | pcd->prgDuplicates[pcd->cDuplicates].dwDuplicateCabFileIndex = dwDuplicateCabFileIndex; | ||
| 813 | pcd->prgFiles[dwFileArrayIndex].fHasDuplicates = TRUE; // Mark original file as having duplicates | ||
| 814 | |||
| 815 | hr = StrAllocString(&pcd->prgDuplicates[pcd->cDuplicates].pwzSourcePath, wzSourcePath, 0); | ||
| 816 | ExitOnFailure(hr, "Failed to copy duplicate file path: %ls", wzSourcePath); | ||
| 817 | |||
| 818 | if (wzToken && *wzToken) | ||
| 819 | { | ||
| 820 | hr = StrAllocString(&pcd->prgDuplicates[pcd->cDuplicates].pwzToken, wzToken, 0); | ||
| 821 | ExitOnFailure(hr, "Failed to copy duplicate file token: %ls", wzToken); | ||
| 822 | } | ||
| 823 | |||
| 824 | ++pcd->cDuplicates; | ||
| 825 | |||
| 826 | LExit: | ||
| 827 | ReleaseMem(pv); | ||
| 828 | return hr; | ||
| 829 | } | ||
| 830 | |||
| 831 | |||
| 832 | static HRESULT AddNonDuplicateFile( | ||
| 833 | __in CABC_DATA *pcd, | ||
| 834 | __in LPCWSTR wzFile, | ||
| 835 | __in_opt LPCWSTR wzToken, | ||
| 836 | __in_opt const MSIFILEHASHINFO* pmfHash, | ||
| 837 | __in LONGLONG llFileSize, | ||
| 838 | __in DWORD dwCabFileIndex | ||
| 839 | ) | ||
| 840 | { | ||
| 841 | HRESULT hr = S_OK; | ||
| 842 | LPVOID pv = NULL; | ||
| 843 | |||
| 844 | // Ensure there is enough memory to store this file index. | ||
| 845 | if (pcd->cFilePaths == pcd->cMaxFilePaths) | ||
| 846 | { | ||
| 847 | pcd->cMaxFilePaths += 100; // grow by a reasonable number (100 is reasonable, right?) | ||
| 848 | size_t cbFilePaths = 0; | ||
| 849 | |||
| 850 | hr = ::SizeTMult(pcd->cMaxFilePaths, sizeof(CABC_FILE), &cbFilePaths); | ||
| 851 | ExitOnFailure(hr, "Maximum allocation exceeded."); | ||
| 852 | |||
| 853 | pv = MemReAlloc(pcd->prgFiles, cbFilePaths, FALSE); | ||
| 854 | ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to reallocate memory for file."); | ||
| 855 | |||
| 856 | ZeroMemory(reinterpret_cast<BYTE*>(pv) + (pcd->cFilePaths * sizeof(CABC_FILE)), (pcd->cMaxFilePaths - pcd->cFilePaths) * sizeof(CABC_FILE)); | ||
| 857 | |||
| 858 | pcd->prgFiles = static_cast<CABC_FILE*>(pv); | ||
| 859 | pv = NULL; | ||
| 860 | } | ||
| 861 | |||
| 862 | // Store the file index information. | ||
| 863 | // TODO: add this to a sorted list so we can do a binary search later. | ||
| 864 | CABC_FILE *pcf = pcd->prgFiles + pcd->cFilePaths; | ||
| 865 | pcf->dwCabFileIndex = dwCabFileIndex; | ||
| 866 | pcf->llFileSize = llFileSize; | ||
| 867 | |||
| 868 | if (pmfHash && sizeof(MSIFILEHASHINFO) == pmfHash->dwFileHashInfoSize) | ||
| 869 | { | ||
| 870 | pcf->pmfHash = (PMSIFILEHASHINFO)MemAlloc(sizeof(MSIFILEHASHINFO), FALSE); | ||
| 871 | ExitOnNull(pcf->pmfHash, hr, E_OUTOFMEMORY, "Failed to allocate memory for individual file's MSI file hash"); | ||
| 872 | |||
| 873 | pcf->pmfHash->dwFileHashInfoSize = sizeof(MSIFILEHASHINFO); | ||
| 874 | pcf->pmfHash->dwData[0] = pmfHash->dwData[0]; | ||
| 875 | pcf->pmfHash->dwData[1] = pmfHash->dwData[1]; | ||
| 876 | pcf->pmfHash->dwData[2] = pmfHash->dwData[2]; | ||
| 877 | pcf->pmfHash->dwData[3] = pmfHash->dwData[3]; | ||
| 878 | } | ||
| 879 | |||
| 880 | hr = StrAllocString(&pcf->pwzSourcePath, wzFile, 0); | ||
| 881 | ExitOnFailure(hr, "Failed to copy file path: %ls", wzFile); | ||
| 882 | |||
| 883 | if (wzToken && *wzToken) | ||
| 884 | { | ||
| 885 | hr = StrAllocString(&pcf->pwzToken, wzToken, 0); | ||
| 886 | ExitOnFailure(hr, "Failed to copy file token: %ls", wzToken); | ||
| 887 | } | ||
| 888 | |||
| 889 | ++pcd->cFilePaths; | ||
| 890 | |||
| 891 | hr = DictAddValue(pcd->shDictHandle, pcf); | ||
| 892 | ExitOnFailure(hr, "Failed to add file to dictionary of added files"); | ||
| 893 | |||
| 894 | LExit: | ||
| 895 | ReleaseMem(pv); | ||
| 896 | return hr; | ||
| 897 | } | ||
| 898 | |||
| 899 | |||
| 900 | static HRESULT UpdateDuplicateFiles( | ||
| 901 | __in const CABC_DATA *pcd | ||
| 902 | ) | ||
| 903 | { | ||
| 904 | HRESULT hr = S_OK; | ||
| 905 | DWORD cbCabinet = 0; | ||
| 906 | LARGE_INTEGER liCabinetSize = { }; | ||
| 907 | HANDLE hCabinet = INVALID_HANDLE_VALUE; | ||
| 908 | HANDLE hCabinetMapping = NULL; | ||
| 909 | LPVOID pv = NULL; | ||
| 910 | MS_CABINET_HEADER *pCabinetHeader = NULL; | ||
| 911 | |||
| 912 | hCabinet = ::CreateFileW(pcd->wzCabinetPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | ||
| 913 | if (INVALID_HANDLE_VALUE == hCabinet) | ||
| 914 | { | ||
| 915 | ExitWithLastError(hr, "Failed to open cabinet: %ls", pcd->wzCabinetPath); | ||
| 916 | } | ||
| 917 | |||
| 918 | // Shouldn't need more than 16 MB to get the whole cabinet header into memory so use that as | ||
| 919 | // the upper bound for the memory map. | ||
| 920 | if (!::GetFileSizeEx(hCabinet, &liCabinetSize)) | ||
| 921 | { | ||
| 922 | ExitWithLastError(hr, "Failed to get size of cabinet: %ls", pcd->wzCabinetPath); | ||
| 923 | } | ||
| 924 | |||
| 925 | if (0 == liCabinetSize.HighPart && liCabinetSize.LowPart < MAX_CABINET_HEADER_SIZE) | ||
| 926 | { | ||
| 927 | cbCabinet = liCabinetSize.LowPart; | ||
| 928 | } | ||
| 929 | else | ||
| 930 | { | ||
| 931 | cbCabinet = MAX_CABINET_HEADER_SIZE; | ||
| 932 | } | ||
| 933 | |||
| 934 | // CreateFileMapping() returns NULL on failure, not INVALID_HANDLE_VALUE | ||
| 935 | hCabinetMapping = ::CreateFileMappingW(hCabinet, NULL, PAGE_READWRITE | SEC_COMMIT, 0, cbCabinet, NULL); | ||
| 936 | if (NULL == hCabinetMapping || INVALID_HANDLE_VALUE == hCabinetMapping) | ||
| 937 | { | ||
| 938 | ExitWithLastError(hr, "Failed to memory map cabinet file: %ls", pcd->wzCabinetPath); | ||
| 939 | } | ||
| 940 | |||
| 941 | pv = ::MapViewOfFile(hCabinetMapping, FILE_MAP_WRITE, 0, 0, 0); | ||
| 942 | ExitOnNullWithLastError(pv, hr, "Failed to map view of cabinet file: %ls", pcd->wzCabinetPath); | ||
| 943 | |||
| 944 | pCabinetHeader = static_cast<MS_CABINET_HEADER*>(pv); | ||
| 945 | |||
| 946 | for (DWORD i = 0; i < pcd->cDuplicates; ++i) | ||
| 947 | { | ||
| 948 | const CABC_DUPLICATEFILE *pDuplicateFile = pcd->prgDuplicates + i; | ||
| 949 | |||
| 950 | hr = DuplicateFile(pCabinetHeader, pcd, pDuplicateFile); | ||
| 951 | ExitOnFailure(hr, "Failed to find cabinet file items at index: %d and %d", pDuplicateFile->dwFileArrayIndex, pDuplicateFile->dwDuplicateCabFileIndex); | ||
| 952 | } | ||
| 953 | |||
| 954 | LExit: | ||
| 955 | if (pv) | ||
| 956 | { | ||
| 957 | ::UnmapViewOfFile(pv); | ||
| 958 | } | ||
| 959 | if (hCabinetMapping) | ||
| 960 | { | ||
| 961 | ::CloseHandle(hCabinetMapping); | ||
| 962 | } | ||
| 963 | ReleaseFileHandle(hCabinet); | ||
| 964 | |||
| 965 | return hr; | ||
| 966 | } | ||
| 967 | |||
| 968 | |||
| 969 | static HRESULT DuplicateFile( | ||
| 970 | __in MS_CABINET_HEADER *pHeader, | ||
| 971 | __in const CABC_DATA *pcd, | ||
| 972 | __in const CABC_DUPLICATEFILE *pDuplicate | ||
| 973 | ) | ||
| 974 | { | ||
| 975 | HRESULT hr = S_OK; | ||
| 976 | BYTE *pbHeader = reinterpret_cast<BYTE*>(pHeader); | ||
| 977 | BYTE* pbItem = pbHeader + pHeader->coffFiles; | ||
| 978 | const MS_CABINET_ITEM *pOriginalItem = NULL; | ||
| 979 | MS_CABINET_ITEM *pDuplicateItem = NULL; | ||
| 980 | |||
| 981 | if (pHeader->cFiles <= pcd->prgFiles[pDuplicate->dwFileArrayIndex].dwCabFileIndex || | ||
| 982 | pHeader->cFiles <= pDuplicate->dwDuplicateCabFileIndex || | ||
| 983 | pDuplicate->dwDuplicateCabFileIndex <= pcd->prgFiles[pDuplicate->dwFileArrayIndex].dwCabFileIndex) | ||
| 984 | { | ||
| 985 | hr = E_UNEXPECTED; | ||
| 986 | ExitOnFailure(hr, "Unexpected duplicate file indices, header cFiles: %d, file index: %d, duplicate index: %d", pHeader->cFiles, pcd->prgFiles[pDuplicate->dwFileArrayIndex].dwCabFileIndex, pDuplicate->dwDuplicateCabFileIndex); | ||
| 987 | } | ||
| 988 | |||
| 989 | // Step through each cabinet items until we get to the original | ||
| 990 | // file's index. Notice that the name of the cabinet item is | ||
| 991 | // appended to the end of the MS_CABINET_INFO, that's why we can't | ||
| 992 | // index straight to the data we want. | ||
| 993 | for (DWORD i = 0; i < pcd->prgFiles[pDuplicate->dwFileArrayIndex].dwCabFileIndex; ++i) | ||
| 994 | { | ||
| 995 | LPCSTR szItemName = reinterpret_cast<LPCSTR>(pbItem + sizeof(MS_CABINET_ITEM)); | ||
| 996 | pbItem = pbItem + sizeof(MS_CABINET_ITEM) + lstrlenA(szItemName) + 1; | ||
| 997 | } | ||
| 998 | |||
| 999 | pOriginalItem = reinterpret_cast<const MS_CABINET_ITEM*>(pbItem); | ||
| 1000 | |||
| 1001 | // Now pick up where we left off after the original file's index | ||
| 1002 | // was found and loop until we find the duplicate file's index. | ||
| 1003 | for (DWORD i = pcd->prgFiles[pDuplicate->dwFileArrayIndex].dwCabFileIndex; i < pDuplicate->dwDuplicateCabFileIndex; ++i) | ||
| 1004 | { | ||
| 1005 | LPCSTR szItemName = reinterpret_cast<LPCSTR>(pbItem + sizeof(MS_CABINET_ITEM)); | ||
| 1006 | pbItem = pbItem + sizeof(MS_CABINET_ITEM) + lstrlenA(szItemName) + 1; | ||
| 1007 | } | ||
| 1008 | |||
| 1009 | pDuplicateItem = reinterpret_cast<MS_CABINET_ITEM*>(pbItem); | ||
| 1010 | |||
| 1011 | if (0 != pDuplicateItem->cbFile) | ||
| 1012 | { | ||
| 1013 | hr = E_UNEXPECTED; | ||
| 1014 | ExitOnFailure(hr, "Failed because duplicate file does not have a file size of zero: %d", pDuplicateItem->cbFile); | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | pDuplicateItem->cbFile = pOriginalItem->cbFile; | ||
| 1018 | pDuplicateItem->uoffFolderStart = pOriginalItem->uoffFolderStart; | ||
| 1019 | pDuplicateItem->iFolder = pOriginalItem->iFolder; | ||
| 1020 | // Note: we do *not* duplicate the date/time and attributes metadata from | ||
| 1021 | // the original item to the duplicate. The following lines are commented | ||
| 1022 | // so people are not tempted to put them back. | ||
| 1023 | //pDuplicateItem->date = pOriginalItem->date; | ||
| 1024 | //pDuplicateItem->time = pOriginalItem->time; | ||
| 1025 | //pDuplicateItem->attribs = pOriginalItem->attribs; | ||
| 1026 | |||
| 1027 | LExit: | ||
| 1028 | return hr; | ||
| 1029 | } | ||
| 1030 | |||
| 1031 | |||
| 1032 | static HRESULT UtcFileTimeToLocalDosDateTime( | ||
| 1033 | __in const FILETIME* pFileTime, | ||
| 1034 | __out USHORT* pDate, | ||
| 1035 | __out USHORT* pTime | ||
| 1036 | ) | ||
| 1037 | { | ||
| 1038 | HRESULT hr = S_OK; | ||
| 1039 | FILETIME ftLocal = { }; | ||
| 1040 | |||
| 1041 | if (!::FileTimeToLocalFileTime(pFileTime, &ftLocal)) | ||
| 1042 | { | ||
| 1043 | ExitWithLastError(hr, "Filed to convert file time to local file time."); | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | if (!::FileTimeToDosDateTime(&ftLocal, pDate, pTime)) | ||
| 1047 | { | ||
| 1048 | ExitWithLastError(hr, "Filed to convert file time to DOS date time."); | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | LExit: | ||
| 1052 | return hr; | ||
| 1053 | } | ||
| 1054 | |||
| 1055 | |||
| 1056 | /******************************************************************** | ||
| 1057 | FCI callback functions | ||
| 1058 | |||
| 1059 | *********************************************************************/ | ||
| 1060 | static __callback int DIAMONDAPI CabCFilePlaced( | ||
| 1061 | __in PCCAB pccab, | ||
| 1062 | __in_z PSTR szFile, | ||
| 1063 | __in long cbFile, | ||
| 1064 | __in BOOL fContinuation, | ||
| 1065 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1066 | ) | ||
| 1067 | { | ||
| 1068 | UNREFERENCED_PARAMETER(pccab); | ||
| 1069 | UNREFERENCED_PARAMETER(szFile); | ||
| 1070 | UNREFERENCED_PARAMETER(cbFile); | ||
| 1071 | UNREFERENCED_PARAMETER(fContinuation); | ||
| 1072 | UNREFERENCED_PARAMETER(pv); | ||
| 1073 | return 0; | ||
| 1074 | } | ||
| 1075 | |||
| 1076 | |||
| 1077 | static __callback void * DIAMONDAPI CabCAlloc( | ||
| 1078 | __in ULONG cb | ||
| 1079 | ) | ||
| 1080 | { | ||
| 1081 | return MemAlloc(cb, FALSE); | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | |||
| 1085 | static __callback void DIAMONDAPI CabCFree( | ||
| 1086 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1087 | ) | ||
| 1088 | { | ||
| 1089 | MemFree(pv); | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | static __callback INT_PTR DIAMONDAPI CabCOpen( | ||
| 1093 | __in_z PSTR pszFile, | ||
| 1094 | __in int oflag, | ||
| 1095 | __in int pmode, | ||
| 1096 | __out int *err, | ||
| 1097 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1098 | ) | ||
| 1099 | { | ||
| 1100 | CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv); | ||
| 1101 | HRESULT hr = S_OK; | ||
| 1102 | INT_PTR pFile = -1; | ||
| 1103 | DWORD dwAccess = 0; | ||
| 1104 | DWORD dwDisposition = 0; | ||
| 1105 | DWORD dwAttributes = 0; | ||
| 1106 | |||
| 1107 | // | ||
| 1108 | // Translate flags for CreateFile | ||
| 1109 | // | ||
| 1110 | if (oflag & _O_CREAT) | ||
| 1111 | { | ||
| 1112 | if (pmode == _S_IREAD) | ||
| 1113 | dwAccess |= GENERIC_READ; | ||
| 1114 | else if (pmode == _S_IWRITE) | ||
| 1115 | dwAccess |= GENERIC_WRITE; | ||
| 1116 | else if (pmode == (_S_IWRITE | _S_IREAD)) | ||
| 1117 | dwAccess |= GENERIC_READ | GENERIC_WRITE; | ||
| 1118 | |||
| 1119 | if (oflag & _O_SHORT_LIVED) | ||
| 1120 | dwDisposition = FILE_ATTRIBUTE_TEMPORARY; | ||
| 1121 | else if (oflag & _O_TEMPORARY) | ||
| 1122 | dwAttributes |= FILE_FLAG_DELETE_ON_CLOSE; | ||
| 1123 | else if (oflag & _O_EXCL) | ||
| 1124 | dwDisposition = CREATE_NEW; | ||
| 1125 | } | ||
| 1126 | if (oflag & _O_TRUNC) | ||
| 1127 | dwDisposition = CREATE_ALWAYS; | ||
| 1128 | |||
| 1129 | if (!dwAccess) | ||
| 1130 | dwAccess = GENERIC_READ; | ||
| 1131 | if (!dwDisposition) | ||
| 1132 | dwDisposition = OPEN_EXISTING; | ||
| 1133 | if (!dwAttributes) | ||
| 1134 | dwAttributes = FILE_ATTRIBUTE_NORMAL; | ||
| 1135 | |||
| 1136 | // Check to see if we were passed the magic character that says 'Unicode string follows'. | ||
| 1137 | if (pszFile && CABC_MAGIC_UNICODE_STRING_MARKER == *pszFile) | ||
| 1138 | { | ||
| 1139 | pFile = reinterpret_cast<INT_PTR>(::CreateFileW(reinterpret_cast<LPCWSTR>(pszFile + 1), dwAccess, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, dwDisposition, dwAttributes, NULL)); | ||
| 1140 | } | ||
| 1141 | else | ||
| 1142 | { | ||
| 1143 | #pragma prefast(push) | ||
| 1144 | #pragma prefast(disable:25068) // We intentionally don't use the unicode API here | ||
| 1145 | pFile = reinterpret_cast<INT_PTR>(::CreateFileA(pszFile, dwAccess, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, dwDisposition, dwAttributes, NULL)); | ||
| 1146 | #pragma prefast(pop) | ||
| 1147 | } | ||
| 1148 | |||
| 1149 | if (INVALID_HANDLE_VALUE == reinterpret_cast<HANDLE>(pFile)) | ||
| 1150 | { | ||
| 1151 | ExitOnLastError(hr, "failed to open file: %s", pszFile); | ||
| 1152 | } | ||
| 1153 | |||
| 1154 | LExit: | ||
| 1155 | if (FAILED(hr)) | ||
| 1156 | pcd->hrLastError = *err = hr; | ||
| 1157 | |||
| 1158 | return FAILED(hr) ? -1 : pFile; | ||
| 1159 | } | ||
| 1160 | |||
| 1161 | |||
| 1162 | static __callback UINT FAR DIAMONDAPI CabCRead( | ||
| 1163 | __in INT_PTR hf, | ||
| 1164 | __out_bcount(cb) void FAR *memory, | ||
| 1165 | __in UINT cb, | ||
| 1166 | __out int *err, | ||
| 1167 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1168 | ) | ||
| 1169 | { | ||
| 1170 | CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv); | ||
| 1171 | HRESULT hr = S_OK; | ||
| 1172 | DWORD cbRead = 0; | ||
| 1173 | |||
| 1174 | ExitOnNull(hf, *err, E_INVALIDARG, "Failed to read during cabinet extraction because no file handle was provided"); | ||
| 1175 | if (!::ReadFile(reinterpret_cast<HANDLE>(hf), memory, cb, &cbRead, NULL)) | ||
| 1176 | { | ||
| 1177 | *err = ::GetLastError(); | ||
| 1178 | ExitOnLastError(hr, "failed to read during cabinet extraction"); | ||
| 1179 | } | ||
| 1180 | |||
| 1181 | LExit: | ||
| 1182 | if (FAILED(hr)) | ||
| 1183 | { | ||
| 1184 | pcd->hrLastError = *err = hr; | ||
| 1185 | } | ||
| 1186 | |||
| 1187 | return FAILED(hr) ? -1 : cbRead; | ||
| 1188 | } | ||
| 1189 | |||
| 1190 | |||
| 1191 | static __callback UINT FAR DIAMONDAPI CabCWrite( | ||
| 1192 | __in INT_PTR hf, | ||
| 1193 | __in_bcount(cb) void FAR *memory, | ||
| 1194 | __in UINT cb, | ||
| 1195 | __out int *err, | ||
| 1196 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1197 | ) | ||
| 1198 | { | ||
| 1199 | CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv); | ||
| 1200 | HRESULT hr = S_OK; | ||
| 1201 | DWORD cbWrite = 0; | ||
| 1202 | |||
| 1203 | ExitOnNull(hf, *err, E_INVALIDARG, "Failed to write during cabinet extraction because no file handle was provided"); | ||
| 1204 | if (!::WriteFile(reinterpret_cast<HANDLE>(hf), memory, cb, &cbWrite, NULL)) | ||
| 1205 | { | ||
| 1206 | *err = ::GetLastError(); | ||
| 1207 | ExitOnLastError(hr, "failed to write during cabinet extraction"); | ||
| 1208 | } | ||
| 1209 | |||
| 1210 | LExit: | ||
| 1211 | if (FAILED(hr)) | ||
| 1212 | pcd->hrLastError = *err = hr; | ||
| 1213 | |||
| 1214 | return FAILED(hr) ? -1 : cbWrite; | ||
| 1215 | } | ||
| 1216 | |||
| 1217 | |||
| 1218 | static __callback long FAR DIAMONDAPI CabCSeek( | ||
| 1219 | __in INT_PTR hf, | ||
| 1220 | __in long dist, | ||
| 1221 | __in int seektype, | ||
| 1222 | __out int *err, | ||
| 1223 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1224 | ) | ||
| 1225 | { | ||
| 1226 | CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv); | ||
| 1227 | HRESULT hr = S_OK; | ||
| 1228 | DWORD dwMoveMethod; | ||
| 1229 | LONG lMove = 0; | ||
| 1230 | |||
| 1231 | switch (seektype) | ||
| 1232 | { | ||
| 1233 | case 0: // SEEK_SET | ||
| 1234 | dwMoveMethod = FILE_BEGIN; | ||
| 1235 | break; | ||
| 1236 | case 1: /// SEEK_CUR | ||
| 1237 | dwMoveMethod = FILE_CURRENT; | ||
| 1238 | break; | ||
| 1239 | case 2: // SEEK_END | ||
| 1240 | dwMoveMethod = FILE_END; | ||
| 1241 | break; | ||
| 1242 | default : | ||
| 1243 | dwMoveMethod = 0; | ||
| 1244 | hr = E_UNEXPECTED; | ||
| 1245 | ExitOnFailure(hr, "unexpected seektype in FCISeek(): %d", seektype); | ||
| 1246 | } | ||
| 1247 | |||
| 1248 | // SetFilePointer returns -1 if it fails (this will cause FDI to quit with an FDIERROR_USER_ABORT error. | ||
| 1249 | // (Unless this happens while working on a cabinet, in which case FDI returns FDIERROR_CORRUPT_CABINET) | ||
| 1250 | // Todo: update these comments for FCI (are they accurate for FCI as well?) | ||
| 1251 | lMove = ::SetFilePointer(reinterpret_cast<HANDLE>(hf), dist, NULL, dwMoveMethod); | ||
| 1252 | if (DWORD_MAX == lMove) | ||
| 1253 | { | ||
| 1254 | *err = ::GetLastError(); | ||
| 1255 | ExitOnLastError(hr, "failed to move file pointer %d bytes", dist); | ||
| 1256 | } | ||
| 1257 | |||
| 1258 | LExit: | ||
| 1259 | if (FAILED(hr)) | ||
| 1260 | { | ||
| 1261 | pcd->hrLastError = *err = hr; | ||
| 1262 | } | ||
| 1263 | |||
| 1264 | return FAILED(hr) ? -1 : lMove; | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | |||
| 1268 | static __callback int FAR DIAMONDAPI CabCClose( | ||
| 1269 | __in INT_PTR hf, | ||
| 1270 | __out int *err, | ||
| 1271 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1272 | ) | ||
| 1273 | { | ||
| 1274 | CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv); | ||
| 1275 | HRESULT hr = S_OK; | ||
| 1276 | |||
| 1277 | if (!::CloseHandle(reinterpret_cast<HANDLE>(hf))) | ||
| 1278 | { | ||
| 1279 | *err = ::GetLastError(); | ||
| 1280 | ExitOnLastError(hr, "failed to close file during cabinet extraction"); | ||
| 1281 | } | ||
| 1282 | |||
| 1283 | LExit: | ||
| 1284 | if (FAILED(hr)) | ||
| 1285 | { | ||
| 1286 | pcd->hrLastError = *err = hr; | ||
| 1287 | } | ||
| 1288 | |||
| 1289 | return FAILED(hr) ? -1 : 0; | ||
| 1290 | } | ||
| 1291 | |||
| 1292 | static __callback int DIAMONDAPI CabCDelete( | ||
| 1293 | __in_z PSTR szFile, | ||
| 1294 | __out int *err, | ||
| 1295 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1296 | ) | ||
| 1297 | { | ||
| 1298 | UNREFERENCED_PARAMETER(err); | ||
| 1299 | UNREFERENCED_PARAMETER(pv); | ||
| 1300 | |||
| 1301 | #pragma prefast(push) | ||
| 1302 | #pragma prefast(disable:25068) // We intentionally don't use the unicode API here | ||
| 1303 | ::DeleteFileA(szFile); | ||
| 1304 | #pragma prefast(pop) | ||
| 1305 | |||
| 1306 | return 0; | ||
| 1307 | } | ||
| 1308 | |||
| 1309 | |||
| 1310 | __success(return != FALSE) | ||
| 1311 | static __callback BOOL DIAMONDAPI CabCGetTempFile( | ||
| 1312 | __out_bcount_z(cbFile) char *szFile, | ||
| 1313 | __in int cbFile, | ||
| 1314 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1315 | ) | ||
| 1316 | { | ||
| 1317 | CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv); | ||
| 1318 | static volatile DWORD dwIndex = 0; | ||
| 1319 | |||
| 1320 | HRESULT hr = S_OK; | ||
| 1321 | char szTempPath[MAX_PATH] = { }; | ||
| 1322 | DWORD cchTempPath = MAX_PATH; | ||
| 1323 | DWORD dwProcessId = ::GetCurrentProcessId(); | ||
| 1324 | HANDLE hTempFile = INVALID_HANDLE_VALUE; | ||
| 1325 | |||
| 1326 | if (MAX_PATH < ::GetTempPathA(cchTempPath, szTempPath)) | ||
| 1327 | { | ||
| 1328 | ExitWithLastError(hr, "Failed to get temp path during cabinet creation."); | ||
| 1329 | } | ||
| 1330 | |||
| 1331 | for (DWORD i = 0; i < DWORD_MAX; ++i) | ||
| 1332 | { | ||
| 1333 | LONG dwTempIndex = ::InterlockedIncrement(reinterpret_cast<volatile LONG*>(&dwIndex)); | ||
| 1334 | |||
| 1335 | hr = ::StringCbPrintfA(szFile, cbFile, "%s\\%08x.%03x", szTempPath, dwTempIndex, dwProcessId); | ||
| 1336 | ExitOnFailure(hr, "failed to format log file path."); | ||
| 1337 | |||
| 1338 | hTempFile = ::CreateFileA(szFile, 0, FILE_SHARE_DELETE, NULL, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); | ||
| 1339 | if (INVALID_HANDLE_VALUE != hTempFile) | ||
| 1340 | { | ||
| 1341 | // we found one that doesn't exist | ||
| 1342 | hr = S_OK; | ||
| 1343 | break; | ||
| 1344 | } | ||
| 1345 | else | ||
| 1346 | { | ||
| 1347 | hr = E_FAIL; // this file was taken so be pessimistic and assume we're not going to find one. | ||
| 1348 | } | ||
| 1349 | } | ||
| 1350 | ExitOnFailure(hr, "failed to find temporary file."); | ||
| 1351 | |||
| 1352 | LExit: | ||
| 1353 | ReleaseFileHandle(hTempFile); | ||
| 1354 | |||
| 1355 | if (FAILED(hr)) | ||
| 1356 | { | ||
| 1357 | pcd->hrLastError = hr; | ||
| 1358 | } | ||
| 1359 | |||
| 1360 | return FAILED(hr)? FALSE : TRUE; | ||
| 1361 | } | ||
| 1362 | |||
| 1363 | |||
| 1364 | __success(return != FALSE) | ||
| 1365 | static __callback BOOL DIAMONDAPI CabCGetNextCabinet( | ||
| 1366 | __in PCCAB pccab, | ||
| 1367 | __in ULONG ul, | ||
| 1368 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1369 | ) | ||
| 1370 | { | ||
| 1371 | UNREFERENCED_PARAMETER(ul); | ||
| 1372 | |||
| 1373 | // Construct next cab names like cab1a.cab, cab1b.cab, cab1c.cab, ........ | ||
| 1374 | CABC_DATA *pcd = reinterpret_cast<CABC_DATA*>(pv); | ||
| 1375 | HRESULT hr = S_OK; | ||
| 1376 | LPWSTR pwzFileToken = NULL; | ||
| 1377 | WCHAR wzNewCabName[MAX_PATH] = L""; | ||
| 1378 | |||
| 1379 | if (pccab->iCab == 1) | ||
| 1380 | { | ||
| 1381 | pcd->wzFirstCabinetName[0] = '\0'; | ||
| 1382 | LPCWSTR pwzCabinetName = FileFromPath(pcd->wzCabinetPath); | ||
| 1383 | size_t len = wcsnlen(pwzCabinetName, sizeof(pwzCabinetName)); | ||
| 1384 | if (len > 4) | ||
| 1385 | { | ||
| 1386 | len -= 4; // remove Extention ".cab" of 8.3 Format | ||
| 1387 | } | ||
| 1388 | hr = ::StringCchCatNW(pcd->wzFirstCabinetName, countof(pcd->wzFirstCabinetName), pwzCabinetName, len); | ||
| 1389 | ExitOnFailure(hr, "Failed to remove extension to create next Cabinet File Name"); | ||
| 1390 | } | ||
| 1391 | |||
| 1392 | const int nAlphabets = 26; // Number of Alphabets from a to z | ||
| 1393 | if (pccab->iCab <= nAlphabets) | ||
| 1394 | { | ||
| 1395 | // Construct next cab names like cab1a.cab, cab1b.cab, cab1c.cab, ........ | ||
| 1396 | hr = ::StringCchPrintfA(pccab->szCab, sizeof(pccab->szCab), "%ls%c.cab", pcd->wzFirstCabinetName, char(((int)('a') - 1) + pccab->iCab)); | ||
| 1397 | ExitOnFailure(hr, "Failed to create next Cabinet File Name"); | ||
| 1398 | hr = ::StringCchPrintfW(wzNewCabName, countof(wzNewCabName), L"%ls%c.cab", pcd->wzFirstCabinetName, WCHAR(((int)('a') - 1) + pccab->iCab)); | ||
| 1399 | ExitOnFailure(hr, "Failed to create next Cabinet File Name"); | ||
| 1400 | } | ||
| 1401 | else if (pccab->iCab <= nAlphabets*nAlphabets) | ||
| 1402 | { | ||
| 1403 | // Construct next cab names like cab1aa.cab, cab1ab.cab, cab1ac.cab, ......, cabaz.cab, cabaa.cab, cabab.cab, cabac.cab, ...... | ||
| 1404 | int char2 = (pccab->iCab) % nAlphabets; | ||
| 1405 | int char1 = (pccab->iCab - char2)/nAlphabets; | ||
| 1406 | if (char2 == 0) | ||
| 1407 | { | ||
| 1408 | // e.g. when iCab = 52, we want az | ||
| 1409 | char2 = nAlphabets; // Second char must be 'z' in this case | ||
| 1410 | char1--; // First Char must be decremented by 1 | ||
| 1411 | } | ||
| 1412 | hr = ::StringCchPrintfA(pccab->szCab, sizeof(pccab->szCab), "%ls%c%c.cab", pcd->wzFirstCabinetName, char(((int)('a') - 1) + char1), char(((int)('a') - 1) + char2)); | ||
| 1413 | ExitOnFailure(hr, "Failed to create next Cabinet File Name"); | ||
| 1414 | hr = ::StringCchPrintfW(wzNewCabName, countof(wzNewCabName), L"%ls%c%c.cab", pcd->wzFirstCabinetName, WCHAR(((int)('a') - 1) + char1), WCHAR(((int)('a') - 1) + char2)); | ||
| 1415 | ExitOnFailure(hr, "Failed to create next Cabinet File Name"); | ||
| 1416 | } | ||
| 1417 | else | ||
| 1418 | { | ||
| 1419 | hr = DISP_E_BADINDEX; // Value 0x8002000B stands for Invalid index. | ||
| 1420 | ExitOnFailure(hr, "Cannot Split Cabinet more than 26*26 = 676 times. Failed to create next Cabinet File Name"); | ||
| 1421 | } | ||
| 1422 | |||
| 1423 | // Callback from PFNFCIGETNEXTCABINET CabCGetNextCabinet method | ||
| 1424 | if(pcd->fileSplitCabNamesCallback != 0) | ||
| 1425 | { | ||
| 1426 | // In following if/else block, getting the Token for the First File in the Cabinets that are getting Split | ||
| 1427 | // This code will need updation if we need to send all file tokens for the splitting Cabinets | ||
| 1428 | if (pcd->prgFiles[0].pwzToken) | ||
| 1429 | { | ||
| 1430 | pwzFileToken = pcd->prgFiles[0].pwzToken; | ||
| 1431 | } | ||
| 1432 | else | ||
| 1433 | { | ||
| 1434 | LPCWSTR wzSourcePath = pcd->prgFiles[0].pwzSourcePath; | ||
| 1435 | pwzFileToken = FileFromPath(wzSourcePath); | ||
| 1436 | } | ||
| 1437 | |||
| 1438 | // The call back to Binder to Add File Transfer for new Cab and add new Cab to Media table | ||
| 1439 | pcd->fileSplitCabNamesCallback(pcd->wzFirstCabinetName, wzNewCabName, pwzFileToken); | ||
| 1440 | } | ||
| 1441 | |||
| 1442 | LExit: | ||
| 1443 | if (FAILED(hr)) | ||
| 1444 | { | ||
| 1445 | // Returning False in case of error here as stated by Documentation, However It fails to Abort Cab Creation!!! | ||
| 1446 | // So Using separate check for pcd->hrLastError after ::FCIAddFile for Cabinet Splitting | ||
| 1447 | pcd->hrLastError = hr; | ||
| 1448 | return FALSE; | ||
| 1449 | } | ||
| 1450 | else | ||
| 1451 | { | ||
| 1452 | return TRUE; | ||
| 1453 | } | ||
| 1454 | } | ||
| 1455 | |||
| 1456 | |||
| 1457 | static __callback INT_PTR DIAMONDAPI CabCGetOpenInfo( | ||
| 1458 | __in_z PSTR pszName, | ||
| 1459 | __out USHORT *pdate, | ||
| 1460 | __out USHORT *ptime, | ||
| 1461 | __out USHORT *pattribs, | ||
| 1462 | __out int *err, | ||
| 1463 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1464 | ) | ||
| 1465 | { | ||
| 1466 | HRESULT hr = S_OK; | ||
| 1467 | CABC_INTERNAL_ADDFILEINFO* pFileInfo = reinterpret_cast<CABC_INTERNAL_ADDFILEINFO*>(pszName); | ||
| 1468 | LPCWSTR wzFile = NULL; | ||
| 1469 | DWORD cbFile = 0; | ||
| 1470 | LPSTR pszFilePlusMagic = NULL; | ||
| 1471 | DWORD cbFilePlusMagic = 0; | ||
| 1472 | WIN32_FILE_ATTRIBUTE_DATA fad = { }; | ||
| 1473 | INT_PTR iResult = -1; | ||
| 1474 | |||
| 1475 | // If there is an empty file provided, use that as the source path to cab (since we | ||
| 1476 | // must be dealing with a duplicate file). Otherwise, use the source path you'd expect. | ||
| 1477 | wzFile = pFileInfo->wzEmptyPath ? pFileInfo->wzEmptyPath : pFileInfo->wzSourcePath; | ||
| 1478 | cbFile = (lstrlenW(wzFile) + 1) * sizeof(WCHAR); | ||
| 1479 | |||
| 1480 | // Convert the source file path into an Ansi string that our APIs will recognize as | ||
| 1481 | // a Unicode string (due to the magic character). | ||
| 1482 | cbFilePlusMagic = cbFile + 1; // add one for the magic. | ||
| 1483 | pszFilePlusMagic = reinterpret_cast<LPSTR>(MemAlloc(cbFilePlusMagic, TRUE)); | ||
| 1484 | |||
| 1485 | *pszFilePlusMagic = CABC_MAGIC_UNICODE_STRING_MARKER; | ||
| 1486 | memcpy_s(pszFilePlusMagic + 1, cbFilePlusMagic - 1, wzFile, cbFile); | ||
| 1487 | |||
| 1488 | if (!::GetFileAttributesExW(pFileInfo->wzSourcePath, GetFileExInfoStandard, &fad)) | ||
| 1489 | { | ||
| 1490 | ExitWithLastError(hr, "Failed to get file attributes on '%s'.", pFileInfo->wzSourcePath); | ||
| 1491 | } | ||
| 1492 | |||
| 1493 | // Set the attributes but only allow the few attributes that CAB supports. | ||
| 1494 | *pattribs = static_cast<USHORT>(fad.dwFileAttributes) & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE); | ||
| 1495 | |||
| 1496 | hr = UtcFileTimeToLocalDosDateTime(&fad.ftLastWriteTime, pdate, ptime); | ||
| 1497 | if (FAILED(hr)) | ||
| 1498 | { | ||
| 1499 | // NOTE: Changed this from ftLastWriteTime to ftCreationTime because of issues around how different OSs were | ||
| 1500 | // handling the access of the FILETIME structure and how it would fail conversion to DOS time if it wasn't | ||
| 1501 | // found. This would create further problems if the file was written to the CAB without this value. Windows | ||
| 1502 | // Installer would then fail to extract the file. | ||
| 1503 | hr = UtcFileTimeToLocalDosDateTime(&fad.ftCreationTime, pdate, ptime); | ||
| 1504 | ExitOnFailure(hr, "Filed to read a valid file time stucture on file '%s'.", pszName); | ||
| 1505 | } | ||
| 1506 | |||
| 1507 | iResult = CabCOpen(pszFilePlusMagic, _O_BINARY|_O_RDONLY, 0, err, pv); | ||
| 1508 | |||
| 1509 | LExit: | ||
| 1510 | ReleaseMem(pszFilePlusMagic); | ||
| 1511 | if (FAILED(hr)) | ||
| 1512 | { | ||
| 1513 | *err = (int)hr; | ||
| 1514 | } | ||
| 1515 | |||
| 1516 | return FAILED(hr) ? -1 : iResult; | ||
| 1517 | } | ||
| 1518 | |||
| 1519 | |||
| 1520 | static __callback long DIAMONDAPI CabCStatus( | ||
| 1521 | __in UINT ui, | ||
| 1522 | __in ULONG cb1, | ||
| 1523 | __in ULONG cb2, | ||
| 1524 | __out_bcount(CABC_HANDLE_BYTES) void *pv | ||
| 1525 | ) | ||
| 1526 | { | ||
| 1527 | UNREFERENCED_PARAMETER(ui); | ||
| 1528 | UNREFERENCED_PARAMETER(cb1); | ||
| 1529 | UNREFERENCED_PARAMETER(cb2); | ||
| 1530 | UNREFERENCED_PARAMETER(pv); | ||
| 1531 | return 0; | ||
| 1532 | } | ||
