aboutsummaryrefslogtreecommitdiff
path: root/src/dutil/cabcutil.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dutil/cabcutil.cpp')
-rw-r--r--src/dutil/cabcutil.cpp1532
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
5static const WCHAR CABC_MAGIC_UNICODE_STRING_MARKER = '?';
6static 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.
13static const DWORD MINFLUSHTHRESHHOLD = 0;
14
15// structs
16struct 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
33struct MS_CABINET_ITEM
34{
35 DWORD cbFile;
36 DWORD uoffFolderStart;
37 WORD iFolder;
38 WORD date;
39 WORD time;
40 WORD attribs;
41};
42
43struct CABC_INTERNAL_ADDFILEINFO
44{
45 LPCWSTR wzSourcePath;
46 LPCWSTR wzEmptyPath;
47};
48
49struct CABC_DUPLICATEFILE
50{
51 DWORD dwFileArrayIndex;
52 DWORD dwDuplicateCabFileIndex;
53 LPWSTR pwzSourcePath;
54 LPWSTR pwzToken;
55};
56
57
58struct CABC_FILE
59{
60 DWORD dwCabFileIndex;
61 LPWSTR pwzSourcePath;
62 LPWSTR pwzToken;
63 PMSIFILEHASHINFO pmfHash;
64 LONGLONG llFileSize;
65 BOOL fHasDuplicates;
66};
67
68
69struct 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
103const int CABC_HANDLE_BYTES = sizeof(CABC_DATA);
104
105//
106// prototypes
107//
108static void FreeCabCData(
109 __in CABC_DATA* pcd
110 );
111static 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 );
118static 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 );
125static 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 );
133static HRESULT UpdateDuplicateFiles(
134 __in const CABC_DATA *pcd
135 );
136static HRESULT DuplicateFile(
137 __in MS_CABINET_HEADER *pHeader,
138 __in const CABC_DATA *pcd,
139 __in const CABC_DUPLICATEFILE *pDuplicate
140 );
141static HRESULT UtcFileTimeToLocalDosDateTime(
142 __in const FILETIME* pFileTime,
143 __out USHORT* pDate,
144 __out USHORT* pTime
145 );
146
147static __callback int DIAMONDAPI CabCFilePlaced(__in PCCAB pccab, __in_z PSTR szFile, __in long cbFile, __in BOOL fContinuation, __out_bcount(CABC_HANDLE_BYTES) void *pv);
148static __callback void * DIAMONDAPI CabCAlloc(__in ULONG cb);
149static __callback void DIAMONDAPI CabCFree(__out_bcount(CABC_HANDLE_BYTES) void *pv);
150static __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);
151static __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);
152static __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);
153static __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);
154static __callback int FAR DIAMONDAPI CabCClose(__in INT_PTR hf, __out int *err, __out_bcount(CABC_HANDLE_BYTES) void *pv);
155static __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);
158static __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);
159static __callback long DIAMONDAPI CabCStatus(__in UINT uiTypeStatus, __in ULONG cb1, __in ULONG cb2, __out_bcount(CABC_HANDLE_BYTES) void *pv);
160
161
162/********************************************************************
163CabcBegin - begins creating a cabinet
164
165NOTE: 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********************************************************************/
170extern "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
344LExit:
345 if (FAILED(hr) && pcd && pcd->hfci)
346 {
347 ::FCIDestroy(pcd->hfci);
348 }
349
350 return hr;
351}
352
353
354/********************************************************************
355CabCNextCab - This will be useful when creating multiple cabs.
356Haven't needed it yet.
357********************************************************************/
358extern "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/********************************************************************
369CabcAddFile - adds a file to a cabinet
370
371NOTE: hContext must be the same used in Begin and Finish
372if wzToken is null, the file's original name is used within the cab
373********************************************************************/
374extern "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
425LExit:
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/********************************************************************
439CabcFinish - finishes making a cabinet
440
441NOTE: hContext must be the same used in Begin and AddFile
442*********************************************************************/
443extern "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
642LExit:
643 ::FCIDestroy(pcd->hfci);
644 FreeCabCData(pcd);
645 ReleaseNullStr(pszFileToken);
646
647 return hr;
648}
649
650
651/********************************************************************
652CabCCancel - cancels making a cabinet
653
654NOTE: hContext must be the same used in Begin and AddFile
655*********************************************************************/
656extern "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
672static 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
697static 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
767LExit:
768
769 return hr;
770}
771
772
773static 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
826LExit:
827 ReleaseMem(pv);
828 return hr;
829}
830
831
832static 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
894LExit:
895 ReleaseMem(pv);
896 return hr;
897}
898
899
900static 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
954LExit:
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
969static 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
1027LExit:
1028 return hr;
1029}
1030
1031
1032static 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
1051LExit:
1052 return hr;
1053}
1054
1055
1056/********************************************************************
1057 FCI callback functions
1058
1059*********************************************************************/
1060static __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
1077static __callback void * DIAMONDAPI CabCAlloc(
1078 __in ULONG cb
1079 )
1080{
1081 return MemAlloc(cb, FALSE);
1082}
1083
1084
1085static __callback void DIAMONDAPI CabCFree(
1086 __out_bcount(CABC_HANDLE_BYTES) void *pv
1087 )
1088{
1089 MemFree(pv);
1090}
1091
1092static __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
1154LExit:
1155 if (FAILED(hr))
1156 pcd->hrLastError = *err = hr;
1157
1158 return FAILED(hr) ? -1 : pFile;
1159}
1160
1161
1162static __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
1181LExit:
1182 if (FAILED(hr))
1183 {
1184 pcd->hrLastError = *err = hr;
1185 }
1186
1187 return FAILED(hr) ? -1 : cbRead;
1188}
1189
1190
1191static __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
1210LExit:
1211 if (FAILED(hr))
1212 pcd->hrLastError = *err = hr;
1213
1214 return FAILED(hr) ? -1 : cbWrite;
1215}
1216
1217
1218static __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
1258LExit:
1259 if (FAILED(hr))
1260 {
1261 pcd->hrLastError = *err = hr;
1262 }
1263
1264 return FAILED(hr) ? -1 : lMove;
1265}
1266
1267
1268static __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
1283LExit:
1284 if (FAILED(hr))
1285 {
1286 pcd->hrLastError = *err = hr;
1287 }
1288
1289 return FAILED(hr) ? -1 : 0;
1290}
1291
1292static __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)
1311static __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
1352LExit:
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)
1365static __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
1442LExit:
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
1457static __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
1509LExit:
1510 ReleaseMem(pszFilePlusMagic);
1511 if (FAILED(hr))
1512 {
1513 *err = (int)hr;
1514 }
1515
1516 return FAILED(hr) ? -1 : iResult;
1517}
1518
1519
1520static __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}