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