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