summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-helper-apps
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Main/src-helper-apps
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/VBox/Main/src-helper-apps/Makefile.kup0
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk107
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTest.cpp109
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestApp.cpp526
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/OpenGLTestDarwin.cpp183
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/VBoxFBOverlayCommon.h131
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/VBoxGLSupportInfo.cpp724
-rw-r--r--src/VBox/Main/src-helper-apps/OpenGLTest/VBoxTestOGL.rc61
-rw-r--r--src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp2016
-rw-r--r--src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.rc61
-rw-r--r--src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp107
-rw-r--r--src/VBox/Main/src-helper-apps/os2/Makefile.kmk66
-rw-r--r--src/VBox/Main/src-helper-apps/os2/os2_util.c1031
-rw-r--r--src/VBox/Main/src-helper-apps/os2/os2_utilA.asm54
14 files changed, 5176 insertions, 0 deletions
diff --git a/src/VBox/Main/src-helper-apps/Makefile.kup b/src/VBox/Main/src-helper-apps/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-helper-apps/Makefile.kup
diff --git a/src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk b/src/VBox/Main/src-helper-apps/OpenGLTest/Makefile.kmk
new file mode 100644
index 00000000..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 <https://www.gnu.org/licenses>.
+#
+# 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/err.h>
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/env.h>
+#include <iprt/log.h>
+
+#include <VBox/VBoxOGL.h>
+
+bool RTCALL VBoxOglIs3DAccelerationSupported(void)
+{
+ if (RTEnvExist("VBOX_3D_FORCE_SUPPORTED"))
+ {
+ LogRel(("VBOX_3D_FORCE_SUPPORTED is specified, skipping 3D test, and treating as supported\n"));
+ return true;
+ }
+
+ static char pszVBoxPath[RTPATH_MAX];
+ const char *papszArgs[4] = { NULL, "-test", "3D", NULL};
+
+#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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+# include <sys/resource.h>
+# include <fcntl.h>
+# include <unistd.h>
+#endif
+
+#include <string.h>
+
+#define VBOXGLTEST_WITH_LOGGING /** @todo r=andy Is this intentional? */
+
+#ifdef VBOXGLTEST_WITH_LOGGING
+# include "package-generated.h"
+
+# include <iprt/log.h>
+# include <iprt/param.h>
+# include <iprt/time.h>
+# include <iprt/system.h>
+# include <iprt/process.h>
+# include <iprt/env.h>
+
+# include <VBox/log.h>
+# include <VBox/version.h>
+#endif /* VBOXGLTEST_WITH_LOGGING */
+
+#ifndef RT_OS_WINDOWS
+# include <GL/gl.h> /* For GLubyte and friends. */
+#endif
+
+#ifdef VBOX_WITH_VIDEOHWACCEL
+# include <QApplication>
+# if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && defined(RT_OS_WINDOWS)
+# include <QGLWidget> /* for GL headers on windows */
+# endif
+# include <VBox/VBoxGL2D.h>
+#endif
+
+/**
+ * The OpenGL methods to look for when checking 3D presence.
+ */
+static const char * const g_apszOglMethods[] =
+{
+#ifdef RT_OS_WINDOWS
+ "wglCreateContext",
+ "wglDeleteContext",
+ "wglMakeCurrent",
+ "wglShareLists",
+#elif defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) || defined(RT_OS_SOLARIS)
+ "glXQueryVersion",
+ "glXChooseVisual",
+ "glXCreateContext",
+ "glXMakeCurrent",
+ "glXDestroyContext",
+#endif
+ "glAlphaFunc",
+ "glBindTexture",
+ "glBlendFunc",
+ "glClear",
+ "glClearColor",
+ "glClearDepth",
+ "glClearStencil",
+ "glClipPlane",
+ "glColorMask",
+ "glColorPointer",
+ "glCullFace",
+ "glDeleteTextures",
+ "glDepthFunc",
+ "glDepthMask",
+ "glDepthRange",
+ "glDisable",
+ "glDisableClientState",
+ "glDrawArrays",
+ "glDrawElements",
+ "glEnable",
+ "glEnableClientState",
+ "glFogf",
+ "glFogfv",
+ "glFogi",
+ "glFrontFace",
+ "glGenTextures",
+ "glGetBooleanv",
+ "glGetError",
+ "glGetFloatv",
+ "glGetIntegerv",
+ "glGetString",
+ "glGetTexImage",
+ "glLightModelfv",
+ "glLightf",
+ "glLightfv",
+ "glLineWidth",
+ "glLoadIdentity",
+ "glLoadMatrixf",
+ "glMaterialfv",
+ "glMatrixMode",
+ "glMultMatrixf",
+ "glNormalPointer",
+ "glPixelStorei",
+ "glPointSize",
+ "glPolygonMode",
+ "glPolygonOffset",
+ "glPopAttrib",
+ "glPopMatrix",
+ "glPushAttrib",
+ "glPushMatrix",
+ "glScissor",
+ "glShadeModel",
+ "glStencilFunc",
+ "glStencilMask",
+ "glStencilOp",
+ "glTexCoordPointer",
+ "glTexImage2D",
+ "glTexParameterf",
+ "glTexParameterfv",
+ "glTexParameteri",
+ "glTexSubImage2D",
+ "glVertexPointer",
+ "glViewport"
+};
+
+
+/**
+ * Tries to resolve the given OpenGL symbol.
+ *
+ * @returns Pointer to the symbol or nULL on error.
+ * @param pszSymbol The symbol to resolve.
+ */
+DECLINLINE(PFNRT) vboxTestOglGetProc(const char *pszSymbol)
+{
+ int 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_file_name> 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=<log_file_name> 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/VBoxOGL.h>
+
+#include <IOKit/IOKitLib.h>
+#include <OpenGL/OpenGL.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include <OpenGL/gl.h>
+#include <OpenGL/glu.h>
+
+#include <iprt/env.h>
+#include <iprt/log.h>
+#include <iprt/once.h>
+
+
+
+/**
+ * @callback_method_impl{FNRTONCE,
+ * For determining the cached VBoxOglIsOfflineRenderingAppropriate result.}
+ */
+static DECLCALLBACK(int32_t) vboxOglIsOfflineRenderingAppropriateOnce(void *pvUser)
+{
+ bool *pfAppropriate = (bool *)pvUser;
+
+ /* It is assumed that it is makes sense to enable offline rendering
+ only in case if host has more than one GPU installed. This routine
+ counts all the PCI devices in IORegistry which have IOName property
+ set to "display". If the number of such devices is greater than one,
+ it sets pfAppropriate to TRUE, otherwise to FALSE. */
+
+ CFStringRef apKeyStrings[] = { CFSTR(kIOProviderClassKey), CFSTR(kIONameMatchKey) };
+ CFStringRef apValueStrings[] = { CFSTR("IOPCIDevice"), CFSTR("display") };
+ Assert(RT_ELEMENTS(apKeyStrings) == RT_ELEMENTS(apValueStrings));
+
+ CFDictionaryRef pMatchingDictionary = CFDictionaryCreate(kCFAllocatorDefault,
+ (const void **)apKeyStrings,
+ (const void **)apValueStrings,
+ RT_ELEMENTS(apKeyStrings),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (pMatchingDictionary)
+ {
+ /* The reference to pMatchingDictionary is consumed by the function below => no IORelease(pMatchingDictionary)! */
+ io_iterator_t matchingServices;
+ kern_return_t krc = IOServiceGetMatchingServices(kIOMasterPortDefault, pMatchingDictionary, &matchingServices);
+ if (krc == kIOReturnSuccess)
+ {
+ io_object_t matchingService;
+ int cMatchingServices = 0;
+
+ while ((matchingService = IOIteratorNext(matchingServices)) != 0)
+ {
+ cMatchingServices++;
+ IOObjectRelease(matchingService);
+ }
+
+ *pfAppropriate = cMatchingServices > 1;
+
+ IOObjectRelease(matchingServices);
+ }
+ }
+
+ LogRel(("OpenGL: Offline rendering support is %s (pid=%d)\n", *pfAppropriate ? "ON" : "OFF", (int)getpid()));
+ return VINF_SUCCESS;
+}
+
+
+bool RTCALL VBoxOglIsOfflineRenderingAppropriate(void)
+{
+ /* In order to do not slowdown 3D engine which can ask about offline rendering several times,
+ let's cache the result and assume that renderers amount value is constant. Use the IPRT
+ execute once construct to make sure there aren't any threading issues. */
+ static RTONCE s_Once = RTONCE_INITIALIZER;
+ static bool s_fCached = false;
+ int 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 <https://www.gnu.org/licenses>.
+ *
+ * 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 <iprt/time.h>
+
+#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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h> /* QGLWidget drags in Windows.h; -Wall forces us to use wrapper. */
+# include <iprt/stdint.h> /* QGLWidget drags in stdint.h; -Wall forces us to use wrapper. */
+#endif
+
+#include <QGuiApplication> /* For QT_VERSION */
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+# include <QMainWindow>
+# include <QOpenGLWidget>
+# include <QOpenGLContext>
+#else
+# include <QGLWidget>
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/log.h>
+#include <iprt/env.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+
+#include <VBox/VBoxGL2D.h>
+#include "VBoxFBOverlayCommon.h"
+#include <iprt/err.h>
+
+
+/*****************/
+
+/* 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox OpenGL Test Tool\0"
+ VALUE "InternalName", "VBoxTestOGL\0"
+ VALUE "OriginalFilename", "VBoxTestOGL.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp b/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp
new file mode 100644
index 00000000..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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "../include/ExtPackUtil.h"
+
+#include <iprt/buildconfig.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/fs.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/manifest.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/thread.h>
+#include <iprt/utf16.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+#include <iprt/cpp/ministring.h>
+
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <VBox/sup.h>
+#include <VBox/version.h>
+
+#ifdef RT_OS_WINDOWS
+# define _WIN32_WINNT 0x0501
+# include <iprt/win/windows.h> /* ShellExecuteEx, ++ */
+# include <iprt/win/objbase.h> /* CoInitializeEx */
+# ifdef DEBUG
+# include <Sddl.h>
+# endif
+#endif
+
+#ifdef RT_OS_DARWIN
+# include <Security/Authorization.h>
+# include <Security/AuthorizationTags.h>
+# include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#if !defined(RT_OS_OS2)
+# include <stdio.h>
+# include <errno.h>
+# if !defined(RT_OS_WINDOWS)
+# include <sys/types.h>
+# include <unistd.h> /* geteuid */
+# endif
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Enable elevation on Windows and Darwin. */
+#if !defined(RT_OS_OS2) || defined(DOXYGEN_RUNNING)
+# define WITH_ELEVATION
+#endif
+
+
+/** @name Command and option names
+ * @{ */
+#define CMD_INSTALL 1000
+#define CMD_UNINSTALL 1001
+#define CMD_CLEANUP 1002
+#ifdef WITH_ELEVATION
+# define OPT_ELEVATED 1090
+# define OPT_STDOUT 1091
+# define OPT_STDERR 1092
+#endif
+#define OPT_DISP_INFO_HACK 1093
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+static HINSTANCE g_hInstance;
+#endif
+
+#ifdef IN_RT_R3
+/* Override RTAssertShouldPanic to prevent gdb process creation. */
+RTDECL(bool) RTAssertShouldPanic(void)
+{
+ return true;
+}
+#endif
+
+
+
+/**
+ * Handle the special standard options when these are specified after the
+ * command.
+ *
+ * @param ch The option character.
+ */
+static RTEXITCODE DoStandardOption(int ch)
+{
+ switch (ch)
+ {
+ case 'h':
+ {
+ RTMsgInfo(VBOX_PRODUCT " Extension Pack Helper App\n"
+ "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
+ "\n"
+ "This NOT intended for general use, please use VBoxManage instead\n"
+ "or call the IExtPackManager API directly.\n"
+ "\n"
+ "Usage: %s <command> [options]\n"
+ "Commands:\n"
+ " install --base-dir <dir> --cert-dir <dir> --name <name> \\\n"
+ " --tarball <tarball> --tarball-fd <fd>\n"
+ " uninstall --base-dir <dir> --name <name>\n"
+ " cleanup --base-dir <dir>\n"
+ , RTProcShortName());
+ return RTEXITCODE_SUCCESS;
+ }
+
+ case 'V':
+ RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ AssertFailedReturn(RTEXITCODE_FAILURE);
+ }
+}
+
+
+/**
+ * Checks if the cerficiate directory is valid.
+ *
+ * @returns true if it is valid, false if it isn't.
+ * @param pszCertDir The certificate directory to validate.
+ */
+static bool IsValidCertificateDir(const char *pszCertDir)
+{
+ /*
+ * Just be darn strict for now.
+ */
+ char szCorrect[RTPATH_MAX];
+ int 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "FileDescription", "VirtualBox ExtPack Helper\0"
+ VALUE "InternalName", "VBoxExtPackHelperApp\0"
+ VALUE "OriginalFilename", "VBoxExtPackHelperApp.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp b/src/VBox/Main/src-helper-apps/VBoxVolInfo.cpp
new file mode 100644
index 00000000..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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <dirent.h>
+extern "C"
+{
+#define private privatekw
+#include <libdevmapper.h>
+}
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+/*********************************************************************************************************************************
+* Function Prototypes *
+*********************************************************************************************************************************/
+void print_dev_name(dev_t devid);
+
+/*
+ * Extracts logical volume dependencies via devmapper API and print them out.
+ */
+int main(int argc, char **argv)
+{
+ struct dm_task *dmtask;
+ struct dm_info dminfo;
+
+ if (argc != 2)
+ {
+ fprintf(stderr, "USAGE: %s <volume_name>\n", argv[0]);
+ return 1;
+ }
+
+ dmtask = dm_task_create(DM_DEVICE_DEPS);
+ if (!dmtask)
+ return 2;
+
+ if (dm_task_set_name(dmtask, argv[1]))
+ if (dm_task_run(dmtask))
+ if (dm_task_get_info(dmtask, &dminfo))
+ {
+ struct dm_deps *dmdeps = dm_task_get_deps(dmtask);
+ if (dmdeps)
+ {
+ unsigned i;
+ for (i = 0; i < dmdeps->count; ++i)
+ print_dev_name(dmdeps->device[i]);
+ }
+ }
+
+ dm_task_destroy(dmtask);
+ return 0;
+}
+
+/*
+ * Looks up device name by id using /dev directory. Prints it to stdout.
+ */
+void print_dev_name(dev_t devid)
+{
+ char path[PATH_MAX];
+ struct dirent *de;
+ DIR *dir = opendir("/dev");
+
+ while ((de = readdir(dir)) != NULL)
+ {
+ struct stat st;
+ snprintf(path, sizeof(path), "/dev/%s", de->d_name);
+ if (!stat(path, &st))
+ if (S_ISBLK(st.st_mode))
+ if (devid == st.st_rdev)
+ {
+ puts(de->d_name);
+ break;
+ }
+ }
+ closedir(dir);
+}
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 <https://www.gnu.org/licenses>.
+#
+# 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define INCL_BASE
+#include <os2.h>
+#include <iprt/asm-amd64-x86.h>
+#include <VBox/log.h>
+
+#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<filename>|--tee-to-file <filename>] \\\r\n"
+ " [-b|--tee-to-backdoor] [-z<exit>|--as-zero <exit> [..]] \\\r\n"
+ " -- <prog> [args]\r\n"
+ " or Os2Util.exe <-w<msg>|--write-backdoor <msg>>\r\n"
+ " or Os2Util.exe <-c<file>|--file-to-backdoor <file>>\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 <https://www.gnu.org/licenses>.
+;
+; 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
+