summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-helper-apps
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-helper-apps')
-rw-r--r--src/VBox/Main/src-helper-apps/Makefile.kup0
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk86
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp103
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp514
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp172
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc51
-rw-r--r--src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp2009
-rw-r--r--src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc51
-rw-r--r--src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp97
9 files changed, 3083 insertions, 0 deletions
diff --git a/src/VBox/Main/src-helper-apps/Makefile.kup b/src/VBox/Main/src-helper-apps/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/Makefile.kup
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk b/src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk
new file mode 100644
index 00000000..0b78da5c
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk
@@ -0,0 +1,86 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the OpenGLTest helper app.
+#
+
+#
+# Copyright (C) 2008-2020 Oracle Corporation
+#
+# This file is part of VirtualBox Open Source Edition (OSE), as
+# available from http://www.virtualbox.org. This file is free software;
+# you can redistribute it and/or modify it under the terms of the GNU
+# General Public License (GPL) as published by the Free Software
+# Foundation, in version 2 as it comes in the "COPYING" file of the
+# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+
+#
+# Target lists.
+#
+LIBRARIES += VBoxOGLTest
+VBoxOGLTest_TEMPLATE = VBOXR3NP
+ifneq ($(KBUILD_TARGET),darwin)
+ VBoxOGLTest_SOURCES = OpenGLTest.cpp
+endif
+VBoxOGLTest_SOURCES.darwin = OpenGLTestDarwin.cpp
+
+#
+# VBoxTestOGL - OpenGL support test app.
+# Note! Doesn't link with VBOX_WITH_DEBUG_VCC_CRT defined because it uses Qt.
+#
+if ( defined(VBOX_WITH_QTGUI) \
+ && (defined(VBOX_WITH_VMSVGA3D) || defined(VBOX_WITH_VIDEOHWACCEL)) \
+ && !defined(VBOX_WITH_DEBUG_VCC_CRT))
+ ifneq ($(KBUILD_TARGET),darwin)
+ ifdef VBOX_WITH_VIDEOHWACCEL
+ USES += qt5
+ endif
+ PROGRAMS += VBoxTestOGL
+ VBoxTestOGL_TEMPLATE = $(if $(VBOX_WITH_VIDEOHWACCEL),$(if $(VBOX_WITH_HARDENING),VBOXQTGUI,VBOXQTGUIEXE),VBOXMAINEXE)
+ VBoxTestOGL_DEFS.win = _WIN32_WINNT=0x0500 WINDOWS=1
+ VBoxTestOGL_DEFS.linux = Linux=1 _GNU_SOURCE
+ VBoxTestOGL_DEFS.solaris = SunOS=1 _GNU_SOURCE #GLEXT_64_TYPES_DEFINED
+ VBoxTestOGL_DEFS.darwin = DARWIN=1 GL_GLEXT_LEGACY _GNU_SOURCE
+ VBoxTestOGL_DEFS.freebsd = FreeBSD=1 _GNU_SOURCE
+ VBoxTestOGL_SOURCES = OpenGLTestApp.cpp
+ VBoxTestOGL_SOURCES.win = VBoxTestOGL.rc
+ VBoxTestOGL_LIBS = \
+ $(if $(VBOX_WITH_VIDEOHWACCEL), $(PATH_STAGE_LIB)/VBoxOGL2D$(VBOX_SUFF_LIB),) \
+ $(LIB_RUNTIME)
+ VBoxTestOGL_DEFS += \
+ VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\" \
+ $(if $(VBOX_WITH_VIDEOHWACCEL), VBOX_WITH_VIDEOHWACCEL,)
+ ifdef VBOX_WITH_VIDEOHWACCEL
+ VBoxTestOGL_QT_MODULES += Core Gui OpenGL Widgets
+ VBoxTestOGL_LIBS.linux += xcb
+ VBoxTestOGL_LIBS.solaris += xcb
+ VBoxTestOGL_LIBS.freebsd += xcb
+ VBoxTestOGL_LDFLAGS.darwin += -framework OpenGL -framework IOKit
+ VBoxTestOGL_LIBS.win += $(PATH_SDK_$(VBOX_WINPSDK)_LIB)/Opengl32.lib
+ if1of ($(KBUILD_TARGET), solaris linux freebsd)
+ # must come after VBoxOGL2D, therefore don't set the arch-specific LIBS variable here!
+ VBoxTestOGL_LIBS += GL pthread dl
+ endif
+ endif
+ if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) # the X11 gang
+ VBoxTestOGL_LIBS += \
+ X11 \
+ Xext
+ VBoxTestOGL_LIBPATH = \
+ $(VBOX_LIBPATH_X11)
+endif
+ # Don't let ld strip out explicitly linked libraries even when they are not needed.
+ # This was causing some dynamic library loading problems in case of indirect dependencies
+ # in systems where RUNPATH instead of RPATH is utilized.
+ VBoxTestOGL_LDFLAGS.linux = -Wl,--no-as-needed
+ VBoxTestOGL_LDFLAGS.win = /SUBSYSTEM:windows
+ endif
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp
new file mode 100644
index 00000000..0322ff91
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp
@@ -0,0 +1,103 @@
+/* $Id: OpenGLTest.cpp $ */
+/** @file
+ * VBox host opengl support test - generic implementation.
+ */
+
+/*
+ * Copyright (C) 2009-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#include <VBox/err.h>
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/env.h>
+#include <iprt/log.h>
+
+#include <VBox/VBoxOGL.h>
+
+bool RTCALL VBoxOglIs3DAccelerationSupported(void)
+{
+ if (RTEnvExist("VBOX_3D_FORCE_SUPPORTED"))
+ {
+ LogRel(("VBOX_3D_FORCE_SUPPORTED is specified, skipping 3D test, and treating as supported\n"));
+ return true;
+ }
+
+ static char pszVBoxPath[RTPATH_MAX];
+ const char *papszArgs[4] = { NULL, "-test", "3D", NULL};
+ int rc;
+ RTPROCESS Process;
+ RTPROCSTATUS ProcStatus;
+ uint64_t StartTS;
+
+#ifdef __SANITIZE_ADDRESS__
+ /* The OpenGL test tool contains a number of memory leaks which cause it to
+ * return failure when run with ASAN unless we disable the leak detector. */
+ RTENV env;
+ if (RT_FAILURE(RTEnvClone(&env, RTENV_DEFAULT)))
+ return false;
+ RTEnvPutEx(env, "ASAN_OPTIONS=detect_leaks=0"); /* If this fails we will notice later */
+#endif
+ rc = RTPathExecDir(pszVBoxPath, RTPATH_MAX); AssertRCReturn(rc, false);
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ rc = RTPathAppend(pszVBoxPath, RTPATH_MAX, "VBoxTestOGL.exe");
+#else
+ rc = RTPathAppend(pszVBoxPath, RTPATH_MAX, "VBoxTestOGL");
+#endif
+ papszArgs[0] = pszVBoxPath; /* argv[0] */
+ AssertRCReturn(rc, false);
+
+#ifndef __SANITIZE_ADDRESS__
+ rc = RTProcCreate(pszVBoxPath, papszArgs, RTENV_DEFAULT, 0, &Process);
+#else
+ rc = RTProcCreate(pszVBoxPath, papszArgs, env, 0, &Process);
+ RTEnvDestroy(env);
+#endif
+ if (RT_FAILURE(rc))
+ return false;
+
+ StartTS = RTTimeMilliTS();
+
+ while (1)
+ {
+ rc = RTProcWait(Process, RTPROCWAIT_FLAGS_NOBLOCK, &ProcStatus);
+ if (rc != VERR_PROCESS_RUNNING)
+ break;
+
+#ifndef DEBUG_misha
+ if (RTTimeMilliTS() - StartTS > 30*1000 /* 30 sec */)
+ {
+ RTProcTerminate(Process);
+ RTThreadSleep(100);
+ RTProcWait(Process, RTPROCWAIT_FLAGS_NOBLOCK, &ProcStatus);
+ return false;
+ }
+#endif
+ RTThreadSleep(100);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if ((ProcStatus.enmReason==RTPROCEXITREASON_NORMAL) && (ProcStatus.iStatus==0))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp
new file mode 100644
index 00000000..90b3df08
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp
@@ -0,0 +1,514 @@
+/* $Id: OpenGLTestApp.cpp $ */
+/** @file
+ * VBox host opengl support test application.
+ */
+
+/*
+ * Copyright (C) 2009-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/stream.h>
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+# include <sys/resource.h>
+# include <fcntl.h>
+# include <unistd.h>
+#endif
+
+#include <string.h>
+
+#define VBOXGLTEST_WITH_LOGGING
+
+#ifdef VBOXGLTEST_WITH_LOGGING
+#include "package-generated.h"
+
+#include <iprt/log.h>
+#include <iprt/param.h>
+#include <iprt/time.h>
+#include <iprt/system.h>
+#include <iprt/process.h>
+#include <iprt/env.h>
+
+#include <VBox/log.h>
+#include <VBox/version.h>
+#endif
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+#include <QGLWidget>
+#include <QApplication>
+#include <VBox/VBoxGL2D.h>
+#endif
+
+/**
+ * The OpenGL methods to look for when checking 3D presence.
+ */
+static const char * const g_apszOglMethods[] =
+{
+#ifdef RT_OS_WINDOWS
+ "wglCreateContext",
+ "wglDeleteContext",
+ "wglMakeCurrent",
+ "wglShareLists",
+#elif defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) || defined(RT_OS_SOLARIS)
+ "glXQueryVersion",
+ "glXChooseVisual",
+ "glXCreateContext",
+ "glXMakeCurrent",
+ "glXDestroyContext",
+#endif
+ "glAlphaFunc",
+ "glBindTexture",
+ "glBlendFunc",
+ "glClear",
+ "glClearColor",
+ "glClearDepth",
+ "glClearStencil",
+ "glClipPlane",
+ "glColorMask",
+ "glColorPointer",
+ "glCullFace",
+ "glDeleteTextures",
+ "glDepthFunc",
+ "glDepthMask",
+ "glDepthRange",
+ "glDisable",
+ "glDisableClientState",
+ "glDrawArrays",
+ "glDrawElements",
+ "glEnable",
+ "glEnableClientState",
+ "glFogf",
+ "glFogfv",
+ "glFogi",
+ "glFrontFace",
+ "glGenTextures",
+ "glGetBooleanv",
+ "glGetError",
+ "glGetFloatv",
+ "glGetIntegerv",
+ "glGetString",
+ "glGetTexImage",
+ "glLightModelfv",
+ "glLightf",
+ "glLightfv",
+ "glLineWidth",
+ "glLoadIdentity",
+ "glLoadMatrixf",
+ "glMaterialfv",
+ "glMatrixMode",
+ "glMultMatrixf",
+ "glNormalPointer",
+ "glPixelStorei",
+ "glPointSize",
+ "glPolygonMode",
+ "glPolygonOffset",
+ "glPopAttrib",
+ "glPopMatrix",
+ "glPushAttrib",
+ "glPushMatrix",
+ "glScissor",
+ "glShadeModel",
+ "glStencilFunc",
+ "glStencilMask",
+ "glStencilOp",
+ "glTexCoordPointer",
+ "glTexImage2D",
+ "glTexParameterf",
+ "glTexParameterfv",
+ "glTexParameteri",
+ "glTexSubImage2D",
+ "glVertexPointer",
+ "glViewport"
+};
+
+
+/**
+ * Tries to resolve the given OpenGL symbol.
+ *
+ * @returns Pointer to the symbol or nULL on error.
+ * @param pszSymbol The symbol to resolve.
+ */
+DECLINLINE(PFNRT) vboxTestOglGetProc(const char *pszSymbol)
+{
+ int rc;
+
+#ifdef RT_OS_WINDOWS
+ static RTLDRMOD s_hOpenGL32 = NULL;
+ if (s_hOpenGL32 == NULL)
+ {
+ rc = RTLdrLoadSystem("opengl32", /* fNoUnload = */ true, &s_hOpenGL32);
+ if (RT_FAILURE(rc))
+ s_hOpenGL32 = NULL;
+ }
+
+ typedef PROC (WINAPI *PFNWGLGETPROCADDRESS)(LPCSTR);
+ static PFNWGLGETPROCADDRESS s_wglGetProcAddress = NULL;
+ if (s_wglGetProcAddress == NULL)
+ {
+ if (s_hOpenGL32 != NULL)
+ {
+ rc = RTLdrGetSymbol(s_hOpenGL32, "wglGetProcAddress", (void **)&s_wglGetProcAddress);
+ if (RT_FAILURE(rc))
+ s_wglGetProcAddress = NULL;
+ }
+ }
+
+ if (s_wglGetProcAddress)
+ {
+ /* Khronos: [on failure] "some implementations will return other values. 1, 2, and 3 are used, as well as -1". */
+ PFNRT p = (PFNRT)s_wglGetProcAddress(pszSymbol);
+ if (RT_VALID_PTR(p))
+ return p;
+
+ /* Might be an exported symbol. */
+ rc = RTLdrGetSymbol(s_hOpenGL32, pszSymbol, (void **)&p);
+ if (RT_SUCCESS(rc))
+ return p;
+ }
+#else /* The X11 gang */
+ static RTLDRMOD s_hGL = NULL;
+ if (s_hGL == NULL)
+ {
+ static const char s_szLibGL[] = "libGL.so.1";
+ rc = RTLdrLoadEx(s_szLibGL, &s_hGL, RTLDRLOAD_FLAGS_GLOBAL | RTLDRLOAD_FLAGS_NO_UNLOAD, NULL);
+ if (RT_FAILURE(rc))
+ {
+ s_hGL = NULL;
+ return NULL;
+ }
+ }
+
+ typedef PFNRT (* PFNGLXGETPROCADDRESS)(const GLubyte * procName);
+ static PFNGLXGETPROCADDRESS s_glXGetProcAddress = NULL;
+ if (s_glXGetProcAddress == NULL)
+ {
+ rc = RTLdrGetSymbol(s_hGL, "glXGetProcAddress", (void **)&s_glXGetProcAddress);
+ if (RT_FAILURE(rc))
+ {
+ s_glXGetProcAddress = NULL;
+ return NULL;
+ }
+ }
+
+ PFNRT p = s_glXGetProcAddress((const GLubyte *)pszSymbol);
+ if (RT_VALID_PTR(p))
+ return p;
+
+ /* Might be an exported symbol. */
+ rc = RTLdrGetSymbol(s_hGL, pszSymbol, (void **)&p);
+ if (RT_SUCCESS(rc))
+ return p;
+#endif
+
+ return NULL;
+}
+
+static int vboxCheck3DAccelerationSupported()
+{
+ LogRel(("Testing 3D Support:\n"));
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_apszOglMethods); i++)
+ {
+ PFNRT pfn = vboxTestOglGetProc(g_apszOglMethods[i]);
+ if (!pfn)
+ {
+ LogRel(("Testing 3D Failed\n"));
+ return 1;
+ }
+ }
+
+ LogRel(("Testing 3D Succeeded!\n"));
+ return 0;
+}
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+static int vboxCheck2DVideoAccelerationSupported()
+{
+ LogRel(("Testing 2D Support:\n"));
+ static int dummyArgc = 1;
+ static char * dummyArgv = (char*)"GlTest";
+ QApplication app (dummyArgc, &dummyArgv);
+
+ VBoxGLTmpContext ctx;
+ const QGLContext *pContext = ctx.makeCurrent();
+ if(pContext)
+ {
+ VBoxVHWAInfo supportInfo;
+ supportInfo.init(pContext);
+ if(supportInfo.isVHWASupported())
+ {
+ LogRel(("Testing 2D Succeeded!\n"));
+ return 0;
+ }
+ }
+ else
+ {
+ LogRel(("Failed to create gl context\n"));
+ }
+ LogRel(("Testing 2D Failed\n"));
+ return 1;
+}
+#endif
+
+#ifdef VBOXGLTEST_WITH_LOGGING
+static int vboxInitLogging(const char *pszFilename, bool bGenNameSuffix)
+{
+ PRTLOGGER loggerRelease;
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ RTUINT fFlags = RTLOGFLAGS_PREFIX_TIME_PROG;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ fFlags |= RTLOGFLAGS_USECRLF;
+#endif
+ const char * pszFilenameFmt;
+ RTLOGDEST enmLogDest;
+ if(pszFilename)
+ {
+ if(bGenNameSuffix)
+ pszFilenameFmt = "%s.%ld.log";
+ else
+ pszFilenameFmt = "%s";
+ enmLogDest = RTLOGDEST_FILE;
+ }
+ else
+ {
+ pszFilenameFmt = NULL;
+ enmLogDest = RTLOGDEST_STDOUT;
+ }
+
+ int vrc = RTLogCreateEx(&loggerRelease, fFlags, "all",
+ "VBOX_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX, enmLogDest,
+ NULL /* pfnBeginEnd */, 0 /* cHistory */, 0 /* cbHistoryFileMax */, 0 /* uHistoryTimeMax */,
+ NULL /* pErrInfo */, pszFilenameFmt, pszFilename, RTTimeMilliTS());
+ if (RT_SUCCESS(vrc))
+ {
+ /* some introductory information */
+ RTTIMESPEC timeSpec;
+ char szTmp[256];
+ RTTimeSpecToString(RTTimeNow(&timeSpec), szTmp, sizeof(szTmp));
+ RTLogRelLogger(loggerRelease, 0, ~0U,
+ "VBoxTestGL %s r%u %s (%s %s) release log\n"
+#ifdef VBOX_BLEEDING_EDGE
+ "EXPERIMENTAL build " VBOX_BLEEDING_EDGE "\n"
+#endif
+ "Log opened %s\n",
+ VBOX_VERSION_STRING, RTBldCfgRevision(), VBOX_BUILD_TARGET,
+ __DATE__, __TIME__, szTmp);
+
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ RTLogRelLogger(loggerRelease, 0, ~0U, "OS Product: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ RTLogRelLogger(loggerRelease, 0, ~0U, "OS Release: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ RTLogRelLogger(loggerRelease, 0, ~0U, "OS Version: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ RTLogRelLogger(loggerRelease, 0, ~0U, "OS Service Pack: %s\n", szTmp);
+// RTLogRelLogger(loggerRelease, 0, ~0U, "Host RAM: %uMB RAM, available: %uMB\n",
+// uHostRamMb, uHostRamAvailMb);
+ /* the package type is interesting for Linux distributions */
+ char szExecName[RTPATH_MAX];
+ char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName));
+ RTLogRelLogger(loggerRelease, 0, ~0U,
+ "Executable: %s\n"
+ "Process ID: %u\n"
+ "Package type: %s"
+#ifdef VBOX_OSE
+ " (OSE)"
+#endif
+ "\n",
+ pszExecName ? pszExecName : "unknown",
+ RTProcSelf(),
+ VBOX_PACKAGE_STRING);
+
+ /* register this logger as the release logger */
+ RTLogRelSetDefaultInstance(loggerRelease);
+
+ return VINF_SUCCESS;
+ }
+
+ return vrc;
+}
+#endif
+
+static int vboxInitQuietMode()
+{
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ /* This small test application might crash on some hosts. Do never
+ * generate a core dump as most likely some OpenGL library is
+ * responsible. */
+ struct rlimit lim = { 0, 0 };
+ setrlimit(RLIMIT_CORE, &lim);
+
+ /* Redirect stderr to /dev/null */
+ int fd = open("/dev/null", O_WRONLY);
+ if (fd != -1)
+ dup2(fd, STDERR_FILENO);
+#endif
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int rc = 0;
+
+ RTR3InitExe(argc, &argv, 0);
+
+ if(argc < 2)
+ {
+ /* backwards compatibility: check 3D */
+ rc = vboxCheck3DAccelerationSupported();
+ }
+ else
+ {
+ static const RTGETOPTDEF s_aOptionDefs[] =
+ {
+ { "--test", 't', RTGETOPT_REQ_STRING },
+ { "-test", 't', RTGETOPT_REQ_STRING },
+#ifdef VBOXGLTEST_WITH_LOGGING
+ { "--log", 'l', RTGETOPT_REQ_STRING },
+#endif
+ };
+
+ RTGETOPTSTATE State;
+ rc = RTGetOptInit(&State, argc-1, argv+1, &s_aOptionDefs[0], RT_ELEMENTS(s_aOptionDefs), 0, 0);
+ AssertRCReturn(rc, 49);
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ bool bTest2D = false;
+#endif
+ bool bTest3D = false;
+#ifdef VBOXGLTEST_WITH_LOGGING
+ bool bLog = false;
+ bool bLogSuffix = false;
+ const char * pLog = NULL;
+#endif
+
+ for (;;)
+ {
+ RTGETOPTUNION Val;
+ rc = RTGetOpt(&State, &Val);
+ if (!rc)
+ break;
+ switch (rc)
+ {
+ case 't':
+ if (!strcmp(Val.psz, "3D") || !strcmp(Val.psz, "3d"))
+ {
+ bTest3D = true;
+ rc = 0;
+ break;
+ }
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ if (!strcmp(Val.psz, "2D") || !strcmp(Val.psz, "2d"))
+ {
+ bTest2D = true;
+ rc = 0;
+ break;
+ }
+#endif
+ rc = 1;
+ break;
+#ifdef VBOXGLTEST_WITH_LOGGING
+ case 'l':
+ bLog = true;
+ pLog = Val.psz;
+ rc = 0;
+ break;
+#endif
+ case 'h':
+ RTPrintf(VBOX_PRODUCT " Helper for testing 2D/3D OpenGL capabilities %u.%u.%u\n"
+ "(C) 2009-" VBOX_C_YEAR " " VBOX_VENDOR "\n"
+ "All rights reserved.\n"
+ "\n"
+ "Parameters:\n"
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ " --test 2D test for 2D (video) OpenGL capabilities\n"
+#endif
+ " --test 3D test for 3D OpenGL capabilities\n"
+#ifdef VBOXGLTEST_WITH_LOGGING
+ " --log <log_file_name> log the GL test result to the given file\n"
+ "\n"
+ "Logging can alternatively be enabled by specifying the VBOXGLTEST_LOG=<log_file_name> env variable\n"
+
+#endif
+ "\n",
+ RTBldCfgVersionMajor(), RTBldCfgVersionMinor(), RTBldCfgVersionBuild());
+ break;
+
+ case 'V':
+ RTPrintf("$Revision: 135976 $\n");
+ return 0;
+
+ case VERR_GETOPT_UNKNOWN_OPTION:
+ case VINF_GETOPT_NOT_OPTION:
+ rc = 1;
+
+ default:
+ /* complain? RTGetOptPrintError(rc, &Val); */
+ break;
+ }
+
+ if (rc)
+ break;
+ }
+
+ if(!rc)
+ {
+#ifdef VBOXGLTEST_WITH_LOGGING
+ if(!bLog)
+ {
+ /* check the VBOXGLTEST_LOG env var */
+ pLog = RTEnvGet("VBOXGLTEST_LOG");
+ if(pLog)
+ bLog = true;
+ bLogSuffix = true;
+ }
+ if(bLog)
+ rc = vboxInitLogging(pLog, bLogSuffix);
+ else
+#endif
+ rc = vboxInitQuietMode();
+
+ if(!rc && bTest3D)
+ rc = vboxCheck3DAccelerationSupported();
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+ if(!rc && bTest2D)
+ rc = vboxCheck2DVideoAccelerationSupported();
+#endif
+
+ }
+ }
+
+ /*RTR3Term();*/
+ return rc;
+
+}
+
+#ifdef RT_OS_WINDOWS
+extern "C" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
+{
+ RT_NOREF(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
+ return main(__argc, __argv);
+}
+#endif
+
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp
new file mode 100644
index 00000000..86bbc2af
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp
@@ -0,0 +1,172 @@
+/* $Id: OpenGLTestDarwin.cpp $ */
+/** @file
+ * VBox host opengl support test
+ */
+
+/*
+ * Copyright (C) 2009-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/VBoxOGL.h>
+
+#include <IOKit/IOKitLib.h>
+#include <OpenGL/OpenGL.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include <OpenGL/gl.h>
+#include <OpenGL/glu.h>
+
+#include <iprt/env.h>
+#include <iprt/log.h>
+#include <iprt/once.h>
+
+
+
+/**
+ * @callback_method_impl{FNRTONCE,
+ * For determining the cached VBoxOglIsOfflineRenderingAppropriate result.}
+ */
+static DECLCALLBACK(int32_t) vboxOglIsOfflineRenderingAppropriateOnce(void *pvUser)
+{
+ bool *pfAppropriate = (bool *)pvUser;
+
+ /* It is assumed that it is makes sense to enable offline rendering
+ only in case if host has more than one GPU installed. This routine
+ counts all the PCI devices in IORegistry which have IOName property
+ set to "display". If the number of such devices is greater than one,
+ it sets pfAppropriate to TRUE, otherwise to FALSE. */
+
+ CFStringRef apKeyStrings[] = { CFSTR(kIOProviderClassKey), CFSTR(kIONameMatchKey) };
+ CFStringRef apValueStrings[] = { CFSTR("IOPCIDevice"), CFSTR("display") };
+ Assert(RT_ELEMENTS(apKeyStrings) == RT_ELEMENTS(apValueStrings));
+
+ CFDictionaryRef pMatchingDictionary = CFDictionaryCreate(kCFAllocatorDefault,
+ (const void **)apKeyStrings,
+ (const void **)apValueStrings,
+ RT_ELEMENTS(apKeyStrings),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (pMatchingDictionary)
+ {
+ /* The reference to pMatchingDictionary is consumed by the function below => no IORelease(pMatchingDictionary)! */
+ io_iterator_t matchingServices;
+ kern_return_t krc = IOServiceGetMatchingServices(kIOMasterPortDefault, pMatchingDictionary, &matchingServices);
+ if (krc == kIOReturnSuccess)
+ {
+ io_object_t matchingService;
+ int cMatchingServices = 0;
+
+ while ((matchingService = IOIteratorNext(matchingServices)) != 0)
+ {
+ cMatchingServices++;
+ IOObjectRelease(matchingService);
+ }
+
+ *pfAppropriate = cMatchingServices > 1;
+
+ IOObjectRelease(matchingServices);
+ }
+ }
+
+ LogRel(("OpenGL: Offline rendering support is %s (pid=%d)\n", *pfAppropriate ? "ON" : "OFF", (int)getpid()));
+ return VINF_SUCCESS;
+}
+
+
+bool RTCALL VBoxOglIsOfflineRenderingAppropriate(void)
+{
+ /* In order to do not slowdown 3D engine which can ask about offline rendering several times,
+ let's cache the result and assume that renderers amount value is constant. Use the IPRT
+ execute once construct to make sure there aren't any threading issues. */
+ static RTONCE s_Once = RTONCE_INITIALIZER;
+ static bool s_fCached = false;
+ int rc = RTOnce(&s_Once, vboxOglIsOfflineRenderingAppropriateOnce, &s_fCached);
+ AssertRC(rc);
+ return s_fCached;
+}
+
+
+bool RTCALL VBoxOglIs3DAccelerationSupported(void)
+{
+ if (RTEnvExist("VBOX_3D_FORCE_SUPPORTED"))
+ {
+ LogRel(("VBOX_3D_FORCE_SUPPORTED is specified, skipping 3D test, and treating as supported\n"));
+ return true;
+ }
+
+ CGOpenGLDisplayMask cglDisplayMask = CGDisplayIDToOpenGLDisplayMask(CGMainDisplayID());
+ CGLPixelFormatAttribute aAttribs[] =
+ {
+ kCGLPFADisplayMask,
+ (CGLPixelFormatAttribute)cglDisplayMask,
+ kCGLPFAAccelerated,
+ kCGLPFADoubleBuffer,
+ VBoxOglIsOfflineRenderingAppropriate() ? kCGLPFAAllowOfflineRenderers : (CGLPixelFormatAttribute)NULL,
+ (CGLPixelFormatAttribute)NULL
+ };
+ CGLPixelFormatObj pPixelFormat = NULL;
+ GLint cPixelFormatsIgnored = 0;
+ CGLError rcCgl = CGLChoosePixelFormat(aAttribs, &pPixelFormat, &cPixelFormatsIgnored);
+ if (rcCgl != kCGLNoError)
+ {
+ LogRel(("OpenGL Info: 3D test unable to choose pixel format (rcCgl=0x%X)\n", rcCgl));
+ return false;
+ }
+
+ if (pPixelFormat)
+ {
+ CGLContextObj pCglContext = 0;
+ rcCgl = CGLCreateContext(pPixelFormat, NULL, &pCglContext);
+ CGLDestroyPixelFormat(pPixelFormat);
+
+ if (rcCgl != kCGLNoError)
+ {
+ LogRel(("OpenGL Info: 3D test unable to create context (rcCgl=0x%X)\n", rcCgl));
+ return false;
+ }
+
+ if (pCglContext)
+ {
+ GLboolean isSupported = GL_TRUE;
+
+ /*
+ * In the Cocoa port we depend on the GL_EXT_framebuffer_object &
+ * the GL_EXT_texture_rectangle extension. If they are not
+ * available, disable 3D support.
+ */
+ CGLSetCurrentContext(pCglContext);
+ const GLubyte *pszExts = glGetString(GL_EXTENSIONS);
+ isSupported = gluCheckExtension((const GLubyte *)"GL_EXT_framebuffer_object", pszExts);
+ if (isSupported)
+ {
+ isSupported = gluCheckExtension((const GLubyte *)"GL_EXT_texture_rectangle", pszExts);
+ if (!isSupported)
+ LogRel(("OpenGL Info: 3D test found that GL_EXT_texture_rectangle extension not supported.\n"));
+ }
+ else
+ LogRel(("OpenGL Info: 3D test found that GL_EXT_framebuffer_object extension not supported.\n"));
+
+ CGLDestroyContext(pCglContext);
+ LogRel(("OpenGL Info: 3D test %spassed\n", isSupported == GL_TRUE ? "" : "not "));
+ return isSupported == GL_TRUE;
+ }
+
+ LogRel(("OpenGL Info: 3D test unable to create context (internal error).\n"));
+ }
+ else
+ LogRel(("OpenGL Info: 3D test unable to choose pixel format (internal error).\n"));
+
+ return false;
+}
+
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc
new file mode 100644
index 00000000..6a737fdf
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc
@@ -0,0 +1,51 @@
+/* $Id: VBoxTestOGL.rc $ */
+/** @file
+ * VBoxTestOGL - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2015-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox OpenGL Test Tool\0"
+ VALUE "InternalName", "VBoxTestOGL\0"
+ VALUE "OriginalFilename", "VBoxTestOGL.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
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..cce46324
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp
@@ -0,0 +1,2009 @@
+/* $Id: VBoxExtPackHelperApp.cpp $ */
+/** @file
+ * VirtualBox Main - Extension Pack Helper Application, usually set-uid-to-root.
+ */
+
+/*
+ * Copyright (C) 2010-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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"
+ "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
+ "All rights reserved.\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;
+ }
+ MSG Msg;
+ 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
+
diff --git a/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc
new file mode 100644
index 00000000..dc9776dd
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc
@@ -0,0 +1,51 @@
+/* $Id: VBoxExtPackHelperApp.rc $ */
+/** @file
+ * VBoxExtPackHelperApp - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2015-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox ExtPack Helper\0"
+ VALUE "InternalName", "VBoxExtPackHelperApp\0"
+ VALUE "OriginalFilename", "VBoxExtPackHelperApp.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp b/src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp
new file mode 100644
index 00000000..d1958f19
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp
@@ -0,0 +1,97 @@
+/* $Id: VBoxVolInfo.cpp $ */
+/** @file
+ * Apps - VBoxVolInfo, Volume information tool.
+ */
+
+/*
+ * Copyright (C) 2012-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <dirent.h>
+extern "C"
+{
+#define private privatekw
+#include <libdevmapper.h>
+}
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+/*********************************************************************************************************************************
+* Function Prototypes *
+*********************************************************************************************************************************/
+void print_dev_name(dev_t devid);
+
+/*
+ * Extracts logical volume dependencies via devmapper API and print them out.
+ */
+int main(int argc, char **argv)
+{
+ struct dm_task *dmtask;
+ struct dm_info dminfo;
+
+ if (argc != 2)
+ {
+ fprintf(stderr, "USAGE: %s <volume_name>\n", argv[0]);
+ return 1;
+ }
+
+ dmtask = dm_task_create(DM_DEVICE_DEPS);
+ if (!dmtask)
+ return 2;
+
+ if (dm_task_set_name(dmtask, argv[1]))
+ if (dm_task_run(dmtask))
+ if (dm_task_get_info(dmtask, &dminfo))
+ {
+ struct dm_deps *dmdeps = dm_task_get_deps(dmtask);
+ if (dmdeps)
+ {
+ unsigned i;
+ for (i = 0; i < dmdeps->count; ++i)
+ print_dev_name(dmdeps->device[i]);
+ }
+ }
+
+ dm_task_destroy(dmtask);
+ return 0;
+}
+
+/*
+ * Looks up device name by id using /dev directory. Prints it to stdout.
+ */
+void print_dev_name(dev_t devid)
+{
+ char path[PATH_MAX];
+ struct dirent *de;
+ DIR *dir = opendir("/dev");
+
+ while ((de = readdir(dir)) != NULL)
+ {
+ struct stat st;
+ snprintf(path, sizeof(path), "/dev/%s", de->d_name);
+ if (!stat(path, &st))
+ if (S_ISBLK(st.st_mode))
+ if (devid == st.st_rdev)
+ {
+ puts(de->d_name);
+ break;
+ }
+ }
+ closedir(dir);
+}