diff options
Diffstat (limited to 'src/VBox/Main/src-helper-apps')
-rw-r--r-- | src/VBox/Main/src-helper-apps/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk | 86 | ||||
-rw-r--r-- | src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp | 103 | ||||
-rw-r--r-- | src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp | 514 | ||||
-rw-r--r-- | src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp | 172 | ||||
-rw-r--r-- | src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc | 51 | ||||
-rw-r--r-- | src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp | 2009 | ||||
-rw-r--r-- | src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc | 51 | ||||
-rw-r--r-- | src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp | 97 |
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); +} |