From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Additions/common/VBoxService/Makefile.kmk | 220 ++ .../common/VBoxService/VBoxService-os2.def | 33 + .../common/VBoxService/VBoxService-win.cpp | 670 +++++ .../Additions/common/VBoxService/VBoxService.cpp | 1311 +++++++++ .../common/VBoxService/VBoxServiceAutoMount.cpp | 2194 +++++++++++++++ .../common/VBoxService/VBoxServiceBalloon.cpp | 457 ++++ .../VBoxService/VBoxServiceClipboard-os2.cpp | 1140 ++++++++ .../common/VBoxService/VBoxServiceControl.cpp | 629 +++++ .../common/VBoxService/VBoxServiceControl.h | 297 ++ .../VBoxService/VBoxServiceControlProcess.cpp | 2201 +++++++++++++++ .../VBoxService/VBoxServiceControlSession.cpp | 2886 ++++++++++++++++++++ .../common/VBoxService/VBoxServiceCpuHotPlug.cpp | 672 +++++ .../common/VBoxService/VBoxServiceInternal.h | 284 ++ .../common/VBoxService/VBoxServicePageSharing.cpp | 803 ++++++ .../common/VBoxService/VBoxServicePropCache.cpp | 439 +++ .../common/VBoxService/VBoxServicePropCache.h | 66 + .../common/VBoxService/VBoxServiceResource-win.h | 37 + .../common/VBoxService/VBoxServiceStats.cpp | 747 +++++ .../common/VBoxService/VBoxServiceTimeSync.cpp | 807 ++++++ .../common/VBoxService/VBoxServiceToolBox.cpp | 1769 ++++++++++++ .../common/VBoxService/VBoxServiceToolBox.h | 42 + .../common/VBoxService/VBoxServiceUtils.cpp | 324 +++ .../common/VBoxService/VBoxServiceUtils.h | 49 + .../common/VBoxService/VBoxServiceVMInfo-win.cpp | 1363 +++++++++ .../common/VBoxService/VBoxServiceVMInfo.cpp | 1707 ++++++++++++ .../common/VBoxService/VBoxServiceVMInfo.h | 42 + .../common/VBoxService/testcase/Makefile.kmk | 42 + .../common/VBoxService/testcase/tstUserInfo.cpp | 87 + 28 files changed, 21318 insertions(+) create mode 100644 src/VBox/Additions/common/VBoxService/Makefile.kmk create mode 100644 src/VBox/Additions/common/VBoxService/VBoxService-os2.def create mode 100644 src/VBox/Additions/common/VBoxService/VBoxService-win.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxService.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceControl.h create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp create mode 100644 src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h create mode 100644 src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk create mode 100644 src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp (limited to 'src/VBox/Additions/common/VBoxService') 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 . +# +# 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 . +; +; 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include /* For querying OS version. */ +#include + +#define WIN32_NO_STATUS +#include +#include +#undef WIN32_NO_STATUS +#include +#include +#include +#include +#define _NTDEF_ +#include + +#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 . + * + * 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 +#endif +#ifndef RT_OS_WINDOWS +# include +# include +# ifdef RT_OS_OS2 +# define pthread_sigmask sigprocmask +# endif +#endif +#ifdef RT_OS_FREEBSD +# include +#endif + +#include +#include "product-generated.h" + +#include +#include +#include +#include +#ifdef DEBUG +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 ]\n" + " [-p|--pidfile ] [-i|--interval ]\n" + " [--disable-] [--enable-]\n" + " [--only-] [-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 Enables logging to a file.\n" + " -p | --pidfile 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 : "", 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#else +# include +# include +# include +# ifdef RT_OS_SOLARIS +# include +# include +# include +# elif defined(RT_OS_LINUX) +# include +# include +# include +RT_C_DECLS_BEGIN +# include "../../linux/sharedfolders/vbsfmount.h" +RT_C_DECLS_END +# else +# error "Port me!" +# endif +# include +#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 : "", 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" + +#ifdef RT_OS_LINUX +# include +# include +# 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 . + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 ]" + , + /* 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 . + * + * 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 +#include +#include + +#include +#include +#include + + +/** + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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] : ""); + + 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* 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 : "", 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 : "", + pStartupInfo->cEnvVars ? pStartupInfo->pszEnv : "", + 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 : "", 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "VBoxServiceInternal.h" + +#ifdef RT_OS_LINUX +# include +# include /* 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: */ + { true, "LNXSYSTM:*" } +}; + +/** Possible combinations of all path components for level 2. */ +static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl2[] = +{ + /** device: */ + { true, "device:*" }, + /** LNXSYBUS: */ + { true, "LNXSYBUS:*" } +}; + +/** Possible combinations of all path components for level 3 */ +static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl3[] = +{ + /** ACPI0004: */ + { true, "ACPI0004:*" } +}; + +/** Possible combinations of all path components for level 4 */ +static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl4[] = +{ + /** LNXCPU: */ + { true, "LNXCPU:*" }, + /** ACPI_CPU: */ + { 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 . + * + * 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 +#endif + +#include +#include +#include /* RTPATH_MAX */ +#include + +#include +#include + + +#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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef RT_OS_WINDOWS +#include +# include +# include +# include +#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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include + +#include +#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 . + * + * 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 . + * + * 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 . + * + * 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 +# include +# include + +#elif defined(RT_OS_LINUX) +# include +# include +# include + +#elif defined(RT_OS_SOLARIS) +# include +# include +# include +#else +/** @todo port me. */ + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* For VMMDevReportGuestStats and indirectly VbglR3StatReport. */ +#include + +#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 . + * + * 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 +#else +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#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 ] [--timesync-min-adjust ]\n" + " [--timesync-latency-factor ] [--timesync-max-latency ]\n" + " [--timesync-set-threshold ]\n" + " [--timesync-set-start|--timesync-no-set-start]\n" + " [--timesync-set-on-restore|--timesync-no-set-on-restore]\n" + " [--timesync-verbosity ]" + , + /* 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef RT_OS_WINDOWS +# include /* need umask */ +#endif + +#include +#include + +#include + +#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_ [] \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 [] ...\n" + " vbox_ls [] [--dereference|-L] [-l] [-R]\n" + " [--verbose|-v] [...]\n" + " vbox_rm [] [-r|-R] ...\n" + " vbox_mktemp [] [--directory|-d] [--mode|-m ]\n" + " [--secure|-s] [--tmpdir|-t ]