diff options
Diffstat (limited to 'src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp')
-rw-r--r-- | src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp | 2017 |
1 files changed, 2017 insertions, 0 deletions
diff --git a/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp new file mode 100644 index 00000000..811116e1 --- /dev/null +++ b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp @@ -0,0 +1,2017 @@ +/* $Id: VBoxExtPackHelperApp.cpp $ */ +/** @file + * VirtualBox Main - Extension Pack Helper Application, usually set-uid-to-root. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "../include/ExtPackUtil.h" + +#include <iprt/buildconfig.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/fs.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/manifest.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/sha.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/thread.h> +#include <iprt/utf16.h> +#include <iprt/vfs.h> +#include <iprt/zip.h> +#include <iprt/cpp/ministring.h> + +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/sup.h> +#include <VBox/version.h> + +#ifdef RT_OS_WINDOWS +# define _WIN32_WINNT 0x0501 +# include <iprt/win/windows.h> /* ShellExecuteEx, ++ */ +# include <iprt/win/objbase.h> /* CoInitializeEx */ +# ifdef DEBUG +# include <Sddl.h> +# endif +#endif + +#ifdef RT_OS_DARWIN +# include <Security/Authorization.h> +# include <Security/AuthorizationTags.h> +# include <CoreFoundation/CoreFoundation.h> +#endif + +#if !defined(RT_OS_OS2) +# include <stdio.h> +# include <errno.h> +# if !defined(RT_OS_WINDOWS) +# include <sys/types.h> +# include <unistd.h> /* geteuid */ +# endif +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Enable elevation on Windows and Darwin. */ +#if !defined(RT_OS_OS2) || defined(DOXYGEN_RUNNING) +# define WITH_ELEVATION +#endif + + +/** @name Command and option names + * @{ */ +#define CMD_INSTALL 1000 +#define CMD_UNINSTALL 1001 +#define CMD_CLEANUP 1002 +#ifdef WITH_ELEVATION +# define OPT_ELEVATED 1090 +# define OPT_STDOUT 1091 +# define OPT_STDERR 1092 +#endif +#define OPT_DISP_INFO_HACK 1093 +/** @} */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +static HINSTANCE g_hInstance; +#endif + +#ifdef IN_RT_R3 +/* Override RTAssertShouldPanic to prevent gdb process creation. */ +RTDECL(bool) RTAssertShouldPanic(void) +{ + return true; +} +#endif + + + +/** + * Handle the special standard options when these are specified after the + * command. + * + * @param ch The option character. + */ +static RTEXITCODE DoStandardOption(int ch) +{ + switch (ch) + { + case 'h': + { + RTMsgInfo(VBOX_PRODUCT " Extension Pack Helper App\n" + "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n" + "\n" + "This NOT intended for general use, please use VBoxManage instead\n" + "or call the IExtPackManager API directly.\n" + "\n" + "Usage: %s <command> [options]\n" + "Commands:\n" + " install --base-dir <dir> --cert-dir <dir> --name <name> \\\n" + " --tarball <tarball> --tarball-fd <fd>\n" + " uninstall --base-dir <dir> --name <name>\n" + " cleanup --base-dir <dir>\n" + , RTProcShortName()); + return RTEXITCODE_SUCCESS; + } + + case 'V': + RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision()); + return RTEXITCODE_SUCCESS; + + default: + AssertFailedReturn(RTEXITCODE_FAILURE); + } +} + + +/** + * Checks if the cerficiate directory is valid. + * + * @returns true if it is valid, false if it isn't. + * @param pszCertDir The certificate directory to validate. + */ +static bool IsValidCertificateDir(const char *pszCertDir) +{ + /* + * Just be darn strict for now. + */ + char szCorrect[RTPATH_MAX]; + int rc = RTPathAppPrivateNoArch(szCorrect, sizeof(szCorrect)); + if (RT_FAILURE(rc)) + return false; + rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_CERT_DIR); + if (RT_FAILURE(rc)) + return false; + + return RTPathCompare(szCorrect, pszCertDir) == 0; +} + + +/** + * Checks if the base directory is valid. + * + * @returns true if it is valid, false if it isn't. + * @param pszBaesDir The base directory to validate. + */ +static bool IsValidBaseDir(const char *pszBaseDir) +{ + /* + * Just be darn strict for now. + */ + char szCorrect[RTPATH_MAX]; + int rc = RTPathAppPrivateArchTop(szCorrect, sizeof(szCorrect)); + if (RT_FAILURE(rc)) + return false; + rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_INSTALL_DIR); + if (RT_FAILURE(rc)) + return false; + + return RTPathCompare(szCorrect, pszBaseDir) == 0; +} + + +/** + * Cleans up a temporary extension pack directory. + * + * This is used by 'uninstall', 'cleanup' and in the failure path of 'install'. + * + * @returns The program exit code. + * @param pszDir The directory to clean up. The caller is + * responsible for making sure this is valid. + * @param fTemporary Whether this is a temporary install directory or + * not. + */ +static RTEXITCODE RemoveExtPackDir(const char *pszDir, bool fTemporary) +{ + /** @todo May have to undo 555 modes here later. */ + int rc = RTDirRemoveRecursive(pszDir, RTDIRRMREC_F_CONTENT_AND_DIR); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to delete the %sextension pack directory: %Rrc ('%s')", + fTemporary ? "temporary " : "", rc, pszDir); + return RTEXITCODE_SUCCESS; +} + + +/** + * Wrapper around RTDirRename that may retry the operation for up to 15 seconds + * on windows to deal with AV software. + */ +static int CommonDirRenameWrapper(const char *pszSrc, const char *pszDst, uint32_t fFlags) +{ +#ifdef RT_OS_WINDOWS + uint64_t nsNow = RTTimeNanoTS(); + for (;;) + { + int rc = RTDirRename(pszSrc, pszDst, fFlags); + if ( ( rc != VERR_ACCESS_DENIED + && rc != VERR_SHARING_VIOLATION) + || RTTimeNanoTS() - nsNow > RT_NS_15SEC) + return rc; + RTThreadSleep(128); + } +#else + return RTDirRename(pszSrc, pszDst, fFlags); +#endif +} + +/** + * Common uninstall worker used by both uninstall and install --replace. + * + * @returns success or failure, message displayed on failure. + * @param pszExtPackDir The extension pack directory name. + */ +static RTEXITCODE CommonUninstallWorker(const char *pszExtPackDir) +{ + /* Rename the extension pack directory before deleting it to prevent new + VM processes from picking it up. */ + char szExtPackUnInstDir[RTPATH_MAX]; + int rc = RTStrCopy(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), pszExtPackDir); + if (RT_SUCCESS(rc)) + rc = RTStrCat(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), "-_-uninst"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct temporary extension pack path: %Rrc", rc); + + rc = CommonDirRenameWrapper(pszExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE); + if (rc == VERR_ALREADY_EXISTS) + { + /* Automatic cleanup and try again. It's in theory possible that we're + racing another cleanup operation here, so just ignore errors and try + again. (There is no installation race due to the exclusive temporary + installation directory.) */ + RemoveExtPackDir(szExtPackUnInstDir, false /*fTemporary*/); + rc = CommonDirRenameWrapper(pszExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE); + } + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to rename the extension pack directory: %Rrc\n" + "If the problem persists, try running the command: VBoxManage extpack cleanup", rc); + + /* Recursively delete the directory content. */ + return RemoveExtPackDir(szExtPackUnInstDir, false /*fTemporary*/); +} + + +/** + * Wrapper around VBoxExtPackOpenTarFss. + * + * @returns success or failure, message displayed on failure. + * @param hTarballFile The handle to the tarball file. + * @param phTarFss Where to return the filesystem stream handle. + */ +static RTEXITCODE OpenTarFss(RTFILE hTarballFile, PRTVFSFSSTREAM phTarFss) +{ + char szError[8192]; + int rc = VBoxExtPackOpenTarFss(hTarballFile, szError, sizeof(szError), phTarFss, NULL); + if (RT_FAILURE(rc)) + { + Assert(szError[0]); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError); + } + Assert(!szError[0]); + return RTEXITCODE_SUCCESS; +} + + +/** + * Sets the permissions of the temporary extension pack directory just before + * renaming it. + * + * By default the temporary directory is only accessible by root, this function + * will make it world readable and browseable. + * + * @returns The program exit code. + * @param pszDir The temporary extension pack directory. + */ +static RTEXITCODE SetExtPackPermissions(const char *pszDir) +{ + RTMsgInfo("Setting permissions..."); +#if !defined(RT_OS_WINDOWS) + int rc = RTPathSetMode(pszDir, 0755); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions: %Rrc ('%s')", rc, pszDir); +#else + /** @todo TrustedInstaller? */ + RT_NOREF1(pszDir); +#endif + + return RTEXITCODE_SUCCESS; +} + + +/** + * Wrapper around VBoxExtPackValidateMember. + * + * @returns Program exit code, failure with message. + * @param pszName The name of the directory. + * @param enmType The object type. + * @param hVfsObj The VFS object. + */ +static RTEXITCODE ValidateMemberOfExtPack(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj) +{ + char szError[8192]; + int rc = VBoxExtPackValidateMember(pszName, enmType, hVfsObj, szError, sizeof(szError)); + if (RT_FAILURE(rc)) + { + Assert(szError[0]); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError); + } + Assert(!szError[0]); + return RTEXITCODE_SUCCESS; +} + + +/** + * Validates the extension pack tarball prior to unpacking. + * + * Operations performed: + * - Hardening checks. + * + * @returns The program exit code. + * @param pszDir The directory where the extension pack has been + * unpacked. + * @param pszExtPackName The expected extension pack name. + * @param pszTarball The name of the tarball in case we have to + * complain about something. + */ +static RTEXITCODE ValidateUnpackedExtPack(const char *pszDir, const char *pszTarball, const char *pszExtPackName) +{ + RT_NOREF2(pszTarball, pszExtPackName); + RTMsgInfo("Validating unpacked extension pack..."); + + RTERRINFOSTATIC ErrInfo; + RTErrInfoInitStatic(&ErrInfo); + int rc = SUPR3HardenedVerifyDir(pszDir, true /*fRecursive*/, true /*fCheckFiles*/, &ErrInfo.Core); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Hardening check failed with %Rrc: %s", rc, ErrInfo.Core.pszMsg); + return RTEXITCODE_SUCCESS; +} + + +/** + * Unpacks a directory from an extension pack tarball. + * + * @returns Program exit code, failure with message. + * @param pszDstDirName The name of the unpacked directory. + * @param hVfsObj The source object for the directory. + */ +static RTEXITCODE UnpackExtPackDir(const char *pszDstDirName, RTVFSOBJ hVfsObj) +{ + /* + * Get the mode mask before creating the directory. + */ + RTFSOBJINFO ObjInfo; + int rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszDstDirName, rc); + ObjInfo.Attr.fMode &= ~(RTFS_UNIX_IWOTH | RTFS_UNIX_IWGRP); + + rc = RTDirCreate(pszDstDirName, ObjInfo.Attr.fMode, 0); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create directory '%s': %Rrc", pszDstDirName, rc); + +#ifndef RT_OS_WINDOWS + /* + * Because of umask, we have to apply the mode again. + */ + rc = RTPathSetMode(pszDstDirName, ObjInfo.Attr.fMode); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions on '%s': %Rrc", pszDstDirName, rc); +#else + /** @todo Ownership tricks on windows? */ +#endif + return RTEXITCODE_SUCCESS; +} + + +/** + * Unpacks a file from an extension pack tarball. + * + * @returns Program exit code, failure with message. + * @param pszName The name in the tarball. + * @param pszDstFilename The name of the unpacked file. + * @param hVfsIosSrc The source stream for the file. + * @param hUnpackManifest The manifest to add the file digest to. + */ +static RTEXITCODE UnpackExtPackFile(const char *pszName, const char *pszDstFilename, + RTVFSIOSTREAM hVfsIosSrc, RTMANIFEST hUnpackManifest) +{ + /* + * Query the object info, we'll need it for buffer sizing as well as + * setting the file mode. + */ + RTFSOBJINFO ObjInfo; + int rc = RTVfsIoStrmQueryInfo(hVfsIosSrc, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmQueryInfo failed with %Rrc on '%s'", rc, pszDstFilename); + + /* + * Create the file. + */ + uint32_t fFlags = RTFILE_O_WRITE | RTFILE_O_DENY_ALL | RTFILE_O_CREATE | (0600 << RTFILE_O_CREATE_MODE_SHIFT); + RTFILE hFile; + rc = RTFileOpen(&hFile, pszDstFilename, fFlags); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create '%s': %Rrc", pszDstFilename, rc); + + /* + * Create a I/O stream for the destination file, stack a manifest entry + * creator on top of it. + */ + RTVFSIOSTREAM hVfsIosDst2; + rc = RTVfsIoStrmFromRTFile(hFile, fFlags, true /*fLeaveOpen*/, &hVfsIosDst2); + if (RT_SUCCESS(rc)) + { + RTVFSIOSTREAM hVfsIosDst; + rc = RTManifestEntryAddPassthruIoStream(hUnpackManifest, hVfsIosDst2, pszName, + RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256, + false /*fReadOrWrite*/, &hVfsIosDst); + RTVfsIoStrmRelease(hVfsIosDst2); + if (RT_SUCCESS(rc)) + { + /* + * Pump the data thru. + */ + rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(ObjInfo.cbObject, _1G)); + if (RT_SUCCESS(rc)) + { + rc = RTManifestPtIosAddEntryNow(hVfsIosDst); + if (RT_SUCCESS(rc)) + { + RTVfsIoStrmRelease(hVfsIosDst); + hVfsIosDst = NIL_RTVFSIOSTREAM; + + /* + * Set the mode mask. + */ + ObjInfo.Attr.fMode &= ~(RTFS_UNIX_IWOTH | RTFS_UNIX_IWGRP); + rc = RTFileSetMode(hFile, ObjInfo.Attr.fMode); + /** @todo Windows needs to do more here, I think. */ + if (RT_SUCCESS(rc)) + { + RTFileClose(hFile); + return RTEXITCODE_SUCCESS; + } + + RTMsgError("Failed to set the mode of '%s' to %RTfmode: %Rrc", pszDstFilename, ObjInfo.Attr.fMode, rc); + } + else + RTMsgError("RTManifestPtIosAddEntryNow failed for '%s': %Rrc", pszDstFilename, rc); + } + else + RTMsgError("RTVfsUtilPumpIoStreams failed for '%s': %Rrc", pszDstFilename, rc); + RTVfsIoStrmRelease(hVfsIosDst); + } + else + RTMsgError("RTManifestEntryAddPassthruIoStream failed: %Rrc", rc); + } + else + RTMsgError("RTVfsIoStrmFromRTFile failed: %Rrc", rc); + RTFileClose(hFile); + return RTEXITCODE_FAILURE; +} + + +/** + * Unpacks the extension pack into the specified directory. + * + * This will apply ownership and permission changes to all the content, the + * exception is @a pszDirDst which will be handled by SetExtPackPermissions. + * + * @returns The program exit code. + * @param hTarballFile The tarball to unpack. + * @param pszDirDst Where to unpack it. + * @param hValidManifest The manifest we've validated. + * @param pszTarball The name of the tarball in case we have to + * complain about something. + */ +static RTEXITCODE UnpackExtPack(RTFILE hTarballFile, const char *pszDirDst, RTMANIFEST hValidManifest, + const char *pszTarball) +{ + RT_NOREF1(pszTarball); + RTMsgInfo("Unpacking extension pack into '%s'...", pszDirDst); + + /* + * Set up the destination path. + */ + char szDstPath[RTPATH_MAX]; + int rc = RTPathAbs(pszDirDst, szDstPath, sizeof(szDstPath) - VBOX_EXTPACK_MAX_MEMBER_NAME_LENGTH - 2); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs('%s',,) failed: %Rrc", pszDirDst, rc); + size_t offDstPath = RTPathStripTrailingSlash(szDstPath); + szDstPath[offDstPath++] = '/'; + szDstPath[offDstPath] = '\0'; + + /* + * Open the tar.gz filesystem stream and set up an manifest in-memory file. + */ + RTVFSFSSTREAM hTarFss; + RTEXITCODE rcExit = OpenTarFss(hTarballFile, &hTarFss); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + RTMANIFEST hUnpackManifest; + rc = RTManifestCreate(0 /*fFlags*/, &hUnpackManifest); + if (RT_SUCCESS(rc)) + { + /* + * Process the tarball (would be nice to move this to a function). + */ + for (;;) + { + /* + * Get the next stream object. + */ + char *pszName; + RTVFSOBJ hVfsObj; + RTVFSOBJTYPE enmType; + rc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj); + if (RT_FAILURE(rc)) + { + if (rc != VERR_EOF) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext failed: %Rrc", rc); + break; + } + const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName; + + /* + * Check the type & name validity then unpack it. + */ + rcExit = ValidateMemberOfExtPack(pszName, enmType, hVfsObj); + if (rcExit == RTEXITCODE_SUCCESS) + { + szDstPath[offDstPath] = '\0'; + rc = RTStrCopy(&szDstPath[offDstPath], sizeof(szDstPath) - offDstPath, pszAdjName); + if (RT_SUCCESS(rc)) + { + if ( enmType == RTVFSOBJTYPE_FILE + || enmType == RTVFSOBJTYPE_IO_STREAM) + { + RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj); + rcExit = UnpackExtPackFile(pszAdjName, szDstPath, hVfsIos, hUnpackManifest); + RTVfsIoStrmRelease(hVfsIos); + } + else if (*pszAdjName && strcmp(pszAdjName, ".")) + rcExit = UnpackExtPackDir(szDstPath, hVfsObj); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Name is too long: '%s' (%Rrc)", pszAdjName, rc); + } + + /* + * Clean up and break out on failure. + */ + RTVfsObjRelease(hVfsObj); + RTStrFree(pszName); + if (rcExit != RTEXITCODE_SUCCESS) + break; + } + + /* + * Check that what we just extracted matches the already verified + * manifest. + */ + if (rcExit == RTEXITCODE_SUCCESS) + { + char szError[RTPATH_MAX]; + rc = RTManifestEqualsEx(hUnpackManifest, hValidManifest, NULL /*papszIgnoreEntries*/, NULL /*papszIgnoreAttr*/, + 0 /*fFlags*/, szError, sizeof(szError)); + if (RT_SUCCESS(rc)) + rcExit = RTEXITCODE_SUCCESS; + else if (rc == VERR_NOT_EQUAL && szError[0]) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Manifest mismatch: %s", szError); + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTManifestEqualsEx failed: %Rrc", rc); + } +#if 0 + RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM; + RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut); + RTVfsIoStrmWrite(hVfsIosStdOut, "Unpack:\n", sizeof("Unpack:\n") - 1, true, NULL); + RTManifestWriteStandard(hUnpackManifest, hVfsIosStdOut); + RTVfsIoStrmWrite(hVfsIosStdOut, "Valid:\n", sizeof("Valid:\n") - 1, true, NULL); + RTManifestWriteStandard(hValidManifest, hVfsIosStdOut); +#endif + RTManifestRelease(hUnpackManifest); + } + RTVfsFsStrmRelease(hTarFss); + + return rcExit; +} + + + +/** + * Wrapper around VBoxExtPackValidateTarball. + * + * @returns The program exit code. + * @param hTarballFile The handle to open the @a pszTarball file. + * @param pszExtPackName The name of the extension pack name. + * @param pszTarball The name of the tarball in case we have to + * complain about something. + * @param pszTarballDigest The SHA-256 digest of the tarball. + * @param phValidManifest Where to return the handle to fully validated + * the manifest for the extension pack. This + * includes all files. + */ +static RTEXITCODE ValidateExtPackTarball(RTFILE hTarballFile, const char *pszExtPackName, const char *pszTarball, + const char *pszTarballDigest, PRTMANIFEST phValidManifest) +{ + *phValidManifest = NIL_RTMANIFEST; + RTMsgInfo("Validating extension pack '%s' ('%s')...", pszTarball, pszExtPackName); + Assert(pszTarballDigest && *pszTarballDigest); + + char szError[8192]; + int rc = VBoxExtPackValidateTarball(hTarballFile, pszExtPackName, pszTarball, pszTarballDigest, + szError, sizeof(szError), phValidManifest, NULL /*phXmlFile*/, NULL /*pStrDigest*/); + if (RT_FAILURE(rc)) + { + Assert(szError[0]); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError); + } + Assert(!szError[0]); + return RTEXITCODE_SUCCESS; +} + + +/** + * The 2nd part of the installation process. + * + * @returns The program exit code. + * @param pszBaseDir The base directory. + * @param pszCertDir The certificat directory. + * @param pszTarball The tarball name. + * @param pszTarballDigest The SHA-256 digest of the tarball. Empty string + * if no digest available. + * @param hTarballFile The handle to open the @a pszTarball file. + * @param hTarballFileOpt The tarball file handle (optional). + * @param pszName The extension pack name. + * @param pszMangledName The mangled extension pack name. + * @param fReplace Whether to replace any existing ext pack. + */ +static RTEXITCODE DoInstall2(const char *pszBaseDir, const char *pszCertDir, const char *pszTarball, + const char *pszTarballDigest, RTFILE hTarballFile, RTFILE hTarballFileOpt, + const char *pszName, const char *pszMangledName, bool fReplace) +{ + RT_NOREF1(pszCertDir); + + /* + * Do some basic validation of the tarball file. + */ + RTFSOBJINFO ObjInfo; + int rc = RTFileQueryInfo(hTarballFile, &ObjInfo, RTFSOBJATTRADD_UNIX); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on '%s'", rc, pszTarball); + if (!RTFS_IS_FILE(ObjInfo.Attr.fMode)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Not a regular file: %s", pszTarball); + + if (hTarballFileOpt != NIL_RTFILE) + { + RTFSOBJINFO ObjInfo2; + rc = RTFileQueryInfo(hTarballFileOpt, &ObjInfo2, RTFSOBJATTRADD_UNIX); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on --tarball-fd", rc); + if ( ObjInfo.Attr.u.Unix.INodeIdDevice != ObjInfo2.Attr.u.Unix.INodeIdDevice + || ObjInfo.Attr.u.Unix.INodeId != ObjInfo2.Attr.u.Unix.INodeId) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "--tarball and --tarball-fd does not match"); + } + + /* + * Construct the paths to the two directories we'll be using. + */ + char szFinalPath[RTPATH_MAX]; + rc = RTPathJoin(szFinalPath, sizeof(szFinalPath), pszBaseDir, pszMangledName); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to construct the path to the final extension pack directory: %Rrc", rc); + + char szTmpPath[RTPATH_MAX]; + rc = RTPathJoin(szTmpPath, sizeof(szTmpPath) - 64, pszBaseDir, pszMangledName); + if (RT_SUCCESS(rc)) + { + size_t cchTmpPath = strlen(szTmpPath); + RTStrPrintf(&szTmpPath[cchTmpPath], sizeof(szTmpPath) - cchTmpPath, "-_-inst-%u", (uint32_t)RTProcSelf()); + } + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to construct the path to the temporary extension pack directory: %Rrc", rc); + + /* + * Check that they don't exist at this point in time, unless fReplace=true. + */ + rc = RTPathQueryInfoEx(szFinalPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + { + if (!fReplace) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "The extension pack is already installed. You must uninstall the old one first."); + } + else if (RT_SUCCESS(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Found non-directory file system object where the extension pack would be installed ('%s')", + szFinalPath); + else if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath); + + rc = RTPathQueryInfoEx(szTmpPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath); + + /* + * Create the temporary directory and prepare the extension pack within it. + * If all checks out correctly, rename it to the final directory. + */ + RTDirCreate(pszBaseDir, 0755, 0); +#ifndef RT_OS_WINDOWS + /* + * Because of umask, we have to apply the mode again. + */ + rc = RTPathSetMode(pszBaseDir, 0755); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions on '%s': %Rrc", pszBaseDir, rc); +#else + /** @todo Ownership tricks on windows? */ +#endif + rc = RTDirCreate(szTmpPath, 0700, 0); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary directory: %Rrc ('%s')", rc, szTmpPath); + + RTMANIFEST hValidManifest = NIL_RTMANIFEST; + RTEXITCODE rcExit = ValidateExtPackTarball(hTarballFile, pszName, pszTarball, pszTarballDigest, &hValidManifest); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = UnpackExtPack(hTarballFile, szTmpPath, hValidManifest, pszTarball); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = ValidateUnpackedExtPack(szTmpPath, pszTarball, pszName); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = SetExtPackPermissions(szTmpPath); + RTManifestRelease(hValidManifest); + + if (rcExit == RTEXITCODE_SUCCESS) + { + rc = CommonDirRenameWrapper(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE); + if ( RT_FAILURE(rc) + && fReplace + && RTDirExists(szFinalPath)) + { + /* Automatic uninstall if --replace was given. */ + rcExit = CommonUninstallWorker(szFinalPath); + if (rcExit == RTEXITCODE_SUCCESS) + rc = CommonDirRenameWrapper(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE); + } + if (RT_SUCCESS(rc)) + RTMsgInfo("Successfully installed '%s' (%s)", pszName, pszTarball); + else if (rcExit == RTEXITCODE_SUCCESS) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to rename the temporary directory to the final one: %Rrc ('%s' -> '%s')", + rc, szTmpPath, szFinalPath); + } + + /* + * Clean up the temporary directory on failure. + */ + if (rcExit != RTEXITCODE_SUCCESS) + RemoveExtPackDir(szTmpPath, true /*fTemporary*/); + + return rcExit; +} + + +/** + * Implements the 'install' command. + * + * @returns The program exit code. + * @param argc The number of program arguments. + * @param argv The program arguments. + */ +static RTEXITCODE DoInstall(int argc, char **argv) +{ + /* + * Parse the parameters. + * + * Note! The --base-dir and --cert-dir are only for checking that the + * caller and this help applications have the same idea of where + * things are. Likewise, the --name is for verifying assumptions + * the caller made about the name. The optional --tarball-fd option + * is just for easing the paranoia on the user side. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--base-dir", 'b', RTGETOPT_REQ_STRING }, + { "--cert-dir", 'c', RTGETOPT_REQ_STRING }, + { "--name", 'n', RTGETOPT_REQ_STRING }, + { "--tarball", 't', RTGETOPT_REQ_STRING }, + { "--tarball-fd", 'd', RTGETOPT_REQ_UINT64 }, + { "--replace", 'r', RTGETOPT_REQ_NOTHING }, + { "--sha-256", 's', RTGETOPT_REQ_STRING } + }; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc); + + const char *pszBaseDir = NULL; + const char *pszCertDir = NULL; + const char *pszName = NULL; + const char *pszTarball = NULL; + const char *pszTarballDigest = NULL; + RTFILE hTarballFileOpt = NIL_RTFILE; + bool fReplace = false; + RTGETOPTUNION ValueUnion; + int ch; + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'b': + if (pszBaseDir) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options"); + pszBaseDir = ValueUnion.psz; + if (!IsValidBaseDir(pszBaseDir)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir); + break; + + case 'c': + if (pszCertDir) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --cert-dir options"); + pszCertDir = ValueUnion.psz; + if (!IsValidCertificateDir(pszCertDir)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid certificate directory: '%s'", pszCertDir); + break; + + case 'n': + if (pszName) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options"); + pszName = ValueUnion.psz; + if (!VBoxExtPackIsValidName(pszName)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName); + break; + + case 't': + if (pszTarball) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball options"); + pszTarball = ValueUnion.psz; + break; + + case 'd': + { + if (hTarballFileOpt != NIL_RTFILE) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball-fd options"); + RTHCUINTPTR hNative = (RTHCUINTPTR)ValueUnion.u64; + if (hNative != ValueUnion.u64) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --tarball-fd value is out of range: %#RX64", ValueUnion.u64); + rc = RTFileFromNative(&hTarballFileOpt, hNative); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTFileFromNative failed on --target-fd value: %Rrc", rc); + break; + } + + case 'r': + fReplace = true; + break; + + case 's': + { + if (pszTarballDigest) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --sha-256 options"); + pszTarballDigest = ValueUnion.psz; + + uint8_t abDigest[RTSHA256_HASH_SIZE]; + rc = RTSha256FromString(pszTarballDigest, abDigest); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Bad SHA-256 string: %Rrc", rc); + break; + } + + case 'h': + case 'V': + return DoStandardOption(ch); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + if (!pszName) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option"); + if (!pszBaseDir) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option"); + if (!pszCertDir) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --cert-dir option"); + if (!pszTarball) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --tarball option"); + if (!pszTarballDigest) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --sha-256 option"); + + /* + * Ok, down to business. + */ + RTCString *pstrMangledName = VBoxExtPackMangleName(pszName); + if (!pstrMangledName) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName); + + RTEXITCODE rcExit; + RTFILE hTarballFile; + rc = RTFileOpen(&hTarballFile, pszTarball, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + rcExit = DoInstall2(pszBaseDir, pszCertDir, pszTarball, pszTarballDigest, hTarballFile, hTarballFileOpt, + pszName, pstrMangledName->c_str(), fReplace); + RTFileClose(hTarballFile); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open the extension pack tarball: %Rrc ('%s')", rc, pszTarball); + + delete pstrMangledName; + return rcExit; +} + + +/** + * Implements the 'uninstall' command. + * + * @returns The program exit code. + * @param argc The number of program arguments. + * @param argv The program arguments. + */ +static RTEXITCODE DoUninstall(int argc, char **argv) +{ + /* + * Parse the parameters. + * + * Note! The --base-dir is only for checking that the caller and this help + * applications have the same idea of where things are. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--base-dir", 'b', RTGETOPT_REQ_STRING }, + { "--name", 'n', RTGETOPT_REQ_STRING }, + { "--forced", 'f', RTGETOPT_REQ_NOTHING }, + }; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc); + + const char *pszBaseDir = NULL; + const char *pszName = NULL; + RTGETOPTUNION ValueUnion; + int ch; + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'b': + if (pszBaseDir) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options"); + pszBaseDir = ValueUnion.psz; + if (!IsValidBaseDir(pszBaseDir)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir); + break; + + case 'n': + if (pszName) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options"); + pszName = ValueUnion.psz; + if (!VBoxExtPackIsValidName(pszName)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName); + break; + + case 'f': + /* ignored */ + break; + + case 'h': + case 'V': + return DoStandardOption(ch); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + if (!pszName) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option"); + if (!pszBaseDir) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option"); + + /* + * Mangle the name so we can construct the directory names. + */ + RTCString *pstrMangledName = VBoxExtPackMangleName(pszName); + if (!pstrMangledName) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName); + RTCString strMangledName(*pstrMangledName); + delete pstrMangledName; + + /* + * Ok, down to business. + */ + /* Check that it exists. */ + char szExtPackDir[RTPATH_MAX]; + rc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), pszBaseDir, strMangledName.c_str()); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct extension pack path: %Rrc", rc); + + if (!RTDirExists(szExtPackDir)) + { + RTMsgInfo("Extension pack not installed. Nothing to do."); + return RTEXITCODE_SUCCESS; + } + + RTEXITCODE rcExit = CommonUninstallWorker(szExtPackDir); + if (rcExit == RTEXITCODE_SUCCESS) + RTMsgInfo("Successfully removed extension pack '%s'\n", pszName); + + return rcExit; +} + +/** + * Implements the 'cleanup' command. + * + * @returns The program exit code. + * @param argc The number of program arguments. + * @param argv The program arguments. + */ +static RTEXITCODE DoCleanup(int argc, char **argv) +{ + /* + * Parse the parameters. + * + * Note! The --base-dir is only for checking that the caller and this help + * applications have the same idea of where things are. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--base-dir", 'b', RTGETOPT_REQ_STRING }, + }; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc); + + const char *pszBaseDir = NULL; + RTGETOPTUNION ValueUnion; + int ch; + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'b': + if (pszBaseDir) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options"); + pszBaseDir = ValueUnion.psz; + if (!IsValidBaseDir(pszBaseDir)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir); + break; + + case 'h': + case 'V': + return DoStandardOption(ch); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + if (!pszBaseDir) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option"); + + /* + * Ok, down to business. + */ + RTDIR hDir; + rc = RTDirOpen(&hDir, pszBaseDir); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed open the base directory: %Rrc ('%s')", rc, pszBaseDir); + + uint32_t cCleaned = 0; + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + for (;;) + { + RTDIRENTRYEX Entry; + rc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_FAILURE(rc)) + { + if (rc != VERR_NO_MORE_FILES) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirReadEx returns %Rrc", rc); + break; + } + + /* + * Only directories which conform with our temporary install/uninstall + * naming scheme are candidates for cleaning. + */ + if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode) + && strcmp(Entry.szName, ".") != 0 + && strcmp(Entry.szName, "..") != 0) + { + bool fCandidate = false; + char *pszMarker = strstr(Entry.szName, "-_-"); + if ( pszMarker + && ( !strcmp(pszMarker, "-_-uninst") + || !strncmp(pszMarker, RT_STR_TUPLE("-_-inst")))) + fCandidate = VBoxExtPackIsValidMangledName(Entry.szName, pszMarker - &Entry.szName[0]); + if (fCandidate) + { + /* + * Recursive delete, safe. + */ + char szPath[RTPATH_MAX]; + rc = RTPathJoin(szPath, sizeof(szPath), pszBaseDir, Entry.szName); + if (RT_SUCCESS(rc)) + { + RTEXITCODE rcExit2 = RemoveExtPackDir(szPath, true /*fTemporary*/); + if (rcExit2 == RTEXITCODE_SUCCESS) + RTMsgInfo("Successfully removed '%s'.", Entry.szName); + else if (rcExit == RTEXITCODE_SUCCESS) + rcExit = rcExit2; + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathJoin failed with %Rrc for '%s'", rc, Entry.szName); + cCleaned++; + } + } + } + RTDirClose(hDir); + if (!cCleaned) + RTMsgInfo("Nothing to clean."); + return rcExit; +} + +#ifdef WITH_ELEVATION + +#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_DARWIN) +/** + * Looks in standard locations for a suitable exec tool. + * + * @returns true if found, false if not. + * @param pszPath Where to store the path to the tool on + * successs. + * @param cbPath The size of the buffer @a pszPath points to. + * @param pszName The name of the tool we're looking for. + */ +static bool FindExecTool(char *pszPath, size_t cbPath, const char *pszName) +{ + static const char * const s_apszPaths[] = + { + "/bin", + "/usr/bin", + "/usr/local/bin", + "/sbin", + "/usr/sbin", + "/usr/local/sbin", +#ifdef RT_OS_SOLARIS + "/usr/sfw/bin", + "/usr/gnu/bin", + "/usr/xpg4/bin", + "/usr/xpg6/bin", + "/usr/openwin/bin", + "/usr/ucb" +#endif + }; + + for (unsigned i = 0; i < RT_ELEMENTS(s_apszPaths); i++) + { + int rc = RTPathJoin(pszPath, cbPath, s_apszPaths[i], pszName); + if (RT_SUCCESS(rc)) + { + RTFSOBJINFO ObjInfo; + rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK); + if (RT_SUCCESS(rc)) + { + if (!(ObjInfo.Attr.fMode & RTFS_UNIX_IWOTH)) + return true; + } + } + } + return false; +} +#endif + + +/** + * Copies the content of a file to a stream. + * + * @param hSrc The source file. + * @param pDst The destination stream. + * @param fComplain Whether to complain about errors (i.e. is this + * stderr, if not keep the trap shut because it + * may be missing when running under VBoxSVC.) + */ +static void CopyFileToStdXxx(RTFILE hSrc, PRTSTREAM pDst, bool fComplain) +{ + int rc; + for (;;) + { + char abBuf[0x1000]; + size_t cbRead; + rc = RTFileRead(hSrc, abBuf, sizeof(abBuf), &cbRead); + if (RT_FAILURE(rc)) + { + RTMsgError("RTFileRead failed: %Rrc", rc); + break; + } + if (!cbRead) + break; + rc = RTStrmWrite(pDst, abBuf, cbRead); + if (RT_FAILURE(rc)) + { + if (fComplain) + RTMsgError("RTStrmWrite failed: %Rrc", rc); + break; + } + } + rc = RTStrmFlush(pDst); + if (RT_FAILURE(rc) && fComplain) + RTMsgError("RTStrmFlush failed: %Rrc", rc); +} + + +/** + * Relaunches ourselves as a elevated process using platform specific facilities. + * + * @returns Program exit code. + * @param pszExecPath The executable path. + * @param papszArgs The arguments. + * @param cSuArgs The number of argument entries reserved for the + * 'su' like programs at the start of papszArgs. + * @param cMyArgs The number of arguments following @a cSuArgs. + * @param iCmd The command that is being executed. (For + * selecting messages.) + * @param pszDisplayInfoHack Display information hack. Platform specific++. + */ +static RTEXITCODE RelaunchElevatedNative(const char *pszExecPath, const char **papszArgs, int cSuArgs, int cMyArgs, + int iCmd, const char *pszDisplayInfoHack) +{ + RT_NOREF1(cMyArgs); + RTEXITCODE rcExit = RTEXITCODE_FAILURE; +#ifdef RT_OS_WINDOWS + NOREF(iCmd); + + MSG Msg; + PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE); + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + + SHELLEXECUTEINFOW Info; + + Info.cbSize = sizeof(Info); + Info.fMask = SEE_MASK_NOCLOSEPROCESS; + Info.hwnd = NULL; + Info.lpVerb = L"runas"; + int rc = RTStrToUtf16(pszExecPath, (PRTUTF16 *)&Info.lpFile); + if (RT_SUCCESS(rc)) + { + char *pszCmdLine; + rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs + 1], RTGETOPTARGV_CNV_QUOTE_MS_CRT); + if (RT_SUCCESS(rc)) + { + rc = RTStrToUtf16(pszCmdLine, (PRTUTF16 *)&Info.lpParameters); + if (RT_SUCCESS(rc)) + { + Info.lpDirectory = NULL; + Info.nShow = SW_SHOWMAXIMIZED; + Info.hInstApp = NULL; + Info.lpIDList = NULL; + Info.lpClass = NULL; + Info.hkeyClass = NULL; + Info.dwHotKey = 0; + Info.hMonitor = NULL; + Info.hProcess = INVALID_HANDLE_VALUE; + + /* Apply display hacks. */ + if (pszDisplayInfoHack) + { + const char *pszArg = strstr(pszDisplayInfoHack, "hwnd="); + if (pszArg) + { + uint64_t u64Hwnd; + rc = RTStrToUInt64Ex(pszArg + sizeof("hwnd=") - 1, NULL, 0, &u64Hwnd); + if (RT_SUCCESS(rc)) + { + HWND hwnd = (HWND)(uintptr_t)u64Hwnd; + Info.hwnd = hwnd; + Info.hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + } + } + } + if (Info.hMonitor == NULL) + { + POINT Pt = {0,0}; + Info.hMonitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY); + } + if (Info.hMonitor != NULL) + Info.fMask |= SEE_MASK_HMONITOR; + + if (ShellExecuteExW(&Info)) + { + if (Info.hProcess != INVALID_HANDLE_VALUE) + { + /* + * Wait for the process, make sure the deal with messages. + */ + for (;;) + { + DWORD dwRc = MsgWaitForMultipleObjects(1, &Info.hProcess, FALSE, 5000/*ms*/, QS_ALLEVENTS); + if (dwRc == WAIT_OBJECT_0) + break; + if ( dwRc != WAIT_TIMEOUT + && dwRc != WAIT_OBJECT_0 + 1) + { + RTMsgError("MsgWaitForMultipleObjects returned: %#x (%d), err=%u", dwRc, dwRc, GetLastError()); + break; + } + while (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&Msg); + DispatchMessageW(&Msg); + } + } + + DWORD dwExitCode; + if (GetExitCodeProcess(Info.hProcess, &dwExitCode)) + { + if (dwExitCode < 128) + rcExit = (RTEXITCODE)dwExitCode; + else + rcExit = RTEXITCODE_FAILURE; + } + CloseHandle(Info.hProcess); + } + else + RTMsgError("ShellExecuteExW return INVALID_HANDLE_VALUE as Info.hProcess"); + } + else + RTMsgError("ShellExecuteExW failed: %u (%#x)", GetLastError(), GetLastError()); + + + RTUtf16Free((PRTUTF16)Info.lpParameters); + } + RTStrFree(pszCmdLine); + } + + RTUtf16Free((PRTUTF16)Info.lpFile); + } + else + RTMsgError("RTStrToUtf16 failed: %Rc", rc); + +#elif defined(RT_OS_DARWIN) + RT_NOREF(pszDisplayInfoHack); + char szIconName[RTPATH_MAX]; + int rc = RTPathAppPrivateArch(szIconName, sizeof(szIconName)); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szIconName, sizeof(szIconName), "../Resources/virtualbox.png"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct icon path: %Rrc", rc); + + AuthorizationRef AuthRef; + OSStatus orc = AuthorizationCreate(NULL, 0, kAuthorizationFlagDefaults, &AuthRef); + if (orc == errAuthorizationSuccess) + { + /* + * Preautorize the privileged execution of ourselves. + */ + AuthorizationItem AuthItem = { kAuthorizationRightExecute, 0, NULL, 0 }; + AuthorizationRights AuthRights = { 1, &AuthItem }; + + NOREF(iCmd); + static char s_szPrompt[] = "VirtualBox needs further rights to make changes to your installation.\n\n"; + AuthorizationItem aAuthEnvItems[] = + { + { kAuthorizationEnvironmentPrompt, strlen(s_szPrompt), s_szPrompt, 0 }, + { kAuthorizationEnvironmentIcon, strlen(szIconName), szIconName, 0 } + }; + AuthorizationEnvironment AuthEnv = { RT_ELEMENTS(aAuthEnvItems), aAuthEnvItems }; + + orc = AuthorizationCopyRights(AuthRef, &AuthRights, &AuthEnv, + kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed + | kAuthorizationFlagExtendRights, + NULL); + if (orc == errAuthorizationSuccess) + { + /* + * Execute with extra permissions + */ + FILE *pSocketStrm; +#if defined(__clang__) || RT_GNUC_PREREQ(4, 4) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + orc = AuthorizationExecuteWithPrivileges(AuthRef, pszExecPath, kAuthorizationFlagDefaults, + (char * const *)&papszArgs[cSuArgs + 3], + &pSocketStrm); +#if defined(__clang__) || RT_GNUC_PREREQ(4, 4) +# pragma GCC diagnostic pop +#endif + if (orc == errAuthorizationSuccess) + { + /* + * Read the output of the tool, the read will fail when it quits. + */ + for (;;) + { + char achBuf[1024]; + size_t cbRead = fread(achBuf, 1, sizeof(achBuf), pSocketStrm); + if (!cbRead) + break; + fwrite(achBuf, 1, cbRead, stdout); + } + rcExit = RTEXITCODE_SUCCESS; + fclose(pSocketStrm); + } + else + RTMsgError("AuthorizationExecuteWithPrivileges failed: %d", orc); + } + else if (orc == errAuthorizationCanceled) + RTMsgError("Authorization canceled by the user"); + else + RTMsgError("AuthorizationCopyRights failed: %d", orc); + AuthorizationFree(AuthRef, kAuthorizationFlagDefaults); + } + else + RTMsgError("AuthorizationCreate failed: %d", orc); + +#else + + RT_NOREF2(pszExecPath, pszDisplayInfoHack); + + /* + * Several of the alternatives below will require a command line. + */ + char *pszCmdLine; + int rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs], RTGETOPTARGV_CNV_QUOTE_BOURNE_SH); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvToString failed: %Rrc", rc); + + /* + * Look for various standard stuff for executing a program as root. + * + * N.B. When adding new arguments, please make 100% sure RelaunchElevated + * allocates enough array entries. + * + * TODO: Feel free to contribute code for using PolicyKit directly. + */ + bool fHaveDisplayVar = RTEnvExist("DISPLAY"); + int iSuArg = cSuArgs; + char szExecTool[260]; + char szXterm[260]; + + /* + * kdesudo is available on KDE3/KDE4 + */ + if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "kdesudo")) + { + iSuArg = cSuArgs - 4; + papszArgs[cSuArgs - 4] = szExecTool; + papszArgs[cSuArgs - 3] = "--comment"; + papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL + ? "VirtualBox extension pack installer" + : iCmd == CMD_UNINSTALL + ? "VirtualBox extension pack uninstaller" + : "VirtualBox extension pack maintainer"; + papszArgs[cSuArgs - 1] = "--"; + } + /* + * gksu is our favorite as it is very well integrated. + */ + else if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "gksu")) + { +#if 0 /* older gksu does not grok --description nor '--' and multiple args. */ + iSuArg = cSuArgs - 4; + papszArgs[cSuArgs - 4] = szExecTool; + papszArgs[cSuArgs - 3] = "--description"; + papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL + ? "VirtualBox extension pack installer" + : iCmd == CMD_UNINSTALL + ? "VirtualBox extension pack uninstaller" + : "VirtualBox extension pack maintainer"; + papszArgs[cSuArgs - 1] = "--"; +#elif defined(RT_OS_SOLARIS) /* Force it not to use pfexec as it won't wait then. */ + iSuArg = cSuArgs - 4; + papszArgs[cSuArgs - 4] = szExecTool; + papszArgs[cSuArgs - 3] = "-au"; + papszArgs[cSuArgs - 2] = "root"; + papszArgs[cSuArgs - 1] = pszCmdLine; + papszArgs[cSuArgs] = NULL; +#else + iSuArg = cSuArgs - 2; + papszArgs[cSuArgs - 2] = szExecTool; + papszArgs[cSuArgs - 1] = pszCmdLine; + papszArgs[cSuArgs] = NULL; +#endif + } + /* + * pkexec may work for ssh console sessions as well if the right agents + * are installed. However it is very generic and does not allow for any + * custom messages. Thus it comes after gksu. + */ + else if (FindExecTool(szExecTool, sizeof(szExecTool), "pkexec")) + { + iSuArg = cSuArgs - 1; + papszArgs[cSuArgs - 1] = szExecTool; + } + /* + * The ultimate fallback is running 'su -' within an xterm. We use the + * title of the xterm to tell what is going on. + */ + else if ( fHaveDisplayVar + && FindExecTool(szExecTool, sizeof(szExecTool), "su") + && FindExecTool(szXterm, sizeof(szXterm), "xterm")) + { + iSuArg = cSuArgs - 9; + papszArgs[cSuArgs - 9] = szXterm; + papszArgs[cSuArgs - 8] = "-T"; + papszArgs[cSuArgs - 7] = iCmd == CMD_INSTALL + ? "VirtualBox extension pack installer - su" + : iCmd == CMD_UNINSTALL + ? "VirtualBox extension pack uninstaller - su" + : "VirtualBox extension pack maintainer - su"; + papszArgs[cSuArgs - 6] = "-e"; + papszArgs[cSuArgs - 5] = szExecTool; + papszArgs[cSuArgs - 4] = "-"; + papszArgs[cSuArgs - 3] = "root"; + papszArgs[cSuArgs - 2] = "-c"; + papszArgs[cSuArgs - 1] = pszCmdLine; + papszArgs[cSuArgs] = NULL; + } + else if (fHaveDisplayVar) + RTMsgError("Unable to locate 'pkexec', 'gksu' or 'su+xterm'. Try perform the operation using VBoxManage running as root"); + else + RTMsgError("Unable to locate 'pkexec'. Try perform the operation using VBoxManage running as root"); + if (iSuArg != cSuArgs) + { + AssertRelease(iSuArg >= 0); + + /* + * Argument list constructed, execute it and wait for the exec + * program to complete. + */ + RTPROCESS hProcess; + rc = RTProcCreateEx(papszArgs[iSuArg], &papszArgs[iSuArg], RTENV_DEFAULT, 0 /*fFlags*/, NULL /*phStdIn*/, + NULL /*phStdOut*/, NULL /*phStdErr*/, NULL /*pszAsUser*/, NULL /*pszPassword*/, NULL /* pvExtraData*/, + &hProcess); + if (RT_SUCCESS(rc)) + { + RTPROCSTATUS Status; + rc = RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status); + if (RT_SUCCESS(rc)) + { + if (Status.enmReason == RTPROCEXITREASON_NORMAL) + rcExit = (RTEXITCODE)Status.iStatus; + else + rcExit = RTEXITCODE_FAILURE; + } + else + RTMsgError("Error while waiting for '%s': %Rrc", papszArgs[iSuArg], rc); + } + else + RTMsgError("Failed to execute '%s': %Rrc", papszArgs[iSuArg], rc); + } + RTStrFree(pszCmdLine); + +#endif + return rcExit; +} + + +/** + * Relaunches ourselves as a elevated process using platform specific facilities. + * + * @returns Program exit code. + * @param argc The number of arguments. + * @param argv The arguments. + * @param iCmd The command that is being executed. + * @param pszDisplayInfoHack Display information hack. Platform specific++. + */ +static RTEXITCODE RelaunchElevated(int argc, char **argv, int iCmd, const char *pszDisplayInfoHack) +{ + /* + * We need the executable name later, so get it now when it's easy to quit. + */ + char szExecPath[RTPATH_MAX]; + if (!RTProcGetExecutablePath(szExecPath,sizeof(szExecPath))) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed"); + + /* + * Create a couple of temporary files for stderr and stdout. + */ + char szTempDir[RTPATH_MAX - sizeof("/stderr")]; + int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathTemp failed: %Rrc", rc); + rc = RTPathAppend(szTempDir, sizeof(szTempDir), "VBoxExtPackHelper-XXXXXX"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAppend failed: %Rrc", rc); + rc = RTDirCreateTemp(szTempDir, 0700); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirCreateTemp failed: %Rrc", rc); + + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + char szStdOut[RTPATH_MAX]; + char szStdErr[RTPATH_MAX]; + rc = RTPathJoin(szStdOut, sizeof(szStdOut), szTempDir, "stdout"); + if (RT_SUCCESS(rc)) + rc = RTPathJoin(szStdErr, sizeof(szStdErr), szTempDir, "stderr"); + if (RT_SUCCESS(rc)) + { + RTFILE hStdOut; + rc = RTFileOpen(&hStdOut, szStdOut, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE + | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + RTFILE hStdErr; + rc = RTFileOpen(&hStdErr, szStdErr, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE + | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + /* + * Insert the --elevated and stdout/err names into the argument + * list. Note that darwin skips the --stdout bit, so don't + * change the order here. + */ + int const cSuArgs = 12; + int cArgs = argc + 5 + 1; + char const **papszArgs = (char const **)RTMemTmpAllocZ((cSuArgs + cArgs + 1) * sizeof(const char *)); + if (papszArgs) + { + int iDst = cSuArgs; + papszArgs[iDst++] = argv[0]; + papszArgs[iDst++] = "--stdout"; + papszArgs[iDst++] = szStdOut; + papszArgs[iDst++] = "--stderr"; + papszArgs[iDst++] = szStdErr; + papszArgs[iDst++] = "--elevated"; + for (int iSrc = 1; iSrc <= argc; iSrc++) + papszArgs[iDst++] = argv[iSrc]; + + /* + * Do the platform specific process execution (waiting included). + */ + rcExit = RelaunchElevatedNative(szExecPath, papszArgs, cSuArgs, cArgs, iCmd, pszDisplayInfoHack); + + /* + * Copy the standard files to our standard handles. + */ + CopyFileToStdXxx(hStdErr, g_pStdErr, true /*fComplain*/); + CopyFileToStdXxx(hStdOut, g_pStdOut, false); + + RTMemTmpFree(papszArgs); + } + + RTFileClose(hStdErr); + RTFileDelete(szStdErr); + } + RTFileClose(hStdOut); + RTFileDelete(szStdOut); + } + } + RTDirRemove(szTempDir); + + return rcExit; +} + + +/** + * Checks if the process is elevated or not. + * + * @returns RTEXITCODE_SUCCESS if preconditions are fine, + * otherwise error message + RTEXITCODE_FAILURE. + * @param pfElevated Where to store the elevation indicator. + */ +static RTEXITCODE ElevationCheck(bool *pfElevated) +{ + *pfElevated = false; + +# if defined(RT_OS_WINDOWS) + /** @todo This should probably check if UAC is diabled and if we are + * Administrator first. Also needs to check for Vista+ first, probably. + */ + DWORD cb; + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + HANDLE hToken; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "OpenProcessToken failed: %u (%#x)", GetLastError(), GetLastError()); + + /* + * Check if we're member of the Administrators group. If we aren't, there + * is no way to elevate ourselves to system admin. + * N.B. CheckTokenMembership does not do the job here (due to attributes?). + */ + BOOL fIsAdmin = FALSE; + SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; + PSID pAdminGrpSid; + if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminGrpSid)) + { +# ifdef DEBUG + char *pszAdminGrpSid = NULL; + ConvertSidToStringSid(pAdminGrpSid, &pszAdminGrpSid); +# endif + + if ( !GetTokenInformation(hToken, TokenGroups, NULL, 0, &cb) + && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + PTOKEN_GROUPS pTokenGroups = (PTOKEN_GROUPS)RTMemAllocZ(cb); + if (GetTokenInformation(hToken, TokenGroups, pTokenGroups, cb, &cb)) + { + for (DWORD iGrp = 0; iGrp < pTokenGroups->GroupCount; iGrp++) + { +# ifdef DEBUG + char *pszGrpSid = NULL; + ConvertSidToStringSid(pTokenGroups->Groups[iGrp].Sid, &pszGrpSid); +# endif + if (EqualSid(pAdminGrpSid, pTokenGroups->Groups[iGrp].Sid)) + { + /* That it's listed is enough I think, ignore attributes. */ + fIsAdmin = TRUE; + break; + } + } + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,cb) failed: %u (%#x)", GetLastError(), GetLastError()); + RTMemFree(pTokenGroups); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,0) failed: %u (%#x)", GetLastError(), GetLastError()); + + FreeSid(pAdminGrpSid); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "AllocateAndInitializeSid failed: %u (%#x)", GetLastError(), GetLastError()); + if (fIsAdmin) + { + /* + * Check the integrity level (Vista / UAC). + */ +# define MY_SECURITY_MANDATORY_HIGH_RID 0x00003000L +# define MY_TokenIntegrityLevel ((TOKEN_INFORMATION_CLASS)25) + if ( !GetTokenInformation(hToken, MY_TokenIntegrityLevel, NULL, 0, &cb) + && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + PSID_AND_ATTRIBUTES pSidAndAttr = (PSID_AND_ATTRIBUTES)RTMemAlloc(cb); + if (GetTokenInformation(hToken, MY_TokenIntegrityLevel, pSidAndAttr, cb, &cb)) + { + DWORD dwIntegrityLevel = *GetSidSubAuthority(pSidAndAttr->Sid, *GetSidSubAuthorityCount(pSidAndAttr->Sid) - 1U); + + if (dwIntegrityLevel >= MY_SECURITY_MANDATORY_HIGH_RID) + *pfElevated = true; + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError()); + RTMemFree(pSidAndAttr); + } + else if ( GetLastError() == ERROR_INVALID_PARAMETER + || GetLastError() == ERROR_NOT_SUPPORTED) + *pfElevated = true; /* Older Windows version. */ + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError()); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Membership in the Administrators group is required to perform this action"); + + CloseHandle(hToken); + return rcExit; + +# else + /* + * On Unixy systems, we check if the executable and the current user is + * the same. This heuristic works fine for both hardened and development + * builds. + */ + char szExecPath[RTPATH_MAX]; + if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)) == NULL) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed"); + + RTFSOBJINFO ObjInfo; + int rc = RTPathQueryInfoEx(szExecPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathQueryInfoEx failed"); + + *pfElevated = ObjInfo.Attr.u.Unix.uid == geteuid() + || ObjInfo.Attr.u.Unix.uid == getuid(); + return RTEXITCODE_SUCCESS; +# endif +} + +#endif /* WITH_ELEVATION */ + +int main(int argc, char **argv) +{ + /* + * Initialize IPRT and check that we're correctly installed. + */ +#ifdef RT_OS_WINDOWS + int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_UTF8_ARGV); /* WinMain gives us UTF-8, see below. */ +#else + int rc = RTR3InitExe(argc, &argv, 0); +#endif + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + SUPR3HardenedVerifyInit(); + RTERRINFOSTATIC ErrInfo; + RTErrInfoInitStatic(&ErrInfo); + rc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, &ErrInfo.Core); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", ErrInfo.Core.pszMsg); + + /* + * Elevation check. + */ + const char *pszDisplayInfoHack = NULL; + RTEXITCODE rcExit; +#ifdef WITH_ELEVATION + bool fElevated; + rcExit = ElevationCheck(&fElevated); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; +#endif + + /* + * Parse the top level arguments until we find a command. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "install", CMD_INSTALL, RTGETOPT_REQ_NOTHING }, + { "uninstall", CMD_UNINSTALL, RTGETOPT_REQ_NOTHING }, + { "cleanup", CMD_CLEANUP, RTGETOPT_REQ_NOTHING }, +#ifdef WITH_ELEVATION + { "--elevated", OPT_ELEVATED, RTGETOPT_REQ_NOTHING }, + { "--stdout", OPT_STDOUT, RTGETOPT_REQ_STRING }, + { "--stderr", OPT_STDERR, RTGETOPT_REQ_STRING }, +#endif + { "--display-info-hack", OPT_DISP_INFO_HACK, RTGETOPT_REQ_STRING }, + }; + RTGETOPTSTATE GetState; + rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc); + for (;;) + { + RTGETOPTUNION ValueUnion; + int ch = RTGetOpt(&GetState, &ValueUnion); + switch (ch) + { + case 0: + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified"); + + case CMD_INSTALL: + case CMD_UNINSTALL: + case CMD_CLEANUP: + { +#ifdef WITH_ELEVATION + if (!fElevated) + return RelaunchElevated(argc, argv, ch, pszDisplayInfoHack); +#endif + int cCmdargs = argc - GetState.iNext; + char **papszCmdArgs = argv + GetState.iNext; + switch (ch) + { + case CMD_INSTALL: + rcExit = DoInstall( cCmdargs, papszCmdArgs); + break; + case CMD_UNINSTALL: + rcExit = DoUninstall(cCmdargs, papszCmdArgs); + break; + case CMD_CLEANUP: + rcExit = DoCleanup( cCmdargs, papszCmdArgs); + break; + default: + AssertReleaseFailedReturn(RTEXITCODE_FAILURE); + } + + /* + * Standard error should end with rcExit=RTEXITCODE_SUCCESS on + * success since the exit code may otherwise get lost in the + * process elevation fun. + */ + RTStrmFlush(g_pStdOut); + RTStrmFlush(g_pStdErr); + switch (rcExit) + { + case RTEXITCODE_SUCCESS: + RTStrmPrintf(g_pStdErr, "rcExit=RTEXITCODE_SUCCESS\n"); + break; + default: + RTStrmPrintf(g_pStdErr, "rcExit=%d\n", rcExit); + break; + } + RTStrmFlush(g_pStdErr); + RTStrmFlush(g_pStdOut); + return rcExit; + } + +#ifdef WITH_ELEVATION + case OPT_ELEVATED: + fElevated = true; + break; + + case OPT_STDERR: + case OPT_STDOUT: + { +# ifdef RT_OS_WINDOWS + PRTUTF16 pwszName = NULL; + rc = RTStrToUtf16(ValueUnion.psz, &pwszName); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error converting '%s' to UTF-16: %Rrc\n", ValueUnion.psz, rc); + FILE *pFile = _wfreopen(pwszName, L"r+", ch == OPT_STDOUT ? stdout : stderr); + RTUtf16Free(pwszName); +# else + FILE *pFile = freopen(ValueUnion.psz, "r+", ch == OPT_STDOUT ? stdout : stderr); +# endif + if (!pFile) + { + rc = RTErrConvertFromErrno(errno); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "freopen on '%s': %Rrc", ValueUnion.psz, rc); + } + break; + } +#endif + + case OPT_DISP_INFO_HACK: + if (pszDisplayInfoHack) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "--display-info-hack shall only occur once"); + pszDisplayInfoHack = ValueUnion.psz; + break; + + case 'h': + case 'V': + return DoStandardOption(ch); + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + /* not currently reached */ + } + /* not reached */ +} + + +#ifdef RT_OS_WINDOWS +extern "C" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +{ + g_hInstance = hInstance; + NOREF(hPrevInstance); NOREF(nShowCmd); NOREF(lpCmdLine); + + int rc = RTR3InitExeNoArguments(0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + LPWSTR pwszCmdLine = GetCommandLineW(); + if (!pwszCmdLine) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "GetCommandLineW failed"); + + char *pszCmdLine; + rc = RTUtf16ToUtf8(pwszCmdLine, &pszCmdLine); /* leaked */ + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to convert the command line: %Rrc", rc); + + int cArgs; + char **papszArgs; + rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszCmdLine, RTGETOPTARGV_CNV_QUOTE_MS_CRT, NULL); + if (RT_SUCCESS(rc)) + { + + rc = main(cArgs, papszArgs); + + RTGetOptArgvFree(papszArgs); + } + else + rc = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvFromString failed: %Rrc", rc); + RTStrFree(pszCmdLine); + + return rc; +} +#endif + |