summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/common/VBoxService
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Additions/common/VBoxService')
-rw-r--r--src/VBox/Additions/common/VBoxService/Makefile.kmk220
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxService-os2.def33
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxService-win.cpp670
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxService.cpp1311
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp2194
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp457
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp1140
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp629
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceControl.h297
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp2201
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp2886
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp672
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h284
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp803
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp439
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h66
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h37
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp747
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp807
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp1769
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h42
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp324
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h49
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp1363
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp1707
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h42
-rw-r--r--src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk42
-rw-r--r--src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp87
28 files changed, 21318 insertions, 0 deletions
diff --git a/src/VBox/Additions/common/VBoxService/Makefile.kmk b/src/VBox/Additions/common/VBoxService/Makefile.kmk
new file mode 100644
index 00000000..27fcd8b0
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/Makefile.kmk
@@ -0,0 +1,220 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Cross Platform Guest Addition Services.
+#
+
+#
+# Copyright (C) 2007-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
+
+#
+# Incldue testcases.
+#
+include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
+
+
+#
+# Target lists.
+#
+PROGRAMS += VBoxService
+
+
+#
+# Globals?
+#
+# Enable the timesync service within VBoxService.
+VBOX_WITH_VBOXSERVICE_TIMESYNC := 1
+
+# Busybox-like toolbox, embedded into VBoxService.
+VBOX_WITH_VBOXSERVICE_TOOLBOX := 1
+
+# VM-management functions, like memory ballooning and statistics.
+VBOX_WITH_VBOXSERVICE_MANAGEMENT := 1
+
+if1of ($(KBUILD_TARGET), linux)
+ # CPU hotplugging.
+ VBOX_WITH_VBOXSERVICE_CPUHOTPLUG := 1
+endif
+
+# Page Sharing (Page Fusion).
+if1of ($(KBUILD_TARGET), win)
+ VBOX_WITH_VBOXSERVICE_PAGE_SHARING := 1
+endif
+
+ifdef VBOX_WITH_GUEST_PROPS
+ VBOX_WITH_VBOXSERVICE_VMINFO := 1
+endif
+
+# Guest Control.
+ifdef VBOX_WITH_GUEST_CONTROL
+ VBOX_WITH_VBOXSERVICE_CONTROL := 1
+endif
+
+# Shared Clipboard.
+ifdef VBOX_WITH_SHARED_CLIPBOARD
+ VBOX_WITH_VBOXSERVICE_CLIPBOARD := 1
+endif
+
+# DRM Resize.
+if "$(KBUILD_TARGET)" == "linux" && defined(VBOX_WITH_GUEST_PROPS)
+ # The DRM resizing code needs guest properties.
+ VBOX_WITH_VBOXSERVICE_DRMRESIZE := 1
+endif
+
+
+#
+# VBoxService
+#
+VBoxService_TEMPLATE = VBoxGuestR3Exe
+
+VBoxService_DEFS = \
+ $(if $(VBOX_WITH_VBOXSERVICE_CONTROL),VBOX_WITH_VBOXSERVICE_CONTROL,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_CPUHOTPLUG),VBOX_WITH_VBOXSERVICE_CPUHOTPLUG,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_DRMRESIZE),VBOX_WITH_VBOXSERVICE_DRMRESIZE,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_MANAGEMENT),VBOX_WITH_VBOXSERVICE_MANAGEMENT,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_PAGE_SHARING),VBOX_WITH_VBOXSERVICE_PAGE_SHARING,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_TIMESYNC),VBOX_WITH_VBOXSERVICE_TIMESYNC,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_TOOLBOX),VBOX_WITH_VBOXSERVICE_TOOLBOX,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_VMINFO),VBOX_WITH_VBOXSERVICE_VMINFO,) \
+ $(if $(VBOX_WITH_DBUS),VBOX_WITH_DBUS,) \
+ $(if $(VBOX_WITH_GUEST_CONTROL),VBOX_WITH_GUEST_CONTROL,) \
+ $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \
+ $(if $(VBOX_WITH_HGCM),VBOX_WITH_HGCM,)
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ VBoxService_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+else
+ VBoxService_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+endif
+VBoxService_DEFS.win += _WIN32_WINNT=0x0501
+VBoxService_DEFS.os2 = VBOX_WITH_HGCM
+
+VBoxService_SOURCES = \
+ VBoxService.cpp \
+ VBoxServiceUtils.cpp \
+ VBoxServiceStats.cpp
+
+ifdef VBOX_WITH_VBOXSERVICE_TIMESYNC
+ VBoxService_SOURCES += \
+ VBoxServiceTimeSync.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_CLIPBOARD
+ VBoxService_DEFS.os2 += VBOX_WITH_VBOXSERVICE_CLIPBOARD VBOX_WITH_SHARED_CLIPBOARD
+ VBoxService_SOURCES.os2 += \
+ VBoxServiceClipboard-os2.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX
+ VBoxService_SOURCES += \
+ VBoxServiceToolBox.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_CONTROL
+ VBoxService_SOURCES += \
+ VBoxServiceControl.cpp \
+ VBoxServiceControlProcess.cpp \
+ VBoxServiceControlSession.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_MANAGEMENT
+ ifdef VBOX_WITH_MEMBALLOON
+ VBoxService_SOURCES += \
+ VBoxServiceBalloon.cpp
+ VBoxService_DEFS += VBOX_WITH_MEMBALLOON
+ endif
+endif
+
+if1of ($(KBUILD_TARGET), win)
+ VBoxService_SOURCES += \
+ VBoxServicePageSharing.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_VMINFO
+ VBoxService_SOURCES.win += \
+ VBoxServiceVMInfo-win.cpp
+ VBoxService_SOURCES += \
+ VBoxServiceVMInfo.cpp \
+ VBoxServicePropCache.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_CPUHOTPLUG
+ VBoxService_SOURCES += \
+ VBoxServiceCpuHotPlug.cpp
+endif
+
+ifdef VBOX_WITH_SHARED_FOLDERS
+ if1of ($(KBUILD_TARGET), linux os2 solaris win)
+ VBoxService_DEFS += VBOX_WITH_SHARED_FOLDERS
+ VBoxService_SOURCES += \
+ VBoxServiceAutoMount.cpp
+ VBoxService_SOURCES.linux += \
+ ../../linux/sharedfolders/vbsfmount.c
+ VBoxService_LIBS.win += \
+ Mpr.Lib
+ endif
+endif
+
+VBoxService_SOURCES.win += \
+ VBoxService-win.cpp
+
+VBoxService_SOURCES.os2 += \
+ VBoxService-os2.def
+
+VBoxService_LDFLAGS.darwin = -framework IOKit
+
+VBoxService_LIBS += \
+ $(VBOX_LIB_IPRT_GUEST_R3) \
+ $(VBOX_LIB_VBGL_R3) \
+ $(VBOX_LIB_IPRT_GUEST_R3) # (The joy of unix linkers.)
+ifdef VBOX_WITH_DBUS
+ if1of ($(KBUILD_TARGET), linux solaris) # FreeBSD?
+ VBoxService_LIBS += \
+ dl
+ endif
+endif
+VBoxService_LIBS.netbsd += crypt
+ifdef VBOX_WITH_GUEST_PROPS
+ VBoxService_LIBS.win += \
+ Secur32.lib \
+ WtsApi32.lib \
+ Psapi.lib
+ VBoxService_LIBS.solaris += \
+ nsl \
+ kstat \
+ contract
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_VMINFO
+ VBoxServiceVMInfo.cpp_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV)
+ VBoxServiceVMInfo.cpp_DEPS = $(VBOX_SVN_REV_KMK)
+endif
+
+VBoxService_USES.win += vboximportchecker
+VBoxService_VBOX_IMPORT_CHECKER.win.x86 = nt31
+VBoxService_VBOX_IMPORT_CHECKER.win.amd64 = xp64
+
+$(call VBOX_SET_VER_INFO_EXE,VBoxService,VirtualBox Guest Additions Service,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description.
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/common/VBoxService/VBoxService-os2.def b/src/VBox/Additions/common/VBoxService/VBoxService-os2.def
new file mode 100644
index 00000000..b50cc211
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxService-os2.def
@@ -0,0 +1,33 @@
+; $Id: VBoxService-os2.def $
+;; @file
+; VBoxService - OS/2 definition file.
+;
+
+;
+; Copyright (C) 2007-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
+;
+
+
+NAME VBoxSvc
+DESCRIPTION 'VirtualBox Guest Additions Service for OS/2.'
+CODE SHARED
+DATA MULTIPLE NONSHARED
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp b/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp
new file mode 100644
index 00000000..fb2769fb
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp
@@ -0,0 +1,670 @@
+/* $Id: VBoxService-win.cpp $ */
+/** @file
+ * VBoxService - Guest Additions Service Skeleton, Windows Specific Parts.
+ */
+
+/*
+ * 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 <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/ldr.h>
+#include <iprt/system.h> /* For querying OS version. */
+#include <VBox/VBoxGuestLib.h>
+
+#define WIN32_NO_STATUS
+#include <iprt/win/ws2tcpip.h>
+#include <iprt/win/winsock2.h>
+#undef WIN32_NO_STATUS
+#include <iprt/nt/nt-and-windows.h>
+#include <iprt/win/iphlpapi.h>
+#include <aclapi.h>
+#include <tlhelp32.h>
+#define _NTDEF_
+#include <Ntsecapi.h>
+
+#include "VBoxServiceInternal.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void WINAPI vgsvcWinMain(DWORD argc, LPTSTR *argv);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static DWORD g_dwWinServiceLastStatus = 0;
+SERVICE_STATUS_HANDLE g_hWinServiceStatus = NULL;
+/** The semaphore for the dummy Windows service. */
+static RTSEMEVENT g_WindowsEvent = NIL_RTSEMEVENT;
+
+static SERVICE_TABLE_ENTRY const g_aServiceTable[] =
+{
+ { VBOXSERVICE_NAME, vgsvcWinMain },
+ { NULL, NULL}
+};
+
+/** @name APIs from ADVAPI32.DLL.
+ * @{ */
+decltype(RegisterServiceCtrlHandlerExA) *g_pfnRegisterServiceCtrlHandlerExA; /**< W2K+ */
+decltype(ChangeServiceConfig2A) *g_pfnChangeServiceConfig2A; /**< W2K+ */
+decltype(GetNamedSecurityInfoA) *g_pfnGetNamedSecurityInfoA; /**< NT4+ */
+decltype(SetEntriesInAclA) *g_pfnSetEntriesInAclA; /**< NT4+ */
+decltype(SetNamedSecurityInfoA) *g_pfnSetNamedSecurityInfoA; /**< NT4+ */
+decltype(LsaNtStatusToWinError) *g_pfnLsaNtStatusToWinError; /**< NT3.51+ */
+/** @} */
+
+/** @name API from KERNEL32.DLL
+ * @{ */
+decltype(CreateToolhelp32Snapshot) *g_pfnCreateToolhelp32Snapshot; /**< W2K+, but Geoff says NT4. Hmm. */
+decltype(Process32First) *g_pfnProcess32First; /**< W2K+, but Geoff says NT4. Hmm. */
+decltype(Process32Next) *g_pfnProcess32Next; /**< W2K+, but Geoff says NT4. Hmm. */
+decltype(Module32First) *g_pfnModule32First; /**< W2K+, but Geoff says NT4. Hmm. */
+decltype(Module32Next) *g_pfnModule32Next; /**< W2K+, but Geoff says NT4. Hmm. */
+decltype(GetSystemTimeAdjustment) *g_pfnGetSystemTimeAdjustment; /**< NT 3.50+ */
+decltype(SetSystemTimeAdjustment) *g_pfnSetSystemTimeAdjustment; /**< NT 3.50+ */
+/** @} */
+
+/** @name API from NTDLL.DLL
+ * @{ */
+decltype(ZwQuerySystemInformation) *g_pfnZwQuerySystemInformation; /**< NT4 (where as NtQuerySystemInformation is W2K). */
+/** @} */
+
+/** @name API from IPHLPAPI.DLL
+ * @{ */
+decltype(GetAdaptersInfo) *g_pfnGetAdaptersInfo;
+/** @} */
+
+/** @name APIs from WS2_32.DLL
+ * @note WSAIoctl is not present in wsock32.dll, so no point in trying the
+ * fallback here.
+ * @{ */
+decltype(WSAStartup) *g_pfnWSAStartup;
+decltype(WSACleanup) *g_pfnWSACleanup;
+decltype(WSASocketA) *g_pfnWSASocketA;
+decltype(WSAIoctl) *g_pfnWSAIoctl;
+decltype(WSAGetLastError) *g_pfnWSAGetLastError;
+decltype(closesocket) *g_pfnclosesocket;
+decltype(inet_ntoa) *g_pfninet_ntoa;
+
+/** @} */
+
+/**
+ * Resolve APIs not present on older windows versions.
+ */
+void VGSvcWinResolveApis(void)
+{
+ RTLDRMOD hLdrMod;
+#define RESOLVE_SYMBOL(a_fn) do { RT_CONCAT(g_pfn, a_fn) = (decltype(a_fn) *)RTLdrGetFunction(hLdrMod, #a_fn); } while (0)
+
+ /* From ADVAPI32.DLL: */
+ int rc = RTLdrLoadSystem("advapi32.dll", true /*fNoUnload*/, &hLdrMod);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ RESOLVE_SYMBOL(RegisterServiceCtrlHandlerExA);
+ RESOLVE_SYMBOL(ChangeServiceConfig2A);
+ RESOLVE_SYMBOL(GetNamedSecurityInfoA);
+ RESOLVE_SYMBOL(SetEntriesInAclA);
+ RESOLVE_SYMBOL(SetNamedSecurityInfoA);
+ RESOLVE_SYMBOL(LsaNtStatusToWinError);
+ RTLdrClose(hLdrMod);
+ }
+
+ /* From KERNEL32.DLL: */
+ rc = RTLdrLoadSystem("kernel32.dll", true /*fNoUnload*/, &hLdrMod);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ RESOLVE_SYMBOL(CreateToolhelp32Snapshot);
+ RESOLVE_SYMBOL(Process32First);
+ RESOLVE_SYMBOL(Process32Next);
+ RESOLVE_SYMBOL(Module32First);
+ RESOLVE_SYMBOL(Module32Next);
+ RESOLVE_SYMBOL(GetSystemTimeAdjustment);
+ RESOLVE_SYMBOL(SetSystemTimeAdjustment);
+ RTLdrClose(hLdrMod);
+ }
+
+ /* From NTDLL.DLL: */
+ rc = RTLdrLoadSystem("ntdll.dll", true /*fNoUnload*/, &hLdrMod);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ RESOLVE_SYMBOL(ZwQuerySystemInformation);
+ RTLdrClose(hLdrMod);
+ }
+
+ /* From IPHLPAPI.DLL: */
+ rc = RTLdrLoadSystem("iphlpapi.dll", true /*fNoUnload*/, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ RESOLVE_SYMBOL(GetAdaptersInfo);
+ RTLdrClose(hLdrMod);
+ }
+
+ /* From WS2_32.DLL: */
+ rc = RTLdrLoadSystem("ws2_32.dll", true /*fNoUnload*/, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ RESOLVE_SYMBOL(WSAStartup);
+ RESOLVE_SYMBOL(WSACleanup);
+ RESOLVE_SYMBOL(WSASocketA);
+ RESOLVE_SYMBOL(WSAIoctl);
+ RESOLVE_SYMBOL(WSAGetLastError);
+ RESOLVE_SYMBOL(closesocket);
+ RESOLVE_SYMBOL(inet_ntoa);
+ RTLdrClose(hLdrMod);
+ }
+}
+
+
+/**
+ * @todo Add full unicode support.
+ * @todo Add event log capabilities / check return values.
+ */
+static int vgsvcWinAddAceToObjectsSecurityDescriptor(LPTSTR pszObjName, SE_OBJECT_TYPE enmObjectType, const char *pszTrustee,
+ TRUSTEE_FORM enmTrusteeForm, DWORD dwAccessRights, ACCESS_MODE fAccessMode,
+ DWORD dwInheritance)
+{
+ int rc;
+ if ( g_pfnGetNamedSecurityInfoA
+ && g_pfnSetEntriesInAclA
+ && g_pfnSetNamedSecurityInfoA)
+ {
+ /* Get a pointer to the existing DACL. */
+ PSECURITY_DESCRIPTOR pSD = NULL;
+ PACL pOldDACL = NULL;
+ DWORD rcWin = g_pfnGetNamedSecurityInfoA(pszObjName, enmObjectType, DACL_SECURITY_INFORMATION,
+ NULL, NULL, &pOldDACL, NULL, &pSD);
+ if (rcWin == ERROR_SUCCESS)
+ {
+ /* Initialize an EXPLICIT_ACCESS structure for the new ACE. */
+ EXPLICIT_ACCESSA ExplicitAccess;
+ RT_ZERO(ExplicitAccess);
+ ExplicitAccess.grfAccessPermissions = dwAccessRights;
+ ExplicitAccess.grfAccessMode = fAccessMode;
+ ExplicitAccess.grfInheritance = dwInheritance;
+ ExplicitAccess.Trustee.TrusteeForm = enmTrusteeForm;
+ ExplicitAccess.Trustee.ptstrName = (char *)pszTrustee;
+
+ /* Create a new ACL that merges the new ACE into the existing DACL. */
+ PACL pNewDACL = NULL;
+ rcWin = g_pfnSetEntriesInAclA(1, &ExplicitAccess, pOldDACL, &pNewDACL);
+ if (rcWin == ERROR_SUCCESS)
+ {
+ /* Attach the new ACL as the object's DACL. */
+ rcWin = g_pfnSetNamedSecurityInfoA(pszObjName, enmObjectType, DACL_SECURITY_INFORMATION,
+ NULL, NULL, pNewDACL, NULL);
+ if (rcWin == ERROR_SUCCESS)
+ rc = VINF_SUCCESS;
+ else
+ {
+ VGSvcError("AddAceToObjectsSecurityDescriptor: SetNamedSecurityInfo: Error %u\n", rcWin);
+ rc = RTErrConvertFromWin32(rcWin);
+ }
+ if (pNewDACL)
+ LocalFree(pNewDACL);
+ }
+ else
+ {
+ VGSvcError("AddAceToObjectsSecurityDescriptor: SetEntriesInAcl: Error %u\n", rcWin);
+ rc = RTErrConvertFromWin32(rcWin);
+ }
+ if (pSD)
+ LocalFree(pSD);
+ }
+ else
+ {
+ if (rcWin == ERROR_FILE_NOT_FOUND)
+ VGSvcError("AddAceToObjectsSecurityDescriptor: Object not found/installed: %s\n", pszObjName);
+ else
+ VGSvcError("AddAceToObjectsSecurityDescriptor: GetNamedSecurityInfo: Error %u\n", rcWin);
+ rc = RTErrConvertFromWin32(rcWin);
+ }
+ }
+ else
+ rc = VINF_SUCCESS; /* fake it */
+ return rc;
+}
+
+
+/** Reports our current status to the SCM. */
+static BOOL vgsvcWinSetStatus(DWORD dwStatus, DWORD dwCheckPoint)
+{
+ if (g_hWinServiceStatus == NULL) /* Program could be in testing mode, so no service environment available. */
+ return FALSE;
+
+ VGSvcVerbose(2, "Setting service status to: %ld\n", dwStatus);
+ g_dwWinServiceLastStatus = dwStatus;
+
+ SERVICE_STATUS ss;
+ RT_ZERO(ss);
+
+ ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ ss.dwCurrentState = dwStatus;
+ /* Don't accept controls when in start pending state. */
+ if (ss.dwCurrentState != SERVICE_START_PENDING)
+ {
+ ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
+
+ /* Don't use SERVICE_ACCEPT_SESSIONCHANGE on Windows 2000 or earlier. This makes SCM angry. */
+ char szOSVersion[32];
+ int rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOSVersion, sizeof(szOSVersion));
+ if (RT_SUCCESS(rc))
+ {
+ if (RTStrVersionCompare(szOSVersion, "5.1") >= 0)
+ ss.dwControlsAccepted |= SERVICE_ACCEPT_SESSIONCHANGE;
+ }
+ else
+ VGSvcError("Error determining OS version, rc=%Rrc\n", rc);
+ }
+
+ ss.dwWin32ExitCode = NO_ERROR;
+ ss.dwServiceSpecificExitCode = 0; /* Not used */
+ ss.dwCheckPoint = dwCheckPoint;
+ ss.dwWaitHint = 3000;
+
+ BOOL fStatusSet = SetServiceStatus(g_hWinServiceStatus, &ss);
+ if (!fStatusSet)
+ VGSvcError("Error reporting service status=%ld (controls=%x, checkpoint=%ld) to SCM: %ld\n",
+ dwStatus, ss.dwControlsAccepted, dwCheckPoint, GetLastError());
+ return fStatusSet;
+}
+
+
+/**
+ * Reports SERVICE_STOP_PENDING to SCM.
+ *
+ * @param uCheckPoint Some number.
+ */
+void VGSvcWinSetStopPendingStatus(uint32_t uCheckPoint)
+{
+ vgsvcWinSetStatus(SERVICE_STOP_PENDING, uCheckPoint);
+}
+
+
+static RTEXITCODE vgsvcWinSetDesc(SC_HANDLE hService)
+{
+ /* On W2K+ there's ChangeServiceConfig2() which lets us set some fields
+ like a longer service description. */
+ if (g_pfnChangeServiceConfig2A)
+ {
+ /** @todo On Vista+ SERVICE_DESCRIPTION also supports localized strings! */
+ SERVICE_DESCRIPTION desc;
+ desc.lpDescription = VBOXSERVICE_DESCRIPTION;
+ if (!g_pfnChangeServiceConfig2A(hService, SERVICE_CONFIG_DESCRIPTION, &desc))
+ {
+ VGSvcError("Cannot set the service description! Error: %ld\n", GetLastError());
+ return RTEXITCODE_FAILURE;
+ }
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Installs the service.
+ */
+RTEXITCODE VGSvcWinInstall(void)
+{
+ VGSvcVerbose(1, "Installing service ...\n");
+
+ TCHAR imagePath[MAX_PATH] = { 0 };
+ GetModuleFileName(NULL, imagePath, sizeof(imagePath));
+
+ SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+ if (hSCManager == NULL)
+ {
+ VGSvcError("Could not open SCM! Error: %ld\n", GetLastError());
+ return RTEXITCODE_FAILURE;
+ }
+
+ RTEXITCODE rc = RTEXITCODE_SUCCESS;
+ SC_HANDLE hService = CreateService(hSCManager,
+ VBOXSERVICE_NAME, VBOXSERVICE_FRIENDLY_NAME,
+ SERVICE_ALL_ACCESS,
+ SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
+ SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
+ imagePath, NULL, NULL, NULL, NULL, NULL);
+ if (hService != NULL)
+ VGSvcVerbose(0, "Service successfully installed!\n");
+ else
+ {
+ DWORD dwErr = GetLastError();
+ switch (dwErr)
+ {
+ case ERROR_SERVICE_EXISTS:
+ VGSvcVerbose(1, "Service already exists, just updating the service config.\n");
+ hService = OpenService(hSCManager, VBOXSERVICE_NAME, SERVICE_ALL_ACCESS);
+ if (hService)
+ {
+ if (ChangeServiceConfig(hService,
+ SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
+ SERVICE_DEMAND_START,
+ SERVICE_ERROR_NORMAL,
+ imagePath,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ VBOXSERVICE_FRIENDLY_NAME))
+ VGSvcVerbose(1, "The service config has been successfully updated.\n");
+ else
+ rc = VGSvcError("Could not change service config! Error: %ld\n", GetLastError());
+ }
+ else
+ rc = VGSvcError("Could not open service! Error: %ld\n", GetLastError());
+ break;
+
+ default:
+ rc = VGSvcError("Could not create service! Error: %ld\n", dwErr);
+ break;
+ }
+ }
+
+ if (rc == RTEXITCODE_SUCCESS)
+ rc = vgsvcWinSetDesc(hService);
+
+ CloseServiceHandle(hService);
+ CloseServiceHandle(hSCManager);
+ return rc;
+}
+
+/**
+ * Uninstalls the service.
+ */
+RTEXITCODE VGSvcWinUninstall(void)
+{
+ VGSvcVerbose(1, "Uninstalling service ...\n");
+
+ SC_HANDLE hSCManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
+ if (hSCManager == NULL)
+ {
+ VGSvcError("Could not open SCM! Error: %d\n", GetLastError());
+ return RTEXITCODE_FAILURE;
+ }
+
+ RTEXITCODE rcExit;
+ SC_HANDLE hService = OpenService(hSCManager, VBOXSERVICE_NAME, SERVICE_ALL_ACCESS );
+ if (hService != NULL)
+ {
+ if (DeleteService(hService))
+ {
+ /*
+ * ???
+ */
+ HKEY hKey = NULL;
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+ "SYSTEM\\CurrentControlSet\\Services\\EventLog\\System",
+ 0,
+ KEY_ALL_ACCESS,
+ &hKey)
+ == ERROR_SUCCESS)
+ {
+ RegDeleteKey(hKey, VBOXSERVICE_NAME);
+ RegCloseKey(hKey);
+ }
+
+ VGSvcVerbose(0, "Service successfully uninstalled!\n");
+ rcExit = RTEXITCODE_SUCCESS;
+ }
+ else
+ rcExit = VGSvcError("Could not remove service! Error: %d\n", GetLastError());
+ CloseServiceHandle(hService);
+ }
+ else
+ rcExit = VGSvcError("Could not open service! Error: %d\n", GetLastError());
+ CloseServiceHandle(hSCManager);
+
+ return rcExit;
+}
+
+
+static int vgsvcWinStart(void)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Create a well-known SID for the "Builtin Users" group and modify the ACE
+ * for the shared folders miniport redirector DN (whatever DN means).
+ */
+ PSID pBuiltinUsersSID = NULL;
+ SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_LOCAL_SID_AUTHORITY;
+ if (AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_LOCAL_RID, 0, 0, 0, 0, 0, 0, 0, &pBuiltinUsersSID))
+ {
+ rc = vgsvcWinAddAceToObjectsSecurityDescriptor(TEXT("\\\\.\\VBoxMiniRdrDN"), SE_FILE_OBJECT,
+ (LPTSTR)pBuiltinUsersSID, TRUSTEE_IS_SID,
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE, SET_ACCESS, NO_INHERITANCE);
+ /* If we don't find our "VBoxMiniRdrDN" (for Shared Folders) object above,
+ don't report an error; it just might be not installed. Otherwise this
+ would cause the SCM to hang on starting up the service. */
+ if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
+ rc = VINF_SUCCESS;
+
+ FreeSid(pBuiltinUsersSID);
+ }
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Start the service.
+ */
+ vgsvcWinSetStatus(SERVICE_START_PENDING, 0);
+
+ rc = VGSvcStartServices();
+ if (RT_SUCCESS(rc))
+ {
+ vgsvcWinSetStatus(SERVICE_RUNNING, 0);
+ VGSvcMainWait();
+ }
+ else
+ {
+ vgsvcWinSetStatus(SERVICE_STOPPED, 0);
+#if 0 /** @todo r=bird: Enable this if SERVICE_CONTROL_STOP isn't triggered automatically */
+ VGSvcStopServices();
+#endif
+ }
+ }
+ else
+ vgsvcWinSetStatus(SERVICE_STOPPED, 0);
+
+ if (RT_FAILURE(rc))
+ VGSvcError("Service failed to start with rc=%Rrc!\n", rc);
+
+ return rc;
+}
+
+
+/**
+ * Call StartServiceCtrlDispatcher.
+ *
+ * The main() thread invokes this when not started in foreground mode. It
+ * won't return till the service is being shutdown (unless start up fails).
+ *
+ * @returns RTEXITCODE_SUCCESS on normal return after service shutdown.
+ * Something else on failure, error will have been reported.
+ */
+RTEXITCODE VGSvcWinEnterCtrlDispatcher(void)
+{
+ if (!StartServiceCtrlDispatcher(&g_aServiceTable[0]))
+ return VGSvcError("StartServiceCtrlDispatcher: %u. Please start %s with option -f (foreground)!\n",
+ GetLastError(), g_pszProgName);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Event code to description.
+ *
+ * @returns String.
+ * @param dwEvent The event code.
+ */
+static const char *vgsvcWTSStateToString(DWORD dwEvent)
+{
+ switch (dwEvent)
+ {
+ case WTS_CONSOLE_CONNECT: return "A session was connected to the console terminal";
+ case WTS_CONSOLE_DISCONNECT: return "A session was disconnected from the console terminal";
+ case WTS_REMOTE_CONNECT: return "A session connected to the remote terminal";
+ case WTS_REMOTE_DISCONNECT: return "A session was disconnected from the remote terminal";
+ case WTS_SESSION_LOGON: return "A user has logged on to a session";
+ case WTS_SESSION_LOGOFF: return "A user has logged off the session";
+ case WTS_SESSION_LOCK: return "A session has been locked";
+ case WTS_SESSION_UNLOCK: return "A session has been unlocked";
+ case WTS_SESSION_REMOTE_CONTROL: return "A session has changed its remote controlled status";
+#ifdef WTS_SESSION_CREATE
+ case WTS_SESSION_CREATE: return "A session has been created";
+#endif
+#ifdef WTS_SESSION_TERMINATE
+ case WTS_SESSION_TERMINATE: return "The session has been terminated";
+#endif
+ default: return "Uknonwn state";
+ }
+}
+
+
+/**
+ * Common control handler.
+ *
+ * @returns Return code for NT5+.
+ * @param dwControl The control code.
+ */
+static DWORD vgsvcWinCtrlHandlerCommon(DWORD dwControl)
+{
+ DWORD rcRet = NO_ERROR;
+ switch (dwControl)
+ {
+ case SERVICE_CONTROL_INTERROGATE:
+ vgsvcWinSetStatus(g_dwWinServiceLastStatus, 0);
+ break;
+
+ case SERVICE_CONTROL_STOP:
+ case SERVICE_CONTROL_SHUTDOWN:
+ {
+ vgsvcWinSetStatus(SERVICE_STOP_PENDING, 0);
+
+ int rc2 = VGSvcStopServices();
+ if (RT_FAILURE(rc2))
+ rcRet = ERROR_GEN_FAILURE;
+ else
+ {
+ rc2 = VGSvcReportStatus(VBoxGuestFacilityStatus_Terminated);
+ AssertRC(rc2);
+ }
+
+ vgsvcWinSetStatus(SERVICE_STOPPED, 0);
+ break;
+ }
+
+ default:
+ VGSvcVerbose(1, "Control handler: Function not implemented: %#x\n", dwControl);
+ rcRet = ERROR_CALL_NOT_IMPLEMENTED;
+ break;
+ }
+
+ return rcRet;
+}
+
+
+/**
+ * Callback registered by RegisterServiceCtrlHandler on NT4 and earlier.
+ */
+static VOID WINAPI vgsvcWinCtrlHandlerNt4(DWORD dwControl) RT_NOTHROW_DEF
+{
+ VGSvcVerbose(2, "Control handler (NT4): dwControl=%#x\n", dwControl);
+ vgsvcWinCtrlHandlerCommon(dwControl);
+}
+
+
+/**
+ * Callback registered by RegisterServiceCtrlHandler on NT5 and later.
+ */
+static DWORD WINAPI
+vgsvcWinCtrlHandlerNt5Plus(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) RT_NOTHROW_DEF
+{
+ VGSvcVerbose(2, "Control handler: dwControl=%#x, dwEventType=%#x\n", dwControl, dwEventType);
+ RT_NOREF1(lpContext);
+
+ switch (dwControl)
+ {
+ default:
+ return vgsvcWinCtrlHandlerCommon(dwControl);
+
+ case SERVICE_CONTROL_SESSIONCHANGE: /* Only Windows 2000 and up. */
+ {
+ AssertPtr(lpEventData);
+ PWTSSESSION_NOTIFICATION pNotify = (PWTSSESSION_NOTIFICATION)lpEventData;
+ Assert(pNotify->cbSize == sizeof(WTSSESSION_NOTIFICATION));
+
+ VGSvcVerbose(1, "Control handler: %s (Session=%ld, Event=%#x)\n",
+ vgsvcWTSStateToString(dwEventType), pNotify->dwSessionId, dwEventType);
+
+ /* Handle all events, regardless of dwEventType. */
+ int rc2 = VGSvcVMInfoSignal();
+ AssertRC(rc2);
+
+ return NO_ERROR;
+ }
+ }
+}
+
+
+static void WINAPI vgsvcWinMain(DWORD argc, LPTSTR *argv)
+{
+ RT_NOREF2(argc, argv);
+ VGSvcVerbose(2, "Registering service control handler ...\n");
+ if (g_pfnRegisterServiceCtrlHandlerExA)
+ g_hWinServiceStatus = g_pfnRegisterServiceCtrlHandlerExA(VBOXSERVICE_NAME, vgsvcWinCtrlHandlerNt5Plus, NULL);
+ else
+ g_hWinServiceStatus = RegisterServiceCtrlHandlerA(VBOXSERVICE_NAME, vgsvcWinCtrlHandlerNt4);
+ if (g_hWinServiceStatus != NULL)
+ {
+ VGSvcVerbose(2, "Service control handler registered.\n");
+ vgsvcWinStart();
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ switch (dwErr)
+ {
+ case ERROR_INVALID_NAME:
+ VGSvcError("Invalid service name!\n");
+ break;
+ case ERROR_SERVICE_DOES_NOT_EXIST:
+ VGSvcError("Service does not exist!\n");
+ break;
+ default:
+ VGSvcError("Could not register service control handle! Error: %ld\n", dwErr);
+ break;
+ }
+ }
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxService.cpp b/src/VBox/Additions/common/VBoxService/VBoxService.cpp
new file mode 100644
index 00000000..5b5dc5ee
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxService.cpp
@@ -0,0 +1,1311 @@
+/* $Id: VBoxService.cpp $ */
+/** @file
+ * VBoxService - Guest Additions Service Skeleton.
+ */
+
+/*
+ * Copyright (C) 2007-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
+ */
+
+
+/** @page pg_vgsvc VBoxService
+ *
+ * VBoxService is a root daemon for implementing guest additions features.
+ *
+ * It is structured as one binary that contains many sub-services. The reason
+ * for this is partially historical and partially practical. The practical
+ * reason is that the VBoxService binary is typically statically linked, at
+ * least with IPRT and the guest library, so we save quite a lot of space having
+ * on single binary instead individual binaries for each sub-service and their
+ * helpers (currently up to 9 subservices and 8 helpers). The historical is
+ * simply that it started its life on OS/2 dreaming of conquring Windows next,
+ * so it kind of felt natural to have it all in one binary.
+ *
+ * Even if it's structured as a single binary, it is possible, by using command
+ * line options, to start each subservice as an individual process.
+ *
+ * Subservices:
+ * - @subpage pg_vgsvc_timesync "Time Synchronization"
+ * - @subpage pg_vgsvc_vminfo "VM Information"
+ * - @subpage pg_vgsvc_vmstats "VM Statistics"
+ * - @subpage pg_vgsvc_gstctrl "Guest Control"
+ * - @subpage pg_vgsvc_pagesharing "Page Sharing"
+ * - @subpage pg_vgsvc_memballoon "Memory Balooning"
+ * - @subpage pg_vgsvc_cpuhotplug "CPU Hot-Plugging"
+ * - @subpage pg_vgsvc_automount "Shared Folder Automounting"
+ * - @subpage pg_vgsvc_clipboard "Clipboard (OS/2 only)"
+ *
+ * Now, since the service predates a lot of stuff, including RTGetOpt, we're
+ * currently doing our own version of argument parsing here, which is kind of
+ * stupid. That will hopefully be cleaned up eventually.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+/** @todo LOG_GROUP*/
+#ifndef _MSC_VER
+# include <unistd.h>
+#endif
+#ifndef RT_OS_WINDOWS
+# include <errno.h>
+# include <signal.h>
+# ifdef RT_OS_OS2
+# define pthread_sigmask sigprocmask
+# endif
+#endif
+#ifdef RT_OS_FREEBSD
+# include <pthread.h>
+#endif
+
+#include <package-generated.h>
+#include "product-generated.h"
+
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/initterm.h>
+#include <iprt/file.h>
+#ifdef DEBUG
+# include <iprt/memtracker.h>
+#endif
+#include <iprt/env.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/system.h>
+#include <iprt/thread.h>
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+#ifdef VBOX_WITH_VBOXSERVICE_CONTROL
+# include "VBoxServiceControl.h"
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX
+# include "VBoxServiceToolBox.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The program name (derived from argv[0]). */
+char *g_pszProgName = (char *)"";
+/** The current verbosity level. */
+unsigned g_cVerbosity = 0;
+char g_szLogFile[RTPATH_MAX + 128] = "";
+char g_szPidFile[RTPATH_MAX] = "";
+/** Logging parameters. */
+/** @todo Make this configurable later. */
+static PRTLOGGER g_pLoggerRelease = NULL;
+static uint32_t g_cHistory = 10; /* Enable log rotation, 10 files. */
+static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; /* Max 1 day per file. */
+static uint64_t g_uHistoryFileSize = 100 * _1M; /* Max 100MB per file. */
+/** Critical section for (debug) logging. */
+#ifdef DEBUG
+ RTCRITSECT g_csLog;
+#endif
+/** The default service interval (the -i | --interval) option). */
+uint32_t g_DefaultInterval = 0;
+#ifdef RT_OS_WINDOWS
+/** Signal shutdown to the Windows service thread. */
+static bool volatile g_fWindowsServiceShutdown;
+/** Event the Windows service thread waits for shutdown. */
+static RTSEMEVENT g_hEvtWindowsService;
+#endif
+
+/**
+ * The details of the services that has been compiled in.
+ */
+static struct
+{
+ /** Pointer to the service descriptor. */
+ PCVBOXSERVICE pDesc;
+ /** The worker thread. NIL_RTTHREAD if it's the main thread. */
+ RTTHREAD Thread;
+ /** Whether Pre-init was called. */
+ bool fPreInited;
+ /** Shutdown indicator. */
+ bool volatile fShutdown;
+ /** Indicator set by the service thread exiting. */
+ bool volatile fStopped;
+ /** Whether the service was started or not. */
+ bool fStarted;
+ /** Whether the service is enabled or not. */
+ bool fEnabled;
+} g_aServices[] =
+{
+#ifdef VBOX_WITH_VBOXSERVICE_CONTROL
+ { &g_Control, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_TIMESYNC
+ { &g_TimeSync, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_CLIPBOARD
+ { &g_Clipboard, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_VMINFO
+ { &g_VMInfo, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_CPUHOTPLUG
+ { &g_CpuHotPlug, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_MANAGEMENT
+# ifdef VBOX_WITH_MEMBALLOON
+ { &g_MemBalloon, NIL_RTTHREAD, false, false, false, false, true },
+# endif
+ { &g_VMStatistics, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#if defined(VBOX_WITH_VBOXSERVICE_PAGE_SHARING)
+ { &g_PageSharing, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_SHARED_FOLDERS
+ { &g_AutoMount, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+};
+
+
+/*
+ * Default call-backs for services which do not need special behaviour.
+ */
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnPreInit, Default Implementation}
+ */
+DECLCALLBACK(int) VGSvcDefaultPreInit(void)
+{
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnOption, Default Implementation}
+ */
+DECLCALLBACK(int) VGSvcDefaultOption(const char **ppszShort, int argc,
+ char **argv, int *pi)
+{
+ NOREF(ppszShort);
+ NOREF(argc);
+ NOREF(argv);
+ NOREF(pi);
+
+ return -1;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit, Default Implementation}
+ */
+DECLCALLBACK(int) VGSvcDefaultInit(void)
+{
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm, Default Implementation}
+ */
+DECLCALLBACK(void) VGSvcDefaultTerm(void)
+{
+ return;
+}
+
+
+/**
+ * @callback_method_impl{FNRTLOGPHASE, Release logger callback}
+ */
+static DECLCALLBACK(void) vgsvcLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog)
+{
+ /* Some introductory information. */
+ static RTTIMESPEC s_TimeSpec;
+ char szTmp[256];
+ if (enmPhase == RTLOGPHASE_BEGIN)
+ RTTimeNow(&s_TimeSpec);
+ RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp));
+
+ switch (enmPhase)
+ {
+ case RTLOGPHASE_BEGIN:
+ {
+ pfnLog(pLoggerRelease,
+ "VBoxService %s r%s (verbosity: %u) %s (%s %s) release log\n"
+ "Log opened %s\n",
+ RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, VBOX_BUILD_TARGET,
+ __DATE__, __TIME__, szTmp);
+
+ int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Product: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Release: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Version: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Service Pack: %s\n", szTmp);
+
+ /* the package type is interesting for Linux distributions */
+ char szExecName[RTPATH_MAX];
+ char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName));
+ pfnLog(pLoggerRelease,
+ "Executable: %s\n"
+ "Process ID: %u\n"
+ "Package type: %s"
+#ifdef VBOX_OSE
+ " (OSE)"
+#endif
+ "\n",
+ pszExecName ? pszExecName : "unknown",
+ RTProcSelf(),
+ VBOX_PACKAGE_STRING);
+ break;
+ }
+
+ case RTLOGPHASE_PREROTATE:
+ pfnLog(pLoggerRelease, "Log rotated - Log started %s\n", szTmp);
+ break;
+
+ case RTLOGPHASE_POSTROTATE:
+ pfnLog(pLoggerRelease, "Log continuation - Log started %s\n", szTmp);
+ break;
+
+ case RTLOGPHASE_END:
+ pfnLog(pLoggerRelease, "End of log file - Log started %s\n", szTmp);
+ break;
+
+ default:
+ /* nothing */
+ break;
+ }
+}
+
+
+/**
+ * Creates the default release logger outputting to the specified file.
+ *
+ * Pass NULL to disabled logging.
+ *
+ * @return IPRT status code.
+ * @param pszLogFile Filename for log output. NULL disables logging
+ * (r=bird: No, it doesn't!).
+ */
+int VGSvcLogCreate(const char *pszLogFile)
+{
+ /* Create release logger (stdout + file). */
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ fFlags |= RTLOGFLAGS_USECRLF;
+#endif
+ int rc = RTLogCreateEx(&g_pLoggerRelease, "VBOXSERVICE_RELEASE_LOG", fFlags, "all",
+ RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX /*cMaxEntriesPerGroup*/,
+ 0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT | RTLOGDEST_USER,
+ vgsvcLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime,
+ NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
+ NULL /*pErrInfo*/, "%s", pszLogFile ? pszLogFile : "");
+ if (RT_SUCCESS(rc))
+ {
+ /* register this logger as the release logger */
+ RTLogRelSetDefaultInstance(g_pLoggerRelease);
+
+ /* Explicitly flush the log in case of VBOXSERVICE_RELEASE_LOG=buffered. */
+ RTLogFlush(g_pLoggerRelease);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Logs a verbose message.
+ *
+ * @param pszFormat The message text.
+ * @param va Format arguments.
+ */
+void VGSvcLogV(const char *pszFormat, va_list va)
+{
+#ifdef DEBUG
+ int rc = RTCritSectEnter(&g_csLog);
+ if (RT_SUCCESS(rc))
+ {
+#endif
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, va);
+
+ AssertPtr(psz);
+ LogRel(("%s", psz));
+
+ RTStrFree(psz);
+#ifdef DEBUG
+ RTCritSectLeave(&g_csLog);
+ }
+#endif
+}
+
+
+/**
+ * Destroys the currently active logging instance.
+ */
+void VGSvcLogDestroy(void)
+{
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+}
+
+
+/**
+ * Displays the program usage message.
+ *
+ * @returns 1.
+ */
+static int vgsvcUsage(void)
+{
+ RTPrintf("Usage: %s [-f|--foreground] [-v|--verbose] [-l|--logfile <file>]\n"
+ " [-p|--pidfile <file>] [-i|--interval <seconds>]\n"
+ " [--disable-<service>] [--enable-<service>]\n"
+ " [--only-<service>] [-h|-?|--help]\n", g_pszProgName);
+#ifdef RT_OS_WINDOWS
+ RTPrintf(" [-r|--register] [-u|--unregister]\n");
+#endif
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ if (g_aServices[j].pDesc->pszUsage)
+ RTPrintf("%s\n", g_aServices[j].pDesc->pszUsage);
+ RTPrintf("\n"
+ "Options:\n"
+ " -i | --interval The default interval.\n"
+ " -f | --foreground Don't daemonize the program. For debugging.\n"
+ " -l | --logfile <file> Enables logging to a file.\n"
+ " -p | --pidfile <file> Write the process ID to a file.\n"
+ " -v | --verbose Increment the verbosity level. For debugging.\n"
+ " -V | --version Show version information.\n"
+ " -h | -? | --help Show this message and exit with status 1.\n"
+ );
+#ifdef RT_OS_WINDOWS
+ RTPrintf(" -r | --register Installs the service.\n"
+ " -u | --unregister Uninstall service.\n");
+#endif
+
+ RTPrintf("\n"
+ "Service-specific options:\n");
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ {
+ RTPrintf(" --enable-%-14s Enables the %s service. (default)\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName);
+ RTPrintf(" --disable-%-13s Disables the %s service.\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName);
+ RTPrintf(" --only-%-16s Only enables the %s service.\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName);
+ if (g_aServices[j].pDesc->pszOptions)
+ RTPrintf("%s", g_aServices[j].pDesc->pszOptions);
+ }
+ RTPrintf("\n"
+ " Copyright (C) 2009-" VBOX_C_YEAR " " VBOX_VENDOR "\n");
+
+ return 1;
+}
+
+
+/**
+ * Displays an error message.
+ *
+ * @returns RTEXITCODE_FAILURE.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+RTEXITCODE VGSvcError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtr(psz);
+ LogRel(("Error: %s", psz));
+
+ RTStrFree(psz);
+
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Displays a verbose message based on the currently
+ * set global verbosity level.
+ *
+ * @param iLevel Minimum log level required to display this message.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+void VGSvcVerbose(unsigned iLevel, const char *pszFormat, ...)
+{
+ if (iLevel <= g_cVerbosity)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ VGSvcLogV(pszFormat, va);
+ va_end(va);
+ }
+}
+
+
+/**
+ * Reports the current VBoxService status to the host.
+ *
+ * This makes sure that the Failed state is sticky.
+ *
+ * @return IPRT status code.
+ * @param enmStatus Status to report to the host.
+ */
+int VGSvcReportStatus(VBoxGuestFacilityStatus enmStatus)
+{
+ /*
+ * VBoxGuestFacilityStatus_Failed is sticky.
+ */
+ static VBoxGuestFacilityStatus s_enmLastStatus = VBoxGuestFacilityStatus_Inactive;
+ VGSvcVerbose(4, "Setting VBoxService status to %u\n", enmStatus);
+ if (s_enmLastStatus != VBoxGuestFacilityStatus_Failed)
+ {
+ int rc = VbglR3ReportAdditionsStatus(VBoxGuestFacilityType_VBoxService, enmStatus, 0 /* Flags */);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Could not report VBoxService status (%u), rc=%Rrc\n", enmStatus, rc);
+ return rc;
+ }
+ s_enmLastStatus = enmStatus;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets a 32-bit value argument.
+ * @todo Get rid of this and VGSvcArgString() as soon as we have RTOpt handling.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param argc The argument count.
+ * @param argv The argument vector
+ * @param psz Where in *pi to start looking for the value argument.
+ * @param pi Where to find and perhaps update the argument index.
+ * @param pu32 Where to store the 32-bit value.
+ * @param u32Min The minimum value.
+ * @param u32Max The maximum value.
+ */
+int VGSvcArgUInt32(int argc, char **argv, const char *psz, int *pi, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max)
+{
+ if (*psz == ':' || *psz == '=')
+ psz++;
+ if (!*psz)
+ {
+ if (*pi + 1 >= argc)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing value for the '%s' argument\n", argv[*pi]);
+ psz = argv[++*pi];
+ }
+
+ char *pszNext;
+ int rc = RTStrToUInt32Ex(psz, &pszNext, 0, pu32);
+ if (RT_FAILURE(rc) || *pszNext)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Failed to convert interval '%s' to a number\n", psz);
+ if (*pu32 < u32Min || *pu32 > u32Max)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The timesync interval of %RU32 seconds is out of range [%RU32..%RU32]\n",
+ *pu32, u32Min, u32Max);
+ return 0;
+}
+
+
+/** @todo Get rid of this and VGSvcArgUInt32() as soon as we have RTOpt handling. */
+static int vgsvcArgString(int argc, char **argv, const char *psz, int *pi, char *pszBuf, size_t cbBuf)
+{
+ AssertPtrReturn(pszBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+
+ if (*psz == ':' || *psz == '=')
+ psz++;
+ if (!*psz)
+ {
+ if (*pi + 1 >= argc)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing string for the '%s' argument\n", argv[*pi]);
+ psz = argv[++*pi];
+ }
+
+ if (!RTStrPrintf(pszBuf, cbBuf, "%s", psz))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "String for '%s' argument too big\n", argv[*pi]);
+ return 0;
+}
+
+
+/**
+ * The service thread.
+ *
+ * @returns Whatever the worker function returns.
+ * @param ThreadSelf My thread handle.
+ * @param pvUser The service index.
+ */
+static DECLCALLBACK(int) vgsvcThread(RTTHREAD ThreadSelf, void *pvUser)
+{
+ const unsigned i = (uintptr_t)pvUser;
+
+#ifndef RT_OS_WINDOWS
+ /*
+ * Block all signals for this thread. Only the main thread will handle signals.
+ */
+ sigset_t signalMask;
+ sigfillset(&signalMask);
+ pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
+#endif
+
+ int rc = g_aServices[i].pDesc->pfnWorker(&g_aServices[i].fShutdown);
+ ASMAtomicXchgBool(&g_aServices[i].fShutdown, true);
+ RTThreadUserSignal(ThreadSelf);
+ return rc;
+}
+
+
+/**
+ * Lazily calls the pfnPreInit method on each service.
+ *
+ * @returns VBox status code, error message displayed.
+ */
+static RTEXITCODE vgsvcLazyPreInit(void)
+{
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ if (!g_aServices[j].fPreInited)
+ {
+ int rc = g_aServices[j].pDesc->pfnPreInit();
+ if (RT_FAILURE(rc))
+ return VGSvcError("Service '%s' failed pre-init: %Rrc\n", g_aServices[j].pDesc->pszName, rc);
+ g_aServices[j].fPreInited = true;
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Count the number of enabled services.
+ */
+static unsigned vgsvcCountEnabledServices(void)
+{
+ unsigned cEnabled = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aServices); i++)
+ cEnabled += g_aServices[i].fEnabled;
+ return cEnabled;
+}
+
+
+#ifdef RT_OS_WINDOWS
+/**
+ * Console control event callback.
+ *
+ * @returns TRUE if handled, FALSE if not.
+ * @param dwCtrlType The control event type.
+ *
+ * @remarks This is generally called on a new thread, so we're racing every
+ * other thread in the process.
+ */
+static BOOL WINAPI vgsvcWinConsoleControlHandler(DWORD dwCtrlType) RT_NOTHROW_DEF
+{
+ int rc = VINF_SUCCESS;
+ bool fEventHandled = FALSE;
+ switch (dwCtrlType)
+ {
+ /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
+ * via GenerateConsoleCtrlEvent(). */
+ case CTRL_BREAK_EVENT:
+ case CTRL_CLOSE_EVENT:
+ case CTRL_C_EVENT:
+ VGSvcVerbose(2, "ControlHandler: Received break/close event\n");
+ rc = VGSvcStopServices();
+ fEventHandled = TRUE;
+ break;
+ default:
+ break;
+ /** @todo Add other events here. */
+ }
+
+ if (RT_FAILURE(rc))
+ VGSvcError("ControlHandler: Event %ld handled with error rc=%Rrc\n",
+ dwCtrlType, rc);
+ return fEventHandled;
+}
+#endif /* RT_OS_WINDOWS */
+
+
+/**
+ * Starts the service.
+ *
+ * @returns VBox status code, errors are fully bitched.
+ *
+ * @remarks Also called from VBoxService-win.cpp, thus not static.
+ */
+int VGSvcStartServices(void)
+{
+ int rc;
+
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Init);
+
+ /*
+ * Initialize the services.
+ */
+ VGSvcVerbose(2, "Initializing services ...\n");
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ if (g_aServices[j].fEnabled)
+ {
+ rc = g_aServices[j].pDesc->pfnInit();
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_SERVICE_DISABLED)
+ {
+ VGSvcError("Service '%s' failed to initialize: %Rrc\n", g_aServices[j].pDesc->pszName, rc);
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Failed);
+ return rc;
+ }
+
+ g_aServices[j].fEnabled = false;
+ VGSvcVerbose(0, "Service '%s' was disabled because of missing functionality\n", g_aServices[j].pDesc->pszName);
+ }
+ }
+
+ /*
+ * Start the service(s).
+ */
+ VGSvcVerbose(2, "Starting services ...\n");
+ rc = VINF_SUCCESS;
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ {
+ if (!g_aServices[j].fEnabled)
+ continue;
+
+ VGSvcVerbose(2, "Starting service '%s' ...\n", g_aServices[j].pDesc->pszName);
+ rc = RTThreadCreate(&g_aServices[j].Thread, vgsvcThread, (void *)(uintptr_t)j, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, g_aServices[j].pDesc->pszName);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("RTThreadCreate failed, rc=%Rrc\n", rc);
+ break;
+ }
+ g_aServices[j].fStarted = true;
+
+ /* Wait for the thread to initialize. */
+ /** @todo There is a race between waiting and checking
+ * the fShutdown flag of a thread here and processing
+ * the thread's actual worker loop. If the thread decides
+ * to exit the loop before we skipped the fShutdown check
+ * below the service will fail to start! */
+ /** @todo This presumably means either a one-shot service or that
+ * something has gone wrong. In the second case treating it as failure
+ * to start is probably right, so we need a way to signal the first
+ * rather than leaving the idle thread hanging around. A flag in the
+ * service description? */
+ RTThreadUserWait(g_aServices[j].Thread, 60 * 1000);
+ if (g_aServices[j].fShutdown)
+ {
+ VGSvcError("Service '%s' failed to start!\n", g_aServices[j].pDesc->pszName);
+ rc = VERR_GENERAL_FAILURE;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(1, "All services started.\n");
+ else
+ {
+ VGSvcError("An error occcurred while the services!\n");
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Failed);
+ }
+ return rc;
+}
+
+
+/**
+ * Stops and terminates the services.
+ *
+ * This should be called even when VBoxServiceStartServices fails so it can
+ * clean up anything that we succeeded in starting.
+ *
+ * @remarks Also called from VBoxService-win.cpp, thus not static.
+ */
+int VGSvcStopServices(void)
+{
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Terminating);
+
+ /*
+ * Signal all the services.
+ */
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ ASMAtomicWriteBool(&g_aServices[j].fShutdown, true);
+
+ /*
+ * Do the pfnStop callback on all running services.
+ */
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ if (g_aServices[j].fStarted)
+ {
+ VGSvcVerbose(3, "Calling stop function for service '%s' ...\n", g_aServices[j].pDesc->pszName);
+ g_aServices[j].pDesc->pfnStop();
+ }
+
+ VGSvcVerbose(3, "All stop functions for services called\n");
+
+ /*
+ * Wait for all the service threads to complete.
+ */
+ int rc = VINF_SUCCESS;
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ {
+ if (!g_aServices[j].fEnabled) /* Only stop services which were started before. */
+ continue;
+ if (g_aServices[j].Thread != NIL_RTTHREAD)
+ {
+ VGSvcVerbose(2, "Waiting for service '%s' to stop ...\n", g_aServices[j].pDesc->pszName);
+ int rc2 = VINF_SUCCESS;
+ for (int i = 0; i < 30; i++) /* Wait 30 seconds in total */
+ {
+ rc2 = RTThreadWait(g_aServices[j].Thread, 1000 /* Wait 1 second */, NULL);
+ if (RT_SUCCESS(rc2))
+ break;
+#ifdef RT_OS_WINDOWS
+ /* Notify SCM that it takes a bit longer ... */
+ VGSvcWinSetStopPendingStatus(i + j*32);
+#endif
+ }
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Service '%s' failed to stop. (%Rrc)\n", g_aServices[j].pDesc->pszName, rc2);
+ rc = rc2;
+ }
+ }
+ VGSvcVerbose(3, "Terminating service '%s' (%d) ...\n", g_aServices[j].pDesc->pszName, j);
+ g_aServices[j].pDesc->pfnTerm();
+ }
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Wake up and tell the main() thread that we're shutting down (it's
+ * sleeping in VBoxServiceMainWait).
+ */
+ ASMAtomicWriteBool(&g_fWindowsServiceShutdown, true);
+ if (g_hEvtWindowsService != NIL_RTSEMEVENT)
+ {
+ VGSvcVerbose(3, "Stopping the main thread...\n");
+ int rc2 = RTSemEventSignal(g_hEvtWindowsService);
+ AssertRC(rc2);
+ }
+#endif
+
+ VGSvcVerbose(2, "Stopping services returning: %Rrc\n", rc);
+ VGSvcReportStatus(RT_SUCCESS(rc) ? VBoxGuestFacilityStatus_Paused : VBoxGuestFacilityStatus_Failed);
+ return rc;
+}
+
+
+/**
+ * Block the main thread until the service shuts down.
+ *
+ * @remarks Also called from VBoxService-win.cpp, thus not static.
+ */
+void VGSvcMainWait(void)
+{
+ int rc;
+
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Active);
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Wait for the semaphore to be signalled.
+ */
+ VGSvcVerbose(1, "Waiting in main thread\n");
+ rc = RTSemEventCreate(&g_hEvtWindowsService);
+ AssertRC(rc);
+ while (!ASMAtomicReadBool(&g_fWindowsServiceShutdown))
+ {
+ rc = RTSemEventWait(g_hEvtWindowsService, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ RTSemEventDestroy(g_hEvtWindowsService);
+ g_hEvtWindowsService = NIL_RTSEMEVENT;
+#else
+ /*
+ * Wait explicitly for a HUP, INT, QUIT, ABRT or TERM signal, blocking
+ * all important signals.
+ *
+ * The annoying EINTR/ERESTART loop is for the benefit of Solaris where
+ * sigwait returns when we receive a SIGCHLD. Kind of makes sense since
+ * the signal has to be delivered... Anyway, darwin (10.9.5) has a much
+ * worse way of dealing with SIGCHLD, apparently it'll just return any
+ * of the signals we're waiting on when SIGCHLD becomes pending on this
+ * thread. So, we wait for SIGCHLD here and ignores it.
+ */
+ sigset_t signalMask;
+ sigemptyset(&signalMask);
+ sigaddset(&signalMask, SIGHUP);
+ sigaddset(&signalMask, SIGINT);
+ sigaddset(&signalMask, SIGQUIT);
+ sigaddset(&signalMask, SIGABRT);
+ sigaddset(&signalMask, SIGTERM);
+ sigaddset(&signalMask, SIGCHLD);
+ pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
+
+ int iSignal;
+ do
+ {
+ iSignal = -1;
+ rc = sigwait(&signalMask, &iSignal);
+ }
+ while ( rc == EINTR
+# ifdef ERESTART
+ || rc == ERESTART
+# endif
+ || iSignal == SIGCHLD
+ );
+
+ VGSvcVerbose(3, "VGSvcMainWait: Received signal %d (rc=%d)\n", iSignal, rc);
+#endif /* !RT_OS_WINDOWS */
+}
+
+
+/**
+ * Report VbglR3InitUser / VbglR3Init failure.
+ *
+ * @returns RTEXITCODE_FAILURE
+ * @param rcVbgl The failing status code.
+ */
+static RTEXITCODE vbglInitFailure(int rcVbgl)
+{
+ if (rcVbgl == VERR_ACCESS_DENIED)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "Insufficient privileges to start %s! Please start with Administrator/root privileges!\n",
+ g_pszProgName);
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "VbglR3Init failed with rc=%Rrc\n", rcVbgl);
+}
+
+
+int main(int argc, char **argv)
+{
+ RTEXITCODE rcExit;
+
+ /*
+ * Init globals and such.
+ *
+ * Note! The --utf8-argv stuff is an internal hack to avoid locale configuration
+ * issues preventing us from passing non-ASCII string to child processes.
+ */
+ uint32_t fIprtFlags = 0;
+#ifdef VBOXSERVICE_ARG1_UTF8_ARGV
+ if (argc > 1 && strcmp(argv[1], VBOXSERVICE_ARG1_UTF8_ARGV) == 0)
+ {
+ argv[1] = argv[0];
+ argv++;
+ argc--;
+ fIprtFlags |= RTR3INIT_FLAGS_UTF8_ARGV;
+ }
+#endif
+ int rc = RTR3InitExe(argc, &argv, fIprtFlags);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ g_pszProgName = RTPathFilename(argv[0]);
+#ifdef RT_OS_WINDOWS
+ VGSvcWinResolveApis();
+#endif
+#ifdef DEBUG
+ rc = RTCritSectInit(&g_csLog);
+ AssertRC(rc);
+#endif
+
+#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX
+ /*
+ * Run toolbox code before all other stuff since these things are simpler
+ * shell/file/text utility like programs that just happens to be inside
+ * VBoxService and shouldn't be subject to /dev/vboxguest, pid-files and
+ * global mutex restrictions.
+ */
+ if (VGSvcToolboxMain(argc, argv, &rcExit))
+ return rcExit;
+#endif
+
+ bool fUserSession = false;
+#ifdef VBOX_WITH_VBOXSERVICE_CONTROL
+ /*
+ * Check if we're the specially spawned VBoxService.exe process that
+ * handles a guest control session.
+ */
+ if ( argc >= 2
+ && !RTStrICmp(argv[1], VBOXSERVICECTRLSESSION_GETOPT_PREFIX))
+ fUserSession = true;
+#endif
+
+ /*
+ * Connect to the kernel part before daemonizing and *before* we do the sub-service
+ * pre-init just in case one of services needs do to some initial stuff with it.
+ *
+ * However, we do not fail till after we've parsed arguments, because that will
+ * prevent useful stuff like --help, --register, --unregister and --version from
+ * working when the driver hasn't been installed/loaded yet.
+ */
+ int const rcVbgl = fUserSession ? VbglR3InitUser() : VbglR3Init();
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Check if we're the specially spawned VBoxService.exe process that
+ * handles page fusion. This saves an extra statically linked executable.
+ */
+ if ( argc == 2
+ && !RTStrICmp(argv[1], "pagefusion"))
+ {
+ if (RT_SUCCESS(rcVbgl))
+ return VGSvcPageSharingWorkerChild();
+ return vbglInitFailure(rcVbgl);
+ }
+#endif
+
+#ifdef VBOX_WITH_VBOXSERVICE_CONTROL
+ /*
+ * Check if we're the specially spawned VBoxService.exe process that
+ * handles a guest control session.
+ */
+ if (fUserSession)
+ {
+ if (RT_SUCCESS(rcVbgl))
+ return VGSvcGstCtrlSessionSpawnInit(argc, argv);
+ return vbglInitFailure(rcVbgl);
+ }
+#endif
+
+ /*
+ * Parse the arguments.
+ *
+ * Note! This code predates RTGetOpt, thus the manual parsing.
+ */
+ bool fDaemonize = true;
+ bool fDaemonized = false;
+ for (int i = 1; i < argc; i++)
+ {
+ const char *psz = argv[i];
+ if (*psz != '-')
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown argument '%s'\n", psz);
+ psz++;
+
+ /* translate long argument to short */
+ if (*psz == '-')
+ {
+ psz++;
+ size_t cch = strlen(psz);
+#define MATCHES(strconst) ( cch == sizeof(strconst) - 1 \
+ && !memcmp(psz, strconst, sizeof(strconst) - 1) )
+ if (MATCHES("foreground"))
+ psz = "f";
+ else if (MATCHES("verbose"))
+ psz = "v";
+ else if (MATCHES("version"))
+ psz = "V";
+ else if (MATCHES("help"))
+ psz = "h";
+ else if (MATCHES("interval"))
+ psz = "i";
+#ifdef RT_OS_WINDOWS
+ else if (MATCHES("register"))
+ psz = "r";
+ else if (MATCHES("unregister"))
+ psz = "u";
+#endif
+ else if (MATCHES("logfile"))
+ psz = "l";
+ else if (MATCHES("pidfile"))
+ psz = "p";
+ else if (MATCHES("daemonized"))
+ {
+ fDaemonized = true;
+ continue;
+ }
+ else
+ {
+ bool fFound = false;
+
+ if (cch > sizeof("enable-") && !memcmp(psz, RT_STR_TUPLE("enable-")))
+ for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++)
+ if ((fFound = !RTStrICmp(psz + sizeof("enable-") - 1, g_aServices[j].pDesc->pszName)))
+ g_aServices[j].fEnabled = true;
+
+ if (cch > sizeof("disable-") && !memcmp(psz, RT_STR_TUPLE("disable-")))
+ for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++)
+ if ((fFound = !RTStrICmp(psz + sizeof("disable-") - 1, g_aServices[j].pDesc->pszName)))
+ g_aServices[j].fEnabled = false;
+
+ if (cch > sizeof("only-") && !memcmp(psz, RT_STR_TUPLE("only-")))
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ {
+ g_aServices[j].fEnabled = !RTStrICmp(psz + sizeof("only-") - 1, g_aServices[j].pDesc->pszName);
+ if (g_aServices[j].fEnabled)
+ fFound = true;
+ }
+
+ if (!fFound)
+ {
+ rcExit = vgsvcLazyPreInit();
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++)
+ {
+ rc = g_aServices[j].pDesc->pfnOption(NULL, argc, argv, &i);
+ fFound = rc == VINF_SUCCESS;
+ if (fFound)
+ break;
+ if (rc != -1)
+ return rc;
+ }
+ }
+ if (!fFound)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown option '%s'\n", argv[i]);
+ continue;
+ }
+#undef MATCHES
+ }
+
+ /* handle the string of short options. */
+ do
+ {
+ switch (*psz)
+ {
+ case 'i':
+ rc = VGSvcArgUInt32(argc, argv, psz + 1, &i, &g_DefaultInterval, 1, (UINT32_MAX / 1000) - 1);
+ if (rc)
+ return rc;
+ psz = NULL;
+ break;
+
+ case 'f':
+ fDaemonize = false;
+ break;
+
+ case 'v':
+ g_cVerbosity++;
+ break;
+
+ case 'V':
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return RTEXITCODE_SUCCESS;
+
+ case 'h':
+ case '?':
+ return vgsvcUsage();
+
+#ifdef RT_OS_WINDOWS
+ case 'r':
+ return VGSvcWinInstall();
+
+ case 'u':
+ return VGSvcWinUninstall();
+#endif
+
+ case 'l':
+ {
+ rc = vgsvcArgString(argc, argv, psz + 1, &i, g_szLogFile, sizeof(g_szLogFile));
+ if (rc)
+ return rc;
+ psz = NULL;
+ break;
+ }
+
+ case 'p':
+ {
+ rc = vgsvcArgString(argc, argv, psz + 1, &i, g_szPidFile, sizeof(g_szPidFile));
+ if (rc)
+ return rc;
+ psz = NULL;
+ break;
+ }
+
+ default:
+ {
+ rcExit = vgsvcLazyPreInit();
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ bool fFound = false;
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ {
+ rc = g_aServices[j].pDesc->pfnOption(&psz, argc, argv, &i);
+ fFound = rc == VINF_SUCCESS;
+ if (fFound)
+ break;
+ if (rc != -1)
+ return rc;
+ }
+ if (!fFound)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown option '%c' (%s)\n", *psz, argv[i]);
+ break;
+ }
+ }
+ } while (psz && *++psz);
+ }
+
+ /* Now we can report the VBGL failure. */
+ if (RT_FAILURE(rcVbgl))
+ return vbglInitFailure(rcVbgl);
+
+ /* Check that at least one service is enabled. */
+ if (vgsvcCountEnabledServices() == 0)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "At least one service must be enabled\n");
+
+ rc = VGSvcLogCreate(g_szLogFile[0] ? g_szLogFile : NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create release log '%s', rc=%Rrc\n",
+ g_szLogFile[0] ? g_szLogFile : "<None>", rc);
+
+ /* Call pre-init if we didn't do it already. */
+ rcExit = vgsvcLazyPreInit();
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+#ifdef VBOX_WITH_VBOXSERVICE_DRMRESIZE
+# ifdef RT_OS_LINUX
+ rc = VbglR3DrmClientStart();
+ if (RT_FAILURE(rc))
+ VGSvcVerbose(0, "VMSVGA DRM resizing client not started, rc=%Rrc\n", rc);
+# endif /* RT_OS_LINUX */
+#endif /* VBOX_WITH_VBOXSERVICE_DRMRESIZE */
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Make sure only one instance of VBoxService runs at a time. Create a
+ * global mutex for that.
+ *
+ * Note! The \\Global\ namespace was introduced with Win2K, thus the
+ * version check.
+ * Note! If the mutex exists CreateMutex will open it and set last error to
+ * ERROR_ALREADY_EXISTS.
+ */
+ OSVERSIONINFOEX OSInfoEx;
+ RT_ZERO(OSInfoEx);
+ OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+
+
+ SetLastError(NO_ERROR);
+ HANDLE hMutexAppRunning;
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(5,0,0)) /* Windows 2000 */
+ hMutexAppRunning = CreateMutexW(NULL, FALSE, L"Global\\" RT_CONCAT(L,VBOXSERVICE_NAME));
+ else
+ hMutexAppRunning = CreateMutexW(NULL, FALSE, RT_CONCAT(L,VBOXSERVICE_NAME));
+ if (hMutexAppRunning == NULL)
+ {
+ DWORD dwErr = GetLastError();
+ if ( dwErr == ERROR_ALREADY_EXISTS
+ || dwErr == ERROR_ACCESS_DENIED)
+ {
+ VGSvcError("%s is already running! Terminating.\n", g_pszProgName);
+ return RTEXITCODE_FAILURE;
+ }
+
+ VGSvcError("CreateMutex failed with last error %u! Terminating.\n", GetLastError());
+ return RTEXITCODE_FAILURE;
+ }
+
+#else /* !RT_OS_WINDOWS */
+ /* On other OSes we have PID file support provided by the actual service definitions / service wrapper scripts,
+ * like vboxadd-service.sh on Linux or vboxservice.xml on Solaris. */
+#endif /* !RT_OS_WINDOWS */
+
+ VGSvcVerbose(0, "%s r%s started. Verbose level = %d\n", RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity);
+
+ /*
+ * Daemonize if requested.
+ */
+ if (fDaemonize && !fDaemonized)
+ {
+#ifdef RT_OS_WINDOWS
+ VGSvcVerbose(2, "Starting service dispatcher ...\n");
+ rcExit = VGSvcWinEnterCtrlDispatcher();
+#else
+ VGSvcVerbose(1, "Daemonizing...\n");
+ rc = VbglR3Daemonize(false /* fNoChDir */, false /* fNoClose */,
+ false /* fRespawn */, NULL /* pcRespawn */);
+ if (RT_FAILURE(rc))
+ return VGSvcError("Daemon failed: %Rrc\n", rc);
+ /* in-child */
+#endif
+ }
+#ifdef RT_OS_WINDOWS
+ else
+#endif
+ {
+ /*
+ * Windows: We're running the service as a console application now. Start the
+ * services, enter the main thread's run loop and stop them again
+ * when it returns.
+ *
+ * POSIX: This is used for both daemons and console runs. Start all services
+ * and return immediately.
+ */
+#ifdef RT_OS_WINDOWS
+ /* Install console control handler. */
+ if (!SetConsoleCtrlHandler(vgsvcWinConsoleControlHandler, TRUE /* Add handler */))
+ {
+ VGSvcError("Unable to add console control handler, error=%ld\n", GetLastError());
+ /* Just skip this error, not critical. */
+ }
+#endif /* RT_OS_WINDOWS */
+ rc = VGSvcStartServices();
+ RTFILE hPidFile = NIL_RTFILE;
+ if (RT_SUCCESS(rc))
+ if (g_szPidFile[0])
+ rc = VbglR3PidFile(g_szPidFile, &hPidFile);
+ rcExit = RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+ if (RT_SUCCESS(rc))
+ VGSvcMainWait();
+ if (g_szPidFile[0] && hPidFile != NIL_RTFILE)
+ VbglR3ClosePidFile(g_szPidFile, hPidFile);
+#ifdef RT_OS_WINDOWS
+ /* Uninstall console control handler. */
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
+ {
+ VGSvcError("Unable to remove console control handler, error=%ld\n", GetLastError());
+ /* Just skip this error, not critical. */
+ }
+#else /* !RT_OS_WINDOWS */
+ /* On Windows - since we're running as a console application - we already stopped all services
+ * through the console control handler. So only do the stopping of services here on other platforms
+ * where the break/shutdown/whatever signal was just received. */
+ VGSvcStopServices();
+#endif /* RT_OS_WINDOWS */
+ }
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Terminated);
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Cleanup mutex.
+ */
+ CloseHandle(hMutexAppRunning);
+#endif
+
+ VGSvcVerbose(0, "Ended.\n");
+
+#ifdef DEBUG
+ RTCritSectDelete(&g_csLog);
+ //RTMemTrackerDumpAllToStdOut();
+#endif
+
+ VGSvcLogDestroy();
+
+ return rcExit;
+}
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp
new file mode 100644
index 00000000..75bf718a
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp
@@ -0,0 +1,2194 @@
+/* $Id: VBoxServiceAutoMount.cpp $ */
+/** @file
+ * VBoxService - Auto-mounting for Shared Folders, only Linux & Solaris atm.
+ */
+
+/*
+ * 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
+ */
+
+
+/** @page pg_vgsvc_automount VBoxService - Shared Folder Automounter
+ *
+ * The Shared Folder Automounter subservice mounts shared folders upon request
+ * from the host.
+ *
+ * This retrieves shared folder automount requests from Main via the VMMDev.
+ * The current implemention only does this once, for some inexplicable reason,
+ * so the run-time addition of automounted shared folders are not heeded.
+ *
+ * This subservice is only used on linux and solaris. On Windows the current
+ * thinking is this is better of done from VBoxTray, some one argue that for
+ * drive letter assigned shared folders it would be better to do some magic here
+ * (obviously not involving NDAddConnection).
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/sort.h>
+#include <iprt/string.h>
+#include <VBox/err.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/shflsvc.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+
+#ifdef RT_OS_WINDOWS
+#elif defined(RT_OS_OS2)
+# define INCL_DOSFILEMGR
+# define INCL_ERRORS
+# define OS2EMX_PLAIN_CHAR
+# include <os2emx.h>
+#else
+# include <errno.h>
+# include <grp.h>
+# include <sys/mount.h>
+# ifdef RT_OS_SOLARIS
+# include <sys/mntent.h>
+# include <sys/mnttab.h>
+# include <sys/vfs.h>
+# elif defined(RT_OS_LINUX)
+# include <mntent.h>
+# include <paths.h>
+# include <sys/utsname.h>
+RT_C_DECLS_BEGIN
+# include "../../linux/sharedfolders/vbsfmount.h"
+RT_C_DECLS_END
+# else
+# error "Port me!"
+# endif
+# include <unistd.h>
+#endif
+
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR
+ * Default mount directory (unix only).
+ */
+#ifndef VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR
+# define VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR "/media"
+#endif
+
+/** @def VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX
+ * Default mount prefix (unix only).
+ */
+#ifndef VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX
+# define VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX "sf_"
+#endif
+
+#ifndef _PATH_MOUNTED
+# ifdef RT_OS_SOLARIS
+# define _PATH_MOUNTED "/etc/mnttab"
+# else
+# define _PATH_MOUNTED "/etc/mtab"
+# endif
+#endif
+
+/** @def VBOXSERVICE_AUTOMOUNT_MIQF
+ * The drive letter / path mount point flag. */
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+# define VBOXSERVICE_AUTOMOUNT_MIQF SHFL_MIQF_DRIVE_LETTER
+#else
+# define VBOXSERVICE_AUTOMOUNT_MIQF SHFL_MIQF_PATH
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Automounter mount table entry.
+ *
+ * This holds the information returned by SHFL_FN_QUERY_MAP_INFO and
+ * additional mount state info. We only keep entries for mounted mappings.
+ */
+typedef struct VBSVCAUTOMOUNTERENTRY
+{
+ /** The root ID. */
+ uint32_t idRoot;
+ /** The root ID version. */
+ uint32_t uRootIdVersion;
+ /** Map info flags, SHFL_MIF_XXX. */
+ uint64_t fFlags;
+ /** The shared folder (mapping) name. */
+ char *pszName;
+ /** The configured mount point, NULL if none. */
+ char *pszMountPoint;
+ /** The actual mount point, NULL if not mount. */
+ char *pszActualMountPoint;
+} VBSVCAUTOMOUNTERENTRY;
+/** Pointer to an automounter entry. */
+typedef VBSVCAUTOMOUNTERENTRY *PVBSVCAUTOMOUNTERENTRY;
+
+/** Automounter mount table. */
+typedef struct VBSVCAUTOMOUNTERTABLE
+{
+ /** Current number of entries in the array. */
+ uint32_t cEntries;
+ /** Max number of entries the array can hold w/o growing it. */
+ uint32_t cAllocated;
+ /** Pointer to an array of entry pointers. */
+ PVBSVCAUTOMOUNTERENTRY *papEntries;
+} VBSVCAUTOMOUNTERTABLE;
+/** Pointer to an automounter mount table. */
+typedef VBSVCAUTOMOUNTERTABLE *PVBSVCAUTOMOUNTERTABLE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_hAutoMountEvent = NIL_RTSEMEVENTMULTI;
+/** The Shared Folders service client ID. */
+static uint32_t g_idClientSharedFolders = 0;
+/** Set if we can wait on changes to the mappings. */
+static bool g_fHostSupportsWaitAndInfoQuery = false;
+
+#ifdef RT_OS_OS2
+/** The attachment tag we use to identify attchments that belongs to us. */
+static char const g_szTag[] = "VBoxAutomounter";
+#elif defined(RT_OS_LINUX)
+/** Tag option value that lets us identify mounts that belongs to us. */
+static char const g_szTag[] = "VBoxAutomounter";
+#elif defined(RT_OS_SOLARIS)
+/** Dummy mount option that lets us identify mounts that belongs to us. */
+static char const g_szTag[] = "VBoxAutomounter";
+#endif
+
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbsvcAutomounterInit(void)
+{
+ VGSvcVerbose(3, "vbsvcAutomounterInit\n");
+
+ int rc = RTSemEventMultiCreate(&g_hAutoMountEvent);
+ AssertRCReturn(rc, rc);
+
+ rc = VbglR3SharedFolderConnect(&g_idClientSharedFolders);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterInit: Service Client ID: %#x\n", g_idClientSharedFolders);
+ g_fHostSupportsWaitAndInfoQuery = RT_SUCCESS(VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders));
+ }
+ else
+ {
+ /* If the service was not found, we disable this service without
+ causing VBoxService to fail. */
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
+ {
+ VGSvcVerbose(0, "vbsvcAutomounterInit: Shared Folders service is not available\n");
+ rc = VERR_SERVICE_DISABLED;
+ }
+ else
+ VGSvcError("Control: Failed to connect to the Shared Folders service! Error: %Rrc\n", rc);
+ RTSemEventMultiDestroy(g_hAutoMountEvent);
+ g_hAutoMountEvent = NIL_RTSEMEVENTMULTI;
+ }
+
+ return rc;
+}
+
+
+#if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) /* The old code: */
+
+/**
+ * @todo Integrate into RTFsQueryMountpoint()?
+ */
+static bool vbsvcAutoMountShareIsMountedOld(const char *pszShare, char *pszMountPoint, size_t cbMountPoint)
+{
+ AssertPtrReturn(pszShare, false);
+ AssertPtrReturn(pszMountPoint, false);
+ AssertReturn(cbMountPoint, false);
+
+ bool fMounted = false;
+
+# if defined(RT_OS_SOLARIS)
+ /** @todo What to do if we have a relative path in mtab instead
+ * of an absolute one ("temp" vs. "/media/temp")?
+ * procfs contains the full path but not the actual share name ...
+ * FILE *pFh = setmntent("/proc/mounts", "r+t"); */
+ FILE *pFh = fopen(_PATH_MOUNTED, "r");
+ if (!pFh)
+ VGSvcError("vbsvcAutoMountShareIsMountedOld: Could not open mount tab '%s'!\n", _PATH_MOUNTED);
+ else
+ {
+ mnttab mntTab;
+ while ((getmntent(pFh, &mntTab)))
+ {
+ if (!RTStrICmp(mntTab.mnt_special, pszShare))
+ {
+ fMounted = RTStrPrintf(pszMountPoint, cbMountPoint, "%s", mntTab.mnt_mountp)
+ ? true : false;
+ break;
+ }
+ }
+ fclose(pFh);
+ }
+# elif defined(RT_OS_LINUX)
+ FILE *pFh = setmntent(_PATH_MOUNTED, "r+t"); /** @todo r=bird: why open it for writing? (the '+') */
+ if (pFh == NULL)
+ VGSvcError("vbsvcAutoMountShareIsMountedOld: Could not open mount tab '%s'!\n", _PATH_MOUNTED);
+ else
+ {
+ mntent *pMntEnt;
+ while ((pMntEnt = getmntent(pFh)))
+ {
+ if (!RTStrICmp(pMntEnt->mnt_fsname, pszShare))
+ {
+ fMounted = RTStrPrintf(pszMountPoint, cbMountPoint, "%s", pMntEnt->mnt_dir)
+ ? true : false;
+ break;
+ }
+ }
+ endmntent(pFh);
+ }
+# else
+# error "PORTME!"
+# endif
+
+ VGSvcVerbose(4, "vbsvcAutoMountShareIsMountedOld: Share '%s' at mount point '%s' = %s\n",
+ pszShare, fMounted ? pszMountPoint : "<None>", fMounted ? "Yes" : "No");
+ return fMounted;
+}
+
+
+/**
+ * Unmounts a shared folder.
+ *
+ * @returns VBox status code
+ * @param pszMountPoint The shared folder mount point.
+ */
+static int vbsvcAutoMountUnmountOld(const char *pszMountPoint)
+{
+ AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ uint8_t uTries = 0;
+ int r;
+ while (uTries++ < 3)
+ {
+ r = umount(pszMountPoint);
+ if (r == 0)
+ break;
+/** @todo r=bird: Why do sleep 5 seconds after the final retry?
+ * May also be a good idea to check for EINVAL or other signs that someone
+ * else have already unmounted the share. */
+ RTThreadSleep(5000); /* Wait a while ... */
+ }
+ if (r == -1) /** @todo r=bird: RTThreadSleep set errno. */
+ rc = RTErrConvertFromErrno(errno);
+ return rc;
+}
+
+
+/**
+ * Prepares a mount point (create it, set group and mode).
+ *
+ * @returns VBox status code
+ * @param pszMountPoint The mount point.
+ * @param pszShareName Unused.
+ * @param gidGroup The group ID.
+ */
+static int vbsvcAutoMountPrepareMountPointOld(const char *pszMountPoint, const char *pszShareName, RTGID gidGroup)
+{
+ AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszShareName, VERR_INVALID_PARAMETER);
+
+ /** @todo r=bird: There is no reason why gidGroup should have write access?
+ * Seriously, what kind of non-sense is this? */
+
+ RTFMODE fMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG; /* Owner (=root) and the group (=vboxsf) have full access. */
+ int rc = RTDirCreateFullPath(pszMountPoint, fMode);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPathSetOwnerEx(pszMountPoint, NIL_RTUID /* Owner, unchanged */, gidGroup, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPathSetMode(pszMountPoint, fMode);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_WRITE_PROTECT)
+ {
+ VGSvcVerbose(3, "vbsvcAutoMountPrepareMountPointOld: Mount directory '%s' already is used/mounted\n",
+ pszMountPoint);
+ rc = VINF_SUCCESS;
+ }
+ else
+ VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not set mode %RTfmode for mount directory '%s', rc = %Rrc\n",
+ fMode, pszMountPoint, rc);
+ }
+ }
+ else
+ VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not set permissions for mount directory '%s', rc = %Rrc\n",
+ pszMountPoint, rc);
+ }
+ else
+ VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not create mount directory '%s' with mode %RTfmode, rc = %Rrc\n",
+ pszMountPoint, fMode, rc);
+ return rc;
+}
+
+
+/**
+ * Mounts a shared folder.
+ *
+ * @returns VBox status code reflecting unmount and mount point preparation
+ * results, but not actual mounting
+ *
+ * @param pszShareName The shared folder name.
+ * @param pszMountPoint The mount point.
+ */
+static int vbsvcAutoMountSharedFolderOld(const char *pszShareName, const char *pszMountPoint)
+{
+ /*
+ * Linux and solaris share the same mount structure.
+ */
+ struct group *grp_vboxsf = getgrnam("vboxsf");
+ if (!grp_vboxsf)
+ {
+ VGSvcError("vbsvcAutoMountWorker: Group 'vboxsf' does not exist\n");
+ return VINF_SUCCESS;
+ }
+
+ int rc = vbsvcAutoMountPrepareMountPointOld(pszMountPoint, pszShareName, grp_vboxsf->gr_gid);
+ if (RT_SUCCESS(rc))
+ {
+# ifdef RT_OS_SOLARIS
+ int const fFlags = MS_OPTIONSTR;
+ char szOptBuf[MAX_MNTOPT_STR] = { '\0', };
+ RTStrPrintf(szOptBuf, sizeof(szOptBuf), "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000", grp_vboxsf->gr_gid);
+ int r = mount(pszShareName,
+ pszMountPoint,
+ fFlags,
+ "vboxfs",
+ NULL, /* char *dataptr */
+ 0, /* int datalen */
+ szOptBuf,
+ sizeof(szOptBuf));
+ if (r == 0)
+ VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint);
+ else if (errno != EBUSY) /* Share is already mounted? Then skip error msg. */
+ VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s', error = %s\n",
+ pszShareName, pszMountPoint, strerror(errno));
+
+# else /* RT_OS_LINUX */
+ struct utsname uts;
+ AssertStmt(uname(&uts) != -1, strcpy(uts.release, "4.4.0"));
+
+ unsigned long const fFlags = MS_NODEV;
+ char szOpts[MAX_MNTOPT_STR] = { '\0' };
+ ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts), "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000",
+ grp_vboxsf->gr_gid);
+ if (cchOpts > 0 && RTStrVersionCompare(uts.release, "2.6.0") < 0)
+ cchOpts = RTStrPrintf2(&szOpts[cchOpts], sizeof(szOpts) - cchOpts, ",sf_name=%s", pszShareName);
+ if (cchOpts <= 0)
+ {
+ VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd (share %s)\n", cchOpts, pszShareName);
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ int r = mount(pszShareName,
+ pszMountPoint,
+ "vboxsf",
+ fFlags,
+ szOpts);
+ if (r == 0)
+ {
+ VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint);
+
+ r = vbsfmount_complete(pszShareName, pszMountPoint, fFlags, szOpts);
+ switch (r)
+ {
+ case 0: /* Success. */
+ errno = 0; /* Clear all errors/warnings. */
+ break;
+ case 1:
+ VGSvcError("vbsvcAutoMountWorker: Could not update mount table (malloc failure)\n");
+ break;
+ case 2:
+ VGSvcError("vbsvcAutoMountWorker: Could not open mount table for update: %s\n", strerror(errno));
+ break;
+ case 3:
+ /* VGSvcError("vbsvcAutoMountWorker: Could not add an entry to the mount table: %s\n", strerror(errno)); */
+ errno = 0;
+ break;
+ default:
+ VGSvcError("vbsvcAutoMountWorker: Unknown error while completing mount operation: %d\n", r);
+ break;
+ }
+ }
+ else /* r == -1, we got some error in errno. */
+ {
+ switch (errno)
+ {
+ /* If we get EINVAL here, the system already has mounted the Shared Folder to another
+ * mount point. */
+ case EINVAL:
+ VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' is already mounted!\n", pszShareName);
+ /* Ignore this error! */
+ break;
+ case EBUSY:
+ /* Ignore these errors! */
+ break;
+ default:
+ VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s': %s (%d)\n",
+ pszShareName, pszMountPoint, strerror(errno), errno);
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+ }
+# endif
+ }
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Mounting returned with rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * Processes shared folder mappings retrieved from the host.
+ *
+ * @returns VBox status code.
+ * @param paMappings The mappings.
+ * @param cMappings The number of mappings.
+ * @param pszMountDir The mount directory.
+ * @param pszSharePrefix The share prefix.
+ * @param uClientID The shared folder service (HGCM) client ID.
+ */
+static int vbsvcAutoMountProcessMappingsOld(PCVBGLR3SHAREDFOLDERMAPPING paMappings, uint32_t cMappings,
+ const char *pszMountDir, const char *pszSharePrefix, uint32_t uClientID)
+{
+ if (cMappings == 0)
+ return VINF_SUCCESS;
+ AssertPtrReturn(paMappings, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszMountDir, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszSharePrefix, VERR_INVALID_PARAMETER);
+ AssertReturn(uClientID > 0, VERR_INVALID_PARAMETER);
+
+ /** @todo r=bird: Why is this loop schitzoid about status codes? It quits if
+ * RTPathJoin fails (i.e. if the user specifies a very long name), but happily
+ * continues if RTStrAPrintf failes (mem alloc).
+ *
+ * It also happily continues if the 'vboxsf' group is missing, which is a waste
+ * of effort... In fact, retrieving the group ID could probably be done up
+ * front, outside the loop. */
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < cMappings && RT_SUCCESS(rc); i++)
+ {
+ char *pszShareName = NULL;
+ rc = VbglR3SharedFolderGetName(uClientID, paMappings[i].u32Root, &pszShareName);
+ if ( RT_SUCCESS(rc)
+ && *pszShareName)
+ {
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Connecting share %u (%s) ...\n", i+1, pszShareName);
+
+ /** @todo r=bird: why do you copy things twice here and waste heap space?
+ * szMountPoint has a fixed size.
+ * @code
+ * char szMountPoint[RTPATH_MAX];
+ * rc = RTPathJoin(szMountPoint, sizeof(szMountPoint), pszMountDir, *pszSharePrefix ? pszSharePrefix : pszShareName);
+ * if (RT_SUCCESS(rc) && *pszSharePrefix)
+ * rc = RTStrCat(szMountPoint, sizeof(szMountPoint), pszShareName);
+ * @endcode */
+ char *pszShareNameFull = NULL;
+ if (RTStrAPrintf(&pszShareNameFull, "%s%s", pszSharePrefix, pszShareName) > 0)
+ {
+ char szMountPoint[RTPATH_MAX];
+ rc = RTPathJoin(szMountPoint, sizeof(szMountPoint), pszMountDir, pszShareNameFull);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "vbsvcAutoMountWorker: Processing mount point '%s'\n", szMountPoint);
+
+ /*
+ * Already mounted?
+ */
+ /** @todo r-bird: this does not take into account that a shared folder could
+ * be mounted twice... We're really just interested in whether the
+ * folder is mounted on 'szMountPoint', no where else... */
+ bool fSkip = false;
+ char szAlreadyMountedOn[RTPATH_MAX];
+ if (vbsvcAutoMountShareIsMountedOld(pszShareName, szAlreadyMountedOn, sizeof(szAlreadyMountedOn)))
+ {
+ /* Do if it not mounted to our desired mount point */
+ if (RTStrICmp(szMountPoint, szAlreadyMountedOn))
+ {
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder '%s' already mounted on '%s', unmounting ...\n",
+ pszShareName, szAlreadyMountedOn);
+ rc = vbsvcAutoMountUnmountOld(szAlreadyMountedOn);
+ if (RT_SUCCESS(rc))
+ fSkip = false;
+ else
+ VGSvcError("vbsvcAutoMountWorker: Failed to unmount '%s', %s (%d)! (rc=%Rrc)\n",
+ szAlreadyMountedOn, strerror(errno), errno, rc); /** @todo errno isn't reliable at this point */
+ }
+ if (fSkip)
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder '%s' already mounted on '%s', skipping\n",
+ pszShareName, szAlreadyMountedOn);
+ }
+ if (!fSkip)
+ {
+ /*
+ * Mount it.
+ */
+ rc = vbsvcAutoMountSharedFolderOld(pszShareName, szMountPoint);
+ }
+ }
+ else
+ VGSvcError("vbsvcAutoMountWorker: Unable to join mount point/prefix/shrae, rc = %Rrc\n", rc);
+ RTStrFree(pszShareNameFull);
+ }
+ else
+ VGSvcError("vbsvcAutoMountWorker: Unable to allocate full share name\n");
+ RTStrFree(pszShareName);
+ }
+ else
+ VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder name for root node = %u, rc = %Rrc\n",
+ paMappings[i].u32Root, rc);
+ } /* for cMappings. */
+ return rc;
+}
+
+#endif /* defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) - the old code*/
+
+
+/**
+ * Service worker function for old host.
+ *
+ * This only mount stuff on startup.
+ *
+ * @returns VBox status code.
+ * @param pfShutdown Shutdown indicator.
+ */
+static int vbsvcAutoMountWorkerOld(bool volatile *pfShutdown)
+{
+#if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX)
+ /*
+ * We only do a single pass here.
+ */
+ uint32_t cMappings;
+ PVBGLR3SHAREDFOLDERMAPPING paMappings;
+ int rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /* Only process auto-mounted folders */,
+ &paMappings, &cMappings);
+ if ( RT_SUCCESS(rc)
+ && cMappings)
+ {
+ char *pszMountDir;
+ rc = VbglR3SharedFolderGetMountDir(&pszMountDir);
+ if (rc == VERR_NOT_FOUND)
+ rc = RTStrDupEx(&pszMountDir, VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder mount dir set to '%s'\n", pszMountDir);
+
+ char *pszSharePrefix;
+ rc = VbglR3SharedFolderGetMountPrefix(&pszSharePrefix);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder mount prefix set to '%s'\n", pszSharePrefix);
+# ifdef USE_VIRTUAL_SHARES
+ /* Check for a fixed/virtual auto-mount share. */
+ if (VbglR3SharedFolderExists(g_idClientSharedFolders, "vbsfAutoMount"))
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Host supports auto-mount root\n");
+ else
+ {
+# endif
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Got %u shared folder mappings\n", cMappings);
+ rc = vbsvcAutoMountProcessMappingsOld(paMappings, cMappings, pszMountDir, pszSharePrefix,
+ g_idClientSharedFolders);
+# ifdef USE_VIRTUAL_SHARES
+ }
+# endif
+ RTStrFree(pszSharePrefix);
+ } /* Mount share prefix. */
+ else
+ VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder mount prefix, rc = %Rrc\n", rc);
+ RTStrFree(pszMountDir);
+ }
+ else
+ VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder directory, rc = %Rrc\n", rc);
+ VbglR3SharedFolderFreeMappings(paMappings);
+ }
+ else if (RT_FAILURE(rc))
+ VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder mappings, rc = %Rrc\n", rc);
+ else
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: No shared folder mappings found\n");
+
+#else
+ int rc = VINF_SUCCESS;
+#endif /* defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) */
+
+
+ /*
+ * Wait on shutdown (this used to be a silly RTThreadSleep(500) loop).
+ */
+ while (!*pfShutdown)
+ {
+ rc = RTSemEventMultiWait(g_hAutoMountEvent, RT_MS_1MIN);
+ if (rc != VERR_TIMEOUT)
+ break;
+ }
+
+ VGSvcVerbose(3, "vbsvcAutoMountWorkerOld: Finished with rc=%Rrc\n", rc);
+ return rc;
+}
+
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+/**
+ * Assembles the mount directory and prefix into @a pszDst.
+ *
+ * Will fall back on defaults if we have trouble with the configuration from the
+ * host. This ASSUMES that @a cbDst is rather large and won't cause trouble
+ * with the default.
+ *
+ * @returns IPRT status code.
+ * @param pszDst Where to return the prefix.
+ * @param cbDst The size of the prefix buffer.
+ */
+static int vbsvcAutomounterQueryMountDirAndPrefix(char *pszDst, size_t cbDst)
+{
+ /*
+ * Query the config first.
+ */
+ /* Mount directory: */
+ const char *pszDir = VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR;
+ char *pszCfgDir;
+ int rc = VbglR3SharedFolderGetMountDir(&pszCfgDir);
+ if (RT_SUCCESS(rc))
+ {
+ if (*pszCfgDir == '/')
+ pszDir = pszCfgDir;
+ }
+ else
+ pszCfgDir = NULL;
+
+ /* Prefix: */
+ const char *pszPrefix = VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX;
+ char *pszCfgPrefix;
+ rc = VbglR3SharedFolderGetMountPrefix(&pszCfgPrefix);
+ if (RT_SUCCESS(rc))
+ {
+ if ( strchr(pszCfgPrefix, '/') == NULL
+ && strchr(pszCfgPrefix, '\\') == NULL
+ && strcmp(pszCfgPrefix, "..") != 0)
+ pszPrefix = pszCfgPrefix;
+ }
+ else
+ pszCfgPrefix = NULL;
+
+ /*
+ * Try combine the two.
+ */
+ rc = RTPathAbs(pszDir, pszDst, cbDst);
+ if (RT_SUCCESS(rc))
+ {
+ if (*pszPrefix)
+ {
+ rc = RTPathAppend(pszDst, cbDst, pszPrefix);
+ if (RT_FAILURE(rc))
+ VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathAppend(%s,,%s) -> %Rrc\n", pszDst, pszPrefix, rc);
+ }
+ else
+ {
+ rc = RTPathEnsureTrailingSeparator(pszDst, cbDst);
+ if (RT_FAILURE(rc))
+ VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathEnsureTrailingSeparator(%s) -> %Rrc\n", pszDst, rc);
+ }
+ }
+ else
+ VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathAbs(%s) -> %Rrc\n", pszDir, rc);
+
+
+ /*
+ * Return the default dir + prefix if the above failed.
+ */
+ if (RT_FAILURE(rc))
+ {
+ rc = RTStrCopy(pszDst, cbDst, VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR "/" VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX);
+ AssertRC(rc);
+ }
+
+ RTStrFree(pszCfgDir);
+ RTStrFree(pszCfgPrefix);
+ return rc;
+}
+#endif /* !RT_OS_WINDOW && !RT_OS_OS2 */
+
+
+/**
+ * @callback_method_impl{FNRTSORTCMP, For sorting mount table by root ID. }
+ */
+static DECLCALLBACK(int) vbsvcAutomounterCompareEntry(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF_PV(pvUser);
+ PVBSVCAUTOMOUNTERENTRY pEntry1 = (PVBSVCAUTOMOUNTERENTRY)pvElement1;
+ PVBSVCAUTOMOUNTERENTRY pEntry2 = (PVBSVCAUTOMOUNTERENTRY)pvElement2;
+ return pEntry1->idRoot < pEntry2->idRoot ? -1
+ : pEntry1->idRoot > pEntry2->idRoot ? 1 : 0;
+}
+
+
+/**
+ * Worker for vbsvcAutomounterPopulateTable for adding discovered entries.
+ *
+ * This is puts dummies in for missing values, depending on
+ * vbsvcAutomounterPopulateTable to query them later.
+ *
+ * @returns VINF_SUCCESS or VERR_NO_MEMORY;
+ * @param pMountTable The mount table to add an entry to.
+ * @param pszName The shared folder name.
+ * @param pszMountPoint The mount point.
+ */
+static int vbsvcAutomounterAddEntry(PVBSVCAUTOMOUNTERTABLE pMountTable, const char *pszName, const char *pszMountPoint)
+{
+ VGSvcVerbose(2, "vbsvcAutomounterAddEntry: %s -> %s\n", pszMountPoint, pszName);
+ PVBSVCAUTOMOUNTERENTRY pEntry = (PVBSVCAUTOMOUNTERENTRY)RTMemAlloc(sizeof(*pEntry));
+ pEntry->idRoot = UINT32_MAX;
+ pEntry->uRootIdVersion = UINT32_MAX;
+ pEntry->fFlags = UINT64_MAX;
+ pEntry->pszName = RTStrDup(pszName);
+ pEntry->pszMountPoint = NULL;
+ pEntry->pszActualMountPoint = RTStrDup(pszMountPoint);
+ if (pEntry->pszName && pEntry->pszActualMountPoint)
+ {
+ if (pMountTable->cEntries + 1 <= pMountTable->cAllocated)
+ {
+ pMountTable->papEntries[pMountTable->cEntries++] = pEntry;
+ return VINF_SUCCESS;
+ }
+
+ void *pvNew = RTMemRealloc(pMountTable->papEntries, (pMountTable->cAllocated + 8) * sizeof(pMountTable->papEntries[0]));
+ if (pvNew)
+ {
+ pMountTable->cAllocated += 8;
+ pMountTable->papEntries = (PVBSVCAUTOMOUNTERENTRY *)pvNew;
+
+ pMountTable->papEntries[pMountTable->cEntries++] = pEntry;
+ return VINF_SUCCESS;
+ }
+ }
+ RTMemFree(pEntry->pszActualMountPoint);
+ RTMemFree(pEntry->pszName);
+ RTMemFree(pEntry);
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Populates the mount table as best we can with existing automount entries.
+ *
+ * @returns VINF_SUCCESS or VERR_NO_MEMORY;
+ * @param pMountTable The mount table (empty).
+ */
+static int vbsvcAutomounterPopulateTable(PVBSVCAUTOMOUNTERTABLE pMountTable)
+{
+ int rc;
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Loop thru the drive letters and check out each of them using QueryDosDeviceW.
+ */
+ static const char s_szDevicePath[] = "\\Device\\VBoxMiniRdr\\;";
+ for (char chDrive = 'Z'; chDrive >= 'A'; chDrive--)
+ {
+ RTUTF16 const wszMountPoint[4] = { (RTUTF16)chDrive, ':', '\0', '\0' };
+ RTUTF16 wszTargetPath[RTPATH_MAX];
+ DWORD const cwcResult = QueryDosDeviceW(wszMountPoint, wszTargetPath, RT_ELEMENTS(wszTargetPath));
+ if ( cwcResult > sizeof(s_szDevicePath)
+ && RTUtf16NICmpAscii(wszTargetPath, RT_STR_TUPLE(s_szDevicePath)) == 0)
+ {
+ PCRTUTF16 pwsz = &wszTargetPath[RT_ELEMENTS(s_szDevicePath) - 1];
+ Assert(pwsz[-1] == ';');
+ if ( (pwsz[0] & ~(RTUTF16)0x20) == chDrive
+ && pwsz[1] == ':'
+ && pwsz[2] == '\\')
+ {
+ /* For now we'll just use the special capitalization of the
+ "server" name to identify it as our work. We could check
+ if the symlink is from \Global?? or \??, but that trick does
+ work for older OS versions (<= XP) or when running the
+ service manually for testing/wathever purposes. */
+ /** @todo Modify the windows shared folder driver to allow tagging drives.*/
+ if (RTUtf16NCmpAscii(&pwsz[3], RT_STR_TUPLE("VBoxSvr\\")) == 0)
+ {
+ pwsz += 3 + 8;
+ if (*pwsz != '\\' && *pwsz)
+ {
+ /* The shared folder name should follow immediately after the server prefix. */
+ char *pszMountedName = NULL;
+ rc = RTUtf16ToUtf8(pwsz, &pszMountedName);
+ if (RT_SUCCESS(rc))
+ {
+ char const szMountPoint[4] = { chDrive, ':', '\0', '\0' };
+ rc = vbsvcAutomounterAddEntry(pMountTable, pszMountedName, szMountPoint);
+ RTStrFree(pszMountedName);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ VGSvcVerbose(2, "vbsvcAutomounterPopulateTable: Malformed, not ours: %ls -> %ls\n",
+ wszMountPoint, wszTargetPath);
+ }
+ else
+ VGSvcVerbose(3, "vbsvcAutomounterPopulateTable: Not ours: %ls -> %ls\n", wszMountPoint, wszTargetPath);
+ }
+ }
+ }
+
+#elif defined(RT_OS_OS2)
+ /*
+ * Just loop thru the drive letters and check the attachment of each.
+ */
+ for (char chDrive = 'Z'; chDrive >= 'A'; chDrive--)
+ {
+ char const szMountPoint[4] = { chDrive, ':', '\0', '\0' };
+ union
+ {
+ FSQBUFFER2 FsQueryBuf;
+ char achPadding[1024];
+ } uBuf;
+ RT_ZERO(uBuf);
+ ULONG cbBuf = sizeof(uBuf) - 2;
+ APIRET rcOs2 = DosQueryFSAttach(szMountPoint, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf);
+ if (rcOs2 == NO_ERROR)
+ {
+ const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1];
+ if ( uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV
+ && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0)
+ {
+ const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1];
+ const char *pszTag = pszMountedName + strlen(pszMountedName) + 1; /* (Safe. Always two trailing zero bytes, see above.) */
+ if (strcmp(pszTag, g_szTag) == 0)
+ {
+ rc = vbsvcAutomounterAddEntry(pMountTable, pszMountedName, szMountPoint);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ }
+ }
+
+#elif defined(RT_OS_LINUX)
+ /*
+ * Scan the mount table file for the mount point and then match file system
+ * and device/share. We identify our mounts by mount path + prefix for now,
+ * but later we may use the same approach as on solaris.
+ */
+ FILE *pFile = setmntent("/proc/mounts", "r");
+ int iErrMounts = errno;
+ if (!pFile)
+ pFile = setmntent("/etc/mtab", "r");
+ if (pFile)
+ {
+ rc = VWRN_NOT_FOUND;
+ struct mntent *pEntry;
+ while ((pEntry = getmntent(pFile)) != NULL)
+ if (strcmp(pEntry->mnt_type, "vboxsf") == 0)
+ if (strstr(pEntry->mnt_opts, g_szTag) != NULL)
+ {
+ rc = vbsvcAutomounterAddEntry(pMountTable, pEntry->mnt_fsname, pEntry->mnt_dir);
+ if (RT_FAILURE(rc))
+ {
+ endmntent(pFile);
+ return rc;
+ }
+ }
+ endmntent(pFile);
+ }
+ else
+ VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d) or '/proc/mounts' (errno=%d)\n",
+ _PATH_MOUNTED, errno, iErrMounts);
+
+#elif defined(RT_OS_SOLARIS)
+ /*
+ * Look thru the system mount table and inspect the vboxsf mounts.
+ */
+ FILE *pFile = fopen(_PATH_MOUNTED, "r");
+ if (pFile)
+ {
+ rc = VINF_SUCCESS;
+ struct mnttab Entry;
+ while (getmntent(pFile, &Entry) == 0)
+ if (strcmp(Entry.mnt_fstype, "vboxfs") == 0)
+ {
+ /* Look for the dummy automounter option. */
+ if ( Entry.mnt_mntopts != NULL
+ && strstr(Entry.mnt_mntopts, g_szTag) != NULL)
+ {
+ rc = vbsvcAutomounterAddEntry(pMountTable, Entry.mnt_special, Entry.mnt_mountp);
+ if (RT_FAILURE(rc))
+ {
+ fclose(pFile);
+ return rc;
+ }
+ }
+ }
+ fclose(pFile);
+ }
+ else
+ VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d)\n", _PATH_MOUNTED, errno);
+
+#else
+# error "PORTME!"
+#endif
+
+ /*
+ * Try reconcile the detected folders with data from the host.
+ */
+ uint32_t cMappings = 0;
+ PVBGLR3SHAREDFOLDERMAPPING paMappings = NULL;
+ rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /*fAutoMountOnly*/, &paMappings, &cMappings);
+ if (RT_SUCCESS(rc))
+ {
+ for (uint32_t i = 0; i < cMappings && RT_SUCCESS(rc); i++)
+ {
+ uint32_t const idRootSrc = paMappings[i].u32Root;
+
+ uint32_t uRootIdVer = UINT32_MAX;
+ uint64_t fFlags = 0;
+ char *pszName = NULL;
+ char *pszMntPt = NULL;
+ int rc2 = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF,
+ &pszName, &pszMntPt, &fFlags, &uRootIdVer);
+ if (RT_SUCCESS(rc2))
+ {
+ uint32_t iPrevHit = UINT32_MAX;
+ for (uint32_t iTable = 0; iTable < pMountTable->cEntries; iTable++)
+ {
+ PVBSVCAUTOMOUNTERENTRY pEntry = pMountTable->papEntries[iTable];
+ if (RTStrICmp(pEntry->pszName, pszName) == 0)
+ {
+ VGSvcVerbose(2, "vbsvcAutomounterPopulateTable: Identified %s -> %s: idRoot=%u ver=%u fFlags=%#x AutoMntPt=%s\n",
+ pEntry->pszActualMountPoint, pEntry->pszName, idRootSrc, uRootIdVer, fFlags, pszMntPt);
+ pEntry->fFlags = fFlags;
+ pEntry->idRoot = idRootSrc;
+ pEntry->uRootIdVersion = uRootIdVer;
+ RTStrFree(pEntry->pszMountPoint);
+ pEntry->pszMountPoint = RTStrDup(pszMntPt);
+ if (!pEntry->pszMountPoint)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* If multiple mappings of the same folder, pick the first or the one
+ with matching mount point. */
+ if (iPrevHit == UINT32_MAX)
+ iPrevHit = iTable;
+ else if (RTPathCompare(pszMntPt, pEntry->pszActualMountPoint) == 0)
+ {
+ if (iPrevHit != UINT32_MAX)
+ pMountTable->papEntries[iPrevHit]->uRootIdVersion -= 1;
+ iPrevHit = iTable;
+ }
+ else
+ pEntry->uRootIdVersion -= 1;
+ }
+ }
+
+ RTStrFree(pszName);
+ RTStrFree(pszMntPt);
+ }
+ else
+ VGSvcError("vbsvcAutomounterPopulateTable: VbglR3SharedFolderQueryFolderInfo(%u) failed: %Rrc\n", idRootSrc, rc2);
+ }
+
+ VbglR3SharedFolderFreeMappings(paMappings);
+
+ /*
+ * Sort the table by root ID.
+ */
+ if (pMountTable->cEntries > 1)
+ RTSortApvShell((void **)pMountTable->papEntries, pMountTable->cEntries, vbsvcAutomounterCompareEntry, NULL);
+
+ for (uint32_t iTable = 0; iTable < pMountTable->cEntries; iTable++)
+ {
+ PVBSVCAUTOMOUNTERENTRY pEntry = pMountTable->papEntries[iTable];
+ if (pMountTable->papEntries[iTable]->idRoot != UINT32_MAX)
+ VGSvcVerbose(1, "vbsvcAutomounterPopulateTable: #%u: %s -> %s idRoot=%u ver=%u fFlags=%#x AutoMntPt=%s\n",
+ iTable, pEntry->pszActualMountPoint, pEntry->pszName, pEntry->idRoot, pEntry->uRootIdVersion,
+ pEntry->fFlags, pEntry->pszMountPoint);
+ else
+ VGSvcVerbose(1, "vbsvcAutomounterPopulateTable: #%u: %s -> %s - not identified!\n",
+ iTable, pEntry->pszActualMountPoint, pEntry->pszName);
+ }
+ }
+ else
+ VGSvcError("vbsvcAutomounterPopulateTable: VbglR3SharedFolderGetMappings failed: %Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * Checks whether the shared folder @a pszName is mounted on @a pszMountPoint.
+ *
+ * @returns Exactly one of the following IPRT status codes;
+ * @retval VINF_SUCCESS if mounted
+ * @retval VWRN_NOT_FOUND if nothing is mounted at @a pszMountPoint.
+ * @retval VERR_RESOURCE_BUSY if a different shared folder is mounted there.
+ * @retval VERR_ACCESS_DENIED if a non-shared folder file system is mounted
+ * there.
+ *
+ * @param pszMountPoint The mount point to check.
+ * @param pszName The name of the shared folder (mapping).
+ */
+static int vbsvcAutomounterQueryMountPoint(const char *pszMountPoint, const char *pszName)
+{
+ VGSvcVerbose(4, "vbsvcAutomounterQueryMountPoint: pszMountPoint=%s pszName=%s\n", pszMountPoint, pszName);
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * We could've used RTFsQueryType here but would then have to
+ * calling RTFsQueryLabel for the share name hint, ending up
+ * doing the same work twice. We could also use QueryDosDeviceW,
+ * but output is less clear...
+ */
+ PRTUTF16 pwszMountPoint = NULL;
+ int rc = RTStrToUtf16(pszMountPoint, &pwszMountPoint);
+ if (RT_SUCCESS(rc))
+ {
+ DWORD uSerial = 0;
+ DWORD cchCompMax = 0;
+ DWORD fFlags = 0;
+ RTUTF16 wszLabel[512];
+ RTUTF16 wszFileSystem[256];
+ RT_ZERO(wszLabel);
+ RT_ZERO(wszFileSystem);
+ if (GetVolumeInformationW(pwszMountPoint, wszLabel, RT_ELEMENTS(wszLabel) - 1, &uSerial, &cchCompMax, &fFlags,
+ wszFileSystem, RT_ELEMENTS(wszFileSystem) - 1))
+ {
+ if (RTUtf16ICmpAscii(wszFileSystem, "VBoxSharedFolderFS") == 0)
+ {
+ char *pszLabel = NULL;
+ rc = RTUtf16ToUtf8(wszLabel, &pszLabel);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszMountedName = pszLabel;
+ if (RTStrStartsWith(pszMountedName, "VBOX_"))
+ pszMountedName += sizeof("VBOX_") - 1;
+ if (RTStrICmp(pszMountedName, pszName) == 0)
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
+ pszName, pszMountPoint);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
+ pszMountedName, pszMountPoint, pszName);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ RTStrFree(pszLabel);
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: RTUtf16ToUtf8(%ls,) failed: %Rrc\n", wszLabel, rc);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%ls' with label '%ls' mount at '%s', not '%s'...\n",
+ wszFileSystem, wszLabel, pszMountPoint, pszName);
+ rc = VERR_ACCESS_DENIED;
+ }
+ }
+ else
+ {
+ rc = GetLastError();
+ if (rc != ERROR_PATH_NOT_FOUND || g_cVerbosity >= 4)
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: GetVolumeInformationW('%ls',,,,) failed: %u\n", pwszMountPoint, rc);
+ if (rc == ERROR_PATH_NOT_FOUND)
+ rc = VWRN_NOT_FOUND;
+ else if ( RT_C_IS_ALPHA(pszMountPoint[0])
+ && pszMountPoint[1] == ':'
+ && ( pszMountPoint[2] == '\0'
+ || (RTPATH_IS_SLASH(pszMountPoint[2]) && pszMountPoint[3] == '\0')))
+ {
+ /* See whether QueryDosDeviceW thinks its a malfunctioning shared folder or
+ something else (it doesn't access the file system). We've seen
+ VERR_NET_HOST_NOT_FOUND here for shared folders that was removed on the
+ host side.
+
+ Note! This duplicates code from vbsvcAutomounterPopulateTable. */
+ rc = VERR_ACCESS_DENIED;
+ static const char s_szDevicePath[] = "\\Device\\VBoxMiniRdr\\;";
+ wszFileSystem[0] = pwszMountPoint[0];
+ wszFileSystem[1] = pwszMountPoint[1];
+ wszFileSystem[2] = '\0';
+ DWORD const cwcResult = QueryDosDeviceW(wszFileSystem, wszLabel, RT_ELEMENTS(wszLabel));
+ if ( cwcResult > sizeof(s_szDevicePath)
+ && RTUtf16NICmpAscii(wszLabel, RT_STR_TUPLE(s_szDevicePath)) == 0)
+ {
+ PCRTUTF16 pwsz = &wszLabel[RT_ELEMENTS(s_szDevicePath) - 1];
+ Assert(pwsz[-1] == ';');
+ if ( (pwsz[0] & ~(RTUTF16)0x20) == (wszFileSystem[0] & ~(RTUTF16)0x20)
+ && pwsz[1] == ':'
+ && pwsz[2] == '\\')
+ {
+ if (RTUtf16NICmpAscii(&pwsz[3], RT_STR_TUPLE("VBoxSvr\\")) == 0)
+ {
+ pwsz += 3 + 8;
+ char *pszMountedName = NULL;
+ rc = RTUtf16ToUtf8(pwsz, &pszMountedName);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTStrICmp(pszMountedName, pszName) == 0)
+ {
+ rc = VINF_SUCCESS;
+ VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s' (using QueryDosDeviceW).\n",
+ pszName, pszMountPoint);
+ }
+ else
+ {
+ VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s' (using QueryDosDeviceW), not '%s'...\n",
+ pszMountedName, pszMountPoint, pszName);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ RTStrFree(pszMountedName);
+ }
+ else
+ {
+ VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: RTUtf16ToUtf8 failed: %Rrc\n", rc);
+ AssertRC(rc);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ }
+ }
+ }
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+ }
+ RTUtf16Free(pwszMountPoint);
+ }
+ else
+ {
+ VGSvcError("vbsvcAutomounterQueryMountPoint: RTStrToUtf16(%s,) -> %Rrc\n", pszMountPoint, rc);
+ rc = VWRN_NOT_FOUND;
+ }
+ return rc;
+
+#elif defined(RT_OS_OS2)
+ /*
+ * Query file system attachment info for the given drive letter.
+ */
+ union
+ {
+ FSQBUFFER2 FsQueryBuf;
+ char achPadding[512];
+ } uBuf;
+ RT_ZERO(uBuf);
+
+ ULONG cbBuf = sizeof(uBuf);
+ APIRET rcOs2 = DosQueryFSAttach(pszMountPoint, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf);
+ int rc;
+ if (rcOs2 == NO_ERROR)
+ {
+ const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1];
+ if ( uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV
+ && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0)
+ {
+ const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1];
+ if (RTStrICmp(pszMountedName, pszName) == 0)
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
+ pszName, pszMountPoint);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
+ pszMountedName, pszMountPoint, pszName);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' type %u mount at '%s', not '%s'...\n",
+ pszFsdName, uBuf.FsQueryBuf.iType, pszMountPoint, pszName);
+ rc = VERR_ACCESS_DENIED;
+ }
+ }
+ else
+ {
+ rc = VWRN_NOT_FOUND;
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: DosQueryFSAttach(%s) -> %u\n", pszMountPoint, rcOs2);
+ AssertMsgStmt(rcOs2 != ERROR_BUFFER_OVERFLOW && rcOs2 != ERROR_INVALID_PARAMETER,
+ ("%s -> %u\n", pszMountPoint, rcOs2), rc = VERR_ACCESS_DENIED);
+ }
+ return rc;
+
+#elif defined(RT_OS_LINUX)
+ /*
+ * Scan one of the mount table file for the mount point and then
+ * match file system and device/share.
+ */
+ FILE *pFile = setmntent("/proc/mounts", "r");
+ int rc = errno;
+ if (!pFile)
+ pFile = setmntent(_PATH_MOUNTED, "r");
+ if (pFile)
+ {
+ rc = VWRN_NOT_FOUND;
+ struct mntent *pEntry;
+ while ((pEntry = getmntent(pFile)) != NULL)
+ if (RTPathCompare(pEntry->mnt_dir, pszMountPoint) == 0)
+ {
+ if (strcmp(pEntry->mnt_type, "vboxsf") == 0)
+ {
+ if (RTStrICmp(pEntry->mnt_fsname, pszName) == 0)
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
+ pszName, pszMountPoint);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
+ pEntry->mnt_fsname, pszMountPoint, pszName);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' mount of '%s' at '%s', not '%s'...\n",
+ pEntry->mnt_type, pEntry->mnt_fsname, pszMountPoint, pszName);
+ rc = VERR_ACCESS_DENIED;
+ }
+ /* We continue searching in case of stacked mounts, we want the last one. */
+ }
+ endmntent(pFile);
+ }
+ else
+ {
+ VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '/proc/mounts' (errno=%d) or '%s' (errno=%d)\n",
+ rc, _PATH_MOUNTED, errno);
+ rc = VERR_ACCESS_DENIED;
+ }
+ return rc;
+
+#elif defined(RT_OS_SOLARIS)
+ /*
+ * Similar to linux.
+ */
+ int rc;
+ FILE *pFile = fopen(_PATH_MOUNTED, "r");
+ if (pFile)
+ {
+ rc = VWRN_NOT_FOUND;
+ struct mnttab Entry;
+ while (getmntent(pFile, &Entry) == 0)
+ if (RTPathCompare(Entry.mnt_mountp, pszMountPoint) == 0)
+ {
+ if (strcmp(Entry.mnt_fstype, "vboxfs") == 0)
+ {
+ if (RTStrICmp(Entry.mnt_special, pszName) == 0)
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
+ pszName, pszMountPoint);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
+ Entry.mnt_special, pszMountPoint, pszName);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' mount of '%s' at '%s', not '%s'...\n",
+ Entry.mnt_fstype, Entry.mnt_special, pszMountPoint, pszName);
+ rc = VERR_ACCESS_DENIED;
+ }
+ /* We continue searching in case of stacked mounts, we want the last one. */
+ }
+ fclose(pFile);
+ }
+ else
+ {
+ VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d)\n", _PATH_MOUNTED, errno);
+ rc = VERR_ACCESS_DENIED;
+ }
+ return rc;
+#else
+# error "PORTME"
+#endif
+}
+
+
+/**
+ * Worker for vbsvcAutomounterMountNewEntry that does the OS mounting.
+ *
+ * @returns IPRT status code.
+ * @param pEntry The entry to try mount.
+ */
+static int vbsvcAutomounterMountIt(PVBSVCAUTOMOUNTERENTRY pEntry)
+{
+ VGSvcVerbose(3, "vbsvcAutomounterMountIt: Trying to mount '%s' (idRoot=%#x) on '%s'...\n",
+ pEntry->pszName, pEntry->idRoot, pEntry->pszActualMountPoint);
+#ifdef RT_OS_WINDOWS
+ /*
+ * Attach the shared folder using WNetAddConnection2W.
+ *
+ * According to google we should get a drive symlink in \\GLOBAL?? when
+ * we are running under the system account. Otherwise it will be a session
+ * local link (\\??).
+ */
+ Assert(RT_C_IS_UPPER(pEntry->pszActualMountPoint[0]) && pEntry->pszActualMountPoint[1] == ':' && pEntry->pszActualMountPoint[2] == '\0');
+ RTUTF16 wszDrive[4] = { (RTUTF16)pEntry->pszActualMountPoint[0], ':', '\0', '\0' };
+
+ RTUTF16 wszPrefixedName[RTPATH_MAX];
+ int rc = RTUtf16CopyAscii(wszPrefixedName, RT_ELEMENTS(wszPrefixedName), "\\\\VBoxSvr\\");
+ AssertRC(rc);
+
+ size_t const offName = RTUtf16Len(wszPrefixedName);
+ PRTUTF16 pwszName = &wszPrefixedName[offName];
+ rc = RTStrToUtf16Ex(pEntry->pszName, RTSTR_MAX, &pwszName, sizeof(wszPrefixedName) - offName, NULL);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("vbsvcAutomounterMountIt: RTStrToUtf16Ex failed on '%s': %Rrc\n", pEntry->pszName, rc);
+ return rc;
+ }
+
+ VGSvcVerbose(3, "vbsvcAutomounterMountIt: wszDrive='%ls', wszPrefixedName='%ls'\n",
+ wszDrive, wszPrefixedName);
+
+ NETRESOURCEW NetRsrc;
+ RT_ZERO(NetRsrc);
+ NetRsrc.dwType = RESOURCETYPE_DISK;
+ NetRsrc.lpLocalName = wszDrive;
+ NetRsrc.lpRemoteName = wszPrefixedName;
+ NetRsrc.lpProvider = L"VirtualBox Shared Folders"; /* Only try our provider. */
+ NetRsrc.lpComment = pwszName;
+
+ DWORD dwErr = WNetAddConnection2W(&NetRsrc, NULL /*pwszPassword*/, NULL /*pwszUserName*/, 0 /*dwFlags*/);
+ if (dwErr == NO_ERROR)
+ {
+ VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
+ pEntry->pszName, pEntry->pszActualMountPoint);
+ return VINF_SUCCESS;
+ }
+ VGSvcError("vbsvcAutomounterMountIt: Failed to attach '%s' to '%s': %Rrc (%u)\n",
+ pEntry->pszName, pEntry->pszActualMountPoint, RTErrConvertFromWin32(dwErr), dwErr);
+ return VERR_OPEN_FAILED;
+
+#elif defined(RT_OS_OS2)
+ /*
+ * It's a rather simple affair on OS/2.
+ *
+ * In order to be able to detect our mounts we add a 2nd string after
+ * the folder name that tags the attachment. The IFS will remember this
+ * and return it when DosQueryFSAttach is called.
+ *
+ * Note! Kernel currently accepts limited 7-bit ASCII names. We could
+ * change that to UTF-8 if we like as that means no extra string
+ * encoding conversion fun here.
+ */
+ char szzNameAndTag[256];
+ size_t cchName = strlen(pEntry->pszName);
+ if (cchName + 1 + sizeof(g_szTag) <= sizeof(szzNameAndTag))
+ {
+ memcpy(szzNameAndTag, pEntry->pszName, cchName);
+ szzNameAndTag[cchName] = '\0';
+ memcpy(&szzNameAndTag[cchName + 1], g_szTag, sizeof(g_szTag));
+
+ APIRET rc = DosFSAttach(pEntry->pszActualMountPoint, "VBOXSF", szzNameAndTag, cchName + 1 + sizeof(g_szTag), FS_ATTACH);
+ if (rc == NO_ERROR)
+ {
+ VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
+ pEntry->pszName, pEntry->pszActualMountPoint);
+ return VINF_SUCCESS;
+ }
+ VGSvcError("vbsvcAutomounterMountIt: DosFSAttach failed to attach '%s' to '%s': %u\n",
+ pEntry->pszName, pEntry->pszActualMountPoint, rc);
+ }
+ else
+ VGSvcError("vbsvcAutomounterMountIt: Share name for attach to '%s' is too long: %u chars - '%s'\n",
+ pEntry->pszActualMountPoint, cchName, pEntry->pszName);
+ return VERR_OPEN_FAILED;
+
+#else
+ /*
+ * Common work for unix-like systems: Get group, make sure mount directory exist.
+ */
+ int rc = RTDirCreateFullPath(pEntry->pszActualMountPoint,
+ RTFS_UNIX_IRWXU | RTFS_UNIX_IXGRP | RTFS_UNIX_IRGRP | RTFS_UNIX_IXOTH | RTFS_UNIX_IROTH);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("vbsvcAutomounterMountIt: Failed to create mount path '%s' for share '%s': %Rrc\n",
+ pEntry->pszActualMountPoint, pEntry->pszName, rc);
+ return rc;
+ }
+
+ gid_t gidMount;
+ struct group *grp_vboxsf = getgrnam("vboxsf");
+ if (grp_vboxsf)
+ gidMount = grp_vboxsf->gr_gid;
+ else
+ {
+ VGSvcError("vbsvcAutomounterMountIt: Group 'vboxsf' does not exist\n");
+ gidMount = 0;
+ }
+
+# if defined(RT_OS_LINUX)
+ /*
+ * Linux a bit more work...
+ */
+ struct utsname uts;
+ AssertStmt(uname(&uts) != -1, strcpy(uts.release, "4.4.0"));
+
+ /* Built mount option string. Need st_name for pre 2.6.0 kernels. */
+ unsigned long const fFlags = MS_NODEV;
+ char szOpts[MAX_MNTOPT_STR] = { '\0' };
+ ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts),
+ "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000,tag=%s", gidMount, g_szTag);
+ if (RTStrVersionCompare(uts.release, "2.6.0") < 0 && cchOpts > 0)
+ cchOpts += RTStrPrintf2(&szOpts[cchOpts], sizeof(szOpts) - cchOpts, ",sf_name=%s", pEntry->pszName);
+ if (cchOpts <= 0)
+ {
+ VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd\n", cchOpts);
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* Do the mounting. The fallback w/o tag is for the Linux vboxsf fork
+ which lagged a lot behind when it first appeared in 5.6. */
+ errno = 0;
+ rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, szOpts);
+ if (rc != 0 && errno == EINVAL && RTStrVersionCompare(uts.release, "5.6.0") >= 0)
+ {
+ VGSvcVerbose(2, "vbsvcAutomounterMountIt: mount returned EINVAL, retrying without the tag.\n");
+ *strstr(szOpts, ",tag=") = '\0';
+ errno = 0;
+ rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, szOpts);
+ if (rc == 0)
+ VGSvcVerbose(0, "vbsvcAutomounterMountIt: Running outdated vboxsf module without support for the 'tag' option?\n");
+ }
+ if (rc == 0)
+ {
+ VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
+ pEntry->pszName, pEntry->pszActualMountPoint);
+
+ errno = 0;
+ rc = vbsfmount_complete(pEntry->pszName, pEntry->pszActualMountPoint, fFlags, szOpts);
+ if (rc != 0) /* Ignorable. /etc/mtab is probably a link to /proc/mounts. */
+ VGSvcVerbose(1, "vbsvcAutomounterMountIt: vbsfmount_complete failed: %s (%d/%d)\n",
+ rc == 1 ? "malloc" : rc == 2 ? "setmntent" : rc == 3 ? "addmntent" : "unknown", rc, errno);
+ return VINF_SUCCESS;
+ }
+
+ if (errno == EINVAL)
+ VGSvcError("vbsvcAutomounterMountIt: Failed to mount '%s' on '%s' because it is probably mounted elsewhere arleady! (%d,%d)\n",
+ pEntry->pszName, pEntry->pszActualMountPoint, rc, errno);
+ else
+ VGSvcError("vbsvcAutomounterMountIt: Failed to mount '%s' on '%s': %s (%d,%d)\n",
+ pEntry->pszName, pEntry->pszActualMountPoint, strerror(errno), rc, errno);
+ return VERR_WRITE_ERROR;
+
+# elif defined(RT_OS_SOLARIS)
+ /*
+ * Solaris is rather simple compared to linux.
+ *
+ * The ',VBoxService=auto' option (g_szTag) is ignored by the kernel but helps
+ * us identify our own mounts on restart. See vbsvcAutomounterPopulateTable().
+ *
+ * Note! Must pass MAX_MNTOPT_STR rather than cchOpts to mount, as it may fail
+ * with EOVERFLOW in vfs_buildoptionstr() during domount() otherwise.
+ */
+ char szOpts[MAX_MNTOPT_STR] = { '\0', };
+ ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts),
+ "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000,tag=%s", gidMount, g_szTag);
+ if (cchOpts <= 0)
+ {
+ VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd\n", cchOpts);
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, MS_OPTIONSTR, "vboxfs",
+ NULL /*dataptr*/, 0 /* datalen */, szOpts, MAX_MNTOPT_STR);
+ if (rc == 0)
+ {
+ VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
+ pEntry->pszName, pEntry->pszActualMountPoint);
+ return VINF_SUCCESS;
+ }
+
+ rc = errno;
+ VGSvcError("vbsvcAutomounterMountIt: mount failed for '%s' on '%s' (szOpts=%s): %s (%d)\n",
+ pEntry->pszName, pEntry->pszActualMountPoint, szOpts, strerror(rc), rc);
+ return VERR_OPEN_FAILED;
+
+# else
+# error "PORTME!"
+# endif
+#endif
+}
+
+
+/**
+ * Attempts to mount the given shared folder, adding it to the mount table on
+ * success.
+ *
+ * @returns iTable + 1 on success, iTable on failure.
+ * @param pTable The mount table.
+ * @param iTable The mount table index at which to add the mount.
+ * @param pszName The name of the shared folder mapping.
+ * @param pszMntPt The mount point (hint) specified by the host.
+ * @param fFlags The shared folder flags, SHFL_MIF_XXX.
+ * @param idRoot The root ID.
+ * @param uRootIdVersion The root ID version.
+ * @param fAutoMntPt Whether to try automatically assign a mount point if
+ * pszMntPt doesn't work out. This is set in pass \#3.
+ */
+static uint32_t vbsvcAutomounterMountNewEntry(PVBSVCAUTOMOUNTERTABLE pTable, uint32_t iTable,
+ const char *pszName, const char *pszMntPt, uint64_t fFlags,
+ uint32_t idRoot, uint32_t uRootIdVersion, bool fAutoMntPt)
+{
+ VGSvcVerbose(3, "vbsvcAutomounterMountNewEntry: #%u: '%s' at '%s'%s\n",
+ iTable, pszName, pszMntPt, fAutoMntPt ? " auto-assign" : "");
+
+ /*
+ * First we need to figure out the actual mount point.
+ */
+ char szActualMountPoint[RTPATH_MAX];
+
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ /*
+ * Drive letter based. We only care about the first two characters
+ * and ignore the rest (see further down).
+ */
+ char chNextLetter = 'Z';
+ if (RT_C_IS_ALPHA(pszMntPt[0]) && pszMntPt[1] == ':')
+ szActualMountPoint[0] = RT_C_TO_UPPER(pszMntPt[0]);
+ else if (!fAutoMntPt)
+ return iTable;
+ else
+ szActualMountPoint[0] = chNextLetter--;
+ szActualMountPoint[1] = ':';
+ szActualMountPoint[2] = '\0';
+
+ int rc;
+ for (;;)
+ {
+ rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
+ if (rc == VWRN_NOT_FOUND)
+ break;
+
+ /* next */
+ if (chNextLetter == 'A' || !fAutoMntPt)
+ return iTable;
+ szActualMountPoint[0] = chNextLetter--;
+ }
+
+#else
+ /*
+ * Path based #1: Host specified mount point.
+ */
+
+ /* Skip DOS drive letter if there is a UNIX mount point path following it: */
+ if ( pszMntPt[0] != '/'
+ && pszMntPt[0] != '\0'
+ && pszMntPt[1] == ':'
+ && pszMntPt[2] == '/')
+ pszMntPt += 2;
+
+ /* Try specified mount point if it starts with a UNIX slash: */
+ int rc = VERR_ACCESS_DENIED;
+ if (*pszMntPt == '/')
+ {
+ rc = RTPathAbs(pszMntPt, szActualMountPoint, sizeof(szActualMountPoint));
+ if (RT_SUCCESS(rc))
+ {
+ static const char * const s_apszBlacklist[] =
+ { "/", "/dev", "/bin", "/sbin", "/lib", "/etc", "/var", "/tmp", "/usr", "/usr/bin", "/usr/sbin", "/usr/lib" };
+ for (size_t i = 0; i < RT_ELEMENTS(s_apszBlacklist); i++)
+ if (strcmp(szActualMountPoint, s_apszBlacklist[i]) == 0)
+ {
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
+ }
+ }
+ if (rc != VWRN_NOT_FOUND)
+ {
+ if (!fAutoMntPt)
+ return iTable;
+
+ /*
+ * Path based #2: Mount dir + prefix + share.
+ */
+ rc = vbsvcAutomounterQueryMountDirAndPrefix(szActualMountPoint, sizeof(szActualMountPoint));
+ if (RT_SUCCESS(rc))
+ {
+ /* Append a sanitized share name: */
+ size_t const offShare = strlen(szActualMountPoint);
+ size_t offDst = offShare;
+ size_t offSrc = 0;
+ for (;;)
+ {
+ char ch = pszName[offSrc++];
+ if (ch == ' ' || ch == '/' || ch == '\\' || ch == ':' || ch == '$')
+ ch = '_';
+ else if (!ch)
+ break;
+ else if (ch < 0x20 || ch == 0x7f)
+ continue;
+ if (offDst < sizeof(szActualMountPoint) - 1)
+ szActualMountPoint[offDst++] = ch;
+ }
+ szActualMountPoint[offDst] = '\0';
+ if (offDst > offShare)
+ {
+ rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
+ if (rc != VWRN_NOT_FOUND)
+ {
+ /*
+ * Path based #3: Mount dir + prefix + share + _ + number.
+ */
+ if (offDst + 2 >= sizeof(szActualMountPoint))
+ return iTable;
+
+ szActualMountPoint[offDst++] = '_';
+ for (uint32_t iTry = 1; iTry < 10 && rc != VWRN_NOT_FOUND; iTry++)
+ {
+ szActualMountPoint[offDst] = '0' + iTry;
+ szActualMountPoint[offDst + 1] = '\0';
+ rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
+ }
+ if (rc != VWRN_NOT_FOUND)
+ return iTable;
+ }
+ }
+ else
+ VGSvcError("vbsvcAutomounterMountNewEntry: Bad share name: %.*Rhxs", strlen(pszName), pszName);
+ }
+ else
+ VGSvcError("vbsvcAutomounterMountNewEntry: Failed to construct basic auto mount point for '%s'", pszName);
+ }
+#endif
+
+ /*
+ * Prepare a table entry and ensure space in the table..
+ */
+ if (pTable->cEntries + 1 > pTable->cAllocated)
+ {
+ void *pvEntries = RTMemRealloc(pTable->papEntries, sizeof(pTable->papEntries[0]) * (pTable->cAllocated + 8));
+ if (!pvEntries)
+ {
+ VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for growing table (size %u)\n", pTable->cAllocated);
+ return iTable;
+ }
+ pTable->cAllocated += 8;
+ pTable->papEntries = (PVBSVCAUTOMOUNTERENTRY *)pvEntries;
+ }
+
+ PVBSVCAUTOMOUNTERENTRY pEntry = (PVBSVCAUTOMOUNTERENTRY)RTMemAlloc(sizeof(*pEntry));
+ if (pEntry)
+ {
+ pEntry->idRoot = idRoot;
+ pEntry->uRootIdVersion = uRootIdVersion;
+ pEntry->fFlags = fFlags;
+ pEntry->pszName = RTStrDup(pszName);
+ pEntry->pszMountPoint = RTStrDup(pszMntPt);
+ pEntry->pszActualMountPoint = RTStrDup(szActualMountPoint);
+ if (pEntry->pszName && pEntry->pszMountPoint && pEntry->pszActualMountPoint)
+ {
+ /*
+ * Now try mount it.
+ */
+ rc = vbsvcAutomounterMountIt(pEntry);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cToMove = pTable->cEntries - iTable;
+ if (cToMove > 0)
+ memmove(&pTable->papEntries[iTable + 1], &pTable->papEntries[iTable], cToMove * sizeof(pTable->papEntries[0]));
+ pTable->papEntries[iTable] = pEntry;
+ pTable->cEntries++;
+ return iTable + 1;
+ }
+ }
+ else
+ VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for table entry!\n");
+ RTMemFree(pEntry->pszActualMountPoint);
+ RTMemFree(pEntry->pszMountPoint);
+ RTMemFree(pEntry->pszName);
+ RTMemFree(pEntry);
+ }
+ else
+ VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for table entry!\n");
+ return iTable;
+}
+
+
+
+/**
+ * Does the actual unmounting.
+ *
+ * @returns Exactly one of the following IPRT status codes;
+ * @retval VINF_SUCCESS if successfully umounted or nothing was mounted there.
+ * @retval VERR_TRY_AGAIN if the shared folder is busy.
+ * @retval VERR_RESOURCE_BUSY if a different shared folder is mounted there.
+ * @retval VERR_ACCESS_DENIED if a non-shared folder file system is mounted
+ * there.
+ *
+ * @param pszMountPoint The mount point.
+ * @param pszName The shared folder (mapping) name.
+ */
+static int vbsvcAutomounterUnmount(const char *pszMountPoint, const char *pszName)
+{
+ /*
+ * Retry for 5 seconds in a hope that busy mounts will quiet down.
+ */
+ for (unsigned iTry = 0; ; iTry++)
+ {
+ /*
+ * Check what's mounted there before we start umounting stuff.
+ */
+ int rc = vbsvcAutomounterQueryMountPoint(pszMountPoint, pszName);
+ if (rc == VINF_SUCCESS)
+ { /* pszName is mounted there */ }
+ else if (rc == VWRN_NOT_FOUND) /* nothing mounted there */
+ return VINF_SUCCESS;
+ else
+ {
+ Assert(rc == VERR_RESOURCE_BUSY || rc == VERR_ACCESS_DENIED);
+ return VERR_RESOURCE_BUSY;
+ }
+
+ /*
+ * Do host specific unmounting.
+ */
+#ifdef RT_OS_WINDOWS
+ Assert(RT_C_IS_UPPER(pszMountPoint[0]) && pszMountPoint[1] == ':' && pszMountPoint[2] == '\0');
+ RTUTF16 const wszDrive[4] = { (RTUTF16)pszMountPoint[0], ':', '\0', '\0' };
+ DWORD dwErr = WNetCancelConnection2W(wszDrive, 0 /*dwFlags*/, FALSE /*fForce*/);
+ if (dwErr == NO_ERROR)
+ return VINF_SUCCESS;
+ VGSvcVerbose(2, "vbsvcAutomounterUnmount: WNetCancelConnection2W returns %u for '%s' ('%s')\n", dwErr, pszMountPoint, pszName);
+ if (dwErr == ERROR_NOT_CONNECTED)
+ return VINF_SUCCESS;
+
+#elif defined(RT_OS_OS2)
+ APIRET rcOs2 = DosFSAttach(pszMountPoint, "VBOXSF", NULL, 0, FS_DETACH);
+ if (rcOs2 == NO_ERROR)
+ return VINF_SUCCESS;
+ VGSvcVerbose(2, "vbsvcAutomounterUnmount: DosFSAttach failed on '%s' ('%s'): %u\n", pszMountPoint, pszName, rcOs2);
+ if (rcOs2 == ERROR_INVALID_FSD_NAME)
+ return VERR_ACCESS_DENIED;
+ if ( rcOs2 == ERROR_INVALID_DRIVE
+ || rcOs2 == ERROR_INVALID_PATH)
+ return VERR_TRY_AGAIN;
+
+#else
+ int rc2 = umount(pszMountPoint);
+ if (rc2 == 0)
+ {
+ /* Remove the mount directory if not directly under the root dir. */
+ RTPATHPARSED Parsed;
+ RT_ZERO(Parsed);
+ RTPathParse(pszMountPoint, &Parsed, sizeof(Parsed), RTPATH_STR_F_STYLE_HOST);
+ if (Parsed.cComps >= 3)
+ RTDirRemove(pszMountPoint);
+
+ return VINF_SUCCESS;
+ }
+ rc2 = errno;
+ VGSvcVerbose(2, "vbsvcAutomounterUnmount: umount failed on '%s' ('%s'): %d\n", pszMountPoint, pszName, rc2);
+ if (rc2 != EBUSY && rc2 != EAGAIN)
+ return VERR_ACCESS_DENIED;
+#endif
+
+ /*
+ * Check what's mounted there before we start delaying.
+ */
+ RTThreadSleep(8); /* fudge */
+ rc = vbsvcAutomounterQueryMountPoint(pszMountPoint, pszName);
+ if (rc == VINF_SUCCESS)
+ { /* pszName is mounted there */ }
+ else if (rc == VWRN_NOT_FOUND) /* nothing mounted there */
+ return VINF_SUCCESS;
+ else
+ {
+ Assert(rc == VERR_RESOURCE_BUSY || rc == VERR_ACCESS_DENIED);
+ return VERR_RESOURCE_BUSY;
+ }
+
+ if (iTry >= 5)
+ return VERR_TRY_AGAIN;
+ RTThreadSleep(1000);
+ }
+}
+
+
+/**
+ * Unmounts a mount table entry and evicts it from the table if successful.
+ *
+ * @returns The next iTable (same value on success, +1 on failure).
+ * @param pTable The mount table.
+ * @param iTable The table entry.
+ * @param pszReason Why we're here.
+ */
+static uint32_t vbsvcAutomounterUnmountEntry(PVBSVCAUTOMOUNTERTABLE pTable, uint32_t iTable, const char *pszReason)
+{
+ Assert(iTable < pTable->cEntries);
+ PVBSVCAUTOMOUNTERENTRY pEntry = pTable->papEntries[iTable];
+ VGSvcVerbose(2, "vbsvcAutomounterUnmountEntry: #%u: '%s' at '%s' (reason: %s)\n",
+ iTable, pEntry->pszName, pEntry->pszActualMountPoint, pszReason);
+
+ /*
+ * Do we need to umount the entry? Return if unmount fails and we .
+ */
+ if (pEntry->pszActualMountPoint)
+ {
+ int rc = vbsvcAutomounterUnmount(pEntry->pszActualMountPoint, pEntry->pszName);
+ if (rc == VERR_TRY_AGAIN)
+ {
+ VGSvcVerbose(1, "vbsvcAutomounterUnmountEntry: Keeping '%s' -> '%s' (VERR_TRY_AGAIN)\n",
+ pEntry->pszActualMountPoint, pEntry->pszName);
+ return iTable + 1;
+ }
+ }
+
+ /*
+ * Remove the entry by shifting up the ones after it.
+ */
+ pTable->cEntries -= 1;
+ uint32_t cAfter = pTable->cEntries - iTable;
+ if (cAfter)
+ memmove(&pTable->papEntries[iTable], &pTable->papEntries[iTable + 1], cAfter * sizeof(pTable->papEntries[0]));
+ pTable->papEntries[pTable->cEntries] = NULL;
+
+ RTStrFree(pEntry->pszActualMountPoint);
+ pEntry->pszActualMountPoint = NULL;
+ RTStrFree(pEntry->pszMountPoint);
+ pEntry->pszMountPoint = NULL;
+ RTStrFree(pEntry->pszName);
+ pEntry->pszName = NULL;
+ RTMemFree(pEntry);
+
+ return iTable;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSORTCMP, For sorting the mappings by ID. }
+ */
+static DECLCALLBACK(int) vbsvcSharedFolderMappingCompare(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF_PV(pvUser);
+ PVBGLR3SHAREDFOLDERMAPPING pMapping1 = (PVBGLR3SHAREDFOLDERMAPPING)pvElement1;
+ PVBGLR3SHAREDFOLDERMAPPING pMapping2 = (PVBGLR3SHAREDFOLDERMAPPING)pvElement2;
+ return pMapping1->u32Root < pMapping2->u32Root ? -1 : pMapping1->u32Root != pMapping2->u32Root ? 1 : 0;
+}
+
+
+/**
+ * Refreshes the mount table.
+ *
+ * @returns true if we've processed the current config, false if we failed to
+ * query the mappings.
+ * @param pTable The mount table to refresh.
+ */
+static bool vbsvcAutomounterRefreshTable(PVBSVCAUTOMOUNTERTABLE pTable)
+{
+ /*
+ * Query the root IDs of all auto-mountable shared folder mappings.
+ */
+ uint32_t cMappings = 0;
+ PVBGLR3SHAREDFOLDERMAPPING paMappings = NULL;
+ int rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /*fAutoMountOnly*/, &paMappings, &cMappings);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("vbsvcAutomounterRefreshTable: VbglR3SharedFolderGetMappings failed: %Rrc\n", rc);
+ return false;
+ }
+
+ /*
+ * Walk the table and the mappings in parallel, so we have to make sure
+ * they are both sorted by root ID.
+ */
+ if (cMappings > 1)
+ RTSortShell(paMappings, cMappings, sizeof(paMappings[0]), vbsvcSharedFolderMappingCompare, NULL);
+
+ /*
+ * Pass #1: Do all the umounting.
+ *
+ * By doing the umount pass separately from the mount pass, we can
+ * better handle changing involving the same mount points (switching
+ * mount points between two shares, new share on same mount point but
+ * with lower root ID, ++).
+ */
+ uint32_t iTable = 0;
+ for (uint32_t iSrc = 0; iSrc < cMappings; iSrc++)
+ {
+ /*
+ * Unmount table entries up to idRootSrc.
+ */
+ uint32_t const idRootSrc = paMappings[iSrc].u32Root;
+ while ( iTable < pTable->cEntries
+ && pTable->papEntries[iTable]->idRoot < idRootSrc)
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "dropped");
+
+ /*
+ * If the paMappings entry and the mount table entry has the same
+ * root ID, umount if anything has changed or if we cannot query
+ * the mapping data.
+ */
+ if (iTable < pTable->cEntries)
+ {
+ PVBSVCAUTOMOUNTERENTRY pEntry = pTable->papEntries[iTable];
+ if (pEntry->idRoot == idRootSrc)
+ {
+ uint32_t uRootIdVer = UINT32_MAX;
+ uint64_t fFlags = 0;
+ char *pszName = NULL;
+ char *pszMntPt = NULL;
+ rc = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF,
+ &pszName, &pszMntPt, &fFlags, &uRootIdVer);
+ if (RT_FAILURE(rc))
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "VbglR3SharedFolderQueryFolderInfo failed");
+ else if (pEntry->uRootIdVersion != uRootIdVer)
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "root ID version changed");
+ else if (RTPathCompare(pEntry->pszMountPoint, pszMntPt) != 0)
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "mount point changed");
+ else if (RTStrICmp(pEntry->pszName, pszName) != 0)
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "name changed");
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterRefreshTable: Unchanged: %s -> %s\n", pEntry->pszMountPoint, pEntry->pszName);
+ iTable++;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pszName);
+ RTStrFree(pszMntPt);
+ }
+ }
+ }
+ }
+
+ while (iTable < pTable->cEntries)
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "dropped (tail)");
+
+ VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u entries in mount table after pass #1.\n", pTable->cEntries);
+
+ /*
+ * Pass #2: Try mount new folders that has mount points assigned.
+ * Pass #3: Try mount new folders not mounted in pass #2.
+ */
+ for (uint32_t iPass = 2; iPass <= 3; iPass++)
+ {
+ iTable = 0;
+ for (uint32_t iSrc = 0; iSrc < cMappings; iSrc++)
+ {
+ uint32_t const idRootSrc = paMappings[iSrc].u32Root;
+
+ /*
+ * Skip tabel entries we couldn't umount in pass #1.
+ */
+ while ( iTable < pTable->cEntries
+ && pTable->papEntries[iTable]->idRoot < idRootSrc)
+ {
+ VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: Skipping idRoot=%u %s\n",
+ iPass, iSrc, iTable, pTable->papEntries[iTable]->idRoot, pTable->papEntries[iTable]->pszName);
+ iTable++;
+ }
+
+ /*
+ * New share?
+ */
+ if ( iTable >= pTable->cEntries
+ || pTable->papEntries[iTable]->idRoot != idRootSrc)
+ {
+ uint32_t uRootIdVer = UINT32_MAX;
+ uint64_t fFlags = 0;
+ char *pszName = NULL;
+ char *pszMntPt = NULL;
+ rc = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF,
+ &pszName, &pszMntPt, &fFlags, &uRootIdVer);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: Mounting idRoot=%u/%u %s\n", iPass, iSrc, iTable,
+ idRootSrc, iTable >= pTable->cEntries ? UINT32_MAX : pTable->papEntries[iTable]->idRoot, pszName);
+ iTable = vbsvcAutomounterMountNewEntry(pTable, iTable, pszName, pszMntPt, fFlags,
+ idRootSrc, uRootIdVer, iPass == 3);
+
+ RTStrFree(pszName);
+ RTStrFree(pszMntPt);
+ }
+ else
+ VGSvcVerbose(1, "vbsvcAutomounterRefreshTable: VbglR3SharedFolderQueryFolderInfo failed: %Rrc\n", rc);
+ }
+ else
+ VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: idRootSrc=%u vs idRoot=%u %s\n", iPass, iSrc,
+ iTable, idRootSrc, pTable->papEntries[iTable]->idRoot, pTable->papEntries[iTable]->pszName);
+ }
+ }
+
+ VbglR3SharedFolderFreeMappings(paMappings);
+ return true;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbsvcAutomounterWorker(bool volatile *pfShutdown)
+{
+ /*
+ * Tell the control thread that it can continue spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /* Divert old hosts to original auto-mount code. */
+ if (!g_fHostSupportsWaitAndInfoQuery)
+ return vbsvcAutoMountWorkerOld(pfShutdown);
+
+ /*
+ * Initialize the state in case we're restarted...
+ */
+ VBSVCAUTOMOUNTERTABLE MountTable = { 0, 0, NULL };
+ int rc = vbsvcAutomounterPopulateTable(&MountTable);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("vbsvcAutomounterWorker: vbsvcAutomounterPopulateTable failed (%Rrc), quitting!\n", rc);
+ return rc;
+ }
+
+ /*
+ * Work loop.
+ */
+ uint32_t uConfigVer = UINT32_MAX;
+ uint32_t uNewVersion = 0;
+ bool fForceRefresh = true;
+ while (!*pfShutdown)
+ {
+ /*
+ * Update the mounts.
+ */
+ if ( uConfigVer != uNewVersion
+ || fForceRefresh)
+ {
+ fForceRefresh = !vbsvcAutomounterRefreshTable(&MountTable);
+ uConfigVer = uNewVersion;
+ }
+
+ /*
+ * Wait for more to do.
+ */
+ if (!*pfShutdown)
+ {
+ uNewVersion = uConfigVer - 1;
+ VGSvcVerbose(2, "vbsvcAutomounterWorker: Waiting with uConfigVer=%u\n", uConfigVer);
+ rc = VbglR3SharedFolderWaitForMappingsChanges(g_idClientSharedFolders, uConfigVer, &uNewVersion);
+ VGSvcVerbose(2, "vbsvcAutomounterWorker: Woke up with uNewVersion=%u and rc=%Rrc\n", uNewVersion, rc);
+
+ /* Delay a little before doing a table refresh so the GUI can finish
+ all its updates. Delay a little longer on non-shutdown failure to
+ avoid eating too many CPU cycles if something goes wrong here... */
+ if (!*pfShutdown)
+ RTSemEventMultiWait(g_hAutoMountEvent, RT_SUCCESS(rc) ? 256 : 1000);
+ }
+ }
+
+ /*
+ * Destroy the mount table.
+ */
+ while (MountTable.cEntries-- > 0)
+ RTMemFree(MountTable.papEntries[MountTable.cEntries]);
+ MountTable.papEntries = NULL;
+
+ VGSvcVerbose(3, "vbsvcAutomounterWorker: Finished\n");
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbsvcAutomounterStop(void)
+{
+ RTSemEventMultiSignal(g_hAutoMountEvent);
+ if (g_fHostSupportsWaitAndInfoQuery)
+ VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vbsvcAutomounterTerm(void)
+{
+ VGSvcVerbose(3, "vbsvcAutoMountTerm\n");
+
+ if (g_fHostSupportsWaitAndInfoQuery)
+ VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders);
+
+ VbglR3SharedFolderDisconnect(g_idClientSharedFolders);
+ g_idClientSharedFolders = 0;
+
+ if (g_hAutoMountEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_hAutoMountEvent);
+ g_hAutoMountEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'automount' service description.
+ */
+VBOXSERVICE g_AutoMount =
+{
+ /* pszName. */
+ "automount",
+ /* pszDescription. */
+ "Automounter for Shared Folders",
+ /* pszUsage. */
+ NULL,
+ /* pszOptions. */
+ NULL,
+ /* methods */
+ VGSvcDefaultPreInit,
+ VGSvcDefaultOption,
+ vbsvcAutomounterInit,
+ vbsvcAutomounterWorker,
+ vbsvcAutomounterStop,
+ vbsvcAutomounterTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp
new file mode 100644
index 00000000..a1229257
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp
@@ -0,0 +1,457 @@
+/* $Id: VBoxServiceBalloon.cpp $ */
+/** @file
+ * VBoxService - Memory Ballooning.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/** @page pg_vgsvc_memballoon VBoxService - Memory Ballooning
+ *
+ * The Memory Ballooning subservice works with VBoxGuest, PGM and GMM to
+ * dynamically reallocate memory between VMs.
+ *
+ * Memory ballooning is typically used to deal with overcomitting memory on the
+ * host. It allowes you to borrow memory from one or more VMs and make it
+ * available to others. In theory it could also be used to make memory
+ * available to the host system, however memory fragmentation typically makes
+ * that difficult.
+ *
+ * The memory ballooning subservices talks to PGM, GMM and Main via the VMMDev.
+ * It polls for change requests at an interval and executes them when they
+ * arrive. There are two ways we implement the actual ballooning, either
+ * VBoxGuest allocates kernel memory and donates it to the host, or this service
+ * allocates process memory which VBoxGuest then locks down and donates to the
+ * host. While we prefer the former method it is not practicable on all OS and
+ * we have to use the latter.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/system.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <VBox/err.h>
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+
+#ifdef RT_OS_LINUX
+# include <iprt/param.h>
+# include <sys/mman.h>
+# ifndef MADV_DONTFORK
+# define MADV_DONTFORK 10
+# endif
+#endif
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The balloon size. */
+static uint32_t g_cMemBalloonChunks = 0;
+
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_MemBalloonEvent = NIL_RTSEMEVENTMULTI;
+
+/** The array holding the R3 pointers of the balloon. */
+static void **g_pavBalloon = NULL;
+
+#ifdef RT_OS_LINUX
+/** True = madvise(MADV_DONTFORK) works, false otherwise. */
+static bool g_fSysMadviseWorks;
+#endif
+
+
+/**
+ * Check whether madvise() works.
+ */
+static void vgsvcBalloonInitMadvise(void)
+{
+#ifdef RT_OS_LINUX
+ void *pv = (void*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (pv != MAP_FAILED)
+ {
+ g_fSysMadviseWorks = madvise(pv, PAGE_SIZE, MADV_DONTFORK) == 0;
+ munmap(pv, PAGE_SIZE);
+ }
+#endif
+}
+
+
+/**
+ * Allocate a chunk of the balloon. Fulfil the prerequisite that we can lock this memory
+ * and protect it against fork() in R0. See also suplibOsPageAlloc().
+ */
+static void *VGSvcBalloonAllocChunk(void)
+{
+ size_t cb = VMMDEV_MEMORY_BALLOON_CHUNK_SIZE;
+ char *pu8;
+
+#ifdef RT_OS_LINUX
+ if (!g_fSysMadviseWorks)
+ cb += 2 * PAGE_SIZE;
+
+ pu8 = (char*)mmap(NULL, cb, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (pu8 == MAP_FAILED)
+ return NULL;
+
+ if (g_fSysMadviseWorks)
+ {
+ /*
+ * It is not fatal if we fail here but a forked child (e.g. the ALSA sound server)
+ * could crash. Linux < 2.6.16 does not implement madvise(MADV_DONTFORK) but the
+ * kernel seems to split bigger VMAs and that is all that we want -- later we set the
+ * VM_DONTCOPY attribute in supdrvOSLockMemOne().
+ */
+ madvise(pu8, cb, MADV_DONTFORK);
+ }
+ else
+ {
+ /*
+ * madvise(MADV_DONTFORK) is not available (most probably Linux 2.4). Enclose any
+ * mmapped region by two unmapped pages to guarantee that there is exactly one VM
+ * area struct of the very same size as the mmap area.
+ */
+ RTMemProtect(pu8, PAGE_SIZE, RTMEM_PROT_NONE);
+ RTMemProtect(pu8 + cb - PAGE_SIZE, PAGE_SIZE, RTMEM_PROT_NONE);
+ pu8 += PAGE_SIZE;
+ }
+
+#else
+
+ pu8 = (char*)RTMemPageAlloc(cb);
+ if (!pu8)
+ return pu8;
+
+#endif
+
+ memset(pu8, 0, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE);
+ return pu8;
+}
+
+
+/**
+ * Free an allocated chunk undoing VGSvcBalloonAllocChunk().
+ */
+static void vgsvcBalloonFreeChunk(void *pv)
+{
+ char *pu8 = (char*)pv;
+ size_t cb = VMMDEV_MEMORY_BALLOON_CHUNK_SIZE;
+
+#ifdef RT_OS_LINUX
+
+ if (!g_fSysMadviseWorks)
+ {
+ cb += 2 * PAGE_SIZE;
+ pu8 -= PAGE_SIZE;
+ /* This is not really necessary */
+ RTMemProtect(pu8, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE);
+ RTMemProtect(pu8 + cb - PAGE_SIZE, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE);
+ }
+ munmap(pu8, cb);
+
+#else
+
+ RTMemPageFree(pu8, cb);
+
+#endif
+}
+
+
+/**
+ * Adapt the R0 memory balloon by granting/reclaiming 1MB chunks to/from R0.
+ *
+ * returns IPRT status code.
+ * @param cNewChunks The new number of 1MB chunks in the balloon.
+ */
+static int vgsvcBalloonSetUser(uint32_t cNewChunks)
+{
+ if (cNewChunks == g_cMemBalloonChunks)
+ return VINF_SUCCESS;
+
+ VGSvcVerbose(3, "vgsvcBalloonSetUser: cNewChunks=%u g_cMemBalloonChunks=%u\n", cNewChunks, g_cMemBalloonChunks);
+ int rc = VINF_SUCCESS;
+ if (cNewChunks > g_cMemBalloonChunks)
+ {
+ /* inflate */
+ g_pavBalloon = (void**)RTMemRealloc(g_pavBalloon, cNewChunks * sizeof(void*));
+ uint32_t i;
+ for (i = g_cMemBalloonChunks; i < cNewChunks; i++)
+ {
+ void *pv = VGSvcBalloonAllocChunk();
+ if (!pv)
+ break;
+ rc = VbglR3MemBalloonChange(pv, /* inflate=*/ true);
+ if (RT_SUCCESS(rc))
+ {
+ g_pavBalloon[i] = pv;
+#ifndef RT_OS_SOLARIS
+ /*
+ * Protect against access by dangling pointers (ignore errors as it may fail).
+ * On Solaris it corrupts the address space leaving the process unkillable. This
+ * could perhaps be related to what the underlying segment driver does; currently
+ * just disable it.
+ */
+ RTMemProtect(pv, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE, RTMEM_PROT_NONE);
+#endif
+ g_cMemBalloonChunks++;
+ }
+ else
+ {
+ vgsvcBalloonFreeChunk(pv);
+ break;
+ }
+ }
+ VGSvcVerbose(3, "vgsvcBalloonSetUser: inflation complete. chunks=%u rc=%d\n", i, rc);
+ }
+ else
+ {
+ /* deflate */
+ uint32_t i;
+ for (i = g_cMemBalloonChunks; i-- > cNewChunks;)
+ {
+ void *pv = g_pavBalloon[i];
+ rc = VbglR3MemBalloonChange(pv, /* inflate=*/ false);
+ if (RT_SUCCESS(rc))
+ {
+#ifndef RT_OS_SOLARIS
+ /* unprotect */
+ RTMemProtect(pv, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE);
+#endif
+ vgsvcBalloonFreeChunk(pv);
+ g_pavBalloon[i] = NULL;
+ g_cMemBalloonChunks--;
+ }
+ else
+ break;
+ VGSvcVerbose(3, "vgsvcBalloonSetUser: deflation complete. chunks=%u rc=%d\n", i, rc);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vgsvcBalloonInit(void)
+{
+ VGSvcVerbose(3, "vgsvcBalloonInit\n");
+
+ int rc = RTSemEventMultiCreate(&g_MemBalloonEvent);
+ AssertRCReturn(rc, rc);
+
+ vgsvcBalloonInitMadvise();
+
+ g_cMemBalloonChunks = 0;
+ uint32_t cNewChunks = 0;
+ bool fHandleInR3;
+
+ /* Check balloon size */
+ rc = VbglR3MemBalloonRefresh(&cNewChunks, &fHandleInR3);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "MemBalloon: New balloon size %d MB (%s memory)\n", cNewChunks, fHandleInR3 ? "R3" : "R0");
+ if (fHandleInR3)
+ rc = vgsvcBalloonSetUser(cNewChunks);
+ else
+ g_cMemBalloonChunks = cNewChunks;
+ }
+ if (RT_FAILURE(rc))
+ {
+ /* If the service was not found, we disable this service without
+ causing VBoxService to fail. */
+ if ( rc == VERR_NOT_IMPLEMENTED
+#ifdef RT_OS_WINDOWS /** @todo r=bird: Windows kernel driver should return VERR_NOT_IMPLEMENTED,
+ * VERR_INVALID_PARAMETER has too many other uses. */
+ || rc == VERR_INVALID_PARAMETER
+#endif
+ )
+ {
+ VGSvcVerbose(0, "MemBalloon: Memory ballooning support is not available\n");
+ rc = VERR_SERVICE_DISABLED;
+ }
+ else
+ {
+ VGSvcVerbose(3, "MemBalloon: VbglR3MemBalloonRefresh failed with %Rrc\n", rc);
+ rc = VERR_SERVICE_DISABLED; /** @todo Playing safe for now, figure out the exact status codes here. */
+ }
+ RTSemEventMultiDestroy(g_MemBalloonEvent);
+ g_MemBalloonEvent = NIL_RTSEMEVENTMULTI;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Query the size of the memory balloon, given as a page count.
+ *
+ * @returns Number of pages.
+ * @param cbPage The page size.
+ */
+uint32_t VGSvcBalloonQueryPages(uint32_t cbPage)
+{
+ Assert(cbPage > 0);
+ return g_cMemBalloonChunks * (VMMDEV_MEMORY_BALLOON_CHUNK_SIZE / cbPage);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vgsvcBalloonWorker(bool volatile *pfShutdown)
+{
+ /* Start monitoring of the stat event change event. */
+ int rc = VbglR3CtlFilterMask(VMMDEV_EVENT_BALLOON_CHANGE_REQUEST, 0);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3CtlFilterMask failed with %Rrc\n", rc);
+ return rc;
+ }
+
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Now enter the loop retrieving runtime data continuously.
+ */
+ for (;;)
+ {
+ uint32_t fEvents = 0;
+
+ /* Check if an update interval change is pending. */
+ rc = VbglR3WaitEvent(VMMDEV_EVENT_BALLOON_CHANGE_REQUEST, 0 /* no wait */, &fEvents);
+ if ( RT_SUCCESS(rc)
+ && (fEvents & VMMDEV_EVENT_BALLOON_CHANGE_REQUEST))
+ {
+ uint32_t cNewChunks;
+ bool fHandleInR3;
+ rc = VbglR3MemBalloonRefresh(&cNewChunks, &fHandleInR3);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "vgsvcBalloonInit: new balloon size %d MB (%s memory)\n", cNewChunks, fHandleInR3 ? "R3" : "R0");
+ if (fHandleInR3)
+ {
+ rc = vgsvcBalloonSetUser(cNewChunks);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(3, "vgsvcBalloonInit: failed to set balloon size %d MB (%s memory)\n",
+ cNewChunks, fHandleInR3 ? "R3" : "R0");
+ }
+ else
+ VGSvcVerbose(3, "vgsvcBalloonInit: successfully set requested balloon size %d.\n", cNewChunks);
+ }
+ else
+ g_cMemBalloonChunks = cNewChunks;
+ }
+ else
+ VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3MemBalloonRefresh failed with %Rrc\n", rc);
+ }
+
+ /*
+ * Block for a while.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ int rc2 = RTSemEventMultiWait(g_MemBalloonEvent, 5000);
+ if (*pfShutdown)
+ break;
+ if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
+ {
+ VGSvcError("vgsvcBalloonInit: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
+ rc = rc2;
+ break;
+ }
+ }
+
+ /* Cancel monitoring of the memory balloon change event. */
+ rc = VbglR3CtlFilterMask(0, VMMDEV_EVENT_BALLOON_CHANGE_REQUEST);
+ if (RT_FAILURE(rc))
+ VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3CtlFilterMask failed with %Rrc\n", rc);
+
+ VGSvcVerbose(3, "vgsvcBalloonInit: finished mem balloon change request thread\n");
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcBalloonStop(void)
+{
+ RTSemEventMultiSignal(g_MemBalloonEvent);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcBalloonTerm(void)
+{
+ if (g_MemBalloonEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_MemBalloonEvent);
+ g_MemBalloonEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'memballoon' service description.
+ */
+VBOXSERVICE g_MemBalloon =
+{
+ /* pszName. */
+ "memballoon",
+ /* pszDescription. */
+ "Memory Ballooning",
+ /* pszUsage. */
+ NULL,
+ /* pszOptions. */
+ NULL,
+ /* methods */
+ VGSvcDefaultPreInit,
+ VGSvcDefaultOption,
+ vgsvcBalloonInit,
+ vgsvcBalloonWorker,
+ vgsvcBalloonStop,
+ vgsvcBalloonTerm
+};
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp
new file mode 100644
index 00000000..35b123e8
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp
@@ -0,0 +1,1140 @@
+/** $Id: VBoxServiceClipboard-os2.cpp $ */
+/** @file
+ * VBoxService - Guest Additions Clipboard Service, OS/2.
+ */
+
+/*
+ * Copyright (C) 2007-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
+ */
+
+
+/** @page pg_vgsvc_clipboard VBoxService - Clipboard (OS/2)
+ *
+ * The Clipboard subservice provides clipboard sharing for OS/2 guests only.
+ *
+ * This was the second subservice that was added to VBoxService. OS/2 is a
+ * single user system and we don't provide any VBoxTray or VBoxClient like
+ * processes. Because it's kind of simple system, it became natural to put the
+ * clipboard sharing here in VBoxService for OS/2.
+ *
+ * In addition to integrating with the native OS/2 PM clipboard formats, we also
+ * try provide the Odin32, a windows API layer for OS/2 (developed by Sander van
+ * Leeuwen and friends, later mainly InnoTek), with additional formats.
+ *
+ * Bitmaps are currently not supported, but that can easily be added should the
+ * need ever arrise.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define INCL_BASE
+#define INCL_PM
+#define INCL_ERRORS
+#include <os2.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <iprt/utf16.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include "VBoxServiceInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Header for Odin32 specific clipboard entries.
+ * (Used to get the correct size of the data.)
+ */
+typedef struct _Odin32ClipboardHeader
+{
+ /** magic number */
+ char achMagic[8];
+ /** Size of the following data.
+ * (The interpretation depends on the type.) */
+ unsigned cbData;
+ /** Odin32 format number. */
+ unsigned uFormat;
+} CLIPHEADER, *PCLIPHEADER;
+
+#define CLIPHEADER_MAGIC "Odin\1\0\1"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** The control thread (main) handle.
+ * Only used to avoid some queue creation trouble. */
+static RTTHREAD g_ThreadCtrl = NIL_RTTHREAD;
+/** The HAB of the control thread (main). */
+static HAB g_habCtrl = NULLHANDLE;
+/** The HMQ of the control thread (main). */
+static HMQ g_hmqCtrl = NULLHANDLE;
+
+/** The Listener thread handle. */
+static RTTHREAD g_ThreadListener = NIL_RTTHREAD;
+/** The HAB of the listener thread. */
+static HAB g_habListener = NULLHANDLE;
+/** The HMQ of the listener thread. */
+static HMQ g_hmqListener = NULLHANDLE;
+/** Indicator that gets set if the listener thread is successfully initialized. */
+static bool volatile g_fListenerOkay = false;
+
+/** The HAB of the worker thread. */
+static HAB g_habWorker = NULLHANDLE;
+/** The HMQ of the worker thread. */
+static HMQ g_hmqWorker = NULLHANDLE;
+/** The object window handle. */
+static HWND g_hwndWorker = NULLHANDLE;
+/** The timer id returned by WinStartTimer. */
+static ULONG g_idWorkerTimer = ~0UL;
+/** The state of the clipboard.
+ * @remark I'm trying out the 'k' prefix from the mac here, bear with me. */
+static enum
+{
+ /** The clipboard hasn't been initialized yet. */
+ kClipboardState_Uninitialized = 0,
+ /** WinSetClipbrdViewer call in progress, ignore WM_DRAWCLIPBOARD. */
+ kClipboardState_SettingViewer,
+ /** We're monitoring the clipboard as a viewer. */
+ kClipboardState_Viewer,
+ /** We're monitoring the clipboard using polling.
+ * This usually means something is wrong... */
+ kClipboardState_Polling,
+ /** We're destroying the clipboard content, ignore WM_DESTROYCLIPBOARD. */
+ kClipboardState_Destroying,
+ /** We're owning the clipboard (i.e. we have data on it). */
+ kClipboardState_Owner
+} g_enmState = kClipboardState_Uninitialized;
+/** Set if the clipboard was empty the last time we polled it. */
+static bool g_fEmptyClipboard = false;
+
+/** A clipboard format atom for the dummy clipboard data we insert
+ * watching for clipboard changes. If this format is found on the
+ * clipboard, the empty clipboard function has not been called
+ * since we last polled it. */
+static ATOM g_atomNothingChanged = 0;
+
+/** The clipboard connection client ID. */
+static uint32_t g_u32ClientId;
+/** Odin32 CF_UNICODETEXT. See user32.cpp. */
+static ATOM g_atomOdin32UnicodeText = 0;
+/** Odin32 CF_UNICODETEXT. See user32.cpp. */
+#define SZFMT_ODIN32_UNICODETEXT (PCSZ)"Odin32 UnicodeText"
+
+
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnPreInit}
+ */
+static DECLCALLBACK(int) vgsvcClipboardOs2PreInit(void)
+{
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnOption}
+ */
+static DECLCALLBACK(int) vgsvcClipboardOs2Option(const char **ppszShort, int argc, char **argv, int *pi)
+{
+ NOREF(ppszShort);
+ NOREF(argc);
+ NOREF(argv);
+ NOREF(pi);
+
+ return -1;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vgsvcClipboardOs2Init(void)
+{
+ int rc = VERR_GENERAL_FAILURE;
+ g_ThreadCtrl = RTThreadSelf();
+
+ /*
+ * Make PM happy.
+ */
+ PPIB pPib;
+ PTIB pTib;
+ DosGetInfoBlocks(&pTib, &pPib);
+ pPib->pib_ultype = 3; /* PM session type */
+
+ /*
+ * Since we have to send shutdown messages and such from the
+ * service controller (main) thread, create a HAB and HMQ for it.
+ */
+ g_habCtrl = WinInitialize(0);
+ if (g_habCtrl == NULLHANDLE)
+ {
+ VGSvcError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE));
+ return VERR_GENERAL_FAILURE;
+ }
+ g_hmqCtrl = WinCreateMsgQueue(g_habCtrl, 0);
+ if (g_hmqCtrl != NULLHANDLE)
+ {
+ WinCancelShutdown(g_hmqCtrl, TRUE); /* We don't care about shutdown */
+
+ /*
+ * Create the 'nothing-changed' format.
+ */
+ g_atomNothingChanged = WinAddAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service");
+ LONG lLastError = WinGetLastError(g_habCtrl);
+ if (g_atomNothingChanged == 0)
+ g_atomNothingChanged = WinFindAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service");
+ if (g_atomNothingChanged)
+ {
+ /*
+ * Connect to the clipboard service.
+ */
+ VGSvcVerbose(4, "clipboard: connecting\n");
+ rc = VbglR3ClipboardConnect(&g_u32ClientId);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create any extra clipboard type atoms, like the odin unicode text.
+ */
+ g_atomOdin32UnicodeText = WinAddAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT);
+ lLastError = WinGetLastError(g_habCtrl);
+ if (g_atomOdin32UnicodeText == 0)
+ g_atomOdin32UnicodeText = WinFindAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT);
+ if (g_atomOdin32UnicodeText == 0)
+ VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n",
+ lLastError, WinGetLastError(g_habCtrl));
+
+ VGSvcVerbose(2, "g_u32ClientId=%RX32 g_atomNothingChanged=%#x g_atomOdin32UnicodeText=%#x\n",
+ g_u32ClientId, g_atomNothingChanged, g_atomOdin32UnicodeText);
+ return VINF_SUCCESS;
+ }
+
+ VGSvcError("Failed to connect to the clipboard service, rc=%Rrc!\n", rc);
+ }
+ else
+ VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n",
+ lLastError, WinGetLastError(g_habCtrl));
+ }
+ else
+ VGSvcError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl));
+ WinTerminate(g_habCtrl);
+ return rc;
+}
+
+
+/**
+ * Check that we're still the view / try make us the viewer.
+ */
+static void vgsvcClipboardOs2PollViewer(void)
+{
+ const int iOrgState = g_enmState;
+
+ HWND hwndClipboardViewer = WinQueryClipbrdViewer(g_habWorker);
+ if (hwndClipboardViewer == g_hwndWorker)
+ return;
+
+ if (hwndClipboardViewer == NULLHANDLE)
+ {
+ /* The API will send a WM_DRAWCLIPBOARD message before returning. */
+ g_enmState = kClipboardState_SettingViewer;
+ if (WinSetClipbrdViewer(g_habWorker, g_hwndWorker))
+ g_enmState = kClipboardState_Viewer;
+ else
+ g_enmState = kClipboardState_Polling;
+ }
+ else
+ g_enmState = kClipboardState_Polling;
+ if ((int)g_enmState != iOrgState)
+ {
+ if (g_enmState == kClipboardState_Viewer)
+ VGSvcVerbose(3, "clipboard: viewer\n");
+ else
+ VGSvcVerbose(3, "clipboard: poller\n");
+ }
+}
+
+
+/**
+ * Advertise the formats available from the host.
+ *
+ * @param fFormats The formats available on the host.
+ */
+static void vgsvcClipboardOs2AdvertiseHostFormats(uint32_t fFormats)
+{
+ /*
+ * Open the clipboard and switch to 'destruction' mode.
+ * Make sure we stop being viewer. Temporarily also make sure we're
+ * not the owner so that PM won't send us any WM_DESTROYCLIPBOARD message.
+ */
+ if (WinOpenClipbrd(g_habWorker))
+ {
+ if (g_enmState == kClipboardState_Viewer)
+ WinSetClipbrdViewer(g_habWorker, NULLHANDLE);
+ if (g_enmState == kClipboardState_Owner)
+ WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
+
+ g_enmState = kClipboardState_Destroying;
+ if (WinEmptyClipbrd(g_habWorker))
+ {
+ /*
+ * Take clipboard ownership.
+ */
+ if (WinSetClipbrdOwner(g_habWorker, g_hwndWorker))
+ {
+ g_enmState = kClipboardState_Owner;
+
+ /*
+ * Do the format advertising.
+ */
+ if (fFormats & (VBOX_SHCL_FMT_UNICODETEXT/* | VBOX_SHCL_FMT_HTML ?? */))
+ {
+ if (!WinSetClipbrdData(g_habWorker, 0, CF_TEXT, CFI_POINTER))
+ VGSvcError("WinSetClipbrdData(,,CF_TEXT,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ if ( g_atomOdin32UnicodeText
+ && !WinSetClipbrdData(g_habWorker, 0, g_atomOdin32UnicodeText, CFI_POINTER))
+ VGSvcError("WinSetClipbrdData(,,g_atomOdin32UnicodeText,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ }
+ if (fFormats & VBOX_SHCL_FMT_BITMAP)
+ {
+ /** @todo bitmaps */
+ }
+ }
+ else
+ {
+ VGSvcError("WinSetClipbrdOwner failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ g_enmState = kClipboardState_Polling;
+ }
+ }
+ else
+ {
+ VGSvcError("WinEmptyClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ g_enmState = kClipboardState_Polling;
+ }
+
+ if (g_enmState == kClipboardState_Polling)
+ {
+ g_fEmptyClipboard = true;
+ vgsvcClipboardOs2PollViewer();
+ }
+
+ WinCloseClipbrd(g_habWorker);
+ }
+ else
+ VGSvcError("vgsvcClipboardOs2AdvertiseHostFormats: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+}
+
+
+/**
+ * Converts (render) to an Odin32 clipboard format.
+ *
+ * We ASSUME we get windows data from the host and all we've got to do here is
+ * slapping an Odin32 header on it.
+ *
+ * @returns Pointer to the data (DosFreeMem).
+ * @param fFormat The host format.
+ * @param usFmt The PM/Odin32 format.
+ * @param pv The data in host formatting.
+ * @param cb The size of the data.
+ */
+static void *vgsvcClipboardOs2ConvertToOdin32(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb)
+{
+ PVOID pvPM = NULL;
+ APIRET rc = DosAllocSharedMem(&pvPM, NULL, cb + sizeof(CLIPHEADER), OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
+ if (rc)
+ {
+ PCLIPHEADER pHdr = (PCLIPHEADER)pvPM;
+ memcpy(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic));
+ pHdr->cbData = cb;
+ if (usFmt == g_atomOdin32UnicodeText)
+ pHdr->uFormat = usFmt;
+ else
+ AssertFailed();
+ memcpy(pHdr + 1, pv, cb);
+ }
+ else
+ {
+ VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), rc);
+ pvPM = NULL;
+ }
+ return pvPM;
+}
+
+
+/**
+ * Converts (render) to a PM clipboard format.
+ *
+ * @returns Pointer to the data (DosFreeMem).
+ * @param fFormat The host format.
+ * @param usFmt The PM/Odin32 format.
+ * @param pv The data in host formatting.
+ * @param cb The size of the data.
+ */
+static void *vgsvcClipboardOs2ConvertToPM(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb)
+{
+ void *pvPM = NULL;
+
+ /*
+ * The Odin32 stuff is simple, we just assume windows data from the host
+ * and all we need to do is add the header.
+ */
+ if ( usFmt
+ && ( usFmt == g_atomOdin32UnicodeText
+ /* || usFmt == ...*/
+ )
+ )
+ pvPM = vgsvcClipboardOs2ConvertToOdin32(fFormat, usFmt, pv, cb);
+ else if (usFmt == CF_TEXT)
+ {
+ /*
+ * Convert the unicode text to the current ctype locale.
+ *
+ * Note that we probably should be using the current PM or DOS codepage
+ * here instead of the LC_CTYPE one which iconv uses by default.
+ * -lazybird
+ */
+ Assert(fFormat & VBOX_SHCL_FMT_UNICODETEXT);
+ char *pszUtf8;
+ int rc = RTUtf16ToUtf8((PCRTUTF16)pv, &pszUtf8);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszLocale;
+ rc = RTStrUtf8ToCurrentCP(&pszLocale, pszUtf8);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbPM = strlen(pszLocale) + 1;
+ APIRET orc = DosAllocSharedMem(&pvPM, NULL, cbPM, OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
+ if (orc == NO_ERROR)
+ memcpy(pvPM, pszLocale, cbPM);
+ else
+ {
+ VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), orc);
+ pvPM = NULL;
+ }
+ RTStrFree(pszLocale);
+ }
+ else
+ VGSvcError("RTStrUtf8ToCurrentCP() -> %Rrc\n", rc);
+ RTStrFree(pszUtf8);
+ }
+ else
+ VGSvcError("RTUtf16ToUtf8() -> %Rrc\n", rc);
+ }
+
+ return pvPM;
+}
+
+
+/**
+ * Tries to deliver an advertised host format.
+ *
+ * @param usFmt The PM format name.
+ *
+ * @remark We must not try open the clipboard here because WM_RENDERFMT is a
+ * request send synchronously by someone who has already opened the
+ * clipboard. We would enter a deadlock trying to open it here.
+ */
+static void vgsvcClipboardOs2RenderFormat(USHORT usFmt)
+{
+ bool fSucceeded = false;
+
+ /*
+ * Determine which format.
+ */
+ uint32_t fFormat;
+ if ( usFmt == CF_TEXT
+ || usFmt == g_atomOdin32UnicodeText)
+ fFormat = VBOX_SHCL_FMT_UNICODETEXT;
+ else /** @todo bitmaps */
+ fFormat = 0;
+ if (fFormat)
+ {
+ /*
+ * Query the data from the host.
+ * This might require two iterations because of buffer guessing.
+ */
+ uint32_t cb = _4K;
+ uint32_t cbAllocated = cb;
+ int rc = VERR_NO_MEMORY;
+ void *pv = RTMemPageAllocZ(cbAllocated);
+ if (pv)
+ {
+ VGSvcVerbose(4, "clipboard: reading host data (%#x)\n", fFormat);
+ rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb);
+ if (rc == VINF_BUFFER_OVERFLOW)
+ {
+ RTMemPageFree(pv, cbAllocated);
+ cbAllocated = cb = RT_ALIGN_32(cb, PAGE_SIZE);
+ pv = RTMemPageAllocZ(cbAllocated);
+ rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb);
+ }
+ if (RT_FAILURE(rc))
+ RTMemPageFree(pv, cbAllocated);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "clipboard: read %u bytes\n", cb);
+
+ /*
+ * Convert the host clipboard data to PM clipboard data and set it.
+ */
+ PVOID pvPM = vgsvcClipboardOs2ConvertToPM(fFormat, usFmt, pv, cb);
+ if (pvPM)
+ {
+ if (WinSetClipbrdData(g_habWorker, (ULONG)pvPM, usFmt, CFI_POINTER))
+ fSucceeded = true;
+ else
+ {
+ VGSvcError("vgsvcClipboardOs2RenderFormat: WinSetClipbrdData(,%p,%#x, CF_POINTER) failed, lasterror=%lx\n",
+ pvPM, usFmt, WinGetLastError(g_habWorker));
+ DosFreeMem(pvPM);
+ }
+ }
+ RTMemPageFree(pv, cbAllocated);
+ }
+ else
+ VGSvcError("vgsvcClipboardOs2RenderFormat: Failed to query / allocate data. rc=%Rrc cb=%#RX32\n", rc, cb);
+ }
+
+ /*
+ * Empty the clipboard on failure so we don't end up in any loops.
+ */
+ if (!fSucceeded)
+ {
+ WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
+ g_enmState = kClipboardState_Destroying;
+ WinEmptyClipbrd(g_habWorker);
+ g_enmState = kClipboardState_Polling;
+ g_fEmptyClipboard = true;
+ vgsvcClipboardOs2PollViewer();
+ }
+}
+
+
+/**
+ * Sends data to the host.
+ *
+ * @param fFormat The data format the host is requesting.
+ */
+static void vgsvcClipboardOs2SendDataToHost(uint32_t fFormat)
+{
+ if (WinOpenClipbrd(g_habWorker))
+ {
+ PRTUTF16 pwszFree = NULL;
+ void *pv = NULL;
+ uint32_t cb = 0;
+
+ if (fFormat & VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ /* Got any odin32 unicode text? */
+ PVOID pvPM;
+ PCLIPHEADER pHdr = (PCLIPHEADER)WinQueryClipbrdData(g_habWorker, g_atomOdin32UnicodeText);
+ if ( pHdr
+ && !memcmp(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic)))
+ {
+ pv = pHdr + 1;
+ cb = pHdr->cbData;
+ }
+
+ /* Got any CF_TEXT? */
+ if ( !pv
+ && (pvPM = (PVOID)WinQueryClipbrdData(g_habWorker, CF_TEXT)) != NULL)
+ {
+ char *pszUtf8;
+ int rc = RTStrCurrentCPToUtf8(&pszUtf8, (const char *)pvPM);
+ if (RT_SUCCESS(rc))
+ {
+ PRTUTF16 pwsz;
+ rc = RTStrToUtf16(pszUtf8, &pwsz);
+ if (RT_SUCCESS(rc))
+ {
+ pv = pwszFree = pwsz;
+ cb = (RTUtf16Len(pwsz) + 1) * sizeof(RTUTF16);
+ }
+ RTStrFree(pszUtf8);
+ }
+ }
+ }
+ if (!pv)
+ VGSvcError("vgsvcClipboardOs2SendDataToHost: couldn't find data for %#x\n", fFormat);
+
+ /*
+ * Now, sent whatever we've got to the host (it's waiting).
+ */
+ VGSvcVerbose(4, "clipboard: writing %pv/%#d (fFormat=%#x)\n", pv, cb, fFormat);
+ VbglR3ClipboardWriteData(g_u32ClientId, fFormat, pv, cb);
+ RTUtf16Free(pwszFree);
+
+ WinCloseClipbrd(g_habWorker);
+ }
+ else
+ {
+ VGSvcError("vgsvcClipboardOs2SendDataToHost: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ VGSvcVerbose(4, "clipboard: writing NULL/0 (fFormat=%x)\n", fFormat);
+ VbglR3ClipboardWriteData(g_u32ClientId, fFormat, NULL, 0);
+ }
+}
+
+
+/**
+ * Figure out what's on the clipboard and report it to the host.
+ */
+static void vgsvcClipboardOs2ReportFormats(void)
+{
+ uint32_t fFormats = 0;
+ ULONG ulFormat = 0;
+ while ((ulFormat = WinEnumClipbrdFmts(g_habWorker, ulFormat)) != 0)
+ {
+ if ( ulFormat == CF_TEXT
+ || ulFormat == g_atomOdin32UnicodeText)
+ fFormats |= VBOX_SHCL_FMT_UNICODETEXT;
+ /** @todo else bitmaps and stuff. */
+ }
+ VGSvcVerbose(4, "clipboard: reporting fFormats=%#x\n", fFormats);
+ VbglR3ClipboardReportFormats(g_u32ClientId, fFormats);
+}
+
+
+/**
+ * Poll the clipboard for changes.
+ *
+ * This is called both when we're the viewer and when we're
+ * falling back to polling. If something has changed it will
+ * notify the host.
+ */
+static void vgsvcClipboardOs2Poll(void)
+{
+ if (WinOpenClipbrd(g_habWorker))
+ {
+ /*
+ * If our dummy is no longer there, something has actually changed,
+ * unless the clipboard is really empty.
+ */
+ ULONG fFmtInfo;
+ if (!WinQueryClipbrdFmtInfo(g_habWorker, g_atomNothingChanged, &fFmtInfo))
+ {
+ if (WinEnumClipbrdFmts(g_habWorker, 0) != 0)
+ {
+ g_fEmptyClipboard = false;
+ vgsvcClipboardOs2ReportFormats();
+
+ /* inject the dummy */
+ PVOID pv;
+ APIRET rc = DosAllocSharedMem(&pv, NULL, 1, OBJ_GIVEABLE | OBJ_GETTABLE | PAG_READ | PAG_WRITE | PAG_COMMIT);
+ if (rc == NO_ERROR)
+ {
+ if (WinSetClipbrdData(g_habWorker, (ULONG)pv, g_atomNothingChanged, CFI_POINTER))
+ VGSvcVerbose(4, "clipboard: Added dummy item.\n");
+ else
+ {
+ VGSvcError("vgsvcClipboardOs2Poll: WinSetClipbrdData failed, lasterr=%#lx\n", WinGetLastError(g_habWorker));
+ DosFreeMem(pv);
+ }
+ }
+ else
+ VGSvcError("vgsvcClipboardOs2Poll: DosAllocSharedMem(,,1,) -> %ld\n", rc);
+ }
+ else if (!g_fEmptyClipboard)
+ {
+ g_fEmptyClipboard = true;
+ VGSvcVerbose(3, "Reporting empty clipboard\n");
+ VbglR3ClipboardReportFormats(g_u32ClientId, 0);
+ }
+ }
+ WinCloseClipbrd(g_habWorker);
+ }
+ else
+ VGSvcError("vgsvcClipboardOs2Poll: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+}
+
+
+/**
+ * The clipboard we owned was destroyed by someone else.
+ */
+static void vgsvcClipboardOs2Destroyed(void)
+{
+ /* make sure we're no longer the owner. */
+ if (WinQueryClipbrdOwner(g_habWorker) == g_hwndWorker)
+ WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
+
+ /* switch to polling state and notify the host. */
+ g_enmState = kClipboardState_Polling;
+ g_fEmptyClipboard = true;
+ VGSvcVerbose(3, "Reporting empty clipboard\n");
+ VbglR3ClipboardReportFormats(g_u32ClientId, 0);
+
+ vgsvcClipboardOs2PollViewer();
+}
+
+
+/**
+ * The window procedure for the object window.
+ *
+ * @returns Message result.
+ *
+ * @param hwnd The window handle.
+ * @param msg The message.
+ * @param mp1 Message parameter 1.
+ * @param mp2 Message parameter 2.
+ */
+static MRESULT EXPENTRY vgsvcClipboardOs2WinProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
+{
+ if (msg != WM_TIMER)
+ VGSvcVerbose(6, "vgsvcClipboardOs2WinProc: hwnd=%#lx msg=%#lx mp1=%#lx mp2=%#lx\n", hwnd, msg, mp1, mp2);
+
+ switch (msg)
+ {
+ /*
+ * Handle the two system defined messages for object windows.
+ *
+ * We'll just use the CREATE/DESTROY message to create that timer we're
+ * using for the viewer checks and polling fallback.
+ */
+ case WM_CREATE:
+ g_idWorkerTimer = WinStartTimer(g_habWorker, hwnd, 1 /* id */, 1000 /* 1 second */);
+ g_fEmptyClipboard = true;
+ g_enmState = kClipboardState_Polling;
+ return NULL; /* FALSE(/NULL) == Continue*/
+
+ case WM_DESTROY:
+ WinStopTimer(g_habWorker, hwnd, g_idWorkerTimer);
+ g_idWorkerTimer = ~0UL;
+ g_hwndWorker = NULLHANDLE;
+ break;
+
+ /*
+ * Clipboard viewer message - the content has been changed.
+ * This is sent *after* releasing the clipboard sem
+ * and during the WinSetClipbrdViewer call.
+ */
+ case WM_DRAWCLIPBOARD:
+ if (g_enmState == kClipboardState_SettingViewer)
+ break;
+ AssertMsgBreak(g_enmState == kClipboardState_Viewer, ("g_enmState=%d\n", g_enmState));
+ vgsvcClipboardOs2Poll();
+ break;
+
+ /*
+ * Clipboard owner message - the content was replaced.
+ * This is sent by someone with an open clipboard, so don't try open it now.
+ */
+ case WM_DESTROYCLIPBOARD:
+ if (g_enmState == kClipboardState_Destroying)
+ break; /* it's us doing the replacing, ignore. */
+ AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState));
+ vgsvcClipboardOs2Destroyed();
+ break;
+
+ /*
+ * Clipboard owner message - somebody is requesting us to render a format.
+ * This is called by someone which owns the clipboard, but that's fine.
+ */
+ case WM_RENDERFMT:
+ AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState));
+ vgsvcClipboardOs2RenderFormat(SHORT1FROMMP(mp1));
+ break;
+
+ /*
+ * Clipboard owner message - we're about to quit and should render all formats.
+ *
+ * However, because we're lazy, we'll just ASSUME that since we're quitting
+ * we're probably about to shutdown or something and there is no point in
+ * doing anything here except for emptying the clipboard and removing
+ * ourselves as owner. Any failures at this point are silently ignored.
+ */
+ case WM_RENDERALLFMTS:
+ WinOpenClipbrd(g_habWorker);
+ WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
+ g_enmState = kClipboardState_Destroying;
+ WinEmptyClipbrd(g_habWorker);
+ g_enmState = kClipboardState_Polling;
+ g_fEmptyClipboard = true;
+ WinCloseClipbrd(g_habWorker);
+ break;
+
+ /*
+ * Listener message - the host has new formats to offer.
+ */
+ case WM_USER + VBOX_SHCL_HOST_MSG_FORMATS_REPORT:
+ vgsvcClipboardOs2AdvertiseHostFormats(LONGFROMMP(mp1));
+ break;
+
+ /*
+ * Listener message - the host wish to read our clipboard data.
+ */
+ case WM_USER + VBOX_SHCL_HOST_MSG_READ_DATA:
+ vgsvcClipboardOs2SendDataToHost(LONGFROMMP(mp1));
+ break;
+
+ /*
+ * This is just a fallback polling strategy in case some other
+ * app is trying to view the clipboard too. We also use this
+ * to try recover from errors.
+ *
+ * Because the way the clipboard service works, we have to monitor
+ * it all the time and cannot get away with simpler solutions like
+ * synergy is employing (basically checking upon entering and leaving
+ * a desktop).
+ */
+ case WM_TIMER:
+ if ( g_enmState != kClipboardState_Viewer
+ && g_enmState != kClipboardState_Polling)
+ break;
+
+ /* Lost the position as clipboard viewer?*/
+ if (g_enmState == kClipboardState_Viewer)
+ {
+ if (WinQueryClipbrdViewer(g_habWorker) == hwnd)
+ break;
+ g_enmState = kClipboardState_Polling;
+ }
+
+ /* poll for changes */
+ vgsvcClipboardOs2Poll();
+ vgsvcClipboardOs2PollViewer();
+ break;
+
+
+ /*
+ * Clipboard owner messages dealing with owner drawn content.
+ * We shouldn't be seeing any of these.
+ */
+ case WM_PAINTCLIPBOARD:
+ case WM_SIZECLIPBOARD:
+ case WM_HSCROLLCLIPBOARD:
+ case WM_VSCROLLCLIPBOARD:
+ AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg));
+ break;
+
+ /*
+ * We shouldn't be seeing any other messages according to the docs.
+ * But for whatever reason, PM sends us a WM_ADJUSTWINDOWPOS message
+ * during WinCreateWindow. So, ignore that and assert on anything else.
+ */
+ default:
+ AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg));
+ case WM_ADJUSTWINDOWPOS:
+ break;
+ }
+ return NULL;
+}
+
+
+/**
+ * The listener thread.
+ *
+ * This thread is dedicated to listening for host messages and forwarding
+ * these to the worker thread (using PM).
+ *
+ * The thread will set g_fListenerOkay and signal its user event when it has
+ * completed initialization. In the case of init failure g_fListenerOkay will
+ * not be set.
+ *
+ * @returns Init error code or VINF_SUCCESS.
+ * @param ThreadSelf Our thread handle.
+ * @param pvUser Pointer to the clipboard service shutdown indicator.
+ */
+static DECLCALLBACK(int) vgsvcClipboardOs2Listener(RTTHREAD ThreadSelf, void *pvUser)
+{
+ bool volatile *pfShutdown = (bool volatile *)pvUser;
+ int rc = VERR_GENERAL_FAILURE;
+ VGSvcVerbose(3, "vgsvcClipboardOs2Listener: ThreadSelf=%RTthrd\n", ThreadSelf);
+
+ g_habListener = WinInitialize(0);
+ if (g_habListener != NULLHANDLE)
+ {
+ g_hmqListener = WinCreateMsgQueue(g_habListener, 0);
+ if (g_hmqListener != NULLHANDLE)
+ {
+ WinCancelShutdown(g_hmqListener, TRUE); /* We don't care about shutdown */
+
+ /*
+ * Tell the worker thread that we're good.
+ */
+ rc = VINF_SUCCESS;
+ ASMAtomicXchgBool(&g_fListenerOkay, true);
+ RTThreadUserSignal(ThreadSelf);
+ VGSvcVerbose(3, "vgsvcClipboardOs2Listener: Started successfully\n");
+
+ /*
+ * Loop until termination is requested.
+ */
+ bool fQuit = false;
+ while (!*pfShutdown && !fQuit)
+ {
+ uint32_t Msg;
+ uint32_t fFormats;
+ rc = VbglR3ClipboardGetHostMsgOld(g_u32ClientId, &Msg, &fFormats);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "vgsvcClipboardOs2Listener: Msg=%#x fFormats=%#x\n", Msg, fFormats);
+ switch (Msg)
+ {
+ /*
+ * The host has announced available clipboard formats.
+ * Forward the information to the window, so it can later
+ * respond do WM_RENDERFORMAT message.
+ */
+ case VBOX_SHCL_HOST_MSG_FORMATS_REPORT:
+ if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHCL_HOST_MSG_FORMATS_REPORT,
+ MPFROMLONG(fFormats), 0))
+ VGSvcError("WinPostMsg(%lx, FORMATS,,) failed, lasterr=%#lx\n",
+ g_hwndWorker, WinGetLastError(g_habListener));
+ break;
+
+ /*
+ * The host needs data in the specified format.
+ */
+ case VBOX_SHCL_HOST_MSG_READ_DATA:
+ if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHCL_HOST_MSG_READ_DATA,
+ MPFROMLONG(fFormats), 0))
+ VGSvcError("WinPostMsg(%lx, READ_DATA,,) failed, lasterr=%#lx\n",
+ g_hwndWorker, WinGetLastError(g_habListener));
+ break;
+
+ /*
+ * The host is terminating.
+ */
+ case VBOX_SHCL_HOST_MSG_QUIT:
+ fQuit = true;
+ break;
+
+ default:
+ VGSvcVerbose(1, "vgsvcClipboardOs2Listener: Unknown message %RU32\n", Msg);
+ break;
+ }
+ }
+ else
+ {
+ if (*pfShutdown)
+ break;
+ VGSvcError("VbglR3ClipboardGetHostMsg failed, rc=%Rrc\n", rc);
+ RTThreadSleep(1000);
+ }
+ } /* the loop */
+
+ WinDestroyMsgQueue(g_hmqListener);
+ }
+ WinTerminate(g_habListener);
+ g_habListener = NULLHANDLE;
+ }
+
+ /* Signal our semaphore to make the worker catch on. */
+ RTThreadUserSignal(ThreadSelf);
+ VGSvcVerbose(3, "vgsvcClipboardOs2Listener: terminating, rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vgsvcClipboardOs2Worker(bool volatile *pfShutdown)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ /*
+ * Standard PM init.
+ */
+ g_habWorker = RTThreadSelf() != g_ThreadCtrl ? WinInitialize(0) : g_habCtrl;
+ if (g_habWorker != NULLHANDLE)
+ {
+ g_hmqWorker = RTThreadSelf() != g_ThreadCtrl ? WinCreateMsgQueue(g_habWorker, 0) : g_hmqCtrl;
+ if (g_hmqWorker != NULLHANDLE)
+ {
+ if (g_hmqWorker != g_hmqCtrl)
+ WinCancelShutdown(g_hmqWorker, TRUE); /* We don't care about shutdown */
+
+ /*
+ * Create the object window.
+ */
+ if (WinRegisterClass(g_habWorker, (PCSZ)"VBoxServiceClipboardClass", vgsvcClipboardOs2WinProc, 0, 0))
+ {
+ g_hwndWorker = WinCreateWindow(HWND_OBJECT, /* hwndParent */
+ (PCSZ)"VBoxServiceClipboardClass", /* pszClass */
+ (PCSZ)"VirtualBox Clipboard Service", /* pszName */
+ 0, /* flStyle */
+ 0, 0, 0, 0, /* x, y, cx, cy */
+ NULLHANDLE, /* hwndOwner */
+ HWND_BOTTOM, /* hwndInsertBehind */
+ 42, /* id */
+ NULL, /* pCtlData */
+ NULL); /* pPresParams */
+ if (g_hwndWorker != NULLHANDLE)
+ {
+ VGSvcVerbose(3, "g_hwndWorker=%#lx g_habWorker=%#lx g_hmqWorker=%#lx\n", g_hwndWorker, g_habWorker, g_hmqWorker);
+
+ /*
+ * Create the listener thread.
+ */
+ g_fListenerOkay = false;
+ rc = RTThreadCreate(&g_ThreadListener, vgsvcClipboardOs2Listener, (void *)pfShutdown, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "CLIPLISTEN");
+ if (RT_SUCCESS(rc))
+ {
+ RTThreadUserWait(g_ThreadListener, 30*1000);
+ RTThreadUserReset(g_ThreadListener);
+ if (!g_fListenerOkay)
+ RTThreadWait(g_ThreadListener, 60*1000, NULL);
+ if (g_fListenerOkay)
+ {
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * The PM event pump.
+ */
+ VGSvcVerbose(2, "clipboard: Entering PM message loop.\n");
+ rc = VINF_SUCCESS;
+ QMSG qmsg;
+ while (WinGetMsg(g_habWorker, &qmsg, NULLHANDLE, NULLHANDLE, 0))
+ {
+ if (qmsg.msg != WM_TIMER)
+ VGSvcVerbose(6, "WinGetMsg -> hwnd=%p msg=%#x mp1=%p mp2=%p time=%#x ptl=%d,%d rsrv=%#x\n",
+ qmsg.hwnd, qmsg.msg, qmsg.mp1, qmsg.mp2, qmsg.time, qmsg.ptl.x, qmsg.ptl.y, qmsg.reserved);
+ WinDispatchMsg(g_habWorker, &qmsg);
+ }
+ VGSvcVerbose(2, "clipboard: Exited PM message loop. *pfShutdown=%RTbool\n", *pfShutdown);
+
+ RTThreadWait(g_ThreadListener, 60*1000, NULL);
+ }
+ g_ThreadListener = NIL_RTTHREAD;
+ }
+
+ /*
+ * Got a WM_QUIT, clean up.
+ */
+ if (g_hwndWorker != NULLHANDLE)
+ {
+ WinDestroyWindow(g_hwndWorker);
+ g_hwndWorker = NULLHANDLE;
+ }
+ }
+ else
+ VGSvcError("WinCreateWindow() failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ /* no class deregistration in PM. */
+ }
+ else
+ VGSvcError("WinRegisterClass() failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+
+ if (g_hmqCtrl != g_hmqWorker)
+ WinDestroyMsgQueue(g_hmqWorker);
+ g_hmqWorker = NULLHANDLE;
+ }
+ else
+ VGSvcError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+
+ if (g_habCtrl != g_habWorker)
+ WinTerminate(g_habWorker);
+ g_habWorker = NULLHANDLE;
+ }
+ else
+ VGSvcError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE));
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcClipboardOs2Stop(void)
+{
+ if ( g_hmqWorker != NULLHANDLE
+ && !WinPostQueueMsg(g_hmqWorker, WM_QUIT, NULL, NULL))
+ VGSvcError("WinPostQueueMsg(g_hmqWorker, WM_QUIT, 0,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl));
+
+ /* Must disconnect the clipboard here otherwise the listner won't quit and
+ the service shutdown will not stop. */
+ if (g_u32ClientId != 0)
+ {
+ if (g_hmqWorker != NULLHANDLE)
+ RTThreadSleep(32); /* fudge */
+
+ VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId);
+ int rc = VbglR3ClipboardDisconnect(g_u32ClientId);
+ if (RT_SUCCESS(rc))
+ g_u32ClientId = 0;
+ else
+ VGSvcError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc);
+ }
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcClipboardOs2Term(void)
+{
+ if (g_u32ClientId != 0)
+ {
+ VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId);
+ int rc = VbglR3ClipboardDisconnect(g_u32ClientId);
+ if (RT_SUCCESS(rc))
+ g_u32ClientId = 0;
+ else
+ VGSvcError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc);
+ }
+ WinDestroyMsgQueue(g_hmqCtrl);
+ g_hmqCtrl = NULLHANDLE;
+ WinTerminate(g_habCtrl);
+ g_habCtrl = NULLHANDLE;
+}
+
+
+/**
+ * The OS/2 'clipboard' service description.
+ */
+VBOXSERVICE g_Clipboard =
+{
+ /* pszName. */
+ "clipboard",
+ /* pszDescription. */
+ "Shared Clipboard",
+ /* pszUsage. */
+ ""
+ ,
+ /* pszOptions. */
+ ""
+ ,
+ /* methods */
+ vgsvcClipboardOs2PreInit,
+ vgsvcClipboardOs2Option,
+ vgsvcClipboardOs2Init,
+ vgsvcClipboardOs2Worker,
+ vgsvcClipboardOs2Stop,
+ vgsvcClipboardOs2Term
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp
new file mode 100644
index 00000000..6e30cdff
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp
@@ -0,0 +1,629 @@
+/* $Id: VBoxServiceControl.cpp $ */
+/** @file
+ * VBoxServiceControl - Host-driven Guest Control.
+ */
+
+/*
+ * 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
+ */
+
+/** @page pg_vgsvc_gstctrl VBoxService - Guest Control
+ *
+ * The Guest Control subservice helps implementing the IGuest APIs.
+ *
+ * The communication between this service (and its children) and IGuest goes
+ * over the HGCM GuestControl service.
+ *
+ * The IGuest APIs provides means to manipulate (control) files, directories,
+ * symbolic links and processes within the guest. Most of these means requires
+ * credentials of a guest OS user to operate, though some restricted ones
+ * operates directly as the VBoxService user (root / system service account).
+ *
+ * The current design is that a subprocess is spawned for handling operations as
+ * a given user. This process is represented as IGuestSession in the API. The
+ * subprocess will be spawned as the given use, giving up the privileges the
+ * parent subservice had.
+ *
+ * It will try handle as many of the operations directly from within the
+ * subprocess, but for more complicated things (or things that haven't yet been
+ * converted), it will spawn a helper process that does the actual work.
+ *
+ * These helpers are the typically modeled on similar unix core utilities, like
+ * mkdir, rm, rmdir, cat and so on. The helper tools can also be launched
+ * directly from VBoxManage by the user by prepending the 'vbox_' prefix to the
+ * unix command.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <VBox/err.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/HostServices/GuestControlSvc.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceControl.h"
+#include "VBoxServiceUtils.h"
+
+using namespace guestControl;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The control interval (milliseconds). */
+static uint32_t g_msControlInterval = 0;
+/** The semaphore we're blocking our main control thread on. */
+static RTSEMEVENTMULTI g_hControlEvent = NIL_RTSEMEVENTMULTI;
+/** The VM session ID. Changes whenever the VM is restored or reset. */
+static uint64_t g_idControlSession;
+/** The guest control service client ID. */
+uint32_t g_idControlSvcClient = 0;
+/** VBOX_GUESTCTRL_HF_XXX */
+uint64_t g_fControlHostFeatures0 = 0;
+#if 0 /** @todo process limit */
+/** How many started guest processes are kept into memory for supplying
+ * information to the host. Default is 256 processes. If 0 is specified,
+ * the maximum number of processes is unlimited. */
+static uint32_t g_uControlProcsMaxKept = 256;
+#endif
+/** List of guest control session threads (VBOXSERVICECTRLSESSIONTHREAD).
+ * A guest session thread represents a forked guest session process
+ * of VBoxService. */
+RTLISTANCHOR g_lstControlSessionThreads;
+/** The local session object used for handling all session-related stuff.
+ * When using the legacy guest control protocol (< 2), this session runs
+ * under behalf of the VBoxService main process. On newer protocol versions
+ * each session is a forked version of VBoxService using the appropriate
+ * user credentials for opening a guest session. These forked sessions then
+ * are kept in VBOXSERVICECTRLSESSIONTHREAD structures. */
+VBOXSERVICECTRLSESSION g_Session;
+/** Copy of VbglR3GuestCtrlSupportsOptimizations().*/
+bool g_fControlSupportsOptimizations = true;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int vgsvcGstCtrlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx);
+static int vgsvcGstCtrlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx);
+static int vgsvcGstCtrlInvalidate(void);
+static void vgsvcGstCtrlShutdown(void);
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnPreInit}
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlPreInit(void)
+{
+ int rc;
+#ifdef VBOX_WITH_GUEST_PROPS
+ /*
+ * Read the service options from the VM's guest properties.
+ * Note that these options can be overridden by the command line options later.
+ */
+ uint32_t uGuestPropSvcClientID;
+ rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
+ {
+ VGSvcVerbose(0, "Guest property service is not available, skipping\n");
+ rc = VINF_SUCCESS;
+ }
+ else
+ VGSvcError("Failed to connect to the guest property service, rc=%Rrc\n", rc);
+ }
+ else
+ VbglR3GuestPropDisconnect(uGuestPropSvcClientID);
+
+ if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */
+ rc = VINF_SUCCESS;
+#else
+ /* Nothing to do here yet. */
+ rc = VINF_SUCCESS;
+#endif
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Init session object. */
+ rc = VGSvcGstCtrlSessionInit(&g_Session, 0 /* Flags */);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnOption}
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlOption(const char **ppszShort, int argc, char **argv, int *pi)
+{
+ int rc = -1;
+ if (ppszShort)
+ /* no short options */;
+ else if (!strcmp(argv[*pi], "--control-interval"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi,
+ &g_msControlInterval, 1, UINT32_MAX - 1);
+#ifdef DEBUG
+ else if (!strcmp(argv[*pi], "--control-dump-stdout"))
+ {
+ g_Session.fFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT;
+ rc = 0; /* Flag this command as parsed. */
+ }
+ else if (!strcmp(argv[*pi], "--control-dump-stderr"))
+ {
+ g_Session.fFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR;
+ rc = 0; /* Flag this command as parsed. */
+ }
+#endif
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlInit(void)
+{
+ /*
+ * If not specified, find the right interval default.
+ * Then create the event sem to block on.
+ */
+ if (!g_msControlInterval)
+ g_msControlInterval = 1000;
+
+ int rc = RTSemEventMultiCreate(&g_hControlEvent);
+ AssertRCReturn(rc, rc);
+
+ VbglR3GetSessionId(&g_idControlSession); /* The status code is ignored as this information is not available with VBox < 3.2.10. */
+
+ RTListInit(&g_lstControlSessionThreads);
+
+ /*
+ * Try connect to the host service and tell it we want to be master (if supported).
+ */
+ rc = VbglR3GuestCtrlConnect(&g_idControlSvcClient);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vgsvcGstCtrlInvalidate();
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ else
+ {
+ /* If the service was not found, we disable this service without
+ causing VBoxService to fail. */
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
+ {
+ VGSvcVerbose(0, "Guest control service is not available\n");
+ rc = VERR_SERVICE_DISABLED;
+ }
+ else
+ VGSvcError("Failed to connect to the guest control service! Error: %Rrc\n", rc);
+ }
+ RTSemEventMultiDestroy(g_hControlEvent);
+ g_hControlEvent = NIL_RTSEMEVENTMULTI;
+ g_idControlSvcClient = 0;
+ return rc;
+}
+
+static int vgsvcGstCtrlInvalidate(void)
+{
+ VGSvcVerbose(1, "Invalidating configuration ...\n");
+
+ int rc = VINF_SUCCESS;
+
+ g_fControlSupportsOptimizations = VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient);
+ if (g_fControlSupportsOptimizations)
+ rc = VbglR3GuestCtrlMakeMeMaster(g_idControlSvcClient);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "Guest control service client ID=%RU32%s\n",
+ g_idControlSvcClient, g_fControlSupportsOptimizations ? " w/ optimizations" : "");
+
+ /*
+ * Report features to the host.
+ */
+ const uint64_t fGuestFeatures = VBOX_GUESTCTRL_GF_0_SET_SIZE
+ | VBOX_GUESTCTRL_GF_0_PROCESS_ARGV0
+ | VBOX_GUESTCTRL_GF_0_PROCESS_DYNAMIC_SIZES
+ | VBOX_GUESTCTRL_GF_0_SHUTDOWN;
+
+ rc = VbglR3GuestCtrlReportFeatures(g_idControlSvcClient, fGuestFeatures, &g_fControlHostFeatures0);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "Host features: %#RX64\n", g_fControlHostFeatures0);
+ else
+ VGSvcVerbose(1, "Warning! Feature reporing failed: %Rrc\n", rc);
+
+ return VINF_SUCCESS;
+ }
+ VGSvcError("Failed to become guest control master: %Rrc\n", rc);
+ VbglR3GuestCtrlDisconnect(g_idControlSvcClient);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlWorker(bool volatile *pfShutdown)
+{
+ /*
+ * Tell the control thread that it can continue spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+ Assert(g_idControlSvcClient > 0);
+
+ /* Allocate a scratch buffer for messages which also send
+ * payload data with them. */
+ uint32_t cbScratchBuf = _64K; /** @todo Make buffer size configurable via guest properties/argv! */
+ AssertReturn(RT_IS_POWER_OF_TWO(cbScratchBuf), VERR_INVALID_PARAMETER);
+ uint8_t *pvScratchBuf = (uint8_t*)RTMemAlloc(cbScratchBuf);
+ AssertReturn(pvScratchBuf, VERR_NO_MEMORY);
+
+ int rc = VINF_SUCCESS; /* (shut up compiler warnings) */
+ int cRetrievalFailed = 0; /* Number of failed message retrievals in a row. */
+ while (!*pfShutdown)
+ {
+ VGSvcVerbose(3, "GstCtrl: Waiting for host msg ...\n");
+ VBGLR3GUESTCTRLCMDCTX ctxHost = { g_idControlSvcClient, 0 /*idContext*/, 2 /*uProtocol*/, 0 /*cParms*/ };
+ uint32_t idMsg = 0;
+ rc = VbglR3GuestCtrlMsgPeekWait(g_idControlSvcClient, &idMsg, &ctxHost.uNumParms, &g_idControlSession);
+ if (RT_SUCCESS(rc))
+ {
+ cRetrievalFailed = 0; /* Reset failed retrieval count. */
+ VGSvcVerbose(4, "idMsg=%RU32 (%s) (%RU32 parms) retrieved\n",
+ idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), ctxHost.uNumParms);
+
+ /*
+ * Handle the host message.
+ */
+ switch (idMsg)
+ {
+ case HOST_MSG_CANCEL_PENDING_WAITS:
+ VGSvcVerbose(1, "We were asked to quit ...\n");
+ break;
+
+ case HOST_MSG_SESSION_CREATE:
+ rc = vgsvcGstCtrlHandleSessionOpen(&ctxHost);
+ break;
+
+ /* This message is also sent to the child session process (by the host). */
+ case HOST_MSG_SESSION_CLOSE:
+ rc = vgsvcGstCtrlHandleSessionClose(&ctxHost);
+ break;
+
+ default:
+ if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient))
+ {
+ rc = VbglR3GuestCtrlMsgSkip(g_idControlSvcClient, VERR_NOT_SUPPORTED, idMsg);
+ VGSvcVerbose(1, "Skipped unexpected message idMsg=%RU32 (%s), cParms=%RU32 (rc=%Rrc)\n",
+ idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), ctxHost.uNumParms, rc);
+ }
+ else
+ {
+ rc = VbglR3GuestCtrlMsgSkipOld(g_idControlSvcClient);
+ VGSvcVerbose(3, "Skipped idMsg=%RU32, cParms=%RU32, rc=%Rrc\n", idMsg, ctxHost.uNumParms, rc);
+ }
+ break;
+ }
+
+ /* Do we need to shutdown? */
+ if (idMsg == HOST_MSG_CANCEL_PENDING_WAITS)
+ break;
+
+ /* Let's sleep for a bit and let others run ... */
+ RTThreadYield();
+ }
+ /*
+ * Handle restore notification from host. All the context IDs (sessions,
+ * files, proceses, etc) are invalidated by a VM restore and must be closed.
+ */
+ else if (rc == VERR_VM_RESTORED)
+ {
+ VGSvcVerbose(1, "The VM session ID changed (i.e. restored), closing stale root session\n");
+
+ /* Make sure that all other session threads are gone.
+ * This is necessary, as the new VM session (NOT to be confused with guest session!) will re-use
+ * the guest session IDs. */
+ int rc2 = VGSvcGstCtrlSessionThreadDestroyAll(&g_lstControlSessionThreads, 0 /* Flags */);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Closing session threads failed with rc=%Rrc\n", rc2);
+
+ /* Make sure to also close the root session (session 0). */
+ rc2 = VGSvcGstCtrlSessionClose(&g_Session);
+ AssertRC(rc2);
+
+ rc2 = VbglR3GuestCtrlSessionHasChanged(g_idControlSvcClient, g_idControlSession);
+ AssertRC(rc2);
+
+ /* Invalidate the internal state to match the current host we got restored from. */
+ rc2 = vgsvcGstCtrlInvalidate();
+ AssertRC(rc2);
+ }
+ else
+ {
+ /* Note: VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */
+ /** @todo r=bird: Above comment makes no sense. How can you get a timeout in a blocking HGCM call? */
+ VGSvcError("GstCtrl: Getting host message failed with %Rrc\n", rc);
+
+ /* Check for VM session change. */
+ /** @todo We don't need to check the host here. */
+ uint64_t idNewSession = g_idControlSession;
+ int rc2 = VbglR3GetSessionId(&idNewSession);
+ if ( RT_SUCCESS(rc2)
+ && (idNewSession != g_idControlSession))
+ {
+ VGSvcVerbose(1, "GstCtrl: The VM session ID changed\n");
+ g_idControlSession = idNewSession;
+
+ /* Close all opened guest sessions -- all context IDs, sessions etc.
+ * are now invalid. */
+ rc2 = VGSvcGstCtrlSessionClose(&g_Session);
+ AssertRC(rc2);
+
+ /* Do a reconnect. */
+ VGSvcVerbose(1, "Reconnecting to HGCM service ...\n");
+ rc2 = VbglR3GuestCtrlConnect(&g_idControlSvcClient);
+ if (RT_SUCCESS(rc2))
+ {
+ VGSvcVerbose(3, "Guest control service client ID=%RU32\n", g_idControlSvcClient);
+ cRetrievalFailed = 0;
+ continue; /* Skip waiting. */
+ }
+ VGSvcError("Unable to re-connect to HGCM service, rc=%Rrc, bailing out\n", rc);
+ break;
+ }
+
+ if (rc == VERR_INTERRUPTED)
+ RTThreadYield(); /* To be on the safe side... */
+ else if (++cRetrievalFailed <= 16) /** @todo Make this configurable? */
+ RTThreadSleep(1000); /* Wait a bit before retrying. */
+ else
+ {
+ VGSvcError("Too many failed attempts in a row to get next message, bailing out\n");
+ break;
+ }
+ }
+ }
+
+ VGSvcVerbose(0, "Guest control service stopped\n");
+
+ /* Delete scratch buffer. */
+ if (pvScratchBuf)
+ RTMemFree(pvScratchBuf);
+
+ VGSvcVerbose(0, "Guest control worker returned with rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+static int vgsvcGstCtrlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the message parameters.
+ */
+ PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo;
+ int rc = VbglR3GuestCtrlSessionGetOpen(pHostCtx, &pStartupInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Flat out refuse to work with protocol v1 hosts.
+ */
+ if (pStartupInfo->uProtocol == 2)
+ {
+ pHostCtx->uProtocol = pStartupInfo->uProtocol;
+ VGSvcVerbose(3, "Client ID=%RU32 now is using protocol %RU32\n", pHostCtx->uClientID, pHostCtx->uProtocol);
+
+/** @todo Someone explain why this code isn't in this file too? v1 support? */
+ rc = VGSvcGstCtrlSessionThreadCreate(&g_lstControlSessionThreads, pStartupInfo, NULL /* ppSessionThread */);
+ /* Report failures to the host (successes are taken care of by the session thread). */
+ }
+ else
+ {
+ VGSvcError("The host wants to use protocol v%u, we only support v2!\n", pStartupInfo->uProtocol);
+ rc = VERR_VERSION_MISMATCH;
+ }
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx, GUEST_SESSION_NOTIFYTYPE_ERROR, rc);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Reporting session error status on open failed with rc=%Rrc\n", rc2);
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for opening guest session: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ VbglR3GuestCtrlSessionStartupInfoFree(pStartupInfo);
+ pStartupInfo = NULL;
+
+ VGSvcVerbose(3, "Opening a new guest session returned rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+static int vgsvcGstCtrlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ uint32_t idSession;
+ uint32_t fFlags;
+ int rc = VbglR3GuestCtrlSessionGetClose(pHostCtx, &fFlags, &idSession);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_NOT_FOUND;
+
+ PVBOXSERVICECTRLSESSIONTHREAD pThread;
+ RTListForEach(&g_lstControlSessionThreads, pThread, VBOXSERVICECTRLSESSIONTHREAD, Node)
+ {
+ if ( pThread->pStartupInfo
+ && pThread->pStartupInfo->uSessionID == idSession)
+ {
+ rc = VGSvcGstCtrlSessionThreadDestroy(pThread, fFlags);
+ break;
+ }
+ }
+
+#if 0 /** @todo A bit of a mess here as this message goes to both to this process (master) and the session process. */
+ if (RT_FAILURE(rc))
+ {
+ /* Report back on failure. On success this will be done
+ * by the forked session thread. */
+ int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx,
+ GUEST_SESSION_NOTIFYTYPE_ERROR, rc);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Reporting session error status on close failed with rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+#endif
+ VGSvcVerbose(2, "Closing guest session %RU32 returned rc=%Rrc\n", idSession, rc);
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for closing guest session: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcGstCtrlStop(void)
+{
+ VGSvcVerbose(3, "Stopping ...\n");
+
+ /** @todo Later, figure what to do if we're in RTProcWait(). It's a very
+ * annoying call since doesn't support timeouts in the posix world. */
+ if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiSignal(g_hControlEvent);
+
+ /*
+ * Ask the host service to cancel all pending requests for the main
+ * control thread so that we can shutdown properly here.
+ */
+ if (g_idControlSvcClient)
+ {
+ VGSvcVerbose(3, "Cancelling pending waits (client ID=%u) ...\n",
+ g_idControlSvcClient);
+
+ int rc = VbglR3GuestCtrlCancelPendingWaits(g_idControlSvcClient);
+ if (RT_FAILURE(rc))
+ VGSvcError("Cancelling pending waits failed; rc=%Rrc\n", rc);
+ }
+}
+
+
+/**
+ * Destroys all guest process threads which are still active.
+ */
+static void vgsvcGstCtrlShutdown(void)
+{
+ VGSvcVerbose(2, "Shutting down ...\n");
+
+ int rc2 = VGSvcGstCtrlSessionThreadDestroyAll(&g_lstControlSessionThreads, 0 /* Flags */);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Closing session threads failed with rc=%Rrc\n", rc2);
+
+ rc2 = VGSvcGstCtrlSessionClose(&g_Session);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Closing session failed with rc=%Rrc\n", rc2);
+
+ VGSvcVerbose(2, "Shutting down complete\n");
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcGstCtrlTerm(void)
+{
+ VGSvcVerbose(3, "Terminating ...\n");
+
+ vgsvcGstCtrlShutdown();
+
+ VGSvcVerbose(3, "Disconnecting client ID=%u ...\n", g_idControlSvcClient);
+ VbglR3GuestCtrlDisconnect(g_idControlSvcClient);
+ g_idControlSvcClient = 0;
+
+ if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_hControlEvent);
+ g_hControlEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'vminfo' service description.
+ */
+VBOXSERVICE g_Control =
+{
+ /* pszName. */
+ "control",
+ /* pszDescription. */
+ "Host-driven Guest Control",
+ /* pszUsage. */
+#ifdef DEBUG
+ " [--control-dump-stderr] [--control-dump-stdout]\n"
+#endif
+ " [--control-interval <ms>]"
+ ,
+ /* pszOptions. */
+#ifdef DEBUG
+ " --control-dump-stderr Dumps all guest proccesses stderr data to the\n"
+ " temporary directory.\n"
+ " --control-dump-stdout Dumps all guest proccesses stdout data to the\n"
+ " temporary directory.\n"
+#endif
+ " --control-interval Specifies the interval at which to check for\n"
+ " new control messages. The default is 1000 ms.\n"
+ ,
+ /* methods */
+ vgsvcGstCtrlPreInit,
+ vgsvcGstCtrlOption,
+ vgsvcGstCtrlInit,
+ vgsvcGstCtrlWorker,
+ vgsvcGstCtrlStop,
+ vgsvcGstCtrlTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h
new file mode 100644
index 00000000..2a0d6513
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h
@@ -0,0 +1,297 @@
+/* $Id: VBoxServiceControl.h $ */
+/** @file
+ * VBoxServiceControl.h - Internal guest control definitions.
+ */
+
+/*
+ * Copyright (C) 2013-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 GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/critsect.h>
+#include <iprt/list.h>
+#include <iprt/req.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/GuestHost/GuestControl.h>
+#include <VBox/HostServices/GuestControlSvc.h>
+
+
+/**
+ * Pipe IDs for handling the guest process poll set.
+ */
+typedef enum VBOXSERVICECTRLPIPEID
+{
+ VBOXSERVICECTRLPIPEID_UNKNOWN = 0,
+ VBOXSERVICECTRLPIPEID_STDIN = 10,
+ VBOXSERVICECTRLPIPEID_STDIN_WRITABLE = 11,
+ /** Pipe for reading from guest process' stdout. */
+ VBOXSERVICECTRLPIPEID_STDOUT = 40,
+ /** Pipe for reading from guest process' stderr. */
+ VBOXSERVICECTRLPIPEID_STDERR = 50,
+ /** Notification pipe for waking up the guest process
+ * control thread. */
+ VBOXSERVICECTRLPIPEID_IPC_NOTIFY = 100
+} VBOXSERVICECTRLPIPEID;
+
+/**
+ * Structure for one (opened) guest file.
+ */
+typedef struct VBOXSERVICECTRLFILE
+{
+ /** Pointer to list archor of following
+ * list node.
+ * @todo Would be nice to have a RTListGetAnchor(). */
+ PRTLISTANCHOR pAnchor;
+ /** Node to global guest control file list. */
+ /** @todo Use a map later? */
+ RTLISTNODE Node;
+ /** The file name. */
+ char *pszName;
+ /** The file handle on the guest. */
+ RTFILE hFile;
+ /** File handle to identify this file. */
+ uint32_t uHandle;
+ /** Context ID. */
+ uint32_t uContextID;
+ /** RTFILE_O_XXX flags. */
+ uint64_t fOpen;
+} VBOXSERVICECTRLFILE;
+/** Pointer to thread data. */
+typedef VBOXSERVICECTRLFILE *PVBOXSERVICECTRLFILE;
+
+/**
+ * Structure for a guest session thread to
+ * observe/control the forked session instance from
+ * the VBoxService main executable.
+ */
+typedef struct VBOXSERVICECTRLSESSIONTHREAD
+{
+ /** Node to global guest control session list. */
+ /** @todo Use a map later? */
+ RTLISTNODE Node;
+ /** The sessions's startup info. */
+ PVBGLR3GUESTCTRLSESSIONSTARTUPINFO
+ pStartupInfo;
+ /** Critical section for thread-safe use. */
+ RTCRITSECT CritSect;
+ /** The worker thread. */
+ RTTHREAD Thread;
+ /** Process handle for forked child. */
+ RTPROCESS hProcess;
+ /** Shutdown indicator; will be set when the thread
+ * needs (or is asked) to shutdown. */
+ bool volatile fShutdown;
+ /** Indicator set by the service thread exiting. */
+ bool volatile fStopped;
+ /** Whether the thread was started or not. */
+ bool fStarted;
+#if 0 /* Pipe IPC not used yet. */
+ /** Pollset containing all the pipes. */
+ RTPOLLSET hPollSet;
+ RTPIPE hStdInW;
+ RTPIPE hStdOutR;
+ RTPIPE hStdErrR;
+ struct StdPipe
+ {
+ RTHANDLE hChild;
+ PRTHANDLE phChild;
+ } StdIn,
+ StdOut,
+ StdErr;
+ /** The notification pipe associated with this guest session.
+ * This is NIL_RTPIPE for output pipes. */
+ RTPIPE hNotificationPipeW;
+ /** The other end of hNotificationPipeW. */
+ RTPIPE hNotificationPipeR;
+#endif
+ /** Pipe for handing the secret key to the session process. */
+ RTPIPE hKeyPipe;
+ /** Secret key. */
+ uint8_t abKey[_4K];
+} VBOXSERVICECTRLSESSIONTHREAD;
+/** Pointer to thread data. */
+typedef VBOXSERVICECTRLSESSIONTHREAD *PVBOXSERVICECTRLSESSIONTHREAD;
+
+/** Defines the prefix being used for telling our service executable that we're going
+ * to spawn a new (Guest Control) user session. */
+#define VBOXSERVICECTRLSESSION_GETOPT_PREFIX "guestsession"
+
+/** Flag indicating that this session has been spawned from
+ * the main executable. */
+#define VBOXSERVICECTRLSESSION_FLAG_SPAWN RT_BIT(0)
+/** Flag indicating that this session is anonymous, that is,
+ * it will run start guest processes with the same credentials
+ * as the main executable. */
+#define VBOXSERVICECTRLSESSION_FLAG_ANONYMOUS RT_BIT(1)
+/** Flag indicating that started guest processes will dump their
+ * stdout output to a separate file on disk. For debugging. */
+#define VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT RT_BIT(2)
+/** Flag indicating that started guest processes will dump their
+ * stderr output to a separate file on disk. For debugging. */
+#define VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR RT_BIT(3)
+
+/**
+ * Structure for maintaining a guest session. This also
+ * contains all started threads (e.g. for guest processes).
+ *
+ * This structure can act in two different ways:
+ * - For legacy guest control handling (protocol version < 2)
+ * this acts as a per-guest process structure containing all
+ * the information needed to get a guest process up and running.
+ * - For newer guest control protocols (>= 2) this structure is
+ * part of the forked session child, maintaining all guest
+ * control objects under it.
+ */
+typedef struct VBOXSERVICECTRLSESSION
+{
+ /* The session's startup information. */
+ VBGLR3GUESTCTRLSESSIONSTARTUPINFO
+ StartupInfo;
+ /** List of active guest process threads
+ * (VBOXSERVICECTRLPROCESS). */
+ RTLISTANCHOR lstProcesses;
+ /** Number of guest processes in the process list. */
+ uint32_t cProcesses;
+ /** List of guest control files (VBOXSERVICECTRLFILE). */
+ RTLISTANCHOR lstFiles;
+ /** Number of guest files in the file list. */
+ uint32_t cFiles;
+ /** The session's critical section. */
+ RTCRITSECT CritSect;
+ /** Internal session flags, not related
+ * to StartupInfo stuff.
+ * @sa VBOXSERVICECTRLSESSION_FLAG_* flags. */
+ uint32_t fFlags;
+ /** How many processes do we allow keeping around at a time? */
+ uint32_t uProcsMaxKept;
+} VBOXSERVICECTRLSESSION;
+/** Pointer to guest session. */
+typedef VBOXSERVICECTRLSESSION *PVBOXSERVICECTRLSESSION;
+
+/**
+ * Structure for holding data for one (started) guest process.
+ */
+typedef struct VBOXSERVICECTRLPROCESS
+{
+ /** Node. */
+ RTLISTNODE Node;
+ /** Process handle. */
+ RTPROCESS hProcess;
+ /** Number of references using this struct. */
+ uint32_t cRefs;
+ /** The worker thread. */
+ RTTHREAD Thread;
+ /** The session this guest process
+ * is bound to. */
+ PVBOXSERVICECTRLSESSION pSession;
+ /** Shutdown indicator; will be set when the thread
+ * needs (or is asked) to shutdown. */
+ bool volatile fShutdown;
+ /** Whether the guest process thread was stopped or not. */
+ bool volatile fStopped;
+ /** Whether the guest process thread was started or not. */
+ bool fStarted;
+ /** Context ID. */
+ uint32_t uContextID;
+ /** Critical section for thread-safe use. */
+ RTCRITSECT CritSect;
+ /** Process startup information. */
+ PVBGLR3GUESTCTRLPROCSTARTUPINFO
+ pStartupInfo;
+ /** The process' PID assigned by the guest OS. */
+ uint32_t uPID;
+ /** The process' request queue to handle requests
+ * from the outside, e.g. the session. */
+ RTREQQUEUE hReqQueue;
+ /** Our pollset, used for accessing the process'
+ * std* pipes + the notification pipe. */
+ RTPOLLSET hPollSet;
+ /** StdIn pipe for addressing writes to the
+ * guest process' stdin.*/
+ RTPIPE hPipeStdInW;
+ /** StdOut pipe for addressing reads from
+ * guest process' stdout.*/
+ RTPIPE hPipeStdOutR;
+ /** StdOut pipe for addressing reads from
+ * guest process' stderr.*/
+ RTPIPE hPipeStdErrR;
+
+ /** The write end of the notification pipe that is used to poke the thread
+ * monitoring the process.
+ * This is NIL_RTPIPE for output pipes. */
+ RTPIPE hNotificationPipeW;
+ /** The other end of hNotificationPipeW, read by vgsvcGstCtrlProcessProcLoop(). */
+ RTPIPE hNotificationPipeR;
+} VBOXSERVICECTRLPROCESS;
+/** Pointer to thread data. */
+typedef VBOXSERVICECTRLPROCESS *PVBOXSERVICECTRLPROCESS;
+
+RT_C_DECLS_BEGIN
+
+extern RTLISTANCHOR g_lstControlSessionThreads;
+extern VBOXSERVICECTRLSESSION g_Session;
+extern uint32_t g_idControlSvcClient;
+extern uint64_t g_fControlHostFeatures0;
+extern bool g_fControlSupportsOptimizations;
+
+
+/** @name Guest session thread handling.
+ * @{ */
+extern int VGSvcGstCtrlSessionThreadCreate(PRTLISTANCHOR pList, const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo, PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread);
+extern int VGSvcGstCtrlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pSession, uint32_t uFlags);
+extern int VGSvcGstCtrlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t uFlags);
+extern int VGSvcGstCtrlSessionThreadTerminate(PVBOXSERVICECTRLSESSIONTHREAD pSession);
+extern RTEXITCODE VGSvcGstCtrlSessionSpawnInit(int argc, char **argv);
+/** @} */
+/** @name Per-session functions.
+ * @{ */
+extern PVBOXSERVICECTRLPROCESS VGSvcGstCtrlSessionRetainProcess(PVBOXSERVICECTRLSESSION pSession, uint32_t uPID);
+extern int VGSvcGstCtrlSessionClose(PVBOXSERVICECTRLSESSION pSession);
+extern int VGSvcGstCtrlSessionDestroy(PVBOXSERVICECTRLSESSION pSession);
+extern int VGSvcGstCtrlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t uFlags);
+extern int VGSvcGstCtrlSessionHandler(PVBOXSERVICECTRLSESSION pSession, uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx, void *pvScratchBuf, size_t cbScratchBuf, volatile bool *pfShutdown);
+extern int VGSvcGstCtrlSessionProcessAdd(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess);
+extern int VGSvcGstCtrlSessionProcessRemove(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess);
+extern int VGSvcGstCtrlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession, bool *pfAllowed);
+extern int VGSvcGstCtrlSessionReapProcesses(PVBOXSERVICECTRLSESSION pSession);
+/** @} */
+/** @name Per-guest process functions.
+ * @{ */
+extern int VGSvcGstCtrlProcessFree(PVBOXSERVICECTRLPROCESS pProcess);
+extern int VGSvcGstCtrlProcessHandleInput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fPendingClose, void *pvBuf, uint32_t cbBuf);
+extern int VGSvcGstCtrlProcessHandleOutput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags);
+extern int VGSvcGstCtrlProcessHandleTerm(PVBOXSERVICECTRLPROCESS pProcess);
+extern void VGSvcGstCtrlProcessRelease(PVBOXSERVICECTRLPROCESS pProcess);
+extern int VGSvcGstCtrlProcessStart(const PVBOXSERVICECTRLSESSION pSession, const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo, uint32_t uContext);
+extern int VGSvcGstCtrlProcessStop(PVBOXSERVICECTRLPROCESS pProcess);
+extern int VGSvcGstCtrlProcessWait(const PVBOXSERVICECTRLPROCESS pProcess, RTMSINTERVAL msTimeout, int *pRc);
+/** @} */
+
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp
new file mode 100644
index 00000000..e45fb77e
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp
@@ -0,0 +1,2201 @@
+/* $Id: VBoxServiceControlProcess.cpp $ */
+/** @file
+ * VBoxServiceControlThread - Guest process handling.
+ */
+
+/*
+ * 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 <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/handle.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/thread.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/HostServices/GuestControlSvc.h>
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceControl.h"
+#include "VBoxServiceToolBox.h"
+
+using namespace guestControl;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int vgsvcGstCtrlProcessAssignPID(PVBOXSERVICECTRLPROCESS pThread, uint32_t uPID);
+static int vgsvcGstCtrlProcessLock(PVBOXSERVICECTRLPROCESS pProcess);
+static int vgsvcGstCtrlProcessSetupPipe(const char *pszHowTo, int fd, PRTHANDLE ph, PRTHANDLE *pph,
+ PRTPIPE phPipe);
+static int vgsvcGstCtrlProcessUnlock(PVBOXSERVICECTRLPROCESS pProcess);
+/* Request handlers. */
+static DECLCALLBACK(int) vgsvcGstCtrlProcessOnInput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ bool fPendingClose, void *pvBuf, uint32_t cbBuf);
+static DECLCALLBACK(int) vgsvcGstCtrlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags);
+
+
+
+/**
+ * Initialies the passed in thread data structure with the parameters given.
+ *
+ * @return IPRT status code.
+ * @param pProcess Process to initialize.
+ * @param pSession Guest session the process is bound to.
+ * @param pStartupInfo Startup information.
+ * @param u32ContextID The context ID bound to this request / command.
+ */
+static int vgsvcGstCtrlProcessInit(PVBOXSERVICECTRLPROCESS pProcess,
+ const PVBOXSERVICECTRLSESSION pSession,
+ const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo,
+ uint32_t u32ContextID)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER);
+
+ /* General stuff. */
+ pProcess->hProcess = NIL_RTPROCESS;
+ pProcess->pSession = pSession;
+ pProcess->Node.pPrev = NULL;
+ pProcess->Node.pNext = NULL;
+
+ pProcess->fShutdown = false;
+ pProcess->fStarted = false;
+ pProcess->fStopped = false;
+
+ pProcess->uPID = 0; /* Don't have a PID yet. */
+ pProcess->cRefs = 0;
+ /*
+ * Use the initial context ID we got for starting
+ * the process to report back its status with the
+ * same context ID.
+ */
+ pProcess->uContextID = u32ContextID;
+ /*
+ * Note: pProcess->ClientID will be assigned when thread is started;
+ * every guest process has its own client ID to detect crashes on
+ * a per-guest-process level.
+ */
+
+ int rc = RTCritSectInit(&pProcess->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pProcess->hPollSet = NIL_RTPOLLSET;
+ pProcess->hPipeStdInW = NIL_RTPIPE;
+ pProcess->hPipeStdOutR = NIL_RTPIPE;
+ pProcess->hPipeStdErrR = NIL_RTPIPE;
+ pProcess->hNotificationPipeW = NIL_RTPIPE;
+ pProcess->hNotificationPipeR = NIL_RTPIPE;
+
+ rc = RTReqQueueCreate(&pProcess->hReqQueue);
+ AssertReleaseRC(rc);
+
+ /* Duplicate startup info. */
+ pProcess->pStartupInfo = VbglR3GuestCtrlProcStartupInfoDup(pStartupInfo);
+ AssertPtrReturn(pProcess->pStartupInfo, VERR_NO_MEMORY);
+
+ /* Adjust timeout value. */
+ if ( pProcess->pStartupInfo->uTimeLimitMS == UINT32_MAX
+ || pProcess->pStartupInfo->uTimeLimitMS == 0)
+ pProcess->pStartupInfo->uTimeLimitMS = RT_INDEFINITE_WAIT;
+
+ if (RT_FAILURE(rc)) /* Clean up on failure. */
+ VGSvcGstCtrlProcessFree(pProcess);
+ return rc;
+}
+
+
+/**
+ * Frees a guest process. On success, pProcess will be
+ * free'd and thus won't be available anymore.
+ *
+ * @return IPRT status code.
+ * @param pProcess Guest process to free.
+ * The pointer will not be valid anymore after return.
+ */
+int VGSvcGstCtrlProcessFree(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pProcess->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Freeing (cRefs=%RU32)...\n", pProcess->uPID, pProcess->cRefs);
+
+ AssertReturn(pProcess->cRefs == 0, VERR_WRONG_ORDER);
+ AssertReturn(pProcess->fStopped, VERR_WRONG_ORDER);
+ AssertReturn(pProcess->fShutdown, VERR_WRONG_ORDER);
+
+ VbglR3GuestCtrlProcStartupInfoFree(pProcess->pStartupInfo);
+ pProcess->pStartupInfo = NULL;
+
+ /*
+ * Destroy other thread data.
+ */
+ rc = RTPollSetDestroy(pProcess->hPollSet);
+ AssertRC(rc);
+
+ rc = RTReqQueueDestroy(pProcess->hReqQueue);
+ AssertRC(rc);
+
+ rc = RTPipeClose(pProcess->hNotificationPipeR);
+ AssertRC(rc);
+ rc = RTPipeClose(pProcess->hNotificationPipeW);
+ AssertRC(rc);
+
+ rc = RTPipeClose(pProcess->hPipeStdInW);
+ AssertRC(rc);
+ rc = RTPipeClose(pProcess->hPipeStdErrR);
+ AssertRC(rc);
+ rc = RTPipeClose(pProcess->hPipeStdOutR);
+ AssertRC(rc);
+
+ rc = RTCritSectLeave(&pProcess->CritSect);
+ AssertRC(rc);
+
+ RTCritSectDelete(&pProcess->CritSect);
+
+ /*
+ * Destroy thread structure as final step.
+ */
+ RTMemFree(pProcess);
+ pProcess = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Signals a guest process thread that we want it to shut down in
+ * a gentle way.
+ *
+ * @return IPRT status code.
+ * @param pProcess Process to stop.
+ */
+int VGSvcGstCtrlProcessStop(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ VGSvcVerbose(3, "[PID %RU32]: Stopping ...\n", pProcess->uPID);
+
+ /* Do *not* set pThread->fShutdown or other stuff here!
+ * The guest thread loop will clean up itself. */
+
+ return VGSvcGstCtrlProcessHandleTerm(pProcess);
+}
+
+
+/**
+ * Releases a previously acquired guest process (decreases the refcount).
+ *
+ * @param pProcess Process to release.
+ */
+void VGSvcGstCtrlProcessRelease(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturnVoid(pProcess);
+
+ int rc2 = RTCritSectEnter(&pProcess->CritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ AssertReturnVoid(pProcess->cRefs);
+ pProcess->cRefs--;
+
+ VGSvcVerbose(3, "[PID %RU32]: cRefs=%RU32, fShutdown=%RTbool, fStopped=%RTbool\n",
+ pProcess->uPID, pProcess->cRefs, pProcess->fShutdown, pProcess->fStopped);
+
+ rc2 = RTCritSectLeave(&pProcess->CritSect);
+ AssertRC(rc2);
+ }
+}
+
+
+/**
+ * Wait for a guest process thread to shut down.
+ *
+ * @return IPRT status code.
+ * @param pProcess Process to wait shutting down for.
+ * @param msTimeout Timeout in ms to wait for shutdown.
+ * @param prc Where to store the thread's return code.
+ * Optional.
+ */
+int VGSvcGstCtrlProcessWait(const PVBOXSERVICECTRLPROCESS pProcess, RTMSINTERVAL msTimeout, int *prc)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(prc, VERR_INVALID_POINTER);
+
+ int rc = vgsvcGstCtrlProcessLock(pProcess);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTThreadGetState(pProcess->Thread) != RTTHREADSTATE_INVALID) /* Is there a thread we can wait for? */
+ {
+ VGSvcVerbose(2, "[PID %RU32]: Waiting for shutdown (%RU32ms) ...\n", pProcess->uPID, msTimeout);
+
+ AssertMsgReturn(pProcess->fStarted,
+ ("Tried to wait on guest process=%p (PID %RU32) which has not been started yet\n",
+ pProcess, pProcess->uPID), VERR_INVALID_PARAMETER);
+
+ /* Unlock process before waiting. */
+ rc = vgsvcGstCtrlProcessUnlock(pProcess);
+ AssertRC(rc);
+
+ /* Do the actual waiting. */
+ int rcThread;
+ Assert(pProcess->Thread != NIL_RTTHREAD);
+ rc = RTThreadWait(pProcess->Thread, msTimeout, &rcThread);
+
+ int rc2 = vgsvcGstCtrlProcessLock(pProcess);
+ AssertRC(rc2);
+
+ if (RT_SUCCESS(rc))
+ {
+ pProcess->Thread = NIL_RTTHREAD;
+ VGSvcVerbose(3, "[PID %RU32]: Thread shutdown complete, thread rc=%Rrc\n", pProcess->uPID, rcThread);
+ if (prc)
+ *prc = rcThread;
+ }
+ }
+
+ int rc2 = vgsvcGstCtrlProcessUnlock(pProcess);
+ AssertRC(rc2);
+ }
+
+ if (RT_FAILURE(rc))
+ VGSvcError("[PID %RU32]: Waiting for shutting down thread returned error rc=%Rrc\n", pProcess->uPID, rc);
+
+ VGSvcVerbose(3, "[PID %RU32]: Waiting resulted in rc=%Rrc\n", pProcess->uPID, rc);
+ return rc;
+}
+
+
+/**
+ * Closes the stdin pipe of a guest process.
+ *
+ * @return IPRT status code.
+ * @param pProcess The process which input pipe we close.
+ * @param phStdInW The standard input pipe handle.
+ */
+static int vgsvcGstCtrlProcessPollsetCloseInput(PVBOXSERVICECTRLPROCESS pProcess, PRTPIPE phStdInW)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ AssertPtrReturn(phStdInW, VERR_INVALID_POINTER);
+
+ int rc = RTPollSetRemove(pProcess->hPollSet, VBOXSERVICECTRLPIPEID_STDIN);
+ if (rc != VERR_POLL_HANDLE_ID_NOT_FOUND)
+ AssertRC(rc);
+
+ if (*phStdInW != NIL_RTPIPE)
+ {
+ rc = RTPipeClose(*phStdInW);
+ AssertRC(rc);
+ *phStdInW = NIL_RTPIPE;
+ }
+
+ return rc;
+}
+
+
+#ifdef DEBUG
+/**
+ * Names a poll handle ID.
+ *
+ * @returns Pointer to read-only string.
+ * @param idPollHnd What to name.
+ */
+static const char *vgsvcGstCtrlProcessPollHandleToString(uint32_t idPollHnd)
+{
+ switch (idPollHnd)
+ {
+ case VBOXSERVICECTRLPIPEID_UNKNOWN:
+ return "unknown";
+ case VBOXSERVICECTRLPIPEID_STDIN:
+ return "stdin";
+ case VBOXSERVICECTRLPIPEID_STDIN_WRITABLE:
+ return "stdin_writable";
+ case VBOXSERVICECTRLPIPEID_STDOUT:
+ return "stdout";
+ case VBOXSERVICECTRLPIPEID_STDERR:
+ return "stderr";
+ case VBOXSERVICECTRLPIPEID_IPC_NOTIFY:
+ return "ipc_notify";
+ default:
+ return "unknown";
+ }
+}
+#endif /* DEBUG */
+
+
+/**
+ * Handle an error event on standard input.
+ *
+ * @return IPRT status code.
+ * @param pProcess Process to handle pollset for.
+ * @param fPollEvt The event mask returned by RTPollNoResume.
+ * @param phStdInW The standard input pipe handle.
+ */
+static int vgsvcGstCtrlProcessPollsetOnInput(PVBOXSERVICECTRLPROCESS pProcess, uint32_t fPollEvt, PRTPIPE phStdInW)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ NOREF(fPollEvt);
+
+ return vgsvcGstCtrlProcessPollsetCloseInput(pProcess, phStdInW);
+}
+
+
+/**
+ * Handle pending output data or error on standard out or standard error.
+ *
+ * @returns IPRT status code from client send.
+ * @param pProcess Process to handle pollset for.
+ * @param fPollEvt The event mask returned by RTPollNoResume.
+ * @param phPipeR The pipe handle.
+ * @param idPollHnd The pipe ID to handle.
+ */
+static int vgsvcGstCtrlProcessHandleOutputError(PVBOXSERVICECTRLPROCESS pProcess,
+ uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd)
+{
+ RT_NOREF1(fPollEvt);
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ if (!phPipeR)
+ return VINF_SUCCESS;
+
+#ifdef DEBUG
+ VGSvcVerbose(4, "[PID %RU32]: Output error: idPollHnd=%s, fPollEvt=0x%x\n",
+ pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), fPollEvt);
+#endif
+
+ /* Remove pipe from poll set. */
+ int rc2 = RTPollSetRemove(pProcess->hPollSet, idPollHnd);
+ AssertMsg(RT_SUCCESS(rc2) || rc2 == VERR_POLL_HANDLE_ID_NOT_FOUND, ("%Rrc\n", rc2));
+
+ bool fClosePipe = true; /* By default close the pipe. */
+
+ /* Check if there's remaining data to read from the pipe. */
+ if (*phPipeR != NIL_RTPIPE)
+ {
+ size_t cbReadable;
+ rc2 = RTPipeQueryReadable(*phPipeR, &cbReadable);
+ if ( RT_SUCCESS(rc2)
+ && cbReadable)
+ {
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: idPollHnd=%s has %zu bytes left, vetoing close\n",
+ pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), cbReadable);
+#endif
+ /* Veto closing the pipe yet because there's still stuff to read
+ * from the pipe. This can happen on UNIX-y systems where on
+ * error/hangup there still can be data to be read out. */
+ fClosePipe = false;
+ }
+ }
+#ifdef DEBUG
+ else
+ VGSvcVerbose(3, "[PID %RU32]: idPollHnd=%s will be closed\n",
+ pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd));
+#endif
+
+ if ( *phPipeR != NIL_RTPIPE
+ && fClosePipe)
+ {
+ rc2 = RTPipeClose(*phPipeR);
+ AssertRC(rc2);
+ *phPipeR = NIL_RTPIPE;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Handle pending output data or error on standard out or standard error.
+ *
+ * @returns IPRT status code from client send.
+ * @param pProcess Process to handle pollset for.
+ * @param fPollEvt The event mask returned by RTPollNoResume.
+ * @param phPipeR The pipe handle.
+ * @param idPollHnd The pipe ID to handle.
+ *
+ */
+static int vgsvcGstCtrlProcessPollsetOnOutput(PVBOXSERVICECTRLPROCESS pProcess,
+ uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+#ifdef DEBUG
+ VGSvcVerbose(4, "[PID %RU32]: Output event phPipeR=%p, idPollHnd=%s, fPollEvt=0x%x\n",
+ pProcess->uPID, phPipeR, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), fPollEvt);
+#endif
+
+ if (!phPipeR)
+ return VINF_SUCCESS;
+
+ int rc = VINF_SUCCESS;
+
+#ifdef DEBUG
+ if (*phPipeR != NIL_RTPIPE)
+ {
+ size_t cbReadable;
+ rc = RTPipeQueryReadable(*phPipeR, &cbReadable);
+ if ( RT_SUCCESS(rc)
+ && cbReadable)
+ {
+ VGSvcVerbose(4, "[PID %RU32]: Output event cbReadable=%zu\n", pProcess->uPID, cbReadable);
+ }
+ }
+#endif
+
+#if 0
+ /* Push output to the host. */
+ if (fPollEvt & RTPOLL_EVT_READ)
+ {
+ size_t cbRead = 0;
+ uint8_t byData[_64K];
+ rc = RTPipeRead(*phPipeR, byData, sizeof(byData), &cbRead);
+ VGSvcVerbose(4, "VGSvcGstCtrlProcessHandleOutputEvent cbRead=%u, rc=%Rrc\n", cbRead, rc);
+
+ /* Make sure we go another poll round in case there was too much data
+ for the buffer to hold. */
+ fPollEvt &= RTPOLL_EVT_ERROR;
+ }
+#endif
+
+ if (fPollEvt & RTPOLL_EVT_ERROR)
+ rc = vgsvcGstCtrlProcessHandleOutputError(pProcess, fPollEvt, phPipeR, idPollHnd);
+ return rc;
+}
+
+
+/**
+ * Execution loop which runs in a dedicated per-started-process thread and
+ * handles all pipe input/output and signalling stuff.
+ *
+ * @return IPRT status code.
+ * @param pProcess The guest process to handle.
+ */
+static int vgsvcGstCtrlProcessProcLoop(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ int rc;
+ int rc2;
+ uint64_t const uMsStart = RTTimeMilliTS();
+ RTPROCSTATUS ProcessStatus = { 254, RTPROCEXITREASON_ABEND };
+ bool fProcessAlive = true;
+ bool fProcessTimedOut = false;
+ uint64_t MsProcessKilled = UINT64_MAX;
+ RTMSINTERVAL const cMsPollBase = pProcess->hPipeStdInW != NIL_RTPIPE
+ ? 100 /* Need to poll for input. */
+ : 1000; /* Need only poll for process exit and aborts. */
+ RTMSINTERVAL cMsPollCur = 0;
+
+ /*
+ * Assign PID to thread data.
+ * Also check if there already was a thread with the same PID and shut it down -- otherwise
+ * the first (stale) entry will be found and we get really weird results!
+ */
+ rc = vgsvcGstCtrlProcessAssignPID(pProcess, pProcess->hProcess /* Opaque PID handle */);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Unable to assign PID=%u, to new thread, rc=%Rrc\n", pProcess->hProcess, rc);
+ return rc;
+ }
+
+ /*
+ * Before entering the loop, tell the host that we've started the guest
+ * and that it's now OK to send input to the process.
+ */
+ VGSvcVerbose(2, "[PID %RU32]: Process '%s' started, CID=%u, User=%s, cMsTimeout=%RU32\n",
+ pProcess->uPID, pProcess->pStartupInfo->pszCmd, pProcess->uContextID,
+ pProcess->pStartupInfo->pszUser, pProcess->pStartupInfo->uTimeLimitMS);
+ VBGLR3GUESTCTRLCMDCTX ctxStart = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ };
+ rc = VbglR3GuestCtrlProcCbStatus(&ctxStart,
+ pProcess->uPID, PROC_STS_STARTED, 0 /* u32Flags */,
+ NULL /* pvData */, 0 /* cbData */);
+ if (rc == VERR_INTERRUPTED)
+ rc = VINF_SUCCESS; /* SIGCHLD send by quick childs! */
+ if (RT_FAILURE(rc))
+ VGSvcError("[PID %RU32]: Error reporting starting status to host, rc=%Rrc\n", pProcess->uPID, rc);
+
+ /*
+ * Process input, output, the test pipe and client requests.
+ */
+ while ( RT_SUCCESS(rc)
+ && RT_UNLIKELY(!pProcess->fShutdown))
+ {
+ /*
+ * Wait/Process all pending events.
+ */
+ uint32_t idPollHnd;
+ uint32_t fPollEvt;
+ rc2 = RTPollNoResume(pProcess->hPollSet, cMsPollCur, &fPollEvt, &idPollHnd);
+ if (pProcess->fShutdown)
+ continue;
+
+ cMsPollCur = 0; /* No rest until we've checked everything. */
+
+ if (RT_SUCCESS(rc2))
+ {
+ switch (idPollHnd)
+ {
+ case VBOXSERVICECTRLPIPEID_STDIN:
+ rc = vgsvcGstCtrlProcessPollsetOnInput(pProcess, fPollEvt, &pProcess->hPipeStdInW);
+ break;
+
+ case VBOXSERVICECTRLPIPEID_STDOUT:
+ rc = vgsvcGstCtrlProcessPollsetOnOutput(pProcess, fPollEvt, &pProcess->hPipeStdOutR, idPollHnd);
+ break;
+
+ case VBOXSERVICECTRLPIPEID_STDERR:
+ rc = vgsvcGstCtrlProcessPollsetOnOutput(pProcess, fPollEvt, &pProcess->hPipeStdErrR, idPollHnd);
+ break;
+
+ case VBOXSERVICECTRLPIPEID_IPC_NOTIFY:
+#ifdef DEBUG_andy
+ VGSvcVerbose(4, "[PID %RU32]: IPC notify\n", pProcess->uPID);
+#endif
+ rc2 = vgsvcGstCtrlProcessLock(pProcess);
+ if (RT_SUCCESS(rc2))
+ {
+ /* Drain the notification pipe. */
+ uint8_t abBuf[8];
+ size_t cbIgnore;
+ rc2 = RTPipeRead(pProcess->hNotificationPipeR, abBuf, sizeof(abBuf), &cbIgnore);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Draining IPC notification pipe failed with rc=%Rrc\n", rc2);
+
+ /* Process all pending requests. */
+ VGSvcVerbose(4, "[PID %RU32]: Processing pending requests ...\n", pProcess->uPID);
+ Assert(pProcess->hReqQueue != NIL_RTREQQUEUE);
+ rc2 = RTReqQueueProcess(pProcess->hReqQueue,
+ 0 /* Only process all pending requests, don't wait for new ones */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_TIMEOUT)
+ VGSvcError("Processing requests failed with with rc=%Rrc\n", rc2);
+
+ int rc3 = vgsvcGstCtrlProcessUnlock(pProcess);
+ AssertRC(rc3);
+#ifdef DEBUG
+ VGSvcVerbose(4, "[PID %RU32]: Processing pending requests done, rc=%Rrc\n", pProcess->uPID, rc2);
+#endif
+ }
+
+ break;
+
+ default:
+ AssertMsgFailed(("Unknown idPollHnd=%RU32\n", idPollHnd));
+ break;
+ }
+
+ if (RT_FAILURE(rc) || rc == VINF_EOF)
+ break; /* Abort command, or client dead or something. */
+ }
+#if 0
+ VGSvcVerbose(4, "[PID %RU32]: Polling done, pollRc=%Rrc, pollCnt=%RU32, idPollHnd=%s, rc=%Rrc, fProcessAlive=%RTbool, fShutdown=%RTbool\n",
+ pProcess->uPID, rc2, RTPollSetGetCount(hPollSet), vgsvcGstCtrlProcessPollHandleToString(idPollHnd), rc, fProcessAlive, pProcess->fShutdown);
+ VGSvcVerbose(4, "[PID %RU32]: stdOut=%s, stdErrR=%s\n",
+ pProcess->uPID,
+ *phStdOutR == NIL_RTPIPE ? "closed" : "open",
+ *phStdErrR == NIL_RTPIPE ? "closed" : "open");
+#endif
+ if (RT_UNLIKELY(pProcess->fShutdown))
+ break; /* We were asked to shutdown. */
+
+ /*
+ * Check for process death.
+ */
+ if (fProcessAlive)
+ {
+ rc2 = RTProcWaitNoResume(pProcess->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus);
+ if (RT_SUCCESS_NP(rc2))
+ {
+ fProcessAlive = false;
+ /* Note: Don't bail out here yet. First check in the next block below
+ * if all needed pipe outputs have been consumed. */
+ }
+ else
+ {
+ if (RT_UNLIKELY(rc2 == VERR_INTERRUPTED))
+ continue;
+ if (RT_UNLIKELY(rc2 == VERR_PROCESS_NOT_FOUND))
+ {
+ fProcessAlive = false;
+ ProcessStatus.enmReason = RTPROCEXITREASON_ABEND;
+ ProcessStatus.iStatus = 255;
+ AssertFailed();
+ }
+ else
+ AssertMsg(rc2 == VERR_PROCESS_RUNNING, ("%Rrc\n", rc2));
+ }
+ }
+
+ /*
+ * If the process has terminated and all output has been consumed,
+ * we should be heading out.
+ */
+ if (!fProcessAlive)
+ {
+ if ( fProcessTimedOut
+ || ( pProcess->hPipeStdOutR == NIL_RTPIPE
+ && pProcess->hPipeStdErrR == NIL_RTPIPE)
+ )
+ {
+ VGSvcVerbose(3, "[PID %RU32]: RTProcWaitNoResume=%Rrc\n", pProcess->uPID, rc2);
+ break;
+ }
+ }
+
+ /*
+ * Check for timed out, killing the process.
+ */
+ uint32_t cMilliesLeft = RT_INDEFINITE_WAIT;
+ if ( pProcess->pStartupInfo->uTimeLimitMS != RT_INDEFINITE_WAIT
+ && pProcess->pStartupInfo->uTimeLimitMS != 0)
+ {
+ uint64_t u64Now = RTTimeMilliTS();
+ uint64_t cMsElapsed = u64Now - uMsStart;
+ if (cMsElapsed >= pProcess->pStartupInfo->uTimeLimitMS)
+ {
+ fProcessTimedOut = true;
+ if ( MsProcessKilled == UINT64_MAX
+ || u64Now - MsProcessKilled > 1000)
+ {
+ if (u64Now - MsProcessKilled > 20*60*1000)
+ break; /* Give up after 20 mins. */
+
+ VGSvcVerbose(3, "[PID %RU32]: Timed out (%RU64ms elapsed > %RU32ms timeout), killing ...\n",
+ pProcess->uPID, cMsElapsed, pProcess->pStartupInfo->uTimeLimitMS);
+
+ rc2 = RTProcTerminate(pProcess->hProcess);
+ VGSvcVerbose(3, "[PID %RU32]: Killing process resulted in rc=%Rrc\n",
+ pProcess->uPID, rc2);
+ MsProcessKilled = u64Now;
+ continue;
+ }
+ cMilliesLeft = 10000;
+ }
+ else
+ cMilliesLeft = pProcess->pStartupInfo->uTimeLimitMS - (uint32_t)cMsElapsed;
+ }
+
+ /* Reset the polling interval since we've done all pending work. */
+ cMsPollCur = fProcessAlive
+ ? cMsPollBase
+ : RT_MS_1MIN;
+ if (cMilliesLeft < cMsPollCur)
+ cMsPollCur = cMilliesLeft;
+ }
+
+ VGSvcVerbose(3, "[PID %RU32]: Loop ended: rc=%Rrc, fShutdown=%RTbool, fProcessAlive=%RTbool, fProcessTimedOut=%RTbool, MsProcessKilled=%RU64 (%RX64)\n",
+ pProcess->uPID, rc, pProcess->fShutdown, fProcessAlive, fProcessTimedOut, MsProcessKilled, MsProcessKilled);
+ VGSvcVerbose(3, "[PID %RU32]: *phStdOutR=%s, *phStdErrR=%s\n",
+ pProcess->uPID,
+ pProcess->hPipeStdOutR == NIL_RTPIPE ? "closed" : "open",
+ pProcess->hPipeStdErrR == NIL_RTPIPE ? "closed" : "open");
+
+ /* Signal that this thread is in progress of shutting down. */
+ ASMAtomicWriteBool(&pProcess->fShutdown, true);
+
+ /*
+ * Try killing the process if it's still alive at this point.
+ */
+ if (fProcessAlive)
+ {
+ if (MsProcessKilled == UINT64_MAX)
+ {
+ VGSvcVerbose(2, "[PID %RU32]: Is still alive and not killed yet\n", pProcess->uPID);
+
+ MsProcessKilled = RTTimeMilliTS();
+ rc2 = RTProcTerminate(pProcess->hProcess);
+ if (rc2 == VERR_NOT_FOUND)
+ {
+ fProcessAlive = false;
+ }
+ else if (RT_FAILURE(rc2))
+ VGSvcError("[PID %RU32]: Killing process failed with rc=%Rrc\n", pProcess->uPID, rc2);
+ RTThreadSleep(500);
+ }
+
+ for (int i = 0; i < 10 && fProcessAlive; i++)
+ {
+ VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Waiting to exit ...\n", pProcess->uPID, i + 1);
+ rc2 = RTProcWait(pProcess->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus);
+ if (RT_SUCCESS(rc2))
+ {
+ VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Exited\n", pProcess->uPID, i + 1);
+ fProcessAlive = false;
+ break;
+ }
+ if (i >= 5)
+ {
+ VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Trying to terminate ...\n", pProcess->uPID, i + 1);
+ rc2 = RTProcTerminate(pProcess->hProcess);
+ if ( RT_FAILURE(rc)
+ && rc2 != VERR_NOT_FOUND)
+ VGSvcError("PID %RU32]: Killing process failed with rc=%Rrc\n",
+ pProcess->uPID, rc2);
+ }
+ RTThreadSleep(i >= 5 ? 2000 : 500);
+ }
+
+ if (fProcessAlive)
+ VGSvcError("[PID %RU32]: Could not be killed\n", pProcess->uPID);
+ }
+
+ /*
+ * Shutdown procedure:
+ * - Set the pProcess->fShutdown indicator to let others know we're
+ * not accepting any new requests anymore.
+ * - After setting the indicator, try to process all outstanding
+ * requests to make sure they're getting delivered.
+ *
+ * Note: After removing the process from the session's list it's not
+ * even possible for the session anymore to control what's
+ * happening to this thread, so be careful and don't mess it up.
+ */
+
+ rc2 = vgsvcGstCtrlProcessLock(pProcess);
+ if (RT_SUCCESS(rc2))
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Processing outstanding requests ...\n", pProcess->uPID);
+
+ /* Process all pending requests (but don't wait for new ones). */
+ Assert(pProcess->hReqQueue != NIL_RTREQQUEUE);
+ rc2 = RTReqQueueProcess(pProcess->hReqQueue, 0 /* No timeout */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_TIMEOUT)
+ VGSvcError("[PID %RU32]: Processing outstanding requests failed with with rc=%Rrc\n", pProcess->uPID, rc2);
+
+ VGSvcVerbose(3, "[PID %RU32]: Processing outstanding requests done, rc=%Rrc\n", pProcess->uPID, rc2);
+
+ rc2 = vgsvcGstCtrlProcessUnlock(pProcess);
+ AssertRC(rc2);
+ }
+
+ /*
+ * If we don't have a client problem (RT_FAILURE(rc)) we'll reply to the
+ * clients exec packet now.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uStatus = PROC_STS_UNDEFINED;
+ uint32_t fFlags = 0;
+
+ if ( fProcessTimedOut && !fProcessAlive && MsProcessKilled != UINT64_MAX)
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Timed out and got killed\n", pProcess->uPID);
+ uStatus = PROC_STS_TOK;
+ }
+ else if (fProcessTimedOut && fProcessAlive && MsProcessKilled != UINT64_MAX)
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Timed out and did *not* get killed\n", pProcess->uPID);
+ uStatus = PROC_STS_TOA;
+ }
+ else if (pProcess->fShutdown && (fProcessAlive || MsProcessKilled != UINT64_MAX))
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Got terminated because system/service is about to shutdown\n", pProcess->uPID);
+ uStatus = PROC_STS_DWN; /* Service is stopping, process was killed. */
+ fFlags = pProcess->pStartupInfo->fFlags; /* Return handed-in execution flags back to the host. */
+ }
+ else if (fProcessAlive)
+ VGSvcError("[PID %RU32]: Is alive when it should not!\n", pProcess->uPID);
+ else if (MsProcessKilled != UINT64_MAX)
+ VGSvcError("[PID %RU32]: Has been killed when it should not!\n", pProcess->uPID);
+ else if (ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL)
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_NORMAL (Exit code: %d)\n",
+ pProcess->uPID, ProcessStatus.iStatus);
+ uStatus = PROC_STS_TEN;
+ fFlags = ProcessStatus.iStatus;
+ }
+ else if (ProcessStatus.enmReason == RTPROCEXITREASON_SIGNAL)
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_SIGNAL (Signal: %u)\n",
+ pProcess->uPID, ProcessStatus.iStatus);
+ uStatus = PROC_STS_TES;
+ fFlags = ProcessStatus.iStatus;
+ }
+ else if (ProcessStatus.enmReason == RTPROCEXITREASON_ABEND)
+ {
+ /* ProcessStatus.iStatus will be undefined. */
+ VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_ABEND\n", pProcess->uPID);
+ uStatus = PROC_STS_TEA;
+ fFlags = ProcessStatus.iStatus;
+ }
+ else
+ VGSvcVerbose(1, "[PID %RU32]: Handling process status %u not implemented\n", pProcess->uPID, ProcessStatus.enmReason);
+ VBGLR3GUESTCTRLCMDCTX ctxEnd = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ };
+ VGSvcVerbose(2, "[PID %RU32]: Ended, ClientID=%u, CID=%u, Status=%u, Flags=0x%x\n",
+ pProcess->uPID, ctxEnd.uClientID, pProcess->uContextID, uStatus, fFlags);
+
+ rc2 = VbglR3GuestCtrlProcCbStatus(&ctxEnd, pProcess->uPID, uStatus, fFlags, NULL /* pvData */, 0 /* cbData */);
+ if ( RT_FAILURE(rc2)
+ && rc2 == VERR_NOT_FOUND)
+ VGSvcError("[PID %RU32]: Error reporting final status to host; rc=%Rrc\n", pProcess->uPID, rc2);
+ }
+
+ VGSvcVerbose(3, "[PID %RU32]: Process loop returned with rc=%Rrc\n", pProcess->uPID, rc);
+ return rc;
+}
+
+
+#if 0 /* unused */
+/**
+ * Initializes a pipe's handle and pipe object.
+ *
+ * @return IPRT status code.
+ * @param ph The pipe's handle to initialize.
+ * @param phPipe The pipe's object to initialize.
+ */
+static int vgsvcGstCtrlProcessInitPipe(PRTHANDLE ph, PRTPIPE phPipe)
+{
+ AssertPtrReturn(ph, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phPipe, VERR_INVALID_PARAMETER);
+
+ ph->enmType = RTHANDLETYPE_PIPE;
+ ph->u.hPipe = NIL_RTPIPE;
+ *phPipe = NIL_RTPIPE;
+
+ return VINF_SUCCESS;
+}
+#endif
+
+
+/**
+ * Sets up the redirection / pipe / nothing for one of the standard handles.
+ *
+ * @returns IPRT status code. No client replies made.
+ * @param pszHowTo How to set up this standard handle.
+ * @param fd Which standard handle it is (0 == stdin, 1 ==
+ * stdout, 2 == stderr).
+ * @param ph The generic handle that @a pph may be set
+ * pointing to. Always set.
+ * @param pph Pointer to the RTProcCreateExec argument.
+ * Always set.
+ * @param phPipe Where to return the end of the pipe that we
+ * should service.
+ */
+static int vgsvcGstCtrlProcessSetupPipe(const char *pszHowTo, int fd, PRTHANDLE ph, PRTHANDLE *pph, PRTPIPE phPipe)
+{
+ AssertPtrReturn(ph, VERR_INVALID_POINTER);
+ AssertPtrReturn(pph, VERR_INVALID_POINTER);
+ AssertPtrReturn(phPipe, VERR_INVALID_POINTER);
+
+ int rc;
+
+ ph->enmType = RTHANDLETYPE_PIPE;
+ ph->u.hPipe = NIL_RTPIPE;
+ *pph = NULL;
+ *phPipe = NIL_RTPIPE;
+
+ if (!strcmp(pszHowTo, "|"))
+ {
+ /*
+ * Setup a pipe for forwarding to/from the client.
+ * The ph union struct will be filled with a pipe read/write handle
+ * to represent the "other" end to phPipe.
+ */
+ if (fd == 0) /* stdin? */
+ {
+ /* Connect a wrtie pipe specified by phPipe to stdin. */
+ rc = RTPipeCreate(&ph->u.hPipe, phPipe, RTPIPE_C_INHERIT_READ);
+ }
+ else /* stdout or stderr. */
+ {
+ /* Connect a read pipe specified by phPipe to stdout or stderr. */
+ rc = RTPipeCreate(phPipe, &ph->u.hPipe, RTPIPE_C_INHERIT_WRITE);
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ ph->enmType = RTHANDLETYPE_PIPE;
+ *pph = ph;
+ }
+ else if (!strcmp(pszHowTo, "/dev/null"))
+ {
+ /*
+ * Redirect to/from /dev/null.
+ */
+ RTFILE hFile;
+ rc = RTFileOpenBitBucket(&hFile, fd == 0 ? RTFILE_O_READ : RTFILE_O_WRITE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ ph->enmType = RTHANDLETYPE_FILE;
+ ph->u.hFile = hFile;
+ *pph = ph;
+ }
+ else /* Add other piping stuff here. */
+ rc = VINF_SUCCESS; /* Same as parent (us). */
+
+ return rc;
+}
+
+
+/**
+ * Expands a file name / path to its real content.
+ *
+ * ~~This only works on Windows for now (e.g. translating "%TEMP%\foo.exe" to
+ * "C:\Windows\Temp" when starting with system / administrative rights).~~ See
+ * todo in code.
+ *
+ * @return IPRT status code.
+ * @param pszPath Path to resolve.
+ * @param pszExpanded Pointer to string to store the resolved path in.
+ * @param cbExpanded Size (in bytes) of string to store the resolved path.
+ */
+static int vgsvcGstCtrlProcessMakeFullPath(const char *pszPath, char *pszExpanded, size_t cbExpanded)
+{
+/** @todo r=bird: This feature shall be made optional, i.e. require a
+ * flag to be passed down. Further, it shall work on the environment
+ * block of the new process (i.e. include env changes passed down from
+ * the caller). I would also suggest using the unix variable expansion
+ * syntax, not the DOS one.
+ *
+ * Since this currently not available on non-windows guests, I suggest
+ * we disable it until such a time as it is implemented correctly. */
+#if 0 /*def RT_OS_WINDOWS - see above. Don't know why this wasn't disabled before 7.0, didn't see the @todo yet? */
+ int rc = VINF_SUCCESS;
+ if (!ExpandEnvironmentStrings(pszPath, pszExpanded, (DWORD)cbExpanded))
+ rc = RTErrConvertFromWin32(GetLastError());
+#else
+ /* There is no expansion anywhere yet, see above @todo. */
+ int rc = RTStrCopy(pszExpanded, cbExpanded, pszPath);
+#endif
+#ifdef DEBUG
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessMakeFullPath: %s -> %s\n", pszPath, pszExpanded);
+#endif
+ return rc;
+}
+
+
+/**
+ * Resolves the full path of a specified executable name.
+ *
+ * This function also resolves internal VBoxService tools to its appropriate
+ * executable path + name if VBOXSERVICE_NAME is specified as pszFilename.
+ *
+ * @return IPRT status code.
+ * @param pszFilename File name to resolve.
+ * @param pszResolved Pointer to a string where the resolved file name will be stored.
+ * @param cbResolved Size (in bytes) of resolved file name string.
+ */
+static int vgsvcGstCtrlProcessResolveExecutable(const char *pszFilename, char *pszResolved, size_t cbResolved)
+{
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszResolved, VERR_INVALID_POINTER);
+ AssertReturn(cbResolved, VERR_INVALID_PARAMETER);
+
+ const char * const pszOrgFilename = pszFilename;
+ if ( RTStrICmp(pszFilename, g_pszProgName) == 0
+ || RTStrICmp(pszFilename, VBOXSERVICE_NAME) == 0)
+ pszFilename = RTProcExecutablePath();
+
+ int rc = vgsvcGstCtrlProcessMakeFullPath(pszFilename, pszResolved, cbResolved);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "Looked up executable: %s -> %s\n", pszOrgFilename, pszResolved);
+ return rc;
+}
+
+
+/**
+ * Constructs the argv command line by resolving environment variables
+ * and relative paths.
+ *
+ * @return IPRT status code.
+ * @param pszArgv0 First argument (argv0), either original or modified version.
+ * @param papszArgs Original argv command line from the host, starting at argv[1].
+ * @param fFlags The process creation flags pass to us from the host.
+ * @param fExecutingSelf Set if we're executing the VBoxService executable
+ * and should inject the --utf8-argv trick.
+ * @param ppapszArgv Pointer to a pointer with the new argv command line.
+ * Needs to be freed with RTGetOptArgvFree.
+ */
+static int vgsvcGstCtrlProcessAllocateArgv(const char *pszArgv0, const char * const *papszArgs, uint32_t fFlags,
+ bool fExecutingSelf, char ***ppapszArgv)
+{
+ VGSvcVerbose(3, "VGSvcGstCtrlProcessPrepareArgv: pszArgv0=%p, papszArgs=%p, fFlags=%#x, fExecutingSelf=%d, ppapszArgv=%p\n",
+ pszArgv0, papszArgs, fFlags, fExecutingSelf, ppapszArgv);
+
+ AssertPtrReturn(pszArgv0, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppapszArgv, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & GUEST_PROC_CREATE_FLAG_EXPAND_ARGUMENTS), VERR_INVALID_FLAGS); /** @todo implement me */
+
+#ifndef VBOXSERVICE_ARG1_UTF8_ARGV
+ fExecutingSelf = false;
+#endif
+
+ /* Count arguments: */
+ int rc = VINF_SUCCESS;
+ uint32_t cArgs;
+ for (cArgs = 0; papszArgs[cArgs]; cArgs++)
+ {
+ if (cArgs >= UINT32_MAX - 2)
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* Allocate new argv vector (adding + 2 for argv0 + termination). */
+ size_t cbSize = (fExecutingSelf + cArgs + 2) * sizeof(char *);
+ char **papszNewArgv = (char **)RTMemAlloc(cbSize);
+ if (!papszNewArgv)
+ return VERR_NO_MEMORY;
+
+ VGSvcVerbose(3, "VGSvcGstCtrlProcessAllocateArgv: pszArgv0 = '%s', cArgs=%RU32, cbSize=%zu\n", pszArgv0, cArgs, cbSize);
+#ifdef DEBUG /* Never log this stuff in release mode! */
+ if (cArgs)
+ {
+ for (uint32_t i = 0; i < cArgs; i++)
+ VGSvcVerbose(3, "VGSvcGstCtrlProcessAllocateArgv: papszArgs[%RU32] = '%s'\n", i, papszArgs[i]);
+ }
+#endif
+
+ /* HACK ALERT! Older hosts (< VBox 6.1.x) did not allow the user to really specify
+ the first argument separately from the executable image, so we have
+ to fudge a little in the unquoted argument case to deal with executables
+ containing spaces. Windows only, as RTPROC_FLAGS_UNQUOTED_ARGS is
+ ignored on all other hosts. */
+#ifdef RT_OS_WINDOWS
+ if ( (fFlags & GUEST_PROC_CREATE_FLAG_UNQUOTED_ARGS)
+ && strpbrk(pszArgv0, " \t\n\r")
+ && pszArgv0[0] == '"')
+ {
+ size_t cchArgv0 = strlen(pszArgv0);
+ AssertReturn(cchArgv0, VERR_INVALID_PARAMETER); /* Paranoia. */
+ rc = RTStrAllocEx(&papszNewArgv[0], 1 + cchArgv0 + 1 + 1);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszDst = papszNewArgv[0];
+ *pszDst++ = '"';
+ memcpy(pszDst, pszArgv0, cchArgv0);
+ pszDst += cchArgv0;
+ *pszDst++ = '"';
+ *pszDst = '\0';
+ }
+ }
+ else
+#endif
+ rc = RTStrDupEx(&papszNewArgv[0], pszArgv0);
+ if (RT_SUCCESS(rc))
+ {
+ size_t iDst = 1;
+
+#ifdef VBOXSERVICE_ARG1_UTF8_ARGV
+ /* Insert --utf8-argv as the first argument if executing the VBoxService binary. */
+ if (fExecutingSelf)
+ {
+ rc = RTStrDupEx(&papszNewArgv[iDst], VBOXSERVICE_ARG1_UTF8_ARGV);
+ if (RT_SUCCESS(rc))
+ iDst++;
+ }
+#endif
+ /* Copy over the other arguments. */
+ if (RT_SUCCESS(rc))
+ for (size_t iSrc = 0; iSrc < cArgs; iSrc++)
+ {
+#if 0 /* Arguments expansion -- untested. */
+ if (fFlags & GUEST_PROC_CREATE_FLAG_EXPAND_ARGUMENTS)
+ {
+/** @todo r=bird: If you want this, we need a generic implementation, preferably in RTEnv or somewhere like that. The marking
+ * up of the variables must be the same on all platforms. */
+ /* According to MSDN the limit on older Windows version is 32K, whereas
+ * Vista+ there are no limits anymore. We still stick to 4K. */
+ char szExpanded[_4K];
+# ifdef RT_OS_WINDOWS
+ if (!ExpandEnvironmentStrings(papszArgs[i], szExpanded, sizeof(szExpanded)))
+ rc = RTErrConvertFromWin32(GetLastError());
+# else
+ /* No expansion for non-Windows yet. */
+ rc = RTStrCopy(papszArgs[i], sizeof(szExpanded), szExpanded);
+# endif
+ if (RT_SUCCESS(rc))
+ rc = RTStrDupEx(&pszArg, szExpanded);
+ }
+ else
+#endif
+ rc = RTStrDupEx(&papszNewArgv[iDst], papszArgs[iSrc]);
+ if (RT_SUCCESS(rc))
+ iDst++;
+ else
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Terminate array. */
+ papszNewArgv[iDst] = NULL;
+
+ *ppapszArgv = papszNewArgv;
+ return VINF_SUCCESS;
+ }
+
+ /* Failed, bail out. */
+ while (iDst-- > 0)
+ RTStrFree(papszNewArgv[iDst]);
+ }
+ RTMemFree(papszNewArgv);
+ return rc;
+}
+
+
+/**
+ * Assigns a valid PID to a guest control thread and also checks if there already was
+ * another (stale) guest process which was using that PID before and destroys it.
+ *
+ * @return IPRT status code.
+ * @param pProcess Process to assign PID to.
+ * @param uPID PID to assign to the specified guest control execution thread.
+ */
+static int vgsvcGstCtrlProcessAssignPID(PVBOXSERVICECTRLPROCESS pProcess, uint32_t uPID)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ AssertReturn(uPID, VERR_INVALID_PARAMETER);
+
+ AssertPtr(pProcess->pSession);
+ int rc = RTCritSectEnter(&pProcess->pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /* Search old threads using the desired PID and shut them down completely -- it's
+ * not used anymore. */
+ bool fTryAgain;
+ do
+ {
+ fTryAgain = false;
+ PVBOXSERVICECTRLPROCESS pProcessCur;
+ RTListForEach(&pProcess->pSession->lstProcesses, pProcessCur, VBOXSERVICECTRLPROCESS, Node)
+ {
+ if (pProcessCur->uPID == uPID)
+ {
+ Assert(pProcessCur != pProcess); /* can't happen */
+ uint32_t uTriedPID = uPID;
+ uPID += 391939;
+ VGSvcVerbose(2, "PID %RU32 was used before (process %p), trying again with %RU32 ...\n",
+ uTriedPID, pProcessCur, uPID);
+ fTryAgain = true;
+ break;
+ }
+ }
+ } while (fTryAgain);
+
+ /* Assign PID to current thread. */
+ pProcess->uPID = uPID;
+
+ rc = RTCritSectLeave(&pProcess->pSession->CritSect);
+ AssertRC(rc);
+ }
+
+ return rc;
+}
+
+
+static void vgsvcGstCtrlProcessFreeArgv(char **papszArgv)
+{
+ if (papszArgv)
+ {
+ size_t i = 0;
+ while (papszArgv[i])
+ RTStrFree(papszArgv[i++]);
+ RTMemFree(papszArgv);
+ }
+}
+
+
+/**
+ * Helper function to create/start a process on the guest.
+ *
+ * @return IPRT status code.
+ * @param pszExec Full qualified path of process to start (without arguments).
+ * @param papszArgs Pointer to array of command line arguments.
+ * @param hEnv Handle to environment block to use.
+ * @param fFlags Process execution flags.
+ * @param phStdIn Handle for the process' stdin pipe.
+ * @param phStdOut Handle for the process' stdout pipe.
+ * @param phStdErr Handle for the process' stderr pipe.
+ * @param pszAsUser User name (account) to start the process under.
+ * @param pszPassword Password of the specified user.
+ * @param pszDomain Domain to use for authentication.
+ * @param phProcess Pointer which will receive the process handle after
+ * successful process start.
+ */
+static int vgsvcGstCtrlProcessCreateProcess(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
+ PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr,
+ const char *pszAsUser, const char *pszPassword, const char *pszDomain,
+ PRTPROCESS phProcess)
+{
+#ifndef RT_OS_WINDOWS
+ RT_NOREF1(pszDomain);
+#endif
+ AssertPtrReturn(pszExec, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
+ /* phStdIn is optional. */
+ /* phStdOut is optional. */
+ /* phStdErr is optional. */
+ /* pszPassword is optional. */
+ /* pszDomain is optional. */
+ AssertPtrReturn(phProcess, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ char szExecExp[RTPATH_MAX];
+
+#ifdef DEBUG
+ /* Never log this in release mode! */
+ VGSvcVerbose(4, "pszUser=%s, pszPassword=%s, pszDomain=%s\n", pszAsUser, pszPassword, pszDomain);
+#endif
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * If sysprep should be executed do this in the context of VBoxService, which
+ * (usually, if started by SCM) has administrator rights. Because of that a UI
+ * won't be shown (doesn't have a desktop).
+ */
+ if (!RTStrICmp(pszExec, "sysprep"))
+ {
+ /* Use a predefined sysprep path as default. */
+ char szSysprepCmd[RTPATH_MAX] = "C:\\sysprep\\sysprep.exe";
+ /** @todo Check digital signature of file above before executing it? */
+
+ /*
+ * On Windows Vista (and up) sysprep is located in "system32\\Sysprep\\sysprep.exe",
+ * so detect the OS and use a different path.
+ */
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6,0,0) /* Vista and later */)
+ {
+ rc = RTEnvGetEx(RTENV_DEFAULT, "windir", szSysprepCmd, sizeof(szSysprepCmd), NULL);
+#ifndef RT_ARCH_AMD64
+ /* Don't execute 64-bit sysprep from a 32-bit service host! */
+ char szSysWow64[RTPATH_MAX];
+ if (RTStrPrintf(szSysWow64, sizeof(szSysWow64), "%s", szSysprepCmd))
+ {
+ rc = RTPathAppend(szSysWow64, sizeof(szSysWow64), "SysWow64");
+ AssertRC(rc);
+ }
+ if ( RT_SUCCESS(rc)
+ && RTPathExists(szSysWow64))
+ VGSvcVerbose(0, "Warning: This service is 32-bit; could not execute sysprep on 64-bit OS!\n");
+#endif
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(szSysprepCmd, sizeof(szSysprepCmd), "system32\\Sysprep\\sysprep.exe");
+ if (RT_SUCCESS(rc))
+ RTPathChangeToDosSlashes(szSysprepCmd, false /* No forcing necessary */);
+
+ if (RT_FAILURE(rc))
+ VGSvcError("Failed to detect sysrep location, rc=%Rrc\n", rc);
+ }
+
+ VGSvcVerbose(3, "Sysprep executable is: %s\n", szSysprepCmd);
+
+ if (RT_SUCCESS(rc))
+ {
+ char **papszArgsExp;
+ rc = vgsvcGstCtrlProcessAllocateArgv(szSysprepCmd /* argv0 */, papszArgs, fFlags,
+ false /*fExecutingSelf*/, &papszArgsExp);
+ if (RT_SUCCESS(rc))
+ {
+ /* As we don't specify credentials for the sysprep process, it will
+ * run under behalf of the account VBoxService was started under, most
+ * likely local system. */
+ rc = RTProcCreateEx(szSysprepCmd, papszArgsExp, hEnv, 0 /* fFlags */,
+ phStdIn, phStdOut, phStdErr, NULL /* pszAsUser */,
+ NULL /* pszPassword */, NULL, phProcess);
+ vgsvcGstCtrlProcessFreeArgv(papszArgsExp);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ VGSvcVerbose(3, "Starting sysprep returned rc=%Rrc\n", rc);
+
+ return rc;
+ }
+#endif /* RT_OS_WINDOWS */
+
+ bool fExecutingSelf = false;
+#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX
+ /* The "vbox_" prefix is reserved for the toolbox (vbox_cat, vbox_mkdir,
+ et al.) and we will replace pszExec with the full VBoxService path instead. */
+ if (RTStrStartsWith(pszExec, "vbox_"))
+ {
+ fExecutingSelf = true;
+ rc = vgsvcGstCtrlProcessResolveExecutable(VBOXSERVICE_NAME, szExecExp, sizeof(szExecExp));
+ }
+ else
+ {
+#endif
+ /*
+ * Do the environment variables expansion on executable and arguments.
+ */
+ rc = vgsvcGstCtrlProcessResolveExecutable(pszExec, szExecExp, sizeof(szExecExp));
+#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX
+ }
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * This one is a bit tricky to also support older hosts:
+ *
+ * - If the host does not provide a dedicated argv[0] (< VBox 6.1.x), we use the
+ * unmodified executable name (pszExec) as the (default) argv[0]. This is wrong, but we can't do
+ * much about it. The rest (argv[1,2,n]) then gets set starting at papszArgs[0].
+ *
+ * - Newer hosts (>= VBox 6.1.x) provide a correct argv[0] independently of the actual
+ * executable name though, so actually use argv[0] *and* argv[1,2,n] as intended.
+ */
+ const bool fHasArgv0 = RT_BOOL(g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_PROCESS_ARGV0);
+
+ const char *pcszArgv0 = (fHasArgv0 && papszArgs[0]) ? papszArgs[0] : pszExec;
+ AssertPtrReturn(pcszArgv0, VERR_INVALID_POINTER); /* Paranoia. */
+
+ const uint32_t uArgvIdx = pcszArgv0 == papszArgs[0] ? 1 : 0;
+
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessCreateProcess: fHasArgv0=%RTbool, pcszArgv0=%p, uArgvIdx=%RU32, "
+ "g_fControlHostFeatures0=%#x\n",
+ fHasArgv0, pcszArgv0, uArgvIdx, g_fControlHostFeatures0);
+
+ char **papszArgsExp;
+ rc = vgsvcGstCtrlProcessAllocateArgv(pcszArgv0, &papszArgs[uArgvIdx], fFlags, fExecutingSelf, &papszArgsExp);
+ if (RT_FAILURE(rc))
+ {
+ /* Don't print any arguments -- may contain passwords or other sensible data! */
+ VGSvcError("Could not prepare arguments, rc=%Rrc\n", rc);
+ }
+ else
+ {
+ uint32_t fProcCreateFlags = 0;
+ if (fExecutingSelf)
+ fProcCreateFlags |= VBOXSERVICE_PROC_F_UTF8_ARGV;
+ if (fFlags)
+ {
+ if (fFlags & GUEST_PROC_CREATE_FLAG_HIDDEN)
+ fProcCreateFlags |= RTPROC_FLAGS_HIDDEN;
+ if (fFlags & GUEST_PROC_CREATE_FLAG_PROFILE)
+ fProcCreateFlags |= RTPROC_FLAGS_PROFILE;
+ if (fFlags & GUEST_PROC_CREATE_FLAG_UNQUOTED_ARGS)
+ fProcCreateFlags |= RTPROC_FLAGS_UNQUOTED_ARGS;
+ }
+
+ /* If no user name specified run with current credentials (e.g.
+ * full service/system rights). This is prohibited via official Main API!
+ *
+ * Otherwise use the RTPROC_FLAGS_SERVICE to use some special authentication
+ * code (at least on Windows) for running processes as different users
+ * started from our system service. */
+ if (pszAsUser && *pszAsUser)
+ fProcCreateFlags |= RTPROC_FLAGS_SERVICE;
+#ifdef DEBUG
+ VGSvcVerbose(3, "Command: %s\n", szExecExp);
+ for (size_t i = 0; papszArgsExp[i]; i++)
+ VGSvcVerbose(3, " argv[%zu]: %s\n", i, papszArgsExp[i]);
+#endif
+ VGSvcVerbose(3, "Starting process '%s' ...\n", szExecExp);
+
+#ifdef RT_OS_WINDOWS
+ /* If a domain name is given, construct an UPN (User Principle Name) with
+ * the domain name built-in, e.g. "joedoe@example.com". */
+ char *pszUserUPN = NULL;
+ if (pszDomain && *pszDomain != '\0')
+ {
+ pszAsUser = pszUserUPN = RTStrAPrintf2("%s@%s", pszAsUser, pszDomain);
+ if (pszAsUser)
+ VGSvcVerbose(3, "Using UPN: %s\n", pszAsUser);
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ if (RT_SUCCESS(rc))
+#endif
+ {
+ /* Do normal execution. */
+ rc = RTProcCreateEx(szExecExp, papszArgsExp, hEnv, fProcCreateFlags,
+ phStdIn, phStdOut, phStdErr,
+ pszAsUser,
+ pszPassword && *pszPassword ? pszPassword : NULL,
+ NULL /*pvExtraData*/,
+ phProcess);
+
+#ifdef RT_OS_WINDOWS
+ RTStrFree(pszUserUPN);
+#endif
+ VGSvcVerbose(3, "Starting process '%s' returned rc=%Rrc\n", szExecExp, rc);
+ }
+ vgsvcGstCtrlProcessFreeArgv(papszArgsExp);
+ }
+ }
+ return rc;
+}
+
+
+#ifdef DEBUG
+/**
+ * Dumps content to a file in the OS temporary directory.
+ *
+ * @returns VBox status code.
+ * @param pvBuf Buffer of content to dump.
+ * @param cbBuf Size (in bytes) of content to dump.
+ * @param pszFileNmFmt Pointer to the file name format string, @see pg_rt_str_format.
+ * @param ... The format argument.
+ */
+static int vgsvcGstCtrlProcessDbgDumpToFileF(const void *pvBuf, size_t cbBuf, const char *pszFileNmFmt, ...)
+{
+ AssertPtrReturn(pszFileNmFmt, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+
+ if (!cbBuf)
+ return VINF_SUCCESS;
+
+ va_list va;
+ va_start(va, pszFileNmFmt);
+
+ char *pszFileName = NULL;
+ const int cchFileName = RTStrAPrintfV(&pszFileName, pszFileNmFmt, va);
+
+ va_end(va);
+
+ if (!cchFileName)
+ return VERR_NO_MEMORY;
+
+ char szPathFileAbs[RTPATH_MAX];
+ int rc = RTPathTemp(szPathFileAbs, sizeof(szPathFileAbs));
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(szPathFileAbs, sizeof(szPathFileAbs), pszFileName);
+
+ RTStrFree(pszFileName);
+
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "Dumping %zu bytes to '%s'\n", cbBuf, szPathFileAbs);
+
+ RTFILE fh;
+ rc = RTFileOpen(&fh, szPathFileAbs, RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(fh, pvBuf, cbBuf, NULL /* pcbWritten */);
+ RTFileClose(fh);
+ }
+ }
+
+ return rc;
+}
+#endif /* DEBUG */
+
+
+/**
+ * The actual worker routine (loop) for a started guest process.
+ *
+ * @return IPRT status code.
+ * @param pProcess The process we're servicing and monitoring.
+ */
+static int vgsvcGstCtrlProcessProcessWorker(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ VGSvcVerbose(3, "Thread of process pThread=0x%p = '%s' started\n", pProcess, pProcess->pStartupInfo->pszCmd);
+
+ VGSvcVerbose(3, "Guest process '%s', flags=0x%x\n", pProcess->pStartupInfo->pszCmd, pProcess->pStartupInfo->fFlags);
+
+ int rc = VGSvcGstCtrlSessionProcessAdd(pProcess->pSession, pProcess);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Error while adding guest process '%s' (%p) to session process list, rc=%Rrc\n",
+ pProcess->pStartupInfo->pszCmd, pProcess, rc);
+ RTThreadUserSignal(RTThreadSelf());
+ return rc;
+ }
+
+ bool fSignalled = false; /* Indicator whether we signalled the thread user event already. */
+
+ /*
+ * Prepare argument list.
+ */
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: fHostFeatures0 = %#x\n", g_fControlHostFeatures0);
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.szCmd = '%s'\n", pProcess->pStartupInfo->pszCmd);
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.uNumArgs = '%RU32'\n", pProcess->pStartupInfo->cArgs);
+#ifdef DEBUG /* Never log this stuff in release mode! */
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.szArgs = '%s'\n", pProcess->pStartupInfo->pszArgs);
+#endif
+
+ char **papszArgs;
+ int cArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */
+ rc = RTGetOptArgvFromString(&papszArgs, &cArgs,
+ pProcess->pStartupInfo->cArgs > 0 ? pProcess->pStartupInfo->pszArgs : "",
+ RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL);
+
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: cArgs = %d\n", cArgs);
+#ifdef VBOX_STRICT
+ for (int i = 0; i < cArgs; i++)
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: papszArgs[%d] = '%s'\n", i, papszArgs[i] ? papszArgs[i] : "<NULL>");
+
+ const bool fHasArgv0 = RT_BOOL(g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_PROCESS_ARGV0); RT_NOREF(fHasArgv0);
+ const int cArgsToCheck = cArgs + (fHasArgv0 ? 0 : 1);
+
+ /* Did we get the same result?
+ * Take into account that we might not have supplied a (correct) argv[0] from the host. */
+ AssertMsg((int)pProcess->pStartupInfo->cArgs == cArgsToCheck,
+ ("rc=%Rrc, StartupInfo.uNumArgs=%RU32 != cArgsToCheck=%d, cArgs=%d, fHostFeatures0=%#x\n",
+ rc, pProcess->pStartupInfo->cArgs, cArgsToCheck, cArgs, g_fControlHostFeatures0));
+#endif
+
+ /*
+ * Create the environment.
+ */
+ uint32_t const cbEnv = pProcess->pStartupInfo->cbEnv;
+ if (RT_SUCCESS(rc))
+ AssertStmt( cbEnv <= GUEST_PROC_MAX_ENV_LEN
+ || pProcess->pStartupInfo->cEnvVars == 0,
+ rc = VERR_INVALID_PARAMETER);
+ if (RT_SUCCESS(rc))
+ {
+ RTENV hEnv;
+ rc = RTEnvClone(&hEnv, RTENV_DEFAULT);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "Additional environment variables: %RU32 (%RU32 bytes)\n",
+ pProcess->pStartupInfo->cEnvVars, cbEnv);
+
+ if ( pProcess->pStartupInfo->cEnvVars
+ && cbEnv > 0)
+ {
+ size_t offCur = 0;
+ while (offCur < cbEnv)
+ {
+ const char * const pszCur = &pProcess->pStartupInfo->pszEnv[offCur];
+ size_t const cchCur = RTStrNLen(pszCur, cbEnv - offCur);
+ AssertBreakStmt(cchCur < cbEnv - offCur, rc = VERR_INVALID_PARAMETER);
+ VGSvcVerbose(3, "Setting environment variable: '%s'\n", pszCur);
+ rc = RTEnvPutEx(hEnv, pszCur);
+ if (RT_SUCCESS(rc))
+ offCur += cchCur + 1;
+ else
+ {
+ VGSvcError("Setting environment variable '%s' failed: %Rrc\n", pszCur, rc);
+ break;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Setup the redirection of the standard stuff.
+ */
+ /** @todo consider supporting: gcc stuff.c >file 2>&1. */
+ RTHANDLE hStdIn;
+ PRTHANDLE phStdIn;
+ rc = vgsvcGstCtrlProcessSetupPipe("|", 0 /*STDIN_FILENO*/,
+ &hStdIn, &phStdIn, &pProcess->hPipeStdInW);
+ if (RT_SUCCESS(rc))
+ {
+ RTHANDLE hStdOut;
+ PRTHANDLE phStdOut;
+ rc = vgsvcGstCtrlProcessSetupPipe( (pProcess->pStartupInfo->fFlags & GUEST_PROC_CREATE_FLAG_WAIT_STDOUT)
+ ? "|" : "/dev/null",
+ 1 /*STDOUT_FILENO*/,
+ &hStdOut, &phStdOut, &pProcess->hPipeStdOutR);
+ if (RT_SUCCESS(rc))
+ {
+ RTHANDLE hStdErr;
+ PRTHANDLE phStdErr;
+ rc = vgsvcGstCtrlProcessSetupPipe( (pProcess->pStartupInfo->fFlags & GUEST_PROC_CREATE_FLAG_WAIT_STDERR)
+ ? "|" : "/dev/null",
+ 2 /*STDERR_FILENO*/,
+ &hStdErr, &phStdErr, &pProcess->hPipeStdErrR);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a poll set for the pipes and let the
+ * transport layer add stuff to it as well.
+ */
+ rc = RTPollSetCreate(&pProcess->hPollSet);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uFlags = RTPOLL_EVT_ERROR;
+#if 0
+ /* Add reading event to pollset to get some more information. */
+ uFlags |= RTPOLL_EVT_READ;
+#endif
+ /* Stdin. */
+ if (RT_SUCCESS(rc))
+ rc = RTPollSetAddPipe(pProcess->hPollSet,
+ pProcess->hPipeStdInW, RTPOLL_EVT_ERROR, VBOXSERVICECTRLPIPEID_STDIN);
+ /* Stdout. */
+ if (RT_SUCCESS(rc))
+ rc = RTPollSetAddPipe(pProcess->hPollSet,
+ pProcess->hPipeStdOutR, uFlags, VBOXSERVICECTRLPIPEID_STDOUT);
+ /* Stderr. */
+ if (RT_SUCCESS(rc))
+ rc = RTPollSetAddPipe(pProcess->hPollSet,
+ pProcess->hPipeStdErrR, uFlags, VBOXSERVICECTRLPIPEID_STDERR);
+ /* IPC notification pipe. */
+ if (RT_SUCCESS(rc))
+ rc = RTPipeCreate(&pProcess->hNotificationPipeR, &pProcess->hNotificationPipeW, 0 /* Flags */);
+ if (RT_SUCCESS(rc))
+ rc = RTPollSetAddPipe(pProcess->hPollSet,
+ pProcess->hNotificationPipeR, RTPOLL_EVT_READ, VBOXSERVICECTRLPIPEID_IPC_NOTIFY);
+ if (RT_SUCCESS(rc))
+ {
+ AssertPtr(pProcess->pSession);
+ bool fNeedsImpersonation = !(pProcess->pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_SPAWN);
+
+ rc = vgsvcGstCtrlProcessCreateProcess(pProcess->pStartupInfo->pszCmd, papszArgs, hEnv,
+ pProcess->pStartupInfo->fFlags,
+ phStdIn, phStdOut, phStdErr,
+ fNeedsImpersonation ? pProcess->pStartupInfo->pszUser : NULL,
+ fNeedsImpersonation ? pProcess->pStartupInfo->pszPassword : NULL,
+ fNeedsImpersonation ? pProcess->pStartupInfo->pszDomain : NULL,
+ &pProcess->hProcess);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error starting process, rc=%Rrc\n", rc);
+ /*
+ * Tell the session thread that it can continue
+ * spawning guest processes. This needs to be done after the new
+ * process has been started because otherwise signal handling
+ * on (Open) Solaris does not work correctly (see @bugref{5068}).
+ */
+ int rc2 = RTThreadUserSignal(RTThreadSelf());
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ fSignalled = true;
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Close the child ends of any pipes and redirected files.
+ */
+ rc2 = RTHandleClose(phStdIn); AssertRC(rc2);
+ phStdIn = NULL;
+ rc2 = RTHandleClose(phStdOut); AssertRC(rc2);
+ phStdOut = NULL;
+ rc2 = RTHandleClose(phStdErr); AssertRC(rc2);
+ phStdErr = NULL;
+
+ /* Enter the process main loop. */
+ rc = vgsvcGstCtrlProcessProcLoop(pProcess);
+
+ /*
+ * The handles that are no longer in the set have
+ * been closed by the above call in order to prevent
+ * the guest from getting stuck accessing them.
+ * So, NIL the handles to avoid closing them again.
+ */
+ if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
+ VBOXSERVICECTRLPIPEID_IPC_NOTIFY, NULL)))
+ pProcess->hNotificationPipeW = NIL_RTPIPE;
+ if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
+ VBOXSERVICECTRLPIPEID_STDERR, NULL)))
+ pProcess->hPipeStdErrR = NIL_RTPIPE;
+ if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
+ VBOXSERVICECTRLPIPEID_STDOUT, NULL)))
+ pProcess->hPipeStdOutR = NIL_RTPIPE;
+ if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
+ VBOXSERVICECTRLPIPEID_STDIN, NULL)))
+ pProcess->hPipeStdInW = NIL_RTPIPE;
+ }
+ }
+ RTPollSetDestroy(pProcess->hPollSet);
+ pProcess->hPollSet = NIL_RTPOLLSET;
+
+ RTPipeClose(pProcess->hNotificationPipeR);
+ pProcess->hNotificationPipeR = NIL_RTPIPE;
+ RTPipeClose(pProcess->hNotificationPipeW);
+ pProcess->hNotificationPipeW = NIL_RTPIPE;
+ }
+ RTPipeClose(pProcess->hPipeStdErrR);
+ pProcess->hPipeStdErrR = NIL_RTPIPE;
+ RTHandleClose(&hStdErr);
+ if (phStdErr)
+ RTHandleClose(phStdErr);
+ }
+ RTPipeClose(pProcess->hPipeStdOutR);
+ pProcess->hPipeStdOutR = NIL_RTPIPE;
+ RTHandleClose(&hStdOut);
+ if (phStdOut)
+ RTHandleClose(phStdOut);
+ }
+ RTPipeClose(pProcess->hPipeStdInW);
+ pProcess->hPipeStdInW = NIL_RTPIPE;
+ RTHandleClose(&hStdIn);
+ if (phStdIn)
+ RTHandleClose(phStdIn);
+ }
+ }
+ RTEnvDestroy(hEnv);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ VBGLR3GUESTCTRLCMDCTX ctx = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ };
+ int rc2 = VbglR3GuestCtrlProcCbStatus(&ctx,
+ pProcess->uPID, PROC_STS_ERROR, rc,
+ NULL /* pvData */, 0 /* cbData */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_NOT_FOUND)
+ VGSvcError("[PID %RU32]: Could not report process failure error; rc=%Rrc (process error %Rrc)\n",
+ pProcess->uPID, rc2, rc);
+ }
+
+ /* Update stopped status. */
+ ASMAtomicWriteBool(&pProcess->fStopped, true);
+
+ if (cArgs)
+ RTGetOptArgvFree(papszArgs);
+
+ /*
+ * If something went wrong signal the user event so that others don't wait
+ * forever on this thread.
+ */
+ if ( RT_FAILURE(rc)
+ && !fSignalled)
+ {
+ RTThreadUserSignal(RTThreadSelf());
+ }
+
+ /* Set shut down flag in case we've forgotten it. */
+ ASMAtomicWriteBool(&pProcess->fShutdown, true);
+
+ VGSvcVerbose(3, "[PID %RU32]: Thread of process '%s' ended with rc=%Rrc (fSignalled=%RTbool)\n",
+ pProcess->uPID, pProcess->pStartupInfo->pszCmd, rc, fSignalled);
+
+ return rc;
+}
+
+
+static int vgsvcGstCtrlProcessLock(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ int rc = RTCritSectEnter(&pProcess->CritSect);
+ AssertRC(rc);
+ return rc;
+}
+
+
+/**
+ * Thread main routine for a started process.
+ *
+ * @return IPRT status code.
+ * @param hThreadSelf The thread handle.
+ * @param pvUser Pointer to a VBOXSERVICECTRLPROCESS structure.
+ *
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlProcessThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF1(hThreadSelf);
+ PVBOXSERVICECTRLPROCESS pProcess = (PVBOXSERVICECTRLPROCESS)pvUser;
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ return vgsvcGstCtrlProcessProcessWorker(pProcess);
+}
+
+
+static int vgsvcGstCtrlProcessUnlock(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ int rc = RTCritSectLeave(&pProcess->CritSect);
+ AssertRC(rc);
+ return rc;
+}
+
+
+/**
+ * Executes (starts) a process on the guest. This causes a new thread to be created
+ * so that this function will not block the overall program execution.
+ *
+ * @return IPRT status code.
+ * @param pSession Guest session.
+ * @param pStartupInfo Startup info.
+ * @param uContextID Context ID to associate the process to start with.
+ */
+int VGSvcGstCtrlProcessStart(const PVBOXSERVICECTRLSESSION pSession,
+ const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo, uint32_t uContextID)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER);
+
+ /*
+ * Allocate new thread data and assign it to our thread list.
+ */
+ PVBOXSERVICECTRLPROCESS pProcess = (PVBOXSERVICECTRLPROCESS)RTMemAlloc(sizeof(VBOXSERVICECTRLPROCESS));
+ if (!pProcess)
+ return VERR_NO_MEMORY;
+
+ int rc = vgsvcGstCtrlProcessInit(pProcess, pSession, pStartupInfo, uContextID);
+ if (RT_SUCCESS(rc))
+ {
+ static uint32_t s_uCtrlExecThread = 0;
+ rc = RTThreadCreateF(&pProcess->Thread, vgsvcGstCtrlProcessThread,
+ pProcess /*pvUser*/, 0 /*cbStack*/,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctl%RU32", s_uCtrlExecThread++);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Creating thread for guest process '%s' failed: rc=%Rrc, pProcess=%p\n",
+ pStartupInfo->pszCmd, rc, pProcess);
+
+ /* Process has not been added to the session's process list yet, so skip VGSvcGstCtrlSessionProcessRemove() here. */
+ VGSvcGstCtrlProcessFree(pProcess);
+ }
+ else
+ {
+ VGSvcVerbose(4, "Waiting for thread to initialize ...\n");
+
+ /* Wait for the thread to initialize. */
+ rc = RTThreadUserWait(pProcess->Thread, 60 * 1000 /* 60 seconds max. */);
+ AssertRC(rc);
+ if ( ASMAtomicReadBool(&pProcess->fShutdown)
+ || ASMAtomicReadBool(&pProcess->fStopped)
+ || RT_FAILURE(rc))
+ {
+ VGSvcError("Thread for process '%s' failed to start, rc=%Rrc\n", pStartupInfo->pszCmd, rc);
+ int rc2 = RTThreadWait(pProcess->Thread, RT_MS_1SEC * 30, NULL);
+ if (RT_SUCCESS(rc2))
+ pProcess->Thread = NIL_RTTHREAD;
+
+ VGSvcGstCtrlSessionProcessRemove(pSession, pProcess);
+ VGSvcGstCtrlProcessFree(pProcess);
+ }
+ else
+ {
+ ASMAtomicXchgBool(&pProcess->fStarted, true);
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vgsvcGstCtrlProcessOnInput(PVBOXSERVICECTRLPROCESS pThis,
+ const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ bool fPendingClose, void *pvBuf, uint32_t cbBuf)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ int rc;
+
+ size_t cbWritten = 0;
+ if (pvBuf && cbBuf)
+ {
+ if (pThis->hPipeStdInW != NIL_RTPIPE)
+ rc = RTPipeWrite(pThis->hPipeStdInW, pvBuf, cbBuf, &cbWritten);
+ else
+ rc = VINF_EOF;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ /*
+ * If this is the last write + we have really have written all data
+ * we need to close the stdin pipe on our end and remove it from
+ * the poll set.
+ */
+ if ( fPendingClose
+ && cbBuf == cbWritten)
+ {
+ int rc2 = vgsvcGstCtrlProcessPollsetCloseInput(pThis, &pThis->hPipeStdInW);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status to send back to the host. */
+ uint32_t fFlags = 0; /* No flags at the moment. */
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "[PID %RU32]: Written %RU32 bytes input, CID=%RU32, fPendingClose=%RTbool\n",
+ pThis->uPID, cbWritten, pHostCtx->uContextID, fPendingClose);
+ uStatus = INPUT_STS_WRITTEN;
+ }
+ else
+ {
+ if (rc == VERR_BAD_PIPE)
+ uStatus = INPUT_STS_TERMINATED;
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ uStatus = INPUT_STS_OVERFLOW;
+ /* else undefined */
+ }
+
+ /*
+ * If there was an error and we did not set the host status
+ * yet, then do it now.
+ */
+ if ( RT_FAILURE(rc)
+ && uStatus == INPUT_STS_UNDEFINED)
+ {
+ uStatus = INPUT_STS_ERROR;
+ fFlags = rc; /* funny thing to call a "flag"... */
+ }
+ Assert(uStatus > INPUT_STS_UNDEFINED);
+
+ int rc2 = VbglR3GuestCtrlProcCbStatusInput(pHostCtx, pThis->uPID, uStatus, fFlags, (uint32_t)cbWritten);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessOnInput returned with rc=%Rrc\n", pThis->uPID, rc);
+#endif
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vgsvcGstCtrlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis,
+ const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ uint32_t uHandle, uint32_t cbToRead, uint32_t fFlags)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ const PVBOXSERVICECTRLSESSION pSession = pThis->pSession;
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ int rc;
+
+ uint32_t cbBuf = cbToRead;
+ uint8_t *pvBuf = (uint8_t *)RTMemAlloc(cbBuf);
+ if (pvBuf)
+ {
+ PRTPIPE phPipe = uHandle == GUEST_PROC_OUT_H_STDOUT
+ ? &pThis->hPipeStdOutR
+ : &pThis->hPipeStdErrR;
+ AssertPtr(phPipe);
+
+ size_t cbRead = 0;
+ if (*phPipe != NIL_RTPIPE)
+ {
+ rc = RTPipeRead(*phPipe, pvBuf, cbBuf, &cbRead);
+ if (RT_FAILURE(rc))
+ {
+ RTPollSetRemove(pThis->hPollSet, uHandle == GUEST_PROC_OUT_H_STDERR
+ ? VBOXSERVICECTRLPIPEID_STDERR : VBOXSERVICECTRLPIPEID_STDOUT);
+ RTPipeClose(*phPipe);
+ *phPipe = NIL_RTPIPE;
+ if (rc == VERR_BROKEN_PIPE)
+ rc = VINF_EOF;
+ }
+ }
+ else
+ rc = VINF_EOF;
+
+#ifdef DEBUG
+ if (RT_SUCCESS(rc))
+ {
+ if ( pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT
+ && ( uHandle == GUEST_PROC_OUT_H_STDOUT
+ || uHandle == GUEST_PROC_OUT_H_STDOUT_DEPRECATED)
+ )
+ {
+ rc = vgsvcGstCtrlProcessDbgDumpToFileF(pvBuf, cbRead, "VBoxService_Session%RU32_PID%RU32_StdOut.txt",
+ pSession->StartupInfo.uSessionID, pThis->uPID);
+ AssertRC(rc);
+ }
+ else if ( pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR
+ && uHandle == GUEST_PROC_OUT_H_STDERR)
+ {
+ rc = vgsvcGstCtrlProcessDbgDumpToFileF(pvBuf, cbRead, "VBoxService_Session%RU32_PID%RU32_StdErr.txt",
+ pSession->StartupInfo.uSessionID, pThis->uPID);
+ AssertRC(rc);
+ }
+ }
+#endif
+
+ if (RT_SUCCESS(rc))
+ {
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: Read %RU32 bytes output: uHandle=%RU32, CID=%RU32, fFlags=%x\n",
+ pThis->uPID, cbRead, uHandle, pHostCtx->uContextID, fFlags);
+#endif
+ /** Note: Don't convert/touch/modify/whatever the output data here! This might be binary
+ * data which the host needs to work with -- so just pass through all data unfiltered! */
+
+ /* Note: Since the context ID is unique the request *has* to be completed here,
+ * regardless whether we got data or not! Otherwise the waiting events
+ * on the host never will get completed! */
+ Assert((uint32_t)cbRead == cbRead);
+ rc = VbglR3GuestCtrlProcCbOutput(pHostCtx, pThis->uPID, uHandle, fFlags, pvBuf, (uint32_t)cbRead);
+ if ( RT_FAILURE(rc)
+ && rc == VERR_NOT_FOUND) /* Not critical if guest PID is not found on the host (anymore). */
+ rc = VINF_SUCCESS;
+ }
+
+ RTMemFree(pvBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: Reading output returned with rc=%Rrc\n", pThis->uPID, rc);
+#endif
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vgsvcGstCtrlProcessOnTerm(PVBOXSERVICECTRLPROCESS pThis)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ if (!ASMAtomicXchgBool(&pThis->fShutdown, true))
+ VGSvcVerbose(3, "[PID %RU32]: Setting shutdown flag ...\n", pThis->uPID);
+
+ return VINF_SUCCESS;
+}
+
+
+static int vgsvcGstCtrlProcessRequestExV(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fAsync,
+ RTMSINTERVAL uTimeoutMS, PFNRT pfnFunction, unsigned cArgs, va_list Args)
+{
+ RT_NOREF1(pHostCtx);
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ /* pHostCtx is optional. */
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+ if (!fAsync)
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+
+ int rc = vgsvcGstCtrlProcessLock(pProcess);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessRequestExV fAsync=%RTbool, uTimeoutMS=%RU32, cArgs=%u\n",
+ pProcess->uPID, fAsync, uTimeoutMS, cArgs);
+#endif
+ uint32_t fFlags = RTREQFLAGS_IPRT_STATUS;
+ if (fAsync)
+ {
+ Assert(uTimeoutMS == 0);
+ fFlags |= RTREQFLAGS_NO_WAIT;
+ }
+
+ PRTREQ hReq = NIL_RTREQ;
+ rc = RTReqQueueCallV(pProcess->hReqQueue, &hReq, uTimeoutMS, fFlags, pfnFunction, cArgs, Args);
+ RTReqRelease(hReq);
+ if (RT_SUCCESS(rc))
+ {
+ /* Wake up the process' notification pipe to get
+ * the request being processed. */
+ Assert(pProcess->hNotificationPipeW != NIL_RTPIPE || pProcess->fShutdown /* latter in case of race */);
+ size_t cbWritten = 0;
+ rc = RTPipeWrite(pProcess->hNotificationPipeW, "i", 1, &cbWritten);
+ if ( RT_SUCCESS(rc)
+ && cbWritten != 1)
+ {
+ VGSvcError("[PID %RU32]: Notification pipe got %zu bytes instead of 1\n",
+ pProcess->uPID, cbWritten);
+ }
+ else if (RT_UNLIKELY(RT_FAILURE(rc)))
+ VGSvcError("[PID %RU32]: Writing to notification pipe failed, rc=%Rrc\n",
+ pProcess->uPID, rc);
+ }
+ else
+ VGSvcError("[PID %RU32]: RTReqQueueCallV failed, rc=%Rrc\n",
+ pProcess->uPID, rc);
+
+ int rc2 = vgsvcGstCtrlProcessUnlock(pProcess);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessRequestExV returned rc=%Rrc\n", pProcess->uPID, rc);
+#endif
+ return rc;
+}
+
+
+static int vgsvcGstCtrlProcessRequestAsync(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ /* pHostCtx is optional. */
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+
+ va_list va;
+ va_start(va, cArgs);
+ int rc = vgsvcGstCtrlProcessRequestExV(pProcess, pHostCtx, true /* fAsync */, 0 /* uTimeoutMS */,
+ pfnFunction, cArgs, va);
+ va_end(va);
+
+ return rc;
+}
+
+
+#if 0 /* unused */
+static int vgsvcGstCtrlProcessRequestWait(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ RTMSINTERVAL uTimeoutMS, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ /* pHostCtx is optional. */
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+
+ va_list va;
+ va_start(va, cArgs);
+ int rc = vgsvcGstCtrlProcessRequestExV(pProcess, pHostCtx, false /* fAsync */, uTimeoutMS,
+ pfnFunction, cArgs, va);
+ va_end(va);
+
+ return rc;
+}
+#endif
+
+
+int VGSvcGstCtrlProcessHandleInput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ bool fPendingClose, void *pvBuf, uint32_t cbBuf)
+{
+ if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped))
+ return vgsvcGstCtrlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)vgsvcGstCtrlProcessOnInput,
+ 5 /* cArgs */, pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf);
+
+ return vgsvcGstCtrlProcessOnInput(pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf);
+}
+
+
+int VGSvcGstCtrlProcessHandleOutput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ uint32_t uHandle, uint32_t cbToRead, uint32_t fFlags)
+{
+ if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped))
+ return vgsvcGstCtrlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)vgsvcGstCtrlProcessOnOutput,
+ 5 /* cArgs */, pProcess, pHostCtx, uHandle, cbToRead, fFlags);
+
+ return vgsvcGstCtrlProcessOnOutput(pProcess, pHostCtx, uHandle, cbToRead, fFlags);
+}
+
+
+int VGSvcGstCtrlProcessHandleTerm(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped))
+ return vgsvcGstCtrlProcessRequestAsync(pProcess, NULL /* pHostCtx */, (PFNRT)vgsvcGstCtrlProcessOnTerm,
+ 1 /* cArgs */, pProcess);
+
+ return vgsvcGstCtrlProcessOnTerm(pProcess);
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp
new file mode 100644
index 00000000..001d468d
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp
@@ -0,0 +1,2886 @@
+/* $Id: VBoxServiceControlSession.cpp $ */
+/** @file
+ * VBoxServiceControlSession - Guest session handling. Also handles the spawned session processes.
+ */
+
+/*
+ * Copyright (C) 2013-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 <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/handle.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+#include <iprt/process.h>
+#include <iprt/rand.h>
+#include <iprt/system.h> /* For RTShutdown. */
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+#include "VBoxServiceControl.h"
+
+using namespace guestControl;
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Generic option indices for session spawn arguments. */
+enum
+{
+ VBOXSERVICESESSIONOPT_FIRST = 1000, /* For initialization. */
+ VBOXSERVICESESSIONOPT_DOMAIN,
+#ifdef DEBUG
+ VBOXSERVICESESSIONOPT_DUMP_STDOUT,
+ VBOXSERVICESESSIONOPT_DUMP_STDERR,
+#endif
+ VBOXSERVICESESSIONOPT_LOG_FILE,
+ VBOXSERVICESESSIONOPT_USERNAME,
+ VBOXSERVICESESSIONOPT_SESSION_ID,
+ VBOXSERVICESESSIONOPT_SESSION_PROTO,
+ VBOXSERVICESESSIONOPT_THREAD_ID
+};
+
+
+static int vgsvcGstCtrlSessionCleanupProcesses(const PVBOXSERVICECTRLSESSION pSession);
+static int vgsvcGstCtrlSessionProcessRemoveInternal(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess);
+
+
+/**
+ * Helper that grows the scratch buffer.
+ * @returns Success indicator.
+ */
+static bool vgsvcGstCtrlSessionGrowScratchBuf(void **ppvScratchBuf, uint32_t *pcbScratchBuf, uint32_t cbMinBuf)
+{
+ uint32_t cbNew = *pcbScratchBuf * 2;
+ if ( cbNew <= VMMDEV_MAX_HGCM_DATA_SIZE
+ && cbMinBuf <= VMMDEV_MAX_HGCM_DATA_SIZE)
+ {
+ while (cbMinBuf > cbNew)
+ cbNew *= 2;
+ void *pvNew = RTMemRealloc(*ppvScratchBuf, cbNew);
+ if (pvNew)
+ {
+ *ppvScratchBuf = pvNew;
+ *pcbScratchBuf = cbNew;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+static int vgsvcGstCtrlSessionFileFree(PVBOXSERVICECTRLFILE pFile)
+{
+ AssertPtrReturn(pFile, VERR_INVALID_POINTER);
+
+ int rc = RTFileClose(pFile->hFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pFile->pszName);
+
+ /* Remove file entry in any case. */
+ RTListNodeRemove(&pFile->Node);
+ /* Destroy this object. */
+ RTMemFree(pFile);
+ }
+
+ return rc;
+}
+
+
+/** @todo No locking done yet! */
+static PVBOXSERVICECTRLFILE vgsvcGstCtrlSessionFileGetLocked(const PVBOXSERVICECTRLSESSION pSession, uint32_t uHandle)
+{
+ AssertPtrReturn(pSession, NULL);
+
+ /** @todo Use a map later! */
+ PVBOXSERVICECTRLFILE pFileCur;
+ RTListForEach(&pSession->lstFiles, pFileCur, VBOXSERVICECTRLFILE, Node)
+ {
+ if (pFileCur->uHandle == uHandle)
+ return pFileCur;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Recursion worker for vgsvcGstCtrlSessionHandleDirRemove.
+ * Only (recursively) removes directory structures which are not empty. Will fail if not empty.
+ *
+ * @returns IPRT status code.
+ * @param pszDir The directory buffer, RTPATH_MAX in length.
+ * Contains the abs path to the directory to
+ * recurse into. Trailing slash.
+ * @param cchDir The length of the directory we're recursing into,
+ * including the trailing slash.
+ * @param pDirEntry The dir entry buffer. (Shared to save stack.)
+ */
+static int vgsvcGstCtrlSessionHandleDirRemoveSub(char *pszDir, size_t cchDir, PRTDIRENTRY pDirEntry)
+{
+ RTDIR hDir;
+ int rc = RTDirOpen(&hDir, pszDir);
+ if (RT_FAILURE(rc))
+ {
+ /* Ignore non-existing directories like RTDirRemoveRecursive does: */
+ if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
+ return VINF_SUCCESS;
+ return rc;
+ }
+
+ for (;;)
+ {
+ rc = RTDirRead(hDir, pDirEntry, NULL);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ if (!RTDirEntryIsStdDotLink(pDirEntry))
+ {
+ /* Construct the full name of the entry. */
+ if (cchDir + pDirEntry->cbName + 1 /* dir slash */ < RTPATH_MAX)
+ memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1);
+ else
+ {
+ rc = VERR_FILENAME_TOO_LONG;
+ break;
+ }
+
+ /* Make sure we've got the entry type. */
+ if (pDirEntry->enmType == RTDIRENTRYTYPE_UNKNOWN)
+ RTDirQueryUnknownType(pszDir, false /*fFollowSymlinks*/, &pDirEntry->enmType);
+
+ /* Recurse into subdirs and remove them: */
+ if (pDirEntry->enmType == RTDIRENTRYTYPE_DIRECTORY)
+ {
+ size_t cchSubDir = cchDir + pDirEntry->cbName;
+ pszDir[cchSubDir++] = RTPATH_SLASH;
+ pszDir[cchSubDir] = '\0';
+ rc = vgsvcGstCtrlSessionHandleDirRemoveSub(pszDir, cchSubDir, pDirEntry);
+ if (RT_SUCCESS(rc))
+ {
+ pszDir[cchSubDir] = '\0';
+ rc = RTDirRemove(pszDir);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ break;
+ }
+ /* Not a subdirectory - fail: */
+ else
+ {
+ rc = VERR_DIR_NOT_EMPTY;
+ break;
+ }
+ }
+ }
+
+ RTDirClose(hDir);
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleDirRemove(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the message.
+ */
+ char szDir[RTPATH_MAX];
+ uint32_t fFlags; /* DIRREMOVE_FLAG_XXX */
+ int rc = VbglR3GuestCtrlDirGetRemove(pHostCtx, szDir, sizeof(szDir), &fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do some validating before executing the job.
+ */
+ if (!(fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK))
+ {
+ if (fFlags & DIRREMOVEREC_FLAG_RECURSIVE)
+ {
+ if (fFlags & (DIRREMOVEREC_FLAG_CONTENT_AND_DIR | DIRREMOVEREC_FLAG_CONTENT_ONLY))
+ {
+ uint32_t fFlagsRemRec = fFlags & DIRREMOVEREC_FLAG_CONTENT_AND_DIR
+ ? RTDIRRMREC_F_CONTENT_AND_DIR : RTDIRRMREC_F_CONTENT_ONLY;
+ rc = RTDirRemoveRecursive(szDir, fFlagsRemRec);
+ }
+ else /* Only remove empty directory structures. Will fail if non-empty. */
+ {
+ RTDIRENTRY DirEntry;
+ RTPathEnsureTrailingSeparator(szDir, sizeof(szDir));
+ rc = vgsvcGstCtrlSessionHandleDirRemoveSub(szDir, strlen(szDir), &DirEntry);
+ }
+ VGSvcVerbose(4, "[Dir %s]: rmdir /s (%#x) -> rc=%Rrc\n", szDir, fFlags, rc);
+ }
+ else
+ {
+ /* Only delete directory if not empty. */
+ rc = RTDirRemove(szDir);
+ VGSvcVerbose(4, "[Dir %s]: rmdir (%#x), rc=%Rrc\n", szDir, fFlags, rc);
+ }
+ }
+ else
+ {
+ VGSvcError("[Dir %s]: Unsupported flags: %#x (all %#x)\n", szDir, (fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK), fFlags);
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("[Dir %s]: Failed to report removing status, rc=%Rrc\n", szDir, rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for rmdir operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ VGSvcVerbose(6, "Removing directory '%s' returned rc=%Rrc\n", szDir, rc);
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileOpen(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the message.
+ */
+ char szFile[RTPATH_MAX];
+ char szAccess[64];
+ char szDisposition[64];
+ char szSharing[64];
+ uint32_t uCreationMode = 0;
+ uint64_t offOpen = 0;
+ uint32_t uHandle = 0;
+ int rc = VbglR3GuestCtrlFileGetOpen(pHostCtx,
+ /* File to open. */
+ szFile, sizeof(szFile),
+ /* Open mode. */
+ szAccess, sizeof(szAccess),
+ /* Disposition. */
+ szDisposition, sizeof(szDisposition),
+ /* Sharing. */
+ szSharing, sizeof(szSharing),
+ /* Creation mode. */
+ &uCreationMode,
+ /* Offset. */
+ &offOpen);
+ VGSvcVerbose(4, "[File %s]: szAccess=%s, szDisposition=%s, szSharing=%s, offOpen=%RU64, rc=%Rrc\n",
+ szFile, szAccess, szDisposition, szSharing, offOpen, rc);
+ if (RT_SUCCESS(rc))
+ {
+ PVBOXSERVICECTRLFILE pFile = (PVBOXSERVICECTRLFILE)RTMemAllocZ(sizeof(VBOXSERVICECTRLFILE));
+ if (pFile)
+ {
+ pFile->hFile = NIL_RTFILE; /* Not zero or NULL! */
+ if (szFile[0])
+ {
+ pFile->pszName = RTStrDup(szFile);
+ if (!pFile->pszName)
+ rc = VERR_NO_MEMORY;
+/** @todo
+ * Implement szSharing!
+ */
+ uint64_t fFlags;
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileModeToFlagsEx(szAccess, szDisposition, NULL /* pszSharing, not used yet */, &fFlags);
+ VGSvcVerbose(4, "[File %s] Opening with fFlags=%#RX64 -> rc=%Rrc\n", pFile->pszName, fFlags, rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ fFlags |= (uCreationMode << RTFILE_O_CREATE_MODE_SHIFT) & RTFILE_O_CREATE_MODE_MASK;
+ /* If we're opening a file in read-only mode, strip truncation mode.
+ * rtFileRecalcAndValidateFlags() will validate it anyway, but avoid asserting in debug builds. */
+ if (fFlags & RTFILE_O_READ)
+ fFlags &= ~RTFILE_O_TRUNCATE;
+ rc = RTFileOpen(&pFile->hFile, pFile->pszName, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ RTFSOBJINFO objInfo;
+ rc = RTFileQueryInfo(pFile->hFile, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ /* Make sure that we only open stuff we really support.
+ * Only POSIX / UNIX we could open stuff like directories and sockets as well. */
+ if ( RT_LIKELY(RTFS_IS_FILE(objInfo.Attr.fMode))
+ || RTFS_IS_SYMLINK(objInfo.Attr.fMode))
+ {
+ /* Seeking is optional. However, the whole operation
+ * will fail if we don't succeed seeking to the wanted position. */
+ if (offOpen)
+ rc = RTFileSeek(pFile->hFile, (int64_t)offOpen, RTFILE_SEEK_BEGIN, NULL /* Current offset */);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Succeeded!
+ */
+ uHandle = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pHostCtx->uContextID);
+ pFile->uHandle = uHandle;
+ pFile->fOpen = fFlags;
+ RTListAppend(&pSession->lstFiles, &pFile->Node);
+ VGSvcVerbose(2, "[File %s] Opened (ID=%RU32)\n", pFile->pszName, pFile->uHandle);
+ }
+ else
+ VGSvcError("[File %s] Seeking to offset %RU64 failed: rc=%Rrc\n", pFile->pszName, offOpen, rc);
+ }
+ else
+ {
+ VGSvcError("[File %s] Unsupported mode %#x\n", pFile->pszName, objInfo.Attr.fMode);
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ VGSvcError("[File %s] Getting mode failed with rc=%Rrc\n", pFile->pszName, rc);
+ }
+ else
+ VGSvcError("[File %s] Opening failed with rc=%Rrc\n", pFile->pszName, rc);
+ }
+ }
+ else
+ {
+ VGSvcError("[File %s] empty filename!\n", szFile);
+ rc = VERR_INVALID_NAME;
+ }
+
+ /* clean up if we failed. */
+ if (RT_FAILURE(rc))
+ {
+ RTStrFree(pFile->pszName);
+ if (pFile->hFile != NIL_RTFILE)
+ RTFileClose(pFile->hFile);
+ RTMemFree(pFile);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlFileCbOpen(pHostCtx, rc, uHandle);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("[File %s]: Failed to report file open status, rc=%Rrc\n", szFile, rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for open file operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ VGSvcVerbose(4, "[File %s] Opening (open mode='%s', disposition='%s', creation mode=0x%x) returned rc=%Rrc\n",
+ szFile, szAccess, szDisposition, uCreationMode, rc);
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileClose(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the message.
+ */
+ uint32_t uHandle = 0;
+ int rc = VbglR3GuestCtrlFileGetClose(pHostCtx, &uHandle /* File handle to close */);
+ if (RT_SUCCESS(rc))
+ {
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ VGSvcVerbose(2, "[File %s] Closing (handle=%RU32)\n", pFile ? pFile->pszName : "<Not found>", uHandle);
+ rc = vgsvcGstCtrlSessionFileFree(pFile);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlFileCbClose(pHostCtx, rc);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file close status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for close file operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileRead(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uHandle = 0;
+ uint32_t cbToRead;
+ int rc = VbglR3GuestCtrlFileGetRead(pHostCtx, &uHandle, &cbToRead);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and do the reading.
+ *
+ * If the request is larger than our scratch buffer, try grow it - just
+ * ignore failure as the host better respect our buffer limits.
+ */
+ uint32_t offNew = 0;
+ size_t cbRead = 0;
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ if (*pcbScratchBuf < cbToRead)
+ vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToRead);
+
+ rc = RTFileRead(pFile->hFile, *ppvScratchBuf, RT_MIN(cbToRead, *pcbScratchBuf), &cbRead);
+ offNew = (int64_t)RTFileTell(pFile->hFile);
+ VGSvcVerbose(5, "[File %s] Read %zu/%RU32 bytes, rc=%Rrc, offNew=%RI64\n", pFile->pszName, cbRead, cbToRead, rc, offNew);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result and data back to the host.
+ */
+ int rc2;
+ if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET)
+ rc2 = VbglR3GuestCtrlFileCbReadOffset(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead, offNew);
+ else
+ rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file read status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file read operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileReadAt(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uHandle = 0;
+ uint32_t cbToRead;
+ uint64_t offReadAt;
+ int rc = VbglR3GuestCtrlFileGetReadAt(pHostCtx, &uHandle, &cbToRead, &offReadAt);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and do the reading.
+ *
+ * If the request is larger than our scratch buffer, try grow it - just
+ * ignore failure as the host better respect our buffer limits.
+ */
+ int64_t offNew = 0;
+ size_t cbRead = 0;
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ if (*pcbScratchBuf < cbToRead)
+ vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToRead);
+
+ rc = RTFileReadAt(pFile->hFile, (RTFOFF)offReadAt, *ppvScratchBuf, RT_MIN(cbToRead, *pcbScratchBuf), &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ offNew = offReadAt + cbRead;
+ RTFileSeek(pFile->hFile, offNew, RTFILE_SEEK_BEGIN, NULL); /* RTFileReadAt does not always change position. */
+ }
+ else
+ offNew = (int64_t)RTFileTell(pFile->hFile);
+ VGSvcVerbose(5, "[File %s] Read %zu bytes @ %RU64, rc=%Rrc, offNew=%RI64\n", pFile->pszName, cbRead, offReadAt, rc, offNew);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result and data back to the host.
+ */
+ int rc2;
+ if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET)
+ rc2 = VbglR3GuestCtrlFileCbReadOffset(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead, offNew);
+ else
+ rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file read at status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file read at operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileWrite(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request and data to write.
+ */
+ uint32_t uHandle = 0;
+ uint32_t cbToWrite;
+ int rc = VbglR3GuestCtrlFileGetWrite(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite);
+ if ( rc == VERR_BUFFER_OVERFLOW
+ && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToWrite))
+ rc = VbglR3GuestCtrlFileGetWrite(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and do the writing.
+ */
+ int64_t offNew = 0;
+ size_t cbWritten = 0;
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ rc = RTFileWrite(pFile->hFile, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), &cbWritten);
+ offNew = (int64_t)RTFileTell(pFile->hFile);
+ VGSvcVerbose(5, "[File %s] Writing %p LB %RU32 => %Rrc, cbWritten=%zu, offNew=%RI64\n",
+ pFile->pszName, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), rc, cbWritten, offNew);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2;
+ if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET)
+ rc2 = VbglR3GuestCtrlFileCbWriteOffset(pHostCtx, rc, (uint32_t)cbWritten, offNew);
+ else
+ rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file write status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file write operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileWriteAt(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request and data to write.
+ */
+ uint32_t uHandle = 0;
+ uint32_t cbToWrite;
+ uint64_t offWriteAt;
+ int rc = VbglR3GuestCtrlFileGetWriteAt(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite, &offWriteAt);
+ if ( rc == VERR_BUFFER_OVERFLOW
+ && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToWrite))
+ rc = VbglR3GuestCtrlFileGetWriteAt(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite, &offWriteAt);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and do the writing.
+ */
+ int64_t offNew = 0;
+ size_t cbWritten = 0;
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ rc = RTFileWriteAt(pFile->hFile, (RTFOFF)offWriteAt, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), &cbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ offNew = offWriteAt + cbWritten;
+
+ /* RTFileWriteAt does not always change position: */
+ if (!(pFile->fOpen & RTFILE_O_APPEND))
+ RTFileSeek(pFile->hFile, offNew, RTFILE_SEEK_BEGIN, NULL);
+ else
+ RTFileSeek(pFile->hFile, 0, RTFILE_SEEK_END, (uint64_t *)&offNew);
+ }
+ else
+ offNew = (int64_t)RTFileTell(pFile->hFile);
+ VGSvcVerbose(5, "[File %s] Writing %p LB %RU32 @ %RU64 => %Rrc, cbWritten=%zu, offNew=%RI64\n",
+ pFile->pszName, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), offWriteAt, rc, cbWritten, offNew);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2;
+ if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET)
+ rc2 = VbglR3GuestCtrlFileCbWriteOffset(pHostCtx, rc, (uint32_t)cbWritten, offNew);
+ else
+ rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file write status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file write at operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileSeek(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uHandle = 0;
+ uint32_t uSeekMethod;
+ uint64_t offSeek; /* Will be converted to int64_t. */
+ int rc = VbglR3GuestCtrlFileGetSeek(pHostCtx, &uHandle, &uSeekMethod, &offSeek);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t offActual = 0;
+
+ /*
+ * Validate and convert the seek method to IPRT speak.
+ */
+ static const uint8_t s_abMethods[GUEST_FILE_SEEKTYPE_END + 1] =
+ {
+ UINT8_MAX, RTFILE_SEEK_BEGIN, UINT8_MAX, UINT8_MAX, RTFILE_SEEK_CURRENT,
+ UINT8_MAX, UINT8_MAX, UINT8_MAX, RTFILE_SEEK_END
+ };
+ if ( uSeekMethod < RT_ELEMENTS(s_abMethods)
+ && s_abMethods[uSeekMethod] != UINT8_MAX)
+ {
+ /*
+ * Locate the file and do the seek.
+ */
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ rc = RTFileSeek(pFile->hFile, (int64_t)offSeek, s_abMethods[uSeekMethod], &offActual);
+ VGSvcVerbose(5, "[File %s]: Seeking to offSeek=%RI64, uSeekMethodIPRT=%u, rc=%Rrc\n",
+ pFile->pszName, offSeek, s_abMethods[uSeekMethod], rc);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+ }
+ else
+ {
+ VGSvcError("Invalid seek method: %#x\n", uSeekMethod);
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlFileCbSeek(pHostCtx, rc, offActual);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file seek status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file seek operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileTell(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uHandle = 0;
+ int rc = VbglR3GuestCtrlFileGetTell(pHostCtx, &uHandle);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and ask for the current position.
+ */
+ uint64_t offCurrent = 0;
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ offCurrent = RTFileTell(pFile->hFile);
+ VGSvcVerbose(5, "[File %s]: Telling offCurrent=%RU64\n", pFile->pszName, offCurrent);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlFileCbTell(pHostCtx, rc, offCurrent);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file tell status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file tell operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileSetSize(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uHandle = 0;
+ uint64_t cbNew = 0;
+ int rc = VbglR3GuestCtrlFileGetSetSize(pHostCtx, &uHandle, &cbNew);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and ask for the current position.
+ */
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ rc = RTFileSetSize(pFile->hFile, cbNew);
+ VGSvcVerbose(5, "[File %s]: Changing size to %RU64 (%#RX64), rc=%Rrc\n", pFile->pszName, cbNew, cbNew, rc);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ cbNew = UINT64_MAX;
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlFileCbSetSize(pHostCtx, rc, cbNew);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file tell status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file tell operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandlePathRename(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ char szSource[RTPATH_MAX];
+ char szDest[RTPATH_MAX];
+ uint32_t fFlags = 0; /* PATHRENAME_FLAG_XXX */
+ int rc = VbglR3GuestCtrlPathGetRename(pHostCtx, szSource, sizeof(szSource), szDest, sizeof(szDest), &fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Validate the flags (kudos for using the same as IPRT), then do the renaming.
+ */
+ AssertCompile(PATHRENAME_FLAG_NO_REPLACE == RTPATHRENAME_FLAGS_NO_REPLACE);
+ AssertCompile(PATHRENAME_FLAG_REPLACE == RTPATHRENAME_FLAGS_REPLACE);
+ AssertCompile(PATHRENAME_FLAG_NO_SYMLINKS == RTPATHRENAME_FLAGS_NO_SYMLINKS);
+ AssertCompile(PATHRENAME_FLAG_VALID_MASK == (RTPATHRENAME_FLAGS_NO_REPLACE | RTPATHRENAME_FLAGS_REPLACE | RTPATHRENAME_FLAGS_NO_SYMLINKS));
+ if (!(fFlags & ~PATHRENAME_FLAG_VALID_MASK))
+ {
+ VGSvcVerbose(4, "Renaming '%s' to '%s', fFlags=%#x, rc=%Rrc\n", szSource, szDest, fFlags, rc);
+ rc = RTPathRename(szSource, szDest, fFlags);
+ }
+ else
+ {
+ VGSvcError("Invalid rename flags: %#x\n", fFlags);
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report renaming status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for rename operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ VGSvcVerbose(5, "Renaming '%s' to '%s' returned rc=%Rrc\n", szSource, szDest, rc);
+ return rc;
+}
+
+
+/**
+ * Handles getting the user's documents directory.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session.
+ * @param pHostCtx Host context.
+ */
+static int vgsvcGstCtrlSessionHandlePathUserDocuments(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ int rc = VbglR3GuestCtrlPathGetUserDocuments(pHostCtx);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Get the path and pass it back to the host..
+ */
+ char szPath[RTPATH_MAX];
+ rc = RTPathUserDocuments(szPath, sizeof(szPath));
+#ifdef DEBUG
+ VGSvcVerbose(2, "User documents is '%s', rc=%Rrc\n", szPath, rc);
+#endif
+
+ int rc2 = VbglR3GuestCtrlMsgReplyEx(pHostCtx, rc, 0 /* Type */, szPath,
+ RT_SUCCESS(rc) ? (uint32_t)strlen(szPath) + 1 /* Include terminating zero */ : 0);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report user documents, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for user documents path request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+/**
+ * Handles shutting down / rebooting the guest OS.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session.
+ * @param pHostCtx Host context.
+ */
+static int vgsvcGstCtrlSessionHandleShutdown(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t fAction;
+ int rc = VbglR3GuestCtrlGetShutdown(pHostCtx, &fAction);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(1, "Host requested to %s system ...\n", (fAction & RTSYSTEM_SHUTDOWN_REBOOT) ? "reboot" : "shutdown");
+
+ /* Reply first to the host, in order to avoid host hangs when issuing the guest shutdown. */
+ rc = VbglR3GuestCtrlMsgReply(pHostCtx, VINF_SUCCESS);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Failed to reply to shutdown / reboot request, rc=%Rrc\n", rc);
+ }
+ else
+ {
+ int fSystemShutdown = RTSYSTEM_SHUTDOWN_PLANNED;
+
+ /* Translate SHUTDOWN_FLAG_ into RTSYSTEM_SHUTDOWN_ flags. */
+ if (fAction & GUEST_SHUTDOWN_FLAG_REBOOT)
+ fSystemShutdown |= RTSYSTEM_SHUTDOWN_REBOOT;
+ else /* SHUTDOWN_FLAG_POWER_OFF */
+ fSystemShutdown |= RTSYSTEM_SHUTDOWN_POWER_OFF;
+
+ if (fAction & GUEST_SHUTDOWN_FLAG_FORCE)
+ fSystemShutdown |= RTSYSTEM_SHUTDOWN_FORCE;
+
+ rc = RTSystemShutdown(0 /*cMsDelay*/, fSystemShutdown, "VBoxService");
+ if (RT_FAILURE(rc))
+ VGSvcError("%s system failed with %Rrc\n",
+ (fAction & RTSYSTEM_SHUTDOWN_REBOOT) ? "Rebooting" : "Shutting down", rc);
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for shutdown / reboot request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Handles getting the user's home directory.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session.
+ * @param pHostCtx Host context.
+ */
+static int vgsvcGstCtrlSessionHandlePathUserHome(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ int rc = VbglR3GuestCtrlPathGetUserHome(pHostCtx);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Get the path and pass it back to the host..
+ */
+ char szPath[RTPATH_MAX];
+ rc = RTPathUserHome(szPath, sizeof(szPath));
+
+#ifdef DEBUG
+ VGSvcVerbose(2, "User home is '%s', rc=%Rrc\n", szPath, rc);
+#endif
+ /* Report back in any case. */
+ int rc2 = VbglR3GuestCtrlMsgReplyEx(pHostCtx, rc, 0 /* Type */, szPath,
+ RT_SUCCESS(rc) ?(uint32_t)strlen(szPath) + 1 /* Include terminating zero */ : 0);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report user home, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for user home directory path request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+/**
+ * Handles starting a guest processes.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session.
+ * @param pHostCtx Host context.
+ */
+static int vgsvcGstCtrlSessionHandleProcExec(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /* Initialize maximum environment block size -- needed as input
+ * parameter to retrieve the stuff from the host. On output this then
+ * will contain the actual block size. */
+ PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo;
+ int rc = VbglR3GuestCtrlProcGetStart(pHostCtx, &pStartupInfo);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "Request to start process szCmd=%s, fFlags=0x%x, szArgs=%s, szEnv=%s, uTimeout=%RU32\n",
+ pStartupInfo->pszCmd, pStartupInfo->fFlags,
+ pStartupInfo->cArgs ? pStartupInfo->pszArgs : "<None>",
+ pStartupInfo->cEnvVars ? pStartupInfo->pszEnv : "<None>",
+ pStartupInfo->uTimeLimitMS);
+
+ bool fStartAllowed = false; /* Flag indicating whether starting a process is allowed or not. */
+ rc = VGSvcGstCtrlSessionProcessStartAllowed(pSession, &fStartAllowed);
+ if (RT_SUCCESS(rc))
+ {
+ vgsvcGstCtrlSessionCleanupProcesses(pSession);
+
+ if (fStartAllowed)
+ rc = VGSvcGstCtrlProcessStart(pSession, pStartupInfo, pHostCtx->uContextID);
+ else
+ rc = VERR_MAX_PROCS_REACHED; /* Maximum number of processes reached. */
+ }
+
+ /* We're responsible for signaling errors to the host (it will wait for ever otherwise). */
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Starting process failed with rc=%Rrc, protocol=%RU32, parameters=%RU32\n",
+ rc, pHostCtx->uProtocol, pHostCtx->uNumParms);
+ int rc2 = VbglR3GuestCtrlProcCbStatus(pHostCtx, 0 /*nil-PID*/, PROC_STS_ERROR, rc, NULL /*pvData*/, 0 /*cbData*/);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Error sending start process status to host, rc=%Rrc\n", rc2);
+ }
+
+ VbglR3GuestCtrlProcStartupInfoFree(pStartupInfo);
+ pStartupInfo = NULL;
+ }
+ else
+ {
+ VGSvcError("Failed to retrieve parameters for process start: %Rrc (cParms=%u)\n", rc, pHostCtx->uNumParms);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Sends stdin input to a specific guest process.
+ *
+ * @returns VBox status code.
+ * @param pSession The session which is in charge.
+ * @param pHostCtx The host context to use.
+ * @param ppvScratchBuf The scratch buffer, we may grow it.
+ * @param pcbScratchBuf The scratch buffer size for retrieving the input
+ * data.
+ */
+static int vgsvcGstCtrlSessionHandleProcInput(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the data from the host.
+ */
+ uint32_t uPID;
+ uint32_t fFlags;
+ uint32_t cbInput;
+ int rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &fFlags, *ppvScratchBuf, *pcbScratchBuf, &cbInput);
+ if ( rc == VERR_BUFFER_OVERFLOW
+ && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbInput))
+ rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &fFlags, *ppvScratchBuf, *pcbScratchBuf, &cbInput);
+ if (RT_SUCCESS(rc))
+ {
+ if (fFlags & GUEST_PROC_IN_FLAG_EOF)
+ VGSvcVerbose(4, "Got last process input block for PID=%RU32 (%RU32 bytes) ...\n", uPID, cbInput);
+
+ /*
+ * Locate the process and feed it.
+ */
+ PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID);
+ if (pProcess)
+ {
+ rc = VGSvcGstCtrlProcessHandleInput(pProcess, pHostCtx, RT_BOOL(fFlags & GUEST_PROC_IN_FLAG_EOF),
+ *ppvScratchBuf, RT_MIN(cbInput, *pcbScratchBuf));
+ if (RT_FAILURE(rc))
+ VGSvcError("Error handling input message for PID=%RU32, rc=%Rrc\n", uPID, rc);
+ VGSvcGstCtrlProcessRelease(pProcess);
+ }
+ else
+ {
+ VGSvcError("Could not find PID %u for feeding %u bytes to it.\n", uPID, cbInput);
+ rc = VERR_PROCESS_NOT_FOUND;
+ VbglR3GuestCtrlProcCbStatusInput(pHostCtx, uPID, INPUT_STS_ERROR, rc, 0);
+ }
+ }
+ else
+ {
+ VGSvcError("Failed to retrieve parameters for process input: %Rrc (scratch %u bytes)\n", rc, *pcbScratchBuf);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ VGSvcVerbose(6, "Feeding input to PID=%RU32 resulted in rc=%Rrc\n", uPID, rc);
+ return rc;
+}
+
+
+/**
+ * Gets stdout/stderr output of a specific guest process.
+ *
+ * @returns VBox status code.
+ * @param pSession The session which is in charge.
+ * @param pHostCtx The host context to use.
+ */
+static int vgsvcGstCtrlSessionHandleProcOutput(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uPID;
+ uint32_t uHandleID;
+ uint32_t fFlags;
+ int rc = VbglR3GuestCtrlProcGetOutput(pHostCtx, &uPID, &uHandleID, &fFlags);
+#ifdef DEBUG_andy
+ VGSvcVerbose(4, "Getting output for PID=%RU32, CID=%RU32, uHandleID=%RU32, fFlags=%RU32\n",
+ uPID, pHostCtx->uContextID, uHandleID, fFlags);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the process and hand it the output request.
+ */
+ PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID);
+ if (pProcess)
+ {
+ rc = VGSvcGstCtrlProcessHandleOutput(pProcess, pHostCtx, uHandleID, _64K /* cbToRead */, fFlags);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error getting output for PID=%RU32, rc=%Rrc\n", uPID, rc);
+ VGSvcGstCtrlProcessRelease(pProcess);
+ }
+ else
+ {
+ VGSvcError("Could not find PID %u for draining handle %u (%#x).\n", uPID, uHandleID, uHandleID);
+ rc = VERR_PROCESS_NOT_FOUND;
+/** @todo r=bird:
+ *
+ * No way to report status status code for output requests?
+ *
+ */
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for process output request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+#ifdef DEBUG_andy
+ VGSvcVerbose(4, "Getting output for PID=%RU32 resulted in rc=%Rrc\n", uPID, rc);
+#endif
+ return rc;
+}
+
+
+/**
+ * Tells a guest process to terminate.
+ *
+ * @returns VBox status code.
+ * @param pSession The session which is in charge.
+ * @param pHostCtx The host context to use.
+ */
+static int vgsvcGstCtrlSessionHandleProcTerminate(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uPID;
+ int rc = VbglR3GuestCtrlProcGetTerminate(pHostCtx, &uPID);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the process and terminate it.
+ */
+ PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID);
+ if (pProcess)
+ {
+ rc = VGSvcGstCtrlProcessHandleTerm(pProcess);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error terminating PID=%RU32, rc=%Rrc\n", uPID, rc);
+
+ VGSvcGstCtrlProcessRelease(pProcess);
+ }
+ else
+ {
+ VGSvcError("Could not find PID %u for termination.\n", uPID);
+ rc = VERR_PROCESS_NOT_FOUND;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for process termination request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+#ifdef DEBUG_andy
+ VGSvcVerbose(4, "Terminating PID=%RU32 resulted in rc=%Rrc\n", uPID, rc);
+#endif
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleProcWaitFor(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uPID;
+ uint32_t uWaitFlags;
+ uint32_t uTimeoutMS;
+ int rc = VbglR3GuestCtrlProcGetWaitFor(pHostCtx, &uPID, &uWaitFlags, &uTimeoutMS);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the process and the realize that this call makes no sense
+ * since we'll notify the host when a process terminates anyway and
+ * hopefully don't need any additional encouragement.
+ */
+ PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID);
+ if (pProcess)
+ {
+ rc = VERR_NOT_IMPLEMENTED; /** @todo */
+ VGSvcGstCtrlProcessRelease(pProcess);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for process wait request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+int VGSvcGstCtrlSessionHandler(PVBOXSERVICECTRLSESSION pSession, uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf, volatile bool *pfShutdown)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(*ppvScratchBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfShutdown, VERR_INVALID_POINTER);
+
+
+ /*
+ * Only anonymous sessions (that is, sessions which run with local
+ * service privileges) or spawned session processes can do certain
+ * operations.
+ */
+ bool const fImpersonated = RT_BOOL(pSession->fFlags & ( VBOXSERVICECTRLSESSION_FLAG_SPAWN
+ | VBOXSERVICECTRLSESSION_FLAG_ANONYMOUS));
+ int rc = VERR_NOT_SUPPORTED; /* Play safe by default. */
+
+ switch (uMsg)
+ {
+ case HOST_MSG_SESSION_CLOSE:
+ /* Shutdown (this spawn). */
+ rc = VGSvcGstCtrlSessionClose(pSession);
+ *pfShutdown = true; /* Shutdown in any case. */
+ break;
+
+ case HOST_MSG_DIR_REMOVE:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleDirRemove(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_EXEC_CMD:
+ rc = vgsvcGstCtrlSessionHandleProcExec(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_EXEC_SET_INPUT:
+ rc = vgsvcGstCtrlSessionHandleProcInput(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf);
+ break;
+
+ case HOST_MSG_EXEC_GET_OUTPUT:
+ rc = vgsvcGstCtrlSessionHandleProcOutput(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_EXEC_TERMINATE:
+ rc = vgsvcGstCtrlSessionHandleProcTerminate(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_EXEC_WAIT_FOR:
+ rc = vgsvcGstCtrlSessionHandleProcWaitFor(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_FILE_OPEN:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileOpen(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_FILE_CLOSE:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileClose(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_FILE_READ:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileRead(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf);
+ break;
+
+ case HOST_MSG_FILE_READ_AT:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileReadAt(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf);
+ break;
+
+ case HOST_MSG_FILE_WRITE:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileWrite(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf);
+ break;
+
+ case HOST_MSG_FILE_WRITE_AT:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileWriteAt(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf);
+ break;
+
+ case HOST_MSG_FILE_SEEK:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileSeek(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_FILE_TELL:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileTell(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_FILE_SET_SIZE:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileSetSize(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_PATH_RENAME:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandlePathRename(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_PATH_USER_DOCUMENTS:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandlePathUserDocuments(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_PATH_USER_HOME:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandlePathUserHome(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_SHUTDOWN:
+ rc = vgsvcGstCtrlSessionHandleShutdown(pSession, pHostCtx);
+ break;
+
+ default: /* Not supported, see next code block. */
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc != VERR_NOT_SUPPORTED) /* Note: Reply to host must must be sent by above handler. */
+ VGSvcError("Error while handling message (uMsg=%RU32, cParms=%RU32), rc=%Rrc\n", uMsg, pHostCtx->uNumParms, rc);
+ else
+ {
+ /* We must skip and notify host here as best we can... */
+ VGSvcVerbose(1, "Unsupported message (uMsg=%RU32, cParms=%RU32) from host, skipping\n", uMsg, pHostCtx->uNumParms);
+ if (VbglR3GuestCtrlSupportsOptimizations(pHostCtx->uClientID))
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, VERR_NOT_SUPPORTED, uMsg);
+ else
+ VbglR3GuestCtrlMsgSkipOld(pHostCtx->uClientID);
+ rc = VINF_SUCCESS;
+ }
+
+ if (RT_FAILURE(rc))
+ VGSvcError("Error while handling message (uMsg=%RU32, cParms=%RU32), rc=%Rrc\n", uMsg, pHostCtx->uNumParms, rc);
+
+ return rc;
+}
+
+
+/**
+ * Thread main routine for a spawned guest session process.
+ *
+ * This thread runs in the main executable to control the spawned session process.
+ *
+ * @returns VBox status code.
+ * @param hThreadSelf Thread handle.
+ * @param pvUser Pointer to a VBOXSERVICECTRLSESSIONTHREAD structure.
+ *
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlSessionThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ PVBOXSERVICECTRLSESSIONTHREAD pThread = (PVBOXSERVICECTRLSESSIONTHREAD)pvUser;
+ AssertPtrReturn(pThread, VERR_INVALID_POINTER);
+
+ uint32_t const idSession = pThread->pStartupInfo->uSessionID;
+ uint32_t const idClient = g_idControlSvcClient;
+ VGSvcVerbose(3, "Session ID=%RU32 thread running\n", idSession);
+
+ /* Let caller know that we're done initializing, regardless of the result. */
+ int rc2 = RTThreadUserSignal(hThreadSelf);
+ AssertRC(rc2);
+
+ /*
+ * Wait for the child process to stop or the shutdown flag to be signalled.
+ */
+ RTPROCSTATUS ProcessStatus = { 0, RTPROCEXITREASON_NORMAL };
+ bool fProcessAlive = true;
+ bool fSessionCancelled = VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient);
+ uint32_t cMsShutdownTimeout = 30 * 1000; /** @todo Make this configurable. Later. */
+ uint64_t msShutdownStart = 0;
+ uint64_t const msStart = RTTimeMilliTS();
+ size_t offSecretKey = 0;
+ int rcWait;
+ for (;;)
+ {
+ /* Secret key feeding. */
+ if (offSecretKey < sizeof(pThread->abKey))
+ {
+ size_t cbWritten = 0;
+ rc2 = RTPipeWrite(pThread->hKeyPipe, &pThread->abKey[offSecretKey], sizeof(pThread->abKey) - offSecretKey, &cbWritten);
+ if (RT_SUCCESS(rc2))
+ offSecretKey += cbWritten;
+ }
+
+ /* Poll child process status. */
+ rcWait = RTProcWaitNoResume(pThread->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus);
+ if ( rcWait == VINF_SUCCESS
+ || rcWait == VERR_PROCESS_NOT_FOUND)
+ {
+ fProcessAlive = false;
+ break;
+ }
+ AssertMsgBreak(rcWait == VERR_PROCESS_RUNNING || rcWait == VERR_INTERRUPTED,
+ ("Got unexpected rc=%Rrc while waiting for session process termination\n", rcWait));
+
+ /* Shutting down? */
+ if (ASMAtomicReadBool(&pThread->fShutdown))
+ {
+ if (!msShutdownStart)
+ {
+ VGSvcVerbose(3, "Notifying guest session process (PID=%RU32, session ID=%RU32) ...\n",
+ pThread->hProcess, idSession);
+
+ VBGLR3GUESTCTRLCMDCTX hostCtx =
+ {
+ /* .idClient = */ idClient,
+ /* .idContext = */ VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession),
+ /* .uProtocol = */ pThread->pStartupInfo->uProtocol,
+ /* .cParams = */ 2
+ };
+ rc2 = VbglR3GuestCtrlSessionClose(&hostCtx, 0 /* fFlags */);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Unable to notify guest session process (PID=%RU32, session ID=%RU32), rc=%Rrc\n",
+ pThread->hProcess, idSession, rc2);
+
+ if (rc2 == VERR_NOT_SUPPORTED)
+ {
+ /* Terminate guest session process in case it's not supported by a too old host. */
+ rc2 = RTProcTerminate(pThread->hProcess);
+ VGSvcVerbose(3, "Terminating guest session process (PID=%RU32) ended with rc=%Rrc\n",
+ pThread->hProcess, rc2);
+ }
+ break;
+ }
+
+ VGSvcVerbose(3, "Guest session ID=%RU32 thread was asked to terminate, waiting for session process to exit (%RU32 ms timeout) ...\n",
+ idSession, cMsShutdownTimeout);
+ msShutdownStart = RTTimeMilliTS();
+ continue; /* Don't waste time on waiting. */
+ }
+ if (RTTimeMilliTS() - msShutdownStart > cMsShutdownTimeout)
+ {
+ VGSvcVerbose(3, "Guest session ID=%RU32 process did not shut down within time\n", idSession);
+ break;
+ }
+ }
+
+ /* Cancel the prepared session stuff after 30 seconds. */
+ if ( !fSessionCancelled
+ && RTTimeMilliTS() - msStart >= 30000)
+ {
+ VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, idSession);
+ fSessionCancelled = true;
+ }
+
+/** @todo r=bird: This 100ms sleep is _extremely_ sucky! */
+ RTThreadSleep(100); /* Wait a bit. */
+ }
+
+ if (!fSessionCancelled)
+ VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, idSession);
+
+ if (!fProcessAlive)
+ {
+ VGSvcVerbose(2, "Guest session process (ID=%RU32) terminated with rc=%Rrc, reason=%d, status=%d\n",
+ idSession, rcWait, ProcessStatus.enmReason, ProcessStatus.iStatus);
+ if (ProcessStatus.iStatus == RTEXITCODE_INIT)
+ {
+ VGSvcError("Guest session process (ID=%RU32) failed to initialize. Here some hints:\n", idSession);
+ VGSvcError("- Is logging enabled and the output directory is read-only by the guest session user?\n");
+ /** @todo Add more here. */
+ }
+ }
+
+ uint32_t uSessionStatus = GUEST_SESSION_NOTIFYTYPE_UNDEFINED;
+ int32_t iSessionResult = VINF_SUCCESS;
+
+ if (fProcessAlive)
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ if (i)
+ RTThreadSleep(3000);
+
+ VGSvcVerbose(2, "Guest session ID=%RU32 process still alive, killing attempt %d/3\n", idSession, i + 1);
+
+ rc2 = RTProcTerminate(pThread->hProcess);
+ if (RT_SUCCESS(rc2))
+ break;
+ }
+
+ VGSvcVerbose(2, "Guest session ID=%RU32 process termination resulted in rc=%Rrc\n", idSession, rc2);
+ uSessionStatus = RT_SUCCESS(rc2) ? GUEST_SESSION_NOTIFYTYPE_TOK : GUEST_SESSION_NOTIFYTYPE_TOA;
+ }
+ else if (RT_SUCCESS(rcWait))
+ {
+ switch (ProcessStatus.enmReason)
+ {
+ case RTPROCEXITREASON_NORMAL:
+ uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN;
+ iSessionResult = ProcessStatus.iStatus; /* Report back the session's exit code. */
+ break;
+
+ case RTPROCEXITREASON_ABEND:
+ uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA;
+ /* iSessionResult is undefined (0). */
+ break;
+
+ case RTPROCEXITREASON_SIGNAL:
+ uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TES;
+ iSessionResult = ProcessStatus.iStatus; /* Report back the signal number. */
+ break;
+
+ default:
+ AssertMsgFailed(("Unhandled process termination reason (%d)\n", ProcessStatus.enmReason));
+ uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA;
+ break;
+ }
+ }
+ else
+ {
+ /* If we didn't find the guest process anymore, just assume it terminated normally. */
+ uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN;
+ }
+
+ /* Make sure to set stopped state before we let the host know. */
+ ASMAtomicWriteBool(&pThread->fStopped, true);
+
+ /* Report final status, regardless if we failed to wait above, so that the host knows what's going on. */
+ VGSvcVerbose(3, "Reporting final status %RU32 of session ID=%RU32\n", uSessionStatus, idSession);
+ Assert(uSessionStatus != GUEST_SESSION_NOTIFYTYPE_UNDEFINED);
+
+ VBGLR3GUESTCTRLCMDCTX ctx = { idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession),
+ 0 /* uProtocol, unused */, 0 /* uNumParms, unused */ };
+ rc2 = VbglR3GuestCtrlSessionNotify(&ctx, uSessionStatus, iSessionResult);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Reporting final status of session ID=%RU32 failed with rc=%Rrc\n", idSession, rc2);
+
+ VGSvcVerbose(3, "Thread for session ID=%RU32 ended with sessionStatus=%#x (%RU32), sessionRc=%#x (%Rrc)\n",
+ idSession, uSessionStatus, uSessionStatus, iSessionResult, iSessionResult);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Reads the secret key the parent VBoxService instance passed us and pass it
+ * along as a authentication token to the host service.
+ *
+ * For older hosts, this sets up the message filtering.
+ *
+ * @returns VBox status code.
+ * @param idClient The HGCM client ID.
+ * @param idSession The session ID.
+ */
+static int vgsvcGstCtrlSessionReadKeyAndAccept(uint32_t idClient, uint32_t idSession)
+{
+ /*
+ * Read it.
+ */
+ RTHANDLE Handle;
+ int rc = RTHandleGetStandard(RTHANDLESTD_INPUT, true /*fLeaveOpen*/, &Handle);
+ if (RT_SUCCESS(rc))
+ {
+ if (Handle.enmType == RTHANDLETYPE_PIPE)
+ {
+ uint8_t abSecretKey[RT_SIZEOFMEMB(VBOXSERVICECTRLSESSIONTHREAD, abKey)];
+ rc = RTPipeReadBlocking(Handle.u.hPipe, abSecretKey, sizeof(abSecretKey), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "Got secret key from standard input.\n");
+
+ /*
+ * Do the accepting, if appropriate.
+ */
+ if (g_fControlSupportsOptimizations)
+ {
+ rc = VbglR3GuestCtrlSessionAccept(idClient, idSession, abSecretKey, sizeof(abSecretKey));
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "Session %u accepted (client ID %u)\n", idClient, idSession);
+ else
+ VGSvcError("Failed to accept session %u (client ID %u): %Rrc\n", idClient, idSession, rc);
+ }
+ else
+ {
+ /* For legacy hosts, we do the filtering thingy. */
+ rc = VbglR3GuestCtrlMsgFilterSet(idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession),
+ VBOX_GUESTCTRL_FILTER_BY_SESSION(idSession), 0);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "Session %u filtering successfully enabled\n", idSession);
+ else
+ VGSvcError("Failed to set session filter: %Rrc\n", rc);
+ }
+ }
+ else
+ VGSvcError("Error reading secret key from standard input: %Rrc\n", rc);
+ }
+ else
+ {
+ VGSvcError("Standard input is not a pipe!\n");
+ rc = VERR_INVALID_HANDLE;
+ }
+ RTHandleClose(&Handle);
+ }
+ else
+ VGSvcError("RTHandleGetStandard failed on standard input: %Rrc\n", rc);
+ return rc;
+}
+
+/**
+ * Invalidates a guest session by updating all it's internal parameters like host features and stuff.
+ *
+ * @param pSession Session to invalidate.
+ * @param idClient Client ID to use.
+ */
+static void vgsvcGstCtrlSessionInvalidate(PVBOXSERVICECTRLSESSION pSession, uint32_t idClient)
+{
+ RT_NOREF(pSession);
+
+ VGSvcVerbose(1, "Invalidating session %RU32 (client ID=%RU32)\n", idClient, pSession->StartupInfo.uSessionID);
+
+ int rc2 = VbglR3GuestCtrlQueryFeatures(idClient, &g_fControlHostFeatures0);
+ if (RT_SUCCESS(rc2)) /* Querying host features is not fatal -- do not use rc here. */
+ {
+ VGSvcVerbose(1, "g_fControlHostFeatures0=%#x\n", g_fControlHostFeatures0);
+ }
+ else
+ VGSvcVerbose(1, "Querying host features failed with %Rrc\n", rc2);
+}
+
+/**
+ * Main message handler for the guest control session process.
+ *
+ * @returns exit code.
+ * @param pSession Pointer to g_Session.
+ * @thread main.
+ */
+static RTEXITCODE vgsvcGstCtrlSessionSpawnWorker(PVBOXSERVICECTRLSESSION pSession)
+{
+ AssertPtrReturn(pSession, RTEXITCODE_FAILURE);
+ VGSvcVerbose(0, "Hi, this is guest session ID=%RU32\n", pSession->StartupInfo.uSessionID);
+
+ /*
+ * Connect to the host service.
+ */
+ uint32_t idClient;
+ int rc = VbglR3GuestCtrlConnect(&idClient);
+ if (RT_FAILURE(rc))
+ return VGSvcError("Error connecting to guest control service, rc=%Rrc\n", rc);
+ g_fControlSupportsOptimizations = VbglR3GuestCtrlSupportsOptimizations(idClient);
+ g_idControlSvcClient = idClient;
+
+ VGSvcVerbose(1, "Using client ID=%RU32\n", idClient);
+
+ vgsvcGstCtrlSessionInvalidate(pSession, idClient);
+
+ rc = vgsvcGstCtrlSessionReadKeyAndAccept(idClient, pSession->StartupInfo.uSessionID);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Report started status.
+ * If session status cannot be posted to the host for some reason, bail out.
+ */
+ VBGLR3GUESTCTRLCMDCTX ctx = { idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(pSession->StartupInfo.uSessionID),
+ 0 /* uProtocol, unused */, 0 /* uNumParms, unused */ };
+ rc = VbglR3GuestCtrlSessionNotify(&ctx, GUEST_SESSION_NOTIFYTYPE_STARTED, VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate a scratch buffer for messages which also send payload data with them.
+ * This buffer may grow if the host sends us larger chunks of data.
+ */
+ uint32_t cbScratchBuf = _64K;
+ void *pvScratchBuf = RTMemAlloc(cbScratchBuf);
+ if (pvScratchBuf)
+ {
+ int cFailedMsgPeeks = 0;
+
+ /*
+ * Message processing loop.
+ */
+ VBGLR3GUESTCTRLCMDCTX CtxHost = { idClient, 0 /* Context ID */, pSession->StartupInfo.uProtocol, 0 };
+ for (;;)
+ {
+ VGSvcVerbose(3, "Waiting for host msg ...\n");
+ uint32_t uMsg = 0;
+ rc = VbglR3GuestCtrlMsgPeekWait(idClient, &uMsg, &CtxHost.uNumParms, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "Msg=%RU32 (%RU32 parms) retrieved (%Rrc)\n", uMsg, CtxHost.uNumParms, rc);
+
+ /*
+ * Pass it on to the session handler.
+ * Note! Only when handling HOST_SESSION_CLOSE is the rc used.
+ */
+ bool fShutdown = false;
+ rc = VGSvcGstCtrlSessionHandler(pSession, uMsg, &CtxHost, &pvScratchBuf, &cbScratchBuf, &fShutdown);
+ if (fShutdown)
+ break;
+
+ cFailedMsgPeeks = 0;
+
+ /* Let others run (guests are often single CPU) ... */
+ RTThreadYield();
+ }
+ /*
+ * Handle restore notification from host. All the context IDs (sessions,
+ * files, proceses, etc) are invalidated by a VM restore and must be closed.
+ */
+ else if (rc == VERR_VM_RESTORED)
+ {
+ VGSvcVerbose(1, "The VM session ID changed (i.e. restored), closing stale session %RU32\n",
+ pSession->StartupInfo.uSessionID);
+
+ /* We currently don't serialize guest sessions, guest processes and other guest control objects
+ * within saved states. So just close this session and report success to the parent process.
+ *
+ * Note: Not notifying the host here is intentional, as it wouldn't have any information
+ * about what to do with it.
+ */
+ rc = VINF_SUCCESS; /* Report success as exit code. */
+ break;
+ }
+ else
+ {
+ VGSvcVerbose(1, "Getting host message failed with %Rrc\n", rc);
+
+ if (cFailedMsgPeeks++ == 3)
+ break;
+
+ RTThreadSleep(3 * RT_MS_1SEC);
+
+ /** @todo Shouldn't we have a plan for handling connection loss and such? */
+ }
+ }
+
+ /*
+ * Shutdown.
+ */
+ RTMemFree(pvScratchBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ VGSvcVerbose(0, "Session %RU32 ended\n", pSession->StartupInfo.uSessionID);
+ }
+ else
+ VGSvcError("Reporting session ID=%RU32 started status failed with rc=%Rrc\n", pSession->StartupInfo.uSessionID, rc);
+ }
+ else
+ VGSvcError("Setting message filterAdd=0x%x failed with rc=%Rrc\n", pSession->StartupInfo.uSessionID, rc);
+
+ VGSvcVerbose(3, "Disconnecting client ID=%RU32 ...\n", idClient);
+ VbglR3GuestCtrlDisconnect(idClient);
+ g_idControlSvcClient = 0;
+
+ VGSvcVerbose(3, "Session worker returned with rc=%Rrc\n", rc);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Finds a (formerly) started guest process given by its PID and increases its
+ * reference count.
+ *
+ * Must be decreased by the caller with VGSvcGstCtrlProcessRelease().
+ *
+ * @returns Guest process if found, otherwise NULL.
+ * @param pSession Pointer to guest session where to search process in.
+ * @param uPID PID to search for.
+ *
+ * @note This does *not lock the process!
+ */
+PVBOXSERVICECTRLPROCESS VGSvcGstCtrlSessionRetainProcess(PVBOXSERVICECTRLSESSION pSession, uint32_t uPID)
+{
+ AssertPtrReturn(pSession, NULL);
+
+ PVBOXSERVICECTRLPROCESS pProcess = NULL;
+ int rc = RTCritSectEnter(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ PVBOXSERVICECTRLPROCESS pCurProcess;
+ RTListForEach(&pSession->lstProcesses, pCurProcess, VBOXSERVICECTRLPROCESS, Node)
+ {
+ if (pCurProcess->uPID == uPID)
+ {
+ rc = RTCritSectEnter(&pCurProcess->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pCurProcess->cRefs++;
+ rc = RTCritSectLeave(&pCurProcess->CritSect);
+ AssertRC(rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ pProcess = pCurProcess;
+ break;
+ }
+ }
+
+ rc = RTCritSectLeave(&pSession->CritSect);
+ AssertRC(rc);
+ }
+
+ return pProcess;
+}
+
+
+int VGSvcGstCtrlSessionClose(PVBOXSERVICECTRLSESSION pSession)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ VGSvcVerbose(0, "Session %RU32 is about to close ...\n", pSession->StartupInfo.uSessionID);
+
+ int rc = RTCritSectEnter(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Close all guest processes.
+ */
+ VGSvcVerbose(0, "Stopping all guest processes ...\n");
+
+ /* Signal all guest processes in the active list that we want to shutdown. */
+ PVBOXSERVICECTRLPROCESS pProcess;
+ RTListForEach(&pSession->lstProcesses, pProcess, VBOXSERVICECTRLPROCESS, Node)
+ VGSvcGstCtrlProcessStop(pProcess);
+
+ VGSvcVerbose(1, "%RU32 guest processes were signalled to stop\n", pSession->cProcesses);
+
+ /* Wait for all active threads to shutdown and destroy the active thread list. */
+ PVBOXSERVICECTRLPROCESS pProcessNext;
+ RTListForEachSafe(&pSession->lstProcesses, pProcess, pProcessNext, VBOXSERVICECTRLPROCESS, Node)
+ {
+ int rc3 = RTCritSectLeave(&pSession->CritSect);
+ AssertRC(rc3);
+
+ int rc2 = VGSvcGstCtrlProcessWait(pProcess, 30 * 1000 /* Wait 30 seconds max. */, NULL /* rc */);
+
+ rc3 = RTCritSectEnter(&pSession->CritSect);
+ AssertRC(rc3);
+
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = vgsvcGstCtrlSessionProcessRemoveInternal(pSession, pProcess);
+ if (RT_SUCCESS(rc2))
+ {
+ VGSvcGstCtrlProcessFree(pProcess);
+ pProcess = NULL;
+ }
+ }
+ }
+
+ AssertMsg(pSession->cProcesses == 0,
+ ("Session process list still contains %RU32 when it should not\n", pSession->cProcesses));
+ AssertMsg(RTListIsEmpty(&pSession->lstProcesses),
+ ("Session process list is not empty when it should\n"));
+
+ /*
+ * Close all left guest files.
+ */
+ VGSvcVerbose(0, "Closing all guest files ...\n");
+
+ PVBOXSERVICECTRLFILE pFile, pFileNext;
+ RTListForEachSafe(&pSession->lstFiles, pFile, pFileNext, VBOXSERVICECTRLFILE, Node)
+ {
+ int rc2 = vgsvcGstCtrlSessionFileFree(pFile);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Unable to close file '%s'; rc=%Rrc\n", pFile->pszName, rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Keep going. */
+ }
+
+ pFile = NULL; /* To make it obvious. */
+ }
+
+ AssertMsg(pSession->cFiles == 0,
+ ("Session file list still contains %RU32 when it should not\n", pSession->cFiles));
+ AssertMsg(RTListIsEmpty(&pSession->lstFiles),
+ ("Session file list is not empty when it should\n"));
+
+ int rc2 = RTCritSectLeave(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+
+int VGSvcGstCtrlSessionDestroy(PVBOXSERVICECTRLSESSION pSession)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ int rc = VGSvcGstCtrlSessionClose(pSession);
+
+ /* Destroy critical section. */
+ RTCritSectDelete(&pSession->CritSect);
+
+ return rc;
+}
+
+
+int VGSvcGstCtrlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t fFlags)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ RTListInit(&pSession->lstProcesses);
+ RTListInit(&pSession->lstFiles);
+
+ pSession->cProcesses = 0;
+ pSession->cFiles = 0;
+
+ pSession->fFlags = fFlags;
+
+ /* Init critical section for protecting the thread lists. */
+ int rc = RTCritSectInit(&pSession->CritSect);
+ AssertRC(rc);
+
+ return rc;
+}
+
+
+/**
+ * Adds a guest process to a session's process list.
+ *
+ * @return VBox status code.
+ * @param pSession Guest session to add process to.
+ * @param pProcess Guest process to add.
+ */
+int VGSvcGstCtrlSessionProcessAdd(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "Adding process (PID %RU32) to session ID=%RU32\n", pProcess->uPID, pSession->StartupInfo.uSessionID);
+
+ /* Add process to session list. */
+ RTListAppend(&pSession->lstProcesses, &pProcess->Node);
+
+ pSession->cProcesses++;
+ VGSvcVerbose(3, "Now session ID=%RU32 has %RU32 processes total\n",
+ pSession->StartupInfo.uSessionID, pSession->cProcesses);
+
+ int rc2 = RTCritSectLeave(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Removes a guest process from a session's process list.
+ * Internal version, does not do locking.
+ *
+ * @return VBox status code.
+ * @param pSession Guest session to remove process from.
+ * @param pProcess Guest process to remove.
+ */
+static int vgsvcGstCtrlSessionProcessRemoveInternal(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess)
+{
+ VGSvcVerbose(3, "Removing process (PID %RU32) from session ID=%RU32\n", pProcess->uPID, pSession->StartupInfo.uSessionID);
+ AssertReturn(pProcess->cRefs == 0, VERR_WRONG_ORDER);
+
+ RTListNodeRemove(&pProcess->Node);
+
+ AssertReturn(pSession->cProcesses, VERR_WRONG_ORDER);
+ pSession->cProcesses--;
+ VGSvcVerbose(3, "Now session ID=%RU32 has %RU32 processes total\n",
+ pSession->StartupInfo.uSessionID, pSession->cProcesses);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Removes a guest process from a session's process list.
+ *
+ * @return VBox status code.
+ * @param pSession Guest session to remove process from.
+ * @param pProcess Guest process to remove.
+ */
+int VGSvcGstCtrlSessionProcessRemove(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vgsvcGstCtrlSessionProcessRemoveInternal(pSession, pProcess);
+
+ int rc2 = RTCritSectLeave(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Determines whether starting a new guest process according to the
+ * maximum number of concurrent guest processes defined is allowed or not.
+ *
+ * @return VBox status code.
+ * @param pSession The guest session.
+ * @param pfAllowed \c True if starting (another) guest process
+ * is allowed, \c false if not.
+ */
+int VGSvcGstCtrlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession, bool *pfAllowed)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfAllowed, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check if we're respecting our memory policy by checking
+ * how many guest processes are started and served already.
+ */
+ bool fLimitReached = false;
+ if (pSession->uProcsMaxKept) /* If we allow unlimited processes (=0), take a shortcut. */
+ {
+ VGSvcVerbose(3, "Maximum kept guest processes set to %RU32, acurrent=%RU32\n",
+ pSession->uProcsMaxKept, pSession->cProcesses);
+
+ int32_t iProcsLeft = (pSession->uProcsMaxKept - pSession->cProcesses - 1);
+ if (iProcsLeft < 0)
+ {
+ VGSvcVerbose(3, "Maximum running guest processes reached (%RU32)\n", pSession->uProcsMaxKept);
+ fLimitReached = true;
+ }
+ }
+
+ *pfAllowed = !fLimitReached;
+
+ int rc2 = RTCritSectLeave(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Cleans up stopped and no longer used processes.
+ *
+ * This will free and remove processes from the session's process list.
+ *
+ * @returns VBox status code.
+ * @param pSession Session to clean up processes for.
+ */
+static int vgsvcGstCtrlSessionCleanupProcesses(const PVBOXSERVICECTRLSESSION pSession)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ VGSvcVerbose(3, "Cleaning up stopped processes for session %RU32 ...\n", pSession->StartupInfo.uSessionID);
+
+ int rc2 = RTCritSectEnter(&pSession->CritSect);
+ AssertRC(rc2);
+
+ int rc = VINF_SUCCESS;
+
+ PVBOXSERVICECTRLPROCESS pCurProcess, pNextProcess;
+ RTListForEachSafe(&pSession->lstProcesses, pCurProcess, pNextProcess, VBOXSERVICECTRLPROCESS, Node)
+ {
+ if (ASMAtomicReadBool(&pCurProcess->fStopped))
+ {
+ rc2 = RTCritSectLeave(&pSession->CritSect);
+ AssertRC(rc2);
+
+ rc = VGSvcGstCtrlProcessWait(pCurProcess, 30 * 1000 /* Wait 30 seconds max. */, NULL /* rc */);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcGstCtrlSessionProcessRemove(pSession, pCurProcess);
+ VGSvcGstCtrlProcessFree(pCurProcess);
+ }
+
+ rc2 = RTCritSectEnter(&pSession->CritSect);
+ AssertRC(rc2);
+
+ /* If failed, try next time we're being called. */
+ }
+ }
+
+ rc2 = RTCritSectLeave(&pSession->CritSect);
+ AssertRC(rc2);
+
+ if (RT_FAILURE(rc))
+ VGSvcError("Cleaning up stopped processes for session %RU32 failed with %Rrc\n", pSession->StartupInfo.uSessionID, rc);
+
+ return rc;
+}
+
+
+/**
+ * Creates the process for a guest session.
+ *
+ * @return VBox status code.
+ * @param pSessionStartupInfo Session startup info.
+ * @param pSessionThread The session thread under construction.
+ * @param uCtrlSessionThread The session thread debug ordinal.
+ */
+static int vgsvcVGSvcGstCtrlSessionThreadCreateProcess(const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo,
+ PVBOXSERVICECTRLSESSIONTHREAD pSessionThread, uint32_t uCtrlSessionThread)
+{
+ RT_NOREF(uCtrlSessionThread);
+
+ /*
+ * Is this an anonymous session? Anonymous sessions run with the same
+ * privileges as the main VBoxService executable.
+ */
+ bool const fAnonymous = pSessionThread->pStartupInfo->pszUser
+ && pSessionThread->pStartupInfo->pszUser[0] == '\0';
+ if (fAnonymous)
+ {
+ Assert(!strlen(pSessionThread->pStartupInfo->pszPassword));
+ Assert(!strlen(pSessionThread->pStartupInfo->pszDomain));
+
+ VGSvcVerbose(3, "New anonymous guest session ID=%RU32 created, fFlags=%x, using protocol %RU32\n",
+ pSessionStartupInfo->uSessionID,
+ pSessionStartupInfo->fFlags,
+ pSessionStartupInfo->uProtocol);
+ }
+ else
+ {
+ VGSvcVerbose(3, "Spawning new guest session ID=%RU32, szUser=%s, szPassword=%s, szDomain=%s, fFlags=%x, using protocol %RU32\n",
+ pSessionStartupInfo->uSessionID,
+ pSessionStartupInfo->pszUser,
+#ifdef DEBUG
+ pSessionStartupInfo->pszPassword,
+#else
+ "XXX", /* Never show passwords in release mode. */
+#endif
+ pSessionStartupInfo->pszDomain,
+ pSessionStartupInfo->fFlags,
+ pSessionStartupInfo->uProtocol);
+ }
+
+ /*
+ * Spawn a child process for doing the actual session handling.
+ * Start by assembling the argument list.
+ */
+ char szExeName[RTPATH_MAX];
+ char *pszExeName = RTProcGetExecutablePath(szExeName, sizeof(szExeName));
+ AssertPtrReturn(pszExeName, VERR_FILENAME_TOO_LONG);
+
+ char szParmSessionID[32];
+ RTStrPrintf(szParmSessionID, sizeof(szParmSessionID), "--session-id=%RU32", pSessionThread->pStartupInfo->uSessionID);
+
+ char szParmSessionProto[32];
+ RTStrPrintf(szParmSessionProto, sizeof(szParmSessionProto), "--session-proto=%RU32",
+ pSessionThread->pStartupInfo->uProtocol);
+#ifdef DEBUG
+ char szParmThreadId[32];
+ RTStrPrintf(szParmThreadId, sizeof(szParmThreadId), "--thread-id=%RU32", uCtrlSessionThread);
+#endif
+ unsigned idxArg = 0; /* Next index in argument vector. */
+ char const *apszArgs[24];
+
+ apszArgs[idxArg++] = pszExeName;
+#ifdef VBOXSERVICE_ARG1_UTF8_ARGV
+ apszArgs[idxArg++] = VBOXSERVICE_ARG1_UTF8_ARGV; Assert(idxArg == 2);
+#endif
+ apszArgs[idxArg++] = "guestsession";
+ apszArgs[idxArg++] = szParmSessionID;
+ apszArgs[idxArg++] = szParmSessionProto;
+#ifdef DEBUG
+ apszArgs[idxArg++] = szParmThreadId;
+#endif
+ if (!fAnonymous) /* Do we need to pass a user name? */
+ {
+ apszArgs[idxArg++] = "--user";
+ apszArgs[idxArg++] = pSessionThread->pStartupInfo->pszUser;
+
+ if (strlen(pSessionThread->pStartupInfo->pszDomain))
+ {
+ apszArgs[idxArg++] = "--domain";
+ apszArgs[idxArg++] = pSessionThread->pStartupInfo->pszDomain;
+ }
+ }
+
+ /* Add same verbose flags as parent process. */
+ char szParmVerbose[32];
+ if (g_cVerbosity > 0)
+ {
+ unsigned cVs = RT_MIN(g_cVerbosity, RT_ELEMENTS(szParmVerbose) - 2);
+ szParmVerbose[0] = '-';
+ memset(&szParmVerbose[1], 'v', cVs);
+ szParmVerbose[1 + cVs] = '\0';
+ apszArgs[idxArg++] = szParmVerbose;
+ }
+
+ /* Add log file handling. Each session will have an own
+ * log file, naming based on the parent log file. */
+ char szParmLogFile[sizeof(g_szLogFile) + 128];
+ if (g_szLogFile[0])
+ {
+ const char *pszSuffix = RTPathSuffix(g_szLogFile);
+ if (!pszSuffix)
+ pszSuffix = strchr(g_szLogFile, '\0');
+ size_t cchBase = pszSuffix - g_szLogFile;
+
+ RTTIMESPEC Now;
+ RTTimeNow(&Now);
+ char szTime[64];
+ RTTimeSpecToString(&Now, szTime, sizeof(szTime));
+
+ /* Replace out characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */
+ static const RTUNICP s_uszValidRangePairs[] =
+ {
+ ' ', ' ',
+ '(', ')',
+ '-', '.',
+ '0', '9',
+ 'A', 'Z',
+ 'a', 'z',
+ '_', '_',
+ 0xa0, 0xd7af,
+ '\0'
+ };
+ ssize_t cReplaced = RTStrPurgeComplementSet(szTime, s_uszValidRangePairs, '_' /* chReplacement */);
+ AssertReturn(cReplaced, VERR_INVALID_UTF8_ENCODING);
+
+#ifndef DEBUG
+ RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), "%.*s-%RU32-%s-%s%s",
+ cchBase, g_szLogFile, pSessionStartupInfo->uSessionID, pSessionStartupInfo->pszUser, szTime, pszSuffix);
+#else
+ RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), "%.*s-%RU32-%RU32-%s-%s%s",
+ cchBase, g_szLogFile, pSessionStartupInfo->uSessionID, uCtrlSessionThread,
+ pSessionStartupInfo->pszUser, szTime, pszSuffix);
+#endif
+ apszArgs[idxArg++] = "--logfile";
+ apszArgs[idxArg++] = szParmLogFile;
+ }
+
+#ifdef DEBUG
+ if (g_Session.fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT)
+ apszArgs[idxArg++] = "--dump-stdout";
+ if (g_Session.fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR)
+ apszArgs[idxArg++] = "--dump-stderr";
+#endif
+ apszArgs[idxArg] = NULL;
+ Assert(idxArg < RT_ELEMENTS(apszArgs));
+
+ if (g_cVerbosity > 3)
+ {
+ VGSvcVerbose(4, "Spawning parameters:\n");
+ for (idxArg = 0; apszArgs[idxArg]; idxArg++)
+ VGSvcVerbose(4, " %s\n", apszArgs[idxArg]);
+ }
+
+ /*
+ * Flags.
+ */
+ uint32_t const fProcCreate = RTPROC_FLAGS_PROFILE
+#ifdef RT_OS_WINDOWS
+ | RTPROC_FLAGS_SERVICE
+ | RTPROC_FLAGS_HIDDEN
+#endif
+ | VBOXSERVICE_PROC_F_UTF8_ARGV;
+
+ /*
+ * Configure standard handles.
+ */
+ RTHANDLE hStdIn;
+ int rc = RTPipeCreate(&hStdIn.u.hPipe, &pSessionThread->hKeyPipe, RTPIPE_C_INHERIT_READ);
+ if (RT_SUCCESS(rc))
+ {
+ hStdIn.enmType = RTHANDLETYPE_PIPE;
+
+ RTHANDLE hStdOutAndErr;
+ rc = RTFileOpenBitBucket(&hStdOutAndErr.u.hFile, RTFILE_O_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ hStdOutAndErr.enmType = RTHANDLETYPE_FILE;
+
+ /*
+ * Windows: If a domain name is given, construct an UPN (User Principle Name)
+ * with the domain name built-in, e.g. "joedoe@example.com".
+ */
+ const char *pszUser = pSessionThread->pStartupInfo->pszUser;
+#ifdef RT_OS_WINDOWS
+ char *pszUserUPN = NULL;
+ if (pSessionThread->pStartupInfo->pszDomain[0])
+ {
+ int cchbUserUPN = RTStrAPrintf(&pszUserUPN, "%s@%s",
+ pSessionThread->pStartupInfo->pszUser,
+ pSessionThread->pStartupInfo->pszDomain);
+ if (cchbUserUPN > 0)
+ {
+ pszUser = pszUserUPN;
+ VGSvcVerbose(3, "Using UPN: %s\n", pszUserUPN);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ if (RT_SUCCESS(rc))
+#endif
+ {
+ /*
+ * Finally, create the process.
+ */
+ rc = RTProcCreateEx(pszExeName, apszArgs, RTENV_DEFAULT, fProcCreate,
+ &hStdIn, &hStdOutAndErr, &hStdOutAndErr,
+ !fAnonymous ? pszUser : NULL,
+ !fAnonymous ? pSessionThread->pStartupInfo->pszPassword : NULL,
+ NULL /*pvExtraData*/,
+ &pSessionThread->hProcess);
+ }
+#ifdef RT_OS_WINDOWS
+ RTStrFree(pszUserUPN);
+#endif
+ RTFileClose(hStdOutAndErr.u.hFile);
+ }
+
+ RTPipeClose(hStdIn.u.hPipe);
+ }
+ return rc;
+}
+
+
+/**
+ * Creates a guest session.
+ *
+ * This will spawn a new VBoxService.exe instance under behalf of the given user
+ * which then will act as a session host. On successful open, the session will
+ * be added to the given session thread list.
+ *
+ * @return VBox status code.
+ * @param pList Which list to use to store the session thread in.
+ * @param pSessionStartupInfo Session startup info.
+ * @param ppSessionThread Returns newly created session thread on success.
+ * Optional.
+ */
+int VGSvcGstCtrlSessionThreadCreate(PRTLISTANCHOR pList, const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo,
+ PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSessionStartupInfo, VERR_INVALID_POINTER);
+ /* ppSessionThread is optional. */
+
+#ifdef VBOX_STRICT
+ /* Check for existing session in debug mode. Should never happen because of
+ * Main consistency. */
+ PVBOXSERVICECTRLSESSIONTHREAD pSessionCur;
+ RTListForEach(pList, pSessionCur, VBOXSERVICECTRLSESSIONTHREAD, Node)
+ {
+ AssertMsgReturn( pSessionCur->fStopped == true
+ || pSessionCur->pStartupInfo->uSessionID != pSessionStartupInfo->uSessionID,
+ ("Guest session thread ID=%RU32 already exists (fStopped=%RTbool)\n",
+ pSessionCur->pStartupInfo->uSessionID, pSessionCur->fStopped), VERR_ALREADY_EXISTS);
+ }
+#endif
+
+ /* Static counter to help tracking session thread <-> process relations. */
+ static uint32_t s_uCtrlSessionThread = 0;
+
+ /*
+ * Allocate and initialize the session thread structure.
+ */
+ int rc;
+ PVBOXSERVICECTRLSESSIONTHREAD pSessionThread = (PVBOXSERVICECTRLSESSIONTHREAD)RTMemAllocZ(sizeof(*pSessionThread));
+ if (pSessionThread)
+ {
+ //pSessionThread->fShutdown = false;
+ //pSessionThread->fStarted = false;
+ //pSessionThread->fStopped = false;
+ pSessionThread->hKeyPipe = NIL_RTPIPE;
+ pSessionThread->Thread = NIL_RTTHREAD;
+ pSessionThread->hProcess = NIL_RTPROCESS;
+
+ /* Duplicate startup info. */
+ pSessionThread->pStartupInfo = VbglR3GuestCtrlSessionStartupInfoDup(pSessionStartupInfo);
+ AssertPtrReturn(pSessionThread->pStartupInfo, VERR_NO_MEMORY);
+
+ /* Generate the secret key. */
+ RTRandBytes(pSessionThread->abKey, sizeof(pSessionThread->abKey));
+
+ rc = RTCritSectInit(&pSessionThread->CritSect);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Give the session key to the host so it can validate the client.
+ */
+ if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient))
+ {
+ for (uint32_t i = 0; i < 10; i++)
+ {
+ rc = VbglR3GuestCtrlSessionPrepare(g_idControlSvcClient, pSessionStartupInfo->uSessionID,
+ pSessionThread->abKey, sizeof(pSessionThread->abKey));
+ if (rc != VERR_OUT_OF_RESOURCES)
+ break;
+ RTThreadSleep(100);
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ s_uCtrlSessionThread++;
+
+ /*
+ * Start the session child process.
+ */
+ rc = vgsvcVGSvcGstCtrlSessionThreadCreateProcess(pSessionStartupInfo, pSessionThread, s_uCtrlSessionThread);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Start the session thread.
+ */
+ rc = RTThreadCreateF(&pSessionThread->Thread, vgsvcGstCtrlSessionThread, pSessionThread /*pvUser*/, 0 /*cbStack*/,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctls%RU32", s_uCtrlSessionThread);
+ if (RT_SUCCESS(rc))
+ {
+ /* Wait for the thread to initialize. */
+ rc = RTThreadUserWait(pSessionThread->Thread, RT_MS_1MIN);
+ if ( RT_SUCCESS(rc)
+ && !ASMAtomicReadBool(&pSessionThread->fShutdown))
+ {
+ VGSvcVerbose(2, "Thread for session ID=%RU32 started\n", pSessionThread->pStartupInfo->uSessionID);
+
+ ASMAtomicXchgBool(&pSessionThread->fStarted, true);
+
+ /* Add session to list. */
+ RTListAppend(pList, &pSessionThread->Node);
+ if (ppSessionThread) /* Return session if wanted. */
+ *ppSessionThread = pSessionThread;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Bail out.
+ */
+ VGSvcError("Thread for session ID=%RU32 failed to start, rc=%Rrc\n",
+ pSessionThread->pStartupInfo->uSessionID, rc);
+ if (RT_SUCCESS_NP(rc))
+ rc = VERR_CANT_CREATE; /** @todo Find a better rc. */
+ }
+ else
+ VGSvcError("Creating session thread failed, rc=%Rrc\n", rc);
+
+ RTProcTerminate(pSessionThread->hProcess);
+ uint32_t cMsWait = 1;
+ while ( RTProcWait(pSessionThread->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, NULL) == VERR_PROCESS_RUNNING
+ && cMsWait <= 9) /* 1023 ms */
+ {
+ RTThreadSleep(cMsWait);
+ cMsWait <<= 1;
+ }
+ }
+
+ if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient))
+ VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, pSessionStartupInfo->uSessionID);
+ }
+ else
+ VGSvcVerbose(3, "VbglR3GuestCtrlSessionPrepare failed: %Rrc\n", rc);
+ RTPipeClose(pSessionThread->hKeyPipe);
+ pSessionThread->hKeyPipe = NIL_RTPIPE;
+ RTCritSectDelete(&pSessionThread->CritSect);
+ }
+ RTMemFree(pSessionThread);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ VGSvcVerbose(3, "Spawning session thread returned returned rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * Waits for a formerly opened guest session process to close.
+ *
+ * @return VBox status code.
+ * @param pThread Guest session thread to wait for.
+ * @param uTimeoutMS Waiting timeout (in ms).
+ * @param fFlags Closing flags.
+ */
+int VGSvcGstCtrlSessionThreadWait(PVBOXSERVICECTRLSESSIONTHREAD pThread, uint32_t uTimeoutMS, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ AssertPtrReturn(pThread, VERR_INVALID_POINTER);
+ /** @todo Validate closing flags. */
+
+ AssertMsgReturn(pThread->Thread != NIL_RTTHREAD,
+ ("Guest session thread of session %p does not exist when it should\n", pThread),
+ VERR_NOT_FOUND);
+
+ int rc = VINF_SUCCESS;
+
+ /*
+ * The spawned session process should have received the same closing request,
+ * so just wait for the process to close.
+ */
+ if (ASMAtomicReadBool(&pThread->fStarted))
+ {
+ /* Ask the thread to shutdown. */
+ ASMAtomicXchgBool(&pThread->fShutdown, true);
+
+ VGSvcVerbose(3, "Waiting for session thread ID=%RU32 to close (%RU32ms) ...\n",
+ pThread->pStartupInfo->uSessionID, uTimeoutMS);
+
+ int rcThread;
+ rc = RTThreadWait(pThread->Thread, uTimeoutMS, &rcThread);
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsg(pThread->fStopped, ("Thread of session ID=%RU32 not in stopped state when it should\n",
+ pThread->pStartupInfo->uSessionID));
+
+ VGSvcVerbose(3, "Session thread ID=%RU32 ended with rc=%Rrc\n", pThread->pStartupInfo->uSessionID, rcThread);
+ }
+ else
+ VGSvcError("Waiting for session thread ID=%RU32 to close failed with rc=%Rrc\n", pThread->pStartupInfo->uSessionID, rc);
+ }
+ else
+ VGSvcVerbose(3, "Thread for session ID=%RU32 not in started state, skipping wait\n", pThread->pStartupInfo->uSessionID);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Waits for the specified session thread to end and remove
+ * it from the session thread list.
+ *
+ * @return VBox status code.
+ * @param pThread Session thread to destroy.
+ * @param fFlags Closing flags.
+ */
+int VGSvcGstCtrlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pThread, uint32_t fFlags)
+{
+ AssertPtrReturn(pThread, VERR_INVALID_POINTER);
+ AssertPtrReturn(pThread->pStartupInfo, VERR_WRONG_ORDER);
+
+ const uint32_t uSessionID = pThread->pStartupInfo->uSessionID;
+
+ VGSvcVerbose(3, "Destroying session ID=%RU32 ...\n", uSessionID);
+
+ int rc = VGSvcGstCtrlSessionThreadWait(pThread, 5 * 60 * 1000 /* 5 minutes timeout */, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ VbglR3GuestCtrlSessionStartupInfoFree(pThread->pStartupInfo);
+ pThread->pStartupInfo = NULL;
+
+ RTPipeClose(pThread->hKeyPipe);
+ pThread->hKeyPipe = NIL_RTPIPE;
+
+ RTCritSectDelete(&pThread->CritSect);
+
+ /* Remove session from list and destroy object. */
+ RTListNodeRemove(&pThread->Node);
+
+ RTMemFree(pThread);
+ pThread = NULL;
+ }
+
+ VGSvcVerbose(3, "Destroyed session ID=%RU32 with %Rrc\n", uSessionID, rc);
+ return rc;
+}
+
+/**
+ * Close all open guest session threads.
+ *
+ * @note Caller is responsible for locking!
+ *
+ * @return VBox status code.
+ * @param pList Which list to close the session threads for.
+ * @param fFlags Closing flags.
+ */
+int VGSvcGstCtrlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t fFlags)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ /*int rc = VbglR3GuestCtrlClose
+ if (RT_FAILURE(rc))
+ VGSvcError("Cancelling pending waits failed; rc=%Rrc\n", rc);*/
+
+ PVBOXSERVICECTRLSESSIONTHREAD pSessIt;
+ PVBOXSERVICECTRLSESSIONTHREAD pSessItNext;
+ RTListForEachSafe(pList, pSessIt, pSessItNext, VBOXSERVICECTRLSESSIONTHREAD, Node)
+ {
+ int rc2 = VGSvcGstCtrlSessionThreadDestroy(pSessIt, fFlags);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Closing session thread '%s' failed with rc=%Rrc\n", RTThreadGetName(pSessIt->Thread), rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Keep going. */
+ }
+ }
+
+ VGSvcVerbose(4, "Destroying guest session threads ended with %Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * Main function for the session process.
+ *
+ * @returns exit code.
+ * @param argc Argument count.
+ * @param argv Argument vector (UTF-8).
+ */
+RTEXITCODE VGSvcGstCtrlSessionSpawnInit(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--domain", VBOXSERVICESESSIONOPT_DOMAIN, RTGETOPT_REQ_STRING },
+#ifdef DEBUG
+ { "--dump-stdout", VBOXSERVICESESSIONOPT_DUMP_STDOUT, RTGETOPT_REQ_NOTHING },
+ { "--dump-stderr", VBOXSERVICESESSIONOPT_DUMP_STDERR, RTGETOPT_REQ_NOTHING },
+#endif
+ { "--logfile", VBOXSERVICESESSIONOPT_LOG_FILE, RTGETOPT_REQ_STRING },
+ { "--user", VBOXSERVICESESSIONOPT_USERNAME, RTGETOPT_REQ_STRING },
+ { "--session-id", VBOXSERVICESESSIONOPT_SESSION_ID, RTGETOPT_REQ_UINT32 },
+ { "--session-proto", VBOXSERVICESESSIONOPT_SESSION_PROTO, RTGETOPT_REQ_UINT32 },
+#ifdef DEBUG
+ { "--thread-id", VBOXSERVICESESSIONOPT_THREAD_ID, RTGETOPT_REQ_UINT32 },
+#endif /* DEBUG */
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
+ };
+
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv,
+ s_aOptions, RT_ELEMENTS(s_aOptions),
+ 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+
+ uint32_t fSession = VBOXSERVICECTRLSESSION_FLAG_SPAWN;
+
+ /* Protocol and session ID must be specified explicitly. */
+ g_Session.StartupInfo.uProtocol = UINT32_MAX;
+ g_Session.StartupInfo.uSessionID = UINT32_MAX;
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case VBOXSERVICESESSIONOPT_DOMAIN:
+ /* Information not needed right now, skip. */
+ break;
+#ifdef DEBUG
+ case VBOXSERVICESESSIONOPT_DUMP_STDOUT:
+ fSession |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT;
+ break;
+
+ case VBOXSERVICESESSIONOPT_DUMP_STDERR:
+ fSession |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR;
+ break;
+#endif
+ case VBOXSERVICESESSIONOPT_SESSION_ID:
+ g_Session.StartupInfo.uSessionID = ValueUnion.u32;
+ break;
+
+ case VBOXSERVICESESSIONOPT_SESSION_PROTO:
+ g_Session.StartupInfo.uProtocol = ValueUnion.u32;
+ break;
+#ifdef DEBUG
+ case VBOXSERVICESESSIONOPT_THREAD_ID:
+ /* Not handled. Mainly for processs listing. */
+ break;
+#endif
+ case VBOXSERVICESESSIONOPT_LOG_FILE:
+ {
+ int rc = RTStrCopy(g_szLogFile, sizeof(g_szLogFile), ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error copying log file name: %Rrc", rc);
+ break;
+ }
+
+ case VBOXSERVICESESSIONOPT_USERNAME:
+ /* Information not needed right now, skip. */
+ break;
+
+ /** @todo Implement help? */
+
+ case 'v':
+ g_cVerbosity++;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ if (!RTStrICmp(ValueUnion.psz, VBOXSERVICECTRLSESSION_GETOPT_PREFIX))
+ break;
+ /* else fall through and bail out. */
+ RT_FALL_THROUGH();
+ }
+ default:
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown argument '%s'", ValueUnion.psz);
+ }
+ }
+
+ /* Check that we've got all the required options. */
+ if (g_Session.StartupInfo.uProtocol == UINT32_MAX)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No protocol version specified");
+
+ if (g_Session.StartupInfo.uSessionID == UINT32_MAX)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No session ID specified");
+
+ /* Init the session object. */
+ int rc = VGSvcGstCtrlSessionInit(&g_Session, fSession);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_INIT, "Failed to initialize session object, rc=%Rrc\n", rc);
+
+ rc = VGSvcLogCreate(g_szLogFile[0] ? g_szLogFile : NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_INIT, "Failed to create log file '%s', rc=%Rrc\n",
+ g_szLogFile[0] ? g_szLogFile : "<None>", rc);
+
+ RTEXITCODE rcExit = vgsvcGstCtrlSessionSpawnWorker(&g_Session);
+
+ VGSvcLogDestroy();
+ return rcExit;
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp
new file mode 100644
index 00000000..d1aa77cc
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp
@@ -0,0 +1,672 @@
+/* $Id: VBoxServiceCpuHotPlug.cpp $ */
+/** @file
+ * VBoxService - Guest Additions CPU Hot-Plugging Service.
+ */
+
+/*
+ * 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
+ */
+
+/** @page pg_vgsvc_cpuhotplug VBoxService - CPU Hot-Plugging
+ *
+ * The CPU Hot-Plugging subservice helps execute and coordinate CPU hot-plugging
+ * between the guest OS and the VMM.
+ *
+ * CPU Hot-Plugging is useful for reallocating CPU resources from one VM to
+ * other VMs or/and the host. It talks to the VMM via VMMDev, new hot-plugging
+ * events being signalled with an interrupt (no polling).
+ *
+ * Currently only supported for linux guests.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+
+#ifdef RT_OS_LINUX
+# include <iprt/linux/sysfs.h>
+# include <errno.h> /* For the sysfs API */
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef RT_OS_LINUX
+
+/** @name Paths to access the CPU device
+ * @{
+ */
+# define SYSFS_ACPI_CPU_PATH "/sys/devices"
+# define SYSFS_CPU_PATH "/sys/devices/system/cpu"
+/** @} */
+
+/** Path component for the ACPI CPU path. */
+typedef struct SYSFSCPUPATHCOMP
+{
+ /** Flag whether the name is suffixed with a number */
+ bool fNumberedSuffix;
+ /** Name of the component */
+ const char *pcszName;
+} SYSFSCPUPATHCOMP, *PSYSFSCPUPATHCOMP;
+/** Pointer to a const component. */
+typedef const SYSFSCPUPATHCOMP *PCSYSFSCPUPATHCOMP;
+
+/**
+ * Structure which defines how the entries are assembled.
+ */
+typedef struct SYSFSCPUPATH
+{
+ /** Id when probing for the correct path. */
+ uint32_t uId;
+ /** Array holding the possible components. */
+ PCSYSFSCPUPATHCOMP aComponentsPossible;
+ /** Number of entries in the array, excluding the terminator. */
+ unsigned cComponents;
+ /** Directory handle */
+ RTDIR hDir;
+ /** Current directory to try. */
+ char *pszPath;
+} SYSFSCPUPATH, *PSYSFSCPUPATH;
+
+/** Content of uId if the path wasn't probed yet. */
+# define ACPI_CPU_PATH_NOT_PROBED UINT32_MAX
+#endif /* RT_OS_LINUX*/
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef RT_OS_LINUX
+/** Possible combinations of all path components for level 1. */
+static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl1[] =
+{
+ /** LNXSYSTEM:<id> */
+ { true, "LNXSYSTM:*" }
+};
+
+/** Possible combinations of all path components for level 2. */
+static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl2[] =
+{
+ /** device:<id> */
+ { true, "device:*" },
+ /** LNXSYBUS:<id> */
+ { true, "LNXSYBUS:*" }
+};
+
+/** Possible combinations of all path components for level 3 */
+static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl3[] =
+{
+ /** ACPI0004:<id> */
+ { true, "ACPI0004:*" }
+};
+
+/** Possible combinations of all path components for level 4 */
+static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl4[] =
+{
+ /** LNXCPU:<id> */
+ { true, "LNXCPU:*" },
+ /** ACPI_CPU:<id> */
+ { true, "ACPI_CPU:*" }
+};
+
+/** All possible combinations. */
+static SYSFSCPUPATH g_aAcpiCpuPath[] =
+{
+ /** Level 1 */
+ { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl1, RT_ELEMENTS(g_aAcpiCpuPathLvl1), NULL, NULL },
+ /** Level 2 */
+ { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl2, RT_ELEMENTS(g_aAcpiCpuPathLvl2), NULL, NULL },
+ /** Level 3 */
+ { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl3, RT_ELEMENTS(g_aAcpiCpuPathLvl3), NULL, NULL },
+ /** Level 4 */
+ { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl4, RT_ELEMENTS(g_aAcpiCpuPathLvl4), NULL, NULL },
+};
+
+/**
+ * Possible directories to get to the topology directory for reading core and package id.
+ *
+ * @remark: This is not part of the path above because the eject file is not in one of the directories
+ * below and would make the hot unplug code fail.
+ */
+static const char *g_apszTopologyPath[] =
+{
+ "sysdev",
+ "physical_node"
+};
+
+#endif /* RT_OS_LINUX*/
+
+
+#ifdef RT_OS_LINUX
+
+/**
+ * Probes for the correct path to the ACPI CPU object in sysfs for the
+ * various different kernel versions and distro's.
+ *
+ * @returns VBox status code.
+ */
+static int vgsvcCpuHotPlugProbePath(void)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Probe for the correct path if we didn't already. */
+ if (RT_UNLIKELY(g_aAcpiCpuPath[0].uId == ACPI_CPU_PATH_NOT_PROBED))
+ {
+ char *pszPath = NULL; /** < Current path, increasing while we dig deeper. */
+
+ pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH);
+ if (!pszPath)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Simple algorithm to find the path.
+ * Performance is not a real problem because it is
+ * only executed once.
+ */
+ for (unsigned iLvlCurr = 0; iLvlCurr < RT_ELEMENTS(g_aAcpiCpuPath); iLvlCurr++)
+ {
+ PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
+
+ for (unsigned iCompCurr = 0; iCompCurr < pAcpiCpuPathLvl->cComponents; iCompCurr++)
+ {
+ PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[iCompCurr];
+
+ /* Open the directory */
+ RTDIR hDirCurr = NIL_RTDIR;
+ char *pszPathTmp = RTPathJoinA(pszPath, pPathComponent->pcszName);
+ if (pszPathTmp)
+ {
+ rc = RTDirOpenFiltered(&hDirCurr, pszPathTmp, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ RTStrFree(pszPathTmp);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Search if the current directory contains one of the possible parts. */
+ size_t cchName = strlen(pPathComponent->pcszName);
+ RTDIRENTRY DirFolderContent;
+ bool fFound = false;
+
+ /* Get rid of the * filter which is in the path component. */
+ if (pPathComponent->fNumberedSuffix)
+ cchName--;
+
+ while (RT_SUCCESS(RTDirRead(hDirCurr, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
+ {
+ if ( DirFolderContent.cbName >= cchName
+ && !strncmp(DirFolderContent.szName, pPathComponent->pcszName, cchName))
+ {
+ /* Found, use the complete name to dig deeper. */
+ fFound = true;
+ pAcpiCpuPathLvl->uId = iCompCurr;
+ char *pszPathLvl = RTPathJoinA(pszPath, DirFolderContent.szName);
+ if (pszPathLvl)
+ {
+ RTStrFree(pszPath);
+ pszPath = pszPathLvl;
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+ }
+ RTDirClose(hDirCurr);
+
+ if (fFound)
+ break;
+ } /* For every possible component. */
+
+ /* No matching component for this part, no need to continue */
+ if (RT_FAILURE(rc))
+ break;
+ } /* For every level */
+
+ VGSvcVerbose(1, "Final path after probing %s rc=%Rrc\n", pszPath, rc);
+ RTStrFree(pszPath);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Returns the path of the ACPI CPU device with the given core and package ID.
+ *
+ * @returns VBox status code.
+ * @param ppszPath Where to store the path.
+ * @param idCpuCore The core ID of the CPU.
+ * @param idCpuPackage The package ID of the CPU.
+ */
+static int vgsvcCpuHotPlugGetACPIDevicePath(char **ppszPath, uint32_t idCpuCore, uint32_t idCpuPackage)
+{
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(ppszPath, VERR_INVALID_PARAMETER);
+
+ rc = vgsvcCpuHotPlugProbePath();
+ if (RT_SUCCESS(rc))
+ {
+ /* Build the path from all components. */
+ bool fFound = false;
+ unsigned iLvlCurr = 0;
+ char *pszPath = NULL;
+ char *pszPathDir = NULL;
+ PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
+
+ /* Init everything. */
+ Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED);
+ pszPath = RTPathJoinA(SYSFS_ACPI_CPU_PATH, pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId].pcszName);
+ if (!pszPath)
+ return VERR_NO_STR_MEMORY;
+
+ pAcpiCpuPathLvl->pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH);
+ if (!pAcpiCpuPathLvl->pszPath)
+ {
+ RTStrFree(pszPath);
+ return VERR_NO_STR_MEMORY;
+ }
+
+ /* Open the directory */
+ rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->hDir, pszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pszPath);
+ pszPath = NULL;
+
+ /* Search for CPU */
+ while (!fFound)
+ {
+ /* Get the next directory. */
+ RTDIRENTRY DirFolderContent;
+ rc = RTDirRead(pAcpiCpuPathLvl->hDir, &DirFolderContent, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create the new path. */
+ char *pszPathCurr = RTPathJoinA(pAcpiCpuPathLvl->pszPath, DirFolderContent.szName);
+ if (!pszPathCurr)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+
+ /* If this is the last level check for the given core and package id. */
+ if (iLvlCurr == RT_ELEMENTS(g_aAcpiCpuPath) - 1)
+ {
+ /* Get the sysdev */
+ uint32_t idCore = 0;
+ uint32_t idPackage = 0;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_apszTopologyPath); i++)
+ {
+ int64_t i64Core = 0;
+ int64_t i64Package = 0;
+
+ int rc2 = RTLinuxSysFsReadIntFile(10, &i64Core, "%s/%s/topology/core_id",
+ pszPathCurr, g_apszTopologyPath[i]);
+ if (RT_SUCCESS(rc2))
+ rc2 = RTLinuxSysFsReadIntFile(10, &i64Package, "%s/%s/topology/physical_package_id",
+ pszPathCurr, g_apszTopologyPath[i]);
+
+ if (RT_SUCCESS(rc2))
+ {
+ idCore = (uint32_t)i64Core;
+ idPackage = (uint32_t)i64Package;
+ break;
+ }
+ }
+
+ if ( idCore == idCpuCore
+ && idPackage == idCpuPackage)
+ {
+ /* Return the path */
+ pszPath = pszPathCurr;
+ fFound = true;
+ VGSvcVerbose(3, "CPU found\n");
+ break;
+ }
+ else
+ {
+ /* Get the next directory. */
+ RTStrFree(pszPathCurr);
+ pszPathCurr = NULL;
+ VGSvcVerbose(3, "CPU doesn't match, next directory\n");
+ }
+ }
+ else
+ {
+ /* Go deeper */
+ iLvlCurr++;
+
+ VGSvcVerbose(3, "Going deeper (iLvlCurr=%u)\n", iLvlCurr);
+
+ pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
+
+ Assert(pAcpiCpuPathLvl->hDir == NIL_RTDIR);
+ Assert(!pAcpiCpuPathLvl->pszPath);
+ pAcpiCpuPathLvl->pszPath = pszPathCurr;
+ PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId];
+
+ Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED);
+
+ pszPathDir = RTPathJoinA(pszPathCurr, pPathComponent->pcszName);
+ if (!pszPathDir)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+
+ VGSvcVerbose(3, "New path %s\n", pszPathDir);
+
+ /* Open the directory */
+ rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->hDir, pszPathDir, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ RTStrFree(pszPathDir);
+ pszPathDir = NULL;
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else
+ {
+ RTDirClose(pAcpiCpuPathLvl->hDir);
+ RTStrFree(pAcpiCpuPathLvl->pszPath);
+ pAcpiCpuPathLvl->hDir = NIL_RTDIR;
+ pAcpiCpuPathLvl->pszPath = NULL;
+
+ /*
+ * If we reached the end we didn't find the matching path
+ * meaning the CPU is already offline.
+ */
+ if (!iLvlCurr)
+ {
+ rc = VERR_NOT_FOUND;
+ break;
+ }
+
+ iLvlCurr--;
+ pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
+ VGSvcVerbose(3, "Directory not found, going back (iLvlCurr=%u)\n", iLvlCurr);
+ }
+ } /* while not found */
+ } /* Successful init */
+
+ /* Cleanup */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aAcpiCpuPath); i++)
+ {
+ if (g_aAcpiCpuPath[i].hDir)
+ RTDirClose(g_aAcpiCpuPath[i].hDir);
+ if (g_aAcpiCpuPath[i].pszPath)
+ RTStrFree(g_aAcpiCpuPath[i].pszPath);
+ g_aAcpiCpuPath[i].hDir = NIL_RTDIR;
+ g_aAcpiCpuPath[i].pszPath = NULL;
+ }
+ if (pszPathDir)
+ RTStrFree(pszPathDir);
+ if (RT_FAILURE(rc) && pszPath)
+ RTStrFree(pszPath);
+
+ if (RT_SUCCESS(rc))
+ *ppszPath = pszPath;
+ }
+
+ return rc;
+}
+
+#endif /* RT_OS_LINUX */
+
+/**
+ * Handles VMMDevCpuEventType_Plug.
+ *
+ * @param idCpuCore The CPU core ID.
+ * @param idCpuPackage The CPU package ID.
+ */
+static void vgsvcCpuHotPlugHandlePlugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
+{
+#ifdef RT_OS_LINUX
+ /*
+ * The topology directory (containing the physical and core id properties)
+ * is not available until the CPU is online. So we just iterate over all directories
+ * and enable the first CPU which is not online already.
+ * Because the directory might not be available immediately we try a few times.
+ *
+ */
+ /** @todo Maybe use udev to monitor hot-add events from the kernel */
+ bool fCpuOnline = false;
+ unsigned cTries = 5;
+
+ do
+ {
+ RTDIR hDirDevices = NULL;
+ int rc = RTDirOpen(&hDirDevices, SYSFS_CPU_PATH);
+ if (RT_SUCCESS(rc))
+ {
+ RTDIRENTRY DirFolderContent;
+ while (RT_SUCCESS(RTDirRead(hDirDevices, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
+ {
+ /* Check if this is a CPU object which can be brought online. */
+ if (RTLinuxSysFsExists("%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName))
+ {
+ /* Check the status of the CPU by reading the online flag. */
+ int64_t i64Status = 0;
+ rc = RTLinuxSysFsReadIntFile(10 /*uBase*/, &i64Status, "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName);
+ if ( RT_SUCCESS(rc)
+ && i64Status == 0)
+ {
+ /* CPU is offline, turn it on. */
+ rc = RTLinuxSysFsWriteU8File(10 /*uBase*/, 1, "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was brought online\n", idCpuPackage, idCpuCore);
+ fCpuOnline = true;
+ break;
+ }
+ }
+ else if (RT_FAILURE(rc))
+ VGSvcError("CpuHotPlug: Failed to open '%s/%s/online' rc=%Rrc\n",
+ SYSFS_CPU_PATH, DirFolderContent.szName, rc);
+ else
+ {
+ /*
+ * Check whether the topology matches what we got (which means someone raced us and brought the CPU
+ * online already).
+ */
+ int64_t i64Core = 0;
+ int64_t i64Package = 0;
+
+ int rc2 = RTLinuxSysFsReadIntFile(10, &i64Core, "%s/%s/topology/core_id",
+ SYSFS_CPU_PATH, DirFolderContent.szName);
+ if (RT_SUCCESS(rc2))
+ rc2 = RTLinuxSysFsReadIntFile(10, &i64Package, "%s/%s/topology/physical_package_id",
+ SYSFS_CPU_PATH, DirFolderContent.szName);
+ if ( RT_SUCCESS(rc2)
+ && idCpuPackage == i64Package
+ && idCpuCore == i64Core)
+ {
+ VGSvcVerbose(1, "CpuHotPlug: '%s' is already online\n", DirFolderContent.szName);
+ fCpuOnline = true;
+ break;
+ }
+ }
+ }
+ }
+ RTDirClose(hDirDevices);
+ }
+ else
+ VGSvcError("CpuHotPlug: Failed to open path %s rc=%Rrc\n", SYSFS_CPU_PATH, rc);
+
+ /* Sleep a bit */
+ if (!fCpuOnline)
+ RTThreadSleep(100);
+
+ } while ( !fCpuOnline
+ && cTries-- > 0);
+#else
+# error "Port me"
+#endif
+}
+
+
+/**
+ * Handles VMMDevCpuEventType_Unplug.
+ *
+ * @param idCpuCore The CPU core ID.
+ * @param idCpuPackage The CPU package ID.
+ */
+static void vgsvcCpuHotPlugHandleUnplugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
+{
+#ifdef RT_OS_LINUX
+ char *pszCpuDevicePath = NULL;
+ int rc = vgsvcCpuHotPlugGetACPIDevicePath(&pszCpuDevicePath, idCpuCore, idCpuPackage);
+ if (RT_SUCCESS(rc))
+ {
+ RTFILE hFileCpuEject;
+ rc = RTFileOpenF(&hFileCpuEject, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, "%s/eject", pszCpuDevicePath);
+ if (RT_SUCCESS(rc))
+ {
+ /* Write a 1 to eject the CPU */
+ rc = RTFileWrite(hFileCpuEject, "1", 1, NULL);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was ejected\n", idCpuPackage, idCpuCore);
+ else
+ VGSvcError("CpuHotPlug: Failed to eject CPU %u/%u rc=%Rrc\n", idCpuPackage, idCpuCore, rc);
+
+ RTFileClose(hFileCpuEject);
+ }
+ else
+ VGSvcError("CpuHotPlug: Failed to open '%s/eject' rc=%Rrc\n", pszCpuDevicePath, rc);
+ RTStrFree(pszCpuDevicePath);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was aleady ejected by someone else!\n", idCpuPackage, idCpuCore);
+ else
+ VGSvcError("CpuHotPlug: Failed to get CPU device path rc=%Rrc\n", rc);
+#else
+# error "Port me"
+#endif
+}
+
+
+/** @interface_method_impl{VBOXSERVICE,pfnWorker} */
+static DECLCALLBACK(int) vgsvcCpuHotPlugWorker(bool volatile *pfShutdown)
+{
+ /*
+ * Tell the control thread that it can continue spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Enable the CPU hotplug notifier.
+ */
+ int rc = VbglR3CpuHotPlugInit();
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * The Work Loop.
+ */
+ for (;;)
+ {
+ /* Wait for CPU hot-plugging event. */
+ uint32_t idCpuCore;
+ uint32_t idCpuPackage;
+ VMMDevCpuEventType enmEventType;
+ rc = VbglR3CpuHotPlugWaitForEvent(&enmEventType, &idCpuCore, &idCpuPackage);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "CpuHotPlug: Event happened idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
+ idCpuCore, idCpuPackage, enmEventType);
+ switch (enmEventType)
+ {
+ case VMMDevCpuEventType_Plug:
+ vgsvcCpuHotPlugHandlePlugEvent(idCpuCore, idCpuPackage);
+ break;
+
+ case VMMDevCpuEventType_Unplug:
+ vgsvcCpuHotPlugHandleUnplugEvent(idCpuCore, idCpuPackage);
+ break;
+
+ default:
+ {
+ static uint32_t s_iErrors = 0;
+ if (s_iErrors++ < 10)
+ VGSvcError("CpuHotPlug: Unknown event: idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
+ idCpuCore, idCpuPackage, enmEventType);
+ break;
+ }
+ }
+ }
+ else if (rc != VERR_INTERRUPTED && rc != VERR_TRY_AGAIN)
+ {
+ VGSvcError("CpuHotPlug: VbglR3CpuHotPlugWaitForEvent returned %Rrc\n", rc);
+ break;
+ }
+
+ if (*pfShutdown)
+ break;
+ }
+
+ VbglR3CpuHotPlugTerm();
+ return rc;
+}
+
+
+/** @interface_method_impl{VBOXSERVICE,pfnStop} */
+static DECLCALLBACK(void) vgsvcCpuHotPlugStop(void)
+{
+ VbglR3InterruptEventWaits();
+ return;
+}
+
+
+/**
+ * The 'CpuHotPlug' service description.
+ */
+VBOXSERVICE g_CpuHotPlug =
+{
+ /* pszName. */
+ "cpuhotplug",
+ /* pszDescription. */
+ "CPU hot-plugging monitor",
+ /* pszUsage. */
+ NULL,
+ /* pszOptions. */
+ NULL,
+ /* methods */
+ VGSvcDefaultPreInit,
+ VGSvcDefaultOption,
+ VGSvcDefaultInit,
+ vgsvcCpuHotPlugWorker,
+ vgsvcCpuHotPlugStop,
+ VGSvcDefaultTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h b/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h
new file mode 100644
index 00000000..cb85b59b
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h
@@ -0,0 +1,284 @@
+/* $Id: VBoxServiceInternal.h $ */
+/** @file
+ * VBoxService - Guest Additions Services.
+ */
+
+/*
+ * Copyright (C) 2007-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 GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+
+#include <iprt/list.h>
+#include <iprt/critsect.h>
+#include <iprt/path.h> /* RTPATH_MAX */
+#include <iprt/stdarg.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/HostServices/GuestControlSvc.h>
+
+
+#if !defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING)
+/** Special argv[1] value that indicates that argv is UTF-8.
+ * This causes RTR3Init to be called with RTR3INIT_FLAGS_UTF8_ARGV and helps
+ * work around potential issues caused by a user's locale config not being
+ * UTF-8. See @bugref{10153}.
+ *
+ * @note We don't need this on windows and it would be harmful to enable it
+ * as the argc/argv vs __argc/__argv comparison would fail and we would
+ * not use the unicode command line to create a UTF-8 argv. Since the
+ * original argv is ANSI, it may be missing codepoints not present in
+ * the ANSI code page of the process. */
+# define VBOXSERVICE_ARG1_UTF8_ARGV "--utf8-argv"
+#endif
+/** RTProcCreateEx flags corresponding to VBOXSERVICE_ARG1_UTF8_ARGV. */
+#ifdef VBOXSERVICE_ARG1_UTF8_ARGV
+# define VBOXSERVICE_PROC_F_UTF8_ARGV RTPROC_FLAGS_UTF8_ARGV
+#else
+# define VBOXSERVICE_PROC_F_UTF8_ARGV 0
+#endif
+
+
+/**
+ * A service descriptor.
+ */
+typedef struct
+{
+ /** The short service name. */
+ const char *pszName;
+ /** The longer service name. */
+ const char *pszDescription;
+ /** The usage options stuff for the --help screen. */
+ const char *pszUsage;
+ /** The option descriptions for the --help screen. */
+ const char *pszOptions;
+
+ /**
+ * Called before parsing arguments.
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnPreInit,(void));
+
+ /**
+ * Tries to parse the given command line option.
+ *
+ * @returns 0 if we parsed, -1 if it didn't and anything else means exit.
+ * @param ppszShort If not NULL it points to the short option iterator. a short argument.
+ * If NULL examine argv[*pi].
+ * @param argc The argument count.
+ * @param argv The argument vector.
+ * @param pi The argument vector index. Update if any value(s) are eaten.
+ */
+ DECLCALLBACKMEMBER(int, pfnOption,(const char **ppszShort, int argc, char **argv, int *pi));
+
+ /**
+ * Called before parsing arguments.
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnInit,(void));
+
+ /** Called from the worker thread.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS if exitting because *pfShutdown was set.
+ * @param pfShutdown Pointer to a per service termination flag to check
+ * before and after blocking.
+ */
+ DECLCALLBACKMEMBER(int, pfnWorker,(bool volatile *pfShutdown));
+
+ /**
+ * Stops a service.
+ */
+ DECLCALLBACKMEMBER(void, pfnStop,(void));
+
+ /**
+ * Does termination cleanups.
+ *
+ * @remarks This may be called even if pfnInit hasn't been called!
+ */
+ DECLCALLBACKMEMBER(void, pfnTerm,(void));
+} VBOXSERVICE;
+/** Pointer to a VBOXSERVICE. */
+typedef VBOXSERVICE *PVBOXSERVICE;
+/** Pointer to a const VBOXSERVICE. */
+typedef VBOXSERVICE const *PCVBOXSERVICE;
+
+/* Default call-backs for services which do not need special behaviour. */
+DECLCALLBACK(int) VGSvcDefaultPreInit(void);
+DECLCALLBACK(int) VGSvcDefaultOption(const char **ppszShort, int argc, char **argv, int *pi);
+DECLCALLBACK(int) VGSvcDefaultInit(void);
+DECLCALLBACK(void) VGSvcDefaultTerm(void);
+
+/** The service name.
+ * @note Used on windows to name the service as well as the global mutex. */
+#define VBOXSERVICE_NAME "VBoxService"
+
+#ifdef RT_OS_WINDOWS
+/** The friendly service name. */
+# define VBOXSERVICE_FRIENDLY_NAME "VirtualBox Guest Additions Service"
+/** The service description (only W2K+ atm) */
+# define VBOXSERVICE_DESCRIPTION "Manages VM runtime information, time synchronization, guest control execution and miscellaneous utilities for guest operating systems."
+/** The following constant may be defined by including NtStatus.h. */
+# define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#endif /* RT_OS_WINDOWS */
+
+#ifdef VBOX_WITH_GUEST_PROPS
+/**
+ * A guest property cache.
+ */
+typedef struct VBOXSERVICEVEPROPCACHE
+{
+ /** The client ID for HGCM communication. */
+ uint32_t uClientID;
+ /** Head in a list of VBOXSERVICEVEPROPCACHEENTRY nodes. */
+ RTLISTANCHOR NodeHead;
+ /** Critical section for thread-safe use. */
+ RTCRITSECT CritSect;
+} VBOXSERVICEVEPROPCACHE;
+/** Pointer to a guest property cache. */
+typedef VBOXSERVICEVEPROPCACHE *PVBOXSERVICEVEPROPCACHE;
+
+/**
+ * An entry in the property cache (VBOXSERVICEVEPROPCACHE).
+ */
+typedef struct VBOXSERVICEVEPROPCACHEENTRY
+{
+ /** Node to successor.
+ * @todo r=bird: This is not really the node to the successor, but
+ * rather the OUR node in the list. If it helps, remember that
+ * its a doubly linked list. */
+ RTLISTNODE NodeSucc;
+ /** Name (and full path) of guest property. */
+ char *pszName;
+ /** The last value stored (for reference). */
+ char *pszValue;
+ /** Reset value to write if property is temporary. If NULL, it will be
+ * deleted. */
+ char *pszValueReset;
+ /** Flags. */
+ uint32_t fFlags;
+} VBOXSERVICEVEPROPCACHEENTRY;
+/** Pointer to a cached guest property. */
+typedef VBOXSERVICEVEPROPCACHEENTRY *PVBOXSERVICEVEPROPCACHEENTRY;
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+RT_C_DECLS_BEGIN
+
+extern char *g_pszProgName;
+extern unsigned g_cVerbosity;
+extern char g_szLogFile[RTPATH_MAX + 128];
+extern uint32_t g_DefaultInterval;
+extern VBOXSERVICE g_TimeSync;
+#ifdef VBOX_WITH_VBOXSERVICE_CLIPBOARD
+extern VBOXSERVICE g_Clipboard;
+#endif
+extern VBOXSERVICE g_Control;
+extern VBOXSERVICE g_VMInfo;
+extern VBOXSERVICE g_CpuHotPlug;
+#ifdef VBOX_WITH_VBOXSERVICE_MANAGEMENT
+extern VBOXSERVICE g_MemBalloon;
+extern VBOXSERVICE g_VMStatistics;
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_PAGE_SHARING
+extern VBOXSERVICE g_PageSharing;
+#endif
+#ifdef VBOX_WITH_SHARED_FOLDERS
+extern VBOXSERVICE g_AutoMount;
+#endif
+#ifdef DEBUG
+extern RTCRITSECT g_csLog; /* For guest process stdout dumping. */
+#endif
+
+extern RTEXITCODE VGSvcSyntax(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2);
+extern RTEXITCODE VGSvcError(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2);
+extern void VGSvcVerbose(unsigned iLevel, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(2, 3);
+extern int VGSvcLogCreate(const char *pszLogFile);
+extern void VGSvcLogV(const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(1, 0);
+extern void VGSvcLogDestroy(void);
+extern int VGSvcArgUInt32(int argc, char **argv, const char *psz, int *pi, uint32_t *pu32,
+ uint32_t u32Min, uint32_t u32Max);
+
+/* Exposing the following bits because of windows: */
+extern int VGSvcStartServices(void);
+extern int VGSvcStopServices(void);
+extern void VGSvcMainWait(void);
+extern int VGSvcReportStatus(VBoxGuestFacilityStatus enmStatus);
+#ifdef RT_OS_WINDOWS
+extern void VGSvcWinResolveApis(void);
+extern RTEXITCODE VGSvcWinInstall(void);
+extern RTEXITCODE VGSvcWinUninstall(void);
+extern RTEXITCODE VGSvcWinEnterCtrlDispatcher(void);
+extern void VGSvcWinSetStopPendingStatus(uint32_t uCheckPoint);
+# ifdef TH32CS_SNAPHEAPLIST
+extern decltype(CreateToolhelp32Snapshot) *g_pfnCreateToolhelp32Snapshot;
+extern decltype(Process32First) *g_pfnProcess32First;
+extern decltype(Process32Next) *g_pfnProcess32Next;
+extern decltype(Module32First) *g_pfnModule32First;
+extern decltype(Module32Next) *g_pfnModule32Next;
+# endif
+extern decltype(GetSystemTimeAdjustment) *g_pfnGetSystemTimeAdjustment;
+extern decltype(SetSystemTimeAdjustment) *g_pfnSetSystemTimeAdjustment;
+# ifdef IPRT_INCLUDED_nt_nt_h
+extern decltype(ZwQuerySystemInformation) *g_pfnZwQuerySystemInformation;
+# endif
+extern ULONG (WINAPI *g_pfnGetAdaptersInfo)(struct _IP_ADAPTER_INFO *, PULONG);
+#ifdef WINSOCK_VERSION
+extern decltype(WSAStartup) *g_pfnWSAStartup;
+extern decltype(WSACleanup) *g_pfnWSACleanup;
+extern decltype(WSASocketA) *g_pfnWSASocketA;
+extern decltype(WSAIoctl) *g_pfnWSAIoctl;
+extern decltype(WSAGetLastError) *g_pfnWSAGetLastError;
+extern decltype(closesocket) *g_pfnclosesocket;
+extern decltype(inet_ntoa) *g_pfninet_ntoa;
+# endif /* WINSOCK_VERSION */
+
+#ifdef SE_INTERACTIVE_LOGON_NAME
+extern decltype(LsaNtStatusToWinError) *g_pfnLsaNtStatusToWinError;
+#endif
+
+# ifdef VBOX_WITH_GUEST_PROPS
+extern int VGSvcVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, char **ppszUserList, uint32_t *pcUsersInList);
+extern int VGSvcVMInfoWinGetComponentVersions(uint32_t uClientID);
+# endif /* VBOX_WITH_GUEST_PROPS */
+
+#endif /* RT_OS_WINDOWS */
+
+#ifdef VBOX_WITH_MEMBALLOON
+extern uint32_t VGSvcBalloonQueryPages(uint32_t cbPage);
+#endif
+#if defined(VBOX_WITH_VBOXSERVICE_PAGE_SHARING)
+extern RTEXITCODE VGSvcPageSharingWorkerChild(void);
+#endif
+extern int VGSvcVMInfoSignal(void);
+
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp b/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp
new file mode 100644
index 00000000..c8e72c62
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp
@@ -0,0 +1,803 @@
+/* $Id: VBoxServicePageSharing.cpp $ */
+/** @file
+ * VBoxService - Guest page sharing.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/** @page pg_vgsvc_pagesharing VBoxService - Page Sharing
+ *
+ * The Page Sharing subservice is responsible for finding memory mappings
+ * suitable page fusions.
+ *
+ * It is the driving force behind the Page Fusion feature in VirtualBox.
+ * Working with PGM and GMM (ring-0) thru the VMMDev interface. Every so often
+ * it reenumerates the memory mappings (executables and shared libraries) of the
+ * guest OS and reports additions and removals to GMM. For each mapping there
+ * is a filename and version as well as and address range and subsections. GMM
+ * will match the mapping with mapping with the same name and version from other
+ * VMs and see if there are any identical pages between the two.
+ *
+ * To increase the hit rate and reduce the volatility, the service launches a
+ * child process which loads all the Windows system DLLs it can. The child
+ * process is necessary as the DLLs are loaded without running the init code,
+ * and therefore not actually callable for other VBoxService code (may crash).
+ *
+ * This is currently only implemented on Windows. There is no technical reason
+ * for it not to be doable for all the other guests too, it's just a matter of
+ * customer demand and engineering time.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/ldr.h>
+#include <iprt/process.h>
+#include <iprt/env.h>
+#include <iprt/stream.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <VBox/err.h>
+#include <VBox/VMMDev.h>
+#include <VBox/VBoxGuestLib.h>
+
+#ifdef RT_OS_WINDOWS
+#include <iprt/nt/nt-and-windows.h>
+# include <tlhelp32.h>
+# include <psapi.h>
+# include <winternl.h>
+#endif
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+typedef struct
+{
+ AVLPVNODECORE Core;
+#ifdef RT_OS_WINDOWS
+ HMODULE hModule;
+ char szFileVersion[16];
+ MODULEENTRY32 Info;
+#endif
+} VGSVCPGSHKNOWNMOD, *PVGSVCPGSHKNOWNMOD;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_PageSharingEvent = NIL_RTSEMEVENTMULTI;
+
+static PAVLPVNODECORE g_pKnownModuleTree = NULL;
+static uint64_t g_idSession = 0;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) vgsvcPageSharingEmptyTreeCallback(PAVLPVNODECORE pNode, void *pvUser);
+
+
+#ifdef RT_OS_WINDOWS
+
+/**
+ * Registers a new module with the VMM
+ * @param pModule Module ptr
+ * @param fValidateMemory Validate/touch memory pages or not
+ */
+static void vgsvcPageSharingRegisterModule(PVGSVCPGSHKNOWNMOD pModule, bool fValidateMemory)
+{
+ VMMDEVSHAREDREGIONDESC aRegions[VMMDEVSHAREDREGIONDESC_MAX];
+ DWORD dwModuleSize = pModule->Info.modBaseSize;
+ BYTE *pBaseAddress = pModule->Info.modBaseAddr;
+
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule\n");
+
+ DWORD dwDummy;
+ DWORD cbVersion = GetFileVersionInfoSize(pModule->Info.szExePath, &dwDummy);
+ if (!cbVersion)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: GetFileVersionInfoSize failed with %d\n", GetLastError());
+ return;
+ }
+ BYTE *pVersionInfo = (BYTE *)RTMemAllocZ(cbVersion);
+ if (!pVersionInfo)
+ return;
+
+ if (!GetFileVersionInfo(pModule->Info.szExePath, 0, cbVersion, pVersionInfo))
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: GetFileVersionInfo failed with %d\n", GetLastError());
+ goto end;
+ }
+
+ /* Fetch default code page. */
+ struct LANGANDCODEPAGE
+ {
+ WORD wLanguage;
+ WORD wCodePage;
+ } *lpTranslate;
+
+ UINT cbTranslate;
+ BOOL fRet = VerQueryValue(pVersionInfo, TEXT("\\VarFileInfo\\Translation"), (LPVOID *)&lpTranslate, &cbTranslate);
+ if ( !fRet
+ || cbTranslate < 4)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VerQueryValue failed with %d (cb=%d)\n", GetLastError(), cbTranslate);
+ goto end;
+ }
+
+ unsigned i;
+ UINT cbFileVersion;
+ char *pszFileVersion = NULL; /* Shut up MSC */
+ unsigned cTranslationBlocks = cbTranslate/sizeof(struct LANGANDCODEPAGE);
+
+ pModule->szFileVersion[0] = '\0';
+ for (i = 0; i < cTranslationBlocks; i++)
+ {
+ /* Fetch file version string. */
+ char szFileVersionLocation[256];
+
+/** @todo r=bird: Mixing ANSI and TCHAR crap again. This code is a mess. We
+ * always use the wide version of the API and convert to UTF-8/whatever. */
+
+ RTStrPrintf(szFileVersionLocation, sizeof(szFileVersionLocation),
+ "\\StringFileInfo\\%04x%04x\\FileVersion", lpTranslate[i].wLanguage, lpTranslate[i].wCodePage);
+ fRet = VerQueryValue(pVersionInfo, szFileVersionLocation, (LPVOID *)&pszFileVersion, &cbFileVersion);
+ if (fRet)
+ {
+ RTStrCopy(pModule->szFileVersion, sizeof(pModule->szFileVersion), pszFileVersion);
+ break;
+ }
+ }
+ if (i == cTranslationBlocks)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: no file version found!\n");
+ goto end;
+ }
+
+ unsigned idxRegion = 0;
+
+ if (fValidateMemory)
+ {
+ do
+ {
+ MEMORY_BASIC_INFORMATION MemInfo;
+ SIZE_T cbRet = VirtualQuery(pBaseAddress, &MemInfo, sizeof(MemInfo));
+ Assert(cbRet);
+ if (!cbRet)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VirtualQueryEx failed with %d\n", GetLastError());
+ break;
+ }
+
+ if ( MemInfo.State == MEM_COMMIT
+ && MemInfo.Type == MEM_IMAGE)
+ {
+ switch (MemInfo.Protect)
+ {
+ case PAGE_EXECUTE:
+ case PAGE_EXECUTE_READ:
+ case PAGE_READONLY:
+ {
+ char *pRegion = (char *)MemInfo.BaseAddress;
+
+ /* Skip the first region as it only contains the image file header. */
+ if (pRegion != (char *)pModule->Info.modBaseAddr)
+ {
+ /* Touch all pages. */
+ while ((uintptr_t)pRegion < (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize)
+ {
+ /* Try to trick the optimizer to leave the page touching code in place. */
+ ASMProbeReadByte(pRegion);
+ pRegion += PAGE_SIZE;
+ }
+ }
+#ifdef RT_ARCH_X86
+ aRegions[idxRegion].GCRegionAddr = (RTGCPTR32)MemInfo.BaseAddress;
+#else
+ aRegions[idxRegion].GCRegionAddr = (RTGCPTR64)MemInfo.BaseAddress;
+#endif
+ aRegions[idxRegion].cbRegion = MemInfo.RegionSize;
+ idxRegion++;
+
+ break;
+ }
+
+ default:
+ break; /* ignore */
+ }
+ }
+
+ pBaseAddress = (BYTE *)MemInfo.BaseAddress + MemInfo.RegionSize;
+ if (dwModuleSize > MemInfo.RegionSize)
+ dwModuleSize -= MemInfo.RegionSize;
+ else
+ {
+ dwModuleSize = 0;
+ break;
+ }
+
+ if (idxRegion >= RT_ELEMENTS(aRegions))
+ break; /* out of room */
+ }
+ while (dwModuleSize);
+ }
+ else
+ {
+ /* We can't probe kernel memory ranges, so pretend it's one big region. */
+#ifdef RT_ARCH_X86
+ aRegions[idxRegion].GCRegionAddr = (RTGCPTR32)pBaseAddress;
+#else
+ aRegions[idxRegion].GCRegionAddr = (RTGCPTR64)pBaseAddress;
+#endif
+ aRegions[idxRegion].cbRegion = dwModuleSize;
+ idxRegion++;
+ }
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VbglR3RegisterSharedModule %s %s base=%p size=%x cregions=%d\n", pModule->Info.szModule, pModule->szFileVersion, pModule->Info.modBaseAddr, pModule->Info.modBaseSize, idxRegion);
+ int rc = VbglR3RegisterSharedModule(pModule->Info.szModule, pModule->szFileVersion, (uintptr_t)pModule->Info.modBaseAddr,
+ pModule->Info.modBaseSize, idxRegion, aRegions);
+ if (RT_FAILURE(rc))
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VbglR3RegisterSharedModule failed with %Rrc\n", rc);
+
+end:
+ RTMemFree(pVersionInfo);
+ return;
+}
+
+
+/**
+ * Inspect all loaded modules for the specified process
+ *
+ * @param dwProcessId Process id
+ * @param ppNewTree The module tree we're assembling from modules found
+ * in this process. Modules found are moved from
+ * g_pKnownModuleTree or created new.
+ */
+static void vgsvcPageSharingInspectModules(DWORD dwProcessId, PAVLPVNODECORE *ppNewTree)
+{
+ /* Get a list of all the modules in this process. */
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE /* no child process handle inheritance */, dwProcessId);
+ if (hProcess == NULL)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingInspectModules: OpenProcess %x failed with %d\n", dwProcessId, GetLastError());
+ return;
+ }
+
+ HANDLE hSnapshot = g_pfnCreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingInspectModules: CreateToolhelp32Snapshot failed with %d\n", GetLastError());
+ CloseHandle(hProcess);
+ return;
+ }
+
+ VGSvcVerbose(3, "vgsvcPageSharingInspectModules\n");
+
+ MODULEENTRY32 ModuleInfo;
+ BOOL bRet;
+
+ ModuleInfo.dwSize = sizeof(ModuleInfo);
+ bRet = g_pfnModule32First(hSnapshot, &ModuleInfo);
+ do
+ {
+ /** @todo when changing this make sure VBoxService.exe is excluded! */
+ char *pszDot = strrchr(ModuleInfo.szModule, '.');
+ if ( pszDot
+ && (pszDot[1] == 'e' || pszDot[1] == 'E'))
+ continue; /* ignore executables for now. */
+
+ /* Found it before? */
+ PAVLPVNODECORE pRec = RTAvlPVGet(ppNewTree, ModuleInfo.modBaseAddr);
+ if (!pRec)
+ {
+ pRec = RTAvlPVRemove(&g_pKnownModuleTree, ModuleInfo.modBaseAddr);
+ if (!pRec)
+ {
+ /* New module; register it. */
+ PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)RTMemAllocZ(sizeof(*pModule));
+ Assert(pModule);
+ if (!pModule)
+ break;
+
+ pModule->Info = ModuleInfo;
+ pModule->Core.Key = ModuleInfo.modBaseAddr;
+ pModule->hModule = LoadLibraryEx(ModuleInfo.szExePath, 0, DONT_RESOLVE_DLL_REFERENCES);
+ if (pModule->hModule)
+ vgsvcPageSharingRegisterModule(pModule, true /* validate pages */);
+
+ VGSvcVerbose(3, "\n\n MODULE NAME: %s", ModuleInfo.szModule );
+ VGSvcVerbose(3, "\n executable = %s", ModuleInfo.szExePath );
+ VGSvcVerbose(3, "\n process ID = 0x%08X", ModuleInfo.th32ProcessID );
+ VGSvcVerbose(3, "\n base address = %#010p", (uintptr_t) ModuleInfo.modBaseAddr );
+ VGSvcVerbose(3, "\n base size = %d", ModuleInfo.modBaseSize );
+
+ pRec = &pModule->Core;
+ }
+ bool ret = RTAvlPVInsert(ppNewTree, pRec);
+ Assert(ret); NOREF(ret);
+ }
+ } while (g_pfnModule32Next(hSnapshot, &ModuleInfo));
+
+ CloseHandle(hSnapshot);
+ CloseHandle(hProcess);
+}
+
+
+/**
+ * Inspect all running processes for executables and dlls that might be worth sharing
+ * with other VMs.
+ *
+ */
+static void vgsvcPageSharingInspectGuest(void)
+{
+ VGSvcVerbose(3, "vgsvcPageSharingInspectGuest\n");
+ PAVLPVNODECORE pNewTree = NULL;
+
+ /*
+ * Check loaded modules for all running processes.
+ */
+ if ( g_pfnProcess32First
+ && g_pfnProcess32Next
+ && g_pfnModule32First
+ && g_pfnModule32Next
+ && g_pfnCreateToolhelp32Snapshot)
+ {
+ HANDLE hSnapshot = g_pfnCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingInspectGuest: CreateToolhelp32Snapshot failed with %d\n", GetLastError());
+ return;
+ }
+
+ DWORD const dwProcessId = GetCurrentProcessId();
+
+ PROCESSENTRY32 ProcessInfo;
+ ProcessInfo.dwSize = sizeof(ProcessInfo);
+ g_pfnProcess32First(hSnapshot, &ProcessInfo);
+
+ do
+ {
+ /* Skip our own process. */
+ if (ProcessInfo.th32ProcessID != dwProcessId)
+ vgsvcPageSharingInspectModules(ProcessInfo.th32ProcessID, &pNewTree);
+ }
+ while (g_pfnProcess32Next(hSnapshot, &ProcessInfo));
+
+ CloseHandle(hSnapshot);
+ }
+
+ /*
+ * Check all loaded kernel modules.
+ */
+ if (g_pfnZwQuerySystemInformation)
+ {
+ ULONG cbBuffer = 0;
+ PVOID pBuffer = NULL;
+ PRTL_PROCESS_MODULES pSystemModules;
+
+ NTSTATUS ret = g_pfnZwQuerySystemInformation(SystemModuleInformation, (PVOID)&cbBuffer, 0, &cbBuffer);
+ if (!cbBuffer)
+ {
+ VGSvcVerbose(1, "ZwQuerySystemInformation returned length 0\n");
+ goto skipkernelmodules;
+ }
+
+ pBuffer = RTMemAllocZ(cbBuffer);
+ if (!pBuffer)
+ goto skipkernelmodules;
+
+ ret = g_pfnZwQuerySystemInformation(SystemModuleInformation, pBuffer, cbBuffer, &cbBuffer);
+ if (ret != STATUS_SUCCESS)
+ {
+ VGSvcVerbose(1, "ZwQuerySystemInformation returned %x (1)\n", ret);
+ goto skipkernelmodules;
+ }
+
+ pSystemModules = (PRTL_PROCESS_MODULES)pBuffer;
+ for (unsigned i = 0; i < pSystemModules->NumberOfModules; i++)
+ {
+ VGSvcVerbose(4, "\n\n KERNEL MODULE NAME: %s", pSystemModules->Modules[i].FullPathName[pSystemModules->Modules[i].OffsetToFileName] );
+ VGSvcVerbose(4, "\n executable = %s", pSystemModules->Modules[i].FullPathName );
+ VGSvcVerbose(4, "\n flags = 0x%08X\n", pSystemModules->Modules[i].Flags);
+
+ /* User-mode modules seem to have no flags set; skip them as we detected them above. */
+ if (pSystemModules->Modules[i].Flags == 0)
+ continue;
+
+ /* Found it before? */
+ PAVLPVNODECORE pRec = RTAvlPVGet(&pNewTree, pSystemModules->Modules[i].ImageBase);
+ if (!pRec)
+ {
+ pRec = RTAvlPVRemove(&g_pKnownModuleTree, pSystemModules->Modules[i].ImageBase);
+ if (!pRec)
+ {
+ /* New module; register it. */
+ char szFullFilePath[512];
+ PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)RTMemAllocZ(sizeof(*pModule));
+ Assert(pModule);
+ if (!pModule)
+ break;
+
+/** @todo FullPathName not an UTF-8 string is! An ANSI string it is
+ * according to the SYSTEM locale. Best use RtlAnsiStringToUnicodeString to
+ * convert to UTF-16. */
+ strcpy(pModule->Info.szModule,
+ (const char *)&pSystemModules->Modules[i].FullPathName[pSystemModules->Modules[i].OffsetToFileName]);
+ GetSystemDirectoryA(szFullFilePath, sizeof(szFullFilePath));
+
+ /* skip \Systemroot\system32 */
+ char *lpPath = strchr((char *)&pSystemModules->Modules[i].FullPathName[1], '\\');
+ if (!lpPath)
+ {
+ /* Seen just file names in XP; try to locate the file in the system32 and system32\drivers directories. */
+ RTStrCat(szFullFilePath, sizeof(szFullFilePath), "\\");
+ RTStrCat(szFullFilePath, sizeof(szFullFilePath), (const char *)pSystemModules->Modules[i].FullPathName);
+ VGSvcVerbose(3, "Unexpected kernel module name try %s\n", szFullFilePath);
+ if (RTFileExists(szFullFilePath) == false)
+ {
+ GetSystemDirectoryA(szFullFilePath, sizeof(szFullFilePath));
+ RTStrCat(szFullFilePath, sizeof(szFullFilePath), "\\drivers\\");
+ RTStrCat(szFullFilePath, sizeof(szFullFilePath), (const char *)pSystemModules->Modules[i].FullPathName);
+ VGSvcVerbose(3, "Unexpected kernel module name try %s\n", szFullFilePath);
+ if (RTFileExists(szFullFilePath) == false)
+ {
+ VGSvcVerbose(1, "Unexpected kernel module name %s\n", pSystemModules->Modules[i].FullPathName);
+ RTMemFree(pModule);
+ continue;
+ }
+ }
+ }
+ else
+ {
+ lpPath = strchr(lpPath + 1, '\\');
+ if (!lpPath)
+ {
+ VGSvcVerbose(1, "Unexpected kernel module name %s (2)\n", pSystemModules->Modules[i].FullPathName);
+ RTMemFree(pModule);
+ continue;
+ }
+
+ RTStrCat(szFullFilePath, sizeof(szFullFilePath), lpPath);
+ }
+
+ strcpy(pModule->Info.szExePath, szFullFilePath);
+ pModule->Info.modBaseAddr = (BYTE *)pSystemModules->Modules[i].ImageBase;
+ pModule->Info.modBaseSize = pSystemModules->Modules[i].ImageSize;
+
+ pModule->Core.Key = pSystemModules->Modules[i].ImageBase;
+ vgsvcPageSharingRegisterModule(pModule, false /* don't check memory pages */);
+
+ VGSvcVerbose(3, "\n\n KERNEL MODULE NAME: %s", pModule->Info.szModule );
+ VGSvcVerbose(3, "\n executable = %s", pModule->Info.szExePath );
+ VGSvcVerbose(3, "\n base address = %#010p", (uintptr_t)pModule->Info.modBaseAddr );
+ VGSvcVerbose(3, "\n flags = 0x%08X", pSystemModules->Modules[i].Flags);
+ VGSvcVerbose(3, "\n base size = %d", pModule->Info.modBaseSize );
+
+ pRec = &pModule->Core;
+ }
+ bool fRet = RTAvlPVInsert(&pNewTree, pRec);
+ Assert(fRet); NOREF(fRet);
+ }
+ }
+skipkernelmodules:
+ if (pBuffer)
+ RTMemFree(pBuffer);
+ }
+
+ /* Delete leftover modules in the old tree. */
+ RTAvlPVDestroy(&g_pKnownModuleTree, vgsvcPageSharingEmptyTreeCallback, NULL);
+
+ /* Check all registered modules. */
+ VbglR3CheckSharedModules();
+
+ /* Activate new module tree. */
+ g_pKnownModuleTree = pNewTree;
+}
+
+
+/**
+ * RTAvlPVDestroy callback.
+ */
+static DECLCALLBACK(int) vgsvcPageSharingEmptyTreeCallback(PAVLPVNODECORE pNode, void *pvUser)
+{
+ PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)pNode;
+ bool *pfUnregister = (bool *)pvUser;
+
+ VGSvcVerbose(3, "vgsvcPageSharingEmptyTreeCallback %s %s\n", pModule->Info.szModule, pModule->szFileVersion);
+
+ /* Dereference module in the hypervisor. */
+ if ( !pfUnregister
+ || *pfUnregister)
+ {
+ int rc = VbglR3UnregisterSharedModule(pModule->Info.szModule, pModule->szFileVersion,
+ (uintptr_t)pModule->Info.modBaseAddr, pModule->Info.modBaseSize);
+ AssertRC(rc);
+ }
+
+ if (pModule->hModule)
+ FreeLibrary(pModule->hModule);
+ RTMemFree(pNode);
+ return 0;
+}
+
+
+#else /* !RT_OS_WINDOWS */
+
+static void vgsvcPageSharingInspectGuest(void)
+{
+ /** @todo other platforms */
+}
+
+#endif /* !RT_OS_WINDOWS */
+
+/** @interface_method_impl{VBOXSERVICE,pfnInit} */
+static DECLCALLBACK(int) vgsvcPageSharingInit(void)
+{
+ VGSvcVerbose(3, "vgsvcPageSharingInit\n");
+
+ int rc = RTSemEventMultiCreate(&g_PageSharingEvent);
+ AssertRCReturn(rc, rc);
+
+#ifdef RT_OS_WINDOWS
+ rc = VbglR3GetSessionId(&g_idSession);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_IO_GEN_FAILURE)
+ VGSvcVerbose(0, "PageSharing: Page sharing support is not available by the host\n");
+ else
+ VGSvcError("vgsvcPageSharingInit: Failed with rc=%Rrc\n", rc);
+
+ rc = VERR_SERVICE_DISABLED;
+
+ RTSemEventMultiDestroy(g_PageSharingEvent);
+ g_PageSharingEvent = NIL_RTSEMEVENTMULTI;
+
+ }
+#endif
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vgsvcPageSharingWorker(bool volatile *pfShutdown)
+{
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Now enter the loop retrieving runtime data continuously.
+ */
+ for (;;)
+ {
+ bool fEnabled = VbglR3PageSharingIsEnabled();
+ VGSvcVerbose(3, "vgsvcPageSharingWorker: enabled=%d\n", fEnabled);
+
+ if (fEnabled)
+ vgsvcPageSharingInspectGuest();
+
+ /*
+ * Block for a minute.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ int rc = RTSemEventMultiWait(g_PageSharingEvent, 60000);
+ if (*pfShutdown)
+ break;
+ if (rc != VERR_TIMEOUT && RT_FAILURE(rc))
+ {
+ VGSvcError("vgsvcPageSharingWorker: RTSemEventMultiWait failed; rc=%Rrc\n", rc);
+ break;
+ }
+#ifdef RT_OS_WINDOWS
+ uint64_t idNewSession = g_idSession;
+ rc = VbglR3GetSessionId(&idNewSession);
+ AssertRC(rc);
+
+ if (idNewSession != g_idSession)
+ {
+ bool fUnregister = false;
+
+ VGSvcVerbose(3, "vgsvcPageSharingWorker: VM was restored!!\n");
+ /* The VM was restored, so reregister all modules the next time. */
+ RTAvlPVDestroy(&g_pKnownModuleTree, vgsvcPageSharingEmptyTreeCallback, &fUnregister);
+ g_pKnownModuleTree = NULL;
+
+ g_idSession = idNewSession;
+ }
+#endif
+ }
+
+ RTSemEventMultiDestroy(g_PageSharingEvent);
+ g_PageSharingEvent = NIL_RTSEMEVENTMULTI;
+
+ VGSvcVerbose(3, "vgsvcPageSharingWorker: finished thread\n");
+ return 0;
+}
+
+#ifdef RT_OS_WINDOWS
+
+/**
+ * This gets control when VBoxService is launched with "pagefusion" by
+ * vgsvcPageSharingWorkerProcess().
+ *
+ * @returns RTEXITCODE_SUCCESS.
+ *
+ * @remarks It won't normally return since the parent drops the shutdown hint
+ * via RTProcTerminate().
+ */
+RTEXITCODE VGSvcPageSharingWorkerChild(void)
+{
+ VGSvcVerbose(3, "vgsvcPageSharingInitFork\n");
+
+ bool fShutdown = false;
+ vgsvcPageSharingInit();
+ vgsvcPageSharingWorker(&fShutdown);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vgsvcPageSharingWorkerProcess(bool volatile *pfShutdown)
+{
+ RTPROCESS hProcess = NIL_RTPROCESS;
+ int rc;
+
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Now enter the loop retrieving runtime data continuously.
+ */
+ for (;;)
+ {
+ bool fEnabled = VbglR3PageSharingIsEnabled();
+ VGSvcVerbose(3, "vgsvcPageSharingWorkerProcess: enabled=%d\n", fEnabled);
+
+ /*
+ * Start a 2nd VBoxService process to deal with page fusion as we do
+ * not wish to dummy load dlls into this process. (First load with
+ * DONT_RESOLVE_DLL_REFERENCES, 2nd normal -> dll init routines not called!)
+ */
+ if ( fEnabled
+ && hProcess == NIL_RTPROCESS)
+ {
+ char szExeName[256];
+ char *pszExeName = RTProcGetExecutablePath(szExeName, sizeof(szExeName));
+ if (pszExeName)
+ {
+ char const *papszArgs[3];
+ papszArgs[0] = pszExeName;
+ papszArgs[1] = "pagefusion";
+ papszArgs[2] = NULL;
+ rc = RTProcCreate(pszExeName, papszArgs, RTENV_DEFAULT, 0 /* normal child */, &hProcess);
+ if (RT_FAILURE(rc))
+ VGSvcError("vgsvcPageSharingWorkerProcess: RTProcCreate %s failed; rc=%Rrc\n", pszExeName, rc);
+ }
+ }
+
+ /*
+ * Block for a minute.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ rc = RTSemEventMultiWait(g_PageSharingEvent, 60000);
+ if (*pfShutdown)
+ break;
+ if (rc != VERR_TIMEOUT && RT_FAILURE(rc))
+ {
+ VGSvcError("vgsvcPageSharingWorkerProcess: RTSemEventMultiWait failed; rc=%Rrc\n", rc);
+ break;
+ }
+ }
+
+ if (hProcess != NIL_RTPROCESS)
+ RTProcTerminate(hProcess);
+
+ VGSvcVerbose(3, "vgsvcPageSharingWorkerProcess: finished thread\n");
+ return 0;
+}
+
+#endif /* RT_OS_WINDOWS */
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcPageSharingStop(void)
+{
+ RTSemEventMultiSignal(g_PageSharingEvent);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcPageSharingTerm(void)
+{
+ if (g_PageSharingEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_PageSharingEvent);
+ g_PageSharingEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'pagesharing' service description.
+ */
+VBOXSERVICE g_PageSharing =
+{
+ /* pszName. */
+ "pagesharing",
+ /* pszDescription. */
+ "Page Sharing",
+ /* pszUsage. */
+ NULL,
+ /* pszOptions. */
+ NULL,
+ /* methods */
+ VGSvcDefaultPreInit,
+ VGSvcDefaultOption,
+ vgsvcPageSharingInit,
+#ifdef RT_OS_WINDOWS
+ vgsvcPageSharingWorkerProcess,
+#else
+ vgsvcPageSharingWorker,
+#endif
+ vgsvcPageSharingStop,
+ vgsvcPageSharingTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp
new file mode 100644
index 00000000..6df00ba3
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp
@@ -0,0 +1,439 @@
+/* $Id: VBoxServicePropCache.cpp $ */
+/** @file
+ * VBoxServicePropCache - Guest property cache.
+ */
+
+/*
+ * 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 <iprt/assert.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+#include "VBoxServicePropCache.h"
+
+
+
+/** @todo Docs */
+static PVBOXSERVICEVEPROPCACHEENTRY vgsvcPropCacheFindInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName,
+ uint32_t fFlags)
+{
+ RT_NOREF1(fFlags);
+ AssertPtrReturn(pCache, NULL);
+ AssertPtrReturn(pszName, NULL);
+
+ /** @todo This is a O(n) lookup, maybe improve this later to O(1) using a
+ * map.
+ * r=bird: Use a string space (RTstrSpace*). That is O(log n) in its current
+ * implementation (AVL tree). However, this is not important at the
+ * moment. */
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = NULL;
+ if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect)))
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNodeIt;
+ RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc)
+ {
+ if (strcmp(pNodeIt->pszName, pszName) == 0)
+ {
+ pNode = pNodeIt;
+ break;
+ }
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+ return pNode;
+}
+
+
+/** @todo Docs */
+static PVBOXSERVICEVEPROPCACHEENTRY vgsvcPropCacheInsertEntryInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName)
+{
+ AssertPtrReturn(pCache, NULL);
+ AssertPtrReturn(pszName, NULL);
+
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = (PVBOXSERVICEVEPROPCACHEENTRY)RTMemAlloc(sizeof(VBOXSERVICEVEPROPCACHEENTRY));
+ if (pNode)
+ {
+ pNode->pszName = RTStrDup(pszName);
+ if (!pNode->pszName)
+ {
+ RTMemFree(pNode);
+ return NULL;
+ }
+ pNode->pszValue = NULL;
+ pNode->fFlags = 0;
+ pNode->pszValueReset = NULL;
+
+ int rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTListAppend(&pCache->NodeHead, &pNode->NodeSucc);
+ rc = RTCritSectLeave(&pCache->CritSect);
+ }
+ }
+ return pNode;
+}
+
+
+/** @todo Docs */
+static int vgsvcPropCacheWritePropF(uint32_t u32ClientId, const char *pszName, uint32_t fFlags, const char *pszValueFormat, ...)
+{
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+
+ int rc;
+ if (pszValueFormat != NULL)
+ {
+ va_list va;
+ va_start(va, pszValueFormat);
+
+ char *pszValue;
+ if (RTStrAPrintfV(&pszValue, pszValueFormat, va) >= 0)
+ {
+ if (fFlags & VGSVCPROPCACHE_FLAGS_TRANSIENT)
+ {
+ /*
+ * Because a value can be temporary we have to make sure it also
+ * gets deleted when the property cache did not have the chance to
+ * gracefully clean it up (due to a hard VM reset etc), so set this
+ * guest property using the TRANSRESET flag..
+ */
+ rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, "TRANSRESET");
+ if (rc == VERR_PARSE_ERROR)
+ {
+ /* Host does not support the "TRANSRESET" flag, so only
+ * use the "TRANSIENT" flag -- better than nothing :-). */
+ rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, "TRANSIENT");
+ /** @todo r=bird: Remember that the host doesn't support
+ * this. */
+ }
+ }
+ else
+ rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, pszValue /* No transient flags set */);
+ RTStrFree(pszValue);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ va_end(va);
+ }
+ else
+ rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, NULL);
+ return rc;
+}
+
+
+/**
+ * Creates a property cache.
+ *
+ * @returns IPRT status code.
+ * @param pCache Pointer to the cache.
+ * @param uClientId The HGCM handle of to the guest property service.
+ */
+int VGSvcPropCacheCreate(PVBOXSERVICEVEPROPCACHE pCache, uint32_t uClientId)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ /** @todo Prevent init the cache twice!
+ * r=bird: Use a magic. */
+ RTListInit(&pCache->NodeHead);
+ pCache->uClientID = uClientId;
+ return RTCritSectInit(&pCache->CritSect);
+}
+
+
+/**
+ * Updates a cache entry without submitting any changes to the host.
+ *
+ * This is handy for defining default values/flags.
+ *
+ * @returns VBox status code.
+ *
+ * @param pCache The property cache.
+ * @param pszName The property name.
+ * @param fFlags The property flags to set.
+ * @param pszValueReset The property reset value.
+ */
+int VGSvcPropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, uint32_t fFlags, const char *pszValueReset)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = vgsvcPropCacheFindInternal(pCache, pszName, 0);
+ if (pNode == NULL)
+ pNode = vgsvcPropCacheInsertEntryInternal(pCache, pszName);
+
+ int rc;
+ if (pNode != NULL)
+ {
+ rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pNode->fFlags = fFlags;
+ if (pszValueReset)
+ {
+ if (pNode->pszValueReset)
+ RTStrFree(pNode->pszValueReset);
+ pNode->pszValueReset = RTStrDup(pszValueReset);
+ AssertPtr(pNode->pszValueReset);
+ }
+ rc = RTCritSectLeave(&pCache->CritSect);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Updates the local guest property cache and writes it to HGCM if outdated.
+ *
+ * @returns VBox status code.
+ *
+ * @param pCache The property cache.
+ * @param pszName The property name.
+ * @param pszValueFormat The property format string. If this is NULL then
+ * the property will be deleted (if possible).
+ * @param ... Format arguments.
+ */
+int VGSvcPropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, const char *pszValueFormat, ...)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+
+ Assert(pCache->uClientID);
+
+ /*
+ * Format the value first.
+ */
+ char *pszValue = NULL;
+ if (pszValueFormat)
+ {
+ va_list va;
+ va_start(va, pszValueFormat);
+ RTStrAPrintfV(&pszValue, pszValueFormat, va);
+ va_end(va);
+ if (!pszValue)
+ return VERR_NO_STR_MEMORY;
+ }
+
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = vgsvcPropCacheFindInternal(pCache, pszName, 0);
+
+ /* Lock the cache. */
+ int rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (pNode == NULL)
+ pNode = vgsvcPropCacheInsertEntryInternal(pCache, pszName);
+
+ AssertPtr(pNode);
+ if (pszValue) /* Do we have a value to check for? */
+ {
+ bool fUpdate = false;
+ /* Always update this property, no matter what? */
+ if (pNode->fFlags & VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE)
+ fUpdate = true;
+ /* Did the value change so we have to update? */
+ else if (pNode->pszValue && strcmp(pNode->pszValue, pszValue) != 0)
+ fUpdate = true;
+ /* No value stored at the moment but we have a value now? */
+ else if (pNode->pszValue == NULL)
+ fUpdate = true;
+
+ if (fUpdate)
+ {
+ /* Write the update. */
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, pNode->fFlags, pszValue);
+ VGSvcVerbose(4, "[PropCache %p]: Written '%s'='%s' (flags: %x), rc=%Rrc\n",
+ pCache, pNode->pszName, pszValue, pNode->fFlags, rc);
+ if (RT_SUCCESS(rc)) /* Only update the node's value on successful write. */
+ {
+ RTStrFree(pNode->pszValue);
+ pNode->pszValue = RTStrDup(pszValue);
+ if (!pNode->pszValue)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VINF_NO_CHANGE; /* No update needed. */
+ }
+ else
+ {
+ /* No value specified. Deletion (or no action required). */
+ if (pNode->pszValue) /* Did we have a value before? Then the value needs to be deleted. */
+ {
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName,
+ 0, /* Flags */ NULL /* Value */);
+ VGSvcVerbose(4, "[PropCache %p]: Deleted '%s'='%s' (flags: %x), rc=%Rrc\n",
+ pCache, pNode->pszName, pNode->pszValue, pNode->fFlags, rc);
+ if (RT_SUCCESS(rc)) /* Only delete property value on successful Vbgl deletion. */
+ {
+ /* Delete property (but do not remove from cache) if not deleted yet. */
+ RTStrFree(pNode->pszValue);
+ pNode->pszValue = NULL;
+ }
+ }
+ else
+ rc = VINF_NO_CHANGE; /* No update needed. */
+ }
+
+ /* Release cache. */
+ RTCritSectLeave(&pCache->CritSect);
+ }
+
+ VGSvcVerbose(4, "[PropCache %p]: Updating '%s' resulted in rc=%Rrc\n", pCache, pszName, rc);
+
+ /* Delete temp stuff. */
+ RTStrFree(pszValue);
+ return rc;
+}
+
+
+/**
+ * Updates all cache values which are matching the specified path.
+ *
+ * @returns VBox status code.
+ *
+ * @param pCache The property cache.
+ * @param pszValue The value to set. A NULL will delete the value.
+ * @param fFlags Flags to set.
+ * @param pszPathFormat The path format string. May not be null and has
+ * to be an absolute path.
+ * @param ... Format arguments.
+ */
+int VGSvcPropCacheUpdateByPath(PVBOXSERVICEVEPROPCACHE pCache, const char *pszValue, uint32_t fFlags,
+ const char *pszPathFormat, ...)
+{
+ RT_NOREF1(fFlags);
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPathFormat, VERR_INVALID_POINTER);
+
+ int rc = VERR_NOT_FOUND;
+ if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect)))
+ {
+ /*
+ * Format the value first.
+ */
+ char *pszPath = NULL;
+ va_list va;
+ va_start(va, pszPathFormat);
+ RTStrAPrintfV(&pszPath, pszPathFormat, va);
+ va_end(va);
+ if (!pszPath)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ }
+ else
+ {
+ /* Iterate through all nodes and compare their paths. */
+ PVBOXSERVICEVEPROPCACHEENTRY pNodeIt;
+ RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc)
+ {
+ if (RTStrStr(pNodeIt->pszName, pszPath) == pNodeIt->pszName)
+ {
+ /** @todo Use some internal function to update the node directly, this is slow atm. */
+ rc = VGSvcPropCacheUpdate(pCache, pNodeIt->pszName, pszValue);
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTStrFree(pszPath);
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+ return rc;
+}
+
+
+/**
+ * Flushes the cache by writing every item regardless of its state.
+ *
+ * @param pCache The property cache.
+ */
+int VGSvcPropCacheFlush(PVBOXSERVICEVEPROPCACHE pCache)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect)))
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNodeIt;
+ RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc)
+ {
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNodeIt->pszName, pNodeIt->fFlags, pNodeIt->pszValue);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+ return rc;
+}
+
+
+/**
+ * Reset all temporary properties and destroy the cache.
+ *
+ * @param pCache The property cache.
+ */
+void VGSvcPropCacheDestroy(PVBOXSERVICEVEPROPCACHE pCache)
+{
+ AssertPtrReturnVoid(pCache);
+ Assert(pCache->uClientID);
+
+ /* Lock the cache. */
+ int rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = RTListGetFirst(&pCache->NodeHead, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc);
+ while (pNode)
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNext = RTListNodeIsLast(&pCache->NodeHead, &pNode->NodeSucc)
+ ? NULL :
+ RTListNodeGetNext(&pNode->NodeSucc,
+ VBOXSERVICEVEPROPCACHEENTRY, NodeSucc);
+ RTListNodeRemove(&pNode->NodeSucc);
+
+ if (pNode->fFlags & VGSVCPROPCACHE_FLAGS_TEMPORARY)
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, pNode->fFlags, pNode->pszValueReset);
+
+ AssertPtr(pNode->pszName);
+ RTStrFree(pNode->pszName);
+ RTStrFree(pNode->pszValue);
+ RTStrFree(pNode->pszValueReset);
+ pNode->fFlags = 0;
+
+ RTMemFree(pNode);
+
+ pNode = pNext;
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+
+ /* Destroy critical section. */
+ RTCritSectDelete(&pCache->CritSect);
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h
new file mode 100644
index 00000000..4abc4085
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h
@@ -0,0 +1,66 @@
+/* $Id: */
+/** @file
+ * VBoxServicePropCache - Guest property cache.
+ */
+
+/*
+ * 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
+ */
+
+#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBoxServiceInternal.h"
+
+#ifdef VBOX_WITH_GUEST_PROPS
+
+/** @name VGSVCPROPCACHE_FLAG_XXX - Guest Property Cache Flags.
+ * @{ */
+/** Indicates wheter a guest property is temporary and either should
+ * - a) get a "reset" value assigned (via VBoxServicePropCacheUpdateEntry)
+ * as soon as the property cache gets destroyed, or
+ * - b) get deleted when no reset value is specified.
+ */
+# define VGSVCPROPCACHE_FLAGS_TEMPORARY RT_BIT(1)
+/** Indicates whether a property every time needs to be updated, regardless
+ * if its real value changed or not. */
+# define VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE RT_BIT(2)
+/** The guest property gets deleted when
+ * - a) the property cache gets destroyed, or
+ * - b) the VM gets reset / shutdown / destroyed.
+ */
+# define VGSVCPROPCACHE_FLAGS_TRANSIENT RT_BIT(3)
+/** @} */
+
+int VGSvcPropCacheCreate(PVBOXSERVICEVEPROPCACHE pCache, uint32_t uClientId);
+int VGSvcPropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, uint32_t fFlags, const char *pszValueReset);
+int VGSvcPropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, const char *pszValueFormat, ...);
+int VGSvcPropCacheUpdateByPath(PVBOXSERVICEVEPROPCACHE pCache, const char *pszValue, uint32_t fFlags,
+ const char *pszPathFormat, ...);
+int VGSvcPropCacheFlush(PVBOXSERVICEVEPROPCACHE pCache);
+void VGSvcPropCacheDestroy(PVBOXSERVICEVEPROPCACHE pCache);
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h b/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h
new file mode 100644
index 00000000..b11e862d
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h
@@ -0,0 +1,37 @@
+/* $Id: VBoxServiceResource-win.h $ */
+/** @file
+ * VBoxService - Guest Additions Service, resource IDs.
+ */
+
+/*
+ * Copyright (C) 2006-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 GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define IDI_VIRTUALBOX 101
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp
new file mode 100644
index 00000000..8d68b082
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp
@@ -0,0 +1,747 @@
+/* $Id: VBoxServiceStats.cpp $ */
+/** @file
+ * VBoxStats - Guest statistics notification
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+/** @page pg_vgsvc_vmstats VBoxService - VM Statistics
+ *
+ * The VM statistics subservice helps out the performance collector API on the
+ * host side by providing metrics from inside the guest.
+ *
+ * See IPerformanceCollector, CollectorGuest and the "Guest/" submetrics that
+ * gets registered by Machine::i_registerMetrics in Main.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#if defined(RT_OS_WINDOWS)
+# include <iprt/win/windows.h>
+# include <psapi.h>
+# include <winternl.h>
+
+#elif defined(RT_OS_LINUX)
+# include <iprt/ctype.h>
+# include <iprt/stream.h>
+# include <unistd.h>
+
+#elif defined(RT_OS_SOLARIS)
+# include <kstat.h>
+# include <sys/sysinfo.h>
+# include <unistd.h>
+#else
+/** @todo port me. */
+
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/ldr.h>
+#include <VBox/param.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <VBox/err.h>
+#include <VBox/VMMDev.h> /* For VMMDevReportGuestStats and indirectly VbglR3StatReport. */
+#include <VBox/VBoxGuestLib.h>
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct VBOXSTATSCONTEXT
+{
+ RTMSINTERVAL cMsStatInterval;
+
+ uint64_t au64LastCpuLoad_Idle[VMM_MAX_CPU_COUNT];
+ uint64_t au64LastCpuLoad_Kernel[VMM_MAX_CPU_COUNT];
+ uint64_t au64LastCpuLoad_User[VMM_MAX_CPU_COUNT];
+ uint64_t au64LastCpuLoad_Nice[VMM_MAX_CPU_COUNT];
+
+#ifdef RT_OS_WINDOWS
+ DECLCALLBACKMEMBER_EX(NTSTATUS, WINAPI, pfnNtQuerySystemInformation,(SYSTEM_INFORMATION_CLASS SystemInformationClass,
+ PVOID SystemInformation, ULONG SystemInformationLength,
+ PULONG ReturnLength));
+ DECLCALLBACKMEMBER_EX(void, WINAPI, pfnGlobalMemoryStatusEx,(LPMEMORYSTATUSEX lpBuffer));
+ DECLCALLBACKMEMBER_EX(BOOL, WINAPI, pfnGetPerformanceInfo,(PPERFORMANCE_INFORMATION pPerformanceInformation, DWORD cb));
+#endif
+} VBOXSTATSCONTEXT;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Global data. */
+static VBOXSTATSCONTEXT g_VMStat = {0};
+
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_VMStatEvent = NIL_RTSEMEVENTMULTI;
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vgsvcVMStatsInit(void)
+{
+ VGSvcVerbose(3, "vgsvcVMStatsInit\n");
+
+ int rc = RTSemEventMultiCreate(&g_VMStatEvent);
+ AssertRCReturn(rc, rc);
+
+ g_VMStat.cMsStatInterval = 0; /* default; update disabled */
+ RT_ZERO(g_VMStat.au64LastCpuLoad_Idle);
+ RT_ZERO(g_VMStat.au64LastCpuLoad_Kernel);
+ RT_ZERO(g_VMStat.au64LastCpuLoad_User);
+ RT_ZERO(g_VMStat.au64LastCpuLoad_Nice);
+
+ rc = VbglR3StatQueryInterval(&g_VMStat.cMsStatInterval);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsInit: New statistics interval %u seconds\n", g_VMStat.cMsStatInterval);
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsInit: DeviceIoControl failed with %d\n", rc);
+
+#ifdef RT_OS_WINDOWS
+ /* NtQuerySystemInformation might be dropped in future releases, so load
+ it dynamically as per Microsoft's recommendation. */
+ *(void **)&g_VMStat.pfnNtQuerySystemInformation = RTLdrGetSystemSymbol("ntdll.dll", "NtQuerySystemInformation");
+ if (g_VMStat.pfnNtQuerySystemInformation)
+ VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.pfnNtQuerySystemInformation = %x\n", g_VMStat.pfnNtQuerySystemInformation);
+ else
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsInit: ntdll.NtQuerySystemInformation not found!\n");
+ return VERR_SERVICE_DISABLED;
+ }
+
+ /* GlobalMemoryStatus is win2k and up, so load it dynamically */
+ *(void **)&g_VMStat.pfnGlobalMemoryStatusEx = RTLdrGetSystemSymbol("kernel32.dll", "GlobalMemoryStatusEx");
+ if (g_VMStat.pfnGlobalMemoryStatusEx)
+ VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.GlobalMemoryStatusEx = %x\n", g_VMStat.pfnGlobalMemoryStatusEx);
+ else
+ {
+ /** @todo Now fails in NT4; do we care? */
+ VGSvcVerbose(3, "vgsvcVMStatsInit: kernel32.GlobalMemoryStatusEx not found!\n");
+ return VERR_SERVICE_DISABLED;
+ }
+
+ /* GetPerformanceInfo is xp and up, so load it dynamically */
+ *(void **)&g_VMStat.pfnGetPerformanceInfo = RTLdrGetSystemSymbol("psapi.dll", "GetPerformanceInfo");
+ if (g_VMStat.pfnGetPerformanceInfo)
+ VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.pfnGetPerformanceInfo= %x\n", g_VMStat.pfnGetPerformanceInfo);
+#endif /* RT_OS_WINDOWS */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gathers VM statistics and reports them to the host.
+ */
+static void vgsvcVMStatsReport(void)
+{
+#if defined(RT_OS_WINDOWS)
+ Assert(g_VMStat.pfnGlobalMemoryStatusEx && g_VMStat.pfnNtQuerySystemInformation);
+ if ( !g_VMStat.pfnGlobalMemoryStatusEx
+ || !g_VMStat.pfnNtQuerySystemInformation)
+ return;
+
+ /* Clear the report so we don't report garbage should NtQuerySystemInformation
+ behave in an unexpected manner. */
+ VMMDevReportGuestStats req;
+ RT_ZERO(req);
+
+ /* Query and report guest statistics */
+ SYSTEM_INFO systemInfo;
+ GetSystemInfo(&systemInfo);
+
+ MEMORYSTATUSEX memStatus;
+ memStatus.dwLength = sizeof(memStatus);
+ g_VMStat.pfnGlobalMemoryStatusEx(&memStatus);
+
+ req.guestStats.u32PageSize = systemInfo.dwPageSize;
+ req.guestStats.u32PhysMemTotal = (uint32_t)(memStatus.ullTotalPhys / _4K);
+ req.guestStats.u32PhysMemAvail = (uint32_t)(memStatus.ullAvailPhys / _4K);
+ /* The current size of the committed memory limit, in bytes. This is physical
+ memory plus the size of the page file, minus a small overhead. */
+ req.guestStats.u32PageFileSize = (uint32_t)(memStatus.ullTotalPageFile / _4K) - req.guestStats.u32PhysMemTotal;
+ req.guestStats.u32MemoryLoad = memStatus.dwMemoryLoad;
+ req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL
+ | VBOX_GUEST_STAT_PHYS_MEM_AVAIL
+ | VBOX_GUEST_STAT_PAGE_FILE_SIZE
+ | VBOX_GUEST_STAT_MEMORY_LOAD;
+# ifdef VBOX_WITH_MEMBALLOON
+ req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K);
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON;
+# else
+ req.guestStats.u32PhysMemBalloon = 0;
+# endif
+
+ if (g_VMStat.pfnGetPerformanceInfo)
+ {
+ PERFORMANCE_INFORMATION perfInfo;
+
+ if (g_VMStat.pfnGetPerformanceInfo(&perfInfo, sizeof(perfInfo)))
+ {
+ req.guestStats.u32Processes = perfInfo.ProcessCount;
+ req.guestStats.u32Threads = perfInfo.ThreadCount;
+ req.guestStats.u32Handles = perfInfo.HandleCount;
+ req.guestStats.u32MemCommitTotal = perfInfo.CommitTotal; /* already in pages */
+ req.guestStats.u32MemKernelTotal = perfInfo.KernelTotal; /* already in pages */
+ req.guestStats.u32MemKernelPaged = perfInfo.KernelPaged; /* already in pages */
+ req.guestStats.u32MemKernelNonPaged = perfInfo.KernelNonpaged; /* already in pages */
+ req.guestStats.u32MemSystemCache = perfInfo.SystemCache; /* already in pages */
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PROCESSES | VBOX_GUEST_STAT_THREADS | VBOX_GUEST_STAT_HANDLES
+ | VBOX_GUEST_STAT_MEM_COMMIT_TOTAL | VBOX_GUEST_STAT_MEM_KERNEL_TOTAL
+ | VBOX_GUEST_STAT_MEM_KERNEL_PAGED | VBOX_GUEST_STAT_MEM_KERNEL_NONPAGED
+ | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE;
+ }
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: GetPerformanceInfo failed with %d\n", GetLastError());
+ }
+
+ /* Query CPU load information */
+ uint32_t cbStruct = systemInfo.dwNumberOfProcessors * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION);
+ PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION pProcInfo;
+ pProcInfo = (PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)RTMemAlloc(cbStruct);
+ if (!pProcInfo)
+ return;
+
+ /* Unfortunately GetSystemTimes is XP SP1 and up only, so we need to use the semi-undocumented NtQuerySystemInformation */
+ bool fCpuInfoAvail = false;
+ DWORD cbReturned;
+ NTSTATUS rcNt = g_VMStat.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned);
+ if ( !rcNt
+ && cbReturned == cbStruct)
+ {
+ for (uint32_t i = 0; i < systemInfo.dwNumberOfProcessors; i++)
+ {
+ if (i >= VMM_MAX_CPU_COUNT)
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPUs %u..%u\n", i, systemInfo.dwNumberOfProcessors);
+ break;
+ }
+
+ if (g_VMStat.au64LastCpuLoad_Kernel[i] == 0)
+ {
+ /* first time */
+ g_VMStat.au64LastCpuLoad_Idle[i] = pProcInfo[i].IdleTime.QuadPart;
+ g_VMStat.au64LastCpuLoad_Kernel[i] = pProcInfo[i].KernelTime.QuadPart;
+ g_VMStat.au64LastCpuLoad_User[i] = pProcInfo[i].UserTime.QuadPart;
+
+ Sleep(250);
+
+ rcNt = g_VMStat.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned);
+ Assert(!rcNt);
+ }
+
+ uint64_t deltaIdle = (pProcInfo[i].IdleTime.QuadPart - g_VMStat.au64LastCpuLoad_Idle[i]);
+ uint64_t deltaKernel = (pProcInfo[i].KernelTime.QuadPart - g_VMStat.au64LastCpuLoad_Kernel[i]);
+ uint64_t deltaUser = (pProcInfo[i].UserTime.QuadPart - g_VMStat.au64LastCpuLoad_User[i]);
+ deltaKernel -= deltaIdle; /* idle time is added to kernel time */
+ uint64_t ullTotalTime = deltaIdle + deltaKernel + deltaUser;
+ if (ullTotalTime == 0) /* Prevent division through zero. */
+ ullTotalTime = 1;
+
+ req.guestStats.u32CpuLoad_Idle = (uint32_t)(deltaIdle * 100 / ullTotalTime);
+ req.guestStats.u32CpuLoad_Kernel = (uint32_t)(deltaKernel* 100 / ullTotalTime);
+ req.guestStats.u32CpuLoad_User = (uint32_t)(deltaUser * 100 / ullTotalTime);
+
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE
+ | VBOX_GUEST_STAT_CPU_LOAD_KERNEL
+ | VBOX_GUEST_STAT_CPU_LOAD_USER;
+ req.guestStats.u32CpuId = i;
+ fCpuInfoAvail = true;
+ int rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", i);
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: VbglR3StatReport failed with rc=%Rrc\n", rc);
+
+ g_VMStat.au64LastCpuLoad_Idle[i] = pProcInfo[i].IdleTime.QuadPart;
+ g_VMStat.au64LastCpuLoad_Kernel[i] = pProcInfo[i].KernelTime.QuadPart;
+ g_VMStat.au64LastCpuLoad_User[i] = pProcInfo[i].UserTime.QuadPart;
+ }
+ }
+ RTMemFree(pProcInfo);
+
+ if (!fCpuInfoAvail)
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n");
+ int rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n");
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc);
+ }
+
+#elif defined(RT_OS_LINUX)
+ VMMDevReportGuestStats req;
+ RT_ZERO(req);
+ PRTSTREAM pStrm;
+ char szLine[256];
+ char *psz;
+
+ int rc = RTStrmOpen("/proc/meminfo", "r", &pStrm);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t u64Kb;
+ uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0;
+ for (;;)
+ {
+ rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine));
+ if (RT_FAILURE(rc))
+ break;
+ if (strstr(szLine, "MemTotal:") == szLine)
+ {
+ rc = RTStrToUInt64Ex(RTStrStripL(&szLine[9]), &psz, 0, &u64Kb);
+ if (RT_SUCCESS(rc))
+ u64Total = u64Kb * _1K;
+ }
+ else if (strstr(szLine, "MemFree:") == szLine)
+ {
+ rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb);
+ if (RT_SUCCESS(rc))
+ u64Free = u64Kb * _1K;
+ }
+ else if (strstr(szLine, "Buffers:") == szLine)
+ {
+ rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb);
+ if (RT_SUCCESS(rc))
+ u64Buffers = u64Kb * _1K;
+ }
+ else if (strstr(szLine, "Cached:") == szLine)
+ {
+ rc = RTStrToUInt64Ex(RTStrStripL(&szLine[7]), &psz, 0, &u64Kb);
+ if (RT_SUCCESS(rc))
+ u64Cached = u64Kb * _1K;
+ }
+ else if (strstr(szLine, "SwapTotal:") == szLine)
+ {
+ rc = RTStrToUInt64Ex(RTStrStripL(&szLine[10]), &psz, 0, &u64Kb);
+ if (RT_SUCCESS(rc))
+ u64PagedTotal = u64Kb * _1K;
+ }
+ }
+ req.guestStats.u32PhysMemTotal = u64Total / _4K;
+ req.guestStats.u32PhysMemAvail = (u64Free + u64Buffers + u64Cached) / _4K;
+ req.guestStats.u32MemSystemCache = (u64Buffers + u64Cached) / _4K;
+ req.guestStats.u32PageFileSize = u64PagedTotal / _4K;
+ RTStrmClose(pStrm);
+ }
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: memory info not available!\n");
+
+ req.guestStats.u32PageSize = getpagesize();
+ req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL
+ | VBOX_GUEST_STAT_PHYS_MEM_AVAIL
+ | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE
+ | VBOX_GUEST_STAT_PAGE_FILE_SIZE;
+# ifdef VBOX_WITH_MEMBALLOON
+ req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K);
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON;
+# else
+ req.guestStats.u32PhysMemBalloon = 0;
+# endif
+
+
+ /** @todo req.guestStats.u32Threads */
+ /** @todo req.guestStats.u32Processes */
+ /* req.guestStats.u32Handles doesn't make sense here. */
+ /** @todo req.guestStats.u32MemoryLoad */
+ /** @todo req.guestStats.u32MemCommitTotal */
+ /** @todo req.guestStats.u32MemKernelTotal */
+ /** @todo req.guestStats.u32MemKernelPaged, make any sense? = u32MemKernelTotal? */
+ /** @todo req.guestStats.u32MemKernelNonPaged, make any sense? = 0? */
+
+ bool fCpuInfoAvail = false;
+ rc = RTStrmOpen("/proc/stat", "r", &pStrm);
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine));
+ if (RT_FAILURE(rc))
+ break;
+ if ( strstr(szLine, "cpu") == szLine
+ && strlen(szLine) > 3
+ && RT_C_IS_DIGIT(szLine[3]))
+ {
+ uint32_t u32CpuId;
+ rc = RTStrToUInt32Ex(&szLine[3], &psz, 0, &u32CpuId);
+ if (u32CpuId < VMM_MAX_CPU_COUNT)
+ {
+ uint64_t u64User = 0;
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64User);
+
+ uint64_t u64Nice = 0;
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Nice);
+
+ uint64_t u64System = 0;
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64System);
+
+ uint64_t u64Idle = 0;
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Idle);
+
+ uint64_t u64DeltaIdle = u64Idle - g_VMStat.au64LastCpuLoad_Idle[u32CpuId];
+ uint64_t u64DeltaSystem = u64System - g_VMStat.au64LastCpuLoad_Kernel[u32CpuId];
+ uint64_t u64DeltaUser = u64User - g_VMStat.au64LastCpuLoad_User[u32CpuId];
+ uint64_t u64DeltaNice = u64Nice - g_VMStat.au64LastCpuLoad_Nice[u32CpuId];
+
+ uint64_t u64DeltaAll = u64DeltaIdle
+ + u64DeltaSystem
+ + u64DeltaUser
+ + u64DeltaNice;
+ if (u64DeltaAll == 0) /* Prevent division through zero. */
+ u64DeltaAll = 1;
+
+ g_VMStat.au64LastCpuLoad_Idle[u32CpuId] = u64Idle;
+ g_VMStat.au64LastCpuLoad_Kernel[u32CpuId] = u64System;
+ g_VMStat.au64LastCpuLoad_User[u32CpuId] = u64User;
+ g_VMStat.au64LastCpuLoad_Nice[u32CpuId] = u64Nice;
+
+ req.guestStats.u32CpuLoad_Idle = (uint32_t)(u64DeltaIdle * 100 / u64DeltaAll);
+ req.guestStats.u32CpuLoad_Kernel = (uint32_t)(u64DeltaSystem * 100 / u64DeltaAll);
+ req.guestStats.u32CpuLoad_User = (uint32_t)((u64DeltaUser
+ + u64DeltaNice) * 100 / u64DeltaAll);
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE
+ | VBOX_GUEST_STAT_CPU_LOAD_KERNEL
+ | VBOX_GUEST_STAT_CPU_LOAD_USER;
+ req.guestStats.u32CpuId = u32CpuId;
+ fCpuInfoAvail = true;
+ rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", u32CpuId);
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc);
+ }
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPU%u\n", u32CpuId);
+ }
+ }
+ RTStrmClose(pStrm);
+ }
+ if (!fCpuInfoAvail)
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n");
+ rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n");
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc);
+ }
+
+#elif defined(RT_OS_SOLARIS)
+ VMMDevReportGuestStats req;
+ RT_ZERO(req);
+ kstat_ctl_t *pStatKern = kstat_open();
+ if (pStatKern)
+ {
+ /*
+ * Memory statistics.
+ */
+ uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0;
+ int rc = -1;
+ kstat_t *pStatPages = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"system_pages");
+ if (pStatPages)
+ {
+ rc = kstat_read(pStatKern, pStatPages, NULL /* optional-copy-buf */);
+ if (rc != -1)
+ {
+ kstat_named_t *pStat = NULL;
+ pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"pagestotal");
+ if (pStat)
+ u64Total = pStat->value.ul;
+
+ pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"freemem");
+ if (pStat)
+ u64Free = pStat->value.ul;
+ }
+ }
+
+ kstat_t *pStatZFS = kstat_lookup(pStatKern, (char *)"zfs", 0 /* instance */, (char *)"arcstats");
+ if (pStatZFS)
+ {
+ rc = kstat_read(pStatKern, pStatZFS, NULL /* optional-copy-buf */);
+ if (rc != -1)
+ {
+ kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(pStatZFS, (char *)"size");
+ if (pStat)
+ u64Cached = pStat->value.ul;
+ }
+ }
+
+ /*
+ * The vminfo are accumulative counters updated every "N" ticks. Let's get the
+ * number of stat updates so far and use that to divide the swap counter.
+ */
+ kstat_t *pStatInfo = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"sysinfo");
+ if (pStatInfo)
+ {
+ sysinfo_t SysInfo;
+ rc = kstat_read(pStatKern, pStatInfo, &SysInfo);
+ if (rc != -1)
+ {
+ kstat_t *pStatVMInfo = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"vminfo");
+ if (pStatVMInfo)
+ {
+ vminfo_t VMInfo;
+ rc = kstat_read(pStatKern, pStatVMInfo, &VMInfo);
+ if (rc != -1)
+ {
+ Assert(SysInfo.updates != 0);
+ u64PagedTotal = VMInfo.swap_avail / SysInfo.updates;
+ }
+ }
+ }
+ }
+
+ req.guestStats.u32PhysMemTotal = u64Total; /* already in pages */
+ req.guestStats.u32PhysMemAvail = u64Free; /* already in pages */
+ req.guestStats.u32MemSystemCache = u64Cached / _4K;
+ req.guestStats.u32PageFileSize = u64PagedTotal; /* already in pages */
+ /** @todo req.guestStats.u32Threads */
+ /** @todo req.guestStats.u32Processes */
+ /** @todo req.guestStats.u32Handles -- ??? */
+ /** @todo req.guestStats.u32MemoryLoad */
+ /** @todo req.guestStats.u32MemCommitTotal */
+ /** @todo req.guestStats.u32MemKernelTotal */
+ /** @todo req.guestStats.u32MemKernelPaged */
+ /** @todo req.guestStats.u32MemKernelNonPaged */
+ req.guestStats.u32PageSize = getpagesize();
+
+ req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL
+ | VBOX_GUEST_STAT_PHYS_MEM_AVAIL
+ | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE
+ | VBOX_GUEST_STAT_PAGE_FILE_SIZE;
+# ifdef VBOX_WITH_MEMBALLOON
+ req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K);
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON;
+# else
+ req.guestStats.u32PhysMemBalloon = 0;
+# endif
+
+ /*
+ * CPU statistics.
+ */
+ cpu_stat_t StatCPU;
+ RT_ZERO(StatCPU);
+ kstat_t *pStatNode = NULL;
+ uint32_t cCPUs = 0;
+ bool fCpuInfoAvail = false;
+ for (pStatNode = pStatKern->kc_chain; pStatNode != NULL; pStatNode = pStatNode->ks_next)
+ {
+ if (!strcmp(pStatNode->ks_module, "cpu_stat"))
+ {
+ rc = kstat_read(pStatKern, pStatNode, &StatCPU);
+ if (rc == -1)
+ break;
+
+ if (cCPUs < VMM_MAX_CPU_COUNT)
+ {
+ uint64_t u64Idle = StatCPU.cpu_sysinfo.cpu[CPU_IDLE];
+ uint64_t u64User = StatCPU.cpu_sysinfo.cpu[CPU_USER];
+ uint64_t u64System = StatCPU.cpu_sysinfo.cpu[CPU_KERNEL];
+
+ uint64_t u64DeltaIdle = u64Idle - g_VMStat.au64LastCpuLoad_Idle[cCPUs];
+ uint64_t u64DeltaSystem = u64System - g_VMStat.au64LastCpuLoad_Kernel[cCPUs];
+ uint64_t u64DeltaUser = u64User - g_VMStat.au64LastCpuLoad_User[cCPUs];
+
+ uint64_t u64DeltaAll = u64DeltaIdle + u64DeltaSystem + u64DeltaUser;
+ if (u64DeltaAll == 0) /* Prevent division through zero. */
+ u64DeltaAll = 1;
+
+ g_VMStat.au64LastCpuLoad_Idle[cCPUs] = u64Idle;
+ g_VMStat.au64LastCpuLoad_Kernel[cCPUs] = u64System;
+ g_VMStat.au64LastCpuLoad_User[cCPUs] = u64User;
+
+ req.guestStats.u32CpuId = cCPUs;
+ req.guestStats.u32CpuLoad_Idle = (uint32_t)(u64DeltaIdle * 100 / u64DeltaAll);
+ req.guestStats.u32CpuLoad_Kernel = (uint32_t)(u64DeltaSystem * 100 / u64DeltaAll);
+ req.guestStats.u32CpuLoad_User = (uint32_t)(u64DeltaUser * 100 / u64DeltaAll);
+
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE
+ | VBOX_GUEST_STAT_CPU_LOAD_KERNEL
+ | VBOX_GUEST_STAT_CPU_LOAD_USER;
+ fCpuInfoAvail = true;
+ rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", cCPUs);
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc);
+ cCPUs++;
+ }
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPU%u\n", cCPUs);
+ }
+ }
+
+ /*
+ * Report whatever statistics were collected.
+ */
+ if (!fCpuInfoAvail)
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n");
+ rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n");
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc);
+ }
+
+ kstat_close(pStatKern);
+ }
+
+#else
+ /** @todo implement for other platforms. */
+
+#endif
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+DECLCALLBACK(int) vgsvcVMStatsWorker(bool volatile *pfShutdown)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Start monitoring of the stat event change event. */
+ rc = VbglR3CtlFilterMask(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc);
+ return rc;
+ }
+
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Now enter the loop retrieving runtime data continuously.
+ */
+ for (;;)
+ {
+ uint32_t fEvents = 0;
+ RTMSINTERVAL cWaitMillies;
+
+ /* Check if an update interval change is pending. */
+ rc = VbglR3WaitEvent(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0 /* no wait */, &fEvents);
+ if ( RT_SUCCESS(rc)
+ && (fEvents & VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST))
+ VbglR3StatQueryInterval(&g_VMStat.cMsStatInterval);
+
+ if (g_VMStat.cMsStatInterval)
+ {
+ vgsvcVMStatsReport();
+ cWaitMillies = g_VMStat.cMsStatInterval;
+ }
+ else
+ cWaitMillies = 3000;
+
+ /*
+ * Block for a while.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ int rc2 = RTSemEventMultiWait(g_VMStatEvent, cWaitMillies);
+ if (*pfShutdown)
+ break;
+ if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
+ {
+ VGSvcError("vgsvcVMStatsWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
+ rc = rc2;
+ break;
+ }
+ }
+
+ /* Cancel monitoring of the stat event change event. */
+ rc = VbglR3CtlFilterMask(0, VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST);
+ if (RT_FAILURE(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc);
+
+ VGSvcVerbose(3, "VBoxStatsThread: finished statistics change request thread\n");
+ return 0;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcVMStatsStop(void)
+{
+ RTSemEventMultiSignal(g_VMStatEvent);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcVMStatsTerm(void)
+{
+ if (g_VMStatEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_VMStatEvent);
+ g_VMStatEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'vminfo' service description.
+ */
+VBOXSERVICE g_VMStatistics =
+{
+ /* pszName. */
+ "vmstats",
+ /* pszDescription. */
+ "Virtual Machine Statistics",
+ /* pszUsage. */
+ NULL,
+ /* pszOptions. */
+ NULL,
+ /* methods */
+ VGSvcDefaultPreInit,
+ VGSvcDefaultOption,
+ vgsvcVMStatsInit,
+ vgsvcVMStatsWorker,
+ vgsvcVMStatsStop,
+ vgsvcVMStatsTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp
new file mode 100644
index 00000000..ae5dd69b
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp
@@ -0,0 +1,807 @@
+/* $Id: VBoxServiceTimeSync.cpp $ */
+/** @file
+ * VBoxService - Guest Additions TimeSync Service.
+ */
+
+/*
+ * Copyright (C) 2007-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
+ */
+
+
+/** @page pg_vgsvc_timesync VBoxService - The Time Sync Service
+ *
+ * The time sync subservice synchronizes the guest OS walltime with the host.
+ *
+ * The time sync service plays along with the Time Manager (TM) in the VMM
+ * to keep the guest time accurate using the host machine as a reference.
+ * Communication is facilitated by VMMDev. TM will try its best to make sure
+ * all timer ticks get delivered so that there isn't normally any need to
+ * adjust the guest time.
+ *
+ * There are three normal (= acceptable) cases:
+ * -# When the service starts up. This is because ticks and such might
+ * be lost during VM and OS startup. (Need to figure out exactly why!)
+ * -# When the TM is unable to deliver all the ticks and swallows a
+ * backlog of ticks. The threshold for this is configurable with
+ * a default of 60 seconds.
+ * -# The time is adjusted on the host. This can be caused manually by
+ * the user or by some time sync daemon (NTP, LAN server, etc.).
+ *
+ * There are a number of very odd case where adjusting is needed. Here
+ * are some of them:
+ * -# Timer device emulation inaccuracies (like rounding).
+ * -# Inaccuracies in time source VirtualBox uses.
+ * -# The Guest and/or Host OS doesn't perform proper time keeping. This
+ * can come about as a result of OS and/or hardware issues.
+ *
+ * The TM is our source for the host time and will make adjustments for
+ * current timer delivery lag. The simplistic approach taken by TM is to
+ * adjust the host time by the current guest timer delivery lag, meaning that
+ * if the guest is behind 1 second with PIT/RTC/++ ticks, this should be
+ * reflected in the guest wall time as well.
+ *
+ * Now, there is any amount of trouble we can cause by changing the time.
+ * Most applications probably use the wall time when they need to measure
+ * things. A walltime that is being juggled about every so often, even if just
+ * a little bit, could occasionally upset these measurements by for instance
+ * yielding negative results.
+ *
+ * This bottom line here is that the time sync service isn't really supposed
+ * to do anything and will try avoid having to do anything when possible.
+ *
+ * The implementation uses the latency it takes to query host time as the
+ * absolute maximum precision to avoid messing up under timer tick catchup
+ * and/or heavy host/guest load. (Rationale is that a *lot* of stuff may
+ * happen on our way back from ring-3 and TM/VMMDev since we're taking the
+ * route thru the inner EM loop with its force flag processing.)
+ *
+ * But this latency has to be measured from our perspective, which means it
+ * could just as easily come out as 0. (OS/2 and Windows guests only update
+ * the current time when the timer ticks for instance.) The good thing is
+ * that this isn't really a problem since we won't ever do anything unless
+ * the drift is noticeable.
+ *
+ * It now boils down to these three (configuration) factors:
+ * -# g_cMsTimeSyncMinAdjust - The minimum drift we will ever bother with.
+ * -# g_TimeSyncLatencyFactor - The factor we multiply the latency by to
+ * calculate the dynamic minimum adjust factor.
+ * -# g_cMsTimeSyncMaxLatency - When to start discarding the data as utterly
+ * useless and take a rest (someone is too busy to give us good data).
+ * -# g_TimeSyncSetThreshold - The threshold at which we will just set the time
+ * instead of trying to adjust it (milliseconds).
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#else
+# include <unistd.h>
+# include <errno.h>
+# include <time.h>
+# include <sys/time.h>
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <VBox/err.h>
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The timesync interval (milliseconds). */
+static uint32_t g_TimeSyncInterval = 0;
+/**
+ * @see pg_vgsvc_timesync
+ *
+ * @remark OS/2: There is either a 1 second resolution on the DosSetDateTime
+ * API or a bug in my settimeofday implementation. Thus, don't
+ * bother unless there is at least a 1 second drift.
+ */
+#ifdef RT_OS_OS2
+static uint32_t g_cMsTimeSyncMinAdjust = 1000;
+#else
+static uint32_t g_cMsTimeSyncMinAdjust = 100;
+#endif
+/** @see pg_vgsvc_timesync */
+static uint32_t g_TimeSyncLatencyFactor = 8;
+/** @see pg_vgsvc_timesync */
+static uint32_t g_cMsTimeSyncMaxLatency = 250;
+/** @see pg_vgsvc_timesync */
+static uint32_t g_TimeSyncSetThreshold = 20*60*1000;
+/** Whether the next adjustment should just set the time instead of trying to
+ * adjust it. This is used to implement --timesync-set-start.
+ * For purposes of setting the kernel timezone, OS/2 always starts with this. */
+#ifdef RT_OS_OS2
+static bool volatile g_fTimeSyncSetOnStart = true;
+#else
+static bool volatile g_fTimeSyncSetOnStart = false;
+#endif
+/** Whether to set the time when the VM was restored. */
+static bool g_fTimeSyncSetOnRestore = true;
+/** The logging verbosity level.
+ * This uses the global verbosity level by default. */
+static uint32_t g_cTimeSyncVerbosity = 0;
+
+/** Current error count. Used to decide when to bitch and when not to. */
+static uint32_t g_cTimeSyncErrors = 0;
+
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_TimeSyncEvent = NIL_RTSEMEVENTMULTI;
+
+/** The VM session ID. Changes whenever the VM is restored or reset. */
+static uint64_t g_idTimeSyncSession;
+
+#ifdef RT_OS_WINDOWS
+/** Process token. */
+static HANDLE g_hTokenProcess = NULL;
+/** Old token privileges. */
+static TOKEN_PRIVILEGES g_TkOldPrivileges;
+/** Backup values for time adjustment. */
+static DWORD g_dwWinTimeAdjustment;
+static DWORD g_dwWinTimeIncrement;
+static BOOL g_bWinTimeAdjustmentDisabled;
+#endif
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnPreInit}
+ */
+static DECLCALLBACK(int) vgsvcTimeSyncPreInit(void)
+{
+ /* Use global verbosity as default. */
+ g_cTimeSyncVerbosity = g_cVerbosity;
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ /** @todo Merge this function with vgsvcTimeSyncOption() to generalize
+ * the "command line args override guest property values" behavior. */
+
+ /*
+ * Read the service options from the VM's guest properties.
+ * Note that these options can be overridden by the command line options later.
+ */
+ uint32_t uGuestPropSvcClientID;
+ int rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
+ {
+ VGSvcVerbose(0, "VMInfo: Guest property service is not available, skipping\n");
+ rc = VINF_SUCCESS;
+ }
+ else
+ VGSvcError("Failed to connect to the guest property service! Error: %Rrc\n", rc);
+ }
+ else
+ {
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-interval",
+ &g_TimeSyncInterval, 50, UINT32_MAX - 1);
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_NOT_FOUND)
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-min-adjust",
+ &g_cMsTimeSyncMinAdjust, 0, 3600000);
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_NOT_FOUND)
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-latency-factor",
+ &g_TimeSyncLatencyFactor, 1, 1024);
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_NOT_FOUND)
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-max-latency",
+ &g_cMsTimeSyncMaxLatency, 1, 3600000);
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_NOT_FOUND)
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold",
+ &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000 /* a week */);
+
+ if (VbglR3GuestPropExist(uGuestPropSvcClientID,
+ "/VirtualBox/GuestAdd/VBoxService/--timesync-set-start"))
+ g_fTimeSyncSetOnStart = true;
+
+ if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-start"))
+ g_fTimeSyncSetOnStart = false;
+
+
+ if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-on-restore"))
+ g_fTimeSyncSetOnRestore = true;
+
+ if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-on-restore"))
+ g_fTimeSyncSetOnRestore = false;
+
+ uint32_t uValue;
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-verbosity",
+ &uValue, 0 /*uMin*/, 255 /*uMax*/);
+ if (RT_SUCCESS(rc))
+ g_cTimeSyncVerbosity = uValue;
+
+ VbglR3GuestPropDisconnect(uGuestPropSvcClientID);
+ }
+
+ if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */
+ rc = VINF_SUCCESS;
+ return rc;
+#else
+ /* Nothing to do here yet. */
+ return VINF_SUCCESS;
+#endif
+}
+
+
+/**
+ * Displays a verbose message based on the currently
+ * set timesync verbosity level.
+ *
+ * @param iLevel Minimum log level required to display this message.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+static void vgsvcTimeSyncLog(unsigned iLevel, const char *pszFormat, ...)
+{
+ if (iLevel <= g_cTimeSyncVerbosity)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ VGSvcLogV(pszFormat, va);
+ va_end(va);
+ }
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnOption}
+ */
+static DECLCALLBACK(int) vgsvcTimeSyncOption(const char **ppszShort, int argc, char **argv, int *pi)
+{
+ int rc = VINF_SUCCESS;
+ if (ppszShort)
+ rc = -1 ;/* no short options */
+ else if (!strcmp(argv[*pi], "--timesync-interval"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncInterval, 50, UINT32_MAX - 1);
+ else if (!strcmp(argv[*pi], "--timesync-min-adjust"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMinAdjust, 0, 3600000);
+ else if (!strcmp(argv[*pi], "--timesync-latency-factor"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncLatencyFactor, 1, 1024);
+ else if (!strcmp(argv[*pi], "--timesync-max-latency"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMaxLatency, 1, 3600000);
+ else if (!strcmp(argv[*pi], "--timesync-set-threshold"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000); /* a week */
+ else if (!strcmp(argv[*pi], "--timesync-set-start"))
+ g_fTimeSyncSetOnStart = true;
+ else if (!strcmp(argv[*pi], "--timesync-no-set-start"))
+ g_fTimeSyncSetOnStart = false;
+ else if (!strcmp(argv[*pi], "--timesync-set-on-restore"))
+ g_fTimeSyncSetOnRestore = true;
+ else if (!strcmp(argv[*pi], "--timesync-no-set-on-restore"))
+ g_fTimeSyncSetOnRestore = false;
+ else if (!strcmp(argv[*pi], "--timesync-verbosity"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cTimeSyncVerbosity, 0 /*uMin*/, 255 /*uMax*/);
+ else
+ rc = -1;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vgsvcTimeSyncInit(void)
+{
+ /*
+ * If not specified, find the right interval default.
+ * Then create the event sem to block on.
+ */
+ if (!g_TimeSyncInterval)
+ g_TimeSyncInterval = g_DefaultInterval * 1000;
+ if (!g_TimeSyncInterval)
+ g_TimeSyncInterval = 10 * 1000;
+
+ VbglR3GetSessionId(&g_idTimeSyncSession);
+ /* The status code is ignored as this information is not available with VBox < 3.2.10. */
+
+ int rc = RTSemEventMultiCreate(&g_TimeSyncEvent);
+ AssertRC(rc);
+#ifdef RT_OS_WINDOWS
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Adjust privileges of this process so we can make system time adjustments.
+ */
+ if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &g_hTokenProcess))
+ {
+ TOKEN_PRIVILEGES tkPriv;
+ RT_ZERO(tkPriv);
+ tkPriv.PrivilegeCount = 1;
+ tkPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+ if (LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &tkPriv.Privileges[0].Luid))
+ {
+ DWORD cbRet = sizeof(g_TkOldPrivileges);
+ if (AdjustTokenPrivileges(g_hTokenProcess, FALSE, &tkPriv, sizeof(TOKEN_PRIVILEGES), &g_TkOldPrivileges, &cbRet))
+ rc = VINF_SUCCESS;
+ else
+ {
+ DWORD dwErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwErr);
+ VGSvcError("vgsvcTimeSyncInit: Adjusting token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n",
+ dwErr, rc);
+ }
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwErr);
+ VGSvcError("vgsvcTimeSyncInit: Looking up token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n",
+ dwErr, rc);
+ }
+ if (RT_FAILURE(rc))
+ {
+ CloseHandle(g_hTokenProcess);
+ g_hTokenProcess = NULL;
+ }
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwErr);
+ VGSvcError("vgsvcTimeSyncInit: Opening process token (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n",
+ dwErr, rc);
+ g_hTokenProcess = NULL;
+ }
+ }
+
+ if (g_pfnGetSystemTimeAdjustment)
+ {
+ if (g_pfnGetSystemTimeAdjustment(&g_dwWinTimeAdjustment, &g_dwWinTimeIncrement, &g_bWinTimeAdjustmentDisabled))
+ vgsvcTimeSyncLog(0, "vgsvcTimeSyncInit: Initially %ld (100ns) units per %ld (100 ns) units interval, disabled=%d\n",
+ g_dwWinTimeAdjustment, g_dwWinTimeIncrement, g_bWinTimeAdjustmentDisabled ? 1 : 0);
+ else
+ {
+ DWORD dwErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwErr);
+ VGSvcError("vgsvcTimeSyncInit: Could not get time adjustment values! Last error: %ld!\n", dwErr);
+ }
+ }
+#endif /* RT_OS_WINDOWS */
+
+ return rc;
+}
+
+
+/**
+ * Try adjusting the time using adjtime or similar.
+ *
+ * @returns true on success, false on failure.
+ *
+ * @param pDrift The time adjustment.
+ */
+static bool vgsvcTimeSyncAdjust(PCRTTIMESPEC pDrift)
+{
+#ifdef RT_OS_WINDOWS
+/** @todo r=bird: g_hTokenProcess cannot be NULL here.
+ * vgsvcTimeSyncInit will fail and the service will not be started with
+ * it being NULL. vgsvcTimeSyncInit OTOH will *NOT* be called until the
+ * service thread has terminated. If anything
+ * else is the case, there is buggy code somewhere.*/
+ if (g_hTokenProcess == NULL) /* Is the token already closed when shutting down? */
+ return false;
+
+ /* The API appeared in NT 3.50. */
+ if ( !g_pfnSetSystemTimeAdjustment
+ || !g_pfnGetSystemTimeAdjustment)
+ return false;
+
+ DWORD dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwWinTimeIncrement;
+ BOOL fWinTimeAdjustmentDisabled;
+ if (g_pfnGetSystemTimeAdjustment(&dwWinTimeAdjustment, &dwWinTimeIncrement, &fWinTimeAdjustmentDisabled))
+ {
+ DWORD dwDiffMax = g_dwWinTimeAdjustment * 0.50;
+ DWORD dwDiffNew = dwWinTimeAdjustment * 0.10;
+
+ if (RTTimeSpecGetMilli(pDrift) > 0)
+ {
+ dwWinNewTimeAdjustment = dwWinTimeAdjustment + dwDiffNew;
+ if (dwWinNewTimeAdjustment > (g_dwWinTimeAdjustment + dwDiffMax))
+ {
+ dwWinNewTimeAdjustment = g_dwWinTimeAdjustment + dwDiffMax;
+ dwDiffNew = dwDiffMax;
+ }
+ }
+ else
+ {
+ dwWinNewTimeAdjustment = dwWinTimeAdjustment - dwDiffNew;
+ if (dwWinNewTimeAdjustment < (g_dwWinTimeAdjustment - dwDiffMax))
+ {
+ dwWinNewTimeAdjustment = g_dwWinTimeAdjustment - dwDiffMax;
+ dwDiffNew = dwDiffMax;
+ }
+ }
+
+ vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: Drift=%lldms\n", RTTimeSpecGetMilli(pDrift));
+ vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: OrgTA=%ld, CurTA=%ld, NewTA=%ld, DiffNew=%ld, DiffMax=%ld\n",
+ g_dwWinTimeAdjustment, dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwDiffNew, dwDiffMax);
+ if (g_pfnSetSystemTimeAdjustment(dwWinNewTimeAdjustment, FALSE /* Periodic adjustments enabled. */))
+ {
+ g_cTimeSyncErrors = 0;
+ return true;
+ }
+
+ if (g_cTimeSyncErrors++ < 10)
+ VGSvcError("vgsvcTimeSyncAdjust: SetSystemTimeAdjustment failed, error=%u\n", GetLastError());
+ }
+ else if (g_cTimeSyncErrors++ < 10)
+ VGSvcError("vgsvcTimeSyncAdjust: GetSystemTimeAdjustment failed, error=%ld\n", GetLastError());
+
+#elif defined(RT_OS_OS2) || defined(RT_OS_HAIKU)
+ /* No API for doing gradual time adjustments. */
+
+#else /* PORTME */
+ /*
+ * Try using adjtime(), most unix-like systems have this.
+ */
+ struct timeval tv;
+ RTTimeSpecGetTimeval(pDrift, &tv);
+ if (adjtime(&tv, NULL) == 0)
+ {
+ vgsvcTimeSyncLog(1, "vgsvcTimeSyncAdjust: adjtime by %RDtimespec\n", pDrift);
+ g_cTimeSyncErrors = 0;
+ return true;
+ }
+#endif
+
+ /* failed */
+ return false;
+}
+
+
+/**
+ * Cancels any pending time adjustment.
+ *
+ * Called when we've caught up and before calls to vgsvcTimeSyncSet.
+ */
+static void vgsvcTimeSyncCancelAdjust(void)
+{
+#ifdef RT_OS_WINDOWS
+/** @todo r=bird: g_hTokenProcess cannot be NULL here. See argumentation in
+ * vgsvcTimeSyncAdjust. */
+ if (g_hTokenProcess == NULL) /* No process token (anymore)? */
+ return;
+ if (!g_pfnSetSystemTimeAdjustment)
+ return;
+ if (g_pfnSetSystemTimeAdjustment(0, TRUE /* Periodic adjustments disabled. */))
+ vgsvcTimeSyncLog(5, "vgsvcTimeSyncCancelAdjust: Windows Time Adjustment is now disabled.\n");
+ else if (g_cTimeSyncErrors++ < 10)
+ VGSvcError("vgsvcTimeSyncCancelAdjust: SetSystemTimeAdjustment(,disable) failed, error=%u\n", GetLastError());
+#endif /* !RT_OS_WINDOWS */
+}
+
+
+/**
+ * Set the wall clock to compensate for drift.
+ *
+ * @param pDrift The time adjustment.
+ */
+static void vgsvcTimeSyncSet(PCRTTIMESPEC pDrift)
+{
+ /*
+ * Query the current time, adjust it by adding the drift and set it.
+ */
+ RTTIMESPEC NewGuestTime;
+ int rc = RTTimeSet(RTTimeSpecAdd(RTTimeNow(&NewGuestTime), pDrift));
+ if (RT_SUCCESS(rc))
+ {
+ /* Succeeded - reset the error count and log the change. */
+ g_cTimeSyncErrors = 0;
+
+ if (g_cTimeSyncVerbosity >= 1)
+ {
+ char sz[64];
+ RTTIME Time;
+ vgsvcTimeSyncLog(1, "time set to %s\n", RTTimeToString(RTTimeExplode(&Time, &NewGuestTime), sz, sizeof(sz)));
+#ifdef DEBUG
+ RTTIMESPEC Tmp;
+ vgsvcTimeSyncLog(3, " now %s\n", RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&Tmp)), sz, sizeof(sz)));
+#endif
+ }
+ }
+ else if (g_cTimeSyncErrors++ < 10)
+ VGSvcError("vgsvcTimeSyncSet: RTTimeSet(%RDtimespec) failed: %Rrc\n", &NewGuestTime, rc);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+DECLCALLBACK(int) vgsvcTimeSyncWorker(bool volatile *pfShutdown)
+{
+ RTTIME Time;
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Tell the control thread that it can continue spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Initialize the last host and guest times to prevent log message.
+ * We also track whether we set the time in the previous loop.
+ */
+ RTTIMESPEC HostLast;
+ if (RT_FAILURE(VbglR3GetHostTime(&HostLast)))
+ RTTimeSpecSetNano(&HostLast, 0);
+ RTTIMESPEC GuestLast;
+ RTTimeNow(&GuestLast);
+ bool fSetTimeLastLoop = false;
+
+ /*
+ * The Work Loop.
+ */
+ for (;;)
+ {
+ /*
+ * Try to get a reliable time reading.
+ */
+ int cTries = 3;
+ do
+ {
+ /*
+ * Query the session id (first to keep lantency low) and the time.
+ */
+ uint64_t idNewSession = g_idTimeSyncSession;
+ if (g_fTimeSyncSetOnRestore)
+ VbglR3GetSessionId(&idNewSession);
+
+ RTTIMESPEC GuestNow0;
+ RTTimeNow(&GuestNow0);
+
+ RTTIMESPEC HostNow;
+ int rc2 = VbglR3GetHostTime(&HostNow);
+ if (RT_FAILURE(rc2))
+ {
+ if (g_cTimeSyncErrors++ < 10)
+ VGSvcError("vgsvcTimeSyncWorker: VbglR3GetHostTime failed; rc2=%Rrc\n", rc2);
+ break;
+ }
+
+ RTTIMESPEC GuestNow;
+ RTTimeNow(&GuestNow);
+
+ /*
+ * Calc latency and check if it's ok.
+ */
+ RTTIMESPEC GuestElapsed = GuestNow;
+ RTTimeSpecSub(&GuestElapsed, &GuestNow0);
+ if ((uint32_t)RTTimeSpecGetMilli(&GuestElapsed) < g_cMsTimeSyncMaxLatency)
+ {
+ /*
+ * If we were just restored, set the adjustment threshold to zero to force a resync.
+ */
+ uint32_t TimeSyncSetThreshold = g_TimeSyncSetThreshold;
+ if ( g_fTimeSyncSetOnRestore
+ && idNewSession != g_idTimeSyncSession)
+ {
+ vgsvcTimeSyncLog(2, "vgsvcTimeSyncWorker: The VM session ID changed, forcing resync.\n");
+ g_idTimeSyncSession = idNewSession;
+ TimeSyncSetThreshold = 0;
+ }
+
+ /*
+ * Calculate the adjustment threshold and the current drift.
+ */
+ uint32_t MinAdjust = RTTimeSpecGetMilli(&GuestElapsed) * g_TimeSyncLatencyFactor;
+ if (MinAdjust < g_cMsTimeSyncMinAdjust)
+ MinAdjust = g_cMsTimeSyncMinAdjust;
+
+ RTTIMESPEC Drift = HostNow;
+ RTTimeSpecSub(&Drift, &GuestNow);
+ if (RTTimeSpecGetMilli(&Drift) < 0)
+ MinAdjust += g_cMsTimeSyncMinAdjust; /* extra buffer against moving time backwards. */
+
+ RTTIMESPEC AbsDrift = Drift;
+ RTTimeSpecAbsolute(&AbsDrift);
+
+ if (g_cTimeSyncVerbosity >= 4)
+ {
+ char sz1[64];
+ char sz2[64];
+ vgsvcTimeSyncLog(4, "vgsvcTimeSyncWorker: Host: %s (MinAdjust: %RU32 ms), Guest: %s => %RDtimespec drift\n",
+ RTTimeToString(RTTimeExplode(&Time, &HostNow), sz1, sizeof(sz1)), MinAdjust,
+ RTTimeToString(RTTimeExplode(&Time, &GuestNow), sz2, sizeof(sz2)), &Drift);
+ }
+
+ bool fSetTimeInThisLoop = false;
+ uint64_t AbsDriftMilli = RTTimeSpecGetMilli(&AbsDrift);
+ if ( AbsDriftMilli > MinAdjust
+ || g_fTimeSyncSetOnStart)
+ {
+ /*
+ * Ok, the drift is above the threshold.
+ *
+ * Try a gradual adjustment first, if that fails or the drift is
+ * too big, fall back on just setting the time.
+ */
+ if ( AbsDriftMilli > TimeSyncSetThreshold
+ || g_fTimeSyncSetOnStart
+ || !vgsvcTimeSyncAdjust(&Drift))
+ {
+ vgsvcTimeSyncCancelAdjust();
+ vgsvcTimeSyncSet(&Drift);
+ fSetTimeInThisLoop = true;
+ }
+
+ /*
+ * Log radical host time changes.
+ */
+ int64_t cNsHostDelta = RTTimeSpecGetNano(&HostNow) - RTTimeSpecGetNano(&HostLast);
+ if ((uint64_t)RT_ABS(cNsHostDelta) > RT_NS_1HOUR / 2)
+ vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical host time change: %'RI64ns (HostNow=%RDtimespec HostLast=%RDtimespec)\n",
+ cNsHostDelta, &HostNow, &HostLast);
+ }
+ else
+ vgsvcTimeSyncCancelAdjust();
+ HostLast = HostNow;
+
+ /*
+ * Log radical guest time changes (we could be the cause of these, mind).
+ * Note! Right now we don't care about an extra log line after we called
+ * vgsvcTimeSyncSet. fSetTimeLastLoop helps show it though.
+ */
+ int64_t cNsGuestDelta = RTTimeSpecGetNano(&GuestNow) - RTTimeSpecGetNano(&GuestLast);
+ if ((uint64_t)RT_ABS(cNsGuestDelta) > RT_NS_1HOUR / 2)
+ vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical guest time change: %'RI64ns (GuestNow=%RDtimespec GuestLast=%RDtimespec fSetTimeLastLoop=%RTbool)\n",
+ cNsGuestDelta, &GuestNow, &GuestLast, fSetTimeLastLoop);
+ GuestLast = GuestNow;
+ fSetTimeLastLoop = fSetTimeInThisLoop;
+ break;
+ }
+ vgsvcTimeSyncLog(3, "vgsvcTimeSyncWorker: %RDtimespec: latency too high (%RDtimespec, max %ums) sleeping 1s\n",
+ &GuestNow, &GuestElapsed, g_cMsTimeSyncMaxLatency);
+ RTThreadSleep(1000);
+ } while (--cTries > 0);
+
+ /* Clear the set-next/set-start flag. */
+ g_fTimeSyncSetOnStart = false;
+
+ /*
+ * Block for a while.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ int rc2 = RTSemEventMultiWait(g_TimeSyncEvent, g_TimeSyncInterval);
+ if (*pfShutdown)
+ break;
+ if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
+ {
+ VGSvcError("vgsvcTimeSyncWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
+ rc = rc2;
+ break;
+ }
+ }
+
+ vgsvcTimeSyncCancelAdjust();
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcTimeSyncStop(void)
+{
+ if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiSignal(g_TimeSyncEvent);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcTimeSyncTerm(void)
+{
+#ifdef RT_OS_WINDOWS
+ /*
+ * Restore the SE_SYSTEMTIME_NAME token privileges (if init succeeded).
+ */
+ if (g_hTokenProcess)
+ {
+ if (!AdjustTokenPrivileges(g_hTokenProcess, FALSE, &g_TkOldPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
+ {
+ DWORD dwErr = GetLastError();
+ VGSvcError("vgsvcTimeSyncTerm: Restoring token privileges (SE_SYSTEMTIME_NAME) failed with code %u!\n", dwErr);
+ }
+ CloseHandle(g_hTokenProcess);
+ g_hTokenProcess = NULL;
+ }
+#endif /* !RT_OS_WINDOWS */
+
+ if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_TimeSyncEvent);
+ g_TimeSyncEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'timesync' service description.
+ */
+VBOXSERVICE g_TimeSync =
+{
+ /* pszName. */
+ "timesync",
+ /* pszDescription. */
+ "Time synchronization",
+ /* pszUsage. */
+ " [--timesync-interval <ms>] [--timesync-min-adjust <ms>]\n"
+ " [--timesync-latency-factor <x>] [--timesync-max-latency <ms>]\n"
+ " [--timesync-set-threshold <ms>]\n"
+ " [--timesync-set-start|--timesync-no-set-start]\n"
+ " [--timesync-set-on-restore|--timesync-no-set-on-restore]\n"
+ " [--timesync-verbosity <level>]"
+ ,
+ /* pszOptions. */
+ " --timesync-interval Specifies the interval at which to synchronize the\n"
+ " time with the host. The default is 10000 ms.\n"
+ " --timesync-min-adjust The minimum absolute drift value measured in\n"
+ " milliseconds to make adjustments for.\n"
+ " The default is 1000 ms on OS/2 and 100 ms elsewhere.\n"
+ " --timesync-latency-factor\n"
+ " The factor to multiply the time query latency with\n"
+ " to calculate the dynamic minimum adjust time.\n"
+ " The default is 8 times.\n"
+ " --timesync-max-latency The max host timer query latency to accept.\n"
+ " The default is 250 ms.\n"
+ " --timesync-set-threshold\n"
+ " The absolute drift threshold, given as milliseconds,\n"
+ " where to start setting the time instead of trying to\n"
+ " adjust it. The default is 20 min.\n"
+ " --timesync-set-start, --timesync-no-set-start \n"
+ " Set the time when starting the time sync service.\n"
+#ifdef RT_OS_OS2
+ " Default: --timesync-set-start\n"
+#else
+ " Default: --timesync-no-set-start\n"
+#endif
+ " --timesync-set-on-restore, --timesync-no-set-on-restore\n"
+ " Whether to immediately set the time when the VM is\n"
+ " restored or not. Default: --timesync-set-on-restore\n"
+ " --timesync-verbosity Sets the verbosity level. Defaults to service wide\n"
+ " verbosity level.\n"
+ ,
+ /* methods */
+ vgsvcTimeSyncPreInit,
+ vgsvcTimeSyncOption,
+ vgsvcTimeSyncInit,
+ vgsvcTimeSyncWorker,
+ vgsvcTimeSyncStop,
+ vgsvcTimeSyncTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp
new file mode 100644
index 00000000..a4c9dbe5
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp
@@ -0,0 +1,1769 @@
+/* $Id: VBoxServiceToolBox.cpp $ */
+/** @file
+ * VBoxServiceToolbox - Internal (BusyBox-like) toolbox.
+ */
+
+/*
+ * 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 <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/symlink.h>
+
+#ifndef RT_OS_WINDOWS
+# include <sys/stat.h> /* need umask */
+#endif
+
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/version.h>
+
+#include <VBox/GuestHost/GuestControl.h>
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceToolBox.h"
+#include "VBoxServiceUtils.h"
+
+using namespace guestControl;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** Generic option indices for commands. */
+enum
+{
+ VBOXSERVICETOOLBOXOPT_MACHINE_READABLE = 1000,
+ VBOXSERVICETOOLBOXOPT_VERBOSE
+};
+
+/** Options indices for "vbox_cat". */
+typedef enum VBOXSERVICETOOLBOXCATOPT
+{
+ VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED = 1000
+} VBOXSERVICETOOLBOXCATOPT;
+
+/** Flags for "vbox_ls". */
+typedef enum VBOXSERVICETOOLBOXLSFLAG
+{
+ VBOXSERVICETOOLBOXLSFLAG_NONE,
+ VBOXSERVICETOOLBOXLSFLAG_RECURSIVE,
+ VBOXSERVICETOOLBOXLSFLAG_SYMLINKS
+} VBOXSERVICETOOLBOXLSFLAG;
+
+/** Flags for fs object output. */
+typedef enum VBOXSERVICETOOLBOXOUTPUTFLAG
+{
+ VBOXSERVICETOOLBOXOUTPUTFLAG_NONE,
+ VBOXSERVICETOOLBOXOUTPUTFLAG_LONG,
+ VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE
+} VBOXSERVICETOOLBOXOUTPUTFLAG;
+
+/** The size of the directory entry buffer we're using. */
+#define VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE (sizeof(RTDIRENTRYEX) + RTPATH_MAX)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a tool handler function. */
+typedef RTEXITCODE (*PFNHANDLER)(int , char **);
+
+/** Definition for a specific toolbox tool. */
+typedef struct VBOXSERVICETOOLBOXTOOL
+{
+ /** Friendly name of the tool. */
+ const char *pszName;
+ /** Main handler to be invoked to use the tool. */
+ RTEXITCODE (*pfnHandler)(int argc, char **argv);
+ /** Conversion routine to convert the tool's exit code back to an IPRT rc. Optional.
+ *
+ * @todo r=bird: You better revert this, i.e. having pfnHandler return a VBox
+ * status code and have a routine for converting it to RTEXITCODE.
+ * Unless, what you really want to do here is to get a cached status, in
+ * which case you better call it what it is.
+ */
+ int (*pfnExitCodeConvertToRc)(RTEXITCODE rcExit);
+} VBOXSERVICETOOLBOXTOOL;
+/** Pointer to a const tool definition. */
+typedef VBOXSERVICETOOLBOXTOOL const *PCVBOXSERVICETOOLBOXTOOL;
+
+/**
+ * An file/directory entry. Used to cache
+ * file names/paths for later processing.
+ */
+typedef struct VBOXSERVICETOOLBOXPATHENTRY
+{
+ /** Our node. */
+ RTLISTNODE Node;
+ /** Name of the entry. */
+ char *pszName;
+} VBOXSERVICETOOLBOXPATHENTRY, *PVBOXSERVICETOOLBOXPATHENTRY;
+
+/** ID cache entry. */
+typedef struct VGSVCTOOLBOXUIDENTRY
+{
+ /** The identifier name. */
+ uint32_t id;
+ /** Set if UID, clear if GID. */
+ bool fIsUid;
+ /** The name. */
+ char szName[128 - 4 - 1];
+} VGSVCTOOLBOXUIDENTRY;
+typedef VGSVCTOOLBOXUIDENTRY *PVGSVCTOOLBOXUIDENTRY;
+
+
+/** ID cache. */
+typedef struct VGSVCTOOLBOXIDCACHE
+{
+ /** Number of valid cache entries. */
+ uint32_t cEntries;
+ /** The next entry to replace. */
+ uint32_t iNextReplace;
+ /** The cache entries. */
+ VGSVCTOOLBOXUIDENTRY aEntries[16];
+} VGSVCTOOLBOXIDCACHE;
+typedef VGSVCTOOLBOXIDCACHE *PVGSVCTOOLBOXIDCACHE;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static RTEXITCODE vgsvcToolboxCat(int argc, char **argv);
+static RTEXITCODE vgsvcToolboxLs(int argc, char **argv);
+static RTEXITCODE vgsvcToolboxRm(int argc, char **argv);
+static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv);
+static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv);
+static RTEXITCODE vgsvcToolboxStat(int argc, char **argv);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Tool definitions. */
+static VBOXSERVICETOOLBOXTOOL const g_aTools[] =
+{
+ { VBOXSERVICE_TOOL_CAT, vgsvcToolboxCat , NULL },
+ { VBOXSERVICE_TOOL_LS, vgsvcToolboxLs , NULL },
+ { VBOXSERVICE_TOOL_RM, vgsvcToolboxRm , NULL },
+ { VBOXSERVICE_TOOL_MKTEMP, vgsvcToolboxMkTemp, NULL },
+ { VBOXSERVICE_TOOL_MKDIR, vgsvcToolboxMkDir , NULL },
+ { VBOXSERVICE_TOOL_STAT, vgsvcToolboxStat , NULL }
+};
+
+
+
+
+/**
+ * Displays a common header for all help text to stdout.
+ */
+static void vgsvcToolboxShowUsageHeader(void)
+{
+ RTPrintf(VBOX_PRODUCT " Guest Toolbox Version "
+ VBOX_VERSION_STRING "\n"
+ "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+ RTPrintf("Usage:\n\n");
+}
+
+
+/**
+ * Displays a help text to stdout.
+ */
+static void vgsvcToolboxShowUsage(void)
+{
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf(" VBoxService [--use-toolbox] vbox_<command> [<general options>] <parameters>\n\n"
+ "General options:\n\n"
+ " --machinereadable produce all output in machine-readable form\n"
+ " -V print version number and exit\n"
+ "\n"
+ "Commands:\n\n"
+ " vbox_cat [<general options>] <file>...\n"
+ " vbox_ls [<general options>] [--dereference|-L] [-l] [-R]\n"
+ " [--verbose|-v] [<file>...]\n"
+ " vbox_rm [<general options>] [-r|-R] <file>...\n"
+ " vbox_mktemp [<general options>] [--directory|-d] [--mode|-m <mode>]\n"
+ " [--secure|-s] [--tmpdir|-t <path>] <template>\n"
+ " vbox_mkdir [<general options>] [--mode|-m <mode>] [--parents|-p]\n"
+ " [--verbose|-v] <directory>...\n"
+ " vbox_stat [<general options>] [--file-system|-f]\n"
+ " [--dereference|-L] [--terse|-t] [--verbose|-v] <file>...\n"
+ "\n");
+}
+
+
+/**
+ * Displays the program's version number.
+ */
+static void vgsvcToolboxShowVersion(void)
+{
+ RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
+}
+
+
+/**
+ * Initializes the parseable stream(s).
+ *
+ * @return IPRT status code.
+ */
+static int vgsvcToolboxStrmInit(void)
+{
+ /* Set stdout's mode to binary. This is required for outputting all the machine-readable
+ * data correctly. */
+ int rc = RTStrmSetMode(g_pStdOut, true /* Binary mode */, -1 /* Current code set, not changed */);
+ if (RT_FAILURE(rc))
+ RTMsgError("Unable to set stdout to binary mode, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+
+/**
+ * Prints a parseable stream header which contains the actual tool
+ * which was called/used along with its stream version.
+ *
+ * @param pszToolName Name of the tool being used, e.g. "vbt_ls".
+ * @param uVersion Stream version name. Handy for distinguishing
+ * different stream versions later.
+ */
+static void vgsvcToolboxPrintStrmHeader(const char *pszToolName, uint32_t uVersion)
+{
+ AssertPtrReturnVoid(pszToolName);
+ RTPrintf("hdr_id=%s%chdr_ver=%u%c", pszToolName, 0, uVersion, 0);
+}
+
+
+/**
+ * Prints a standardized termination sequence indicating that the
+ * parseable stream just ended.
+ */
+static void vgsvcToolboxPrintStrmTermination()
+{
+ RTPrintf("%c%c%c%c", 0, 0, 0, 0);
+}
+
+
+/**
+ * Parse a file mode string from the command line (currently octal only)
+ * and print an error message and return an error if necessary.
+ */
+static int vgsvcToolboxParseMode(const char *pcszMode, RTFMODE *pfMode)
+{
+ int rc = RTStrToUInt32Ex(pcszMode, NULL, 8 /* Base */, pfMode);
+ if (RT_FAILURE(rc)) /* Only octet based values supported right now! */
+ RTMsgError("Mode flag strings not implemented yet! Use octal numbers instead. (%s)\n", pcszMode);
+ return rc;
+}
+
+
+/**
+ * Destroys a path buffer list.
+ *
+ * @param pList Pointer to list to destroy.
+ */
+static void vgsvcToolboxPathBufDestroy(PRTLISTNODE pList)
+{
+ if (!pList)
+ return;
+
+ PVBOXSERVICETOOLBOXPATHENTRY pEntry, pEntryNext;
+ RTListForEachSafe(pList, pEntry, pEntryNext, VBOXSERVICETOOLBOXPATHENTRY, Node)
+ {
+ RTListNodeRemove(&pEntry->Node);
+
+ RTStrFree(pEntry->pszName);
+ RTMemFree(pEntry);
+ }
+}
+
+
+/**
+ * Adds a path entry (file/directory/whatever) to a given path buffer list.
+ *
+ * @return IPRT status code.
+ * @param pList Pointer to list to add entry to.
+ * @param pszName Name of entry to add.
+ */
+static int vgsvcToolboxPathBufAddPathEntry(PRTLISTNODE pList, const char *pszName)
+{
+ AssertPtrReturn(pList, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ PVBOXSERVICETOOLBOXPATHENTRY pNode = (PVBOXSERVICETOOLBOXPATHENTRY)RTMemAlloc(sizeof(VBOXSERVICETOOLBOXPATHENTRY));
+ if (pNode)
+ {
+ pNode->pszName = RTStrDup(pszName);
+ AssertPtr(pNode->pszName);
+
+ RTListAppend(pList, &pNode->Node);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Performs the actual output operation of "vbox_cat".
+ *
+ * @return IPRT status code.
+ * @param hInput Handle of input file (if any) to use;
+ * else stdin will be used.
+ * @param hOutput Handle of output file (if any) to use;
+ * else stdout will be used.
+ */
+static int vgsvcToolboxCatOutput(RTFILE hInput, RTFILE hOutput)
+{
+ int rc = VINF_SUCCESS;
+ if (hInput == NIL_RTFILE)
+ {
+ rc = RTFileFromNative(&hInput, RTFILE_NATIVE_STDIN);
+ if (RT_FAILURE(rc))
+ RTMsgError("Could not translate input file to native handle, rc=%Rrc\n", rc);
+ }
+
+ if (hOutput == NIL_RTFILE)
+ {
+ rc = RTFileFromNative(&hOutput, RTFILE_NATIVE_STDOUT);
+ if (RT_FAILURE(rc))
+ RTMsgError("Could not translate output file to native handle, rc=%Rrc\n", rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t abBuf[_64K];
+ size_t cbRead;
+ for (;;)
+ {
+ rc = RTFileRead(hInput, abBuf, sizeof(abBuf), &cbRead);
+ if (RT_SUCCESS(rc) && cbRead > 0)
+ {
+ rc = RTFileWrite(hOutput, abBuf, cbRead, NULL /* Try to write all at once! */);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("Error while writing output, rc=%Rrc\n", rc);
+ break;
+ }
+ }
+ else
+ {
+ if (rc == VERR_BROKEN_PIPE)
+ rc = VINF_SUCCESS;
+ else if (RT_FAILURE(rc))
+ RTMsgError("Error while reading input, rc=%Rrc\n", rc);
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+
+/** @todo Document options! */
+static char g_paszCatHelp[] =
+ " VBoxService [--use-toolbox] vbox_cat [<general options>] <file>...\n\n"
+ "Concatenate files, or standard input, to standard output.\n"
+ "\n";
+
+
+/**
+ * Main function for tool "vbox_cat".
+ *
+ * @return RTEXITCODE.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ */
+static RTEXITCODE vgsvcToolboxCat(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* Sorted by short ops. */
+ { "--show-all", 'a', RTGETOPT_REQ_NOTHING },
+ { "--number-nonblank", 'b', RTGETOPT_REQ_NOTHING},
+ { NULL, 'e', RTGETOPT_REQ_NOTHING},
+ { NULL, 'E', RTGETOPT_REQ_NOTHING},
+ { "--flags", 'f', RTGETOPT_REQ_STRING},
+ { "--no-content-indexed", VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED, RTGETOPT_REQ_NOTHING},
+ { "--number", 'n', RTGETOPT_REQ_NOTHING},
+ { "--output", 'o', RTGETOPT_REQ_STRING},
+ { "--squeeze-blank", 's', RTGETOPT_REQ_NOTHING},
+ { NULL, 't', RTGETOPT_REQ_NOTHING},
+ { "--show-tabs", 'T', RTGETOPT_REQ_NOTHING},
+ { NULL, 'u', RTGETOPT_REQ_NOTHING},
+ { "--show-noneprinting", 'v', RTGETOPT_REQ_NOTHING}
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, 0 /*fFlags*/);
+
+ int rc = VINF_SUCCESS;
+
+ const char *pszOutput = NULL;
+ RTFILE hOutput = NIL_RTFILE;
+ uint32_t fFlags = RTFILE_O_CREATE_REPLACE /* Output file flags. */
+ | RTFILE_O_WRITE
+ | RTFILE_O_DENY_WRITE;
+
+ /* Init directory list. */
+ RTLISTANCHOR inputList;
+ RTListInit(&inputList);
+
+ while ( (ch = RTGetOpt(&GetState, &ValueUnion))
+ && RT_SUCCESS(rc))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'a':
+ case 'b':
+ case 'e':
+ case 'E':
+ case 'n':
+ case 's':
+ case 't':
+ case 'T':
+ case 'v':
+ RTMsgError("Sorry, option '%s' is not implemented yet!\n",
+ ValueUnion.pDef->pszLong);
+ rc = VERR_INVALID_PARAMETER;
+ break;
+
+ case 'h':
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf("%s", g_paszCatHelp);
+ return RTEXITCODE_SUCCESS;
+
+ case 'o':
+ pszOutput = ValueUnion.psz;
+ break;
+
+ case 'u':
+ /* Ignored. */
+ break;
+
+ case 'V':
+ vgsvcToolboxShowVersion();
+ return RTEXITCODE_SUCCESS;
+
+ case VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED:
+ fFlags |= RTFILE_O_NOT_CONTENT_INDEXED;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ /* Add file(s) to buffer. This enables processing multiple paths
+ * at once.
+ *
+ * Since the non-options (RTGETOPTINIT_FLAGS_OPTS_FIRST) come last when
+ * processing this loop it's safe to immediately exit on syntax errors
+ * or showing the help text (see above). */
+ rc = vgsvcToolboxPathBufAddPathEntry(&inputList, ValueUnion.psz);
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pszOutput)
+ {
+ rc = RTFileOpen(&hOutput, pszOutput, fFlags);
+ if (RT_FAILURE(rc))
+ RTMsgError("Could not create output file '%s', rc=%Rrc\n", pszOutput, rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Process each input file. */
+ RTFILE hInput = NIL_RTFILE;
+ PVBOXSERVICETOOLBOXPATHENTRY pNodeIt;
+ RTListForEach(&inputList, pNodeIt, VBOXSERVICETOOLBOXPATHENTRY, Node)
+ {
+ rc = RTFileOpen(&hInput, pNodeIt->pszName,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vgsvcToolboxCatOutput(hInput, hOutput);
+ RTFileClose(hInput);
+ }
+ else
+ RTMsgError("Could not open input file '%s': %Rrc\n", pNodeIt->pszName, rc);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /* If no input files were defined, process stdin. */
+ if (RTListNodeIsFirst(&inputList, &inputList))
+ rc = vgsvcToolboxCatOutput(hInput, hOutput);
+ }
+ }
+
+ if (hOutput != NIL_RTFILE)
+ RTFileClose(hOutput);
+ vgsvcToolboxPathBufDestroy(&inputList);
+
+ if (RT_FAILURE(rc))
+ {
+ switch (rc)
+ {
+ case VERR_ACCESS_DENIED:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED;
+
+ case VERR_FILE_NOT_FOUND:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND;
+
+ case VERR_PATH_NOT_FOUND:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND;
+
+ case VERR_SHARING_VIOLATION:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION;
+
+ case VERR_IS_A_DIRECTORY:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_IS_A_DIRECTORY;
+
+ default:
+#ifdef DEBUG_andy
+ AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc));
+#endif
+ break;
+ }
+
+ return RTEXITCODE_FAILURE;
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Resolves the UID to a name as best as we can.
+ *
+ * @returns Read-only name string. Only valid till the next cache call.
+ * @param pIdCache The ID cache.
+ * @param uid The UID to resolve.
+ * @param pszEntry The filename of the UID.
+ * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute.
+ */
+static const char *vgsvcToolboxIdCacheGetUidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTUID uid,
+ const char *pszEntry, const char *pszRelativeTo)
+{
+ /* Check cached entries. */
+ for (uint32_t i = 0; i < pIdCache->cEntries; i++)
+ if ( pIdCache->aEntries[i].id == uid
+ && pIdCache->aEntries[i].fIsUid)
+ return pIdCache->aEntries[i].szName;
+
+ /* Miss. */
+ RTFSOBJINFO ObjInfo;
+ RT_ZERO(ObjInfo); /* shut up msc */
+ int rc;
+ if (!pszRelativeTo)
+ rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
+ else
+ {
+ char szPath[RTPATH_MAX];
+ rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry);
+ if (RT_SUCCESS(rc))
+ rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && ObjInfo.Attr.u.UnixOwner.uid == uid)
+ {
+ uint32_t i = pIdCache->cEntries;
+ if (i < RT_ELEMENTS(pIdCache->aEntries))
+ pIdCache->cEntries = i + 1;
+ else
+ i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries);
+ pIdCache->aEntries[i].id = uid;
+ pIdCache->aEntries[i].fIsUid = true;
+ RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixOwner.szName);
+ return pIdCache->aEntries[i].szName;
+ }
+ return "";
+}
+
+
+/**
+ * Resolves the GID to a name as best as we can.
+ *
+ * @returns Read-only name string. Only valid till the next cache call.
+ * @param pIdCache The ID cache.
+ * @param gid The GID to resolve.
+ * @param pszEntry The filename of the GID.
+ * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute.
+ */
+static const char *vgsvcToolboxIdCacheGetGidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTGID gid,
+ const char *pszEntry, const char *pszRelativeTo)
+{
+ /* Check cached entries. */
+ for (uint32_t i = 0; i < pIdCache->cEntries; i++)
+ if ( pIdCache->aEntries[i].id == gid
+ && !pIdCache->aEntries[i].fIsUid)
+ return pIdCache->aEntries[i].szName;
+
+ /* Miss. */
+ RTFSOBJINFO ObjInfo;
+ RT_ZERO(ObjInfo); /* shut up msc */
+ int rc;
+ if (!pszRelativeTo)
+ rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
+ else
+ {
+ char szPath[RTPATH_MAX];
+ rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry);
+ if (RT_SUCCESS(rc))
+ rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && ObjInfo.Attr.u.UnixGroup.gid == gid)
+ {
+ uint32_t i = pIdCache->cEntries;
+ if (i < RT_ELEMENTS(pIdCache->aEntries))
+ pIdCache->cEntries = i + 1;
+ else
+ i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries);
+ pIdCache->aEntries[i].id = gid;
+ pIdCache->aEntries[i].fIsUid = false;
+ RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixGroup.szName);
+ return pIdCache->aEntries[i].szName;
+ }
+ return "";
+}
+
+
+/**
+ * Prints information (based on given flags) of a file system object (file/directory/...)
+ * to stdout.
+ *
+ * @return IPRT status code.
+ * @param pszName Object name.
+ * @param cchName Length of pszName.
+ * @param fOutputFlags Output / handling flags of type
+ * VBOXSERVICETOOLBOXOUTPUTFLAG.
+ * @param pszRelativeTo What pszName is relative to.
+ * @param pIdCache The ID cache.
+ * @param pObjInfo Pointer to object information.
+ */
+static int vgsvcToolboxPrintFsInfo(const char *pszName, size_t cchName, uint32_t fOutputFlags, const char *pszRelativeTo,
+ PVGSVCTOOLBOXIDCACHE pIdCache, PRTFSOBJINFO pObjInfo)
+{
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertReturn(cchName, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
+
+ RTFMODE fMode = pObjInfo->Attr.fMode;
+ char chFileType;
+ switch (fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FIFO: chFileType = 'f'; break;
+ case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break;
+ case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break;
+ case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break;
+ case RTFS_TYPE_FILE: chFileType = '-'; break;
+ case RTFS_TYPE_SYMLINK: chFileType = 'l'; break;
+ case RTFS_TYPE_SOCKET: chFileType = 's'; break;
+ case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break;
+ default: chFileType = '?'; break;
+ }
+ /** @todo sticy bits++ */
+
+/** @todo r=bird: turns out the host doesn't use or need cname_len, so perhaps we could drop it? */
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_LONG))
+ {
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
+ {
+ RTPrintf("ftype=%c%cnode_id=%RU64%cinode_dev=%RU32%ccname_len=%zu%cname=%s%c",
+ chFileType, 0, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0,
+ (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0, cchName, 0, pszName, 0);
+ RTPrintf("%c%c", 0, 0);
+ }
+ else
+ RTPrintf("%c %#18llx %3zu %s\n", chFileType, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, cchName, pszName);
+ }
+ else
+ {
+ char szTimeBirth[RTTIME_STR_LEN];
+ char szTimeChange[RTTIME_STR_LEN];
+ char szTimeModification[RTTIME_STR_LEN];
+ char szTimeAccess[RTTIME_STR_LEN];
+
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
+ {
+ RTPrintf("ftype=%c%c", chFileType, 0);
+ if (pObjInfo->Attr.u.Unix.INodeId || pObjInfo->Attr.u.Unix.INodeIdDevice)
+ RTPrintf("node_id=%RU64%cinode_dev=%RU32%c", (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0,
+ (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0);
+ RTPrintf("owner_mask=%c%c%c%c",
+ fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
+ fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
+ fMode & RTFS_UNIX_IXUSR ? 'x' : '-', 0);
+ RTPrintf("group_mask=%c%c%c%c",
+ fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
+ fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
+ fMode & RTFS_UNIX_IXGRP ? 'x' : '-', 0);
+ RTPrintf("other_mask=%c%c%c%c",
+ fMode & RTFS_UNIX_IROTH ? 'r' : '-',
+ fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
+ fMode & RTFS_UNIX_IXOTH ? 'x' : '-', 0);
+ /** @todo sticky bits. */
+ RTPrintf("dos_mask=%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c",
+ fMode & RTFS_DOS_READONLY ? 'R' : '-',
+ fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
+ fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
+ fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
+ fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
+ fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
+ fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
+ fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
+ fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
+ fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
+ fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
+ fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
+ fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
+ fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-', 0);
+ RTPrintf("hlinks=%RU32%cst_size=%RI64%calloc=%RI64%c",
+ pObjInfo->Attr.u.Unix.cHardlinks, 0,
+ pObjInfo->cbObject, 0,
+ pObjInfo->cbAllocated, 0);
+ RTPrintf("st_birthtime=%s%cst_ctime=%s%cst_mtime=%s%cst_atime=%s%c",
+ RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), 0,
+ RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), 0,
+ RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), 0,
+ RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)), 0);
+ if (pObjInfo->Attr.u.Unix.uid != NIL_RTUID)
+ RTPrintf("uid=%RU32%cusername=%s%c", pObjInfo->Attr.u.Unix.uid, 0,
+ vgsvcToolboxIdCacheGetUidName(pIdCache, pObjInfo->Attr.u.Unix.uid, pszName, pszRelativeTo), 0);
+ if (pObjInfo->Attr.u.Unix.gid != NIL_RTGID)
+ RTPrintf("gid=%RU32%cgroupname=%s%c", pObjInfo->Attr.u.Unix.gid, 0,
+ vgsvcToolboxIdCacheGetGidName(pIdCache, pObjInfo->Attr.u.Unix.gid, pszName, pszRelativeTo), 0);
+ if ( (RTFS_IS_DEV_BLOCK(pObjInfo->Attr.fMode) || RTFS_IS_DEV_CHAR(pObjInfo->Attr.fMode))
+ && pObjInfo->Attr.u.Unix.Device)
+ RTPrintf("st_rdev=%RU32%c", pObjInfo->Attr.u.Unix.Device, 0);
+ if (pObjInfo->Attr.u.Unix.GenerationId)
+ RTPrintf("st_gen=%RU32%c", pObjInfo->Attr.u.Unix.GenerationId, 0);
+ if (pObjInfo->Attr.u.Unix.fFlags)
+ RTPrintf("st_flags=%RU32%c", pObjInfo->Attr.u.Unix.fFlags, 0);
+ RTPrintf("cname_len=%zu%cname=%s%c", cchName, 0, pszName, 0);
+ RTPrintf("%c%c", 0, 0); /* End of data block. */
+ }
+ else
+ {
+ RTPrintf("%c", chFileType);
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
+ fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
+ fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
+ fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
+ fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IROTH ? 'r' : '-',
+ fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
+ fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
+ RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
+ fMode & RTFS_DOS_READONLY ? 'R' : '-',
+ fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
+ fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
+ fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
+ fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
+ fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
+ fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
+ fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
+ fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
+ fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
+ fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
+ fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
+ fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
+ fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
+ RTPrintf(" %d %4d %4d %10lld %10lld",
+ pObjInfo->Attr.u.Unix.cHardlinks,
+ pObjInfo->Attr.u.Unix.uid,
+ pObjInfo->Attr.u.Unix.gid,
+ pObjInfo->cbObject,
+ pObjInfo->cbAllocated);
+ RTPrintf(" %s %s %s %s",
+ RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)),
+ RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)),
+ RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)),
+ RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) );
+ RTPrintf(" %2zu %s\n", cchName, pszName);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Helper routine for ls tool for handling sub directories.
+ *
+ * @return IPRT status code.
+ * @param pszDir Pointer to the directory buffer.
+ * @param cchDir The length of pszDir in pszDir.
+ * @param pDirEntry Pointer to the directory entry.
+ * @param fFlags Flags of type VBOXSERVICETOOLBOXLSFLAG.
+ * @param fOutputFlags Flags of type VBOXSERVICETOOLBOXOUTPUTFLAG.
+ * @param pIdCache The ID cache.
+ */
+static int vgsvcToolboxLsHandleDirSub(char *pszDir, size_t cchDir, PRTDIRENTRYEX pDirEntry,
+ uint32_t fFlags, uint32_t fOutputFlags, PVGSVCTOOLBOXIDCACHE pIdCache)
+{
+ Assert(cchDir > 0); Assert(pszDir[cchDir] == '\0');
+
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
+ RTPrintf("dname=%s%c", pszDir, 0);
+ else if (fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE)
+ RTPrintf("%s:\n", pszDir);
+
+ /* Make sure we've got some room in the path, to save us extra work further down. */
+ if (cchDir + 3 >= RTPATH_MAX)
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Path too long: '%s'\n", pszDir);
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* Open directory. */
+ RTDIR hDir;
+ int rc = RTDirOpen(&hDir, pszDir);
+ if (RT_FAILURE(rc))
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Failed to open directory '%s', rc=%Rrc\n", pszDir, rc);
+ return rc;
+ }
+
+ /* Ensure we've got a trailing slash (there is space for it see above). */
+ if (!RTPATH_IS_SEP(pszDir[cchDir - 1]))
+ {
+ pszDir[cchDir++] = RTPATH_SLASH;
+ pszDir[cchDir] = '\0';
+ }
+
+ /*
+ * Process the files and subdirs.
+ */
+ for (;;)
+ {
+ /* Get the next directory. */
+ size_t cbDirEntry = VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE;
+ rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Check length. */
+ if (pDirEntry->cbName + cchDir + 3 >= RTPATH_MAX)
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Path too long: '%s' in '%.*s'\n", pDirEntry->szName, cchDir, pszDir);
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_SYMLINK:
+ {
+ if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS))
+ break;
+ RT_FALL_THRU();
+ }
+ case RTFS_TYPE_DIRECTORY:
+ {
+ rc = vgsvcToolboxPrintFsInfo(pDirEntry->szName, pDirEntry->cbName, fOutputFlags, pszDir,
+ pIdCache, &pDirEntry->Info);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ continue;
+
+ if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE))
+ continue;
+
+ memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1);
+ int rc2 = vgsvcToolboxLsHandleDirSub(pszDir, cchDir + pDirEntry->cbName, pDirEntry, fFlags, fOutputFlags, pIdCache);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ rc = vgsvcToolboxPrintFsInfo(pDirEntry->szName, pDirEntry->cbName, fOutputFlags, pszDir,
+ pIdCache, &pDirEntry->Info);
+ break;
+ }
+
+ default:
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Entry '%.*s%s' of mode %#x not supported, skipping",
+ cchDir, pszDir, pDirEntry->szName, pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK);
+ break;
+ }
+ }
+ }
+ if (rc != VERR_NO_MORE_FILES)
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("RTDirReadEx failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir);
+ }
+
+ rc = RTDirClose(hDir);
+ if (RT_FAILURE(rc))
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("RTDirClose failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir);
+ }
+
+ return rc;
+}
+
+/**
+ * Helper routine for ls tool doing the actual parsing and output of
+ * a specified directory.
+ *
+ * @return IPRT status code.
+ * @param pszDir Absolute path to directory to ouptut.
+ * @param fFlags Flags of type VBOXSERVICETOOLBOXLSFLAG.
+ * @param fOutputFlags Flags of type VBOXSERVICETOOLBOXOUTPUTFLAG.
+ * @param pIdCache The ID cache.
+ */
+static int vgsvcToolboxLsHandleDir(const char *pszDir, uint32_t fFlags, uint32_t fOutputFlags, PVGSVCTOOLBOXIDCACHE pIdCache)
+{
+ AssertPtrReturn(pszDir, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pIdCache, VERR_INVALID_PARAMETER);
+
+ char szPath[RTPATH_MAX];
+ int rc = RTPathAbs(pszDir, szPath, sizeof(szPath));
+ if (RT_FAILURE(rc))
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("RTPathAbs failed on '%s': %Rrc\n", pszDir, rc);
+ return rc;
+ }
+
+ union
+ {
+ uint8_t abPadding[VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE];
+ RTDIRENTRYEX DirEntry;
+ } uBuf;
+ return vgsvcToolboxLsHandleDirSub(szPath, strlen(szPath), &uBuf.DirEntry, fFlags, fOutputFlags, pIdCache);
+}
+
+
+/** @todo Document options! */
+static char g_paszLsHelp[] =
+ " VBoxService [--use-toolbox] vbox_ls [<general options>] [option]...\n"
+ " [<file>...]\n\n"
+ "List information about files (the current directory by default).\n\n"
+ "Options:\n\n"
+ " [--dereference|-L]\n"
+ " [-l][-R]\n"
+ " [--verbose|-v]\n"
+ " [<file>...]\n"
+ "\n";
+
+
+/**
+ * Main function for tool "vbox_ls".
+ *
+ * @return RTEXITCODE.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ */
+static RTEXITCODE vgsvcToolboxLs(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
+ { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
+ { NULL, 'l', RTGETOPT_REQ_NOTHING },
+ { NULL, 'R', RTGETOPT_REQ_NOTHING },
+ { "--verbose", VBOXSERVICETOOLBOXOPT_VERBOSE, RTGETOPT_REQ_NOTHING}
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions),
+ 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_INIT);
+
+ bool fVerbose = false;
+ uint32_t fFlags = VBOXSERVICETOOLBOXLSFLAG_NONE;
+ uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_NONE;
+
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'h':
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf("%s", g_paszLsHelp);
+ return RTEXITCODE_SUCCESS;
+
+ case 'L': /* Dereference symlinks. */
+ fFlags |= VBOXSERVICETOOLBOXLSFLAG_SYMLINKS;
+ break;
+
+ case 'l': /* Print long format. */
+ fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_LONG;
+ break;
+
+ case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE:
+ fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE;
+ break;
+
+ case 'R': /* Recursive processing. */
+ fFlags |= VBOXSERVICETOOLBOXLSFLAG_RECURSIVE;
+ break;
+
+ case VBOXSERVICETOOLBOXOPT_VERBOSE:
+ fVerbose = true;
+ break;
+
+ case 'V':
+ vgsvcToolboxShowVersion();
+ return RTEXITCODE_SUCCESS;
+
+ case VINF_GETOPT_NOT_OPTION:
+ Assert(GetState.iNext);
+ GetState.iNext--;
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+
+ /* All flags / options processed? Bail out here.
+ * Processing the file / directory list comes down below. */
+ if (ch == VINF_GETOPT_NOT_OPTION)
+ break;
+ }
+
+ /* Print magic/version. */
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
+ {
+ rc = vgsvcToolboxStrmInit();
+ if (RT_FAILURE(rc))
+ RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc);
+ vgsvcToolboxPrintStrmHeader("vbt_ls", 1 /* Stream version */);
+ }
+
+ VGSVCTOOLBOXIDCACHE IdCache;
+ RT_ZERO(IdCache);
+
+ char szDirCur[RTPATH_MAX];
+ rc = RTPathGetCurrent(szDirCur, sizeof(szDirCur));
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("Getting current directory failed, rc=%Rrc\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ ch = RTGetOpt(&GetState, &ValueUnion);
+ do
+ {
+ char const *pszPath;
+
+ if (ch == 0) /* Use current directory if no element specified. */
+ pszPath = szDirCur;
+ else
+ pszPath = ValueUnion.psz;
+
+ RTFSOBJINFO objInfo;
+ int rc2 = RTPathQueryInfoEx(pszPath, &objInfo,
+ RTFSOBJATTRADD_UNIX,
+ fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc2))
+ {
+ if ( RTFS_IS_FILE(objInfo.Attr.fMode)
+ || ( RTFS_IS_SYMLINK(objInfo.Attr.fMode)
+ && (fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS)))
+ {
+ rc2 = vgsvcToolboxPrintFsInfo(pszPath, strlen(pszPath), fOutputFlags, NULL, &IdCache, &objInfo);
+ if (RT_SUCCESS(rc)) /* Keep initial failing rc. */
+ rc = rc2;
+ }
+ else if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ rc2 = vgsvcToolboxLsHandleDir(pszPath, fFlags, fOutputFlags, &IdCache);
+ if (RT_SUCCESS(rc)) /* Keep initial failing rc. */
+ rc = rc2;
+ }
+ }
+ else
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Cannot access '%s': No such file or directory\n", pszPath);
+ if (RT_SUCCESS(rc))
+ rc = VERR_FILE_NOT_FOUND;
+ /* Do not break here -- process every element in the list
+ * and keep failing rc. */
+ }
+
+ } while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0);
+
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
+ vgsvcToolboxPrintStrmTermination();
+
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/* Try using RTPathRmCmd. */
+static RTEXITCODE vgsvcToolboxRm(int argc, char **argv)
+{
+ return RTPathRmCmd(argc, argv);
+}
+
+
+static char g_paszMkTempHelp[] =
+ " VBoxService [--use-toolbox] vbox_mktemp [<general options>] [<options>]\n"
+ " <template>\n\n"
+ "Create a temporary directory based on the template supplied. The first string\n"
+ "of consecutive 'X' characters in the template will be replaced to form a unique\n"
+ "name for the directory. The template may not contain a path. The default\n"
+ "creation mode is 0600 for files and 0700 for directories. If no path is\n"
+ "specified the default temporary directory will be used.\n"
+ "Options:\n\n"
+ " [--directory|-d] Create a directory instead of a file.\n"
+ " [--mode|-m <mode>] Create the object with mode <mode>.\n"
+ " [--secure|-s] Fail if the object cannot be created securely.\n"
+ " [--tmpdir|-t <path>] Create the object with the absolute path <path>.\n"
+ "\n";
+
+
+/**
+ * Report the result of a vbox_mktemp operation.
+ *
+ * Either errors to stderr (not machine-readable) or everything to stdout as
+ * {name}\0{rc}\0 (machine- readable format). The message may optionally
+ * contain a '%s' for the file name and an %Rrc for the result code in that
+ * order. In future a "verbose" flag may be added, without which nothing will
+ * be output in non-machine- readable mode. Sets prc if rc is a non-success
+ * code.
+ */
+static void toolboxMkTempReport(const char *pcszMessage, const char *pcszFile,
+ bool fActive, int rc, uint32_t fOutputFlags, int *prc)
+{
+ if (!fActive)
+ return;
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ if (RT_SUCCESS(rc))
+ RTPrintf(pcszMessage, pcszFile, rc);
+ else
+ RTMsgError(pcszMessage, pcszFile, rc);
+ else
+ RTPrintf("name=%s%crc=%d%c", pcszFile, 0, rc, 0);
+ if (prc && RT_FAILURE(rc))
+ *prc = rc;
+}
+
+
+/**
+ * Main function for tool "vbox_mktemp".
+ *
+ * @return RTEXITCODE.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ */
+static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE,
+ RTGETOPT_REQ_NOTHING },
+ { "--directory", 'd', RTGETOPT_REQ_NOTHING },
+ { "--mode", 'm', RTGETOPT_REQ_STRING },
+ { "--secure", 's', RTGETOPT_REQ_NOTHING },
+ { "--tmpdir", 't', RTGETOPT_REQ_STRING },
+ };
+
+ enum
+ {
+ /* Isn't that a bit long? s/VBOXSERVICETOOLBOX/VSTB/ ? */
+ /** Create a temporary directory instead of a temporary file. */
+ VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY = RT_BIT_32(0),
+ /** Only create the temporary object if the operation is expected
+ * to be secure. Not guaranteed to be supported on a particular
+ * set-up. */
+ VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE = RT_BIT_32(1)
+ };
+
+ int ch, rc;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_INIT);
+
+ uint32_t fFlags = 0;
+ uint32_t fOutputFlags = 0;
+ int cNonOptions = 0;
+ RTFMODE fMode = 0700;
+ bool fModeSet = false;
+ const char *pcszPath = NULL;
+ const char *pcszTemplate;
+ char szTemplateWithPath[RTPATH_MAX] = "";
+
+ while ( (ch = RTGetOpt(&GetState, &ValueUnion))
+ && RT_SUCCESS(rc))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'h':
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf("%s", g_paszMkTempHelp);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ vgsvcToolboxShowVersion();
+ return RTEXITCODE_SUCCESS;
+
+ case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE:
+ fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE;
+ break;
+
+ case 'd':
+ fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY;
+ break;
+
+ case 'm':
+ rc = vgsvcToolboxParseMode(ValueUnion.psz, &fMode);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_SYNTAX;
+ fModeSet = true;
+#ifndef RT_OS_WINDOWS
+ umask(0); /* RTDirCreate workaround */
+#endif
+ break;
+ case 's':
+ fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE;
+ break;
+
+ case 't':
+ pcszPath = ValueUnion.psz;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ /* RTGetOpt will sort these to the end of the argv vector so
+ * that we will deal with them afterwards. */
+ ++cNonOptions;
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+
+ /* Print magic/version. */
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
+ {
+ rc = vgsvcToolboxStrmInit();
+ if (RT_FAILURE(rc))
+ RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc);
+ vgsvcToolboxPrintStrmHeader("vbt_mktemp", 1 /* Stream version */);
+ }
+
+ if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE && fModeSet)
+ {
+ toolboxMkTempReport("'-s' and '-m' parameters cannot be used together.\n", "",
+ true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_SYNTAX;
+ }
+
+ /* We need exactly one template, containing at least one 'X'. */
+ if (cNonOptions != 1)
+ {
+ toolboxMkTempReport("Please specify exactly one template.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_SYNTAX;
+ }
+ pcszTemplate = argv[argc - 1];
+
+ /* Validate that the template is as IPRT requires (asserted by IPRT). */
+ if ( RTPathHasPath(pcszTemplate)
+ || ( !strstr(pcszTemplate, "XXX")
+ && pcszTemplate[strlen(pcszTemplate) - 1] != 'X'))
+ {
+ toolboxMkTempReport("Template '%s' should contain a file name with no path and at least three consecutive 'X' characters or ending in 'X'.\n",
+ pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_FAILURE;
+ }
+ if (pcszPath && !RTPathStartsWithRoot(pcszPath))
+ {
+ toolboxMkTempReport("Path '%s' should be absolute.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_FAILURE;
+ }
+ if (pcszPath)
+ {
+ rc = RTStrCopy(szTemplateWithPath, sizeof(szTemplateWithPath), pcszPath);
+ if (RT_FAILURE(rc))
+ {
+ toolboxMkTempReport("Path '%s' too long.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_FAILURE;
+ }
+ }
+ else
+ {
+ rc = RTPathTemp(szTemplateWithPath, sizeof(szTemplateWithPath));
+ if (RT_FAILURE(rc))
+ {
+ toolboxMkTempReport("Failed to get the temporary directory.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_FAILURE;
+ }
+ }
+ rc = RTPathAppend(szTemplateWithPath, sizeof(szTemplateWithPath), pcszTemplate);
+ if (RT_FAILURE(rc))
+ {
+ toolboxMkTempReport("Template '%s' too long for path.\n", pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY)
+ {
+ rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE
+ ? RTDirCreateTempSecure(szTemplateWithPath)
+ : RTDirCreateTemp(szTemplateWithPath, fMode);
+ toolboxMkTempReport("Created temporary directory '%s'.\n",
+ szTemplateWithPath, RT_SUCCESS(rc), rc,
+ fOutputFlags, NULL);
+ /* RTDirCreateTemp[Secure] sets the template to "" on failure. */
+ toolboxMkTempReport("The following error occurred while creating a temporary directory from template '%s': %Rrc.\n",
+ pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/);
+ }
+ else
+ {
+ rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE
+ ? RTFileCreateTempSecure(szTemplateWithPath)
+ : RTFileCreateTemp(szTemplateWithPath, fMode);
+ toolboxMkTempReport("Created temporary file '%s'.\n",
+ szTemplateWithPath, RT_SUCCESS(rc), rc,
+ fOutputFlags, NULL);
+ /* RTFileCreateTemp[Secure] sets the template to "" on failure. */
+ toolboxMkTempReport("The following error occurred while creating a temporary file from template '%s': %Rrc.\n",
+ pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/);
+ }
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
+ vgsvcToolboxPrintStrmTermination();
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/** @todo Document options! */
+static char g_paszMkDirHelp[] =
+ " VBoxService [--use-toolbox] vbox_mkdir [<general options>] [<options>]\n"
+ " <directory>...\n\n"
+ "Options:\n\n"
+ " [--mode|-m <mode>] The file mode to set (chmod) on the created\n"
+ " directories. Default: a=rwx & umask.\n"
+ " [--parents|-p] Create parent directories as needed, no\n"
+ " error if the directory already exists.\n"
+ " [--verbose|-v] Display a message for each created directory.\n"
+ "\n";
+
+
+/**
+ * Main function for tool "vbox_mkdir".
+ *
+ * @return RTEXITCODE.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ */
+static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--mode", 'm', RTGETOPT_REQ_STRING },
+ { "--parents", 'p', RTGETOPT_REQ_NOTHING},
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING}
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions),
+ 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_INIT);
+
+ bool fMakeParentDirs = false;
+ bool fVerbose = false;
+ RTFMODE fDirMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO;
+ int cDirsCreated = 0;
+
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'p':
+ fMakeParentDirs = true;
+ break;
+
+ case 'm':
+ rc = vgsvcToolboxParseMode(ValueUnion.psz, &fDirMode);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_SYNTAX;
+#ifndef RT_OS_WINDOWS
+ umask(0); /* RTDirCreate workaround */
+#endif
+ break;
+
+ case 'v':
+ fVerbose = true;
+ break;
+
+ case 'h':
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf("%s", g_paszMkDirHelp);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ vgsvcToolboxShowVersion();
+ return RTEXITCODE_SUCCESS;
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (fMakeParentDirs)
+ /** @todo r=bird: If fVerbose is set, we should also show
+ * which directories that get created, parents as well as
+ * omitting existing final dirs. Annoying, but check any
+ * mkdir implementation (try "mkdir -pv asdf/1/2/3/4"
+ * twice). */
+ rc = RTDirCreateFullPath(ValueUnion.psz, fDirMode);
+ else
+ rc = RTDirCreate(ValueUnion.psz, fDirMode, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Could not create directory '%s': %Rra\n",
+ ValueUnion.psz, rc);
+ if (fVerbose)
+ RTMsgInfo("Created directory '%s', mode %#RTfmode\n", ValueUnion.psz, fDirMode);
+ cDirsCreated++;
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ AssertRC(rc);
+
+ if (cDirsCreated == 0)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No directory argument.");
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/** @todo Document options! */
+static char g_paszStatHelp[] =
+ " VBoxService [--use-toolbox] vbox_stat [<general options>] [<options>]\n"
+ " <file>...\n\n"
+ "Display file or file system status.\n\n"
+ "Options:\n\n"
+ " [--file-system|-f]\n"
+ " [--dereference|-L]\n"
+ " [--terse|-t]\n"
+ " [--verbose|-v]\n"
+ "\n";
+
+
+/**
+ * Main function for tool "vbox_stat".
+ *
+ * @return RTEXITCODE.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ */
+static RTEXITCODE vgsvcToolboxStat(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--file-system", 'f', RTGETOPT_REQ_NOTHING },
+ { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
+ { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
+ { "--terse", 't', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+
+ int rc = VINF_SUCCESS;
+ uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_LONG; /* Use long mode by default. */
+ uint32_t fQueryInfoFlags = RTPATH_F_ON_LINK;
+
+ while ( (ch = RTGetOpt(&GetState, &ValueUnion))
+ && RT_SUCCESS(rc))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'f':
+ RTMsgError("Sorry, option '%s' is not implemented yet!\n", ValueUnion.pDef->pszLong);
+ rc = VERR_INVALID_PARAMETER;
+ break;
+
+ case 'L':
+ fQueryInfoFlags &= ~RTPATH_F_ON_LINK;
+ fQueryInfoFlags |= RTPATH_F_FOLLOW_LINK;
+ break;
+
+ case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE:
+ fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE;
+ break;
+
+ case 'h':
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf("%s", g_paszStatHelp);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ vgsvcToolboxShowVersion();
+ return RTEXITCODE_SUCCESS;
+
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ Assert(GetState.iNext);
+ GetState.iNext--;
+ break;
+ }
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+
+ /* All flags / options processed? Bail out here.
+ * Processing the file / directory list comes down below. */
+ if (ch == VINF_GETOPT_NOT_OPTION)
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
+ {
+ rc = vgsvcToolboxStrmInit();
+ if (RT_FAILURE(rc))
+ RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc);
+ vgsvcToolboxPrintStrmHeader("vbt_stat", 1 /* Stream version */);
+ }
+
+ VGSVCTOOLBOXIDCACHE IdCache;
+ RT_ZERO(IdCache);
+
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ RTFSOBJINFO objInfo;
+ int rc2 = RTPathQueryInfoEx(ValueUnion.psz, &objInfo, RTFSOBJATTRADD_UNIX, fQueryInfoFlags);
+ if (RT_FAILURE(rc2))
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Cannot stat for '%s': %Rrc\n", ValueUnion.psz, rc2);
+ }
+ else
+ rc2 = vgsvcToolboxPrintFsInfo(ValueUnion.psz, strlen(ValueUnion.psz), fOutputFlags, NULL, &IdCache, &objInfo);
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Do not break here -- process every element in the list
+ * and keep (initial) failing rc. */
+ }
+
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
+ vgsvcToolboxPrintStrmTermination();
+
+ /* At this point the overall result (success/failure) should be in rc. */
+ }
+ else
+ RTMsgError("Failed with rc=%Rrc\n", rc);
+
+ if (RT_FAILURE(rc))
+ {
+ switch (rc)
+ {
+ case VERR_ACCESS_DENIED:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_ACCESS_DENIED;
+
+ case VERR_FILE_NOT_FOUND:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_FILE_NOT_FOUND;
+
+ case VERR_PATH_NOT_FOUND:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_PATH_NOT_FOUND;
+
+ case VERR_NET_PATH_NOT_FOUND:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_NET_PATH_NOT_FOUND;
+
+ case VERR_INVALID_NAME:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_INVALID_NAME;
+
+ default:
+#ifdef DEBUG_andy
+ AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc));
+#endif
+ break;
+ }
+
+ return RTEXITCODE_FAILURE;
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Looks up the tool definition entry for the tool give by @a pszTool.
+ *
+ * @returns Pointer to the tool definition. NULL if not found.
+ * @param pszTool The name of the tool.
+ */
+static PCVBOXSERVICETOOLBOXTOOL vgsvcToolboxLookUp(const char *pszTool)
+{
+ AssertPtrReturn(pszTool, NULL);
+
+ /* Do a linear search, since we don't have that much stuff in the table. */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aTools); i++)
+ if (!strcmp(g_aTools[i].pszName, pszTool))
+ return &g_aTools[i];
+
+ return NULL;
+}
+
+
+/**
+ * Converts a tool's exit code back to an IPRT error code.
+ *
+ * @return Converted IPRT status code.
+ * @param pszTool Name of the toolbox tool to convert exit code for.
+ * @param rcExit The tool's exit code to convert.
+ */
+int VGSvcToolboxExitCodeConvertToRc(const char *pszTool, RTEXITCODE rcExit)
+{
+ AssertPtrReturn(pszTool, VERR_INVALID_POINTER);
+
+ PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool);
+ if (pTool)
+ return pTool->pfnExitCodeConvertToRc(rcExit);
+
+ AssertMsgFailed(("Tool '%s' not found\n", pszTool));
+ return VERR_GENERAL_FAILURE; /* Lookup failed, should not happen. */
+}
+
+
+/**
+ * Entry point for internal toolbox.
+ *
+ * @return True if an internal tool was handled, false if not.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ * @param prcExit Where to store the exit code when an
+ * internal toolbox command was handled.
+ */
+bool VGSvcToolboxMain(int argc, char **argv, RTEXITCODE *prcExit)
+{
+
+ /*
+ * Check if the file named in argv[0] is one of the toolbox programs.
+ */
+ AssertReturn(argc > 0, false);
+ const char *pszTool = RTPathFilename(argv[0]);
+ PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool);
+ if (!pTool)
+ {
+ /*
+ * For debugging and testing purposes we also allow toolbox program access
+ * when the first VBoxService argument is --use-toolbox.
+ */
+ if (argc < 2 || strcmp(argv[1], "--use-toolbox"))
+ {
+ /* We must match vgsvcGstCtrlProcessCreateProcess here and claim
+ everything starting with "vbox_". */
+ if (!RTStrStartsWith(pszTool, "vbox_"))
+ return false;
+ RTMsgError("Unknown tool: %s\n", pszTool);
+ *prcExit = RTEXITCODE_SYNTAX;
+ return true;
+ }
+
+ /* No tool specified? Show toolbox help. */
+ if (argc < 3)
+ {
+ RTMsgError("No tool following --use-toolbox\n");
+ *prcExit = RTEXITCODE_SYNTAX;
+ return true;
+ }
+
+ argc -= 2;
+ argv += 2;
+ pszTool = argv[0];
+ pTool = vgsvcToolboxLookUp(pszTool);
+ if (!pTool)
+ {
+ *prcExit = RTEXITCODE_SUCCESS;
+ if ( !strcmp(pszTool, "-V")
+ || !strcmp(pszTool, "version"))
+ vgsvcToolboxShowVersion();
+ else if ( !strcmp(pszTool, "help")
+ || !strcmp(pszTool, "--help")
+ || !strcmp(pszTool, "-h"))
+ vgsvcToolboxShowUsage();
+ else
+ {
+ RTMsgError("Unknown tool: %s\n", pszTool);
+ *prcExit = RTEXITCODE_SYNTAX;
+ }
+ return true;
+ }
+ }
+
+ /*
+ * Invoke the handler.
+ */
+ RTMsgSetProgName("VBoxService/%s", pszTool);
+ AssertPtr(pTool);
+ *prcExit = pTool->pfnHandler(argc, argv);
+
+ return true;
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h
new file mode 100644
index 00000000..e9f1f4ea
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h
@@ -0,0 +1,42 @@
+/* $Id: VBoxServiceToolBox.h $ */
+/** @file
+ * VBoxService - Toolbox header for sharing defines between toolbox binary and VBoxService.
+ */
+
+/*
+ * Copyright (C) 2016-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 GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/GuestHost/GuestControl.h>
+
+RT_C_DECLS_BEGIN
+extern bool VGSvcToolboxMain(int argc, char **argv, RTEXITCODE *prcExit);
+extern int VGSvcToolboxExitCodeConvertToRc(const char *pszTool, RTEXITCODE rcExit);
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp
new file mode 100644
index 00000000..5949dd84
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp
@@ -0,0 +1,324 @@
+/* $Id: VBoxServiceUtils.cpp $ */
+/** @file
+ * VBoxServiceUtils - Some utility functions.
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+# include <iprt/param.h>
+# include <iprt/path.h>
+#endif
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+
+
+#ifdef VBOX_WITH_GUEST_PROPS
+/**
+ * Reads a guest property as a 32-bit value.
+ *
+ * @returns VBox status code, fully bitched.
+ *
+ * @param u32ClientId The HGCM client ID for the guest property session.
+ * @param pszPropName The property name.
+ * @param pu32 Where to store the 32-bit value.
+ *
+ */
+int VGSvcReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max)
+{
+ char *pszValue;
+ int rc = VbglR3GuestPropReadEx(u32ClientId, pszPropName, &pszValue, NULL /* ppszFlags */, NULL /* puTimestamp */);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszNext;
+ rc = RTStrToUInt32Ex(pszValue, &pszNext, 0, pu32);
+ if ( RT_SUCCESS(rc)
+ && (*pu32 < u32Min || *pu32 > u32Max))
+ rc = VGSvcError("The guest property value %s = %RU32 is out of range [%RU32..%RU32].\n",
+ pszPropName, *pu32, u32Min, u32Max);
+ RTStrFree(pszValue);
+ }
+ return rc;
+}
+
+/**
+ * Reads a guest property from the host side.
+ *
+ * @returns IPRT status code, fully bitched.
+ * @param u32ClientId The HGCM client ID for the guest property session.
+ * @param pszPropName The property name.
+ * @param fReadOnly Whether or not this property needs to be read only
+ * by the guest side. Otherwise VERR_ACCESS_DENIED will
+ * be returned.
+ * @param ppszValue Where to return the value. This is always set
+ * to NULL. Free it using RTStrFree().
+ * @param ppszFlags Where to return the value flags. Free it
+ * using RTStrFree(). Optional.
+ * @param puTimestamp Where to return the timestamp. This is only set
+ * on success. Optional.
+ */
+int VGSvcReadHostProp(uint32_t u32ClientId, const char *pszPropName, bool fReadOnly,
+ char **ppszValue, char **ppszFlags, uint64_t *puTimestamp)
+{
+ AssertPtrReturn(ppszValue, VERR_INVALID_PARAMETER);
+
+ char *pszValue = NULL;
+ char *pszFlags = NULL;
+ int rc = VbglR3GuestPropReadEx(u32ClientId, pszPropName, &pszValue, &pszFlags, puTimestamp);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check security bits. */
+ if ( fReadOnly /* Do we except a guest read-only property */
+ && !RTStrStr(pszFlags, "RDONLYGUEST"))
+ {
+ /* If we want a property which is read-only on the guest
+ * and it is *not* marked as such, deny access! */
+ rc = VERR_ACCESS_DENIED;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppszValue = pszValue;
+
+ if (ppszFlags)
+ *ppszFlags = pszFlags;
+ else if (pszFlags)
+ RTStrFree(pszFlags);
+ }
+ else
+ {
+ if (pszValue)
+ RTStrFree(pszValue);
+ if (pszFlags)
+ RTStrFree(pszFlags);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Wrapper around VbglR3GuestPropWriteValue that does value formatting and
+ * logging.
+ *
+ * @returns VBox status code. Errors will be logged.
+ *
+ * @param u32ClientId The HGCM client ID for the guest property session.
+ * @param pszName The property name.
+ * @param pszValueFormat The property format string. If this is NULL then
+ * the property will be deleted (if possible).
+ * @param ... Format arguments.
+ */
+int VGSvcWritePropF(uint32_t u32ClientId, const char *pszName, const char *pszValueFormat, ...)
+{
+ AssertPtr(pszName);
+ int rc;
+ if (pszValueFormat != NULL)
+ {
+ va_list va;
+ va_start(va, pszValueFormat);
+ VGSvcVerbose(3, "Writing guest property '%s' = '%N'\n", pszName, pszValueFormat, &va);
+ va_end(va);
+
+ va_start(va, pszValueFormat);
+ rc = VbglR3GuestPropWriteValueV(u32ClientId, pszName, pszValueFormat, va);
+ va_end(va);
+
+ if (RT_FAILURE(rc))
+ VGSvcError("Error writing guest property '%s' (rc=%Rrc)\n", pszName, rc);
+ }
+ else
+ {
+ VGSvcVerbose(3, "Deleting guest property '%s'\n", pszName);
+ rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, NULL);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error deleting guest property '%s' (rc=%Rrc)\n", pszName, rc);
+ }
+ return rc;
+}
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+#ifdef RT_OS_WINDOWS
+
+/**
+ * Helper for vgsvcUtilGetFileVersion and attempts to read and parse
+ * FileVersion.
+ *
+ * @returns Success indicator.
+ */
+static bool vgsvcUtilGetFileVersionOwn(LPSTR pVerData, uint32_t *puMajor, uint32_t *puMinor,
+ uint32_t *puBuildNumber, uint32_t *puRevisionNumber)
+{
+ UINT cchStrValue = 0;
+ LPTSTR pStrValue = NULL;
+ if (!VerQueryValueA(pVerData, "\\StringFileInfo\\040904b0\\FileVersion", (LPVOID *)&pStrValue, &cchStrValue))
+ return false;
+
+ char *pszNext = pStrValue;
+ int rc = RTStrToUInt32Ex(pszNext, &pszNext, 0, puMajor);
+ AssertReturn(rc == VWRN_TRAILING_CHARS, false);
+ AssertReturn(*pszNext == '.', false);
+
+ rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puMinor);
+ AssertReturn(rc == VWRN_TRAILING_CHARS, false);
+ AssertReturn(*pszNext == '.', false);
+
+ rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puBuildNumber);
+ AssertReturn(rc == VWRN_TRAILING_CHARS, false);
+ AssertReturn(*pszNext == '.', false);
+
+ rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puRevisionNumber);
+ AssertReturn(rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS /*??*/, false);
+
+ return true;
+}
+
+
+/**
+ * Worker for VGSvcUtilWinGetFileVersionString.
+ *
+ * @returns VBox status code.
+ * @param pszFilename ASCII & ANSI & UTF-8 compliant name.
+ * @param puMajor Where to return the major version number.
+ * @param puMinor Where to return the minor version number.
+ * @param puBuildNumber Where to return the build number.
+ * @param puRevisionNumber Where to return the revision number.
+ */
+static int vgsvcUtilGetFileVersion(const char *pszFilename, uint32_t *puMajor, uint32_t *puMinor, uint32_t *puBuildNumber,
+ uint32_t *puRevisionNumber)
+{
+ int rc;
+
+ *puMajor = *puMinor = *puBuildNumber = *puRevisionNumber = 0;
+
+ /*
+ * Get the file version info.
+ */
+ DWORD dwHandleIgnored;
+ DWORD cbVerData = GetFileVersionInfoSizeA(pszFilename, &dwHandleIgnored);
+ if (cbVerData)
+ {
+ LPTSTR pVerData = (LPTSTR)RTMemTmpAllocZ(cbVerData);
+ if (pVerData)
+ {
+ if (GetFileVersionInfoA(pszFilename, dwHandleIgnored, cbVerData, pVerData))
+ {
+ /*
+ * Try query and parse the FileVersion string our selves first
+ * since this will give us the correct revision number when
+ * it goes beyond the range of an uint16_t / WORD.
+ */
+ if (vgsvcUtilGetFileVersionOwn(pVerData, puMajor, puMinor, puBuildNumber, puRevisionNumber))
+ rc = VINF_SUCCESS;
+ else
+ {
+ /* Fall back on VS_FIXEDFILEINFO */
+ UINT cbFileInfoIgnored = 0;
+ VS_FIXEDFILEINFO *pFileInfo = NULL;
+ if (VerQueryValue(pVerData, "\\", (LPVOID *)&pFileInfo, &cbFileInfoIgnored))
+ {
+ *puMajor = HIWORD(pFileInfo->dwFileVersionMS);
+ *puMinor = LOWORD(pFileInfo->dwFileVersionMS);
+ *puBuildNumber = HIWORD(pFileInfo->dwFileVersionLS);
+ *puRevisionNumber = LOWORD(pFileInfo->dwFileVersionLS);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ VGSvcVerbose(3, "No file version value for file '%s' available! (%d / rc=%Rrc)\n",
+ pszFilename, GetLastError(), rc);
+ }
+ }
+ }
+ else
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ VGSvcVerbose(0, "GetFileVersionInfo(%s) -> %u / %Rrc\n", pszFilename, GetLastError(), rc);
+ }
+
+ RTMemTmpFree(pVerData);
+ }
+ else
+ {
+ VGSvcVerbose(0, "Failed to allocate %u byte for file version info for '%s'\n", cbVerData, pszFilename);
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ }
+ else
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ VGSvcVerbose(3, "GetFileVersionInfoSize(%s) -> %u / %Rrc\n", pszFilename, GetLastError(), rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Gets a re-formatted version string from the VS_FIXEDFILEINFO table.
+ *
+ * @returns VBox status code. The output buffer is always valid and the status
+ * code can safely be ignored.
+ *
+ * @param pszPath The base path.
+ * @param pszFilename The filename.
+ * @param pszVersion Where to return the version string.
+ * @param cbVersion The size of the version string buffer. This MUST be
+ * at least 2 bytes!
+ */
+int VGSvcUtilWinGetFileVersionString(const char *pszPath, const char *pszFilename, char *pszVersion, size_t cbVersion)
+{
+ /*
+ * We will ALWAYS return with a valid output buffer.
+ */
+ AssertReturn(cbVersion >= 2, VERR_BUFFER_OVERFLOW);
+ pszVersion[0] = '-';
+ pszVersion[1] = '\0';
+
+ /*
+ * Create the path and query the bits.
+ */
+ char szFullPath[RTPATH_MAX];
+ int rc = RTPathJoin(szFullPath, sizeof(szFullPath), pszPath, pszFilename);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uMajor, uMinor, uBuild, uRev;
+ rc = vgsvcUtilGetFileVersion(szFullPath, &uMajor, &uMinor, &uBuild, &uRev);
+ if (RT_SUCCESS(rc))
+ RTStrPrintf(pszVersion, cbVersion, "%u.%u.%ur%u", uMajor, uMinor, uBuild, uRev);
+ }
+ return rc;
+}
+
+#endif /* RT_OS_WINDOWS */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h
new file mode 100644
index 00000000..8eb4468d
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h
@@ -0,0 +1,49 @@
+/* $Id: VBoxServiceUtils.h $ */
+/** @file
+ * VBoxServiceUtils - Guest Additions Services (Utilities).
+ */
+
+/*
+ * 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 GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBoxServiceInternal.h"
+
+#ifdef VBOX_WITH_GUEST_PROPS
+int VGSvcReadProp(uint32_t u32ClientId, const char *pszPropName, char **ppszValue, char **ppszFlags, uint64_t *puTimestamp);
+int VGSvcReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max);
+int VGSvcReadHostProp(uint32_t u32ClientId, const char *pszPropName, bool fReadOnly, char **ppszValue, char **ppszFlags,
+ uint64_t *puTimestamp);
+int VGSvcWritePropF(uint32_t u32ClientId, const char *pszName, const char *pszValueFormat, ...);
+#endif
+
+#ifdef RT_OS_WINDOWS
+int VGSvcUtilWinGetFileVersionString(const char *pszPath, const char *pszFileName, char *pszVersion, size_t cbVersion);
+#endif
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp
new file mode 100644
index 00000000..8560b6d8
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp
@@ -0,0 +1,1363 @@
+/* $Id: VBoxServiceVMInfo-win.cpp $ */
+/** @file
+ * VBoxService - Virtual Machine Information for the Host, Windows specifics.
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0600
+# undef _WIN32_WINNT
+# define _WIN32_WINNT 0x0600 /* QueryFullProcessImageNameW in recent SDKs. */
+#endif
+#include <iprt/win/windows.h>
+#include <wtsapi32.h> /* For WTS* calls. */
+#include <psapi.h> /* EnumProcesses. */
+#include <Ntsecapi.h> /* Needed for process security information. */
+
+#include <iprt/assert.h>
+#include <iprt/ldr.h>
+#include <iprt/localipc.h>
+#include <iprt/mem.h>
+#include <iprt/once.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/system.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/utf16.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+#include "VBoxServiceVMInfo.h"
+#include "../../WINNT/VBoxTray/VBoxTrayMsg.h" /* For IPC. */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Structure for storing the looked up user information. */
+typedef struct VBOXSERVICEVMINFOUSER
+{
+ WCHAR wszUser[MAX_PATH];
+ WCHAR wszAuthenticationPackage[MAX_PATH];
+ WCHAR wszLogonDomain[MAX_PATH];
+ /** Number of assigned user processes. */
+ ULONG ulNumProcs;
+ /** Last (highest) session ID. This
+ * is needed for distinguishing old session
+ * process counts from new (current) session
+ * ones. */
+ ULONG ulLastSession;
+} VBOXSERVICEVMINFOUSER, *PVBOXSERVICEVMINFOUSER;
+
+/** Structure for the file information lookup. */
+typedef struct VBOXSERVICEVMINFOFILE
+{
+ char *pszFilePath;
+ char *pszFileName;
+} VBOXSERVICEVMINFOFILE, *PVBOXSERVICEVMINFOFILE;
+
+/** Structure for process information lookup. */
+typedef struct VBOXSERVICEVMINFOPROC
+{
+ /** The PID. */
+ DWORD id;
+ /** The SID. */
+ PSID pSid;
+ /** The LUID. */
+ LUID luid;
+ /** Interactive process. */
+ bool fInteractive;
+} VBOXSERVICEVMINFOPROC, *PVBOXSERVICEVMINFOPROC;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static uint32_t vgsvcVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs);
+static bool vgsvcVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER a_pUserInfo, PLUID a_pSession);
+static int vgsvcVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppProc, DWORD *pdwCount);
+static void vgsvcVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs);
+static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static uint32_t s_uDebugGuestPropClientID = 0;
+static uint32_t s_uDebugIter = 0;
+/** Whether to skip the logged-in user detection over RDP or not.
+ * See notes in this section why we might want to skip this. */
+static bool s_fSkipRDPDetection = false;
+
+static RTONCE g_vgsvcWinVmInitOnce = RTONCE_INITIALIZER;
+
+/** @name Secur32.dll imports are dynamically resolved because of NT4.
+ * @{ */
+static decltype(LsaGetLogonSessionData) *g_pfnLsaGetLogonSessionData = NULL;
+static decltype(LsaEnumerateLogonSessions) *g_pfnLsaEnumerateLogonSessions = NULL;
+static decltype(LsaFreeReturnBuffer) *g_pfnLsaFreeReturnBuffer = NULL;
+/** @} */
+
+/** @name WtsApi32.dll imports are dynamically resolved because of NT4.
+ * @{ */
+static decltype(WTSFreeMemory) *g_pfnWTSFreeMemory = NULL;
+static decltype(WTSQuerySessionInformationA) *g_pfnWTSQuerySessionInformationA = NULL;
+/** @} */
+
+/** @name PsApi.dll imports are dynamically resolved because of NT4.
+ * @{ */
+static decltype(EnumProcesses) *g_pfnEnumProcesses = NULL;
+static decltype(GetModuleFileNameExW) *g_pfnGetModuleFileNameExW = NULL;
+/** @} */
+
+/** @name New Kernel32.dll APIs we may use when present.
+ * @{ */
+static decltype(QueryFullProcessImageNameW) *g_pfnQueryFullProcessImageNameW = NULL;
+
+/** @} */
+
+
+/**
+ * An RTOnce callback function.
+ */
+static DECLCALLBACK(int) vgsvcWinVmInfoInitOnce(void *pvIgnored)
+{
+ RT_NOREF1(pvIgnored);
+
+ /* SECUR32 */
+ RTLDRMOD hLdrMod;
+ int rc = RTLdrLoadSystem("secur32.dll", true /*fNoUnload*/, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hLdrMod, "LsaGetLogonSessionData", (void **)&g_pfnLsaGetLogonSessionData);
+ if (RT_SUCCESS(rc))
+ rc = RTLdrGetSymbol(hLdrMod, "LsaEnumerateLogonSessions", (void **)&g_pfnLsaEnumerateLogonSessions);
+ if (RT_SUCCESS(rc))
+ rc = RTLdrGetSymbol(hLdrMod, "LsaFreeReturnBuffer", (void **)&g_pfnLsaFreeReturnBuffer);
+ AssertRC(rc);
+ RTLdrClose(hLdrMod);
+ }
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(1, "Secur32.dll APIs are not available (%Rrc)\n", rc);
+ g_pfnLsaGetLogonSessionData = NULL;
+ g_pfnLsaEnumerateLogonSessions = NULL;
+ g_pfnLsaFreeReturnBuffer = NULL;
+ Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0));
+ }
+
+ /* WTSAPI32 */
+ rc = RTLdrLoadSystem("wtsapi32.dll", true /*fNoUnload*/, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hLdrMod, "WTSFreeMemory", (void **)&g_pfnWTSFreeMemory);
+ if (RT_SUCCESS(rc))
+ rc = RTLdrGetSymbol(hLdrMod, "WTSQuerySessionInformationA", (void **)&g_pfnWTSQuerySessionInformationA);
+ AssertRC(rc);
+ RTLdrClose(hLdrMod);
+ }
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(1, "WtsApi32.dll APIs are not available (%Rrc)\n", rc);
+ g_pfnWTSFreeMemory = NULL;
+ g_pfnWTSQuerySessionInformationA = NULL;
+ Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0));
+ }
+
+ /* PSAPI */
+ rc = RTLdrLoadSystem("psapi.dll", true /*fNoUnload*/, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hLdrMod, "EnumProcesses", (void **)&g_pfnEnumProcesses);
+ if (RT_SUCCESS(rc))
+ rc = RTLdrGetSymbol(hLdrMod, "GetModuleFileNameExW", (void **)&g_pfnGetModuleFileNameExW);
+ AssertRC(rc);
+ RTLdrClose(hLdrMod);
+ }
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(1, "psapi.dll APIs are not available (%Rrc)\n", rc);
+ g_pfnEnumProcesses = NULL;
+ g_pfnGetModuleFileNameExW = NULL;
+ Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0));
+ }
+
+ /* Kernel32: */
+ rc = RTLdrLoadSystem("kernel32.dll", true /*fNoUnload*/, &hLdrMod);
+ AssertRCReturn(rc, rc);
+ rc = RTLdrGetSymbol(hLdrMod, "QueryFullProcessImageNameW", (void **)&g_pfnQueryFullProcessImageNameW);
+ if (RT_FAILURE(rc))
+ {
+ Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(6, 0, 0));
+ g_pfnQueryFullProcessImageNameW = NULL;
+ }
+ RTLdrClose(hLdrMod);
+
+ return VINF_SUCCESS;
+}
+
+
+static bool vgsvcVMInfoSession0Separation(void)
+{
+ return RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0); /* Vista */
+}
+
+
+/**
+ * Retrieves the module name of a given process.
+ *
+ * @return IPRT status code.
+ */
+static int vgsvcVMInfoWinProcessesGetModuleNameW(PVBOXSERVICEVMINFOPROC const pProc, PRTUTF16 *ppszName)
+{
+ *ppszName = NULL;
+ AssertPtrReturn(ppszName, VERR_INVALID_POINTER);
+ AssertPtrReturn(pProc, VERR_INVALID_POINTER);
+ AssertReturn(g_pfnGetModuleFileNameExW || g_pfnQueryFullProcessImageNameW, VERR_NOT_SUPPORTED);
+
+ /*
+ * Open the process.
+ */
+ DWORD dwFlags = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0)) /* Vista and later */
+ dwFlags = PROCESS_QUERY_LIMITED_INFORMATION; /* possible to do on more processes */
+
+ HANDLE hProcess = OpenProcess(dwFlags, FALSE, pProc->id);
+ if (hProcess == NULL)
+ {
+ DWORD dwErr = GetLastError();
+ if (g_cVerbosity)
+ VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr);
+ return RTErrConvertFromWin32(dwErr);
+ }
+
+ /*
+ * Since GetModuleFileNameEx has trouble with cross-bitness stuff (32-bit apps
+ * cannot query 64-bit apps and vice verse) we have to use a different code
+ * path for Vista and up.
+ *
+ * So use QueryFullProcessImageNameW when available (Vista+), fall back on
+ * GetModuleFileNameExW on older windows version (
+ */
+ WCHAR wszName[_1K];
+ DWORD dwLen = _1K;
+ BOOL fRc;
+ if (g_pfnQueryFullProcessImageNameW)
+ fRc = g_pfnQueryFullProcessImageNameW(hProcess, 0 /*PROCESS_NAME_NATIVE*/, wszName, &dwLen);
+ else
+ fRc = g_pfnGetModuleFileNameExW(hProcess, NULL /* Get main executable */, wszName, dwLen);
+
+ int rc;
+ if (fRc)
+ rc = RTUtf16DupEx(ppszName, wszName, 0);
+ else
+ {
+ DWORD dwErr = GetLastError();
+ if (g_cVerbosity > 3)
+ VGSvcError("Unable to retrieve process name for PID=%u, LastError=%Rwc\n", pProc->id, dwErr);
+ rc = RTErrConvertFromWin32(dwErr);
+ }
+
+ CloseHandle(hProcess);
+ return rc;
+}
+
+
+/**
+ * Fills in more data for a process.
+ *
+ * @returns VBox status code.
+ * @param pProc The process structure to fill data into.
+ * @param tkClass The kind of token information to get.
+ */
+static int vgsvcVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pProc, TOKEN_INFORMATION_CLASS tkClass)
+{
+ AssertPtrReturn(pProc, VERR_INVALID_POINTER);
+
+ DWORD dwErr = ERROR_SUCCESS;
+ HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pProc->id);
+ if (h == NULL)
+ {
+ dwErr = GetLastError();
+ if (g_cVerbosity > 4)
+ VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr);
+ return RTErrConvertFromWin32(dwErr);
+ }
+
+ int rc = VINF_SUCCESS;
+ HANDLE hToken;
+ if (OpenProcessToken(h, TOKEN_QUERY, &hToken))
+ {
+ void *pvTokenInfo = NULL;
+ DWORD dwTokenInfoSize;
+ switch (tkClass)
+ {
+ case TokenStatistics:
+ /** @todo r=bird: Someone has been reading too many MSDN examples. You shall
+ * use RTMemAlloc here! There is absolutely not reason for
+ * complicating things uncessarily by using HeapAlloc! */
+ dwTokenInfoSize = sizeof(TOKEN_STATISTICS);
+ pvTokenInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwTokenInfoSize);
+ AssertPtr(pvTokenInfo);
+ break;
+
+ case TokenGroups:
+ dwTokenInfoSize = 0;
+ /* Allocation will follow in a second step. */
+ break;
+
+ case TokenUser:
+ dwTokenInfoSize = 0;
+ /* Allocation will follow in a second step. */
+ break;
+
+ default:
+ VGSvcError("Token class not implemented: %d\n", tkClass);
+ rc = VERR_NOT_IMPLEMENTED;
+ dwTokenInfoSize = 0; /* Shut up MSC. */
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ DWORD dwRetLength;
+ if (!GetTokenInformation(hToken, tkClass, pvTokenInfo, dwTokenInfoSize, &dwRetLength))
+ {
+ dwErr = GetLastError();
+ if (dwErr == ERROR_INSUFFICIENT_BUFFER)
+ {
+ dwErr = ERROR_SUCCESS;
+
+ switch (tkClass)
+ {
+ case TokenGroups:
+ pvTokenInfo = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwRetLength);
+ if (!pvTokenInfo)
+ dwErr = GetLastError();
+ dwTokenInfoSize = dwRetLength;
+ break;
+
+ case TokenUser:
+ pvTokenInfo = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwRetLength);
+ if (!pvTokenInfo)
+ dwErr = GetLastError();
+ dwTokenInfoSize = dwRetLength;
+ break;
+
+ default:
+ AssertMsgFailed(("Re-allocating of token information for token class not implemented\n"));
+ break;
+ }
+
+ if (dwErr == ERROR_SUCCESS)
+ {
+ if (!GetTokenInformation(hToken, tkClass, pvTokenInfo, dwTokenInfoSize, &dwRetLength))
+ dwErr = GetLastError();
+ }
+ }
+ }
+
+ if (dwErr == ERROR_SUCCESS)
+ {
+ rc = VINF_SUCCESS;
+
+ switch (tkClass)
+ {
+ case TokenStatistics:
+ {
+ PTOKEN_STATISTICS pStats = (PTOKEN_STATISTICS)pvTokenInfo;
+ AssertPtr(pStats);
+ memcpy(&pProc->luid, &pStats->AuthenticationId, sizeof(LUID));
+ /** @todo Add more information of TOKEN_STATISTICS as needed. */
+ break;
+ }
+
+ case TokenGroups:
+ {
+ pProc->fInteractive = false;
+
+ SID_IDENTIFIER_AUTHORITY sidAuthNT = SECURITY_NT_AUTHORITY;
+ PSID pSidInteractive = NULL; /* S-1-5-4 */
+ if (!AllocateAndInitializeSid(&sidAuthNT, 1, 4, 0, 0, 0, 0, 0, 0, 0, &pSidInteractive))
+ dwErr = GetLastError();
+
+ PSID pSidLocal = NULL; /* S-1-2-0 */
+ if (dwErr == ERROR_SUCCESS)
+ {
+ SID_IDENTIFIER_AUTHORITY sidAuthLocal = SECURITY_LOCAL_SID_AUTHORITY;
+ if (!AllocateAndInitializeSid(&sidAuthLocal, 1, 0, 0, 0, 0, 0, 0, 0, 0, &pSidLocal))
+ dwErr = GetLastError();
+ }
+
+ if (dwErr == ERROR_SUCCESS)
+ {
+ PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)pvTokenInfo;
+ AssertPtr(pGroups);
+ for (DWORD i = 0; i < pGroups->GroupCount; i++)
+ {
+ if ( EqualSid(pGroups->Groups[i].Sid, pSidInteractive)
+ || EqualSid(pGroups->Groups[i].Sid, pSidLocal)
+ || pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID)
+ {
+ pProc->fInteractive = true;
+ break;
+ }
+ }
+ }
+
+ if (pSidInteractive)
+ FreeSid(pSidInteractive);
+ if (pSidLocal)
+ FreeSid(pSidLocal);
+ break;
+ }
+
+ case TokenUser:
+ {
+ PTOKEN_USER pUser = (PTOKEN_USER)pvTokenInfo;
+ AssertPtr(pUser);
+
+ DWORD dwLength = GetLengthSid(pUser->User.Sid);
+ Assert(dwLength);
+ if (dwLength)
+ {
+ pProc->pSid = (PSID)HeapAlloc(GetProcessHeap(),
+ HEAP_ZERO_MEMORY, dwLength);
+ AssertPtr(pProc->pSid);
+ if (CopySid(dwLength, pProc->pSid, pUser->User.Sid))
+ {
+ if (!IsValidSid(pProc->pSid))
+ dwErr = ERROR_INVALID_NAME;
+ }
+ else
+ dwErr = GetLastError();
+ }
+ else
+ dwErr = ERROR_NO_DATA;
+
+ if (dwErr != ERROR_SUCCESS)
+ {
+ VGSvcError("Error retrieving SID of process PID=%u: %u\n", pProc->id, dwErr);
+ if (pProc->pSid)
+ {
+ HeapFree(GetProcessHeap(), 0 /* Flags */, pProc->pSid);
+ pProc->pSid = NULL;
+ }
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Unhandled token information class\n"));
+ break;
+ }
+ }
+
+ if (pvTokenInfo)
+ HeapFree(GetProcessHeap(), 0 /* Flags */, pvTokenInfo);
+ }
+ CloseHandle(hToken);
+ }
+ else
+ dwErr = GetLastError();
+
+ if (dwErr != ERROR_SUCCESS)
+ {
+ if (g_cVerbosity)
+ VGSvcError("Unable to query token information for PID=%u, error=%u\n", pProc->id, dwErr);
+ rc = RTErrConvertFromWin32(dwErr);
+ }
+
+ CloseHandle(h);
+ return rc;
+}
+
+
+/**
+ * Enumerate all the processes in the system and get the logon user IDs for
+ * them.
+ *
+ * @returns VBox status code.
+ * @param ppaProcs Where to return the process snapshot. This must be
+ * freed by calling vgsvcVMInfoWinProcessesFree.
+ *
+ * @param pcProcs Where to store the returned process count.
+ */
+static int vgsvcVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDWORD pcProcs)
+{
+ AssertPtr(ppaProcs);
+ AssertPtr(pcProcs);
+
+ if (!g_pfnEnumProcesses)
+ return VERR_NOT_SUPPORTED;
+
+ /*
+ * Call EnumProcesses with an increasingly larger buffer until it all fits
+ * or we think something is screwed up.
+ */
+ DWORD cProcesses = 64;
+ PDWORD paPID = NULL;
+ int rc = VINF_SUCCESS;
+ do
+ {
+ /* Allocate / grow the buffer first. */
+ cProcesses *= 2;
+ void *pvNew = RTMemRealloc(paPID, cProcesses * sizeof(DWORD));
+ if (!pvNew)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ paPID = (PDWORD)pvNew;
+
+ /* Query the processes. Not the cbRet == buffer size means there could be more work to be done. */
+ DWORD cbRet;
+ if (!g_pfnEnumProcesses(paPID, cProcesses * sizeof(DWORD), &cbRet))
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ break;
+ }
+ if (cbRet < cProcesses * sizeof(DWORD))
+ {
+ cProcesses = cbRet / sizeof(DWORD);
+ break;
+ }
+ } while (cProcesses <= _32K); /* Should be enough; see: http://blogs.technet.com/markrussinovich/archive/2009/07/08/3261309.aspx */
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate out process structures and fill data into them.
+ * We currently only try lookup their LUID's.
+ */
+ PVBOXSERVICEVMINFOPROC paProcs;
+ paProcs = (PVBOXSERVICEVMINFOPROC)RTMemAllocZ(cProcesses * sizeof(VBOXSERVICEVMINFOPROC));
+ if (paProcs)
+ {
+ for (DWORD i = 0; i < cProcesses; i++)
+ {
+ paProcs[i].id = paPID[i];
+ paProcs[i].pSid = NULL;
+
+ int rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenUser);
+ if (RT_FAILURE(rc2) && g_cVerbosity)
+ VGSvcError("Get token class 'user' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2);
+
+ rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenGroups);
+ if (RT_FAILURE(rc2) && g_cVerbosity)
+ VGSvcError("Get token class 'groups' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2);
+
+ rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenStatistics);
+ if (RT_FAILURE(rc2) && g_cVerbosity)
+ VGSvcError("Get token class 'statistics' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2);
+ }
+
+ /* Save number of processes */
+ if (RT_SUCCESS(rc))
+ {
+ *pcProcs = cProcesses;
+ *ppaProcs = paProcs;
+ }
+ else
+ vgsvcVMInfoWinProcessesFree(cProcesses, paProcs);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ RTMemFree(paPID);
+ return rc;
+}
+
+/**
+ * Frees the process structures returned by
+ * vgsvcVMInfoWinProcessesEnumerate() before.
+ *
+ * @param cProcs Number of processes in paProcs.
+ * @param paProcs The process array.
+ */
+static void vgsvcVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs)
+{
+ for (DWORD i = 0; i < cProcs; i++)
+ if (paProcs[i].pSid)
+ {
+ HeapFree(GetProcessHeap(), 0 /* Flags */, paProcs[i].pSid);
+ paProcs[i].pSid = NULL;
+ }
+ RTMemFree(paProcs);
+}
+
+/**
+ * Determines whether the specified session has processes on the system.
+ *
+ * @returns Number of processes found for a specified session.
+ * @param pSession The current user's SID.
+ * @param paProcs The process snapshot.
+ * @param cProcs The number of processes in the snaphot.
+ * @param puTerminalSession Where to return terminal session number.
+ * Optional.
+ */
+/** @todo r=bird: The 'Has' indicates a predicate function, which this is
+ * not. Predicate functions always returns bool. */
+static uint32_t vgsvcVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs,
+ PULONG puTerminalSession)
+{
+ if (!pSession)
+ {
+ VGSvcVerbose(1, "Session became invalid while enumerating!\n");
+ return 0;
+ }
+ if (!g_pfnLsaGetLogonSessionData)
+ return 0;
+
+ PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
+ NTSTATUS rcNt = g_pfnLsaGetLogonSessionData(pSession, &pSessionData);
+ if (rcNt != STATUS_SUCCESS)
+ {
+ VGSvcError("Could not get logon session data! rcNt=%#x\n", rcNt);
+ return 0;
+ }
+
+ if (!IsValidSid(pSessionData->Sid))
+ {
+ VGSvcError("User SID=%p is not valid\n", pSessionData->Sid);
+ if (pSessionData)
+ g_pfnLsaFreeReturnBuffer(pSessionData);
+ return 0;
+ }
+
+
+ /*
+ * Even if a user seems to be logged in, it could be a stale/orphaned logon
+ * session. So check if we have some processes bound to it by comparing the
+ * session <-> process LUIDs.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cProcessesFound = 0;
+ for (DWORD i = 0; i < cProcs; i++)
+ {
+ PSID pProcSID = paProcs[i].pSid;
+ if ( RT_SUCCESS(rc)
+ && pProcSID
+ && IsValidSid(pProcSID))
+ {
+ if (EqualSid(pSessionData->Sid, paProcs[i].pSid))
+ {
+ if (g_cVerbosity)
+ {
+ PRTUTF16 pszName;
+ int rc2 = vgsvcVMInfoWinProcessesGetModuleNameW(&paProcs[i], &pszName);
+ VGSvcVerbose(4, "Session %RU32: PID=%u (fInt=%RTbool): %ls\n",
+ pSessionData->Session, paProcs[i].id, paProcs[i].fInteractive,
+ RT_SUCCESS(rc2) ? pszName : L"<Unknown>");
+ if (RT_SUCCESS(rc2))
+ RTUtf16Free(pszName);
+ }
+
+ if (paProcs[i].fInteractive)
+ {
+ cProcessesFound++;
+ if (!g_cVerbosity) /* We want a bit more info on higher verbosity. */
+ break;
+ }
+ }
+ }
+ }
+
+ if (puTerminalSession)
+ *puTerminalSession = pSessionData->Session;
+
+ g_pfnLsaFreeReturnBuffer(pSessionData);
+
+ return cProcessesFound;
+}
+
+
+/**
+ * Save and noisy string copy.
+ *
+ * @param pwszDst Destination buffer.
+ * @param cbDst Size in bytes - not WCHAR count!
+ * @param pSrc Source string.
+ * @param pszWhat What this is. For the log.
+ */
+static void vgsvcVMInfoWinSafeCopy(PWCHAR pwszDst, size_t cbDst, LSA_UNICODE_STRING const *pSrc, const char *pszWhat)
+{
+ Assert(RT_ALIGN(cbDst, sizeof(WCHAR)) == cbDst);
+
+ size_t cbCopy = pSrc->Length;
+ if (cbCopy + sizeof(WCHAR) > cbDst)
+ {
+ VGSvcVerbose(0, "%s is too long - %u bytes, buffer %u bytes! It will be truncated.\n", pszWhat, cbCopy, cbDst);
+ cbCopy = cbDst - sizeof(WCHAR);
+ }
+ if (cbCopy)
+ memcpy(pwszDst, pSrc->Buffer, cbCopy);
+ pwszDst[cbCopy / sizeof(WCHAR)] = '\0';
+}
+
+
+/**
+ * Detects whether a user is logged on.
+ *
+ * @returns true if logged in, false if not (or error).
+ * @param pUserInfo Where to return the user information.
+ * @param pSession The session to check.
+ */
+static bool vgsvcVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER pUserInfo, PLUID pSession)
+{
+ AssertPtrReturn(pUserInfo, false);
+ if (!pSession)
+ return false;
+ if ( !g_pfnLsaGetLogonSessionData
+ || !g_pfnLsaNtStatusToWinError)
+ return false;
+
+ PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
+ NTSTATUS rcNt = g_pfnLsaGetLogonSessionData(pSession, &pSessionData);
+ if (rcNt != STATUS_SUCCESS)
+ {
+ ULONG ulError = g_pfnLsaNtStatusToWinError(rcNt);
+ switch (ulError)
+ {
+ case ERROR_NOT_ENOUGH_MEMORY:
+ /* If we don't have enough memory it's hard to judge whether the specified user
+ * is logged in or not, so just assume he/she's not. */
+ VGSvcVerbose(3, "Not enough memory to retrieve logon session data!\n");
+ break;
+
+ case ERROR_NO_SUCH_LOGON_SESSION:
+ /* Skip session data which is not valid anymore because it may have been
+ * already terminated. */
+ break;
+
+ default:
+ VGSvcError("LsaGetLogonSessionData failed with error %u\n", ulError);
+ break;
+ }
+ if (pSessionData)
+ g_pfnLsaFreeReturnBuffer(pSessionData);
+ return false;
+ }
+ if (!pSessionData)
+ {
+ VGSvcError("Invalid logon session data!\n");
+ return false;
+ }
+
+ VGSvcVerbose(3, "Session data: Name=%ls, SessionID=%RU32, LogonID=%d,%u, LogonType=%u\n",
+ pSessionData->UserName.Buffer, pSessionData->Session,
+ pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart, pSessionData->LogonType);
+
+ if (vgsvcVMInfoSession0Separation())
+ {
+ /* Starting at Windows Vista user sessions begin with session 1, so
+ * ignore (stale) session 0 users. */
+ if ( pSessionData->Session == 0
+ /* Also check the logon time. */
+ || pSessionData->LogonTime.QuadPart == 0)
+ {
+ g_pfnLsaFreeReturnBuffer(pSessionData);
+ return false;
+ }
+ }
+
+ /*
+ * Only handle users which can login interactively or logged in
+ * remotely over native RDP.
+ */
+ bool fFoundUser = false;
+ if ( IsValidSid(pSessionData->Sid)
+ && ( (SECURITY_LOGON_TYPE)pSessionData->LogonType == Interactive
+ || (SECURITY_LOGON_TYPE)pSessionData->LogonType == RemoteInteractive
+ /* Note: We also need CachedInteractive in case Windows cached the credentials
+ * or just wants to reuse them! */
+ || (SECURITY_LOGON_TYPE)pSessionData->LogonType == CachedInteractive))
+ {
+ VGSvcVerbose(3, "Session LogonType=%u is supported -- looking up SID + type ...\n", pSessionData->LogonType);
+
+ /*
+ * Copy out relevant data.
+ */
+ vgsvcVMInfoWinSafeCopy(pUserInfo->wszUser, sizeof(pUserInfo->wszUser), &pSessionData->UserName, "User name");
+ vgsvcVMInfoWinSafeCopy(pUserInfo->wszAuthenticationPackage, sizeof(pUserInfo->wszAuthenticationPackage),
+ &pSessionData->AuthenticationPackage, "Authentication pkg name");
+ vgsvcVMInfoWinSafeCopy(pUserInfo->wszLogonDomain, sizeof(pUserInfo->wszLogonDomain),
+ &pSessionData->LogonDomain, "Logon domain name");
+
+ TCHAR szOwnerName[MAX_PATH] = { 0 };
+ DWORD dwOwnerNameSize = sizeof(szOwnerName);
+ TCHAR szDomainName[MAX_PATH] = { 0 };
+ DWORD dwDomainNameSize = sizeof(szDomainName);
+ SID_NAME_USE enmOwnerType = SidTypeInvalid;
+ if (!LookupAccountSid(NULL,
+ pSessionData->Sid,
+ szOwnerName,
+ &dwOwnerNameSize,
+ szDomainName,
+ &dwDomainNameSize,
+ &enmOwnerType))
+ {
+ DWORD dwErr = GetLastError();
+ /*
+ * If a network time-out prevents the function from finding the name or
+ * if a SID that does not have a corresponding account name (such as a
+ * logon SID that identifies a logon session), we get ERROR_NONE_MAPPED
+ * here that we just skip.
+ */
+ if (dwErr != ERROR_NONE_MAPPED)
+ VGSvcError("Failed looking up account info for user=%ls, error=$ld!\n", pUserInfo->wszUser, dwErr);
+ }
+ else
+ {
+ if (enmOwnerType == SidTypeUser) /* Only recognize users; we don't care about the rest! */
+ {
+ VGSvcVerbose(3, "Account User=%ls, Session=%u, LogonID=%d,%u, AuthPkg=%ls, Domain=%ls\n",
+ pUserInfo->wszUser, pSessionData->Session, pSessionData->LogonId.HighPart,
+ pSessionData->LogonId.LowPart, pUserInfo->wszAuthenticationPackage, pUserInfo->wszLogonDomain);
+
+ /* KB970910 (check http://support.microsoft.com/kb/970910 on archive.org)
+ * indicates that WTSQuerySessionInformation may leak memory and return the
+ * wrong status code for WTSApplicationName and WTSInitialProgram queries.
+ *
+ * The system must be low on resources, and presumably some internal operation
+ * must fail because of this, triggering an error handling path that forgets
+ * to free memory and set last error.
+ *
+ * bird 2022-08-26: However, we do not query either of those info items. We
+ * query WTSConnectState, which is a rather simple affair. So, I've
+ * re-enabled the code for all systems that includes the API.
+ */
+ if (!s_fSkipRDPDetection)
+ {
+ /* Skip if we don't have the WTS API. */
+ if (!g_pfnWTSQuerySessionInformationA)
+ s_fSkipRDPDetection = true;
+#if 0 /* bird: see above */
+ /* Skip RDP detection on Windows 2000 and older.
+ For Windows 2000 however we don't have any hotfixes, so just skip the
+ RDP detection in any case. */
+ else if (RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 1, 0)) /* older than XP */
+ s_fSkipRDPDetection = true;
+#endif
+ if (s_fSkipRDPDetection)
+ VGSvcVerbose(0, "Detection of logged-in users via RDP is disabled\n");
+ }
+
+ if (!s_fSkipRDPDetection)
+ {
+ Assert(g_pfnWTSQuerySessionInformationA);
+ Assert(g_pfnWTSFreeMemory);
+
+ /* Detect RDP sessions as well. */
+ LPTSTR pBuffer = NULL;
+ DWORD cbRet = 0;
+ int iState = -1;
+ if (g_pfnWTSQuerySessionInformationA(WTS_CURRENT_SERVER_HANDLE,
+ pSessionData->Session,
+ WTSConnectState,
+ &pBuffer,
+ &cbRet))
+ {
+ if (cbRet)
+ iState = *pBuffer;
+ VGSvcVerbose(3, "Account User=%ls, WTSConnectState=%d (%u)\n", pUserInfo->wszUser, iState, cbRet);
+ if ( iState == WTSActive /* User logged on to WinStation. */
+ || iState == WTSShadow /* Shadowing another WinStation. */
+ || iState == WTSDisconnected) /* WinStation logged on without client. */
+ {
+ /** @todo On Vista and W2K, always "old" user name are still
+ * there. Filter out the old one! */
+ VGSvcVerbose(3, "Account User=%ls using TCS/RDP, state=%d \n", pUserInfo->wszUser, iState);
+ fFoundUser = true;
+ }
+ if (pBuffer)
+ g_pfnWTSFreeMemory(pBuffer);
+ }
+ else
+ {
+ DWORD dwLastErr = GetLastError();
+ switch (dwLastErr)
+ {
+ /*
+ * Terminal services don't run (for example in W2K,
+ * nothing to worry about ...). ... or is on the Vista
+ * fast user switching page!
+ */
+ case ERROR_CTX_WINSTATION_NOT_FOUND:
+ VGSvcVerbose(3, "No WinStation found for user=%ls\n", pUserInfo->wszUser);
+ break;
+
+ default:
+ VGSvcVerbose(3, "Cannot query WTS connection state for user=%ls, error=%u\n",
+ pUserInfo->wszUser, dwLastErr);
+ break;
+ }
+
+ fFoundUser = true;
+ }
+ }
+ }
+ else
+ VGSvcVerbose(3, "SID owner type=%d not handled, skipping\n", enmOwnerType);
+ }
+
+ VGSvcVerbose(3, "Account User=%ls %s logged in\n", pUserInfo->wszUser, fFoundUser ? "is" : "is not");
+ }
+
+ if (fFoundUser)
+ pUserInfo->ulLastSession = pSessionData->Session;
+
+ g_pfnLsaFreeReturnBuffer(pSessionData);
+ return fFoundUser;
+}
+
+
+static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszUser, VERR_INVALID_POINTER);
+ /* pszDomain is optional. */
+
+ char szPipeName[512 + sizeof(VBOXTRAY_IPC_PIPE_PREFIX)];
+ memcpy(szPipeName, VBOXTRAY_IPC_PIPE_PREFIX, sizeof(VBOXTRAY_IPC_PIPE_PREFIX));
+ int rc = RTStrCat(szPipeName, sizeof(szPipeName), pszUser);
+ if (RT_SUCCESS(rc))
+ {
+ bool fReportToHost = false;
+ VBoxGuestUserState userState = VBoxGuestUserState_Unknown;
+
+ RTLOCALIPCSESSION hSession;
+ rc = RTLocalIpcSessionConnect(&hSession, szPipeName, RTLOCALIPC_FLAGS_NATIVE_NAME);
+ if (RT_SUCCESS(rc))
+ {
+ VBOXTRAYIPCHEADER ipcHdr =
+ {
+ /* .uMagic = */ VBOXTRAY_IPC_HDR_MAGIC,
+ /* .uVersion = */ VBOXTRAY_IPC_HDR_VERSION,
+ /* .enmMsgType = */ VBOXTRAYIPCMSGTYPE_USER_LAST_INPUT,
+ /* .cbPayload = */ 0 /* No payload */
+ };
+
+ rc = RTLocalIpcSessionWrite(hSession, &ipcHdr, sizeof(ipcHdr));
+ if (RT_SUCCESS(rc))
+ {
+ VBOXTRAYIPCREPLY_USER_LAST_INPUT_T ipcReply;
+ rc = RTLocalIpcSessionRead(hSession, &ipcReply, sizeof(ipcReply), NULL /* Exact read */);
+ if ( RT_SUCCESS(rc)
+ /* If uLastInput is set to UINT32_MAX VBoxTray was not able to retrieve the
+ * user's last input time. This might happen when running on Windows NT4 or older. */
+ && ipcReply.cSecSinceLastInput != UINT32_MAX)
+ {
+ userState = ipcReply.cSecSinceLastInput * 1000 < g_uVMInfoUserIdleThresholdMS
+ ? VBoxGuestUserState_InUse
+ : VBoxGuestUserState_Idle;
+
+ rc = VGSvcUserUpdateF(pCache, pszUser, pszDomain, "UsageState",
+ userState == VBoxGuestUserState_InUse ? "InUse" : "Idle");
+
+ /*
+ * Note: vboxServiceUserUpdateF can return VINF_NO_CHANGE in case there wasn't anything
+ * to update. So only report the user's status to host when we really got something
+ * new.
+ */
+ fReportToHost = rc == VINF_SUCCESS;
+ VGSvcVerbose(4, "User '%s' (domain '%s') is idle for %RU32, fReportToHost=%RTbool\n",
+ pszUser, pszDomain ? pszDomain : "<None>", ipcReply.cSecSinceLastInput, fReportToHost);
+
+#if 0 /* Do we want to write the idle time as well? */
+ /* Also write the user's current idle time, if there is any. */
+ if (userState == VBoxGuestUserState_Idle)
+ rc = vgsvcUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", "%RU32", ipcReply.cSecSinceLastInput);
+ else
+ rc = vgsvcUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", NULL /* Delete property */);
+
+ if (RT_SUCCESS(rc))
+#endif
+ }
+#ifdef DEBUG
+ else if (RT_SUCCESS(rc) && ipcReply.cSecSinceLastInput == UINT32_MAX)
+ VGSvcVerbose(4, "Last input for user '%s' is not supported, skipping\n", pszUser, rc);
+#endif
+ }
+#ifdef DEBUG
+ VGSvcVerbose(4, "Getting last input for user '%s' ended with rc=%Rrc\n", pszUser, rc);
+#endif
+ int rc2 = RTLocalIpcSessionClose(hSession);
+ if (RT_SUCCESS(rc) && RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ else
+ {
+ switch (rc)
+ {
+ case VERR_FILE_NOT_FOUND:
+ {
+ /* No VBoxTray (or too old version which does not support IPC) running
+ for the given user. Not much we can do then. */
+ VGSvcVerbose(4, "VBoxTray for user '%s' not running (anymore), no last input available\n", pszUser);
+
+ /* Overwrite rc from above. */
+ rc = VGSvcUserUpdateF(pCache, pszUser, pszDomain, "UsageState", "Idle");
+
+ fReportToHost = rc == VINF_SUCCESS;
+ if (fReportToHost)
+ userState = VBoxGuestUserState_Idle;
+ break;
+ }
+
+ default:
+ VGSvcError("Error querying last input for user '%s', rc=%Rrc\n", pszUser, rc);
+ break;
+ }
+ }
+
+ if (fReportToHost)
+ {
+ Assert(userState != VBoxGuestUserState_Unknown);
+ int rc2 = VbglR3GuestUserReportState(pszUser, pszDomain, userState, NULL /* No details */, 0);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Error reporting usage state %d for user '%s' to host, rc=%Rrc\n", userState, pszUser, rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Retrieves the currently logged in users and stores their names along with the
+ * user count.
+ *
+ * @returns VBox status code.
+ * @param pCache Property cache to use for storing some of the lookup
+ * data in between calls.
+ * @param ppszUserList Where to store the user list (separated by commas).
+ * Must be freed with RTStrFree().
+ * @param pcUsersInList Where to store the number of users in the list.
+ */
+int VGSvcVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, char **ppszUserList, uint32_t *pcUsersInList)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszUserList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcUsersInList, VERR_INVALID_POINTER);
+
+ int rc = RTOnce(&g_vgsvcWinVmInitOnce, vgsvcWinVmInfoInitOnce, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!g_pfnLsaEnumerateLogonSessions || !g_pfnEnumProcesses || !g_pfnLsaNtStatusToWinError)
+ return VERR_NOT_SUPPORTED;
+
+ rc = VbglR3GuestPropConnect(&s_uDebugGuestPropClientID);
+ AssertRC(rc);
+
+ char *pszUserList = NULL;
+ uint32_t cUsersInList = 0;
+
+ /* This function can report stale or orphaned interactive logon sessions
+ of already logged off users (especially in Windows 2000). */
+ PLUID paSessions = NULL;
+ ULONG cSessions = 0;
+ NTSTATUS rcNt = g_pfnLsaEnumerateLogonSessions(&cSessions, &paSessions);
+ if (rcNt != STATUS_SUCCESS)
+ {
+ ULONG uError = g_pfnLsaNtStatusToWinError(rcNt);
+ switch (uError)
+ {
+ case ERROR_NOT_ENOUGH_MEMORY:
+ VGSvcError("Not enough memory to enumerate logon sessions!\n");
+ break;
+
+ case ERROR_SHUTDOWN_IN_PROGRESS:
+ /* If we're about to shutdown when we were in the middle of enumerating the logon
+ * sessions, skip the error to not confuse the user with an unnecessary log message. */
+ VGSvcVerbose(3, "Shutdown in progress ...\n");
+ uError = ERROR_SUCCESS;
+ break;
+
+ default:
+ VGSvcError("LsaEnumerate failed with error %RU32\n", uError);
+ break;
+ }
+
+ if (paSessions)
+ g_pfnLsaFreeReturnBuffer(paSessions);
+
+ return RTErrConvertFromWin32(uError);
+ }
+ VGSvcVerbose(3, "Found %u sessions\n", cSessions);
+
+ PVBOXSERVICEVMINFOPROC paProcs;
+ DWORD cProcs;
+ rc = vgsvcVMInfoWinProcessesEnumerate(&paProcs, &cProcs);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MEMORY)
+ VGSvcError("Not enough memory to enumerate processes\n");
+ else
+ VGSvcError("Failed to enumerate processes, rc=%Rrc\n", rc);
+ }
+ else
+ {
+ PVBOXSERVICEVMINFOUSER pUserInfo;
+ pUserInfo = (PVBOXSERVICEVMINFOUSER)RTMemAllocZ(cSessions * sizeof(VBOXSERVICEVMINFOUSER) + 1);
+ if (!pUserInfo)
+ VGSvcError("Not enough memory to store enumerated users!\n");
+ else
+ {
+ ULONG cUniqueUsers = 0;
+
+ /*
+ * Note: The cSessions loop variable does *not* correlate with
+ * the Windows session ID!
+ */
+ for (ULONG i = 0; i < cSessions; i++)
+ {
+ VGSvcVerbose(3, "Handling session %RU32 (of %RU32)\n", i + 1, cSessions);
+
+ VBOXSERVICEVMINFOUSER userSession;
+ if (vgsvcVMInfoWinIsLoggedIn(&userSession, &paSessions[i]))
+ {
+ VGSvcVerbose(4, "Handling user=%ls, domain=%ls, package=%ls, session=%RU32\n",
+ userSession.wszUser, userSession.wszLogonDomain, userSession.wszAuthenticationPackage,
+ userSession.ulLastSession);
+
+ /* Retrieve assigned processes of current session. */
+ uint32_t cCurSessionProcs = vgsvcVMInfoWinSessionHasProcesses(&paSessions[i], paProcs, cProcs,
+ NULL /* Terminal session ID */);
+ /* Don't return here when current session does not have assigned processes
+ * anymore -- in that case we have to search through the unique users list below
+ * and see if got a stale user/session entry. */
+
+ if (g_cVerbosity > 3)
+ {
+ char szDebugSessionPath[255];
+ RTStrPrintf(szDebugSessionPath, sizeof(szDebugSessionPath),
+ "/VirtualBox/GuestInfo/Debug/LSA/Session/%RU32", userSession.ulLastSession);
+ VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugSessionPath,
+ "#%RU32: cSessionProcs=%RU32 (of %RU32 procs total)",
+ s_uDebugIter, cCurSessionProcs, cProcs);
+ }
+
+ bool fFoundUser = false;
+ for (ULONG a = 0; a < cUniqueUsers; a++)
+ {
+ PVBOXSERVICEVMINFOUSER pCurUser = &pUserInfo[a];
+ AssertPtr(pCurUser);
+
+ if ( !RTUtf16Cmp(userSession.wszUser, pCurUser->wszUser)
+ && !RTUtf16Cmp(userSession.wszLogonDomain, pCurUser->wszLogonDomain)
+ && !RTUtf16Cmp(userSession.wszAuthenticationPackage, pCurUser->wszAuthenticationPackage))
+ {
+ /*
+ * Only respect the highest session for the current user.
+ */
+ if (userSession.ulLastSession > pCurUser->ulLastSession)
+ {
+ VGSvcVerbose(4, "Updating user=%ls to %u processes (last used session: %RU32)\n",
+ pCurUser->wszUser, cCurSessionProcs, userSession.ulLastSession);
+
+ if (!cCurSessionProcs)
+ VGSvcVerbose(3, "Stale session for user=%ls detected! Processes: %RU32 -> %RU32, Session: %RU32 -> %RU32\n",
+ pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs,
+ pCurUser->ulLastSession, userSession.ulLastSession);
+
+ pCurUser->ulNumProcs = cCurSessionProcs;
+ pCurUser->ulLastSession = userSession.ulLastSession;
+ }
+ /* There can be multiple session objects using the same session ID for the
+ * current user -- so when we got the same session again just add the found
+ * processes to it. */
+ else if (pCurUser->ulLastSession == userSession.ulLastSession)
+ {
+ VGSvcVerbose(4, "Updating processes for user=%ls (old procs=%RU32, new procs=%RU32, session=%RU32)\n",
+ pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs, pCurUser->ulLastSession);
+
+ pCurUser->ulNumProcs = cCurSessionProcs;
+ }
+
+ fFoundUser = true;
+ break;
+ }
+ }
+
+ if (!fFoundUser)
+ {
+ VGSvcVerbose(4, "Adding new user=%ls (session=%RU32) with %RU32 processes\n",
+ userSession.wszUser, userSession.ulLastSession, cCurSessionProcs);
+
+ memcpy(&pUserInfo[cUniqueUsers], &userSession, sizeof(VBOXSERVICEVMINFOUSER));
+ pUserInfo[cUniqueUsers].ulNumProcs = cCurSessionProcs;
+ cUniqueUsers++;
+ Assert(cUniqueUsers <= cSessions);
+ }
+ }
+ }
+
+ if (g_cVerbosity > 3)
+ VGSvcWritePropF(s_uDebugGuestPropClientID, "/VirtualBox/GuestInfo/Debug/LSA",
+ "#%RU32: cSessions=%RU32, cProcs=%RU32, cUniqueUsers=%RU32",
+ s_uDebugIter, cSessions, cProcs, cUniqueUsers);
+
+ VGSvcVerbose(3, "Found %u unique logged-in user(s)\n", cUniqueUsers);
+
+ for (ULONG i = 0; i < cUniqueUsers; i++)
+ {
+ if (g_cVerbosity > 3)
+ {
+ char szDebugUserPath[255]; RTStrPrintf(szDebugUserPath, sizeof(szDebugUserPath), "/VirtualBox/GuestInfo/Debug/LSA/User/%RU32", i);
+ VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugUserPath,
+ "#%RU32: szName=%ls, sessionID=%RU32, cProcs=%RU32",
+ s_uDebugIter, pUserInfo[i].wszUser, pUserInfo[i].ulLastSession, pUserInfo[i].ulNumProcs);
+ }
+
+ bool fAddUser = false;
+ if (pUserInfo[i].ulNumProcs)
+ fAddUser = true;
+
+ if (fAddUser)
+ {
+ VGSvcVerbose(3, "User '%ls' has %RU32 interactive processes (session=%RU32)\n",
+ pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, pUserInfo[i].ulLastSession);
+
+ if (cUsersInList > 0)
+ {
+ rc = RTStrAAppend(&pszUserList, ",");
+ AssertRCBreakStmt(rc, RTStrFree(pszUserList));
+ }
+
+ cUsersInList += 1;
+
+ char *pszUser = NULL;
+ char *pszDomain = NULL;
+ rc = RTUtf16ToUtf8(pUserInfo[i].wszUser, &pszUser);
+ if ( RT_SUCCESS(rc)
+ && pUserInfo[i].wszLogonDomain)
+ rc = RTUtf16ToUtf8(pUserInfo[i].wszLogonDomain, &pszDomain);
+ if (RT_SUCCESS(rc))
+ {
+ /* Append user to users list. */
+ rc = RTStrAAppend(&pszUserList, pszUser);
+
+ /* Do idle detection. */
+ if (RT_SUCCESS(rc))
+ rc = vgsvcVMInfoWinWriteLastInput(pCache, pszUser, pszDomain);
+ }
+ else
+ rc = RTStrAAppend(&pszUserList, "<string-conversion-error>");
+
+ RTStrFree(pszUser);
+ RTStrFree(pszDomain);
+
+ AssertRCBreakStmt(rc, RTStrFree(pszUserList));
+ }
+ }
+
+ RTMemFree(pUserInfo);
+ }
+ vgsvcVMInfoWinProcessesFree(cProcs, paProcs);
+ }
+ if (paSessions)
+ g_pfnLsaFreeReturnBuffer(paSessions);
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppszUserList = pszUserList;
+ *pcUsersInList = cUsersInList;
+ }
+
+ s_uDebugIter++;
+ VbglR3GuestPropDisconnect(s_uDebugGuestPropClientID);
+
+ return rc;
+}
+
+
+int VGSvcVMInfoWinGetComponentVersions(uint32_t uClientID)
+{
+ int rc;
+ char szSysDir[MAX_PATH] = {0};
+ char szWinDir[MAX_PATH] = {0};
+ char szDriversDir[MAX_PATH + 32] = {0};
+
+ /* ASSUME: szSysDir and szWinDir and derivatives are always ASCII compatible. */
+ GetSystemDirectory(szSysDir, MAX_PATH);
+ GetWindowsDirectory(szWinDir, MAX_PATH);
+ RTStrPrintf(szDriversDir, sizeof(szDriversDir), "%s\\drivers", szSysDir);
+#ifdef RT_ARCH_AMD64
+ char szSysWowDir[MAX_PATH + 32] = {0};
+ RTStrPrintf(szSysWowDir, sizeof(szSysWowDir), "%s\\SysWow64", szWinDir);
+#endif
+
+ /* The file information table. */
+ const VBOXSERVICEVMINFOFILE aVBoxFiles[] =
+ {
+ { szSysDir, "VBoxControl.exe" },
+ { szSysDir, "VBoxHook.dll" },
+ { szSysDir, "VBoxDisp.dll" },
+ { szSysDir, "VBoxTray.exe" },
+ { szSysDir, "VBoxService.exe" },
+ { szSysDir, "VBoxMRXNP.dll" },
+ { szSysDir, "VBoxGINA.dll" },
+ { szSysDir, "VBoxCredProv.dll" },
+
+ /* On 64-bit we don't yet have the OpenGL DLLs in native format.
+ So just enumerate the 32-bit files in the SYSWOW directory. */
+#ifdef RT_ARCH_AMD64
+ { szSysWowDir, "VBoxOGL-x86.dll" },
+#else /* !RT_ARCH_AMD64 */
+ { szSysDir, "VBoxOGL.dll" },
+#endif /* !RT_ARCH_AMD64 */
+
+ { szDriversDir, "VBoxGuest.sys" },
+ { szDriversDir, "VBoxMouseNT.sys" },
+ { szDriversDir, "VBoxMouse.sys" },
+ { szDriversDir, "VBoxSF.sys" },
+ { szDriversDir, "VBoxVideo.sys" },
+ };
+
+ for (unsigned i = 0; i < RT_ELEMENTS(aVBoxFiles); i++)
+ {
+ char szVer[128];
+ rc = VGSvcUtilWinGetFileVersionString(aVBoxFiles[i].pszFilePath, aVBoxFiles[i].pszFileName, szVer, sizeof(szVer));
+ char szPropPath[256];
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestAdd/Components/%s", aVBoxFiles[i].pszFileName);
+ if ( rc != VERR_FILE_NOT_FOUND
+ && rc != VERR_PATH_NOT_FOUND)
+ VGSvcWritePropF(uClientID, szPropPath, "%s", szVer);
+ else
+ VGSvcWritePropF(uClientID, szPropPath, NULL);
+ }
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp
new file mode 100644
index 00000000..f7e42756
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp
@@ -0,0 +1,1707 @@
+/* $Id: VBoxServiceVMInfo.cpp $ */
+/** @file
+ * VBoxService - Virtual Machine Information for the Host.
+ */
+
+/*
+ * 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
+ */
+
+/** @page pg_vgsvc_vminfo VBoxService - VM Information
+ *
+ * The VM Information subservice provides heaps of useful information about the
+ * VM via guest properties.
+ *
+ * Guest properties is a limited database maintained by the HGCM GuestProperties
+ * service in cooperation with the Main API (VBoxSVC). Properties have a name
+ * (ours are path like), a string value, and a nanosecond timestamp (unix
+ * epoch). The timestamp lets the user see how recent the information is. As
+ * an laternative to polling on changes, it is also possible to wait on changes
+ * via the Main API or VBoxManage on the host side and VBoxControl in the guest.
+ *
+ * The namespace "/VirtualBox/" is reserved for value provided by VirtualBox.
+ * This service provides all the information under "/VirtualBox/GuestInfo/".
+ *
+ *
+ * @section sec_vgsvc_vminfo_beacons Beacons
+ *
+ * The subservice does not write properties unless there are changes. So, in
+ * order for the host side to know that information is up to date despite an
+ * oldish timestamp we define a couple of values that are always updated and can
+ * reliably used to figure how old the information actually is.
+ *
+ * For the networking part "/VirtualBox/GuestInfo/Net/Count" is the value to
+ * watch out for.
+ *
+ * For the login part, it's possible that we intended to use
+ * "/VirtualBox/GuestInfo/OS/LoggedInUsers" for this, however it is not defined
+ * correctly and current does NOT work as a beacon.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/winsock2.h>
+# include <iprt/win/iphlpapi.h>
+# include <iprt/win/ws2tcpip.h>
+# include <iprt/win/windows.h>
+# include <Ntsecapi.h>
+#else
+# define __STDC_LIMIT_MACROS
+# include <arpa/inet.h>
+# include <errno.h>
+# include <netinet/in.h>
+# include <sys/ioctl.h>
+# include <sys/socket.h>
+# include <net/if.h>
+# include <pwd.h> /* getpwuid */
+# include <unistd.h>
+# if !defined(RT_OS_OS2) && !defined(RT_OS_FREEBSD) && !defined(RT_OS_HAIKU)
+# include <utmpx.h> /** @todo FreeBSD 9 should have this. */
+# endif
+# ifdef RT_OS_OS2
+# include <net/if_dl.h>
+# endif
+# ifdef RT_OS_SOLARIS
+# include <sys/sockio.h>
+# include <net/if_arp.h>
+# endif
+# if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD)
+# include <ifaddrs.h> /* getifaddrs, freeifaddrs */
+# include <net/if_dl.h> /* LLADDR */
+# include <netdb.h> /* getnameinfo */
+# endif
+# ifdef VBOX_WITH_DBUS
+# include <VBox/dbus.h>
+# endif
+#endif
+
+#include <iprt/mem.h>
+#include <iprt/thread.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/system.h>
+#include <iprt/time.h>
+#include <iprt/assert.h>
+#include <VBox/err.h>
+#include <VBox/version.h>
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+#include "VBoxServicePropCache.h"
+
+
+/** Structure containing information about a location awarness
+ * client provided by the host. */
+/** @todo Move this (and functions) into VbglR3. */
+typedef struct VBOXSERVICELACLIENTINFO
+{
+ uint32_t uID;
+ char *pszName;
+ char *pszLocation;
+ char *pszDomain;
+ bool fAttached;
+ uint64_t uAttachedTS;
+} VBOXSERVICELACLIENTINFO, *PVBOXSERVICELACLIENTINFO;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The vminfo interval (milliseconds). */
+static uint32_t g_cMsVMInfoInterval = 0;
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_hVMInfoEvent = NIL_RTSEMEVENTMULTI;
+/** The guest property service client ID. */
+static uint32_t g_uVMInfoGuestPropSvcClientID = 0;
+/** Number of currently logged in users in OS. */
+static uint32_t g_cVMInfoLoggedInUsers = 0;
+/** The guest property cache. */
+static VBOXSERVICEVEPROPCACHE g_VMInfoPropCache;
+static const char *g_pszPropCacheValLoggedInUsersList = "/VirtualBox/GuestInfo/OS/LoggedInUsersList";
+static const char *g_pszPropCacheValLoggedInUsers = "/VirtualBox/GuestInfo/OS/LoggedInUsers";
+static const char *g_pszPropCacheValNoLoggedInUsers = "/VirtualBox/GuestInfo/OS/NoLoggedInUsers";
+static const char *g_pszPropCacheValNetCount = "/VirtualBox/GuestInfo/Net/Count";
+/** A guest user's guest property root key. */
+static const char *g_pszPropCacheValUser = "/VirtualBox/GuestInfo/User/";
+/** The VM session ID. Changes whenever the VM is restored or reset. */
+static uint64_t g_idVMInfoSession;
+/** The last attached locartion awareness (LA) client timestamp. */
+static uint64_t g_LAClientAttachedTS = 0;
+/** The current LA client info. */
+static VBOXSERVICELACLIENTINFO g_LAClientInfo;
+/** User idle threshold (in ms). This specifies the minimum time a user is considered
+ * as being idle and then will be reported to the host. Default is 5s. */
+uint32_t g_uVMInfoUserIdleThresholdMS = 5 * 1000;
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+static const char *g_pszLAActiveClient = "/VirtualBox/HostInfo/VRDP/ActiveClient";
+
+#ifdef VBOX_WITH_DBUS
+/** @name ConsoleKit defines (taken from 0.4.5).
+ * @{ */
+# define CK_NAME "org.freedesktop.ConsoleKit"
+# define CK_PATH "/org/freedesktop/ConsoleKit"
+# define CK_INTERFACE "org.freedesktop.ConsoleKit"
+# define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager"
+# define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager"
+# define CK_SEAT_INTERFACE "org.freedesktop.ConsoleKit.Seat"
+# define CK_SESSION_INTERFACE "org.freedesktop.ConsoleKit.Session"
+/** @} */
+#endif
+
+
+
+/**
+ * Signals the event so that a re-enumeration of VM-specific
+ * information (like logged in users) can happen.
+ *
+ * @return IPRT status code.
+ */
+int VGSvcVMInfoSignal(void)
+{
+ /* Trigger a re-enumeration of all logged-in users by unblocking
+ * the multi event semaphore of the VMInfo thread. */
+ if (g_hVMInfoEvent)
+ return RTSemEventMultiSignal(g_hVMInfoEvent);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnPreInit}
+ */
+static DECLCALLBACK(int) vbsvcVMInfoPreInit(void)
+{
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnOption}
+ */
+static DECLCALLBACK(int) vbsvcVMInfoOption(const char **ppszShort, int argc, char **argv, int *pi)
+{
+ /** @todo Use RTGetOpt here. */
+
+ int rc = -1;
+ if (ppszShort)
+ /* no short options */;
+ else if (!strcmp(argv[*pi], "--vminfo-interval"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsVMInfoInterval, 1, UINT32_MAX - 1);
+ else if (!strcmp(argv[*pi], "--vminfo-user-idle-threshold"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_uVMInfoUserIdleThresholdMS, 1, UINT32_MAX - 1);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbsvcVMInfoInit(void)
+{
+ /*
+ * If not specified, find the right interval default.
+ * Then create the event sem to block on.
+ */
+ if (!g_cMsVMInfoInterval)
+ g_cMsVMInfoInterval = g_DefaultInterval * 1000;
+ if (!g_cMsVMInfoInterval)
+ {
+ /* Set it to 5s by default for location awareness checks. */
+ g_cMsVMInfoInterval = 5 * 1000;
+ }
+
+ int rc = RTSemEventMultiCreate(&g_hVMInfoEvent);
+ AssertRCReturn(rc, rc);
+
+ VbglR3GetSessionId(&g_idVMInfoSession);
+ /* The status code is ignored as this information is not available with VBox < 3.2.10. */
+
+ /* Initialize the LA client object. */
+ RT_ZERO(g_LAClientInfo);
+
+ rc = VbglR3GuestPropConnect(&g_uVMInfoGuestPropSvcClientID);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "Property Service Client ID: %#x\n", g_uVMInfoGuestPropSvcClientID);
+ else
+ {
+ /* If the service was not found, we disable this service without
+ causing VBoxService to fail. */
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
+ {
+ VGSvcVerbose(0, "Guest property service is not available, disabling the service\n");
+ rc = VERR_SERVICE_DISABLED;
+ }
+ else
+ VGSvcError("Failed to connect to the guest property service! Error: %Rrc\n", rc);
+ RTSemEventMultiDestroy(g_hVMInfoEvent);
+ g_hVMInfoEvent = NIL_RTSEMEVENTMULTI;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcPropCacheCreate(&g_VMInfoPropCache, g_uVMInfoGuestPropSvcClientID);
+
+ /*
+ * Declare some guest properties with flags and reset values.
+ */
+ int rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList,
+ VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT,
+ NULL /* Delete on exit */);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValLoggedInUsersList, rc2);
+
+ rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers,
+ VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, "0");
+ if (RT_FAILURE(rc2))
+ VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValLoggedInUsers, rc2);
+
+ rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers,
+ VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, "true");
+ if (RT_FAILURE(rc2))
+ VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValNoLoggedInUsers, rc2);
+
+ rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNetCount,
+ VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE,
+ NULL /* Delete on exit */);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValNetCount, rc2);
+
+ /*
+ * Get configuration guest properties from the host.
+ * Note: All properties should have sensible defaults in case the lookup here fails.
+ */
+ char *pszValue;
+ rc2 = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--vminfo-user-idle-threshold",
+ true /* Read only */, &pszValue, NULL /* Flags */, NULL /* Timestamp */);
+ if (RT_SUCCESS(rc2))
+ {
+ AssertPtr(pszValue);
+ g_uVMInfoUserIdleThresholdMS = RT_CLAMP(RTStrToUInt32(pszValue), 1000, UINT32_MAX - 1);
+ RTStrFree(pszValue);
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Retrieves a specifiy client LA property.
+ *
+ * @return IPRT status code.
+ * @param uClientID LA client ID to retrieve property for.
+ * @param pszProperty Property (without path) to retrieve.
+ * @param ppszValue Where to store value of property.
+ * @param puTimestamp Timestamp of property to retrieve. Optional.
+ */
+static int vgsvcGetLAClientValue(uint32_t uClientID, const char *pszProperty, char **ppszValue, uint64_t *puTimestamp)
+{
+ AssertReturn(uClientID, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszProperty, VERR_INVALID_POINTER);
+
+ int rc;
+
+ char pszClientPath[255];
+/** @todo r=bird: Another pointless RTStrPrintf test with wrong status code to boot. */
+ if (RTStrPrintf(pszClientPath, sizeof(pszClientPath),
+ "/VirtualBox/HostInfo/VRDP/Client/%RU32/%s", uClientID, pszProperty))
+ {
+ rc = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, pszClientPath, true /* Read only */,
+ ppszValue, NULL /* Flags */, puTimestamp);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * Retrieves LA client information. On success the returned structure will have allocated
+ * objects which need to be free'd with vboxServiceFreeLAClientInfo.
+ *
+ * @return IPRT status code.
+ * @param uClientID Client ID to retrieve information for.
+ * @param pClient Pointer where to store the client information.
+ */
+static int vgsvcGetLAClientInfo(uint32_t uClientID, PVBOXSERVICELACLIENTINFO pClient)
+{
+ AssertReturn(uClientID, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pClient, VERR_INVALID_POINTER);
+
+ int rc = vgsvcGetLAClientValue(uClientID, "Name", &pClient->pszName,
+ NULL /* Timestamp */);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszAttach;
+ rc = vgsvcGetLAClientValue(uClientID, "Attach", &pszAttach, &pClient->uAttachedTS);
+ if (RT_SUCCESS(rc))
+ {
+ AssertPtr(pszAttach);
+ pClient->fAttached = RTStrICmp(pszAttach, "1") == 0;
+
+ RTStrFree(pszAttach);
+ }
+ }
+ if (RT_SUCCESS(rc))
+ rc = vgsvcGetLAClientValue(uClientID, "Location", &pClient->pszLocation, NULL /* Timestamp */);
+ if (RT_SUCCESS(rc))
+ rc = vgsvcGetLAClientValue(uClientID, "Domain", &pClient->pszDomain, NULL /* Timestamp */);
+ if (RT_SUCCESS(rc))
+ pClient->uID = uClientID;
+
+ return rc;
+}
+
+
+/**
+ * Frees all allocated LA client information of a structure.
+ *
+ * @param pClient Pointer to client information structure to free.
+ */
+static void vgsvcFreeLAClientInfo(PVBOXSERVICELACLIENTINFO pClient)
+{
+ if (pClient)
+ {
+ if (pClient->pszName)
+ {
+ RTStrFree(pClient->pszName);
+ pClient->pszName = NULL;
+ }
+ if (pClient->pszLocation)
+ {
+ RTStrFree(pClient->pszLocation);
+ pClient->pszLocation = NULL;
+ }
+ if (pClient->pszDomain)
+ {
+ RTStrFree(pClient->pszDomain);
+ pClient->pszDomain = NULL;
+ }
+ }
+}
+
+
+/**
+ * Updates a per-guest user guest property inside the given property cache.
+ *
+ * @return IPRT status code.
+ * @param pCache Pointer to guest property cache to update user in.
+ * @param pszUser Name of guest user to update.
+ * @param pszDomain Domain of guest user to update. Optional.
+ * @param pszKey Key name of guest property to update.
+ * @param pszValueFormat Guest property value to set. Pass NULL for deleting
+ * the property.
+ */
+int VGSvcUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain,
+ const char *pszKey, const char *pszValueFormat, ...)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszUser, VERR_INVALID_POINTER);
+ /* pszDomain is optional. */
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ /* pszValueFormat is optional. */
+
+ int rc = VINF_SUCCESS;
+
+ char *pszName;
+ if (pszDomain)
+ {
+ if (RTStrAPrintf(&pszName, "%s%s@%s/%s", g_pszPropCacheValUser, pszUser, pszDomain, pszKey) < 0)
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ if (RTStrAPrintf(&pszName, "%s%s/%s", g_pszPropCacheValUser, pszUser, pszKey) < 0)
+ rc = VERR_NO_MEMORY;
+ }
+
+ char *pszValue = NULL;
+ if ( RT_SUCCESS(rc)
+ && pszValueFormat)
+ {
+ va_list va;
+ va_start(va, pszValueFormat);
+ if (RTStrAPrintfV(&pszValue, pszValueFormat, va) < 0)
+ rc = VERR_NO_MEMORY;
+ va_end(va);
+ if ( RT_SUCCESS(rc)
+ && !pszValue)
+ rc = VERR_NO_STR_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = VGSvcPropCacheUpdate(pCache, pszName, pszValue);
+ if (rc == VINF_SUCCESS) /* VGSvcPropCacheUpdate will also return VINF_NO_CHANGE. */
+ {
+ /** @todo Combine updating flags w/ updating the actual value. */
+ rc = VGSvcPropCacheUpdateEntry(pCache, pszName,
+ VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT,
+ NULL /* Delete on exit */);
+ }
+
+ RTStrFree(pszValue);
+ RTStrFree(pszName);
+ return rc;
+}
+
+
+/**
+ * Writes the properties that won't change while the service is running.
+ *
+ * Errors are ignored.
+ */
+static void vgsvcVMInfoWriteFixedProperties(void)
+{
+ /*
+ * First get OS information that won't change.
+ */
+ char szInfo[256];
+ int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szInfo, sizeof(szInfo));
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Product",
+ "%s", RT_FAILURE(rc) ? "" : szInfo);
+
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szInfo, sizeof(szInfo));
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Release",
+ "%s", RT_FAILURE(rc) ? "" : szInfo);
+
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szInfo, sizeof(szInfo));
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Version",
+ "%s", RT_FAILURE(rc) ? "" : szInfo);
+
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szInfo, sizeof(szInfo));
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/ServicePack",
+ "%s", RT_FAILURE(rc) ? "" : szInfo);
+
+ /*
+ * Retrieve version information about Guest Additions and installed files (components).
+ */
+ char *pszAddVer;
+ char *pszAddVerExt;
+ char *pszAddRev;
+ rc = VbglR3GetAdditionsVersion(&pszAddVer, &pszAddVerExt, &pszAddRev);
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/Version",
+ "%s", RT_FAILURE(rc) ? "" : pszAddVer);
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/VersionExt",
+ "%s", RT_FAILURE(rc) ? "" : pszAddVerExt);
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/Revision",
+ "%s", RT_FAILURE(rc) ? "" : pszAddRev);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pszAddVer);
+ RTStrFree(pszAddVerExt);
+ RTStrFree(pszAddRev);
+ }
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Do windows specific properties.
+ */
+ char *pszInstDir;
+ rc = VbglR3GetAdditionsInstallationPath(&pszInstDir);
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/InstallDir",
+ "%s", RT_FAILURE(rc) ? "" : pszInstDir);
+ if (RT_SUCCESS(rc))
+ RTStrFree(pszInstDir);
+
+ VGSvcVMInfoWinGetComponentVersions(g_uVMInfoGuestPropSvcClientID);
+#endif
+}
+
+
+#if defined(VBOX_WITH_DBUS) && defined(RT_OS_LINUX) /* Not yet for Solaris/FreeBSB. */
+/*
+ * Simple wrapper to work around compiler-specific va_list madness.
+ */
+static dbus_bool_t vboxService_dbus_message_get_args(DBusMessage *message, DBusError *error, int first_arg_type, ...)
+{
+ va_list va;
+ va_start(va, first_arg_type);
+ dbus_bool_t ret = dbus_message_get_args_valist(message, error, first_arg_type, va);
+ va_end(va);
+ return ret;
+}
+#endif
+
+
+/**
+ * Provide information about active users.
+ */
+static int vgsvcVMInfoWriteUsers(void)
+{
+ int rc;
+ char *pszUserList = NULL;
+ uint32_t cUsersInList = 0;
+
+#ifdef RT_OS_WINDOWS
+ rc = VGSvcVMInfoWinWriteUsers(&g_VMInfoPropCache, &pszUserList, &cUsersInList);
+
+#elif defined(RT_OS_FREEBSD)
+ /** @todo FreeBSD: Port logged on user info retrieval.
+ * However, FreeBSD 9 supports utmpx, so we could use the code
+ * block below (?). */
+ rc = VERR_NOT_IMPLEMENTED;
+
+#elif defined(RT_OS_HAIKU)
+ /** @todo Haiku: Port logged on user info retrieval. */
+ rc = VERR_NOT_IMPLEMENTED;
+
+#elif defined(RT_OS_OS2)
+ /** @todo OS/2: Port logged on (LAN/local/whatever) user info retrieval. */
+ rc = VERR_NOT_IMPLEMENTED;
+
+#else
+ setutxent();
+ utmpx *ut_user;
+ uint32_t cListSize = 32;
+
+ /* Allocate a first array to hold 32 users max. */
+ char **papszUsers = (char **)RTMemAllocZ(cListSize * sizeof(char *));
+ if (papszUsers)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_NO_MEMORY;
+
+ /* Process all entries in the utmp file.
+ * Note: This only handles */
+ while ( (ut_user = getutxent())
+ && RT_SUCCESS(rc))
+ {
+# ifdef RT_OS_DARWIN /* No ut_user->ut_session on Darwin */
+ VGSvcVerbose(4, "Found entry '%s' (type: %d, PID: %RU32)\n", ut_user->ut_user, ut_user->ut_type, ut_user->ut_pid);
+# else
+ VGSvcVerbose(4, "Found entry '%s' (type: %d, PID: %RU32, session: %RU32)\n",
+ ut_user->ut_user, ut_user->ut_type, ut_user->ut_pid, ut_user->ut_session);
+# endif
+ if (cUsersInList > cListSize)
+ {
+ cListSize += 32;
+ void *pvNew = RTMemRealloc(papszUsers, cListSize * sizeof(char*));
+ AssertBreakStmt(pvNew, cListSize -= 32);
+ papszUsers = (char **)pvNew;
+ }
+
+ /* Make sure we don't add user names which are not
+ * part of type USER_PROCES. */
+ if (ut_user->ut_type == USER_PROCESS) /* Regular user process. */
+ {
+ bool fFound = false;
+ for (uint32_t i = 0; i < cUsersInList && !fFound; i++)
+ fFound = strncmp(papszUsers[i], ut_user->ut_user, sizeof(ut_user->ut_user)) == 0;
+
+ if (!fFound)
+ {
+ VGSvcVerbose(4, "Adding user '%s' (type: %d) to list\n", ut_user->ut_user, ut_user->ut_type);
+
+ rc = RTStrDupEx(&papszUsers[cUsersInList], (const char *)ut_user->ut_user);
+ if (RT_FAILURE(rc))
+ break;
+ cUsersInList++;
+ }
+ }
+ }
+
+# ifdef VBOX_WITH_DBUS
+# if defined(RT_OS_LINUX) /* Not yet for Solaris/FreeBSB. */
+ DBusError dbErr;
+ DBusConnection *pConnection = NULL;
+ int rc2 = RTDBusLoadLib();
+ bool fHaveLibDbus = false;
+ if (RT_SUCCESS(rc2))
+ {
+ /* Handle desktop sessions using ConsoleKit. */
+ VGSvcVerbose(4, "Checking ConsoleKit sessions ...\n");
+ fHaveLibDbus = true;
+ dbus_error_init(&dbErr);
+ pConnection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbErr);
+ }
+
+ if ( pConnection
+ && !dbus_error_is_set(&dbErr))
+ {
+ /* Get all available sessions. */
+/** @todo r=bird: What's the point of hardcoding things here when we've taken the pain of defining CK_XXX constants at the top of the file (or vice versa)? */
+ DBusMessage *pMsgSessions = dbus_message_new_method_call("org.freedesktop.ConsoleKit",
+ "/org/freedesktop/ConsoleKit/Manager",
+ "org.freedesktop.ConsoleKit.Manager",
+ "GetSessions");
+ if ( pMsgSessions
+ && dbus_message_get_type(pMsgSessions) == DBUS_MESSAGE_TYPE_METHOD_CALL)
+ {
+ DBusMessage *pReplySessions = dbus_connection_send_with_reply_and_block(pConnection,
+ pMsgSessions, 30 * 1000 /* 30s timeout */,
+ &dbErr);
+ if ( pReplySessions
+ && !dbus_error_is_set(&dbErr))
+ {
+ char **ppszSessions;
+ int cSessions;
+ if ( dbus_message_get_type(pMsgSessions) == DBUS_MESSAGE_TYPE_METHOD_CALL
+ && vboxService_dbus_message_get_args(pReplySessions, &dbErr, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_OBJECT_PATH, &ppszSessions, &cSessions,
+ DBUS_TYPE_INVALID /* Termination */))
+ {
+ VGSvcVerbose(4, "ConsoleKit: retrieved %RU16 session(s)\n", cSessions);
+
+ char **ppszCurSession = ppszSessions;
+ for (ppszCurSession; ppszCurSession && *ppszCurSession; ppszCurSession++)
+ {
+ VGSvcVerbose(4, "ConsoleKit: processing session '%s' ...\n", *ppszCurSession);
+
+ /* Only respect active sessions .*/
+ bool fActive = false;
+ DBusMessage *pMsgSessionActive = dbus_message_new_method_call("org.freedesktop.ConsoleKit",
+ *ppszCurSession,
+ "org.freedesktop.ConsoleKit.Session",
+ "IsActive");
+ if ( pMsgSessionActive
+ && dbus_message_get_type(pMsgSessionActive) == DBUS_MESSAGE_TYPE_METHOD_CALL)
+ {
+ DBusMessage *pReplySessionActive = dbus_connection_send_with_reply_and_block(pConnection,
+ pMsgSessionActive,
+ 30 * 1000 /*sec*/,
+ &dbErr);
+ if ( pReplySessionActive
+ && !dbus_error_is_set(&dbErr))
+ {
+ DBusMessageIter itMsg;
+ if ( dbus_message_iter_init(pReplySessionActive, &itMsg)
+ && dbus_message_iter_get_arg_type(&itMsg) == DBUS_TYPE_BOOLEAN)
+ {
+ /* Get uid from message. */
+ int val;
+ dbus_message_iter_get_basic(&itMsg, &val);
+ fActive = val >= 1;
+ }
+
+ if (pReplySessionActive)
+ dbus_message_unref(pReplySessionActive);
+ }
+
+ if (pMsgSessionActive)
+ dbus_message_unref(pMsgSessionActive);
+ }
+
+ VGSvcVerbose(4, "ConsoleKit: session '%s' is %s\n",
+ *ppszCurSession, fActive ? "active" : "not active");
+
+ /* *ppszCurSession now contains the object path
+ * (e.g. "/org/freedesktop/ConsoleKit/Session1"). */
+ DBusMessage *pMsgUnixUser = dbus_message_new_method_call("org.freedesktop.ConsoleKit",
+ *ppszCurSession,
+ "org.freedesktop.ConsoleKit.Session",
+ "GetUnixUser");
+ if ( fActive
+ && pMsgUnixUser
+ && dbus_message_get_type(pMsgUnixUser) == DBUS_MESSAGE_TYPE_METHOD_CALL)
+ {
+ DBusMessage *pReplyUnixUser = dbus_connection_send_with_reply_and_block(pConnection,
+ pMsgUnixUser,
+ 30 * 1000 /* 30s timeout */,
+ &dbErr);
+ if ( pReplyUnixUser
+ && !dbus_error_is_set(&dbErr))
+ {
+ DBusMessageIter itMsg;
+ if ( dbus_message_iter_init(pReplyUnixUser, &itMsg)
+ && dbus_message_iter_get_arg_type(&itMsg) == DBUS_TYPE_UINT32)
+ {
+ /* Get uid from message. */
+ uint32_t uid;
+ dbus_message_iter_get_basic(&itMsg, &uid);
+
+ /** @todo Add support for getting UID_MIN (/etc/login.defs on
+ * Debian). */
+ uint32_t uid_min = 1000;
+
+ /* Look up user name (realname) from uid. */
+ setpwent();
+ struct passwd *ppwEntry = getpwuid(uid);
+ if ( ppwEntry
+ && ppwEntry->pw_name)
+ {
+ if (ppwEntry->pw_uid >= uid_min /* Only respect users, not daemons etc. */)
+ {
+ VGSvcVerbose(4, "ConsoleKit: session '%s' -> %s (uid: %RU32)\n",
+ *ppszCurSession, ppwEntry->pw_name, uid);
+
+ bool fFound = false;
+ for (uint32_t i = 0; i < cUsersInList && !fFound; i++)
+ fFound = strcmp(papszUsers[i], ppwEntry->pw_name) == 0;
+
+ if (!fFound)
+ {
+ VGSvcVerbose(4, "ConsoleKit: adding user '%s' to list\n", ppwEntry->pw_name);
+
+ rc = RTStrDupEx(&papszUsers[cUsersInList], (const char *)ppwEntry->pw_name);
+ if (RT_FAILURE(rc))
+ break;
+ cUsersInList++;
+ }
+ }
+ /* else silently ignore the user */
+ }
+ else
+ VGSvcError("ConsoleKit: unable to lookup user name for uid=%RU32\n", uid);
+ }
+ else
+ AssertMsgFailed(("ConsoleKit: GetUnixUser returned a wrong argument type\n"));
+ }
+
+ if (pReplyUnixUser)
+ dbus_message_unref(pReplyUnixUser);
+ }
+ else if (fActive) /* don't bitch about inactive users */
+ {
+ static int s_iBitchedAboutConsoleKit = 0;
+ if (s_iBitchedAboutConsoleKit < 1)
+ {
+ s_iBitchedAboutConsoleKit++;
+ VGSvcError("ConsoleKit: unable to retrieve user for session '%s' (msg type=%d): %s\n",
+ *ppszCurSession, dbus_message_get_type(pMsgUnixUser),
+ dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available");
+ }
+ }
+
+ if (pMsgUnixUser)
+ dbus_message_unref(pMsgUnixUser);
+ }
+
+ dbus_free_string_array(ppszSessions);
+ }
+ else
+ VGSvcError("ConsoleKit: unable to retrieve session parameters (msg type=%d): %s\n",
+ dbus_message_get_type(pMsgSessions),
+ dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available");
+ dbus_message_unref(pReplySessions);
+ }
+
+ if (pMsgSessions)
+ {
+ dbus_message_unref(pMsgSessions);
+ pMsgSessions = NULL;
+ }
+ }
+ else
+ {
+ static int s_iBitchedAboutConsoleKit = 0;
+ if (s_iBitchedAboutConsoleKit < 3)
+ {
+ s_iBitchedAboutConsoleKit++;
+ VGSvcError("Unable to invoke ConsoleKit (%d/3) -- maybe not installed / used? Error: %s\n",
+ s_iBitchedAboutConsoleKit,
+ dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available");
+ }
+ }
+
+ if (pMsgSessions)
+ dbus_message_unref(pMsgSessions);
+ }
+ else
+ {
+ static int s_iBitchedAboutDBus = 0;
+ if (s_iBitchedAboutDBus < 3)
+ {
+ s_iBitchedAboutDBus++;
+ VGSvcError("Unable to connect to system D-Bus (%d/3): %s\n", s_iBitchedAboutDBus,
+ fHaveLibDbus && dbus_error_is_set(&dbErr) ? dbErr.message : "D-Bus not installed");
+ }
+ }
+
+ if ( fHaveLibDbus
+ && dbus_error_is_set(&dbErr))
+ dbus_error_free(&dbErr);
+# endif /* RT_OS_LINUX */
+# endif /* VBOX_WITH_DBUS */
+
+ /** @todo Fedora/others: Handle systemd-loginctl. */
+
+ /* Calc the string length. */
+ size_t cchUserList = 0;
+ if (RT_SUCCESS(rc))
+ for (uint32_t i = 0; i < cUsersInList; i++)
+ cchUserList += (i != 0) + strlen(papszUsers[i]);
+
+ /* Build the user list. */
+ if (cchUserList > 0)
+ {
+ if (RT_SUCCESS(rc))
+ rc = RTStrAllocEx(&pszUserList, cchUserList + 1);
+ if (RT_SUCCESS(rc))
+ {
+ char *psz = pszUserList;
+ for (uint32_t i = 0; i < cUsersInList; i++)
+ {
+ if (i != 0)
+ *psz++ = ',';
+ size_t cch = strlen(papszUsers[i]);
+ memcpy(psz, papszUsers[i], cch);
+ psz += cch;
+ }
+ *psz = '\0';
+ }
+ }
+
+ /* Cleanup. */
+ for (uint32_t i = 0; i < cUsersInList; i++)
+ RTStrFree(papszUsers[i]);
+ RTMemFree(papszUsers);
+
+ endutxent(); /* Close utmpx file. */
+#endif /* !RT_OS_WINDOWS && !RT_OS_FREEBSD && !RT_OS_HAIKU && !RT_OS_OS2 */
+
+ Assert(RT_FAILURE(rc) || cUsersInList == 0 || (pszUserList && *pszUserList));
+
+ /*
+ * If the user enumeration above failed, reset the user count to 0 except
+ * we didn't have enough memory anymore. In that case we want to preserve
+ * the previous user count in order to not confuse third party tools which
+ * rely on that count.
+ */
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MEMORY)
+ {
+ static int s_iVMInfoBitchedOOM = 0;
+ if (s_iVMInfoBitchedOOM++ < 3)
+ VGSvcVerbose(0, "Warning: Not enough memory available to enumerate users! Keeping old value (%RU32)\n",
+ g_cVMInfoLoggedInUsers);
+ cUsersInList = g_cVMInfoLoggedInUsers;
+ }
+ else
+ cUsersInList = 0;
+ }
+ else /* Preserve logged in users count. */
+ g_cVMInfoLoggedInUsers = cUsersInList;
+
+ VGSvcVerbose(4, "cUsersInList=%RU32, pszUserList=%s, rc=%Rrc\n", cUsersInList, pszUserList ? pszUserList : "<NULL>", rc);
+
+ if (pszUserList)
+ {
+ AssertMsg(cUsersInList, ("pszUserList contains users whereas cUsersInList is 0\n"));
+ rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, "%s", pszUserList);
+ }
+ else
+ rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, NULL);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error writing logged in users list, rc=%Rrc\n", rc);
+
+ rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers, "%RU32", cUsersInList);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error writing logged in users count, rc=%Rrc\n", rc);
+
+/** @todo r=bird: What's this 'beacon' nonsense here? It's _not_ defined with
+ * the VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE flag set!! */
+ rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers, cUsersInList == 0 ? "true" : "false");
+ if (RT_FAILURE(rc))
+ VGSvcError("Error writing no logged in users beacon, rc=%Rrc\n", rc);
+
+ if (pszUserList)
+ RTStrFree(pszUserList);
+
+ VGSvcVerbose(4, "Writing users returned with rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * Provide information about the guest network.
+ */
+static int vgsvcVMInfoWriteNetwork(void)
+{
+ uint32_t cIfsReported = 0;
+ char szPropPath[256];
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Check that the APIs we need are present.
+ */
+ if ( !g_pfnWSAIoctl
+ || !g_pfnWSASocketA
+ || !g_pfnWSAGetLastError
+ || !g_pfninet_ntoa
+ || !g_pfnclosesocket)
+ return VINF_SUCCESS;
+
+ /*
+ * Query the IP adapter info first, if we have the API.
+ */
+ IP_ADAPTER_INFO *pAdpInfo = NULL;
+ if (g_pfnGetAdaptersInfo)
+ {
+ ULONG cbAdpInfo = RT_MAX(sizeof(IP_ADAPTER_INFO) * 2, _2K);
+ pAdpInfo = (IP_ADAPTER_INFO *)RTMemAllocZ(cbAdpInfo);
+ if (!pAdpInfo)
+ {
+ VGSvcError("VMInfo/Network: Failed to allocate two IP_ADAPTER_INFO structures\n");
+ return VERR_NO_MEMORY;
+ }
+
+ DWORD dwRet = g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo);
+ if (dwRet == ERROR_BUFFER_OVERFLOW)
+ {
+ IP_ADAPTER_INFO *pAdpInfoNew = (IP_ADAPTER_INFO*)RTMemRealloc(pAdpInfo, cbAdpInfo);
+ if (pAdpInfoNew)
+ {
+ pAdpInfo = pAdpInfoNew;
+ RT_BZERO(pAdpInfo, cbAdpInfo);
+ dwRet = g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo);
+ }
+ }
+ if (dwRet != NO_ERROR)
+ {
+ RTMemFree(pAdpInfo);
+ pAdpInfo = NULL;
+ if (dwRet == ERROR_NO_DATA)
+ /* If no network adapters available / present in the
+ system we pretend success to not bail out too early. */
+ VGSvcVerbose(3, "VMInfo/Network: No network adapters present according to GetAdaptersInfo.\n");
+ else
+ {
+ VGSvcError("VMInfo/Network: Failed to get adapter info: Error %d\n", dwRet);
+ return RTErrConvertFromWin32(dwRet);
+ }
+ }
+ }
+
+ /*
+ * Ask the TCP/IP stack for an interface list.
+ */
+ SOCKET sd = g_pfnWSASocketA(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
+ if (sd == SOCKET_ERROR) /* Socket invalid. */
+ {
+ int const wsaErr = g_pfnWSAGetLastError();
+ RTMemFree(pAdpInfo);
+
+ /* Don't complain/bail out with an error if network stack is not up; can happen
+ * on NT4 due to start up when not connected shares dialogs pop up. */
+ if (wsaErr == WSAENETDOWN)
+ {
+ VGSvcVerbose(0, "VMInfo/Network: Network is not up yet.\n");
+ return VINF_SUCCESS;
+ }
+ VGSvcError("VMInfo/Network: Failed to get a socket: Error %d\n", wsaErr);
+ return RTErrConvertFromWin32(wsaErr);
+ }
+
+ INTERFACE_INFO aInterfaces[20] = {{0}};
+ DWORD cbReturned = 0;
+# ifdef RT_ARCH_X86
+ /* Workaround for uninitialized variable used in memcpy in GetTcpipInterfaceList
+ (NT4SP1 at least). It seems to be happy enough with garbages, no failure
+ returns so far, so we just need to prevent it from crashing by filling the
+ stack with valid pointer values prior to the API call. */
+ _asm
+ {
+ mov edx, edi
+ lea eax, aInterfaces
+ mov [esp - 0x1000], eax
+ mov [esp - 0x2000], eax
+ mov ecx, 0x2000/4 - 1
+ cld
+ lea edi, [esp - 0x2000]
+ rep stosd
+ mov edi, edx
+ }
+# endif
+ int rc = g_pfnWSAIoctl(sd,
+ SIO_GET_INTERFACE_LIST,
+ NULL, /* pvInBuffer */
+ 0, /* cbInBuffer */
+ &aInterfaces[0], /* pvOutBuffer */
+ sizeof(aInterfaces), /* cbOutBuffer */
+ &cbReturned,
+ NULL, /* pOverlapped */
+ NULL); /* pCompletionRoutine */
+ if (rc == SOCKET_ERROR)
+ {
+ VGSvcError("VMInfo/Network: Failed to WSAIoctl() on socket: Error: %d\n", g_pfnWSAGetLastError());
+ RTMemFree(pAdpInfo);
+ g_pfnclosesocket(sd);
+ return RTErrConvertFromWin32(g_pfnWSAGetLastError());
+ }
+ g_pfnclosesocket(sd);
+ int cIfacesSystem = cbReturned / sizeof(INTERFACE_INFO);
+
+ /*
+ * Iterate the inteface list we got back from the TCP/IP,
+ * using the pAdpInfo list to supply the MAC address.
+ */
+ /** @todo Use GetAdaptersInfo() and GetAdapterAddresses (IPv4 + IPv6) for more information. */
+ for (int i = 0; i < cIfacesSystem; ++i)
+ {
+ if (aInterfaces[i].iiFlags & IFF_LOOPBACK) /* Skip loopback device. */
+ continue;
+ sockaddr_in *pAddress = &aInterfaces[i].iiAddress.AddressIn;
+ char szIp[32];
+ RTStrPrintf(szIp, sizeof(szIp), "%s", g_pfninet_ntoa(pAddress->sin_addr));
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szIp);
+
+ pAddress = &aInterfaces[i].iiBroadcastAddress.AddressIn;
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", g_pfninet_ntoa(pAddress->sin_addr));
+
+ pAddress = (sockaddr_in *)&aInterfaces[i].iiNetmask;
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", g_pfninet_ntoa(pAddress->sin_addr));
+
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, aInterfaces[i].iiFlags & IFF_UP ? "Up" : "Down");
+
+ if (pAdpInfo)
+ {
+ IP_ADAPTER_INFO *pAdp;
+ for (pAdp = pAdpInfo; pAdp; pAdp = pAdp->Next)
+ if (!strcmp(pAdp->IpAddressList.IpAddress.String, szIp))
+ break;
+
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfsReported);
+ if (pAdp)
+ {
+ char szMac[32];
+ RTStrPrintf(szMac, sizeof(szMac), "%02X%02X%02X%02X%02X%02X",
+ pAdp->Address[0], pAdp->Address[1], pAdp->Address[2],
+ pAdp->Address[3], pAdp->Address[4], pAdp->Address[5]);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac);
+ }
+ else
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, NULL);
+ }
+
+ cIfsReported++;
+ }
+
+ RTMemFree(pAdpInfo);
+
+#elif defined(RT_OS_HAIKU)
+ /** @todo Haiku: implement network info. retreival */
+ return VERR_NOT_IMPLEMENTED;
+
+#elif defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD)
+ struct ifaddrs *pIfHead = NULL;
+
+ /* Get all available interfaces */
+ int rc = getifaddrs(&pIfHead);
+ if (rc < 0)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to get all interfaces: Error %Rrc\n");
+ return rc;
+ }
+
+ /* Loop through all interfaces and set the data. */
+ for (struct ifaddrs *pIfCurr = pIfHead; pIfCurr; pIfCurr = pIfCurr->ifa_next)
+ {
+ /*
+ * Only AF_INET and no loopback interfaces
+ */
+ /** @todo IPv6 interfaces */
+ if ( pIfCurr->ifa_addr->sa_family == AF_INET
+ && !(pIfCurr->ifa_flags & IFF_LOOPBACK))
+ {
+ char szInetAddr[NI_MAXHOST];
+
+ memset(szInetAddr, 0, NI_MAXHOST);
+ getnameinfo(pIfCurr->ifa_addr, sizeof(struct sockaddr_in),
+ szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr);
+
+ memset(szInetAddr, 0, NI_MAXHOST);
+ getnameinfo(pIfCurr->ifa_broadaddr, sizeof(struct sockaddr_in),
+ szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr);
+
+ memset(szInetAddr, 0, NI_MAXHOST);
+ getnameinfo(pIfCurr->ifa_netmask, sizeof(struct sockaddr_in),
+ szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr);
+
+ /* Search for the AF_LINK interface of the current AF_INET one and get the mac. */
+ for (struct ifaddrs *pIfLinkCurr = pIfHead; pIfLinkCurr; pIfLinkCurr = pIfLinkCurr->ifa_next)
+ {
+ if ( pIfLinkCurr->ifa_addr->sa_family == AF_LINK
+ && !strcmp(pIfCurr->ifa_name, pIfLinkCurr->ifa_name))
+ {
+ char szMac[32];
+ uint8_t *pu8Mac = NULL;
+ struct sockaddr_dl *pLinkAddress = (struct sockaddr_dl *)pIfLinkCurr->ifa_addr;
+
+ AssertPtr(pLinkAddress);
+ pu8Mac = (uint8_t *)LLADDR(pLinkAddress);
+ RTStrPrintf(szMac, sizeof(szMac), "%02X%02X%02X%02X%02X%02X",
+ pu8Mac[0], pu8Mac[1], pu8Mac[2], pu8Mac[3], pu8Mac[4], pu8Mac[5]);
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac);
+ break;
+ }
+ }
+
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, pIfCurr->ifa_flags & IFF_UP ? "Up" : "Down");
+
+ cIfsReported++;
+ }
+ }
+
+ /* Free allocated resources. */
+ freeifaddrs(pIfHead);
+
+#else /* !RT_OS_WINDOWS && !RT_OS_FREEBSD */
+ /*
+ * Use SIOCGIFCONF to get a list of interface/protocol configurations.
+ *
+ * See "UNIX Network Programming Volume 1" by W. R. Stevens, section 17.6
+ * for details on this ioctl.
+ */
+ int sd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sd < 0)
+ {
+ int rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to get a socket: Error %Rrc\n", rc);
+ return rc;
+ }
+
+ /* Call SIOCGIFCONF with the right sized buffer (remember the size). */
+ static int s_cbBuf = 256; // 1024
+ int cbBuf = s_cbBuf;
+ char *pchBuf;
+ struct ifconf IfConf;
+ int rc = VINF_SUCCESS;
+ for (;;)
+ {
+ pchBuf = (char *)RTMemTmpAllocZ(cbBuf);
+ if (!pchBuf)
+ {
+ rc = VERR_NO_TMP_MEMORY;
+ break;
+ }
+
+ IfConf.ifc_len = cbBuf;
+ IfConf.ifc_buf = pchBuf;
+ if (ioctl(sd, SIOCGIFCONF, &IfConf) >= 0)
+ {
+ /* Hard to anticipate how space an address might possibly take, so
+ making some generous assumptions here to avoid performing the
+ query twice with different buffer sizes. */
+ if (IfConf.ifc_len + 128 < cbBuf)
+ break;
+ }
+ else if (errno != EOVERFLOW)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+
+ /* grow the buffer */
+ s_cbBuf = cbBuf *= 2;
+ RTMemFree(pchBuf);
+ }
+ if (RT_FAILURE(rc))
+ {
+ close(sd);
+ RTMemTmpFree(pchBuf);
+ VGSvcError("VMInfo/Network: Error doing SIOCGIFCONF (cbBuf=%d): %Rrc\n", cbBuf, rc);
+ return rc;
+ }
+
+ /*
+ * Iterate the interface/protocol configurations.
+ *
+ * Note! The current code naively assumes one IPv4 address per interface.
+ * This means that guest assigning more than one address to an
+ * interface will get multiple entries for one physical interface.
+ */
+# ifdef RT_OS_OS2
+ struct ifreq *pPrevLinkAddr = NULL;
+# endif
+ struct ifreq *pCur = IfConf.ifc_req;
+ size_t cbLeft = IfConf.ifc_len;
+ while (cbLeft >= sizeof(*pCur))
+ {
+# if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX)
+ /* These two do not provide the sa_len member but only support address
+ * families which do not need extra bytes on the end. */
+# define SA_LEN(pAddr) sizeof(struct sockaddr)
+# elif !defined(SA_LEN)
+# define SA_LEN(pAddr) (pAddr)->sa_len
+# endif
+ /* Figure the size of the current request. */
+ size_t cbCur = RT_UOFFSETOF(struct ifreq, ifr_addr)
+ + SA_LEN(&pCur->ifr_addr);
+ cbCur = RT_MAX(cbCur, sizeof(struct ifreq));
+# if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX)
+ Assert(pCur->ifr_addr.sa_family == AF_INET);
+# endif
+ AssertBreak(cbCur <= cbLeft);
+
+# ifdef RT_OS_OS2
+ /* On OS/2 we get the MAC address in the AF_LINK that the BSD 4.4 stack
+ emits. We boldly ASSUME these always comes first. */
+ if ( pCur->ifr_addr.sa_family == AF_LINK
+ && ((struct sockaddr_dl *)&pCur->ifr_addr)->sdl_alen == 6)
+ pPrevLinkAddr = pCur;
+# endif
+
+ /* Skip it if it's not the kind of address we're looking for. */
+ struct ifreq IfReqTmp;
+ bool fIfUp = false;
+ bool fSkip = false;
+ if (pCur->ifr_addr.sa_family != AF_INET)
+ fSkip = true;
+ else
+ {
+ /* Get the interface flags so we can detect loopback and check if it's up. */
+ IfReqTmp = *pCur;
+ if (ioctl(sd, SIOCGIFFLAGS, &IfReqTmp) < 0)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFFLAGS,%s) on socket: Error %Rrc\n", pCur->ifr_name, rc);
+ break;
+ }
+ fIfUp = !!(IfReqTmp.ifr_flags & IFF_UP);
+ if (IfReqTmp.ifr_flags & IFF_LOOPBACK) /* Skip the loopback device. */
+ fSkip = true;
+ }
+ if (!fSkip)
+ {
+ size_t offSubProp = RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32", cIfsReported);
+
+ sockaddr_in *pAddress = (sockaddr_in *)&pCur->ifr_addr;
+ strcpy(&szPropPath[offSubProp], "/V4/IP");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr));
+
+ /* Get the broadcast address. */
+ IfReqTmp = *pCur;
+ if (ioctl(sd, SIOCGIFBRDADDR, &IfReqTmp) < 0)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFBRDADDR) on socket: Error %Rrc\n", rc);
+ break;
+ }
+ pAddress = (sockaddr_in *)&IfReqTmp.ifr_broadaddr;
+ strcpy(&szPropPath[offSubProp], "/V4/Broadcast");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr));
+
+ /* Get the net mask. */
+ IfReqTmp = *pCur;
+ if (ioctl(sd, SIOCGIFNETMASK, &IfReqTmp) < 0)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFNETMASK) on socket: Error %Rrc\n", rc);
+ break;
+ }
+# if defined(RT_OS_OS2) || defined(RT_OS_SOLARIS)
+ pAddress = (sockaddr_in *)&IfReqTmp.ifr_addr;
+# else
+ pAddress = (sockaddr_in *)&IfReqTmp.ifr_netmask;
+# endif
+ strcpy(&szPropPath[offSubProp], "/V4/Netmask");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr));
+
+# if defined(RT_OS_SOLARIS)
+ /*
+ * "ifreq" is obsolete on Solaris. We use the recommended "lifreq".
+ * We might fail if the interface has not been assigned an IP address.
+ * That doesn't matter; as long as it's plumbed we can pick it up.
+ * But, if it has not acquired an IP address we cannot obtain it's MAC
+ * address this way, so we just use all zeros there.
+ */
+ RTMAC IfMac;
+ struct lifreq IfReq;
+ RT_ZERO(IfReq);
+ AssertCompile(sizeof(IfReq.lifr_name) >= sizeof(pCur->ifr_name));
+ strncpy(IfReq.lifr_name, pCur->ifr_name, sizeof(IfReq.lifr_name));
+ if (ioctl(sd, SIOCGLIFADDR, &IfReq) >= 0)
+ {
+ struct arpreq ArpReq;
+ RT_ZERO(ArpReq);
+ memcpy(&ArpReq.arp_pa, &IfReq.lifr_addr, sizeof(struct sockaddr_in));
+
+ if (ioctl(sd, SIOCGARP, &ArpReq) >= 0)
+ memcpy(&IfMac, ArpReq.arp_ha.sa_data, sizeof(IfMac));
+ else
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: failed to ioctl(SIOCGARP) on socket: Error %Rrc\n", rc);
+ break;
+ }
+ }
+ else
+ {
+ VGSvcVerbose(2, "VMInfo/Network: Interface '%s' has no assigned IP address, skipping ...\n", pCur->ifr_name);
+ continue;
+ }
+# elif defined(RT_OS_OS2)
+ RTMAC IfMac;
+ if ( pPrevLinkAddr
+ && strncmp(pCur->ifr_name, pPrevLinkAddr->ifr_name, sizeof(pCur->ifr_name)) == 0)
+ {
+ struct sockaddr_dl *pDlAddr = (struct sockaddr_dl *)&pPrevLinkAddr->ifr_addr;
+ IfMac = *(PRTMAC)&pDlAddr->sdl_data[pDlAddr->sdl_nlen];
+ }
+ else
+ RT_ZERO(IfMac);
+#else
+ if (ioctl(sd, SIOCGIFHWADDR, &IfReqTmp) < 0)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFHWADDR) on socket: Error %Rrc\n", rc);
+ break;
+ }
+ RTMAC IfMac = *(PRTMAC)&IfReqTmp.ifr_hwaddr.sa_data[0];
+# endif
+ strcpy(&szPropPath[offSubProp], "/MAC");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%02X%02X%02X%02X%02X%02X",
+ IfMac.au8[0], IfMac.au8[1], IfMac.au8[2], IfMac.au8[3], IfMac.au8[4], IfMac.au8[5]);
+
+ strcpy(&szPropPath[offSubProp], "/Status");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, fIfUp ? "Up" : "Down");
+
+ /* The name. */
+ int rc2 = RTStrValidateEncodingEx(pCur->ifr_name, sizeof(pCur->ifr_name), 0);
+ if (RT_SUCCESS(rc2))
+ {
+ strcpy(&szPropPath[offSubProp], "/Name");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%.*s", sizeof(pCur->ifr_name), pCur->ifr_name);
+ }
+
+ cIfsReported++;
+ }
+
+ /*
+ * Next interface/protocol configuration.
+ */
+ pCur = (struct ifreq *)((uintptr_t)pCur + cbCur);
+ cbLeft -= cbCur;
+ }
+
+ RTMemTmpFree(pchBuf);
+ close(sd);
+ if (RT_FAILURE(rc))
+ VGSvcError("VMInfo/Network: Network enumeration for interface %RU32 failed with error %Rrc\n", cIfsReported, rc);
+
+#endif /* !RT_OS_WINDOWS */
+
+#if 0 /* Zapping not enabled yet, needs more testing first. */
+ /*
+ * Zap all stale network interface data if the former (saved) network ifaces count
+ * is bigger than the current one.
+ */
+
+ /* Get former count. */
+ uint32_t cIfsReportedOld;
+ rc = VGSvcReadPropUInt32(g_uVMInfoGuestPropSvcClientID, g_pszPropCacheValNetCount, &cIfsReportedOld,
+ 0 /* Min */, UINT32_MAX /* Max */);
+ if ( RT_SUCCESS(rc)
+ && cIfsReportedOld > cIfsReported) /* Are some ifaces not around anymore? */
+ {
+ VGSvcVerbose(3, "VMInfo/Network: Stale interface data detected (%RU32 old vs. %RU32 current)\n",
+ cIfsReportedOld, cIfsReported);
+
+ uint32_t uIfaceDeleteIdx = cIfsReported;
+ do
+ {
+ VGSvcVerbose(3, "VMInfo/Network: Deleting stale data of interface %d ...\n", uIfaceDeleteIdx);
+ rc = VGSvcPropCacheUpdateByPath(&g_VMInfoPropCache, NULL /* Value, delete */, 0 /* Flags */, "/VirtualBox/GuestInfo/Net/%RU32", uIfaceDeleteIdx++);
+ } while (RT_SUCCESS(rc));
+ }
+ else if ( RT_FAILURE(rc)
+ && rc != VERR_NOT_FOUND)
+ {
+ VGSvcError("VMInfo/Network: Failed retrieving old network interfaces count with error %Rrc\n", rc);
+ }
+#endif
+
+ /*
+ * This property is a beacon which is _always_ written, even if the network configuration
+ * does not change. If this property is missing, the host assumes that all other GuestInfo
+ * properties are no longer valid.
+ */
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNetCount, "%RU32", cIfsReported);
+
+ /* Don't fail here; just report everything we got. */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbsvcVMInfoWorker(bool volatile *pfShutdown)
+{
+ int rc;
+
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+#ifdef RT_OS_WINDOWS
+ /* Required for network information (must be called per thread). */
+ if (g_pfnWSAStartup)
+ {
+ WSADATA wsaData;
+ RT_ZERO(wsaData);
+ if (g_pfnWSAStartup(MAKEWORD(2, 2), &wsaData))
+ VGSvcError("VMInfo/Network: WSAStartup failed! Error: %Rrc\n", RTErrConvertFromWin32(g_pfnWSAGetLastError()));
+ }
+#endif
+
+ /*
+ * Write the fixed properties first.
+ */
+ vgsvcVMInfoWriteFixedProperties();
+
+ /*
+ * Now enter the loop retrieving runtime data continuously.
+ */
+ for (;;)
+ {
+ rc = vgsvcVMInfoWriteUsers();
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vgsvcVMInfoWriteNetwork();
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Whether to wait for event semaphore or not. */
+ bool fWait = true;
+
+ /* Check for location awareness. This most likely only
+ * works with VBox (latest) 4.1 and up. */
+
+ /* Check for new connection. */
+ char *pszLAClientID = NULL;
+ int rc2 = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, g_pszLAActiveClient, true /* Read only */,
+ &pszLAClientID, NULL /* Flags */, NULL /* Timestamp */);
+ if (RT_SUCCESS(rc2))
+ {
+ AssertPtr(pszLAClientID);
+ if (RTStrICmp(pszLAClientID, "0")) /* Is a client connected? */
+ {
+ uint32_t uLAClientID = RTStrToInt32(pszLAClientID);
+ uint64_t uLAClientAttachedTS;
+
+ /* Peek at "Attach" value to figure out if hotdesking happened. */
+ char *pszAttach = NULL;
+ rc2 = vgsvcGetLAClientValue(uLAClientID, "Attach", &pszAttach,
+ &uLAClientAttachedTS);
+
+ if ( RT_SUCCESS(rc2)
+ && ( !g_LAClientAttachedTS
+ || (g_LAClientAttachedTS != uLAClientAttachedTS)))
+ {
+ vgsvcFreeLAClientInfo(&g_LAClientInfo);
+
+ /* Note: There is a race between setting the guest properties by the host and getting them by
+ * the guest. */
+ rc2 = vgsvcGetLAClientInfo(uLAClientID, &g_LAClientInfo);
+ if (RT_SUCCESS(rc2))
+ {
+ VGSvcVerbose(1, "VRDP: Hotdesk client %s with ID=%RU32, Name=%s, Domain=%s\n",
+ /* If g_LAClientAttachedTS is 0 this means there already was an active
+ * hotdesk session when VBoxService started. */
+ !g_LAClientAttachedTS ? "already active" : g_LAClientInfo.fAttached ? "connected" : "disconnected",
+ uLAClientID, g_LAClientInfo.pszName, g_LAClientInfo.pszDomain);
+
+ g_LAClientAttachedTS = g_LAClientInfo.uAttachedTS;
+
+ /* Don't wait for event semaphore below anymore because we now know that the client
+ * changed. This means we need to iterate all VM information again immediately. */
+ fWait = false;
+ }
+ else
+ {
+ static int s_iBitchedAboutLAClientInfo = 0;
+ if (s_iBitchedAboutLAClientInfo < 10)
+ {
+ s_iBitchedAboutLAClientInfo++;
+ VGSvcError("Error getting active location awareness client info, rc=%Rrc\n", rc2);
+ }
+ }
+ }
+ else if (RT_FAILURE(rc2))
+ VGSvcError("Error getting attached value of location awareness client %RU32, rc=%Rrc\n", uLAClientID, rc2);
+ if (pszAttach)
+ RTStrFree(pszAttach);
+ }
+ else
+ {
+ VGSvcVerbose(1, "VRDP: UTTSC disconnected from VRDP server\n");
+ vgsvcFreeLAClientInfo(&g_LAClientInfo);
+ }
+
+ RTStrFree(pszLAClientID);
+ }
+ else
+ {
+ static int s_iBitchedAboutLAClient = 0;
+ if ( (rc2 != VERR_NOT_FOUND) /* No location awareness installed, skip. */
+ && s_iBitchedAboutLAClient < 3)
+ {
+ s_iBitchedAboutLAClient++;
+ VGSvcError("VRDP: Querying connected location awareness client failed with rc=%Rrc\n", rc2);
+ }
+ }
+
+ VGSvcVerbose(3, "VRDP: Handling location awareness done\n");
+
+ /*
+ * Flush all properties if we were restored.
+ */
+ uint64_t idNewSession = g_idVMInfoSession;
+ VbglR3GetSessionId(&idNewSession);
+ if (idNewSession != g_idVMInfoSession)
+ {
+ VGSvcVerbose(3, "The VM session ID changed, flushing all properties\n");
+ vgsvcVMInfoWriteFixedProperties();
+ VGSvcPropCacheFlush(&g_VMInfoPropCache);
+ g_idVMInfoSession = idNewSession;
+ }
+
+ /*
+ * Block for a while.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ if (fWait)
+ rc2 = RTSemEventMultiWait(g_hVMInfoEvent, g_cMsVMInfoInterval);
+ if (*pfShutdown)
+ break;
+ if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
+ {
+ VGSvcError("RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
+ rc = rc2;
+ break;
+ }
+ else if (RT_LIKELY(RT_SUCCESS(rc2)))
+ {
+ /* Reset event semaphore if it got triggered. */
+ rc2 = RTSemEventMultiReset(g_hVMInfoEvent);
+ if (RT_FAILURE(rc2))
+ rc2 = VGSvcError("RTSemEventMultiReset failed; rc2=%Rrc\n", rc2);
+ }
+ }
+
+#ifdef RT_OS_WINDOWS
+ if (g_pfnWSACleanup)
+ g_pfnWSACleanup();
+#endif
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbsvcVMInfoStop(void)
+{
+ RTSemEventMultiSignal(g_hVMInfoEvent);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vbsvcVMInfoTerm(void)
+{
+ if (g_hVMInfoEvent != NIL_RTSEMEVENTMULTI)
+ {
+ /** @todo temporary solution: Zap all values which are not valid
+ * anymore when VM goes down (reboot/shutdown ). Needs to
+ * be replaced with "temporary properties" later.
+ *
+ * One idea is to introduce a (HGCM-)session guest property
+ * flag meaning that a guest property is only valid as long
+ * as the HGCM session isn't closed (e.g. guest application
+ * terminates). [don't remove till implemented]
+ */
+ /** @todo r=bird: Drop the VbglR3GuestPropDelSet call here and use the cache
+ * since it remembers what we've written. */
+ /* Delete the "../Net" branch. */
+ const char *apszPat[1] = { "/VirtualBox/GuestInfo/Net/*" };
+ int rc = VbglR3GuestPropDelSet(g_uVMInfoGuestPropSvcClientID, &apszPat[0], RT_ELEMENTS(apszPat));
+
+ /* Destroy LA client info. */
+ vgsvcFreeLAClientInfo(&g_LAClientInfo);
+
+ /* Destroy property cache. */
+ VGSvcPropCacheDestroy(&g_VMInfoPropCache);
+
+ /* Disconnect from guest properties service. */
+ rc = VbglR3GuestPropDisconnect(g_uVMInfoGuestPropSvcClientID);
+ if (RT_FAILURE(rc))
+ VGSvcError("Failed to disconnect from guest property service! Error: %Rrc\n", rc);
+ g_uVMInfoGuestPropSvcClientID = 0;
+
+ RTSemEventMultiDestroy(g_hVMInfoEvent);
+ g_hVMInfoEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'vminfo' service description.
+ */
+VBOXSERVICE g_VMInfo =
+{
+ /* pszName. */
+ "vminfo",
+ /* pszDescription. */
+ "Virtual Machine Information",
+ /* pszUsage. */
+ " [--vminfo-interval <ms>] [--vminfo-user-idle-threshold <ms>]"
+ ,
+ /* pszOptions. */
+ " --vminfo-interval Specifies the interval at which to retrieve the\n"
+ " VM information. The default is 10000 ms.\n"
+ " --vminfo-user-idle-threshold <ms>\n"
+ " Specifies the user idle threshold (in ms) for\n"
+ " considering a guest user as being idle. The default\n"
+ " is 5000 (5 seconds).\n"
+ ,
+ /* methods */
+ vbsvcVMInfoPreInit,
+ vbsvcVMInfoOption,
+ vbsvcVMInfoInit,
+ vbsvcVMInfoWorker,
+ vbsvcVMInfoStop,
+ vbsvcVMInfoTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h
new file mode 100644
index 00000000..10e380be
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h
@@ -0,0 +1,42 @@
+/* $Id: VBoxServiceVMInfo.h $ */
+/** @file
+ * VBoxServiceVMInfo.h - Internal VM info definitions.
+ */
+
+/*
+ * Copyright (C) 2013-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 GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+extern int VGSvcUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain,
+ const char *pszKey, const char *pszValueFormat, ...);
+
+
+extern uint32_t g_uVMInfoUserIdleThresholdMS;
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk b/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk
new file mode 100644
index 00000000..9a92d243
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk
@@ -0,0 +1,42 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for VBoxServicec test cases.
+#
+
+#
+# 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
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# tstUserInfo
+#
+PROGRAMS.win += tstUserInfo
+tstUserInfo_TEMPLATE = VBoxGuestR3Exe
+tstUserInfo_SOURCES = \
+ tstUserInfo.cpp
+tstUserInfo_VBOX_IMPORT_CHECKER.win.x86 = xp
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp b/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp
new file mode 100644
index 00000000..3fa93638
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp
@@ -0,0 +1,87 @@
+/* $Id: tstUserInfo.cpp $ */
+/** @file
+ * Test case for correct user environment.
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+# include <iprt/win/shlobj.h>
+#endif
+
+#include <iprt/initterm.h>
+#include <iprt/path.h>
+#include <iprt/env.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <VBox/log.h>
+#include <VBox/version.h>
+#include <VBox/VBoxGuestLib.h>
+
+
+int main()
+{
+ /*
+ * Init globals and such.
+ */
+ RTR3InitExeNoArguments(0);
+
+ int rc = VbglR3Init();
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("VbglR3Init failed with rc=%Rrc.\n", rc);
+ return -1;
+ }
+#ifdef RT_OS_WINDOWS
+ WCHAR wszPath[MAX_PATH];
+ HRESULT hRes = SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, wszPath);
+
+ if (SUCCEEDED(hRes))
+ {
+ RTPrintf("SHGetFolderPathW (CSIDL_APPDATA) = %ls\n", wszPath);
+ hRes = SHGetFolderPathW(0, CSIDL_PERSONAL, 0, 0, wszPath);
+ if (SUCCEEDED(hRes))
+ {
+ RTPrintf("SHGetFolderPathW (CSIDL_PERSONAL) = %ls\n", wszPath);
+ }
+ else
+ RTPrintf("SHGetFolderPathW (CSIDL_PERSONAL) returned error: 0x%x\n", hRes);
+ }
+ else
+ RTPrintf("SHGetFolderPathW (CSIDL_APPDATA) returned error: 0x%x\n", hRes);
+
+ if (FAILED(hRes))
+ rc = RTErrConvertFromWin32(hRes);
+
+ /* Dump env bits. */
+ RTPrintf("Environment:\n\n");
+ RTPrintf("APPDATA = %s\n", RTEnvGet("APPDATA"));
+#endif
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+