From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Main/src-helper-apps/Makefile.kup | 0 .../Main/src-helper-apps/OpenGLTest/Makefile.kmk | 107 ++ .../Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp | 109 ++ .../src-helper-apps/OpenGLTest/OpenGLTestApp.cpp | 526 +++++ .../OpenGLTest/OpenGLTestDarwin.cpp | 183 ++ .../OpenGLTest/VBoxFBOverlayCommon.h | 131 ++ .../OpenGLTest/VBoxGLSupportInfo.cpp | 724 +++++++ .../Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc | 61 + .../Main/src-helper-apps/VBoxExtPackHelperApp.cpp | 2016 ++++++++++++++++++++ .../Main/src-helper-apps/VBoxExtPackHelperApp.rc | 61 + src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp | 107 ++ src/VBox/Main/src-helper-apps/os2/Makefile.kmk | 66 + src/VBox/Main/src-helper-apps/os2/os2_util.c | 1031 ++++++++++ src/VBox/Main/src-helper-apps/os2/os2_utilA.asm | 54 + 14 files changed, 5176 insertions(+) create mode 100644 src/VBox/Main/src-helper-apps/Makefile.kup create mode 100644 src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk create mode 100644 src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp create mode 100644 src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp create mode 100644 src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp create mode 100644 src/VBox/Main/src-helper-apps/OpenGLTest/VBoxFBOverlayCommon.h create mode 100644 src/VBox/Main/src-helper-apps/OpenGLTest/VBoxGLSupportInfo.cpp create mode 100644 src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc create mode 100644 src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp create mode 100644 src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc create mode 100644 src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp create mode 100644 src/VBox/Main/src-helper-apps/os2/Makefile.kmk create mode 100644 src/VBox/Main/src-helper-apps/os2/os2_util.c create mode 100644 src/VBox/Main/src-helper-apps/os2/os2_utilA.asm (limited to 'src/VBox/Main/src-helper-apps') 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 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..f35d21df --- /dev/null +++ b/src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk @@ -0,0 +1,107 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the OpenGLTest helper app. +# + +# +# Copyright (C) 2008-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +# +# VBoxOGLTest - Library that VBoxSVC is linked with. +# +# On darwin this does the whole job, while on the other platforms it just +# starts VBoxTestOGL. +# +LIBRARIES += VBoxOGLTest +VBoxOGLTest_TEMPLATE := VBoxR3Dll +ifneq ($(KBUILD_TARGET),darwin) + VBoxOGLTest_SOURCES := OpenGLTest.cpp +else + VBoxOGLTest_SOURCES.darwin := OpenGLTestDarwin.cpp + VBoxOGLTest_CXXFLAGS.darwin = $(VBOX_GCC_Wno-deprecated-declarations) +endif + + +# +# 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) \ + && "$(KBUILD_TARGET)" != "darwin" + ifdef VBOX_WITH_VIDEOHWACCEL + ifndef VBOX_WITH_QT6 + USES += qt5 + else + USES += qt6 + endif + endif + PROGRAMS += VBoxTestOGL + VBoxTestOGL_TEMPLATE = $(if $(VBOX_WITH_VIDEOHWACCEL),$(if $(VBOX_WITH_HARDENING),VBoxQtGuiDll,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.freebsd = FreeBSD=1 _GNU_SOURCE + ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + VBoxTestOGL_DEFS = VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)" + else + VBoxTestOGL_DEFS = VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\" + endif + VBoxTestOGL_SOURCES = OpenGLTestApp.cpp + VBoxTestOGL_SOURCES.win = VBoxTestOGL.rc + VBoxTestOGL_LIBS = \ + $(LIB_RUNTIME) + if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) # the X11 gang + VBoxTestOGL_LIBS += \ + X11 \ + Xext + VBoxTestOGL_LIBPATH = \ + $(VBOX_LIBPATH_X11) + endif + + ifdef VBOX_WITH_VIDEOHWACCEL + VBoxTestOGL_DEFS += VBOX_WITH_VIDEOHWACCEL + VBoxTestOGL_QT_MODULES = Core Gui OpenGL Widgets + ifdef VBOX_WITH_QT6 + VBoxTestOGL_QT_MODULES += OpenGLWidgets + endif + if1of ($(KBUILD_TARGET), solaris linux freebsd) + VBoxTestOGL_LIBS += xcb GL pthread dl + endif + VBoxTestOGL_LIBS.win += $(PATH_SDK_$(VBOX_WINPSDK)_LIB)/Opengl32.lib + VBoxTestOGL_SOURCES += VBoxGLSupportInfo.cpp + 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 + +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..80c5bdcd --- /dev/null +++ b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp @@ -0,0 +1,109 @@ +/* $Id: OpenGLTest.cpp $ */ +/** @file + * VBox host opengl support test - generic implementation. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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}; + +#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 + int vrc = RTPathExecDir(pszVBoxPath, RTPATH_MAX); + AssertRCReturn(vrc, false); +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + vrc = RTPathAppend(pszVBoxPath, RTPATH_MAX, "VBoxTestOGL.exe"); +#else + vrc = RTPathAppend(pszVBoxPath, RTPATH_MAX, "VBoxTestOGL"); +#endif + papszArgs[0] = pszVBoxPath; /* argv[0] */ + AssertRCReturn(vrc, false); + + RTPROCESS Process; +#ifndef __SANITIZE_ADDRESS__ + vrc = RTProcCreate(pszVBoxPath, papszArgs, RTENV_DEFAULT, 0, &Process); +#else + vrc = RTProcCreate(pszVBoxPath, papszArgs, env, 0, &Process); + RTEnvDestroy(env); +#endif + if (RT_FAILURE(vrc)) + return false; + + uint64_t StartTS = RTTimeMilliTS(); + + RTPROCSTATUS ProcStatus = {0}; + while (1) + { + vrc = RTProcWait(Process, RTPROCWAIT_FLAGS_NOBLOCK, &ProcStatus); + if (vrc != 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(vrc)) + 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..b3f00aeb --- /dev/null +++ b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp @@ -0,0 +1,526 @@ +/* $Id: OpenGLTestApp.cpp $ */ +/** @file + * VBox host opengl support test application. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef RT_OS_WINDOWS +# include +#endif +#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) +# include +# include +# include +#endif + +#include + +#define VBOXGLTEST_WITH_LOGGING /** @todo r=andy Is this intentional? */ + +#ifdef VBOXGLTEST_WITH_LOGGING +# include "package-generated.h" + +# include +# include +# include +# include +# include +# include + +# include +# include +#endif /* VBOXGLTEST_WITH_LOGGING */ + +#ifndef RT_OS_WINDOWS +# include /* For GLubyte and friends. */ +#endif + +#ifdef VBOX_WITH_VIDEOHWACCEL +# include +# if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && defined(RT_OS_WINDOWS) +# include /* for GL headers on windows */ +# endif +# include +#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 vrc; + +#ifdef RT_OS_WINDOWS + static RTLDRMOD s_hOpenGL32 = NULL; + if (s_hOpenGL32 == NULL) + { + vrc = RTLdrLoadSystem("opengl32", /* fNoUnload = */ true, &s_hOpenGL32); + if (RT_FAILURE(vrc)) + s_hOpenGL32 = NULL; + } + + typedef PROC (WINAPI *PFNWGLGETPROCADDRESS)(LPCSTR); + static PFNWGLGETPROCADDRESS s_wglGetProcAddress = NULL; + if (s_wglGetProcAddress == NULL) + { + if (s_hOpenGL32 != NULL) + { + vrc = RTLdrGetSymbol(s_hOpenGL32, "wglGetProcAddress", (void **)&s_wglGetProcAddress); + if (RT_FAILURE(vrc)) + 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. */ + vrc = RTLdrGetSymbol(s_hOpenGL32, pszSymbol, (void **)&p); + if (RT_SUCCESS(vrc)) + return p; + } +#else /* The X11 gang */ + static RTLDRMOD s_hGL = NULL; + if (s_hGL == NULL) + { + static const char s_szLibGL[] = "libGL.so.1"; + vrc = RTLdrLoadEx(s_szLibGL, &s_hGL, RTLDRLOAD_FLAGS_GLOBAL | RTLDRLOAD_FLAGS_NO_UNLOAD, NULL); + if (RT_FAILURE(vrc)) + { + s_hGL = NULL; + return NULL; + } + } + + typedef PFNRT (* PFNGLXGETPROCADDRESS)(const GLubyte * procName); + static PFNGLXGETPROCADDRESS s_glXGetProcAddress = NULL; + if (s_glXGetProcAddress == NULL) + { + vrc = RTLdrGetSymbol(s_hGL, "glXGetProcAddress", (void **)&s_glXGetProcAddress); + if (RT_FAILURE(vrc)) + { + s_glXGetProcAddress = NULL; + return NULL; + } + } + + PFNRT p = s_glXGetProcAddress((const GLubyte *)pszSymbol); + if (RT_VALID_PTR(p)) + return p; + + /* Might be an exported symbol. */ + vrc = RTLdrGetSymbol(s_hGL, pszSymbol, (void **)&p); + if (RT_SUCCESS(vrc)) + 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")); + char *apszDummyArgs[] = { (char *)"GLTest", NULL }; + int cDummyArgs = RT_ELEMENTS(apszDummyArgs) - 1; + QApplication app(cDummyArgs, apszDummyArgs); + + VBoxGLTmpContext ctx; + const MY_QOpenGLContext *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, "VBOX_RELEASE_LOG", fFlags, "all", RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX, + 0 /*cBufDescs*/, NULL /*paBufDescs*/, enmLogDest, + NULL /*pfnBeginEnd*/, 0 /*cHistory*/, 0 /*cbHistoryFileMax*/, 0 /*uHistoryTimeMax*/, + NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/, + 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) +{ + RTR3InitExe(argc, &argv, 0); + + int vrc = 0; + if (argc < 2) + { + /* backwards compatibility: check 3D */ + vrc = 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 }, + { "--log-to-stdout", 'L', RTGETOPT_REQ_NOTHING }, +#endif + }; + + RTGETOPTSTATE State; + vrc = RTGetOptInit(&State, argc, argv, &s_aOptionDefs[0], RT_ELEMENTS(s_aOptionDefs), 1, 0); + AssertRCReturn(vrc, 49); + +#ifdef VBOX_WITH_VIDEOHWACCEL + bool fTest2D = false; +#endif + bool fTest3D = false; +#ifdef VBOXGLTEST_WITH_LOGGING + bool fLog = false; + bool fLogSuffix = false; + const char * pLog = NULL; +#endif + + for (;;) + { + RTGETOPTUNION Val; + vrc = RTGetOpt(&State, &Val); + if (!vrc) + break; + switch (vrc) + { + case 't': + if (!strcmp(Val.psz, "3D") || !strcmp(Val.psz, "3d")) + fTest3D = true; +#ifdef VBOX_WITH_VIDEOHWACCEL + else if (!strcmp(Val.psz, "2D") || !strcmp(Val.psz, "2d")) + fTest2D = true; +#endif + else + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unknown test: %s", Val.psz); + break; + +#ifdef VBOXGLTEST_WITH_LOGGING + case 'l': + fLog = true; + pLog = Val.psz; + break; + case 'L': + fLog = true; + pLog = NULL; + break; +#endif + case 'h': + RTPrintf(VBOX_PRODUCT " Helper for testing 2D/3D OpenGL capabilities %u.%u.%u\n" + "Copyright (C) 2009-" VBOX_C_YEAR " " VBOX_VENDOR "\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 the GL test result to the given file\n" + " --log-to-stdout log the GL test result to stdout\n" + "\n" + "Logging can alternatively be enabled by specifying the VBOXGLTEST_LOG= env variable\n" + +#endif + "\n", + RTBldCfgVersionMajor(), RTBldCfgVersionMinor(), RTBldCfgVersionBuild()); + return RTEXITCODE_SUCCESS; + + case 'V': + RTPrintf("$Revision: 155484 $\n"); + return RTEXITCODE_SUCCESS; + + case VERR_GETOPT_UNKNOWN_OPTION: + case VINF_GETOPT_NOT_OPTION: + default: + return RTGetOptPrintError(vrc, &Val); + } + } + + /* + * Init logging and output. + */ +#ifdef VBOXGLTEST_WITH_LOGGING + if (!fLog) + { + /* check the VBOXGLTEST_LOG env var */ + pLog = RTEnvGet("VBOXGLTEST_LOG"); + if(pLog) + fLog = true; + fLogSuffix = true; + } + if (fLog) + vrc = vboxInitLogging(pLog, fLogSuffix); + else +#endif + vrc = vboxInitQuietMode(); + + /* + * Do the job. + */ + if (!vrc && fTest3D) + vrc = vboxCheck3DAccelerationSupported(); + +#ifdef VBOX_WITH_VIDEOHWACCEL + if (!vrc && fTest2D) + vrc = vboxCheck2DVideoAccelerationSupported(); +#endif + } + + /*RTR3Term();*/ + return vrc; + +} + +#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..7ee1c313 --- /dev/null +++ b/src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp @@ -0,0 +1,183 @@ +/* $Id: OpenGLTestDarwin.cpp $ */ +/** @file + * VBox host opengl support test + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + + + +/** + * @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 vrc = RTOnce(&s_Once, vboxOglIsOfflineRenderingAppropriateOnce, &s_fCached); + AssertRC(vrc); + 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. + */ +#pragma clang diagnostic ignored "-Wdeprecated-declarations" /* gluCheckExtension deprecated since 10.10 - use MetalKit. */ + 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/VBoxFBOverlayCommon.h b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxFBOverlayCommon.h new file mode 100644 index 00000000..1d86050b --- /dev/null +++ b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxFBOverlayCommon.h @@ -0,0 +1,131 @@ +/* $Id: VBoxFBOverlayCommon.h $ */ +/** @file + * VBox Qt GUI - VBoxFrameBuffer Overlay classes declarations. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef MAIN_INCLUDED_SRC_src_helper_apps_OpenGLTest_VBoxFBOverlayCommon_h +#define MAIN_INCLUDED_SRC_src_helper_apps_OpenGLTest_VBoxFBOverlayCommon_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#if 0 //defined(DEBUG_misha) +DECLINLINE(VOID) vboxDbgPrintF(LPCSTR szString, ...) +{ + char szBuffer[4096] = {0}; + va_list pArgList; + va_start(pArgList, szString); + _vsnprintf(szBuffer, sizeof(szBuffer) / sizeof(szBuffer[0]), szString, pArgList); + va_end(pArgList); + + OutputDebugStringA(szBuffer); +} + +# include "iprt/stream.h" +# define VBOXQGLLOG(_m) RTPrintf _m +# define VBOXQGLLOGREL(_m) do { RTPrintf _m ; LogRel( _m ); } while(0) +# define VBOXQGLDBGPRINT(_m) vboxDbgPrintF _m +#else +# define VBOXQGLLOG(_m) do {}while(0) +# define VBOXQGLLOGREL(_m) LogRel( _m ) +# define VBOXQGLDBGPRINT(_m) do {}while(0) +#endif +#define VBOXQGLLOG_ENTER(_m) do {}while(0) +//do{VBOXQGLLOG(("==>[%s]:", __FUNCTION__)); VBOXQGLLOG(_m);}while(0) +#define VBOXQGLLOG_EXIT(_m) do {}while(0) +//do{VBOXQGLLOG(("<==[%s]:", __FUNCTION__)); VBOXQGLLOG(_m);}while(0) +#ifdef DEBUG + #define VBOXQGL_ASSERTNOERR() \ + do { GLenum err = glGetError(); \ + if(err != GL_NO_ERROR) VBOXQGLLOG(("gl error occurred (0x%x)\n", err)); \ + Assert(err == GL_NO_ERROR); \ + }while(0) + + #define VBOXQGL_CHECKERR(_op) \ + do { \ + glGetError(); \ + _op \ + VBOXQGL_ASSERTNOERR(); \ + }while(0) +#else + #define VBOXQGL_ASSERTNOERR() \ + do {}while(0) + + #define VBOXQGL_CHECKERR(_op) \ + do { \ + _op \ + }while(0) +#endif + +#ifdef DEBUG +#include + +#define VBOXGETTIME() RTTimeNanoTS() + +#define VBOXPRINTDIF(_nano, _m) do{\ + uint64_t cur = VBOXGETTIME(); NOREF(cur); \ + VBOXQGLLOG(_m); \ + VBOXQGLLOG(("(%Lu)\n", cur - (_nano))); \ + }while(0) + +class VBoxVHWADbgTimeCounter +{ +public: + VBoxVHWADbgTimeCounter(const char* msg) {mTime = VBOXGETTIME(); mMsg=msg;} + ~VBoxVHWADbgTimeCounter() {VBOXPRINTDIF(mTime, (mMsg));} +private: + uint64_t mTime; + const char* mMsg; +}; + +#define VBOXQGLLOG_METHODTIME(_m) VBoxVHWADbgTimeCounter _dbgTimeCounter(_m) + +#define VBOXQG_CHECKCONTEXT() \ + { \ + const GLubyte * str; \ + VBOXQGL_CHECKERR( \ + str = glGetString(GL_VERSION); \ + ); \ + Assert(str); \ + if(str) \ + { \ + Assert(str[0]); \ + } \ + } +#else +#define VBOXQGLLOG_METHODTIME(_m) +#define VBOXQG_CHECKCONTEXT() do{}while(0) +#endif + +#define VBOXQGLLOG_QRECT(_p, _pr, _s) do{\ + VBOXQGLLOG((_p " x(%d), y(%d), w(%d), h(%d)" _s, (_pr)->x(), (_pr)->y(), (_pr)->width(), (_pr)->height()));\ + }while(0) + +#define VBOXQGLLOG_CKEY(_p, _pck, _s) do{\ + VBOXQGLLOG((_p " l(0x%x), u(0x%x)" _s, (_pck)->lower(), (_pck)->upper()));\ + }while(0) + +#endif /* !MAIN_INCLUDED_SRC_src_helper_apps_OpenGLTest_VBoxFBOverlayCommon_h */ + diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxGLSupportInfo.cpp b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxGLSupportInfo.cpp new file mode 100644 index 00000000..539eba29 --- /dev/null +++ b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxGLSupportInfo.cpp @@ -0,0 +1,724 @@ +/* $Id: VBoxGLSupportInfo.cpp $ */ +/** @file + * VBox Qt GUI - OpenGL support info used for 2D support detection. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifdef RT_OS_WINDOWS +# include /* QGLWidget drags in Windows.h; -Wall forces us to use wrapper. */ +# include /* QGLWidget drags in stdint.h; -Wall forces us to use wrapper. */ +#endif + +#include /* For QT_VERSION */ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +# include +# include +# include +#else +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "VBoxFBOverlayCommon.h" +#include + + +/*****************/ + +/* functions */ + +PFNVBOXVHWA_ACTIVE_TEXTURE vboxglActiveTexture = NULL; +PFNVBOXVHWA_MULTI_TEX_COORD2I vboxglMultiTexCoord2i = NULL; +PFNVBOXVHWA_MULTI_TEX_COORD2D vboxglMultiTexCoord2d = NULL; +PFNVBOXVHWA_MULTI_TEX_COORD2F vboxglMultiTexCoord2f = NULL; + + +PFNVBOXVHWA_CREATE_SHADER vboxglCreateShader = NULL; +PFNVBOXVHWA_SHADER_SOURCE vboxglShaderSource = NULL; +PFNVBOXVHWA_COMPILE_SHADER vboxglCompileShader = NULL; +PFNVBOXVHWA_DELETE_SHADER vboxglDeleteShader = NULL; + +PFNVBOXVHWA_CREATE_PROGRAM vboxglCreateProgram = NULL; +PFNVBOXVHWA_ATTACH_SHADER vboxglAttachShader = NULL; +PFNVBOXVHWA_DETACH_SHADER vboxglDetachShader = NULL; +PFNVBOXVHWA_LINK_PROGRAM vboxglLinkProgram = NULL; +PFNVBOXVHWA_USE_PROGRAM vboxglUseProgram = NULL; +PFNVBOXVHWA_DELETE_PROGRAM vboxglDeleteProgram = NULL; + +PFNVBOXVHWA_IS_SHADER vboxglIsShader = NULL; +PFNVBOXVHWA_GET_SHADERIV vboxglGetShaderiv = NULL; +PFNVBOXVHWA_IS_PROGRAM vboxglIsProgram = NULL; +PFNVBOXVHWA_GET_PROGRAMIV vboxglGetProgramiv = NULL; +PFNVBOXVHWA_GET_ATTACHED_SHADERS vboxglGetAttachedShaders = NULL; +PFNVBOXVHWA_GET_SHADER_INFO_LOG vboxglGetShaderInfoLog = NULL; +PFNVBOXVHWA_GET_PROGRAM_INFO_LOG vboxglGetProgramInfoLog = NULL; + +PFNVBOXVHWA_GET_UNIFORM_LOCATION vboxglGetUniformLocation = NULL; + +PFNVBOXVHWA_UNIFORM1F vboxglUniform1f = NULL; +PFNVBOXVHWA_UNIFORM2F vboxglUniform2f = NULL; +PFNVBOXVHWA_UNIFORM3F vboxglUniform3f = NULL; +PFNVBOXVHWA_UNIFORM4F vboxglUniform4f = NULL; + +PFNVBOXVHWA_UNIFORM1I vboxglUniform1i = NULL; +PFNVBOXVHWA_UNIFORM2I vboxglUniform2i = NULL; +PFNVBOXVHWA_UNIFORM3I vboxglUniform3i = NULL; +PFNVBOXVHWA_UNIFORM4I vboxglUniform4i = NULL; + +PFNVBOXVHWA_GEN_BUFFERS vboxglGenBuffers = NULL; +PFNVBOXVHWA_DELETE_BUFFERS vboxglDeleteBuffers = NULL; +PFNVBOXVHWA_BIND_BUFFER vboxglBindBuffer = NULL; +PFNVBOXVHWA_BUFFER_DATA vboxglBufferData = NULL; +PFNVBOXVHWA_MAP_BUFFER vboxglMapBuffer = NULL; +PFNVBOXVHWA_UNMAP_BUFFER vboxglUnmapBuffer = NULL; + +PFNVBOXVHWA_IS_FRAMEBUFFER vboxglIsFramebuffer = NULL; +PFNVBOXVHWA_BIND_FRAMEBUFFER vboxglBindFramebuffer = NULL; +PFNVBOXVHWA_DELETE_FRAMEBUFFERS vboxglDeleteFramebuffers = NULL; +PFNVBOXVHWA_GEN_FRAMEBUFFERS vboxglGenFramebuffers = NULL; +PFNVBOXVHWA_CHECK_FRAMEBUFFER_STATUS vboxglCheckFramebufferStatus = NULL; +PFNVBOXVHWA_FRAMEBUFFER_TEXTURE1D vboxglFramebufferTexture1D = NULL; +PFNVBOXVHWA_FRAMEBUFFER_TEXTURE2D vboxglFramebufferTexture2D = NULL; +PFNVBOXVHWA_FRAMEBUFFER_TEXTURE3D vboxglFramebufferTexture3D = NULL; +PFNVBOXVHWA_GET_FRAMEBUFFER_ATTACHMENT_PARAMETRIV vboxglGetFramebufferAttachmentParameteriv = NULL; + +#define VBOXVHWA_GETPROCADDRESS(_c, _t, _n) ((_t)(uintptr_t)(_c).getProcAddress(_n)) + +#define VBOXVHWA_PFNINIT_SAME(_c, _t, _v, _rc) \ + do { \ + if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v)) == NULL) \ + { \ + VBOXQGLLOGREL(("ERROR: '%s' function not found\n", "gl"#_v));\ + AssertBreakpoint(); \ + if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v"ARB")) == NULL) \ + { \ + VBOXQGLLOGREL(("ERROR: '%s' function not found\n", "gl"#_v"ARB"));\ + AssertBreakpoint(); \ + if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v"EXT")) == NULL) \ + { \ + VBOXQGLLOGREL(("ERROR: '%s' function not found\n", "gl"#_v"EXT"));\ + AssertBreakpoint(); \ + (_rc)++; \ + } \ + } \ + } \ + }while(0) + +#define VBOXVHWA_PFNINIT(_c, _t, _v, _f,_rc) \ + do { \ + if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_f)) == NULL) \ + { \ + VBOXQGLLOGREL(("ERROR: '%s' function is not found\n", "gl"#_f));\ + AssertBreakpoint(); \ + (_rc)++; \ + } \ + }while(0) + +#define VBOXVHWA_PFNINIT_OBJECT_ARB(_c, _t, _v, _rc) \ + do { \ + if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v"ObjectARB")) == NULL) \ + { \ + VBOXQGLLOGREL(("ERROR: '%s' function is not found\n", "gl"#_v"ObjectARB"));\ + AssertBreakpoint(); \ + (_rc)++; \ + } \ + }while(0) + +#define VBOXVHWA_PFNINIT_ARB(_c, _t, _v, _rc) \ + do { \ + if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v"ARB")) == NULL) \ + { \ + VBOXQGLLOGREL(("ERROR: '%s' function is not found\n", "gl"#_v"ARB"));\ + AssertBreakpoint(); \ + (_rc)++; \ + } \ + }while(0) + +#define VBOXVHWA_PFNINIT_EXT(_c, _t, _v, _rc) \ + do { \ + if((vboxgl##_v = VBOXVHWA_GETPROCADDRESS(_c, _t, "gl"#_v"EXT")) == NULL) \ + { \ + VBOXQGLLOGREL(("ERROR: '%s' function is not found\n", "gl"#_v"EXT"));\ + AssertBreakpoint(); \ + (_rc)++; \ + } \ + }while(0) + +static int vboxVHWAGlParseSubver(const GLubyte * ver, const GLubyte ** pNext, bool bSpacePrefixAllowed) +{ + int val = 0; + + for(;;++ver) + { + if(*ver >= '0' && *ver <= '9') + { + if(!val) + { + if(*ver == '0') + continue; + } + else + { + val *= 10; + } + val += *ver - '0'; + } + else if(*ver == '.') + { + *pNext = ver+1; + break; + } + else if(*ver == '\0') + { + *pNext = NULL; + break; + } + else if(*ver == ' ' || *ver == '\t' || *ver == 0x0d || *ver == 0x0a) + { + if(bSpacePrefixAllowed) + { + if(!val) + { + continue; + } + } + + /* treat this as the end ov version string */ + *pNext = NULL; + break; + } + else + { + Assert(0); + val = -1; + break; + } + } + + return val; +} + +/* static */ +int VBoxGLInfo::parseVersion(const GLubyte * ver) +{ + int iVer = vboxVHWAGlParseSubver(ver, &ver, true); + if(iVer) + { + iVer <<= 16; + if(ver) + { + int tmp = vboxVHWAGlParseSubver(ver, &ver, false); + if(tmp >= 0) + { + iVer |= tmp << 8; + if(ver) + { + tmp = vboxVHWAGlParseSubver(ver, &ver, false); + if(tmp >= 0) + { + iVer |= tmp; + } + else + { + Assert(0); + iVer = -1; + } + } + } + else + { + Assert(0); + iVer = -1; + } + } + } + return iVer; +} + +void VBoxGLInfo::init(const MY_QOpenGLContext *pContext) +{ + if (mInitialized) + return; + + mInitialized = true; + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + if (!QGLFormat::hasOpenGL()) + { + VBOXQGLLOGREL (("no gl support available\n")); + return; + } +#endif + +// pContext->makeCurrent(); + + const GLubyte * str; + VBOXQGL_CHECKERR( + str = glGetString(GL_VERSION); + ); + + if (str) + { + VBOXQGLLOGREL (("gl version string: 0%s\n", str)); + + mGLVersion = parseVersion (str); + Assert(mGLVersion > 0); + if(mGLVersion < 0) + { + mGLVersion = 0; + } + else + { + VBOXQGLLOGREL (("gl version: 0x%x\n", mGLVersion)); + VBOXQGL_CHECKERR( + str = glGetString (GL_EXTENSIONS); + ); + + VBOXQGLLOGREL (("gl extensions: %s\n", str)); + + const char * pos = strstr((const char *)str, "GL_ARB_multitexture"); + m_GL_ARB_multitexture = pos != NULL; + VBOXQGLLOGREL (("GL_ARB_multitexture: %d\n", m_GL_ARB_multitexture)); + + pos = strstr((const char *)str, "GL_ARB_shader_objects"); + m_GL_ARB_shader_objects = pos != NULL; + VBOXQGLLOGREL (("GL_ARB_shader_objects: %d\n", m_GL_ARB_shader_objects)); + + pos = strstr((const char *)str, "GL_ARB_fragment_shader"); + m_GL_ARB_fragment_shader = pos != NULL; + VBOXQGLLOGREL (("GL_ARB_fragment_shader: %d\n", m_GL_ARB_fragment_shader)); + + pos = strstr((const char *)str, "GL_ARB_pixel_buffer_object"); + m_GL_ARB_pixel_buffer_object = pos != NULL; + VBOXQGLLOGREL (("GL_ARB_pixel_buffer_object: %d\n", m_GL_ARB_pixel_buffer_object)); + + pos = strstr((const char *)str, "GL_ARB_texture_rectangle"); + m_GL_ARB_texture_rectangle = pos != NULL; + VBOXQGLLOGREL (("GL_ARB_texture_rectangle: %d\n", m_GL_ARB_texture_rectangle)); + + pos = strstr((const char *)str, "GL_EXT_texture_rectangle"); + m_GL_EXT_texture_rectangle = pos != NULL; + VBOXQGLLOGREL (("GL_EXT_texture_rectangle: %d\n", m_GL_EXT_texture_rectangle)); + + pos = strstr((const char *)str, "GL_NV_texture_rectangle"); + m_GL_NV_texture_rectangle = pos != NULL; + VBOXQGLLOGREL (("GL_NV_texture_rectangle: %d\n", m_GL_NV_texture_rectangle)); + + pos = strstr((const char *)str, "GL_ARB_texture_non_power_of_two"); + m_GL_ARB_texture_non_power_of_two = pos != NULL; + VBOXQGLLOGREL (("GL_ARB_texture_non_power_of_two: %d\n", m_GL_ARB_texture_non_power_of_two)); + + pos = strstr((const char *)str, "GL_EXT_framebuffer_object"); + m_GL_EXT_framebuffer_object = pos != NULL; + VBOXQGLLOGREL (("GL_EXT_framebuffer_object: %d\n", m_GL_EXT_framebuffer_object)); + + + initExtSupport(*pContext); + } + } + else + { + VBOXQGLLOGREL (("failed to make the context current, treating as unsupported\n")); + } +} + +void VBoxGLInfo::initExtSupport(const MY_QOpenGLContext &context) +{ + int vrc = VINF_SUCCESS; + do + { + mMultiTexNumSupported = 1; /* default, 1 means not supported */ + if(mGLVersion >= 0x010201) /* ogl >= 1.2.1 */ + { + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_ACTIVE_TEXTURE, ActiveTexture, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_MULTI_TEX_COORD2I, MultiTexCoord2i, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_MULTI_TEX_COORD2D, MultiTexCoord2d, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_MULTI_TEX_COORD2F, MultiTexCoord2f, vrc); + } + else if(m_GL_ARB_multitexture) + { + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_ACTIVE_TEXTURE, ActiveTexture, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_MULTI_TEX_COORD2I, MultiTexCoord2i, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_MULTI_TEX_COORD2D, MultiTexCoord2d, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_MULTI_TEX_COORD2F, MultiTexCoord2f, vrc); + } + else + { + break; + } + + if(RT_FAILURE(vrc)) + break; + + GLint maxCoords, maxUnits; + glGetIntegerv(GL_MAX_TEXTURE_COORDS, &maxCoords); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxUnits); + + VBOXQGLLOGREL(("Max Tex Coords (%d), Img Units (%d)\n", maxCoords, maxUnits)); + /* take the minimum of those */ + if(maxUnits < maxCoords) + maxCoords = maxUnits; + if(maxUnits < 2) + { + VBOXQGLLOGREL(("Max Tex Coord or Img Units < 2 disabling MultiTex support\n")); + break; + } + + mMultiTexNumSupported = maxUnits; + }while(0); + + + do + { + vrc = 0; + mPBOSupported = false; + + if(m_GL_ARB_pixel_buffer_object) + { + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_GEN_BUFFERS, GenBuffers, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_DELETE_BUFFERS, DeleteBuffers, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_BIND_BUFFER, BindBuffer, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_BUFFER_DATA, BufferData, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_MAP_BUFFER, MapBuffer, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNMAP_BUFFER, UnmapBuffer, vrc); + } + else + { + break; + } + + if(RT_FAILURE(vrc)) + break; + + mPBOSupported = true; + } while(0); + + do + { + vrc = 0; + mFragmentShaderSupported = false; + + if(mGLVersion >= 0x020000) /* if ogl >= 2.0*/ + { + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_CREATE_SHADER, CreateShader, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_SHADER_SOURCE, ShaderSource, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_COMPILE_SHADER, CompileShader, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_DELETE_SHADER, DeleteShader, vrc); + + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_CREATE_PROGRAM, CreateProgram, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_ATTACH_SHADER, AttachShader, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_DETACH_SHADER, DetachShader, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_LINK_PROGRAM, LinkProgram, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_USE_PROGRAM, UseProgram, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_DELETE_PROGRAM, DeleteProgram, vrc); + + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_IS_SHADER, IsShader, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_SHADERIV, GetShaderiv, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_IS_PROGRAM, IsProgram, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_PROGRAMIV, GetProgramiv, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_ATTACHED_SHADERS, GetAttachedShaders, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_SHADER_INFO_LOG, GetShaderInfoLog, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_PROGRAM_INFO_LOG, GetProgramInfoLog, vrc); + + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_GET_UNIFORM_LOCATION, GetUniformLocation, vrc); + + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM1F, Uniform1f, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM2F, Uniform2f, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM3F, Uniform3f, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM4F, Uniform4f, vrc); + + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM1I, Uniform1i, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM2I, Uniform2i, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM3I, Uniform3i, vrc); + VBOXVHWA_PFNINIT_SAME(context, PFNVBOXVHWA_UNIFORM4I, Uniform4i, vrc); + } + else if(m_GL_ARB_shader_objects && m_GL_ARB_fragment_shader) + { + VBOXVHWA_PFNINIT_OBJECT_ARB(context, PFNVBOXVHWA_CREATE_SHADER, CreateShader, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_SHADER_SOURCE, ShaderSource, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_COMPILE_SHADER, CompileShader, vrc); + VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_DELETE_SHADER, DeleteShader, DeleteObjectARB, vrc); + + VBOXVHWA_PFNINIT_OBJECT_ARB(context, PFNVBOXVHWA_CREATE_PROGRAM, CreateProgram, vrc); + VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_ATTACH_SHADER, AttachShader, AttachObjectARB, vrc); + VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_DETACH_SHADER, DetachShader, DetachObjectARB, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_LINK_PROGRAM, LinkProgram, vrc); + VBOXVHWA_PFNINIT_OBJECT_ARB(context, PFNVBOXVHWA_USE_PROGRAM, UseProgram, vrc); + VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_DELETE_PROGRAM, DeleteProgram, DeleteObjectARB, vrc); + + /// @todo VBOXVHWA_PFNINIT(PFNVBOXVHWA_IS_SHADER, IsShader, vrc); + VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_GET_SHADERIV, GetShaderiv, GetObjectParameterivARB, vrc); + /// @todo VBOXVHWA_PFNINIT(PFNVBOXVHWA_IS_PROGRAM, IsProgram, vrc); + VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_GET_PROGRAMIV, GetProgramiv, GetObjectParameterivARB, vrc); + VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_GET_ATTACHED_SHADERS, GetAttachedShaders, GetAttachedObjectsARB, vrc); + VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_GET_SHADER_INFO_LOG, GetShaderInfoLog, GetInfoLogARB, vrc); + VBOXVHWA_PFNINIT(context, PFNVBOXVHWA_GET_PROGRAM_INFO_LOG, GetProgramInfoLog, GetInfoLogARB, vrc); + + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_GET_UNIFORM_LOCATION, GetUniformLocation, vrc); + + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM1F, Uniform1f, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM2F, Uniform2f, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM3F, Uniform3f, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM4F, Uniform4f, vrc); + + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM1I, Uniform1i, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM2I, Uniform2i, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM3I, Uniform3i, vrc); + VBOXVHWA_PFNINIT_ARB(context, PFNVBOXVHWA_UNIFORM4I, Uniform4i, vrc); + } + else + { + break; + } + + if(RT_FAILURE(vrc)) + break; + + mFragmentShaderSupported = true; + } while(0); + + do + { + vrc = 0; + mFBOSupported = false; + + if(m_GL_EXT_framebuffer_object) + { + VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_IS_FRAMEBUFFER, IsFramebuffer, vrc); + VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_BIND_FRAMEBUFFER, BindFramebuffer, vrc); + VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_DELETE_FRAMEBUFFERS, DeleteFramebuffers, vrc); + VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_GEN_FRAMEBUFFERS, GenFramebuffers, vrc); + VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_CHECK_FRAMEBUFFER_STATUS, CheckFramebufferStatus, vrc); + VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_FRAMEBUFFER_TEXTURE1D, FramebufferTexture1D, vrc); + VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_FRAMEBUFFER_TEXTURE2D, FramebufferTexture2D, vrc); + VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_FRAMEBUFFER_TEXTURE3D, FramebufferTexture3D, vrc); + VBOXVHWA_PFNINIT_EXT(context, PFNVBOXVHWA_GET_FRAMEBUFFER_ATTACHMENT_PARAMETRIV, GetFramebufferAttachmentParameteriv, vrc); + } + else + { + break; + } + + if(RT_FAILURE(vrc)) + break; + + mFBOSupported = true; + } while(0); + + if(m_GL_ARB_texture_rectangle || m_GL_EXT_texture_rectangle || m_GL_NV_texture_rectangle) + { + mTextureRectangleSupported = true; + } + else + { + mTextureRectangleSupported = false; + } + + mTextureNP2Supported = m_GL_ARB_texture_non_power_of_two; +} + +void VBoxVHWAInfo::init(const MY_QOpenGLContext *pContext) +{ + if(mInitialized) + return; + + mInitialized = true; + + mglInfo.init(pContext); + + if(mglInfo.isFragmentShaderSupported() && mglInfo.isTextureRectangleSupported()) + { + uint32_t num = 0; + mFourccSupportedList[num++] = FOURCC_AYUV; + mFourccSupportedList[num++] = FOURCC_UYVY; + mFourccSupportedList[num++] = FOURCC_YUY2; + if(mglInfo.getMultiTexNumSupported() >= 4) + { + /* YV12 currently requires 3 units (for each color component) + * + 1 unit for dst texture for color-keying + 3 units for each color component + * TODO: we could store YV12 data in one texture to eliminate this requirement*/ + mFourccSupportedList[num++] = FOURCC_YV12; + } + + Assert(num <= VBOXVHWA_NUMFOURCC); + mFourccSupportedCount = num; + } + else + { + mFourccSupportedCount = 0; + } +} + +bool VBoxVHWAInfo::isVHWASupported() const +{ + if(mglInfo.getGLVersion() <= 0) + { + /* error occurred while gl info initialization */ + VBOXQGLLOGREL(("2D not supported: gl version info not initialized properly\n")); + return false; + } + +#ifndef DEBUGVHWASTRICT + /* in case we do not support shaders & multitexturing we can not support dst colorkey, + * no sense to report Video Acceleration supported */ + if(!mglInfo.isFragmentShaderSupported()) + { + VBOXQGLLOGREL(("2D not supported: fragment shader unsupported\n")); + return false; + } +#endif + if(mglInfo.getMultiTexNumSupported() < 2) + { + VBOXQGLLOGREL(("2D not supported: multitexture unsupported\n")); + return false; + } + + /* color conversion now supported only GL_TEXTURE_RECTANGLE + * in this case only stretching is accelerated + * report as unsupported, TODO: probably should report as supported for stretch acceleration */ + if(!mglInfo.isTextureRectangleSupported()) + { + VBOXQGLLOGREL(("2D not supported: texture rectangle unsupported\n")); + return false; + } + + VBOXQGLLOGREL(("2D is supported!\n")); + return true; +} + +/* static */ +bool VBoxVHWAInfo::checkVHWASupport() +{ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) + static char pszVBoxPath[RTPATH_MAX]; + const char *papszArgs[] = { NULL, "-test", "2D", NULL}; + + int vrc = RTPathExecDir(pszVBoxPath, RTPATH_MAX); AssertRCReturn(vrc, false); +# if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + vrc = RTPathAppend(pszVBoxPath, RTPATH_MAX, "VBoxTestOGL.exe"); +# else + vrc = RTPathAppend(pszVBoxPath, RTPATH_MAX, "VBoxTestOGL"); +# endif + papszArgs[0] = pszVBoxPath; /* argv[0] */ + AssertRCReturn(vrc, false); + + RTPROCESS Process; + vrc = RTProcCreate(pszVBoxPath, papszArgs, RTENV_DEFAULT, 0, &Process); + if (RT_FAILURE(vrc)) + { + VBOXQGLLOGREL(("2D support test failed: failed to create a test process\n")); + return false; + } + + uint64_t const StartTS = RTTimeMilliTS(); + + RTPROCSTATUS ProcStatus = {0}; + while (1) + { + vrc = RTProcWait(Process, RTPROCWAIT_FLAGS_NOBLOCK, &ProcStatus); + if (vrc != VERR_PROCESS_RUNNING) + break; + + if (RTTimeMilliTS() - StartTS > 30*1000 /* 30 sec */) + { + RTProcTerminate(Process); + RTThreadSleep(100); + RTProcWait(Process, RTPROCWAIT_FLAGS_NOBLOCK, &ProcStatus); + VBOXQGLLOGREL(("2D support test failed: the test did not complete within 30 sec\n")); + return false; + } + RTThreadSleep(100); + } + + if (RT_SUCCESS(vrc)) + { + if ((ProcStatus.enmReason==RTPROCEXITREASON_NORMAL) && (ProcStatus.iStatus==0)) + { + VBOXQGLLOGREL(("2D support test succeeded\n")); + return true; + } + } + + VBOXQGLLOGREL(("2D support test failed: err code (%Rra)\n", vrc)); + + return false; +#else + /** @todo test & enable external app approach*/ + VBoxGLTmpContext ctx; + const MY_QOpenGLContext *pContext = ctx.makeCurrent(); + Assert(pContext); + if (pContext) + { + VBoxVHWAInfo info; + info.init(pContext); + return info.isVHWASupported(); + } + return false; +#endif +} + +VBoxGLTmpContext::VBoxGLTmpContext() +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + mWidget = new MY_QOpenGLWidget(/*new QMainWindow()*/); +#else + if (QGLFormat::hasOpenGL()) + mWidget = new MY_QOpenGLWidget(); + else + mWidget = NULL; +#endif +} + +VBoxGLTmpContext::~VBoxGLTmpContext() +{ + if (mWidget) + { + delete mWidget; + mWidget = NULL; + } +} + +const MY_QOpenGLContext *VBoxGLTmpContext::makeCurrent() +{ + if (mWidget) + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + mWidget->grabFramebuffer(); /* This is a hack to trigger GL initialization or context() will return NULL. */ +#endif + mWidget->makeCurrent(); + return mWidget->context(); + } + return NULL; +} + 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..48cbc4e2 --- /dev/null +++ b/src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxTestOGL.rc $ */ +/** @file + * VBoxTestOGL - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include + +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..f8724e2f --- /dev/null +++ b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp @@ -0,0 +1,2016 @@ +/* $Id: VBoxExtPackHelperApp.cpp $ */ +/** @file + * VirtualBox Main - Extension Pack Helper Application, usually set-uid-to-root. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "../include/ExtPackUtil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef RT_OS_WINDOWS +# define _WIN32_WINNT 0x0501 +# include /* ShellExecuteEx, ++ */ +# include /* CoInitializeEx */ +# ifdef DEBUG +# include +# endif +#endif + +#ifdef RT_OS_DARWIN +# include +# include +# include +#endif + +#if !defined(RT_OS_OS2) +# include +# include +# if !defined(RT_OS_WINDOWS) +# include +# include /* geteuid */ +# endif +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Enable elevation on Windows and Darwin. */ +#if !defined(RT_OS_OS2) || defined(DOXYGEN_RUNNING) +# define WITH_ELEVATION +#endif + + +/** @name Command and option names + * @{ */ +#define CMD_INSTALL 1000 +#define CMD_UNINSTALL 1001 +#define CMD_CLEANUP 1002 +#ifdef WITH_ELEVATION +# define OPT_ELEVATED 1090 +# define OPT_STDOUT 1091 +# define OPT_STDERR 1092 +#endif +#define OPT_DISP_INFO_HACK 1093 +/** @} */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +static HINSTANCE g_hInstance; +#endif + +#ifdef IN_RT_R3 +/* Override RTAssertShouldPanic to prevent gdb process creation. */ +RTDECL(bool) RTAssertShouldPanic(void) +{ + return true; +} +#endif + + + +/** + * Handle the special standard options when these are specified after the + * command. + * + * @param ch The option character. + */ +static RTEXITCODE DoStandardOption(int ch) +{ + switch (ch) + { + case 'h': + { + RTMsgInfo(VBOX_PRODUCT " Extension Pack Helper App\n" + "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n" + "\n" + "This NOT intended for general use, please use VBoxManage instead\n" + "or call the IExtPackManager API directly.\n" + "\n" + "Usage: %s [options]\n" + "Commands:\n" + " install --base-dir --cert-dir --name \\\n" + " --tarball --tarball-fd \n" + " uninstall --base-dir --name \n" + " cleanup --base-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 vrc = RTPathAppPrivateNoArch(szCorrect, sizeof(szCorrect)); + if (RT_FAILURE(vrc)) + return false; + vrc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_CERT_DIR); + if (RT_FAILURE(vrc)) + 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 vrc = RTPathAppPrivateArchTop(szCorrect, sizeof(szCorrect)); + if (RT_FAILURE(vrc)) + return false; + vrc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_INSTALL_DIR); + if (RT_FAILURE(vrc)) + 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 vrc = RTDirRemoveRecursive(pszDir, RTDIRRMREC_F_CONTENT_AND_DIR); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to delete the %sextension pack directory: %Rrc ('%s')", + fTemporary ? "temporary " : "", vrc, 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 vrc = RTDirRename(pszSrc, pszDst, fFlags); + if ( ( vrc != VERR_ACCESS_DENIED + && vrc != VERR_SHARING_VIOLATION) + || RTTimeNanoTS() - nsNow > RT_NS_15SEC) + return vrc; + 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 vrc = RTStrCopy(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), pszExtPackDir); + if (RT_SUCCESS(vrc)) + vrc = RTStrCat(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), "-_-uninst"); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct temporary extension pack path: %Rrc", vrc); + + vrc = CommonDirRenameWrapper(pszExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE); + if (vrc == 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*/); + vrc = CommonDirRenameWrapper(pszExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE); + } + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to rename the extension pack directory: %Rrc\n" + "If the problem persists, try running the command: VBoxManage extpack cleanup", vrc); + + /* 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 vrc = VBoxExtPackOpenTarFss(hTarballFile, szError, sizeof(szError), phTarFss, NULL); + if (RT_FAILURE(vrc)) + { + 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 vrc = RTPathSetMode(pszDir, 0755); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions: %Rrc ('%s')", vrc, 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 vrc = VBoxExtPackValidateMember(pszName, enmType, hVfsObj, szError, sizeof(szError)); + if (RT_FAILURE(vrc)) + { + 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 vrc = SUPR3HardenedVerifyDir(pszDir, true /*fRecursive*/, true /*fCheckFiles*/, &ErrInfo.Core); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Hardening check failed with %Rrc: %s", vrc, 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 vrc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszDstDirName, vrc); + ObjInfo.Attr.fMode &= ~(RTFS_UNIX_IWOTH | RTFS_UNIX_IWGRP); + + vrc = RTDirCreate(pszDstDirName, ObjInfo.Attr.fMode, 0); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create directory '%s': %Rrc", pszDstDirName, vrc); + +#ifndef RT_OS_WINDOWS + /* + * Because of umask, we have to apply the mode again. + */ + vrc = RTPathSetMode(pszDstDirName, ObjInfo.Attr.fMode); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions on '%s': %Rrc", pszDstDirName, vrc); +#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 vrc = RTVfsIoStrmQueryInfo(hVfsIosSrc, &ObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmQueryInfo failed with %Rrc on '%s'", vrc, 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; + vrc = RTFileOpen(&hFile, pszDstFilename, fFlags); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create '%s': %Rrc", pszDstFilename, vrc); + + /* + * Create a I/O stream for the destination file, stack a manifest entry + * creator on top of it. + */ + RTVFSIOSTREAM hVfsIosDst2; + vrc = RTVfsIoStrmFromRTFile(hFile, fFlags, true /*fLeaveOpen*/, &hVfsIosDst2); + if (RT_SUCCESS(vrc)) + { + RTVFSIOSTREAM hVfsIosDst; + vrc = RTManifestEntryAddPassthruIoStream(hUnpackManifest, hVfsIosDst2, pszName, + RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256, + false /*fReadOrWrite*/, &hVfsIosDst); + RTVfsIoStrmRelease(hVfsIosDst2); + if (RT_SUCCESS(vrc)) + { + /* + * Pump the data thru. + */ + vrc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(ObjInfo.cbObject, _1G)); + if (RT_SUCCESS(vrc)) + { + vrc = RTManifestPtIosAddEntryNow(hVfsIosDst); + if (RT_SUCCESS(vrc)) + { + RTVfsIoStrmRelease(hVfsIosDst); + hVfsIosDst = NIL_RTVFSIOSTREAM; + + /* + * Set the mode mask. + */ + ObjInfo.Attr.fMode &= ~(RTFS_UNIX_IWOTH | RTFS_UNIX_IWGRP); + vrc = RTFileSetMode(hFile, ObjInfo.Attr.fMode); + /** @todo Windows needs to do more here, I think. */ + if (RT_SUCCESS(vrc)) + { + RTFileClose(hFile); + return RTEXITCODE_SUCCESS; + } + + RTMsgError("Failed to set the mode of '%s' to %RTfmode: %Rrc", pszDstFilename, ObjInfo.Attr.fMode, vrc); + } + else + RTMsgError("RTManifestPtIosAddEntryNow failed for '%s': %Rrc", pszDstFilename, vrc); + } + else + RTMsgError("RTVfsUtilPumpIoStreams failed for '%s': %Rrc", pszDstFilename, vrc); + RTVfsIoStrmRelease(hVfsIosDst); + } + else + RTMsgError("RTManifestEntryAddPassthruIoStream failed: %Rrc", vrc); + } + else + RTMsgError("RTVfsIoStrmFromRTFile failed: %Rrc", vrc); + 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 vrc = RTPathAbs(pszDirDst, szDstPath, sizeof(szDstPath) - VBOX_EXTPACK_MAX_MEMBER_NAME_LENGTH - 2); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs('%s',,) failed: %Rrc", pszDirDst, vrc); + 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; + vrc = RTManifestCreate(0 /*fFlags*/, &hUnpackManifest); + if (RT_SUCCESS(vrc)) + { + /* + * Process the tarball (would be nice to move this to a function). + */ + for (;;) + { + /* + * Get the next stream object. + */ + char *pszName; + RTVFSOBJ hVfsObj; + RTVFSOBJTYPE enmType; + vrc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj); + if (RT_FAILURE(vrc)) + { + if (vrc != VERR_EOF) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext failed: %Rrc", vrc); + 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'; + vrc = RTStrCopy(&szDstPath[offDstPath], sizeof(szDstPath) - offDstPath, pszAdjName); + if (RT_SUCCESS(vrc)) + { + 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, vrc); + } + + /* + * 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]; + vrc = RTManifestEqualsEx(hUnpackManifest, hValidManifest, NULL /*papszIgnoreEntries*/, NULL /*papszIgnoreAttr*/, + 0 /*fFlags*/, szError, sizeof(szError)); + if (RT_SUCCESS(vrc)) + rcExit = RTEXITCODE_SUCCESS; + else if (vrc == VERR_NOT_EQUAL && szError[0]) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Manifest mismatch: %s", szError); + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTManifestEqualsEx failed: %Rrc", vrc); + } +#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 vrc = VBoxExtPackValidateTarball(hTarballFile, pszExtPackName, pszTarball, pszTarballDigest, + szError, sizeof(szError), phValidManifest, NULL /*phXmlFile*/, NULL /*pStrDigest*/); + if (RT_FAILURE(vrc)) + { + 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 vrc = RTFileQueryInfo(hTarballFile, &ObjInfo, RTFSOBJATTRADD_UNIX); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on '%s'", vrc, pszTarball); + if (!RTFS_IS_FILE(ObjInfo.Attr.fMode)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Not a regular file: %s", pszTarball); + + if (hTarballFileOpt != NIL_RTFILE) + { + RTFSOBJINFO ObjInfo2; + vrc = RTFileQueryInfo(hTarballFileOpt, &ObjInfo2, RTFSOBJATTRADD_UNIX); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on --tarball-fd", vrc); + 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]; + vrc = RTPathJoin(szFinalPath, sizeof(szFinalPath), pszBaseDir, pszMangledName); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to construct the path to the final extension pack directory: %Rrc", vrc); + + char szTmpPath[RTPATH_MAX]; + vrc = RTPathJoin(szTmpPath, sizeof(szTmpPath) - 64, pszBaseDir, pszMangledName); + if (RT_SUCCESS(vrc)) + { + size_t cchTmpPath = strlen(szTmpPath); + RTStrPrintf(&szTmpPath[cchTmpPath], sizeof(szTmpPath) - cchTmpPath, "-_-inst-%u", (uint32_t)RTProcSelf()); + } + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Failed to construct the path to the temporary extension pack directory: %Rrc", vrc); + + /* + * Check that they don't exist at this point in time, unless fReplace=true. + */ + vrc = RTPathQueryInfoEx(szFinalPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_SUCCESS(vrc) && 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(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Found non-directory file system object where the extension pack would be installed ('%s')", + szFinalPath); + else if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", vrc, szFinalPath); + + vrc = RTPathQueryInfoEx(szTmpPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (vrc != VERR_FILE_NOT_FOUND && vrc != VERR_PATH_NOT_FOUND) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", vrc, 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. + */ + vrc = RTPathSetMode(pszBaseDir, 0755); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions on '%s': %Rrc", pszBaseDir, vrc); +#else + /** @todo Ownership tricks on windows? */ +#endif + vrc = RTDirCreate(szTmpPath, 0700, 0); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary directory: %Rrc ('%s')", vrc, 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) + { + vrc = CommonDirRenameWrapper(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE); + if ( RT_FAILURE(vrc) + && fReplace + && RTDirExists(szFinalPath)) + { + /* Automatic uninstall if --replace was given. */ + rcExit = CommonUninstallWorker(szFinalPath); + if (rcExit == RTEXITCODE_SUCCESS) + vrc = CommonDirRenameWrapper(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE); + } + if (RT_SUCCESS(vrc)) + 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')", + vrc, 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 vrc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", vrc); + + 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); + vrc = RTFileFromNative(&hTarballFileOpt, hNative); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTFileFromNative failed on --target-fd value: %Rrc", vrc); + 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]; + vrc = RTSha256FromString(pszTarballDigest, abDigest); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Bad SHA-256 string: %Rrc", vrc); + 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; + vrc = RTFileOpen(&hTarballFile, pszTarball, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(vrc)) + { + 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')", vrc, 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 vrc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", vrc); + + 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]; + vrc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), pszBaseDir, strMangledName.c_str()); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct extension pack path: %Rrc", vrc); + + 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 vrc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", vrc); + + 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; + vrc = RTDirOpen(&hDir, pszBaseDir); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed open the base directory: %Rrc ('%s')", vrc, pszBaseDir); + + uint32_t cCleaned = 0; + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + for (;;) + { + RTDIRENTRYEX Entry; + vrc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_FAILURE(vrc)) + { + if (vrc != VERR_NO_MORE_FILES) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirReadEx returns %Rrc", vrc); + 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]; + vrc = RTPathJoin(szPath, sizeof(szPath), pszBaseDir, Entry.szName); + if (RT_SUCCESS(vrc)) + { + 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'", vrc, 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 vrc = RTPathJoin(pszPath, cbPath, s_apszPaths[i], pszName); + if (RT_SUCCESS(vrc)) + { + RTFSOBJINFO ObjInfo; + vrc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK); + if (RT_SUCCESS(vrc)) + { + 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 vrc; + for (;;) + { + char abBuf[0x1000]; + size_t cbRead; + vrc = RTFileRead(hSrc, abBuf, sizeof(abBuf), &cbRead); + if (RT_FAILURE(vrc)) + { + RTMsgError("RTFileRead failed: %Rrc", vrc); + break; + } + if (!cbRead) + break; + vrc = RTStrmWrite(pDst, abBuf, cbRead); + if (RT_FAILURE(vrc)) + { + if (fComplain) + RTMsgError("RTStrmWrite failed: %Rrc", vrc); + break; + } + } + vrc = RTStrmFlush(pDst); + if (RT_FAILURE(vrc) && fComplain) + RTMsgError("RTStrmFlush failed: %Rrc", vrc); +} + + +/** + * 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 vrc = RTStrToUtf16(pszExecPath, (PRTUTF16 *)&Info.lpFile); + if (RT_SUCCESS(vrc)) + { + char *pszCmdLine; + vrc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs + 1], RTGETOPTARGV_CNV_QUOTE_MS_CRT); + if (RT_SUCCESS(vrc)) + { + vrc = RTStrToUtf16(pszCmdLine, (PRTUTF16 *)&Info.lpParameters); + if (RT_SUCCESS(vrc)) + { + 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; + vrc = RTStrToUInt64Ex(pszArg + sizeof("hwnd=") - 1, NULL, 0, &u64Hwnd); + if (RT_SUCCESS(vrc)) + { + HWND hwnd = (HWND)(uintptr_t)u64Hwnd; + Info.hwnd = hwnd; + Info.hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + } + } + } + if (Info.hMonitor == NULL) + { + POINT Pt = {0,0}; + Info.hMonitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY); + } + if (Info.hMonitor != NULL) + Info.fMask |= SEE_MASK_HMONITOR; + + if (ShellExecuteExW(&Info)) + { + if (Info.hProcess != INVALID_HANDLE_VALUE) + { + /* + * Wait for the process, make sure the deal with messages. + */ + for (;;) + { + DWORD dwRc = MsgWaitForMultipleObjects(1, &Info.hProcess, FALSE, 5000/*ms*/, QS_ALLEVENTS); + if (dwRc == WAIT_OBJECT_0) + break; + if ( dwRc != WAIT_TIMEOUT + && dwRc != WAIT_OBJECT_0 + 1) + { + RTMsgError("MsgWaitForMultipleObjects returned: %#x (%d), err=%u", dwRc, dwRc, GetLastError()); + break; + } + while (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&Msg); + DispatchMessageW(&Msg); + } + } + + DWORD dwExitCode; + if (GetExitCodeProcess(Info.hProcess, &dwExitCode)) + { + if (dwExitCode < 128) + rcExit = (RTEXITCODE)dwExitCode; + else + rcExit = RTEXITCODE_FAILURE; + } + CloseHandle(Info.hProcess); + } + else + RTMsgError("ShellExecuteExW return INVALID_HANDLE_VALUE as Info.hProcess"); + } + else + RTMsgError("ShellExecuteExW failed: %u (%#x)", GetLastError(), GetLastError()); + + + RTUtf16Free((PRTUTF16)Info.lpParameters); + } + RTStrFree(pszCmdLine); + } + + RTUtf16Free((PRTUTF16)Info.lpFile); + } + else + RTMsgError("RTStrToUtf16 failed: %Rc", vrc); + +#elif defined(RT_OS_DARWIN) + RT_NOREF(pszDisplayInfoHack); + char szIconName[RTPATH_MAX]; + int vrc = RTPathAppPrivateArch(szIconName, sizeof(szIconName)); + if (RT_SUCCESS(vrc)) + vrc = RTPathAppend(szIconName, sizeof(szIconName), "../Resources/virtualbox.png"); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct icon path: %Rrc", vrc); + + 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 vrc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs], RTGETOPTARGV_CNV_QUOTE_BOURNE_SH); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvToString failed: %Rrc", vrc); + + /* + * 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; + vrc = 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(vrc)) + { + RTPROCSTATUS Status; + vrc = RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status); + if (RT_SUCCESS(vrc)) + { + if (Status.enmReason == RTPROCEXITREASON_NORMAL) + rcExit = (RTEXITCODE)Status.iStatus; + else + rcExit = RTEXITCODE_FAILURE; + } + else + RTMsgError("Error while waiting for '%s': %Rrc", papszArgs[iSuArg], vrc); + } + else + RTMsgError("Failed to execute '%s': %Rrc", papszArgs[iSuArg], vrc); + } + 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 vrc = RTPathTemp(szTempDir, sizeof(szTempDir)); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathTemp failed: %Rrc", vrc); + vrc = RTPathAppend(szTempDir, sizeof(szTempDir), "VBoxExtPackHelper-XXXXXX"); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAppend failed: %Rrc", vrc); + vrc = RTDirCreateTemp(szTempDir, 0700); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirCreateTemp failed: %Rrc", vrc); + + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + char szStdOut[RTPATH_MAX]; + char szStdErr[RTPATH_MAX]; + vrc = RTPathJoin(szStdOut, sizeof(szStdOut), szTempDir, "stdout"); + if (RT_SUCCESS(vrc)) + vrc = RTPathJoin(szStdErr, sizeof(szStdErr), szTempDir, "stderr"); + if (RT_SUCCESS(vrc)) + { + RTFILE hStdOut; + vrc = RTFileOpen(&hStdOut, szStdOut, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE + | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(vrc)) + { + RTFILE hStdErr; + vrc = RTFileOpen(&hStdErr, szStdErr, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE + | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(vrc)) + { + /* + * 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 vrc = RTPathQueryInfoEx(szExecPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_FAILURE(vrc)) + 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 vrc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_UTF8_ARGV); /* WinMain gives us UTF-8, see below. */ +#else + int vrc = RTR3InitExe(argc, &argv, 0); +#endif + if (RT_FAILURE(vrc)) + return RTMsgInitFailure(vrc); + + SUPR3HardenedVerifyInit(); + RTERRINFOSTATIC ErrInfo; + RTErrInfoInitStatic(&ErrInfo); + vrc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, &ErrInfo.Core); + if (RT_FAILURE(vrc)) + 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; + vrc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", vrc); + 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; + vrc = RTStrToUtf16(ValueUnion.psz, &pwszName); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error converting '%s' to UTF-16: %Rrc\n", ValueUnion.psz, vrc); + 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) + { + vrc = RTErrConvertFromErrno(errno); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "freopen on '%s': %Rrc", ValueUnion.psz, vrc); + } + 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 vrc = RTR3InitExeNoArguments(0); + if (RT_FAILURE(vrc)) + return RTMsgInitFailure(vrc); + + LPWSTR pwszCmdLine = GetCommandLineW(); + if (!pwszCmdLine) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "GetCommandLineW failed"); + + char *pszCmdLine; + vrc = RTUtf16ToUtf8(pwszCmdLine, &pszCmdLine); /* leaked */ + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to convert the command line: %Rrc", vrc); + + int cArgs; + char **papszArgs; + vrc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszCmdLine, RTGETOPTARGV_CNV_QUOTE_MS_CRT, NULL); + if (RT_SUCCESS(vrc)) + { + vrc = main(cArgs, papszArgs); + + RTGetOptArgvFree(papszArgs); + } + else + vrc = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvFromString failed: %Rrc", vrc); + RTStrFree(pszCmdLine); + + return vrc; +} +#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..3e81c0b1 --- /dev/null +++ b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxExtPackHelperApp.rc $ */ +/** @file + * VBoxExtPackHelperApp - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include + +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..ff976b83 --- /dev/null +++ b/src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp @@ -0,0 +1,107 @@ +/* $Id: VBoxVolInfo.cpp $ */ +/** @file + * Apps - VBoxVolInfo, Volume information tool. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +extern "C" +{ +#define private privatekw +#include +} +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 \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); +} diff --git a/src/VBox/Main/src-helper-apps/os2/Makefile.kmk b/src/VBox/Main/src-helper-apps/os2/Makefile.kmk new file mode 100644 index 00000000..f5038915 --- /dev/null +++ b/src/VBox/Main/src-helper-apps/os2/Makefile.kmk @@ -0,0 +1,66 @@ +# $Id: Makefile.kmk $ +## @file +# Top-level makefile for src/VBox/Main/src-helper-apps/os2. +# + +# +# Copyright (C) 2022-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# OS/2 Unattended installation helper utility. +# This is checked in as a binary, this is just the makefile for re-builting it. +# +ifdef VBOX_WITH_OPEN_WATCOM + PROGRAMS += os2_util +endif +os2_util_TEMPLATE = DUMMY +os2_util_TOOL = OPENWATCOM-16 +os2_util_ASTOOL = OPENWATCOM-16 +os2_util_LDTOOL = OPENWATCOM-WL +os2_util_BLD_TRG = os2 +os2_util_BLD_TRG_ARCH = x86 +os2_util_EXESUFF = .exe +os2_util_INST = $(INST_UNATTENDED_TEMPLATES) +os2_util_MODE = a+r,u+w +os2_util_DEFS = IN_RING3 +os2_util_CFLAGS = -zl -s -ml -os +os2_util_LDFLAGS = \ + SYSTEM os2 \ + OPTION START=_Os2UtilMain \ + OPTION STACK=8192 \ + OPTION HEAPSize=4096 \ + OPTION NEWFile \ + OPTION PROTmode \ + SEGMENT IOPL IOPL EXECUTEREAD +if 0 + os2_util_LDFLAGS += Debug Watcom All + os2_util_CFLAGS += -d2 -hw +endif + +os2_util_INCS = $(PATH_TOOL_OPENWATCOM)/h/os21x +os2_util_SOURCES = os2_util.c os2_utilA.asm + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Main/src-helper-apps/os2/os2_util.c b/src/VBox/Main/src-helper-apps/os2/os2_util.c new file mode 100644 index 00000000..735d43f6 --- /dev/null +++ b/src/VBox/Main/src-helper-apps/os2/os2_util.c @@ -0,0 +1,1031 @@ +/* $Id: os2_util.c $ */ +/** @file + * Os2Util - Unattended Installation Helper Utility for OS/2. + * + * Helps TEE'ing the installation script output to VBox.log and guest side log + * files. Also helps with displaying program exit codes, something CMD.exe can't. + */ + +/* + * Copyright (C) 2015-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define INCL_BASE +#include +#include +#include + +#include "VBox/version.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define IS_BLANK(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\r' || (ch) == '\n') + +/** NIL HQUEUE value. */ +#define NIL_HQUEUE (~(HQUEUE)0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to buffered output. */ +typedef struct MYBUFFER __far *PMYBUFFER; + +/** Buffered output. */ +typedef struct MYBUFFER +{ + PMYBUFFER pNext; + USHORT cb; + USHORT off; + CHAR sz[65536 - sizeof(USHORT) * 2 - sizeof(PMYBUFFER) - 2]; +} MYBUFFER; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +void __far VBoxBackdoorPrint(PSZ psz, unsigned cch); +static PSZ MyGetOptValue(PSZ psz, PSZ pszOption, PSZ *ppszValue); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static HFILE g_hStdOut = 1; +static HFILE g_hStdErr = 2; +static BOOL g_fOutputToBackdoor = FALSE; +static USHORT g_cBuffers = 0; +static PMYBUFFER g_pBufferHead = NULL; +static PMYBUFFER g_pBufferTail = NULL; + + + +/** strlen-like function. */ +static unsigned MyStrLen(PSZ psz) +{ + unsigned cch = 0; + while (psz[cch] != '\0') + cch++; + return cch; +} + + +/** strchr-like function. */ +static char __far *MyStrChr(const char __far *psz, char chNeedle) +{ + char ch; + while ((ch = *psz) != '\0') + { + if (ch == chNeedle) + return (char __far *)psz; + psz++; + } + return NULL; +} + + +/** memcpy-like function. */ +static void *MyMemCopy(void __far *pvDst, void const __far *pvSrc, USHORT cb) +{ + BYTE __far *pbDst = (BYTE __far *)pvDst; + BYTE const __far *pbSrc = (BYTE const __far *)pvSrc; + while (cb-- > 0) + *pbDst++ = *pbSrc++; + return pvDst; +} + + +static void MyOutStr(PSZ psz) +{ + unsigned const cch = MyStrLen(psz); + USHORT usIgnored; + DosWrite(g_hStdErr, psz, cch, &usIgnored); + if (g_fOutputToBackdoor) + VBoxBackdoorPrint(psz, cch); +} + + +static PSZ MyNumToString(PSZ pszBuf, unsigned uNum) +{ + /* Convert to decimal and inverted digit order: */ + char szTmp[32]; + unsigned off = 0; + do + { + szTmp[off++] = uNum % 10 + '0'; + uNum /= 10; + } while (uNum); + + /* Copy it out to the destination buffer in the right order and add a terminator: */ + while (off-- > 0) + *pszBuf++ = szTmp[off]; + *pszBuf = '\0'; + return pszBuf; +} + + +static void MyOutNum(unsigned uNum) +{ + char szTmp[32]; + MyNumToString(szTmp, uNum); + MyOutStr(szTmp); +} + + +static DECL_NO_RETURN(void) MyApiErrorAndQuit(PSZ pszOperation, USHORT rc) +{ + MyOutStr("Os2Util: error: "); + MyOutStr(pszOperation); + MyOutStr(" failed: "); + MyOutNum(rc); + MyOutStr("\r\n"); + DosExit(EXIT_PROCESS, 1); +} + + +static DECL_NO_RETURN(void) MyApiError3AndQuit(PSZ pszOperation, PSZ psz2, PSZ psz3, USHORT rc) +{ + MyOutStr("Os2Util: error: "); + MyOutStr(pszOperation); + MyOutStr(psz2); + MyOutStr(psz3); + MyOutStr(" failed: "); + MyOutNum(rc); + MyOutStr("\r\n"); + DosExit(EXIT_PROCESS, 1); +} + + +static DECL_NO_RETURN(void) MySyntaxErrorAndQuit(PSZ pszMsg) +{ + MyOutStr("Os2Util: syntax error: "); + MyOutStr(pszMsg); + MyOutStr("\r\n"); + DosExit(EXIT_PROCESS, 1); +} + + +static HFILE OpenTeeFile(PSZ pszTeeToFile, BOOL fAppend, PSZ pszToWrite, USHORT cchToWrite) +{ + PMYBUFFER pBuf, pNext; + USHORT usIgnored; + USHORT usAction = 0; + HFILE hFile = -1; + USHORT rc; + rc = DosOpen(pszTeeToFile, &hFile, &usAction, 0 /*cbInitial*/, 0 /*fFileAttribs*/, + OPEN_ACTION_CREATE_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS, + OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYNONE | OPEN_FLAGS_NOINHERIT | OPEN_FLAGS_SEQUENTIAL, 0 /*Reserved*/); + if (rc == NO_ERROR) + { + + if (fAppend) + { + ULONG offNew = 0; + DosChgFilePtr(hFile, 0, FILE_END, &offNew); + } + + /* + * Write out buffered data + */ + /** @todo this does not seem to work. */ + pBuf = g_pBufferHead; + while (pBuf) + { + do + rc = DosWrite(hFile, pBuf->sz, pBuf->off, &usIgnored); + while (rc == ERROR_INTERRUPT); + pNext = pBuf->pNext; + DosFreeSeg((__segment)pBuf); + pBuf = pNext; + } + g_pBufferTail = g_pBufferHead = NULL; + + /* + * Write the current output. + */ + do + rc = DosWrite(hFile, pszToWrite, cchToWrite, &usIgnored); + while (rc == ERROR_INTERRUPT); + } + else + { + /* + * Failed to open the file. Buffer a bit in case the file can be + * opened later (like when we've formatted the disk). + */ + pBuf = g_pBufferTail; + if (pBuf && pBuf->off < pBuf->cb) + { + USHORT cbToCopy = pBuf->cb - pBuf->off; + if (cbToCopy > cchToWrite) + cbToCopy = cchToWrite; + MyMemCopy(&pBuf->sz[pBuf->off], pszToWrite, cbToCopy); + pszToWrite += cbToCopy; + cchToWrite -= cbToCopy; + } + if (cchToWrite > 0) + { + USHORT uSel = 0xffff; + if ( g_cBuffers < 10 + && (rc = DosAllocSeg(0 /*64KiB*/, &uSel, 0 /*fFlags*/)) == NO_ERROR) + { + pBuf = ((__segment)uSel) :> ((MYBUFFER __near *)0); + pBuf->pNext = NULL; + pBuf->cb = sizeof(pBuf->sz); + pBuf->off = cchToWrite; + MyMemCopy(&pBuf->sz[0], pszToWrite, cchToWrite); + + if (g_pBufferTail) + g_pBufferTail->pNext = pBuf; + else + g_pBufferHead = pBuf; + g_pBufferTail = pBuf; + } + else if (g_cBuffers > 0) + { + pBuf = g_pBufferHead; + pBuf->off = cchToWrite; + MyMemCopy(&pBuf->sz[0], pszToWrite, cchToWrite); + + if (g_pBufferTail != pBuf) + { + g_pBufferHead = pBuf->pNext; + pBuf->pNext = NULL; + g_pBufferTail->pNext = pBuf; + g_pBufferTail = pBuf; + } + } + } + hFile = -1; + } + return hFile; +} + + +/** + * Waits for the child progress to complete, returning it's status code. + */ +static void DoWait(PID pidChild, USHORT idSession, HQUEUE hQueue, PRESULTCODES pResultCodes) +{ + /* + * Can we use DosCwait? + */ + if (hQueue == NIL_HQUEUE) + { + for (;;) + { + PID pidIgnored; + USHORT rc = DosCwait(DCWA_PROCESS, DCWW_WAIT, pResultCodes, &pidIgnored, pidChild); + if (rc == NO_ERROR) + break; + if (rc != ERROR_INTERRUPT) + { + MyOutStr("Os2Util: error: DosCwait(DCWA_PROCESS,DCWW_WAIT,,,"); + MyOutNum(pidChild); + MyOutStr(") failed: "); + MyOutNum(rc); + MyOutStr("\r\n"); + } + } + } + else + { + /* + * No we have to use the queue interface to the session manager. + */ + for (;;) + { + ULONG ulAdderPidAndEvent = 0; + PUSHORT pausData = NULL; + USHORT cbData = 0; + BYTE bPriority = 0; + HSEM hSem = NULL; + USHORT rc = DosReadQueue(hQueue, &ulAdderPidAndEvent, &cbData, (PULONG)&pausData, + 0 /*uElementCode*/, 0 /* fNoWait */, &bPriority, &hSem); + if (rc == NO_ERROR) + { + if (cbData >= sizeof(USHORT) * 2) + { + USHORT idTermSession = pausData[0]; + USHORT uExitCode = pausData[1]; + if (idTermSession == idSession) + { + pResultCodes->codeTerminate = 0; + pResultCodes->codeResult = uExitCode; + break; + } + if (1) + { + MyOutStr("OutUtil: info: idTermSession="); + MyOutNum(idTermSession); + MyOutStr(" uExitCode="); + MyOutNum(uExitCode); + MyOutStr("\r\n"); + } + } + else + { + MyOutStr("OutUtil: warning: bogus queue element size: cbData="); + MyOutNum(cbData); + MyOutStr("\r\n"); + } + DosFreeSeg((__segment)pausData); + } + else if (rc != ERROR_INTERRUPT) + { + DosCloseQueue(hQueue); + MyApiErrorAndQuit("DosReadQueue", rc); + } + } + } +} + + +/** + * Handles --file-to-backdoor / -c. + */ +static void CopyFileToBackdoorAndQuit(PSZ psz, BOOL fLongOpt, PSZ pszBuf, USHORT cbBuf) +{ + HFILE hFile = 0; + USHORT usAction = 0; + USHORT rc; + + /* + * Get the filename and check that it is the last thing on the commandline. + */ + PSZ pszFilename = NULL; + CHAR ch; + psz = MyGetOptValue(psz, fLongOpt ? "--file-to-backdoor" : "-c", &pszFilename); + while ((ch = *psz) != '\0' && IS_BLANK(ch)) + psz++; + if (ch != '\0') + MySyntaxErrorAndQuit("No options allowed after -c/--file-to-backdoor"); + + /* + * Open the file + */ + rc = DosOpen(pszFilename, &hFile, &usAction, 0 /*cbInitial*/, 0 /*fFileAttribs*/, + OPEN_ACTION_FAIL_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS, + OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE | OPEN_FLAGS_NOINHERIT | OPEN_FLAGS_SEQUENTIAL, 0 /*Reserved*/); + if (rc != NO_ERROR) + MyApiError3AndQuit("Failed to open \"", pszFilename, "\" for reading", rc); + + VBoxBackdoorPrint(RT_STR_TUPLE("--- BEGIN OF \"")); + VBoxBackdoorPrint(pszFilename, MyStrLen(pszFilename)); + VBoxBackdoorPrint(RT_STR_TUPLE("\" ---\n")); + + for (;;) + { + USHORT cbRead = 0; + rc = DosRead(hFile, pszBuf, cbBuf, &cbRead); + if (rc == NO_ERROR) + { + if (cbRead == 0) + break; + VBoxBackdoorPrint(pszBuf, cbRead); + } + else if (rc != ERROR_INTERRUPT) + MyApiError3AndQuit("Reading \"", pszFilename, "\"", rc); + } + + VBoxBackdoorPrint(RT_STR_TUPLE("--- END OF \"")); + VBoxBackdoorPrint(pszFilename, MyStrLen(pszFilename)); + VBoxBackdoorPrint(RT_STR_TUPLE("\" ---\n")); + + DosClose(hFile); + DosExit(EXIT_PROCESS, 1); +} + + +/** Displays version string and quits. */ +static DECL_NO_RETURN(void) ShowVersionAndQuit(void) +{ + CHAR szVer[] = "$Rev: 155244 $\r\n"; + USHORT usIgnored; + DosWrite(g_hStdOut, szVer, sizeof(szVer) - 1, &usIgnored); + DosExit(EXIT_PROCESS, 0); +} + + +/** Displays usage info and quits. */ +static DECL_NO_RETURN(void) ShowUsageAndQuit(void) +{ + static char s_szHelp[] = + VBOX_PRODUCT " OS/2 Unattended Helper Version " VBOX_VERSION_STRING "\r\n" + "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\r\n" + "\r\n" + "Os2Util.exe is tiny helper utility that implements TEE'ing to the VBox release\r\n" + "log, files and shows the actual exit code of a program. Standard error and\r\n" + "output will be merged into one for simplicity reasons.\r\n" + "\r\n" + "Usage: Os2Util.exe [-a|--append] [-f|--tee-to-file ] \\\r\n" + " [-b|--tee-to-backdoor] [-z|--as-zero [..]] \\\r\n" + " -- [args]\r\n" + " or Os2Util.exe <-w|--write-backdoor >\r\n" + " or Os2Util.exe <-c|--file-to-backdoor >\r\n" + "\r\n" + "Note! Does not supported any kind of quoting before the child arguments.\r\n" + ; + USHORT usIgnored; + DosWrite(g_hStdOut, s_szHelp, sizeof(s_szHelp) - 1, &usIgnored); + DosExit(EXIT_PROCESS, 0); +} + + +/** + * Gets the an option value. + * + * The option value string will be terminated. + */ +static PSZ MyGetOptValue(PSZ psz, PSZ pszOption, PSZ *ppszValue) +{ + CHAR ch; + while ((ch = *psz) != '\0' && IS_BLANK(ch)) + psz++; + if (*psz == '\0') + { + MyOutStr("Os2Util: syntax error: Option '"); + MyOutStr(pszOption); + MyOutStr("' takes a value\r\n"); + DosExit(EXIT_PROCESS, 2); + } + + *ppszValue = psz; + + while ((ch = *psz) != '\0' && !IS_BLANK(ch)) + psz++; + if (ch != '\0') + *psz++ = '\0'; + return psz; +} + + +/** + * Gets the an numeric option value. + */ +static PSZ MyGetOptNum(PSZ psz, PSZ pszOption, PUSHORT puValue) +{ + PSZ pszError = NULL; + PSZ pszValue = NULL; + PSZ const pszRet = MyGetOptValue(psz, pszOption, &pszValue); + PSZ const pszValueStart = pszValue; + USHORT uValue = 0; + CHAR ch; + if (pszValue[0] == '0' && ((ch = pszValue[1]) == 'x' || ch == 'X')) + { + pszValue += 2; + while ((ch = *pszValue++) != '\0') + { + BYTE bDigit; + if (ch <= '9' && ch >= '0') + bDigit = ch - '0'; + else if (ch <= 'f' && ch >= 'a') + bDigit = ch - 'a' + 10; + else if (ch <= 'F' && ch >= 'A') + bDigit = ch - 'A' + 10; + else + { + pszError = "': invalid hex value\r\n"; + break; + } + if (uValue >> 12) + { + pszError = "': hex value out of range\r\n"; + break; + } + uValue <<= 4; + uValue |= bDigit; + } + } + else + { + while ((ch = *pszValue++) != '\0') + { + BYTE bDigit; + if (ch <= '9' && ch >= '0') + bDigit = ch - '0'; + else + { + pszError = "': invalid decimal value\r\n"; + break; + } + if (uValue * 10 / 10 != uValue) + { + pszError = "': decimal value out of range\r\n"; + break; + } + uValue *= 10; + uValue += bDigit; + } + } + + if (pszError) + { + MyOutStr("Os2Util: syntax error: Option '"); + MyOutStr(pszOption); + MyOutStr("' with value '"); + MyOutStr(pszValueStart); + MyOutStr(pszError); + DosExit(EXIT_PROCESS, 2); + } + + *puValue = uValue; + return pszRet; +} + + +/** + * Checks if @a pszOption matches @a *ppsz, advance *ppsz if TRUE. + */ +static BOOL MyMatchLongOption(PSZ _far *ppsz, PSZ pszOption, unsigned cchOption) +{ + /* Match option and command line strings: */ + PSZ psz = *ppsz; + while (cchOption-- > 0) + { + if (*psz != *pszOption) + return FALSE; + psz++; + pszOption++; + } + + /* Is this the end of a word on the command line? */ + if (*psz == '\0') + *ppsz = psz; + else if (IS_BLANK(*psz)) + *ppsz = psz + 1; + else + return FALSE; + return TRUE; +} + + +/** + * The entrypoint (no crt). + */ +#pragma aux Os2UtilMain "_*" parm caller [ ax ] [ bx ]; +void Os2UtilMain(USHORT uSelEnv, USHORT offCmdLine) +{ + PSZ pszzEnv = ((__segment)uSelEnv) :> ((char _near *)0); + PSZ pszzCmdLine = ((__segment)uSelEnv) :> ((char _near *)offCmdLine); + USHORT uExitCode = 1; + BOOL fTeeToBackdoor = FALSE; + BOOL fAppend = FALSE; + PSZ pszTeeToFile = NULL; + HFILE hTeeToFile = -1; + HFILE hPipeRead = -1; + PSZ pszzNewCmdLine; + PSZ psz; + CHAR ch; + USHORT usIgnored; + USHORT rc; + RESULTCODES ResultCodes = { 0xffff, 0xffff }; + CHAR szBuf[512]; + CHAR szExeFull[CCHMAXPATH]; + PSZ pszExe; + USHORT uExeType; + USHORT idSession = 0; + PID pidChild = 0; + HQUEUE hQueue = NIL_HQUEUE; + CHAR szQueueName[64]; + unsigned cAsZero = 0; + USHORT auAsZero[16]; + + /* + * Parse the command line. + * Note! We do not accept any kind of quoting. + */ + /* Skip the executable filename: */ + psz = pszzCmdLine; + while (*psz != '\0') + psz++; + psz++; + + /* Now parse arguments. */ + while ((ch = *psz) != '\0') + { + if (IS_BLANK(ch)) + psz++; + else if (ch != '-') + break; + else + { + PSZ const pszOptStart = psz; + ch = *++psz; + if (ch == '-') + { + ch = *++psz; + if (IS_BLANK(ch)) + { + /* Found end-of-arguments marker "--" */ + psz++; + break; + } + if (ch == 'a' && MyMatchLongOption(&psz, RT_STR_TUPLE("append"))) + fAppend = TRUE; + else if (ch == 'a' && MyMatchLongOption(&psz, RT_STR_TUPLE("as-zero"))) + { + if (cAsZero > RT_ELEMENTS(auAsZero)) + MySyntaxErrorAndQuit("Too many --as-zero/-z options"); + psz = MyGetOptNum(psz, "--as-zero", &auAsZero[cAsZero]); + cAsZero++; + } + else if (ch == 'f' && MyMatchLongOption(&psz, RT_STR_TUPLE("file-to-backdoor"))) + CopyFileToBackdoorAndQuit(psz, TRUE /*fLongOpt*/, szBuf, sizeof(szBuf)); + else if (ch == 'h' && MyMatchLongOption(&psz, RT_STR_TUPLE("help"))) + ShowUsageAndQuit(); + else if (ch == 't' && MyMatchLongOption(&psz, RT_STR_TUPLE("tee-to-backdoor"))) + g_fOutputToBackdoor = fTeeToBackdoor = TRUE; + else if (ch == 't' && MyMatchLongOption(&psz, RT_STR_TUPLE("tee-to-file"))) + psz = MyGetOptValue(psz, "--tee-to-file", &pszTeeToFile); + else if (ch == 'v' && MyMatchLongOption(&psz, RT_STR_TUPLE("version"))) + ShowVersionAndQuit(); + else if (ch == 'w' && MyMatchLongOption(&psz, RT_STR_TUPLE("write-backdoor"))) + { + VBoxBackdoorPrint(psz, MyStrLen(psz)); + VBoxBackdoorPrint("\n", 1); + DosExit(EXIT_PROCESS, 0); + } + else + { + MyOutStr("Os2util: syntax error: "); + MyOutStr(pszOptStart); + MyOutStr("\r\n"); + DosExit(EXIT_PROCESS, 2); + } + } + else + { + do + { + if (ch == 'a') + fAppend = TRUE; + else if (ch == 'b') + g_fOutputToBackdoor = fTeeToBackdoor = TRUE; + else if (ch == 'c') + CopyFileToBackdoorAndQuit(psz + 1, FALSE /*fLongOpt*/, szBuf, sizeof(szBuf)); + else if (ch == 'f') + { + psz = MyGetOptValue(psz + 1, "-f", &pszTeeToFile); + break; + } + else if (ch == 'w') + { + psz++; + VBoxBackdoorPrint(psz, MyStrLen(psz)); + VBoxBackdoorPrint("\n", 1); + DosExit(EXIT_PROCESS, 0); + } + else if (ch == 'z') + { + if (cAsZero > RT_ELEMENTS(auAsZero)) + MySyntaxErrorAndQuit("Too many --as-zero/-z options"); + psz = MyGetOptNum(psz + 1, "-z", &auAsZero[cAsZero]); + cAsZero++; + } + else if (ch == '?' || ch == 'h' || ch == 'H') + ShowUsageAndQuit(); + else if (ch == 'V') + ShowVersionAndQuit(); + else + { + MyOutStr("Os2util: syntax error: "); + if (ch) + DosWrite(g_hStdErr, &ch, 1, &usIgnored); + else + MyOutStr("lone dash"); + MyOutStr(" ("); + MyOutStr(pszOptStart); + MyOutStr(")\r\n"); + DosExit(EXIT_PROCESS, 2); + } + ch = *++psz; + } while (!IS_BLANK(ch) && ch != '\0'); + } + } + } + + /* + * Zero terminate the executable name in the command line. + */ + pszzNewCmdLine = psz; + if (ch == '\0') + { + MyOutStr("Os2Util: syntax error: No program specified\r\n"); + DosExit(EXIT_PROCESS, 2); + } + psz++; + while ((ch = *psz) != '\0' && !IS_BLANK(ch)) + psz++; + *psz++ = '\0'; + + /* + * Find the executable and check its type. + */ + if ( pszzNewCmdLine[1] == ':' + || MyStrChr(pszzNewCmdLine, '\\') + || MyStrChr(pszzNewCmdLine, '/')) + pszExe = pszzNewCmdLine; + else + { + rc = DosSearchPath(SEARCH_CUR_DIRECTORY | SEARCH_ENVIRONMENT | SEARCH_IGNORENETERRS, "PATH", + pszzNewCmdLine, szExeFull, sizeof(szExeFull)); + if (rc != NO_ERROR) + MyApiError3AndQuit("DosSearchPath(7, \"PATH\", \"", pszzNewCmdLine, "\",,)", rc); + pszExe = &szExeFull[0]; + } + + /* Perhapse we should use WinQueryProgramType here instead? */ + rc = DosQAppType(pszExe, &uExeType); + if (rc != NO_ERROR) + MyApiErrorAndQuit("DosQAppType(pszExe, &uExeType)", rc); +#ifdef DEBUG + MyOutStr("Os2Util: debug: uExeType="); MyOutNum(uExeType); MyOutStr("\r\n"); +#endif + /** @todo deal with launching winos2 programs too... */ + + /* + * Prepare redirection. + */ + if (fTeeToBackdoor || pszTeeToFile != NULL) + { + HFILE hPipeWrite = -1; + HFILE hDup; + + /* Make new copies of the standard handles. */ + hDup = 0xffff; + rc = DosDupHandle(g_hStdErr, &hDup); + if (rc != NO_ERROR) + MyApiErrorAndQuit("DosDupHandle(g_hStdErr, &hDup)", rc); + g_hStdErr = hDup; + DosSetFHandState(hDup, OPEN_FLAGS_NOINHERIT); /* not strictly necessary, so ignore errors */ + + hDup = 0xffff; + rc = DosDupHandle(g_hStdOut, &hDup); + if (rc != NO_ERROR) + MyApiErrorAndQuit("DosDupHandle(g_hStdOut, &hDup)", rc); + g_hStdOut = hDup; + DosSetFHandState(hDup, OPEN_FLAGS_NOINHERIT); /* not strictly necessary, so ignore errors */ + + /* Create the pipe and make the read-end non-inheritable (we'll hang otherwise). */ + rc = DosMakePipe(&hPipeRead, &hPipeWrite, 0 /*default size*/); + if (rc != NO_ERROR) + MyApiErrorAndQuit("DosMakePipe", rc); + + rc = DosSetFHandState(hPipeRead, OPEN_FLAGS_NOINHERIT); + if (rc != NO_ERROR) + MyApiErrorAndQuit("DosSetFHandState(hPipeRead, OPEN_FLAGS_NOINHERIT)", rc); + + /* Replace standard output and standard error with the write end of the pipe. */ + hDup = 1; + rc = DosDupHandle(hPipeWrite, &hDup); + if (rc != NO_ERROR) + MyApiErrorAndQuit("DosDupHandle(hPipeWrite, &hDup[=1])", rc); + + hDup = 2; + rc = DosDupHandle(hPipeWrite, &hDup); + if (rc != NO_ERROR) + MyApiErrorAndQuit("DosDupHandle(hPipeWrite, &hDup[=2])", rc); + + /* We can close the write end of the pipe as we don't need the original handle any more. */ + DosClose(hPipeWrite); + } + + /* + * Execute the program. + */ + szBuf[0] = '\0'; +#define FAPPTYP_TYPE_MASK 7 + if ((uExeType & FAPPTYP_TYPE_MASK) == PT_WINDOWABLEVIO) /** @todo what if we're in fullscreen ourselves? */ + { + /* For same type programs we can use DosExecPgm: */ + rc = DosExecPgm(szBuf, sizeof(szBuf), hPipeRead == -1 ? EXEC_SYNC : EXEC_ASYNCRESULT, + pszzNewCmdLine, pszzEnv, &ResultCodes, pszExe); + if (rc != NO_ERROR) + { + MyOutStr("Os2Util: error: DosExecPgm failed for \""); + MyOutStr(pszzNewCmdLine); + MyOutStr("\": "); + MyOutNum(rc); + if (szBuf[0]) + { + MyOutStr(" ErrObj="); + szBuf[sizeof(szBuf) - 1] = '\0'; + MyOutStr(szBuf); + } + MyOutStr("\r\n"); + DosExit(EXIT_PROCESS, 1); + } + if (hPipeRead != -1) + { + pidChild = ResultCodes.codeTerminate; + MyOutStr("info: started pid "); + MyOutNum(pidChild); + MyOutStr("\r\n"); + } + } + else + { + /* For different typed programs we have to use DosStartSession, which + is a lot more tedious to use. */ + static const char s_szQueueBase[] = "\\QUEUES\\OS2_UTIL-"; + union + { + STARTDATA StartData; + BYTE abPadding[sizeof(STARTDATA) + 64]; + struct + { + STARTDATA Core; + ULONG ulReserved; + PSZ pszBuf; + USHORT cbBuf; + } s; + } u; + PIDINFO PidInfo = {0, 0, 0}; + + /* Create the wait queue first. */ + DosGetPID(&PidInfo); + MyMemCopy(szQueueName, s_szQueueBase, sizeof(s_szQueueBase)); + MyNumToString(&szQueueName[sizeof(s_szQueueBase) - 1], PidInfo.pid); + + rc = DosCreateQueue(&hQueue, 0 /*FIFO*/, szQueueName); + if (rc != NO_ERROR) + MyApiError3AndQuit("DosCreateQueue(&hQueue, 0, \"", szQueueName, "\")", rc); + + u.StartData.Length = sizeof(u.StartData); + u.StartData.Related = 1 /* SSF_RELATED_CHILD */; + u.StartData.FgBg = (uExeType & FAPPTYP_TYPE_MASK) == PT_PM + ? 1 /* SSF_FGBG_BACK - try avoid ERROR_SMG_START_IN_BACKGROUND */ + : 0 /* SSF_FGBG_FORE */; + u.StartData.TraceOpt = 0 /* SSF_TRACEOPT_NONE */; + u.StartData.PgmTitle = NULL; + u.StartData.PgmName = pszExe; + u.StartData.PgmInputs = psz; /* just arguments, not exec apparently.*/ + u.StartData.TermQ = szQueueName; + u.StartData.Environment = NULL; /* Inherit our env. Note! Using pszzEnv causes it to be freed + and we'll crash reporting the error. */ + u.StartData.InheritOpt = 1 /* SSF_INHERTOPT_PARENT */; + u.StartData.SessionType = uExeType & FAPPTYP_TYPE_MASK; + if (uExeType & 0x20 /*FAPPTYP_DOS*/) + u.StartData.SessionType = 4 /* SSF_TYPE_VDM */; + u.StartData.IconFile = NULL; + u.StartData.PgmHandle = 0; + u.StartData.PgmControl = 0 /* SSF_CONTROL_VISIBLE */; + u.StartData.InitXPos = 0; + u.StartData.InitYPos = 0; + u.StartData.InitXSize = 0; + u.StartData.InitYSize = 0; + u.s.ulReserved = 0; + u.s.pszBuf = NULL; + u.s.cbBuf = 0; + + rc = DosStartSession(&u.StartData, &idSession, &pidChild); + if (rc != NO_ERROR && rc != ERROR_SMG_START_IN_BACKGROUND) + { + DosCloseQueue(hQueue); + MyApiError3AndQuit("DosStartSession for \"", pszExe, "\"", rc); + } + + if (1) + { + MyOutStr("info: started session "); + MyOutNum(idSession); + MyOutStr(", pid "); + MyOutNum(pidChild); + MyOutStr("\r\n"); + } + } + + /* + * Wait for the child process to complete. + */ + if (hPipeRead != -1) + { + + /* Close the write handles or we'll hang in the read loop. */ + DosClose(1); + DosClose(2); + + /* Disable hard error popups (file output to unformatted disks). */ + DosError(2 /* only exceptions */); + + /* + * Read the pipe and tee it to the desired outputs + */ + for (;;) + { + USHORT cbRead = 0; + rc = DosRead(hPipeRead, szBuf, sizeof(szBuf), &cbRead); + if (rc == NO_ERROR) + { + if (cbRead == 0) + break; /* No more writers. */ + + /* Standard output: */ + do + rc = DosWrite(g_hStdOut, szBuf, cbRead, &usIgnored); + while (rc == ERROR_INTERRUPT); + + /* Backdoor: */ + if (fTeeToBackdoor) + VBoxBackdoorPrint(szBuf, cbRead); + + /* File: */ + if (hTeeToFile != -1) + do + rc = DosWrite(hTeeToFile, szBuf, cbRead, &usIgnored); + while (rc == ERROR_INTERRUPT); + else if (pszTeeToFile != NULL) + hTeeToFile = OpenTeeFile(pszTeeToFile, fAppend, szBuf, cbRead); + } + else if (rc == ERROR_BROKEN_PIPE) + break; + else + { + MyOutStr("Os2Util: error: Error reading pipe: "); + MyOutNum(rc); + MyOutStr("\r\n"); + break; + } + } + + DosClose(hPipeRead); + + /* + * Wait for the process to complete. + */ + DoWait(pidChild, idSession, hQueue, &ResultCodes); + } + /* + * Must wait for the session completion too. + */ + else if (idSession != 0) + DoWait(pidChild, idSession, hQueue, &ResultCodes); + + /* + * Report the status code and quit. + */ + MyOutStr("Os2Util: Child: "); + MyOutStr(pszzNewCmdLine); + MyOutStr(" "); + MyOutStr(psz); + MyOutStr("\r\n" + "Os2Util: codeTerminate="); + MyOutNum(ResultCodes.codeTerminate); + MyOutStr(" codeResult="); + MyOutNum(ResultCodes.codeResult); + MyOutStr("\r\n"); + + /* Treat it as zero? */ + if (ResultCodes.codeTerminate == 0) + { + unsigned i = cAsZero; + while (i-- > 0) + if (auAsZero[i] == ResultCodes.codeResult) + { + MyOutStr("Os2Util: info: treating status as zero\r\n"); + ResultCodes.codeResult = 0; + break; + } + } + + if (idSession != 0) + DosCloseQueue(hQueue); + for (;;) + DosExit(EXIT_PROCESS, ResultCodes.codeTerminate == 0 ? ResultCodes.codeResult : 127); +} + + +/** + * Backdoor print function living in an IOPL=2 segment. + */ +#pragma code_seg("IOPL", "CODE") +void __far VBoxBackdoorPrint(PSZ psz, unsigned cch) +{ + ASMOutStrU8(RTLOG_DEBUG_PORT, psz, cch); +} + diff --git a/src/VBox/Main/src-helper-apps/os2/os2_utilA.asm b/src/VBox/Main/src-helper-apps/os2/os2_utilA.asm new file mode 100644 index 00000000..b309f83b --- /dev/null +++ b/src/VBox/Main/src-helper-apps/os2/os2_utilA.asm @@ -0,0 +1,54 @@ +; $Id: os2_utilA.asm $ +;; @file +; Os2UtilA - Watcom assembly file that defines the stack. +; + +; +; Copyright (C) 2022-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see . +; +; SPDX-License-Identifier: GPL-3.0-only +; + +DGROUP group _NULL,CONST,CONST2,STRINGS,_DATA,_BSS,STACK + +_NULL segment para public 'BEGDATA' + dw 10 dup(0) +_NULL ends + +CONST segment word public 'DATA' +CONST ends + +CONST2 segment word public 'DATA' +CONST2 ends + +STRINGS segment word public 'DATA' +STRINGS ends + +_DATA segment word public 'DATA' +_DATA ends + +_BSS segment word public 'BSS' +_BSS ends + +STACK segment para stack 'STACK' + db 1000h dup(?) +STACK ends + + end + -- cgit v1.2.3