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