diff options
Diffstat (limited to 'src/VBox/Installer/win/Stub/VBoxStub.cpp')
-rw-r--r-- | src/VBox/Installer/win/Stub/VBoxStub.cpp | 1124 |
1 files changed, 720 insertions, 404 deletions
diff --git a/src/VBox/Installer/win/Stub/VBoxStub.cpp b/src/VBox/Installer/win/Stub/VBoxStub.cpp index 22b9bccb..188bf5e6 100644 --- a/src/VBox/Installer/win/Stub/VBoxStub.cpp +++ b/src/VBox/Installer/win/Stub/VBoxStub.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2010 Oracle Corporation + * Copyright (C) 2010-2013 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -18,11 +18,12 @@ /******************************************************************************* * Header Files * *******************************************************************************/ -#include <windows.h> +#include <Windows.h> #include <commctrl.h> #include <lmerr.h> #include <msiquery.h> #include <objbase.h> + #include <shlobj.h> #include <stdlib.h> #include <stdio.h> @@ -34,10 +35,14 @@ #include <iprt/assert.h> #include <iprt/dir.h> #include <iprt/file.h> +#include <iprt/getopt.h> #include <iprt/initterm.h> +#include <iprt/list.h> #include <iprt/mem.h> -#include <iprt/path.h> +#include <iprt/message.h> #include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/stream.h> #include <iprt/string.h> #include <iprt/thread.h> @@ -45,109 +50,192 @@ #include "../StubBld/VBoxStubBld.h" #include "resource.h" -#ifndef _UNICODE -#define _UNICODE +#ifdef VBOX_WITH_CODE_SIGNING +# include "VBoxStubCertUtil.h" +# include "VBoxStubPublicCert.h" #endif +/******************************************************************************* +* Defined Constants And Macros * +*******************************************************************************/ +#define MY_UNICODE_SUB(str) L ##str +#define MY_UNICODE(str) MY_UNICODE_SUB(str) + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ /** - * Shows a message box with a printf() style formatted string. - * - * @returns Message box result (IDOK, IDCANCEL, ...). - * - * @param uType Type of the message box (see MSDN). - * @param pszFmt Printf-style format string to show in the message box body. - * + * Cleanup record. */ -static int ShowInfo(const char *pszFmt, ...) +typedef struct STUBCLEANUPREC { - char *pszMsg; - va_list va; + /** List entry. */ + RTLISTNODE ListEntry; + /** True if file, false if directory. */ + bool fFile; + /** The path to the file or directory to clean up. */ + char szPath[1]; +} STUBCLEANUPREC; +/** Pointer to a cleanup record. */ +typedef STUBCLEANUPREC *PSTUBCLEANUPREC; - va_start(va, pszFmt); - RTStrAPrintfV(&pszMsg, pszFmt, va); - va_end(va); - int rc; - if (pszMsg) - rc = MessageBox(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION); - else - rc = MessageBox(GetDesktopWindow(), pszFmt, VBOX_STUB_TITLE, MB_ICONINFORMATION); - RTStrFree(pszMsg); - return rc; -} +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +/** Whether it's a silent or interactive GUI driven install. */ +static bool g_fSilent = false; +/** List of temporary files. */ +static RTLISTANCHOR g_TmpFiles; + /** * Shows an error message box with a printf() style formatted string. * - * @returns Message box result (IDOK, IDCANCEL, ...). - * + * @returns RTEXITCODE_FAILURE * @param pszFmt Printf-style format string to show in the message box body. * */ -static int ShowError(const char *pszFmt, ...) +static RTEXITCODE ShowError(const char *pszFmt, ...) { char *pszMsg; va_list va; - int rc; va_start(va, pszFmt); if (RTStrAPrintfV(&pszMsg, pszFmt, va)) { - rc = MessageBox(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR); + if (g_fSilent) + RTMsgError("%s", pszMsg); + else + { + PRTUTF16 pwszMsg; + int rc = RTStrToUtf16(pszMsg, &pwszMsg); + if (RT_SUCCESS(rc)) + { + MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONERROR); + RTUtf16Free(pwszMsg); + } + else + MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR); + } RTStrFree(pszMsg); } else /* Should never happen! */ AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt)); va_end(va); - return rc; + return RTEXITCODE_FAILURE; } /** - * Reads data from a built-in resource. - * - * @returns iprt status code. + * Shows a message box with a printf() style formatted string. * - * @param hInst Instance to read the data from. - * @param pszDataName Name of resource to read. - * @param ppvResource Pointer to buffer which holds the read resource data. - * @param pdwSize Pointer which holds the read data size. + * @param uType Type of the message box (see MSDN). + * @param pszFmt Printf-style format string to show in the message box body. * */ -static int ReadData(HINSTANCE hInst, - const char *pszDataName, - PVOID *ppvResource, - DWORD *pdwSize) +static void ShowInfo(const char *pszFmt, ...) { - do + char *pszMsg; + va_list va; + va_start(va, pszFmt); + int rc = RTStrAPrintfV(&pszMsg, pszFmt, va); + va_end(va); + if (rc >= 0) { - AssertMsgBreak(pszDataName, ("Resource name is empty!\n")); + if (g_fSilent) + RTPrintf("%s\n", pszMsg); + else + { + PRTUTF16 pwszMsg; + int rc = RTStrToUtf16(pszMsg, &pwszMsg); + if (RT_SUCCESS(rc)) + { + MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONINFORMATION); + RTUtf16Free(pwszMsg); + } + else + MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION); + } + } + else /* Should never happen! */ + AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt)); + RTStrFree(pszMsg); +} - /* Find our resource. */ - HRSRC hRsrc = FindResourceEx(hInst, RT_RCDATA, pszDataName, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); - AssertMsgBreak(hRsrc, ("Could not find resource!\n")); - /* Get resource size. */ - *pdwSize = SizeofResource(hInst, hRsrc); - AssertMsgBreak(*pdwSize > 0, ("Size of resource is invalid!\n")); +/** + * Finds the specified in the resource section of the executable. + * + * @returns IPRT status code. + * + * @param pszDataName Name of resource to read. + * @param ppvResource Where to return the pointer to the data. + * @param pdwSize Where to return the size of the data (if found). + * Optional. + */ +static int FindData(const char *pszDataName, PVOID *ppvResource, DWORD *pdwSize) +{ + AssertReturn(pszDataName, VERR_INVALID_PARAMETER); + HINSTANCE hInst = NULL; /* indicates the executable image */ + + /* Find our resource. */ + PRTUTF16 pwszDataName; + int rc = RTStrToUtf16(pszDataName, &pwszDataName); + AssertRCReturn(rc, rc); + HRSRC hRsrc = FindResourceExW(hInst, + (LPWSTR)RT_RCDATA, + pwszDataName, + MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); + RTUtf16Free(pwszDataName); + AssertReturn(hRsrc, VERR_IO_GEN_FAILURE); + + /* Get resource size. */ + DWORD cb = SizeofResource(hInst, hRsrc); + AssertReturn(cb > 0, VERR_NO_DATA); + if (pdwSize) + *pdwSize = cb; + + /* Get pointer to resource. */ + HGLOBAL hData = LoadResource(hInst, hRsrc); + AssertReturn(hData, VERR_IO_GEN_FAILURE); + + /* Lock resource. */ + *ppvResource = LockResource(hData); + AssertReturn(*ppvResource, VERR_IO_GEN_FAILURE); + return VINF_SUCCESS; +} - /* Get pointer to resource. */ - HGLOBAL hData = LoadResource(hInst, hRsrc); - AssertMsgBreak(hData, ("Could not load resource!\n")); - /* Lock resource. */ - *ppvResource = LockResource(hData); - AssertMsgBreak(*ppvResource, ("Could not lock resource!\n")); - return VINF_SUCCESS; +/** + * Finds the header for the given package. + * + * @returns Pointer to the package header on success. On failure NULL is + * returned after ShowError has been invoked. + * @param iPackage The package number. + */ +static PVBOXSTUBPKG FindPackageHeader(unsigned iPackage) +{ + char szHeaderName[32]; + RTStrPrintf(szHeaderName, sizeof(szHeaderName), "HDR_%02d", iPackage); - } while (0); + PVBOXSTUBPKG pPackage; + int rc = FindData(szHeaderName, (PVOID *)&pPackage, NULL); + if (RT_FAILURE(rc)) + { + ShowError("Internal error: Could not find package header #%u: %Rrc", iPackage, rc); + return NULL; + } - return VERR_IO_GEN_FAILURE; + /** @todo validate it. */ + return pPackage; } + /** * Constructs a full temporary file path from the given parameters. * @@ -192,7 +280,7 @@ static int ExtractFile(const char *pszResourceName, /* Read the data of the built-in resource. */ PVOID pvData = NULL; DWORD dwDataSize = 0; - rc = ReadData(NULL, pszResourceName, &pvData, &dwDataSize); + rc = FindData(pszResourceName, &pvData, &dwDataSize); AssertMsgRCBreak(rc, ("Could not read resource data!\n")); /* Create new (and replace an old) file. */ @@ -266,61 +354,427 @@ static BOOL IsWow64(void) /** * Decides whether we need a specified package to handle or not. * - * @returns TRUE if we need to handle the specified package, FALSE if not. + * @returns @c true if we need to handle the specified package, @c false if not. * * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource. * */ -static BOOL PackageIsNeeded(PVBOXSTUBPKG pPackage) +static bool PackageIsNeeded(PVBOXSTUBPKG pPackage) { - BOOL bIsWow64 = IsWow64(); - if ((bIsWow64 && pPackage->byArch == VBOXSTUBPKGARCH_AMD64)) /* 64bit Windows. */ + if (pPackage->byArch == VBOXSTUBPKGARCH_ALL) + return true; + VBOXSTUBPKGARCH enmArch = IsWow64() ? VBOXSTUBPKGARCH_AMD64 : VBOXSTUBPKGARCH_X86; + return pPackage->byArch == enmArch; +} + + +/** + * Adds a cleanup record. + * + * @returns Fully complained boolean success indicator. + * @param pszPath The path to the file or directory to clean up. + * @param fFile @c true if file, @c false if directory. + */ +static bool AddCleanupRec(const char *pszPath, bool fFile) +{ + size_t cchPath = strlen(pszPath); Assert(cchPath > 0); + PSTUBCLEANUPREC pRec = (PSTUBCLEANUPREC)RTMemAlloc(RT_OFFSETOF(STUBCLEANUPREC, szPath[cchPath + 1])); + if (!pRec) { - return TRUE; + ShowError("Out of memory!"); + return false; } - else if ((!bIsWow64 && pPackage->byArch == VBOXSTUBPKGARCH_X86)) /* 32bit. */ + pRec->fFile = fFile; + memcpy(pRec->szPath, pszPath, cchPath + 1); + + RTListPrepend(&g_TmpFiles, &pRec->ListEntry); + return true; +} + + +/** + * Cleans up all the extracted files and optionally removes the package + * directory. + * + * @param cPackages The number of packages. + * @param pszPkgDir The package directory, NULL if it shouldn't be + * removed. + */ +static void CleanUp(unsigned cPackages, const char *pszPkgDir) +{ + for (int i = 0; i < 5; i++) { - return TRUE; + int rc; + bool fFinalTry = i == 4; + + PSTUBCLEANUPREC pCur, pNext; + RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry) + { + if (pCur->fFile) + rc = RTFileDelete(pCur->szPath); + else + { + rc = RTDirRemoveRecursive(pCur->szPath, RTDIRRMREC_F_CONTENT_AND_DIR); + if (rc == VERR_DIR_NOT_EMPTY && fFinalTry) + rc = VINF_SUCCESS; + } + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + rc = VINF_SUCCESS; + if (RT_SUCCESS(rc)) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + } + else if (fFinalTry) + { + if (pCur->fFile) + ShowError("Failed to delete temporary file '%s': %Rrc", pCur->szPath, rc); + else + ShowError("Failed to delete temporary directory '%s': %Rrc", pCur->szPath, rc); + } + } + + if (RTListIsEmpty(&g_TmpFiles) || fFinalTry) + { + if (!pszPkgDir) + return; + rc = RTDirRemove(pszPkgDir); + if (RT_SUCCESS(rc) || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND || fFinalTry) + return; + } + + /* Delay a little and try again. */ + RTThreadSleep(i == 0 ? 100 : 3000); + } +} + + +/** + * Processes an MSI package. + * + * @returns Fully complained exit code. + * @param pszMsi The path to the MSI to process. + * @param pszMsiArgs Any additional installer (MSI) argument + * @param fLogging Whether to enable installer logging. + */ +static RTEXITCODE ProcessMsiPackage(const char *pszMsi, const char *pszMsiArgs, bool fLogging) +{ + int rc; + + /* + * Set UI level. + */ + INSTALLUILEVEL enmDesiredUiLevel = g_fSilent ? INSTALLUILEVEL_NONE : INSTALLUILEVEL_FULL; + INSTALLUILEVEL enmRet = MsiSetInternalUI(enmDesiredUiLevel, NULL); + if (enmRet == INSTALLUILEVEL_NOCHANGE /* means error */) + return ShowError("Internal error: MsiSetInternalUI failed."); + + /* + * Enable logging? + */ + if (fLogging) + { + char szLogFile[RTPATH_MAX]; + rc = RTStrCopy(szLogFile, sizeof(szLogFile), pszMsi); + if (RT_SUCCESS(rc)) + { + RTPathStripFilename(szLogFile); + rc = RTPathAppend(szLogFile, sizeof(szLogFile), "VBoxInstallLog.txt"); + } + if (RT_FAILURE(rc)) + return ShowError("Internal error: Filename path too long."); + + PRTUTF16 pwszLogFile; + rc = RTStrToUtf16(szLogFile, &pwszLogFile); + if (RT_FAILURE(rc)) + return ShowError("RTStrToUtf16 failed on '%s': %Rrc", szLogFile, rc); + + UINT uLogLevel = MsiEnableLogW(INSTALLLOGMODE_VERBOSE, + pwszLogFile, + INSTALLLOGATTRIBUTES_FLUSHEACHLINE); + RTUtf16Free(pwszLogFile); + if (uLogLevel != ERROR_SUCCESS) + return ShowError("MsiEnableLogW failed"); } - else if (pPackage->byArch == VBOXSTUBPKGARCH_ALL) + + /* + * Initialize the common controls (extended version). This is necessary to + * run the actual .MSI installers with the new fancy visual control + * styles (XP+). Also, an integrated manifest is required. + */ + INITCOMMONCONTROLSEX ccEx; + ccEx.dwSize = sizeof(INITCOMMONCONTROLSEX); + ccEx.dwICC = ICC_LINK_CLASS | ICC_LISTVIEW_CLASSES | ICC_PAGESCROLLER_CLASS | + ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES | + ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES | ICC_WIN95_CLASSES; + InitCommonControlsEx(&ccEx); /* Ignore failure. */ + + /* + * Convert both strings to UTF-16 and start the installation. + */ + PRTUTF16 pwszMsi; + rc = RTStrToUtf16(pszMsi, &pwszMsi); + if (RT_FAILURE(rc)) + return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc); + PRTUTF16 pwszMsiArgs; + rc = RTStrToUtf16(pszMsiArgs, &pwszMsiArgs); + if (RT_FAILURE(rc)) { - return TRUE; + RTUtf16Free(pwszMsi); + return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc); } - return FALSE; + + UINT uStatus = MsiInstallProductW(pwszMsi, pwszMsiArgs); + RTUtf16Free(pwszMsi); + RTUtf16Free(pwszMsiArgs); + + if (uStatus == ERROR_SUCCESS) + return RTEXITCODE_SUCCESS; + if (uStatus == ERROR_SUCCESS_REBOOT_REQUIRED) + return RTEXITCODE_SUCCESS; /* we currently don't indicate this */ + + /* + * Installation failed. Figure out what to say. + */ + switch (uStatus) + { + case ERROR_INSTALL_USEREXIT: + /* Don't say anything? */ + break; + + case ERROR_INSTALL_PACKAGE_VERSION: + ShowError("This installation package cannot be installed by the Windows Installer service.\n" + "You must install a Windows service pack that contains a newer version of the Windows Installer service."); + break; + + case ERROR_INSTALL_PLATFORM_UNSUPPORTED: + ShowError("This installation package is not supported on this platform."); + break; + + default: + { + /* + * Try get windows to format the message. + */ + DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_IGNORE_INSERTS + | FORMAT_MESSAGE_FROM_SYSTEM; + HMODULE hModule = NULL; + if (uStatus >= NERR_BASE && uStatus <= MAX_NERR) + { + hModule = LoadLibraryExW(L"netmsg.dll", + NULL, + LOAD_LIBRARY_AS_DATAFILE); + if (hModule != NULL) + dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE; + } + + PWSTR pwszMsg; + if (FormatMessageW(dwFormatFlags, + hModule, /* If NULL, load system stuff. */ + uStatus, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (PWSTR)&pwszMsg, + 0, + NULL) > 0) + { + ShowError("Installation failed! Error: %ls", pwszMsg); + LocalFree(pwszMsg); + } + else /* If text lookup failed, show at least the error number. */ + ShowError("Installation failed! Error: %u", uStatus); + + if (hModule) + FreeLibrary(hModule); + break; + } + } + + return RTEXITCODE_FAILURE; } /** - * Recursively copies a directory to another location. + * Processes a package. * - * @returns iprt status code. + * @returns Fully complained exit code. + * @param iPackage The package number. + * @param pszPkgDir The package directory (aka extraction dir). + * @param pszMsiArgs Any additional installer (MSI) argument + * @param fLogging Whether to enable installer logging. + */ +static RTEXITCODE ProcessPackage(unsigned iPackage, const char *pszPkgDir, const char *pszMsiArgs, bool fLogging) +{ + /* + * Get the package header and check if it's needed. + */ + PVBOXSTUBPKG pPackage = FindPackageHeader(iPackage); + if (pPackage == NULL) + return RTEXITCODE_FAILURE; + + if (!PackageIsNeeded(pPackage)) + return RTEXITCODE_SUCCESS; + + /* + * Deal with the file based on it's extension. + */ + char szPkgFile[RTPATH_MAX]; + int rc = RTPathJoin(szPkgFile, sizeof(szPkgFile), pszPkgDir, pPackage->szFileName); + if (RT_FAILURE(rc)) + return ShowError("Internal error: RTPathJoin failed: %Rrc", rc); + RTPathChangeToDosSlashes(szPkgFile, true /* Force conversion. */); /* paranoia */ + + RTEXITCODE rcExit; + const char *pszExt = RTPathExt(szPkgFile); + if (RTStrICmp(pszExt, ".msi") == 0) + rcExit = ProcessMsiPackage(szPkgFile, pszMsiArgs, fLogging); + else if (RTStrICmp(pszExt, ".cab") == 0) + rcExit = RTEXITCODE_SUCCESS; /* Ignore .cab files, they're generally referenced by other files. */ + else + rcExit = ShowError("Internal error: Do not know how to handle file '%s'.", pPackage->szFileName); + + return rcExit; +} + + +#ifdef VBOX_WITH_CODE_SIGNING +/** + * Install the public certificate into TrustedPublishers so the installer won't + * prompt the user during silent installs. + * + * @returns Fully complained exit code. + */ +static RTEXITCODE InstallCertificate(void) +{ + if (addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE, + "TrustedPublisher", + g_ab_VBoxStubPublicCert, + sizeof(g_ab_VBoxStubPublicCert))) + return RTEXITCODE_SUCCESS; + return ShowError("Failed to construct install certificate."); +} +#endif /* VBOX_WITH_CODE_SIGNING */ + + +/** + * Copies the "<exepath>.custom" directory to the extraction path if it exists. * - * @param pszDestDir Location to copy the source directory to. - * @param pszSourceDir The source directory to copy. + * This is used by the MSI packages from the resource section. * + * @returns Fully complained exit code. + * @param pszDstDir The destination directory. */ -int CopyDir(const char *pszDestDir, const char *pszSourceDir) +static RTEXITCODE CopyCustomDir(const char *pszDstDir) { - char szDest[RTPATH_MAX + 1]; - char szSource[RTPATH_MAX + 1]; + char szSrcDir[RTPATH_MAX]; + int rc = RTPathExecDir(szSrcDir, sizeof(szSrcDir)); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szSrcDir, sizeof(szSrcDir), ".custom"); + if (RT_FAILURE(rc)) + return ShowError("Failed to construct '.custom' dir path: %Rrc", rc); + + if (RTDirExists(szSrcDir)) + { + /* + * Use SHFileOperation w/ FO_COPY to do the job. This API requires an + * extra zero at the end of both source and destination paths. + */ + size_t cwc; + RTUTF16 wszSrcDir[RTPATH_MAX + 1]; + PRTUTF16 pwszSrcDir = wszSrcDir; + rc = RTStrToUtf16Ex(szSrcDir, RTSTR_MAX, &pwszSrcDir, RTPATH_MAX, &cwc); + if (RT_FAILURE(rc)) + return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", szSrcDir, rc); + wszSrcDir[cwc] = '\0'; + + RTUTF16 wszDstDir[RTPATH_MAX + 1]; + PRTUTF16 pwszDstDir = wszSrcDir; + rc = RTStrToUtf16Ex(pszDstDir, RTSTR_MAX, &pwszDstDir, RTPATH_MAX, &cwc); + if (RT_FAILURE(rc)) + return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", pszDstDir, rc); + wszDstDir[cwc] = '\0'; + + SHFILEOPSTRUCTW FileOp; + RT_ZERO(FileOp); /* paranoia */ + FileOp.hwnd = NULL; + FileOp.wFunc = FO_COPY; + FileOp.pFrom = wszSrcDir; + FileOp.pTo = wszDstDir; + FileOp.fFlags = FOF_SILENT + | FOF_NOCONFIRMATION + | FOF_NOCONFIRMMKDIR + | FOF_NOERRORUI; + FileOp.fAnyOperationsAborted = FALSE; + FileOp.hNameMappings = NULL; + FileOp.lpszProgressTitle = NULL; + + rc = SHFileOperationW(&FileOp); + if (rc != 0) /* Not a Win32 status code! */ + return ShowError("Copying the '.custom' dir failed: %#x", rc); + + /* + * Add a cleanup record for recursively deleting the destination + * .custom directory. We should actually add this prior to calling + * SHFileOperationW since it may partially succeed... + */ + char *pszDstSubDir = RTPathJoinA(pszDstDir, ".custom"); + if (!pszDstSubDir) + return ShowError("Out of memory!"); + bool fRc = AddCleanupRec(pszDstSubDir, false /*fFile*/); + RTStrFree(pszDstSubDir); + if (!fRc) + return RTEXITCODE_FAILURE; + } + + return RTEXITCODE_SUCCESS; +} - AssertStmt(pszDestDir, "Destination directory invalid!"); - AssertStmt(pszSourceDir, "Source directory invalid!"); - SHFILEOPSTRUCT s = {0}; - if ( RTStrPrintf(szDest, _MAX_PATH, "%s%c", pszDestDir, '\0') > 0 - && RTStrPrintf(szSource, _MAX_PATH, "%s%c", pszSourceDir, '\0') > 0) +static RTEXITCODE ExtractFiles(unsigned cPackages, const char *pszDstDir, bool fExtractOnly, bool *pfCreatedExtractDir) +{ + int rc; + + /* + * Make sure the directory exists. + */ + *pfCreatedExtractDir = false; + if (!RTDirExists(pszDstDir)) + { + rc = RTDirCreate(pszDstDir, 0700, 0); + if (RT_FAILURE(rc)) + return ShowError("Failed to create extraction path '%s': %Rrc", pszDstDir, rc); + *pfCreatedExtractDir = true; + } + + /* + * Extract files. + */ + for (unsigned k = 0; k < cPackages; k++) { - s.hwnd = NULL; - s.wFunc = FO_COPY; - s.pTo = szDest; - s.pFrom = szSource; - s.fFlags = FOF_SILENT | - FOF_NOCONFIRMATION | - FOF_NOCONFIRMMKDIR | - FOF_NOERRORUI; + PVBOXSTUBPKG pPackage = FindPackageHeader(k); + if (!pPackage) + return RTEXITCODE_FAILURE; /* Done complaining already. */ + + if (fExtractOnly || PackageIsNeeded(pPackage)) + { + char szDstFile[RTPATH_MAX]; + rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, pPackage->szFileName); + if (RT_FAILURE(rc)) + return ShowError("Internal error: RTPathJoin failed: %Rrc", rc); + + rc = Extract(pPackage, szDstFile); + if (RT_FAILURE(rc)) + return ShowError("Error extracting package #%u: %Rrc", k, rc); + + if (!fExtractOnly && !AddCleanupRec(szDstFile, true /*fFile*/)) + { + RTFileDelete(szDstFile); + return RTEXITCODE_FAILURE; + } + } } - return RTErrConvertFromWin32(SHFileOperation(&s)); + + return RTEXITCODE_SUCCESS; } @@ -334,361 +788,223 @@ int WINAPI WinMain(HINSTANCE hInstance, /* Check if we're already running and jump out if so. */ /* Do not use a global namespace ("Global\\") for mutex name here, will blow up NT4 compatibility! */ - HANDLE hMutexAppRunning = CreateMutex (NULL, FALSE, "VBoxStubInstaller"); - if ( (hMutexAppRunning != NULL) - && (GetLastError() == ERROR_ALREADY_EXISTS)) + HANDLE hMutexAppRunning = CreateMutex(NULL, FALSE, "VBoxStubInstaller"); + if ( hMutexAppRunning != NULL + && GetLastError() == ERROR_ALREADY_EXISTS) { /* Close the mutex for this application instance. */ CloseHandle(hMutexAppRunning); hMutexAppRunning = NULL; - return 1; + return RTEXITCODE_FAILURE; } /* Init IPRT. */ int vrc = RTR3InitExe(argc, &argv, 0); if (RT_FAILURE(vrc)) - return vrc; + return RTMsgInitFailure(vrc); - BOOL fExtractOnly = FALSE; - BOOL fSilent = FALSE; - BOOL fEnableLogging = FALSE; - BOOL fExit = FALSE; + /* + * Parse arguments. + */ - /* Temp variables for arguments. */ + /* Parameter variables. */ + bool fExtractOnly = false; + bool fEnableLogging = false; +#ifdef VBOX_WITH_CODE_SIGNING + bool fEnableSilentCert = true; +#endif char szExtractPath[RTPATH_MAX] = {0}; - char szMSIArgs[RTPATH_MAX] = {0}; + char szMSIArgs[4096] = {0}; - /* Process arguments. */ - for (int i = 0; i < argc; i++) + /* Parameter definitions. */ + static const RTGETOPTDEF s_aOptions[] = { - if ( (0 == RTStrICmp(argv[i], "-x")) - || (0 == RTStrICmp(argv[i], "-extract")) - || (0 == RTStrICmp(argv[i], "/extract"))) - { - fExtractOnly = TRUE; - } - - else if ( (0 == RTStrICmp(argv[i], "-s")) - || (0 == RTStrICmp(argv[i], "-silent")) - || (0 == RTStrICmp(argv[i], "/silent"))) - { - fSilent = TRUE; - } - - else if ( (0 == RTStrICmp(argv[i], "-l")) - || (0 == RTStrICmp(argv[i], "-logging")) - || (0 == RTStrICmp(argv[i], "/logging"))) - { - fEnableLogging = TRUE; - } - - else if (( (0 == RTStrICmp(argv[i], "-p")) - || (0 == RTStrICmp(argv[i], "-path")) - || (0 == RTStrICmp(argv[i], "/path"))) - ) - { - if (argc > i) - { - vrc = ::StringCbCat(szExtractPath, sizeof(szExtractPath), argv[i+1]); - i++; /* Avoid the specified path from being parsed. */ - } - else - { - ShowError("No path for extraction specified!"); - fExit = TRUE; - } - } - - else if (( (0 == RTStrICmp(argv[i], "-msiparams")) - || (0 == RTStrICmp(argv[i], "/msiparams"))) - && (argc > i)) + { "--extract", 'x', RTGETOPT_REQ_NOTHING }, + { "-extract", 'x', RTGETOPT_REQ_NOTHING }, + { "/extract", 'x', RTGETOPT_REQ_NOTHING }, + { "--silent", 's', RTGETOPT_REQ_NOTHING }, + { "-silent", 's', RTGETOPT_REQ_NOTHING }, + { "/silent", 's', RTGETOPT_REQ_NOTHING }, +#ifdef VBOX_WITH_CODE_SIGNING + { "--no-silent-cert", 'c', RTGETOPT_REQ_NOTHING }, + { "-no-silent-cert", 'c', RTGETOPT_REQ_NOTHING }, + { "/no-silent-cert", 'c', RTGETOPT_REQ_NOTHING }, +#endif + { "--logging", 'l', RTGETOPT_REQ_NOTHING }, + { "-logging", 'l', RTGETOPT_REQ_NOTHING }, + { "/logging", 'l', RTGETOPT_REQ_NOTHING }, + { "--path", 'p', RTGETOPT_REQ_STRING }, + { "-path", 'p', RTGETOPT_REQ_STRING }, + { "/path", 'p', RTGETOPT_REQ_STRING }, + { "--msiparams", 'm', RTGETOPT_REQ_STRING }, + { "-msiparams", 'm', RTGETOPT_REQ_STRING }, + { "--version", 'V', RTGETOPT_REQ_NOTHING }, + { "-version", 'V', RTGETOPT_REQ_NOTHING }, + { "/version", 'V', RTGETOPT_REQ_NOTHING }, + { "-v", 'V', RTGETOPT_REQ_NOTHING }, + { "--help", 'h', RTGETOPT_REQ_NOTHING }, + { "-help", 'h', RTGETOPT_REQ_NOTHING }, + { "/help", 'h', RTGETOPT_REQ_NOTHING }, + { "/?", 'h', RTGETOPT_REQ_NOTHING }, + }; + + /* Parse the parameters. */ + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) { - for (int a = i + 1; a < argc; a++) - { - if (a > i+1) /* Insert a space. */ - vrc = ::StringCbCat(szMSIArgs, sizeof(szMSIArgs), " "); + case 'x': + fExtractOnly = true; + break; + + case 's': + g_fSilent = true; + break; + +#ifdef VBOX_WITH_CODE_SIGNING + case 'c': + fEnableSilentCert = false; + break; +#endif - vrc = ::StringCbCat(szMSIArgs, sizeof(szMSIArgs), argv[a]); - } - } + case 'l': + fEnableLogging = true; + break; - else if ( (0 == RTStrICmp(argv[i], "-v")) - || (0 == RTStrICmp(argv[i], "-version")) - || (0 == RTStrICmp(argv[i], "/version"))) - { - ShowInfo("Version: %d.%d.%d.%d", - VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV); - fExit = TRUE; - } + case 'p': + vrc = RTStrCopy(szExtractPath, sizeof(szExtractPath), ValueUnion.psz); + if (RT_FAILURE(vrc)) + return ShowError("Extraction path is too long."); + break; - else if ( (0 == RTStrICmp(argv[i], "-help")) - || (0 == RTStrICmp(argv[i], "/help")) - || (0 == RTStrICmp(argv[i], "/?"))) - { - ShowInfo("-- %s v%d.%d.%d.%d --\n" + case 'm': + if (szMSIArgs[0]) + vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " "); + if (RT_SUCCESS(vrc)) + vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz); + if (RT_FAILURE(vrc)) + return ShowError("MSI parameters are too long."); + break; + + case 'V': + ShowInfo("Version: %d.%d.%d.%d", + VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV); + return VINF_SUCCESS; + + case 'h': + ShowInfo("-- %s v%d.%d.%d.%d --\n" + "\n" "Command Line Parameters:\n\n" - "-extract | -x - Extract file contents to temporary directory\n" - "-silent | -s - Enables silent mode installation\n" - "-path | -p - Sets the path of the extraction directory\n" - "-help | /? - Print this help and exit\n" - "-msiparams <parameters> - Specifies extra parameters for the MSI installers\n" - "-logging | -l - Enables installer logging\n" - "-version | -v - Print version number and exit\n\n" + "--extract - Extract file contents to temporary directory\n" + "--silent - Enables silent mode installation\n" + "--no-silent-cert - Do not install VirtualBox Certificate automatically when --silent option is specified\n" + "--path - Sets the path of the extraction directory\n" + "--msiparams <parameters> - Specifies extra parameters for the MSI installers\n" + "--logging - Enables installer logging\n" + "--help - Print this help and exit\n" + "--version - Print version number and exit\n\n" "Examples:\n" - "%s -msiparams INSTALLDIR=C:\\VBox\n" - "%s -extract -path C:\\VBox\n", + "%s --msiparams INSTALLDIR=C:\\VBox\n" + "%s --extract -path C:\\VBox", VBOX_STUB_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV, argv[0], argv[0]); - fExit = TRUE; - } - else - { - if (i > 0) - { - ShowError("Unknown option \"%s\"!\n" - "Please refer to the command line help by specifying \"/?\"\n" - "to get more information.", argv[i]); - fExit = TRUE; - } - } - } + return VINF_SUCCESS; - if (fExit) - return 0; + default: + if (g_fSilent) + return RTGetOptPrintError(ch, &ValueUnion); + if (ch == VINF_GETOPT_NOT_OPTION || ch == VERR_GETOPT_UNKNOWN_OPTION) + ShowError("Unknown option \"%s\"!\n" + "Please refer to the command line help by specifying \"/?\"\n" + "to get more information.", ValueUnion.psz); + else + ShowError("Parameter parsing error: %Rrc\n" + "Please refer to the command line help by specifying \"/?\"\n" + "to get more information.", ch); + return RTEXITCODE_SYNTAX; - HRESULT hr = S_OK; + } + } - do /* break loop */ + /* + * Determine the extration path if not given by the user, and gather some + * other bits we'll be needing later. + */ + if (szExtractPath[0] == '\0') { - /* Get/create our temp path (only if not already set). */ - if (szExtractPath[0] == '\0') - { - vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath)); - AssertMsgRCBreak(vrc, ("Could not retrieve temp directory!\n")); - + vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath)); + if (RT_SUCCESS(vrc)) vrc = RTPathAppend(szExtractPath, sizeof(szExtractPath), "VirtualBox"); - AssertMsgRCBreak(vrc, ("Could not construct temp directory!\n")); - - /* Convert slahes; this is necessary for MSI routines later! */ - RTPathChangeToDosSlashes(szExtractPath, true /* Force conversion. */); - } - if (!RTDirExists(szExtractPath)) - { - vrc = RTDirCreate(szExtractPath, 0700, 0); - AssertMsgRCBreak(vrc, ("Could not create temp directory!\n")); - } + if (RT_FAILURE(vrc)) + return ShowError("Failed to determin extraction path (%Rrc)", vrc); - /* Get our executable path */ - char szPathExe[_MAX_PATH]; - vrc = RTPathExecDir(szPathExe, sizeof(szPathExe)); - /** @todo error checking */ - - /* Read our manifest. */ - PVBOXSTUBPKGHEADER pHeader = NULL; - DWORD cbHeader = 0; - vrc = ReadData(NULL, "MANIFEST", (LPVOID*)&pHeader, &cbHeader); - AssertMsgRCBreak(vrc, ("Manifest not found!\n")); - - /* Extract files. */ - for (BYTE k = 0; k < pHeader->byCntPkgs; k++) - { - PVBOXSTUBPKG pPackage = NULL; - DWORD cbPackage = 0; - char szHeaderName[RTPATH_MAX + 1] = {0}; - - hr = ::StringCchPrintf(szHeaderName, RTPATH_MAX, "HDR_%02d", k); - vrc = ReadData(NULL, szHeaderName, (LPVOID*)&pPackage, &cbPackage); - AssertMsgRCBreak(vrc, ("Header not found!\n")); /** @todo include header name, how? */ - - if (PackageIsNeeded(pPackage) || fExtractOnly) - { - char *pszTempFile = NULL; - vrc = GetTempFileAlloc(szExtractPath, pPackage->szFileName, &pszTempFile); - AssertMsgRCBreak(vrc, ("Could not create name for temporary extracted file!\n")); - vrc = Extract(pPackage, pszTempFile); - AssertMsgRCBreak(vrc, ("Could not extract file!\n")); - RTStrFree(pszTempFile); - } - } + } + else + { + /** @todo should check if there is a .custom subdirectory there or not. */ + } + RTPathChangeToDosSlashes(szExtractPath, true /* Force conversion. */); /* MSI requirement. */ - if (FALSE == fExtractOnly && !RT_FAILURE(vrc)) + /* Read our manifest. */ + PVBOXSTUBPKGHEADER pHeader; + vrc = FindData("MANIFEST", (PVOID *)&pHeader, NULL); + if (RT_FAILURE(vrc)) + return ShowError("Internal package error: Manifest not found (%Rrc)", vrc); + /** @todo If we could, we should validate the header. Only the magic isn't + * commonly defined, nor the version number... */ + + RTListInit(&g_TmpFiles); + + /* + * Up to this point, we haven't done anything that requires any cleanup. + * From here on, we do everything in function so we can counter clean up. + */ + bool fCreatedExtractDir; + RTEXITCODE rcExit = ExtractFiles(pHeader->byCntPkgs, szExtractPath, fExtractOnly, &fCreatedExtractDir); + if (rcExit == RTEXITCODE_SUCCESS) + { + if (fExtractOnly) + ShowInfo("Files were extracted to: %s", szExtractPath); + else { - /* - * Copy ".custom" directory into temp directory so that the extracted .MSI - * file(s) can use it. - */ - char *pszPathCustomDir = RTPathJoinA(szPathExe, ".custom"); - pszPathCustomDir = RTPathChangeToDosSlashes(pszPathCustomDir, true /* Force conversion. */); - if (pszPathCustomDir && RTDirExists(pszPathCustomDir)) + rcExit = CopyCustomDir(szExtractPath); +#ifdef VBOX_WITH_CODE_SIGNING + if (rcExit == RTEXITCODE_SUCCESS && fEnableSilentCert && g_fSilent) + rcExit = InstallCertificate(); +#endif + unsigned iPackage = 0; + while (iPackage < pHeader->byCntPkgs && rcExit == RTEXITCODE_SUCCESS) { - vrc = CopyDir(szExtractPath, pszPathCustomDir); - if (RT_FAILURE(vrc)) /* Don't fail if it's missing! */ - vrc = VINF_SUCCESS; - - RTStrFree(pszPathCustomDir); + rcExit = ProcessPackage(iPackage, szExtractPath, szMSIArgs, fEnableLogging); + iPackage++; } - /* Do actions on files. */ - for (BYTE k = 0; k < pHeader->byCntPkgs; k++) - { - PVBOXSTUBPKG pPackage = NULL; - DWORD cbPackage = 0; - char szHeaderName[RTPATH_MAX] = {0}; - - hr = StringCchPrintf(szHeaderName, RTPATH_MAX, "HDR_%02d", k); - vrc = ReadData(NULL, szHeaderName, (LPVOID*)&pPackage, &cbPackage); - AssertMsgRCBreak(vrc, ("Package not found!\n")); - - if (PackageIsNeeded(pPackage)) - { - char *pszTempFile = NULL; - - vrc = GetTempFileAlloc(szExtractPath, pPackage->szFileName, &pszTempFile); - AssertMsgRCBreak(vrc, ("Could not create name for temporary action file!\n")); - - /* Handle MSI files. */ - if (RTStrICmp(RTPathExt(pszTempFile), ".msi") == 0) - { - /* Set UI level. */ - INSTALLUILEVEL UILevel = MsiSetInternalUI( fSilent - ? INSTALLUILEVEL_NONE - : INSTALLUILEVEL_FULL, - NULL); - AssertMsgBreak(UILevel != INSTALLUILEVEL_NOCHANGE, ("Could not set installer UI level!\n")); - - /* Enable logging? */ - if (fEnableLogging) - { - char *pszLog = RTPathJoinA(szExtractPath, "VBoxInstallLog.txt"); - /* Convert slahes; this is necessary for MSI routines! */ - pszLog = RTPathChangeToDosSlashes(pszLog, true /* Force conversion. */); - AssertMsgBreak(pszLog, ("Could not construct path for log file!\n")); - UINT uLogLevel = MsiEnableLog(INSTALLLOGMODE_VERBOSE, - pszLog, INSTALLLOGATTRIBUTES_FLUSHEACHLINE); - RTStrFree(pszLog); - AssertMsgBreak(uLogLevel == ERROR_SUCCESS, ("Could not set installer logging level!\n")); - } - - /* Initialize the common controls (extended version). This is necessary to - * run the actual .MSI installers with the new fancy visual control - * styles (XP+). Also, an integrated manifest is required. */ - INITCOMMONCONTROLSEX ccEx; - ccEx.dwSize = sizeof(INITCOMMONCONTROLSEX); - ccEx.dwICC = ICC_LINK_CLASS | ICC_LISTVIEW_CLASSES | ICC_PAGESCROLLER_CLASS | - ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES | - ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES | ICC_WIN95_CLASSES; - InitCommonControlsEx(&ccEx); /* Ignore failure. */ - - UINT uStatus = ::MsiInstallProductA(pszTempFile, szMSIArgs); - if ( (uStatus != ERROR_SUCCESS) - && (uStatus != ERROR_SUCCESS_REBOOT_REQUIRED) - && (uStatus != ERROR_INSTALL_USEREXIT)) - { - if (!fSilent) - { - switch (uStatus) - { - case ERROR_INSTALL_PACKAGE_VERSION: - - ShowError("This installation package cannot be installed by the Windows Installer service.\n" - "You must install a Windows service pack that contains a newer version of the Windows Installer service."); - break; - - case ERROR_INSTALL_PLATFORM_UNSUPPORTED: - - ShowError("This installation package is not supported on this platform."); - break; - - default: - { - DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER - | FORMAT_MESSAGE_IGNORE_INSERTS - | FORMAT_MESSAGE_FROM_SYSTEM; - HMODULE hModule = NULL; - if (uStatus >= NERR_BASE && uStatus <= MAX_NERR) - { - hModule = LoadLibraryEx(TEXT("netmsg.dll"), - NULL, - LOAD_LIBRARY_AS_DATAFILE); - if (hModule != NULL) - dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE; - } - - DWORD dwBufferLength; - LPSTR szMessageBuffer; - if (dwBufferLength = FormatMessageA(dwFormatFlags, - hModule, /* If NULL, load system stuff. */ - uStatus, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&szMessageBuffer, - 0, - NULL)) - { - ShowError("Installation failed! Error: %s", szMessageBuffer); - LocalFree(szMessageBuffer); - } - else /* If text lookup failed, show at least the error number. */ - ShowError("Installation failed! Error: %u", uStatus); - if (hModule) - FreeLibrary(hModule); - break; - } - } - } - - vrc = VERR_NO_CHANGE; /* No change done to the system. */ - } - } - RTStrFree(pszTempFile); - } /* Package needed? */ - } /* For all packages */ - } - - /* Clean up (only on success - prevent deleting the log). */ - if ( !fExtractOnly - && RT_SUCCESS(vrc)) - { - for (int i=0; i<5; i++) - { - vrc = RTDirRemoveRecursive(szExtractPath, 0 /*fFlags*/); - if (RT_SUCCESS(vrc)) - break; - RTThreadSleep(3000 /* Wait 3 seconds.*/); - } + /* Don't fail if cleanup fail. At least for now. */ + CleanUp(pHeader->byCntPkgs, !fEnableLogging && fCreatedExtractDir ? szExtractPath : NULL); } + } - } while (0); - - if (RT_SUCCESS(vrc)) + /* Free any left behind cleanup records (not strictly needed). */ + PSTUBCLEANUPREC pCur, pNext; + RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry) { - if ( fExtractOnly - && !fSilent) - { - ShowInfo("Files were extracted to: %s", szExtractPath); - } - - /** @todo Add more post installation stuff here if required. */ + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); } - /* Release instance mutex. */ + /* + * Release instance mutex. + */ if (hMutexAppRunning != NULL) { CloseHandle(hMutexAppRunning); hMutexAppRunning = NULL; } - /* Set final exit (return) code (error level). */ - if (RT_FAILURE(vrc)) - { - switch(vrc) - { - case VERR_NO_CHANGE: - default: - vrc = 1; - } - } - else /* Always set to (VINF_SUCCESS), even if we got something else (like a VWRN etc). */ - vrc = VINF_SUCCESS; - return vrc; + return rcExit; } |