VirtualBox

source: vbox/trunk/src/VBox/Installer/win/Stub/VBoxStub.cpp@ 47363

Last change on this file since 47363 was 45332, checked in by vboxsync, 12 years ago

VBoxStub.cpp: Don't delete the extraction directory recursively since the user can point this to C:\Windows or worse places via --path. Converted everything to use unicode APIs (won't allow non-koreans to use hangul paths for the actual installation since cabinet.dll doesn't speak unicode).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.4 KB
Line 
1/* $Id: VBoxStub.cpp 45332 2013-04-04 09:43:25Z vboxsync $ */
2/** @file
3 * VBoxStub - VirtualBox's Windows installer stub.
4 */
5
6/*
7 * Copyright (C) 2010-2013 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/*******************************************************************************
19* Header Files *
20*******************************************************************************/
21#include <Windows.h>
22#include <commctrl.h>
23#include <lmerr.h>
24#include <msiquery.h>
25#include <objbase.h>
26
27#include <shlobj.h>
28#include <stdlib.h>
29#include <stdio.h>
30#include <string.h>
31#include <strsafe.h>
32
33#include <VBox/version.h>
34
35#include <iprt/assert.h>
36#include <iprt/dir.h>
37#include <iprt/file.h>
38#include <iprt/getopt.h>
39#include <iprt/initterm.h>
40#include <iprt/list.h>
41#include <iprt/mem.h>
42#include <iprt/message.h>
43#include <iprt/param.h>
44#include <iprt/path.h>
45#include <iprt/stream.h>
46#include <iprt/string.h>
47#include <iprt/thread.h>
48
49#include "VBoxStub.h"
50#include "../StubBld/VBoxStubBld.h"
51#include "resource.h"
52
53#ifdef VBOX_WITH_CODE_SIGNING
54# include "VBoxStubCertUtil.h"
55# include "VBoxStubPublicCert.h"
56#endif
57
58
59/*******************************************************************************
60* Defined Constants And Macros *
61*******************************************************************************/
62#define MY_UNICODE_SUB(str) L ##str
63#define MY_UNICODE(str) MY_UNICODE_SUB(str)
64
65
66/*******************************************************************************
67* Structures and Typedefs *
68*******************************************************************************/
69/**
70 * Cleanup record.
71 */
72typedef struct STUBCLEANUPREC
73{
74 /** List entry. */
75 RTLISTNODE ListEntry;
76 /** True if file, false if directory. */
77 bool fFile;
78 /** The path to the file or directory to clean up. */
79 char szPath[1];
80} STUBCLEANUPREC;
81/** Pointer to a cleanup record. */
82typedef STUBCLEANUPREC *PSTUBCLEANUPREC;
83
84
85/*******************************************************************************
86* Global Variables *
87*******************************************************************************/
88/** Whether it's a silent or interactive GUI driven install. */
89static bool g_fSilent = false;
90/** List of temporary files. */
91static RTLISTANCHOR g_TmpFiles;
92
93
94
95/**
96 * Shows an error message box with a printf() style formatted string.
97 *
98 * @returns RTEXITCODE_FAILURE
99 * @param pszFmt Printf-style format string to show in the message box body.
100 *
101 */
102static RTEXITCODE ShowError(const char *pszFmt, ...)
103{
104 char *pszMsg;
105 va_list va;
106
107 va_start(va, pszFmt);
108 if (RTStrAPrintfV(&pszMsg, pszFmt, va))
109 {
110 if (g_fSilent)
111 RTMsgError("%s", pszMsg);
112 else
113 {
114 PRTUTF16 pwszMsg;
115 int rc = RTStrToUtf16(pszMsg, &pwszMsg);
116 if (RT_SUCCESS(rc))
117 {
118 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONERROR);
119 RTUtf16Free(pwszMsg);
120 }
121 else
122 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR);
123 }
124 RTStrFree(pszMsg);
125 }
126 else /* Should never happen! */
127 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
128 va_end(va);
129 return RTEXITCODE_FAILURE;
130}
131
132
133/**
134 * Shows a message box with a printf() style formatted string.
135 *
136 * @param uType Type of the message box (see MSDN).
137 * @param pszFmt Printf-style format string to show in the message box body.
138 *
139 */
140static void ShowInfo(const char *pszFmt, ...)
141{
142 char *pszMsg;
143 va_list va;
144 va_start(va, pszFmt);
145 int rc = RTStrAPrintfV(&pszMsg, pszFmt, va);
146 va_end(va);
147 if (rc >= 0)
148 {
149 if (g_fSilent)
150 RTPrintf("%s\n", pszMsg);
151 else
152 {
153 PRTUTF16 pwszMsg;
154 int rc = RTStrToUtf16(pszMsg, &pwszMsg);
155 if (RT_SUCCESS(rc))
156 {
157 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONINFORMATION);
158 RTUtf16Free(pwszMsg);
159 }
160 else
161 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION);
162 }
163 }
164 else /* Should never happen! */
165 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
166 RTStrFree(pszMsg);
167}
168
169
170/**
171 * Finds the specified in the resource section of the executable.
172 *
173 * @returns IPRT status code.
174 *
175 * @param pszDataName Name of resource to read.
176 * @param ppvResource Where to return the pointer to the data.
177 * @param pdwSize Where to return the size of the data (if found).
178 * Optional.
179 */
180static int FindData(const char *pszDataName, PVOID *ppvResource, DWORD *pdwSize)
181{
182 AssertReturn(pszDataName, VERR_INVALID_PARAMETER);
183 HINSTANCE hInst = NULL; /* indicates the executable image */
184
185 /* Find our resource. */
186 PRTUTF16 pwszDataName;
187 int rc = RTStrToUtf16(pszDataName, &pwszDataName);
188 AssertRCReturn(rc, rc);
189 HRSRC hRsrc = FindResourceExW(hInst,
190 (LPWSTR)RT_RCDATA,
191 pwszDataName,
192 MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
193 RTUtf16Free(pwszDataName);
194 AssertReturn(hRsrc, VERR_IO_GEN_FAILURE);
195
196 /* Get resource size. */
197 DWORD cb = SizeofResource(hInst, hRsrc);
198 AssertReturn(cb > 0, VERR_NO_DATA);
199 if (pdwSize)
200 *pdwSize = cb;
201
202 /* Get pointer to resource. */
203 HGLOBAL hData = LoadResource(hInst, hRsrc);
204 AssertReturn(hData, VERR_IO_GEN_FAILURE);
205
206 /* Lock resource. */
207 *ppvResource = LockResource(hData);
208 AssertReturn(*ppvResource, VERR_IO_GEN_FAILURE);
209 return VINF_SUCCESS;
210}
211
212
213/**
214 * Finds the header for the given package.
215 *
216 * @returns Pointer to the package header on success. On failure NULL is
217 * returned after ShowError has been invoked.
218 * @param iPackage The package number.
219 */
220static PVBOXSTUBPKG FindPackageHeader(unsigned iPackage)
221{
222 char szHeaderName[32];
223 RTStrPrintf(szHeaderName, sizeof(szHeaderName), "HDR_%02d", iPackage);
224
225 PVBOXSTUBPKG pPackage;
226 int rc = FindData(szHeaderName, (PVOID *)&pPackage, NULL);
227 if (RT_FAILURE(rc))
228 {
229 ShowError("Internal error: Could not find package header #%u: %Rrc", iPackage, rc);
230 return NULL;
231 }
232
233 /** @todo validate it. */
234 return pPackage;
235}
236
237
238
239/**
240 * Constructs a full temporary file path from the given parameters.
241 *
242 * @returns iprt status code.
243 *
244 * @param pszTempPath The pure path to use for construction.
245 * @param pszTargetFileName The pure file name to use for construction.
246 * @param ppszTempFile Pointer to the constructed string. Must be freed
247 * using RTStrFree().
248 */
249static int GetTempFileAlloc(const char *pszTempPath,
250 const char *pszTargetFileName,
251 char **ppszTempFile)
252{
253 if (RTStrAPrintf(ppszTempFile, "%s\\%s", pszTempPath, pszTargetFileName) >= 0)
254 return VINF_SUCCESS;
255 return VERR_NO_STR_MEMORY;
256}
257
258
259/**
260 * Extracts a built-in resource to disk.
261 *
262 * @returns iprt status code.
263 *
264 * @param pszResourceName The resource name to extract.
265 * @param pszTempFile The full file path + name to extract the resource to.
266 *
267 */
268static int ExtractFile(const char *pszResourceName,
269 const char *pszTempFile)
270{
271 int rc;
272 RTFILE fh;
273 BOOL bCreatedFile = FALSE;
274
275 do
276 {
277 AssertMsgBreak(pszResourceName, ("Resource pointer invalid!\n"));
278 AssertMsgBreak(pszTempFile, ("Temp file pointer invalid!"));
279
280 /* Read the data of the built-in resource. */
281 PVOID pvData = NULL;
282 DWORD dwDataSize = 0;
283 rc = FindData(pszResourceName, &pvData, &dwDataSize);
284 AssertMsgRCBreak(rc, ("Could not read resource data!\n"));
285
286 /* Create new (and replace an old) file. */
287 rc = RTFileOpen(&fh, pszTempFile,
288 RTFILE_O_CREATE_REPLACE
289 | RTFILE_O_WRITE
290 | RTFILE_O_DENY_NOT_DELETE
291 | RTFILE_O_DENY_WRITE);
292 AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
293 bCreatedFile = TRUE;
294
295 /* Write contents to new file. */
296 size_t cbWritten = 0;
297 rc = RTFileWrite(fh, pvData, dwDataSize, &cbWritten);
298 AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
299 AssertMsgBreak(dwDataSize == cbWritten, ("File was not extracted completely! Disk full?\n"));
300
301 } while (0);
302
303 if (RTFileIsValid(fh))
304 RTFileClose(fh);
305
306 if (RT_FAILURE(rc))
307 {
308 if (bCreatedFile)
309 RTFileDelete(pszTempFile);
310 }
311 return rc;
312}
313
314
315/**
316 * Extracts a built-in resource to disk.
317 *
318 * @returns iprt status code.
319 *
320 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
321 * @param pszTempFile The full file path + name to extract the resource to.
322 *
323 */
324static int Extract(const PVBOXSTUBPKG pPackage,
325 const char *pszTempFile)
326{
327 return ExtractFile(pPackage->szResourceName,
328 pszTempFile);
329}
330
331
332/**
333 * Detects whether we're running on a 32- or 64-bit platform and returns the result.
334 *
335 * @returns TRUE if we're running on a 64-bit OS, FALSE if not.
336 *
337 */
338static BOOL IsWow64(void)
339{
340 BOOL bIsWow64 = TRUE;
341 fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
342 if (NULL != fnIsWow64Process)
343 {
344 if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64))
345 {
346 /* Error in retrieving process type - assume that we're running on 32bit. */
347 return FALSE;
348 }
349 }
350 return bIsWow64;
351}
352
353
354/**
355 * Decides whether we need a specified package to handle or not.
356 *
357 * @returns @c true if we need to handle the specified package, @c false if not.
358 *
359 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
360 *
361 */
362static bool PackageIsNeeded(PVBOXSTUBPKG pPackage)
363{
364 if (pPackage->byArch == VBOXSTUBPKGARCH_ALL)
365 return true;
366 VBOXSTUBPKGARCH enmArch = IsWow64() ? VBOXSTUBPKGARCH_AMD64 : VBOXSTUBPKGARCH_X86;
367 return pPackage->byArch == enmArch;
368}
369
370
371/**
372 * Adds a cleanup record.
373 *
374 * @returns Fully complained boolean success indicator.
375 * @param pszPath The path to the file or directory to clean up.
376 * @param fFile @c true if file, @c false if directory.
377 */
378static bool AddCleanupRec(const char *pszPath, bool fFile)
379{
380 size_t cchPath = strlen(pszPath); Assert(cchPath > 0);
381 PSTUBCLEANUPREC pRec = (PSTUBCLEANUPREC)RTMemAlloc(RT_OFFSETOF(STUBCLEANUPREC, szPath[cchPath + 1]));
382 if (!pRec)
383 {
384 ShowError("Out of memory!");
385 return false;
386 }
387 pRec->fFile = fFile;
388 memcpy(pRec->szPath, pszPath, cchPath + 1);
389
390 RTListPrepend(&g_TmpFiles, &pRec->ListEntry);
391 return true;
392}
393
394
395/**
396 * Cleans up all the extracted files and optionally removes the package
397 * directory.
398 *
399 * @param cPackages The number of packages.
400 * @param pszPkgDir The package directory, NULL if it shouldn't be
401 * removed.
402 */
403static void CleanUp(unsigned cPackages, const char *pszPkgDir)
404{
405 for (int i = 0; i < 5; i++)
406 {
407 int rc;
408 bool fFinalTry = i == 4;
409
410 PSTUBCLEANUPREC pCur, pNext;
411 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
412 {
413 if (pCur->fFile)
414 rc = RTFileDelete(pCur->szPath);
415 else
416 {
417 rc = RTDirRemoveRecursive(pCur->szPath, RTDIRRMREC_F_CONTENT_AND_DIR);
418 if (rc == VERR_DIR_NOT_EMPTY && fFinalTry)
419 rc = VINF_SUCCESS;
420 }
421 if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
422 rc = VINF_SUCCESS;
423 if (RT_SUCCESS(rc))
424 {
425 RTListNodeRemove(&pCur->ListEntry);
426 RTMemFree(pCur);
427 }
428 else if (fFinalTry)
429 {
430 if (pCur->fFile)
431 ShowError("Failed to delete temporary file '%s': %Rrc", pCur->szPath, rc);
432 else
433 ShowError("Failed to delete temporary directory '%s': %Rrc", pCur->szPath, rc);
434 }
435 }
436
437 if (RTListIsEmpty(&g_TmpFiles) || fFinalTry)
438 {
439 if (!pszPkgDir)
440 return;
441 rc = RTDirRemove(pszPkgDir);
442 if (RT_SUCCESS(rc) || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND || fFinalTry)
443 return;
444 }
445
446 /* Delay a little and try again. */
447 RTThreadSleep(i == 0 ? 100 : 3000);
448 }
449}
450
451
452/**
453 * Processes an MSI package.
454 *
455 * @returns Fully complained exit code.
456 * @param pszMsi The path to the MSI to process.
457 * @param pszMsiArgs Any additional installer (MSI) argument
458 * @param fLogging Whether to enable installer logging.
459 */
460static RTEXITCODE ProcessMsiPackage(const char *pszMsi, const char *pszMsiArgs, bool fLogging)
461{
462 int rc;
463
464 /*
465 * Set UI level.
466 */
467 INSTALLUILEVEL enmDesiredUiLevel = g_fSilent ? INSTALLUILEVEL_NONE : INSTALLUILEVEL_FULL;
468 INSTALLUILEVEL enmRet = MsiSetInternalUI(enmDesiredUiLevel, NULL);
469 if (enmRet == INSTALLUILEVEL_NOCHANGE /* means error */)
470 return ShowError("Internal error: MsiSetInternalUI failed.");
471
472 /*
473 * Enable logging?
474 */
475 if (fLogging)
476 {
477 char szLogFile[RTPATH_MAX];
478 rc = RTStrCopy(szLogFile, sizeof(szLogFile), pszMsi);
479 if (RT_SUCCESS(rc))
480 {
481 RTPathStripFilename(szLogFile);
482 rc = RTPathAppend(szLogFile, sizeof(szLogFile), "VBoxInstallLog.txt");
483 }
484 if (RT_FAILURE(rc))
485 return ShowError("Internal error: Filename path too long.");
486
487 PRTUTF16 pwszLogFile;
488 rc = RTStrToUtf16(szLogFile, &pwszLogFile);
489 if (RT_FAILURE(rc))
490 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", szLogFile, rc);
491
492 UINT uLogLevel = MsiEnableLogW(INSTALLLOGMODE_VERBOSE,
493 pwszLogFile,
494 INSTALLLOGATTRIBUTES_FLUSHEACHLINE);
495 RTUtf16Free(pwszLogFile);
496 if (uLogLevel != ERROR_SUCCESS)
497 return ShowError("MsiEnableLogW failed");
498 }
499
500 /*
501 * Initialize the common controls (extended version). This is necessary to
502 * run the actual .MSI installers with the new fancy visual control
503 * styles (XP+). Also, an integrated manifest is required.
504 */
505 INITCOMMONCONTROLSEX ccEx;
506 ccEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
507 ccEx.dwICC = ICC_LINK_CLASS | ICC_LISTVIEW_CLASSES | ICC_PAGESCROLLER_CLASS |
508 ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES |
509 ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES | ICC_WIN95_CLASSES;
510 InitCommonControlsEx(&ccEx); /* Ignore failure. */
511
512 /*
513 * Convert both strings to UTF-16 and start the installation.
514 */
515 PRTUTF16 pwszMsi;
516 rc = RTStrToUtf16(pszMsi, &pwszMsi);
517 if (RT_FAILURE(rc))
518 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
519 PRTUTF16 pwszMsiArgs;
520 rc = RTStrToUtf16(pszMsiArgs, &pwszMsiArgs);
521 if (RT_FAILURE(rc))
522 {
523 RTUtf16Free(pwszMsi);
524 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
525 }
526
527 UINT uStatus = MsiInstallProductW(pwszMsi, pwszMsiArgs);
528 RTUtf16Free(pwszMsi);
529 RTUtf16Free(pwszMsiArgs);
530
531 if (uStatus == ERROR_SUCCESS)
532 return RTEXITCODE_SUCCESS;
533 if (uStatus == ERROR_SUCCESS_REBOOT_REQUIRED)
534 return RTEXITCODE_SUCCESS; /* we currently don't indicate this */
535
536 /*
537 * Installation failed. Figure out what to say.
538 */
539 switch (uStatus)
540 {
541 case ERROR_INSTALL_USEREXIT:
542 /* Don't say anything? */
543 break;
544
545 case ERROR_INSTALL_PACKAGE_VERSION:
546 ShowError("This installation package cannot be installed by the Windows Installer service.\n"
547 "You must install a Windows service pack that contains a newer version of the Windows Installer service.");
548 break;
549
550 case ERROR_INSTALL_PLATFORM_UNSUPPORTED:
551 ShowError("This installation package is not supported on this platform.");
552 break;
553
554 default:
555 {
556 /*
557 * Try get windows to format the message.
558 */
559 DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER
560 | FORMAT_MESSAGE_IGNORE_INSERTS
561 | FORMAT_MESSAGE_FROM_SYSTEM;
562 HMODULE hModule = NULL;
563 if (uStatus >= NERR_BASE && uStatus <= MAX_NERR)
564 {
565 hModule = LoadLibraryExW(L"netmsg.dll",
566 NULL,
567 LOAD_LIBRARY_AS_DATAFILE);
568 if (hModule != NULL)
569 dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
570 }
571
572 PWSTR pwszMsg;
573 if (FormatMessageW(dwFormatFlags,
574 hModule, /* If NULL, load system stuff. */
575 uStatus,
576 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
577 (PWSTR)&pwszMsg,
578 0,
579 NULL) > 0)
580 {
581 ShowError("Installation failed! Error: %ls", pwszMsg);
582 LocalFree(pwszMsg);
583 }
584 else /* If text lookup failed, show at least the error number. */
585 ShowError("Installation failed! Error: %u", uStatus);
586
587 if (hModule)
588 FreeLibrary(hModule);
589 break;
590 }
591 }
592
593 return RTEXITCODE_FAILURE;
594}
595
596
597/**
598 * Processes a package.
599 *
600 * @returns Fully complained exit code.
601 * @param iPackage The package number.
602 * @param pszPkgDir The package directory (aka extraction dir).
603 * @param pszMsiArgs Any additional installer (MSI) argument
604 * @param fLogging Whether to enable installer logging.
605 */
606static RTEXITCODE ProcessPackage(unsigned iPackage, const char *pszPkgDir, const char *pszMsiArgs, bool fLogging)
607{
608 /*
609 * Get the package header and check if it's needed.
610 */
611 PVBOXSTUBPKG pPackage = FindPackageHeader(iPackage);
612 if (pPackage == NULL)
613 return RTEXITCODE_FAILURE;
614
615 if (!PackageIsNeeded(pPackage))
616 return RTEXITCODE_SUCCESS;
617
618 /*
619 * Deal with the file based on it's extension.
620 */
621 char szPkgFile[RTPATH_MAX];
622 int rc = RTPathJoin(szPkgFile, sizeof(szPkgFile), pszPkgDir, pPackage->szFileName);
623 if (RT_FAILURE(rc))
624 return ShowError("Internal error: RTPathJoin failed: %Rrc", rc);
625 RTPathChangeToDosSlashes(szPkgFile, true /* Force conversion. */); /* paranoia */
626
627 RTEXITCODE rcExit;
628 const char *pszExt = RTPathExt(szPkgFile);
629 if (RTStrICmp(pszExt, ".msi") == 0)
630 rcExit = ProcessMsiPackage(szPkgFile, pszMsiArgs, fLogging);
631 else if (RTStrICmp(pszExt, ".cab") == 0)
632 rcExit = RTEXITCODE_SUCCESS; /* Ignore .cab files, they're generally referenced by other files. */
633 else
634 rcExit = ShowError("Internal error: Do not know how to handle file '%s'.", pPackage->szFileName);
635
636 return rcExit;
637}
638
639
640#ifdef VBOX_WITH_CODE_SIGNING
641/**
642 * Install the public certificate into TrustedPublishers so the installer won't
643 * prompt the user during silent installs.
644 *
645 * @returns Fully complained exit code.
646 */
647static RTEXITCODE InstallCertificate(void)
648{
649 if (addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE,
650 "TrustedPublisher",
651 g_ab_VBoxStubPublicCert,
652 sizeof(g_ab_VBoxStubPublicCert)))
653 return RTEXITCODE_SUCCESS;
654 return ShowError("Failed to construct install certificate.");
655}
656#endif /* VBOX_WITH_CODE_SIGNING */
657
658
659/**
660 * Copies the "<exepath>.custom" directory to the extraction path if it exists.
661 *
662 * This is used by the MSI packages from the resource section.
663 *
664 * @returns Fully complained exit code.
665 * @param pszDstDir The destination directory.
666 */
667static RTEXITCODE CopyCustomDir(const char *pszDstDir)
668{
669 char szSrcDir[RTPATH_MAX];
670 int rc = RTPathExecDir(szSrcDir, sizeof(szSrcDir));
671 if (RT_SUCCESS(rc))
672 rc = RTPathAppend(szSrcDir, sizeof(szSrcDir), ".custom");
673 if (RT_FAILURE(rc))
674 return ShowError("Failed to construct '.custom' dir path: %Rrc", rc);
675
676 if (RTDirExists(szSrcDir))
677 {
678 /*
679 * Use SHFileOperation w/ FO_COPY to do the job. This API requires an
680 * extra zero at the end of both source and destination paths.
681 */
682 size_t cwc;
683 RTUTF16 wszSrcDir[RTPATH_MAX + 1];
684 PRTUTF16 pwszSrcDir = wszSrcDir;
685 rc = RTStrToUtf16Ex(szSrcDir, RTSTR_MAX, &pwszSrcDir, RTPATH_MAX, &cwc);
686 if (RT_FAILURE(rc))
687 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", szSrcDir, rc);
688 wszSrcDir[cwc] = '\0';
689
690 RTUTF16 wszDstDir[RTPATH_MAX + 1];
691 PRTUTF16 pwszDstDir = wszSrcDir;
692 rc = RTStrToUtf16Ex(pszDstDir, RTSTR_MAX, &pwszDstDir, RTPATH_MAX, &cwc);
693 if (RT_FAILURE(rc))
694 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", pszDstDir, rc);
695 wszDstDir[cwc] = '\0';
696
697 SHFILEOPSTRUCTW FileOp;
698 RT_ZERO(FileOp); /* paranoia */
699 FileOp.hwnd = NULL;
700 FileOp.wFunc = FO_COPY;
701 FileOp.pFrom = wszSrcDir;
702 FileOp.pTo = wszDstDir;
703 FileOp.fFlags = FOF_SILENT
704 | FOF_NOCONFIRMATION
705 | FOF_NOCONFIRMMKDIR
706 | FOF_NOERRORUI;
707 FileOp.fAnyOperationsAborted = FALSE;
708 FileOp.hNameMappings = NULL;
709 FileOp.lpszProgressTitle = NULL;
710
711 rc = SHFileOperationW(&FileOp);
712 if (rc != 0) /* Not a Win32 status code! */
713 return ShowError("Copying the '.custom' dir failed: %#x", rc);
714
715 /*
716 * Add a cleanup record for recursively deleting the destination
717 * .custom directory. We should actually add this prior to calling
718 * SHFileOperationW since it may partially succeed...
719 */
720 char *pszDstSubDir = RTPathJoinA(pszDstDir, ".custom");
721 if (!pszDstSubDir)
722 return ShowError("Out of memory!");
723 bool fRc = AddCleanupRec(pszDstSubDir, false /*fFile*/);
724 RTStrFree(pszDstSubDir);
725 if (!fRc)
726 return RTEXITCODE_FAILURE;
727 }
728
729 return RTEXITCODE_SUCCESS;
730}
731
732
733static RTEXITCODE ExtractFiles(unsigned cPackages, const char *pszDstDir, bool fExtractOnly, bool *pfCreatedExtractDir)
734{
735 int rc;
736
737 /*
738 * Make sure the directory exists.
739 */
740 *pfCreatedExtractDir = false;
741 if (!RTDirExists(pszDstDir))
742 {
743 rc = RTDirCreate(pszDstDir, 0700, 0);
744 if (RT_FAILURE(rc))
745 return ShowError("Failed to create extraction path '%s': %Rrc", pszDstDir, rc);
746 *pfCreatedExtractDir = true;
747 }
748
749 /*
750 * Extract files.
751 */
752 for (unsigned k = 0; k < cPackages; k++)
753 {
754 PVBOXSTUBPKG pPackage = FindPackageHeader(k);
755 if (!pPackage)
756 return RTEXITCODE_FAILURE; /* Done complaining already. */
757
758 if (fExtractOnly || PackageIsNeeded(pPackage))
759 {
760 char szDstFile[RTPATH_MAX];
761 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, pPackage->szFileName);
762 if (RT_FAILURE(rc))
763 return ShowError("Internal error: RTPathJoin failed: %Rrc", rc);
764
765 rc = Extract(pPackage, szDstFile);
766 if (RT_FAILURE(rc))
767 return ShowError("Error extracting package #%u: %Rrc", k, rc);
768
769 if (!fExtractOnly && !AddCleanupRec(szDstFile, true /*fFile*/))
770 {
771 RTFileDelete(szDstFile);
772 return RTEXITCODE_FAILURE;
773 }
774 }
775 }
776
777 return RTEXITCODE_SUCCESS;
778}
779
780
781int WINAPI WinMain(HINSTANCE hInstance,
782 HINSTANCE hPrevInstance,
783 char *lpCmdLine,
784 int nCmdShow)
785{
786 char **argv = __argv;
787 int argc = __argc;
788
789 /* Check if we're already running and jump out if so. */
790 /* Do not use a global namespace ("Global\\") for mutex name here, will blow up NT4 compatibility! */
791 HANDLE hMutexAppRunning = CreateMutex(NULL, FALSE, "VBoxStubInstaller");
792 if ( hMutexAppRunning != NULL
793 && GetLastError() == ERROR_ALREADY_EXISTS)
794 {
795 /* Close the mutex for this application instance. */
796 CloseHandle(hMutexAppRunning);
797 hMutexAppRunning = NULL;
798 return RTEXITCODE_FAILURE;
799 }
800
801 /* Init IPRT. */
802 int vrc = RTR3InitExe(argc, &argv, 0);
803 if (RT_FAILURE(vrc))
804 return RTMsgInitFailure(vrc);
805
806 /*
807 * Parse arguments.
808 */
809
810 /* Parameter variables. */
811 bool fExtractOnly = false;
812 bool fEnableLogging = false;
813#ifdef VBOX_WITH_CODE_SIGNING
814 bool fEnableSilentCert = true;
815#endif
816 char szExtractPath[RTPATH_MAX] = {0};
817 char szMSIArgs[4096] = {0};
818
819 /* Parameter definitions. */
820 static const RTGETOPTDEF s_aOptions[] =
821 {
822 { "--extract", 'x', RTGETOPT_REQ_NOTHING },
823 { "-extract", 'x', RTGETOPT_REQ_NOTHING },
824 { "/extract", 'x', RTGETOPT_REQ_NOTHING },
825 { "--silent", 's', RTGETOPT_REQ_NOTHING },
826 { "-silent", 's', RTGETOPT_REQ_NOTHING },
827 { "/silent", 's', RTGETOPT_REQ_NOTHING },
828#ifdef VBOX_WITH_CODE_SIGNING
829 { "--no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
830 { "-no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
831 { "/no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
832#endif
833 { "--logging", 'l', RTGETOPT_REQ_NOTHING },
834 { "-logging", 'l', RTGETOPT_REQ_NOTHING },
835 { "/logging", 'l', RTGETOPT_REQ_NOTHING },
836 { "--path", 'p', RTGETOPT_REQ_STRING },
837 { "-path", 'p', RTGETOPT_REQ_STRING },
838 { "/path", 'p', RTGETOPT_REQ_STRING },
839 { "--msiparams", 'm', RTGETOPT_REQ_STRING },
840 { "-msiparams", 'm', RTGETOPT_REQ_STRING },
841 { "--version", 'V', RTGETOPT_REQ_NOTHING },
842 { "-version", 'V', RTGETOPT_REQ_NOTHING },
843 { "/version", 'V', RTGETOPT_REQ_NOTHING },
844 { "-v", 'V', RTGETOPT_REQ_NOTHING },
845 { "--help", 'h', RTGETOPT_REQ_NOTHING },
846 { "-help", 'h', RTGETOPT_REQ_NOTHING },
847 { "/help", 'h', RTGETOPT_REQ_NOTHING },
848 { "/?", 'h', RTGETOPT_REQ_NOTHING },
849 };
850
851 /* Parse the parameters. */
852 int ch;
853 RTGETOPTUNION ValueUnion;
854 RTGETOPTSTATE GetState;
855 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0);
856 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
857 {
858 switch (ch)
859 {
860 case 'x':
861 fExtractOnly = true;
862 break;
863
864 case 's':
865 g_fSilent = true;
866 break;
867
868#ifdef VBOX_WITH_CODE_SIGNING
869 case 'c':
870 fEnableSilentCert = false;
871 break;
872#endif
873
874 case 'l':
875 fEnableLogging = true;
876 break;
877
878 case 'p':
879 vrc = RTStrCopy(szExtractPath, sizeof(szExtractPath), ValueUnion.psz);
880 if (RT_FAILURE(vrc))
881 return ShowError("Extraction path is too long.");
882 break;
883
884 case 'm':
885 if (szMSIArgs[0])
886 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
887 if (RT_SUCCESS(vrc))
888 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
889 if (RT_FAILURE(vrc))
890 return ShowError("MSI parameters are too long.");
891 break;
892
893 case 'V':
894 ShowInfo("Version: %d.%d.%d.%d",
895 VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV);
896 return VINF_SUCCESS;
897
898 case 'h':
899 ShowInfo("-- %s v%d.%d.%d.%d --\n"
900 "\n"
901 "Command Line Parameters:\n\n"
902 "--extract - Extract file contents to temporary directory\n"
903 "--silent - Enables silent mode installation\n"
904 "--no-silent-cert - Do not install VirtualBox Certificate automatically when --silent option is specified\n"
905 "--path - Sets the path of the extraction directory\n"
906 "--msiparams <parameters> - Specifies extra parameters for the MSI installers\n"
907 "--logging - Enables installer logging\n"
908 "--help - Print this help and exit\n"
909 "--version - Print version number and exit\n\n"
910 "Examples:\n"
911 "%s --msiparams INSTALLDIR=C:\\VBox\n"
912 "%s --extract -path C:\\VBox",
913 VBOX_STUB_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
914 argv[0], argv[0]);
915 return VINF_SUCCESS;
916
917 default:
918 if (g_fSilent)
919 return RTGetOptPrintError(ch, &ValueUnion);
920 if (ch == VINF_GETOPT_NOT_OPTION || ch == VERR_GETOPT_UNKNOWN_OPTION)
921 ShowError("Unknown option \"%s\"!\n"
922 "Please refer to the command line help by specifying \"/?\"\n"
923 "to get more information.", ValueUnion.psz);
924 else
925 ShowError("Parameter parsing error: %Rrc\n"
926 "Please refer to the command line help by specifying \"/?\"\n"
927 "to get more information.", ch);
928 return RTEXITCODE_SYNTAX;
929
930 }
931 }
932
933 /*
934 * Determine the extration path if not given by the user, and gather some
935 * other bits we'll be needing later.
936 */
937 if (szExtractPath[0] == '\0')
938 {
939 vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath));
940 if (RT_SUCCESS(vrc))
941 vrc = RTPathAppend(szExtractPath, sizeof(szExtractPath), "VirtualBox");
942 if (RT_FAILURE(vrc))
943 return ShowError("Failed to determin extraction path (%Rrc)", vrc);
944
945 }
946 else
947 {
948 /** @todo should check if there is a .custom subdirectory there or not. */
949 }
950 RTPathChangeToDosSlashes(szExtractPath, true /* Force conversion. */); /* MSI requirement. */
951
952 /* Read our manifest. */
953 PVBOXSTUBPKGHEADER pHeader;
954 vrc = FindData("MANIFEST", (PVOID *)&pHeader, NULL);
955 if (RT_FAILURE(vrc))
956 return ShowError("Internal package error: Manifest not found (%Rrc)", vrc);
957 /** @todo If we could, we should validate the header. Only the magic isn't
958 * commonly defined, nor the version number... */
959
960 RTListInit(&g_TmpFiles);
961
962 /*
963 * Up to this point, we haven't done anything that requires any cleanup.
964 * From here on, we do everything in function so we can counter clean up.
965 */
966 bool fCreatedExtractDir;
967 RTEXITCODE rcExit = ExtractFiles(pHeader->byCntPkgs, szExtractPath, fExtractOnly, &fCreatedExtractDir);
968 if (rcExit == RTEXITCODE_SUCCESS)
969 {
970 if (fExtractOnly)
971 ShowInfo("Files were extracted to: %s", szExtractPath);
972 else
973 {
974 rcExit = CopyCustomDir(szExtractPath);
975#ifdef VBOX_WITH_CODE_SIGNING
976 if (rcExit == RTEXITCODE_SUCCESS && fEnableSilentCert && g_fSilent)
977 rcExit = InstallCertificate();
978#endif
979 unsigned iPackage = 0;
980 while (iPackage < pHeader->byCntPkgs && rcExit == RTEXITCODE_SUCCESS)
981 {
982 rcExit = ProcessPackage(iPackage, szExtractPath, szMSIArgs, fEnableLogging);
983 iPackage++;
984 }
985
986 /* Don't fail if cleanup fail. At least for now. */
987 CleanUp(pHeader->byCntPkgs, !fEnableLogging && fCreatedExtractDir ? szExtractPath : NULL);
988 }
989 }
990
991 /* Free any left behind cleanup records (not strictly needed). */
992 PSTUBCLEANUPREC pCur, pNext;
993 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
994 {
995 RTListNodeRemove(&pCur->ListEntry);
996 RTMemFree(pCur);
997 }
998
999 /*
1000 * Release instance mutex.
1001 */
1002 if (hMutexAppRunning != NULL)
1003 {
1004 CloseHandle(hMutexAppRunning);
1005 hMutexAppRunning = NULL;
1006 }
1007
1008 return rcExit;
1009}
1010
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette