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