main
cpp 604 lines 18.7 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 #include "precomp.h"
4 #include "rexutil.h"
5
6
7 // Exit macros
8 #define RexExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
9 #define RexExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
10 #define RexExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
11 #define RexExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
12 #define RexExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
13 #define RexExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_REXUTIL, x, s, __VA_ARGS__)
14 #define RexExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_REXUTIL, p, x, e, s, __VA_ARGS__)
15 #define RexExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_REXUTIL, p, x, s, __VA_ARGS__)
16 #define RexExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_REXUTIL, p, x, e, s, __VA_ARGS__)
17 #define RexExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_REXUTIL, p, x, s, __VA_ARGS__)
18 #define RexExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_REXUTIL, e, x, s, __VA_ARGS__)
19 #define RexExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_REXUTIL, g, x, s, __VA_ARGS__)
20
21 //
22 // static globals
23 //
24 static HMODULE vhCabinetDll = NULL;
25 static HFDI vhfdi = NULL;
26 static ERF verf;
27
28 static FAKE_FILE vrgffFileTable[FILETABLESIZE];
29 static DWORD vcbRes;
30 static LPCBYTE vpbRes;
31 static LPSTR vpszResource = NULL;
32 static REX_CALLBACK_WRITE vpfnWrite = NULL;
33
34 static HRESULT vhrLastError = S_OK;
35
36 //
37 // structs
38 //
39 struct REX_CALLBACK_STRUCT
40 {
41 BOOL fStopExtracting; // flag set when no more files are needed
42 LPCWSTR pwzExtract; // file to extract ("*" means extract all)
43 LPCWSTR pwzExtractDir; // directory to extract files to
44 LPCWSTR pwzExtractName; // name of file (pwzExtract can't be "*")
45
46 // possible user data
47 REX_CALLBACK_PROGRESS pfnProgress;
48 LPVOID pvContext;
49 };
50
51 //
52 // prototypes
53 //
54 static __callback LPVOID DIAMONDAPI RexAlloc(DWORD dwSize);
55 static __callback void DIAMONDAPI RexFree(LPVOID pvData);
56 static __callback INT_PTR FAR DIAMONDAPI RexOpen(__in_z char FAR *pszFile, int oflag, int pmode);
57 static __callback UINT FAR DIAMONDAPI RexRead(INT_PTR hf, __out_bcount(cb) void FAR *pv, UINT cb);
58 static __callback UINT FAR DIAMONDAPI RexWrite(INT_PTR hf, __in_bcount(cb) void FAR *pv, UINT cb);
59 static __callback int FAR DIAMONDAPI RexClose(INT_PTR hf);
60 static __callback long FAR DIAMONDAPI RexSeek(INT_PTR hf, long dist, int seektype);
61 static __callback INT_PTR DIAMONDAPI RexCallback(FDINOTIFICATIONTYPE iNotification, FDINOTIFICATION *pFDINotify);
62
63
64 /********************************************************************
65 RexInitialize - initializes internal static variables
66
67 *******************************************************************/
68 extern "C" HRESULT RexInitialize()
69 {
70 Assert(!vhfdi);
71
72 HRESULT hr = S_OK;
73
74 vhfdi = ::FDICreate(RexAlloc, RexFree, RexOpen, RexRead, RexWrite, RexClose, RexSeek, cpuUNKNOWN, &verf);
75 if (!vhfdi)
76 {
77 hr = E_FAIL;
78 RexExitOnFailure(hr, "failed to initialize cabinet.dll"); // TODO: put verf info in trace message here
79 }
80
81 ::ZeroMemory(vrgffFileTable, sizeof(vrgffFileTable));
82
83 LExit:
84 if (FAILED(hr))
85 {
86 ::FDIDestroy(vhfdi);
87 vhfdi = NULL;
88
89 ReleaseNullStr(vpszResource);
90 }
91
92 return hr;
93 }
94
95
96 /********************************************************************
97 RexUninitialize - initializes internal static variables
98
99 *******************************************************************/
100 extern "C" void RexUninitialize()
101 {
102 if (vhfdi)
103 {
104 ::FDIDestroy(vhfdi);
105 vhfdi = NULL;
106
107 ReleaseNullStr(vpszResource);
108 }
109 }
110
111
112 /********************************************************************
113 RexExtract - extracts one or all files from a resource cabinet
114
115 NOTE: wzExtractId can be a single file id or "*" to extract all files
116 wzExttractDir must be normalized (end in a "\")
117 wzExtractName is ignored if wzExtractId is "*"
118 *******************************************************************/
119 extern "C" HRESULT RexExtract(
120 __in_z LPCSTR szResource,
121 __in_z LPCWSTR wzExtractId,
122 __in_z LPCWSTR wzExtractDir,
123 __in_z LPCWSTR wzExtractName,
124 __in REX_CALLBACK_PROGRESS pfnProgress,
125 __in REX_CALLBACK_WRITE pfnWrite,
126 __in LPVOID pvContext
127 )
128 {
129 Assert(vhfdi);
130 HRESULT hr = S_OK;
131 BOOL fResult = FALSE;
132
133 HRSRC hResInfo = NULL;
134 HANDLE hRes = NULL;
135
136 REX_CALLBACK_STRUCT rcs = { };
137
138 // remember the write callback
139 vpfnWrite = pfnWrite;
140
141 //
142 // load the cabinet resource
143 //
144 hResInfo = ::FindResourceExA(NULL, RT_RCDATA, szResource, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
145 RexExitOnNullWithLastError(hResInfo, hr, "Failed to find resource.");
146 //hResInfo = ::FindResourceW(NULL, wzResource, /*RT_RCDATA*/MAKEINTRESOURCEW(10));
147 //ExitOnNullWithLastError(hResInfo, hr, "failed to load resource info");
148
149 hRes = ::LoadResource(NULL, hResInfo);
150 RexExitOnNullWithLastError(hRes, hr, "failed to load resource");
151
152 vcbRes = ::SizeofResource(NULL, hResInfo);
153 vpbRes = (const BYTE*)::LockResource(hRes);
154
155 // TODO: Call FDIIsCabinet to confirm resource is a cabinet before trying to extract from it
156
157 //
158 // convert the resource name to multi-byte
159 //
160 //if (!::WideCharToMultiByte(CP_ACP, 0, wzResource, -1, vszResource, countof(vszResource), NULL, NULL))
161 //{
162 // RexExitOnLastError(hr, "failed to convert cabinet resource name to ASCII: %ls", wzResource);
163 //}
164
165 hr = StrAnsiAllocStringAnsi(&vpszResource, szResource, 0);
166 RexExitOnFailure(hr, "Failed to copy resource name to global.");
167
168 //
169 // iterate through files in cabinet extracting them to the callback function
170 //
171 rcs.fStopExtracting = FALSE;
172 rcs.pwzExtract = wzExtractId;
173 rcs.pwzExtractDir = wzExtractDir;
174 rcs.pwzExtractName = wzExtractName;
175 rcs.pfnProgress = pfnProgress;
176 rcs.pvContext = pvContext;
177
178 fResult = ::FDICopy(vhfdi, vpszResource, "", 0, RexCallback, NULL, static_cast<void*>(&rcs));
179 if (!fResult && !rcs.fStopExtracting) // if something went wrong and it wasn't us just stopping the extraction, then return a failure
180 {
181 hr = vhrLastError; // TODO: put verf info in trace message here
182 }
183
184 LExit:
185 return hr;
186 }
187
188
189 /****************************************************************************
190 default extract routines
191
192 ****************************************************************************/
193 static __callback LPVOID DIAMONDAPI RexAlloc(DWORD dwSize)
194 {
195 return MemAlloc(dwSize, FALSE);
196 }
197
198
199 static __callback void DIAMONDAPI RexFree(LPVOID pvData)
200 {
201 MemFree(pvData);
202 }
203
204
205 static __callback INT_PTR FAR DIAMONDAPI RexOpen(__in_z char FAR *pszFile, int oflag, int pmode)
206 {
207 HRESULT hr = S_OK;
208 HANDLE hFile = INVALID_HANDLE_VALUE;
209 int i = 0;
210
211 // if FDI asks for some unusual mode (__in low memory situation it could ask for a scratch file) fail
212 if ((oflag != (/*_O_BINARY*/ 0x8000 | /*_O_RDONLY*/ 0x0000)) || (pmode != (_S_IREAD | _S_IWRITE)))
213 {
214 hr = E_OUTOFMEMORY;
215 RexExitOnFailure(hr, "FDI asked for to create a scratch file, which is unusual");
216 }
217
218 // find an empty spot in the fake file table
219 for (i = 0; i < FILETABLESIZE; ++i)
220 {
221 if (!vrgffFileTable[i].fUsed)
222 {
223 break;
224 }
225 }
226
227 // we should never run out of space in the fake file table
228 if (FILETABLESIZE <= i)
229 {
230 hr = E_OUTOFMEMORY;
231 RexExitOnFailure(hr, "File table exceeded");
232 }
233
234 if (0 == lstrcmpA(vpszResource, pszFile))
235 {
236 vrgffFileTable[i].fUsed = TRUE;
237 vrgffFileTable[i].fftType = MEMORY_FILE;
238 vrgffFileTable[i].mfFile.vpStart = static_cast<LPCBYTE>(vpbRes);
239 vrgffFileTable[i].mfFile.uiCurrent = 0;
240 vrgffFileTable[i].mfFile.uiLength = vcbRes;
241 }
242 else // it's a real file
243 {
244 hFile = ::CreateFileA(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
245 if (INVALID_HANDLE_VALUE == hFile)
246 {
247 RexExitWithLastError(hr, "failed to open file: %s", pszFile);
248 }
249
250 vrgffFileTable[i].fUsed = TRUE;
251 vrgffFileTable[i].fftType = NORMAL_FILE;
252 vrgffFileTable[i].hFile = hFile;
253 }
254
255 LExit:
256 if (FAILED(hr))
257 {
258 vhrLastError = hr;
259 }
260
261 return FAILED(hr) ? -1 : i;
262 }
263
264
265 static __callback UINT FAR DIAMONDAPI RexRead(INT_PTR hf, __out_bcount(cb) void FAR *pv, UINT cb)
266 {
267 Assert(vrgffFileTable[hf].fUsed);
268
269 HRESULT hr = S_OK;
270 DWORD cbRead = 0;
271 DWORD cbAvailable = 0;
272
273 if (MEMORY_FILE == vrgffFileTable[hf].fftType)
274 {
275 // ensure that we don't read past the length of the resource
276 cbAvailable = vrgffFileTable[hf].mfFile.uiLength - vrgffFileTable[hf].mfFile.uiCurrent;
277 cbRead = cb < cbAvailable? cb : cbAvailable;
278
279 memcpy(pv, static_cast<const void *>(vrgffFileTable[hf].mfFile.vpStart + vrgffFileTable[hf].mfFile.uiCurrent), cbRead);
280
281 vrgffFileTable[hf].mfFile.uiCurrent += cbRead;
282 }
283 else // NORMAL_FILE
284 {
285 Assert(vrgffFileTable[hf].hFile && vrgffFileTable[hf].hFile != INVALID_HANDLE_VALUE);
286
287 if (!::ReadFile(vrgffFileTable[hf].hFile, pv, cb, &cbRead, NULL))
288 {
289 RexExitWithLastError(hr, "failed to read during cabinet extraction");
290 }
291 }
292
293 LExit:
294 if (FAILED(hr))
295 {
296 vhrLastError = hr;
297 }
298
299 return FAILED(hr) ? -1 : cbRead;
300 }
301
302
303 static __callback UINT FAR DIAMONDAPI RexWrite(INT_PTR hf, __in_bcount(cb) void FAR *pv, UINT cb)
304 {
305 Assert(vrgffFileTable[hf].fUsed);
306 Assert(vrgffFileTable[hf].fftType == NORMAL_FILE); // we should never be writing to a memory file
307
308 HRESULT hr = S_OK;
309 DWORD cbWrite = 0;
310
311 Assert(vrgffFileTable[hf].hFile && vrgffFileTable[hf].hFile != INVALID_HANDLE_VALUE);
312 if (!::WriteFile(reinterpret_cast<HANDLE>(vrgffFileTable[hf].hFile), pv, cb, &cbWrite, NULL))
313 {
314 RexExitWithLastError(hr, "failed to write during cabinet extraction");
315 }
316
317 // call the writer callback if defined
318 if (vpfnWrite)
319 {
320 vpfnWrite(cb);
321 }
322
323 LExit:
324 if (FAILED(hr))
325 {
326 vhrLastError = hr;
327 }
328
329 return FAILED(hr) ? -1 : cbWrite;
330 }
331
332
333 static __callback long FAR DIAMONDAPI RexSeek(INT_PTR hf, long dist, int seektype)
334 {
335 Assert(vrgffFileTable[hf].fUsed);
336
337 HRESULT hr = S_OK;
338 DWORD dwMoveMethod;
339 LONG lMove = 0;
340
341 switch (seektype)
342 {
343 case 0: // SEEK_SET
344 dwMoveMethod = FILE_BEGIN;
345 break;
346 case 1: /// SEEK_CUR
347 dwMoveMethod = FILE_CURRENT;
348 break;
349 case 2: // SEEK_END
350 dwMoveMethod = FILE_END;
351 break;
352 default :
353 dwMoveMethod = 0;
354 hr = E_UNEXPECTED;
355 RexExitOnFailure(hr, "unexpected seektype in FDISeek(): %d", seektype);
356 }
357
358 if (MEMORY_FILE == vrgffFileTable[hf].fftType)
359 {
360 if (FILE_BEGIN == dwMoveMethod)
361 {
362 vrgffFileTable[hf].mfFile.uiCurrent = dist;
363 }
364 else if (FILE_CURRENT == dwMoveMethod)
365 {
366 vrgffFileTable[hf].mfFile.uiCurrent += dist;
367 }
368 else // FILE_END
369 {
370 vrgffFileTable[hf].mfFile.uiCurrent = vrgffFileTable[hf].mfFile.uiLength + dist;
371 }
372
373 lMove = vrgffFileTable[hf].mfFile.uiCurrent;
374 }
375 else // NORMAL_FILE
376 {
377 Assert(vrgffFileTable[hf].hFile && vrgffFileTable[hf].hFile != INVALID_HANDLE_VALUE);
378
379 // SetFilePointer returns -1 if it fails (this will cause FDI to quit with an FDIERROR_USER_ABORT error.
380 // (Unless this happens while working on a cabinet, in which case FDI returns FDIERROR_CORRUPT_CABINET)
381 lMove = ::SetFilePointer(vrgffFileTable[hf].hFile, dist, NULL, dwMoveMethod);
382 if (0xFFFFFFFF == lMove)
383 {
384 RexExitWithLastError(hr, "failed to move file pointer %d bytes", dist);
385 }
386 }
387
388 LExit:
389 if (FAILED(hr))
390 {
391 vhrLastError = hr;
392 }
393
394 return FAILED(hr) ? -1 : lMove;
395 }
396
397
398 __callback int FAR DIAMONDAPI RexClose(INT_PTR hf)
399 {
400 Assert(vrgffFileTable[hf].fUsed);
401
402 HRESULT hr = S_OK;
403
404 if (MEMORY_FILE == vrgffFileTable[hf].fftType)
405 {
406 vrgffFileTable[hf].mfFile.vpStart = NULL;
407 vrgffFileTable[hf].mfFile.uiCurrent = 0;
408 vrgffFileTable[hf].mfFile.uiLength = 0;
409 }
410 else
411 {
412 Assert(vrgffFileTable[hf].hFile && vrgffFileTable[hf].hFile != INVALID_HANDLE_VALUE);
413
414 if (!::CloseHandle(vrgffFileTable[hf].hFile))
415 {
416 RexExitWithLastError(hr, "failed to close file during cabinet extraction");
417 }
418
419 vrgffFileTable[hf].hFile = INVALID_HANDLE_VALUE;
420 }
421
422 vrgffFileTable[hf].fUsed = FALSE;
423
424 LExit:
425 if (FAILED(hr))
426 {
427 vhrLastError = hr;
428 }
429
430 return FAILED(hr) ? -1 : 0;
431 }
432
433
434 static __callback INT_PTR DIAMONDAPI RexCallback(FDINOTIFICATIONTYPE iNotification, FDINOTIFICATION *pFDINotify)
435 {
436 Assert(pFDINotify->pv);
437
438 HRESULT hr = S_OK;
439 int ipResult = 0; // result to return on success
440 HANDLE hFile = INVALID_HANDLE_VALUE;
441
442 REX_CALLBACK_STRUCT* prcs = static_cast<REX_CALLBACK_STRUCT*>(pFDINotify->pv);
443 LPCSTR sz = NULL;
444 LPWSTR pwz = NULL;
445 LPWSTR pwzPath = NULL;
446 FILETIME ft = { };
447 int i = 0;
448
449 switch (iNotification)
450 {
451 case fdintCOPY_FILE: // beGIN extracting a resource from cabinet
452 Assert(pFDINotify->psz1 && prcs);
453
454 if (prcs->fStopExtracting)
455 {
456 ExitFunction1(hr = S_FALSE); // no more extracting
457 }
458
459 // convert params to useful variables
460 sz = static_cast<LPCSTR>(pFDINotify->psz1);
461 RexExitOnNull(sz, hr, E_INVALIDARG, "No cabinet file ID given to convert");
462
463 hr = StrAllocStringAnsi(&pwz, sz, 0, CP_ACP);
464 RexExitOnFailure(hr, "failed to convert cabinet file id to unicode: %hs", sz);
465
466 if (prcs->pfnProgress)
467 {
468 hr = prcs->pfnProgress(TRUE, pwz, prcs->pvContext);
469 if (S_OK != hr)
470 {
471 ExitFunction();
472 }
473 }
474
475 if (L'*' == *prcs->pwzExtract || 0 == lstrcmpW(prcs->pwzExtract, pwz))
476 {
477 // get the created date for the resource in the cabinet
478 if (!::DosDateTimeToFileTime(pFDINotify->date, pFDINotify->time, &ft))
479 {
480 RexExitWithLastError(hr, "failed to get time for resource: %ls", pwz);
481 }
482
483 if (L'*' == *prcs->pwzExtract)
484 {
485 hr = PathConcat(prcs->pwzExtractDir, pwz, &pwzPath);
486 RexExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", prcs->pwzExtractDir, pwz);
487 }
488 else
489 {
490 Assert(*prcs->pwzExtractName);
491
492 hr = PathConcat(prcs->pwzExtractDir, prcs->pwzExtractName, &pwzPath);
493 RexExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", prcs->pwzExtractDir, prcs->pwzExtractName);
494 }
495
496 // Quickly chop off the file name part of the path to ensure the path exists
497 // then put the file name back on the path (by putting the first character
498 // back over the null terminator).
499 LPWSTR wzFile = PathFile(pwzPath);
500 WCHAR wzFileFirstChar = *wzFile;
501 *wzFile = L'\0';
502
503 hr = DirEnsureExists(pwzPath, NULL);
504 RexExitOnFailure(hr, "failed to ensure directory: %ls", pwzPath);
505
506 hr = S_OK;
507
508 *wzFile = wzFileFirstChar;
509
510 // find an empty spot in the fake file table
511 for (i = 0; i < FILETABLESIZE; ++i)
512 {
513 if (!vrgffFileTable[i].fUsed)
514 {
515 break;
516 }
517 }
518
519 // we should never run out of space in the fake file table
520 if (FILETABLESIZE <= i)
521 {
522 hr = E_OUTOFMEMORY;
523 RexExitOnFailure(hr, "File table exceeded");
524 }
525
526 // open the file
527 hFile = ::CreateFileW(pwzPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
528 if (INVALID_HANDLE_VALUE == hFile)
529 {
530 RexExitWithLastError(hr, "failed to open file: %ls", pwzPath);
531 }
532
533 vrgffFileTable[i].fUsed = TRUE;
534 vrgffFileTable[i].fftType = NORMAL_FILE;
535 vrgffFileTable[i].hFile = hFile;
536
537 ipResult = i;
538
539 ::SetFileTime(vrgffFileTable[i].hFile, &ft, &ft, &ft); // try to set the file time (who cares if it fails)
540
541 if (::SetFilePointer(vrgffFileTable[i].hFile, pFDINotify->cb, NULL, FILE_BEGIN)) // try to set the end of the file (don't worry if this fails)
542 {
543 if (::SetEndOfFile(vrgffFileTable[i].hFile))
544 {
545 ::SetFilePointer(vrgffFileTable[i].hFile, 0, NULL, FILE_BEGIN); // reset the file pointer
546 }
547 }
548 }
549 else // resource wasn't requested, skip it
550 {
551 hr = S_OK;
552 ipResult = 0;
553 }
554
555 break;
556 case fdintCLOSE_FILE_INFO: // resource extraction complete
557 Assert(pFDINotify->hf && prcs && pFDINotify->psz1);
558
559 // convert params to useful variables
560 sz = static_cast<LPCSTR>(pFDINotify->psz1);
561 RexExitOnNull(sz, hr, E_INVALIDARG, "No cabinet file ID given to convert");
562
563 hr = StrAllocStringAnsi(&pwz, sz, 0, CP_ACP);
564 RexExitOnFailure(hr, "failed to convert cabinet file id to unicode: %hs", sz);
565
566 RexClose(pFDINotify->hf);
567
568 if (prcs->pfnProgress)
569 {
570 hr = prcs->pfnProgress(FALSE, pwz, prcs->pvContext);
571 }
572
573 if (S_OK == hr && L'*' == *prcs->pwzExtract) // if everything is okay and we're extracting all files, keep going
574 {
575 ipResult = TRUE;
576 }
577 else // something went wrong or we only needed to extract one file
578 {
579 hr = S_OK;
580 ipResult = FALSE;
581 prcs->fStopExtracting = TRUE;
582 }
583
584 break;
585 case fdintPARTIAL_FILE: __fallthrough; // no action needed for these messages, fall through
586 case fdintNEXT_CABINET: __fallthrough;
587 case fdintENUMERATE: __fallthrough;
588 case fdintCABINET_INFO:
589 break;
590 default:
591 AssertSz(FALSE, "RexCallback() - unknown FDI notification command");
592 };
593
594 LExit:
595 if (FAILED(hr))
596 {
597 vhrLastError = hr;
598 }
599
600 ReleaseStr(pwz);
601 ReleaseStr(pwzPath);
602
603 return (S_OK == hr) ? ipResult : -1;
604 }