diff options
Diffstat (limited to 'src/VBox/Additions/common/VBoxService')
28 files changed, 21325 insertions, 0 deletions
diff --git a/src/VBox/Additions/common/VBoxService/Makefile.kmk b/src/VBox/Additions/common/VBoxService/Makefile.kmk new file mode 100644 index 00000000..8e166f2c --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/Makefile.kmk @@ -0,0 +1,223 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Cross Platform Guest Addition Services. +# + +# +# Copyright (C) 2007-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Incldue testcases. +# +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# Target lists. +# +PROGRAMS += VBoxService + +# 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 +# +if "$(KBUILD_TARGET)" == "win" || defined(VBOX_WITH_MASOCHISTIC_WARNINGS) ## @todo use VBoxGuestR3Exe everywhere +VBoxService_TEMPLATE = VBoxGuestR3Exe +else +VBoxService_TEMPLATE = NewVBoxGuestR3Exe +endif + +# Define features to be activate. +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,) + +# Import global defines. +VBoxService_DEFS += \ + $(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..b2c5f814 --- /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-2022 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; SPDX-License-Identifier: GPL-3.0-only +; + + +NAME VBoxSvc +DESCRIPTION 'VirtualBox Guest Additions Service for OS/2.' +CODE SHARED +DATA MULTIPLE NONSHARED + diff --git a/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp b/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp new file mode 100644 index 00000000..5a32124f --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/ldr.h> +#include <iprt/system.h> /* For querying OS version. */ +#include <VBox/VBoxGuestLib.h> + +#define WIN32_NO_STATUS +#include <iprt/win/ws2tcpip.h> +#include <iprt/win/winsock2.h> +#undef WIN32_NO_STATUS +#include <iprt/nt/nt-and-windows.h> +#include <iprt/win/iphlpapi.h> +#include <aclapi.h> +#include <tlhelp32.h> +#define _NTDEF_ +#include <Ntsecapi.h> + +#include "VBoxServiceInternal.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void WINAPI vgsvcWinMain(DWORD argc, LPTSTR *argv); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static DWORD g_dwWinServiceLastStatus = 0; +SERVICE_STATUS_HANDLE g_hWinServiceStatus = NULL; +/** The semaphore for the dummy Windows service. */ +static RTSEMEVENT g_WindowsEvent = NIL_RTSEMEVENT; + +static SERVICE_TABLE_ENTRY const g_aServiceTable[] = +{ + { VBOXSERVICE_NAME, vgsvcWinMain }, + { NULL, NULL} +}; + +/** @name APIs from ADVAPI32.DLL. + * @{ */ +decltype(RegisterServiceCtrlHandlerExA) *g_pfnRegisterServiceCtrlHandlerExA; /**< W2K+ */ +decltype(ChangeServiceConfig2A) *g_pfnChangeServiceConfig2A; /**< W2K+ */ +decltype(GetNamedSecurityInfoA) *g_pfnGetNamedSecurityInfoA; /**< NT4+ */ +decltype(SetEntriesInAclA) *g_pfnSetEntriesInAclA; /**< NT4+ */ +decltype(SetNamedSecurityInfoA) *g_pfnSetNamedSecurityInfoA; /**< NT4+ */ +decltype(LsaNtStatusToWinError) *g_pfnLsaNtStatusToWinError; /**< NT3.51+ */ +/** @} */ + +/** @name API from KERNEL32.DLL + * @{ */ +decltype(CreateToolhelp32Snapshot) *g_pfnCreateToolhelp32Snapshot; /**< W2K+, but Geoff says NT4. Hmm. */ +decltype(Process32First) *g_pfnProcess32First; /**< W2K+, but Geoff says NT4. Hmm. */ +decltype(Process32Next) *g_pfnProcess32Next; /**< W2K+, but Geoff says NT4. Hmm. */ +decltype(Module32First) *g_pfnModule32First; /**< W2K+, but Geoff says NT4. Hmm. */ +decltype(Module32Next) *g_pfnModule32Next; /**< W2K+, but Geoff says NT4. Hmm. */ +decltype(GetSystemTimeAdjustment) *g_pfnGetSystemTimeAdjustment; /**< NT 3.50+ */ +decltype(SetSystemTimeAdjustment) *g_pfnSetSystemTimeAdjustment; /**< NT 3.50+ */ +/** @} */ + +/** @name API from NTDLL.DLL + * @{ */ +decltype(ZwQuerySystemInformation) *g_pfnZwQuerySystemInformation; /**< NT4 (where as NtQuerySystemInformation is W2K). */ +/** @} */ + +/** @name API from IPHLPAPI.DLL + * @{ */ +decltype(GetAdaptersInfo) *g_pfnGetAdaptersInfo; +/** @} */ + +/** @name APIs from WS2_32.DLL + * @note WSAIoctl is not present in wsock32.dll, so no point in trying the + * fallback here. + * @{ */ +decltype(WSAStartup) *g_pfnWSAStartup; +decltype(WSACleanup) *g_pfnWSACleanup; +decltype(WSASocketA) *g_pfnWSASocketA; +decltype(WSAIoctl) *g_pfnWSAIoctl; +decltype(WSAGetLastError) *g_pfnWSAGetLastError; +decltype(closesocket) *g_pfnclosesocket; +decltype(inet_ntoa) *g_pfninet_ntoa; + +/** @} */ + +/** + * Resolve APIs not present on older windows versions. + */ +void VGSvcWinResolveApis(void) +{ + RTLDRMOD hLdrMod; +#define RESOLVE_SYMBOL(a_fn) do { RT_CONCAT(g_pfn, a_fn) = (decltype(a_fn) *)RTLdrGetFunction(hLdrMod, #a_fn); } while (0) + + /* From ADVAPI32.DLL: */ + int rc = RTLdrLoadSystem("advapi32.dll", true /*fNoUnload*/, &hLdrMod); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + RESOLVE_SYMBOL(RegisterServiceCtrlHandlerExA); + RESOLVE_SYMBOL(ChangeServiceConfig2A); + RESOLVE_SYMBOL(GetNamedSecurityInfoA); + RESOLVE_SYMBOL(SetEntriesInAclA); + RESOLVE_SYMBOL(SetNamedSecurityInfoA); + RESOLVE_SYMBOL(LsaNtStatusToWinError); + RTLdrClose(hLdrMod); + } + + /* From KERNEL32.DLL: */ + rc = RTLdrLoadSystem("kernel32.dll", true /*fNoUnload*/, &hLdrMod); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + RESOLVE_SYMBOL(CreateToolhelp32Snapshot); + RESOLVE_SYMBOL(Process32First); + RESOLVE_SYMBOL(Process32Next); + RESOLVE_SYMBOL(Module32First); + RESOLVE_SYMBOL(Module32Next); + RESOLVE_SYMBOL(GetSystemTimeAdjustment); + RESOLVE_SYMBOL(SetSystemTimeAdjustment); + RTLdrClose(hLdrMod); + } + + /* From NTDLL.DLL: */ + rc = RTLdrLoadSystem("ntdll.dll", true /*fNoUnload*/, &hLdrMod); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + RESOLVE_SYMBOL(ZwQuerySystemInformation); + RTLdrClose(hLdrMod); + } + + /* From IPHLPAPI.DLL: */ + rc = RTLdrLoadSystem("iphlpapi.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_SUCCESS(rc)) + { + RESOLVE_SYMBOL(GetAdaptersInfo); + RTLdrClose(hLdrMod); + } + + /* From WS2_32.DLL: */ + rc = RTLdrLoadSystem("ws2_32.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_SUCCESS(rc)) + { + RESOLVE_SYMBOL(WSAStartup); + RESOLVE_SYMBOL(WSACleanup); + RESOLVE_SYMBOL(WSASocketA); + RESOLVE_SYMBOL(WSAIoctl); + RESOLVE_SYMBOL(WSAGetLastError); + RESOLVE_SYMBOL(closesocket); + RESOLVE_SYMBOL(inet_ntoa); + RTLdrClose(hLdrMod); + } +} + + +/** + * @todo Add full unicode support. + * @todo Add event log capabilities / check return values. + */ +static int vgsvcWinAddAceToObjectsSecurityDescriptor(LPTSTR pszObjName, SE_OBJECT_TYPE enmObjectType, const char *pszTrustee, + TRUSTEE_FORM enmTrusteeForm, DWORD dwAccessRights, ACCESS_MODE fAccessMode, + DWORD dwInheritance) +{ + int rc; + if ( g_pfnGetNamedSecurityInfoA + && g_pfnSetEntriesInAclA + && g_pfnSetNamedSecurityInfoA) + { + /* Get a pointer to the existing DACL. */ + PSECURITY_DESCRIPTOR pSD = NULL; + PACL pOldDACL = NULL; + DWORD rcWin = g_pfnGetNamedSecurityInfoA(pszObjName, enmObjectType, DACL_SECURITY_INFORMATION, + NULL, NULL, &pOldDACL, NULL, &pSD); + if (rcWin == ERROR_SUCCESS) + { + /* Initialize an EXPLICIT_ACCESS structure for the new ACE. */ + EXPLICIT_ACCESSA ExplicitAccess; + RT_ZERO(ExplicitAccess); + ExplicitAccess.grfAccessPermissions = dwAccessRights; + ExplicitAccess.grfAccessMode = fAccessMode; + ExplicitAccess.grfInheritance = dwInheritance; + ExplicitAccess.Trustee.TrusteeForm = enmTrusteeForm; + ExplicitAccess.Trustee.ptstrName = (char *)pszTrustee; + + /* Create a new ACL that merges the new ACE into the existing DACL. */ + PACL pNewDACL = NULL; + rcWin = g_pfnSetEntriesInAclA(1, &ExplicitAccess, pOldDACL, &pNewDACL); + if (rcWin == ERROR_SUCCESS) + { + /* Attach the new ACL as the object's DACL. */ + rcWin = g_pfnSetNamedSecurityInfoA(pszObjName, enmObjectType, DACL_SECURITY_INFORMATION, + NULL, NULL, pNewDACL, NULL); + if (rcWin == ERROR_SUCCESS) + rc = VINF_SUCCESS; + else + { + VGSvcError("AddAceToObjectsSecurityDescriptor: SetNamedSecurityInfo: Error %u\n", rcWin); + rc = RTErrConvertFromWin32(rcWin); + } + if (pNewDACL) + LocalFree(pNewDACL); + } + else + { + VGSvcError("AddAceToObjectsSecurityDescriptor: SetEntriesInAcl: Error %u\n", rcWin); + rc = RTErrConvertFromWin32(rcWin); + } + if (pSD) + LocalFree(pSD); + } + else + { + if (rcWin == ERROR_FILE_NOT_FOUND) + VGSvcError("AddAceToObjectsSecurityDescriptor: Object not found/installed: %s\n", pszObjName); + else + VGSvcError("AddAceToObjectsSecurityDescriptor: GetNamedSecurityInfo: Error %u\n", rcWin); + rc = RTErrConvertFromWin32(rcWin); + } + } + else + rc = VINF_SUCCESS; /* fake it */ + return rc; +} + + +/** Reports our current status to the SCM. */ +static BOOL vgsvcWinSetStatus(DWORD dwStatus, DWORD dwCheckPoint) +{ + if (g_hWinServiceStatus == NULL) /* Program could be in testing mode, so no service environment available. */ + return FALSE; + + VGSvcVerbose(2, "Setting service status to: %ld\n", dwStatus); + g_dwWinServiceLastStatus = dwStatus; + + SERVICE_STATUS ss; + RT_ZERO(ss); + + ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ss.dwCurrentState = dwStatus; + /* Don't accept controls when in start pending state. */ + if (ss.dwCurrentState != SERVICE_START_PENDING) + { + ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; + + /* Don't use SERVICE_ACCEPT_SESSIONCHANGE on Windows 2000 or earlier. This makes SCM angry. */ + char szOSVersion[32]; + int rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOSVersion, sizeof(szOSVersion)); + if (RT_SUCCESS(rc)) + { + if (RTStrVersionCompare(szOSVersion, "5.1") >= 0) + ss.dwControlsAccepted |= SERVICE_ACCEPT_SESSIONCHANGE; + } + else + VGSvcError("Error determining OS version, rc=%Rrc\n", rc); + } + + ss.dwWin32ExitCode = NO_ERROR; + ss.dwServiceSpecificExitCode = 0; /* Not used */ + ss.dwCheckPoint = dwCheckPoint; + ss.dwWaitHint = 3000; + + BOOL fStatusSet = SetServiceStatus(g_hWinServiceStatus, &ss); + if (!fStatusSet) + VGSvcError("Error reporting service status=%ld (controls=%x, checkpoint=%ld) to SCM: %ld\n", + dwStatus, ss.dwControlsAccepted, dwCheckPoint, GetLastError()); + return fStatusSet; +} + + +/** + * Reports SERVICE_STOP_PENDING to SCM. + * + * @param uCheckPoint Some number. + */ +void VGSvcWinSetStopPendingStatus(uint32_t uCheckPoint) +{ + vgsvcWinSetStatus(SERVICE_STOP_PENDING, uCheckPoint); +} + + +static RTEXITCODE vgsvcWinSetDesc(SC_HANDLE hService) +{ + /* On W2K+ there's ChangeServiceConfig2() which lets us set some fields + like a longer service description. */ + if (g_pfnChangeServiceConfig2A) + { + /** @todo On Vista+ SERVICE_DESCRIPTION also supports localized strings! */ + SERVICE_DESCRIPTION desc; + desc.lpDescription = VBOXSERVICE_DESCRIPTION; + if (!g_pfnChangeServiceConfig2A(hService, SERVICE_CONFIG_DESCRIPTION, &desc)) + { + VGSvcError("Cannot set the service description! Error: %ld\n", GetLastError()); + return RTEXITCODE_FAILURE; + } + } + return RTEXITCODE_SUCCESS; +} + + +/** + * Installs the service. + */ +RTEXITCODE VGSvcWinInstall(void) +{ + VGSvcVerbose(1, "Installing service ...\n"); + + TCHAR imagePath[MAX_PATH] = { 0 }; + GetModuleFileName(NULL, imagePath, sizeof(imagePath)); + + SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (hSCManager == NULL) + { + VGSvcError("Could not open SCM! Error: %ld\n", GetLastError()); + return RTEXITCODE_FAILURE; + } + + RTEXITCODE rc = RTEXITCODE_SUCCESS; + SC_HANDLE hService = CreateService(hSCManager, + VBOXSERVICE_NAME, VBOXSERVICE_FRIENDLY_NAME, + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, + SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, + imagePath, NULL, NULL, NULL, NULL, NULL); + if (hService != NULL) + VGSvcVerbose(0, "Service successfully installed!\n"); + else + { + DWORD dwErr = GetLastError(); + switch (dwErr) + { + case ERROR_SERVICE_EXISTS: + VGSvcVerbose(1, "Service already exists, just updating the service config.\n"); + hService = OpenService(hSCManager, VBOXSERVICE_NAME, SERVICE_ALL_ACCESS); + if (hService) + { + if (ChangeServiceConfig(hService, + SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + imagePath, + NULL, + NULL, + NULL, + NULL, + NULL, + VBOXSERVICE_FRIENDLY_NAME)) + VGSvcVerbose(1, "The service config has been successfully updated.\n"); + else + rc = VGSvcError("Could not change service config! Error: %ld\n", GetLastError()); + } + else + rc = VGSvcError("Could not open service! Error: %ld\n", GetLastError()); + break; + + default: + rc = VGSvcError("Could not create service! Error: %ld\n", dwErr); + break; + } + } + + if (rc == RTEXITCODE_SUCCESS) + rc = vgsvcWinSetDesc(hService); + + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); + return rc; +} + +/** + * Uninstalls the service. + */ +RTEXITCODE VGSvcWinUninstall(void) +{ + VGSvcVerbose(1, "Uninstalling service ...\n"); + + SC_HANDLE hSCManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS); + if (hSCManager == NULL) + { + VGSvcError("Could not open SCM! Error: %d\n", GetLastError()); + return RTEXITCODE_FAILURE; + } + + RTEXITCODE rcExit; + SC_HANDLE hService = OpenService(hSCManager, VBOXSERVICE_NAME, SERVICE_ALL_ACCESS ); + if (hService != NULL) + { + if (DeleteService(hService)) + { + /* + * ??? + */ + HKEY hKey = NULL; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Services\\EventLog\\System", + 0, + KEY_ALL_ACCESS, + &hKey) + == ERROR_SUCCESS) + { + RegDeleteKey(hKey, VBOXSERVICE_NAME); + RegCloseKey(hKey); + } + + VGSvcVerbose(0, "Service successfully uninstalled!\n"); + rcExit = RTEXITCODE_SUCCESS; + } + else + rcExit = VGSvcError("Could not remove service! Error: %d\n", GetLastError()); + CloseServiceHandle(hService); + } + else + rcExit = VGSvcError("Could not open service! Error: %d\n", GetLastError()); + CloseServiceHandle(hSCManager); + + return rcExit; +} + + +static int vgsvcWinStart(void) +{ + int rc = VINF_SUCCESS; + + /* + * Create a well-known SID for the "Builtin Users" group and modify the ACE + * for the shared folders miniport redirector DN (whatever DN means). + */ + PSID pBuiltinUsersSID = NULL; + SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_LOCAL_SID_AUTHORITY; + if (AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_LOCAL_RID, 0, 0, 0, 0, 0, 0, 0, &pBuiltinUsersSID)) + { + rc = vgsvcWinAddAceToObjectsSecurityDescriptor(TEXT("\\\\.\\VBoxMiniRdrDN"), SE_FILE_OBJECT, + (LPTSTR)pBuiltinUsersSID, TRUSTEE_IS_SID, + FILE_GENERIC_READ | FILE_GENERIC_WRITE, SET_ACCESS, NO_INHERITANCE); + /* If we don't find our "VBoxMiniRdrDN" (for Shared Folders) object above, + don't report an error; it just might be not installed. Otherwise this + would cause the SCM to hang on starting up the service. */ + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + rc = VINF_SUCCESS; + + FreeSid(pBuiltinUsersSID); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + if (RT_SUCCESS(rc)) + { + /* + * Start the service. + */ + vgsvcWinSetStatus(SERVICE_START_PENDING, 0); + + rc = VGSvcStartServices(); + if (RT_SUCCESS(rc)) + { + vgsvcWinSetStatus(SERVICE_RUNNING, 0); + VGSvcMainWait(); + } + else + { + vgsvcWinSetStatus(SERVICE_STOPPED, 0); +#if 0 /** @todo r=bird: Enable this if SERVICE_CONTROL_STOP isn't triggered automatically */ + VGSvcStopServices(); +#endif + } + } + else + vgsvcWinSetStatus(SERVICE_STOPPED, 0); + + if (RT_FAILURE(rc)) + VGSvcError("Service failed to start with rc=%Rrc!\n", rc); + + return rc; +} + + +/** + * Call StartServiceCtrlDispatcher. + * + * The main() thread invokes this when not started in foreground mode. It + * won't return till the service is being shutdown (unless start up fails). + * + * @returns RTEXITCODE_SUCCESS on normal return after service shutdown. + * Something else on failure, error will have been reported. + */ +RTEXITCODE VGSvcWinEnterCtrlDispatcher(void) +{ + if (!StartServiceCtrlDispatcher(&g_aServiceTable[0])) + return VGSvcError("StartServiceCtrlDispatcher: %u. Please start %s with option -f (foreground)!\n", + GetLastError(), g_pszProgName); + return RTEXITCODE_SUCCESS; +} + + +/** + * Event code to description. + * + * @returns String. + * @param dwEvent The event code. + */ +static const char *vgsvcWTSStateToString(DWORD dwEvent) +{ + switch (dwEvent) + { + case WTS_CONSOLE_CONNECT: return "A session was connected to the console terminal"; + case WTS_CONSOLE_DISCONNECT: return "A session was disconnected from the console terminal"; + case WTS_REMOTE_CONNECT: return "A session connected to the remote terminal"; + case WTS_REMOTE_DISCONNECT: return "A session was disconnected from the remote terminal"; + case WTS_SESSION_LOGON: return "A user has logged on to a session"; + case WTS_SESSION_LOGOFF: return "A user has logged off the session"; + case WTS_SESSION_LOCK: return "A session has been locked"; + case WTS_SESSION_UNLOCK: return "A session has been unlocked"; + case WTS_SESSION_REMOTE_CONTROL: return "A session has changed its remote controlled status"; +#ifdef WTS_SESSION_CREATE + case WTS_SESSION_CREATE: return "A session has been created"; +#endif +#ifdef WTS_SESSION_TERMINATE + case WTS_SESSION_TERMINATE: return "The session has been terminated"; +#endif + default: return "Uknonwn state"; + } +} + + +/** + * Common control handler. + * + * @returns Return code for NT5+. + * @param dwControl The control code. + */ +static DWORD vgsvcWinCtrlHandlerCommon(DWORD dwControl) +{ + DWORD rcRet = NO_ERROR; + switch (dwControl) + { + case SERVICE_CONTROL_INTERROGATE: + vgsvcWinSetStatus(g_dwWinServiceLastStatus, 0); + break; + + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + { + vgsvcWinSetStatus(SERVICE_STOP_PENDING, 0); + + int rc2 = VGSvcStopServices(); + if (RT_FAILURE(rc2)) + rcRet = ERROR_GEN_FAILURE; + else + { + rc2 = VGSvcReportStatus(VBoxGuestFacilityStatus_Terminated); + AssertRC(rc2); + } + + vgsvcWinSetStatus(SERVICE_STOPPED, 0); + break; + } + + default: + VGSvcVerbose(1, "Control handler: Function not implemented: %#x\n", dwControl); + rcRet = ERROR_CALL_NOT_IMPLEMENTED; + break; + } + + return rcRet; +} + + +/** + * Callback registered by RegisterServiceCtrlHandler on NT4 and earlier. + */ +static VOID WINAPI vgsvcWinCtrlHandlerNt4(DWORD dwControl) RT_NOTHROW_DEF +{ + VGSvcVerbose(2, "Control handler (NT4): dwControl=%#x\n", dwControl); + vgsvcWinCtrlHandlerCommon(dwControl); +} + + +/** + * Callback registered by RegisterServiceCtrlHandler on NT5 and later. + */ +static DWORD WINAPI +vgsvcWinCtrlHandlerNt5Plus(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) RT_NOTHROW_DEF +{ + VGSvcVerbose(2, "Control handler: dwControl=%#x, dwEventType=%#x\n", dwControl, dwEventType); + RT_NOREF1(lpContext); + + switch (dwControl) + { + default: + return vgsvcWinCtrlHandlerCommon(dwControl); + + case SERVICE_CONTROL_SESSIONCHANGE: /* Only Windows 2000 and up. */ + { + AssertPtr(lpEventData); + PWTSSESSION_NOTIFICATION pNotify = (PWTSSESSION_NOTIFICATION)lpEventData; + Assert(pNotify->cbSize == sizeof(WTSSESSION_NOTIFICATION)); + + VGSvcVerbose(1, "Control handler: %s (Session=%ld, Event=%#x)\n", + vgsvcWTSStateToString(dwEventType), pNotify->dwSessionId, dwEventType); + + /* Handle all events, regardless of dwEventType. */ + int rc2 = VGSvcVMInfoSignal(); + AssertRC(rc2); + + return NO_ERROR; + } + } +} + + +static void WINAPI vgsvcWinMain(DWORD argc, LPTSTR *argv) +{ + RT_NOREF2(argc, argv); + VGSvcVerbose(2, "Registering service control handler ...\n"); + if (g_pfnRegisterServiceCtrlHandlerExA) + g_hWinServiceStatus = g_pfnRegisterServiceCtrlHandlerExA(VBOXSERVICE_NAME, vgsvcWinCtrlHandlerNt5Plus, NULL); + else + g_hWinServiceStatus = RegisterServiceCtrlHandlerA(VBOXSERVICE_NAME, vgsvcWinCtrlHandlerNt4); + if (g_hWinServiceStatus != NULL) + { + VGSvcVerbose(2, "Service control handler registered.\n"); + vgsvcWinStart(); + } + else + { + DWORD dwErr = GetLastError(); + switch (dwErr) + { + case ERROR_INVALID_NAME: + VGSvcError("Invalid service name!\n"); + break; + case ERROR_SERVICE_DOES_NOT_EXIST: + VGSvcError("Service does not exist!\n"); + break; + default: + VGSvcError("Could not register service control handle! Error: %ld\n", dwErr); + break; + } + } +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxService.cpp b/src/VBox/Additions/common/VBoxService/VBoxService.cpp new file mode 100644 index 00000000..14e51ad5 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc VBoxService + * + * VBoxService is a root daemon for implementing guest additions features. + * + * It is structured as one binary that contains many sub-services. The reason + * for this is partially historical and partially practical. The practical + * reason is that the VBoxService binary is typically statically linked, at + * least with IPRT and the guest library, so we save quite a lot of space having + * on single binary instead individual binaries for each sub-service and their + * helpers (currently up to 9 subservices and 8 helpers). The historical is + * simply that it started its life on OS/2 dreaming of conquring Windows next, + * so it kind of felt natural to have it all in one binary. + * + * Even if it's structured as a single binary, it is possible, by using command + * line options, to start each subservice as an individual process. + * + * Subservices: + * - @subpage pg_vgsvc_timesync "Time Synchronization" + * - @subpage pg_vgsvc_vminfo "VM Information" + * - @subpage pg_vgsvc_vmstats "VM Statistics" + * - @subpage pg_vgsvc_gstctrl "Guest Control" + * - @subpage pg_vgsvc_pagesharing "Page Sharing" + * - @subpage pg_vgsvc_memballoon "Memory Balooning" + * - @subpage pg_vgsvc_cpuhotplug "CPU Hot-Plugging" + * - @subpage pg_vgsvc_automount "Shared Folder Automounting" + * - @subpage pg_vgsvc_clipboard "Clipboard (OS/2 only)" + * + * Now, since the service predates a lot of stuff, including RTGetOpt, we're + * currently doing our own version of argument parsing here, which is kind of + * stupid. That will hopefully be cleaned up eventually. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +/** @todo LOG_GROUP*/ +#ifndef _MSC_VER +# include <unistd.h> +#endif +#ifndef RT_OS_WINDOWS +# include <errno.h> +# include <signal.h> +# ifdef RT_OS_OS2 +# define pthread_sigmask sigprocmask +# endif +#endif +#ifdef RT_OS_FREEBSD +# include <pthread.h> +#endif + +#include <package-generated.h> +#include "product-generated.h" + +#include <iprt/asm.h> +#include <iprt/buildconfig.h> +#include <iprt/initterm.h> +#include <iprt/file.h> +#ifdef DEBUG +# include <iprt/memtracker.h> +#endif +#include <iprt/env.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/system.h> +#include <iprt/thread.h> + +#include <VBox/err.h> +#include <VBox/log.h> + +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#ifdef VBOX_WITH_VBOXSERVICE_CONTROL +# include "VBoxServiceControl.h" +#endif +#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX +# include "VBoxServiceToolBox.h" +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The program name (derived from argv[0]). */ +char *g_pszProgName = (char *)""; +/** The current verbosity level. */ +unsigned g_cVerbosity = 0; +char g_szLogFile[RTPATH_MAX + 128] = ""; +char g_szPidFile[RTPATH_MAX] = ""; +/** Logging parameters. */ +/** @todo Make this configurable later. */ +static PRTLOGGER g_pLoggerRelease = NULL; +static uint32_t g_cHistory = 10; /* Enable log rotation, 10 files. */ +static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; /* Max 1 day per file. */ +static uint64_t g_uHistoryFileSize = 100 * _1M; /* Max 100MB per file. */ +/** Critical section for (debug) logging. */ +#ifdef DEBUG + RTCRITSECT g_csLog; +#endif +/** The default service interval (the -i | --interval) option). */ +uint32_t g_DefaultInterval = 0; +#ifdef RT_OS_WINDOWS +/** Signal shutdown to the Windows service thread. */ +static bool volatile g_fWindowsServiceShutdown; +/** Event the Windows service thread waits for shutdown. */ +static RTSEMEVENT g_hEvtWindowsService; +#endif + +/** + * The details of the services that has been compiled in. + */ +static struct +{ + /** Pointer to the service descriptor. */ + PCVBOXSERVICE pDesc; + /** The worker thread. NIL_RTTHREAD if it's the main thread. */ + RTTHREAD Thread; + /** Whether Pre-init was called. */ + bool fPreInited; + /** Shutdown indicator. */ + bool volatile fShutdown; + /** Indicator set by the service thread exiting. */ + bool volatile fStopped; + /** Whether the service was started or not. */ + bool fStarted; + /** Whether the service is enabled or not. */ + bool fEnabled; +} g_aServices[] = +{ +#ifdef VBOX_WITH_VBOXSERVICE_CONTROL + { &g_Control, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_VBOXSERVICE_TIMESYNC + { &g_TimeSync, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_VBOXSERVICE_CLIPBOARD + { &g_Clipboard, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_VBOXSERVICE_VMINFO + { &g_VMInfo, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_VBOXSERVICE_CPUHOTPLUG + { &g_CpuHotPlug, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_VBOXSERVICE_MANAGEMENT +# ifdef VBOX_WITH_MEMBALLOON + { &g_MemBalloon, NIL_RTTHREAD, false, false, false, false, true }, +# endif + { &g_VMStatistics, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#if defined(VBOX_WITH_VBOXSERVICE_PAGE_SHARING) + { &g_PageSharing, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS + { &g_AutoMount, NIL_RTTHREAD, false, false, false, false, true }, +#endif +}; + + +/* + * Default call-backs for services which do not need special behaviour. + */ + +/** + * @interface_method_impl{VBOXSERVICE,pfnPreInit, Default Implementation} + */ +DECLCALLBACK(int) VGSvcDefaultPreInit(void) +{ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption, Default Implementation} + */ +DECLCALLBACK(int) VGSvcDefaultOption(const char **ppszShort, int argc, + char **argv, int *pi) +{ + NOREF(ppszShort); + NOREF(argc); + NOREF(argv); + NOREF(pi); + + return -1; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit, Default Implementation} + */ +DECLCALLBACK(int) VGSvcDefaultInit(void) +{ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm, Default Implementation} + */ +DECLCALLBACK(void) VGSvcDefaultTerm(void) +{ + return; +} + + +/** + * @callback_method_impl{FNRTLOGPHASE, Release logger callback} + */ +static DECLCALLBACK(void) vgsvcLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog) +{ + /* Some introductory information. */ + static RTTIMESPEC s_TimeSpec; + char szTmp[256]; + if (enmPhase == RTLOGPHASE_BEGIN) + RTTimeNow(&s_TimeSpec); + RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp)); + + switch (enmPhase) + { + case RTLOGPHASE_BEGIN: + { + pfnLog(pLoggerRelease, + "VBoxService %s r%s (verbosity: %u) %s (%s %s) release log\n" + "Log opened %s\n", + RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, VBOX_BUILD_TARGET, + __DATE__, __TIME__, szTmp); + + int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Product: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Release: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Version: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Service Pack: %s\n", szTmp); + + /* the package type is interesting for Linux distributions */ + char szExecName[RTPATH_MAX]; + char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName)); + pfnLog(pLoggerRelease, + "Executable: %s\n" + "Process ID: %u\n" + "Package type: %s" +#ifdef VBOX_OSE + " (OSE)" +#endif + "\n", + pszExecName ? pszExecName : "unknown", + RTProcSelf(), + VBOX_PACKAGE_STRING); + break; + } + + case RTLOGPHASE_PREROTATE: + pfnLog(pLoggerRelease, "Log rotated - Log started %s\n", szTmp); + break; + + case RTLOGPHASE_POSTROTATE: + pfnLog(pLoggerRelease, "Log continuation - Log started %s\n", szTmp); + break; + + case RTLOGPHASE_END: + pfnLog(pLoggerRelease, "End of log file - Log started %s\n", szTmp); + break; + + default: + /* nothing */ + break; + } +} + + +/** + * Creates the default release logger outputting to the specified file. + * + * Pass NULL to disabled logging. + * + * @return IPRT status code. + * @param pszLogFile Filename for log output. NULL disables logging + * (r=bird: No, it doesn't!). + */ +int VGSvcLogCreate(const char *pszLogFile) +{ + /* Create release logger (stdout + file). */ + static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; + RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME; +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + fFlags |= RTLOGFLAGS_USECRLF; +#endif + int rc = RTLogCreateEx(&g_pLoggerRelease, "VBOXSERVICE_RELEASE_LOG", fFlags, "all", + RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX /*cMaxEntriesPerGroup*/, + 0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT | RTLOGDEST_USER, + vgsvcLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime, + NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/, + NULL /*pErrInfo*/, "%s", pszLogFile ? pszLogFile : ""); + if (RT_SUCCESS(rc)) + { + /* register this logger as the release logger */ + RTLogRelSetDefaultInstance(g_pLoggerRelease); + + /* Explicitly flush the log in case of VBOXSERVICE_RELEASE_LOG=buffered. */ + RTLogFlush(g_pLoggerRelease); + } + + return rc; +} + + +/** + * Logs a verbose message. + * + * @param pszFormat The message text. + * @param va Format arguments. + */ +void VGSvcLogV(const char *pszFormat, va_list va) +{ +#ifdef DEBUG + int rc = RTCritSectEnter(&g_csLog); + if (RT_SUCCESS(rc)) + { +#endif + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, va); + + AssertPtr(psz); + LogRel(("%s", psz)); + + RTStrFree(psz); +#ifdef DEBUG + RTCritSectLeave(&g_csLog); + } +#endif +} + + +/** + * Destroys the currently active logging instance. + */ +void VGSvcLogDestroy(void) +{ + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); +} + + +/** + * Displays the program usage message. + * + * @returns 1. + */ +static int vgsvcUsage(void) +{ + RTPrintf("Usage: %s [-f|--foreground] [-v|--verbose] [-l|--logfile <file>]\n" + " [-p|--pidfile <file>] [-i|--interval <seconds>]\n" + " [--disable-<service>] [--enable-<service>]\n" + " [--only-<service>] [-h|-?|--help]\n", g_pszProgName); +#ifdef RT_OS_WINDOWS + RTPrintf(" [-r|--register] [-u|--unregister]\n"); +#endif + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + if (g_aServices[j].pDesc->pszUsage) + RTPrintf("%s\n", g_aServices[j].pDesc->pszUsage); + RTPrintf("\n" + "Options:\n" + " -i | --interval The default interval.\n" + " -f | --foreground Don't daemonize the program. For debugging.\n" + " -l | --logfile <file> Enables logging to a file.\n" + " -p | --pidfile <file> Write the process ID to a file.\n" + " -v | --verbose Increment the verbosity level. For debugging.\n" + " -V | --version Show version information.\n" + " -h | -? | --help Show this message and exit with status 1.\n" + ); +#ifdef RT_OS_WINDOWS + RTPrintf(" -r | --register Installs the service.\n" + " -u | --unregister Uninstall service.\n"); +#endif + + RTPrintf("\n" + "Service-specific options:\n"); + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + { + RTPrintf(" --enable-%-14s Enables the %s service. (default)\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName); + RTPrintf(" --disable-%-13s Disables the %s service.\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName); + RTPrintf(" --only-%-16s Only enables the %s service.\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName); + if (g_aServices[j].pDesc->pszOptions) + RTPrintf("%s", g_aServices[j].pDesc->pszOptions); + } + RTPrintf("\n" + " Copyright (C) 2009-" VBOX_C_YEAR " " VBOX_VENDOR "\n"); + + return 1; +} + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE. + * @param pszFormat The message text. + * @param ... Format arguments. + */ +RTEXITCODE VGSvcError(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, args); + va_end(args); + + AssertPtr(psz); + LogRel(("Error: %s", psz)); + + RTStrFree(psz); + + return RTEXITCODE_FAILURE; +} + + +/** + * Displays a verbose message based on the currently + * set global verbosity level. + * + * @param iLevel Minimum log level required to display this message. + * @param pszFormat The message text. + * @param ... Format arguments. + */ +void VGSvcVerbose(unsigned iLevel, const char *pszFormat, ...) +{ + if (iLevel <= g_cVerbosity) + { + va_list va; + va_start(va, pszFormat); + VGSvcLogV(pszFormat, va); + va_end(va); + } +} + + +/** + * Reports the current VBoxService status to the host. + * + * This makes sure that the Failed state is sticky. + * + * @return IPRT status code. + * @param enmStatus Status to report to the host. + */ +int VGSvcReportStatus(VBoxGuestFacilityStatus enmStatus) +{ + /* + * VBoxGuestFacilityStatus_Failed is sticky. + */ + static VBoxGuestFacilityStatus s_enmLastStatus = VBoxGuestFacilityStatus_Inactive; + VGSvcVerbose(4, "Setting VBoxService status to %u\n", enmStatus); + if (s_enmLastStatus != VBoxGuestFacilityStatus_Failed) + { + int rc = VbglR3ReportAdditionsStatus(VBoxGuestFacilityType_VBoxService, enmStatus, 0 /* Flags */); + if (RT_FAILURE(rc)) + { + VGSvcError("Could not report VBoxService status (%u), rc=%Rrc\n", enmStatus, rc); + return rc; + } + s_enmLastStatus = enmStatus; + } + return VINF_SUCCESS; +} + + +/** + * Gets a 32-bit value argument. + * @todo Get rid of this and VGSvcArgString() as soon as we have RTOpt handling. + * + * @returns 0 on success, non-zero exit code on error. + * @param argc The argument count. + * @param argv The argument vector + * @param psz Where in *pi to start looking for the value argument. + * @param pi Where to find and perhaps update the argument index. + * @param pu32 Where to store the 32-bit value. + * @param u32Min The minimum value. + * @param u32Max The maximum value. + */ +int VGSvcArgUInt32(int argc, char **argv, const char *psz, int *pi, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max) +{ + if (*psz == ':' || *psz == '=') + psz++; + if (!*psz) + { + if (*pi + 1 >= argc) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing value for the '%s' argument\n", argv[*pi]); + psz = argv[++*pi]; + } + + char *pszNext; + int rc = RTStrToUInt32Ex(psz, &pszNext, 0, pu32); + if (RT_FAILURE(rc) || *pszNext) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Failed to convert interval '%s' to a number\n", psz); + if (*pu32 < u32Min || *pu32 > u32Max) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The timesync interval of %RU32 seconds is out of range [%RU32..%RU32]\n", + *pu32, u32Min, u32Max); + return 0; +} + + +/** @todo Get rid of this and VGSvcArgUInt32() as soon as we have RTOpt handling. */ +static int vgsvcArgString(int argc, char **argv, const char *psz, int *pi, char *pszBuf, size_t cbBuf) +{ + AssertPtrReturn(pszBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + + if (*psz == ':' || *psz == '=') + psz++; + if (!*psz) + { + if (*pi + 1 >= argc) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing string for the '%s' argument\n", argv[*pi]); + psz = argv[++*pi]; + } + + if (!RTStrPrintf(pszBuf, cbBuf, "%s", psz)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "String for '%s' argument too big\n", argv[*pi]); + return 0; +} + + +/** + * The service thread. + * + * @returns Whatever the worker function returns. + * @param ThreadSelf My thread handle. + * @param pvUser The service index. + */ +static DECLCALLBACK(int) vgsvcThread(RTTHREAD ThreadSelf, void *pvUser) +{ + const unsigned i = (uintptr_t)pvUser; + +#ifndef RT_OS_WINDOWS + /* + * Block all signals for this thread. Only the main thread will handle signals. + */ + sigset_t signalMask; + sigfillset(&signalMask); + pthread_sigmask(SIG_BLOCK, &signalMask, NULL); +#endif + + int rc = g_aServices[i].pDesc->pfnWorker(&g_aServices[i].fShutdown); + ASMAtomicXchgBool(&g_aServices[i].fShutdown, true); + RTThreadUserSignal(ThreadSelf); + return rc; +} + + +/** + * Lazily calls the pfnPreInit method on each service. + * + * @returns VBox status code, error message displayed. + */ +static RTEXITCODE vgsvcLazyPreInit(void) +{ + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + if (!g_aServices[j].fPreInited) + { + int rc = g_aServices[j].pDesc->pfnPreInit(); + if (RT_FAILURE(rc)) + return VGSvcError("Service '%s' failed pre-init: %Rrc\n", g_aServices[j].pDesc->pszName, rc); + g_aServices[j].fPreInited = true; + } + return RTEXITCODE_SUCCESS; +} + + +/** + * Count the number of enabled services. + */ +static unsigned vgsvcCountEnabledServices(void) +{ + unsigned cEnabled = 0; + for (unsigned i = 0; i < RT_ELEMENTS(g_aServices); i++) + cEnabled += g_aServices[i].fEnabled; + return cEnabled; +} + + +#ifdef RT_OS_WINDOWS +/** + * Console control event callback. + * + * @returns TRUE if handled, FALSE if not. + * @param dwCtrlType The control event type. + * + * @remarks This is generally called on a new thread, so we're racing every + * other thread in the process. + */ +static BOOL WINAPI vgsvcWinConsoleControlHandler(DWORD dwCtrlType) RT_NOTHROW_DEF +{ + int rc = VINF_SUCCESS; + bool fEventHandled = FALSE; + switch (dwCtrlType) + { + /* User pressed CTRL+C or CTRL+BREAK or an external event was sent + * via GenerateConsoleCtrlEvent(). */ + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_C_EVENT: + VGSvcVerbose(2, "ControlHandler: Received break/close event\n"); + rc = VGSvcStopServices(); + fEventHandled = TRUE; + break; + default: + break; + /** @todo Add other events here. */ + } + + if (RT_FAILURE(rc)) + VGSvcError("ControlHandler: Event %ld handled with error rc=%Rrc\n", + dwCtrlType, rc); + return fEventHandled; +} +#endif /* RT_OS_WINDOWS */ + + +/** + * Starts the service. + * + * @returns VBox status code, errors are fully bitched. + * + * @remarks Also called from VBoxService-win.cpp, thus not static. + */ +int VGSvcStartServices(void) +{ + int rc; + + VGSvcReportStatus(VBoxGuestFacilityStatus_Init); + + /* + * Initialize the services. + */ + VGSvcVerbose(2, "Initializing services ...\n"); + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + if (g_aServices[j].fEnabled) + { + rc = g_aServices[j].pDesc->pfnInit(); + if (RT_FAILURE(rc)) + { + if (rc != VERR_SERVICE_DISABLED) + { + VGSvcError("Service '%s' failed to initialize: %Rrc\n", g_aServices[j].pDesc->pszName, rc); + VGSvcReportStatus(VBoxGuestFacilityStatus_Failed); + return rc; + } + + g_aServices[j].fEnabled = false; + VGSvcVerbose(0, "Service '%s' was disabled because of missing functionality\n", g_aServices[j].pDesc->pszName); + } + } + + /* + * Start the service(s). + */ + VGSvcVerbose(2, "Starting services ...\n"); + rc = VINF_SUCCESS; + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + { + if (!g_aServices[j].fEnabled) + continue; + + VGSvcVerbose(2, "Starting service '%s' ...\n", g_aServices[j].pDesc->pszName); + rc = RTThreadCreate(&g_aServices[j].Thread, vgsvcThread, (void *)(uintptr_t)j, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, g_aServices[j].pDesc->pszName); + if (RT_FAILURE(rc)) + { + VGSvcError("RTThreadCreate failed, rc=%Rrc\n", rc); + break; + } + g_aServices[j].fStarted = true; + + /* Wait for the thread to initialize. */ + /** @todo There is a race between waiting and checking + * the fShutdown flag of a thread here and processing + * the thread's actual worker loop. If the thread decides + * to exit the loop before we skipped the fShutdown check + * below the service will fail to start! */ + /** @todo This presumably means either a one-shot service or that + * something has gone wrong. In the second case treating it as failure + * to start is probably right, so we need a way to signal the first + * rather than leaving the idle thread hanging around. A flag in the + * service description? */ + RTThreadUserWait(g_aServices[j].Thread, 60 * 1000); + if (g_aServices[j].fShutdown) + { + VGSvcError("Service '%s' failed to start!\n", g_aServices[j].pDesc->pszName); + rc = VERR_GENERAL_FAILURE; + } + } + + if (RT_SUCCESS(rc)) + VGSvcVerbose(1, "All services started.\n"); + else + { + VGSvcError("An error occcurred while the services!\n"); + VGSvcReportStatus(VBoxGuestFacilityStatus_Failed); + } + return rc; +} + + +/** + * Stops and terminates the services. + * + * This should be called even when VBoxServiceStartServices fails so it can + * clean up anything that we succeeded in starting. + * + * @remarks Also called from VBoxService-win.cpp, thus not static. + */ +int VGSvcStopServices(void) +{ + VGSvcReportStatus(VBoxGuestFacilityStatus_Terminating); + + /* + * Signal all the services. + */ + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + ASMAtomicWriteBool(&g_aServices[j].fShutdown, true); + + /* + * Do the pfnStop callback on all running services. + */ + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + if (g_aServices[j].fStarted) + { + VGSvcVerbose(3, "Calling stop function for service '%s' ...\n", g_aServices[j].pDesc->pszName); + g_aServices[j].pDesc->pfnStop(); + } + + VGSvcVerbose(3, "All stop functions for services called\n"); + + /* + * Wait for all the service threads to complete. + */ + int rc = VINF_SUCCESS; + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + { + if (!g_aServices[j].fEnabled) /* Only stop services which were started before. */ + continue; + if (g_aServices[j].Thread != NIL_RTTHREAD) + { + VGSvcVerbose(2, "Waiting for service '%s' to stop ...\n", g_aServices[j].pDesc->pszName); + int rc2 = VINF_SUCCESS; + for (int i = 0; i < 30; i++) /* Wait 30 seconds in total */ + { + rc2 = RTThreadWait(g_aServices[j].Thread, 1000 /* Wait 1 second */, NULL); + if (RT_SUCCESS(rc2)) + break; +#ifdef RT_OS_WINDOWS + /* Notify SCM that it takes a bit longer ... */ + VGSvcWinSetStopPendingStatus(i + j*32); +#endif + } + if (RT_FAILURE(rc2)) + { + VGSvcError("Service '%s' failed to stop. (%Rrc)\n", g_aServices[j].pDesc->pszName, rc2); + rc = rc2; + } + } + VGSvcVerbose(3, "Terminating service '%s' (%d) ...\n", g_aServices[j].pDesc->pszName, j); + g_aServices[j].pDesc->pfnTerm(); + } + +#ifdef RT_OS_WINDOWS + /* + * Wake up and tell the main() thread that we're shutting down (it's + * sleeping in VBoxServiceMainWait). + */ + ASMAtomicWriteBool(&g_fWindowsServiceShutdown, true); + if (g_hEvtWindowsService != NIL_RTSEMEVENT) + { + VGSvcVerbose(3, "Stopping the main thread...\n"); + int rc2 = RTSemEventSignal(g_hEvtWindowsService); + AssertRC(rc2); + } +#endif + + VGSvcVerbose(2, "Stopping services returning: %Rrc\n", rc); + VGSvcReportStatus(RT_SUCCESS(rc) ? VBoxGuestFacilityStatus_Paused : VBoxGuestFacilityStatus_Failed); + return rc; +} + + +/** + * Block the main thread until the service shuts down. + * + * @remarks Also called from VBoxService-win.cpp, thus not static. + */ +void VGSvcMainWait(void) +{ + int rc; + + VGSvcReportStatus(VBoxGuestFacilityStatus_Active); + +#ifdef RT_OS_WINDOWS + /* + * Wait for the semaphore to be signalled. + */ + VGSvcVerbose(1, "Waiting in main thread\n"); + rc = RTSemEventCreate(&g_hEvtWindowsService); + AssertRC(rc); + while (!ASMAtomicReadBool(&g_fWindowsServiceShutdown)) + { + rc = RTSemEventWait(g_hEvtWindowsService, RT_INDEFINITE_WAIT); + AssertRC(rc); + } + RTSemEventDestroy(g_hEvtWindowsService); + g_hEvtWindowsService = NIL_RTSEMEVENT; +#else + /* + * Wait explicitly for a HUP, INT, QUIT, ABRT or TERM signal, blocking + * all important signals. + * + * The annoying EINTR/ERESTART loop is for the benefit of Solaris where + * sigwait returns when we receive a SIGCHLD. Kind of makes sense since + * the signal has to be delivered... Anyway, darwin (10.9.5) has a much + * worse way of dealing with SIGCHLD, apparently it'll just return any + * of the signals we're waiting on when SIGCHLD becomes pending on this + * thread. So, we wait for SIGCHLD here and ignores it. + */ + sigset_t signalMask; + sigemptyset(&signalMask); + sigaddset(&signalMask, SIGHUP); + sigaddset(&signalMask, SIGINT); + sigaddset(&signalMask, SIGQUIT); + sigaddset(&signalMask, SIGABRT); + sigaddset(&signalMask, SIGTERM); + sigaddset(&signalMask, SIGCHLD); + pthread_sigmask(SIG_BLOCK, &signalMask, NULL); + + int iSignal; + do + { + iSignal = -1; + rc = sigwait(&signalMask, &iSignal); + } + while ( rc == EINTR +# ifdef ERESTART + || rc == ERESTART +# endif + || iSignal == SIGCHLD + ); + + VGSvcVerbose(3, "VGSvcMainWait: Received signal %d (rc=%d)\n", iSignal, rc); +#endif /* !RT_OS_WINDOWS */ +} + + +/** + * Report VbglR3InitUser / VbglR3Init failure. + * + * @returns RTEXITCODE_FAILURE + * @param rcVbgl The failing status code. + */ +static RTEXITCODE vbglInitFailure(int rcVbgl) +{ + if (rcVbgl == VERR_ACCESS_DENIED) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Insufficient privileges to start %s! Please start with Administrator/root privileges!\n", + g_pszProgName); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "VbglR3Init failed with rc=%Rrc\n", rcVbgl); +} + + +int main(int argc, char **argv) +{ + RTEXITCODE rcExit; + + /* + * Init globals and such. + * + * Note! The --utf8-argv stuff is an internal hack to avoid locale configuration + * issues preventing us from passing non-ASCII string to child processes. + */ + uint32_t fIprtFlags = 0; +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV + if (argc > 1 && strcmp(argv[1], VBOXSERVICE_ARG1_UTF8_ARGV) == 0) + { + argv[1] = argv[0]; + argv++; + argc--; + fIprtFlags |= RTR3INIT_FLAGS_UTF8_ARGV; + } +#endif + int rc = RTR3InitExe(argc, &argv, fIprtFlags); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + g_pszProgName = RTPathFilename(argv[0]); +#ifdef RT_OS_WINDOWS + VGSvcWinResolveApis(); +#endif +#ifdef DEBUG + rc = RTCritSectInit(&g_csLog); + AssertRC(rc); +#endif + +#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX + /* + * Run toolbox code before all other stuff since these things are simpler + * shell/file/text utility like programs that just happens to be inside + * VBoxService and shouldn't be subject to /dev/vboxguest, pid-files and + * global mutex restrictions. + */ + if (VGSvcToolboxMain(argc, argv, &rcExit)) + return rcExit; +#endif + + bool fUserSession = false; +#ifdef VBOX_WITH_VBOXSERVICE_CONTROL + /* + * Check if we're the specially spawned VBoxService.exe process that + * handles a guest control session. + */ + if ( argc >= 2 + && !RTStrICmp(argv[1], VBOXSERVICECTRLSESSION_GETOPT_PREFIX)) + fUserSession = true; +#endif + + /* + * Connect to the kernel part before daemonizing and *before* we do the sub-service + * pre-init just in case one of services needs do to some initial stuff with it. + * + * However, we do not fail till after we've parsed arguments, because that will + * prevent useful stuff like --help, --register, --unregister and --version from + * working when the driver hasn't been installed/loaded yet. + */ + int const rcVbgl = fUserSession ? VbglR3InitUser() : VbglR3Init(); + +#ifdef RT_OS_WINDOWS + /* + * Check if we're the specially spawned VBoxService.exe process that + * handles page fusion. This saves an extra statically linked executable. + */ + if ( argc == 2 + && !RTStrICmp(argv[1], "pagefusion")) + { + if (RT_SUCCESS(rcVbgl)) + return VGSvcPageSharingWorkerChild(); + return vbglInitFailure(rcVbgl); + } +#endif + +#ifdef VBOX_WITH_VBOXSERVICE_CONTROL + /* + * Check if we're the specially spawned VBoxService.exe process that + * handles a guest control session. + */ + if (fUserSession) + { + if (RT_SUCCESS(rcVbgl)) + return VGSvcGstCtrlSessionSpawnInit(argc, argv); + return vbglInitFailure(rcVbgl); + } +#endif + + /* + * Parse the arguments. + * + * Note! This code predates RTGetOpt, thus the manual parsing. + */ + bool fDaemonize = true; + bool fDaemonized = false; + for (int i = 1; i < argc; i++) + { + const char *psz = argv[i]; + if (*psz != '-') + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown argument '%s'\n", psz); + psz++; + + /* translate long argument to short */ + if (*psz == '-') + { + psz++; + size_t cch = strlen(psz); +#define MATCHES(strconst) ( cch == sizeof(strconst) - 1 \ + && !memcmp(psz, strconst, sizeof(strconst) - 1) ) + if (MATCHES("foreground")) + psz = "f"; + else if (MATCHES("verbose")) + psz = "v"; + else if (MATCHES("version")) + psz = "V"; + else if (MATCHES("help")) + psz = "h"; + else if (MATCHES("interval")) + psz = "i"; +#ifdef RT_OS_WINDOWS + else if (MATCHES("register")) + psz = "r"; + else if (MATCHES("unregister")) + psz = "u"; +#endif + else if (MATCHES("logfile")) + psz = "l"; + else if (MATCHES("pidfile")) + psz = "p"; + else if (MATCHES("daemonized")) + { + fDaemonized = true; + continue; + } + else + { + bool fFound = false; + + if (cch > sizeof("enable-") && !memcmp(psz, RT_STR_TUPLE("enable-"))) + for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++) + if ((fFound = !RTStrICmp(psz + sizeof("enable-") - 1, g_aServices[j].pDesc->pszName))) + g_aServices[j].fEnabled = true; + + if (cch > sizeof("disable-") && !memcmp(psz, RT_STR_TUPLE("disable-"))) + for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++) + if ((fFound = !RTStrICmp(psz + sizeof("disable-") - 1, g_aServices[j].pDesc->pszName))) + g_aServices[j].fEnabled = false; + + if (cch > sizeof("only-") && !memcmp(psz, RT_STR_TUPLE("only-"))) + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + { + g_aServices[j].fEnabled = !RTStrICmp(psz + sizeof("only-") - 1, g_aServices[j].pDesc->pszName); + if (g_aServices[j].fEnabled) + fFound = true; + } + + if (!fFound) + { + rcExit = vgsvcLazyPreInit(); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++) + { + rc = g_aServices[j].pDesc->pfnOption(NULL, argc, argv, &i); + fFound = rc == VINF_SUCCESS; + if (fFound) + break; + if (rc != -1) + return rc; + } + } + if (!fFound) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown option '%s'\n", argv[i]); + continue; + } +#undef MATCHES + } + + /* handle the string of short options. */ + do + { + switch (*psz) + { + case 'i': + rc = VGSvcArgUInt32(argc, argv, psz + 1, &i, &g_DefaultInterval, 1, (UINT32_MAX / 1000) - 1); + if (rc) + return rc; + psz = NULL; + break; + + case 'f': + fDaemonize = false; + break; + + case 'v': + g_cVerbosity++; + break; + + case 'V': + RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr()); + return RTEXITCODE_SUCCESS; + + case 'h': + case '?': + return vgsvcUsage(); + +#ifdef RT_OS_WINDOWS + case 'r': + return VGSvcWinInstall(); + + case 'u': + return VGSvcWinUninstall(); +#endif + + case 'l': + { + rc = vgsvcArgString(argc, argv, psz + 1, &i, g_szLogFile, sizeof(g_szLogFile)); + if (rc) + return rc; + psz = NULL; + break; + } + + case 'p': + { + rc = vgsvcArgString(argc, argv, psz + 1, &i, g_szPidFile, sizeof(g_szPidFile)); + if (rc) + return rc; + psz = NULL; + break; + } + + default: + { + rcExit = vgsvcLazyPreInit(); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + bool fFound = false; + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + { + rc = g_aServices[j].pDesc->pfnOption(&psz, argc, argv, &i); + fFound = rc == VINF_SUCCESS; + if (fFound) + break; + if (rc != -1) + return rc; + } + if (!fFound) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown option '%c' (%s)\n", *psz, argv[i]); + break; + } + } + } while (psz && *++psz); + } + + /* Now we can report the VBGL failure. */ + if (RT_FAILURE(rcVbgl)) + return vbglInitFailure(rcVbgl); + + /* Check that at least one service is enabled. */ + if (vgsvcCountEnabledServices() == 0) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "At least one service must be enabled\n"); + + rc = VGSvcLogCreate(g_szLogFile[0] ? g_szLogFile : NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create release log '%s', rc=%Rrc\n", + g_szLogFile[0] ? g_szLogFile : "<None>", rc); + + /* Call pre-init if we didn't do it already. */ + rcExit = vgsvcLazyPreInit(); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + +#ifdef VBOX_WITH_VBOXSERVICE_DRMRESIZE +# ifdef RT_OS_LINUX + rc = VbglR3DrmClientStart(); + if (RT_FAILURE(rc)) + VGSvcVerbose(0, "VMSVGA DRM resizing client not started, rc=%Rrc\n", rc); +# endif /* RT_OS_LINUX */ +#endif /* VBOX_WITH_VBOXSERVICE_DRMRESIZE */ + +#ifdef RT_OS_WINDOWS + /* + * Make sure only one instance of VBoxService runs at a time. Create a + * global mutex for that. + * + * Note! The \\Global\ namespace was introduced with Win2K, thus the + * version check. + * Note! If the mutex exists CreateMutex will open it and set last error to + * ERROR_ALREADY_EXISTS. + */ + OSVERSIONINFOEX OSInfoEx; + RT_ZERO(OSInfoEx); + OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + + SetLastError(NO_ERROR); + HANDLE hMutexAppRunning; + if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(5,0,0)) /* Windows 2000 */ + hMutexAppRunning = CreateMutexW(NULL, FALSE, L"Global\\" RT_CONCAT(L,VBOXSERVICE_NAME)); + else + hMutexAppRunning = CreateMutexW(NULL, FALSE, RT_CONCAT(L,VBOXSERVICE_NAME)); + if (hMutexAppRunning == NULL) + { + DWORD dwErr = GetLastError(); + if ( dwErr == ERROR_ALREADY_EXISTS + || dwErr == ERROR_ACCESS_DENIED) + { + VGSvcError("%s is already running! Terminating.\n", g_pszProgName); + return RTEXITCODE_FAILURE; + } + + VGSvcError("CreateMutex failed with last error %u! Terminating.\n", GetLastError()); + return RTEXITCODE_FAILURE; + } + +#else /* !RT_OS_WINDOWS */ + /* On other OSes we have PID file support provided by the actual service definitions / service wrapper scripts, + * like vboxadd-service.sh on Linux or vboxservice.xml on Solaris. */ +#endif /* !RT_OS_WINDOWS */ + + VGSvcVerbose(0, "%s r%s started. Verbose level = %d\n", RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity); + + /* + * Daemonize if requested. + */ + if (fDaemonize && !fDaemonized) + { +#ifdef RT_OS_WINDOWS + VGSvcVerbose(2, "Starting service dispatcher ...\n"); + rcExit = VGSvcWinEnterCtrlDispatcher(); +#else + VGSvcVerbose(1, "Daemonizing...\n"); + rc = VbglR3Daemonize(false /* fNoChDir */, false /* fNoClose */, + false /* fRespawn */, NULL /* pcRespawn */); + if (RT_FAILURE(rc)) + return VGSvcError("Daemon failed: %Rrc\n", rc); + /* in-child */ +#endif + } +#ifdef RT_OS_WINDOWS + else +#endif + { + /* + * Windows: We're running the service as a console application now. Start the + * services, enter the main thread's run loop and stop them again + * when it returns. + * + * POSIX: This is used for both daemons and console runs. Start all services + * and return immediately. + */ +#ifdef RT_OS_WINDOWS + /* Install console control handler. */ + if (!SetConsoleCtrlHandler(vgsvcWinConsoleControlHandler, TRUE /* Add handler */)) + { + VGSvcError("Unable to add console control handler, error=%ld\n", GetLastError()); + /* Just skip this error, not critical. */ + } +#endif /* RT_OS_WINDOWS */ + rc = VGSvcStartServices(); + RTFILE hPidFile = NIL_RTFILE; + if (RT_SUCCESS(rc)) + if (g_szPidFile[0]) + rc = VbglR3PidFile(g_szPidFile, &hPidFile); + rcExit = RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + if (RT_SUCCESS(rc)) + VGSvcMainWait(); + if (g_szPidFile[0] && hPidFile != NIL_RTFILE) + VbglR3ClosePidFile(g_szPidFile, hPidFile); +#ifdef RT_OS_WINDOWS + /* Uninstall console control handler. */ + if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */)) + { + VGSvcError("Unable to remove console control handler, error=%ld\n", GetLastError()); + /* Just skip this error, not critical. */ + } +#else /* !RT_OS_WINDOWS */ + /* On Windows - since we're running as a console application - we already stopped all services + * through the console control handler. So only do the stopping of services here on other platforms + * where the break/shutdown/whatever signal was just received. */ + VGSvcStopServices(); +#endif /* RT_OS_WINDOWS */ + } + VGSvcReportStatus(VBoxGuestFacilityStatus_Terminated); + +#ifdef RT_OS_WINDOWS + /* + * Cleanup mutex. + */ + CloseHandle(hMutexAppRunning); +#endif + + VGSvcVerbose(0, "Ended.\n"); + +#ifdef DEBUG + RTCritSectDelete(&g_csLog); + //RTMemTrackerDumpAllToStdOut(); +#endif + + VGSvcLogDestroy(); + + return rcExit; +} diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp new file mode 100644 index 00000000..36a93b39 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc_automount VBoxService - Shared Folder Automounter + * + * The Shared Folder Automounter subservice mounts shared folders upon request + * from the host. + * + * This retrieves shared folder automount requests from Main via the VMMDev. + * The current implemention only does this once, for some inexplicable reason, + * so the run-time addition of automounted shared folders are not heeded. + * + * This subservice is only used on linux and solaris. On Windows the current + * thinking is this is better of done from VBoxTray, some one argue that for + * drive letter assigned shared folders it would be better to do some magic here + * (obviously not involving NDAddConnection). + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/semaphore.h> +#include <iprt/sort.h> +#include <iprt/string.h> +#include <VBox/err.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/shflsvc.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" + +#ifdef RT_OS_WINDOWS +#elif defined(RT_OS_OS2) +# define INCL_DOSFILEMGR +# define INCL_ERRORS +# define OS2EMX_PLAIN_CHAR +# include <os2emx.h> +#else +# include <errno.h> +# include <grp.h> +# include <sys/mount.h> +# ifdef RT_OS_SOLARIS +# include <sys/mntent.h> +# include <sys/mnttab.h> +# include <sys/vfs.h> +# elif defined(RT_OS_LINUX) +# include <mntent.h> +# include <paths.h> +# include <sys/utsname.h> +RT_C_DECLS_BEGIN +# include "../../linux/sharedfolders/vbsfmount.h" +RT_C_DECLS_END +# else +# error "Port me!" +# endif +# include <unistd.h> +#endif + + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR + * Default mount directory (unix only). + */ +#ifndef VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR +# define VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR "/media" +#endif + +/** @def VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX + * Default mount prefix (unix only). + */ +#ifndef VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX +# define VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX "sf_" +#endif + +#ifndef _PATH_MOUNTED +# ifdef RT_OS_SOLARIS +# define _PATH_MOUNTED "/etc/mnttab" +# else +# define _PATH_MOUNTED "/etc/mtab" +# endif +#endif + +/** @def VBOXSERVICE_AUTOMOUNT_MIQF + * The drive letter / path mount point flag. */ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) +# define VBOXSERVICE_AUTOMOUNT_MIQF SHFL_MIQF_DRIVE_LETTER +#else +# define VBOXSERVICE_AUTOMOUNT_MIQF SHFL_MIQF_PATH +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Automounter mount table entry. + * + * This holds the information returned by SHFL_FN_QUERY_MAP_INFO and + * additional mount state info. We only keep entries for mounted mappings. + */ +typedef struct VBSVCAUTOMOUNTERENTRY +{ + /** The root ID. */ + uint32_t idRoot; + /** The root ID version. */ + uint32_t uRootIdVersion; + /** Map info flags, SHFL_MIF_XXX. */ + uint64_t fFlags; + /** The shared folder (mapping) name. */ + char *pszName; + /** The configured mount point, NULL if none. */ + char *pszMountPoint; + /** The actual mount point, NULL if not mount. */ + char *pszActualMountPoint; +} VBSVCAUTOMOUNTERENTRY; +/** Pointer to an automounter entry. */ +typedef VBSVCAUTOMOUNTERENTRY *PVBSVCAUTOMOUNTERENTRY; + +/** Automounter mount table. */ +typedef struct VBSVCAUTOMOUNTERTABLE +{ + /** Current number of entries in the array. */ + uint32_t cEntries; + /** Max number of entries the array can hold w/o growing it. */ + uint32_t cAllocated; + /** Pointer to an array of entry pointers. */ + PVBSVCAUTOMOUNTERENTRY *papEntries; +} VBSVCAUTOMOUNTERTABLE; +/** Pointer to an automounter mount table. */ +typedef VBSVCAUTOMOUNTERTABLE *PVBSVCAUTOMOUNTERTABLE; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_hAutoMountEvent = NIL_RTSEMEVENTMULTI; +/** The Shared Folders service client ID. */ +static uint32_t g_idClientSharedFolders = 0; +/** Set if we can wait on changes to the mappings. */ +static bool g_fHostSupportsWaitAndInfoQuery = false; + +#ifdef RT_OS_OS2 +/** The attachment tag we use to identify attchments that belongs to us. */ +static char const g_szTag[] = "VBoxAutomounter"; +#elif defined(RT_OS_LINUX) +/** Tag option value that lets us identify mounts that belongs to us. */ +static char const g_szTag[] = "VBoxAutomounter"; +#elif defined(RT_OS_SOLARIS) +/** Dummy mount option that lets us identify mounts that belongs to us. */ +static char const g_szTag[] = "VBoxAutomounter"; +#endif + + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vbsvcAutomounterInit(void) +{ + VGSvcVerbose(3, "vbsvcAutomounterInit\n"); + + int rc = RTSemEventMultiCreate(&g_hAutoMountEvent); + AssertRCReturn(rc, rc); + + rc = VbglR3SharedFolderConnect(&g_idClientSharedFolders); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "vbsvcAutomounterInit: Service Client ID: %#x\n", g_idClientSharedFolders); + g_fHostSupportsWaitAndInfoQuery = RT_SUCCESS(VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders)); + } + else + { + /* If the service was not found, we disable this service without + causing VBoxService to fail. */ + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ + { + VGSvcVerbose(0, "vbsvcAutomounterInit: Shared Folders service is not available\n"); + rc = VERR_SERVICE_DISABLED; + } + else + VGSvcError("Control: Failed to connect to the Shared Folders service! Error: %Rrc\n", rc); + RTSemEventMultiDestroy(g_hAutoMountEvent); + g_hAutoMountEvent = NIL_RTSEMEVENTMULTI; + } + + return rc; +} + + +#if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) /* The old code: */ + +/** + * @todo Integrate into RTFsQueryMountpoint()? + */ +static bool vbsvcAutoMountShareIsMountedOld(const char *pszShare, char *pszMountPoint, size_t cbMountPoint) +{ + AssertPtrReturn(pszShare, false); + AssertPtrReturn(pszMountPoint, false); + AssertReturn(cbMountPoint, false); + + bool fMounted = false; + +# if defined(RT_OS_SOLARIS) + /** @todo What to do if we have a relative path in mtab instead + * of an absolute one ("temp" vs. "/media/temp")? + * procfs contains the full path but not the actual share name ... + * FILE *pFh = setmntent("/proc/mounts", "r+t"); */ + FILE *pFh = fopen(_PATH_MOUNTED, "r"); + if (!pFh) + VGSvcError("vbsvcAutoMountShareIsMountedOld: Could not open mount tab '%s'!\n", _PATH_MOUNTED); + else + { + mnttab mntTab; + while ((getmntent(pFh, &mntTab))) + { + if (!RTStrICmp(mntTab.mnt_special, pszShare)) + { + fMounted = RTStrPrintf(pszMountPoint, cbMountPoint, "%s", mntTab.mnt_mountp) + ? true : false; + break; + } + } + fclose(pFh); + } +# elif defined(RT_OS_LINUX) + FILE *pFh = setmntent(_PATH_MOUNTED, "r+t"); /** @todo r=bird: why open it for writing? (the '+') */ + if (pFh == NULL) + VGSvcError("vbsvcAutoMountShareIsMountedOld: Could not open mount tab '%s'!\n", _PATH_MOUNTED); + else + { + mntent *pMntEnt; + while ((pMntEnt = getmntent(pFh))) + { + if (!RTStrICmp(pMntEnt->mnt_fsname, pszShare)) + { + fMounted = RTStrPrintf(pszMountPoint, cbMountPoint, "%s", pMntEnt->mnt_dir) + ? true : false; + break; + } + } + endmntent(pFh); + } +# else +# error "PORTME!" +# endif + + VGSvcVerbose(4, "vbsvcAutoMountShareIsMountedOld: Share '%s' at mount point '%s' = %s\n", + pszShare, fMounted ? pszMountPoint : "<None>", fMounted ? "Yes" : "No"); + return fMounted; +} + + +/** + * Unmounts a shared folder. + * + * @returns VBox status code + * @param pszMountPoint The shared folder mount point. + */ +static int vbsvcAutoMountUnmountOld(const char *pszMountPoint) +{ + AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + uint8_t uTries = 0; + int r; + while (uTries++ < 3) + { + r = umount(pszMountPoint); + if (r == 0) + break; +/** @todo r=bird: Why do sleep 5 seconds after the final retry? + * May also be a good idea to check for EINVAL or other signs that someone + * else have already unmounted the share. */ + RTThreadSleep(5000); /* Wait a while ... */ + } + if (r == -1) /** @todo r=bird: RTThreadSleep set errno. */ + rc = RTErrConvertFromErrno(errno); + return rc; +} + + +/** + * Prepares a mount point (create it, set group and mode). + * + * @returns VBox status code + * @param pszMountPoint The mount point. + * @param pszShareName Unused. + * @param gidGroup The group ID. + */ +static int vbsvcAutoMountPrepareMountPointOld(const char *pszMountPoint, const char *pszShareName, RTGID gidGroup) +{ + AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszShareName, VERR_INVALID_PARAMETER); + + /** @todo r=bird: There is no reason why gidGroup should have write access? + * Seriously, what kind of non-sense is this? */ + + RTFMODE fMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG; /* Owner (=root) and the group (=vboxsf) have full access. */ + int rc = RTDirCreateFullPath(pszMountPoint, fMode); + if (RT_SUCCESS(rc)) + { + rc = RTPathSetOwnerEx(pszMountPoint, NIL_RTUID /* Owner, unchanged */, gidGroup, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + { + rc = RTPathSetMode(pszMountPoint, fMode); + if (RT_FAILURE(rc)) + { + if (rc == VERR_WRITE_PROTECT) + { + VGSvcVerbose(3, "vbsvcAutoMountPrepareMountPointOld: Mount directory '%s' already is used/mounted\n", + pszMountPoint); + rc = VINF_SUCCESS; + } + else + VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not set mode %RTfmode for mount directory '%s', rc = %Rrc\n", + fMode, pszMountPoint, rc); + } + } + else + VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not set permissions for mount directory '%s', rc = %Rrc\n", + pszMountPoint, rc); + } + else + VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not create mount directory '%s' with mode %RTfmode, rc = %Rrc\n", + pszMountPoint, fMode, rc); + return rc; +} + + +/** + * Mounts a shared folder. + * + * @returns VBox status code reflecting unmount and mount point preparation + * results, but not actual mounting + * + * @param pszShareName The shared folder name. + * @param pszMountPoint The mount point. + */ +static int vbsvcAutoMountSharedFolderOld(const char *pszShareName, const char *pszMountPoint) +{ + /* + * Linux and solaris share the same mount structure. + */ + struct group *grp_vboxsf = getgrnam("vboxsf"); + if (!grp_vboxsf) + { + VGSvcError("vbsvcAutoMountWorker: Group 'vboxsf' does not exist\n"); + return VINF_SUCCESS; + } + + int rc = vbsvcAutoMountPrepareMountPointOld(pszMountPoint, pszShareName, grp_vboxsf->gr_gid); + if (RT_SUCCESS(rc)) + { +# ifdef RT_OS_SOLARIS + int const fFlags = MS_OPTIONSTR; + char szOptBuf[MAX_MNTOPT_STR] = { '\0', }; + RTStrPrintf(szOptBuf, sizeof(szOptBuf), "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000", grp_vboxsf->gr_gid); + int r = mount(pszShareName, + pszMountPoint, + fFlags, + "vboxfs", + NULL, /* char *dataptr */ + 0, /* int datalen */ + szOptBuf, + sizeof(szOptBuf)); + if (r == 0) + VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint); + else if (errno != EBUSY) /* Share is already mounted? Then skip error msg. */ + VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s', error = %s\n", + pszShareName, pszMountPoint, strerror(errno)); + +# else /* RT_OS_LINUX */ + struct utsname uts; + AssertStmt(uname(&uts) != -1, strcpy(uts.release, "4.4.0")); + + unsigned long const fFlags = MS_NODEV; + char szOpts[MAX_MNTOPT_STR] = { '\0' }; + ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts), "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000", + grp_vboxsf->gr_gid); + if (cchOpts > 0 && RTStrVersionCompare(uts.release, "2.6.0") < 0) + cchOpts = RTStrPrintf2(&szOpts[cchOpts], sizeof(szOpts) - cchOpts, ",sf_name=%s", pszShareName); + if (cchOpts <= 0) + { + VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd (share %s)\n", cchOpts, pszShareName); + return VERR_BUFFER_OVERFLOW; + } + + int r = mount(pszShareName, + pszMountPoint, + "vboxsf", + fFlags, + szOpts); + if (r == 0) + { + VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint); + + r = vbsfmount_complete(pszShareName, pszMountPoint, fFlags, szOpts); + switch (r) + { + case 0: /* Success. */ + errno = 0; /* Clear all errors/warnings. */ + break; + case 1: + VGSvcError("vbsvcAutoMountWorker: Could not update mount table (malloc failure)\n"); + break; + case 2: + VGSvcError("vbsvcAutoMountWorker: Could not open mount table for update: %s\n", strerror(errno)); + break; + case 3: + /* VGSvcError("vbsvcAutoMountWorker: Could not add an entry to the mount table: %s\n", strerror(errno)); */ + errno = 0; + break; + default: + VGSvcError("vbsvcAutoMountWorker: Unknown error while completing mount operation: %d\n", r); + break; + } + } + else /* r == -1, we got some error in errno. */ + { + switch (errno) + { + /* If we get EINVAL here, the system already has mounted the Shared Folder to another + * mount point. */ + case EINVAL: + VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' is already mounted!\n", pszShareName); + /* Ignore this error! */ + break; + case EBUSY: + /* Ignore these errors! */ + break; + default: + VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s': %s (%d)\n", + pszShareName, pszMountPoint, strerror(errno), errno); + rc = RTErrConvertFromErrno(errno); + break; + } + } +# endif + } + VGSvcVerbose(3, "vbsvcAutoMountWorker: Mounting returned with rc=%Rrc\n", rc); + return rc; +} + + +/** + * Processes shared folder mappings retrieved from the host. + * + * @returns VBox status code. + * @param paMappings The mappings. + * @param cMappings The number of mappings. + * @param pszMountDir The mount directory. + * @param pszSharePrefix The share prefix. + * @param uClientID The shared folder service (HGCM) client ID. + */ +static int vbsvcAutoMountProcessMappingsOld(PCVBGLR3SHAREDFOLDERMAPPING paMappings, uint32_t cMappings, + const char *pszMountDir, const char *pszSharePrefix, uint32_t uClientID) +{ + if (cMappings == 0) + return VINF_SUCCESS; + AssertPtrReturn(paMappings, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszMountDir, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszSharePrefix, VERR_INVALID_PARAMETER); + AssertReturn(uClientID > 0, VERR_INVALID_PARAMETER); + + /** @todo r=bird: Why is this loop schitzoid about status codes? It quits if + * RTPathJoin fails (i.e. if the user specifies a very long name), but happily + * continues if RTStrAPrintf failes (mem alloc). + * + * It also happily continues if the 'vboxsf' group is missing, which is a waste + * of effort... In fact, retrieving the group ID could probably be done up + * front, outside the loop. */ + int rc = VINF_SUCCESS; + for (uint32_t i = 0; i < cMappings && RT_SUCCESS(rc); i++) + { + char *pszShareName = NULL; + rc = VbglR3SharedFolderGetName(uClientID, paMappings[i].u32Root, &pszShareName); + if ( RT_SUCCESS(rc) + && *pszShareName) + { + VGSvcVerbose(3, "vbsvcAutoMountWorker: Connecting share %u (%s) ...\n", i+1, pszShareName); + + /** @todo r=bird: why do you copy things twice here and waste heap space? + * szMountPoint has a fixed size. + * @code + * char szMountPoint[RTPATH_MAX]; + * rc = RTPathJoin(szMountPoint, sizeof(szMountPoint), pszMountDir, *pszSharePrefix ? pszSharePrefix : pszShareName); + * if (RT_SUCCESS(rc) && *pszSharePrefix) + * rc = RTStrCat(szMountPoint, sizeof(szMountPoint), pszShareName); + * @endcode */ + char *pszShareNameFull = NULL; + if (RTStrAPrintf(&pszShareNameFull, "%s%s", pszSharePrefix, pszShareName) > 0) + { + char szMountPoint[RTPATH_MAX]; + rc = RTPathJoin(szMountPoint, sizeof(szMountPoint), pszMountDir, pszShareNameFull); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "vbsvcAutoMountWorker: Processing mount point '%s'\n", szMountPoint); + + /* + * Already mounted? + */ + /** @todo r-bird: this does not take into account that a shared folder could + * be mounted twice... We're really just interested in whether the + * folder is mounted on 'szMountPoint', no where else... */ + bool fSkip = false; + char szAlreadyMountedOn[RTPATH_MAX]; + if (vbsvcAutoMountShareIsMountedOld(pszShareName, szAlreadyMountedOn, sizeof(szAlreadyMountedOn))) + { + /* Do if it not mounted to our desired mount point */ + if (RTStrICmp(szMountPoint, szAlreadyMountedOn)) + { + VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder '%s' already mounted on '%s', unmounting ...\n", + pszShareName, szAlreadyMountedOn); + rc = vbsvcAutoMountUnmountOld(szAlreadyMountedOn); + if (RT_SUCCESS(rc)) + fSkip = false; + else + VGSvcError("vbsvcAutoMountWorker: Failed to unmount '%s', %s (%d)! (rc=%Rrc)\n", + szAlreadyMountedOn, strerror(errno), errno, rc); /** @todo errno isn't reliable at this point */ + } + if (fSkip) + VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder '%s' already mounted on '%s', skipping\n", + pszShareName, szAlreadyMountedOn); + } + if (!fSkip) + { + /* + * Mount it. + */ + rc = vbsvcAutoMountSharedFolderOld(pszShareName, szMountPoint); + } + } + else + VGSvcError("vbsvcAutoMountWorker: Unable to join mount point/prefix/shrae, rc = %Rrc\n", rc); + RTStrFree(pszShareNameFull); + } + else + VGSvcError("vbsvcAutoMountWorker: Unable to allocate full share name\n"); + RTStrFree(pszShareName); + } + else + VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder name for root node = %u, rc = %Rrc\n", + paMappings[i].u32Root, rc); + } /* for cMappings. */ + return rc; +} + +#endif /* defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) - the old code*/ + + +/** + * Service worker function for old host. + * + * This only mount stuff on startup. + * + * @returns VBox status code. + * @param pfShutdown Shutdown indicator. + */ +static int vbsvcAutoMountWorkerOld(bool volatile *pfShutdown) +{ +#if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) + /* + * We only do a single pass here. + */ + uint32_t cMappings; + PVBGLR3SHAREDFOLDERMAPPING paMappings; + int rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /* Only process auto-mounted folders */, + &paMappings, &cMappings); + if ( RT_SUCCESS(rc) + && cMappings) + { + char *pszMountDir; + rc = VbglR3SharedFolderGetMountDir(&pszMountDir); + if (rc == VERR_NOT_FOUND) + rc = RTStrDupEx(&pszMountDir, VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder mount dir set to '%s'\n", pszMountDir); + + char *pszSharePrefix; + rc = VbglR3SharedFolderGetMountPrefix(&pszSharePrefix); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder mount prefix set to '%s'\n", pszSharePrefix); +# ifdef USE_VIRTUAL_SHARES + /* Check for a fixed/virtual auto-mount share. */ + if (VbglR3SharedFolderExists(g_idClientSharedFolders, "vbsfAutoMount")) + VGSvcVerbose(3, "vbsvcAutoMountWorker: Host supports auto-mount root\n"); + else + { +# endif + VGSvcVerbose(3, "vbsvcAutoMountWorker: Got %u shared folder mappings\n", cMappings); + rc = vbsvcAutoMountProcessMappingsOld(paMappings, cMappings, pszMountDir, pszSharePrefix, + g_idClientSharedFolders); +# ifdef USE_VIRTUAL_SHARES + } +# endif + RTStrFree(pszSharePrefix); + } /* Mount share prefix. */ + else + VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder mount prefix, rc = %Rrc\n", rc); + RTStrFree(pszMountDir); + } + else + VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder directory, rc = %Rrc\n", rc); + VbglR3SharedFolderFreeMappings(paMappings); + } + else if (RT_FAILURE(rc)) + VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder mappings, rc = %Rrc\n", rc); + else + VGSvcVerbose(3, "vbsvcAutoMountWorker: No shared folder mappings found\n"); + +#else + int rc = VINF_SUCCESS; +#endif /* defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) */ + + + /* + * Wait on shutdown (this used to be a silly RTThreadSleep(500) loop). + */ + while (!*pfShutdown) + { + rc = RTSemEventMultiWait(g_hAutoMountEvent, RT_MS_1MIN); + if (rc != VERR_TIMEOUT) + break; + } + + VGSvcVerbose(3, "vbsvcAutoMountWorkerOld: Finished with rc=%Rrc\n", rc); + return rc; +} + +#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) +/** + * Assembles the mount directory and prefix into @a pszDst. + * + * Will fall back on defaults if we have trouble with the configuration from the + * host. This ASSUMES that @a cbDst is rather large and won't cause trouble + * with the default. + * + * @returns IPRT status code. + * @param pszDst Where to return the prefix. + * @param cbDst The size of the prefix buffer. + */ +static int vbsvcAutomounterQueryMountDirAndPrefix(char *pszDst, size_t cbDst) +{ + /* + * Query the config first. + */ + /* Mount directory: */ + const char *pszDir = VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR; + char *pszCfgDir; + int rc = VbglR3SharedFolderGetMountDir(&pszCfgDir); + if (RT_SUCCESS(rc)) + { + if (*pszCfgDir == '/') + pszDir = pszCfgDir; + } + else + pszCfgDir = NULL; + + /* Prefix: */ + const char *pszPrefix = VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX; + char *pszCfgPrefix; + rc = VbglR3SharedFolderGetMountPrefix(&pszCfgPrefix); + if (RT_SUCCESS(rc)) + { + if ( strchr(pszCfgPrefix, '/') == NULL + && strchr(pszCfgPrefix, '\\') == NULL + && strcmp(pszCfgPrefix, "..") != 0) + pszPrefix = pszCfgPrefix; + } + else + pszCfgPrefix = NULL; + + /* + * Try combine the two. + */ + rc = RTPathAbs(pszDir, pszDst, cbDst); + if (RT_SUCCESS(rc)) + { + if (*pszPrefix) + { + rc = RTPathAppend(pszDst, cbDst, pszPrefix); + if (RT_FAILURE(rc)) + VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathAppend(%s,,%s) -> %Rrc\n", pszDst, pszPrefix, rc); + } + else + { + rc = RTPathEnsureTrailingSeparator(pszDst, cbDst); + if (RT_FAILURE(rc)) + VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathEnsureTrailingSeparator(%s) -> %Rrc\n", pszDst, rc); + } + } + else + VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathAbs(%s) -> %Rrc\n", pszDir, rc); + + + /* + * Return the default dir + prefix if the above failed. + */ + if (RT_FAILURE(rc)) + { + rc = RTStrCopy(pszDst, cbDst, VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR "/" VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX); + AssertRC(rc); + } + + RTStrFree(pszCfgDir); + RTStrFree(pszCfgPrefix); + return rc; +} +#endif /* !RT_OS_WINDOW && !RT_OS_OS2 */ + + +/** + * @callback_method_impl{FNRTSORTCMP, For sorting mount table by root ID. } + */ +static DECLCALLBACK(int) vbsvcAutomounterCompareEntry(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF_PV(pvUser); + PVBSVCAUTOMOUNTERENTRY pEntry1 = (PVBSVCAUTOMOUNTERENTRY)pvElement1; + PVBSVCAUTOMOUNTERENTRY pEntry2 = (PVBSVCAUTOMOUNTERENTRY)pvElement2; + return pEntry1->idRoot < pEntry2->idRoot ? -1 + : pEntry1->idRoot > pEntry2->idRoot ? 1 : 0; +} + + +/** + * Worker for vbsvcAutomounterPopulateTable for adding discovered entries. + * + * This is puts dummies in for missing values, depending on + * vbsvcAutomounterPopulateTable to query them later. + * + * @returns VINF_SUCCESS or VERR_NO_MEMORY; + * @param pMountTable The mount table to add an entry to. + * @param pszName The shared folder name. + * @param pszMountPoint The mount point. + */ +static int vbsvcAutomounterAddEntry(PVBSVCAUTOMOUNTERTABLE pMountTable, const char *pszName, const char *pszMountPoint) +{ + VGSvcVerbose(2, "vbsvcAutomounterAddEntry: %s -> %s\n", pszMountPoint, pszName); + PVBSVCAUTOMOUNTERENTRY pEntry = (PVBSVCAUTOMOUNTERENTRY)RTMemAlloc(sizeof(*pEntry)); + pEntry->idRoot = UINT32_MAX; + pEntry->uRootIdVersion = UINT32_MAX; + pEntry->fFlags = UINT64_MAX; + pEntry->pszName = RTStrDup(pszName); + pEntry->pszMountPoint = NULL; + pEntry->pszActualMountPoint = RTStrDup(pszMountPoint); + if (pEntry->pszName && pEntry->pszActualMountPoint) + { + if (pMountTable->cEntries + 1 <= pMountTable->cAllocated) + { + pMountTable->papEntries[pMountTable->cEntries++] = pEntry; + return VINF_SUCCESS; + } + + void *pvNew = RTMemRealloc(pMountTable->papEntries, (pMountTable->cAllocated + 8) * sizeof(pMountTable->papEntries[0])); + if (pvNew) + { + pMountTable->cAllocated += 8; + pMountTable->papEntries = (PVBSVCAUTOMOUNTERENTRY *)pvNew; + + pMountTable->papEntries[pMountTable->cEntries++] = pEntry; + return VINF_SUCCESS; + } + } + RTMemFree(pEntry->pszActualMountPoint); + RTMemFree(pEntry->pszName); + RTMemFree(pEntry); + return VERR_NO_MEMORY; +} + + +/** + * Populates the mount table as best we can with existing automount entries. + * + * @returns VINF_SUCCESS or VERR_NO_MEMORY; + * @param pMountTable The mount table (empty). + */ +static int vbsvcAutomounterPopulateTable(PVBSVCAUTOMOUNTERTABLE pMountTable) +{ + int rc; + +#ifdef RT_OS_WINDOWS + /* + * Loop thru the drive letters and check out each of them using QueryDosDeviceW. + */ + static const char s_szDevicePath[] = "\\Device\\VBoxMiniRdr\\;"; + for (char chDrive = 'Z'; chDrive >= 'A'; chDrive--) + { + RTUTF16 const wszMountPoint[4] = { (RTUTF16)chDrive, ':', '\0', '\0' }; + RTUTF16 wszTargetPath[RTPATH_MAX]; + DWORD const cwcResult = QueryDosDeviceW(wszMountPoint, wszTargetPath, RT_ELEMENTS(wszTargetPath)); + if ( cwcResult > sizeof(s_szDevicePath) + && RTUtf16NICmpAscii(wszTargetPath, RT_STR_TUPLE(s_szDevicePath)) == 0) + { + PCRTUTF16 pwsz = &wszTargetPath[RT_ELEMENTS(s_szDevicePath) - 1]; + Assert(pwsz[-1] == ';'); + if ( (pwsz[0] & ~(RTUTF16)0x20) == chDrive + && pwsz[1] == ':' + && pwsz[2] == '\\') + { + /* For now we'll just use the special capitalization of the + "server" name to identify it as our work. We could check + if the symlink is from \Global?? or \??, but that trick does + work for older OS versions (<= XP) or when running the + service manually for testing/wathever purposes. */ + /** @todo Modify the windows shared folder driver to allow tagging drives.*/ + if (RTUtf16NCmpAscii(&pwsz[3], RT_STR_TUPLE("VBoxSvr\\")) == 0) + { + pwsz += 3 + 8; + if (*pwsz != '\\' && *pwsz) + { + /* The shared folder name should follow immediately after the server prefix. */ + char *pszMountedName = NULL; + rc = RTUtf16ToUtf8(pwsz, &pszMountedName); + if (RT_SUCCESS(rc)) + { + char const szMountPoint[4] = { chDrive, ':', '\0', '\0' }; + rc = vbsvcAutomounterAddEntry(pMountTable, pszMountedName, szMountPoint); + RTStrFree(pszMountedName); + } + if (RT_FAILURE(rc)) + return rc; + } + else + VGSvcVerbose(2, "vbsvcAutomounterPopulateTable: Malformed, not ours: %ls -> %ls\n", + wszMountPoint, wszTargetPath); + } + else + VGSvcVerbose(3, "vbsvcAutomounterPopulateTable: Not ours: %ls -> %ls\n", wszMountPoint, wszTargetPath); + } + } + } + +#elif defined(RT_OS_OS2) + /* + * Just loop thru the drive letters and check the attachment of each. + */ + for (char chDrive = 'Z'; chDrive >= 'A'; chDrive--) + { + char const szMountPoint[4] = { chDrive, ':', '\0', '\0' }; + union + { + FSQBUFFER2 FsQueryBuf; + char achPadding[1024]; + } uBuf; + RT_ZERO(uBuf); + ULONG cbBuf = sizeof(uBuf) - 2; + APIRET rcOs2 = DosQueryFSAttach(szMountPoint, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf); + if (rcOs2 == NO_ERROR) + { + const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1]; + if ( uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV + && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0) + { + const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1]; + const char *pszTag = pszMountedName + strlen(pszMountedName) + 1; /* (Safe. Always two trailing zero bytes, see above.) */ + if (strcmp(pszTag, g_szTag) == 0) + { + rc = vbsvcAutomounterAddEntry(pMountTable, pszMountedName, szMountPoint); + if (RT_FAILURE(rc)) + return rc; + } + } + } + } + +#elif defined(RT_OS_LINUX) + /* + * Scan the mount table file for the mount point and then match file system + * and device/share. We identify our mounts by mount path + prefix for now, + * but later we may use the same approach as on solaris. + */ + FILE *pFile = setmntent("/proc/mounts", "r"); + int iErrMounts = errno; + if (!pFile) + pFile = setmntent("/etc/mtab", "r"); + if (pFile) + { + rc = VWRN_NOT_FOUND; + struct mntent *pEntry; + while ((pEntry = getmntent(pFile)) != NULL) + if (strcmp(pEntry->mnt_type, "vboxsf") == 0) + if (strstr(pEntry->mnt_opts, g_szTag) != NULL) + { + rc = vbsvcAutomounterAddEntry(pMountTable, pEntry->mnt_fsname, pEntry->mnt_dir); + if (RT_FAILURE(rc)) + { + endmntent(pFile); + return rc; + } + } + endmntent(pFile); + } + else + VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d) or '/proc/mounts' (errno=%d)\n", + _PATH_MOUNTED, errno, iErrMounts); + +#elif defined(RT_OS_SOLARIS) + /* + * Look thru the system mount table and inspect the vboxsf mounts. + */ + FILE *pFile = fopen(_PATH_MOUNTED, "r"); + if (pFile) + { + rc = VINF_SUCCESS; + struct mnttab Entry; + while (getmntent(pFile, &Entry) == 0) + if (strcmp(Entry.mnt_fstype, "vboxfs") == 0) + { + /* Look for the dummy automounter option. */ + if ( Entry.mnt_mntopts != NULL + && strstr(Entry.mnt_mntopts, g_szTag) != NULL) + { + rc = vbsvcAutomounterAddEntry(pMountTable, Entry.mnt_special, Entry.mnt_mountp); + if (RT_FAILURE(rc)) + { + fclose(pFile); + return rc; + } + } + } + fclose(pFile); + } + else + VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d)\n", _PATH_MOUNTED, errno); + +#else +# error "PORTME!" +#endif + + /* + * Try reconcile the detected folders with data from the host. + */ + uint32_t cMappings = 0; + PVBGLR3SHAREDFOLDERMAPPING paMappings = NULL; + rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /*fAutoMountOnly*/, &paMappings, &cMappings); + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < cMappings && RT_SUCCESS(rc); i++) + { + uint32_t const idRootSrc = paMappings[i].u32Root; + + uint32_t uRootIdVer = UINT32_MAX; + uint64_t fFlags = 0; + char *pszName = NULL; + char *pszMntPt = NULL; + int rc2 = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF, + &pszName, &pszMntPt, &fFlags, &uRootIdVer); + if (RT_SUCCESS(rc2)) + { + uint32_t iPrevHit = UINT32_MAX; + for (uint32_t iTable = 0; iTable < pMountTable->cEntries; iTable++) + { + PVBSVCAUTOMOUNTERENTRY pEntry = pMountTable->papEntries[iTable]; + if (RTStrICmp(pEntry->pszName, pszName) == 0) + { + VGSvcVerbose(2, "vbsvcAutomounterPopulateTable: Identified %s -> %s: idRoot=%u ver=%u fFlags=%#x AutoMntPt=%s\n", + pEntry->pszActualMountPoint, pEntry->pszName, idRootSrc, uRootIdVer, fFlags, pszMntPt); + pEntry->fFlags = fFlags; + pEntry->idRoot = idRootSrc; + pEntry->uRootIdVersion = uRootIdVer; + RTStrFree(pEntry->pszMountPoint); + pEntry->pszMountPoint = RTStrDup(pszMntPt); + if (!pEntry->pszMountPoint) + { + rc = VERR_NO_MEMORY; + break; + } + + /* If multiple mappings of the same folder, pick the first or the one + with matching mount point. */ + if (iPrevHit == UINT32_MAX) + iPrevHit = iTable; + else if (RTPathCompare(pszMntPt, pEntry->pszActualMountPoint) == 0) + { + if (iPrevHit != UINT32_MAX) + pMountTable->papEntries[iPrevHit]->uRootIdVersion -= 1; + iPrevHit = iTable; + } + else + pEntry->uRootIdVersion -= 1; + } + } + + RTStrFree(pszName); + RTStrFree(pszMntPt); + } + else + VGSvcError("vbsvcAutomounterPopulateTable: VbglR3SharedFolderQueryFolderInfo(%u) failed: %Rrc\n", idRootSrc, rc2); + } + + VbglR3SharedFolderFreeMappings(paMappings); + + /* + * Sort the table by root ID. + */ + if (pMountTable->cEntries > 1) + RTSortApvShell((void **)pMountTable->papEntries, pMountTable->cEntries, vbsvcAutomounterCompareEntry, NULL); + + for (uint32_t iTable = 0; iTable < pMountTable->cEntries; iTable++) + { + PVBSVCAUTOMOUNTERENTRY pEntry = pMountTable->papEntries[iTable]; + if (pMountTable->papEntries[iTable]->idRoot != UINT32_MAX) + VGSvcVerbose(1, "vbsvcAutomounterPopulateTable: #%u: %s -> %s idRoot=%u ver=%u fFlags=%#x AutoMntPt=%s\n", + iTable, pEntry->pszActualMountPoint, pEntry->pszName, pEntry->idRoot, pEntry->uRootIdVersion, + pEntry->fFlags, pEntry->pszMountPoint); + else + VGSvcVerbose(1, "vbsvcAutomounterPopulateTable: #%u: %s -> %s - not identified!\n", + iTable, pEntry->pszActualMountPoint, pEntry->pszName); + } + } + else + VGSvcError("vbsvcAutomounterPopulateTable: VbglR3SharedFolderGetMappings failed: %Rrc\n", rc); + return rc; +} + + +/** + * Checks whether the shared folder @a pszName is mounted on @a pszMountPoint. + * + * @returns Exactly one of the following IPRT status codes; + * @retval VINF_SUCCESS if mounted + * @retval VWRN_NOT_FOUND if nothing is mounted at @a pszMountPoint. + * @retval VERR_RESOURCE_BUSY if a different shared folder is mounted there. + * @retval VERR_ACCESS_DENIED if a non-shared folder file system is mounted + * there. + * + * @param pszMountPoint The mount point to check. + * @param pszName The name of the shared folder (mapping). + */ +static int vbsvcAutomounterQueryMountPoint(const char *pszMountPoint, const char *pszName) +{ + VGSvcVerbose(4, "vbsvcAutomounterQueryMountPoint: pszMountPoint=%s pszName=%s\n", pszMountPoint, pszName); + +#ifdef RT_OS_WINDOWS + /* + * We could've used RTFsQueryType here but would then have to + * calling RTFsQueryLabel for the share name hint, ending up + * doing the same work twice. We could also use QueryDosDeviceW, + * but output is less clear... + */ + PRTUTF16 pwszMountPoint = NULL; + int rc = RTStrToUtf16(pszMountPoint, &pwszMountPoint); + if (RT_SUCCESS(rc)) + { + DWORD uSerial = 0; + DWORD cchCompMax = 0; + DWORD fFlags = 0; + RTUTF16 wszLabel[512]; + RTUTF16 wszFileSystem[256]; + RT_ZERO(wszLabel); + RT_ZERO(wszFileSystem); + if (GetVolumeInformationW(pwszMountPoint, wszLabel, RT_ELEMENTS(wszLabel) - 1, &uSerial, &cchCompMax, &fFlags, + wszFileSystem, RT_ELEMENTS(wszFileSystem) - 1)) + { + if (RTUtf16ICmpAscii(wszFileSystem, "VBoxSharedFolderFS") == 0) + { + char *pszLabel = NULL; + rc = RTUtf16ToUtf8(wszLabel, &pszLabel); + if (RT_SUCCESS(rc)) + { + const char *pszMountedName = pszLabel; + if (RTStrStartsWith(pszMountedName, "VBOX_")) + pszMountedName += sizeof("VBOX_") - 1; + if (RTStrICmp(pszMountedName, pszName) == 0) + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n", + pszName, pszMountPoint); + rc = VINF_SUCCESS; + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n", + pszMountedName, pszMountPoint, pszName); + rc = VERR_RESOURCE_BUSY; + } + RTStrFree(pszLabel); + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: RTUtf16ToUtf8(%ls,) failed: %Rrc\n", wszLabel, rc); + rc = VERR_RESOURCE_BUSY; + } + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%ls' with label '%ls' mount at '%s', not '%s'...\n", + wszFileSystem, wszLabel, pszMountPoint, pszName); + rc = VERR_ACCESS_DENIED; + } + } + else + { + rc = GetLastError(); + if (rc != ERROR_PATH_NOT_FOUND || g_cVerbosity >= 4) + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: GetVolumeInformationW('%ls',,,,) failed: %u\n", pwszMountPoint, rc); + if (rc == ERROR_PATH_NOT_FOUND) + rc = VWRN_NOT_FOUND; + else if ( RT_C_IS_ALPHA(pszMountPoint[0]) + && pszMountPoint[1] == ':' + && ( pszMountPoint[2] == '\0' + || (RTPATH_IS_SLASH(pszMountPoint[2]) && pszMountPoint[3] == '\0'))) + { + /* See whether QueryDosDeviceW thinks its a malfunctioning shared folder or + something else (it doesn't access the file system). We've seen + VERR_NET_HOST_NOT_FOUND here for shared folders that was removed on the + host side. + + Note! This duplicates code from vbsvcAutomounterPopulateTable. */ + rc = VERR_ACCESS_DENIED; + static const char s_szDevicePath[] = "\\Device\\VBoxMiniRdr\\;"; + wszFileSystem[0] = pwszMountPoint[0]; + wszFileSystem[1] = pwszMountPoint[1]; + wszFileSystem[2] = '\0'; + DWORD const cwcResult = QueryDosDeviceW(wszFileSystem, wszLabel, RT_ELEMENTS(wszLabel)); + if ( cwcResult > sizeof(s_szDevicePath) + && RTUtf16NICmpAscii(wszLabel, RT_STR_TUPLE(s_szDevicePath)) == 0) + { + PCRTUTF16 pwsz = &wszLabel[RT_ELEMENTS(s_szDevicePath) - 1]; + Assert(pwsz[-1] == ';'); + if ( (pwsz[0] & ~(RTUTF16)0x20) == (wszFileSystem[0] & ~(RTUTF16)0x20) + && pwsz[1] == ':' + && pwsz[2] == '\\') + { + if (RTUtf16NICmpAscii(&pwsz[3], RT_STR_TUPLE("VBoxSvr\\")) == 0) + { + pwsz += 3 + 8; + char *pszMountedName = NULL; + rc = RTUtf16ToUtf8(pwsz, &pszMountedName); + if (RT_SUCCESS(rc)) + { + if (RTStrICmp(pszMountedName, pszName) == 0) + { + rc = VINF_SUCCESS; + VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s' (using QueryDosDeviceW).\n", + pszName, pszMountPoint); + } + else + { + VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s' (using QueryDosDeviceW), not '%s'...\n", + pszMountedName, pszMountPoint, pszName); + rc = VERR_RESOURCE_BUSY; + } + RTStrFree(pszMountedName); + } + else + { + VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: RTUtf16ToUtf8 failed: %Rrc\n", rc); + AssertRC(rc); + rc = VERR_RESOURCE_BUSY; + } + } + } + } + } + else + rc = VERR_ACCESS_DENIED; + } + RTUtf16Free(pwszMountPoint); + } + else + { + VGSvcError("vbsvcAutomounterQueryMountPoint: RTStrToUtf16(%s,) -> %Rrc\n", pszMountPoint, rc); + rc = VWRN_NOT_FOUND; + } + return rc; + +#elif defined(RT_OS_OS2) + /* + * Query file system attachment info for the given drive letter. + */ + union + { + FSQBUFFER2 FsQueryBuf; + char achPadding[512]; + } uBuf; + RT_ZERO(uBuf); + + ULONG cbBuf = sizeof(uBuf); + APIRET rcOs2 = DosQueryFSAttach(pszMountPoint, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf); + int rc; + if (rcOs2 == NO_ERROR) + { + const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1]; + if ( uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV + && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0) + { + const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1]; + if (RTStrICmp(pszMountedName, pszName) == 0) + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n", + pszName, pszMountPoint); + rc = VINF_SUCCESS; + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n", + pszMountedName, pszMountPoint, pszName); + rc = VERR_RESOURCE_BUSY; + } + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' type %u mount at '%s', not '%s'...\n", + pszFsdName, uBuf.FsQueryBuf.iType, pszMountPoint, pszName); + rc = VERR_ACCESS_DENIED; + } + } + else + { + rc = VWRN_NOT_FOUND; + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: DosQueryFSAttach(%s) -> %u\n", pszMountPoint, rcOs2); + AssertMsgStmt(rcOs2 != ERROR_BUFFER_OVERFLOW && rcOs2 != ERROR_INVALID_PARAMETER, + ("%s -> %u\n", pszMountPoint, rcOs2), rc = VERR_ACCESS_DENIED); + } + return rc; + +#elif defined(RT_OS_LINUX) + /* + * Scan one of the mount table file for the mount point and then + * match file system and device/share. + */ + FILE *pFile = setmntent("/proc/mounts", "r"); + int rc = errno; + if (!pFile) + pFile = setmntent(_PATH_MOUNTED, "r"); + if (pFile) + { + rc = VWRN_NOT_FOUND; + struct mntent *pEntry; + while ((pEntry = getmntent(pFile)) != NULL) + if (RTPathCompare(pEntry->mnt_dir, pszMountPoint) == 0) + { + if (strcmp(pEntry->mnt_type, "vboxsf") == 0) + { + if (RTStrICmp(pEntry->mnt_fsname, pszName) == 0) + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n", + pszName, pszMountPoint); + rc = VINF_SUCCESS; + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n", + pEntry->mnt_fsname, pszMountPoint, pszName); + rc = VERR_RESOURCE_BUSY; + } + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' mount of '%s' at '%s', not '%s'...\n", + pEntry->mnt_type, pEntry->mnt_fsname, pszMountPoint, pszName); + rc = VERR_ACCESS_DENIED; + } + /* We continue searching in case of stacked mounts, we want the last one. */ + } + endmntent(pFile); + } + else + { + VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '/proc/mounts' (errno=%d) or '%s' (errno=%d)\n", + rc, _PATH_MOUNTED, errno); + rc = VERR_ACCESS_DENIED; + } + return rc; + +#elif defined(RT_OS_SOLARIS) + /* + * Similar to linux. + */ + int rc; + FILE *pFile = fopen(_PATH_MOUNTED, "r"); + if (pFile) + { + rc = VWRN_NOT_FOUND; + struct mnttab Entry; + while (getmntent(pFile, &Entry) == 0) + if (RTPathCompare(Entry.mnt_mountp, pszMountPoint) == 0) + { + if (strcmp(Entry.mnt_fstype, "vboxfs") == 0) + { + if (RTStrICmp(Entry.mnt_special, pszName) == 0) + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n", + pszName, pszMountPoint); + rc = VINF_SUCCESS; + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n", + Entry.mnt_special, pszMountPoint, pszName); + rc = VERR_RESOURCE_BUSY; + } + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' mount of '%s' at '%s', not '%s'...\n", + Entry.mnt_fstype, Entry.mnt_special, pszMountPoint, pszName); + rc = VERR_ACCESS_DENIED; + } + /* We continue searching in case of stacked mounts, we want the last one. */ + } + fclose(pFile); + } + else + { + VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d)\n", _PATH_MOUNTED, errno); + rc = VERR_ACCESS_DENIED; + } + return rc; +#else +# error "PORTME" +#endif +} + + +/** + * Worker for vbsvcAutomounterMountNewEntry that does the OS mounting. + * + * @returns IPRT status code. + * @param pEntry The entry to try mount. + */ +static int vbsvcAutomounterMountIt(PVBSVCAUTOMOUNTERENTRY pEntry) +{ + VGSvcVerbose(3, "vbsvcAutomounterMountIt: Trying to mount '%s' (idRoot=%#x) on '%s'...\n", + pEntry->pszName, pEntry->idRoot, pEntry->pszActualMountPoint); +#ifdef RT_OS_WINDOWS + /* + * Attach the shared folder using WNetAddConnection2W. + * + * According to google we should get a drive symlink in \\GLOBAL?? when + * we are running under the system account. Otherwise it will be a session + * local link (\\??). + */ + Assert(RT_C_IS_UPPER(pEntry->pszActualMountPoint[0]) && pEntry->pszActualMountPoint[1] == ':' && pEntry->pszActualMountPoint[2] == '\0'); + RTUTF16 wszDrive[4] = { (RTUTF16)pEntry->pszActualMountPoint[0], ':', '\0', '\0' }; + + RTUTF16 wszPrefixedName[RTPATH_MAX]; + int rc = RTUtf16CopyAscii(wszPrefixedName, RT_ELEMENTS(wszPrefixedName), "\\\\VBoxSvr\\"); + AssertRC(rc); + + size_t const offName = RTUtf16Len(wszPrefixedName); + PRTUTF16 pwszName = &wszPrefixedName[offName]; + rc = RTStrToUtf16Ex(pEntry->pszName, RTSTR_MAX, &pwszName, sizeof(wszPrefixedName) - offName, NULL); + if (RT_FAILURE(rc)) + { + VGSvcError("vbsvcAutomounterMountIt: RTStrToUtf16Ex failed on '%s': %Rrc\n", pEntry->pszName, rc); + return rc; + } + + VGSvcVerbose(3, "vbsvcAutomounterMountIt: wszDrive='%ls', wszPrefixedName='%ls'\n", + wszDrive, wszPrefixedName); + + NETRESOURCEW NetRsrc; + RT_ZERO(NetRsrc); + NetRsrc.dwType = RESOURCETYPE_DISK; + NetRsrc.lpLocalName = wszDrive; + NetRsrc.lpRemoteName = wszPrefixedName; + NetRsrc.lpProvider = L"VirtualBox Shared Folders"; /* Only try our provider. */ + NetRsrc.lpComment = pwszName; + + DWORD dwErr = WNetAddConnection2W(&NetRsrc, NULL /*pwszPassword*/, NULL /*pwszUserName*/, 0 /*dwFlags*/); + if (dwErr == NO_ERROR) + { + VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n", + pEntry->pszName, pEntry->pszActualMountPoint); + return VINF_SUCCESS; + } + VGSvcError("vbsvcAutomounterMountIt: Failed to attach '%s' to '%s': %Rrc (%u)\n", + pEntry->pszName, pEntry->pszActualMountPoint, RTErrConvertFromWin32(dwErr), dwErr); + return VERR_OPEN_FAILED; + +#elif defined(RT_OS_OS2) + /* + * It's a rather simple affair on OS/2. + * + * In order to be able to detect our mounts we add a 2nd string after + * the folder name that tags the attachment. The IFS will remember this + * and return it when DosQueryFSAttach is called. + * + * Note! Kernel currently accepts limited 7-bit ASCII names. We could + * change that to UTF-8 if we like as that means no extra string + * encoding conversion fun here. + */ + char szzNameAndTag[256]; + size_t cchName = strlen(pEntry->pszName); + if (cchName + 1 + sizeof(g_szTag) <= sizeof(szzNameAndTag)) + { + memcpy(szzNameAndTag, pEntry->pszName, cchName); + szzNameAndTag[cchName] = '\0'; + memcpy(&szzNameAndTag[cchName + 1], g_szTag, sizeof(g_szTag)); + + APIRET rc = DosFSAttach(pEntry->pszActualMountPoint, "VBOXSF", szzNameAndTag, cchName + 1 + sizeof(g_szTag), FS_ATTACH); + if (rc == NO_ERROR) + { + VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n", + pEntry->pszName, pEntry->pszActualMountPoint); + return VINF_SUCCESS; + } + VGSvcError("vbsvcAutomounterMountIt: DosFSAttach failed to attach '%s' to '%s': %u\n", + pEntry->pszName, pEntry->pszActualMountPoint, rc); + } + else + VGSvcError("vbsvcAutomounterMountIt: Share name for attach to '%s' is too long: %u chars - '%s'\n", + pEntry->pszActualMountPoint, cchName, pEntry->pszName); + return VERR_OPEN_FAILED; + +#else + /* + * Common work for unix-like systems: Get group, make sure mount directory exist. + */ + int rc = RTDirCreateFullPath(pEntry->pszActualMountPoint, + RTFS_UNIX_IRWXU | RTFS_UNIX_IXGRP | RTFS_UNIX_IRGRP | RTFS_UNIX_IXOTH | RTFS_UNIX_IROTH); + if (RT_FAILURE(rc)) + { + VGSvcError("vbsvcAutomounterMountIt: Failed to create mount path '%s' for share '%s': %Rrc\n", + pEntry->pszActualMountPoint, pEntry->pszName, rc); + return rc; + } + + gid_t gidMount; + struct group *grp_vboxsf = getgrnam("vboxsf"); + if (grp_vboxsf) + gidMount = grp_vboxsf->gr_gid; + else + { + VGSvcError("vbsvcAutomounterMountIt: Group 'vboxsf' does not exist\n"); + gidMount = 0; + } + +# if defined(RT_OS_LINUX) + /* + * Linux a bit more work... + */ + struct utsname uts; + AssertStmt(uname(&uts) != -1, strcpy(uts.release, "4.4.0")); + + /* Built mount option string. Need st_name for pre 2.6.0 kernels. */ + unsigned long const fFlags = MS_NODEV; + char szOpts[MAX_MNTOPT_STR] = { '\0' }; + ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts), + "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000,tag=%s", gidMount, g_szTag); + if (RTStrVersionCompare(uts.release, "2.6.0") < 0 && cchOpts > 0) + cchOpts += RTStrPrintf2(&szOpts[cchOpts], sizeof(szOpts) - cchOpts, ",sf_name=%s", pEntry->pszName); + if (cchOpts <= 0) + { + VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd\n", cchOpts); + return VERR_BUFFER_OVERFLOW; + } + + /* Do the mounting. The fallback w/o tag is for the Linux vboxsf fork + which lagged a lot behind when it first appeared in 5.6. */ + errno = 0; + rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, szOpts); + if (rc != 0 && errno == EINVAL && RTStrVersionCompare(uts.release, "5.6.0") >= 0) + { + VGSvcVerbose(2, "vbsvcAutomounterMountIt: mount returned EINVAL, retrying without the tag.\n"); + *strstr(szOpts, ",tag=") = '\0'; + errno = 0; + rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, szOpts); + if (rc == 0) + VGSvcVerbose(0, "vbsvcAutomounterMountIt: Running outdated vboxsf module without support for the 'tag' option?\n"); + } + if (rc == 0) + { + VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n", + pEntry->pszName, pEntry->pszActualMountPoint); + + errno = 0; + rc = vbsfmount_complete(pEntry->pszName, pEntry->pszActualMountPoint, fFlags, szOpts); + if (rc != 0) /* Ignorable. /etc/mtab is probably a link to /proc/mounts. */ + VGSvcVerbose(1, "vbsvcAutomounterMountIt: vbsfmount_complete failed: %s (%d/%d)\n", + rc == 1 ? "malloc" : rc == 2 ? "setmntent" : rc == 3 ? "addmntent" : "unknown", rc, errno); + return VINF_SUCCESS; + } + + if (errno == EINVAL) + VGSvcError("vbsvcAutomounterMountIt: Failed to mount '%s' on '%s' because it is probably mounted elsewhere arleady! (%d,%d)\n", + pEntry->pszName, pEntry->pszActualMountPoint, rc, errno); + else + VGSvcError("vbsvcAutomounterMountIt: Failed to mount '%s' on '%s': %s (%d,%d)\n", + pEntry->pszName, pEntry->pszActualMountPoint, strerror(errno), rc, errno); + return VERR_WRITE_ERROR; + +# elif defined(RT_OS_SOLARIS) + /* + * Solaris is rather simple compared to linux. + * + * The ',VBoxService=auto' option (g_szTag) is ignored by the kernel but helps + * us identify our own mounts on restart. See vbsvcAutomounterPopulateTable(). + * + * Note! Must pass MAX_MNTOPT_STR rather than cchOpts to mount, as it may fail + * with EOVERFLOW in vfs_buildoptionstr() during domount() otherwise. + */ + char szOpts[MAX_MNTOPT_STR] = { '\0', }; + ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts), + "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000,tag=%s", gidMount, g_szTag); + if (cchOpts <= 0) + { + VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd\n", cchOpts); + return VERR_BUFFER_OVERFLOW; + } + + rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, MS_OPTIONSTR, "vboxfs", + NULL /*dataptr*/, 0 /* datalen */, szOpts, MAX_MNTOPT_STR); + if (rc == 0) + { + VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n", + pEntry->pszName, pEntry->pszActualMountPoint); + return VINF_SUCCESS; + } + + rc = errno; + VGSvcError("vbsvcAutomounterMountIt: mount failed for '%s' on '%s' (szOpts=%s): %s (%d)\n", + pEntry->pszName, pEntry->pszActualMountPoint, szOpts, strerror(rc), rc); + return VERR_OPEN_FAILED; + +# else +# error "PORTME!" +# endif +#endif +} + + +/** + * Attempts to mount the given shared folder, adding it to the mount table on + * success. + * + * @returns iTable + 1 on success, iTable on failure. + * @param pTable The mount table. + * @param iTable The mount table index at which to add the mount. + * @param pszName The name of the shared folder mapping. + * @param pszMntPt The mount point (hint) specified by the host. + * @param fFlags The shared folder flags, SHFL_MIF_XXX. + * @param idRoot The root ID. + * @param uRootIdVersion The root ID version. + * @param fAutoMntPt Whether to try automatically assign a mount point if + * pszMntPt doesn't work out. This is set in pass \#3. + */ +static uint32_t vbsvcAutomounterMountNewEntry(PVBSVCAUTOMOUNTERTABLE pTable, uint32_t iTable, + const char *pszName, const char *pszMntPt, uint64_t fFlags, + uint32_t idRoot, uint32_t uRootIdVersion, bool fAutoMntPt) +{ + VGSvcVerbose(3, "vbsvcAutomounterMountNewEntry: #%u: '%s' at '%s'%s\n", + iTable, pszName, pszMntPt, fAutoMntPt ? " auto-assign" : ""); + + /* + * First we need to figure out the actual mount point. + */ + char szActualMountPoint[RTPATH_MAX]; + +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + /* + * Drive letter based. We only care about the first two characters + * and ignore the rest (see further down). + */ + char chNextLetter = 'Z'; + if (RT_C_IS_ALPHA(pszMntPt[0]) && pszMntPt[1] == ':') + szActualMountPoint[0] = RT_C_TO_UPPER(pszMntPt[0]); + else if (!fAutoMntPt) + return iTable; + else + szActualMountPoint[0] = chNextLetter--; + szActualMountPoint[1] = ':'; + szActualMountPoint[2] = '\0'; + + int rc; + for (;;) + { + rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName); + if (rc == VWRN_NOT_FOUND) + break; + + /* next */ + if (chNextLetter == 'A' || !fAutoMntPt) + return iTable; + szActualMountPoint[0] = chNextLetter--; + } + +#else + /* + * Path based #1: Host specified mount point. + */ + + /* Skip DOS drive letter if there is a UNIX mount point path following it: */ + if ( pszMntPt[0] != '/' + && pszMntPt[0] != '\0' + && pszMntPt[1] == ':' + && pszMntPt[2] == '/') + pszMntPt += 2; + + /* Try specified mount point if it starts with a UNIX slash: */ + int rc = VERR_ACCESS_DENIED; + if (*pszMntPt == '/') + { + rc = RTPathAbs(pszMntPt, szActualMountPoint, sizeof(szActualMountPoint)); + if (RT_SUCCESS(rc)) + { + static const char * const s_apszBlacklist[] = + { "/", "/dev", "/bin", "/sbin", "/lib", "/etc", "/var", "/tmp", "/usr", "/usr/bin", "/usr/sbin", "/usr/lib" }; + for (size_t i = 0; i < RT_ELEMENTS(s_apszBlacklist); i++) + if (strcmp(szActualMountPoint, s_apszBlacklist[i]) == 0) + { + rc = VERR_ACCESS_DENIED; + break; + } + if (RT_SUCCESS(rc)) + rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName); + } + } + if (rc != VWRN_NOT_FOUND) + { + if (!fAutoMntPt) + return iTable; + + /* + * Path based #2: Mount dir + prefix + share. + */ + rc = vbsvcAutomounterQueryMountDirAndPrefix(szActualMountPoint, sizeof(szActualMountPoint)); + if (RT_SUCCESS(rc)) + { + /* Append a sanitized share name: */ + size_t const offShare = strlen(szActualMountPoint); + size_t offDst = offShare; + size_t offSrc = 0; + for (;;) + { + char ch = pszName[offSrc++]; + if (ch == ' ' || ch == '/' || ch == '\\' || ch == ':' || ch == '$') + ch = '_'; + else if (!ch) + break; + else if (ch < 0x20 || ch == 0x7f) + continue; + if (offDst < sizeof(szActualMountPoint) - 1) + szActualMountPoint[offDst++] = ch; + } + szActualMountPoint[offDst] = '\0'; + if (offDst > offShare) + { + rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName); + if (rc != VWRN_NOT_FOUND) + { + /* + * Path based #3: Mount dir + prefix + share + _ + number. + */ + if (offDst + 2 >= sizeof(szActualMountPoint)) + return iTable; + + szActualMountPoint[offDst++] = '_'; + for (uint32_t iTry = 1; iTry < 10 && rc != VWRN_NOT_FOUND; iTry++) + { + szActualMountPoint[offDst] = '0' + iTry; + szActualMountPoint[offDst + 1] = '\0'; + rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName); + } + if (rc != VWRN_NOT_FOUND) + return iTable; + } + } + else + VGSvcError("vbsvcAutomounterMountNewEntry: Bad share name: %.*Rhxs", strlen(pszName), pszName); + } + else + VGSvcError("vbsvcAutomounterMountNewEntry: Failed to construct basic auto mount point for '%s'", pszName); + } +#endif + + /* + * Prepare a table entry and ensure space in the table.. + */ + if (pTable->cEntries + 1 > pTable->cAllocated) + { + void *pvEntries = RTMemRealloc(pTable->papEntries, sizeof(pTable->papEntries[0]) * (pTable->cAllocated + 8)); + if (!pvEntries) + { + VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for growing table (size %u)\n", pTable->cAllocated); + return iTable; + } + pTable->cAllocated += 8; + pTable->papEntries = (PVBSVCAUTOMOUNTERENTRY *)pvEntries; + } + + PVBSVCAUTOMOUNTERENTRY pEntry = (PVBSVCAUTOMOUNTERENTRY)RTMemAlloc(sizeof(*pEntry)); + if (pEntry) + { + pEntry->idRoot = idRoot; + pEntry->uRootIdVersion = uRootIdVersion; + pEntry->fFlags = fFlags; + pEntry->pszName = RTStrDup(pszName); + pEntry->pszMountPoint = RTStrDup(pszMntPt); + pEntry->pszActualMountPoint = RTStrDup(szActualMountPoint); + if (pEntry->pszName && pEntry->pszMountPoint && pEntry->pszActualMountPoint) + { + /* + * Now try mount it. + */ + rc = vbsvcAutomounterMountIt(pEntry); + if (RT_SUCCESS(rc)) + { + uint32_t cToMove = pTable->cEntries - iTable; + if (cToMove > 0) + memmove(&pTable->papEntries[iTable + 1], &pTable->papEntries[iTable], cToMove * sizeof(pTable->papEntries[0])); + pTable->papEntries[iTable] = pEntry; + pTable->cEntries++; + return iTable + 1; + } + } + else + VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for table entry!\n"); + RTMemFree(pEntry->pszActualMountPoint); + RTMemFree(pEntry->pszMountPoint); + RTMemFree(pEntry->pszName); + RTMemFree(pEntry); + } + else + VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for table entry!\n"); + return iTable; +} + + + +/** + * Does the actual unmounting. + * + * @returns Exactly one of the following IPRT status codes; + * @retval VINF_SUCCESS if successfully umounted or nothing was mounted there. + * @retval VERR_TRY_AGAIN if the shared folder is busy. + * @retval VERR_RESOURCE_BUSY if a different shared folder is mounted there. + * @retval VERR_ACCESS_DENIED if a non-shared folder file system is mounted + * there. + * + * @param pszMountPoint The mount point. + * @param pszName The shared folder (mapping) name. + */ +static int vbsvcAutomounterUnmount(const char *pszMountPoint, const char *pszName) +{ + /* + * Retry for 5 seconds in a hope that busy mounts will quiet down. + */ + for (unsigned iTry = 0; ; iTry++) + { + /* + * Check what's mounted there before we start umounting stuff. + */ + int rc = vbsvcAutomounterQueryMountPoint(pszMountPoint, pszName); + if (rc == VINF_SUCCESS) + { /* pszName is mounted there */ } + else if (rc == VWRN_NOT_FOUND) /* nothing mounted there */ + return VINF_SUCCESS; + else + { + Assert(rc == VERR_RESOURCE_BUSY || rc == VERR_ACCESS_DENIED); + return VERR_RESOURCE_BUSY; + } + + /* + * Do host specific unmounting. + */ +#ifdef RT_OS_WINDOWS + Assert(RT_C_IS_UPPER(pszMountPoint[0]) && pszMountPoint[1] == ':' && pszMountPoint[2] == '\0'); + RTUTF16 const wszDrive[4] = { (RTUTF16)pszMountPoint[0], ':', '\0', '\0' }; + DWORD dwErr = WNetCancelConnection2W(wszDrive, 0 /*dwFlags*/, FALSE /*fForce*/); + if (dwErr == NO_ERROR) + return VINF_SUCCESS; + VGSvcVerbose(2, "vbsvcAutomounterUnmount: WNetCancelConnection2W returns %u for '%s' ('%s')\n", dwErr, pszMountPoint, pszName); + if (dwErr == ERROR_NOT_CONNECTED) + return VINF_SUCCESS; + +#elif defined(RT_OS_OS2) + APIRET rcOs2 = DosFSAttach(pszMountPoint, "VBOXSF", NULL, 0, FS_DETACH); + if (rcOs2 == NO_ERROR) + return VINF_SUCCESS; + VGSvcVerbose(2, "vbsvcAutomounterUnmount: DosFSAttach failed on '%s' ('%s'): %u\n", pszMountPoint, pszName, rcOs2); + if (rcOs2 == ERROR_INVALID_FSD_NAME) + return VERR_ACCESS_DENIED; + if ( rcOs2 == ERROR_INVALID_DRIVE + || rcOs2 == ERROR_INVALID_PATH) + return VERR_TRY_AGAIN; + +#else + int rc2 = umount(pszMountPoint); + if (rc2 == 0) + { + /* Remove the mount directory if not directly under the root dir. */ + RTPATHPARSED Parsed; + RT_ZERO(Parsed); + RTPathParse(pszMountPoint, &Parsed, sizeof(Parsed), RTPATH_STR_F_STYLE_HOST); + if (Parsed.cComps >= 3) + RTDirRemove(pszMountPoint); + + return VINF_SUCCESS; + } + rc2 = errno; + VGSvcVerbose(2, "vbsvcAutomounterUnmount: umount failed on '%s' ('%s'): %d\n", pszMountPoint, pszName, rc2); + if (rc2 != EBUSY && rc2 != EAGAIN) + return VERR_ACCESS_DENIED; +#endif + + /* + * Check what's mounted there before we start delaying. + */ + RTThreadSleep(8); /* fudge */ + rc = vbsvcAutomounterQueryMountPoint(pszMountPoint, pszName); + if (rc == VINF_SUCCESS) + { /* pszName is mounted there */ } + else if (rc == VWRN_NOT_FOUND) /* nothing mounted there */ + return VINF_SUCCESS; + else + { + Assert(rc == VERR_RESOURCE_BUSY || rc == VERR_ACCESS_DENIED); + return VERR_RESOURCE_BUSY; + } + + if (iTry >= 5) + return VERR_TRY_AGAIN; + RTThreadSleep(1000); + } +} + + +/** + * Unmounts a mount table entry and evicts it from the table if successful. + * + * @returns The next iTable (same value on success, +1 on failure). + * @param pTable The mount table. + * @param iTable The table entry. + * @param pszReason Why we're here. + */ +static uint32_t vbsvcAutomounterUnmountEntry(PVBSVCAUTOMOUNTERTABLE pTable, uint32_t iTable, const char *pszReason) +{ + Assert(iTable < pTable->cEntries); + PVBSVCAUTOMOUNTERENTRY pEntry = pTable->papEntries[iTable]; + VGSvcVerbose(2, "vbsvcAutomounterUnmountEntry: #%u: '%s' at '%s' (reason: %s)\n", + iTable, pEntry->pszName, pEntry->pszActualMountPoint, pszReason); + + /* + * Do we need to umount the entry? Return if unmount fails and we . + */ + if (pEntry->pszActualMountPoint) + { + int rc = vbsvcAutomounterUnmount(pEntry->pszActualMountPoint, pEntry->pszName); + if (rc == VERR_TRY_AGAIN) + { + VGSvcVerbose(1, "vbsvcAutomounterUnmountEntry: Keeping '%s' -> '%s' (VERR_TRY_AGAIN)\n", + pEntry->pszActualMountPoint, pEntry->pszName); + return iTable + 1; + } + } + + /* + * Remove the entry by shifting up the ones after it. + */ + pTable->cEntries -= 1; + uint32_t cAfter = pTable->cEntries - iTable; + if (cAfter) + memmove(&pTable->papEntries[iTable], &pTable->papEntries[iTable + 1], cAfter * sizeof(pTable->papEntries[0])); + pTable->papEntries[pTable->cEntries] = NULL; + + RTStrFree(pEntry->pszActualMountPoint); + pEntry->pszActualMountPoint = NULL; + RTStrFree(pEntry->pszMountPoint); + pEntry->pszMountPoint = NULL; + RTStrFree(pEntry->pszName); + pEntry->pszName = NULL; + RTMemFree(pEntry); + + return iTable; +} + + +/** + * @callback_method_impl{FNRTSORTCMP, For sorting the mappings by ID. } + */ +static DECLCALLBACK(int) vbsvcSharedFolderMappingCompare(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF_PV(pvUser); + PVBGLR3SHAREDFOLDERMAPPING pMapping1 = (PVBGLR3SHAREDFOLDERMAPPING)pvElement1; + PVBGLR3SHAREDFOLDERMAPPING pMapping2 = (PVBGLR3SHAREDFOLDERMAPPING)pvElement2; + return pMapping1->u32Root < pMapping2->u32Root ? -1 : pMapping1->u32Root != pMapping2->u32Root ? 1 : 0; +} + + +/** + * Refreshes the mount table. + * + * @returns true if we've processed the current config, false if we failed to + * query the mappings. + * @param pTable The mount table to refresh. + */ +static bool vbsvcAutomounterRefreshTable(PVBSVCAUTOMOUNTERTABLE pTable) +{ + /* + * Query the root IDs of all auto-mountable shared folder mappings. + */ + uint32_t cMappings = 0; + PVBGLR3SHAREDFOLDERMAPPING paMappings = NULL; + int rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /*fAutoMountOnly*/, &paMappings, &cMappings); + if (RT_FAILURE(rc)) + { + VGSvcError("vbsvcAutomounterRefreshTable: VbglR3SharedFolderGetMappings failed: %Rrc\n", rc); + return false; + } + + /* + * Walk the table and the mappings in parallel, so we have to make sure + * they are both sorted by root ID. + */ + if (cMappings > 1) + RTSortShell(paMappings, cMappings, sizeof(paMappings[0]), vbsvcSharedFolderMappingCompare, NULL); + + /* + * Pass #1: Do all the umounting. + * + * By doing the umount pass separately from the mount pass, we can + * better handle changing involving the same mount points (switching + * mount points between two shares, new share on same mount point but + * with lower root ID, ++). + */ + uint32_t iTable = 0; + for (uint32_t iSrc = 0; iSrc < cMappings; iSrc++) + { + /* + * Unmount table entries up to idRootSrc. + */ + uint32_t const idRootSrc = paMappings[iSrc].u32Root; + while ( iTable < pTable->cEntries + && pTable->papEntries[iTable]->idRoot < idRootSrc) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "dropped"); + + /* + * If the paMappings entry and the mount table entry has the same + * root ID, umount if anything has changed or if we cannot query + * the mapping data. + */ + if (iTable < pTable->cEntries) + { + PVBSVCAUTOMOUNTERENTRY pEntry = pTable->papEntries[iTable]; + if (pEntry->idRoot == idRootSrc) + { + uint32_t uRootIdVer = UINT32_MAX; + uint64_t fFlags = 0; + char *pszName = NULL; + char *pszMntPt = NULL; + rc = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF, + &pszName, &pszMntPt, &fFlags, &uRootIdVer); + if (RT_FAILURE(rc)) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "VbglR3SharedFolderQueryFolderInfo failed"); + else if (pEntry->uRootIdVersion != uRootIdVer) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "root ID version changed"); + else if (RTPathCompare(pEntry->pszMountPoint, pszMntPt) != 0) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "mount point changed"); + else if (RTStrICmp(pEntry->pszName, pszName) != 0) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "name changed"); + else + { + VGSvcVerbose(3, "vbsvcAutomounterRefreshTable: Unchanged: %s -> %s\n", pEntry->pszMountPoint, pEntry->pszName); + iTable++; + } + if (RT_SUCCESS(rc)) + { + RTStrFree(pszName); + RTStrFree(pszMntPt); + } + } + } + } + + while (iTable < pTable->cEntries) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "dropped (tail)"); + + VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u entries in mount table after pass #1.\n", pTable->cEntries); + + /* + * Pass #2: Try mount new folders that has mount points assigned. + * Pass #3: Try mount new folders not mounted in pass #2. + */ + for (uint32_t iPass = 2; iPass <= 3; iPass++) + { + iTable = 0; + for (uint32_t iSrc = 0; iSrc < cMappings; iSrc++) + { + uint32_t const idRootSrc = paMappings[iSrc].u32Root; + + /* + * Skip tabel entries we couldn't umount in pass #1. + */ + while ( iTable < pTable->cEntries + && pTable->papEntries[iTable]->idRoot < idRootSrc) + { + VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: Skipping idRoot=%u %s\n", + iPass, iSrc, iTable, pTable->papEntries[iTable]->idRoot, pTable->papEntries[iTable]->pszName); + iTable++; + } + + /* + * New share? + */ + if ( iTable >= pTable->cEntries + || pTable->papEntries[iTable]->idRoot != idRootSrc) + { + uint32_t uRootIdVer = UINT32_MAX; + uint64_t fFlags = 0; + char *pszName = NULL; + char *pszMntPt = NULL; + rc = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF, + &pszName, &pszMntPt, &fFlags, &uRootIdVer); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: Mounting idRoot=%u/%u %s\n", iPass, iSrc, iTable, + idRootSrc, iTable >= pTable->cEntries ? UINT32_MAX : pTable->papEntries[iTable]->idRoot, pszName); + iTable = vbsvcAutomounterMountNewEntry(pTable, iTable, pszName, pszMntPt, fFlags, + idRootSrc, uRootIdVer, iPass == 3); + + RTStrFree(pszName); + RTStrFree(pszMntPt); + } + else + VGSvcVerbose(1, "vbsvcAutomounterRefreshTable: VbglR3SharedFolderQueryFolderInfo failed: %Rrc\n", rc); + } + else + VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: idRootSrc=%u vs idRoot=%u %s\n", iPass, iSrc, + iTable, idRootSrc, pTable->papEntries[iTable]->idRoot, pTable->papEntries[iTable]->pszName); + } + } + + VbglR3SharedFolderFreeMappings(paMappings); + return true; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vbsvcAutomounterWorker(bool volatile *pfShutdown) +{ + /* + * Tell the control thread that it can continue spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* Divert old hosts to original auto-mount code. */ + if (!g_fHostSupportsWaitAndInfoQuery) + return vbsvcAutoMountWorkerOld(pfShutdown); + + /* + * Initialize the state in case we're restarted... + */ + VBSVCAUTOMOUNTERTABLE MountTable = { 0, 0, NULL }; + int rc = vbsvcAutomounterPopulateTable(&MountTable); + if (RT_FAILURE(rc)) + { + VGSvcError("vbsvcAutomounterWorker: vbsvcAutomounterPopulateTable failed (%Rrc), quitting!\n", rc); + return rc; + } + + /* + * Work loop. + */ + uint32_t uConfigVer = UINT32_MAX; + uint32_t uNewVersion = 0; + bool fForceRefresh = true; + while (!*pfShutdown) + { + /* + * Update the mounts. + */ + if ( uConfigVer != uNewVersion + || fForceRefresh) + { + fForceRefresh = !vbsvcAutomounterRefreshTable(&MountTable); + uConfigVer = uNewVersion; + } + + /* + * Wait for more to do. + */ + if (!*pfShutdown) + { + uNewVersion = uConfigVer - 1; + VGSvcVerbose(2, "vbsvcAutomounterWorker: Waiting with uConfigVer=%u\n", uConfigVer); + rc = VbglR3SharedFolderWaitForMappingsChanges(g_idClientSharedFolders, uConfigVer, &uNewVersion); + VGSvcVerbose(2, "vbsvcAutomounterWorker: Woke up with uNewVersion=%u and rc=%Rrc\n", uNewVersion, rc); + + /* Delay a little before doing a table refresh so the GUI can finish + all its updates. Delay a little longer on non-shutdown failure to + avoid eating too many CPU cycles if something goes wrong here... */ + if (!*pfShutdown) + RTSemEventMultiWait(g_hAutoMountEvent, RT_SUCCESS(rc) ? 256 : 1000); + } + } + + /* + * Destroy the mount table. + */ + while (MountTable.cEntries-- > 0) + RTMemFree(MountTable.papEntries[MountTable.cEntries]); + MountTable.papEntries = NULL; + + VGSvcVerbose(3, "vbsvcAutomounterWorker: Finished\n"); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vbsvcAutomounterStop(void) +{ + RTSemEventMultiSignal(g_hAutoMountEvent); + if (g_fHostSupportsWaitAndInfoQuery) + VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vbsvcAutomounterTerm(void) +{ + VGSvcVerbose(3, "vbsvcAutoMountTerm\n"); + + if (g_fHostSupportsWaitAndInfoQuery) + VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders); + + VbglR3SharedFolderDisconnect(g_idClientSharedFolders); + g_idClientSharedFolders = 0; + + if (g_hAutoMountEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_hAutoMountEvent); + g_hAutoMountEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'automount' service description. + */ +VBOXSERVICE g_AutoMount = +{ + /* pszName. */ + "automount", + /* pszDescription. */ + "Automounter for Shared Folders", + /* pszUsage. */ + NULL, + /* pszOptions. */ + NULL, + /* methods */ + VGSvcDefaultPreInit, + VGSvcDefaultOption, + vbsvcAutomounterInit, + vbsvcAutomounterWorker, + vbsvcAutomounterStop, + vbsvcAutomounterTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp new file mode 100644 index 00000000..dbe548bf --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp @@ -0,0 +1,457 @@ +/* $Id: VBoxServiceBalloon.cpp $ */ +/** @file + * VBoxService - Memory Ballooning. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc_memballoon VBoxService - Memory Ballooning + * + * The Memory Ballooning subservice works with VBoxGuest, PGM and GMM to + * dynamically reallocate memory between VMs. + * + * Memory ballooning is typically used to deal with overcomitting memory on the + * host. It allowes you to borrow memory from one or more VMs and make it + * available to others. In theory it could also be used to make memory + * available to the host system, however memory fragmentation typically makes + * that difficult. + * + * The memory ballooning subservices talks to PGM, GMM and Main via the VMMDev. + * It polls for change requests at an interval and executes them when they + * arrive. There are two ways we implement the actual ballooning, either + * VBoxGuest allocates kernel memory and donates it to the host, or this service + * allocates process memory which VBoxGuest then locks down and donates to the + * host. While we prefer the former method it is not practicable on all OS and + * we have to use the latter. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/system.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <VBox/err.h> +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" + +#ifdef RT_OS_LINUX +# include <iprt/param.h> +# include <sys/mman.h> +# ifndef MADV_DONTFORK +# define MADV_DONTFORK 10 +# endif +#endif + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The balloon size. */ +static uint32_t g_cMemBalloonChunks = 0; + +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_MemBalloonEvent = NIL_RTSEMEVENTMULTI; + +/** The array holding the R3 pointers of the balloon. */ +static void **g_pavBalloon = NULL; + +#ifdef RT_OS_LINUX +/** True = madvise(MADV_DONTFORK) works, false otherwise. */ +static bool g_fSysMadviseWorks; +#endif + + +/** + * Check whether madvise() works. + */ +static void vgsvcBalloonInitMadvise(void) +{ +#ifdef RT_OS_LINUX + void *pv = (void*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pv != MAP_FAILED) + { + g_fSysMadviseWorks = madvise(pv, PAGE_SIZE, MADV_DONTFORK) == 0; + munmap(pv, PAGE_SIZE); + } +#endif +} + + +/** + * Allocate a chunk of the balloon. Fulfil the prerequisite that we can lock this memory + * and protect it against fork() in R0. See also suplibOsPageAlloc(). + */ +static void *VGSvcBalloonAllocChunk(void) +{ + size_t cb = VMMDEV_MEMORY_BALLOON_CHUNK_SIZE; + char *pu8; + +#ifdef RT_OS_LINUX + if (!g_fSysMadviseWorks) + cb += 2 * PAGE_SIZE; + + pu8 = (char*)mmap(NULL, cb, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pu8 == MAP_FAILED) + return NULL; + + if (g_fSysMadviseWorks) + { + /* + * It is not fatal if we fail here but a forked child (e.g. the ALSA sound server) + * could crash. Linux < 2.6.16 does not implement madvise(MADV_DONTFORK) but the + * kernel seems to split bigger VMAs and that is all that we want -- later we set the + * VM_DONTCOPY attribute in supdrvOSLockMemOne(). + */ + madvise(pu8, cb, MADV_DONTFORK); + } + else + { + /* + * madvise(MADV_DONTFORK) is not available (most probably Linux 2.4). Enclose any + * mmapped region by two unmapped pages to guarantee that there is exactly one VM + * area struct of the very same size as the mmap area. + */ + RTMemProtect(pu8, PAGE_SIZE, RTMEM_PROT_NONE); + RTMemProtect(pu8 + cb - PAGE_SIZE, PAGE_SIZE, RTMEM_PROT_NONE); + pu8 += PAGE_SIZE; + } + +#else + + pu8 = (char*)RTMemPageAlloc(cb); + if (!pu8) + return pu8; + +#endif + + memset(pu8, 0, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE); + return pu8; +} + + +/** + * Free an allocated chunk undoing VGSvcBalloonAllocChunk(). + */ +static void vgsvcBalloonFreeChunk(void *pv) +{ + char *pu8 = (char*)pv; + size_t cb = VMMDEV_MEMORY_BALLOON_CHUNK_SIZE; + +#ifdef RT_OS_LINUX + + if (!g_fSysMadviseWorks) + { + cb += 2 * PAGE_SIZE; + pu8 -= PAGE_SIZE; + /* This is not really necessary */ + RTMemProtect(pu8, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + RTMemProtect(pu8 + cb - PAGE_SIZE, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + } + munmap(pu8, cb); + +#else + + RTMemPageFree(pu8, cb); + +#endif +} + + +/** + * Adapt the R0 memory balloon by granting/reclaiming 1MB chunks to/from R0. + * + * returns IPRT status code. + * @param cNewChunks The new number of 1MB chunks in the balloon. + */ +static int vgsvcBalloonSetUser(uint32_t cNewChunks) +{ + if (cNewChunks == g_cMemBalloonChunks) + return VINF_SUCCESS; + + VGSvcVerbose(3, "vgsvcBalloonSetUser: cNewChunks=%u g_cMemBalloonChunks=%u\n", cNewChunks, g_cMemBalloonChunks); + int rc = VINF_SUCCESS; + if (cNewChunks > g_cMemBalloonChunks) + { + /* inflate */ + g_pavBalloon = (void**)RTMemRealloc(g_pavBalloon, cNewChunks * sizeof(void*)); + uint32_t i; + for (i = g_cMemBalloonChunks; i < cNewChunks; i++) + { + void *pv = VGSvcBalloonAllocChunk(); + if (!pv) + break; + rc = VbglR3MemBalloonChange(pv, /* inflate=*/ true); + if (RT_SUCCESS(rc)) + { + g_pavBalloon[i] = pv; +#ifndef RT_OS_SOLARIS + /* + * Protect against access by dangling pointers (ignore errors as it may fail). + * On Solaris it corrupts the address space leaving the process unkillable. This + * could perhaps be related to what the underlying segment driver does; currently + * just disable it. + */ + RTMemProtect(pv, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE, RTMEM_PROT_NONE); +#endif + g_cMemBalloonChunks++; + } + else + { + vgsvcBalloonFreeChunk(pv); + break; + } + } + VGSvcVerbose(3, "vgsvcBalloonSetUser: inflation complete. chunks=%u rc=%d\n", i, rc); + } + else + { + /* deflate */ + uint32_t i; + for (i = g_cMemBalloonChunks; i-- > cNewChunks;) + { + void *pv = g_pavBalloon[i]; + rc = VbglR3MemBalloonChange(pv, /* inflate=*/ false); + if (RT_SUCCESS(rc)) + { +#ifndef RT_OS_SOLARIS + /* unprotect */ + RTMemProtect(pv, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); +#endif + vgsvcBalloonFreeChunk(pv); + g_pavBalloon[i] = NULL; + g_cMemBalloonChunks--; + } + else + break; + VGSvcVerbose(3, "vgsvcBalloonSetUser: deflation complete. chunks=%u rc=%d\n", i, rc); + } + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcBalloonInit(void) +{ + VGSvcVerbose(3, "vgsvcBalloonInit\n"); + + int rc = RTSemEventMultiCreate(&g_MemBalloonEvent); + AssertRCReturn(rc, rc); + + vgsvcBalloonInitMadvise(); + + g_cMemBalloonChunks = 0; + uint32_t cNewChunks = 0; + bool fHandleInR3; + + /* Check balloon size */ + rc = VbglR3MemBalloonRefresh(&cNewChunks, &fHandleInR3); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "MemBalloon: New balloon size %d MB (%s memory)\n", cNewChunks, fHandleInR3 ? "R3" : "R0"); + if (fHandleInR3) + rc = vgsvcBalloonSetUser(cNewChunks); + else + g_cMemBalloonChunks = cNewChunks; + } + if (RT_FAILURE(rc)) + { + /* If the service was not found, we disable this service without + causing VBoxService to fail. */ + if ( rc == VERR_NOT_IMPLEMENTED +#ifdef RT_OS_WINDOWS /** @todo r=bird: Windows kernel driver should return VERR_NOT_IMPLEMENTED, + * VERR_INVALID_PARAMETER has too many other uses. */ + || rc == VERR_INVALID_PARAMETER +#endif + ) + { + VGSvcVerbose(0, "MemBalloon: Memory ballooning support is not available\n"); + rc = VERR_SERVICE_DISABLED; + } + else + { + VGSvcVerbose(3, "MemBalloon: VbglR3MemBalloonRefresh failed with %Rrc\n", rc); + rc = VERR_SERVICE_DISABLED; /** @todo Playing safe for now, figure out the exact status codes here. */ + } + RTSemEventMultiDestroy(g_MemBalloonEvent); + g_MemBalloonEvent = NIL_RTSEMEVENTMULTI; + } + + return rc; +} + + +/** + * Query the size of the memory balloon, given as a page count. + * + * @returns Number of pages. + * @param cbPage The page size. + */ +uint32_t VGSvcBalloonQueryPages(uint32_t cbPage) +{ + Assert(cbPage > 0); + return g_cMemBalloonChunks * (VMMDEV_MEMORY_BALLOON_CHUNK_SIZE / cbPage); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vgsvcBalloonWorker(bool volatile *pfShutdown) +{ + /* Start monitoring of the stat event change event. */ + int rc = VbglR3CtlFilterMask(VMMDEV_EVENT_BALLOON_CHANGE_REQUEST, 0); + if (RT_FAILURE(rc)) + { + VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3CtlFilterMask failed with %Rrc\n", rc); + return rc; + } + + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Now enter the loop retrieving runtime data continuously. + */ + for (;;) + { + uint32_t fEvents = 0; + + /* Check if an update interval change is pending. */ + rc = VbglR3WaitEvent(VMMDEV_EVENT_BALLOON_CHANGE_REQUEST, 0 /* no wait */, &fEvents); + if ( RT_SUCCESS(rc) + && (fEvents & VMMDEV_EVENT_BALLOON_CHANGE_REQUEST)) + { + uint32_t cNewChunks; + bool fHandleInR3; + rc = VbglR3MemBalloonRefresh(&cNewChunks, &fHandleInR3); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "vgsvcBalloonInit: new balloon size %d MB (%s memory)\n", cNewChunks, fHandleInR3 ? "R3" : "R0"); + if (fHandleInR3) + { + rc = vgsvcBalloonSetUser(cNewChunks); + if (RT_FAILURE(rc)) + { + VGSvcVerbose(3, "vgsvcBalloonInit: failed to set balloon size %d MB (%s memory)\n", + cNewChunks, fHandleInR3 ? "R3" : "R0"); + } + else + VGSvcVerbose(3, "vgsvcBalloonInit: successfully set requested balloon size %d.\n", cNewChunks); + } + else + g_cMemBalloonChunks = cNewChunks; + } + else + VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3MemBalloonRefresh failed with %Rrc\n", rc); + } + + /* + * Block for a while. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + int rc2 = RTSemEventMultiWait(g_MemBalloonEvent, 5000); + if (*pfShutdown) + break; + if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) + { + VGSvcError("vgsvcBalloonInit: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); + rc = rc2; + break; + } + } + + /* Cancel monitoring of the memory balloon change event. */ + rc = VbglR3CtlFilterMask(0, VMMDEV_EVENT_BALLOON_CHANGE_REQUEST); + if (RT_FAILURE(rc)) + VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3CtlFilterMask failed with %Rrc\n", rc); + + VGSvcVerbose(3, "vgsvcBalloonInit: finished mem balloon change request thread\n"); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcBalloonStop(void) +{ + RTSemEventMultiSignal(g_MemBalloonEvent); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcBalloonTerm(void) +{ + if (g_MemBalloonEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_MemBalloonEvent); + g_MemBalloonEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'memballoon' service description. + */ +VBOXSERVICE g_MemBalloon = +{ + /* pszName. */ + "memballoon", + /* pszDescription. */ + "Memory Ballooning", + /* pszUsage. */ + NULL, + /* pszOptions. */ + NULL, + /* methods */ + VGSvcDefaultPreInit, + VGSvcDefaultOption, + vgsvcBalloonInit, + vgsvcBalloonWorker, + vgsvcBalloonStop, + vgsvcBalloonTerm +}; diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp new file mode 100644 index 00000000..ea88955a --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc_clipboard VBoxService - Clipboard (OS/2) + * + * The Clipboard subservice provides clipboard sharing for OS/2 guests only. + * + * This was the second subservice that was added to VBoxService. OS/2 is a + * single user system and we don't provide any VBoxTray or VBoxClient like + * processes. Because it's kind of simple system, it became natural to put the + * clipboard sharing here in VBoxService for OS/2. + * + * In addition to integrating with the native OS/2 PM clipboard formats, we also + * try provide the Odin32, a windows API layer for OS/2 (developed by Sander van + * Leeuwen and friends, later mainly InnoTek), with additional formats. + * + * Bitmaps are currently not supported, but that can easily be added should the + * need ever arrise. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define INCL_BASE +#define INCL_PM +#define INCL_ERRORS +#include <os2.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/utf16.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include "VBoxServiceInternal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Header for Odin32 specific clipboard entries. + * (Used to get the correct size of the data.) + */ +typedef struct _Odin32ClipboardHeader +{ + /** magic number */ + char achMagic[8]; + /** Size of the following data. + * (The interpretation depends on the type.) */ + unsigned cbData; + /** Odin32 format number. */ + unsigned uFormat; +} CLIPHEADER, *PCLIPHEADER; + +#define CLIPHEADER_MAGIC "Odin\1\0\1" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** The control thread (main) handle. + * Only used to avoid some queue creation trouble. */ +static RTTHREAD g_ThreadCtrl = NIL_RTTHREAD; +/** The HAB of the control thread (main). */ +static HAB g_habCtrl = NULLHANDLE; +/** The HMQ of the control thread (main). */ +static HMQ g_hmqCtrl = NULLHANDLE; + +/** The Listener thread handle. */ +static RTTHREAD g_ThreadListener = NIL_RTTHREAD; +/** The HAB of the listener thread. */ +static HAB g_habListener = NULLHANDLE; +/** The HMQ of the listener thread. */ +static HMQ g_hmqListener = NULLHANDLE; +/** Indicator that gets set if the listener thread is successfully initialized. */ +static bool volatile g_fListenerOkay = false; + +/** The HAB of the worker thread. */ +static HAB g_habWorker = NULLHANDLE; +/** The HMQ of the worker thread. */ +static HMQ g_hmqWorker = NULLHANDLE; +/** The object window handle. */ +static HWND g_hwndWorker = NULLHANDLE; +/** The timer id returned by WinStartTimer. */ +static ULONG g_idWorkerTimer = ~0UL; +/** The state of the clipboard. + * @remark I'm trying out the 'k' prefix from the mac here, bear with me. */ +static enum +{ + /** The clipboard hasn't been initialized yet. */ + kClipboardState_Uninitialized = 0, + /** WinSetClipbrdViewer call in progress, ignore WM_DRAWCLIPBOARD. */ + kClipboardState_SettingViewer, + /** We're monitoring the clipboard as a viewer. */ + kClipboardState_Viewer, + /** We're monitoring the clipboard using polling. + * This usually means something is wrong... */ + kClipboardState_Polling, + /** We're destroying the clipboard content, ignore WM_DESTROYCLIPBOARD. */ + kClipboardState_Destroying, + /** We're owning the clipboard (i.e. we have data on it). */ + kClipboardState_Owner +} g_enmState = kClipboardState_Uninitialized; +/** Set if the clipboard was empty the last time we polled it. */ +static bool g_fEmptyClipboard = false; + +/** A clipboard format atom for the dummy clipboard data we insert + * watching for clipboard changes. If this format is found on the + * clipboard, the empty clipboard function has not been called + * since we last polled it. */ +static ATOM g_atomNothingChanged = 0; + +/** The clipboard connection client ID. */ +static uint32_t g_u32ClientId; +/** Odin32 CF_UNICODETEXT. See user32.cpp. */ +static ATOM g_atomOdin32UnicodeText = 0; +/** Odin32 CF_UNICODETEXT. See user32.cpp. */ +#define SZFMT_ODIN32_UNICODETEXT (PCSZ)"Odin32 UnicodeText" + + + + +/** + * @interface_method_impl{VBOXSERVICE,pfnPreInit} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2PreInit(void) +{ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2Option(const char **ppszShort, int argc, char **argv, int *pi) +{ + NOREF(ppszShort); + NOREF(argc); + NOREF(argv); + NOREF(pi); + + return -1; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2Init(void) +{ + int rc = VERR_GENERAL_FAILURE; + g_ThreadCtrl = RTThreadSelf(); + + /* + * Make PM happy. + */ + PPIB pPib; + PTIB pTib; + DosGetInfoBlocks(&pTib, &pPib); + pPib->pib_ultype = 3; /* PM session type */ + + /* + * Since we have to send shutdown messages and such from the + * service controller (main) thread, create a HAB and HMQ for it. + */ + g_habCtrl = WinInitialize(0); + if (g_habCtrl == NULLHANDLE) + { + VGSvcError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE)); + return VERR_GENERAL_FAILURE; + } + g_hmqCtrl = WinCreateMsgQueue(g_habCtrl, 0); + if (g_hmqCtrl != NULLHANDLE) + { + WinCancelShutdown(g_hmqCtrl, TRUE); /* We don't care about shutdown */ + + /* + * Create the 'nothing-changed' format. + */ + g_atomNothingChanged = WinAddAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service"); + LONG lLastError = WinGetLastError(g_habCtrl); + if (g_atomNothingChanged == 0) + g_atomNothingChanged = WinFindAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service"); + if (g_atomNothingChanged) + { + /* + * Connect to the clipboard service. + */ + VGSvcVerbose(4, "clipboard: connecting\n"); + rc = VbglR3ClipboardConnect(&g_u32ClientId); + if (RT_SUCCESS(rc)) + { + /* + * Create any extra clipboard type atoms, like the odin unicode text. + */ + g_atomOdin32UnicodeText = WinAddAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT); + lLastError = WinGetLastError(g_habCtrl); + if (g_atomOdin32UnicodeText == 0) + g_atomOdin32UnicodeText = WinFindAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT); + if (g_atomOdin32UnicodeText == 0) + VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n", + lLastError, WinGetLastError(g_habCtrl)); + + VGSvcVerbose(2, "g_u32ClientId=%RX32 g_atomNothingChanged=%#x g_atomOdin32UnicodeText=%#x\n", + g_u32ClientId, g_atomNothingChanged, g_atomOdin32UnicodeText); + return VINF_SUCCESS; + } + + VGSvcError("Failed to connect to the clipboard service, rc=%Rrc!\n", rc); + } + else + VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n", + lLastError, WinGetLastError(g_habCtrl)); + } + else + VGSvcError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl)); + WinTerminate(g_habCtrl); + return rc; +} + + +/** + * Check that we're still the view / try make us the viewer. + */ +static void vgsvcClipboardOs2PollViewer(void) +{ + const int iOrgState = g_enmState; + + HWND hwndClipboardViewer = WinQueryClipbrdViewer(g_habWorker); + if (hwndClipboardViewer == g_hwndWorker) + return; + + if (hwndClipboardViewer == NULLHANDLE) + { + /* The API will send a WM_DRAWCLIPBOARD message before returning. */ + g_enmState = kClipboardState_SettingViewer; + if (WinSetClipbrdViewer(g_habWorker, g_hwndWorker)) + g_enmState = kClipboardState_Viewer; + else + g_enmState = kClipboardState_Polling; + } + else + g_enmState = kClipboardState_Polling; + if ((int)g_enmState != iOrgState) + { + if (g_enmState == kClipboardState_Viewer) + VGSvcVerbose(3, "clipboard: viewer\n"); + else + VGSvcVerbose(3, "clipboard: poller\n"); + } +} + + +/** + * Advertise the formats available from the host. + * + * @param fFormats The formats available on the host. + */ +static void vgsvcClipboardOs2AdvertiseHostFormats(uint32_t fFormats) +{ + /* + * Open the clipboard and switch to 'destruction' mode. + * Make sure we stop being viewer. Temporarily also make sure we're + * not the owner so that PM won't send us any WM_DESTROYCLIPBOARD message. + */ + if (WinOpenClipbrd(g_habWorker)) + { + if (g_enmState == kClipboardState_Viewer) + WinSetClipbrdViewer(g_habWorker, NULLHANDLE); + if (g_enmState == kClipboardState_Owner) + WinSetClipbrdOwner(g_habWorker, NULLHANDLE); + + g_enmState = kClipboardState_Destroying; + if (WinEmptyClipbrd(g_habWorker)) + { + /* + * Take clipboard ownership. + */ + if (WinSetClipbrdOwner(g_habWorker, g_hwndWorker)) + { + g_enmState = kClipboardState_Owner; + + /* + * Do the format advertising. + */ + if (fFormats & (VBOX_SHCL_FMT_UNICODETEXT/* | VBOX_SHCL_FMT_HTML ?? */)) + { + if (!WinSetClipbrdData(g_habWorker, 0, CF_TEXT, CFI_POINTER)) + VGSvcError("WinSetClipbrdData(,,CF_TEXT,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + if ( g_atomOdin32UnicodeText + && !WinSetClipbrdData(g_habWorker, 0, g_atomOdin32UnicodeText, CFI_POINTER)) + VGSvcError("WinSetClipbrdData(,,g_atomOdin32UnicodeText,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + } + if (fFormats & VBOX_SHCL_FMT_BITMAP) + { + /** @todo bitmaps */ + } + } + else + { + VGSvcError("WinSetClipbrdOwner failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + g_enmState = kClipboardState_Polling; + } + } + else + { + VGSvcError("WinEmptyClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + g_enmState = kClipboardState_Polling; + } + + if (g_enmState == kClipboardState_Polling) + { + g_fEmptyClipboard = true; + vgsvcClipboardOs2PollViewer(); + } + + WinCloseClipbrd(g_habWorker); + } + else + VGSvcError("vgsvcClipboardOs2AdvertiseHostFormats: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); +} + + +/** + * Converts (render) to an Odin32 clipboard format. + * + * We ASSUME we get windows data from the host and all we've got to do here is + * slapping an Odin32 header on it. + * + * @returns Pointer to the data (DosFreeMem). + * @param fFormat The host format. + * @param usFmt The PM/Odin32 format. + * @param pv The data in host formatting. + * @param cb The size of the data. + */ +static void *vgsvcClipboardOs2ConvertToOdin32(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb) +{ + PVOID pvPM = NULL; + APIRET rc = DosAllocSharedMem(&pvPM, NULL, cb + sizeof(CLIPHEADER), OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT); + if (rc) + { + PCLIPHEADER pHdr = (PCLIPHEADER)pvPM; + memcpy(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic)); + pHdr->cbData = cb; + if (usFmt == g_atomOdin32UnicodeText) + pHdr->uFormat = usFmt; + else + AssertFailed(); + memcpy(pHdr + 1, pv, cb); + } + else + { + VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), rc); + pvPM = NULL; + } + return pvPM; +} + + +/** + * Converts (render) to a PM clipboard format. + * + * @returns Pointer to the data (DosFreeMem). + * @param fFormat The host format. + * @param usFmt The PM/Odin32 format. + * @param pv The data in host formatting. + * @param cb The size of the data. + */ +static void *vgsvcClipboardOs2ConvertToPM(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb) +{ + void *pvPM = NULL; + + /* + * The Odin32 stuff is simple, we just assume windows data from the host + * and all we need to do is add the header. + */ + if ( usFmt + && ( usFmt == g_atomOdin32UnicodeText + /* || usFmt == ...*/ + ) + ) + pvPM = vgsvcClipboardOs2ConvertToOdin32(fFormat, usFmt, pv, cb); + else if (usFmt == CF_TEXT) + { + /* + * Convert the unicode text to the current ctype locale. + * + * Note that we probably should be using the current PM or DOS codepage + * here instead of the LC_CTYPE one which iconv uses by default. + * -lazybird + */ + Assert(fFormat & VBOX_SHCL_FMT_UNICODETEXT); + char *pszUtf8; + int rc = RTUtf16ToUtf8((PCRTUTF16)pv, &pszUtf8); + if (RT_SUCCESS(rc)) + { + char *pszLocale; + rc = RTStrUtf8ToCurrentCP(&pszLocale, pszUtf8); + if (RT_SUCCESS(rc)) + { + size_t cbPM = strlen(pszLocale) + 1; + APIRET orc = DosAllocSharedMem(&pvPM, NULL, cbPM, OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT); + if (orc == NO_ERROR) + memcpy(pvPM, pszLocale, cbPM); + else + { + VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), orc); + pvPM = NULL; + } + RTStrFree(pszLocale); + } + else + VGSvcError("RTStrUtf8ToCurrentCP() -> %Rrc\n", rc); + RTStrFree(pszUtf8); + } + else + VGSvcError("RTUtf16ToUtf8() -> %Rrc\n", rc); + } + + return pvPM; +} + + +/** + * Tries to deliver an advertised host format. + * + * @param usFmt The PM format name. + * + * @remark We must not try open the clipboard here because WM_RENDERFMT is a + * request send synchronously by someone who has already opened the + * clipboard. We would enter a deadlock trying to open it here. + */ +static void vgsvcClipboardOs2RenderFormat(USHORT usFmt) +{ + bool fSucceeded = false; + + /* + * Determine which format. + */ + uint32_t fFormat; + if ( usFmt == CF_TEXT + || usFmt == g_atomOdin32UnicodeText) + fFormat = VBOX_SHCL_FMT_UNICODETEXT; + else /** @todo bitmaps */ + fFormat = 0; + if (fFormat) + { + /* + * Query the data from the host. + * This might require two iterations because of buffer guessing. + */ + uint32_t cb = _4K; + uint32_t cbAllocated = cb; + int rc = VERR_NO_MEMORY; + void *pv = RTMemPageAllocZ(cbAllocated); + if (pv) + { + VGSvcVerbose(4, "clipboard: reading host data (%#x)\n", fFormat); + rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb); + if (rc == VINF_BUFFER_OVERFLOW) + { + RTMemPageFree(pv, cbAllocated); + cbAllocated = cb = RT_ALIGN_32(cb, PAGE_SIZE); + pv = RTMemPageAllocZ(cbAllocated); + rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb); + } + if (RT_FAILURE(rc)) + RTMemPageFree(pv, cbAllocated); + } + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "clipboard: read %u bytes\n", cb); + + /* + * Convert the host clipboard data to PM clipboard data and set it. + */ + PVOID pvPM = vgsvcClipboardOs2ConvertToPM(fFormat, usFmt, pv, cb); + if (pvPM) + { + if (WinSetClipbrdData(g_habWorker, (ULONG)pvPM, usFmt, CFI_POINTER)) + fSucceeded = true; + else + { + VGSvcError("vgsvcClipboardOs2RenderFormat: WinSetClipbrdData(,%p,%#x, CF_POINTER) failed, lasterror=%lx\n", + pvPM, usFmt, WinGetLastError(g_habWorker)); + DosFreeMem(pvPM); + } + } + RTMemPageFree(pv, cbAllocated); + } + else + VGSvcError("vgsvcClipboardOs2RenderFormat: Failed to query / allocate data. rc=%Rrc cb=%#RX32\n", rc, cb); + } + + /* + * Empty the clipboard on failure so we don't end up in any loops. + */ + if (!fSucceeded) + { + WinSetClipbrdOwner(g_habWorker, NULLHANDLE); + g_enmState = kClipboardState_Destroying; + WinEmptyClipbrd(g_habWorker); + g_enmState = kClipboardState_Polling; + g_fEmptyClipboard = true; + vgsvcClipboardOs2PollViewer(); + } +} + + +/** + * Sends data to the host. + * + * @param fFormat The data format the host is requesting. + */ +static void vgsvcClipboardOs2SendDataToHost(uint32_t fFormat) +{ + if (WinOpenClipbrd(g_habWorker)) + { + PRTUTF16 pwszFree = NULL; + void *pv = NULL; + uint32_t cb = 0; + + if (fFormat & VBOX_SHCL_FMT_UNICODETEXT) + { + /* Got any odin32 unicode text? */ + PVOID pvPM; + PCLIPHEADER pHdr = (PCLIPHEADER)WinQueryClipbrdData(g_habWorker, g_atomOdin32UnicodeText); + if ( pHdr + && !memcmp(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic))) + { + pv = pHdr + 1; + cb = pHdr->cbData; + } + + /* Got any CF_TEXT? */ + if ( !pv + && (pvPM = (PVOID)WinQueryClipbrdData(g_habWorker, CF_TEXT)) != NULL) + { + char *pszUtf8; + int rc = RTStrCurrentCPToUtf8(&pszUtf8, (const char *)pvPM); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwsz; + rc = RTStrToUtf16(pszUtf8, &pwsz); + if (RT_SUCCESS(rc)) + { + pv = pwszFree = pwsz; + cb = (RTUtf16Len(pwsz) + 1) * sizeof(RTUTF16); + } + RTStrFree(pszUtf8); + } + } + } + if (!pv) + VGSvcError("vgsvcClipboardOs2SendDataToHost: couldn't find data for %#x\n", fFormat); + + /* + * Now, sent whatever we've got to the host (it's waiting). + */ + VGSvcVerbose(4, "clipboard: writing %pv/%#d (fFormat=%#x)\n", pv, cb, fFormat); + VbglR3ClipboardWriteData(g_u32ClientId, fFormat, pv, cb); + RTUtf16Free(pwszFree); + + WinCloseClipbrd(g_habWorker); + } + else + { + VGSvcError("vgsvcClipboardOs2SendDataToHost: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + VGSvcVerbose(4, "clipboard: writing NULL/0 (fFormat=%x)\n", fFormat); + VbglR3ClipboardWriteData(g_u32ClientId, fFormat, NULL, 0); + } +} + + +/** + * Figure out what's on the clipboard and report it to the host. + */ +static void vgsvcClipboardOs2ReportFormats(void) +{ + uint32_t fFormats = 0; + ULONG ulFormat = 0; + while ((ulFormat = WinEnumClipbrdFmts(g_habWorker, ulFormat)) != 0) + { + if ( ulFormat == CF_TEXT + || ulFormat == g_atomOdin32UnicodeText) + fFormats |= VBOX_SHCL_FMT_UNICODETEXT; + /** @todo else bitmaps and stuff. */ + } + VGSvcVerbose(4, "clipboard: reporting fFormats=%#x\n", fFormats); + VbglR3ClipboardReportFormats(g_u32ClientId, fFormats); +} + + +/** + * Poll the clipboard for changes. + * + * This is called both when we're the viewer and when we're + * falling back to polling. If something has changed it will + * notify the host. + */ +static void vgsvcClipboardOs2Poll(void) +{ + if (WinOpenClipbrd(g_habWorker)) + { + /* + * If our dummy is no longer there, something has actually changed, + * unless the clipboard is really empty. + */ + ULONG fFmtInfo; + if (!WinQueryClipbrdFmtInfo(g_habWorker, g_atomNothingChanged, &fFmtInfo)) + { + if (WinEnumClipbrdFmts(g_habWorker, 0) != 0) + { + g_fEmptyClipboard = false; + vgsvcClipboardOs2ReportFormats(); + + /* inject the dummy */ + PVOID pv; + APIRET rc = DosAllocSharedMem(&pv, NULL, 1, OBJ_GIVEABLE | OBJ_GETTABLE | PAG_READ | PAG_WRITE | PAG_COMMIT); + if (rc == NO_ERROR) + { + if (WinSetClipbrdData(g_habWorker, (ULONG)pv, g_atomNothingChanged, CFI_POINTER)) + VGSvcVerbose(4, "clipboard: Added dummy item.\n"); + else + { + VGSvcError("vgsvcClipboardOs2Poll: WinSetClipbrdData failed, lasterr=%#lx\n", WinGetLastError(g_habWorker)); + DosFreeMem(pv); + } + } + else + VGSvcError("vgsvcClipboardOs2Poll: DosAllocSharedMem(,,1,) -> %ld\n", rc); + } + else if (!g_fEmptyClipboard) + { + g_fEmptyClipboard = true; + VGSvcVerbose(3, "Reporting empty clipboard\n"); + VbglR3ClipboardReportFormats(g_u32ClientId, 0); + } + } + WinCloseClipbrd(g_habWorker); + } + else + VGSvcError("vgsvcClipboardOs2Poll: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); +} + + +/** + * The clipboard we owned was destroyed by someone else. + */ +static void vgsvcClipboardOs2Destroyed(void) +{ + /* make sure we're no longer the owner. */ + if (WinQueryClipbrdOwner(g_habWorker) == g_hwndWorker) + WinSetClipbrdOwner(g_habWorker, NULLHANDLE); + + /* switch to polling state and notify the host. */ + g_enmState = kClipboardState_Polling; + g_fEmptyClipboard = true; + VGSvcVerbose(3, "Reporting empty clipboard\n"); + VbglR3ClipboardReportFormats(g_u32ClientId, 0); + + vgsvcClipboardOs2PollViewer(); +} + + +/** + * The window procedure for the object window. + * + * @returns Message result. + * + * @param hwnd The window handle. + * @param msg The message. + * @param mp1 Message parameter 1. + * @param mp2 Message parameter 2. + */ +static MRESULT EXPENTRY vgsvcClipboardOs2WinProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2) +{ + if (msg != WM_TIMER) + VGSvcVerbose(6, "vgsvcClipboardOs2WinProc: hwnd=%#lx msg=%#lx mp1=%#lx mp2=%#lx\n", hwnd, msg, mp1, mp2); + + switch (msg) + { + /* + * Handle the two system defined messages for object windows. + * + * We'll just use the CREATE/DESTROY message to create that timer we're + * using for the viewer checks and polling fallback. + */ + case WM_CREATE: + g_idWorkerTimer = WinStartTimer(g_habWorker, hwnd, 1 /* id */, 1000 /* 1 second */); + g_fEmptyClipboard = true; + g_enmState = kClipboardState_Polling; + return NULL; /* FALSE(/NULL) == Continue*/ + + case WM_DESTROY: + WinStopTimer(g_habWorker, hwnd, g_idWorkerTimer); + g_idWorkerTimer = ~0UL; + g_hwndWorker = NULLHANDLE; + break; + + /* + * Clipboard viewer message - the content has been changed. + * This is sent *after* releasing the clipboard sem + * and during the WinSetClipbrdViewer call. + */ + case WM_DRAWCLIPBOARD: + if (g_enmState == kClipboardState_SettingViewer) + break; + AssertMsgBreak(g_enmState == kClipboardState_Viewer, ("g_enmState=%d\n", g_enmState)); + vgsvcClipboardOs2Poll(); + break; + + /* + * Clipboard owner message - the content was replaced. + * This is sent by someone with an open clipboard, so don't try open it now. + */ + case WM_DESTROYCLIPBOARD: + if (g_enmState == kClipboardState_Destroying) + break; /* it's us doing the replacing, ignore. */ + AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState)); + vgsvcClipboardOs2Destroyed(); + break; + + /* + * Clipboard owner message - somebody is requesting us to render a format. + * This is called by someone which owns the clipboard, but that's fine. + */ + case WM_RENDERFMT: + AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState)); + vgsvcClipboardOs2RenderFormat(SHORT1FROMMP(mp1)); + break; + + /* + * Clipboard owner message - we're about to quit and should render all formats. + * + * However, because we're lazy, we'll just ASSUME that since we're quitting + * we're probably about to shutdown or something and there is no point in + * doing anything here except for emptying the clipboard and removing + * ourselves as owner. Any failures at this point are silently ignored. + */ + case WM_RENDERALLFMTS: + WinOpenClipbrd(g_habWorker); + WinSetClipbrdOwner(g_habWorker, NULLHANDLE); + g_enmState = kClipboardState_Destroying; + WinEmptyClipbrd(g_habWorker); + g_enmState = kClipboardState_Polling; + g_fEmptyClipboard = true; + WinCloseClipbrd(g_habWorker); + break; + + /* + * Listener message - the host has new formats to offer. + */ + case WM_USER + VBOX_SHCL_HOST_MSG_FORMATS_REPORT: + vgsvcClipboardOs2AdvertiseHostFormats(LONGFROMMP(mp1)); + break; + + /* + * Listener message - the host wish to read our clipboard data. + */ + case WM_USER + VBOX_SHCL_HOST_MSG_READ_DATA: + vgsvcClipboardOs2SendDataToHost(LONGFROMMP(mp1)); + break; + + /* + * This is just a fallback polling strategy in case some other + * app is trying to view the clipboard too. We also use this + * to try recover from errors. + * + * Because the way the clipboard service works, we have to monitor + * it all the time and cannot get away with simpler solutions like + * synergy is employing (basically checking upon entering and leaving + * a desktop). + */ + case WM_TIMER: + if ( g_enmState != kClipboardState_Viewer + && g_enmState != kClipboardState_Polling) + break; + + /* Lost the position as clipboard viewer?*/ + if (g_enmState == kClipboardState_Viewer) + { + if (WinQueryClipbrdViewer(g_habWorker) == hwnd) + break; + g_enmState = kClipboardState_Polling; + } + + /* poll for changes */ + vgsvcClipboardOs2Poll(); + vgsvcClipboardOs2PollViewer(); + break; + + + /* + * Clipboard owner messages dealing with owner drawn content. + * We shouldn't be seeing any of these. + */ + case WM_PAINTCLIPBOARD: + case WM_SIZECLIPBOARD: + case WM_HSCROLLCLIPBOARD: + case WM_VSCROLLCLIPBOARD: + AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg)); + break; + + /* + * We shouldn't be seeing any other messages according to the docs. + * But for whatever reason, PM sends us a WM_ADJUSTWINDOWPOS message + * during WinCreateWindow. So, ignore that and assert on anything else. + */ + default: + AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg)); + case WM_ADJUSTWINDOWPOS: + break; + } + return NULL; +} + + +/** + * The listener thread. + * + * This thread is dedicated to listening for host messages and forwarding + * these to the worker thread (using PM). + * + * The thread will set g_fListenerOkay and signal its user event when it has + * completed initialization. In the case of init failure g_fListenerOkay will + * not be set. + * + * @returns Init error code or VINF_SUCCESS. + * @param ThreadSelf Our thread handle. + * @param pvUser Pointer to the clipboard service shutdown indicator. + */ +static DECLCALLBACK(int) vgsvcClipboardOs2Listener(RTTHREAD ThreadSelf, void *pvUser) +{ + bool volatile *pfShutdown = (bool volatile *)pvUser; + int rc = VERR_GENERAL_FAILURE; + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: ThreadSelf=%RTthrd\n", ThreadSelf); + + g_habListener = WinInitialize(0); + if (g_habListener != NULLHANDLE) + { + g_hmqListener = WinCreateMsgQueue(g_habListener, 0); + if (g_hmqListener != NULLHANDLE) + { + WinCancelShutdown(g_hmqListener, TRUE); /* We don't care about shutdown */ + + /* + * Tell the worker thread that we're good. + */ + rc = VINF_SUCCESS; + ASMAtomicXchgBool(&g_fListenerOkay, true); + RTThreadUserSignal(ThreadSelf); + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: Started successfully\n"); + + /* + * Loop until termination is requested. + */ + bool fQuit = false; + while (!*pfShutdown && !fQuit) + { + uint32_t Msg; + uint32_t fFormats; + rc = VbglR3ClipboardGetHostMsgOld(g_u32ClientId, &Msg, &fFormats); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: Msg=%#x fFormats=%#x\n", Msg, fFormats); + switch (Msg) + { + /* + * The host has announced available clipboard formats. + * Forward the information to the window, so it can later + * respond do WM_RENDERFORMAT message. + */ + case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: + if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHCL_HOST_MSG_FORMATS_REPORT, + MPFROMLONG(fFormats), 0)) + VGSvcError("WinPostMsg(%lx, FORMATS,,) failed, lasterr=%#lx\n", + g_hwndWorker, WinGetLastError(g_habListener)); + break; + + /* + * The host needs data in the specified format. + */ + case VBOX_SHCL_HOST_MSG_READ_DATA: + if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHCL_HOST_MSG_READ_DATA, + MPFROMLONG(fFormats), 0)) + VGSvcError("WinPostMsg(%lx, READ_DATA,,) failed, lasterr=%#lx\n", + g_hwndWorker, WinGetLastError(g_habListener)); + break; + + /* + * The host is terminating. + */ + case VBOX_SHCL_HOST_MSG_QUIT: + fQuit = true; + break; + + default: + VGSvcVerbose(1, "vgsvcClipboardOs2Listener: Unknown message %RU32\n", Msg); + break; + } + } + else + { + if (*pfShutdown) + break; + VGSvcError("VbglR3ClipboardGetHostMsg failed, rc=%Rrc\n", rc); + RTThreadSleep(1000); + } + } /* the loop */ + + WinDestroyMsgQueue(g_hmqListener); + } + WinTerminate(g_habListener); + g_habListener = NULLHANDLE; + } + + /* Signal our semaphore to make the worker catch on. */ + RTThreadUserSignal(ThreadSelf); + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: terminating, rc=%Rrc\n", rc); + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2Worker(bool volatile *pfShutdown) +{ + int rc = VERR_GENERAL_FAILURE; + + /* + * Standard PM init. + */ + g_habWorker = RTThreadSelf() != g_ThreadCtrl ? WinInitialize(0) : g_habCtrl; + if (g_habWorker != NULLHANDLE) + { + g_hmqWorker = RTThreadSelf() != g_ThreadCtrl ? WinCreateMsgQueue(g_habWorker, 0) : g_hmqCtrl; + if (g_hmqWorker != NULLHANDLE) + { + if (g_hmqWorker != g_hmqCtrl) + WinCancelShutdown(g_hmqWorker, TRUE); /* We don't care about shutdown */ + + /* + * Create the object window. + */ + if (WinRegisterClass(g_habWorker, (PCSZ)"VBoxServiceClipboardClass", vgsvcClipboardOs2WinProc, 0, 0)) + { + g_hwndWorker = WinCreateWindow(HWND_OBJECT, /* hwndParent */ + (PCSZ)"VBoxServiceClipboardClass", /* pszClass */ + (PCSZ)"VirtualBox Clipboard Service", /* pszName */ + 0, /* flStyle */ + 0, 0, 0, 0, /* x, y, cx, cy */ + NULLHANDLE, /* hwndOwner */ + HWND_BOTTOM, /* hwndInsertBehind */ + 42, /* id */ + NULL, /* pCtlData */ + NULL); /* pPresParams */ + if (g_hwndWorker != NULLHANDLE) + { + VGSvcVerbose(3, "g_hwndWorker=%#lx g_habWorker=%#lx g_hmqWorker=%#lx\n", g_hwndWorker, g_habWorker, g_hmqWorker); + + /* + * Create the listener thread. + */ + g_fListenerOkay = false; + rc = RTThreadCreate(&g_ThreadListener, vgsvcClipboardOs2Listener, (void *)pfShutdown, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "CLIPLISTEN"); + if (RT_SUCCESS(rc)) + { + RTThreadUserWait(g_ThreadListener, 30*1000); + RTThreadUserReset(g_ThreadListener); + if (!g_fListenerOkay) + RTThreadWait(g_ThreadListener, 60*1000, NULL); + if (g_fListenerOkay) + { + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * The PM event pump. + */ + VGSvcVerbose(2, "clipboard: Entering PM message loop.\n"); + rc = VINF_SUCCESS; + QMSG qmsg; + while (WinGetMsg(g_habWorker, &qmsg, NULLHANDLE, NULLHANDLE, 0)) + { + if (qmsg.msg != WM_TIMER) + VGSvcVerbose(6, "WinGetMsg -> hwnd=%p msg=%#x mp1=%p mp2=%p time=%#x ptl=%d,%d rsrv=%#x\n", + qmsg.hwnd, qmsg.msg, qmsg.mp1, qmsg.mp2, qmsg.time, qmsg.ptl.x, qmsg.ptl.y, qmsg.reserved); + WinDispatchMsg(g_habWorker, &qmsg); + } + VGSvcVerbose(2, "clipboard: Exited PM message loop. *pfShutdown=%RTbool\n", *pfShutdown); + + RTThreadWait(g_ThreadListener, 60*1000, NULL); + } + g_ThreadListener = NIL_RTTHREAD; + } + + /* + * Got a WM_QUIT, clean up. + */ + if (g_hwndWorker != NULLHANDLE) + { + WinDestroyWindow(g_hwndWorker); + g_hwndWorker = NULLHANDLE; + } + } + else + VGSvcError("WinCreateWindow() failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + /* no class deregistration in PM. */ + } + else + VGSvcError("WinRegisterClass() failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + + if (g_hmqCtrl != g_hmqWorker) + WinDestroyMsgQueue(g_hmqWorker); + g_hmqWorker = NULLHANDLE; + } + else + VGSvcError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + + if (g_habCtrl != g_habWorker) + WinTerminate(g_habWorker); + g_habWorker = NULLHANDLE; + } + else + VGSvcError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE)); + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcClipboardOs2Stop(void) +{ + if ( g_hmqWorker != NULLHANDLE + && !WinPostQueueMsg(g_hmqWorker, WM_QUIT, NULL, NULL)) + VGSvcError("WinPostQueueMsg(g_hmqWorker, WM_QUIT, 0,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl)); + + /* Must disconnect the clipboard here otherwise the listner won't quit and + the service shutdown will not stop. */ + if (g_u32ClientId != 0) + { + if (g_hmqWorker != NULLHANDLE) + RTThreadSleep(32); /* fudge */ + + VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId); + int rc = VbglR3ClipboardDisconnect(g_u32ClientId); + if (RT_SUCCESS(rc)) + g_u32ClientId = 0; + else + VGSvcError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc); + } +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcClipboardOs2Term(void) +{ + if (g_u32ClientId != 0) + { + VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId); + int rc = VbglR3ClipboardDisconnect(g_u32ClientId); + if (RT_SUCCESS(rc)) + g_u32ClientId = 0; + else + VGSvcError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc); + } + WinDestroyMsgQueue(g_hmqCtrl); + g_hmqCtrl = NULLHANDLE; + WinTerminate(g_habCtrl); + g_habCtrl = NULLHANDLE; +} + + +/** + * The OS/2 'clipboard' service description. + */ +VBOXSERVICE g_Clipboard = +{ + /* pszName. */ + "clipboard", + /* pszDescription. */ + "Shared Clipboard", + /* pszUsage. */ + "" + , + /* pszOptions. */ + "" + , + /* methods */ + vgsvcClipboardOs2PreInit, + vgsvcClipboardOs2Option, + vgsvcClipboardOs2Init, + vgsvcClipboardOs2Worker, + vgsvcClipboardOs2Stop, + vgsvcClipboardOs2Term +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp new file mode 100644 index 00000000..e58d8ab3 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_vgsvc_gstctrl VBoxService - Guest Control + * + * The Guest Control subservice helps implementing the IGuest APIs. + * + * The communication between this service (and its children) and IGuest goes + * over the HGCM GuestControl service. + * + * The IGuest APIs provides means to manipulate (control) files, directories, + * symbolic links and processes within the guest. Most of these means requires + * credentials of a guest OS user to operate, though some restricted ones + * operates directly as the VBoxService user (root / system service account). + * + * The current design is that a subprocess is spawned for handling operations as + * a given user. This process is represented as IGuestSession in the API. The + * subprocess will be spawned as the given use, giving up the privileges the + * parent subservice had. + * + * It will try handle as many of the operations directly from within the + * subprocess, but for more complicated things (or things that haven't yet been + * converted), it will spawn a helper process that does the actual work. + * + * These helpers are the typically modeled on similar unix core utilities, like + * mkdir, rm, rmdir, cat and so on. The helper tools can also be launched + * directly from VBoxManage by the user by prepending the 'vbox_' prefix to the + * unix command. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#include <VBox/err.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/HostServices/GuestControlSvc.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceControl.h" +#include "VBoxServiceUtils.h" + +using namespace guestControl; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The control interval (milliseconds). */ +static uint32_t g_msControlInterval = 0; +/** The semaphore we're blocking our main control thread on. */ +static RTSEMEVENTMULTI g_hControlEvent = NIL_RTSEMEVENTMULTI; +/** The VM session ID. Changes whenever the VM is restored or reset. */ +static uint64_t g_idControlSession; +/** The guest control service client ID. */ +uint32_t g_idControlSvcClient = 0; +/** VBOX_GUESTCTRL_HF_XXX */ +uint64_t g_fControlHostFeatures0 = 0; +#if 0 /** @todo process limit */ +/** How many started guest processes are kept into memory for supplying + * information to the host. Default is 256 processes. If 0 is specified, + * the maximum number of processes is unlimited. */ +static uint32_t g_uControlProcsMaxKept = 256; +#endif +/** List of guest control session threads (VBOXSERVICECTRLSESSIONTHREAD). + * A guest session thread represents a forked guest session process + * of VBoxService. */ +RTLISTANCHOR g_lstControlSessionThreads; +/** The local session object used for handling all session-related stuff. + * When using the legacy guest control protocol (< 2), this session runs + * under behalf of the VBoxService main process. On newer protocol versions + * each session is a forked version of VBoxService using the appropriate + * user credentials for opening a guest session. These forked sessions then + * are kept in VBOXSERVICECTRLSESSIONTHREAD structures. */ +VBOXSERVICECTRLSESSION g_Session; +/** Copy of VbglR3GuestCtrlSupportsOptimizations().*/ +bool g_fControlSupportsOptimizations = true; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vgsvcGstCtrlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int vgsvcGstCtrlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int vgsvcGstCtrlInvalidate(void); +static void vgsvcGstCtrlShutdown(void); + + +/** + * @interface_method_impl{VBOXSERVICE,pfnPreInit} + */ +static DECLCALLBACK(int) vgsvcGstCtrlPreInit(void) +{ + int rc; +#ifdef VBOX_WITH_GUEST_PROPS + /* + * Read the service options from the VM's guest properties. + * Note that these options can be overridden by the command line options later. + */ + uint32_t uGuestPropSvcClientID; + rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID); + if (RT_FAILURE(rc)) + { + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ + { + VGSvcVerbose(0, "Guest property service is not available, skipping\n"); + rc = VINF_SUCCESS; + } + else + VGSvcError("Failed to connect to the guest property service, rc=%Rrc\n", rc); + } + else + VbglR3GuestPropDisconnect(uGuestPropSvcClientID); + + if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */ + rc = VINF_SUCCESS; +#else + /* Nothing to do here yet. */ + rc = VINF_SUCCESS; +#endif + + if (RT_SUCCESS(rc)) + { + /* Init session object. */ + rc = VGSvcGstCtrlSessionInit(&g_Session, 0 /* Flags */); + } + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption} + */ +static DECLCALLBACK(int) vgsvcGstCtrlOption(const char **ppszShort, int argc, char **argv, int *pi) +{ + int rc = -1; + if (ppszShort) + /* no short options */; + else if (!strcmp(argv[*pi], "--control-interval")) + rc = VGSvcArgUInt32(argc, argv, "", pi, + &g_msControlInterval, 1, UINT32_MAX - 1); +#ifdef DEBUG + else if (!strcmp(argv[*pi], "--control-dump-stdout")) + { + g_Session.fFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT; + rc = 0; /* Flag this command as parsed. */ + } + else if (!strcmp(argv[*pi], "--control-dump-stderr")) + { + g_Session.fFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR; + rc = 0; /* Flag this command as parsed. */ + } +#endif + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcGstCtrlInit(void) +{ + /* + * If not specified, find the right interval default. + * Then create the event sem to block on. + */ + if (!g_msControlInterval) + g_msControlInterval = 1000; + + int rc = RTSemEventMultiCreate(&g_hControlEvent); + AssertRCReturn(rc, rc); + + VbglR3GetSessionId(&g_idControlSession); /* The status code is ignored as this information is not available with VBox < 3.2.10. */ + + RTListInit(&g_lstControlSessionThreads); + + /* + * Try connect to the host service and tell it we want to be master (if supported). + */ + rc = VbglR3GuestCtrlConnect(&g_idControlSvcClient); + if (RT_SUCCESS(rc)) + { + rc = vgsvcGstCtrlInvalidate(); + if (RT_SUCCESS(rc)) + return rc; + } + else + { + /* If the service was not found, we disable this service without + causing VBoxService to fail. */ + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ + { + VGSvcVerbose(0, "Guest control service is not available\n"); + rc = VERR_SERVICE_DISABLED; + } + else + VGSvcError("Failed to connect to the guest control service! Error: %Rrc\n", rc); + } + RTSemEventMultiDestroy(g_hControlEvent); + g_hControlEvent = NIL_RTSEMEVENTMULTI; + g_idControlSvcClient = 0; + return rc; +} + +static int vgsvcGstCtrlInvalidate(void) +{ + VGSvcVerbose(1, "Invalidating configuration ...\n"); + + int rc = VINF_SUCCESS; + + g_fControlSupportsOptimizations = VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient); + if (g_fControlSupportsOptimizations) + rc = VbglR3GuestCtrlMakeMeMaster(g_idControlSvcClient); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "Guest control service client ID=%RU32%s\n", + g_idControlSvcClient, g_fControlSupportsOptimizations ? " w/ optimizations" : ""); + + /* + * Report features to the host. + */ + const uint64_t fGuestFeatures = VBOX_GUESTCTRL_GF_0_SET_SIZE + | VBOX_GUESTCTRL_GF_0_PROCESS_ARGV0 + | VBOX_GUESTCTRL_GF_0_PROCESS_DYNAMIC_SIZES + | VBOX_GUESTCTRL_GF_0_SHUTDOWN; + + rc = VbglR3GuestCtrlReportFeatures(g_idControlSvcClient, fGuestFeatures, &g_fControlHostFeatures0); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Host features: %#RX64\n", g_fControlHostFeatures0); + else + VGSvcVerbose(1, "Warning! Feature reporing failed: %Rrc\n", rc); + + return VINF_SUCCESS; + } + VGSvcError("Failed to become guest control master: %Rrc\n", rc); + VbglR3GuestCtrlDisconnect(g_idControlSvcClient); + + return rc; +} + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vgsvcGstCtrlWorker(bool volatile *pfShutdown) +{ + /* + * Tell the control thread that it can continue spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + Assert(g_idControlSvcClient > 0); + + /* Allocate a scratch buffer for messages which also send + * payload data with them. */ + uint32_t cbScratchBuf = _64K; /** @todo Make buffer size configurable via guest properties/argv! */ + AssertReturn(RT_IS_POWER_OF_TWO(cbScratchBuf), VERR_INVALID_PARAMETER); + uint8_t *pvScratchBuf = (uint8_t*)RTMemAlloc(cbScratchBuf); + AssertReturn(pvScratchBuf, VERR_NO_MEMORY); + + int rc = VINF_SUCCESS; /* (shut up compiler warnings) */ + int cRetrievalFailed = 0; /* Number of failed message retrievals in a row. */ + while (!*pfShutdown) + { + VGSvcVerbose(3, "GstCtrl: Waiting for host msg ...\n"); + VBGLR3GUESTCTRLCMDCTX ctxHost = { g_idControlSvcClient, 0 /*idContext*/, 2 /*uProtocol*/, 0 /*cParms*/ }; + uint32_t idMsg = 0; + rc = VbglR3GuestCtrlMsgPeekWait(g_idControlSvcClient, &idMsg, &ctxHost.uNumParms, &g_idControlSession); + if (RT_SUCCESS(rc)) + { + cRetrievalFailed = 0; /* Reset failed retrieval count. */ + VGSvcVerbose(4, "idMsg=%RU32 (%s) (%RU32 parms) retrieved\n", + idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), ctxHost.uNumParms); + + /* + * Handle the host message. + */ + switch (idMsg) + { + case HOST_MSG_CANCEL_PENDING_WAITS: + VGSvcVerbose(1, "We were asked to quit ...\n"); + break; + + case HOST_MSG_SESSION_CREATE: + rc = vgsvcGstCtrlHandleSessionOpen(&ctxHost); + break; + + /* This message is also sent to the child session process (by the host). */ + case HOST_MSG_SESSION_CLOSE: + rc = vgsvcGstCtrlHandleSessionClose(&ctxHost); + break; + + default: + if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient)) + { + rc = VbglR3GuestCtrlMsgSkip(g_idControlSvcClient, VERR_NOT_SUPPORTED, idMsg); + VGSvcVerbose(1, "Skipped unexpected message idMsg=%RU32 (%s), cParms=%RU32 (rc=%Rrc)\n", + idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), ctxHost.uNumParms, rc); + } + else + { + rc = VbglR3GuestCtrlMsgSkipOld(g_idControlSvcClient); + VGSvcVerbose(3, "Skipped idMsg=%RU32, cParms=%RU32, rc=%Rrc\n", idMsg, ctxHost.uNumParms, rc); + } + break; + } + + /* Do we need to shutdown? */ + if (idMsg == HOST_MSG_CANCEL_PENDING_WAITS) + break; + + /* Let's sleep for a bit and let others run ... */ + RTThreadYield(); + } + /* + * Handle restore notification from host. All the context IDs (sessions, + * files, proceses, etc) are invalidated by a VM restore and must be closed. + */ + else if (rc == VERR_VM_RESTORED) + { + VGSvcVerbose(1, "The VM session ID changed (i.e. restored), closing stale root session\n"); + + /* Make sure that all other session threads are gone. + * This is necessary, as the new VM session (NOT to be confused with guest session!) will re-use + * the guest session IDs. */ + int rc2 = VGSvcGstCtrlSessionThreadDestroyAll(&g_lstControlSessionThreads, 0 /* Flags */); + if (RT_FAILURE(rc2)) + VGSvcError("Closing session threads failed with rc=%Rrc\n", rc2); + + /* Make sure to also close the root session (session 0). */ + rc2 = VGSvcGstCtrlSessionClose(&g_Session); + AssertRC(rc2); + + rc2 = VbglR3GuestCtrlSessionHasChanged(g_idControlSvcClient, g_idControlSession); + AssertRC(rc2); + + /* Invalidate the internal state to match the current host we got restored from. */ + rc2 = vgsvcGstCtrlInvalidate(); + AssertRC(rc2); + } + else + { + /* Note: VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */ + /** @todo r=bird: Above comment makes no sense. How can you get a timeout in a blocking HGCM call? */ + VGSvcError("GstCtrl: Getting host message failed with %Rrc\n", rc); + + /* Check for VM session change. */ + /** @todo We don't need to check the host here. */ + uint64_t idNewSession = g_idControlSession; + int rc2 = VbglR3GetSessionId(&idNewSession); + if ( RT_SUCCESS(rc2) + && (idNewSession != g_idControlSession)) + { + VGSvcVerbose(1, "GstCtrl: The VM session ID changed\n"); + g_idControlSession = idNewSession; + + /* Close all opened guest sessions -- all context IDs, sessions etc. + * are now invalid. */ + rc2 = VGSvcGstCtrlSessionClose(&g_Session); + AssertRC(rc2); + + /* Do a reconnect. */ + VGSvcVerbose(1, "Reconnecting to HGCM service ...\n"); + rc2 = VbglR3GuestCtrlConnect(&g_idControlSvcClient); + if (RT_SUCCESS(rc2)) + { + VGSvcVerbose(3, "Guest control service client ID=%RU32\n", g_idControlSvcClient); + cRetrievalFailed = 0; + continue; /* Skip waiting. */ + } + VGSvcError("Unable to re-connect to HGCM service, rc=%Rrc, bailing out\n", rc); + break; + } + + if (rc == VERR_INTERRUPTED) + RTThreadYield(); /* To be on the safe side... */ + else if (++cRetrievalFailed <= 16) /** @todo Make this configurable? */ + RTThreadSleep(1000); /* Wait a bit before retrying. */ + else + { + VGSvcError("Too many failed attempts in a row to get next message, bailing out\n"); + break; + } + } + } + + VGSvcVerbose(0, "Guest control service stopped\n"); + + /* Delete scratch buffer. */ + if (pvScratchBuf) + RTMemFree(pvScratchBuf); + + VGSvcVerbose(0, "Guest control worker returned with rc=%Rrc\n", rc); + return rc; +} + + +static int vgsvcGstCtrlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the message parameters. + */ + PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo; + int rc = VbglR3GuestCtrlSessionGetOpen(pHostCtx, &pStartupInfo); + if (RT_SUCCESS(rc)) + { + /* + * Flat out refuse to work with protocol v1 hosts. + */ + if (pStartupInfo->uProtocol == 2) + { + pHostCtx->uProtocol = pStartupInfo->uProtocol; + VGSvcVerbose(3, "Client ID=%RU32 now is using protocol %RU32\n", pHostCtx->uClientID, pHostCtx->uProtocol); + +/** @todo Someone explain why this code isn't in this file too? v1 support? */ + rc = VGSvcGstCtrlSessionThreadCreate(&g_lstControlSessionThreads, pStartupInfo, NULL /* ppSessionThread */); + /* Report failures to the host (successes are taken care of by the session thread). */ + } + else + { + VGSvcError("The host wants to use protocol v%u, we only support v2!\n", pStartupInfo->uProtocol); + rc = VERR_VERSION_MISMATCH; + } + if (RT_FAILURE(rc)) + { + int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx, GUEST_SESSION_NOTIFYTYPE_ERROR, rc); + if (RT_FAILURE(rc2)) + VGSvcError("Reporting session error status on open failed with rc=%Rrc\n", rc2); + } + } + else + { + VGSvcError("Error fetching parameters for opening guest session: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + VbglR3GuestCtrlSessionStartupInfoFree(pStartupInfo); + pStartupInfo = NULL; + + VGSvcVerbose(3, "Opening a new guest session returned rc=%Rrc\n", rc); + return rc; +} + + +static int vgsvcGstCtrlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + uint32_t idSession; + uint32_t fFlags; + int rc = VbglR3GuestCtrlSessionGetClose(pHostCtx, &fFlags, &idSession); + if (RT_SUCCESS(rc)) + { + rc = VERR_NOT_FOUND; + + PVBOXSERVICECTRLSESSIONTHREAD pThread; + RTListForEach(&g_lstControlSessionThreads, pThread, VBOXSERVICECTRLSESSIONTHREAD, Node) + { + if ( pThread->pStartupInfo + && pThread->pStartupInfo->uSessionID == idSession) + { + rc = VGSvcGstCtrlSessionThreadDestroy(pThread, fFlags); + break; + } + } + +#if 0 /** @todo A bit of a mess here as this message goes to both to this process (master) and the session process. */ + if (RT_FAILURE(rc)) + { + /* Report back on failure. On success this will be done + * by the forked session thread. */ + int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx, + GUEST_SESSION_NOTIFYTYPE_ERROR, rc); + if (RT_FAILURE(rc2)) + { + VGSvcError("Reporting session error status on close failed with rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } +#endif + VGSvcVerbose(2, "Closing guest session %RU32 returned rc=%Rrc\n", idSession, rc); + } + else + { + VGSvcError("Error fetching parameters for closing guest session: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcGstCtrlStop(void) +{ + VGSvcVerbose(3, "Stopping ...\n"); + + /** @todo Later, figure what to do if we're in RTProcWait(). It's a very + * annoying call since doesn't support timeouts in the posix world. */ + if (g_hControlEvent != NIL_RTSEMEVENTMULTI) + RTSemEventMultiSignal(g_hControlEvent); + + /* + * Ask the host service to cancel all pending requests for the main + * control thread so that we can shutdown properly here. + */ + if (g_idControlSvcClient) + { + VGSvcVerbose(3, "Cancelling pending waits (client ID=%u) ...\n", + g_idControlSvcClient); + + int rc = VbglR3GuestCtrlCancelPendingWaits(g_idControlSvcClient); + if (RT_FAILURE(rc)) + VGSvcError("Cancelling pending waits failed; rc=%Rrc\n", rc); + } +} + + +/** + * Destroys all guest process threads which are still active. + */ +static void vgsvcGstCtrlShutdown(void) +{ + VGSvcVerbose(2, "Shutting down ...\n"); + + int rc2 = VGSvcGstCtrlSessionThreadDestroyAll(&g_lstControlSessionThreads, 0 /* Flags */); + if (RT_FAILURE(rc2)) + VGSvcError("Closing session threads failed with rc=%Rrc\n", rc2); + + rc2 = VGSvcGstCtrlSessionClose(&g_Session); + if (RT_FAILURE(rc2)) + VGSvcError("Closing session failed with rc=%Rrc\n", rc2); + + VGSvcVerbose(2, "Shutting down complete\n"); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcGstCtrlTerm(void) +{ + VGSvcVerbose(3, "Terminating ...\n"); + + vgsvcGstCtrlShutdown(); + + VGSvcVerbose(3, "Disconnecting client ID=%u ...\n", g_idControlSvcClient); + VbglR3GuestCtrlDisconnect(g_idControlSvcClient); + g_idControlSvcClient = 0; + + if (g_hControlEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_hControlEvent); + g_hControlEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'vminfo' service description. + */ +VBOXSERVICE g_Control = +{ + /* pszName. */ + "control", + /* pszDescription. */ + "Host-driven Guest Control", + /* pszUsage. */ +#ifdef DEBUG + " [--control-dump-stderr] [--control-dump-stdout]\n" +#endif + " [--control-interval <ms>]" + , + /* pszOptions. */ +#ifdef DEBUG + " --control-dump-stderr Dumps all guest proccesses stderr data to the\n" + " temporary directory.\n" + " --control-dump-stdout Dumps all guest proccesses stdout data to the\n" + " temporary directory.\n" +#endif + " --control-interval Specifies the interval at which to check for\n" + " new control messages. The default is 1000 ms.\n" + , + /* methods */ + vgsvcGstCtrlPreInit, + vgsvcGstCtrlOption, + vgsvcGstCtrlInit, + vgsvcGstCtrlWorker, + vgsvcGstCtrlStop, + vgsvcGstCtrlTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h new file mode 100644 index 00000000..804e3b48 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/critsect.h> +#include <iprt/list.h> +#include <iprt/req.h> + +#include <VBox/VBoxGuestLib.h> +#include <VBox/GuestHost/GuestControl.h> +#include <VBox/HostServices/GuestControlSvc.h> + + +/** + * Pipe IDs for handling the guest process poll set. + */ +typedef enum VBOXSERVICECTRLPIPEID +{ + VBOXSERVICECTRLPIPEID_UNKNOWN = 0, + VBOXSERVICECTRLPIPEID_STDIN = 10, + VBOXSERVICECTRLPIPEID_STDIN_WRITABLE = 11, + /** Pipe for reading from guest process' stdout. */ + VBOXSERVICECTRLPIPEID_STDOUT = 40, + /** Pipe for reading from guest process' stderr. */ + VBOXSERVICECTRLPIPEID_STDERR = 50, + /** Notification pipe for waking up the guest process + * control thread. */ + VBOXSERVICECTRLPIPEID_IPC_NOTIFY = 100 +} VBOXSERVICECTRLPIPEID; + +/** + * Structure for one (opened) guest file. + */ +typedef struct VBOXSERVICECTRLFILE +{ + /** Pointer to list archor of following + * list node. + * @todo Would be nice to have a RTListGetAnchor(). */ + PRTLISTANCHOR pAnchor; + /** Node to global guest control file list. */ + /** @todo Use a map later? */ + RTLISTNODE Node; + /** The file name. */ + char *pszName; + /** The file handle on the guest. */ + RTFILE hFile; + /** File handle to identify this file. */ + uint32_t uHandle; + /** Context ID. */ + uint32_t uContextID; + /** RTFILE_O_XXX flags. */ + uint64_t fOpen; +} VBOXSERVICECTRLFILE; +/** Pointer to thread data. */ +typedef VBOXSERVICECTRLFILE *PVBOXSERVICECTRLFILE; + +/** + * Structure for a guest session thread to + * observe/control the forked session instance from + * the VBoxService main executable. + */ +typedef struct VBOXSERVICECTRLSESSIONTHREAD +{ + /** Node to global guest control session list. */ + /** @todo Use a map later? */ + RTLISTNODE Node; + /** The sessions's startup info. */ + PVBGLR3GUESTCTRLSESSIONSTARTUPINFO + pStartupInfo; + /** Critical section for thread-safe use. */ + RTCRITSECT CritSect; + /** The worker thread. */ + RTTHREAD Thread; + /** Process handle for forked child. */ + RTPROCESS hProcess; + /** Shutdown indicator; will be set when the thread + * needs (or is asked) to shutdown. */ + bool volatile fShutdown; + /** Indicator set by the service thread exiting. */ + bool volatile fStopped; + /** Whether the thread was started or not. */ + bool fStarted; +#if 0 /* Pipe IPC not used yet. */ + /** Pollset containing all the pipes. */ + RTPOLLSET hPollSet; + RTPIPE hStdInW; + RTPIPE hStdOutR; + RTPIPE hStdErrR; + struct StdPipe + { + RTHANDLE hChild; + PRTHANDLE phChild; + } StdIn, + StdOut, + StdErr; + /** The notification pipe associated with this guest session. + * This is NIL_RTPIPE for output pipes. */ + RTPIPE hNotificationPipeW; + /** The other end of hNotificationPipeW. */ + RTPIPE hNotificationPipeR; +#endif + /** Pipe for handing the secret key to the session process. */ + RTPIPE hKeyPipe; + /** Secret key. */ + uint8_t abKey[_4K]; +} VBOXSERVICECTRLSESSIONTHREAD; +/** Pointer to thread data. */ +typedef VBOXSERVICECTRLSESSIONTHREAD *PVBOXSERVICECTRLSESSIONTHREAD; + +/** Defines the prefix being used for telling our service executable that we're going + * to spawn a new (Guest Control) user session. */ +#define VBOXSERVICECTRLSESSION_GETOPT_PREFIX "guestsession" + +/** Flag indicating that this session has been spawned from + * the main executable. */ +#define VBOXSERVICECTRLSESSION_FLAG_SPAWN RT_BIT(0) +/** Flag indicating that this session is anonymous, that is, + * it will run start guest processes with the same credentials + * as the main executable. */ +#define VBOXSERVICECTRLSESSION_FLAG_ANONYMOUS RT_BIT(1) +/** Flag indicating that started guest processes will dump their + * stdout output to a separate file on disk. For debugging. */ +#define VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT RT_BIT(2) +/** Flag indicating that started guest processes will dump their + * stderr output to a separate file on disk. For debugging. */ +#define VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR RT_BIT(3) + +/** + * Structure for maintaining a guest session. This also + * contains all started threads (e.g. for guest processes). + * + * This structure can act in two different ways: + * - For legacy guest control handling (protocol version < 2) + * this acts as a per-guest process structure containing all + * the information needed to get a guest process up and running. + * - For newer guest control protocols (>= 2) this structure is + * part of the forked session child, maintaining all guest + * control objects under it. + */ +typedef struct VBOXSERVICECTRLSESSION +{ + /* The session's startup information. */ + VBGLR3GUESTCTRLSESSIONSTARTUPINFO + StartupInfo; + /** List of active guest process threads + * (VBOXSERVICECTRLPROCESS). */ + RTLISTANCHOR lstProcesses; + /** Number of guest processes in the process list. */ + uint32_t cProcesses; + /** List of guest control files (VBOXSERVICECTRLFILE). */ + RTLISTANCHOR lstFiles; + /** Number of guest files in the file list. */ + uint32_t cFiles; + /** The session's critical section. */ + RTCRITSECT CritSect; + /** Internal session flags, not related + * to StartupInfo stuff. + * @sa VBOXSERVICECTRLSESSION_FLAG_* flags. */ + uint32_t fFlags; + /** How many processes do we allow keeping around at a time? */ + uint32_t uProcsMaxKept; +} VBOXSERVICECTRLSESSION; +/** Pointer to guest session. */ +typedef VBOXSERVICECTRLSESSION *PVBOXSERVICECTRLSESSION; + +/** + * Structure for holding data for one (started) guest process. + */ +typedef struct VBOXSERVICECTRLPROCESS +{ + /** Node. */ + RTLISTNODE Node; + /** Process handle. */ + RTPROCESS hProcess; + /** Number of references using this struct. */ + uint32_t cRefs; + /** The worker thread. */ + RTTHREAD Thread; + /** The session this guest process + * is bound to. */ + PVBOXSERVICECTRLSESSION pSession; + /** Shutdown indicator; will be set when the thread + * needs (or is asked) to shutdown. */ + bool volatile fShutdown; + /** Whether the guest process thread was stopped or not. */ + bool volatile fStopped; + /** Whether the guest process thread was started or not. */ + bool fStarted; + /** Context ID. */ + uint32_t uContextID; + /** Critical section for thread-safe use. */ + RTCRITSECT CritSect; + /** Process startup information. */ + PVBGLR3GUESTCTRLPROCSTARTUPINFO + pStartupInfo; + /** The process' PID assigned by the guest OS. */ + uint32_t uPID; + /** The process' request queue to handle requests + * from the outside, e.g. the session. */ + RTREQQUEUE hReqQueue; + /** Our pollset, used for accessing the process' + * std* pipes + the notification pipe. */ + RTPOLLSET hPollSet; + /** StdIn pipe for addressing writes to the + * guest process' stdin.*/ + RTPIPE hPipeStdInW; + /** StdOut pipe for addressing reads from + * guest process' stdout.*/ + RTPIPE hPipeStdOutR; + /** StdOut pipe for addressing reads from + * guest process' stderr.*/ + RTPIPE hPipeStdErrR; + + /** The write end of the notification pipe that is used to poke the thread + * monitoring the process. + * This is NIL_RTPIPE for output pipes. */ + RTPIPE hNotificationPipeW; + /** The other end of hNotificationPipeW, read by vgsvcGstCtrlProcessProcLoop(). */ + RTPIPE hNotificationPipeR; +} VBOXSERVICECTRLPROCESS; +/** Pointer to thread data. */ +typedef VBOXSERVICECTRLPROCESS *PVBOXSERVICECTRLPROCESS; + +RT_C_DECLS_BEGIN + +extern RTLISTANCHOR g_lstControlSessionThreads; +extern VBOXSERVICECTRLSESSION g_Session; +extern uint32_t g_idControlSvcClient; +extern uint64_t g_fControlHostFeatures0; +extern bool g_fControlSupportsOptimizations; + + +/** @name Guest session thread handling. + * @{ */ +extern int VGSvcGstCtrlSessionThreadCreate(PRTLISTANCHOR pList, const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo, PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread); +extern int VGSvcGstCtrlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pSession, uint32_t uFlags); +extern int VGSvcGstCtrlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t uFlags); +extern int VGSvcGstCtrlSessionThreadTerminate(PVBOXSERVICECTRLSESSIONTHREAD pSession); +extern RTEXITCODE VGSvcGstCtrlSessionSpawnInit(int argc, char **argv); +/** @} */ +/** @name Per-session functions. + * @{ */ +extern PVBOXSERVICECTRLPROCESS VGSvcGstCtrlSessionRetainProcess(PVBOXSERVICECTRLSESSION pSession, uint32_t uPID); +extern int VGSvcGstCtrlSessionClose(PVBOXSERVICECTRLSESSION pSession); +extern int VGSvcGstCtrlSessionDestroy(PVBOXSERVICECTRLSESSION pSession); +extern int VGSvcGstCtrlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t uFlags); +extern int VGSvcGstCtrlSessionHandler(PVBOXSERVICECTRLSESSION pSession, uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx, void *pvScratchBuf, size_t cbScratchBuf, volatile bool *pfShutdown); +extern int VGSvcGstCtrlSessionProcessAdd(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess); +extern int VGSvcGstCtrlSessionProcessRemove(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess); +extern int VGSvcGstCtrlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession, bool *pfAllowed); +extern int VGSvcGstCtrlSessionReapProcesses(PVBOXSERVICECTRLSESSION pSession); +/** @} */ +/** @name Per-guest process functions. + * @{ */ +extern int VGSvcGstCtrlProcessFree(PVBOXSERVICECTRLPROCESS pProcess); +extern int VGSvcGstCtrlProcessHandleInput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fPendingClose, void *pvBuf, uint32_t cbBuf); +extern int VGSvcGstCtrlProcessHandleOutput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags); +extern int VGSvcGstCtrlProcessHandleTerm(PVBOXSERVICECTRLPROCESS pProcess); +extern void VGSvcGstCtrlProcessRelease(PVBOXSERVICECTRLPROCESS pProcess); +extern int VGSvcGstCtrlProcessStart(const PVBOXSERVICECTRLSESSION pSession, const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo, uint32_t uContext); +extern int VGSvcGstCtrlProcessStop(PVBOXSERVICECTRLPROCESS pProcess); +extern int VGSvcGstCtrlProcessWait(const PVBOXSERVICECTRLPROCESS pProcess, RTMSINTERVAL msTimeout, int *pRc); +/** @} */ + +RT_C_DECLS_END + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp new file mode 100644 index 00000000..b0ec45c8 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/handle.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/thread.h> + +#include <VBox/VBoxGuestLib.h> +#include <VBox/HostServices/GuestControlSvc.h> + +#include "VBoxServiceInternal.h" +#include "VBoxServiceControl.h" +#include "VBoxServiceToolBox.h" + +using namespace guestControl; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vgsvcGstCtrlProcessAssignPID(PVBOXSERVICECTRLPROCESS pThread, uint32_t uPID); +static int vgsvcGstCtrlProcessLock(PVBOXSERVICECTRLPROCESS pProcess); +static int vgsvcGstCtrlProcessSetupPipe(const char *pszHowTo, int fd, PRTHANDLE ph, PRTHANDLE *pph, + PRTPIPE phPipe); +static int vgsvcGstCtrlProcessUnlock(PVBOXSERVICECTRLPROCESS pProcess); +/* Request handlers. */ +static DECLCALLBACK(int) vgsvcGstCtrlProcessOnInput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + bool fPendingClose, void *pvBuf, uint32_t cbBuf); +static DECLCALLBACK(int) vgsvcGstCtrlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags); + + + +/** + * Initialies the passed in thread data structure with the parameters given. + * + * @return IPRT status code. + * @param pProcess Process to initialize. + * @param pSession Guest session the process is bound to. + * @param pStartupInfo Startup information. + * @param u32ContextID The context ID bound to this request / command. + */ +static int vgsvcGstCtrlProcessInit(PVBOXSERVICECTRLPROCESS pProcess, + const PVBOXSERVICECTRLSESSION pSession, + const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo, + uint32_t u32ContextID) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER); + + /* General stuff. */ + pProcess->hProcess = NIL_RTPROCESS; + pProcess->pSession = pSession; + pProcess->Node.pPrev = NULL; + pProcess->Node.pNext = NULL; + + pProcess->fShutdown = false; + pProcess->fStarted = false; + pProcess->fStopped = false; + + pProcess->uPID = 0; /* Don't have a PID yet. */ + pProcess->cRefs = 0; + /* + * Use the initial context ID we got for starting + * the process to report back its status with the + * same context ID. + */ + pProcess->uContextID = u32ContextID; + /* + * Note: pProcess->ClientID will be assigned when thread is started; + * every guest process has its own client ID to detect crashes on + * a per-guest-process level. + */ + + int rc = RTCritSectInit(&pProcess->CritSect); + if (RT_FAILURE(rc)) + return rc; + + pProcess->hPollSet = NIL_RTPOLLSET; + pProcess->hPipeStdInW = NIL_RTPIPE; + pProcess->hPipeStdOutR = NIL_RTPIPE; + pProcess->hPipeStdErrR = NIL_RTPIPE; + pProcess->hNotificationPipeW = NIL_RTPIPE; + pProcess->hNotificationPipeR = NIL_RTPIPE; + + rc = RTReqQueueCreate(&pProcess->hReqQueue); + AssertReleaseRC(rc); + + /* Duplicate startup info. */ + pProcess->pStartupInfo = VbglR3GuestCtrlProcStartupInfoDup(pStartupInfo); + AssertPtrReturn(pProcess->pStartupInfo, VERR_NO_MEMORY); + + /* Adjust timeout value. */ + if ( pProcess->pStartupInfo->uTimeLimitMS == UINT32_MAX + || pProcess->pStartupInfo->uTimeLimitMS == 0) + pProcess->pStartupInfo->uTimeLimitMS = RT_INDEFINITE_WAIT; + + if (RT_FAILURE(rc)) /* Clean up on failure. */ + VGSvcGstCtrlProcessFree(pProcess); + return rc; +} + + +/** + * Frees a guest process. On success, pProcess will be + * free'd and thus won't be available anymore. + * + * @return IPRT status code. + * @param pProcess Guest process to free. + * The pointer will not be valid anymore after return. + */ +int VGSvcGstCtrlProcessFree(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pProcess->CritSect); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "[PID %RU32]: Freeing (cRefs=%RU32)...\n", pProcess->uPID, pProcess->cRefs); + + AssertReturn(pProcess->cRefs == 0, VERR_WRONG_ORDER); + AssertReturn(pProcess->fStopped, VERR_WRONG_ORDER); + AssertReturn(pProcess->fShutdown, VERR_WRONG_ORDER); + + VbglR3GuestCtrlProcStartupInfoFree(pProcess->pStartupInfo); + pProcess->pStartupInfo = NULL; + + /* + * Destroy other thread data. + */ + rc = RTPollSetDestroy(pProcess->hPollSet); + AssertRC(rc); + + rc = RTReqQueueDestroy(pProcess->hReqQueue); + AssertRC(rc); + + rc = RTPipeClose(pProcess->hNotificationPipeR); + AssertRC(rc); + rc = RTPipeClose(pProcess->hNotificationPipeW); + AssertRC(rc); + + rc = RTPipeClose(pProcess->hPipeStdInW); + AssertRC(rc); + rc = RTPipeClose(pProcess->hPipeStdErrR); + AssertRC(rc); + rc = RTPipeClose(pProcess->hPipeStdOutR); + AssertRC(rc); + + rc = RTCritSectLeave(&pProcess->CritSect); + AssertRC(rc); + + RTCritSectDelete(&pProcess->CritSect); + + /* + * Destroy thread structure as final step. + */ + RTMemFree(pProcess); + pProcess = NULL; + } + + return rc; +} + + +/** + * Signals a guest process thread that we want it to shut down in + * a gentle way. + * + * @return IPRT status code. + * @param pProcess Process to stop. + */ +int VGSvcGstCtrlProcessStop(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + VGSvcVerbose(3, "[PID %RU32]: Stopping ...\n", pProcess->uPID); + + /* Do *not* set pThread->fShutdown or other stuff here! + * The guest thread loop will clean up itself. */ + + return VGSvcGstCtrlProcessHandleTerm(pProcess); +} + + +/** + * Releases a previously acquired guest process (decreases the refcount). + * + * @param pProcess Process to release. + */ +void VGSvcGstCtrlProcessRelease(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturnVoid(pProcess); + + int rc2 = RTCritSectEnter(&pProcess->CritSect); + if (RT_SUCCESS(rc2)) + { + AssertReturnVoid(pProcess->cRefs); + pProcess->cRefs--; + + VGSvcVerbose(3, "[PID %RU32]: cRefs=%RU32, fShutdown=%RTbool, fStopped=%RTbool\n", + pProcess->uPID, pProcess->cRefs, pProcess->fShutdown, pProcess->fStopped); + + rc2 = RTCritSectLeave(&pProcess->CritSect); + AssertRC(rc2); + } +} + + +/** + * Wait for a guest process thread to shut down. + * + * @return IPRT status code. + * @param pProcess Process to wait shutting down for. + * @param msTimeout Timeout in ms to wait for shutdown. + * @param prc Where to store the thread's return code. + * Optional. + */ +int VGSvcGstCtrlProcessWait(const PVBOXSERVICECTRLPROCESS pProcess, RTMSINTERVAL msTimeout, int *prc) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + AssertPtrNullReturn(prc, VERR_INVALID_POINTER); + + int rc = vgsvcGstCtrlProcessLock(pProcess); + if (RT_SUCCESS(rc)) + { + if (RTThreadGetState(pProcess->Thread) != RTTHREADSTATE_INVALID) /* Is there a thread we can wait for? */ + { + VGSvcVerbose(2, "[PID %RU32]: Waiting for shutdown (%RU32ms) ...\n", pProcess->uPID, msTimeout); + + AssertMsgReturn(pProcess->fStarted, + ("Tried to wait on guest process=%p (PID %RU32) which has not been started yet\n", + pProcess, pProcess->uPID), VERR_INVALID_PARAMETER); + + /* Unlock process before waiting. */ + rc = vgsvcGstCtrlProcessUnlock(pProcess); + AssertRC(rc); + + /* Do the actual waiting. */ + int rcThread; + Assert(pProcess->Thread != NIL_RTTHREAD); + rc = RTThreadWait(pProcess->Thread, msTimeout, &rcThread); + + int rc2 = vgsvcGstCtrlProcessLock(pProcess); + AssertRC(rc2); + + if (RT_SUCCESS(rc)) + { + pProcess->Thread = NIL_RTTHREAD; + VGSvcVerbose(3, "[PID %RU32]: Thread shutdown complete, thread rc=%Rrc\n", pProcess->uPID, rcThread); + if (prc) + *prc = rcThread; + } + } + + int rc2 = vgsvcGstCtrlProcessUnlock(pProcess); + AssertRC(rc2); + } + + if (RT_FAILURE(rc)) + VGSvcError("[PID %RU32]: Waiting for shutting down thread returned error rc=%Rrc\n", pProcess->uPID, rc); + + VGSvcVerbose(3, "[PID %RU32]: Waiting resulted in rc=%Rrc\n", pProcess->uPID, rc); + return rc; +} + + +/** + * Closes the stdin pipe of a guest process. + * + * @return IPRT status code. + * @param pProcess The process which input pipe we close. + * @param phStdInW The standard input pipe handle. + */ +static int vgsvcGstCtrlProcessPollsetCloseInput(PVBOXSERVICECTRLPROCESS pProcess, PRTPIPE phStdInW) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + AssertPtrReturn(phStdInW, VERR_INVALID_POINTER); + + int rc = RTPollSetRemove(pProcess->hPollSet, VBOXSERVICECTRLPIPEID_STDIN); + if (rc != VERR_POLL_HANDLE_ID_NOT_FOUND) + AssertRC(rc); + + if (*phStdInW != NIL_RTPIPE) + { + rc = RTPipeClose(*phStdInW); + AssertRC(rc); + *phStdInW = NIL_RTPIPE; + } + + return rc; +} + + +#ifdef DEBUG +/** + * Names a poll handle ID. + * + * @returns Pointer to read-only string. + * @param idPollHnd What to name. + */ +static const char *vgsvcGstCtrlProcessPollHandleToString(uint32_t idPollHnd) +{ + switch (idPollHnd) + { + case VBOXSERVICECTRLPIPEID_UNKNOWN: + return "unknown"; + case VBOXSERVICECTRLPIPEID_STDIN: + return "stdin"; + case VBOXSERVICECTRLPIPEID_STDIN_WRITABLE: + return "stdin_writable"; + case VBOXSERVICECTRLPIPEID_STDOUT: + return "stdout"; + case VBOXSERVICECTRLPIPEID_STDERR: + return "stderr"; + case VBOXSERVICECTRLPIPEID_IPC_NOTIFY: + return "ipc_notify"; + default: + return "unknown"; + } +} +#endif /* DEBUG */ + + +/** + * Handle an error event on standard input. + * + * @return IPRT status code. + * @param pProcess Process to handle pollset for. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phStdInW The standard input pipe handle. + */ +static int vgsvcGstCtrlProcessPollsetOnInput(PVBOXSERVICECTRLPROCESS pProcess, uint32_t fPollEvt, PRTPIPE phStdInW) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + NOREF(fPollEvt); + + return vgsvcGstCtrlProcessPollsetCloseInput(pProcess, phStdInW); +} + + +/** + * Handle pending output data or error on standard out or standard error. + * + * @returns IPRT status code from client send. + * @param pProcess Process to handle pollset for. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phPipeR The pipe handle. + * @param idPollHnd The pipe ID to handle. + */ +static int vgsvcGstCtrlProcessHandleOutputError(PVBOXSERVICECTRLPROCESS pProcess, + uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd) +{ + RT_NOREF1(fPollEvt); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + if (!phPipeR) + return VINF_SUCCESS; + +#ifdef DEBUG + VGSvcVerbose(4, "[PID %RU32]: Output error: idPollHnd=%s, fPollEvt=0x%x\n", + pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), fPollEvt); +#endif + + /* Remove pipe from poll set. */ + int rc2 = RTPollSetRemove(pProcess->hPollSet, idPollHnd); + AssertMsg(RT_SUCCESS(rc2) || rc2 == VERR_POLL_HANDLE_ID_NOT_FOUND, ("%Rrc\n", rc2)); + + bool fClosePipe = true; /* By default close the pipe. */ + + /* Check if there's remaining data to read from the pipe. */ + if (*phPipeR != NIL_RTPIPE) + { + size_t cbReadable; + rc2 = RTPipeQueryReadable(*phPipeR, &cbReadable); + if ( RT_SUCCESS(rc2) + && cbReadable) + { +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: idPollHnd=%s has %zu bytes left, vetoing close\n", + pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), cbReadable); +#endif + /* Veto closing the pipe yet because there's still stuff to read + * from the pipe. This can happen on UNIX-y systems where on + * error/hangup there still can be data to be read out. */ + fClosePipe = false; + } + } +#ifdef DEBUG + else + VGSvcVerbose(3, "[PID %RU32]: idPollHnd=%s will be closed\n", + pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd)); +#endif + + if ( *phPipeR != NIL_RTPIPE + && fClosePipe) + { + rc2 = RTPipeClose(*phPipeR); + AssertRC(rc2); + *phPipeR = NIL_RTPIPE; + } + + return VINF_SUCCESS; +} + + +/** + * Handle pending output data or error on standard out or standard error. + * + * @returns IPRT status code from client send. + * @param pProcess Process to handle pollset for. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phPipeR The pipe handle. + * @param idPollHnd The pipe ID to handle. + * + */ +static int vgsvcGstCtrlProcessPollsetOnOutput(PVBOXSERVICECTRLPROCESS pProcess, + uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + +#ifdef DEBUG + VGSvcVerbose(4, "[PID %RU32]: Output event phPipeR=%p, idPollHnd=%s, fPollEvt=0x%x\n", + pProcess->uPID, phPipeR, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), fPollEvt); +#endif + + if (!phPipeR) + return VINF_SUCCESS; + + int rc = VINF_SUCCESS; + +#ifdef DEBUG + if (*phPipeR != NIL_RTPIPE) + { + size_t cbReadable; + rc = RTPipeQueryReadable(*phPipeR, &cbReadable); + if ( RT_SUCCESS(rc) + && cbReadable) + { + VGSvcVerbose(4, "[PID %RU32]: Output event cbReadable=%zu\n", pProcess->uPID, cbReadable); + } + } +#endif + +#if 0 + /* Push output to the host. */ + if (fPollEvt & RTPOLL_EVT_READ) + { + size_t cbRead = 0; + uint8_t byData[_64K]; + rc = RTPipeRead(*phPipeR, byData, sizeof(byData), &cbRead); + VGSvcVerbose(4, "VGSvcGstCtrlProcessHandleOutputEvent cbRead=%u, rc=%Rrc\n", cbRead, rc); + + /* Make sure we go another poll round in case there was too much data + for the buffer to hold. */ + fPollEvt &= RTPOLL_EVT_ERROR; + } +#endif + + if (fPollEvt & RTPOLL_EVT_ERROR) + rc = vgsvcGstCtrlProcessHandleOutputError(pProcess, fPollEvt, phPipeR, idPollHnd); + return rc; +} + + +/** + * Execution loop which runs in a dedicated per-started-process thread and + * handles all pipe input/output and signalling stuff. + * + * @return IPRT status code. + * @param pProcess The guest process to handle. + */ +static int vgsvcGstCtrlProcessProcLoop(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc; + int rc2; + uint64_t const uMsStart = RTTimeMilliTS(); + RTPROCSTATUS ProcessStatus = { 254, RTPROCEXITREASON_ABEND }; + bool fProcessAlive = true; + bool fProcessTimedOut = false; + uint64_t MsProcessKilled = UINT64_MAX; + RTMSINTERVAL const cMsPollBase = pProcess->hPipeStdInW != NIL_RTPIPE + ? 100 /* Need to poll for input. */ + : 1000; /* Need only poll for process exit and aborts. */ + RTMSINTERVAL cMsPollCur = 0; + + /* + * Assign PID to thread data. + * Also check if there already was a thread with the same PID and shut it down -- otherwise + * the first (stale) entry will be found and we get really weird results! + */ + rc = vgsvcGstCtrlProcessAssignPID(pProcess, pProcess->hProcess /* Opaque PID handle */); + if (RT_FAILURE(rc)) + { + VGSvcError("Unable to assign PID=%u, to new thread, rc=%Rrc\n", pProcess->hProcess, rc); + return rc; + } + + /* + * Before entering the loop, tell the host that we've started the guest + * and that it's now OK to send input to the process. + */ + VGSvcVerbose(2, "[PID %RU32]: Process '%s' started, CID=%u, User=%s, cMsTimeout=%RU32\n", + pProcess->uPID, pProcess->pStartupInfo->pszCmd, pProcess->uContextID, + pProcess->pStartupInfo->pszUser, pProcess->pStartupInfo->uTimeLimitMS); + VBGLR3GUESTCTRLCMDCTX ctxStart = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ }; + rc = VbglR3GuestCtrlProcCbStatus(&ctxStart, + pProcess->uPID, PROC_STS_STARTED, 0 /* u32Flags */, + NULL /* pvData */, 0 /* cbData */); + if (rc == VERR_INTERRUPTED) + rc = VINF_SUCCESS; /* SIGCHLD send by quick childs! */ + if (RT_FAILURE(rc)) + VGSvcError("[PID %RU32]: Error reporting starting status to host, rc=%Rrc\n", pProcess->uPID, rc); + + /* + * Process input, output, the test pipe and client requests. + */ + while ( RT_SUCCESS(rc) + && RT_UNLIKELY(!pProcess->fShutdown)) + { + /* + * Wait/Process all pending events. + */ + uint32_t idPollHnd; + uint32_t fPollEvt; + rc2 = RTPollNoResume(pProcess->hPollSet, cMsPollCur, &fPollEvt, &idPollHnd); + if (pProcess->fShutdown) + continue; + + cMsPollCur = 0; /* No rest until we've checked everything. */ + + if (RT_SUCCESS(rc2)) + { + switch (idPollHnd) + { + case VBOXSERVICECTRLPIPEID_STDIN: + rc = vgsvcGstCtrlProcessPollsetOnInput(pProcess, fPollEvt, &pProcess->hPipeStdInW); + break; + + case VBOXSERVICECTRLPIPEID_STDOUT: + rc = vgsvcGstCtrlProcessPollsetOnOutput(pProcess, fPollEvt, &pProcess->hPipeStdOutR, idPollHnd); + break; + + case VBOXSERVICECTRLPIPEID_STDERR: + rc = vgsvcGstCtrlProcessPollsetOnOutput(pProcess, fPollEvt, &pProcess->hPipeStdErrR, idPollHnd); + break; + + case VBOXSERVICECTRLPIPEID_IPC_NOTIFY: +#ifdef DEBUG_andy + VGSvcVerbose(4, "[PID %RU32]: IPC notify\n", pProcess->uPID); +#endif + rc2 = vgsvcGstCtrlProcessLock(pProcess); + if (RT_SUCCESS(rc2)) + { + /* Drain the notification pipe. */ + uint8_t abBuf[8]; + size_t cbIgnore; + rc2 = RTPipeRead(pProcess->hNotificationPipeR, abBuf, sizeof(abBuf), &cbIgnore); + if (RT_FAILURE(rc2)) + VGSvcError("Draining IPC notification pipe failed with rc=%Rrc\n", rc2); + + /* Process all pending requests. */ + VGSvcVerbose(4, "[PID %RU32]: Processing pending requests ...\n", pProcess->uPID); + Assert(pProcess->hReqQueue != NIL_RTREQQUEUE); + rc2 = RTReqQueueProcess(pProcess->hReqQueue, + 0 /* Only process all pending requests, don't wait for new ones */); + if ( RT_FAILURE(rc2) + && rc2 != VERR_TIMEOUT) + VGSvcError("Processing requests failed with with rc=%Rrc\n", rc2); + + int rc3 = vgsvcGstCtrlProcessUnlock(pProcess); + AssertRC(rc3); +#ifdef DEBUG + VGSvcVerbose(4, "[PID %RU32]: Processing pending requests done, rc=%Rrc\n", pProcess->uPID, rc2); +#endif + } + + break; + + default: + AssertMsgFailed(("Unknown idPollHnd=%RU32\n", idPollHnd)); + break; + } + + if (RT_FAILURE(rc) || rc == VINF_EOF) + break; /* Abort command, or client dead or something. */ + } +#if 0 + VGSvcVerbose(4, "[PID %RU32]: Polling done, pollRc=%Rrc, pollCnt=%RU32, idPollHnd=%s, rc=%Rrc, fProcessAlive=%RTbool, fShutdown=%RTbool\n", + pProcess->uPID, rc2, RTPollSetGetCount(hPollSet), vgsvcGstCtrlProcessPollHandleToString(idPollHnd), rc, fProcessAlive, pProcess->fShutdown); + VGSvcVerbose(4, "[PID %RU32]: stdOut=%s, stdErrR=%s\n", + pProcess->uPID, + *phStdOutR == NIL_RTPIPE ? "closed" : "open", + *phStdErrR == NIL_RTPIPE ? "closed" : "open"); +#endif + if (RT_UNLIKELY(pProcess->fShutdown)) + break; /* We were asked to shutdown. */ + + /* + * Check for process death. + */ + if (fProcessAlive) + { + rc2 = RTProcWaitNoResume(pProcess->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus); + if (RT_SUCCESS_NP(rc2)) + { + fProcessAlive = false; + /* Note: Don't bail out here yet. First check in the next block below + * if all needed pipe outputs have been consumed. */ + } + else + { + if (RT_UNLIKELY(rc2 == VERR_INTERRUPTED)) + continue; + if (RT_UNLIKELY(rc2 == VERR_PROCESS_NOT_FOUND)) + { + fProcessAlive = false; + ProcessStatus.enmReason = RTPROCEXITREASON_ABEND; + ProcessStatus.iStatus = 255; + AssertFailed(); + } + else + AssertMsg(rc2 == VERR_PROCESS_RUNNING, ("%Rrc\n", rc2)); + } + } + + /* + * If the process has terminated and all output has been consumed, + * we should be heading out. + */ + if (!fProcessAlive) + { + if ( fProcessTimedOut + || ( pProcess->hPipeStdOutR == NIL_RTPIPE + && pProcess->hPipeStdErrR == NIL_RTPIPE) + ) + { + VGSvcVerbose(3, "[PID %RU32]: RTProcWaitNoResume=%Rrc\n", pProcess->uPID, rc2); + break; + } + } + + /* + * Check for timed out, killing the process. + */ + uint32_t cMilliesLeft = RT_INDEFINITE_WAIT; + if ( pProcess->pStartupInfo->uTimeLimitMS != RT_INDEFINITE_WAIT + && pProcess->pStartupInfo->uTimeLimitMS != 0) + { + uint64_t u64Now = RTTimeMilliTS(); + uint64_t cMsElapsed = u64Now - uMsStart; + if (cMsElapsed >= pProcess->pStartupInfo->uTimeLimitMS) + { + fProcessTimedOut = true; + if ( MsProcessKilled == UINT64_MAX + || u64Now - MsProcessKilled > 1000) + { + if (u64Now - MsProcessKilled > 20*60*1000) + break; /* Give up after 20 mins. */ + + VGSvcVerbose(3, "[PID %RU32]: Timed out (%RU64ms elapsed > %RU32ms timeout), killing ...\n", + pProcess->uPID, cMsElapsed, pProcess->pStartupInfo->uTimeLimitMS); + + rc2 = RTProcTerminate(pProcess->hProcess); + VGSvcVerbose(3, "[PID %RU32]: Killing process resulted in rc=%Rrc\n", + pProcess->uPID, rc2); + MsProcessKilled = u64Now; + continue; + } + cMilliesLeft = 10000; + } + else + cMilliesLeft = pProcess->pStartupInfo->uTimeLimitMS - (uint32_t)cMsElapsed; + } + + /* Reset the polling interval since we've done all pending work. */ + cMsPollCur = fProcessAlive + ? cMsPollBase + : RT_MS_1MIN; + if (cMilliesLeft < cMsPollCur) + cMsPollCur = cMilliesLeft; + } + + VGSvcVerbose(3, "[PID %RU32]: Loop ended: rc=%Rrc, fShutdown=%RTbool, fProcessAlive=%RTbool, fProcessTimedOut=%RTbool, MsProcessKilled=%RU64 (%RX64)\n", + pProcess->uPID, rc, pProcess->fShutdown, fProcessAlive, fProcessTimedOut, MsProcessKilled, MsProcessKilled); + VGSvcVerbose(3, "[PID %RU32]: *phStdOutR=%s, *phStdErrR=%s\n", + pProcess->uPID, + pProcess->hPipeStdOutR == NIL_RTPIPE ? "closed" : "open", + pProcess->hPipeStdErrR == NIL_RTPIPE ? "closed" : "open"); + + /* Signal that this thread is in progress of shutting down. */ + ASMAtomicWriteBool(&pProcess->fShutdown, true); + + /* + * Try killing the process if it's still alive at this point. + */ + if (fProcessAlive) + { + if (MsProcessKilled == UINT64_MAX) + { + VGSvcVerbose(2, "[PID %RU32]: Is still alive and not killed yet\n", pProcess->uPID); + + MsProcessKilled = RTTimeMilliTS(); + rc2 = RTProcTerminate(pProcess->hProcess); + if (rc2 == VERR_NOT_FOUND) + { + fProcessAlive = false; + } + else if (RT_FAILURE(rc2)) + VGSvcError("[PID %RU32]: Killing process failed with rc=%Rrc\n", pProcess->uPID, rc2); + RTThreadSleep(500); + } + + for (int i = 0; i < 10 && fProcessAlive; i++) + { + VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Waiting to exit ...\n", pProcess->uPID, i + 1); + rc2 = RTProcWait(pProcess->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus); + if (RT_SUCCESS(rc2)) + { + VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Exited\n", pProcess->uPID, i + 1); + fProcessAlive = false; + break; + } + if (i >= 5) + { + VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Trying to terminate ...\n", pProcess->uPID, i + 1); + rc2 = RTProcTerminate(pProcess->hProcess); + if ( RT_FAILURE(rc) + && rc2 != VERR_NOT_FOUND) + VGSvcError("PID %RU32]: Killing process failed with rc=%Rrc\n", + pProcess->uPID, rc2); + } + RTThreadSleep(i >= 5 ? 2000 : 500); + } + + if (fProcessAlive) + VGSvcError("[PID %RU32]: Could not be killed\n", pProcess->uPID); + } + + /* + * Shutdown procedure: + * - Set the pProcess->fShutdown indicator to let others know we're + * not accepting any new requests anymore. + * - After setting the indicator, try to process all outstanding + * requests to make sure they're getting delivered. + * + * Note: After removing the process from the session's list it's not + * even possible for the session anymore to control what's + * happening to this thread, so be careful and don't mess it up. + */ + + rc2 = vgsvcGstCtrlProcessLock(pProcess); + if (RT_SUCCESS(rc2)) + { + VGSvcVerbose(3, "[PID %RU32]: Processing outstanding requests ...\n", pProcess->uPID); + + /* Process all pending requests (but don't wait for new ones). */ + Assert(pProcess->hReqQueue != NIL_RTREQQUEUE); + rc2 = RTReqQueueProcess(pProcess->hReqQueue, 0 /* No timeout */); + if ( RT_FAILURE(rc2) + && rc2 != VERR_TIMEOUT) + VGSvcError("[PID %RU32]: Processing outstanding requests failed with with rc=%Rrc\n", pProcess->uPID, rc2); + + VGSvcVerbose(3, "[PID %RU32]: Processing outstanding requests done, rc=%Rrc\n", pProcess->uPID, rc2); + + rc2 = vgsvcGstCtrlProcessUnlock(pProcess); + AssertRC(rc2); + } + + /* + * If we don't have a client problem (RT_FAILURE(rc)) we'll reply to the + * clients exec packet now. + */ + if (RT_SUCCESS(rc)) + { + uint32_t uStatus = PROC_STS_UNDEFINED; + uint32_t fFlags = 0; + + if ( fProcessTimedOut && !fProcessAlive && MsProcessKilled != UINT64_MAX) + { + VGSvcVerbose(3, "[PID %RU32]: Timed out and got killed\n", pProcess->uPID); + uStatus = PROC_STS_TOK; + } + else if (fProcessTimedOut && fProcessAlive && MsProcessKilled != UINT64_MAX) + { + VGSvcVerbose(3, "[PID %RU32]: Timed out and did *not* get killed\n", pProcess->uPID); + uStatus = PROC_STS_TOA; + } + else if (pProcess->fShutdown && (fProcessAlive || MsProcessKilled != UINT64_MAX)) + { + VGSvcVerbose(3, "[PID %RU32]: Got terminated because system/service is about to shutdown\n", pProcess->uPID); + uStatus = PROC_STS_DWN; /* Service is stopping, process was killed. */ + fFlags = pProcess->pStartupInfo->fFlags; /* Return handed-in execution flags back to the host. */ + } + else if (fProcessAlive) + VGSvcError("[PID %RU32]: Is alive when it should not!\n", pProcess->uPID); + else if (MsProcessKilled != UINT64_MAX) + VGSvcError("[PID %RU32]: Has been killed when it should not!\n", pProcess->uPID); + else if (ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL) + { + VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_NORMAL (Exit code: %d)\n", + pProcess->uPID, ProcessStatus.iStatus); + uStatus = PROC_STS_TEN; + fFlags = ProcessStatus.iStatus; + } + else if (ProcessStatus.enmReason == RTPROCEXITREASON_SIGNAL) + { + VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_SIGNAL (Signal: %u)\n", + pProcess->uPID, ProcessStatus.iStatus); + uStatus = PROC_STS_TES; + fFlags = ProcessStatus.iStatus; + } + else if (ProcessStatus.enmReason == RTPROCEXITREASON_ABEND) + { + /* ProcessStatus.iStatus will be undefined. */ + VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_ABEND\n", pProcess->uPID); + uStatus = PROC_STS_TEA; + fFlags = ProcessStatus.iStatus; + } + else + VGSvcVerbose(1, "[PID %RU32]: Handling process status %u not implemented\n", pProcess->uPID, ProcessStatus.enmReason); + VBGLR3GUESTCTRLCMDCTX ctxEnd = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ }; + VGSvcVerbose(2, "[PID %RU32]: Ended, ClientID=%u, CID=%u, Status=%u, Flags=0x%x\n", + pProcess->uPID, ctxEnd.uClientID, pProcess->uContextID, uStatus, fFlags); + + rc2 = VbglR3GuestCtrlProcCbStatus(&ctxEnd, pProcess->uPID, uStatus, fFlags, NULL /* pvData */, 0 /* cbData */); + if ( RT_FAILURE(rc2) + && rc2 == VERR_NOT_FOUND) + VGSvcError("[PID %RU32]: Error reporting final status to host; rc=%Rrc\n", pProcess->uPID, rc2); + } + + VGSvcVerbose(3, "[PID %RU32]: Process loop returned with rc=%Rrc\n", pProcess->uPID, rc); + return rc; +} + + +#if 0 /* unused */ +/** + * Initializes a pipe's handle and pipe object. + * + * @return IPRT status code. + * @param ph The pipe's handle to initialize. + * @param phPipe The pipe's object to initialize. + */ +static int vgsvcGstCtrlProcessInitPipe(PRTHANDLE ph, PRTPIPE phPipe) +{ + AssertPtrReturn(ph, VERR_INVALID_PARAMETER); + AssertPtrReturn(phPipe, VERR_INVALID_PARAMETER); + + ph->enmType = RTHANDLETYPE_PIPE; + ph->u.hPipe = NIL_RTPIPE; + *phPipe = NIL_RTPIPE; + + return VINF_SUCCESS; +} +#endif + + +/** + * Sets up the redirection / pipe / nothing for one of the standard handles. + * + * @returns IPRT status code. No client replies made. + * @param pszHowTo How to set up this standard handle. + * @param fd Which standard handle it is (0 == stdin, 1 == + * stdout, 2 == stderr). + * @param ph The generic handle that @a pph may be set + * pointing to. Always set. + * @param pph Pointer to the RTProcCreateExec argument. + * Always set. + * @param phPipe Where to return the end of the pipe that we + * should service. + */ +static int vgsvcGstCtrlProcessSetupPipe(const char *pszHowTo, int fd, PRTHANDLE ph, PRTHANDLE *pph, PRTPIPE phPipe) +{ + AssertPtrReturn(ph, VERR_INVALID_POINTER); + AssertPtrReturn(pph, VERR_INVALID_POINTER); + AssertPtrReturn(phPipe, VERR_INVALID_POINTER); + + int rc; + + ph->enmType = RTHANDLETYPE_PIPE; + ph->u.hPipe = NIL_RTPIPE; + *pph = NULL; + *phPipe = NIL_RTPIPE; + + if (!strcmp(pszHowTo, "|")) + { + /* + * Setup a pipe for forwarding to/from the client. + * The ph union struct will be filled with a pipe read/write handle + * to represent the "other" end to phPipe. + */ + if (fd == 0) /* stdin? */ + { + /* Connect a wrtie pipe specified by phPipe to stdin. */ + rc = RTPipeCreate(&ph->u.hPipe, phPipe, RTPIPE_C_INHERIT_READ); + } + else /* stdout or stderr. */ + { + /* Connect a read pipe specified by phPipe to stdout or stderr. */ + rc = RTPipeCreate(phPipe, &ph->u.hPipe, RTPIPE_C_INHERIT_WRITE); + } + + if (RT_FAILURE(rc)) + return rc; + + ph->enmType = RTHANDLETYPE_PIPE; + *pph = ph; + } + else if (!strcmp(pszHowTo, "/dev/null")) + { + /* + * Redirect to/from /dev/null. + */ + RTFILE hFile; + rc = RTFileOpenBitBucket(&hFile, fd == 0 ? RTFILE_O_READ : RTFILE_O_WRITE); + if (RT_FAILURE(rc)) + return rc; + + ph->enmType = RTHANDLETYPE_FILE; + ph->u.hFile = hFile; + *pph = ph; + } + else /* Add other piping stuff here. */ + rc = VINF_SUCCESS; /* Same as parent (us). */ + + return rc; +} + + +/** + * Expands a file name / path to its real content. + * + * ~~This only works on Windows for now (e.g. translating "%TEMP%\foo.exe" to + * "C:\Windows\Temp" when starting with system / administrative rights).~~ See + * todo in code. + * + * @return IPRT status code. + * @param pszPath Path to resolve. + * @param pszExpanded Pointer to string to store the resolved path in. + * @param cbExpanded Size (in bytes) of string to store the resolved path. + */ +static int vgsvcGstCtrlProcessMakeFullPath(const char *pszPath, char *pszExpanded, size_t cbExpanded) +{ +/** @todo r=bird: This feature shall be made optional, i.e. require a + * flag to be passed down. Further, it shall work on the environment + * block of the new process (i.e. include env changes passed down from + * the caller). I would also suggest using the unix variable expansion + * syntax, not the DOS one. + * + * Since this currently not available on non-windows guests, I suggest + * we disable it until such a time as it is implemented correctly. */ +#if 0 /*def RT_OS_WINDOWS - see above. Don't know why this wasn't disabled before 7.0, didn't see the @todo yet? */ + int rc = VINF_SUCCESS; + if (!ExpandEnvironmentStrings(pszPath, pszExpanded, (DWORD)cbExpanded)) + rc = RTErrConvertFromWin32(GetLastError()); +#else + /* There is no expansion anywhere yet, see above @todo. */ + int rc = RTStrCopy(pszExpanded, cbExpanded, pszPath); +#endif +#ifdef DEBUG + VGSvcVerbose(3, "vgsvcGstCtrlProcessMakeFullPath: %s -> %s\n", pszPath, pszExpanded); +#endif + return rc; +} + + +/** + * Resolves the full path of a specified executable name. + * + * This function also resolves internal VBoxService tools to its appropriate + * executable path + name if VBOXSERVICE_NAME is specified as pszFilename. + * + * @return IPRT status code. + * @param pszFilename File name to resolve. + * @param pszResolved Pointer to a string where the resolved file name will be stored. + * @param cbResolved Size (in bytes) of resolved file name string. + */ +static int vgsvcGstCtrlProcessResolveExecutable(const char *pszFilename, char *pszResolved, size_t cbResolved) +{ + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertPtrReturn(pszResolved, VERR_INVALID_POINTER); + AssertReturn(cbResolved, VERR_INVALID_PARAMETER); + + const char * const pszOrgFilename = pszFilename; + if ( RTStrICmp(pszFilename, g_pszProgName) == 0 + || RTStrICmp(pszFilename, VBOXSERVICE_NAME) == 0) + pszFilename = RTProcExecutablePath(); + + int rc = vgsvcGstCtrlProcessMakeFullPath(pszFilename, pszResolved, cbResolved); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Looked up executable: %s -> %s\n", pszOrgFilename, pszResolved); + return rc; +} + + +/** + * Constructs the argv command line by resolving environment variables + * and relative paths. + * + * @return IPRT status code. + * @param pszArgv0 First argument (argv0), either original or modified version. + * @param papszArgs Original argv command line from the host, starting at argv[1]. + * @param fFlags The process creation flags pass to us from the host. + * @param fExecutingSelf Set if we're executing the VBoxService executable + * and should inject the --utf8-argv trick. + * @param ppapszArgv Pointer to a pointer with the new argv command line. + * Needs to be freed with RTGetOptArgvFree. + */ +static int vgsvcGstCtrlProcessAllocateArgv(const char *pszArgv0, const char * const *papszArgs, uint32_t fFlags, + bool fExecutingSelf, char ***ppapszArgv) +{ + VGSvcVerbose(3, "VGSvcGstCtrlProcessPrepareArgv: pszArgv0=%p, papszArgs=%p, fFlags=%#x, fExecutingSelf=%d, ppapszArgv=%p\n", + pszArgv0, papszArgs, fFlags, fExecutingSelf, ppapszArgv); + + AssertPtrReturn(pszArgv0, VERR_INVALID_POINTER); + AssertPtrReturn(ppapszArgv, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & GUEST_PROC_CREATE_FLAG_EXPAND_ARGUMENTS), VERR_INVALID_FLAGS); /** @todo implement me */ + +#ifndef VBOXSERVICE_ARG1_UTF8_ARGV + fExecutingSelf = false; +#endif + + /* Count arguments: */ + int rc = VINF_SUCCESS; + uint32_t cArgs; + for (cArgs = 0; papszArgs[cArgs]; cArgs++) + { + if (cArgs >= UINT32_MAX - 2) + return VERR_BUFFER_OVERFLOW; + } + + /* Allocate new argv vector (adding + 2 for argv0 + termination). */ + size_t cbSize = (fExecutingSelf + cArgs + 2) * sizeof(char *); + char **papszNewArgv = (char **)RTMemAlloc(cbSize); + if (!papszNewArgv) + return VERR_NO_MEMORY; + + VGSvcVerbose(3, "VGSvcGstCtrlProcessAllocateArgv: pszArgv0 = '%s', cArgs=%RU32, cbSize=%zu\n", pszArgv0, cArgs, cbSize); +#ifdef DEBUG /* Never log this stuff in release mode! */ + if (cArgs) + { + for (uint32_t i = 0; i < cArgs; i++) + VGSvcVerbose(3, "VGSvcGstCtrlProcessAllocateArgv: papszArgs[%RU32] = '%s'\n", i, papszArgs[i]); + } +#endif + + /* HACK ALERT! Older hosts (< VBox 6.1.x) did not allow the user to really specify + the first argument separately from the executable image, so we have + to fudge a little in the unquoted argument case to deal with executables + containing spaces. Windows only, as RTPROC_FLAGS_UNQUOTED_ARGS is + ignored on all other hosts. */ +#ifdef RT_OS_WINDOWS + if ( (fFlags & GUEST_PROC_CREATE_FLAG_UNQUOTED_ARGS) + && strpbrk(pszArgv0, " \t\n\r") + && pszArgv0[0] == '"') + { + size_t cchArgv0 = strlen(pszArgv0); + AssertReturn(cchArgv0, VERR_INVALID_PARAMETER); /* Paranoia. */ + rc = RTStrAllocEx(&papszNewArgv[0], 1 + cchArgv0 + 1 + 1); + if (RT_SUCCESS(rc)) + { + char *pszDst = papszNewArgv[0]; + *pszDst++ = '"'; + memcpy(pszDst, pszArgv0, cchArgv0); + pszDst += cchArgv0; + *pszDst++ = '"'; + *pszDst = '\0'; + } + } + else +#endif + rc = RTStrDupEx(&papszNewArgv[0], pszArgv0); + if (RT_SUCCESS(rc)) + { + size_t iDst = 1; + +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV + /* Insert --utf8-argv as the first argument if executing the VBoxService binary. */ + if (fExecutingSelf) + { + rc = RTStrDupEx(&papszNewArgv[iDst], VBOXSERVICE_ARG1_UTF8_ARGV); + if (RT_SUCCESS(rc)) + iDst++; + } +#endif + /* Copy over the other arguments. */ + if (RT_SUCCESS(rc)) + for (size_t iSrc = 0; iSrc < cArgs; iSrc++) + { +#if 0 /* Arguments expansion -- untested. */ + if (fFlags & GUEST_PROC_CREATE_FLAG_EXPAND_ARGUMENTS) + { +/** @todo r=bird: If you want this, we need a generic implementation, preferably in RTEnv or somewhere like that. The marking + * up of the variables must be the same on all platforms. */ + /* According to MSDN the limit on older Windows version is 32K, whereas + * Vista+ there are no limits anymore. We still stick to 4K. */ + char szExpanded[_4K]; +# ifdef RT_OS_WINDOWS + if (!ExpandEnvironmentStrings(papszArgs[i], szExpanded, sizeof(szExpanded))) + rc = RTErrConvertFromWin32(GetLastError()); +# else + /* No expansion for non-Windows yet. */ + rc = RTStrCopy(papszArgs[i], sizeof(szExpanded), szExpanded); +# endif + if (RT_SUCCESS(rc)) + rc = RTStrDupEx(&pszArg, szExpanded); + } + else +#endif + rc = RTStrDupEx(&papszNewArgv[iDst], papszArgs[iSrc]); + if (RT_SUCCESS(rc)) + iDst++; + else + break; + } + + if (RT_SUCCESS(rc)) + { + /* Terminate array. */ + papszNewArgv[iDst] = NULL; + + *ppapszArgv = papszNewArgv; + return VINF_SUCCESS; + } + + /* Failed, bail out. */ + while (iDst-- > 0) + RTStrFree(papszNewArgv[iDst]); + } + RTMemFree(papszNewArgv); + return rc; +} + + +/** + * Assigns a valid PID to a guest control thread and also checks if there already was + * another (stale) guest process which was using that PID before and destroys it. + * + * @return IPRT status code. + * @param pProcess Process to assign PID to. + * @param uPID PID to assign to the specified guest control execution thread. + */ +static int vgsvcGstCtrlProcessAssignPID(PVBOXSERVICECTRLPROCESS pProcess, uint32_t uPID) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + AssertReturn(uPID, VERR_INVALID_PARAMETER); + + AssertPtr(pProcess->pSession); + int rc = RTCritSectEnter(&pProcess->pSession->CritSect); + if (RT_SUCCESS(rc)) + { + /* Search old threads using the desired PID and shut them down completely -- it's + * not used anymore. */ + bool fTryAgain; + do + { + fTryAgain = false; + PVBOXSERVICECTRLPROCESS pProcessCur; + RTListForEach(&pProcess->pSession->lstProcesses, pProcessCur, VBOXSERVICECTRLPROCESS, Node) + { + if (pProcessCur->uPID == uPID) + { + Assert(pProcessCur != pProcess); /* can't happen */ + uint32_t uTriedPID = uPID; + uPID += 391939; + VGSvcVerbose(2, "PID %RU32 was used before (process %p), trying again with %RU32 ...\n", + uTriedPID, pProcessCur, uPID); + fTryAgain = true; + break; + } + } + } while (fTryAgain); + + /* Assign PID to current thread. */ + pProcess->uPID = uPID; + + rc = RTCritSectLeave(&pProcess->pSession->CritSect); + AssertRC(rc); + } + + return rc; +} + + +static void vgsvcGstCtrlProcessFreeArgv(char **papszArgv) +{ + if (papszArgv) + { + size_t i = 0; + while (papszArgv[i]) + RTStrFree(papszArgv[i++]); + RTMemFree(papszArgv); + } +} + + +/** + * Helper function to create/start a process on the guest. + * + * @return IPRT status code. + * @param pszExec Full qualified path of process to start (without arguments). + * @param papszArgs Pointer to array of command line arguments. + * @param hEnv Handle to environment block to use. + * @param fFlags Process execution flags. + * @param phStdIn Handle for the process' stdin pipe. + * @param phStdOut Handle for the process' stdout pipe. + * @param phStdErr Handle for the process' stderr pipe. + * @param pszAsUser User name (account) to start the process under. + * @param pszPassword Password of the specified user. + * @param pszDomain Domain to use for authentication. + * @param phProcess Pointer which will receive the process handle after + * successful process start. + */ +static int vgsvcGstCtrlProcessCreateProcess(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags, + PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, + const char *pszAsUser, const char *pszPassword, const char *pszDomain, + PRTPROCESS phProcess) +{ +#ifndef RT_OS_WINDOWS + RT_NOREF1(pszDomain); +#endif + AssertPtrReturn(pszExec, VERR_INVALID_PARAMETER); + AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER); + /* phStdIn is optional. */ + /* phStdOut is optional. */ + /* phStdErr is optional. */ + /* pszPassword is optional. */ + /* pszDomain is optional. */ + AssertPtrReturn(phProcess, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + char szExecExp[RTPATH_MAX]; + +#ifdef DEBUG + /* Never log this in release mode! */ + VGSvcVerbose(4, "pszUser=%s, pszPassword=%s, pszDomain=%s\n", pszAsUser, pszPassword, pszDomain); +#endif + +#ifdef RT_OS_WINDOWS + /* + * If sysprep should be executed do this in the context of VBoxService, which + * (usually, if started by SCM) has administrator rights. Because of that a UI + * won't be shown (doesn't have a desktop). + */ + if (!RTStrICmp(pszExec, "sysprep")) + { + /* Use a predefined sysprep path as default. */ + char szSysprepCmd[RTPATH_MAX] = "C:\\sysprep\\sysprep.exe"; + /** @todo Check digital signature of file above before executing it? */ + + /* + * On Windows Vista (and up) sysprep is located in "system32\\Sysprep\\sysprep.exe", + * so detect the OS and use a different path. + */ + if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6,0,0) /* Vista and later */) + { + rc = RTEnvGetEx(RTENV_DEFAULT, "windir", szSysprepCmd, sizeof(szSysprepCmd), NULL); +#ifndef RT_ARCH_AMD64 + /* Don't execute 64-bit sysprep from a 32-bit service host! */ + char szSysWow64[RTPATH_MAX]; + if (RTStrPrintf(szSysWow64, sizeof(szSysWow64), "%s", szSysprepCmd)) + { + rc = RTPathAppend(szSysWow64, sizeof(szSysWow64), "SysWow64"); + AssertRC(rc); + } + if ( RT_SUCCESS(rc) + && RTPathExists(szSysWow64)) + VGSvcVerbose(0, "Warning: This service is 32-bit; could not execute sysprep on 64-bit OS!\n"); +#endif + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szSysprepCmd, sizeof(szSysprepCmd), "system32\\Sysprep\\sysprep.exe"); + if (RT_SUCCESS(rc)) + RTPathChangeToDosSlashes(szSysprepCmd, false /* No forcing necessary */); + + if (RT_FAILURE(rc)) + VGSvcError("Failed to detect sysrep location, rc=%Rrc\n", rc); + } + + VGSvcVerbose(3, "Sysprep executable is: %s\n", szSysprepCmd); + + if (RT_SUCCESS(rc)) + { + char **papszArgsExp; + rc = vgsvcGstCtrlProcessAllocateArgv(szSysprepCmd /* argv0 */, papszArgs, fFlags, + false /*fExecutingSelf*/, &papszArgsExp); + if (RT_SUCCESS(rc)) + { + /* As we don't specify credentials for the sysprep process, it will + * run under behalf of the account VBoxService was started under, most + * likely local system. */ + rc = RTProcCreateEx(szSysprepCmd, papszArgsExp, hEnv, 0 /* fFlags */, + phStdIn, phStdOut, phStdErr, NULL /* pszAsUser */, + NULL /* pszPassword */, NULL, phProcess); + vgsvcGstCtrlProcessFreeArgv(papszArgsExp); + } + } + + if (RT_FAILURE(rc)) + VGSvcVerbose(3, "Starting sysprep returned rc=%Rrc\n", rc); + + return rc; + } +#endif /* RT_OS_WINDOWS */ + + bool fExecutingSelf = false; +#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX + /* The "vbox_" prefix is reserved for the toolbox (vbox_cat, vbox_mkdir, + et al.) and we will replace pszExec with the full VBoxService path instead. */ + if (RTStrStartsWith(pszExec, "vbox_")) + { + fExecutingSelf = true; + rc = vgsvcGstCtrlProcessResolveExecutable(VBOXSERVICE_NAME, szExecExp, sizeof(szExecExp)); + } + else + { +#endif + /* + * Do the environment variables expansion on executable and arguments. + */ + rc = vgsvcGstCtrlProcessResolveExecutable(pszExec, szExecExp, sizeof(szExecExp)); +#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX + } +#endif + if (RT_SUCCESS(rc)) + { + /* + * This one is a bit tricky to also support older hosts: + * + * - If the host does not provide a dedicated argv[0] (< VBox 6.1.x), we use the + * unmodified executable name (pszExec) as the (default) argv[0]. This is wrong, but we can't do + * much about it. The rest (argv[1,2,n]) then gets set starting at papszArgs[0]. + * + * - Newer hosts (>= VBox 6.1.x) provide a correct argv[0] independently of the actual + * executable name though, so actually use argv[0] *and* argv[1,2,n] as intended. + */ + const bool fHasArgv0 = RT_BOOL(g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_PROCESS_ARGV0); + + const char *pcszArgv0 = (fHasArgv0 && papszArgs[0]) ? papszArgs[0] : pszExec; + AssertPtrReturn(pcszArgv0, VERR_INVALID_POINTER); /* Paranoia. */ + + const uint32_t uArgvIdx = pcszArgv0 == papszArgs[0] ? 1 : 0; + + VGSvcVerbose(3, "vgsvcGstCtrlProcessCreateProcess: fHasArgv0=%RTbool, pcszArgv0=%p, uArgvIdx=%RU32, " + "g_fControlHostFeatures0=%#x\n", + fHasArgv0, pcszArgv0, uArgvIdx, g_fControlHostFeatures0); + + char **papszArgsExp; + rc = vgsvcGstCtrlProcessAllocateArgv(pcszArgv0, &papszArgs[uArgvIdx], fFlags, fExecutingSelf, &papszArgsExp); + if (RT_FAILURE(rc)) + { + /* Don't print any arguments -- may contain passwords or other sensible data! */ + VGSvcError("Could not prepare arguments, rc=%Rrc\n", rc); + } + else + { + uint32_t fProcCreateFlags = 0; + if (fExecutingSelf) + fProcCreateFlags |= VBOXSERVICE_PROC_F_UTF8_ARGV; + if (fFlags) + { + if (fFlags & GUEST_PROC_CREATE_FLAG_HIDDEN) + fProcCreateFlags |= RTPROC_FLAGS_HIDDEN; + if (fFlags & GUEST_PROC_CREATE_FLAG_PROFILE) + fProcCreateFlags |= RTPROC_FLAGS_PROFILE; + if (fFlags & GUEST_PROC_CREATE_FLAG_UNQUOTED_ARGS) + fProcCreateFlags |= RTPROC_FLAGS_UNQUOTED_ARGS; + } + + /* If no user name specified run with current credentials (e.g. + * full service/system rights). This is prohibited via official Main API! + * + * Otherwise use the RTPROC_FLAGS_SERVICE to use some special authentication + * code (at least on Windows) for running processes as different users + * started from our system service. */ + if (pszAsUser && *pszAsUser) + fProcCreateFlags |= RTPROC_FLAGS_SERVICE; +#ifdef DEBUG + VGSvcVerbose(3, "Command: %s\n", szExecExp); + for (size_t i = 0; papszArgsExp[i]; i++) + VGSvcVerbose(3, " argv[%zu]: %s\n", i, papszArgsExp[i]); +#endif + VGSvcVerbose(3, "Starting process '%s' ...\n", szExecExp); + +#ifdef RT_OS_WINDOWS + /* If a domain name is given, construct an UPN (User Principle Name) with + * the domain name built-in, e.g. "joedoe@example.com". */ + char *pszUserUPN = NULL; + if (pszDomain && *pszDomain != '\0') + { + pszAsUser = pszUserUPN = RTStrAPrintf2("%s@%s", pszAsUser, pszDomain); + if (pszAsUser) + VGSvcVerbose(3, "Using UPN: %s\n", pszAsUser); + else + rc = VERR_NO_STR_MEMORY; + } + if (RT_SUCCESS(rc)) +#endif + { + /* Do normal execution. */ + rc = RTProcCreateEx(szExecExp, papszArgsExp, hEnv, fProcCreateFlags, + phStdIn, phStdOut, phStdErr, + pszAsUser, + pszPassword && *pszPassword ? pszPassword : NULL, + NULL /*pvExtraData*/, + phProcess); + +#ifdef RT_OS_WINDOWS + RTStrFree(pszUserUPN); +#endif + VGSvcVerbose(3, "Starting process '%s' returned rc=%Rrc\n", szExecExp, rc); + } + vgsvcGstCtrlProcessFreeArgv(papszArgsExp); + } + } + return rc; +} + + +#ifdef DEBUG +/** + * Dumps content to a file in the OS temporary directory. + * + * @returns VBox status code. + * @param pvBuf Buffer of content to dump. + * @param cbBuf Size (in bytes) of content to dump. + * @param pszFileNmFmt Pointer to the file name format string, @see pg_rt_str_format. + * @param ... The format argument. + */ +static int vgsvcGstCtrlProcessDbgDumpToFileF(const void *pvBuf, size_t cbBuf, const char *pszFileNmFmt, ...) +{ + AssertPtrReturn(pszFileNmFmt, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + + if (!cbBuf) + return VINF_SUCCESS; + + va_list va; + va_start(va, pszFileNmFmt); + + char *pszFileName = NULL; + const int cchFileName = RTStrAPrintfV(&pszFileName, pszFileNmFmt, va); + + va_end(va); + + if (!cchFileName) + return VERR_NO_MEMORY; + + char szPathFileAbs[RTPATH_MAX]; + int rc = RTPathTemp(szPathFileAbs, sizeof(szPathFileAbs)); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szPathFileAbs, sizeof(szPathFileAbs), pszFileName); + + RTStrFree(pszFileName); + + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "Dumping %zu bytes to '%s'\n", cbBuf, szPathFileAbs); + + RTFILE fh; + rc = RTFileOpen(&fh, szPathFileAbs, RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(fh, pvBuf, cbBuf, NULL /* pcbWritten */); + RTFileClose(fh); + } + } + + return rc; +} +#endif /* DEBUG */ + + +/** + * The actual worker routine (loop) for a started guest process. + * + * @return IPRT status code. + * @param pProcess The process we're servicing and monitoring. + */ +static int vgsvcGstCtrlProcessProcessWorker(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + VGSvcVerbose(3, "Thread of process pThread=0x%p = '%s' started\n", pProcess, pProcess->pStartupInfo->pszCmd); + + VGSvcVerbose(3, "Guest process '%s', flags=0x%x\n", pProcess->pStartupInfo->pszCmd, pProcess->pStartupInfo->fFlags); + + int rc = VGSvcGstCtrlSessionProcessAdd(pProcess->pSession, pProcess); + if (RT_FAILURE(rc)) + { + VGSvcError("Error while adding guest process '%s' (%p) to session process list, rc=%Rrc\n", + pProcess->pStartupInfo->pszCmd, pProcess, rc); + RTThreadUserSignal(RTThreadSelf()); + return rc; + } + + bool fSignalled = false; /* Indicator whether we signalled the thread user event already. */ + + /* + * Prepare argument list. + */ + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: fHostFeatures0 = %#x\n", g_fControlHostFeatures0); + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.szCmd = '%s'\n", pProcess->pStartupInfo->pszCmd); + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.uNumArgs = '%RU32'\n", pProcess->pStartupInfo->cArgs); +#ifdef DEBUG /* Never log this stuff in release mode! */ + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.szArgs = '%s'\n", pProcess->pStartupInfo->pszArgs); +#endif + + char **papszArgs; + int cArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */ + rc = RTGetOptArgvFromString(&papszArgs, &cArgs, + pProcess->pStartupInfo->cArgs > 0 ? pProcess->pStartupInfo->pszArgs : "", + RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL); + + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: cArgs = %d\n", cArgs); +#ifdef VBOX_STRICT + for (int i = 0; i < cArgs; i++) + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: papszArgs[%d] = '%s'\n", i, papszArgs[i] ? papszArgs[i] : "<NULL>"); + + const bool fHasArgv0 = RT_BOOL(g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_PROCESS_ARGV0); RT_NOREF(fHasArgv0); + const int cArgsToCheck = cArgs + (fHasArgv0 ? 0 : 1); + + /* Did we get the same result? + * Take into account that we might not have supplied a (correct) argv[0] from the host. */ + AssertMsg((int)pProcess->pStartupInfo->cArgs == cArgsToCheck, + ("rc=%Rrc, StartupInfo.uNumArgs=%RU32 != cArgsToCheck=%d, cArgs=%d, fHostFeatures0=%#x\n", + rc, pProcess->pStartupInfo->cArgs, cArgsToCheck, cArgs, g_fControlHostFeatures0)); +#endif + + /* + * Create the environment. + */ + uint32_t const cbEnv = pProcess->pStartupInfo->cbEnv; + if (RT_SUCCESS(rc)) + AssertStmt( cbEnv <= GUEST_PROC_MAX_ENV_LEN + || pProcess->pStartupInfo->cEnvVars == 0, + rc = VERR_INVALID_PARAMETER); + if (RT_SUCCESS(rc)) + { + RTENV hEnv; + rc = RTEnvClone(&hEnv, RTENV_DEFAULT); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "Additional environment variables: %RU32 (%RU32 bytes)\n", + pProcess->pStartupInfo->cEnvVars, cbEnv); + + if ( pProcess->pStartupInfo->cEnvVars + && cbEnv > 0) + { + size_t offCur = 0; + while (offCur < cbEnv) + { + const char * const pszCur = &pProcess->pStartupInfo->pszEnv[offCur]; + size_t const cchCur = RTStrNLen(pszCur, cbEnv - offCur); + AssertBreakStmt(cchCur < cbEnv - offCur, rc = VERR_INVALID_PARAMETER); + VGSvcVerbose(3, "Setting environment variable: '%s'\n", pszCur); + rc = RTEnvPutEx(hEnv, pszCur); + if (RT_SUCCESS(rc)) + offCur += cchCur + 1; + else + { + VGSvcError("Setting environment variable '%s' failed: %Rrc\n", pszCur, rc); + break; + } + } + } + + if (RT_SUCCESS(rc)) + { + /* + * Setup the redirection of the standard stuff. + */ + /** @todo consider supporting: gcc stuff.c >file 2>&1. */ + RTHANDLE hStdIn; + PRTHANDLE phStdIn; + rc = vgsvcGstCtrlProcessSetupPipe("|", 0 /*STDIN_FILENO*/, + &hStdIn, &phStdIn, &pProcess->hPipeStdInW); + if (RT_SUCCESS(rc)) + { + RTHANDLE hStdOut; + PRTHANDLE phStdOut; + rc = vgsvcGstCtrlProcessSetupPipe( (pProcess->pStartupInfo->fFlags & GUEST_PROC_CREATE_FLAG_WAIT_STDOUT) + ? "|" : "/dev/null", + 1 /*STDOUT_FILENO*/, + &hStdOut, &phStdOut, &pProcess->hPipeStdOutR); + if (RT_SUCCESS(rc)) + { + RTHANDLE hStdErr; + PRTHANDLE phStdErr; + rc = vgsvcGstCtrlProcessSetupPipe( (pProcess->pStartupInfo->fFlags & GUEST_PROC_CREATE_FLAG_WAIT_STDERR) + ? "|" : "/dev/null", + 2 /*STDERR_FILENO*/, + &hStdErr, &phStdErr, &pProcess->hPipeStdErrR); + if (RT_SUCCESS(rc)) + { + /* + * Create a poll set for the pipes and let the + * transport layer add stuff to it as well. + */ + rc = RTPollSetCreate(&pProcess->hPollSet); + if (RT_SUCCESS(rc)) + { + uint32_t uFlags = RTPOLL_EVT_ERROR; +#if 0 + /* Add reading event to pollset to get some more information. */ + uFlags |= RTPOLL_EVT_READ; +#endif + /* Stdin. */ + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pProcess->hPollSet, + pProcess->hPipeStdInW, RTPOLL_EVT_ERROR, VBOXSERVICECTRLPIPEID_STDIN); + /* Stdout. */ + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pProcess->hPollSet, + pProcess->hPipeStdOutR, uFlags, VBOXSERVICECTRLPIPEID_STDOUT); + /* Stderr. */ + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pProcess->hPollSet, + pProcess->hPipeStdErrR, uFlags, VBOXSERVICECTRLPIPEID_STDERR); + /* IPC notification pipe. */ + if (RT_SUCCESS(rc)) + rc = RTPipeCreate(&pProcess->hNotificationPipeR, &pProcess->hNotificationPipeW, 0 /* Flags */); + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pProcess->hPollSet, + pProcess->hNotificationPipeR, RTPOLL_EVT_READ, VBOXSERVICECTRLPIPEID_IPC_NOTIFY); + if (RT_SUCCESS(rc)) + { + AssertPtr(pProcess->pSession); + bool fNeedsImpersonation = !(pProcess->pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_SPAWN); + + rc = vgsvcGstCtrlProcessCreateProcess(pProcess->pStartupInfo->pszCmd, papszArgs, hEnv, + pProcess->pStartupInfo->fFlags, + phStdIn, phStdOut, phStdErr, + fNeedsImpersonation ? pProcess->pStartupInfo->pszUser : NULL, + fNeedsImpersonation ? pProcess->pStartupInfo->pszPassword : NULL, + fNeedsImpersonation ? pProcess->pStartupInfo->pszDomain : NULL, + &pProcess->hProcess); + if (RT_FAILURE(rc)) + VGSvcError("Error starting process, rc=%Rrc\n", rc); + /* + * Tell the session thread that it can continue + * spawning guest processes. This needs to be done after the new + * process has been started because otherwise signal handling + * on (Open) Solaris does not work correctly (see @bugref{5068}). + */ + int rc2 = RTThreadUserSignal(RTThreadSelf()); + if (RT_SUCCESS(rc)) + rc = rc2; + fSignalled = true; + + if (RT_SUCCESS(rc)) + { + /* + * Close the child ends of any pipes and redirected files. + */ + rc2 = RTHandleClose(phStdIn); AssertRC(rc2); + phStdIn = NULL; + rc2 = RTHandleClose(phStdOut); AssertRC(rc2); + phStdOut = NULL; + rc2 = RTHandleClose(phStdErr); AssertRC(rc2); + phStdErr = NULL; + + /* Enter the process main loop. */ + rc = vgsvcGstCtrlProcessProcLoop(pProcess); + + /* + * The handles that are no longer in the set have + * been closed by the above call in order to prevent + * the guest from getting stuck accessing them. + * So, NIL the handles to avoid closing them again. + */ + if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet, + VBOXSERVICECTRLPIPEID_IPC_NOTIFY, NULL))) + pProcess->hNotificationPipeW = NIL_RTPIPE; + if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet, + VBOXSERVICECTRLPIPEID_STDERR, NULL))) + pProcess->hPipeStdErrR = NIL_RTPIPE; + if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet, + VBOXSERVICECTRLPIPEID_STDOUT, NULL))) + pProcess->hPipeStdOutR = NIL_RTPIPE; + if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet, + VBOXSERVICECTRLPIPEID_STDIN, NULL))) + pProcess->hPipeStdInW = NIL_RTPIPE; + } + } + RTPollSetDestroy(pProcess->hPollSet); + pProcess->hPollSet = NIL_RTPOLLSET; + + RTPipeClose(pProcess->hNotificationPipeR); + pProcess->hNotificationPipeR = NIL_RTPIPE; + RTPipeClose(pProcess->hNotificationPipeW); + pProcess->hNotificationPipeW = NIL_RTPIPE; + } + RTPipeClose(pProcess->hPipeStdErrR); + pProcess->hPipeStdErrR = NIL_RTPIPE; + RTHandleClose(&hStdErr); + if (phStdErr) + RTHandleClose(phStdErr); + } + RTPipeClose(pProcess->hPipeStdOutR); + pProcess->hPipeStdOutR = NIL_RTPIPE; + RTHandleClose(&hStdOut); + if (phStdOut) + RTHandleClose(phStdOut); + } + RTPipeClose(pProcess->hPipeStdInW); + pProcess->hPipeStdInW = NIL_RTPIPE; + RTHandleClose(&hStdIn); + if (phStdIn) + RTHandleClose(phStdIn); + } + } + RTEnvDestroy(hEnv); + } + } + + if (RT_FAILURE(rc)) + { + VBGLR3GUESTCTRLCMDCTX ctx = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ }; + int rc2 = VbglR3GuestCtrlProcCbStatus(&ctx, + pProcess->uPID, PROC_STS_ERROR, rc, + NULL /* pvData */, 0 /* cbData */); + if ( RT_FAILURE(rc2) + && rc2 != VERR_NOT_FOUND) + VGSvcError("[PID %RU32]: Could not report process failure error; rc=%Rrc (process error %Rrc)\n", + pProcess->uPID, rc2, rc); + } + + /* Update stopped status. */ + ASMAtomicWriteBool(&pProcess->fStopped, true); + + if (cArgs) + RTGetOptArgvFree(papszArgs); + + /* + * If something went wrong signal the user event so that others don't wait + * forever on this thread. + */ + if ( RT_FAILURE(rc) + && !fSignalled) + { + RTThreadUserSignal(RTThreadSelf()); + } + + /* Set shut down flag in case we've forgotten it. */ + ASMAtomicWriteBool(&pProcess->fShutdown, true); + + VGSvcVerbose(3, "[PID %RU32]: Thread of process '%s' ended with rc=%Rrc (fSignalled=%RTbool)\n", + pProcess->uPID, pProcess->pStartupInfo->pszCmd, rc, fSignalled); + + return rc; +} + + +static int vgsvcGstCtrlProcessLock(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + int rc = RTCritSectEnter(&pProcess->CritSect); + AssertRC(rc); + return rc; +} + + +/** + * Thread main routine for a started process. + * + * @return IPRT status code. + * @param hThreadSelf The thread handle. + * @param pvUser Pointer to a VBOXSERVICECTRLPROCESS structure. + * + */ +static DECLCALLBACK(int) vgsvcGstCtrlProcessThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF1(hThreadSelf); + PVBOXSERVICECTRLPROCESS pProcess = (PVBOXSERVICECTRLPROCESS)pvUser; + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + return vgsvcGstCtrlProcessProcessWorker(pProcess); +} + + +static int vgsvcGstCtrlProcessUnlock(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + int rc = RTCritSectLeave(&pProcess->CritSect); + AssertRC(rc); + return rc; +} + + +/** + * Executes (starts) a process on the guest. This causes a new thread to be created + * so that this function will not block the overall program execution. + * + * @return IPRT status code. + * @param pSession Guest session. + * @param pStartupInfo Startup info. + * @param uContextID Context ID to associate the process to start with. + */ +int VGSvcGstCtrlProcessStart(const PVBOXSERVICECTRLSESSION pSession, + const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo, uint32_t uContextID) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER); + + /* + * Allocate new thread data and assign it to our thread list. + */ + PVBOXSERVICECTRLPROCESS pProcess = (PVBOXSERVICECTRLPROCESS)RTMemAlloc(sizeof(VBOXSERVICECTRLPROCESS)); + if (!pProcess) + return VERR_NO_MEMORY; + + int rc = vgsvcGstCtrlProcessInit(pProcess, pSession, pStartupInfo, uContextID); + if (RT_SUCCESS(rc)) + { + static uint32_t s_uCtrlExecThread = 0; + rc = RTThreadCreateF(&pProcess->Thread, vgsvcGstCtrlProcessThread, + pProcess /*pvUser*/, 0 /*cbStack*/, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctl%RU32", s_uCtrlExecThread++); + if (RT_FAILURE(rc)) + { + VGSvcError("Creating thread for guest process '%s' failed: rc=%Rrc, pProcess=%p\n", + pStartupInfo->pszCmd, rc, pProcess); + + /* Process has not been added to the session's process list yet, so skip VGSvcGstCtrlSessionProcessRemove() here. */ + VGSvcGstCtrlProcessFree(pProcess); + } + else + { + VGSvcVerbose(4, "Waiting for thread to initialize ...\n"); + + /* Wait for the thread to initialize. */ + rc = RTThreadUserWait(pProcess->Thread, 60 * 1000 /* 60 seconds max. */); + AssertRC(rc); + if ( ASMAtomicReadBool(&pProcess->fShutdown) + || ASMAtomicReadBool(&pProcess->fStopped) + || RT_FAILURE(rc)) + { + VGSvcError("Thread for process '%s' failed to start, rc=%Rrc\n", pStartupInfo->pszCmd, rc); + int rc2 = RTThreadWait(pProcess->Thread, RT_MS_1SEC * 30, NULL); + if (RT_SUCCESS(rc2)) + pProcess->Thread = NIL_RTTHREAD; + + VGSvcGstCtrlSessionProcessRemove(pSession, pProcess); + VGSvcGstCtrlProcessFree(pProcess); + } + else + { + ASMAtomicXchgBool(&pProcess->fStarted, true); + } + } + } + + return rc; +} + + +static DECLCALLBACK(int) vgsvcGstCtrlProcessOnInput(PVBOXSERVICECTRLPROCESS pThis, + const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + bool fPendingClose, void *pvBuf, uint32_t cbBuf) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + int rc; + + size_t cbWritten = 0; + if (pvBuf && cbBuf) + { + if (pThis->hPipeStdInW != NIL_RTPIPE) + rc = RTPipeWrite(pThis->hPipeStdInW, pvBuf, cbBuf, &cbWritten); + else + rc = VINF_EOF; + } + else + rc = VERR_INVALID_PARAMETER; + + /* + * If this is the last write + we have really have written all data + * we need to close the stdin pipe on our end and remove it from + * the poll set. + */ + if ( fPendingClose + && cbBuf == cbWritten) + { + int rc2 = vgsvcGstCtrlProcessPollsetCloseInput(pThis, &pThis->hPipeStdInW); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status to send back to the host. */ + uint32_t fFlags = 0; /* No flags at the moment. */ + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "[PID %RU32]: Written %RU32 bytes input, CID=%RU32, fPendingClose=%RTbool\n", + pThis->uPID, cbWritten, pHostCtx->uContextID, fPendingClose); + uStatus = INPUT_STS_WRITTEN; + } + else + { + if (rc == VERR_BAD_PIPE) + uStatus = INPUT_STS_TERMINATED; + else if (rc == VERR_BUFFER_OVERFLOW) + uStatus = INPUT_STS_OVERFLOW; + /* else undefined */ + } + + /* + * If there was an error and we did not set the host status + * yet, then do it now. + */ + if ( RT_FAILURE(rc) + && uStatus == INPUT_STS_UNDEFINED) + { + uStatus = INPUT_STS_ERROR; + fFlags = rc; /* funny thing to call a "flag"... */ + } + Assert(uStatus > INPUT_STS_UNDEFINED); + + int rc2 = VbglR3GuestCtrlProcCbStatusInput(pHostCtx, pThis->uPID, uStatus, fFlags, (uint32_t)cbWritten); + if (RT_SUCCESS(rc)) + rc = rc2; + +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessOnInput returned with rc=%Rrc\n", pThis->uPID, rc); +#endif + return rc; +} + + +static DECLCALLBACK(int) vgsvcGstCtrlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis, + const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + uint32_t uHandle, uint32_t cbToRead, uint32_t fFlags) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + const PVBOXSERVICECTRLSESSION pSession = pThis->pSession; + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + int rc; + + uint32_t cbBuf = cbToRead; + uint8_t *pvBuf = (uint8_t *)RTMemAlloc(cbBuf); + if (pvBuf) + { + PRTPIPE phPipe = uHandle == GUEST_PROC_OUT_H_STDOUT + ? &pThis->hPipeStdOutR + : &pThis->hPipeStdErrR; + AssertPtr(phPipe); + + size_t cbRead = 0; + if (*phPipe != NIL_RTPIPE) + { + rc = RTPipeRead(*phPipe, pvBuf, cbBuf, &cbRead); + if (RT_FAILURE(rc)) + { + RTPollSetRemove(pThis->hPollSet, uHandle == GUEST_PROC_OUT_H_STDERR + ? VBOXSERVICECTRLPIPEID_STDERR : VBOXSERVICECTRLPIPEID_STDOUT); + RTPipeClose(*phPipe); + *phPipe = NIL_RTPIPE; + if (rc == VERR_BROKEN_PIPE) + rc = VINF_EOF; + } + } + else + rc = VINF_EOF; + +#ifdef DEBUG + if (RT_SUCCESS(rc)) + { + if ( pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT + && ( uHandle == GUEST_PROC_OUT_H_STDOUT + || uHandle == GUEST_PROC_OUT_H_STDOUT_DEPRECATED) + ) + { + rc = vgsvcGstCtrlProcessDbgDumpToFileF(pvBuf, cbRead, "VBoxService_Session%RU32_PID%RU32_StdOut.txt", + pSession->StartupInfo.uSessionID, pThis->uPID); + AssertRC(rc); + } + else if ( pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR + && uHandle == GUEST_PROC_OUT_H_STDERR) + { + rc = vgsvcGstCtrlProcessDbgDumpToFileF(pvBuf, cbRead, "VBoxService_Session%RU32_PID%RU32_StdErr.txt", + pSession->StartupInfo.uSessionID, pThis->uPID); + AssertRC(rc); + } + } +#endif + + if (RT_SUCCESS(rc)) + { +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: Read %RU32 bytes output: uHandle=%RU32, CID=%RU32, fFlags=%x\n", + pThis->uPID, cbRead, uHandle, pHostCtx->uContextID, fFlags); +#endif + /** Note: Don't convert/touch/modify/whatever the output data here! This might be binary + * data which the host needs to work with -- so just pass through all data unfiltered! */ + + /* Note: Since the context ID is unique the request *has* to be completed here, + * regardless whether we got data or not! Otherwise the waiting events + * on the host never will get completed! */ + Assert((uint32_t)cbRead == cbRead); + rc = VbglR3GuestCtrlProcCbOutput(pHostCtx, pThis->uPID, uHandle, fFlags, pvBuf, (uint32_t)cbRead); + if ( RT_FAILURE(rc) + && rc == VERR_NOT_FOUND) /* Not critical if guest PID is not found on the host (anymore). */ + rc = VINF_SUCCESS; + } + + RTMemFree(pvBuf); + } + else + rc = VERR_NO_MEMORY; + +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: Reading output returned with rc=%Rrc\n", pThis->uPID, rc); +#endif + return rc; +} + + +static DECLCALLBACK(int) vgsvcGstCtrlProcessOnTerm(PVBOXSERVICECTRLPROCESS pThis) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + if (!ASMAtomicXchgBool(&pThis->fShutdown, true)) + VGSvcVerbose(3, "[PID %RU32]: Setting shutdown flag ...\n", pThis->uPID); + + return VINF_SUCCESS; +} + + +static int vgsvcGstCtrlProcessRequestExV(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fAsync, + RTMSINTERVAL uTimeoutMS, PFNRT pfnFunction, unsigned cArgs, va_list Args) +{ + RT_NOREF1(pHostCtx); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + /* pHostCtx is optional. */ + AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER); + if (!fAsync) + AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER); + + int rc = vgsvcGstCtrlProcessLock(pProcess); + if (RT_SUCCESS(rc)) + { +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessRequestExV fAsync=%RTbool, uTimeoutMS=%RU32, cArgs=%u\n", + pProcess->uPID, fAsync, uTimeoutMS, cArgs); +#endif + uint32_t fFlags = RTREQFLAGS_IPRT_STATUS; + if (fAsync) + { + Assert(uTimeoutMS == 0); + fFlags |= RTREQFLAGS_NO_WAIT; + } + + PRTREQ hReq = NIL_RTREQ; + rc = RTReqQueueCallV(pProcess->hReqQueue, &hReq, uTimeoutMS, fFlags, pfnFunction, cArgs, Args); + RTReqRelease(hReq); + if (RT_SUCCESS(rc)) + { + /* Wake up the process' notification pipe to get + * the request being processed. */ + Assert(pProcess->hNotificationPipeW != NIL_RTPIPE || pProcess->fShutdown /* latter in case of race */); + size_t cbWritten = 0; + rc = RTPipeWrite(pProcess->hNotificationPipeW, "i", 1, &cbWritten); + if ( RT_SUCCESS(rc) + && cbWritten != 1) + { + VGSvcError("[PID %RU32]: Notification pipe got %zu bytes instead of 1\n", + pProcess->uPID, cbWritten); + } + else if (RT_UNLIKELY(RT_FAILURE(rc))) + VGSvcError("[PID %RU32]: Writing to notification pipe failed, rc=%Rrc\n", + pProcess->uPID, rc); + } + else + VGSvcError("[PID %RU32]: RTReqQueueCallV failed, rc=%Rrc\n", + pProcess->uPID, rc); + + int rc2 = vgsvcGstCtrlProcessUnlock(pProcess); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessRequestExV returned rc=%Rrc\n", pProcess->uPID, rc); +#endif + return rc; +} + + +static int vgsvcGstCtrlProcessRequestAsync(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + PFNRT pfnFunction, unsigned cArgs, ...) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + /* pHostCtx is optional. */ + AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER); + + va_list va; + va_start(va, cArgs); + int rc = vgsvcGstCtrlProcessRequestExV(pProcess, pHostCtx, true /* fAsync */, 0 /* uTimeoutMS */, + pfnFunction, cArgs, va); + va_end(va); + + return rc; +} + + +#if 0 /* unused */ +static int vgsvcGstCtrlProcessRequestWait(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + RTMSINTERVAL uTimeoutMS, PFNRT pfnFunction, unsigned cArgs, ...) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + /* pHostCtx is optional. */ + AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER); + + va_list va; + va_start(va, cArgs); + int rc = vgsvcGstCtrlProcessRequestExV(pProcess, pHostCtx, false /* fAsync */, uTimeoutMS, + pfnFunction, cArgs, va); + va_end(va); + + return rc; +} +#endif + + +int VGSvcGstCtrlProcessHandleInput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + bool fPendingClose, void *pvBuf, uint32_t cbBuf) +{ + if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped)) + return vgsvcGstCtrlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)vgsvcGstCtrlProcessOnInput, + 5 /* cArgs */, pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf); + + return vgsvcGstCtrlProcessOnInput(pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf); +} + + +int VGSvcGstCtrlProcessHandleOutput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + uint32_t uHandle, uint32_t cbToRead, uint32_t fFlags) +{ + if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped)) + return vgsvcGstCtrlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)vgsvcGstCtrlProcessOnOutput, + 5 /* cArgs */, pProcess, pHostCtx, uHandle, cbToRead, fFlags); + + return vgsvcGstCtrlProcessOnOutput(pProcess, pHostCtx, uHandle, cbToRead, fFlags); +} + + +int VGSvcGstCtrlProcessHandleTerm(PVBOXSERVICECTRLPROCESS pProcess) +{ + if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped)) + return vgsvcGstCtrlProcessRequestAsync(pProcess, NULL /* pHostCtx */, (PFNRT)vgsvcGstCtrlProcessOnTerm, + 1 /* cArgs */, pProcess); + + return vgsvcGstCtrlProcessOnTerm(pProcess); +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp new file mode 100644 index 00000000..21b7fde4 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/handle.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/system.h> /* For RTShutdown. */ + +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#include "VBoxServiceControl.h" + +using namespace guestControl; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Generic option indices for session spawn arguments. */ +enum +{ + VBOXSERVICESESSIONOPT_FIRST = 1000, /* For initialization. */ + VBOXSERVICESESSIONOPT_DOMAIN, +#ifdef DEBUG + VBOXSERVICESESSIONOPT_DUMP_STDOUT, + VBOXSERVICESESSIONOPT_DUMP_STDERR, +#endif + VBOXSERVICESESSIONOPT_LOG_FILE, + VBOXSERVICESESSIONOPT_USERNAME, + VBOXSERVICESESSIONOPT_SESSION_ID, + VBOXSERVICESESSIONOPT_SESSION_PROTO, + VBOXSERVICESESSIONOPT_THREAD_ID +}; + + +static int vgsvcGstCtrlSessionCleanupProcesses(const PVBOXSERVICECTRLSESSION pSession); +static int vgsvcGstCtrlSessionProcessRemoveInternal(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess); + + +/** + * Helper that grows the scratch buffer. + * @returns Success indicator. + */ +static bool vgsvcGstCtrlSessionGrowScratchBuf(void **ppvScratchBuf, uint32_t *pcbScratchBuf, uint32_t cbMinBuf) +{ + uint32_t cbNew = *pcbScratchBuf * 2; + if ( cbNew <= VMMDEV_MAX_HGCM_DATA_SIZE + && cbMinBuf <= VMMDEV_MAX_HGCM_DATA_SIZE) + { + while (cbMinBuf > cbNew) + cbNew *= 2; + void *pvNew = RTMemRealloc(*ppvScratchBuf, cbNew); + if (pvNew) + { + *ppvScratchBuf = pvNew; + *pcbScratchBuf = cbNew; + return true; + } + } + return false; +} + + + +static int vgsvcGstCtrlSessionFileFree(PVBOXSERVICECTRLFILE pFile) +{ + AssertPtrReturn(pFile, VERR_INVALID_POINTER); + + int rc = RTFileClose(pFile->hFile); + if (RT_SUCCESS(rc)) + { + RTStrFree(pFile->pszName); + + /* Remove file entry in any case. */ + RTListNodeRemove(&pFile->Node); + /* Destroy this object. */ + RTMemFree(pFile); + } + + return rc; +} + + +/** @todo No locking done yet! */ +static PVBOXSERVICECTRLFILE vgsvcGstCtrlSessionFileGetLocked(const PVBOXSERVICECTRLSESSION pSession, uint32_t uHandle) +{ + AssertPtrReturn(pSession, NULL); + + /** @todo Use a map later! */ + PVBOXSERVICECTRLFILE pFileCur; + RTListForEach(&pSession->lstFiles, pFileCur, VBOXSERVICECTRLFILE, Node) + { + if (pFileCur->uHandle == uHandle) + return pFileCur; + } + + return NULL; +} + + +/** + * Recursion worker for vgsvcGstCtrlSessionHandleDirRemove. + * Only (recursively) removes directory structures which are not empty. Will fail if not empty. + * + * @returns IPRT status code. + * @param pszDir The directory buffer, RTPATH_MAX in length. + * Contains the abs path to the directory to + * recurse into. Trailing slash. + * @param cchDir The length of the directory we're recursing into, + * including the trailing slash. + * @param pDirEntry The dir entry buffer. (Shared to save stack.) + */ +static int vgsvcGstCtrlSessionHandleDirRemoveSub(char *pszDir, size_t cchDir, PRTDIRENTRY pDirEntry) +{ + RTDIR hDir; + int rc = RTDirOpen(&hDir, pszDir); + if (RT_FAILURE(rc)) + { + /* Ignore non-existing directories like RTDirRemoveRecursive does: */ + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + return VINF_SUCCESS; + return rc; + } + + for (;;) + { + rc = RTDirRead(hDir, pDirEntry, NULL); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + break; + } + + if (!RTDirEntryIsStdDotLink(pDirEntry)) + { + /* Construct the full name of the entry. */ + if (cchDir + pDirEntry->cbName + 1 /* dir slash */ < RTPATH_MAX) + memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1); + else + { + rc = VERR_FILENAME_TOO_LONG; + break; + } + + /* Make sure we've got the entry type. */ + if (pDirEntry->enmType == RTDIRENTRYTYPE_UNKNOWN) + RTDirQueryUnknownType(pszDir, false /*fFollowSymlinks*/, &pDirEntry->enmType); + + /* Recurse into subdirs and remove them: */ + if (pDirEntry->enmType == RTDIRENTRYTYPE_DIRECTORY) + { + size_t cchSubDir = cchDir + pDirEntry->cbName; + pszDir[cchSubDir++] = RTPATH_SLASH; + pszDir[cchSubDir] = '\0'; + rc = vgsvcGstCtrlSessionHandleDirRemoveSub(pszDir, cchSubDir, pDirEntry); + if (RT_SUCCESS(rc)) + { + pszDir[cchSubDir] = '\0'; + rc = RTDirRemove(pszDir); + if (RT_FAILURE(rc)) + break; + } + else + break; + } + /* Not a subdirectory - fail: */ + else + { + rc = VERR_DIR_NOT_EMPTY; + break; + } + } + } + + RTDirClose(hDir); + return rc; +} + + +static int vgsvcGstCtrlSessionHandleDirRemove(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the message. + */ + char szDir[RTPATH_MAX]; + uint32_t fFlags; /* DIRREMOVE_FLAG_XXX */ + int rc = VbglR3GuestCtrlDirGetRemove(pHostCtx, szDir, sizeof(szDir), &fFlags); + if (RT_SUCCESS(rc)) + { + /* + * Do some validating before executing the job. + */ + if (!(fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK)) + { + if (fFlags & DIRREMOVEREC_FLAG_RECURSIVE) + { + if (fFlags & (DIRREMOVEREC_FLAG_CONTENT_AND_DIR | DIRREMOVEREC_FLAG_CONTENT_ONLY)) + { + uint32_t fFlagsRemRec = fFlags & DIRREMOVEREC_FLAG_CONTENT_AND_DIR + ? RTDIRRMREC_F_CONTENT_AND_DIR : RTDIRRMREC_F_CONTENT_ONLY; + rc = RTDirRemoveRecursive(szDir, fFlagsRemRec); + } + else /* Only remove empty directory structures. Will fail if non-empty. */ + { + RTDIRENTRY DirEntry; + RTPathEnsureTrailingSeparator(szDir, sizeof(szDir)); + rc = vgsvcGstCtrlSessionHandleDirRemoveSub(szDir, strlen(szDir), &DirEntry); + } + VGSvcVerbose(4, "[Dir %s]: rmdir /s (%#x) -> rc=%Rrc\n", szDir, fFlags, rc); + } + else + { + /* Only delete directory if not empty. */ + rc = RTDirRemove(szDir); + VGSvcVerbose(4, "[Dir %s]: rmdir (%#x), rc=%Rrc\n", szDir, fFlags, rc); + } + } + else + { + VGSvcError("[Dir %s]: Unsupported flags: %#x (all %#x)\n", szDir, (fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK), fFlags); + rc = VERR_NOT_SUPPORTED; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc); + if (RT_FAILURE(rc2)) + { + VGSvcError("[Dir %s]: Failed to report removing status, rc=%Rrc\n", szDir, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for rmdir operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + VGSvcVerbose(6, "Removing directory '%s' returned rc=%Rrc\n", szDir, rc); + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileOpen(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the message. + */ + char szFile[RTPATH_MAX]; + char szAccess[64]; + char szDisposition[64]; + char szSharing[64]; + uint32_t uCreationMode = 0; + uint64_t offOpen = 0; + uint32_t uHandle = 0; + int rc = VbglR3GuestCtrlFileGetOpen(pHostCtx, + /* File to open. */ + szFile, sizeof(szFile), + /* Open mode. */ + szAccess, sizeof(szAccess), + /* Disposition. */ + szDisposition, sizeof(szDisposition), + /* Sharing. */ + szSharing, sizeof(szSharing), + /* Creation mode. */ + &uCreationMode, + /* Offset. */ + &offOpen); + VGSvcVerbose(4, "[File %s]: szAccess=%s, szDisposition=%s, szSharing=%s, offOpen=%RU64, rc=%Rrc\n", + szFile, szAccess, szDisposition, szSharing, offOpen, rc); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLFILE pFile = (PVBOXSERVICECTRLFILE)RTMemAllocZ(sizeof(VBOXSERVICECTRLFILE)); + if (pFile) + { + pFile->hFile = NIL_RTFILE; /* Not zero or NULL! */ + if (szFile[0]) + { + pFile->pszName = RTStrDup(szFile); + if (!pFile->pszName) + rc = VERR_NO_MEMORY; +/** @todo + * Implement szSharing! + */ + uint64_t fFlags; + if (RT_SUCCESS(rc)) + { + rc = RTFileModeToFlagsEx(szAccess, szDisposition, NULL /* pszSharing, not used yet */, &fFlags); + VGSvcVerbose(4, "[File %s] Opening with fFlags=%#RX64 -> rc=%Rrc\n", pFile->pszName, fFlags, rc); + } + + if (RT_SUCCESS(rc)) + { + fFlags |= (uCreationMode << RTFILE_O_CREATE_MODE_SHIFT) & RTFILE_O_CREATE_MODE_MASK; + /* If we're opening a file in read-only mode, strip truncation mode. + * rtFileRecalcAndValidateFlags() will validate it anyway, but avoid asserting in debug builds. */ + if (fFlags & RTFILE_O_READ) + fFlags &= ~RTFILE_O_TRUNCATE; + rc = RTFileOpen(&pFile->hFile, pFile->pszName, fFlags); + if (RT_SUCCESS(rc)) + { + RTFSOBJINFO objInfo; + rc = RTFileQueryInfo(pFile->hFile, &objInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { + /* Make sure that we only open stuff we really support. + * Only POSIX / UNIX we could open stuff like directories and sockets as well. */ + if ( RT_LIKELY(RTFS_IS_FILE(objInfo.Attr.fMode)) + || RTFS_IS_SYMLINK(objInfo.Attr.fMode)) + { + /* Seeking is optional. However, the whole operation + * will fail if we don't succeed seeking to the wanted position. */ + if (offOpen) + rc = RTFileSeek(pFile->hFile, (int64_t)offOpen, RTFILE_SEEK_BEGIN, NULL /* Current offset */); + if (RT_SUCCESS(rc)) + { + /* + * Succeeded! + */ + uHandle = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pHostCtx->uContextID); + pFile->uHandle = uHandle; + pFile->fOpen = fFlags; + RTListAppend(&pSession->lstFiles, &pFile->Node); + VGSvcVerbose(2, "[File %s] Opened (ID=%RU32)\n", pFile->pszName, pFile->uHandle); + } + else + VGSvcError("[File %s] Seeking to offset %RU64 failed: rc=%Rrc\n", pFile->pszName, offOpen, rc); + } + else + { + VGSvcError("[File %s] Unsupported mode %#x\n", pFile->pszName, objInfo.Attr.fMode); + rc = VERR_NOT_SUPPORTED; + } + } + else + VGSvcError("[File %s] Getting mode failed with rc=%Rrc\n", pFile->pszName, rc); + } + else + VGSvcError("[File %s] Opening failed with rc=%Rrc\n", pFile->pszName, rc); + } + } + else + { + VGSvcError("[File %s] empty filename!\n", szFile); + rc = VERR_INVALID_NAME; + } + + /* clean up if we failed. */ + if (RT_FAILURE(rc)) + { + RTStrFree(pFile->pszName); + if (pFile->hFile != NIL_RTFILE) + RTFileClose(pFile->hFile); + RTMemFree(pFile); + } + } + else + rc = VERR_NO_MEMORY; + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlFileCbOpen(pHostCtx, rc, uHandle); + if (RT_FAILURE(rc2)) + { + VGSvcError("[File %s]: Failed to report file open status, rc=%Rrc\n", szFile, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for open file operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + VGSvcVerbose(4, "[File %s] Opening (open mode='%s', disposition='%s', creation mode=0x%x) returned rc=%Rrc\n", + szFile, szAccess, szDisposition, uCreationMode, rc); + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileClose(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the message. + */ + uint32_t uHandle = 0; + int rc = VbglR3GuestCtrlFileGetClose(pHostCtx, &uHandle /* File handle to close */); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + VGSvcVerbose(2, "[File %s] Closing (handle=%RU32)\n", pFile ? pFile->pszName : "<Not found>", uHandle); + rc = vgsvcGstCtrlSessionFileFree(pFile); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlFileCbClose(pHostCtx, rc); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file close status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for close file operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileRead(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uHandle = 0; + uint32_t cbToRead; + int rc = VbglR3GuestCtrlFileGetRead(pHostCtx, &uHandle, &cbToRead); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and do the reading. + * + * If the request is larger than our scratch buffer, try grow it - just + * ignore failure as the host better respect our buffer limits. + */ + uint32_t offNew = 0; + size_t cbRead = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + if (*pcbScratchBuf < cbToRead) + vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToRead); + + rc = RTFileRead(pFile->hFile, *ppvScratchBuf, RT_MIN(cbToRead, *pcbScratchBuf), &cbRead); + offNew = (int64_t)RTFileTell(pFile->hFile); + VGSvcVerbose(5, "[File %s] Read %zu/%RU32 bytes, rc=%Rrc, offNew=%RI64\n", pFile->pszName, cbRead, cbToRead, rc, offNew); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result and data back to the host. + */ + int rc2; + if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET) + rc2 = VbglR3GuestCtrlFileCbReadOffset(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead, offNew); + else + rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file read status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file read operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileReadAt(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uHandle = 0; + uint32_t cbToRead; + uint64_t offReadAt; + int rc = VbglR3GuestCtrlFileGetReadAt(pHostCtx, &uHandle, &cbToRead, &offReadAt); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and do the reading. + * + * If the request is larger than our scratch buffer, try grow it - just + * ignore failure as the host better respect our buffer limits. + */ + int64_t offNew = 0; + size_t cbRead = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + if (*pcbScratchBuf < cbToRead) + vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToRead); + + rc = RTFileReadAt(pFile->hFile, (RTFOFF)offReadAt, *ppvScratchBuf, RT_MIN(cbToRead, *pcbScratchBuf), &cbRead); + if (RT_SUCCESS(rc)) + { + offNew = offReadAt + cbRead; + RTFileSeek(pFile->hFile, offNew, RTFILE_SEEK_BEGIN, NULL); /* RTFileReadAt does not always change position. */ + } + else + offNew = (int64_t)RTFileTell(pFile->hFile); + VGSvcVerbose(5, "[File %s] Read %zu bytes @ %RU64, rc=%Rrc, offNew=%RI64\n", pFile->pszName, cbRead, offReadAt, rc, offNew); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result and data back to the host. + */ + int rc2; + if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET) + rc2 = VbglR3GuestCtrlFileCbReadOffset(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead, offNew); + else + rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file read at status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file read at operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileWrite(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request and data to write. + */ + uint32_t uHandle = 0; + uint32_t cbToWrite; + int rc = VbglR3GuestCtrlFileGetWrite(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite); + if ( rc == VERR_BUFFER_OVERFLOW + && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToWrite)) + rc = VbglR3GuestCtrlFileGetWrite(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and do the writing. + */ + int64_t offNew = 0; + size_t cbWritten = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileWrite(pFile->hFile, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), &cbWritten); + offNew = (int64_t)RTFileTell(pFile->hFile); + VGSvcVerbose(5, "[File %s] Writing %p LB %RU32 => %Rrc, cbWritten=%zu, offNew=%RI64\n", + pFile->pszName, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), rc, cbWritten, offNew); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int rc2; + if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET) + rc2 = VbglR3GuestCtrlFileCbWriteOffset(pHostCtx, rc, (uint32_t)cbWritten, offNew); + else + rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file write status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file write operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileWriteAt(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request and data to write. + */ + uint32_t uHandle = 0; + uint32_t cbToWrite; + uint64_t offWriteAt; + int rc = VbglR3GuestCtrlFileGetWriteAt(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite, &offWriteAt); + if ( rc == VERR_BUFFER_OVERFLOW + && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToWrite)) + rc = VbglR3GuestCtrlFileGetWriteAt(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite, &offWriteAt); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and do the writing. + */ + int64_t offNew = 0; + size_t cbWritten = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileWriteAt(pFile->hFile, (RTFOFF)offWriteAt, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), &cbWritten); + if (RT_SUCCESS(rc)) + { + offNew = offWriteAt + cbWritten; + + /* RTFileWriteAt does not always change position: */ + if (!(pFile->fOpen & RTFILE_O_APPEND)) + RTFileSeek(pFile->hFile, offNew, RTFILE_SEEK_BEGIN, NULL); + else + RTFileSeek(pFile->hFile, 0, RTFILE_SEEK_END, (uint64_t *)&offNew); + } + else + offNew = (int64_t)RTFileTell(pFile->hFile); + VGSvcVerbose(5, "[File %s] Writing %p LB %RU32 @ %RU64 => %Rrc, cbWritten=%zu, offNew=%RI64\n", + pFile->pszName, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), offWriteAt, rc, cbWritten, offNew); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int rc2; + if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET) + rc2 = VbglR3GuestCtrlFileCbWriteOffset(pHostCtx, rc, (uint32_t)cbWritten, offNew); + else + rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file write status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file write at operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileSeek(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uHandle = 0; + uint32_t uSeekMethod; + uint64_t offSeek; /* Will be converted to int64_t. */ + int rc = VbglR3GuestCtrlFileGetSeek(pHostCtx, &uHandle, &uSeekMethod, &offSeek); + if (RT_SUCCESS(rc)) + { + uint64_t offActual = 0; + + /* + * Validate and convert the seek method to IPRT speak. + */ + static const uint8_t s_abMethods[GUEST_FILE_SEEKTYPE_END + 1] = + { + UINT8_MAX, RTFILE_SEEK_BEGIN, UINT8_MAX, UINT8_MAX, RTFILE_SEEK_CURRENT, + UINT8_MAX, UINT8_MAX, UINT8_MAX, RTFILE_SEEK_END + }; + if ( uSeekMethod < RT_ELEMENTS(s_abMethods) + && s_abMethods[uSeekMethod] != UINT8_MAX) + { + /* + * Locate the file and do the seek. + */ + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileSeek(pFile->hFile, (int64_t)offSeek, s_abMethods[uSeekMethod], &offActual); + VGSvcVerbose(5, "[File %s]: Seeking to offSeek=%RI64, uSeekMethodIPRT=%u, rc=%Rrc\n", + pFile->pszName, offSeek, s_abMethods[uSeekMethod], rc); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + } + else + { + VGSvcError("Invalid seek method: %#x\n", uSeekMethod); + rc = VERR_NOT_SUPPORTED; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlFileCbSeek(pHostCtx, rc, offActual); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file seek status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file seek operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileTell(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uHandle = 0; + int rc = VbglR3GuestCtrlFileGetTell(pHostCtx, &uHandle); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and ask for the current position. + */ + uint64_t offCurrent = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + offCurrent = RTFileTell(pFile->hFile); + VGSvcVerbose(5, "[File %s]: Telling offCurrent=%RU64\n", pFile->pszName, offCurrent); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlFileCbTell(pHostCtx, rc, offCurrent); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file tell status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file tell operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileSetSize(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uHandle = 0; + uint64_t cbNew = 0; + int rc = VbglR3GuestCtrlFileGetSetSize(pHostCtx, &uHandle, &cbNew); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and ask for the current position. + */ + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileSetSize(pFile->hFile, cbNew); + VGSvcVerbose(5, "[File %s]: Changing size to %RU64 (%#RX64), rc=%Rrc\n", pFile->pszName, cbNew, cbNew, rc); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + cbNew = UINT64_MAX; + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlFileCbSetSize(pHostCtx, rc, cbNew); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file tell status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file tell operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandlePathRename(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + char szSource[RTPATH_MAX]; + char szDest[RTPATH_MAX]; + uint32_t fFlags = 0; /* PATHRENAME_FLAG_XXX */ + int rc = VbglR3GuestCtrlPathGetRename(pHostCtx, szSource, sizeof(szSource), szDest, sizeof(szDest), &fFlags); + if (RT_SUCCESS(rc)) + { + /* + * Validate the flags (kudos for using the same as IPRT), then do the renaming. + */ + AssertCompile(PATHRENAME_FLAG_NO_REPLACE == RTPATHRENAME_FLAGS_NO_REPLACE); + AssertCompile(PATHRENAME_FLAG_REPLACE == RTPATHRENAME_FLAGS_REPLACE); + AssertCompile(PATHRENAME_FLAG_NO_SYMLINKS == RTPATHRENAME_FLAGS_NO_SYMLINKS); + AssertCompile(PATHRENAME_FLAG_VALID_MASK == (RTPATHRENAME_FLAGS_NO_REPLACE | RTPATHRENAME_FLAGS_REPLACE | RTPATHRENAME_FLAGS_NO_SYMLINKS)); + if (!(fFlags & ~PATHRENAME_FLAG_VALID_MASK)) + { + VGSvcVerbose(4, "Renaming '%s' to '%s', fFlags=%#x, rc=%Rrc\n", szSource, szDest, fFlags, rc); + rc = RTPathRename(szSource, szDest, fFlags); + } + else + { + VGSvcError("Invalid rename flags: %#x\n", fFlags); + rc = VERR_NOT_SUPPORTED; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report renaming status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for rename operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + VGSvcVerbose(5, "Renaming '%s' to '%s' returned rc=%Rrc\n", szSource, szDest, rc); + return rc; +} + + +/** + * Handles getting the user's documents directory. + * + * @returns VBox status code. + * @param pSession Guest session. + * @param pHostCtx Host context. + */ +static int vgsvcGstCtrlSessionHandlePathUserDocuments(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + int rc = VbglR3GuestCtrlPathGetUserDocuments(pHostCtx); + if (RT_SUCCESS(rc)) + { + /* + * Get the path and pass it back to the host.. + */ + char szPath[RTPATH_MAX]; + rc = RTPathUserDocuments(szPath, sizeof(szPath)); +#ifdef DEBUG + VGSvcVerbose(2, "User documents is '%s', rc=%Rrc\n", szPath, rc); +#endif + + int rc2 = VbglR3GuestCtrlMsgReplyEx(pHostCtx, rc, 0 /* Type */, szPath, + RT_SUCCESS(rc) ? (uint32_t)strlen(szPath) + 1 /* Include terminating zero */ : 0); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report user documents, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for user documents path request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +/** + * Handles shutting down / rebooting the guest OS. + * + * @returns VBox status code. + * @param pSession Guest session. + * @param pHostCtx Host context. + */ +static int vgsvcGstCtrlSessionHandleShutdown(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t fAction; + int rc = VbglR3GuestCtrlGetShutdown(pHostCtx, &fAction); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(1, "Host requested to %s system ...\n", (fAction & RTSYSTEM_SHUTDOWN_REBOOT) ? "reboot" : "shutdown"); + + /* Reply first to the host, in order to avoid host hangs when issuing the guest shutdown. */ + rc = VbglR3GuestCtrlMsgReply(pHostCtx, VINF_SUCCESS); + if (RT_FAILURE(rc)) + { + VGSvcError("Failed to reply to shutdown / reboot request, rc=%Rrc\n", rc); + } + else + { + int fSystemShutdown = RTSYSTEM_SHUTDOWN_PLANNED; + + /* Translate SHUTDOWN_FLAG_ into RTSYSTEM_SHUTDOWN_ flags. */ + if (fAction & GUEST_SHUTDOWN_FLAG_REBOOT) + fSystemShutdown |= RTSYSTEM_SHUTDOWN_REBOOT; + else /* SHUTDOWN_FLAG_POWER_OFF */ + fSystemShutdown |= RTSYSTEM_SHUTDOWN_POWER_OFF; + + if (fAction & GUEST_SHUTDOWN_FLAG_FORCE) + fSystemShutdown |= RTSYSTEM_SHUTDOWN_FORCE; + + rc = RTSystemShutdown(0 /*cMsDelay*/, fSystemShutdown, "VBoxService"); + if (RT_FAILURE(rc)) + VGSvcError("%s system failed with %Rrc\n", + (fAction & RTSYSTEM_SHUTDOWN_REBOOT) ? "Rebooting" : "Shutting down", rc); + } + } + else + { + VGSvcError("Error fetching parameters for shutdown / reboot request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + return rc; +} + + +/** + * Handles getting the user's home directory. + * + * @returns VBox status code. + * @param pSession Guest session. + * @param pHostCtx Host context. + */ +static int vgsvcGstCtrlSessionHandlePathUserHome(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + int rc = VbglR3GuestCtrlPathGetUserHome(pHostCtx); + if (RT_SUCCESS(rc)) + { + /* + * Get the path and pass it back to the host.. + */ + char szPath[RTPATH_MAX]; + rc = RTPathUserHome(szPath, sizeof(szPath)); + +#ifdef DEBUG + VGSvcVerbose(2, "User home is '%s', rc=%Rrc\n", szPath, rc); +#endif + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlMsgReplyEx(pHostCtx, rc, 0 /* Type */, szPath, + RT_SUCCESS(rc) ?(uint32_t)strlen(szPath) + 1 /* Include terminating zero */ : 0); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report user home, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for user home directory path request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + +/** + * Handles starting a guest processes. + * + * @returns VBox status code. + * @param pSession Guest session. + * @param pHostCtx Host context. + */ +static int vgsvcGstCtrlSessionHandleProcExec(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* Initialize maximum environment block size -- needed as input + * parameter to retrieve the stuff from the host. On output this then + * will contain the actual block size. */ + PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo; + int rc = VbglR3GuestCtrlProcGetStart(pHostCtx, &pStartupInfo); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "Request to start process szCmd=%s, fFlags=0x%x, szArgs=%s, szEnv=%s, uTimeout=%RU32\n", + pStartupInfo->pszCmd, pStartupInfo->fFlags, + pStartupInfo->cArgs ? pStartupInfo->pszArgs : "<None>", + pStartupInfo->cEnvVars ? pStartupInfo->pszEnv : "<None>", + pStartupInfo->uTimeLimitMS); + + bool fStartAllowed = false; /* Flag indicating whether starting a process is allowed or not. */ + rc = VGSvcGstCtrlSessionProcessStartAllowed(pSession, &fStartAllowed); + if (RT_SUCCESS(rc)) + { + vgsvcGstCtrlSessionCleanupProcesses(pSession); + + if (fStartAllowed) + rc = VGSvcGstCtrlProcessStart(pSession, pStartupInfo, pHostCtx->uContextID); + else + rc = VERR_MAX_PROCS_REACHED; /* Maximum number of processes reached. */ + } + + /* We're responsible for signaling errors to the host (it will wait for ever otherwise). */ + if (RT_FAILURE(rc)) + { + VGSvcError("Starting process failed with rc=%Rrc, protocol=%RU32, parameters=%RU32\n", + rc, pHostCtx->uProtocol, pHostCtx->uNumParms); + int rc2 = VbglR3GuestCtrlProcCbStatus(pHostCtx, 0 /*nil-PID*/, PROC_STS_ERROR, rc, NULL /*pvData*/, 0 /*cbData*/); + if (RT_FAILURE(rc2)) + VGSvcError("Error sending start process status to host, rc=%Rrc\n", rc2); + } + + VbglR3GuestCtrlProcStartupInfoFree(pStartupInfo); + pStartupInfo = NULL; + } + else + { + VGSvcError("Failed to retrieve parameters for process start: %Rrc (cParms=%u)\n", rc, pHostCtx->uNumParms); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + return rc; +} + + +/** + * Sends stdin input to a specific guest process. + * + * @returns VBox status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + * @param ppvScratchBuf The scratch buffer, we may grow it. + * @param pcbScratchBuf The scratch buffer size for retrieving the input + * data. + */ +static int vgsvcGstCtrlSessionHandleProcInput(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the data from the host. + */ + uint32_t uPID; + uint32_t fFlags; + uint32_t cbInput; + int rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &fFlags, *ppvScratchBuf, *pcbScratchBuf, &cbInput); + if ( rc == VERR_BUFFER_OVERFLOW + && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbInput)) + rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &fFlags, *ppvScratchBuf, *pcbScratchBuf, &cbInput); + if (RT_SUCCESS(rc)) + { + if (fFlags & GUEST_PROC_IN_FLAG_EOF) + VGSvcVerbose(4, "Got last process input block for PID=%RU32 (%RU32 bytes) ...\n", uPID, cbInput); + + /* + * Locate the process and feed it. + */ + PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = VGSvcGstCtrlProcessHandleInput(pProcess, pHostCtx, RT_BOOL(fFlags & GUEST_PROC_IN_FLAG_EOF), + *ppvScratchBuf, RT_MIN(cbInput, *pcbScratchBuf)); + if (RT_FAILURE(rc)) + VGSvcError("Error handling input message for PID=%RU32, rc=%Rrc\n", uPID, rc); + VGSvcGstCtrlProcessRelease(pProcess); + } + else + { + VGSvcError("Could not find PID %u for feeding %u bytes to it.\n", uPID, cbInput); + rc = VERR_PROCESS_NOT_FOUND; + VbglR3GuestCtrlProcCbStatusInput(pHostCtx, uPID, INPUT_STS_ERROR, rc, 0); + } + } + else + { + VGSvcError("Failed to retrieve parameters for process input: %Rrc (scratch %u bytes)\n", rc, *pcbScratchBuf); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + VGSvcVerbose(6, "Feeding input to PID=%RU32 resulted in rc=%Rrc\n", uPID, rc); + return rc; +} + + +/** + * Gets stdout/stderr output of a specific guest process. + * + * @returns VBox status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + */ +static int vgsvcGstCtrlSessionHandleProcOutput(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uPID; + uint32_t uHandleID; + uint32_t fFlags; + int rc = VbglR3GuestCtrlProcGetOutput(pHostCtx, &uPID, &uHandleID, &fFlags); +#ifdef DEBUG_andy + VGSvcVerbose(4, "Getting output for PID=%RU32, CID=%RU32, uHandleID=%RU32, fFlags=%RU32\n", + uPID, pHostCtx->uContextID, uHandleID, fFlags); +#endif + if (RT_SUCCESS(rc)) + { + /* + * Locate the process and hand it the output request. + */ + PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = VGSvcGstCtrlProcessHandleOutput(pProcess, pHostCtx, uHandleID, _64K /* cbToRead */, fFlags); + if (RT_FAILURE(rc)) + VGSvcError("Error getting output for PID=%RU32, rc=%Rrc\n", uPID, rc); + VGSvcGstCtrlProcessRelease(pProcess); + } + else + { + VGSvcError("Could not find PID %u for draining handle %u (%#x).\n", uPID, uHandleID, uHandleID); + rc = VERR_PROCESS_NOT_FOUND; +/** @todo r=bird: + * + * No way to report status status code for output requests? + * + */ + } + } + else + { + VGSvcError("Error fetching parameters for process output request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + +#ifdef DEBUG_andy + VGSvcVerbose(4, "Getting output for PID=%RU32 resulted in rc=%Rrc\n", uPID, rc); +#endif + return rc; +} + + +/** + * Tells a guest process to terminate. + * + * @returns VBox status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + */ +static int vgsvcGstCtrlSessionHandleProcTerminate(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uPID; + int rc = VbglR3GuestCtrlProcGetTerminate(pHostCtx, &uPID); + if (RT_SUCCESS(rc)) + { + /* + * Locate the process and terminate it. + */ + PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = VGSvcGstCtrlProcessHandleTerm(pProcess); + if (RT_FAILURE(rc)) + VGSvcError("Error terminating PID=%RU32, rc=%Rrc\n", uPID, rc); + + VGSvcGstCtrlProcessRelease(pProcess); + } + else + { + VGSvcError("Could not find PID %u for termination.\n", uPID); + rc = VERR_PROCESS_NOT_FOUND; + } + } + else + { + VGSvcError("Error fetching parameters for process termination request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } +#ifdef DEBUG_andy + VGSvcVerbose(4, "Terminating PID=%RU32 resulted in rc=%Rrc\n", uPID, rc); +#endif + return rc; +} + + +static int vgsvcGstCtrlSessionHandleProcWaitFor(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uPID; + uint32_t uWaitFlags; + uint32_t uTimeoutMS; + int rc = VbglR3GuestCtrlProcGetWaitFor(pHostCtx, &uPID, &uWaitFlags, &uTimeoutMS); + if (RT_SUCCESS(rc)) + { + /* + * Locate the process and the realize that this call makes no sense + * since we'll notify the host when a process terminates anyway and + * hopefully don't need any additional encouragement. + */ + PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = VERR_NOT_IMPLEMENTED; /** @todo */ + VGSvcGstCtrlProcessRelease(pProcess); + } + else + rc = VERR_NOT_FOUND; + } + else + { + VGSvcError("Error fetching parameters for process wait request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +int VGSvcGstCtrlSessionHandler(PVBOXSERVICECTRLSESSION pSession, uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf, volatile bool *pfShutdown) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + AssertPtrReturn(*ppvScratchBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pfShutdown, VERR_INVALID_POINTER); + + + /* + * Only anonymous sessions (that is, sessions which run with local + * service privileges) or spawned session processes can do certain + * operations. + */ + bool const fImpersonated = RT_BOOL(pSession->fFlags & ( VBOXSERVICECTRLSESSION_FLAG_SPAWN + | VBOXSERVICECTRLSESSION_FLAG_ANONYMOUS)); + int rc = VERR_NOT_SUPPORTED; /* Play safe by default. */ + + switch (uMsg) + { + case HOST_MSG_SESSION_CLOSE: + /* Shutdown (this spawn). */ + rc = VGSvcGstCtrlSessionClose(pSession); + *pfShutdown = true; /* Shutdown in any case. */ + break; + + case HOST_MSG_DIR_REMOVE: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleDirRemove(pSession, pHostCtx); + break; + + case HOST_MSG_EXEC_CMD: + rc = vgsvcGstCtrlSessionHandleProcExec(pSession, pHostCtx); + break; + + case HOST_MSG_EXEC_SET_INPUT: + rc = vgsvcGstCtrlSessionHandleProcInput(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf); + break; + + case HOST_MSG_EXEC_GET_OUTPUT: + rc = vgsvcGstCtrlSessionHandleProcOutput(pSession, pHostCtx); + break; + + case HOST_MSG_EXEC_TERMINATE: + rc = vgsvcGstCtrlSessionHandleProcTerminate(pSession, pHostCtx); + break; + + case HOST_MSG_EXEC_WAIT_FOR: + rc = vgsvcGstCtrlSessionHandleProcWaitFor(pSession, pHostCtx); + break; + + case HOST_MSG_FILE_OPEN: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileOpen(pSession, pHostCtx); + break; + + case HOST_MSG_FILE_CLOSE: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileClose(pSession, pHostCtx); + break; + + case HOST_MSG_FILE_READ: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileRead(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf); + break; + + case HOST_MSG_FILE_READ_AT: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileReadAt(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf); + break; + + case HOST_MSG_FILE_WRITE: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileWrite(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf); + break; + + case HOST_MSG_FILE_WRITE_AT: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileWriteAt(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf); + break; + + case HOST_MSG_FILE_SEEK: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileSeek(pSession, pHostCtx); + break; + + case HOST_MSG_FILE_TELL: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileTell(pSession, pHostCtx); + break; + + case HOST_MSG_FILE_SET_SIZE: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileSetSize(pSession, pHostCtx); + break; + + case HOST_MSG_PATH_RENAME: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandlePathRename(pSession, pHostCtx); + break; + + case HOST_MSG_PATH_USER_DOCUMENTS: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandlePathUserDocuments(pSession, pHostCtx); + break; + + case HOST_MSG_PATH_USER_HOME: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandlePathUserHome(pSession, pHostCtx); + break; + + case HOST_MSG_SHUTDOWN: + rc = vgsvcGstCtrlSessionHandleShutdown(pSession, pHostCtx); + break; + + default: /* Not supported, see next code block. */ + break; + } + if (RT_SUCCESS(rc)) + { /* likely */ } + else if (rc != VERR_NOT_SUPPORTED) /* Note: Reply to host must must be sent by above handler. */ + VGSvcError("Error while handling message (uMsg=%RU32, cParms=%RU32), rc=%Rrc\n", uMsg, pHostCtx->uNumParms, rc); + else + { + /* We must skip and notify host here as best we can... */ + VGSvcVerbose(1, "Unsupported message (uMsg=%RU32, cParms=%RU32) from host, skipping\n", uMsg, pHostCtx->uNumParms); + if (VbglR3GuestCtrlSupportsOptimizations(pHostCtx->uClientID)) + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, VERR_NOT_SUPPORTED, uMsg); + else + VbglR3GuestCtrlMsgSkipOld(pHostCtx->uClientID); + rc = VINF_SUCCESS; + } + + if (RT_FAILURE(rc)) + VGSvcError("Error while handling message (uMsg=%RU32, cParms=%RU32), rc=%Rrc\n", uMsg, pHostCtx->uNumParms, rc); + + return rc; +} + + +/** + * Thread main routine for a spawned guest session process. + * + * This thread runs in the main executable to control the spawned session process. + * + * @returns VBox status code. + * @param hThreadSelf Thread handle. + * @param pvUser Pointer to a VBOXSERVICECTRLSESSIONTHREAD structure. + * + */ +static DECLCALLBACK(int) vgsvcGstCtrlSessionThread(RTTHREAD hThreadSelf, void *pvUser) +{ + PVBOXSERVICECTRLSESSIONTHREAD pThread = (PVBOXSERVICECTRLSESSIONTHREAD)pvUser; + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + + uint32_t const idSession = pThread->pStartupInfo->uSessionID; + uint32_t const idClient = g_idControlSvcClient; + VGSvcVerbose(3, "Session ID=%RU32 thread running\n", idSession); + + /* Let caller know that we're done initializing, regardless of the result. */ + int rc2 = RTThreadUserSignal(hThreadSelf); + AssertRC(rc2); + + /* + * Wait for the child process to stop or the shutdown flag to be signalled. + */ + RTPROCSTATUS ProcessStatus = { 0, RTPROCEXITREASON_NORMAL }; + bool fProcessAlive = true; + bool fSessionCancelled = VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient); + uint32_t cMsShutdownTimeout = 30 * 1000; /** @todo Make this configurable. Later. */ + uint64_t msShutdownStart = 0; + uint64_t const msStart = RTTimeMilliTS(); + size_t offSecretKey = 0; + int rcWait; + for (;;) + { + /* Secret key feeding. */ + if (offSecretKey < sizeof(pThread->abKey)) + { + size_t cbWritten = 0; + rc2 = RTPipeWrite(pThread->hKeyPipe, &pThread->abKey[offSecretKey], sizeof(pThread->abKey) - offSecretKey, &cbWritten); + if (RT_SUCCESS(rc2)) + offSecretKey += cbWritten; + } + + /* Poll child process status. */ + rcWait = RTProcWaitNoResume(pThread->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus); + if ( rcWait == VINF_SUCCESS + || rcWait == VERR_PROCESS_NOT_FOUND) + { + fProcessAlive = false; + break; + } + AssertMsgBreak(rcWait == VERR_PROCESS_RUNNING || rcWait == VERR_INTERRUPTED, + ("Got unexpected rc=%Rrc while waiting for session process termination\n", rcWait)); + + /* Shutting down? */ + if (ASMAtomicReadBool(&pThread->fShutdown)) + { + if (!msShutdownStart) + { + VGSvcVerbose(3, "Notifying guest session process (PID=%RU32, session ID=%RU32) ...\n", + pThread->hProcess, idSession); + + VBGLR3GUESTCTRLCMDCTX hostCtx = + { + /* .idClient = */ idClient, + /* .idContext = */ VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession), + /* .uProtocol = */ pThread->pStartupInfo->uProtocol, + /* .cParams = */ 2 + }; + rc2 = VbglR3GuestCtrlSessionClose(&hostCtx, 0 /* fFlags */); + if (RT_FAILURE(rc2)) + { + VGSvcError("Unable to notify guest session process (PID=%RU32, session ID=%RU32), rc=%Rrc\n", + pThread->hProcess, idSession, rc2); + + if (rc2 == VERR_NOT_SUPPORTED) + { + /* Terminate guest session process in case it's not supported by a too old host. */ + rc2 = RTProcTerminate(pThread->hProcess); + VGSvcVerbose(3, "Terminating guest session process (PID=%RU32) ended with rc=%Rrc\n", + pThread->hProcess, rc2); + } + break; + } + + VGSvcVerbose(3, "Guest session ID=%RU32 thread was asked to terminate, waiting for session process to exit (%RU32 ms timeout) ...\n", + idSession, cMsShutdownTimeout); + msShutdownStart = RTTimeMilliTS(); + continue; /* Don't waste time on waiting. */ + } + if (RTTimeMilliTS() - msShutdownStart > cMsShutdownTimeout) + { + VGSvcVerbose(3, "Guest session ID=%RU32 process did not shut down within time\n", idSession); + break; + } + } + + /* Cancel the prepared session stuff after 30 seconds. */ + if ( !fSessionCancelled + && RTTimeMilliTS() - msStart >= 30000) + { + VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, idSession); + fSessionCancelled = true; + } + +/** @todo r=bird: This 100ms sleep is _extremely_ sucky! */ + RTThreadSleep(100); /* Wait a bit. */ + } + + if (!fSessionCancelled) + VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, idSession); + + if (!fProcessAlive) + { + VGSvcVerbose(2, "Guest session process (ID=%RU32) terminated with rc=%Rrc, reason=%d, status=%d\n", + idSession, rcWait, ProcessStatus.enmReason, ProcessStatus.iStatus); + if (ProcessStatus.iStatus == RTEXITCODE_INIT) + { + VGSvcError("Guest session process (ID=%RU32) failed to initialize. Here some hints:\n", idSession); + VGSvcError("- Is logging enabled and the output directory is read-only by the guest session user?\n"); + /** @todo Add more here. */ + } + } + + uint32_t uSessionStatus = GUEST_SESSION_NOTIFYTYPE_UNDEFINED; + int32_t iSessionResult = VINF_SUCCESS; + + if (fProcessAlive) + { + for (int i = 0; i < 3; i++) + { + if (i) + RTThreadSleep(3000); + + VGSvcVerbose(2, "Guest session ID=%RU32 process still alive, killing attempt %d/3\n", idSession, i + 1); + + rc2 = RTProcTerminate(pThread->hProcess); + if (RT_SUCCESS(rc2)) + break; + } + + VGSvcVerbose(2, "Guest session ID=%RU32 process termination resulted in rc=%Rrc\n", idSession, rc2); + uSessionStatus = RT_SUCCESS(rc2) ? GUEST_SESSION_NOTIFYTYPE_TOK : GUEST_SESSION_NOTIFYTYPE_TOA; + } + else if (RT_SUCCESS(rcWait)) + { + switch (ProcessStatus.enmReason) + { + case RTPROCEXITREASON_NORMAL: + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN; + iSessionResult = ProcessStatus.iStatus; /* Report back the session's exit code. */ + break; + + case RTPROCEXITREASON_ABEND: + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA; + /* iSessionResult is undefined (0). */ + break; + + case RTPROCEXITREASON_SIGNAL: + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TES; + iSessionResult = ProcessStatus.iStatus; /* Report back the signal number. */ + break; + + default: + AssertMsgFailed(("Unhandled process termination reason (%d)\n", ProcessStatus.enmReason)); + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA; + break; + } + } + else + { + /* If we didn't find the guest process anymore, just assume it terminated normally. */ + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN; + } + + /* Make sure to set stopped state before we let the host know. */ + ASMAtomicWriteBool(&pThread->fStopped, true); + + /* Report final status, regardless if we failed to wait above, so that the host knows what's going on. */ + VGSvcVerbose(3, "Reporting final status %RU32 of session ID=%RU32\n", uSessionStatus, idSession); + Assert(uSessionStatus != GUEST_SESSION_NOTIFYTYPE_UNDEFINED); + + VBGLR3GUESTCTRLCMDCTX ctx = { idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession), + 0 /* uProtocol, unused */, 0 /* uNumParms, unused */ }; + rc2 = VbglR3GuestCtrlSessionNotify(&ctx, uSessionStatus, iSessionResult); + if (RT_FAILURE(rc2)) + VGSvcError("Reporting final status of session ID=%RU32 failed with rc=%Rrc\n", idSession, rc2); + + VGSvcVerbose(3, "Thread for session ID=%RU32 ended with sessionStatus=%#x (%RU32), sessionRc=%#x (%Rrc)\n", + idSession, uSessionStatus, uSessionStatus, iSessionResult, iSessionResult); + + return VINF_SUCCESS; +} + +/** + * Reads the secret key the parent VBoxService instance passed us and pass it + * along as a authentication token to the host service. + * + * For older hosts, this sets up the message filtering. + * + * @returns VBox status code. + * @param idClient The HGCM client ID. + * @param idSession The session ID. + */ +static int vgsvcGstCtrlSessionReadKeyAndAccept(uint32_t idClient, uint32_t idSession) +{ + /* + * Read it. + */ + RTHANDLE Handle; + int rc = RTHandleGetStandard(RTHANDLESTD_INPUT, true /*fLeaveOpen*/, &Handle); + if (RT_SUCCESS(rc)) + { + if (Handle.enmType == RTHANDLETYPE_PIPE) + { + uint8_t abSecretKey[RT_SIZEOFMEMB(VBOXSERVICECTRLSESSIONTHREAD, abKey)]; + rc = RTPipeReadBlocking(Handle.u.hPipe, abSecretKey, sizeof(abSecretKey), NULL); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "Got secret key from standard input.\n"); + + /* + * Do the accepting, if appropriate. + */ + if (g_fControlSupportsOptimizations) + { + rc = VbglR3GuestCtrlSessionAccept(idClient, idSession, abSecretKey, sizeof(abSecretKey)); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Session %u accepted (client ID %u)\n", idClient, idSession); + else + VGSvcError("Failed to accept session %u (client ID %u): %Rrc\n", idClient, idSession, rc); + } + else + { + /* For legacy hosts, we do the filtering thingy. */ + rc = VbglR3GuestCtrlMsgFilterSet(idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession), + VBOX_GUESTCTRL_FILTER_BY_SESSION(idSession), 0); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Session %u filtering successfully enabled\n", idSession); + else + VGSvcError("Failed to set session filter: %Rrc\n", rc); + } + } + else + VGSvcError("Error reading secret key from standard input: %Rrc\n", rc); + } + else + { + VGSvcError("Standard input is not a pipe!\n"); + rc = VERR_INVALID_HANDLE; + } + RTHandleClose(&Handle); + } + else + VGSvcError("RTHandleGetStandard failed on standard input: %Rrc\n", rc); + return rc; +} + +/** + * Invalidates a guest session by updating all it's internal parameters like host features and stuff. + * + * @param pSession Session to invalidate. + * @param idClient Client ID to use. + */ +static void vgsvcGstCtrlSessionInvalidate(PVBOXSERVICECTRLSESSION pSession, uint32_t idClient) +{ + RT_NOREF(pSession); + + VGSvcVerbose(1, "Invalidating session %RU32 (client ID=%RU32)\n", idClient, pSession->StartupInfo.uSessionID); + + int rc2 = VbglR3GuestCtrlQueryFeatures(idClient, &g_fControlHostFeatures0); + if (RT_SUCCESS(rc2)) /* Querying host features is not fatal -- do not use rc here. */ + { + VGSvcVerbose(1, "g_fControlHostFeatures0=%#x\n", g_fControlHostFeatures0); + } + else + VGSvcVerbose(1, "Querying host features failed with %Rrc\n", rc2); +} + +/** + * Main message handler for the guest control session process. + * + * @returns exit code. + * @param pSession Pointer to g_Session. + * @thread main. + */ +static RTEXITCODE vgsvcGstCtrlSessionSpawnWorker(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, RTEXITCODE_FAILURE); + VGSvcVerbose(0, "Hi, this is guest session ID=%RU32\n", pSession->StartupInfo.uSessionID); + + /* + * Connect to the host service. + */ + uint32_t idClient; + int rc = VbglR3GuestCtrlConnect(&idClient); + if (RT_FAILURE(rc)) + return VGSvcError("Error connecting to guest control service, rc=%Rrc\n", rc); + g_fControlSupportsOptimizations = VbglR3GuestCtrlSupportsOptimizations(idClient); + g_idControlSvcClient = idClient; + + VGSvcVerbose(1, "Using client ID=%RU32\n", idClient); + + vgsvcGstCtrlSessionInvalidate(pSession, idClient); + + rc = vgsvcGstCtrlSessionReadKeyAndAccept(idClient, pSession->StartupInfo.uSessionID); + if (RT_SUCCESS(rc)) + { + /* + * Report started status. + * If session status cannot be posted to the host for some reason, bail out. + */ + VBGLR3GUESTCTRLCMDCTX ctx = { idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(pSession->StartupInfo.uSessionID), + 0 /* uProtocol, unused */, 0 /* uNumParms, unused */ }; + rc = VbglR3GuestCtrlSessionNotify(&ctx, GUEST_SESSION_NOTIFYTYPE_STARTED, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + /* + * Allocate a scratch buffer for messages which also send payload data with them. + * This buffer may grow if the host sends us larger chunks of data. + */ + uint32_t cbScratchBuf = _64K; + void *pvScratchBuf = RTMemAlloc(cbScratchBuf); + if (pvScratchBuf) + { + int cFailedMsgPeeks = 0; + + /* + * Message processing loop. + */ + VBGLR3GUESTCTRLCMDCTX CtxHost = { idClient, 0 /* Context ID */, pSession->StartupInfo.uProtocol, 0 }; + for (;;) + { + VGSvcVerbose(3, "Waiting for host msg ...\n"); + uint32_t uMsg = 0; + rc = VbglR3GuestCtrlMsgPeekWait(idClient, &uMsg, &CtxHost.uNumParms, NULL); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "Msg=%RU32 (%RU32 parms) retrieved (%Rrc)\n", uMsg, CtxHost.uNumParms, rc); + + /* + * Pass it on to the session handler. + * Note! Only when handling HOST_SESSION_CLOSE is the rc used. + */ + bool fShutdown = false; + rc = VGSvcGstCtrlSessionHandler(pSession, uMsg, &CtxHost, &pvScratchBuf, &cbScratchBuf, &fShutdown); + if (fShutdown) + break; + + cFailedMsgPeeks = 0; + + /* Let others run (guests are often single CPU) ... */ + RTThreadYield(); + } + /* + * Handle restore notification from host. All the context IDs (sessions, + * files, proceses, etc) are invalidated by a VM restore and must be closed. + */ + else if (rc == VERR_VM_RESTORED) + { + VGSvcVerbose(1, "The VM session ID changed (i.e. restored), closing stale session %RU32\n", + pSession->StartupInfo.uSessionID); + + /* We currently don't serialize guest sessions, guest processes and other guest control objects + * within saved states. So just close this session and report success to the parent process. + * + * Note: Not notifying the host here is intentional, as it wouldn't have any information + * about what to do with it. + */ + rc = VINF_SUCCESS; /* Report success as exit code. */ + break; + } + else + { + VGSvcVerbose(1, "Getting host message failed with %Rrc\n", rc); + + if (cFailedMsgPeeks++ == 3) + break; + + RTThreadSleep(3 * RT_MS_1SEC); + + /** @todo Shouldn't we have a plan for handling connection loss and such? */ + } + } + + /* + * Shutdown. + */ + RTMemFree(pvScratchBuf); + } + else + rc = VERR_NO_MEMORY; + + VGSvcVerbose(0, "Session %RU32 ended\n", pSession->StartupInfo.uSessionID); + } + else + VGSvcError("Reporting session ID=%RU32 started status failed with rc=%Rrc\n", pSession->StartupInfo.uSessionID, rc); + } + else + VGSvcError("Setting message filterAdd=0x%x failed with rc=%Rrc\n", pSession->StartupInfo.uSessionID, rc); + + VGSvcVerbose(3, "Disconnecting client ID=%RU32 ...\n", idClient); + VbglR3GuestCtrlDisconnect(idClient); + g_idControlSvcClient = 0; + + VGSvcVerbose(3, "Session worker returned with rc=%Rrc\n", rc); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Finds a (formerly) started guest process given by its PID and increases its + * reference count. + * + * Must be decreased by the caller with VGSvcGstCtrlProcessRelease(). + * + * @returns Guest process if found, otherwise NULL. + * @param pSession Pointer to guest session where to search process in. + * @param uPID PID to search for. + * + * @note This does *not lock the process! + */ +PVBOXSERVICECTRLPROCESS VGSvcGstCtrlSessionRetainProcess(PVBOXSERVICECTRLSESSION pSession, uint32_t uPID) +{ + AssertPtrReturn(pSession, NULL); + + PVBOXSERVICECTRLPROCESS pProcess = NULL; + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLPROCESS pCurProcess; + RTListForEach(&pSession->lstProcesses, pCurProcess, VBOXSERVICECTRLPROCESS, Node) + { + if (pCurProcess->uPID == uPID) + { + rc = RTCritSectEnter(&pCurProcess->CritSect); + if (RT_SUCCESS(rc)) + { + pCurProcess->cRefs++; + rc = RTCritSectLeave(&pCurProcess->CritSect); + AssertRC(rc); + } + + if (RT_SUCCESS(rc)) + pProcess = pCurProcess; + break; + } + } + + rc = RTCritSectLeave(&pSession->CritSect); + AssertRC(rc); + } + + return pProcess; +} + + +int VGSvcGstCtrlSessionClose(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + VGSvcVerbose(0, "Session %RU32 is about to close ...\n", pSession->StartupInfo.uSessionID); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Close all guest processes. + */ + VGSvcVerbose(0, "Stopping all guest processes ...\n"); + + /* Signal all guest processes in the active list that we want to shutdown. */ + PVBOXSERVICECTRLPROCESS pProcess; + RTListForEach(&pSession->lstProcesses, pProcess, VBOXSERVICECTRLPROCESS, Node) + VGSvcGstCtrlProcessStop(pProcess); + + VGSvcVerbose(1, "%RU32 guest processes were signalled to stop\n", pSession->cProcesses); + + /* Wait for all active threads to shutdown and destroy the active thread list. */ + PVBOXSERVICECTRLPROCESS pProcessNext; + RTListForEachSafe(&pSession->lstProcesses, pProcess, pProcessNext, VBOXSERVICECTRLPROCESS, Node) + { + int rc3 = RTCritSectLeave(&pSession->CritSect); + AssertRC(rc3); + + int rc2 = VGSvcGstCtrlProcessWait(pProcess, 30 * 1000 /* Wait 30 seconds max. */, NULL /* rc */); + + rc3 = RTCritSectEnter(&pSession->CritSect); + AssertRC(rc3); + + if (RT_SUCCESS(rc2)) + { + rc2 = vgsvcGstCtrlSessionProcessRemoveInternal(pSession, pProcess); + if (RT_SUCCESS(rc2)) + { + VGSvcGstCtrlProcessFree(pProcess); + pProcess = NULL; + } + } + } + + AssertMsg(pSession->cProcesses == 0, + ("Session process list still contains %RU32 when it should not\n", pSession->cProcesses)); + AssertMsg(RTListIsEmpty(&pSession->lstProcesses), + ("Session process list is not empty when it should\n")); + + /* + * Close all left guest files. + */ + VGSvcVerbose(0, "Closing all guest files ...\n"); + + PVBOXSERVICECTRLFILE pFile, pFileNext; + RTListForEachSafe(&pSession->lstFiles, pFile, pFileNext, VBOXSERVICECTRLFILE, Node) + { + int rc2 = vgsvcGstCtrlSessionFileFree(pFile); + if (RT_FAILURE(rc2)) + { + VGSvcError("Unable to close file '%s'; rc=%Rrc\n", pFile->pszName, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + /* Keep going. */ + } + + pFile = NULL; /* To make it obvious. */ + } + + AssertMsg(pSession->cFiles == 0, + ("Session file list still contains %RU32 when it should not\n", pSession->cFiles)); + AssertMsg(RTListIsEmpty(&pSession->lstFiles), + ("Session file list is not empty when it should\n")); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + + +int VGSvcGstCtrlSessionDestroy(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + int rc = VGSvcGstCtrlSessionClose(pSession); + + /* Destroy critical section. */ + RTCritSectDelete(&pSession->CritSect); + + return rc; +} + + +int VGSvcGstCtrlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t fFlags) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + RTListInit(&pSession->lstProcesses); + RTListInit(&pSession->lstFiles); + + pSession->cProcesses = 0; + pSession->cFiles = 0; + + pSession->fFlags = fFlags; + + /* Init critical section for protecting the thread lists. */ + int rc = RTCritSectInit(&pSession->CritSect); + AssertRC(rc); + + return rc; +} + + +/** + * Adds a guest process to a session's process list. + * + * @return VBox status code. + * @param pSession Guest session to add process to. + * @param pProcess Guest process to add. + */ +int VGSvcGstCtrlSessionProcessAdd(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "Adding process (PID %RU32) to session ID=%RU32\n", pProcess->uPID, pSession->StartupInfo.uSessionID); + + /* Add process to session list. */ + RTListAppend(&pSession->lstProcesses, &pProcess->Node); + + pSession->cProcesses++; + VGSvcVerbose(3, "Now session ID=%RU32 has %RU32 processes total\n", + pSession->StartupInfo.uSessionID, pSession->cProcesses); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return VINF_SUCCESS; +} + +/** + * Removes a guest process from a session's process list. + * Internal version, does not do locking. + * + * @return VBox status code. + * @param pSession Guest session to remove process from. + * @param pProcess Guest process to remove. + */ +static int vgsvcGstCtrlSessionProcessRemoveInternal(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess) +{ + VGSvcVerbose(3, "Removing process (PID %RU32) from session ID=%RU32\n", pProcess->uPID, pSession->StartupInfo.uSessionID); + AssertReturn(pProcess->cRefs == 0, VERR_WRONG_ORDER); + + RTListNodeRemove(&pProcess->Node); + + AssertReturn(pSession->cProcesses, VERR_WRONG_ORDER); + pSession->cProcesses--; + VGSvcVerbose(3, "Now session ID=%RU32 has %RU32 processes total\n", + pSession->StartupInfo.uSessionID, pSession->cProcesses); + + return VINF_SUCCESS; +} + +/** + * Removes a guest process from a session's process list. + * + * @return VBox status code. + * @param pSession Guest session to remove process from. + * @param pProcess Guest process to remove. + */ +int VGSvcGstCtrlSessionProcessRemove(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + rc = vgsvcGstCtrlSessionProcessRemoveInternal(pSession, pProcess); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + + +/** + * Determines whether starting a new guest process according to the + * maximum number of concurrent guest processes defined is allowed or not. + * + * @return VBox status code. + * @param pSession The guest session. + * @param pfAllowed \c True if starting (another) guest process + * is allowed, \c false if not. + */ +int VGSvcGstCtrlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession, bool *pfAllowed) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pfAllowed, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Check if we're respecting our memory policy by checking + * how many guest processes are started and served already. + */ + bool fLimitReached = false; + if (pSession->uProcsMaxKept) /* If we allow unlimited processes (=0), take a shortcut. */ + { + VGSvcVerbose(3, "Maximum kept guest processes set to %RU32, acurrent=%RU32\n", + pSession->uProcsMaxKept, pSession->cProcesses); + + int32_t iProcsLeft = (pSession->uProcsMaxKept - pSession->cProcesses - 1); + if (iProcsLeft < 0) + { + VGSvcVerbose(3, "Maximum running guest processes reached (%RU32)\n", pSession->uProcsMaxKept); + fLimitReached = true; + } + } + + *pfAllowed = !fLimitReached; + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + + +/** + * Cleans up stopped and no longer used processes. + * + * This will free and remove processes from the session's process list. + * + * @returns VBox status code. + * @param pSession Session to clean up processes for. + */ +static int vgsvcGstCtrlSessionCleanupProcesses(const PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + VGSvcVerbose(3, "Cleaning up stopped processes for session %RU32 ...\n", pSession->StartupInfo.uSessionID); + + int rc2 = RTCritSectEnter(&pSession->CritSect); + AssertRC(rc2); + + int rc = VINF_SUCCESS; + + PVBOXSERVICECTRLPROCESS pCurProcess, pNextProcess; + RTListForEachSafe(&pSession->lstProcesses, pCurProcess, pNextProcess, VBOXSERVICECTRLPROCESS, Node) + { + if (ASMAtomicReadBool(&pCurProcess->fStopped)) + { + rc2 = RTCritSectLeave(&pSession->CritSect); + AssertRC(rc2); + + rc = VGSvcGstCtrlProcessWait(pCurProcess, 30 * 1000 /* Wait 30 seconds max. */, NULL /* rc */); + if (RT_SUCCESS(rc)) + { + VGSvcGstCtrlSessionProcessRemove(pSession, pCurProcess); + VGSvcGstCtrlProcessFree(pCurProcess); + } + + rc2 = RTCritSectEnter(&pSession->CritSect); + AssertRC(rc2); + + /* If failed, try next time we're being called. */ + } + } + + rc2 = RTCritSectLeave(&pSession->CritSect); + AssertRC(rc2); + + if (RT_FAILURE(rc)) + VGSvcError("Cleaning up stopped processes for session %RU32 failed with %Rrc\n", pSession->StartupInfo.uSessionID, rc); + + return rc; +} + + +/** + * Creates the process for a guest session. + * + * @return VBox status code. + * @param pSessionStartupInfo Session startup info. + * @param pSessionThread The session thread under construction. + * @param uCtrlSessionThread The session thread debug ordinal. + */ +static int vgsvcVGSvcGstCtrlSessionThreadCreateProcess(const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo, + PVBOXSERVICECTRLSESSIONTHREAD pSessionThread, uint32_t uCtrlSessionThread) +{ + RT_NOREF(uCtrlSessionThread); + + /* + * Is this an anonymous session? Anonymous sessions run with the same + * privileges as the main VBoxService executable. + */ + bool const fAnonymous = pSessionThread->pStartupInfo->pszUser + && pSessionThread->pStartupInfo->pszUser[0] == '\0'; + if (fAnonymous) + { + Assert(!strlen(pSessionThread->pStartupInfo->pszPassword)); + Assert(!strlen(pSessionThread->pStartupInfo->pszDomain)); + + VGSvcVerbose(3, "New anonymous guest session ID=%RU32 created, fFlags=%x, using protocol %RU32\n", + pSessionStartupInfo->uSessionID, + pSessionStartupInfo->fFlags, + pSessionStartupInfo->uProtocol); + } + else + { + VGSvcVerbose(3, "Spawning new guest session ID=%RU32, szUser=%s, szPassword=%s, szDomain=%s, fFlags=%x, using protocol %RU32\n", + pSessionStartupInfo->uSessionID, + pSessionStartupInfo->pszUser, +#ifdef DEBUG + pSessionStartupInfo->pszPassword, +#else + "XXX", /* Never show passwords in release mode. */ +#endif + pSessionStartupInfo->pszDomain, + pSessionStartupInfo->fFlags, + pSessionStartupInfo->uProtocol); + } + + /* + * Spawn a child process for doing the actual session handling. + * Start by assembling the argument list. + */ + char szExeName[RTPATH_MAX]; + char *pszExeName = RTProcGetExecutablePath(szExeName, sizeof(szExeName)); + AssertPtrReturn(pszExeName, VERR_FILENAME_TOO_LONG); + + char szParmSessionID[32]; + RTStrPrintf(szParmSessionID, sizeof(szParmSessionID), "--session-id=%RU32", pSessionThread->pStartupInfo->uSessionID); + + char szParmSessionProto[32]; + RTStrPrintf(szParmSessionProto, sizeof(szParmSessionProto), "--session-proto=%RU32", + pSessionThread->pStartupInfo->uProtocol); +#ifdef DEBUG + char szParmThreadId[32]; + RTStrPrintf(szParmThreadId, sizeof(szParmThreadId), "--thread-id=%RU32", uCtrlSessionThread); +#endif + unsigned idxArg = 0; /* Next index in argument vector. */ + char const *apszArgs[24]; + + apszArgs[idxArg++] = pszExeName; +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV + apszArgs[idxArg++] = VBOXSERVICE_ARG1_UTF8_ARGV; Assert(idxArg == 2); +#endif + apszArgs[idxArg++] = "guestsession"; + apszArgs[idxArg++] = szParmSessionID; + apszArgs[idxArg++] = szParmSessionProto; +#ifdef DEBUG + apszArgs[idxArg++] = szParmThreadId; +#endif + if (!fAnonymous) /* Do we need to pass a user name? */ + { + apszArgs[idxArg++] = "--user"; + apszArgs[idxArg++] = pSessionThread->pStartupInfo->pszUser; + + if (strlen(pSessionThread->pStartupInfo->pszDomain)) + { + apszArgs[idxArg++] = "--domain"; + apszArgs[idxArg++] = pSessionThread->pStartupInfo->pszDomain; + } + } + + /* Add same verbose flags as parent process. */ + char szParmVerbose[32]; + if (g_cVerbosity > 0) + { + unsigned cVs = RT_MIN(g_cVerbosity, RT_ELEMENTS(szParmVerbose) - 2); + szParmVerbose[0] = '-'; + memset(&szParmVerbose[1], 'v', cVs); + szParmVerbose[1 + cVs] = '\0'; + apszArgs[idxArg++] = szParmVerbose; + } + + /* Add log file handling. Each session will have an own + * log file, naming based on the parent log file. */ + char szParmLogFile[sizeof(g_szLogFile) + 128]; + if (g_szLogFile[0]) + { + const char *pszSuffix = RTPathSuffix(g_szLogFile); + if (!pszSuffix) + pszSuffix = strchr(g_szLogFile, '\0'); + size_t cchBase = pszSuffix - g_szLogFile; + + RTTIMESPEC Now; + RTTimeNow(&Now); + char szTime[64]; + RTTimeSpecToString(&Now, szTime, sizeof(szTime)); + + /* Replace out characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */ + static const RTUNICP s_uszValidRangePairs[] = + { + ' ', ' ', + '(', ')', + '-', '.', + '0', '9', + 'A', 'Z', + 'a', 'z', + '_', '_', + 0xa0, 0xd7af, + '\0' + }; + ssize_t cReplaced = RTStrPurgeComplementSet(szTime, s_uszValidRangePairs, '_' /* chReplacement */); + AssertReturn(cReplaced, VERR_INVALID_UTF8_ENCODING); + +#ifndef DEBUG + RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), "%.*s-%RU32-%s-%s%s", + cchBase, g_szLogFile, pSessionStartupInfo->uSessionID, pSessionStartupInfo->pszUser, szTime, pszSuffix); +#else + RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), "%.*s-%RU32-%RU32-%s-%s%s", + cchBase, g_szLogFile, pSessionStartupInfo->uSessionID, uCtrlSessionThread, + pSessionStartupInfo->pszUser, szTime, pszSuffix); +#endif + apszArgs[idxArg++] = "--logfile"; + apszArgs[idxArg++] = szParmLogFile; + } + +#ifdef DEBUG + if (g_Session.fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT) + apszArgs[idxArg++] = "--dump-stdout"; + if (g_Session.fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR) + apszArgs[idxArg++] = "--dump-stderr"; +#endif + apszArgs[idxArg] = NULL; + Assert(idxArg < RT_ELEMENTS(apszArgs)); + + if (g_cVerbosity > 3) + { + VGSvcVerbose(4, "Spawning parameters:\n"); + for (idxArg = 0; apszArgs[idxArg]; idxArg++) + VGSvcVerbose(4, " %s\n", apszArgs[idxArg]); + } + + /* + * Flags. + */ + uint32_t const fProcCreate = RTPROC_FLAGS_PROFILE +#ifdef RT_OS_WINDOWS + | RTPROC_FLAGS_SERVICE + | RTPROC_FLAGS_HIDDEN +#endif + | VBOXSERVICE_PROC_F_UTF8_ARGV; + + /* + * Configure standard handles. + */ + RTHANDLE hStdIn; + int rc = RTPipeCreate(&hStdIn.u.hPipe, &pSessionThread->hKeyPipe, RTPIPE_C_INHERIT_READ); + if (RT_SUCCESS(rc)) + { + hStdIn.enmType = RTHANDLETYPE_PIPE; + + RTHANDLE hStdOutAndErr; + rc = RTFileOpenBitBucket(&hStdOutAndErr.u.hFile, RTFILE_O_WRITE); + if (RT_SUCCESS(rc)) + { + hStdOutAndErr.enmType = RTHANDLETYPE_FILE; + + /* + * Windows: If a domain name is given, construct an UPN (User Principle Name) + * with the domain name built-in, e.g. "joedoe@example.com". + */ + const char *pszUser = pSessionThread->pStartupInfo->pszUser; +#ifdef RT_OS_WINDOWS + char *pszUserUPN = NULL; + if (pSessionThread->pStartupInfo->pszDomain[0]) + { + int cchbUserUPN = RTStrAPrintf(&pszUserUPN, "%s@%s", + pSessionThread->pStartupInfo->pszUser, + pSessionThread->pStartupInfo->pszDomain); + if (cchbUserUPN > 0) + { + pszUser = pszUserUPN; + VGSvcVerbose(3, "Using UPN: %s\n", pszUserUPN); + } + else + rc = VERR_NO_STR_MEMORY; + } + if (RT_SUCCESS(rc)) +#endif + { + /* + * Finally, create the process. + */ + rc = RTProcCreateEx(pszExeName, apszArgs, RTENV_DEFAULT, fProcCreate, + &hStdIn, &hStdOutAndErr, &hStdOutAndErr, + !fAnonymous ? pszUser : NULL, + !fAnonymous ? pSessionThread->pStartupInfo->pszPassword : NULL, + NULL /*pvExtraData*/, + &pSessionThread->hProcess); + } +#ifdef RT_OS_WINDOWS + RTStrFree(pszUserUPN); +#endif + RTFileClose(hStdOutAndErr.u.hFile); + } + + RTPipeClose(hStdIn.u.hPipe); + } + return rc; +} + + +/** + * Creates a guest session. + * + * This will spawn a new VBoxService.exe instance under behalf of the given user + * which then will act as a session host. On successful open, the session will + * be added to the given session thread list. + * + * @return VBox status code. + * @param pList Which list to use to store the session thread in. + * @param pSessionStartupInfo Session startup info. + * @param ppSessionThread Returns newly created session thread on success. + * Optional. + */ +int VGSvcGstCtrlSessionThreadCreate(PRTLISTANCHOR pList, const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo, + PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread) +{ + AssertPtrReturn(pList, VERR_INVALID_POINTER); + AssertPtrReturn(pSessionStartupInfo, VERR_INVALID_POINTER); + /* ppSessionThread is optional. */ + +#ifdef VBOX_STRICT + /* Check for existing session in debug mode. Should never happen because of + * Main consistency. */ + PVBOXSERVICECTRLSESSIONTHREAD pSessionCur; + RTListForEach(pList, pSessionCur, VBOXSERVICECTRLSESSIONTHREAD, Node) + { + AssertMsgReturn( pSessionCur->fStopped == true + || pSessionCur->pStartupInfo->uSessionID != pSessionStartupInfo->uSessionID, + ("Guest session thread ID=%RU32 already exists (fStopped=%RTbool)\n", + pSessionCur->pStartupInfo->uSessionID, pSessionCur->fStopped), VERR_ALREADY_EXISTS); + } +#endif + + /* Static counter to help tracking session thread <-> process relations. */ + static uint32_t s_uCtrlSessionThread = 0; + + /* + * Allocate and initialize the session thread structure. + */ + int rc; + PVBOXSERVICECTRLSESSIONTHREAD pSessionThread = (PVBOXSERVICECTRLSESSIONTHREAD)RTMemAllocZ(sizeof(*pSessionThread)); + if (pSessionThread) + { + //pSessionThread->fShutdown = false; + //pSessionThread->fStarted = false; + //pSessionThread->fStopped = false; + pSessionThread->hKeyPipe = NIL_RTPIPE; + pSessionThread->Thread = NIL_RTTHREAD; + pSessionThread->hProcess = NIL_RTPROCESS; + + /* Duplicate startup info. */ + pSessionThread->pStartupInfo = VbglR3GuestCtrlSessionStartupInfoDup(pSessionStartupInfo); + AssertPtrReturn(pSessionThread->pStartupInfo, VERR_NO_MEMORY); + + /* Generate the secret key. */ + RTRandBytes(pSessionThread->abKey, sizeof(pSessionThread->abKey)); + + rc = RTCritSectInit(&pSessionThread->CritSect); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + /* + * Give the session key to the host so it can validate the client. + */ + if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient)) + { + for (uint32_t i = 0; i < 10; i++) + { + rc = VbglR3GuestCtrlSessionPrepare(g_idControlSvcClient, pSessionStartupInfo->uSessionID, + pSessionThread->abKey, sizeof(pSessionThread->abKey)); + if (rc != VERR_OUT_OF_RESOURCES) + break; + RTThreadSleep(100); + } + } + if (RT_SUCCESS(rc)) + { + s_uCtrlSessionThread++; + + /* + * Start the session child process. + */ + rc = vgsvcVGSvcGstCtrlSessionThreadCreateProcess(pSessionStartupInfo, pSessionThread, s_uCtrlSessionThread); + if (RT_SUCCESS(rc)) + { + /* + * Start the session thread. + */ + rc = RTThreadCreateF(&pSessionThread->Thread, vgsvcGstCtrlSessionThread, pSessionThread /*pvUser*/, 0 /*cbStack*/, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctls%RU32", s_uCtrlSessionThread); + if (RT_SUCCESS(rc)) + { + /* Wait for the thread to initialize. */ + rc = RTThreadUserWait(pSessionThread->Thread, RT_MS_1MIN); + if ( RT_SUCCESS(rc) + && !ASMAtomicReadBool(&pSessionThread->fShutdown)) + { + VGSvcVerbose(2, "Thread for session ID=%RU32 started\n", pSessionThread->pStartupInfo->uSessionID); + + ASMAtomicXchgBool(&pSessionThread->fStarted, true); + + /* Add session to list. */ + RTListAppend(pList, &pSessionThread->Node); + if (ppSessionThread) /* Return session if wanted. */ + *ppSessionThread = pSessionThread; + return VINF_SUCCESS; + } + + /* + * Bail out. + */ + VGSvcError("Thread for session ID=%RU32 failed to start, rc=%Rrc\n", + pSessionThread->pStartupInfo->uSessionID, rc); + if (RT_SUCCESS_NP(rc)) + rc = VERR_CANT_CREATE; /** @todo Find a better rc. */ + } + else + VGSvcError("Creating session thread failed, rc=%Rrc\n", rc); + + RTProcTerminate(pSessionThread->hProcess); + uint32_t cMsWait = 1; + while ( RTProcWait(pSessionThread->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, NULL) == VERR_PROCESS_RUNNING + && cMsWait <= 9) /* 1023 ms */ + { + RTThreadSleep(cMsWait); + cMsWait <<= 1; + } + } + + if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient)) + VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, pSessionStartupInfo->uSessionID); + } + else + VGSvcVerbose(3, "VbglR3GuestCtrlSessionPrepare failed: %Rrc\n", rc); + RTPipeClose(pSessionThread->hKeyPipe); + pSessionThread->hKeyPipe = NIL_RTPIPE; + RTCritSectDelete(&pSessionThread->CritSect); + } + RTMemFree(pSessionThread); + } + else + rc = VERR_NO_MEMORY; + + VGSvcVerbose(3, "Spawning session thread returned returned rc=%Rrc\n", rc); + return rc; +} + + +/** + * Waits for a formerly opened guest session process to close. + * + * @return VBox status code. + * @param pThread Guest session thread to wait for. + * @param uTimeoutMS Waiting timeout (in ms). + * @param fFlags Closing flags. + */ +int VGSvcGstCtrlSessionThreadWait(PVBOXSERVICECTRLSESSIONTHREAD pThread, uint32_t uTimeoutMS, uint32_t fFlags) +{ + RT_NOREF(fFlags); + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + /** @todo Validate closing flags. */ + + AssertMsgReturn(pThread->Thread != NIL_RTTHREAD, + ("Guest session thread of session %p does not exist when it should\n", pThread), + VERR_NOT_FOUND); + + int rc = VINF_SUCCESS; + + /* + * The spawned session process should have received the same closing request, + * so just wait for the process to close. + */ + if (ASMAtomicReadBool(&pThread->fStarted)) + { + /* Ask the thread to shutdown. */ + ASMAtomicXchgBool(&pThread->fShutdown, true); + + VGSvcVerbose(3, "Waiting for session thread ID=%RU32 to close (%RU32ms) ...\n", + pThread->pStartupInfo->uSessionID, uTimeoutMS); + + int rcThread; + rc = RTThreadWait(pThread->Thread, uTimeoutMS, &rcThread); + if (RT_SUCCESS(rc)) + { + AssertMsg(pThread->fStopped, ("Thread of session ID=%RU32 not in stopped state when it should\n", + pThread->pStartupInfo->uSessionID)); + + VGSvcVerbose(3, "Session thread ID=%RU32 ended with rc=%Rrc\n", pThread->pStartupInfo->uSessionID, rcThread); + } + else + VGSvcError("Waiting for session thread ID=%RU32 to close failed with rc=%Rrc\n", pThread->pStartupInfo->uSessionID, rc); + } + else + VGSvcVerbose(3, "Thread for session ID=%RU32 not in started state, skipping wait\n", pThread->pStartupInfo->uSessionID); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Waits for the specified session thread to end and remove + * it from the session thread list. + * + * @return VBox status code. + * @param pThread Session thread to destroy. + * @param fFlags Closing flags. + */ +int VGSvcGstCtrlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pThread, uint32_t fFlags) +{ + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + AssertPtrReturn(pThread->pStartupInfo, VERR_WRONG_ORDER); + + const uint32_t uSessionID = pThread->pStartupInfo->uSessionID; + + VGSvcVerbose(3, "Destroying session ID=%RU32 ...\n", uSessionID); + + int rc = VGSvcGstCtrlSessionThreadWait(pThread, 5 * 60 * 1000 /* 5 minutes timeout */, fFlags); + if (RT_SUCCESS(rc)) + { + VbglR3GuestCtrlSessionStartupInfoFree(pThread->pStartupInfo); + pThread->pStartupInfo = NULL; + + RTPipeClose(pThread->hKeyPipe); + pThread->hKeyPipe = NIL_RTPIPE; + + RTCritSectDelete(&pThread->CritSect); + + /* Remove session from list and destroy object. */ + RTListNodeRemove(&pThread->Node); + + RTMemFree(pThread); + pThread = NULL; + } + + VGSvcVerbose(3, "Destroyed session ID=%RU32 with %Rrc\n", uSessionID, rc); + return rc; +} + +/** + * Close all open guest session threads. + * + * @note Caller is responsible for locking! + * + * @return VBox status code. + * @param pList Which list to close the session threads for. + * @param fFlags Closing flags. + */ +int VGSvcGstCtrlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t fFlags) +{ + AssertPtrReturn(pList, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + /*int rc = VbglR3GuestCtrlClose + if (RT_FAILURE(rc)) + VGSvcError("Cancelling pending waits failed; rc=%Rrc\n", rc);*/ + + PVBOXSERVICECTRLSESSIONTHREAD pSessIt; + PVBOXSERVICECTRLSESSIONTHREAD pSessItNext; + RTListForEachSafe(pList, pSessIt, pSessItNext, VBOXSERVICECTRLSESSIONTHREAD, Node) + { + int rc2 = VGSvcGstCtrlSessionThreadDestroy(pSessIt, fFlags); + if (RT_FAILURE(rc2)) + { + VGSvcError("Closing session thread '%s' failed with rc=%Rrc\n", RTThreadGetName(pSessIt->Thread), rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + /* Keep going. */ + } + } + + VGSvcVerbose(4, "Destroying guest session threads ended with %Rrc\n", rc); + return rc; +} + + +/** + * Main function for the session process. + * + * @returns exit code. + * @param argc Argument count. + * @param argv Argument vector (UTF-8). + */ +RTEXITCODE VGSvcGstCtrlSessionSpawnInit(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--domain", VBOXSERVICESESSIONOPT_DOMAIN, RTGETOPT_REQ_STRING }, +#ifdef DEBUG + { "--dump-stdout", VBOXSERVICESESSIONOPT_DUMP_STDOUT, RTGETOPT_REQ_NOTHING }, + { "--dump-stderr", VBOXSERVICESESSIONOPT_DUMP_STDERR, RTGETOPT_REQ_NOTHING }, +#endif + { "--logfile", VBOXSERVICESESSIONOPT_LOG_FILE, RTGETOPT_REQ_STRING }, + { "--user", VBOXSERVICESESSIONOPT_USERNAME, RTGETOPT_REQ_STRING }, + { "--session-id", VBOXSERVICESESSIONOPT_SESSION_ID, RTGETOPT_REQ_UINT32 }, + { "--session-proto", VBOXSERVICESESSIONOPT_SESSION_PROTO, RTGETOPT_REQ_UINT32 }, +#ifdef DEBUG + { "--thread-id", VBOXSERVICESESSIONOPT_THREAD_ID, RTGETOPT_REQ_UINT32 }, +#endif /* DEBUG */ + { "--verbose", 'v', RTGETOPT_REQ_NOTHING } + }; + + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, + s_aOptions, RT_ELEMENTS(s_aOptions), + 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + uint32_t fSession = VBOXSERVICECTRLSESSION_FLAG_SPAWN; + + /* Protocol and session ID must be specified explicitly. */ + g_Session.StartupInfo.uProtocol = UINT32_MAX; + g_Session.StartupInfo.uSessionID = UINT32_MAX; + + int ch; + RTGETOPTUNION ValueUnion; + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case VBOXSERVICESESSIONOPT_DOMAIN: + /* Information not needed right now, skip. */ + break; +#ifdef DEBUG + case VBOXSERVICESESSIONOPT_DUMP_STDOUT: + fSession |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT; + break; + + case VBOXSERVICESESSIONOPT_DUMP_STDERR: + fSession |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR; + break; +#endif + case VBOXSERVICESESSIONOPT_SESSION_ID: + g_Session.StartupInfo.uSessionID = ValueUnion.u32; + break; + + case VBOXSERVICESESSIONOPT_SESSION_PROTO: + g_Session.StartupInfo.uProtocol = ValueUnion.u32; + break; +#ifdef DEBUG + case VBOXSERVICESESSIONOPT_THREAD_ID: + /* Not handled. Mainly for processs listing. */ + break; +#endif + case VBOXSERVICESESSIONOPT_LOG_FILE: + { + int rc = RTStrCopy(g_szLogFile, sizeof(g_szLogFile), ValueUnion.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error copying log file name: %Rrc", rc); + break; + } + + case VBOXSERVICESESSIONOPT_USERNAME: + /* Information not needed right now, skip. */ + break; + + /** @todo Implement help? */ + + case 'v': + g_cVerbosity++; + break; + + case VINF_GETOPT_NOT_OPTION: + { + if (!RTStrICmp(ValueUnion.psz, VBOXSERVICECTRLSESSION_GETOPT_PREFIX)) + break; + /* else fall through and bail out. */ + RT_FALL_THROUGH(); + } + default: + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown argument '%s'", ValueUnion.psz); + } + } + + /* Check that we've got all the required options. */ + if (g_Session.StartupInfo.uProtocol == UINT32_MAX) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No protocol version specified"); + + if (g_Session.StartupInfo.uSessionID == UINT32_MAX) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No session ID specified"); + + /* Init the session object. */ + int rc = VGSvcGstCtrlSessionInit(&g_Session, fSession); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_INIT, "Failed to initialize session object, rc=%Rrc\n", rc); + + rc = VGSvcLogCreate(g_szLogFile[0] ? g_szLogFile : NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_INIT, "Failed to create log file '%s', rc=%Rrc\n", + g_szLogFile[0] ? g_szLogFile : "<None>", rc); + + RTEXITCODE rcExit = vgsvcGstCtrlSessionSpawnWorker(&g_Session); + + VGSvcLogDestroy(); + return rcExit; +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp new file mode 100644 index 00000000..76656414 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_vgsvc_cpuhotplug VBoxService - CPU Hot-Plugging + * + * The CPU Hot-Plugging subservice helps execute and coordinate CPU hot-plugging + * between the guest OS and the VMM. + * + * CPU Hot-Plugging is useful for reallocating CPU resources from one VM to + * other VMs or/and the host. It talks to the VMM via VMMDev, new hot-plugging + * events being signalled with an interrupt (no polling). + * + * Currently only supported for linux guests. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" + +#ifdef RT_OS_LINUX +# include <iprt/linux/sysfs.h> +# include <errno.h> /* For the sysfs API */ +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifdef RT_OS_LINUX + +/** @name Paths to access the CPU device + * @{ + */ +# define SYSFS_ACPI_CPU_PATH "/sys/devices" +# define SYSFS_CPU_PATH "/sys/devices/system/cpu" +/** @} */ + +/** Path component for the ACPI CPU path. */ +typedef struct SYSFSCPUPATHCOMP +{ + /** Flag whether the name is suffixed with a number */ + bool fNumberedSuffix; + /** Name of the component */ + const char *pcszName; +} SYSFSCPUPATHCOMP, *PSYSFSCPUPATHCOMP; +/** Pointer to a const component. */ +typedef const SYSFSCPUPATHCOMP *PCSYSFSCPUPATHCOMP; + +/** + * Structure which defines how the entries are assembled. + */ +typedef struct SYSFSCPUPATH +{ + /** Id when probing for the correct path. */ + uint32_t uId; + /** Array holding the possible components. */ + PCSYSFSCPUPATHCOMP aComponentsPossible; + /** Number of entries in the array, excluding the terminator. */ + unsigned cComponents; + /** Directory handle */ + RTDIR hDir; + /** Current directory to try. */ + char *pszPath; +} SYSFSCPUPATH, *PSYSFSCPUPATH; + +/** Content of uId if the path wasn't probed yet. */ +# define ACPI_CPU_PATH_NOT_PROBED UINT32_MAX +#endif /* RT_OS_LINUX*/ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef RT_OS_LINUX +/** Possible combinations of all path components for level 1. */ +static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl1[] = +{ + /** LNXSYSTEM:<id> */ + { true, "LNXSYSTM:*" } +}; + +/** Possible combinations of all path components for level 2. */ +static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl2[] = +{ + /** device:<id> */ + { true, "device:*" }, + /** LNXSYBUS:<id> */ + { true, "LNXSYBUS:*" } +}; + +/** Possible combinations of all path components for level 3 */ +static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl3[] = +{ + /** ACPI0004:<id> */ + { true, "ACPI0004:*" } +}; + +/** Possible combinations of all path components for level 4 */ +static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl4[] = +{ + /** LNXCPU:<id> */ + { true, "LNXCPU:*" }, + /** ACPI_CPU:<id> */ + { true, "ACPI_CPU:*" } +}; + +/** All possible combinations. */ +static SYSFSCPUPATH g_aAcpiCpuPath[] = +{ + /** Level 1 */ + { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl1, RT_ELEMENTS(g_aAcpiCpuPathLvl1), NULL, NULL }, + /** Level 2 */ + { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl2, RT_ELEMENTS(g_aAcpiCpuPathLvl2), NULL, NULL }, + /** Level 3 */ + { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl3, RT_ELEMENTS(g_aAcpiCpuPathLvl3), NULL, NULL }, + /** Level 4 */ + { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl4, RT_ELEMENTS(g_aAcpiCpuPathLvl4), NULL, NULL }, +}; + +/** + * Possible directories to get to the topology directory for reading core and package id. + * + * @remark: This is not part of the path above because the eject file is not in one of the directories + * below and would make the hot unplug code fail. + */ +static const char *g_apszTopologyPath[] = +{ + "sysdev", + "physical_node" +}; + +#endif /* RT_OS_LINUX*/ + + +#ifdef RT_OS_LINUX + +/** + * Probes for the correct path to the ACPI CPU object in sysfs for the + * various different kernel versions and distro's. + * + * @returns VBox status code. + */ +static int vgsvcCpuHotPlugProbePath(void) +{ + int rc = VINF_SUCCESS; + + /* Probe for the correct path if we didn't already. */ + if (RT_UNLIKELY(g_aAcpiCpuPath[0].uId == ACPI_CPU_PATH_NOT_PROBED)) + { + char *pszPath = NULL; /** < Current path, increasing while we dig deeper. */ + + pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH); + if (!pszPath) + return VERR_NO_MEMORY; + + /* + * Simple algorithm to find the path. + * Performance is not a real problem because it is + * only executed once. + */ + for (unsigned iLvlCurr = 0; iLvlCurr < RT_ELEMENTS(g_aAcpiCpuPath); iLvlCurr++) + { + PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr]; + + for (unsigned iCompCurr = 0; iCompCurr < pAcpiCpuPathLvl->cComponents; iCompCurr++) + { + PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[iCompCurr]; + + /* Open the directory */ + RTDIR hDirCurr = NIL_RTDIR; + char *pszPathTmp = RTPathJoinA(pszPath, pPathComponent->pcszName); + if (pszPathTmp) + { + rc = RTDirOpenFiltered(&hDirCurr, pszPathTmp, RTDIRFILTER_WINNT, 0 /*fFlags*/); + RTStrFree(pszPathTmp); + } + else + rc = VERR_NO_STR_MEMORY; + if (RT_FAILURE(rc)) + break; + + /* Search if the current directory contains one of the possible parts. */ + size_t cchName = strlen(pPathComponent->pcszName); + RTDIRENTRY DirFolderContent; + bool fFound = false; + + /* Get rid of the * filter which is in the path component. */ + if (pPathComponent->fNumberedSuffix) + cchName--; + + while (RT_SUCCESS(RTDirRead(hDirCurr, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */ + { + if ( DirFolderContent.cbName >= cchName + && !strncmp(DirFolderContent.szName, pPathComponent->pcszName, cchName)) + { + /* Found, use the complete name to dig deeper. */ + fFound = true; + pAcpiCpuPathLvl->uId = iCompCurr; + char *pszPathLvl = RTPathJoinA(pszPath, DirFolderContent.szName); + if (pszPathLvl) + { + RTStrFree(pszPath); + pszPath = pszPathLvl; + } + else + rc = VERR_NO_STR_MEMORY; + break; + } + } + RTDirClose(hDirCurr); + + if (fFound) + break; + } /* For every possible component. */ + + /* No matching component for this part, no need to continue */ + if (RT_FAILURE(rc)) + break; + } /* For every level */ + + VGSvcVerbose(1, "Final path after probing %s rc=%Rrc\n", pszPath, rc); + RTStrFree(pszPath); + } + + return rc; +} + + +/** + * Returns the path of the ACPI CPU device with the given core and package ID. + * + * @returns VBox status code. + * @param ppszPath Where to store the path. + * @param idCpuCore The core ID of the CPU. + * @param idCpuPackage The package ID of the CPU. + */ +static int vgsvcCpuHotPlugGetACPIDevicePath(char **ppszPath, uint32_t idCpuCore, uint32_t idCpuPackage) +{ + int rc = VINF_SUCCESS; + + AssertPtrReturn(ppszPath, VERR_INVALID_PARAMETER); + + rc = vgsvcCpuHotPlugProbePath(); + if (RT_SUCCESS(rc)) + { + /* Build the path from all components. */ + bool fFound = false; + unsigned iLvlCurr = 0; + char *pszPath = NULL; + char *pszPathDir = NULL; + PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr]; + + /* Init everything. */ + Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED); + pszPath = RTPathJoinA(SYSFS_ACPI_CPU_PATH, pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId].pcszName); + if (!pszPath) + return VERR_NO_STR_MEMORY; + + pAcpiCpuPathLvl->pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH); + if (!pAcpiCpuPathLvl->pszPath) + { + RTStrFree(pszPath); + return VERR_NO_STR_MEMORY; + } + + /* Open the directory */ + rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->hDir, pszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + RTStrFree(pszPath); + pszPath = NULL; + + /* Search for CPU */ + while (!fFound) + { + /* Get the next directory. */ + RTDIRENTRY DirFolderContent; + rc = RTDirRead(pAcpiCpuPathLvl->hDir, &DirFolderContent, NULL); + if (RT_SUCCESS(rc)) + { + /* Create the new path. */ + char *pszPathCurr = RTPathJoinA(pAcpiCpuPathLvl->pszPath, DirFolderContent.szName); + if (!pszPathCurr) + { + rc = VERR_NO_STR_MEMORY; + break; + } + + /* If this is the last level check for the given core and package id. */ + if (iLvlCurr == RT_ELEMENTS(g_aAcpiCpuPath) - 1) + { + /* Get the sysdev */ + uint32_t idCore = 0; + uint32_t idPackage = 0; + + for (unsigned i = 0; i < RT_ELEMENTS(g_apszTopologyPath); i++) + { + int64_t i64Core = 0; + int64_t i64Package = 0; + + int rc2 = RTLinuxSysFsReadIntFile(10, &i64Core, "%s/%s/topology/core_id", + pszPathCurr, g_apszTopologyPath[i]); + if (RT_SUCCESS(rc2)) + rc2 = RTLinuxSysFsReadIntFile(10, &i64Package, "%s/%s/topology/physical_package_id", + pszPathCurr, g_apszTopologyPath[i]); + + if (RT_SUCCESS(rc2)) + { + idCore = (uint32_t)i64Core; + idPackage = (uint32_t)i64Package; + break; + } + } + + if ( idCore == idCpuCore + && idPackage == idCpuPackage) + { + /* Return the path */ + pszPath = pszPathCurr; + fFound = true; + VGSvcVerbose(3, "CPU found\n"); + break; + } + else + { + /* Get the next directory. */ + RTStrFree(pszPathCurr); + pszPathCurr = NULL; + VGSvcVerbose(3, "CPU doesn't match, next directory\n"); + } + } + else + { + /* Go deeper */ + iLvlCurr++; + + VGSvcVerbose(3, "Going deeper (iLvlCurr=%u)\n", iLvlCurr); + + pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr]; + + Assert(pAcpiCpuPathLvl->hDir == NIL_RTDIR); + Assert(!pAcpiCpuPathLvl->pszPath); + pAcpiCpuPathLvl->pszPath = pszPathCurr; + PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId]; + + Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED); + + pszPathDir = RTPathJoinA(pszPathCurr, pPathComponent->pcszName); + if (!pszPathDir) + { + rc = VERR_NO_STR_MEMORY; + break; + } + + VGSvcVerbose(3, "New path %s\n", pszPathDir); + + /* Open the directory */ + rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->hDir, pszPathDir, RTDIRFILTER_WINNT, 0 /*fFlags*/); + RTStrFree(pszPathDir); + pszPathDir = NULL; + if (RT_FAILURE(rc)) + break; + } + } + else + { + RTDirClose(pAcpiCpuPathLvl->hDir); + RTStrFree(pAcpiCpuPathLvl->pszPath); + pAcpiCpuPathLvl->hDir = NIL_RTDIR; + pAcpiCpuPathLvl->pszPath = NULL; + + /* + * If we reached the end we didn't find the matching path + * meaning the CPU is already offline. + */ + if (!iLvlCurr) + { + rc = VERR_NOT_FOUND; + break; + } + + iLvlCurr--; + pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr]; + VGSvcVerbose(3, "Directory not found, going back (iLvlCurr=%u)\n", iLvlCurr); + } + } /* while not found */ + } /* Successful init */ + + /* Cleanup */ + for (unsigned i = 0; i < RT_ELEMENTS(g_aAcpiCpuPath); i++) + { + if (g_aAcpiCpuPath[i].hDir) + RTDirClose(g_aAcpiCpuPath[i].hDir); + if (g_aAcpiCpuPath[i].pszPath) + RTStrFree(g_aAcpiCpuPath[i].pszPath); + g_aAcpiCpuPath[i].hDir = NIL_RTDIR; + g_aAcpiCpuPath[i].pszPath = NULL; + } + if (pszPathDir) + RTStrFree(pszPathDir); + if (RT_FAILURE(rc) && pszPath) + RTStrFree(pszPath); + + if (RT_SUCCESS(rc)) + *ppszPath = pszPath; + } + + return rc; +} + +#endif /* RT_OS_LINUX */ + +/** + * Handles VMMDevCpuEventType_Plug. + * + * @param idCpuCore The CPU core ID. + * @param idCpuPackage The CPU package ID. + */ +static void vgsvcCpuHotPlugHandlePlugEvent(uint32_t idCpuCore, uint32_t idCpuPackage) +{ +#ifdef RT_OS_LINUX + /* + * The topology directory (containing the physical and core id properties) + * is not available until the CPU is online. So we just iterate over all directories + * and enable the first CPU which is not online already. + * Because the directory might not be available immediately we try a few times. + * + */ + /** @todo Maybe use udev to monitor hot-add events from the kernel */ + bool fCpuOnline = false; + unsigned cTries = 5; + + do + { + RTDIR hDirDevices = NULL; + int rc = RTDirOpen(&hDirDevices, SYSFS_CPU_PATH); + if (RT_SUCCESS(rc)) + { + RTDIRENTRY DirFolderContent; + while (RT_SUCCESS(RTDirRead(hDirDevices, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */ + { + /* Check if this is a CPU object which can be brought online. */ + if (RTLinuxSysFsExists("%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName)) + { + /* Check the status of the CPU by reading the online flag. */ + int64_t i64Status = 0; + rc = RTLinuxSysFsReadIntFile(10 /*uBase*/, &i64Status, "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName); + if ( RT_SUCCESS(rc) + && i64Status == 0) + { + /* CPU is offline, turn it on. */ + rc = RTLinuxSysFsWriteU8File(10 /*uBase*/, 1, "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was brought online\n", idCpuPackage, idCpuCore); + fCpuOnline = true; + break; + } + } + else if (RT_FAILURE(rc)) + VGSvcError("CpuHotPlug: Failed to open '%s/%s/online' rc=%Rrc\n", + SYSFS_CPU_PATH, DirFolderContent.szName, rc); + else + { + /* + * Check whether the topology matches what we got (which means someone raced us and brought the CPU + * online already). + */ + int64_t i64Core = 0; + int64_t i64Package = 0; + + int rc2 = RTLinuxSysFsReadIntFile(10, &i64Core, "%s/%s/topology/core_id", + SYSFS_CPU_PATH, DirFolderContent.szName); + if (RT_SUCCESS(rc2)) + rc2 = RTLinuxSysFsReadIntFile(10, &i64Package, "%s/%s/topology/physical_package_id", + SYSFS_CPU_PATH, DirFolderContent.szName); + if ( RT_SUCCESS(rc2) + && idCpuPackage == i64Package + && idCpuCore == i64Core) + { + VGSvcVerbose(1, "CpuHotPlug: '%s' is already online\n", DirFolderContent.szName); + fCpuOnline = true; + break; + } + } + } + } + RTDirClose(hDirDevices); + } + else + VGSvcError("CpuHotPlug: Failed to open path %s rc=%Rrc\n", SYSFS_CPU_PATH, rc); + + /* Sleep a bit */ + if (!fCpuOnline) + RTThreadSleep(100); + + } while ( !fCpuOnline + && cTries-- > 0); +#else +# error "Port me" +#endif +} + + +/** + * Handles VMMDevCpuEventType_Unplug. + * + * @param idCpuCore The CPU core ID. + * @param idCpuPackage The CPU package ID. + */ +static void vgsvcCpuHotPlugHandleUnplugEvent(uint32_t idCpuCore, uint32_t idCpuPackage) +{ +#ifdef RT_OS_LINUX + char *pszCpuDevicePath = NULL; + int rc = vgsvcCpuHotPlugGetACPIDevicePath(&pszCpuDevicePath, idCpuCore, idCpuPackage); + if (RT_SUCCESS(rc)) + { + RTFILE hFileCpuEject; + rc = RTFileOpenF(&hFileCpuEject, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, "%s/eject", pszCpuDevicePath); + if (RT_SUCCESS(rc)) + { + /* Write a 1 to eject the CPU */ + rc = RTFileWrite(hFileCpuEject, "1", 1, NULL); + if (RT_SUCCESS(rc)) + VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was ejected\n", idCpuPackage, idCpuCore); + else + VGSvcError("CpuHotPlug: Failed to eject CPU %u/%u rc=%Rrc\n", idCpuPackage, idCpuCore, rc); + + RTFileClose(hFileCpuEject); + } + else + VGSvcError("CpuHotPlug: Failed to open '%s/eject' rc=%Rrc\n", pszCpuDevicePath, rc); + RTStrFree(pszCpuDevicePath); + } + else if (rc == VERR_NOT_FOUND) + VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was aleady ejected by someone else!\n", idCpuPackage, idCpuCore); + else + VGSvcError("CpuHotPlug: Failed to get CPU device path rc=%Rrc\n", rc); +#else +# error "Port me" +#endif +} + + +/** @interface_method_impl{VBOXSERVICE,pfnWorker} */ +static DECLCALLBACK(int) vgsvcCpuHotPlugWorker(bool volatile *pfShutdown) +{ + /* + * Tell the control thread that it can continue spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Enable the CPU hotplug notifier. + */ + int rc = VbglR3CpuHotPlugInit(); + if (RT_FAILURE(rc)) + return rc; + + /* + * The Work Loop. + */ + for (;;) + { + /* Wait for CPU hot-plugging event. */ + uint32_t idCpuCore; + uint32_t idCpuPackage; + VMMDevCpuEventType enmEventType; + rc = VbglR3CpuHotPlugWaitForEvent(&enmEventType, &idCpuCore, &idCpuPackage); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "CpuHotPlug: Event happened idCpuCore=%u idCpuPackage=%u enmEventType=%d\n", + idCpuCore, idCpuPackage, enmEventType); + switch (enmEventType) + { + case VMMDevCpuEventType_Plug: + vgsvcCpuHotPlugHandlePlugEvent(idCpuCore, idCpuPackage); + break; + + case VMMDevCpuEventType_Unplug: + vgsvcCpuHotPlugHandleUnplugEvent(idCpuCore, idCpuPackage); + break; + + default: + { + static uint32_t s_iErrors = 0; + if (s_iErrors++ < 10) + VGSvcError("CpuHotPlug: Unknown event: idCpuCore=%u idCpuPackage=%u enmEventType=%d\n", + idCpuCore, idCpuPackage, enmEventType); + break; + } + } + } + else if (rc != VERR_INTERRUPTED && rc != VERR_TRY_AGAIN) + { + VGSvcError("CpuHotPlug: VbglR3CpuHotPlugWaitForEvent returned %Rrc\n", rc); + break; + } + + if (*pfShutdown) + break; + } + + VbglR3CpuHotPlugTerm(); + return rc; +} + + +/** @interface_method_impl{VBOXSERVICE,pfnStop} */ +static DECLCALLBACK(void) vgsvcCpuHotPlugStop(void) +{ + VbglR3InterruptEventWaits(); + return; +} + + +/** + * The 'CpuHotPlug' service description. + */ +VBOXSERVICE g_CpuHotPlug = +{ + /* pszName. */ + "cpuhotplug", + /* pszDescription. */ + "CPU hot-plugging monitor", + /* pszUsage. */ + NULL, + /* pszOptions. */ + NULL, + /* methods */ + VGSvcDefaultPreInit, + VGSvcDefaultOption, + VGSvcDefaultInit, + vgsvcCpuHotPlugWorker, + vgsvcCpuHotPlugStop, + VGSvcDefaultTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h b/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h new file mode 100644 index 00000000..26b477b1 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#endif + +#include <iprt/list.h> +#include <iprt/critsect.h> +#include <iprt/path.h> /* RTPATH_MAX */ +#include <iprt/stdarg.h> + +#include <VBox/VBoxGuestLib.h> +#include <VBox/HostServices/GuestControlSvc.h> + + +#if !defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING) +/** Special argv[1] value that indicates that argv is UTF-8. + * This causes RTR3Init to be called with RTR3INIT_FLAGS_UTF8_ARGV and helps + * work around potential issues caused by a user's locale config not being + * UTF-8. See @bugref{10153}. + * + * @note We don't need this on windows and it would be harmful to enable it + * as the argc/argv vs __argc/__argv comparison would fail and we would + * not use the unicode command line to create a UTF-8 argv. Since the + * original argv is ANSI, it may be missing codepoints not present in + * the ANSI code page of the process. */ +# define VBOXSERVICE_ARG1_UTF8_ARGV "--utf8-argv" +#endif +/** RTProcCreateEx flags corresponding to VBOXSERVICE_ARG1_UTF8_ARGV. */ +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV +# define VBOXSERVICE_PROC_F_UTF8_ARGV RTPROC_FLAGS_UTF8_ARGV +#else +# define VBOXSERVICE_PROC_F_UTF8_ARGV 0 +#endif + + +/** + * A service descriptor. + */ +typedef struct +{ + /** The short service name. */ + const char *pszName; + /** The longer service name. */ + const char *pszDescription; + /** The usage options stuff for the --help screen. */ + const char *pszUsage; + /** The option descriptions for the --help screen. */ + const char *pszOptions; + + /** + * Called before parsing arguments. + * @returns VBox status code. + */ + DECLCALLBACKMEMBER(int, pfnPreInit,(void)); + + /** + * Tries to parse the given command line option. + * + * @returns 0 if we parsed, -1 if it didn't and anything else means exit. + * @param ppszShort If not NULL it points to the short option iterator. a short argument. + * If NULL examine argv[*pi]. + * @param argc The argument count. + * @param argv The argument vector. + * @param pi The argument vector index. Update if any value(s) are eaten. + */ + DECLCALLBACKMEMBER(int, pfnOption,(const char **ppszShort, int argc, char **argv, int *pi)); + + /** + * Called before parsing arguments. + * @returns VBox status code. + */ + DECLCALLBACKMEMBER(int, pfnInit,(void)); + + /** Called from the worker thread. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if exitting because *pfShutdown was set. + * @param pfShutdown Pointer to a per service termination flag to check + * before and after blocking. + */ + DECLCALLBACKMEMBER(int, pfnWorker,(bool volatile *pfShutdown)); + + /** + * Stops a service. + */ + DECLCALLBACKMEMBER(void, pfnStop,(void)); + + /** + * Does termination cleanups. + * + * @remarks This may be called even if pfnInit hasn't been called! + */ + DECLCALLBACKMEMBER(void, pfnTerm,(void)); +} VBOXSERVICE; +/** Pointer to a VBOXSERVICE. */ +typedef VBOXSERVICE *PVBOXSERVICE; +/** Pointer to a const VBOXSERVICE. */ +typedef VBOXSERVICE const *PCVBOXSERVICE; + +/* Default call-backs for services which do not need special behaviour. */ +DECLCALLBACK(int) VGSvcDefaultPreInit(void); +DECLCALLBACK(int) VGSvcDefaultOption(const char **ppszShort, int argc, char **argv, int *pi); +DECLCALLBACK(int) VGSvcDefaultInit(void); +DECLCALLBACK(void) VGSvcDefaultTerm(void); + +/** The service name. + * @note Used on windows to name the service as well as the global mutex. */ +#define VBOXSERVICE_NAME "VBoxService" + +#ifdef RT_OS_WINDOWS +/** The friendly service name. */ +# define VBOXSERVICE_FRIENDLY_NAME "VirtualBox Guest Additions Service" +/** The service description (only W2K+ atm) */ +# define VBOXSERVICE_DESCRIPTION "Manages VM runtime information, time synchronization, guest control execution and miscellaneous utilities for guest operating systems." +/** The following constant may be defined by including NtStatus.h. */ +# define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif /* RT_OS_WINDOWS */ + +#ifdef VBOX_WITH_GUEST_PROPS +/** + * A guest property cache. + */ +typedef struct VBOXSERVICEVEPROPCACHE +{ + /** The client ID for HGCM communication. */ + uint32_t uClientID; + /** Head in a list of VBOXSERVICEVEPROPCACHEENTRY nodes. */ + RTLISTANCHOR NodeHead; + /** Critical section for thread-safe use. */ + RTCRITSECT CritSect; +} VBOXSERVICEVEPROPCACHE; +/** Pointer to a guest property cache. */ +typedef VBOXSERVICEVEPROPCACHE *PVBOXSERVICEVEPROPCACHE; + +/** + * An entry in the property cache (VBOXSERVICEVEPROPCACHE). + */ +typedef struct VBOXSERVICEVEPROPCACHEENTRY +{ + /** Node to successor. + * @todo r=bird: This is not really the node to the successor, but + * rather the OUR node in the list. If it helps, remember that + * its a doubly linked list. */ + RTLISTNODE NodeSucc; + /** Name (and full path) of guest property. */ + char *pszName; + /** The last value stored (for reference). */ + char *pszValue; + /** Reset value to write if property is temporary. If NULL, it will be + * deleted. */ + char *pszValueReset; + /** Flags. */ + uint32_t fFlags; +} VBOXSERVICEVEPROPCACHEENTRY; +/** Pointer to a cached guest property. */ +typedef VBOXSERVICEVEPROPCACHEENTRY *PVBOXSERVICEVEPROPCACHEENTRY; + +#endif /* VBOX_WITH_GUEST_PROPS */ + +RT_C_DECLS_BEGIN + +extern char *g_pszProgName; +extern unsigned g_cVerbosity; +extern char g_szLogFile[RTPATH_MAX + 128]; +extern uint32_t g_DefaultInterval; +extern VBOXSERVICE g_TimeSync; +#ifdef VBOX_WITH_VBOXSERVICE_CLIPBOARD +extern VBOXSERVICE g_Clipboard; +#endif +extern VBOXSERVICE g_Control; +extern VBOXSERVICE g_VMInfo; +extern VBOXSERVICE g_CpuHotPlug; +#ifdef VBOX_WITH_VBOXSERVICE_MANAGEMENT +extern VBOXSERVICE g_MemBalloon; +extern VBOXSERVICE g_VMStatistics; +#endif +#ifdef VBOX_WITH_VBOXSERVICE_PAGE_SHARING +extern VBOXSERVICE g_PageSharing; +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS +extern VBOXSERVICE g_AutoMount; +#endif +#ifdef DEBUG +extern RTCRITSECT g_csLog; /* For guest process stdout dumping. */ +#endif + +extern RTEXITCODE VGSvcSyntax(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2); +extern RTEXITCODE VGSvcError(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2); +extern void VGSvcVerbose(unsigned iLevel, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(2, 3); +extern int VGSvcLogCreate(const char *pszLogFile); +extern void VGSvcLogV(const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(1, 0); +extern void VGSvcLogDestroy(void); +extern int VGSvcArgUInt32(int argc, char **argv, const char *psz, int *pi, uint32_t *pu32, + uint32_t u32Min, uint32_t u32Max); + +/* Exposing the following bits because of windows: */ +extern int VGSvcStartServices(void); +extern int VGSvcStopServices(void); +extern void VGSvcMainWait(void); +extern int VGSvcReportStatus(VBoxGuestFacilityStatus enmStatus); +#ifdef RT_OS_WINDOWS +extern void VGSvcWinResolveApis(void); +extern RTEXITCODE VGSvcWinInstall(void); +extern RTEXITCODE VGSvcWinUninstall(void); +extern RTEXITCODE VGSvcWinEnterCtrlDispatcher(void); +extern void VGSvcWinSetStopPendingStatus(uint32_t uCheckPoint); +# ifdef TH32CS_SNAPHEAPLIST +extern decltype(CreateToolhelp32Snapshot) *g_pfnCreateToolhelp32Snapshot; +extern decltype(Process32First) *g_pfnProcess32First; +extern decltype(Process32Next) *g_pfnProcess32Next; +extern decltype(Module32First) *g_pfnModule32First; +extern decltype(Module32Next) *g_pfnModule32Next; +# endif +extern decltype(GetSystemTimeAdjustment) *g_pfnGetSystemTimeAdjustment; +extern decltype(SetSystemTimeAdjustment) *g_pfnSetSystemTimeAdjustment; +# ifdef IPRT_INCLUDED_nt_nt_h +extern decltype(ZwQuerySystemInformation) *g_pfnZwQuerySystemInformation; +# endif +extern ULONG (WINAPI *g_pfnGetAdaptersInfo)(struct _IP_ADAPTER_INFO *, PULONG); +#ifdef WINSOCK_VERSION +extern decltype(WSAStartup) *g_pfnWSAStartup; +extern decltype(WSACleanup) *g_pfnWSACleanup; +extern decltype(WSASocketA) *g_pfnWSASocketA; +extern decltype(WSAIoctl) *g_pfnWSAIoctl; +extern decltype(WSAGetLastError) *g_pfnWSAGetLastError; +extern decltype(closesocket) *g_pfnclosesocket; +extern decltype(inet_ntoa) *g_pfninet_ntoa; +# endif /* WINSOCK_VERSION */ + +#ifdef SE_INTERACTIVE_LOGON_NAME +extern decltype(LsaNtStatusToWinError) *g_pfnLsaNtStatusToWinError; +#endif + +# ifdef VBOX_WITH_GUEST_PROPS +extern int VGSvcVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, char **ppszUserList, uint32_t *pcUsersInList); +extern int VGSvcVMInfoWinGetComponentVersions(uint32_t uClientID); +# endif /* VBOX_WITH_GUEST_PROPS */ + +#endif /* RT_OS_WINDOWS */ + +#ifdef VBOX_WITH_MEMBALLOON +extern uint32_t VGSvcBalloonQueryPages(uint32_t cbPage); +#endif +#if defined(VBOX_WITH_VBOXSERVICE_PAGE_SHARING) +extern RTEXITCODE VGSvcPageSharingWorkerChild(void); +#endif +extern int VGSvcVMInfoSignal(void); + +RT_C_DECLS_END + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp b/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp new file mode 100644 index 00000000..2da9b101 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc_pagesharing VBoxService - Page Sharing + * + * The Page Sharing subservice is responsible for finding memory mappings + * suitable page fusions. + * + * It is the driving force behind the Page Fusion feature in VirtualBox. + * Working with PGM and GMM (ring-0) thru the VMMDev interface. Every so often + * it reenumerates the memory mappings (executables and shared libraries) of the + * guest OS and reports additions and removals to GMM. For each mapping there + * is a filename and version as well as and address range and subsections. GMM + * will match the mapping with mapping with the same name and version from other + * VMs and see if there are any identical pages between the two. + * + * To increase the hit rate and reduce the volatility, the service launches a + * child process which loads all the Windows system DLLs it can. The child + * process is necessary as the DLLs are loaded without running the init code, + * and therefore not actually callable for other VBoxService code (may crash). + * + * This is currently only implemented on Windows. There is no technical reason + * for it not to be doable for all the other guests too, it's just a matter of + * customer demand and engineering time. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/avl.h> +#include <iprt/asm.h> +#include <iprt/mem.h> +#include <iprt/ldr.h> +#include <iprt/process.h> +#include <iprt/env.h> +#include <iprt/stream.h> +#include <iprt/file.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <VBox/err.h> +#include <VBox/VMMDev.h> +#include <VBox/VBoxGuestLib.h> + +#ifdef RT_OS_WINDOWS +#include <iprt/nt/nt-and-windows.h> +# include <tlhelp32.h> +# include <psapi.h> +# include <winternl.h> +#endif + +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +typedef struct +{ + AVLPVNODECORE Core; +#ifdef RT_OS_WINDOWS + HMODULE hModule; + char szFileVersion[16]; + MODULEENTRY32 Info; +#endif +} VGSVCPGSHKNOWNMOD, *PVGSVCPGSHKNOWNMOD; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_PageSharingEvent = NIL_RTSEMEVENTMULTI; + +static PAVLPVNODECORE g_pKnownModuleTree = NULL; +static uint64_t g_idSession = 0; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) vgsvcPageSharingEmptyTreeCallback(PAVLPVNODECORE pNode, void *pvUser); + + +#ifdef RT_OS_WINDOWS + +/** + * Registers a new module with the VMM + * @param pModule Module ptr + * @param fValidateMemory Validate/touch memory pages or not + */ +static void vgsvcPageSharingRegisterModule(PVGSVCPGSHKNOWNMOD pModule, bool fValidateMemory) +{ + VMMDEVSHAREDREGIONDESC aRegions[VMMDEVSHAREDREGIONDESC_MAX]; + DWORD dwModuleSize = pModule->Info.modBaseSize; + BYTE *pBaseAddress = pModule->Info.modBaseAddr; + + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule\n"); + + DWORD dwDummy; + DWORD cbVersion = GetFileVersionInfoSize(pModule->Info.szExePath, &dwDummy); + if (!cbVersion) + { + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: GetFileVersionInfoSize failed with %d\n", GetLastError()); + return; + } + BYTE *pVersionInfo = (BYTE *)RTMemAllocZ(cbVersion); + if (!pVersionInfo) + return; + + if (!GetFileVersionInfo(pModule->Info.szExePath, 0, cbVersion, pVersionInfo)) + { + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: GetFileVersionInfo failed with %d\n", GetLastError()); + goto end; + } + + /* Fetch default code page. */ + struct LANGANDCODEPAGE + { + WORD wLanguage; + WORD wCodePage; + } *lpTranslate; + + UINT cbTranslate; + BOOL fRet = VerQueryValue(pVersionInfo, TEXT("\\VarFileInfo\\Translation"), (LPVOID *)&lpTranslate, &cbTranslate); + if ( !fRet + || cbTranslate < 4) + { + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VerQueryValue failed with %d (cb=%d)\n", GetLastError(), cbTranslate); + goto end; + } + + unsigned i; + UINT cbFileVersion; + char *pszFileVersion = NULL; /* Shut up MSC */ + unsigned cTranslationBlocks = cbTranslate/sizeof(struct LANGANDCODEPAGE); + + pModule->szFileVersion[0] = '\0'; + for (i = 0; i < cTranslationBlocks; i++) + { + /* Fetch file version string. */ + char szFileVersionLocation[256]; + +/** @todo r=bird: Mixing ANSI and TCHAR crap again. This code is a mess. We + * always use the wide version of the API and convert to UTF-8/whatever. */ + + RTStrPrintf(szFileVersionLocation, sizeof(szFileVersionLocation), + "\\StringFileInfo\\%04x%04x\\FileVersion", lpTranslate[i].wLanguage, lpTranslate[i].wCodePage); + fRet = VerQueryValue(pVersionInfo, szFileVersionLocation, (LPVOID *)&pszFileVersion, &cbFileVersion); + if (fRet) + { + RTStrCopy(pModule->szFileVersion, sizeof(pModule->szFileVersion), pszFileVersion); + break; + } + } + if (i == cTranslationBlocks) + { + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: no file version found!\n"); + goto end; + } + + unsigned idxRegion = 0; + + if (fValidateMemory) + { + do + { + MEMORY_BASIC_INFORMATION MemInfo; + SIZE_T cbRet = VirtualQuery(pBaseAddress, &MemInfo, sizeof(MemInfo)); + Assert(cbRet); + if (!cbRet) + { + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VirtualQueryEx failed with %d\n", GetLastError()); + break; + } + + if ( MemInfo.State == MEM_COMMIT + && MemInfo.Type == MEM_IMAGE) + { + switch (MemInfo.Protect) + { + case PAGE_EXECUTE: + case PAGE_EXECUTE_READ: + case PAGE_READONLY: + { + char *pRegion = (char *)MemInfo.BaseAddress; + + /* Skip the first region as it only contains the image file header. */ + if (pRegion != (char *)pModule->Info.modBaseAddr) + { + /* Touch all pages. */ + while ((uintptr_t)pRegion < (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize) + { + /* Try to trick the optimizer to leave the page touching code in place. */ + ASMProbeReadByte(pRegion); + pRegion += PAGE_SIZE; + } + } +#ifdef RT_ARCH_X86 + aRegions[idxRegion].GCRegionAddr = (RTGCPTR32)MemInfo.BaseAddress; +#else + aRegions[idxRegion].GCRegionAddr = (RTGCPTR64)MemInfo.BaseAddress; +#endif + aRegions[idxRegion].cbRegion = MemInfo.RegionSize; + idxRegion++; + + break; + } + + default: + break; /* ignore */ + } + } + + pBaseAddress = (BYTE *)MemInfo.BaseAddress + MemInfo.RegionSize; + if (dwModuleSize > MemInfo.RegionSize) + dwModuleSize -= MemInfo.RegionSize; + else + { + dwModuleSize = 0; + break; + } + + if (idxRegion >= RT_ELEMENTS(aRegions)) + break; /* out of room */ + } + while (dwModuleSize); + } + else + { + /* We can't probe kernel memory ranges, so pretend it's one big region. */ +#ifdef RT_ARCH_X86 + aRegions[idxRegion].GCRegionAddr = (RTGCPTR32)pBaseAddress; +#else + aRegions[idxRegion].GCRegionAddr = (RTGCPTR64)pBaseAddress; +#endif + aRegions[idxRegion].cbRegion = dwModuleSize; + idxRegion++; + } + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VbglR3RegisterSharedModule %s %s base=%p size=%x cregions=%d\n", pModule->Info.szModule, pModule->szFileVersion, pModule->Info.modBaseAddr, pModule->Info.modBaseSize, idxRegion); + int rc = VbglR3RegisterSharedModule(pModule->Info.szModule, pModule->szFileVersion, (uintptr_t)pModule->Info.modBaseAddr, + pModule->Info.modBaseSize, idxRegion, aRegions); + if (RT_FAILURE(rc)) + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VbglR3RegisterSharedModule failed with %Rrc\n", rc); + +end: + RTMemFree(pVersionInfo); + return; +} + + +/** + * Inspect all loaded modules for the specified process + * + * @param dwProcessId Process id + * @param ppNewTree The module tree we're assembling from modules found + * in this process. Modules found are moved from + * g_pKnownModuleTree or created new. + */ +static void vgsvcPageSharingInspectModules(DWORD dwProcessId, PAVLPVNODECORE *ppNewTree) +{ + /* Get a list of all the modules in this process. */ + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE /* no child process handle inheritance */, dwProcessId); + if (hProcess == NULL) + { + VGSvcVerbose(3, "vgsvcPageSharingInspectModules: OpenProcess %x failed with %d\n", dwProcessId, GetLastError()); + return; + } + + HANDLE hSnapshot = g_pfnCreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId); + if (hSnapshot == INVALID_HANDLE_VALUE) + { + VGSvcVerbose(3, "vgsvcPageSharingInspectModules: CreateToolhelp32Snapshot failed with %d\n", GetLastError()); + CloseHandle(hProcess); + return; + } + + VGSvcVerbose(3, "vgsvcPageSharingInspectModules\n"); + + MODULEENTRY32 ModuleInfo; + BOOL bRet; + + ModuleInfo.dwSize = sizeof(ModuleInfo); + bRet = g_pfnModule32First(hSnapshot, &ModuleInfo); + do + { + /** @todo when changing this make sure VBoxService.exe is excluded! */ + char *pszDot = strrchr(ModuleInfo.szModule, '.'); + if ( pszDot + && (pszDot[1] == 'e' || pszDot[1] == 'E')) + continue; /* ignore executables for now. */ + + /* Found it before? */ + PAVLPVNODECORE pRec = RTAvlPVGet(ppNewTree, ModuleInfo.modBaseAddr); + if (!pRec) + { + pRec = RTAvlPVRemove(&g_pKnownModuleTree, ModuleInfo.modBaseAddr); + if (!pRec) + { + /* New module; register it. */ + PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)RTMemAllocZ(sizeof(*pModule)); + Assert(pModule); + if (!pModule) + break; + + pModule->Info = ModuleInfo; + pModule->Core.Key = ModuleInfo.modBaseAddr; + pModule->hModule = LoadLibraryEx(ModuleInfo.szExePath, 0, DONT_RESOLVE_DLL_REFERENCES); + if (pModule->hModule) + vgsvcPageSharingRegisterModule(pModule, true /* validate pages */); + + VGSvcVerbose(3, "\n\n MODULE NAME: %s", ModuleInfo.szModule ); + VGSvcVerbose(3, "\n executable = %s", ModuleInfo.szExePath ); + VGSvcVerbose(3, "\n process ID = 0x%08X", ModuleInfo.th32ProcessID ); + VGSvcVerbose(3, "\n base address = %#010p", (uintptr_t) ModuleInfo.modBaseAddr ); + VGSvcVerbose(3, "\n base size = %d", ModuleInfo.modBaseSize ); + + pRec = &pModule->Core; + } + bool ret = RTAvlPVInsert(ppNewTree, pRec); + Assert(ret); NOREF(ret); + } + } while (g_pfnModule32Next(hSnapshot, &ModuleInfo)); + + CloseHandle(hSnapshot); + CloseHandle(hProcess); +} + + +/** + * Inspect all running processes for executables and dlls that might be worth sharing + * with other VMs. + * + */ +static void vgsvcPageSharingInspectGuest(void) +{ + VGSvcVerbose(3, "vgsvcPageSharingInspectGuest\n"); + PAVLPVNODECORE pNewTree = NULL; + + /* + * Check loaded modules for all running processes. + */ + if ( g_pfnProcess32First + && g_pfnProcess32Next + && g_pfnModule32First + && g_pfnModule32Next + && g_pfnCreateToolhelp32Snapshot) + { + HANDLE hSnapshot = g_pfnCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) + { + VGSvcVerbose(3, "vgsvcPageSharingInspectGuest: CreateToolhelp32Snapshot failed with %d\n", GetLastError()); + return; + } + + DWORD const dwProcessId = GetCurrentProcessId(); + + PROCESSENTRY32 ProcessInfo; + ProcessInfo.dwSize = sizeof(ProcessInfo); + g_pfnProcess32First(hSnapshot, &ProcessInfo); + + do + { + /* Skip our own process. */ + if (ProcessInfo.th32ProcessID != dwProcessId) + vgsvcPageSharingInspectModules(ProcessInfo.th32ProcessID, &pNewTree); + } + while (g_pfnProcess32Next(hSnapshot, &ProcessInfo)); + + CloseHandle(hSnapshot); + } + + /* + * Check all loaded kernel modules. + */ + if (g_pfnZwQuerySystemInformation) + { + ULONG cbBuffer = 0; + PVOID pBuffer = NULL; + PRTL_PROCESS_MODULES pSystemModules; + + NTSTATUS ret = g_pfnZwQuerySystemInformation(SystemModuleInformation, (PVOID)&cbBuffer, 0, &cbBuffer); + if (!cbBuffer) + { + VGSvcVerbose(1, "ZwQuerySystemInformation returned length 0\n"); + goto skipkernelmodules; + } + + pBuffer = RTMemAllocZ(cbBuffer); + if (!pBuffer) + goto skipkernelmodules; + + ret = g_pfnZwQuerySystemInformation(SystemModuleInformation, pBuffer, cbBuffer, &cbBuffer); + if (ret != STATUS_SUCCESS) + { + VGSvcVerbose(1, "ZwQuerySystemInformation returned %x (1)\n", ret); + goto skipkernelmodules; + } + + pSystemModules = (PRTL_PROCESS_MODULES)pBuffer; + for (unsigned i = 0; i < pSystemModules->NumberOfModules; i++) + { + VGSvcVerbose(4, "\n\n KERNEL MODULE NAME: %s", pSystemModules->Modules[i].FullPathName[pSystemModules->Modules[i].OffsetToFileName] ); + VGSvcVerbose(4, "\n executable = %s", pSystemModules->Modules[i].FullPathName ); + VGSvcVerbose(4, "\n flags = 0x%08X\n", pSystemModules->Modules[i].Flags); + + /* User-mode modules seem to have no flags set; skip them as we detected them above. */ + if (pSystemModules->Modules[i].Flags == 0) + continue; + + /* Found it before? */ + PAVLPVNODECORE pRec = RTAvlPVGet(&pNewTree, pSystemModules->Modules[i].ImageBase); + if (!pRec) + { + pRec = RTAvlPVRemove(&g_pKnownModuleTree, pSystemModules->Modules[i].ImageBase); + if (!pRec) + { + /* New module; register it. */ + char szFullFilePath[512]; + PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)RTMemAllocZ(sizeof(*pModule)); + Assert(pModule); + if (!pModule) + break; + +/** @todo FullPathName not an UTF-8 string is! An ANSI string it is + * according to the SYSTEM locale. Best use RtlAnsiStringToUnicodeString to + * convert to UTF-16. */ + strcpy(pModule->Info.szModule, + (const char *)&pSystemModules->Modules[i].FullPathName[pSystemModules->Modules[i].OffsetToFileName]); + GetSystemDirectoryA(szFullFilePath, sizeof(szFullFilePath)); + + /* skip \Systemroot\system32 */ + char *lpPath = strchr((char *)&pSystemModules->Modules[i].FullPathName[1], '\\'); + if (!lpPath) + { + /* Seen just file names in XP; try to locate the file in the system32 and system32\drivers directories. */ + RTStrCat(szFullFilePath, sizeof(szFullFilePath), "\\"); + RTStrCat(szFullFilePath, sizeof(szFullFilePath), (const char *)pSystemModules->Modules[i].FullPathName); + VGSvcVerbose(3, "Unexpected kernel module name try %s\n", szFullFilePath); + if (RTFileExists(szFullFilePath) == false) + { + GetSystemDirectoryA(szFullFilePath, sizeof(szFullFilePath)); + RTStrCat(szFullFilePath, sizeof(szFullFilePath), "\\drivers\\"); + RTStrCat(szFullFilePath, sizeof(szFullFilePath), (const char *)pSystemModules->Modules[i].FullPathName); + VGSvcVerbose(3, "Unexpected kernel module name try %s\n", szFullFilePath); + if (RTFileExists(szFullFilePath) == false) + { + VGSvcVerbose(1, "Unexpected kernel module name %s\n", pSystemModules->Modules[i].FullPathName); + RTMemFree(pModule); + continue; + } + } + } + else + { + lpPath = strchr(lpPath + 1, '\\'); + if (!lpPath) + { + VGSvcVerbose(1, "Unexpected kernel module name %s (2)\n", pSystemModules->Modules[i].FullPathName); + RTMemFree(pModule); + continue; + } + + RTStrCat(szFullFilePath, sizeof(szFullFilePath), lpPath); + } + + strcpy(pModule->Info.szExePath, szFullFilePath); + pModule->Info.modBaseAddr = (BYTE *)pSystemModules->Modules[i].ImageBase; + pModule->Info.modBaseSize = pSystemModules->Modules[i].ImageSize; + + pModule->Core.Key = pSystemModules->Modules[i].ImageBase; + vgsvcPageSharingRegisterModule(pModule, false /* don't check memory pages */); + + VGSvcVerbose(3, "\n\n KERNEL MODULE NAME: %s", pModule->Info.szModule ); + VGSvcVerbose(3, "\n executable = %s", pModule->Info.szExePath ); + VGSvcVerbose(3, "\n base address = %#010p", (uintptr_t)pModule->Info.modBaseAddr ); + VGSvcVerbose(3, "\n flags = 0x%08X", pSystemModules->Modules[i].Flags); + VGSvcVerbose(3, "\n base size = %d", pModule->Info.modBaseSize ); + + pRec = &pModule->Core; + } + bool fRet = RTAvlPVInsert(&pNewTree, pRec); + Assert(fRet); NOREF(fRet); + } + } +skipkernelmodules: + if (pBuffer) + RTMemFree(pBuffer); + } + + /* Delete leftover modules in the old tree. */ + RTAvlPVDestroy(&g_pKnownModuleTree, vgsvcPageSharingEmptyTreeCallback, NULL); + + /* Check all registered modules. */ + VbglR3CheckSharedModules(); + + /* Activate new module tree. */ + g_pKnownModuleTree = pNewTree; +} + + +/** + * RTAvlPVDestroy callback. + */ +static DECLCALLBACK(int) vgsvcPageSharingEmptyTreeCallback(PAVLPVNODECORE pNode, void *pvUser) +{ + PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)pNode; + bool *pfUnregister = (bool *)pvUser; + + VGSvcVerbose(3, "vgsvcPageSharingEmptyTreeCallback %s %s\n", pModule->Info.szModule, pModule->szFileVersion); + + /* Dereference module in the hypervisor. */ + if ( !pfUnregister + || *pfUnregister) + { + int rc = VbglR3UnregisterSharedModule(pModule->Info.szModule, pModule->szFileVersion, + (uintptr_t)pModule->Info.modBaseAddr, pModule->Info.modBaseSize); + AssertRC(rc); + } + + if (pModule->hModule) + FreeLibrary(pModule->hModule); + RTMemFree(pNode); + return 0; +} + + +#else /* !RT_OS_WINDOWS */ + +static void vgsvcPageSharingInspectGuest(void) +{ + /** @todo other platforms */ +} + +#endif /* !RT_OS_WINDOWS */ + +/** @interface_method_impl{VBOXSERVICE,pfnInit} */ +static DECLCALLBACK(int) vgsvcPageSharingInit(void) +{ + VGSvcVerbose(3, "vgsvcPageSharingInit\n"); + + int rc = RTSemEventMultiCreate(&g_PageSharingEvent); + AssertRCReturn(rc, rc); + +#ifdef RT_OS_WINDOWS + rc = VbglR3GetSessionId(&g_idSession); + if (RT_FAILURE(rc)) + { + if (rc == VERR_IO_GEN_FAILURE) + VGSvcVerbose(0, "PageSharing: Page sharing support is not available by the host\n"); + else + VGSvcError("vgsvcPageSharingInit: Failed with rc=%Rrc\n", rc); + + rc = VERR_SERVICE_DISABLED; + + RTSemEventMultiDestroy(g_PageSharingEvent); + g_PageSharingEvent = NIL_RTSEMEVENTMULTI; + + } +#endif + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vgsvcPageSharingWorker(bool volatile *pfShutdown) +{ + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Now enter the loop retrieving runtime data continuously. + */ + for (;;) + { + bool fEnabled = VbglR3PageSharingIsEnabled(); + VGSvcVerbose(3, "vgsvcPageSharingWorker: enabled=%d\n", fEnabled); + + if (fEnabled) + vgsvcPageSharingInspectGuest(); + + /* + * Block for a minute. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + int rc = RTSemEventMultiWait(g_PageSharingEvent, 60000); + if (*pfShutdown) + break; + if (rc != VERR_TIMEOUT && RT_FAILURE(rc)) + { + VGSvcError("vgsvcPageSharingWorker: RTSemEventMultiWait failed; rc=%Rrc\n", rc); + break; + } +#ifdef RT_OS_WINDOWS + uint64_t idNewSession = g_idSession; + rc = VbglR3GetSessionId(&idNewSession); + AssertRC(rc); + + if (idNewSession != g_idSession) + { + bool fUnregister = false; + + VGSvcVerbose(3, "vgsvcPageSharingWorker: VM was restored!!\n"); + /* The VM was restored, so reregister all modules the next time. */ + RTAvlPVDestroy(&g_pKnownModuleTree, vgsvcPageSharingEmptyTreeCallback, &fUnregister); + g_pKnownModuleTree = NULL; + + g_idSession = idNewSession; + } +#endif + } + + RTSemEventMultiDestroy(g_PageSharingEvent); + g_PageSharingEvent = NIL_RTSEMEVENTMULTI; + + VGSvcVerbose(3, "vgsvcPageSharingWorker: finished thread\n"); + return 0; +} + +#ifdef RT_OS_WINDOWS + +/** + * This gets control when VBoxService is launched with "pagefusion" by + * vgsvcPageSharingWorkerProcess(). + * + * @returns RTEXITCODE_SUCCESS. + * + * @remarks It won't normally return since the parent drops the shutdown hint + * via RTProcTerminate(). + */ +RTEXITCODE VGSvcPageSharingWorkerChild(void) +{ + VGSvcVerbose(3, "vgsvcPageSharingInitFork\n"); + + bool fShutdown = false; + vgsvcPageSharingInit(); + vgsvcPageSharingWorker(&fShutdown); + + return RTEXITCODE_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vgsvcPageSharingWorkerProcess(bool volatile *pfShutdown) +{ + RTPROCESS hProcess = NIL_RTPROCESS; + int rc; + + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Now enter the loop retrieving runtime data continuously. + */ + for (;;) + { + bool fEnabled = VbglR3PageSharingIsEnabled(); + VGSvcVerbose(3, "vgsvcPageSharingWorkerProcess: enabled=%d\n", fEnabled); + + /* + * Start a 2nd VBoxService process to deal with page fusion as we do + * not wish to dummy load dlls into this process. (First load with + * DONT_RESOLVE_DLL_REFERENCES, 2nd normal -> dll init routines not called!) + */ + if ( fEnabled + && hProcess == NIL_RTPROCESS) + { + char szExeName[256]; + char *pszExeName = RTProcGetExecutablePath(szExeName, sizeof(szExeName)); + if (pszExeName) + { + char const *papszArgs[3]; + papszArgs[0] = pszExeName; + papszArgs[1] = "pagefusion"; + papszArgs[2] = NULL; + rc = RTProcCreate(pszExeName, papszArgs, RTENV_DEFAULT, 0 /* normal child */, &hProcess); + if (RT_FAILURE(rc)) + VGSvcError("vgsvcPageSharingWorkerProcess: RTProcCreate %s failed; rc=%Rrc\n", pszExeName, rc); + } + } + + /* + * Block for a minute. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + rc = RTSemEventMultiWait(g_PageSharingEvent, 60000); + if (*pfShutdown) + break; + if (rc != VERR_TIMEOUT && RT_FAILURE(rc)) + { + VGSvcError("vgsvcPageSharingWorkerProcess: RTSemEventMultiWait failed; rc=%Rrc\n", rc); + break; + } + } + + if (hProcess != NIL_RTPROCESS) + RTProcTerminate(hProcess); + + VGSvcVerbose(3, "vgsvcPageSharingWorkerProcess: finished thread\n"); + return 0; +} + +#endif /* RT_OS_WINDOWS */ + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcPageSharingStop(void) +{ + RTSemEventMultiSignal(g_PageSharingEvent); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcPageSharingTerm(void) +{ + if (g_PageSharingEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_PageSharingEvent); + g_PageSharingEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'pagesharing' service description. + */ +VBOXSERVICE g_PageSharing = +{ + /* pszName. */ + "pagesharing", + /* pszDescription. */ + "Page Sharing", + /* pszUsage. */ + NULL, + /* pszOptions. */ + NULL, + /* methods */ + VGSvcDefaultPreInit, + VGSvcDefaultOption, + vgsvcPageSharingInit, +#ifdef RT_OS_WINDOWS + vgsvcPageSharingWorkerProcess, +#else + vgsvcPageSharingWorker, +#endif + vgsvcPageSharingStop, + vgsvcPageSharingTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp new file mode 100644 index 00000000..b03dd8a3 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#include "VBoxServicePropCache.h" + + + +/** @todo Docs */ +static PVBOXSERVICEVEPROPCACHEENTRY vgsvcPropCacheFindInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, + uint32_t fFlags) +{ + RT_NOREF1(fFlags); + AssertPtrReturn(pCache, NULL); + AssertPtrReturn(pszName, NULL); + + /** @todo This is a O(n) lookup, maybe improve this later to O(1) using a + * map. + * r=bird: Use a string space (RTstrSpace*). That is O(log n) in its current + * implementation (AVL tree). However, this is not important at the + * moment. */ + PVBOXSERVICEVEPROPCACHEENTRY pNode = NULL; + if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect))) + { + PVBOXSERVICEVEPROPCACHEENTRY pNodeIt; + RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc) + { + if (strcmp(pNodeIt->pszName, pszName) == 0) + { + pNode = pNodeIt; + break; + } + } + RTCritSectLeave(&pCache->CritSect); + } + return pNode; +} + + +/** @todo Docs */ +static PVBOXSERVICEVEPROPCACHEENTRY vgsvcPropCacheInsertEntryInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName) +{ + AssertPtrReturn(pCache, NULL); + AssertPtrReturn(pszName, NULL); + + PVBOXSERVICEVEPROPCACHEENTRY pNode = (PVBOXSERVICEVEPROPCACHEENTRY)RTMemAlloc(sizeof(VBOXSERVICEVEPROPCACHEENTRY)); + if (pNode) + { + pNode->pszName = RTStrDup(pszName); + if (!pNode->pszName) + { + RTMemFree(pNode); + return NULL; + } + pNode->pszValue = NULL; + pNode->fFlags = 0; + pNode->pszValueReset = NULL; + + int rc = RTCritSectEnter(&pCache->CritSect); + if (RT_SUCCESS(rc)) + { + RTListAppend(&pCache->NodeHead, &pNode->NodeSucc); + rc = RTCritSectLeave(&pCache->CritSect); + } + } + return pNode; +} + + +/** @todo Docs */ +static int vgsvcPropCacheWritePropF(uint32_t u32ClientId, const char *pszName, uint32_t fFlags, const char *pszValueFormat, ...) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + + int rc; + if (pszValueFormat != NULL) + { + va_list va; + va_start(va, pszValueFormat); + + char *pszValue; + if (RTStrAPrintfV(&pszValue, pszValueFormat, va) >= 0) + { + if (fFlags & VGSVCPROPCACHE_FLAGS_TRANSIENT) + { + /* + * Because a value can be temporary we have to make sure it also + * gets deleted when the property cache did not have the chance to + * gracefully clean it up (due to a hard VM reset etc), so set this + * guest property using the TRANSRESET flag.. + */ + rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, "TRANSRESET"); + if (rc == VERR_PARSE_ERROR) + { + /* Host does not support the "TRANSRESET" flag, so only + * use the "TRANSIENT" flag -- better than nothing :-). */ + rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, "TRANSIENT"); + /** @todo r=bird: Remember that the host doesn't support + * this. */ + } + } + else + rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, pszValue /* No transient flags set */); + RTStrFree(pszValue); + } + else + rc = VERR_NO_MEMORY; + va_end(va); + } + else + rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, NULL); + return rc; +} + + +/** + * Creates a property cache. + * + * @returns IPRT status code. + * @param pCache Pointer to the cache. + * @param uClientId The HGCM handle of to the guest property service. + */ +int VGSvcPropCacheCreate(PVBOXSERVICEVEPROPCACHE pCache, uint32_t uClientId) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + /** @todo Prevent init the cache twice! + * r=bird: Use a magic. */ + RTListInit(&pCache->NodeHead); + pCache->uClientID = uClientId; + return RTCritSectInit(&pCache->CritSect); +} + + +/** + * Updates a cache entry without submitting any changes to the host. + * + * This is handy for defining default values/flags. + * + * @returns VBox status code. + * + * @param pCache The property cache. + * @param pszName The property name. + * @param fFlags The property flags to set. + * @param pszValueReset The property reset value. + */ +int VGSvcPropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, uint32_t fFlags, const char *pszValueReset) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + PVBOXSERVICEVEPROPCACHEENTRY pNode = vgsvcPropCacheFindInternal(pCache, pszName, 0); + if (pNode == NULL) + pNode = vgsvcPropCacheInsertEntryInternal(pCache, pszName); + + int rc; + if (pNode != NULL) + { + rc = RTCritSectEnter(&pCache->CritSect); + if (RT_SUCCESS(rc)) + { + pNode->fFlags = fFlags; + if (pszValueReset) + { + if (pNode->pszValueReset) + RTStrFree(pNode->pszValueReset); + pNode->pszValueReset = RTStrDup(pszValueReset); + AssertPtr(pNode->pszValueReset); + } + rc = RTCritSectLeave(&pCache->CritSect); + } + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Updates the local guest property cache and writes it to HGCM if outdated. + * + * @returns VBox status code. + * + * @param pCache The property cache. + * @param pszName The property name. + * @param pszValueFormat The property format string. If this is NULL then + * the property will be deleted (if possible). + * @param ... Format arguments. + */ +int VGSvcPropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, const char *pszValueFormat, ...) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + + Assert(pCache->uClientID); + + /* + * Format the value first. + */ + char *pszValue = NULL; + if (pszValueFormat) + { + va_list va; + va_start(va, pszValueFormat); + RTStrAPrintfV(&pszValue, pszValueFormat, va); + va_end(va); + if (!pszValue) + return VERR_NO_STR_MEMORY; + } + + PVBOXSERVICEVEPROPCACHEENTRY pNode = vgsvcPropCacheFindInternal(pCache, pszName, 0); + + /* Lock the cache. */ + int rc = RTCritSectEnter(&pCache->CritSect); + if (RT_SUCCESS(rc)) + { + if (pNode == NULL) + pNode = vgsvcPropCacheInsertEntryInternal(pCache, pszName); + + AssertPtr(pNode); + if (pszValue) /* Do we have a value to check for? */ + { + bool fUpdate = false; + /* Always update this property, no matter what? */ + if (pNode->fFlags & VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE) + fUpdate = true; + /* Did the value change so we have to update? */ + else if (pNode->pszValue && strcmp(pNode->pszValue, pszValue) != 0) + fUpdate = true; + /* No value stored at the moment but we have a value now? */ + else if (pNode->pszValue == NULL) + fUpdate = true; + + if (fUpdate) + { + /* Write the update. */ + rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, pNode->fFlags, pszValue); + VGSvcVerbose(4, "[PropCache %p]: Written '%s'='%s' (flags: %x), rc=%Rrc\n", + pCache, pNode->pszName, pszValue, pNode->fFlags, rc); + if (RT_SUCCESS(rc)) /* Only update the node's value on successful write. */ + { + RTStrFree(pNode->pszValue); + pNode->pszValue = RTStrDup(pszValue); + if (!pNode->pszValue) + rc = VERR_NO_MEMORY; + } + } + else + rc = VINF_NO_CHANGE; /* No update needed. */ + } + else + { + /* No value specified. Deletion (or no action required). */ + if (pNode->pszValue) /* Did we have a value before? Then the value needs to be deleted. */ + { + rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, + 0, /* Flags */ NULL /* Value */); + VGSvcVerbose(4, "[PropCache %p]: Deleted '%s'='%s' (flags: %x), rc=%Rrc\n", + pCache, pNode->pszName, pNode->pszValue, pNode->fFlags, rc); + if (RT_SUCCESS(rc)) /* Only delete property value on successful Vbgl deletion. */ + { + /* Delete property (but do not remove from cache) if not deleted yet. */ + RTStrFree(pNode->pszValue); + pNode->pszValue = NULL; + } + } + else + rc = VINF_NO_CHANGE; /* No update needed. */ + } + + /* Release cache. */ + RTCritSectLeave(&pCache->CritSect); + } + + VGSvcVerbose(4, "[PropCache %p]: Updating '%s' resulted in rc=%Rrc\n", pCache, pszName, rc); + + /* Delete temp stuff. */ + RTStrFree(pszValue); + return rc; +} + + +/** + * Updates all cache values which are matching the specified path. + * + * @returns VBox status code. + * + * @param pCache The property cache. + * @param pszValue The value to set. A NULL will delete the value. + * @param fFlags Flags to set. + * @param pszPathFormat The path format string. May not be null and has + * to be an absolute path. + * @param ... Format arguments. + */ +int VGSvcPropCacheUpdateByPath(PVBOXSERVICEVEPROPCACHE pCache, const char *pszValue, uint32_t fFlags, + const char *pszPathFormat, ...) +{ + RT_NOREF1(fFlags); + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszPathFormat, VERR_INVALID_POINTER); + + int rc = VERR_NOT_FOUND; + if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect))) + { + /* + * Format the value first. + */ + char *pszPath = NULL; + va_list va; + va_start(va, pszPathFormat); + RTStrAPrintfV(&pszPath, pszPathFormat, va); + va_end(va); + if (!pszPath) + { + rc = VERR_NO_STR_MEMORY; + } + else + { + /* Iterate through all nodes and compare their paths. */ + PVBOXSERVICEVEPROPCACHEENTRY pNodeIt; + RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc) + { + if (RTStrStr(pNodeIt->pszName, pszPath) == pNodeIt->pszName) + { + /** @todo Use some internal function to update the node directly, this is slow atm. */ + rc = VGSvcPropCacheUpdate(pCache, pNodeIt->pszName, pszValue); + } + if (RT_FAILURE(rc)) + break; + } + RTStrFree(pszPath); + } + RTCritSectLeave(&pCache->CritSect); + } + return rc; +} + + +/** + * Flushes the cache by writing every item regardless of its state. + * + * @param pCache The property cache. + */ +int VGSvcPropCacheFlush(PVBOXSERVICEVEPROPCACHE pCache) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect))) + { + PVBOXSERVICEVEPROPCACHEENTRY pNodeIt; + RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc) + { + rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNodeIt->pszName, pNodeIt->fFlags, pNodeIt->pszValue); + if (RT_FAILURE(rc)) + break; + } + RTCritSectLeave(&pCache->CritSect); + } + return rc; +} + + +/** + * Reset all temporary properties and destroy the cache. + * + * @param pCache The property cache. + */ +void VGSvcPropCacheDestroy(PVBOXSERVICEVEPROPCACHE pCache) +{ + AssertPtrReturnVoid(pCache); + Assert(pCache->uClientID); + + /* Lock the cache. */ + int rc = RTCritSectEnter(&pCache->CritSect); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICEVEPROPCACHEENTRY pNode = RTListGetFirst(&pCache->NodeHead, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc); + while (pNode) + { + PVBOXSERVICEVEPROPCACHEENTRY pNext = RTListNodeIsLast(&pCache->NodeHead, &pNode->NodeSucc) + ? NULL : + RTListNodeGetNext(&pNode->NodeSucc, + VBOXSERVICEVEPROPCACHEENTRY, NodeSucc); + RTListNodeRemove(&pNode->NodeSucc); + + if (pNode->fFlags & VGSVCPROPCACHE_FLAGS_TEMPORARY) + rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, pNode->fFlags, pNode->pszValueReset); + + AssertPtr(pNode->pszName); + RTStrFree(pNode->pszName); + RTStrFree(pNode->pszValue); + RTStrFree(pNode->pszValueReset); + pNode->fFlags = 0; + + RTMemFree(pNode); + + pNode = pNext; + } + RTCritSectLeave(&pCache->CritSect); + } + + /* Destroy critical section. */ + RTCritSectDelete(&pCache->CritSect); +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h new file mode 100644 index 00000000..eb362274 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h @@ -0,0 +1,66 @@ +/* $Id: */ +/** @file + * VBoxServicePropCache - Guest property cache. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxServiceInternal.h" + +#ifdef VBOX_WITH_GUEST_PROPS + +/** @name VGSVCPROPCACHE_FLAG_XXX - Guest Property Cache Flags. + * @{ */ +/** Indicates wheter a guest property is temporary and either should + * - a) get a "reset" value assigned (via VBoxServicePropCacheUpdateEntry) + * as soon as the property cache gets destroyed, or + * - b) get deleted when no reset value is specified. + */ +# define VGSVCPROPCACHE_FLAGS_TEMPORARY RT_BIT(1) +/** Indicates whether a property every time needs to be updated, regardless + * if its real value changed or not. */ +# define VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE RT_BIT(2) +/** The guest property gets deleted when + * - a) the property cache gets destroyed, or + * - b) the VM gets reset / shutdown / destroyed. + */ +# define VGSVCPROPCACHE_FLAGS_TRANSIENT RT_BIT(3) +/** @} */ + +int VGSvcPropCacheCreate(PVBOXSERVICEVEPROPCACHE pCache, uint32_t uClientId); +int VGSvcPropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, uint32_t fFlags, const char *pszValueReset); +int VGSvcPropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, const char *pszValueFormat, ...); +int VGSvcPropCacheUpdateByPath(PVBOXSERVICEVEPROPCACHE pCache, const char *pszValue, uint32_t fFlags, + const char *pszPathFormat, ...); +int VGSvcPropCacheFlush(PVBOXSERVICEVEPROPCACHE pCache); +void VGSvcPropCacheDestroy(PVBOXSERVICEVEPROPCACHE pCache); +#endif /* VBOX_WITH_GUEST_PROPS */ + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h b/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h new file mode 100644 index 00000000..d37b30cd --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define IDI_VIRTUALBOX 101 + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp new file mode 100644 index 00000000..22c11ca6 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_vgsvc_vmstats VBoxService - VM Statistics + * + * The VM statistics subservice helps out the performance collector API on the + * host side by providing metrics from inside the guest. + * + * See IPerformanceCollector, CollectorGuest and the "Guest/" submetrics that + * gets registered by Machine::i_registerMetrics in Main. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if defined(RT_OS_WINDOWS) +# include <iprt/win/windows.h> +# include <psapi.h> +# include <winternl.h> + +#elif defined(RT_OS_LINUX) +# include <iprt/ctype.h> +# include <iprt/stream.h> +# include <unistd.h> + +#elif defined(RT_OS_SOLARIS) +# include <kstat.h> +# include <sys/sysinfo.h> +# include <unistd.h> +#else +/** @todo port me. */ + +#endif + +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/ldr.h> +#include <VBox/param.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <VBox/err.h> +#include <VBox/VMMDev.h> /* For VMMDevReportGuestStats and indirectly VbglR3StatReport. */ +#include <VBox/VBoxGuestLib.h> + +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct VBOXSTATSCONTEXT +{ + RTMSINTERVAL cMsStatInterval; + + uint64_t au64LastCpuLoad_Idle[VMM_MAX_CPU_COUNT]; + uint64_t au64LastCpuLoad_Kernel[VMM_MAX_CPU_COUNT]; + uint64_t au64LastCpuLoad_User[VMM_MAX_CPU_COUNT]; + uint64_t au64LastCpuLoad_Nice[VMM_MAX_CPU_COUNT]; + +#ifdef RT_OS_WINDOWS + DECLCALLBACKMEMBER_EX(NTSTATUS, WINAPI, pfnNtQuerySystemInformation,(SYSTEM_INFORMATION_CLASS SystemInformationClass, + PVOID SystemInformation, ULONG SystemInformationLength, + PULONG ReturnLength)); + DECLCALLBACKMEMBER_EX(void, WINAPI, pfnGlobalMemoryStatusEx,(LPMEMORYSTATUSEX lpBuffer)); + DECLCALLBACKMEMBER_EX(BOOL, WINAPI, pfnGetPerformanceInfo,(PPERFORMANCE_INFORMATION pPerformanceInformation, DWORD cb)); +#endif +} VBOXSTATSCONTEXT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Global data. */ +static VBOXSTATSCONTEXT g_VMStat = {0}; + +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_VMStatEvent = NIL_RTSEMEVENTMULTI; + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcVMStatsInit(void) +{ + VGSvcVerbose(3, "vgsvcVMStatsInit\n"); + + int rc = RTSemEventMultiCreate(&g_VMStatEvent); + AssertRCReturn(rc, rc); + + g_VMStat.cMsStatInterval = 0; /* default; update disabled */ + RT_ZERO(g_VMStat.au64LastCpuLoad_Idle); + RT_ZERO(g_VMStat.au64LastCpuLoad_Kernel); + RT_ZERO(g_VMStat.au64LastCpuLoad_User); + RT_ZERO(g_VMStat.au64LastCpuLoad_Nice); + + rc = VbglR3StatQueryInterval(&g_VMStat.cMsStatInterval); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsInit: New statistics interval %u seconds\n", g_VMStat.cMsStatInterval); + else + VGSvcVerbose(3, "vgsvcVMStatsInit: DeviceIoControl failed with %d\n", rc); + +#ifdef RT_OS_WINDOWS + /* NtQuerySystemInformation might be dropped in future releases, so load + it dynamically as per Microsoft's recommendation. */ + *(void **)&g_VMStat.pfnNtQuerySystemInformation = RTLdrGetSystemSymbol("ntdll.dll", "NtQuerySystemInformation"); + if (g_VMStat.pfnNtQuerySystemInformation) + VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.pfnNtQuerySystemInformation = %x\n", g_VMStat.pfnNtQuerySystemInformation); + else + { + VGSvcVerbose(3, "vgsvcVMStatsInit: ntdll.NtQuerySystemInformation not found!\n"); + return VERR_SERVICE_DISABLED; + } + + /* GlobalMemoryStatus is win2k and up, so load it dynamically */ + *(void **)&g_VMStat.pfnGlobalMemoryStatusEx = RTLdrGetSystemSymbol("kernel32.dll", "GlobalMemoryStatusEx"); + if (g_VMStat.pfnGlobalMemoryStatusEx) + VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.GlobalMemoryStatusEx = %x\n", g_VMStat.pfnGlobalMemoryStatusEx); + else + { + /** @todo Now fails in NT4; do we care? */ + VGSvcVerbose(3, "vgsvcVMStatsInit: kernel32.GlobalMemoryStatusEx not found!\n"); + return VERR_SERVICE_DISABLED; + } + + /* GetPerformanceInfo is xp and up, so load it dynamically */ + *(void **)&g_VMStat.pfnGetPerformanceInfo = RTLdrGetSystemSymbol("psapi.dll", "GetPerformanceInfo"); + if (g_VMStat.pfnGetPerformanceInfo) + VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.pfnGetPerformanceInfo= %x\n", g_VMStat.pfnGetPerformanceInfo); +#endif /* RT_OS_WINDOWS */ + + return VINF_SUCCESS; +} + + +/** + * Gathers VM statistics and reports them to the host. + */ +static void vgsvcVMStatsReport(void) +{ +#if defined(RT_OS_WINDOWS) + Assert(g_VMStat.pfnGlobalMemoryStatusEx && g_VMStat.pfnNtQuerySystemInformation); + if ( !g_VMStat.pfnGlobalMemoryStatusEx + || !g_VMStat.pfnNtQuerySystemInformation) + return; + + /* Clear the report so we don't report garbage should NtQuerySystemInformation + behave in an unexpected manner. */ + VMMDevReportGuestStats req; + RT_ZERO(req); + + /* Query and report guest statistics */ + SYSTEM_INFO systemInfo; + GetSystemInfo(&systemInfo); + + MEMORYSTATUSEX memStatus; + memStatus.dwLength = sizeof(memStatus); + g_VMStat.pfnGlobalMemoryStatusEx(&memStatus); + + req.guestStats.u32PageSize = systemInfo.dwPageSize; + req.guestStats.u32PhysMemTotal = (uint32_t)(memStatus.ullTotalPhys / _4K); + req.guestStats.u32PhysMemAvail = (uint32_t)(memStatus.ullAvailPhys / _4K); + /* The current size of the committed memory limit, in bytes. This is physical + memory plus the size of the page file, minus a small overhead. */ + req.guestStats.u32PageFileSize = (uint32_t)(memStatus.ullTotalPageFile / _4K) - req.guestStats.u32PhysMemTotal; + req.guestStats.u32MemoryLoad = memStatus.dwMemoryLoad; + req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL + | VBOX_GUEST_STAT_PHYS_MEM_AVAIL + | VBOX_GUEST_STAT_PAGE_FILE_SIZE + | VBOX_GUEST_STAT_MEMORY_LOAD; +# ifdef VBOX_WITH_MEMBALLOON + req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K); + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON; +# else + req.guestStats.u32PhysMemBalloon = 0; +# endif + + if (g_VMStat.pfnGetPerformanceInfo) + { + PERFORMANCE_INFORMATION perfInfo; + + if (g_VMStat.pfnGetPerformanceInfo(&perfInfo, sizeof(perfInfo))) + { + req.guestStats.u32Processes = perfInfo.ProcessCount; + req.guestStats.u32Threads = perfInfo.ThreadCount; + req.guestStats.u32Handles = perfInfo.HandleCount; + req.guestStats.u32MemCommitTotal = perfInfo.CommitTotal; /* already in pages */ + req.guestStats.u32MemKernelTotal = perfInfo.KernelTotal; /* already in pages */ + req.guestStats.u32MemKernelPaged = perfInfo.KernelPaged; /* already in pages */ + req.guestStats.u32MemKernelNonPaged = perfInfo.KernelNonpaged; /* already in pages */ + req.guestStats.u32MemSystemCache = perfInfo.SystemCache; /* already in pages */ + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PROCESSES | VBOX_GUEST_STAT_THREADS | VBOX_GUEST_STAT_HANDLES + | VBOX_GUEST_STAT_MEM_COMMIT_TOTAL | VBOX_GUEST_STAT_MEM_KERNEL_TOTAL + | VBOX_GUEST_STAT_MEM_KERNEL_PAGED | VBOX_GUEST_STAT_MEM_KERNEL_NONPAGED + | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE; + } + else + VGSvcVerbose(3, "vgsvcVMStatsReport: GetPerformanceInfo failed with %d\n", GetLastError()); + } + + /* Query CPU load information */ + uint32_t cbStruct = systemInfo.dwNumberOfProcessors * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION); + PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION pProcInfo; + pProcInfo = (PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)RTMemAlloc(cbStruct); + if (!pProcInfo) + return; + + /* Unfortunately GetSystemTimes is XP SP1 and up only, so we need to use the semi-undocumented NtQuerySystemInformation */ + bool fCpuInfoAvail = false; + DWORD cbReturned; + NTSTATUS rcNt = g_VMStat.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned); + if ( !rcNt + && cbReturned == cbStruct) + { + for (uint32_t i = 0; i < systemInfo.dwNumberOfProcessors; i++) + { + if (i >= VMM_MAX_CPU_COUNT) + { + VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPUs %u..%u\n", i, systemInfo.dwNumberOfProcessors); + break; + } + + if (g_VMStat.au64LastCpuLoad_Kernel[i] == 0) + { + /* first time */ + g_VMStat.au64LastCpuLoad_Idle[i] = pProcInfo[i].IdleTime.QuadPart; + g_VMStat.au64LastCpuLoad_Kernel[i] = pProcInfo[i].KernelTime.QuadPart; + g_VMStat.au64LastCpuLoad_User[i] = pProcInfo[i].UserTime.QuadPart; + + Sleep(250); + + rcNt = g_VMStat.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned); + Assert(!rcNt); + } + + uint64_t deltaIdle = (pProcInfo[i].IdleTime.QuadPart - g_VMStat.au64LastCpuLoad_Idle[i]); + uint64_t deltaKernel = (pProcInfo[i].KernelTime.QuadPart - g_VMStat.au64LastCpuLoad_Kernel[i]); + uint64_t deltaUser = (pProcInfo[i].UserTime.QuadPart - g_VMStat.au64LastCpuLoad_User[i]); + deltaKernel -= deltaIdle; /* idle time is added to kernel time */ + uint64_t ullTotalTime = deltaIdle + deltaKernel + deltaUser; + if (ullTotalTime == 0) /* Prevent division through zero. */ + ullTotalTime = 1; + + req.guestStats.u32CpuLoad_Idle = (uint32_t)(deltaIdle * 100 / ullTotalTime); + req.guestStats.u32CpuLoad_Kernel = (uint32_t)(deltaKernel* 100 / ullTotalTime); + req.guestStats.u32CpuLoad_User = (uint32_t)(deltaUser * 100 / ullTotalTime); + + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE + | VBOX_GUEST_STAT_CPU_LOAD_KERNEL + | VBOX_GUEST_STAT_CPU_LOAD_USER; + req.guestStats.u32CpuId = i; + fCpuInfoAvail = true; + int rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", i); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: VbglR3StatReport failed with rc=%Rrc\n", rc); + + g_VMStat.au64LastCpuLoad_Idle[i] = pProcInfo[i].IdleTime.QuadPart; + g_VMStat.au64LastCpuLoad_Kernel[i] = pProcInfo[i].KernelTime.QuadPart; + g_VMStat.au64LastCpuLoad_User[i] = pProcInfo[i].UserTime.QuadPart; + } + } + RTMemFree(pProcInfo); + + if (!fCpuInfoAvail) + { + VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n"); + int rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n"); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); + } + +#elif defined(RT_OS_LINUX) + VMMDevReportGuestStats req; + RT_ZERO(req); + PRTSTREAM pStrm; + char szLine[256]; + char *psz; + + int rc = RTStrmOpen("/proc/meminfo", "r", &pStrm); + if (RT_SUCCESS(rc)) + { + uint64_t u64Kb; + uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0; + for (;;) + { + rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine)); + if (RT_FAILURE(rc)) + break; + if (strstr(szLine, "MemTotal:") == szLine) + { + rc = RTStrToUInt64Ex(RTStrStripL(&szLine[9]), &psz, 0, &u64Kb); + if (RT_SUCCESS(rc)) + u64Total = u64Kb * _1K; + } + else if (strstr(szLine, "MemFree:") == szLine) + { + rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb); + if (RT_SUCCESS(rc)) + u64Free = u64Kb * _1K; + } + else if (strstr(szLine, "Buffers:") == szLine) + { + rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb); + if (RT_SUCCESS(rc)) + u64Buffers = u64Kb * _1K; + } + else if (strstr(szLine, "Cached:") == szLine) + { + rc = RTStrToUInt64Ex(RTStrStripL(&szLine[7]), &psz, 0, &u64Kb); + if (RT_SUCCESS(rc)) + u64Cached = u64Kb * _1K; + } + else if (strstr(szLine, "SwapTotal:") == szLine) + { + rc = RTStrToUInt64Ex(RTStrStripL(&szLine[10]), &psz, 0, &u64Kb); + if (RT_SUCCESS(rc)) + u64PagedTotal = u64Kb * _1K; + } + } + req.guestStats.u32PhysMemTotal = u64Total / _4K; + req.guestStats.u32PhysMemAvail = (u64Free + u64Buffers + u64Cached) / _4K; + req.guestStats.u32MemSystemCache = (u64Buffers + u64Cached) / _4K; + req.guestStats.u32PageFileSize = u64PagedTotal / _4K; + RTStrmClose(pStrm); + } + else + VGSvcVerbose(3, "vgsvcVMStatsReport: memory info not available!\n"); + + req.guestStats.u32PageSize = getpagesize(); + req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL + | VBOX_GUEST_STAT_PHYS_MEM_AVAIL + | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE + | VBOX_GUEST_STAT_PAGE_FILE_SIZE; +# ifdef VBOX_WITH_MEMBALLOON + req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K); + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON; +# else + req.guestStats.u32PhysMemBalloon = 0; +# endif + + + /** @todo req.guestStats.u32Threads */ + /** @todo req.guestStats.u32Processes */ + /* req.guestStats.u32Handles doesn't make sense here. */ + /** @todo req.guestStats.u32MemoryLoad */ + /** @todo req.guestStats.u32MemCommitTotal */ + /** @todo req.guestStats.u32MemKernelTotal */ + /** @todo req.guestStats.u32MemKernelPaged, make any sense? = u32MemKernelTotal? */ + /** @todo req.guestStats.u32MemKernelNonPaged, make any sense? = 0? */ + + bool fCpuInfoAvail = false; + rc = RTStrmOpen("/proc/stat", "r", &pStrm); + if (RT_SUCCESS(rc)) + { + for (;;) + { + rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine)); + if (RT_FAILURE(rc)) + break; + if ( strstr(szLine, "cpu") == szLine + && strlen(szLine) > 3 + && RT_C_IS_DIGIT(szLine[3])) + { + uint32_t u32CpuId; + rc = RTStrToUInt32Ex(&szLine[3], &psz, 0, &u32CpuId); + if (u32CpuId < VMM_MAX_CPU_COUNT) + { + uint64_t u64User = 0; + if (RT_SUCCESS(rc)) + rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64User); + + uint64_t u64Nice = 0; + if (RT_SUCCESS(rc)) + rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Nice); + + uint64_t u64System = 0; + if (RT_SUCCESS(rc)) + rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64System); + + uint64_t u64Idle = 0; + if (RT_SUCCESS(rc)) + rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Idle); + + uint64_t u64DeltaIdle = u64Idle - g_VMStat.au64LastCpuLoad_Idle[u32CpuId]; + uint64_t u64DeltaSystem = u64System - g_VMStat.au64LastCpuLoad_Kernel[u32CpuId]; + uint64_t u64DeltaUser = u64User - g_VMStat.au64LastCpuLoad_User[u32CpuId]; + uint64_t u64DeltaNice = u64Nice - g_VMStat.au64LastCpuLoad_Nice[u32CpuId]; + + uint64_t u64DeltaAll = u64DeltaIdle + + u64DeltaSystem + + u64DeltaUser + + u64DeltaNice; + if (u64DeltaAll == 0) /* Prevent division through zero. */ + u64DeltaAll = 1; + + g_VMStat.au64LastCpuLoad_Idle[u32CpuId] = u64Idle; + g_VMStat.au64LastCpuLoad_Kernel[u32CpuId] = u64System; + g_VMStat.au64LastCpuLoad_User[u32CpuId] = u64User; + g_VMStat.au64LastCpuLoad_Nice[u32CpuId] = u64Nice; + + req.guestStats.u32CpuLoad_Idle = (uint32_t)(u64DeltaIdle * 100 / u64DeltaAll); + req.guestStats.u32CpuLoad_Kernel = (uint32_t)(u64DeltaSystem * 100 / u64DeltaAll); + req.guestStats.u32CpuLoad_User = (uint32_t)((u64DeltaUser + + u64DeltaNice) * 100 / u64DeltaAll); + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE + | VBOX_GUEST_STAT_CPU_LOAD_KERNEL + | VBOX_GUEST_STAT_CPU_LOAD_USER; + req.guestStats.u32CpuId = u32CpuId; + fCpuInfoAvail = true; + rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", u32CpuId); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); + } + else + VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPU%u\n", u32CpuId); + } + } + RTStrmClose(pStrm); + } + if (!fCpuInfoAvail) + { + VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n"); + rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n"); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); + } + +#elif defined(RT_OS_SOLARIS) + VMMDevReportGuestStats req; + RT_ZERO(req); + kstat_ctl_t *pStatKern = kstat_open(); + if (pStatKern) + { + /* + * Memory statistics. + */ + uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0; + int rc = -1; + kstat_t *pStatPages = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"system_pages"); + if (pStatPages) + { + rc = kstat_read(pStatKern, pStatPages, NULL /* optional-copy-buf */); + if (rc != -1) + { + kstat_named_t *pStat = NULL; + pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"pagestotal"); + if (pStat) + u64Total = pStat->value.ul; + + pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"freemem"); + if (pStat) + u64Free = pStat->value.ul; + } + } + + kstat_t *pStatZFS = kstat_lookup(pStatKern, (char *)"zfs", 0 /* instance */, (char *)"arcstats"); + if (pStatZFS) + { + rc = kstat_read(pStatKern, pStatZFS, NULL /* optional-copy-buf */); + if (rc != -1) + { + kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(pStatZFS, (char *)"size"); + if (pStat) + u64Cached = pStat->value.ul; + } + } + + /* + * The vminfo are accumulative counters updated every "N" ticks. Let's get the + * number of stat updates so far and use that to divide the swap counter. + */ + kstat_t *pStatInfo = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"sysinfo"); + if (pStatInfo) + { + sysinfo_t SysInfo; + rc = kstat_read(pStatKern, pStatInfo, &SysInfo); + if (rc != -1) + { + kstat_t *pStatVMInfo = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"vminfo"); + if (pStatVMInfo) + { + vminfo_t VMInfo; + rc = kstat_read(pStatKern, pStatVMInfo, &VMInfo); + if (rc != -1) + { + Assert(SysInfo.updates != 0); + u64PagedTotal = VMInfo.swap_avail / SysInfo.updates; + } + } + } + } + + req.guestStats.u32PhysMemTotal = u64Total; /* already in pages */ + req.guestStats.u32PhysMemAvail = u64Free; /* already in pages */ + req.guestStats.u32MemSystemCache = u64Cached / _4K; + req.guestStats.u32PageFileSize = u64PagedTotal; /* already in pages */ + /** @todo req.guestStats.u32Threads */ + /** @todo req.guestStats.u32Processes */ + /** @todo req.guestStats.u32Handles -- ??? */ + /** @todo req.guestStats.u32MemoryLoad */ + /** @todo req.guestStats.u32MemCommitTotal */ + /** @todo req.guestStats.u32MemKernelTotal */ + /** @todo req.guestStats.u32MemKernelPaged */ + /** @todo req.guestStats.u32MemKernelNonPaged */ + req.guestStats.u32PageSize = getpagesize(); + + req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL + | VBOX_GUEST_STAT_PHYS_MEM_AVAIL + | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE + | VBOX_GUEST_STAT_PAGE_FILE_SIZE; +# ifdef VBOX_WITH_MEMBALLOON + req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K); + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON; +# else + req.guestStats.u32PhysMemBalloon = 0; +# endif + + /* + * CPU statistics. + */ + cpu_stat_t StatCPU; + RT_ZERO(StatCPU); + kstat_t *pStatNode = NULL; + uint32_t cCPUs = 0; + bool fCpuInfoAvail = false; + for (pStatNode = pStatKern->kc_chain; pStatNode != NULL; pStatNode = pStatNode->ks_next) + { + if (!strcmp(pStatNode->ks_module, "cpu_stat")) + { + rc = kstat_read(pStatKern, pStatNode, &StatCPU); + if (rc == -1) + break; + + if (cCPUs < VMM_MAX_CPU_COUNT) + { + uint64_t u64Idle = StatCPU.cpu_sysinfo.cpu[CPU_IDLE]; + uint64_t u64User = StatCPU.cpu_sysinfo.cpu[CPU_USER]; + uint64_t u64System = StatCPU.cpu_sysinfo.cpu[CPU_KERNEL]; + + uint64_t u64DeltaIdle = u64Idle - g_VMStat.au64LastCpuLoad_Idle[cCPUs]; + uint64_t u64DeltaSystem = u64System - g_VMStat.au64LastCpuLoad_Kernel[cCPUs]; + uint64_t u64DeltaUser = u64User - g_VMStat.au64LastCpuLoad_User[cCPUs]; + + uint64_t u64DeltaAll = u64DeltaIdle + u64DeltaSystem + u64DeltaUser; + if (u64DeltaAll == 0) /* Prevent division through zero. */ + u64DeltaAll = 1; + + g_VMStat.au64LastCpuLoad_Idle[cCPUs] = u64Idle; + g_VMStat.au64LastCpuLoad_Kernel[cCPUs] = u64System; + g_VMStat.au64LastCpuLoad_User[cCPUs] = u64User; + + req.guestStats.u32CpuId = cCPUs; + req.guestStats.u32CpuLoad_Idle = (uint32_t)(u64DeltaIdle * 100 / u64DeltaAll); + req.guestStats.u32CpuLoad_Kernel = (uint32_t)(u64DeltaSystem * 100 / u64DeltaAll); + req.guestStats.u32CpuLoad_User = (uint32_t)(u64DeltaUser * 100 / u64DeltaAll); + + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE + | VBOX_GUEST_STAT_CPU_LOAD_KERNEL + | VBOX_GUEST_STAT_CPU_LOAD_USER; + fCpuInfoAvail = true; + rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", cCPUs); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); + cCPUs++; + } + else + VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPU%u\n", cCPUs); + } + } + + /* + * Report whatever statistics were collected. + */ + if (!fCpuInfoAvail) + { + VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n"); + rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n"); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); + } + + kstat_close(pStatKern); + } + +#else + /** @todo implement for other platforms. */ + +#endif +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +DECLCALLBACK(int) vgsvcVMStatsWorker(bool volatile *pfShutdown) +{ + int rc = VINF_SUCCESS; + + /* Start monitoring of the stat event change event. */ + rc = VbglR3CtlFilterMask(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0); + if (RT_FAILURE(rc)) + { + VGSvcVerbose(3, "vgsvcVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc); + return rc; + } + + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Now enter the loop retrieving runtime data continuously. + */ + for (;;) + { + uint32_t fEvents = 0; + RTMSINTERVAL cWaitMillies; + + /* Check if an update interval change is pending. */ + rc = VbglR3WaitEvent(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0 /* no wait */, &fEvents); + if ( RT_SUCCESS(rc) + && (fEvents & VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST)) + VbglR3StatQueryInterval(&g_VMStat.cMsStatInterval); + + if (g_VMStat.cMsStatInterval) + { + vgsvcVMStatsReport(); + cWaitMillies = g_VMStat.cMsStatInterval; + } + else + cWaitMillies = 3000; + + /* + * Block for a while. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + int rc2 = RTSemEventMultiWait(g_VMStatEvent, cWaitMillies); + if (*pfShutdown) + break; + if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) + { + VGSvcError("vgsvcVMStatsWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); + rc = rc2; + break; + } + } + + /* Cancel monitoring of the stat event change event. */ + rc = VbglR3CtlFilterMask(0, VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST); + if (RT_FAILURE(rc)) + VGSvcVerbose(3, "vgsvcVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc); + + VGSvcVerbose(3, "VBoxStatsThread: finished statistics change request thread\n"); + return 0; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcVMStatsStop(void) +{ + RTSemEventMultiSignal(g_VMStatEvent); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcVMStatsTerm(void) +{ + if (g_VMStatEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_VMStatEvent); + g_VMStatEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'vminfo' service description. + */ +VBOXSERVICE g_VMStatistics = +{ + /* pszName. */ + "vmstats", + /* pszDescription. */ + "Virtual Machine Statistics", + /* pszUsage. */ + NULL, + /* pszOptions. */ + NULL, + /* methods */ + VGSvcDefaultPreInit, + VGSvcDefaultOption, + vgsvcVMStatsInit, + vgsvcVMStatsWorker, + vgsvcVMStatsStop, + vgsvcVMStatsTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp new file mode 100644 index 00000000..735d4e33 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp @@ -0,0 +1,809 @@ +/* $Id: VBoxServiceTimeSync.cpp $ */ +/** @file + * VBoxService - Guest Additions TimeSync Service. + */ + +/* + * Copyright (C) 2007-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc_timesync VBoxService - The Time Sync Service + * + * The time sync subservice synchronizes the guest OS walltime with the host. + * + * The time sync service plays along with the Time Manager (TM) in the VMM + * to keep the guest time accurate using the host machine as a reference. + * Communication is facilitated by VMMDev. TM will try its best to make sure + * all timer ticks get delivered so that there isn't normally any need to + * adjust the guest time. + * + * There are three normal (= acceptable) cases: + * -# When the service starts up. This is because ticks and such might + * be lost during VM and OS startup. (Need to figure out exactly why!) + * -# When the TM is unable to deliver all the ticks and swallows a + * backlog of ticks. The threshold for this is configurable with + * a default of 60 seconds. + * -# The time is adjusted on the host. This can be caused manually by + * the user or by some time sync daemon (NTP, LAN server, etc.). + * + * There are a number of very odd case where adjusting is needed. Here + * are some of them: + * -# Timer device emulation inaccuracies (like rounding). + * -# Inaccuracies in time source VirtualBox uses. + * -# The Guest and/or Host OS doesn't perform proper time keeping. This + * can come about as a result of OS and/or hardware issues. + * + * The TM is our source for the host time and will make adjustments for + * current timer delivery lag. The simplistic approach taken by TM is to + * adjust the host time by the current guest timer delivery lag, meaning that + * if the guest is behind 1 second with PIT/RTC/++ ticks, this should be + * reflected in the guest wall time as well. + * + * Now, there is any amount of trouble we can cause by changing the time. + * Most applications probably use the wall time when they need to measure + * things. A walltime that is being juggled about every so often, even if just + * a little bit, could occasionally upset these measurements by for instance + * yielding negative results. + * + * This bottom line here is that the time sync service isn't really supposed + * to do anything and will try avoid having to do anything when possible. + * + * The implementation uses the latency it takes to query host time as the + * absolute maximum precision to avoid messing up under timer tick catchup + * and/or heavy host/guest load. (Rationale is that a *lot* of stuff may + * happen on our way back from ring-3 and TM/VMMDev since we're taking the + * route thru the inner EM loop with its force flag processing.) + * + * But this latency has to be measured from our perspective, which means it + * could just as easily come out as 0. (OS/2 and Windows guests only update + * the current time when the timer ticks for instance.) The good thing is + * that this isn't really a problem since we won't ever do anything unless + * the drift is noticeable. + * + * It now boils down to these three (configuration) factors: + * -# g_cMsTimeSyncMinAdjust - The minimum drift we will ever bother with. + * -# g_TimeSyncLatencyFactor - The factor we multiply the latency by to + * calculate the dynamic minimum adjust factor. + * -# g_cMsTimeSyncMaxLatency - When to start discarding the data as utterly + * useless and take a rest (someone is too busy to give us good data). + * -# g_TimeSyncSetThreshold - The threshold at which we will just set the time + * instead of trying to adjust it (milliseconds). + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#else +# include <unistd.h> +# include <errno.h> +# include <time.h> +# include <sys/time.h> +#endif + +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <VBox/err.h> +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The timesync interval (milliseconds). */ +static uint32_t g_TimeSyncInterval = 0; +/** + * @see pg_vgsvc_timesync + * + * @remark OS/2: There is either a 1 second resolution on the DosSetDateTime + * API or a bug in my settimeofday implementation. Thus, don't + * bother unless there is at least a 1 second drift. + */ +#ifdef RT_OS_OS2 +static uint32_t g_cMsTimeSyncMinAdjust = 1000; +#else +static uint32_t g_cMsTimeSyncMinAdjust = 100; +#endif +/** @see pg_vgsvc_timesync */ +static uint32_t g_TimeSyncLatencyFactor = 8; +/** @see pg_vgsvc_timesync */ +static uint32_t g_cMsTimeSyncMaxLatency = 250; +/** @see pg_vgsvc_timesync */ +static uint32_t g_TimeSyncSetThreshold = 20*60*1000; +/** Whether the next adjustment should just set the time instead of trying to + * adjust it. This is used to implement --timesync-set-start. + * For purposes of setting the kernel timezone, OS/2 always starts with this. */ +#ifdef RT_OS_OS2 +static bool volatile g_fTimeSyncSetOnStart = true; +#else +static bool volatile g_fTimeSyncSetOnStart = false; +#endif +/** Whether to set the time when the VM was restored. */ +static bool g_fTimeSyncSetOnRestore = true; +/** The logging verbosity level. + * This uses the global verbosity level by default. */ +static uint32_t g_cTimeSyncVerbosity = 0; + +/** Current error count. Used to decide when to bitch and when not to. */ +static uint32_t g_cTimeSyncErrors = 0; + +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_TimeSyncEvent = NIL_RTSEMEVENTMULTI; + +/** The VM session ID. Changes whenever the VM is restored or reset. */ +static uint64_t g_idTimeSyncSession; + +#ifdef RT_OS_WINDOWS +/** Process token. */ +static HANDLE g_hTokenProcess = NULL; +/** Old token privileges. */ +static TOKEN_PRIVILEGES g_TkOldPrivileges; +/** Backup values for time adjustment. */ +static DWORD g_dwWinTimeAdjustment; +static DWORD g_dwWinTimeIncrement; +static BOOL g_bWinTimeAdjustmentDisabled; +#endif + + +/** + * @interface_method_impl{VBOXSERVICE,pfnPreInit} + */ +static DECLCALLBACK(int) vgsvcTimeSyncPreInit(void) +{ + /* Use global verbosity as default. */ + g_cTimeSyncVerbosity = g_cVerbosity; + +#ifdef VBOX_WITH_GUEST_PROPS + /** @todo Merge this function with vgsvcTimeSyncOption() to generalize + * the "command line args override guest property values" behavior. */ + + /* + * Read the service options from the VM's guest properties. + * Note that these options can be overridden by the command line options later. + */ + uint32_t uGuestPropSvcClientID; + int rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID); + if (RT_FAILURE(rc)) + { + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ + { + VGSvcVerbose(0, "VMInfo: Guest property service is not available, skipping\n"); + rc = VINF_SUCCESS; + } + else + VGSvcError("Failed to connect to the guest property service! Error: %Rrc\n", rc); + } + else + { + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-interval", + &g_TimeSyncInterval, 50, UINT32_MAX - 1); + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-min-adjust", + &g_cMsTimeSyncMinAdjust, 0, 3600000); + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-latency-factor", + &g_TimeSyncLatencyFactor, 1, 1024); + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-max-latency", + &g_cMsTimeSyncMaxLatency, 1, 3600000); + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold", + &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000 /* a week */); + + if (VbglR3GuestPropExist(uGuestPropSvcClientID, + "/VirtualBox/GuestAdd/VBoxService/--timesync-set-start")) + g_fTimeSyncSetOnStart = true; + + if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-start")) + g_fTimeSyncSetOnStart = false; + + + if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-on-restore")) + g_fTimeSyncSetOnRestore = true; + + if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-on-restore")) + g_fTimeSyncSetOnRestore = false; + + uint32_t uValue; + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-verbosity", + &uValue, 0 /*uMin*/, 255 /*uMax*/); + if (RT_SUCCESS(rc)) + g_cTimeSyncVerbosity = uValue; + + VbglR3GuestPropDisconnect(uGuestPropSvcClientID); + } + + if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */ + rc = VINF_SUCCESS; + return rc; +#else + /* Nothing to do here yet. */ + return VINF_SUCCESS; +#endif +} + + +/** + * Displays a verbose message based on the currently + * set timesync verbosity level. + * + * @param iLevel Minimum log level required to display this message. + * @param pszFormat The message text. + * @param ... Format arguments. + */ +static void vgsvcTimeSyncLog(unsigned iLevel, const char *pszFormat, ...) +{ + if (iLevel <= g_cTimeSyncVerbosity) + { + va_list va; + va_start(va, pszFormat); + VGSvcLogV(pszFormat, va); + va_end(va); + } +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption} + */ +static DECLCALLBACK(int) vgsvcTimeSyncOption(const char **ppszShort, int argc, char **argv, int *pi) +{ + int rc = VINF_SUCCESS; + if (ppszShort) + rc = -1 ;/* no short options */ + else if (!strcmp(argv[*pi], "--timesync-interval")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncInterval, 50, UINT32_MAX - 1); + else if (!strcmp(argv[*pi], "--timesync-min-adjust")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMinAdjust, 0, 3600000); + else if (!strcmp(argv[*pi], "--timesync-latency-factor")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncLatencyFactor, 1, 1024); + else if (!strcmp(argv[*pi], "--timesync-max-latency")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMaxLatency, 1, 3600000); + else if (!strcmp(argv[*pi], "--timesync-set-threshold")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000); /* a week */ + else if (!strcmp(argv[*pi], "--timesync-set-start")) + g_fTimeSyncSetOnStart = true; + else if (!strcmp(argv[*pi], "--timesync-no-set-start")) + g_fTimeSyncSetOnStart = false; + else if (!strcmp(argv[*pi], "--timesync-set-on-restore")) + g_fTimeSyncSetOnRestore = true; + else if (!strcmp(argv[*pi], "--timesync-no-set-on-restore")) + g_fTimeSyncSetOnRestore = false; + else if (!strcmp(argv[*pi], "--timesync-verbosity")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cTimeSyncVerbosity, 0 /*uMin*/, 255 /*uMax*/); + else + rc = -1; + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcTimeSyncInit(void) +{ + /* + * If not specified, find the right interval default. + * Then create the event sem to block on. + */ + if (!g_TimeSyncInterval) + g_TimeSyncInterval = g_DefaultInterval * 1000; + if (!g_TimeSyncInterval) + g_TimeSyncInterval = 10 * 1000; + + VbglR3GetSessionId(&g_idTimeSyncSession); + /* The status code is ignored as this information is not available with VBox < 3.2.10. */ + + int rc = RTSemEventMultiCreate(&g_TimeSyncEvent); + AssertRC(rc); +#ifdef RT_OS_WINDOWS + if (RT_SUCCESS(rc)) + { + /* + * Adjust privileges of this process so we can make system time adjustments. + */ + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &g_hTokenProcess)) + { + TOKEN_PRIVILEGES tkPriv; + RT_ZERO(tkPriv); + tkPriv.PrivilegeCount = 1; + tkPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &tkPriv.Privileges[0].Luid)) + { + DWORD cbRet = sizeof(g_TkOldPrivileges); + if (AdjustTokenPrivileges(g_hTokenProcess, FALSE, &tkPriv, sizeof(TOKEN_PRIVILEGES), &g_TkOldPrivileges, &cbRet)) + rc = VINF_SUCCESS; + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Adjusting token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", + dwErr, rc); + } + } + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Looking up token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", + dwErr, rc); + } + if (RT_FAILURE(rc)) + { + CloseHandle(g_hTokenProcess); + g_hTokenProcess = NULL; + } + } + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Opening process token (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", + dwErr, rc); + g_hTokenProcess = NULL; + } + } + + if (g_pfnGetSystemTimeAdjustment) + { + if (g_pfnGetSystemTimeAdjustment(&g_dwWinTimeAdjustment, &g_dwWinTimeIncrement, &g_bWinTimeAdjustmentDisabled)) + vgsvcTimeSyncLog(0, "vgsvcTimeSyncInit: Initially %ld (100ns) units per %ld (100 ns) units interval, disabled=%d\n", + g_dwWinTimeAdjustment, g_dwWinTimeIncrement, g_bWinTimeAdjustmentDisabled ? 1 : 0); + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Could not get time adjustment values! Last error: %ld!\n", dwErr); + } + } +#endif /* RT_OS_WINDOWS */ + + return rc; +} + + +/** + * Try adjusting the time using adjtime or similar. + * + * @returns true on success, false on failure. + * + * @param pDrift The time adjustment. + */ +static bool vgsvcTimeSyncAdjust(PCRTTIMESPEC pDrift) +{ +#ifdef RT_OS_WINDOWS +/** @todo r=bird: g_hTokenProcess cannot be NULL here. + * vgsvcTimeSyncInit will fail and the service will not be started with + * it being NULL. vgsvcTimeSyncInit OTOH will *NOT* be called until the + * service thread has terminated. If anything + * else is the case, there is buggy code somewhere.*/ + if (g_hTokenProcess == NULL) /* Is the token already closed when shutting down? */ + return false; + + /* The API appeared in NT 3.50. */ + if ( !g_pfnSetSystemTimeAdjustment + || !g_pfnGetSystemTimeAdjustment) + return false; + + DWORD dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwWinTimeIncrement; + BOOL fWinTimeAdjustmentDisabled; + if (g_pfnGetSystemTimeAdjustment(&dwWinTimeAdjustment, &dwWinTimeIncrement, &fWinTimeAdjustmentDisabled)) + { + DWORD dwDiffMax = g_dwWinTimeAdjustment * 0.50; + DWORD dwDiffNew = dwWinTimeAdjustment * 0.10; + + if (RTTimeSpecGetMilli(pDrift) > 0) + { + dwWinNewTimeAdjustment = dwWinTimeAdjustment + dwDiffNew; + if (dwWinNewTimeAdjustment > (g_dwWinTimeAdjustment + dwDiffMax)) + { + dwWinNewTimeAdjustment = g_dwWinTimeAdjustment + dwDiffMax; + dwDiffNew = dwDiffMax; + } + } + else + { + dwWinNewTimeAdjustment = dwWinTimeAdjustment - dwDiffNew; + if (dwWinNewTimeAdjustment < (g_dwWinTimeAdjustment - dwDiffMax)) + { + dwWinNewTimeAdjustment = g_dwWinTimeAdjustment - dwDiffMax; + dwDiffNew = dwDiffMax; + } + } + + vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: Drift=%lldms\n", RTTimeSpecGetMilli(pDrift)); + vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: OrgTA=%ld, CurTA=%ld, NewTA=%ld, DiffNew=%ld, DiffMax=%ld\n", + g_dwWinTimeAdjustment, dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwDiffNew, dwDiffMax); + if (g_pfnSetSystemTimeAdjustment(dwWinNewTimeAdjustment, FALSE /* Periodic adjustments enabled. */)) + { + g_cTimeSyncErrors = 0; + return true; + } + + if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncAdjust: SetSystemTimeAdjustment failed, error=%u\n", GetLastError()); + } + else if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncAdjust: GetSystemTimeAdjustment failed, error=%ld\n", GetLastError()); + +#elif defined(RT_OS_OS2) || defined(RT_OS_HAIKU) + /* No API for doing gradual time adjustments. */ + +#else /* PORTME */ + /* + * Try using adjtime(), most unix-like systems have this. + */ + struct timeval tv; + RTTimeSpecGetTimeval(pDrift, &tv); + if (adjtime(&tv, NULL) == 0) + { + vgsvcTimeSyncLog(1, "vgsvcTimeSyncAdjust: adjtime by %RDtimespec\n", pDrift); + g_cTimeSyncErrors = 0; + return true; + } +#endif + + /* failed */ + return false; +} + + +/** + * Cancels any pending time adjustment. + * + * Called when we've caught up and before calls to vgsvcTimeSyncSet. + */ +static void vgsvcTimeSyncCancelAdjust(void) +{ +#ifdef RT_OS_WINDOWS +/** @todo r=bird: g_hTokenProcess cannot be NULL here. See argumentation in + * vgsvcTimeSyncAdjust. */ + if (g_hTokenProcess == NULL) /* No process token (anymore)? */ + return; + if (!g_pfnSetSystemTimeAdjustment) + return; + if (g_pfnSetSystemTimeAdjustment(0, TRUE /* Periodic adjustments disabled. */)) + vgsvcTimeSyncLog(5, "vgsvcTimeSyncCancelAdjust: Windows Time Adjustment is now disabled.\n"); + else if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncCancelAdjust: SetSystemTimeAdjustment(,disable) failed, error=%u\n", GetLastError()); +#endif /* !RT_OS_WINDOWS */ +} + + +/** + * Set the wall clock to compensate for drift. + * + * @returns true on success, false on failure. + * + * @param pDrift The time adjustment. + */ +static void vgsvcTimeSyncSet(PCRTTIMESPEC pDrift) +{ + /* + * Query the current time, adjust it by adding the drift and set it. + */ + RTTIMESPEC NewGuestTime; + int rc = RTTimeSet(RTTimeSpecAdd(RTTimeNow(&NewGuestTime), pDrift)); + if (RT_SUCCESS(rc)) + { + /* Succeeded - reset the error count and log the change. */ + g_cTimeSyncErrors = 0; + + if (g_cTimeSyncVerbosity >= 1) + { + char sz[64]; + RTTIME Time; + vgsvcTimeSyncLog(1, "time set to %s\n", RTTimeToString(RTTimeExplode(&Time, &NewGuestTime), sz, sizeof(sz))); +#ifdef DEBUG + RTTIMESPEC Tmp; + vgsvcTimeSyncLog(3, " now %s\n", RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&Tmp)), sz, sizeof(sz))); +#endif + } + } + else if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncSet: RTTimeSet(%RDtimespec) failed: %Rrc\n", &NewGuestTime, rc); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +DECLCALLBACK(int) vgsvcTimeSyncWorker(bool volatile *pfShutdown) +{ + RTTIME Time; + int rc = VINF_SUCCESS; + + /* + * Tell the control thread that it can continue spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Initialize the last host and guest times to prevent log message. + * We also track whether we set the time in the previous loop. + */ + RTTIMESPEC HostLast; + if (RT_FAILURE(VbglR3GetHostTime(&HostLast))) + RTTimeSpecSetNano(&HostLast, 0); + RTTIMESPEC GuestLast; + RTTimeNow(&GuestLast); + bool fSetTimeLastLoop = false; + + /* + * The Work Loop. + */ + for (;;) + { + /* + * Try to get a reliable time reading. + */ + int cTries = 3; + do + { + /* + * Query the session id (first to keep lantency low) and the time. + */ + uint64_t idNewSession = g_idTimeSyncSession; + if (g_fTimeSyncSetOnRestore) + VbglR3GetSessionId(&idNewSession); + + RTTIMESPEC GuestNow0; + RTTimeNow(&GuestNow0); + + RTTIMESPEC HostNow; + int rc2 = VbglR3GetHostTime(&HostNow); + if (RT_FAILURE(rc2)) + { + if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncWorker: VbglR3GetHostTime failed; rc2=%Rrc\n", rc2); + break; + } + + RTTIMESPEC GuestNow; + RTTimeNow(&GuestNow); + + /* + * Calc latency and check if it's ok. + */ + RTTIMESPEC GuestElapsed = GuestNow; + RTTimeSpecSub(&GuestElapsed, &GuestNow0); + if ((uint32_t)RTTimeSpecGetMilli(&GuestElapsed) < g_cMsTimeSyncMaxLatency) + { + /* + * If we were just restored, set the adjustment threshold to zero to force a resync. + */ + uint32_t TimeSyncSetThreshold = g_TimeSyncSetThreshold; + if ( g_fTimeSyncSetOnRestore + && idNewSession != g_idTimeSyncSession) + { + vgsvcTimeSyncLog(2, "vgsvcTimeSyncWorker: The VM session ID changed, forcing resync.\n"); + g_idTimeSyncSession = idNewSession; + TimeSyncSetThreshold = 0; + } + + /* + * Calculate the adjustment threshold and the current drift. + */ + uint32_t MinAdjust = RTTimeSpecGetMilli(&GuestElapsed) * g_TimeSyncLatencyFactor; + if (MinAdjust < g_cMsTimeSyncMinAdjust) + MinAdjust = g_cMsTimeSyncMinAdjust; + + RTTIMESPEC Drift = HostNow; + RTTimeSpecSub(&Drift, &GuestNow); + if (RTTimeSpecGetMilli(&Drift) < 0) + MinAdjust += g_cMsTimeSyncMinAdjust; /* extra buffer against moving time backwards. */ + + RTTIMESPEC AbsDrift = Drift; + RTTimeSpecAbsolute(&AbsDrift); + + if (g_cTimeSyncVerbosity >= 4) + { + char sz1[64]; + char sz2[64]; + vgsvcTimeSyncLog(4, "vgsvcTimeSyncWorker: Host: %s (MinAdjust: %RU32 ms), Guest: %s => %RDtimespec drift\n", + RTTimeToString(RTTimeExplode(&Time, &HostNow), sz1, sizeof(sz1)), MinAdjust, + RTTimeToString(RTTimeExplode(&Time, &GuestNow), sz2, sizeof(sz2)), &Drift); + } + + bool fSetTimeInThisLoop = false; + uint64_t AbsDriftMilli = RTTimeSpecGetMilli(&AbsDrift); + if ( AbsDriftMilli > MinAdjust + || g_fTimeSyncSetOnStart) + { + /* + * Ok, the drift is above the threshold. + * + * Try a gradual adjustment first, if that fails or the drift is + * too big, fall back on just setting the time. + */ + if ( AbsDriftMilli > TimeSyncSetThreshold + || g_fTimeSyncSetOnStart + || !vgsvcTimeSyncAdjust(&Drift)) + { + vgsvcTimeSyncCancelAdjust(); + vgsvcTimeSyncSet(&Drift); + fSetTimeInThisLoop = true; + } + + /* + * Log radical host time changes. + */ + int64_t cNsHostDelta = RTTimeSpecGetNano(&HostNow) - RTTimeSpecGetNano(&HostLast); + if ((uint64_t)RT_ABS(cNsHostDelta) > RT_NS_1HOUR / 2) + vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical host time change: %'RI64ns (HostNow=%RDtimespec HostLast=%RDtimespec)\n", + cNsHostDelta, &HostNow, &HostLast); + } + else + vgsvcTimeSyncCancelAdjust(); + HostLast = HostNow; + + /* + * Log radical guest time changes (we could be the cause of these, mind). + * Note! Right now we don't care about an extra log line after we called + * vgsvcTimeSyncSet. fSetTimeLastLoop helps show it though. + */ + int64_t cNsGuestDelta = RTTimeSpecGetNano(&GuestNow) - RTTimeSpecGetNano(&GuestLast); + if ((uint64_t)RT_ABS(cNsGuestDelta) > RT_NS_1HOUR / 2) + vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical guest time change: %'RI64ns (GuestNow=%RDtimespec GuestLast=%RDtimespec fSetTimeLastLoop=%RTbool)\n", + cNsGuestDelta, &GuestNow, &GuestLast, fSetTimeLastLoop); + GuestLast = GuestNow; + fSetTimeLastLoop = fSetTimeInThisLoop; + break; + } + vgsvcTimeSyncLog(3, "vgsvcTimeSyncWorker: %RDtimespec: latency too high (%RDtimespec, max %ums) sleeping 1s\n", + &GuestNow, &GuestElapsed, g_cMsTimeSyncMaxLatency); + RTThreadSleep(1000); + } while (--cTries > 0); + + /* Clear the set-next/set-start flag. */ + g_fTimeSyncSetOnStart = false; + + /* + * Block for a while. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + int rc2 = RTSemEventMultiWait(g_TimeSyncEvent, g_TimeSyncInterval); + if (*pfShutdown) + break; + if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) + { + VGSvcError("vgsvcTimeSyncWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); + rc = rc2; + break; + } + } + + vgsvcTimeSyncCancelAdjust(); + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcTimeSyncStop(void) +{ + if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI) + RTSemEventMultiSignal(g_TimeSyncEvent); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcTimeSyncTerm(void) +{ +#ifdef RT_OS_WINDOWS + /* + * Restore the SE_SYSTEMTIME_NAME token privileges (if init succeeded). + */ + if (g_hTokenProcess) + { + if (!AdjustTokenPrivileges(g_hTokenProcess, FALSE, &g_TkOldPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) + { + DWORD dwErr = GetLastError(); + VGSvcError("vgsvcTimeSyncTerm: Restoring token privileges (SE_SYSTEMTIME_NAME) failed with code %u!\n", dwErr); + } + CloseHandle(g_hTokenProcess); + g_hTokenProcess = NULL; + } +#endif /* !RT_OS_WINDOWS */ + + if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_TimeSyncEvent); + g_TimeSyncEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'timesync' service description. + */ +VBOXSERVICE g_TimeSync = +{ + /* pszName. */ + "timesync", + /* pszDescription. */ + "Time synchronization", + /* pszUsage. */ + " [--timesync-interval <ms>] [--timesync-min-adjust <ms>]\n" + " [--timesync-latency-factor <x>] [--timesync-max-latency <ms>]\n" + " [--timesync-set-threshold <ms>]\n" + " [--timesync-set-start|--timesync-no-set-start]\n" + " [--timesync-set-on-restore|--timesync-no-set-on-restore]\n" + " [--timesync-verbosity <level>]" + , + /* pszOptions. */ + " --timesync-interval Specifies the interval at which to synchronize the\n" + " time with the host. The default is 10000 ms.\n" + " --timesync-min-adjust The minimum absolute drift value measured in\n" + " milliseconds to make adjustments for.\n" + " The default is 1000 ms on OS/2 and 100 ms elsewhere.\n" + " --timesync-latency-factor\n" + " The factor to multiply the time query latency with\n" + " to calculate the dynamic minimum adjust time.\n" + " The default is 8 times.\n" + " --timesync-max-latency The max host timer query latency to accept.\n" + " The default is 250 ms.\n" + " --timesync-set-threshold\n" + " The absolute drift threshold, given as milliseconds,\n" + " where to start setting the time instead of trying to\n" + " adjust it. The default is 20 min.\n" + " --timesync-set-start, --timesync-no-set-start \n" + " Set the time when starting the time sync service.\n" +#ifdef RT_OS_OS2 + " Default: --timesync-set-start\n" +#else + " Default: --timesync-no-set-start\n" +#endif + " --timesync-set-on-restore, --timesync-no-set-on-restore\n" + " Whether to immediately set the time when the VM is\n" + " restored or not. Default: --timesync-set-on-restore\n" + " --timesync-verbosity Sets the verbosity level. Defaults to service wide\n" + " verbosity level.\n" + , + /* methods */ + vgsvcTimeSyncPreInit, + vgsvcTimeSyncOption, + vgsvcTimeSyncInit, + vgsvcTimeSyncWorker, + vgsvcTimeSyncStop, + vgsvcTimeSyncTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp new file mode 100644 index 00000000..ce22f656 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp @@ -0,0 +1,1771 @@ +/* $Id: VBoxServiceToolBox.cpp $ */ +/** @file + * VBoxServiceToolbox - Internal (BusyBox-like) toolbox. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/buildconfig.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/symlink.h> + +#ifndef RT_OS_WINDOWS +# include <sys/stat.h> /* need umask */ +#endif + +#include <VBox/VBoxGuestLib.h> +#include <VBox/version.h> + +#include <VBox/GuestHost/GuestControl.h> + +#include "VBoxServiceInternal.h" +#include "VBoxServiceToolBox.h" +#include "VBoxServiceUtils.h" + +using namespace guestControl; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Generic option indices for commands. */ +enum +{ + VBOXSERVICETOOLBOXOPT_MACHINE_READABLE = 1000, + VBOXSERVICETOOLBOXOPT_VERBOSE +}; + +/** Options indices for "vbox_cat". */ +typedef enum VBOXSERVICETOOLBOXCATOPT +{ + VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED = 1000 +} VBOXSERVICETOOLBOXCATOPT; + +/** Flags for "vbox_ls". */ +typedef enum VBOXSERVICETOOLBOXLSFLAG +{ + VBOXSERVICETOOLBOXLSFLAG_NONE, + VBOXSERVICETOOLBOXLSFLAG_RECURSIVE, + VBOXSERVICETOOLBOXLSFLAG_SYMLINKS +} VBOXSERVICETOOLBOXLSFLAG; + +/** Flags for fs object output. */ +typedef enum VBOXSERVICETOOLBOXOUTPUTFLAG +{ + VBOXSERVICETOOLBOXOUTPUTFLAG_NONE, + VBOXSERVICETOOLBOXOUTPUTFLAG_LONG, + VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE +} VBOXSERVICETOOLBOXOUTPUTFLAG; + +/** The size of the directory entry buffer we're using. */ +#define VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE (sizeof(RTDIRENTRYEX) + RTPATH_MAX) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to a tool handler function. */ +typedef RTEXITCODE (*PFNHANDLER)(int , char **); + +/** Definition for a specific toolbox tool. */ +typedef struct VBOXSERVICETOOLBOXTOOL +{ + /** Friendly name of the tool. */ + const char *pszName; + /** Main handler to be invoked to use the tool. */ + RTEXITCODE (*pfnHandler)(int argc, char **argv); + /** Conversion routine to convert the tool's exit code back to an IPRT rc. Optional. + * + * @todo r=bird: You better revert this, i.e. having pfnHandler return a VBox + * status code and have a routine for converting it to RTEXITCODE. + * Unless, what you really want to do here is to get a cached status, in + * which case you better call it what it is. + */ + int (*pfnExitCodeConvertToRc)(RTEXITCODE rcExit); +} VBOXSERVICETOOLBOXTOOL; +/** Pointer to a const tool definition. */ +typedef VBOXSERVICETOOLBOXTOOL const *PCVBOXSERVICETOOLBOXTOOL; + +/** + * An file/directory entry. Used to cache + * file names/paths for later processing. + */ +typedef struct VBOXSERVICETOOLBOXPATHENTRY +{ + /** Our node. */ + RTLISTNODE Node; + /** Name of the entry. */ + char *pszName; +} VBOXSERVICETOOLBOXPATHENTRY, *PVBOXSERVICETOOLBOXPATHENTRY; + +/** ID cache entry. */ +typedef struct VGSVCTOOLBOXUIDENTRY +{ + /** The identifier name. */ + uint32_t id; + /** Set if UID, clear if GID. */ + bool fIsUid; + /** The name. */ + char szName[128 - 4 - 1]; +} VGSVCTOOLBOXUIDENTRY; +typedef VGSVCTOOLBOXUIDENTRY *PVGSVCTOOLBOXUIDENTRY; + + +/** ID cache. */ +typedef struct VGSVCTOOLBOXIDCACHE +{ + /** Number of valid cache entries. */ + uint32_t cEntries; + /** The next entry to replace. */ + uint32_t iNextReplace; + /** The cache entries. */ + VGSVCTOOLBOXUIDENTRY aEntries[16]; +} VGSVCTOOLBOXIDCACHE; +typedef VGSVCTOOLBOXIDCACHE *PVGSVCTOOLBOXIDCACHE; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static RTEXITCODE vgsvcToolboxCat(int argc, char **argv); +static RTEXITCODE vgsvcToolboxLs(int argc, char **argv); +static RTEXITCODE vgsvcToolboxRm(int argc, char **argv); +static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv); +static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv); +static RTEXITCODE vgsvcToolboxStat(int argc, char **argv); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Tool definitions. */ +static VBOXSERVICETOOLBOXTOOL const g_aTools[] = +{ + { VBOXSERVICE_TOOL_CAT, vgsvcToolboxCat , NULL }, + { VBOXSERVICE_TOOL_LS, vgsvcToolboxLs , NULL }, + { VBOXSERVICE_TOOL_RM, vgsvcToolboxRm , NULL }, + { VBOXSERVICE_TOOL_MKTEMP, vgsvcToolboxMkTemp, NULL }, + { VBOXSERVICE_TOOL_MKDIR, vgsvcToolboxMkDir , NULL }, + { VBOXSERVICE_TOOL_STAT, vgsvcToolboxStat , NULL } +}; + + + + +/** + * Displays a common header for all help text to stdout. + */ +static void vgsvcToolboxShowUsageHeader(void) +{ + RTPrintf(VBOX_PRODUCT " Guest Toolbox Version " + VBOX_VERSION_STRING "\n" + "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n\n"); + RTPrintf("Usage:\n\n"); +} + + +/** + * Displays a help text to stdout. + */ +static void vgsvcToolboxShowUsage(void) +{ + vgsvcToolboxShowUsageHeader(); + RTPrintf(" VBoxService [--use-toolbox] vbox_<command> [<general options>] <parameters>\n\n" + "General options:\n\n" + " --machinereadable produce all output in machine-readable form\n" + " -V print version number and exit\n" + "\n" + "Commands:\n\n" + " vbox_cat [<general options>] <file>...\n" + " vbox_ls [<general options>] [--dereference|-L] [-l] [-R]\n" + " [--verbose|-v] [<file>...]\n" + " vbox_rm [<general options>] [-r|-R] <file>...\n" + " vbox_mktemp [<general options>] [--directory|-d] [--mode|-m <mode>]\n" + " [--secure|-s] [--tmpdir|-t <path>] <template>\n" + " vbox_mkdir [<general options>] [--mode|-m <mode>] [--parents|-p]\n" + " [--verbose|-v] <directory>...\n" + " vbox_stat [<general options>] [--file-system|-f]\n" + " [--dereference|-L] [--terse|-t] [--verbose|-v] <file>...\n" + "\n"); +} + + +/** + * Displays the program's version number. + */ +static void vgsvcToolboxShowVersion(void) +{ + RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision()); +} + + +/** + * Initializes the parseable stream(s). + * + * @return IPRT status code. + */ +static int vgsvcToolboxStrmInit(void) +{ + /* Set stdout's mode to binary. This is required for outputting all the machine-readable + * data correctly. */ + int rc = RTStrmSetMode(g_pStdOut, true /* Binary mode */, -1 /* Current code set, not changed */); + if (RT_FAILURE(rc)) + RTMsgError("Unable to set stdout to binary mode, rc=%Rrc\n", rc); + + return rc; +} + + +/** + * Prints a parseable stream header which contains the actual tool + * which was called/used along with its stream version. + * + * @param pszToolName Name of the tool being used, e.g. "vbt_ls". + * @param uVersion Stream version name. Handy for distinguishing + * different stream versions later. + */ +static void vgsvcToolboxPrintStrmHeader(const char *pszToolName, uint32_t uVersion) +{ + AssertPtrReturnVoid(pszToolName); + RTPrintf("hdr_id=%s%chdr_ver=%u%c", pszToolName, 0, uVersion, 0); +} + + +/** + * Prints a standardized termination sequence indicating that the + * parseable stream just ended. + * + */ +static void vgsvcToolboxPrintStrmTermination() +{ + RTPrintf("%c%c%c%c", 0, 0, 0, 0); +} + + +/** + * Parse a file mode string from the command line (currently octal only) + * and print an error message and return an error if necessary. + */ +static int vgsvcToolboxParseMode(const char *pcszMode, RTFMODE *pfMode) +{ + int rc = RTStrToUInt32Ex(pcszMode, NULL, 8 /* Base */, pfMode); + if (RT_FAILURE(rc)) /* Only octet based values supported right now! */ + RTMsgError("Mode flag strings not implemented yet! Use octal numbers instead. (%s)\n", pcszMode); + return rc; +} + + +/** + * Destroys a path buffer list. + * + * @return IPRT status code. + * @param pList Pointer to list to destroy. + */ +static void vgsvcToolboxPathBufDestroy(PRTLISTNODE pList) +{ + if (!pList) + return; + + PVBOXSERVICETOOLBOXPATHENTRY pEntry, pEntryNext; + RTListForEachSafe(pList, pEntry, pEntryNext, VBOXSERVICETOOLBOXPATHENTRY, Node) + { + RTListNodeRemove(&pEntry->Node); + + RTStrFree(pEntry->pszName); + RTMemFree(pEntry); + } +} + + +/** + * Adds a path entry (file/directory/whatever) to a given path buffer list. + * + * @return IPRT status code. + * @param pList Pointer to list to add entry to. + * @param pszName Name of entry to add. + */ +static int vgsvcToolboxPathBufAddPathEntry(PRTLISTNODE pList, const char *pszName) +{ + AssertPtrReturn(pList, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + PVBOXSERVICETOOLBOXPATHENTRY pNode = (PVBOXSERVICETOOLBOXPATHENTRY)RTMemAlloc(sizeof(VBOXSERVICETOOLBOXPATHENTRY)); + if (pNode) + { + pNode->pszName = RTStrDup(pszName); + AssertPtr(pNode->pszName); + + RTListAppend(pList, &pNode->Node); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Performs the actual output operation of "vbox_cat". + * + * @return IPRT status code. + * @param hInput Handle of input file (if any) to use; + * else stdin will be used. + * @param hOutput Handle of output file (if any) to use; + * else stdout will be used. + */ +static int vgsvcToolboxCatOutput(RTFILE hInput, RTFILE hOutput) +{ + int rc = VINF_SUCCESS; + if (hInput == NIL_RTFILE) + { + rc = RTFileFromNative(&hInput, RTFILE_NATIVE_STDIN); + if (RT_FAILURE(rc)) + RTMsgError("Could not translate input file to native handle, rc=%Rrc\n", rc); + } + + if (hOutput == NIL_RTFILE) + { + rc = RTFileFromNative(&hOutput, RTFILE_NATIVE_STDOUT); + if (RT_FAILURE(rc)) + RTMsgError("Could not translate output file to native handle, rc=%Rrc\n", rc); + } + + if (RT_SUCCESS(rc)) + { + uint8_t abBuf[_64K]; + size_t cbRead; + for (;;) + { + rc = RTFileRead(hInput, abBuf, sizeof(abBuf), &cbRead); + if (RT_SUCCESS(rc) && cbRead > 0) + { + rc = RTFileWrite(hOutput, abBuf, cbRead, NULL /* Try to write all at once! */); + if (RT_FAILURE(rc)) + { + RTMsgError("Error while writing output, rc=%Rrc\n", rc); + break; + } + } + else + { + if (rc == VERR_BROKEN_PIPE) + rc = VINF_SUCCESS; + else if (RT_FAILURE(rc)) + RTMsgError("Error while reading input, rc=%Rrc\n", rc); + break; + } + } + } + return rc; +} + + +/** @todo Document options! */ +static char g_paszCatHelp[] = + " VBoxService [--use-toolbox] vbox_cat [<general options>] <file>...\n\n" + "Concatenate files, or standard input, to standard output.\n" + "\n"; + + +/** + * Main function for tool "vbox_cat". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxCat(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + /* Sorted by short ops. */ + { "--show-all", 'a', RTGETOPT_REQ_NOTHING }, + { "--number-nonblank", 'b', RTGETOPT_REQ_NOTHING}, + { NULL, 'e', RTGETOPT_REQ_NOTHING}, + { NULL, 'E', RTGETOPT_REQ_NOTHING}, + { "--flags", 'f', RTGETOPT_REQ_STRING}, + { "--no-content-indexed", VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED, RTGETOPT_REQ_NOTHING}, + { "--number", 'n', RTGETOPT_REQ_NOTHING}, + { "--output", 'o', RTGETOPT_REQ_STRING}, + { "--squeeze-blank", 's', RTGETOPT_REQ_NOTHING}, + { NULL, 't', RTGETOPT_REQ_NOTHING}, + { "--show-tabs", 'T', RTGETOPT_REQ_NOTHING}, + { NULL, 'u', RTGETOPT_REQ_NOTHING}, + { "--show-noneprinting", 'v', RTGETOPT_REQ_NOTHING} + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, 0 /*fFlags*/); + + int rc = VINF_SUCCESS; + + const char *pszOutput = NULL; + RTFILE hOutput = NIL_RTFILE; + uint32_t fFlags = RTFILE_O_CREATE_REPLACE /* Output file flags. */ + | RTFILE_O_WRITE + | RTFILE_O_DENY_WRITE; + + /* Init directory list. */ + RTLISTANCHOR inputList; + RTListInit(&inputList); + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'a': + case 'b': + case 'e': + case 'E': + case 'n': + case 's': + case 't': + case 'T': + case 'v': + RTMsgError("Sorry, option '%s' is not implemented yet!\n", + ValueUnion.pDef->pszLong); + rc = VERR_INVALID_PARAMETER; + break; + + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszCatHelp); + return RTEXITCODE_SUCCESS; + + case 'o': + pszOutput = ValueUnion.psz; + break; + + case 'u': + /* Ignored. */ + break; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED: + fFlags |= RTFILE_O_NOT_CONTENT_INDEXED; + break; + + case VINF_GETOPT_NOT_OPTION: + /* Add file(s) to buffer. This enables processing multiple paths + * at once. + * + * Since the non-options (RTGETOPTINIT_FLAGS_OPTS_FIRST) come last when + * processing this loop it's safe to immediately exit on syntax errors + * or showing the help text (see above). */ + rc = vgsvcToolboxPathBufAddPathEntry(&inputList, ValueUnion.psz); + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + if (RT_SUCCESS(rc)) + { + if (pszOutput) + { + rc = RTFileOpen(&hOutput, pszOutput, fFlags); + if (RT_FAILURE(rc)) + RTMsgError("Could not create output file '%s', rc=%Rrc\n", pszOutput, rc); + } + + if (RT_SUCCESS(rc)) + { + /* Process each input file. */ + RTFILE hInput = NIL_RTFILE; + PVBOXSERVICETOOLBOXPATHENTRY pNodeIt; + RTListForEach(&inputList, pNodeIt, VBOXSERVICETOOLBOXPATHENTRY, Node) + { + rc = RTFileOpen(&hInput, pNodeIt->pszName, + RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + rc = vgsvcToolboxCatOutput(hInput, hOutput); + RTFileClose(hInput); + } + else + RTMsgError("Could not open input file '%s': %Rrc\n", pNodeIt->pszName, rc); + if (RT_FAILURE(rc)) + break; + } + + /* If no input files were defined, process stdin. */ + if (RTListNodeIsFirst(&inputList, &inputList)) + rc = vgsvcToolboxCatOutput(hInput, hOutput); + } + } + + if (hOutput != NIL_RTFILE) + RTFileClose(hOutput); + vgsvcToolboxPathBufDestroy(&inputList); + + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_ACCESS_DENIED: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED; + + case VERR_FILE_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND; + + case VERR_PATH_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND; + + case VERR_SHARING_VIOLATION: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION; + + case VERR_IS_A_DIRECTORY: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_IS_A_DIRECTORY; + + default: +#ifdef DEBUG_andy + AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc)); +#endif + break; + } + + return RTEXITCODE_FAILURE; + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Resolves the UID to a name as best as we can. + * + * @returns Read-only name string. Only valid till the next cache call. + * @param pIdCache The ID cache. + * @param uid The UID to resolve. + * @param pszEntry The filename of the UID. + * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute. + */ +static const char *vgsvcToolboxIdCacheGetUidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTUID uid, + const char *pszEntry, const char *pszRelativeTo) +{ + /* Check cached entries. */ + for (uint32_t i = 0; i < pIdCache->cEntries; i++) + if ( pIdCache->aEntries[i].id == uid + && pIdCache->aEntries[i].fIsUid) + return pIdCache->aEntries[i].szName; + + /* Miss. */ + RTFSOBJINFO ObjInfo; + RT_ZERO(ObjInfo); /* shut up msc */ + int rc; + if (!pszRelativeTo) + rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK); + else + { + char szPath[RTPATH_MAX]; + rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry); + if (RT_SUCCESS(rc)) + rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK); + } + + if ( RT_SUCCESS(rc) + && ObjInfo.Attr.u.UnixOwner.uid == uid) + { + uint32_t i = pIdCache->cEntries; + if (i < RT_ELEMENTS(pIdCache->aEntries)) + pIdCache->cEntries = i + 1; + else + i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries); + pIdCache->aEntries[i].id = uid; + pIdCache->aEntries[i].fIsUid = true; + RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixOwner.szName); + return pIdCache->aEntries[i].szName; + } + return ""; +} + + +/** + * Resolves the GID to a name as best as we can. + * + * @returns Read-only name string. Only valid till the next cache call. + * @param pIdCache The ID cache. + * @param gid The GID to resolve. + * @param pszEntry The filename of the GID. + * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute. + */ +static const char *vgsvcToolboxIdCacheGetGidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTGID gid, + const char *pszEntry, const char *pszRelativeTo) +{ + /* Check cached entries. */ + for (uint32_t i = 0; i < pIdCache->cEntries; i++) + if ( pIdCache->aEntries[i].id == gid + && !pIdCache->aEntries[i].fIsUid) + return pIdCache->aEntries[i].szName; + + /* Miss. */ + RTFSOBJINFO ObjInfo; + RT_ZERO(ObjInfo); /* shut up msc */ + int rc; + if (!pszRelativeTo) + rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK); + else + { + char szPath[RTPATH_MAX]; + rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry); + if (RT_SUCCESS(rc)) + rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK); + } + + if ( RT_SUCCESS(rc) + && ObjInfo.Attr.u.UnixGroup.gid == gid) + { + uint32_t i = pIdCache->cEntries; + if (i < RT_ELEMENTS(pIdCache->aEntries)) + pIdCache->cEntries = i + 1; + else + i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries); + pIdCache->aEntries[i].id = gid; + pIdCache->aEntries[i].fIsUid = false; + RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixGroup.szName); + return pIdCache->aEntries[i].szName; + } + return ""; +} + + +/** + * Prints information (based on given flags) of a file system object (file/directory/...) + * to stdout. + * + * @return IPRT status code. + * @param pszName Object name. + * @param cchName Length of pszName. + * @param fOutputFlags Output / handling flags of type + * VBOXSERVICETOOLBOXOUTPUTFLAG. + * @param pszRelativeTo What pszName is relative to. + * @param pIdCache The ID cache. + * @param pObjInfo Pointer to object information. + */ +static int vgsvcToolboxPrintFsInfo(const char *pszName, size_t cchName, uint32_t fOutputFlags, const char *pszRelativeTo, + PVGSVCTOOLBOXIDCACHE pIdCache, PRTFSOBJINFO pObjInfo) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertReturn(cchName, VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER); + + RTFMODE fMode = pObjInfo->Attr.fMode; + char chFileType; + switch (fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_FIFO: chFileType = 'f'; break; + case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break; + case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break; + case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break; + case RTFS_TYPE_FILE: chFileType = '-'; break; + case RTFS_TYPE_SYMLINK: chFileType = 'l'; break; + case RTFS_TYPE_SOCKET: chFileType = 's'; break; + case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break; + default: chFileType = '?'; break; + } + /** @todo sticy bits++ */ + +/** @todo r=bird: turns out the host doesn't use or need cname_len, so perhaps we could drop it? */ + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_LONG)) + { + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + RTPrintf("ftype=%c%cnode_id=%RU64%inode_dev=%RU32%ccname_len=%zu%cname=%s%c", + chFileType, 0, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0, + (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0, cchName, 0, pszName, 0); + RTPrintf("%c%c", 0, 0); + } + else + RTPrintf("%c %#18llx %3zu %s\n", chFileType, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, cchName, pszName); + } + else + { + char szTimeBirth[RTTIME_STR_LEN]; + char szTimeChange[RTTIME_STR_LEN]; + char szTimeModification[RTTIME_STR_LEN]; + char szTimeAccess[RTTIME_STR_LEN]; + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + RTPrintf("ftype=%c%c", chFileType, 0); + if (pObjInfo->Attr.u.Unix.INodeId || pObjInfo->Attr.u.Unix.INodeIdDevice) + RTPrintf("node_id=%RU64%cinode_dev=%RU32%c", (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0, + (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0); + RTPrintf("owner_mask=%c%c%c%c", + fMode & RTFS_UNIX_IRUSR ? 'r' : '-', + fMode & RTFS_UNIX_IWUSR ? 'w' : '-', + fMode & RTFS_UNIX_IXUSR ? 'x' : '-', 0); + RTPrintf("group_mask=%c%c%c%c", + fMode & RTFS_UNIX_IRGRP ? 'r' : '-', + fMode & RTFS_UNIX_IWGRP ? 'w' : '-', + fMode & RTFS_UNIX_IXGRP ? 'x' : '-', 0); + RTPrintf("other_mask=%c%c%c%c", + fMode & RTFS_UNIX_IROTH ? 'r' : '-', + fMode & RTFS_UNIX_IWOTH ? 'w' : '-', + fMode & RTFS_UNIX_IXOTH ? 'x' : '-', 0); + /** @todo sticky bits. */ + RTPrintf("dos_mask=%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", + fMode & RTFS_DOS_READONLY ? 'R' : '-', + fMode & RTFS_DOS_HIDDEN ? 'H' : '-', + fMode & RTFS_DOS_SYSTEM ? 'S' : '-', + fMode & RTFS_DOS_DIRECTORY ? 'D' : '-', + fMode & RTFS_DOS_ARCHIVED ? 'A' : '-', + fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-', + fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-', + fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-', + fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-', + fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-', + fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-', + fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-', + fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-', + fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-', 0); + RTPrintf("hlinks=%RU32%cst_size=%RI64%calloc=%RI64%c", + pObjInfo->Attr.u.Unix.cHardlinks, 0, + pObjInfo->cbObject, 0, + pObjInfo->cbAllocated, 0); + RTPrintf("st_birthtime=%s%cst_ctime=%s%cst_mtime=%s%cst_atime=%s%c", + RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), 0, + RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), 0, + RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), 0, + RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)), 0); + if (pObjInfo->Attr.u.Unix.uid != NIL_RTUID) + RTPrintf("uid=%RU32%cusername=%s%c", pObjInfo->Attr.u.Unix.uid, 0, + vgsvcToolboxIdCacheGetUidName(pIdCache, pObjInfo->Attr.u.Unix.uid, pszName, pszRelativeTo), 0); + if (pObjInfo->Attr.u.Unix.gid != NIL_RTGID) + RTPrintf("gid=%RU32%cgroupname=%s%c", pObjInfo->Attr.u.Unix.gid, 0, + vgsvcToolboxIdCacheGetGidName(pIdCache, pObjInfo->Attr.u.Unix.gid, pszName, pszRelativeTo), 0); + if ( (RTFS_IS_DEV_BLOCK(pObjInfo->Attr.fMode) || RTFS_IS_DEV_CHAR(pObjInfo->Attr.fMode)) + && pObjInfo->Attr.u.Unix.Device) + RTPrintf("st_rdev=%RU32%c", pObjInfo->Attr.u.Unix.Device, 0); + if (pObjInfo->Attr.u.Unix.GenerationId) + RTPrintf("st_gen=%RU32%c", pObjInfo->Attr.u.Unix.GenerationId, 0); + if (pObjInfo->Attr.u.Unix.fFlags) + RTPrintf("st_flags=%RU32%c", pObjInfo->Attr.u.Unix.fFlags, 0); + RTPrintf("cname_len=%zu%cname=%s%c", cchName, 0, pszName, 0); + RTPrintf("%c%c", 0, 0); /* End of data block. */ + } + else + { + RTPrintf("%c", chFileType); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IRUSR ? 'r' : '-', + fMode & RTFS_UNIX_IWUSR ? 'w' : '-', + fMode & RTFS_UNIX_IXUSR ? 'x' : '-'); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IRGRP ? 'r' : '-', + fMode & RTFS_UNIX_IWGRP ? 'w' : '-', + fMode & RTFS_UNIX_IXGRP ? 'x' : '-'); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IROTH ? 'r' : '-', + fMode & RTFS_UNIX_IWOTH ? 'w' : '-', + fMode & RTFS_UNIX_IXOTH ? 'x' : '-'); + RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c", + fMode & RTFS_DOS_READONLY ? 'R' : '-', + fMode & RTFS_DOS_HIDDEN ? 'H' : '-', + fMode & RTFS_DOS_SYSTEM ? 'S' : '-', + fMode & RTFS_DOS_DIRECTORY ? 'D' : '-', + fMode & RTFS_DOS_ARCHIVED ? 'A' : '-', + fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-', + fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-', + fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-', + fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-', + fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-', + fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-', + fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-', + fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-', + fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-'); + RTPrintf(" %d %4d %4d %10lld %10lld", + pObjInfo->Attr.u.Unix.cHardlinks, + pObjInfo->Attr.u.Unix.uid, + pObjInfo->Attr.u.Unix.gid, + pObjInfo->cbObject, + pObjInfo->cbAllocated); + RTPrintf(" %s %s %s %s", + RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), + RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), + RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), + RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) ); + RTPrintf(" %2zu %s\n", cchName, pszName); + } + } + + return VINF_SUCCESS; +} + +/** + * Helper routine for ls tool for handling sub directories. + * + * @return IPRT status code. + * @param pszDir Pointer to the directory buffer. + * @param cchDir The length of pszDir in pszDir. + * @param pDirEntry Pointer to the directory entry. + * @param fFlags Flags of type VBOXSERVICETOOLBOXLSFLAG. + * @param fOutputFlags Flags of type VBOXSERVICETOOLBOXOUTPUTFLAG. + * @param pIdCache The ID cache. + */ +static int vgsvcToolboxLsHandleDirSub(char *pszDir, size_t cchDir, PRTDIRENTRYEX pDirEntry, + uint32_t fFlags, uint32_t fOutputFlags, PVGSVCTOOLBOXIDCACHE pIdCache) +{ + Assert(cchDir > 0); Assert(pszDir[cchDir] == '\0'); + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + RTPrintf("dname=%s%c", pszDir, 0); + else if (fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE) + RTPrintf("%s:\n", pszDir); + + /* Make sure we've got some room in the path, to save us extra work further down. */ + if (cchDir + 3 >= RTPATH_MAX) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Path too long: '%s'\n", pszDir); + return VERR_BUFFER_OVERFLOW; + } + + /* Open directory. */ + RTDIR hDir; + int rc = RTDirOpen(&hDir, pszDir); + if (RT_FAILURE(rc)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Failed to open directory '%s', rc=%Rrc\n", pszDir, rc); + return rc; + } + + /* Ensure we've got a trailing slash (there is space for it see above). */ + if (!RTPATH_IS_SEP(pszDir[cchDir - 1])) + { + pszDir[cchDir++] = RTPATH_SLASH; + pszDir[cchDir] = '\0'; + } + + /* + * Process the files and subdirs. + */ + for (;;) + { + /* Get the next directory. */ + size_t cbDirEntry = VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE; + rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_FAILURE(rc)) + break; + + /* Check length. */ + if (pDirEntry->cbName + cchDir + 3 >= RTPATH_MAX) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Path too long: '%s' in '%.*s'\n", pDirEntry->szName, cchDir, pszDir); + rc = VERR_BUFFER_OVERFLOW; + break; + } + + switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_SYMLINK: + { + if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS)) + break; + RT_FALL_THRU(); + } + case RTFS_TYPE_DIRECTORY: + { + rc = vgsvcToolboxPrintFsInfo(pDirEntry->szName, pDirEntry->cbName, fOutputFlags, pszDir, + pIdCache, &pDirEntry->Info); + if (RT_FAILURE(rc)) + break; + + if (RTDirEntryExIsStdDotLink(pDirEntry)) + continue; + + if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE)) + continue; + + memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1); + int rc2 = vgsvcToolboxLsHandleDirSub(pszDir, cchDir + pDirEntry->cbName, pDirEntry, fFlags, fOutputFlags, pIdCache); + if (RT_SUCCESS(rc)) + rc = rc2; + break; + } + + case RTFS_TYPE_FILE: + { + rc = vgsvcToolboxPrintFsInfo(pDirEntry->szName, pDirEntry->cbName, fOutputFlags, pszDir, + pIdCache, &pDirEntry->Info); + break; + } + + default: + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Entry '%.*s%s' of mode %#x not supported, skipping", + cchDir, pszDir, pDirEntry->szName, pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK); + break; + } + } + } + if (rc != VERR_NO_MORE_FILES) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("RTDirReadEx failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir); + } + + rc = RTDirClose(hDir); + if (RT_FAILURE(rc)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("RTDirClose failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir); + } + + return rc; +} + +/** + * Helper routine for ls tool doing the actual parsing and output of + * a specified directory. + * + * @return IPRT status code. + * @param pszDir Absolute path to directory to ouptut. + * @param fFlags Flags of type VBOXSERVICETOOLBOXLSFLAG. + * @param fOutputFlags Flags of type VBOXSERVICETOOLBOXOUTPUTFLAG. + * @param pIdCache The ID cache. + */ +static int vgsvcToolboxLsHandleDir(const char *pszDir, uint32_t fFlags, uint32_t fOutputFlags, PVGSVCTOOLBOXIDCACHE pIdCache) +{ + AssertPtrReturn(pszDir, VERR_INVALID_PARAMETER); + AssertPtrReturn(pIdCache, VERR_INVALID_PARAMETER); + + char szPath[RTPATH_MAX]; + int rc = RTPathAbs(pszDir, szPath, sizeof(szPath)); + if (RT_FAILURE(rc)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("RTPathAbs failed on '%s': %Rrc\n", pszDir, rc); + return rc; + } + + union + { + uint8_t abPadding[VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE]; + RTDIRENTRYEX DirEntry; + } uBuf; + return vgsvcToolboxLsHandleDirSub(szPath, strlen(szPath), &uBuf.DirEntry, fFlags, fOutputFlags, pIdCache); +} + + +/** @todo Document options! */ +static char g_paszLsHelp[] = + " VBoxService [--use-toolbox] vbox_ls [<general options>] [option]...\n" + " [<file>...]\n\n" + "List information about files (the current directory by default).\n\n" + "Options:\n\n" + " [--dereference|-L]\n" + " [-l][-R]\n" + " [--verbose|-v]\n" + " [<file>...]\n" + "\n"; + + +/** + * Main function for tool "vbox_ls". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxLs(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING }, + { "--dereference", 'L', RTGETOPT_REQ_NOTHING }, + { NULL, 'l', RTGETOPT_REQ_NOTHING }, + { NULL, 'R', RTGETOPT_REQ_NOTHING }, + { "--verbose", VBOXSERVICETOOLBOXOPT_VERBOSE, RTGETOPT_REQ_NOTHING} + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), + 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_INIT); + + bool fVerbose = false; + uint32_t fFlags = VBOXSERVICETOOLBOXLSFLAG_NONE; + uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_NONE; + + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszLsHelp); + return RTEXITCODE_SUCCESS; + + case 'L': /* Dereference symlinks. */ + fFlags |= VBOXSERVICETOOLBOXLSFLAG_SYMLINKS; + break; + + case 'l': /* Print long format. */ + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_LONG; + break; + + case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE: + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE; + break; + + case 'R': /* Recursive processing. */ + fFlags |= VBOXSERVICETOOLBOXLSFLAG_RECURSIVE; + break; + + case VBOXSERVICETOOLBOXOPT_VERBOSE: + fVerbose = true; + break; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VINF_GETOPT_NOT_OPTION: + Assert(GetState.iNext); + GetState.iNext--; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + + /* All flags / options processed? Bail out here. + * Processing the file / directory list comes down below. */ + if (ch == VINF_GETOPT_NOT_OPTION) + break; + } + + /* Print magic/version. */ + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + rc = vgsvcToolboxStrmInit(); + if (RT_FAILURE(rc)) + RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc); + vgsvcToolboxPrintStrmHeader("vbt_ls", 1 /* Stream version */); + } + + VGSVCTOOLBOXIDCACHE IdCache; + RT_ZERO(IdCache); + + char szDirCur[RTPATH_MAX]; + rc = RTPathGetCurrent(szDirCur, sizeof(szDirCur)); + if (RT_FAILURE(rc)) + { + RTMsgError("Getting current directory failed, rc=%Rrc\n", rc); + return RTEXITCODE_FAILURE; + } + + ch = RTGetOpt(&GetState, &ValueUnion); + do + { + char const *pszPath; + + if (ch == 0) /* Use current directory if no element specified. */ + pszPath = szDirCur; + else + pszPath = ValueUnion.psz; + + RTFSOBJINFO objInfo; + int rc2 = RTPathQueryInfoEx(pszPath, &objInfo, + RTFSOBJATTRADD_UNIX, + fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc2)) + { + if ( RTFS_IS_FILE(objInfo.Attr.fMode) + || ( RTFS_IS_SYMLINK(objInfo.Attr.fMode) + && (fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS))) + { + rc2 = vgsvcToolboxPrintFsInfo(pszPath, strlen(pszPath), fOutputFlags, NULL, &IdCache, &objInfo); + if (RT_SUCCESS(rc)) /* Keep initial failing rc. */ + rc = rc2; + } + else if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode)) + { + rc2 = vgsvcToolboxLsHandleDir(pszPath, fFlags, fOutputFlags, &IdCache); + if (RT_SUCCESS(rc)) /* Keep initial failing rc. */ + rc = rc2; + } + } + else + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Cannot access '%s': No such file or directory\n", pszPath); + if (RT_SUCCESS(rc)) + rc = VERR_FILE_NOT_FOUND; + /* Do not break here -- process every element in the list + * and keep failing rc. */ + } + + } while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0); + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + vgsvcToolboxPrintStrmTermination(); + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/* Try using RTPathRmCmd. */ +static RTEXITCODE vgsvcToolboxRm(int argc, char **argv) +{ + return RTPathRmCmd(argc, argv); +} + + +static char g_paszMkTempHelp[] = + " VBoxService [--use-toolbox] vbox_mktemp [<general options>] [<options>]\n" + " <template>\n\n" + "Create a temporary directory based on the template supplied. The first string\n" + "of consecutive 'X' characters in the template will be replaced to form a unique\n" + "name for the directory. The template may not contain a path. The default\n" + "creation mode is 0600 for files and 0700 for directories. If no path is\n" + "specified the default temporary directory will be used.\n" + "Options:\n\n" + " [--directory|-d] Create a directory instead of a file.\n" + " [--mode|-m <mode>] Create the object with mode <mode>.\n" + " [--secure|-s] Fail if the object cannot be created securely.\n" + " [--tmpdir|-t <path>] Create the object with the absolute path <path>.\n" + "\n"; + + +/** + * Report the result of a vbox_mktemp operation. + * + * Either errors to stderr (not machine-readable) or everything to stdout as + * {name}\0{rc}\0 (machine- readable format). The message may optionally + * contain a '%s' for the file name and an %Rrc for the result code in that + * order. In future a "verbose" flag may be added, without which nothing will + * be output in non-machine- readable mode. Sets prc if rc is a non-success + * code. + */ +static void toolboxMkTempReport(const char *pcszMessage, const char *pcszFile, + bool fActive, int rc, uint32_t fOutputFlags, int *prc) +{ + if (!fActive) + return; + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + if (RT_SUCCESS(rc)) + RTPrintf(pcszMessage, pcszFile, rc); + else + RTMsgError(pcszMessage, pcszFile, rc); + else + RTPrintf("name=%s%crc=%d%c", pcszFile, 0, rc, 0); + if (prc && RT_FAILURE(rc)) + *prc = rc; +} + + +/** + * Main function for tool "vbox_mktemp". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, + RTGETOPT_REQ_NOTHING }, + { "--directory", 'd', RTGETOPT_REQ_NOTHING }, + { "--mode", 'm', RTGETOPT_REQ_STRING }, + { "--secure", 's', RTGETOPT_REQ_NOTHING }, + { "--tmpdir", 't', RTGETOPT_REQ_STRING }, + }; + + enum + { + /* Isn't that a bit long? s/VBOXSERVICETOOLBOX/VSTB/ ? */ + /** Create a temporary directory instead of a temporary file. */ + VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY = RT_BIT_32(0), + /** Only create the temporary object if the operation is expected + * to be secure. Not guaranteed to be supported on a particular + * set-up. */ + VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE = RT_BIT_32(1) + }; + + int ch, rc; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_INIT); + + uint32_t fFlags = 0; + uint32_t fOutputFlags = 0; + int cNonOptions = 0; + RTFMODE fMode = 0700; + bool fModeSet = false; + const char *pcszPath = NULL; + const char *pcszTemplate; + char szTemplateWithPath[RTPATH_MAX] = ""; + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszMkTempHelp); + return RTEXITCODE_SUCCESS; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE: + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE; + break; + + case 'd': + fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY; + break; + + case 'm': + rc = vgsvcToolboxParseMode(ValueUnion.psz, &fMode); + if (RT_FAILURE(rc)) + return RTEXITCODE_SYNTAX; + fModeSet = true; +#ifndef RT_OS_WINDOWS + umask(0); /* RTDirCreate workaround */ +#endif + break; + case 's': + fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE; + break; + + case 't': + pcszPath = ValueUnion.psz; + break; + + case VINF_GETOPT_NOT_OPTION: + /* RTGetOpt will sort these to the end of the argv vector so + * that we will deal with them afterwards. */ + ++cNonOptions; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + /* Print magic/version. */ + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + rc = vgsvcToolboxStrmInit(); + if (RT_FAILURE(rc)) + RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc); + vgsvcToolboxPrintStrmHeader("vbt_mktemp", 1 /* Stream version */); + } + + if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE && fModeSet) + { + toolboxMkTempReport("'-s' and '-m' parameters cannot be used together.\n", "", + true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_SYNTAX; + } + + /* We need exactly one template, containing at least one 'X'. */ + if (cNonOptions != 1) + { + toolboxMkTempReport("Please specify exactly one template.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_SYNTAX; + } + pcszTemplate = argv[argc - 1]; + + /* Validate that the template is as IPRT requires (asserted by IPRT). */ + if ( RTPathHasPath(pcszTemplate) + || ( !strstr(pcszTemplate, "XXX") + && pcszTemplate[strlen(pcszTemplate) - 1] != 'X')) + { + toolboxMkTempReport("Template '%s' should contain a file name with no path and at least three consecutive 'X' characters or ending in 'X'.\n", + pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + if (pcszPath && !RTPathStartsWithRoot(pcszPath)) + { + toolboxMkTempReport("Path '%s' should be absolute.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + if (pcszPath) + { + rc = RTStrCopy(szTemplateWithPath, sizeof(szTemplateWithPath), pcszPath); + if (RT_FAILURE(rc)) + { + toolboxMkTempReport("Path '%s' too long.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + } + else + { + rc = RTPathTemp(szTemplateWithPath, sizeof(szTemplateWithPath)); + if (RT_FAILURE(rc)) + { + toolboxMkTempReport("Failed to get the temporary directory.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + } + rc = RTPathAppend(szTemplateWithPath, sizeof(szTemplateWithPath), pcszTemplate); + if (RT_FAILURE(rc)) + { + toolboxMkTempReport("Template '%s' too long for path.\n", pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + + if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY) + { + rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE + ? RTDirCreateTempSecure(szTemplateWithPath) + : RTDirCreateTemp(szTemplateWithPath, fMode); + toolboxMkTempReport("Created temporary directory '%s'.\n", + szTemplateWithPath, RT_SUCCESS(rc), rc, + fOutputFlags, NULL); + /* RTDirCreateTemp[Secure] sets the template to "" on failure. */ + toolboxMkTempReport("The following error occurred while creating a temporary directory from template '%s': %Rrc.\n", + pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/); + } + else + { + rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE + ? RTFileCreateTempSecure(szTemplateWithPath) + : RTFileCreateTemp(szTemplateWithPath, fMode); + toolboxMkTempReport("Created temporary file '%s'.\n", + szTemplateWithPath, RT_SUCCESS(rc), rc, + fOutputFlags, NULL); + /* RTFileCreateTemp[Secure] sets the template to "" on failure. */ + toolboxMkTempReport("The following error occurred while creating a temporary file from template '%s': %Rrc.\n", + pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/); + } + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + vgsvcToolboxPrintStrmTermination(); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** @todo Document options! */ +static char g_paszMkDirHelp[] = + " VBoxService [--use-toolbox] vbox_mkdir [<general options>] [<options>]\n" + " <directory>...\n\n" + "Options:\n\n" + " [--mode|-m <mode>] The file mode to set (chmod) on the created\n" + " directories. Default: a=rwx & umask.\n" + " [--parents|-p] Create parent directories as needed, no\n" + " error if the directory already exists.\n" + " [--verbose|-v] Display a message for each created directory.\n" + "\n"; + + +/** + * Main function for tool "vbox_mkdir". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--mode", 'm', RTGETOPT_REQ_STRING }, + { "--parents", 'p', RTGETOPT_REQ_NOTHING}, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING} + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), + 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_INIT); + + bool fMakeParentDirs = false; + bool fVerbose = false; + RTFMODE fDirMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO; + int cDirsCreated = 0; + + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'p': + fMakeParentDirs = true; + break; + + case 'm': + rc = vgsvcToolboxParseMode(ValueUnion.psz, &fDirMode); + if (RT_FAILURE(rc)) + return RTEXITCODE_SYNTAX; +#ifndef RT_OS_WINDOWS + umask(0); /* RTDirCreate workaround */ +#endif + break; + + case 'v': + fVerbose = true; + break; + + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszMkDirHelp); + return RTEXITCODE_SUCCESS; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VINF_GETOPT_NOT_OPTION: + if (fMakeParentDirs) + /** @todo r=bird: If fVerbose is set, we should also show + * which directories that get created, parents as well as + * omitting existing final dirs. Annoying, but check any + * mkdir implementation (try "mkdir -pv asdf/1/2/3/4" + * twice). */ + rc = RTDirCreateFullPath(ValueUnion.psz, fDirMode); + else + rc = RTDirCreate(ValueUnion.psz, fDirMode, 0); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Could not create directory '%s': %Rra\n", + ValueUnion.psz, rc); + if (fVerbose) + RTMsgInfo("Created directory '%s', mode %#RTfmode\n", ValueUnion.psz, fDirMode); + cDirsCreated++; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + AssertRC(rc); + + if (cDirsCreated == 0) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No directory argument."); + + return RTEXITCODE_SUCCESS; +} + + +/** @todo Document options! */ +static char g_paszStatHelp[] = + " VBoxService [--use-toolbox] vbox_stat [<general options>] [<options>]\n" + " <file>...\n\n" + "Display file or file system status.\n\n" + "Options:\n\n" + " [--file-system|-f]\n" + " [--dereference|-L]\n" + " [--terse|-t]\n" + " [--verbose|-v]\n" + "\n"; + + +/** + * Main function for tool "vbox_stat". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxStat(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--file-system", 'f', RTGETOPT_REQ_NOTHING }, + { "--dereference", 'L', RTGETOPT_REQ_NOTHING }, + { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING }, + { "--terse", 't', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + int rc = VINF_SUCCESS; + uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_LONG; /* Use long mode by default. */ + uint32_t fQueryInfoFlags = RTPATH_F_ON_LINK; + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'f': + RTMsgError("Sorry, option '%s' is not implemented yet!\n", ValueUnion.pDef->pszLong); + rc = VERR_INVALID_PARAMETER; + break; + + case 'L': + fQueryInfoFlags &= ~RTPATH_F_ON_LINK; + fQueryInfoFlags |= RTPATH_F_FOLLOW_LINK; + break; + + case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE: + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE; + break; + + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszStatHelp); + return RTEXITCODE_SUCCESS; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VINF_GETOPT_NOT_OPTION: + { + Assert(GetState.iNext); + GetState.iNext--; + break; + } + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + + /* All flags / options processed? Bail out here. + * Processing the file / directory list comes down below. */ + if (ch == VINF_GETOPT_NOT_OPTION) + break; + } + + if (RT_SUCCESS(rc)) + { + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + { + rc = vgsvcToolboxStrmInit(); + if (RT_FAILURE(rc)) + RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc); + vgsvcToolboxPrintStrmHeader("vbt_stat", 1 /* Stream version */); + } + + VGSVCTOOLBOXIDCACHE IdCache; + RT_ZERO(IdCache); + + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + RTFSOBJINFO objInfo; + int rc2 = RTPathQueryInfoEx(ValueUnion.psz, &objInfo, RTFSOBJATTRADD_UNIX, fQueryInfoFlags); + if (RT_FAILURE(rc2)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Cannot stat for '%s': %Rrc\n", ValueUnion.psz, rc2); + } + else + rc2 = vgsvcToolboxPrintFsInfo(ValueUnion.psz, strlen(ValueUnion.psz), fOutputFlags, NULL, &IdCache, &objInfo); + + if (RT_SUCCESS(rc)) + rc = rc2; + /* Do not break here -- process every element in the list + * and keep (initial) failing rc. */ + } + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + vgsvcToolboxPrintStrmTermination(); + + /* At this point the overall result (success/failure) should be in rc. */ + } + else + RTMsgError("Failed with rc=%Rrc\n", rc); + + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_ACCESS_DENIED: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_ACCESS_DENIED; + + case VERR_FILE_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_FILE_NOT_FOUND; + + case VERR_PATH_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_PATH_NOT_FOUND; + + case VERR_NET_PATH_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_NET_PATH_NOT_FOUND; + + case VERR_INVALID_NAME: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_INVALID_NAME; + + default: +#ifdef DEBUG_andy + AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc)); +#endif + break; + } + + return RTEXITCODE_FAILURE; + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Looks up the tool definition entry for the tool give by @a pszTool. + * + * @returns Pointer to the tool definition. NULL if not found. + * @param pszTool The name of the tool. + */ +static PCVBOXSERVICETOOLBOXTOOL vgsvcToolboxLookUp(const char *pszTool) +{ + AssertPtrReturn(pszTool, NULL); + + /* Do a linear search, since we don't have that much stuff in the table. */ + for (unsigned i = 0; i < RT_ELEMENTS(g_aTools); i++) + if (!strcmp(g_aTools[i].pszName, pszTool)) + return &g_aTools[i]; + + return NULL; +} + + +/** + * Converts a tool's exit code back to an IPRT error code. + * + * @return Converted IPRT status code. + * @param pszTool Name of the toolbox tool to convert exit code for. + * @param rcExit The tool's exit code to convert. + */ +int VGSvcToolboxExitCodeConvertToRc(const char *pszTool, RTEXITCODE rcExit) +{ + AssertPtrReturn(pszTool, VERR_INVALID_POINTER); + + PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool); + if (pTool) + return pTool->pfnExitCodeConvertToRc(rcExit); + + AssertMsgFailed(("Tool '%s' not found\n", pszTool)); + return VERR_GENERAL_FAILURE; /* Lookup failed, should not happen. */ +} + + +/** + * Entry point for internal toolbox. + * + * @return True if an internal tool was handled, false if not. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + * @param prcExit Where to store the exit code when an + * internal toolbox command was handled. + */ +bool VGSvcToolboxMain(int argc, char **argv, RTEXITCODE *prcExit) +{ + + /* + * Check if the file named in argv[0] is one of the toolbox programs. + */ + AssertReturn(argc > 0, false); + const char *pszTool = RTPathFilename(argv[0]); + PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool); + if (!pTool) + { + /* + * For debugging and testing purposes we also allow toolbox program access + * when the first VBoxService argument is --use-toolbox. + */ + if (argc < 2 || strcmp(argv[1], "--use-toolbox")) + { + /* We must match vgsvcGstCtrlProcessCreateProcess here and claim + everything starting with "vbox_". */ + if (!RTStrStartsWith(pszTool, "vbox_")) + return false; + RTMsgError("Unknown tool: %s\n", pszTool); + *prcExit = RTEXITCODE_SYNTAX; + return true; + } + + /* No tool specified? Show toolbox help. */ + if (argc < 3) + { + RTMsgError("No tool following --use-toolbox\n"); + *prcExit = RTEXITCODE_SYNTAX; + return true; + } + + argc -= 2; + argv += 2; + pszTool = argv[0]; + pTool = vgsvcToolboxLookUp(pszTool); + if (!pTool) + { + *prcExit = RTEXITCODE_SUCCESS; + if ( !strcmp(pszTool, "-V") + || !strcmp(pszTool, "version")) + vgsvcToolboxShowVersion(); + else if ( !strcmp(pszTool, "help") + || !strcmp(pszTool, "--help") + || !strcmp(pszTool, "-h")) + vgsvcToolboxShowUsage(); + else + { + RTMsgError("Unknown tool: %s\n", pszTool); + *prcExit = RTEXITCODE_SYNTAX; + } + return true; + } + } + + /* + * Invoke the handler. + */ + RTMsgSetProgName("VBoxService/%s", pszTool); + AssertPtr(pTool); + *prcExit = pTool->pfnHandler(argc, argv); + + return true; +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h new file mode 100644 index 00000000..5c744156 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h @@ -0,0 +1,42 @@ +/* $Id: VBoxServiceToolBox.h $ */ +/** @file + * VBoxService - Toolbox header for sharing defines between toolbox binary and VBoxService. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/GuestHost/GuestControl.h> + +RT_C_DECLS_BEGIN +extern bool VGSvcToolboxMain(int argc, char **argv, RTEXITCODE *prcExit); +extern int VGSvcToolboxExitCodeConvertToRc(const char *pszTool, RTEXITCODE rcExit); +RT_C_DECLS_END + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp new file mode 100644 index 00000000..83325543 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp @@ -0,0 +1,324 @@ +/* $Id: VBoxServiceUtils.cpp $ */ +/** @file + * VBoxServiceUtils - Some utility functions. + */ + +/* + * Copyright (C) 2009-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +# include <iprt/param.h> +# include <iprt/path.h> +#endif +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" + + +#ifdef VBOX_WITH_GUEST_PROPS +/** + * Reads a guest property as a 32-bit value. + * + * @returns VBox status code, fully bitched. + * + * @param u32ClientId The HGCM client ID for the guest property session. + * @param pszPropName The property name. + * @param pu32 Where to store the 32-bit value. + * + */ +int VGSvcReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max) +{ + char *pszValue; + int rc = VbglR3GuestPropReadEx(u32ClientId, pszPropName, &pszValue, NULL /* ppszFlags */, NULL /* puTimestamp */); + if (RT_SUCCESS(rc)) + { + char *pszNext; + rc = RTStrToUInt32Ex(pszValue, &pszNext, 0, pu32); + if ( RT_SUCCESS(rc) + && (*pu32 < u32Min || *pu32 > u32Max)) + rc = VGSvcError("The guest property value %s = %RU32 is out of range [%RU32..%RU32].\n", + pszPropName, *pu32, u32Min, u32Max); + RTStrFree(pszValue); + } + return rc; +} + +/** + * Reads a guest property from the host side. + * + * @returns IPRT status code, fully bitched. + * @param u32ClientId The HGCM client ID for the guest property session. + * @param pszPropName The property name. + * @param fReadOnly Whether or not this property needs to be read only + * by the guest side. Otherwise VERR_ACCESS_DENIED will + * be returned. + * @param ppszValue Where to return the value. This is always set + * to NULL. Free it using RTStrFree(). + * @param ppszFlags Where to return the value flags. Free it + * using RTStrFree(). Optional. + * @param puTimestamp Where to return the timestamp. This is only set + * on success. Optional. + */ +int VGSvcReadHostProp(uint32_t u32ClientId, const char *pszPropName, bool fReadOnly, + char **ppszValue, char **ppszFlags, uint64_t *puTimestamp) +{ + AssertPtrReturn(ppszValue, VERR_INVALID_PARAMETER); + + char *pszValue = NULL; + char *pszFlags = NULL; + int rc = VbglR3GuestPropReadEx(u32ClientId, pszPropName, &pszValue, &pszFlags, puTimestamp); + if (RT_SUCCESS(rc)) + { + /* Check security bits. */ + if ( fReadOnly /* Do we except a guest read-only property */ + && !RTStrStr(pszFlags, "RDONLYGUEST")) + { + /* If we want a property which is read-only on the guest + * and it is *not* marked as such, deny access! */ + rc = VERR_ACCESS_DENIED; + } + + if (RT_SUCCESS(rc)) + { + *ppszValue = pszValue; + + if (ppszFlags) + *ppszFlags = pszFlags; + else if (pszFlags) + RTStrFree(pszFlags); + } + else + { + if (pszValue) + RTStrFree(pszValue); + if (pszFlags) + RTStrFree(pszFlags); + } + } + + return rc; +} + + +/** + * Wrapper around VbglR3GuestPropWriteValue that does value formatting and + * logging. + * + * @returns VBox status code. Errors will be logged. + * + * @param u32ClientId The HGCM client ID for the guest property session. + * @param pszName The property name. + * @param pszValueFormat The property format string. If this is NULL then + * the property will be deleted (if possible). + * @param ... Format arguments. + */ +int VGSvcWritePropF(uint32_t u32ClientId, const char *pszName, const char *pszValueFormat, ...) +{ + AssertPtr(pszName); + int rc; + if (pszValueFormat != NULL) + { + va_list va; + va_start(va, pszValueFormat); + VGSvcVerbose(3, "Writing guest property '%s' = '%N'\n", pszName, pszValueFormat, &va); + va_end(va); + + va_start(va, pszValueFormat); + rc = VbglR3GuestPropWriteValueV(u32ClientId, pszName, pszValueFormat, va); + va_end(va); + + if (RT_FAILURE(rc)) + VGSvcError("Error writing guest property '%s' (rc=%Rrc)\n", pszName, rc); + } + else + { + VGSvcVerbose(3, "Deleting guest property '%s'\n", pszName); + rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, NULL); + if (RT_FAILURE(rc)) + VGSvcError("Error deleting guest property '%s' (rc=%Rrc)\n", pszName, rc); + } + return rc; +} + +#endif /* VBOX_WITH_GUEST_PROPS */ +#ifdef RT_OS_WINDOWS + +/** + * Helper for vgsvcUtilGetFileVersion and attempts to read and parse + * FileVersion. + * + * @returns Success indicator. + */ +static bool vgsvcUtilGetFileVersionOwn(LPSTR pVerData, uint32_t *puMajor, uint32_t *puMinor, + uint32_t *puBuildNumber, uint32_t *puRevisionNumber) +{ + UINT cchStrValue = 0; + LPTSTR pStrValue = NULL; + if (!VerQueryValueA(pVerData, "\\StringFileInfo\\040904b0\\FileVersion", (LPVOID *)&pStrValue, &cchStrValue)) + return false; + + char *pszNext = pStrValue; + int rc = RTStrToUInt32Ex(pszNext, &pszNext, 0, puMajor); + AssertReturn(rc == VWRN_TRAILING_CHARS, false); + AssertReturn(*pszNext == '.', false); + + rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puMinor); + AssertReturn(rc == VWRN_TRAILING_CHARS, false); + AssertReturn(*pszNext == '.', false); + + rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puBuildNumber); + AssertReturn(rc == VWRN_TRAILING_CHARS, false); + AssertReturn(*pszNext == '.', false); + + rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puRevisionNumber); + AssertReturn(rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS /*??*/, false); + + return true; +} + + +/** + * Worker for VGSvcUtilWinGetFileVersionString. + * + * @returns VBox status code. + * @param pszFilename ASCII & ANSI & UTF-8 compliant name. + * @param puMajor Where to return the major version number. + * @param puMinor Where to return the minor version number. + * @param puBuildNumber Where to return the build number. + * @param puRevisionNumber Where to return the revision number. + */ +static int vgsvcUtilGetFileVersion(const char *pszFilename, uint32_t *puMajor, uint32_t *puMinor, uint32_t *puBuildNumber, + uint32_t *puRevisionNumber) +{ + int rc; + + *puMajor = *puMinor = *puBuildNumber = *puRevisionNumber = 0; + + /* + * Get the file version info. + */ + DWORD dwHandleIgnored; + DWORD cbVerData = GetFileVersionInfoSizeA(pszFilename, &dwHandleIgnored); + if (cbVerData) + { + LPTSTR pVerData = (LPTSTR)RTMemTmpAllocZ(cbVerData); + if (pVerData) + { + if (GetFileVersionInfoA(pszFilename, dwHandleIgnored, cbVerData, pVerData)) + { + /* + * Try query and parse the FileVersion string our selves first + * since this will give us the correct revision number when + * it goes beyond the range of an uint16_t / WORD. + */ + if (vgsvcUtilGetFileVersionOwn(pVerData, puMajor, puMinor, puBuildNumber, puRevisionNumber)) + rc = VINF_SUCCESS; + else + { + /* Fall back on VS_FIXEDFILEINFO */ + UINT cbFileInfoIgnored = 0; + VS_FIXEDFILEINFO *pFileInfo = NULL; + if (VerQueryValue(pVerData, "\\", (LPVOID *)&pFileInfo, &cbFileInfoIgnored)) + { + *puMajor = HIWORD(pFileInfo->dwFileVersionMS); + *puMinor = LOWORD(pFileInfo->dwFileVersionMS); + *puBuildNumber = HIWORD(pFileInfo->dwFileVersionLS); + *puRevisionNumber = LOWORD(pFileInfo->dwFileVersionLS); + rc = VINF_SUCCESS; + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + VGSvcVerbose(3, "No file version value for file '%s' available! (%d / rc=%Rrc)\n", + pszFilename, GetLastError(), rc); + } + } + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + VGSvcVerbose(0, "GetFileVersionInfo(%s) -> %u / %Rrc\n", pszFilename, GetLastError(), rc); + } + + RTMemTmpFree(pVerData); + } + else + { + VGSvcVerbose(0, "Failed to allocate %u byte for file version info for '%s'\n", cbVerData, pszFilename); + rc = VERR_NO_TMP_MEMORY; + } + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + VGSvcVerbose(3, "GetFileVersionInfoSize(%s) -> %u / %Rrc\n", pszFilename, GetLastError(), rc); + } + return rc; +} + + +/** + * Gets a re-formatted version string from the VS_FIXEDFILEINFO table. + * + * @returns VBox status code. The output buffer is always valid and the status + * code can safely be ignored. + * + * @param pszPath The base path. + * @param pszFilename The filename. + * @param pszVersion Where to return the version string. + * @param cbVersion The size of the version string buffer. This MUST be + * at least 2 bytes! + */ +int VGSvcUtilWinGetFileVersionString(const char *pszPath, const char *pszFilename, char *pszVersion, size_t cbVersion) +{ + /* + * We will ALWAYS return with a valid output buffer. + */ + AssertReturn(cbVersion >= 2, VERR_BUFFER_OVERFLOW); + pszVersion[0] = '-'; + pszVersion[1] = '\0'; + + /* + * Create the path and query the bits. + */ + char szFullPath[RTPATH_MAX]; + int rc = RTPathJoin(szFullPath, sizeof(szFullPath), pszPath, pszFilename); + if (RT_SUCCESS(rc)) + { + uint32_t uMajor, uMinor, uBuild, uRev; + rc = vgsvcUtilGetFileVersion(szFullPath, &uMajor, &uMinor, &uBuild, &uRev); + if (RT_SUCCESS(rc)) + RTStrPrintf(pszVersion, cbVersion, "%u.%u.%ur%u", uMajor, uMinor, uBuild, uRev); + } + return rc; +} + +#endif /* RT_OS_WINDOWS */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h new file mode 100644 index 00000000..970d83ae --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h @@ -0,0 +1,49 @@ +/* $Id: VBoxServiceUtils.h $ */ +/** @file + * VBoxServiceUtils - Guest Additions Services (Utilities). + */ + +/* + * Copyright (C) 2009-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxServiceInternal.h" + +#ifdef VBOX_WITH_GUEST_PROPS +int VGSvcReadProp(uint32_t u32ClientId, const char *pszPropName, char **ppszValue, char **ppszFlags, uint64_t *puTimestamp); +int VGSvcReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max); +int VGSvcReadHostProp(uint32_t u32ClientId, const char *pszPropName, bool fReadOnly, char **ppszValue, char **ppszFlags, + uint64_t *puTimestamp); +int VGSvcWritePropF(uint32_t u32ClientId, const char *pszName, const char *pszValueFormat, ...); +#endif + +#ifdef RT_OS_WINDOWS +int VGSvcUtilWinGetFileVersionString(const char *pszPath, const char *pszFileName, char *pszVersion, size_t cbVersion); +#endif + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp new file mode 100644 index 00000000..430b7d90 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp @@ -0,0 +1,1363 @@ +/* $Id: VBoxServiceVMInfo-win.cpp $ */ +/** @file + * VBoxService - Virtual Machine Information for the Host, Windows specifics. + */ + +/* + * Copyright (C) 2009-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0600 +# undef _WIN32_WINNT +# define _WIN32_WINNT 0x0600 /* QueryFullProcessImageNameW in recent SDKs. */ +#endif +#include <iprt/win/windows.h> +#include <wtsapi32.h> /* For WTS* calls. */ +#include <psapi.h> /* EnumProcesses. */ +#include <Ntsecapi.h> /* Needed for process security information. */ + +#include <iprt/assert.h> +#include <iprt/ldr.h> +#include <iprt/localipc.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/system.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/utf16.h> + +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#include "VBoxServiceVMInfo.h" +#include "../../WINNT/VBoxTray/VBoxTrayMsg.h" /* For IPC. */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Structure for storing the looked up user information. */ +typedef struct VBOXSERVICEVMINFOUSER +{ + WCHAR wszUser[MAX_PATH]; + WCHAR wszAuthenticationPackage[MAX_PATH]; + WCHAR wszLogonDomain[MAX_PATH]; + /** Number of assigned user processes. */ + ULONG ulNumProcs; + /** Last (highest) session ID. This + * is needed for distinguishing old session + * process counts from new (current) session + * ones. */ + ULONG ulLastSession; +} VBOXSERVICEVMINFOUSER, *PVBOXSERVICEVMINFOUSER; + +/** Structure for the file information lookup. */ +typedef struct VBOXSERVICEVMINFOFILE +{ + char *pszFilePath; + char *pszFileName; +} VBOXSERVICEVMINFOFILE, *PVBOXSERVICEVMINFOFILE; + +/** Structure for process information lookup. */ +typedef struct VBOXSERVICEVMINFOPROC +{ + /** The PID. */ + DWORD id; + /** The SID. */ + PSID pSid; + /** The LUID. */ + LUID luid; + /** Interactive process. */ + bool fInteractive; +} VBOXSERVICEVMINFOPROC, *PVBOXSERVICEVMINFOPROC; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static uint32_t vgsvcVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs); +static bool vgsvcVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER a_pUserInfo, PLUID a_pSession); +static int vgsvcVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppProc, DWORD *pdwCount); +static void vgsvcVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs); +static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static uint32_t s_uDebugGuestPropClientID = 0; +static uint32_t s_uDebugIter = 0; +/** Whether to skip the logged-in user detection over RDP or not. + * See notes in this section why we might want to skip this. */ +static bool s_fSkipRDPDetection = false; + +static RTONCE g_vgsvcWinVmInitOnce = RTONCE_INITIALIZER; + +/** @name Secur32.dll imports are dynamically resolved because of NT4. + * @{ */ +static decltype(LsaGetLogonSessionData) *g_pfnLsaGetLogonSessionData = NULL; +static decltype(LsaEnumerateLogonSessions) *g_pfnLsaEnumerateLogonSessions = NULL; +static decltype(LsaFreeReturnBuffer) *g_pfnLsaFreeReturnBuffer = NULL; +/** @} */ + +/** @name WtsApi32.dll imports are dynamically resolved because of NT4. + * @{ */ +static decltype(WTSFreeMemory) *g_pfnWTSFreeMemory = NULL; +static decltype(WTSQuerySessionInformationA) *g_pfnWTSQuerySessionInformationA = NULL; +/** @} */ + +/** @name PsApi.dll imports are dynamically resolved because of NT4. + * @{ */ +static decltype(EnumProcesses) *g_pfnEnumProcesses = NULL; +static decltype(GetModuleFileNameExW) *g_pfnGetModuleFileNameExW = NULL; +/** @} */ + +/** @name New Kernel32.dll APIs we may use when present. + * @{ */ +static decltype(QueryFullProcessImageNameW) *g_pfnQueryFullProcessImageNameW = NULL; + +/** @} */ + + +/** + * An RTOnce callback function. + */ +static DECLCALLBACK(int) vgsvcWinVmInfoInitOnce(void *pvIgnored) +{ + RT_NOREF1(pvIgnored); + + /* SECUR32 */ + RTLDRMOD hLdrMod; + int rc = RTLdrLoadSystem("secur32.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hLdrMod, "LsaGetLogonSessionData", (void **)&g_pfnLsaGetLogonSessionData); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hLdrMod, "LsaEnumerateLogonSessions", (void **)&g_pfnLsaEnumerateLogonSessions); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hLdrMod, "LsaFreeReturnBuffer", (void **)&g_pfnLsaFreeReturnBuffer); + AssertRC(rc); + RTLdrClose(hLdrMod); + } + if (RT_FAILURE(rc)) + { + VGSvcVerbose(1, "Secur32.dll APIs are not available (%Rrc)\n", rc); + g_pfnLsaGetLogonSessionData = NULL; + g_pfnLsaEnumerateLogonSessions = NULL; + g_pfnLsaFreeReturnBuffer = NULL; + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0)); + } + + /* WTSAPI32 */ + rc = RTLdrLoadSystem("wtsapi32.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hLdrMod, "WTSFreeMemory", (void **)&g_pfnWTSFreeMemory); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hLdrMod, "WTSQuerySessionInformationA", (void **)&g_pfnWTSQuerySessionInformationA); + AssertRC(rc); + RTLdrClose(hLdrMod); + } + if (RT_FAILURE(rc)) + { + VGSvcVerbose(1, "WtsApi32.dll APIs are not available (%Rrc)\n", rc); + g_pfnWTSFreeMemory = NULL; + g_pfnWTSQuerySessionInformationA = NULL; + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0)); + } + + /* PSAPI */ + rc = RTLdrLoadSystem("psapi.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hLdrMod, "EnumProcesses", (void **)&g_pfnEnumProcesses); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hLdrMod, "GetModuleFileNameExW", (void **)&g_pfnGetModuleFileNameExW); + AssertRC(rc); + RTLdrClose(hLdrMod); + } + if (RT_FAILURE(rc)) + { + VGSvcVerbose(1, "psapi.dll APIs are not available (%Rrc)\n", rc); + g_pfnEnumProcesses = NULL; + g_pfnGetModuleFileNameExW = NULL; + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0)); + } + + /* Kernel32: */ + rc = RTLdrLoadSystem("kernel32.dll", true /*fNoUnload*/, &hLdrMod); + AssertRCReturn(rc, rc); + rc = RTLdrGetSymbol(hLdrMod, "QueryFullProcessImageNameW", (void **)&g_pfnQueryFullProcessImageNameW); + if (RT_FAILURE(rc)) + { + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(6, 0, 0)); + g_pfnQueryFullProcessImageNameW = NULL; + } + RTLdrClose(hLdrMod); + + return VINF_SUCCESS; +} + + +static bool vgsvcVMInfoSession0Separation(void) +{ + return RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0); /* Vista */ +} + + +/** + * Retrieves the module name of a given process. + * + * @return IPRT status code. + */ +static int vgsvcVMInfoWinProcessesGetModuleNameW(PVBOXSERVICEVMINFOPROC const pProc, PRTUTF16 *ppszName) +{ + *ppszName = NULL; + AssertPtrReturn(ppszName, VERR_INVALID_POINTER); + AssertPtrReturn(pProc, VERR_INVALID_POINTER); + AssertReturn(g_pfnGetModuleFileNameExW || g_pfnQueryFullProcessImageNameW, VERR_NOT_SUPPORTED); + + /* + * Open the process. + */ + DWORD dwFlags = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0)) /* Vista and later */ + dwFlags = PROCESS_QUERY_LIMITED_INFORMATION; /* possible to do on more processes */ + + HANDLE hProcess = OpenProcess(dwFlags, FALSE, pProc->id); + if (hProcess == NULL) + { + DWORD dwErr = GetLastError(); + if (g_cVerbosity) + VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr); + return RTErrConvertFromWin32(dwErr); + } + + /* + * Since GetModuleFileNameEx has trouble with cross-bitness stuff (32-bit apps + * cannot query 64-bit apps and vice verse) we have to use a different code + * path for Vista and up. + * + * So use QueryFullProcessImageNameW when available (Vista+), fall back on + * GetModuleFileNameExW on older windows version ( + */ + WCHAR wszName[_1K]; + DWORD dwLen = _1K; + BOOL fRc; + if (g_pfnQueryFullProcessImageNameW) + fRc = g_pfnQueryFullProcessImageNameW(hProcess, 0 /*PROCESS_NAME_NATIVE*/, wszName, &dwLen); + else + fRc = g_pfnGetModuleFileNameExW(hProcess, NULL /* Get main executable */, wszName, dwLen); + + int rc; + if (fRc) + rc = RTUtf16DupEx(ppszName, wszName, 0); + else + { + DWORD dwErr = GetLastError(); + if (g_cVerbosity > 3) + VGSvcError("Unable to retrieve process name for PID=%u, LastError=%Rwc\n", pProc->id, dwErr); + rc = RTErrConvertFromWin32(dwErr); + } + + CloseHandle(hProcess); + return rc; +} + + +/** + * Fills in more data for a process. + * + * @returns VBox status code. + * @param pProc The process structure to fill data into. + * @param tkClass The kind of token information to get. + */ +static int vgsvcVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pProc, TOKEN_INFORMATION_CLASS tkClass) +{ + AssertPtrReturn(pProc, VERR_INVALID_POINTER); + + DWORD dwErr = ERROR_SUCCESS; + HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pProc->id); + if (h == NULL) + { + dwErr = GetLastError(); + if (g_cVerbosity > 4) + VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr); + return RTErrConvertFromWin32(dwErr); + } + + int rc = VINF_SUCCESS; + HANDLE hToken; + if (OpenProcessToken(h, TOKEN_QUERY, &hToken)) + { + void *pvTokenInfo = NULL; + DWORD dwTokenInfoSize; + switch (tkClass) + { + case TokenStatistics: + /** @todo r=bird: Someone has been reading too many MSDN examples. You shall + * use RTMemAlloc here! There is absolutely not reason for + * complicating things uncessarily by using HeapAlloc! */ + dwTokenInfoSize = sizeof(TOKEN_STATISTICS); + pvTokenInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwTokenInfoSize); + AssertPtr(pvTokenInfo); + break; + + case TokenGroups: + dwTokenInfoSize = 0; + /* Allocation will follow in a second step. */ + break; + + case TokenUser: + dwTokenInfoSize = 0; + /* Allocation will follow in a second step. */ + break; + + default: + VGSvcError("Token class not implemented: %d\n", tkClass); + rc = VERR_NOT_IMPLEMENTED; + dwTokenInfoSize = 0; /* Shut up MSC. */ + break; + } + + if (RT_SUCCESS(rc)) + { + DWORD dwRetLength; + if (!GetTokenInformation(hToken, tkClass, pvTokenInfo, dwTokenInfoSize, &dwRetLength)) + { + dwErr = GetLastError(); + if (dwErr == ERROR_INSUFFICIENT_BUFFER) + { + dwErr = ERROR_SUCCESS; + + switch (tkClass) + { + case TokenGroups: + pvTokenInfo = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwRetLength); + if (!pvTokenInfo) + dwErr = GetLastError(); + dwTokenInfoSize = dwRetLength; + break; + + case TokenUser: + pvTokenInfo = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwRetLength); + if (!pvTokenInfo) + dwErr = GetLastError(); + dwTokenInfoSize = dwRetLength; + break; + + default: + AssertMsgFailed(("Re-allocating of token information for token class not implemented\n")); + break; + } + + if (dwErr == ERROR_SUCCESS) + { + if (!GetTokenInformation(hToken, tkClass, pvTokenInfo, dwTokenInfoSize, &dwRetLength)) + dwErr = GetLastError(); + } + } + } + + if (dwErr == ERROR_SUCCESS) + { + rc = VINF_SUCCESS; + + switch (tkClass) + { + case TokenStatistics: + { + PTOKEN_STATISTICS pStats = (PTOKEN_STATISTICS)pvTokenInfo; + AssertPtr(pStats); + memcpy(&pProc->luid, &pStats->AuthenticationId, sizeof(LUID)); + /** @todo Add more information of TOKEN_STATISTICS as needed. */ + break; + } + + case TokenGroups: + { + pProc->fInteractive = false; + + SID_IDENTIFIER_AUTHORITY sidAuthNT = SECURITY_NT_AUTHORITY; + PSID pSidInteractive = NULL; /* S-1-5-4 */ + if (!AllocateAndInitializeSid(&sidAuthNT, 1, 4, 0, 0, 0, 0, 0, 0, 0, &pSidInteractive)) + dwErr = GetLastError(); + + PSID pSidLocal = NULL; /* S-1-2-0 */ + if (dwErr == ERROR_SUCCESS) + { + SID_IDENTIFIER_AUTHORITY sidAuthLocal = SECURITY_LOCAL_SID_AUTHORITY; + if (!AllocateAndInitializeSid(&sidAuthLocal, 1, 0, 0, 0, 0, 0, 0, 0, 0, &pSidLocal)) + dwErr = GetLastError(); + } + + if (dwErr == ERROR_SUCCESS) + { + PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)pvTokenInfo; + AssertPtr(pGroups); + for (DWORD i = 0; i < pGroups->GroupCount; i++) + { + if ( EqualSid(pGroups->Groups[i].Sid, pSidInteractive) + || EqualSid(pGroups->Groups[i].Sid, pSidLocal) + || pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) + { + pProc->fInteractive = true; + break; + } + } + } + + if (pSidInteractive) + FreeSid(pSidInteractive); + if (pSidLocal) + FreeSid(pSidLocal); + break; + } + + case TokenUser: + { + PTOKEN_USER pUser = (PTOKEN_USER)pvTokenInfo; + AssertPtr(pUser); + + DWORD dwLength = GetLengthSid(pUser->User.Sid); + Assert(dwLength); + if (dwLength) + { + pProc->pSid = (PSID)HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, dwLength); + AssertPtr(pProc->pSid); + if (CopySid(dwLength, pProc->pSid, pUser->User.Sid)) + { + if (!IsValidSid(pProc->pSid)) + dwErr = ERROR_INVALID_NAME; + } + else + dwErr = GetLastError(); + } + else + dwErr = ERROR_NO_DATA; + + if (dwErr != ERROR_SUCCESS) + { + VGSvcError("Error retrieving SID of process PID=%u: %u\n", pProc->id, dwErr); + if (pProc->pSid) + { + HeapFree(GetProcessHeap(), 0 /* Flags */, pProc->pSid); + pProc->pSid = NULL; + } + } + break; + } + + default: + AssertMsgFailed(("Unhandled token information class\n")); + break; + } + } + + if (pvTokenInfo) + HeapFree(GetProcessHeap(), 0 /* Flags */, pvTokenInfo); + } + CloseHandle(hToken); + } + else + dwErr = GetLastError(); + + if (dwErr != ERROR_SUCCESS) + { + if (g_cVerbosity) + VGSvcError("Unable to query token information for PID=%u, error=%u\n", pProc->id, dwErr); + rc = RTErrConvertFromWin32(dwErr); + } + + CloseHandle(h); + return rc; +} + + +/** + * Enumerate all the processes in the system and get the logon user IDs for + * them. + * + * @returns VBox status code. + * @param ppaProcs Where to return the process snapshot. This must be + * freed by calling vgsvcVMInfoWinProcessesFree. + * + * @param pcProcs Where to store the returned process count. + */ +static int vgsvcVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDWORD pcProcs) +{ + AssertPtr(ppaProcs); + AssertPtr(pcProcs); + + if (!g_pfnEnumProcesses) + return VERR_NOT_SUPPORTED; + + /* + * Call EnumProcesses with an increasingly larger buffer until it all fits + * or we think something is screwed up. + */ + DWORD cProcesses = 64; + PDWORD paPID = NULL; + int rc = VINF_SUCCESS; + do + { + /* Allocate / grow the buffer first. */ + cProcesses *= 2; + void *pvNew = RTMemRealloc(paPID, cProcesses * sizeof(DWORD)); + if (!pvNew) + { + rc = VERR_NO_MEMORY; + break; + } + paPID = (PDWORD)pvNew; + + /* Query the processes. Not the cbRet == buffer size means there could be more work to be done. */ + DWORD cbRet; + if (!g_pfnEnumProcesses(paPID, cProcesses * sizeof(DWORD), &cbRet)) + { + rc = RTErrConvertFromWin32(GetLastError()); + break; + } + if (cbRet < cProcesses * sizeof(DWORD)) + { + cProcesses = cbRet / sizeof(DWORD); + break; + } + } while (cProcesses <= _32K); /* Should be enough; see: http://blogs.technet.com/markrussinovich/archive/2009/07/08/3261309.aspx */ + + if (RT_SUCCESS(rc)) + { + /* + * Allocate out process structures and fill data into them. + * We currently only try lookup their LUID's. + */ + PVBOXSERVICEVMINFOPROC paProcs; + paProcs = (PVBOXSERVICEVMINFOPROC)RTMemAllocZ(cProcesses * sizeof(VBOXSERVICEVMINFOPROC)); + if (paProcs) + { + for (DWORD i = 0; i < cProcesses; i++) + { + paProcs[i].id = paPID[i]; + paProcs[i].pSid = NULL; + + int rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenUser); + if (RT_FAILURE(rc2) && g_cVerbosity) + VGSvcError("Get token class 'user' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2); + + rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenGroups); + if (RT_FAILURE(rc2) && g_cVerbosity) + VGSvcError("Get token class 'groups' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2); + + rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenStatistics); + if (RT_FAILURE(rc2) && g_cVerbosity) + VGSvcError("Get token class 'statistics' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2); + } + + /* Save number of processes */ + if (RT_SUCCESS(rc)) + { + *pcProcs = cProcesses; + *ppaProcs = paProcs; + } + else + vgsvcVMInfoWinProcessesFree(cProcesses, paProcs); + } + else + rc = VERR_NO_MEMORY; + } + + RTMemFree(paPID); + return rc; +} + +/** + * Frees the process structures returned by + * vgsvcVMInfoWinProcessesEnumerate() before. + * + * @param cProcs Number of processes in paProcs. + * @param paProcs The process array. + */ +static void vgsvcVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs) +{ + for (DWORD i = 0; i < cProcs; i++) + if (paProcs[i].pSid) + { + HeapFree(GetProcessHeap(), 0 /* Flags */, paProcs[i].pSid); + paProcs[i].pSid = NULL; + } + RTMemFree(paProcs); +} + +/** + * Determines whether the specified session has processes on the system. + * + * @returns Number of processes found for a specified session. + * @param pSession The current user's SID. + * @param paProcs The process snapshot. + * @param cProcs The number of processes in the snaphot. + * @param puTerminalSession Where to return terminal session number. + * Optional. + */ +/** @todo r=bird: The 'Has' indicates a predicate function, which this is + * not. Predicate functions always returns bool. */ +static uint32_t vgsvcVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs, + PULONG puTerminalSession) +{ + if (!pSession) + { + VGSvcVerbose(1, "Session became invalid while enumerating!\n"); + return 0; + } + if (!g_pfnLsaGetLogonSessionData) + return 0; + + PSECURITY_LOGON_SESSION_DATA pSessionData = NULL; + NTSTATUS rcNt = g_pfnLsaGetLogonSessionData(pSession, &pSessionData); + if (rcNt != STATUS_SUCCESS) + { + VGSvcError("Could not get logon session data! rcNt=%#x\n", rcNt); + return 0; + } + + if (!IsValidSid(pSessionData->Sid)) + { + VGSvcError("User SID=%p is not valid\n", pSessionData->Sid); + if (pSessionData) + g_pfnLsaFreeReturnBuffer(pSessionData); + return 0; + } + + + /* + * Even if a user seems to be logged in, it could be a stale/orphaned logon + * session. So check if we have some processes bound to it by comparing the + * session <-> process LUIDs. + */ + int rc = VINF_SUCCESS; + uint32_t cProcessesFound = 0; + for (DWORD i = 0; i < cProcs; i++) + { + PSID pProcSID = paProcs[i].pSid; + if ( RT_SUCCESS(rc) + && pProcSID + && IsValidSid(pProcSID)) + { + if (EqualSid(pSessionData->Sid, paProcs[i].pSid)) + { + if (g_cVerbosity) + { + PRTUTF16 pszName; + int rc2 = vgsvcVMInfoWinProcessesGetModuleNameW(&paProcs[i], &pszName); + VGSvcVerbose(4, "Session %RU32: PID=%u (fInt=%RTbool): %ls\n", + pSessionData->Session, paProcs[i].id, paProcs[i].fInteractive, + RT_SUCCESS(rc2) ? pszName : L"<Unknown>"); + if (RT_SUCCESS(rc2)) + RTUtf16Free(pszName); + } + + if (paProcs[i].fInteractive) + { + cProcessesFound++; + if (!g_cVerbosity) /* We want a bit more info on higher verbosity. */ + break; + } + } + } + } + + if (puTerminalSession) + *puTerminalSession = pSessionData->Session; + + g_pfnLsaFreeReturnBuffer(pSessionData); + + return cProcessesFound; +} + + +/** + * Save and noisy string copy. + * + * @param pwszDst Destination buffer. + * @param cbDst Size in bytes - not WCHAR count! + * @param pSrc Source string. + * @param pszWhat What this is. For the log. + */ +static void vgsvcVMInfoWinSafeCopy(PWCHAR pwszDst, size_t cbDst, LSA_UNICODE_STRING const *pSrc, const char *pszWhat) +{ + Assert(RT_ALIGN(cbDst, sizeof(WCHAR)) == cbDst); + + size_t cbCopy = pSrc->Length; + if (cbCopy + sizeof(WCHAR) > cbDst) + { + VGSvcVerbose(0, "%s is too long - %u bytes, buffer %u bytes! It will be truncated.\n", pszWhat, cbCopy, cbDst); + cbCopy = cbDst - sizeof(WCHAR); + } + if (cbCopy) + memcpy(pwszDst, pSrc->Buffer, cbCopy); + pwszDst[cbCopy / sizeof(WCHAR)] = '\0'; +} + + +/** + * Detects whether a user is logged on. + * + * @returns true if logged in, false if not (or error). + * @param pUserInfo Where to return the user information. + * @param pSession The session to check. + */ +static bool vgsvcVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER pUserInfo, PLUID pSession) +{ + AssertPtrReturn(pUserInfo, false); + if (!pSession) + return false; + if ( !g_pfnLsaGetLogonSessionData + || !g_pfnLsaNtStatusToWinError) + return false; + + PSECURITY_LOGON_SESSION_DATA pSessionData = NULL; + NTSTATUS rcNt = g_pfnLsaGetLogonSessionData(pSession, &pSessionData); + if (rcNt != STATUS_SUCCESS) + { + ULONG ulError = g_pfnLsaNtStatusToWinError(rcNt); + switch (ulError) + { + case ERROR_NOT_ENOUGH_MEMORY: + /* If we don't have enough memory it's hard to judge whether the specified user + * is logged in or not, so just assume he/she's not. */ + VGSvcVerbose(3, "Not enough memory to retrieve logon session data!\n"); + break; + + case ERROR_NO_SUCH_LOGON_SESSION: + /* Skip session data which is not valid anymore because it may have been + * already terminated. */ + break; + + default: + VGSvcError("LsaGetLogonSessionData failed with error %u\n", ulError); + break; + } + if (pSessionData) + g_pfnLsaFreeReturnBuffer(pSessionData); + return false; + } + if (!pSessionData) + { + VGSvcError("Invalid logon session data!\n"); + return false; + } + + VGSvcVerbose(3, "Session data: Name=%ls, SessionID=%RU32, LogonID=%d,%u, LogonType=%u\n", + pSessionData->UserName.Buffer, pSessionData->Session, + pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart, pSessionData->LogonType); + + if (vgsvcVMInfoSession0Separation()) + { + /* Starting at Windows Vista user sessions begin with session 1, so + * ignore (stale) session 0 users. */ + if ( pSessionData->Session == 0 + /* Also check the logon time. */ + || pSessionData->LogonTime.QuadPart == 0) + { + g_pfnLsaFreeReturnBuffer(pSessionData); + return false; + } + } + + /* + * Only handle users which can login interactively or logged in + * remotely over native RDP. + */ + bool fFoundUser = false; + if ( IsValidSid(pSessionData->Sid) + && ( (SECURITY_LOGON_TYPE)pSessionData->LogonType == Interactive + || (SECURITY_LOGON_TYPE)pSessionData->LogonType == RemoteInteractive + /* Note: We also need CachedInteractive in case Windows cached the credentials + * or just wants to reuse them! */ + || (SECURITY_LOGON_TYPE)pSessionData->LogonType == CachedInteractive)) + { + VGSvcVerbose(3, "Session LogonType=%u is supported -- looking up SID + type ...\n", pSessionData->LogonType); + + /* + * Copy out relevant data. + */ + vgsvcVMInfoWinSafeCopy(pUserInfo->wszUser, sizeof(pUserInfo->wszUser), &pSessionData->UserName, "User name"); + vgsvcVMInfoWinSafeCopy(pUserInfo->wszAuthenticationPackage, sizeof(pUserInfo->wszAuthenticationPackage), + &pSessionData->AuthenticationPackage, "Authentication pkg name"); + vgsvcVMInfoWinSafeCopy(pUserInfo->wszLogonDomain, sizeof(pUserInfo->wszLogonDomain), + &pSessionData->LogonDomain, "Logon domain name"); + + TCHAR szOwnerName[MAX_PATH] = { 0 }; + DWORD dwOwnerNameSize = sizeof(szOwnerName); + TCHAR szDomainName[MAX_PATH] = { 0 }; + DWORD dwDomainNameSize = sizeof(szDomainName); + SID_NAME_USE enmOwnerType = SidTypeInvalid; + if (!LookupAccountSid(NULL, + pSessionData->Sid, + szOwnerName, + &dwOwnerNameSize, + szDomainName, + &dwDomainNameSize, + &enmOwnerType)) + { + DWORD dwErr = GetLastError(); + /* + * If a network time-out prevents the function from finding the name or + * if a SID that does not have a corresponding account name (such as a + * logon SID that identifies a logon session), we get ERROR_NONE_MAPPED + * here that we just skip. + */ + if (dwErr != ERROR_NONE_MAPPED) + VGSvcError("Failed looking up account info for user=%ls, error=$ld!\n", pUserInfo->wszUser, dwErr); + } + else + { + if (enmOwnerType == SidTypeUser) /* Only recognize users; we don't care about the rest! */ + { + VGSvcVerbose(3, "Account User=%ls, Session=%u, LogonID=%d,%u, AuthPkg=%ls, Domain=%ls\n", + pUserInfo->wszUser, pSessionData->Session, pSessionData->LogonId.HighPart, + pSessionData->LogonId.LowPart, pUserInfo->wszAuthenticationPackage, pUserInfo->wszLogonDomain); + + /* KB970910 (check http://support.microsoft.com/kb/970910 on archive.org) + * indicates that WTSQuerySessionInformation may leak memory and return the + * wrong status code for WTSApplicationName and WTSInitialProgram queries. + * + * The system must be low on resources, and presumably some internal operation + * must fail because of this, triggering an error handling path that forgets + * to free memory and set last error. + * + * bird 2022-08-26: However, we do not query either of those info items. We + * query WTSConnectState, which is a rather simple affair. So, I've + * re-enabled the code for all systems that includes the API. + */ + if (!s_fSkipRDPDetection) + { + /* Skip if we don't have the WTS API. */ + if (!g_pfnWTSQuerySessionInformationA) + s_fSkipRDPDetection = true; +#if 0 /* bird: see above */ + /* Skip RDP detection on Windows 2000 and older. + For Windows 2000 however we don't have any hotfixes, so just skip the + RDP detection in any case. */ + else if (RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 1, 0)) /* older than XP */ + s_fSkipRDPDetection = true; +#endif + if (s_fSkipRDPDetection) + VGSvcVerbose(0, "Detection of logged-in users via RDP is disabled\n"); + } + + if (!s_fSkipRDPDetection) + { + Assert(g_pfnWTSQuerySessionInformationA); + Assert(g_pfnWTSFreeMemory); + + /* Detect RDP sessions as well. */ + LPTSTR pBuffer = NULL; + DWORD cbRet = 0; + int iState = -1; + if (g_pfnWTSQuerySessionInformationA(WTS_CURRENT_SERVER_HANDLE, + pSessionData->Session, + WTSConnectState, + &pBuffer, + &cbRet)) + { + if (cbRet) + iState = *pBuffer; + VGSvcVerbose(3, "Account User=%ls, WTSConnectState=%d (%u)\n", pUserInfo->wszUser, iState, cbRet); + if ( iState == WTSActive /* User logged on to WinStation. */ + || iState == WTSShadow /* Shadowing another WinStation. */ + || iState == WTSDisconnected) /* WinStation logged on without client. */ + { + /** @todo On Vista and W2K, always "old" user name are still + * there. Filter out the old one! */ + VGSvcVerbose(3, "Account User=%ls using TCS/RDP, state=%d \n", pUserInfo->wszUser, iState); + fFoundUser = true; + } + if (pBuffer) + g_pfnWTSFreeMemory(pBuffer); + } + else + { + DWORD dwLastErr = GetLastError(); + switch (dwLastErr) + { + /* + * Terminal services don't run (for example in W2K, + * nothing to worry about ...). ... or is on the Vista + * fast user switching page! + */ + case ERROR_CTX_WINSTATION_NOT_FOUND: + VGSvcVerbose(3, "No WinStation found for user=%ls\n", pUserInfo->wszUser); + break; + + default: + VGSvcVerbose(3, "Cannot query WTS connection state for user=%ls, error=%u\n", + pUserInfo->wszUser, dwLastErr); + break; + } + + fFoundUser = true; + } + } + } + else + VGSvcVerbose(3, "SID owner type=%d not handled, skipping\n", enmOwnerType); + } + + VGSvcVerbose(3, "Account User=%ls %s logged in\n", pUserInfo->wszUser, fFoundUser ? "is" : "is not"); + } + + if (fFoundUser) + pUserInfo->ulLastSession = pSessionData->Session; + + g_pfnLsaFreeReturnBuffer(pSessionData); + return fFoundUser; +} + + +static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszUser, VERR_INVALID_POINTER); + /* pszDomain is optional. */ + + char szPipeName[512 + sizeof(VBOXTRAY_IPC_PIPE_PREFIX)]; + memcpy(szPipeName, VBOXTRAY_IPC_PIPE_PREFIX, sizeof(VBOXTRAY_IPC_PIPE_PREFIX)); + int rc = RTStrCat(szPipeName, sizeof(szPipeName), pszUser); + if (RT_SUCCESS(rc)) + { + bool fReportToHost = false; + VBoxGuestUserState userState = VBoxGuestUserState_Unknown; + + RTLOCALIPCSESSION hSession; + rc = RTLocalIpcSessionConnect(&hSession, szPipeName, RTLOCALIPC_FLAGS_NATIVE_NAME); + if (RT_SUCCESS(rc)) + { + VBOXTRAYIPCHEADER ipcHdr = + { + /* .uMagic = */ VBOXTRAY_IPC_HDR_MAGIC, + /* .uVersion = */ VBOXTRAY_IPC_HDR_VERSION, + /* .enmMsgType = */ VBOXTRAYIPCMSGTYPE_USER_LAST_INPUT, + /* .cbPayload = */ 0 /* No payload */ + }; + + rc = RTLocalIpcSessionWrite(hSession, &ipcHdr, sizeof(ipcHdr)); + if (RT_SUCCESS(rc)) + { + VBOXTRAYIPCREPLY_USER_LAST_INPUT_T ipcReply; + rc = RTLocalIpcSessionRead(hSession, &ipcReply, sizeof(ipcReply), NULL /* Exact read */); + if ( RT_SUCCESS(rc) + /* If uLastInput is set to UINT32_MAX VBoxTray was not able to retrieve the + * user's last input time. This might happen when running on Windows NT4 or older. */ + && ipcReply.cSecSinceLastInput != UINT32_MAX) + { + userState = ipcReply.cSecSinceLastInput * 1000 < g_uVMInfoUserIdleThresholdMS + ? VBoxGuestUserState_InUse + : VBoxGuestUserState_Idle; + + rc = VGSvcUserUpdateF(pCache, pszUser, pszDomain, "UsageState", + userState == VBoxGuestUserState_InUse ? "InUse" : "Idle"); + + /* + * Note: vboxServiceUserUpdateF can return VINF_NO_CHANGE in case there wasn't anything + * to update. So only report the user's status to host when we really got something + * new. + */ + fReportToHost = rc == VINF_SUCCESS; + VGSvcVerbose(4, "User '%s' (domain '%s') is idle for %RU32, fReportToHost=%RTbool\n", + pszUser, pszDomain ? pszDomain : "<None>", ipcReply.cSecSinceLastInput, fReportToHost); + +#if 0 /* Do we want to write the idle time as well? */ + /* Also write the user's current idle time, if there is any. */ + if (userState == VBoxGuestUserState_Idle) + rc = vgsvcUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", "%RU32", ipcReply.cSecSinceLastInput); + else + rc = vgsvcUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", NULL /* Delete property */); + + if (RT_SUCCESS(rc)) +#endif + } +#ifdef DEBUG + else if (RT_SUCCESS(rc) && ipcReply.cSecSinceLastInput == UINT32_MAX) + VGSvcVerbose(4, "Last input for user '%s' is not supported, skipping\n", pszUser, rc); +#endif + } +#ifdef DEBUG + VGSvcVerbose(4, "Getting last input for user '%s' ended with rc=%Rrc\n", pszUser, rc); +#endif + int rc2 = RTLocalIpcSessionClose(hSession); + if (RT_SUCCESS(rc) && RT_FAILURE(rc2)) + rc = rc2; + } + else + { + switch (rc) + { + case VERR_FILE_NOT_FOUND: + { + /* No VBoxTray (or too old version which does not support IPC) running + for the given user. Not much we can do then. */ + VGSvcVerbose(4, "VBoxTray for user '%s' not running (anymore), no last input available\n", pszUser); + + /* Overwrite rc from above. */ + rc = VGSvcUserUpdateF(pCache, pszUser, pszDomain, "UsageState", "Idle"); + + fReportToHost = rc == VINF_SUCCESS; + if (fReportToHost) + userState = VBoxGuestUserState_Idle; + break; + } + + default: + VGSvcError("Error querying last input for user '%s', rc=%Rrc\n", pszUser, rc); + break; + } + } + + if (fReportToHost) + { + Assert(userState != VBoxGuestUserState_Unknown); + int rc2 = VbglR3GuestUserReportState(pszUser, pszDomain, userState, NULL /* No details */, 0); + if (RT_FAILURE(rc2)) + VGSvcError("Error reporting usage state %d for user '%s' to host, rc=%Rrc\n", userState, pszUser, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + + return rc; +} + + +/** + * Retrieves the currently logged in users and stores their names along with the + * user count. + * + * @returns VBox status code. + * @param pCache Property cache to use for storing some of the lookup + * data in between calls. + * @param ppszUserList Where to store the user list (separated by commas). + * Must be freed with RTStrFree(). + * @param pcUsersInList Where to store the number of users in the list. + */ +int VGSvcVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, char **ppszUserList, uint32_t *pcUsersInList) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(ppszUserList, VERR_INVALID_POINTER); + AssertPtrReturn(pcUsersInList, VERR_INVALID_POINTER); + + int rc = RTOnce(&g_vgsvcWinVmInitOnce, vgsvcWinVmInfoInitOnce, NULL); + if (RT_FAILURE(rc)) + return rc; + if (!g_pfnLsaEnumerateLogonSessions || !g_pfnEnumProcesses || !g_pfnLsaNtStatusToWinError) + return VERR_NOT_SUPPORTED; + + rc = VbglR3GuestPropConnect(&s_uDebugGuestPropClientID); + AssertRC(rc); + + char *pszUserList = NULL; + uint32_t cUsersInList = 0; + + /* This function can report stale or orphaned interactive logon sessions + of already logged off users (especially in Windows 2000). */ + PLUID paSessions = NULL; + ULONG cSessions = 0; + NTSTATUS rcNt = g_pfnLsaEnumerateLogonSessions(&cSessions, &paSessions); + if (rcNt != STATUS_SUCCESS) + { + ULONG uError = g_pfnLsaNtStatusToWinError(rcNt); + switch (uError) + { + case ERROR_NOT_ENOUGH_MEMORY: + VGSvcError("Not enough memory to enumerate logon sessions!\n"); + break; + + case ERROR_SHUTDOWN_IN_PROGRESS: + /* If we're about to shutdown when we were in the middle of enumerating the logon + * sessions, skip the error to not confuse the user with an unnecessary log message. */ + VGSvcVerbose(3, "Shutdown in progress ...\n"); + uError = ERROR_SUCCESS; + break; + + default: + VGSvcError("LsaEnumerate failed with error %RU32\n", uError); + break; + } + + if (paSessions) + g_pfnLsaFreeReturnBuffer(paSessions); + + return RTErrConvertFromWin32(uError); + } + VGSvcVerbose(3, "Found %u sessions\n", cSessions); + + PVBOXSERVICEVMINFOPROC paProcs; + DWORD cProcs; + rc = vgsvcVMInfoWinProcessesEnumerate(&paProcs, &cProcs); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NO_MEMORY) + VGSvcError("Not enough memory to enumerate processes\n"); + else + VGSvcError("Failed to enumerate processes, rc=%Rrc\n", rc); + } + else + { + PVBOXSERVICEVMINFOUSER pUserInfo; + pUserInfo = (PVBOXSERVICEVMINFOUSER)RTMemAllocZ(cSessions * sizeof(VBOXSERVICEVMINFOUSER) + 1); + if (!pUserInfo) + VGSvcError("Not enough memory to store enumerated users!\n"); + else + { + ULONG cUniqueUsers = 0; + + /* + * Note: The cSessions loop variable does *not* correlate with + * the Windows session ID! + */ + for (ULONG i = 0; i < cSessions; i++) + { + VGSvcVerbose(3, "Handling session %RU32 (of %RU32)\n", i + 1, cSessions); + + VBOXSERVICEVMINFOUSER userSession; + if (vgsvcVMInfoWinIsLoggedIn(&userSession, &paSessions[i])) + { + VGSvcVerbose(4, "Handling user=%ls, domain=%ls, package=%ls, session=%RU32\n", + userSession.wszUser, userSession.wszLogonDomain, userSession.wszAuthenticationPackage, + userSession.ulLastSession); + + /* Retrieve assigned processes of current session. */ + uint32_t cCurSessionProcs = vgsvcVMInfoWinSessionHasProcesses(&paSessions[i], paProcs, cProcs, + NULL /* Terminal session ID */); + /* Don't return here when current session does not have assigned processes + * anymore -- in that case we have to search through the unique users list below + * and see if got a stale user/session entry. */ + + if (g_cVerbosity > 3) + { + char szDebugSessionPath[255]; + RTStrPrintf(szDebugSessionPath, sizeof(szDebugSessionPath), + "/VirtualBox/GuestInfo/Debug/LSA/Session/%RU32", userSession.ulLastSession); + VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugSessionPath, + "#%RU32: cSessionProcs=%RU32 (of %RU32 procs total)", + s_uDebugIter, cCurSessionProcs, cProcs); + } + + bool fFoundUser = false; + for (ULONG a = 0; a < cUniqueUsers; a++) + { + PVBOXSERVICEVMINFOUSER pCurUser = &pUserInfo[a]; + AssertPtr(pCurUser); + + if ( !RTUtf16Cmp(userSession.wszUser, pCurUser->wszUser) + && !RTUtf16Cmp(userSession.wszLogonDomain, pCurUser->wszLogonDomain) + && !RTUtf16Cmp(userSession.wszAuthenticationPackage, pCurUser->wszAuthenticationPackage)) + { + /* + * Only respect the highest session for the current user. + */ + if (userSession.ulLastSession > pCurUser->ulLastSession) + { + VGSvcVerbose(4, "Updating user=%ls to %u processes (last used session: %RU32)\n", + pCurUser->wszUser, cCurSessionProcs, userSession.ulLastSession); + + if (!cCurSessionProcs) + VGSvcVerbose(3, "Stale session for user=%ls detected! Processes: %RU32 -> %RU32, Session: %RU32 -> %RU32\n", + pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs, + pCurUser->ulLastSession, userSession.ulLastSession); + + pCurUser->ulNumProcs = cCurSessionProcs; + pCurUser->ulLastSession = userSession.ulLastSession; + } + /* There can be multiple session objects using the same session ID for the + * current user -- so when we got the same session again just add the found + * processes to it. */ + else if (pCurUser->ulLastSession == userSession.ulLastSession) + { + VGSvcVerbose(4, "Updating processes for user=%ls (old procs=%RU32, new procs=%RU32, session=%RU32)\n", + pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs, pCurUser->ulLastSession); + + pCurUser->ulNumProcs = cCurSessionProcs; + } + + fFoundUser = true; + break; + } + } + + if (!fFoundUser) + { + VGSvcVerbose(4, "Adding new user=%ls (session=%RU32) with %RU32 processes\n", + userSession.wszUser, userSession.ulLastSession, cCurSessionProcs); + + memcpy(&pUserInfo[cUniqueUsers], &userSession, sizeof(VBOXSERVICEVMINFOUSER)); + pUserInfo[cUniqueUsers].ulNumProcs = cCurSessionProcs; + cUniqueUsers++; + Assert(cUniqueUsers <= cSessions); + } + } + } + + if (g_cVerbosity > 3) + VGSvcWritePropF(s_uDebugGuestPropClientID, "/VirtualBox/GuestInfo/Debug/LSA", + "#%RU32: cSessions=%RU32, cProcs=%RU32, cUniqueUsers=%RU32", + s_uDebugIter, cSessions, cProcs, cUniqueUsers); + + VGSvcVerbose(3, "Found %u unique logged-in user(s)\n", cUniqueUsers); + + for (ULONG i = 0; i < cUniqueUsers; i++) + { + if (g_cVerbosity > 3) + { + char szDebugUserPath[255]; RTStrPrintf(szDebugUserPath, sizeof(szDebugUserPath), "/VirtualBox/GuestInfo/Debug/LSA/User/%RU32", i); + VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugUserPath, + "#%RU32: szName=%ls, sessionID=%RU32, cProcs=%RU32", + s_uDebugIter, pUserInfo[i].wszUser, pUserInfo[i].ulLastSession, pUserInfo[i].ulNumProcs); + } + + bool fAddUser = false; + if (pUserInfo[i].ulNumProcs) + fAddUser = true; + + if (fAddUser) + { + VGSvcVerbose(3, "User '%ls' has %RU32 interactive processes (session=%RU32)\n", + pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, pUserInfo[i].ulLastSession); + + if (cUsersInList > 0) + { + rc = RTStrAAppend(&pszUserList, ","); + AssertRCBreakStmt(rc, RTStrFree(pszUserList)); + } + + cUsersInList += 1; + + char *pszUser = NULL; + char *pszDomain = NULL; + rc = RTUtf16ToUtf8(pUserInfo[i].wszUser, &pszUser); + if ( RT_SUCCESS(rc) + && pUserInfo[i].wszLogonDomain) + rc = RTUtf16ToUtf8(pUserInfo[i].wszLogonDomain, &pszDomain); + if (RT_SUCCESS(rc)) + { + /* Append user to users list. */ + rc = RTStrAAppend(&pszUserList, pszUser); + + /* Do idle detection. */ + if (RT_SUCCESS(rc)) + rc = vgsvcVMInfoWinWriteLastInput(pCache, pszUser, pszDomain); + } + else + rc = RTStrAAppend(&pszUserList, "<string-conversion-error>"); + + RTStrFree(pszUser); + RTStrFree(pszDomain); + + AssertRCBreakStmt(rc, RTStrFree(pszUserList)); + } + } + + RTMemFree(pUserInfo); + } + vgsvcVMInfoWinProcessesFree(cProcs, paProcs); + } + if (paSessions) + g_pfnLsaFreeReturnBuffer(paSessions); + + if (RT_SUCCESS(rc)) + { + *ppszUserList = pszUserList; + *pcUsersInList = cUsersInList; + } + + s_uDebugIter++; + VbglR3GuestPropDisconnect(s_uDebugGuestPropClientID); + + return rc; +} + + +int VGSvcVMInfoWinGetComponentVersions(uint32_t uClientID) +{ + int rc; + char szSysDir[MAX_PATH] = {0}; + char szWinDir[MAX_PATH] = {0}; + char szDriversDir[MAX_PATH + 32] = {0}; + + /* ASSUME: szSysDir and szWinDir and derivatives are always ASCII compatible. */ + GetSystemDirectory(szSysDir, MAX_PATH); + GetWindowsDirectory(szWinDir, MAX_PATH); + RTStrPrintf(szDriversDir, sizeof(szDriversDir), "%s\\drivers", szSysDir); +#ifdef RT_ARCH_AMD64 + char szSysWowDir[MAX_PATH + 32] = {0}; + RTStrPrintf(szSysWowDir, sizeof(szSysWowDir), "%s\\SysWow64", szWinDir); +#endif + + /* The file information table. */ + const VBOXSERVICEVMINFOFILE aVBoxFiles[] = + { + { szSysDir, "VBoxControl.exe" }, + { szSysDir, "VBoxHook.dll" }, + { szSysDir, "VBoxDisp.dll" }, + { szSysDir, "VBoxTray.exe" }, + { szSysDir, "VBoxService.exe" }, + { szSysDir, "VBoxMRXNP.dll" }, + { szSysDir, "VBoxGINA.dll" }, + { szSysDir, "VBoxCredProv.dll" }, + + /* On 64-bit we don't yet have the OpenGL DLLs in native format. + So just enumerate the 32-bit files in the SYSWOW directory. */ +#ifdef RT_ARCH_AMD64 + { szSysWowDir, "VBoxOGL-x86.dll" }, +#else /* !RT_ARCH_AMD64 */ + { szSysDir, "VBoxOGL.dll" }, +#endif /* !RT_ARCH_AMD64 */ + + { szDriversDir, "VBoxGuest.sys" }, + { szDriversDir, "VBoxMouseNT.sys" }, + { szDriversDir, "VBoxMouse.sys" }, + { szDriversDir, "VBoxSF.sys" }, + { szDriversDir, "VBoxVideo.sys" }, + }; + + for (unsigned i = 0; i < RT_ELEMENTS(aVBoxFiles); i++) + { + char szVer[128]; + rc = VGSvcUtilWinGetFileVersionString(aVBoxFiles[i].pszFilePath, aVBoxFiles[i].pszFileName, szVer, sizeof(szVer)); + char szPropPath[256]; + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestAdd/Components/%s", aVBoxFiles[i].pszFileName); + if ( rc != VERR_FILE_NOT_FOUND + && rc != VERR_PATH_NOT_FOUND) + VGSvcWritePropF(uClientID, szPropPath, "%s", szVer); + else + VGSvcWritePropF(uClientID, szPropPath, NULL); + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp new file mode 100644 index 00000000..1070be82 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp @@ -0,0 +1,1707 @@ +/* $Id: VBoxServiceVMInfo.cpp $ */ +/** @file + * VBoxService - Virtual Machine Information for the Host. + */ + +/* + * Copyright (C) 2009-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_vgsvc_vminfo VBoxService - VM Information + * + * The VM Information subservice provides heaps of useful information about the + * VM via guest properties. + * + * Guest properties is a limited database maintained by the HGCM GuestProperties + * service in cooperation with the Main API (VBoxSVC). Properties have a name + * (ours are path like), a string value, and a nanosecond timestamp (unix + * epoch). The timestamp lets the user see how recent the information is. As + * an laternative to polling on changes, it is also possible to wait on changes + * via the Main API or VBoxManage on the host side and VBoxControl in the guest. + * + * The namespace "/VirtualBox/" is reserved for value provided by VirtualBox. + * This service provides all the information under "/VirtualBox/GuestInfo/". + * + * + * @section sec_vgsvc_vminfo_beacons Beacons + * + * The subservice does not write properties unless there are changes. So, in + * order for the host side to know that information is up to date despite an + * oldish timestamp we define a couple of values that are always updated and can + * reliably used to figure how old the information actually is. + * + * For the networking part "/VirtualBox/GuestInfo/Net/Count" is the value to + * watch out for. + * + * For the login part, it's possible that we intended to use + * "/VirtualBox/GuestInfo/OS/LoggedInUsers" for this, however it is not defined + * correctly and current does NOT work as a beacon. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/winsock2.h> +# include <iprt/win/iphlpapi.h> +# include <iprt/win/ws2tcpip.h> +# include <iprt/win/windows.h> +# include <Ntsecapi.h> +#else +# define __STDC_LIMIT_MACROS +# include <arpa/inet.h> +# include <errno.h> +# include <netinet/in.h> +# include <sys/ioctl.h> +# include <sys/socket.h> +# include <net/if.h> +# include <pwd.h> /* getpwuid */ +# include <unistd.h> +# if !defined(RT_OS_OS2) && !defined(RT_OS_FREEBSD) && !defined(RT_OS_HAIKU) +# include <utmpx.h> /** @todo FreeBSD 9 should have this. */ +# endif +# ifdef RT_OS_OS2 +# include <net/if_dl.h> +# endif +# ifdef RT_OS_SOLARIS +# include <sys/sockio.h> +# include <net/if_arp.h> +# endif +# if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) +# include <ifaddrs.h> /* getifaddrs, freeifaddrs */ +# include <net/if_dl.h> /* LLADDR */ +# include <netdb.h> /* getnameinfo */ +# endif +# ifdef VBOX_WITH_DBUS +# include <VBox/dbus.h> +# endif +#endif + +#include <iprt/mem.h> +#include <iprt/thread.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/system.h> +#include <iprt/time.h> +#include <iprt/assert.h> +#include <VBox/err.h> +#include <VBox/version.h> +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#include "VBoxServicePropCache.h" + + +/** Structure containing information about a location awarness + * client provided by the host. */ +/** @todo Move this (and functions) into VbglR3. */ +typedef struct VBOXSERVICELACLIENTINFO +{ + uint32_t uID; + char *pszName; + char *pszLocation; + char *pszDomain; + bool fAttached; + uint64_t uAttachedTS; +} VBOXSERVICELACLIENTINFO, *PVBOXSERVICELACLIENTINFO; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The vminfo interval (milliseconds). */ +static uint32_t g_cMsVMInfoInterval = 0; +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_hVMInfoEvent = NIL_RTSEMEVENTMULTI; +/** The guest property service client ID. */ +static uint32_t g_uVMInfoGuestPropSvcClientID = 0; +/** Number of currently logged in users in OS. */ +static uint32_t g_cVMInfoLoggedInUsers = 0; +/** The guest property cache. */ +static VBOXSERVICEVEPROPCACHE g_VMInfoPropCache; +static const char *g_pszPropCacheValLoggedInUsersList = "/VirtualBox/GuestInfo/OS/LoggedInUsersList"; +static const char *g_pszPropCacheValLoggedInUsers = "/VirtualBox/GuestInfo/OS/LoggedInUsers"; +static const char *g_pszPropCacheValNoLoggedInUsers = "/VirtualBox/GuestInfo/OS/NoLoggedInUsers"; +static const char *g_pszPropCacheValNetCount = "/VirtualBox/GuestInfo/Net/Count"; +/** A guest user's guest property root key. */ +static const char *g_pszPropCacheValUser = "/VirtualBox/GuestInfo/User/"; +/** The VM session ID. Changes whenever the VM is restored or reset. */ +static uint64_t g_idVMInfoSession; +/** The last attached locartion awareness (LA) client timestamp. */ +static uint64_t g_LAClientAttachedTS = 0; +/** The current LA client info. */ +static VBOXSERVICELACLIENTINFO g_LAClientInfo; +/** User idle threshold (in ms). This specifies the minimum time a user is considered + * as being idle and then will be reported to the host. Default is 5s. */ +uint32_t g_uVMInfoUserIdleThresholdMS = 5 * 1000; + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ +static const char *g_pszLAActiveClient = "/VirtualBox/HostInfo/VRDP/ActiveClient"; + +#ifdef VBOX_WITH_DBUS +/** @name ConsoleKit defines (taken from 0.4.5). + * @{ */ +# define CK_NAME "org.freedesktop.ConsoleKit" +# define CK_PATH "/org/freedesktop/ConsoleKit" +# define CK_INTERFACE "org.freedesktop.ConsoleKit" +# define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager" +# define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager" +# define CK_SEAT_INTERFACE "org.freedesktop.ConsoleKit.Seat" +# define CK_SESSION_INTERFACE "org.freedesktop.ConsoleKit.Session" +/** @} */ +#endif + + + +/** + * Signals the event so that a re-enumeration of VM-specific + * information (like logged in users) can happen. + * + * @return IPRT status code. + */ +int VGSvcVMInfoSignal(void) +{ + /* Trigger a re-enumeration of all logged-in users by unblocking + * the multi event semaphore of the VMInfo thread. */ + if (g_hVMInfoEvent) + return RTSemEventMultiSignal(g_hVMInfoEvent); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnPreInit} + */ +static DECLCALLBACK(int) vbsvcVMInfoPreInit(void) +{ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption} + */ +static DECLCALLBACK(int) vbsvcVMInfoOption(const char **ppszShort, int argc, char **argv, int *pi) +{ + /** @todo Use RTGetOpt here. */ + + int rc = -1; + if (ppszShort) + /* no short options */; + else if (!strcmp(argv[*pi], "--vminfo-interval")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsVMInfoInterval, 1, UINT32_MAX - 1); + else if (!strcmp(argv[*pi], "--vminfo-user-idle-threshold")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_uVMInfoUserIdleThresholdMS, 1, UINT32_MAX - 1); + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vbsvcVMInfoInit(void) +{ + /* + * If not specified, find the right interval default. + * Then create the event sem to block on. + */ + if (!g_cMsVMInfoInterval) + g_cMsVMInfoInterval = g_DefaultInterval * 1000; + if (!g_cMsVMInfoInterval) + { + /* Set it to 5s by default for location awareness checks. */ + g_cMsVMInfoInterval = 5 * 1000; + } + + int rc = RTSemEventMultiCreate(&g_hVMInfoEvent); + AssertRCReturn(rc, rc); + + VbglR3GetSessionId(&g_idVMInfoSession); + /* The status code is ignored as this information is not available with VBox < 3.2.10. */ + + /* Initialize the LA client object. */ + RT_ZERO(g_LAClientInfo); + + rc = VbglR3GuestPropConnect(&g_uVMInfoGuestPropSvcClientID); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Property Service Client ID: %#x\n", g_uVMInfoGuestPropSvcClientID); + else + { + /* If the service was not found, we disable this service without + causing VBoxService to fail. */ + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ + { + VGSvcVerbose(0, "Guest property service is not available, disabling the service\n"); + rc = VERR_SERVICE_DISABLED; + } + else + VGSvcError("Failed to connect to the guest property service! Error: %Rrc\n", rc); + RTSemEventMultiDestroy(g_hVMInfoEvent); + g_hVMInfoEvent = NIL_RTSEMEVENTMULTI; + } + + if (RT_SUCCESS(rc)) + { + VGSvcPropCacheCreate(&g_VMInfoPropCache, g_uVMInfoGuestPropSvcClientID); + + /* + * Declare some guest properties with flags and reset values. + */ + int rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, + VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, + NULL /* Delete on exit */); + if (RT_FAILURE(rc2)) + VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValLoggedInUsersList, rc2); + + rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers, + VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, "0"); + if (RT_FAILURE(rc2)) + VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValLoggedInUsers, rc2); + + rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers, + VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, "true"); + if (RT_FAILURE(rc2)) + VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValNoLoggedInUsers, rc2); + + rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNetCount, + VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE, + NULL /* Delete on exit */); + if (RT_FAILURE(rc2)) + VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValNetCount, rc2); + + /* + * Get configuration guest properties from the host. + * Note: All properties should have sensible defaults in case the lookup here fails. + */ + char *pszValue; + rc2 = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--vminfo-user-idle-threshold", + true /* Read only */, &pszValue, NULL /* Flags */, NULL /* Timestamp */); + if (RT_SUCCESS(rc2)) + { + AssertPtr(pszValue); + g_uVMInfoUserIdleThresholdMS = RT_CLAMP(RTStrToUInt32(pszValue), 1000, UINT32_MAX - 1); + RTStrFree(pszValue); + } + } + return rc; +} + + +/** + * Retrieves a specifiy client LA property. + * + * @return IPRT status code. + * @param uClientID LA client ID to retrieve property for. + * @param pszProperty Property (without path) to retrieve. + * @param ppszValue Where to store value of property. + * @param puTimestamp Timestamp of property to retrieve. Optional. + */ +static int vgsvcGetLAClientValue(uint32_t uClientID, const char *pszProperty, char **ppszValue, uint64_t *puTimestamp) +{ + AssertReturn(uClientID, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszProperty, VERR_INVALID_POINTER); + + int rc; + + char pszClientPath[255]; +/** @todo r=bird: Another pointless RTStrPrintf test with wrong status code to boot. */ + if (RTStrPrintf(pszClientPath, sizeof(pszClientPath), + "/VirtualBox/HostInfo/VRDP/Client/%RU32/%s", uClientID, pszProperty)) + { + rc = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, pszClientPath, true /* Read only */, + ppszValue, NULL /* Flags */, puTimestamp); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +/** + * Retrieves LA client information. On success the returned structure will have allocated + * objects which need to be free'd with vboxServiceFreeLAClientInfo. + * + * @return IPRT status code. + * @param uClientID Client ID to retrieve information for. + * @param pClient Pointer where to store the client information. + */ +static int vgsvcGetLAClientInfo(uint32_t uClientID, PVBOXSERVICELACLIENTINFO pClient) +{ + AssertReturn(uClientID, VERR_INVALID_PARAMETER); + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + int rc = vgsvcGetLAClientValue(uClientID, "Name", &pClient->pszName, + NULL /* Timestamp */); + if (RT_SUCCESS(rc)) + { + char *pszAttach; + rc = vgsvcGetLAClientValue(uClientID, "Attach", &pszAttach, &pClient->uAttachedTS); + if (RT_SUCCESS(rc)) + { + AssertPtr(pszAttach); + pClient->fAttached = RTStrICmp(pszAttach, "1") == 0; + + RTStrFree(pszAttach); + } + } + if (RT_SUCCESS(rc)) + rc = vgsvcGetLAClientValue(uClientID, "Location", &pClient->pszLocation, NULL /* Timestamp */); + if (RT_SUCCESS(rc)) + rc = vgsvcGetLAClientValue(uClientID, "Domain", &pClient->pszDomain, NULL /* Timestamp */); + if (RT_SUCCESS(rc)) + pClient->uID = uClientID; + + return rc; +} + + +/** + * Frees all allocated LA client information of a structure. + * + * @param pClient Pointer to client information structure to free. + */ +static void vgsvcFreeLAClientInfo(PVBOXSERVICELACLIENTINFO pClient) +{ + if (pClient) + { + if (pClient->pszName) + { + RTStrFree(pClient->pszName); + pClient->pszName = NULL; + } + if (pClient->pszLocation) + { + RTStrFree(pClient->pszLocation); + pClient->pszLocation = NULL; + } + if (pClient->pszDomain) + { + RTStrFree(pClient->pszDomain); + pClient->pszDomain = NULL; + } + } +} + + +/** + * Updates a per-guest user guest property inside the given property cache. + * + * @return IPRT status code. + * @param pCache Pointer to guest property cache to update user in. + * @param pszUser Name of guest user to update. + * @param pszDomain Domain of guest user to update. Optional. + * @param pszKey Key name of guest property to update. + * @param pszValueFormat Guest property value to set. Pass NULL for deleting + * the property. + */ +int VGSvcUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain, + const char *pszKey, const char *pszValueFormat, ...) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszUser, VERR_INVALID_POINTER); + /* pszDomain is optional. */ + AssertPtrReturn(pszKey, VERR_INVALID_POINTER); + /* pszValueFormat is optional. */ + + int rc = VINF_SUCCESS; + + char *pszName; + if (pszDomain) + { + if (RTStrAPrintf(&pszName, "%s%s@%s/%s", g_pszPropCacheValUser, pszUser, pszDomain, pszKey) < 0) + rc = VERR_NO_MEMORY; + } + else + { + if (RTStrAPrintf(&pszName, "%s%s/%s", g_pszPropCacheValUser, pszUser, pszKey) < 0) + rc = VERR_NO_MEMORY; + } + + char *pszValue = NULL; + if ( RT_SUCCESS(rc) + && pszValueFormat) + { + va_list va; + va_start(va, pszValueFormat); + if (RTStrAPrintfV(&pszValue, pszValueFormat, va) < 0) + rc = VERR_NO_MEMORY; + va_end(va); + if ( RT_SUCCESS(rc) + && !pszValue) + rc = VERR_NO_STR_MEMORY; + } + + if (RT_SUCCESS(rc)) + rc = VGSvcPropCacheUpdate(pCache, pszName, pszValue); + if (rc == VINF_SUCCESS) /* VGSvcPropCacheUpdate will also return VINF_NO_CHANGE. */ + { + /** @todo Combine updating flags w/ updating the actual value. */ + rc = VGSvcPropCacheUpdateEntry(pCache, pszName, + VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, + NULL /* Delete on exit */); + } + + RTStrFree(pszValue); + RTStrFree(pszName); + return rc; +} + + +/** + * Writes the properties that won't change while the service is running. + * + * Errors are ignored. + */ +static void vgsvcVMInfoWriteFixedProperties(void) +{ + /* + * First get OS information that won't change. + */ + char szInfo[256]; + int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szInfo, sizeof(szInfo)); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Product", + "%s", RT_FAILURE(rc) ? "" : szInfo); + + rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szInfo, sizeof(szInfo)); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Release", + "%s", RT_FAILURE(rc) ? "" : szInfo); + + rc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szInfo, sizeof(szInfo)); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Version", + "%s", RT_FAILURE(rc) ? "" : szInfo); + + rc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szInfo, sizeof(szInfo)); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/ServicePack", + "%s", RT_FAILURE(rc) ? "" : szInfo); + + /* + * Retrieve version information about Guest Additions and installed files (components). + */ + char *pszAddVer; + char *pszAddVerExt; + char *pszAddRev; + rc = VbglR3GetAdditionsVersion(&pszAddVer, &pszAddVerExt, &pszAddRev); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/Version", + "%s", RT_FAILURE(rc) ? "" : pszAddVer); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/VersionExt", + "%s", RT_FAILURE(rc) ? "" : pszAddVerExt); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/Revision", + "%s", RT_FAILURE(rc) ? "" : pszAddRev); + if (RT_SUCCESS(rc)) + { + RTStrFree(pszAddVer); + RTStrFree(pszAddVerExt); + RTStrFree(pszAddRev); + } + +#ifdef RT_OS_WINDOWS + /* + * Do windows specific properties. + */ + char *pszInstDir; + rc = VbglR3GetAdditionsInstallationPath(&pszInstDir); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/InstallDir", + "%s", RT_FAILURE(rc) ? "" : pszInstDir); + if (RT_SUCCESS(rc)) + RTStrFree(pszInstDir); + + VGSvcVMInfoWinGetComponentVersions(g_uVMInfoGuestPropSvcClientID); +#endif +} + + +#if defined(VBOX_WITH_DBUS) && defined(RT_OS_LINUX) /* Not yet for Solaris/FreeBSB. */ +/* + * Simple wrapper to work around compiler-specific va_list madness. + */ +static dbus_bool_t vboxService_dbus_message_get_args(DBusMessage *message, DBusError *error, int first_arg_type, ...) +{ + va_list va; + va_start(va, first_arg_type); + dbus_bool_t ret = dbus_message_get_args_valist(message, error, first_arg_type, va); + va_end(va); + return ret; +} +#endif + + +/** + * Provide information about active users. + */ +static int vgsvcVMInfoWriteUsers(void) +{ + int rc; + char *pszUserList = NULL; + uint32_t cUsersInList = 0; + +#ifdef RT_OS_WINDOWS + rc = VGSvcVMInfoWinWriteUsers(&g_VMInfoPropCache, &pszUserList, &cUsersInList); + +#elif defined(RT_OS_FREEBSD) + /** @todo FreeBSD: Port logged on user info retrieval. + * However, FreeBSD 9 supports utmpx, so we could use the code + * block below (?). */ + rc = VERR_NOT_IMPLEMENTED; + +#elif defined(RT_OS_HAIKU) + /** @todo Haiku: Port logged on user info retrieval. */ + rc = VERR_NOT_IMPLEMENTED; + +#elif defined(RT_OS_OS2) + /** @todo OS/2: Port logged on (LAN/local/whatever) user info retrieval. */ + rc = VERR_NOT_IMPLEMENTED; + +#else + setutxent(); + utmpx *ut_user; + uint32_t cListSize = 32; + + /* Allocate a first array to hold 32 users max. */ + char **papszUsers = (char **)RTMemAllocZ(cListSize * sizeof(char *)); + if (papszUsers) + rc = VINF_SUCCESS; + else + rc = VERR_NO_MEMORY; + + /* Process all entries in the utmp file. + * Note: This only handles */ + while ( (ut_user = getutxent()) + && RT_SUCCESS(rc)) + { +# ifdef RT_OS_DARWIN /* No ut_user->ut_session on Darwin */ + VGSvcVerbose(4, "Found entry '%s' (type: %d, PID: %RU32)\n", ut_user->ut_user, ut_user->ut_type, ut_user->ut_pid); +# else + VGSvcVerbose(4, "Found entry '%s' (type: %d, PID: %RU32, session: %RU32)\n", + ut_user->ut_user, ut_user->ut_type, ut_user->ut_pid, ut_user->ut_session); +# endif + if (cUsersInList > cListSize) + { + cListSize += 32; + void *pvNew = RTMemRealloc(papszUsers, cListSize * sizeof(char*)); + AssertBreakStmt(pvNew, cListSize -= 32); + papszUsers = (char **)pvNew; + } + + /* Make sure we don't add user names which are not + * part of type USER_PROCES. */ + if (ut_user->ut_type == USER_PROCESS) /* Regular user process. */ + { + bool fFound = false; + for (uint32_t i = 0; i < cUsersInList && !fFound; i++) + fFound = strncmp(papszUsers[i], ut_user->ut_user, sizeof(ut_user->ut_user)) == 0; + + if (!fFound) + { + VGSvcVerbose(4, "Adding user '%s' (type: %d) to list\n", ut_user->ut_user, ut_user->ut_type); + + rc = RTStrDupEx(&papszUsers[cUsersInList], (const char *)ut_user->ut_user); + if (RT_FAILURE(rc)) + break; + cUsersInList++; + } + } + } + +# ifdef VBOX_WITH_DBUS +# if defined(RT_OS_LINUX) /* Not yet for Solaris/FreeBSB. */ + DBusError dbErr; + DBusConnection *pConnection = NULL; + int rc2 = RTDBusLoadLib(); + bool fHaveLibDbus = false; + if (RT_SUCCESS(rc2)) + { + /* Handle desktop sessions using ConsoleKit. */ + VGSvcVerbose(4, "Checking ConsoleKit sessions ...\n"); + fHaveLibDbus = true; + dbus_error_init(&dbErr); + pConnection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbErr); + } + + if ( pConnection + && !dbus_error_is_set(&dbErr)) + { + /* Get all available sessions. */ +/** @todo r=bird: What's the point of hardcoding things here when we've taken the pain of defining CK_XXX constants at the top of the file (or vice versa)? */ + DBusMessage *pMsgSessions = dbus_message_new_method_call("org.freedesktop.ConsoleKit", + "/org/freedesktop/ConsoleKit/Manager", + "org.freedesktop.ConsoleKit.Manager", + "GetSessions"); + if ( pMsgSessions + && dbus_message_get_type(pMsgSessions) == DBUS_MESSAGE_TYPE_METHOD_CALL) + { + DBusMessage *pReplySessions = dbus_connection_send_with_reply_and_block(pConnection, + pMsgSessions, 30 * 1000 /* 30s timeout */, + &dbErr); + if ( pReplySessions + && !dbus_error_is_set(&dbErr)) + { + char **ppszSessions; + int cSessions; + if ( dbus_message_get_type(pMsgSessions) == DBUS_MESSAGE_TYPE_METHOD_CALL + && vboxService_dbus_message_get_args(pReplySessions, &dbErr, DBUS_TYPE_ARRAY, + DBUS_TYPE_OBJECT_PATH, &ppszSessions, &cSessions, + DBUS_TYPE_INVALID /* Termination */)) + { + VGSvcVerbose(4, "ConsoleKit: retrieved %RU16 session(s)\n", cSessions); + + char **ppszCurSession = ppszSessions; + for (ppszCurSession; ppszCurSession && *ppszCurSession; ppszCurSession++) + { + VGSvcVerbose(4, "ConsoleKit: processing session '%s' ...\n", *ppszCurSession); + + /* Only respect active sessions .*/ + bool fActive = false; + DBusMessage *pMsgSessionActive = dbus_message_new_method_call("org.freedesktop.ConsoleKit", + *ppszCurSession, + "org.freedesktop.ConsoleKit.Session", + "IsActive"); + if ( pMsgSessionActive + && dbus_message_get_type(pMsgSessionActive) == DBUS_MESSAGE_TYPE_METHOD_CALL) + { + DBusMessage *pReplySessionActive = dbus_connection_send_with_reply_and_block(pConnection, + pMsgSessionActive, + 30 * 1000 /*sec*/, + &dbErr); + if ( pReplySessionActive + && !dbus_error_is_set(&dbErr)) + { + DBusMessageIter itMsg; + if ( dbus_message_iter_init(pReplySessionActive, &itMsg) + && dbus_message_iter_get_arg_type(&itMsg) == DBUS_TYPE_BOOLEAN) + { + /* Get uid from message. */ + int val; + dbus_message_iter_get_basic(&itMsg, &val); + fActive = val >= 1; + } + + if (pReplySessionActive) + dbus_message_unref(pReplySessionActive); + } + + if (pMsgSessionActive) + dbus_message_unref(pMsgSessionActive); + } + + VGSvcVerbose(4, "ConsoleKit: session '%s' is %s\n", + *ppszCurSession, fActive ? "active" : "not active"); + + /* *ppszCurSession now contains the object path + * (e.g. "/org/freedesktop/ConsoleKit/Session1"). */ + DBusMessage *pMsgUnixUser = dbus_message_new_method_call("org.freedesktop.ConsoleKit", + *ppszCurSession, + "org.freedesktop.ConsoleKit.Session", + "GetUnixUser"); + if ( fActive + && pMsgUnixUser + && dbus_message_get_type(pMsgUnixUser) == DBUS_MESSAGE_TYPE_METHOD_CALL) + { + DBusMessage *pReplyUnixUser = dbus_connection_send_with_reply_and_block(pConnection, + pMsgUnixUser, + 30 * 1000 /* 30s timeout */, + &dbErr); + if ( pReplyUnixUser + && !dbus_error_is_set(&dbErr)) + { + DBusMessageIter itMsg; + if ( dbus_message_iter_init(pReplyUnixUser, &itMsg) + && dbus_message_iter_get_arg_type(&itMsg) == DBUS_TYPE_UINT32) + { + /* Get uid from message. */ + uint32_t uid; + dbus_message_iter_get_basic(&itMsg, &uid); + + /** @todo Add support for getting UID_MIN (/etc/login.defs on + * Debian). */ + uint32_t uid_min = 1000; + + /* Look up user name (realname) from uid. */ + setpwent(); + struct passwd *ppwEntry = getpwuid(uid); + if ( ppwEntry + && ppwEntry->pw_name) + { + if (ppwEntry->pw_uid >= uid_min /* Only respect users, not daemons etc. */) + { + VGSvcVerbose(4, "ConsoleKit: session '%s' -> %s (uid: %RU32)\n", + *ppszCurSession, ppwEntry->pw_name, uid); + + bool fFound = false; + for (uint32_t i = 0; i < cUsersInList && !fFound; i++) + fFound = strcmp(papszUsers[i], ppwEntry->pw_name) == 0; + + if (!fFound) + { + VGSvcVerbose(4, "ConsoleKit: adding user '%s' to list\n", ppwEntry->pw_name); + + rc = RTStrDupEx(&papszUsers[cUsersInList], (const char *)ppwEntry->pw_name); + if (RT_FAILURE(rc)) + break; + cUsersInList++; + } + } + /* else silently ignore the user */ + } + else + VGSvcError("ConsoleKit: unable to lookup user name for uid=%RU32\n", uid); + } + else + AssertMsgFailed(("ConsoleKit: GetUnixUser returned a wrong argument type\n")); + } + + if (pReplyUnixUser) + dbus_message_unref(pReplyUnixUser); + } + else if (fActive) /* don't bitch about inactive users */ + { + static int s_iBitchedAboutConsoleKit = 0; + if (s_iBitchedAboutConsoleKit < 1) + { + s_iBitchedAboutConsoleKit++; + VGSvcError("ConsoleKit: unable to retrieve user for session '%s' (msg type=%d): %s\n", + *ppszCurSession, dbus_message_get_type(pMsgUnixUser), + dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available"); + } + } + + if (pMsgUnixUser) + dbus_message_unref(pMsgUnixUser); + } + + dbus_free_string_array(ppszSessions); + } + else + VGSvcError("ConsoleKit: unable to retrieve session parameters (msg type=%d): %s\n", + dbus_message_get_type(pMsgSessions), + dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available"); + dbus_message_unref(pReplySessions); + } + + if (pMsgSessions) + { + dbus_message_unref(pMsgSessions); + pMsgSessions = NULL; + } + } + else + { + static int s_iBitchedAboutConsoleKit = 0; + if (s_iBitchedAboutConsoleKit < 3) + { + s_iBitchedAboutConsoleKit++; + VGSvcError("Unable to invoke ConsoleKit (%d/3) -- maybe not installed / used? Error: %s\n", + s_iBitchedAboutConsoleKit, + dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available"); + } + } + + if (pMsgSessions) + dbus_message_unref(pMsgSessions); + } + else + { + static int s_iBitchedAboutDBus = 0; + if (s_iBitchedAboutDBus < 3) + { + s_iBitchedAboutDBus++; + VGSvcError("Unable to connect to system D-Bus (%d/3): %s\n", s_iBitchedAboutDBus, + fHaveLibDbus && dbus_error_is_set(&dbErr) ? dbErr.message : "D-Bus not installed"); + } + } + + if ( fHaveLibDbus + && dbus_error_is_set(&dbErr)) + dbus_error_free(&dbErr); +# endif /* RT_OS_LINUX */ +# endif /* VBOX_WITH_DBUS */ + + /** @todo Fedora/others: Handle systemd-loginctl. */ + + /* Calc the string length. */ + size_t cchUserList = 0; + if (RT_SUCCESS(rc)) + for (uint32_t i = 0; i < cUsersInList; i++) + cchUserList += (i != 0) + strlen(papszUsers[i]); + + /* Build the user list. */ + if (cchUserList > 0) + { + if (RT_SUCCESS(rc)) + rc = RTStrAllocEx(&pszUserList, cchUserList + 1); + if (RT_SUCCESS(rc)) + { + char *psz = pszUserList; + for (uint32_t i = 0; i < cUsersInList; i++) + { + if (i != 0) + *psz++ = ','; + size_t cch = strlen(papszUsers[i]); + memcpy(psz, papszUsers[i], cch); + psz += cch; + } + *psz = '\0'; + } + } + + /* Cleanup. */ + for (uint32_t i = 0; i < cUsersInList; i++) + RTStrFree(papszUsers[i]); + RTMemFree(papszUsers); + + endutxent(); /* Close utmpx file. */ +#endif /* !RT_OS_WINDOWS && !RT_OS_FREEBSD && !RT_OS_HAIKU && !RT_OS_OS2 */ + + Assert(RT_FAILURE(rc) || cUsersInList == 0 || (pszUserList && *pszUserList)); + + /* + * If the user enumeration above failed, reset the user count to 0 except + * we didn't have enough memory anymore. In that case we want to preserve + * the previous user count in order to not confuse third party tools which + * rely on that count. + */ + if (RT_FAILURE(rc)) + { + if (rc == VERR_NO_MEMORY) + { + static int s_iVMInfoBitchedOOM = 0; + if (s_iVMInfoBitchedOOM++ < 3) + VGSvcVerbose(0, "Warning: Not enough memory available to enumerate users! Keeping old value (%RU32)\n", + g_cVMInfoLoggedInUsers); + cUsersInList = g_cVMInfoLoggedInUsers; + } + else + cUsersInList = 0; + } + else /* Preserve logged in users count. */ + g_cVMInfoLoggedInUsers = cUsersInList; + + VGSvcVerbose(4, "cUsersInList=%RU32, pszUserList=%s, rc=%Rrc\n", cUsersInList, pszUserList ? pszUserList : "<NULL>", rc); + + if (pszUserList) + { + AssertMsg(cUsersInList, ("pszUserList contains users whereas cUsersInList is 0\n")); + rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, "%s", pszUserList); + } + else + rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, NULL); + if (RT_FAILURE(rc)) + VGSvcError("Error writing logged in users list, rc=%Rrc\n", rc); + + rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers, "%RU32", cUsersInList); + if (RT_FAILURE(rc)) + VGSvcError("Error writing logged in users count, rc=%Rrc\n", rc); + +/** @todo r=bird: What's this 'beacon' nonsense here? It's _not_ defined with + * the VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE flag set!! */ + rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers, cUsersInList == 0 ? "true" : "false"); + if (RT_FAILURE(rc)) + VGSvcError("Error writing no logged in users beacon, rc=%Rrc\n", rc); + + if (pszUserList) + RTStrFree(pszUserList); + + VGSvcVerbose(4, "Writing users returned with rc=%Rrc\n", rc); + return rc; +} + + +/** + * Provide information about the guest network. + */ +static int vgsvcVMInfoWriteNetwork(void) +{ + uint32_t cIfsReported = 0; + char szPropPath[256]; + +#ifdef RT_OS_WINDOWS + /* + * Check that the APIs we need are present. + */ + if ( !g_pfnWSAIoctl + || !g_pfnWSASocketA + || !g_pfnWSAGetLastError + || !g_pfninet_ntoa + || !g_pfnclosesocket) + return VINF_SUCCESS; + + /* + * Query the IP adapter info first, if we have the API. + */ + IP_ADAPTER_INFO *pAdpInfo = NULL; + if (g_pfnGetAdaptersInfo) + { + ULONG cbAdpInfo = RT_MAX(sizeof(IP_ADAPTER_INFO) * 2, _2K); + pAdpInfo = (IP_ADAPTER_INFO *)RTMemAllocZ(cbAdpInfo); + if (!pAdpInfo) + { + VGSvcError("VMInfo/Network: Failed to allocate two IP_ADAPTER_INFO structures\n"); + return VERR_NO_MEMORY; + } + + DWORD dwRet = g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo); + if (dwRet == ERROR_BUFFER_OVERFLOW) + { + IP_ADAPTER_INFO *pAdpInfoNew = (IP_ADAPTER_INFO*)RTMemRealloc(pAdpInfo, cbAdpInfo); + if (pAdpInfoNew) + { + pAdpInfo = pAdpInfoNew; + RT_BZERO(pAdpInfo, cbAdpInfo); + dwRet = g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo); + } + } + if (dwRet != NO_ERROR) + { + RTMemFree(pAdpInfo); + pAdpInfo = NULL; + if (dwRet == ERROR_NO_DATA) + /* If no network adapters available / present in the + system we pretend success to not bail out too early. */ + VGSvcVerbose(3, "VMInfo/Network: No network adapters present according to GetAdaptersInfo.\n"); + else + { + VGSvcError("VMInfo/Network: Failed to get adapter info: Error %d\n", dwRet); + return RTErrConvertFromWin32(dwRet); + } + } + } + + /* + * Ask the TCP/IP stack for an interface list. + */ + SOCKET sd = g_pfnWSASocketA(AF_INET, SOCK_DGRAM, 0, 0, 0, 0); + if (sd == SOCKET_ERROR) /* Socket invalid. */ + { + int const wsaErr = g_pfnWSAGetLastError(); + RTMemFree(pAdpInfo); + + /* Don't complain/bail out with an error if network stack is not up; can happen + * on NT4 due to start up when not connected shares dialogs pop up. */ + if (wsaErr == WSAENETDOWN) + { + VGSvcVerbose(0, "VMInfo/Network: Network is not up yet.\n"); + return VINF_SUCCESS; + } + VGSvcError("VMInfo/Network: Failed to get a socket: Error %d\n", wsaErr); + return RTErrConvertFromWin32(wsaErr); + } + + INTERFACE_INFO aInterfaces[20] = {{0}}; + DWORD cbReturned = 0; +# ifdef RT_ARCH_X86 + /* Workaround for uninitialized variable used in memcpy in GetTcpipInterfaceList + (NT4SP1 at least). It seems to be happy enough with garbages, no failure + returns so far, so we just need to prevent it from crashing by filling the + stack with valid pointer values prior to the API call. */ + _asm + { + mov edx, edi + lea eax, aInterfaces + mov [esp - 0x1000], eax + mov [esp - 0x2000], eax + mov ecx, 0x2000/4 - 1 + cld + lea edi, [esp - 0x2000] + rep stosd + mov edi, edx + } +# endif + int rc = g_pfnWSAIoctl(sd, + SIO_GET_INTERFACE_LIST, + NULL, /* pvInBuffer */ + 0, /* cbInBuffer */ + &aInterfaces[0], /* pvOutBuffer */ + sizeof(aInterfaces), /* cbOutBuffer */ + &cbReturned, + NULL, /* pOverlapped */ + NULL); /* pCompletionRoutine */ + if (rc == SOCKET_ERROR) + { + VGSvcError("VMInfo/Network: Failed to WSAIoctl() on socket: Error: %d\n", g_pfnWSAGetLastError()); + RTMemFree(pAdpInfo); + g_pfnclosesocket(sd); + return RTErrConvertFromWin32(g_pfnWSAGetLastError()); + } + g_pfnclosesocket(sd); + int cIfacesSystem = cbReturned / sizeof(INTERFACE_INFO); + + /* + * Iterate the inteface list we got back from the TCP/IP, + * using the pAdpInfo list to supply the MAC address. + */ + /** @todo Use GetAdaptersInfo() and GetAdapterAddresses (IPv4 + IPv6) for more information. */ + for (int i = 0; i < cIfacesSystem; ++i) + { + if (aInterfaces[i].iiFlags & IFF_LOOPBACK) /* Skip loopback device. */ + continue; + sockaddr_in *pAddress = &aInterfaces[i].iiAddress.AddressIn; + char szIp[32]; + RTStrPrintf(szIp, sizeof(szIp), "%s", g_pfninet_ntoa(pAddress->sin_addr)); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szIp); + + pAddress = &aInterfaces[i].iiBroadcastAddress.AddressIn; + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", g_pfninet_ntoa(pAddress->sin_addr)); + + pAddress = (sockaddr_in *)&aInterfaces[i].iiNetmask; + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", g_pfninet_ntoa(pAddress->sin_addr)); + + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, aInterfaces[i].iiFlags & IFF_UP ? "Up" : "Down"); + + if (pAdpInfo) + { + IP_ADAPTER_INFO *pAdp; + for (pAdp = pAdpInfo; pAdp; pAdp = pAdp->Next) + if (!strcmp(pAdp->IpAddressList.IpAddress.String, szIp)) + break; + + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfsReported); + if (pAdp) + { + char szMac[32]; + RTStrPrintf(szMac, sizeof(szMac), "%02X%02X%02X%02X%02X%02X", + pAdp->Address[0], pAdp->Address[1], pAdp->Address[2], + pAdp->Address[3], pAdp->Address[4], pAdp->Address[5]); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac); + } + else + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, NULL); + } + + cIfsReported++; + } + + RTMemFree(pAdpInfo); + +#elif defined(RT_OS_HAIKU) + /** @todo Haiku: implement network info. retreival */ + return VERR_NOT_IMPLEMENTED; + +#elif defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) + struct ifaddrs *pIfHead = NULL; + + /* Get all available interfaces */ + int rc = getifaddrs(&pIfHead); + if (rc < 0) + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to get all interfaces: Error %Rrc\n"); + return rc; + } + + /* Loop through all interfaces and set the data. */ + for (struct ifaddrs *pIfCurr = pIfHead; pIfCurr; pIfCurr = pIfCurr->ifa_next) + { + /* + * Only AF_INET and no loopback interfaces + */ + /** @todo IPv6 interfaces */ + if ( pIfCurr->ifa_addr->sa_family == AF_INET + && !(pIfCurr->ifa_flags & IFF_LOOPBACK)) + { + char szInetAddr[NI_MAXHOST]; + + memset(szInetAddr, 0, NI_MAXHOST); + getnameinfo(pIfCurr->ifa_addr, sizeof(struct sockaddr_in), + szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr); + + memset(szInetAddr, 0, NI_MAXHOST); + getnameinfo(pIfCurr->ifa_broadaddr, sizeof(struct sockaddr_in), + szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr); + + memset(szInetAddr, 0, NI_MAXHOST); + getnameinfo(pIfCurr->ifa_netmask, sizeof(struct sockaddr_in), + szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr); + + /* Search for the AF_LINK interface of the current AF_INET one and get the mac. */ + for (struct ifaddrs *pIfLinkCurr = pIfHead; pIfLinkCurr; pIfLinkCurr = pIfLinkCurr->ifa_next) + { + if ( pIfLinkCurr->ifa_addr->sa_family == AF_LINK + && !strcmp(pIfCurr->ifa_name, pIfLinkCurr->ifa_name)) + { + char szMac[32]; + uint8_t *pu8Mac = NULL; + struct sockaddr_dl *pLinkAddress = (struct sockaddr_dl *)pIfLinkCurr->ifa_addr; + + AssertPtr(pLinkAddress); + pu8Mac = (uint8_t *)LLADDR(pLinkAddress); + RTStrPrintf(szMac, sizeof(szMac), "%02X%02X%02X%02X%02X%02X", + pu8Mac[0], pu8Mac[1], pu8Mac[2], pu8Mac[3], pu8Mac[4], pu8Mac[5]); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac); + break; + } + } + + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, pIfCurr->ifa_flags & IFF_UP ? "Up" : "Down"); + + cIfsReported++; + } + } + + /* Free allocated resources. */ + freeifaddrs(pIfHead); + +#else /* !RT_OS_WINDOWS && !RT_OS_FREEBSD */ + /* + * Use SIOCGIFCONF to get a list of interface/protocol configurations. + * + * See "UNIX Network Programming Volume 1" by W. R. Stevens, section 17.6 + * for details on this ioctl. + */ + int sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) + { + int rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to get a socket: Error %Rrc\n", rc); + return rc; + } + + /* Call SIOCGIFCONF with the right sized buffer (remember the size). */ + static int s_cbBuf = 256; // 1024 + int cbBuf = s_cbBuf; + char *pchBuf; + struct ifconf IfConf; + int rc = VINF_SUCCESS; + for (;;) + { + pchBuf = (char *)RTMemTmpAllocZ(cbBuf); + if (!pchBuf) + { + rc = VERR_NO_TMP_MEMORY; + break; + } + + IfConf.ifc_len = cbBuf; + IfConf.ifc_buf = pchBuf; + if (ioctl(sd, SIOCGIFCONF, &IfConf) >= 0) + { + /* Hard to anticipate how space an address might possibly take, so + making some generous assumptions here to avoid performing the + query twice with different buffer sizes. */ + if (IfConf.ifc_len + 128 < cbBuf) + break; + } + else if (errno != EOVERFLOW) + { + rc = RTErrConvertFromErrno(errno); + break; + } + + /* grow the buffer */ + s_cbBuf = cbBuf *= 2; + RTMemFree(pchBuf); + } + if (RT_FAILURE(rc)) + { + close(sd); + RTMemTmpFree(pchBuf); + VGSvcError("VMInfo/Network: Error doing SIOCGIFCONF (cbBuf=%d): %Rrc\n", cbBuf, rc); + return rc; + } + + /* + * Iterate the interface/protocol configurations. + * + * Note! The current code naively assumes one IPv4 address per interface. + * This means that guest assigning more than one address to an + * interface will get multiple entries for one physical interface. + */ +# ifdef RT_OS_OS2 + struct ifreq *pPrevLinkAddr = NULL; +# endif + struct ifreq *pCur = IfConf.ifc_req; + size_t cbLeft = IfConf.ifc_len; + while (cbLeft >= sizeof(*pCur)) + { +# if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) + /* These two do not provide the sa_len member but only support address + * families which do not need extra bytes on the end. */ +# define SA_LEN(pAddr) sizeof(struct sockaddr) +# elif !defined(SA_LEN) +# define SA_LEN(pAddr) (pAddr)->sa_len +# endif + /* Figure the size of the current request. */ + size_t cbCur = RT_UOFFSETOF(struct ifreq, ifr_addr) + + SA_LEN(&pCur->ifr_addr); + cbCur = RT_MAX(cbCur, sizeof(struct ifreq)); +# if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) + Assert(pCur->ifr_addr.sa_family == AF_INET); +# endif + AssertBreak(cbCur <= cbLeft); + +# ifdef RT_OS_OS2 + /* On OS/2 we get the MAC address in the AF_LINK that the BSD 4.4 stack + emits. We boldly ASSUME these always comes first. */ + if ( pCur->ifr_addr.sa_family == AF_LINK + && ((struct sockaddr_dl *)&pCur->ifr_addr)->sdl_alen == 6) + pPrevLinkAddr = pCur; +# endif + + /* Skip it if it's not the kind of address we're looking for. */ + struct ifreq IfReqTmp; + bool fIfUp = false; + bool fSkip = false; + if (pCur->ifr_addr.sa_family != AF_INET) + fSkip = true; + else + { + /* Get the interface flags so we can detect loopback and check if it's up. */ + IfReqTmp = *pCur; + if (ioctl(sd, SIOCGIFFLAGS, &IfReqTmp) < 0) + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFFLAGS,%s) on socket: Error %Rrc\n", pCur->ifr_name, rc); + break; + } + fIfUp = !!(IfReqTmp.ifr_flags & IFF_UP); + if (IfReqTmp.ifr_flags & IFF_LOOPBACK) /* Skip the loopback device. */ + fSkip = true; + } + if (!fSkip) + { + size_t offSubProp = RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32", cIfsReported); + + sockaddr_in *pAddress = (sockaddr_in *)&pCur->ifr_addr; + strcpy(&szPropPath[offSubProp], "/V4/IP"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr)); + + /* Get the broadcast address. */ + IfReqTmp = *pCur; + if (ioctl(sd, SIOCGIFBRDADDR, &IfReqTmp) < 0) + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFBRDADDR) on socket: Error %Rrc\n", rc); + break; + } + pAddress = (sockaddr_in *)&IfReqTmp.ifr_broadaddr; + strcpy(&szPropPath[offSubProp], "/V4/Broadcast"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr)); + + /* Get the net mask. */ + IfReqTmp = *pCur; + if (ioctl(sd, SIOCGIFNETMASK, &IfReqTmp) < 0) + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFNETMASK) on socket: Error %Rrc\n", rc); + break; + } +# if defined(RT_OS_OS2) || defined(RT_OS_SOLARIS) + pAddress = (sockaddr_in *)&IfReqTmp.ifr_addr; +# else + pAddress = (sockaddr_in *)&IfReqTmp.ifr_netmask; +# endif + strcpy(&szPropPath[offSubProp], "/V4/Netmask"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr)); + +# if defined(RT_OS_SOLARIS) + /* + * "ifreq" is obsolete on Solaris. We use the recommended "lifreq". + * We might fail if the interface has not been assigned an IP address. + * That doesn't matter; as long as it's plumbed we can pick it up. + * But, if it has not acquired an IP address we cannot obtain it's MAC + * address this way, so we just use all zeros there. + */ + RTMAC IfMac; + struct lifreq IfReq; + RT_ZERO(IfReq); + AssertCompile(sizeof(IfReq.lifr_name) >= sizeof(pCur->ifr_name)); + strncpy(IfReq.lifr_name, pCur->ifr_name, sizeof(IfReq.lifr_name)); + if (ioctl(sd, SIOCGLIFADDR, &IfReq) >= 0) + { + struct arpreq ArpReq; + RT_ZERO(ArpReq); + memcpy(&ArpReq.arp_pa, &IfReq.lifr_addr, sizeof(struct sockaddr_in)); + + if (ioctl(sd, SIOCGARP, &ArpReq) >= 0) + memcpy(&IfMac, ArpReq.arp_ha.sa_data, sizeof(IfMac)); + else + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: failed to ioctl(SIOCGARP) on socket: Error %Rrc\n", rc); + break; + } + } + else + { + VGSvcVerbose(2, "VMInfo/Network: Interface '%s' has no assigned IP address, skipping ...\n", pCur->ifr_name); + continue; + } +# elif defined(RT_OS_OS2) + RTMAC IfMac; + if ( pPrevLinkAddr + && strncmp(pCur->ifr_name, pPrevLinkAddr->ifr_name, sizeof(pCur->ifr_name)) == 0) + { + struct sockaddr_dl *pDlAddr = (struct sockaddr_dl *)&pPrevLinkAddr->ifr_addr; + IfMac = *(PRTMAC)&pDlAddr->sdl_data[pDlAddr->sdl_nlen]; + } + else + RT_ZERO(IfMac); +#else + if (ioctl(sd, SIOCGIFHWADDR, &IfReqTmp) < 0) + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFHWADDR) on socket: Error %Rrc\n", rc); + break; + } + RTMAC IfMac = *(PRTMAC)&IfReqTmp.ifr_hwaddr.sa_data[0]; +# endif + strcpy(&szPropPath[offSubProp], "/MAC"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%02X%02X%02X%02X%02X%02X", + IfMac.au8[0], IfMac.au8[1], IfMac.au8[2], IfMac.au8[3], IfMac.au8[4], IfMac.au8[5]); + + strcpy(&szPropPath[offSubProp], "/Status"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, fIfUp ? "Up" : "Down"); + + /* The name. */ + int rc2 = RTStrValidateEncodingEx(pCur->ifr_name, sizeof(pCur->ifr_name), 0); + if (RT_SUCCESS(rc2)) + { + strcpy(&szPropPath[offSubProp], "/Name"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%.*s", sizeof(pCur->ifr_name), pCur->ifr_name); + } + + cIfsReported++; + } + + /* + * Next interface/protocol configuration. + */ + pCur = (struct ifreq *)((uintptr_t)pCur + cbCur); + cbLeft -= cbCur; + } + + RTMemTmpFree(pchBuf); + close(sd); + if (RT_FAILURE(rc)) + VGSvcError("VMInfo/Network: Network enumeration for interface %RU32 failed with error %Rrc\n", cIfsReported, rc); + +#endif /* !RT_OS_WINDOWS */ + +#if 0 /* Zapping not enabled yet, needs more testing first. */ + /* + * Zap all stale network interface data if the former (saved) network ifaces count + * is bigger than the current one. + */ + + /* Get former count. */ + uint32_t cIfsReportedOld; + rc = VGSvcReadPropUInt32(g_uVMInfoGuestPropSvcClientID, g_pszPropCacheValNetCount, &cIfsReportedOld, + 0 /* Min */, UINT32_MAX /* Max */); + if ( RT_SUCCESS(rc) + && cIfsReportedOld > cIfsReported) /* Are some ifaces not around anymore? */ + { + VGSvcVerbose(3, "VMInfo/Network: Stale interface data detected (%RU32 old vs. %RU32 current)\n", + cIfsReportedOld, cIfsReported); + + uint32_t uIfaceDeleteIdx = cIfsReported; + do + { + VGSvcVerbose(3, "VMInfo/Network: Deleting stale data of interface %d ...\n", uIfaceDeleteIdx); + rc = VGSvcPropCacheUpdateByPath(&g_VMInfoPropCache, NULL /* Value, delete */, 0 /* Flags */, "/VirtualBox/GuestInfo/Net/%RU32", uIfaceDeleteIdx++); + } while (RT_SUCCESS(rc)); + } + else if ( RT_FAILURE(rc) + && rc != VERR_NOT_FOUND) + { + VGSvcError("VMInfo/Network: Failed retrieving old network interfaces count with error %Rrc\n", rc); + } +#endif + + /* + * This property is a beacon which is _always_ written, even if the network configuration + * does not change. If this property is missing, the host assumes that all other GuestInfo + * properties are no longer valid. + */ + VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNetCount, "%RU32", cIfsReported); + + /* Don't fail here; just report everything we got. */ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vbsvcVMInfoWorker(bool volatile *pfShutdown) +{ + int rc; + + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + +#ifdef RT_OS_WINDOWS + /* Required for network information (must be called per thread). */ + if (g_pfnWSAStartup) + { + WSADATA wsaData; + RT_ZERO(wsaData); + if (g_pfnWSAStartup(MAKEWORD(2, 2), &wsaData)) + VGSvcError("VMInfo/Network: WSAStartup failed! Error: %Rrc\n", RTErrConvertFromWin32(g_pfnWSAGetLastError())); + } +#endif + + /* + * Write the fixed properties first. + */ + vgsvcVMInfoWriteFixedProperties(); + + /* + * Now enter the loop retrieving runtime data continuously. + */ + for (;;) + { + rc = vgsvcVMInfoWriteUsers(); + if (RT_FAILURE(rc)) + break; + + rc = vgsvcVMInfoWriteNetwork(); + if (RT_FAILURE(rc)) + break; + + /* Whether to wait for event semaphore or not. */ + bool fWait = true; + + /* Check for location awareness. This most likely only + * works with VBox (latest) 4.1 and up. */ + + /* Check for new connection. */ + char *pszLAClientID = NULL; + int rc2 = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, g_pszLAActiveClient, true /* Read only */, + &pszLAClientID, NULL /* Flags */, NULL /* Timestamp */); + if (RT_SUCCESS(rc2)) + { + AssertPtr(pszLAClientID); + if (RTStrICmp(pszLAClientID, "0")) /* Is a client connected? */ + { + uint32_t uLAClientID = RTStrToInt32(pszLAClientID); + uint64_t uLAClientAttachedTS; + + /* Peek at "Attach" value to figure out if hotdesking happened. */ + char *pszAttach = NULL; + rc2 = vgsvcGetLAClientValue(uLAClientID, "Attach", &pszAttach, + &uLAClientAttachedTS); + + if ( RT_SUCCESS(rc2) + && ( !g_LAClientAttachedTS + || (g_LAClientAttachedTS != uLAClientAttachedTS))) + { + vgsvcFreeLAClientInfo(&g_LAClientInfo); + + /* Note: There is a race between setting the guest properties by the host and getting them by + * the guest. */ + rc2 = vgsvcGetLAClientInfo(uLAClientID, &g_LAClientInfo); + if (RT_SUCCESS(rc2)) + { + VGSvcVerbose(1, "VRDP: Hotdesk client %s with ID=%RU32, Name=%s, Domain=%s\n", + /* If g_LAClientAttachedTS is 0 this means there already was an active + * hotdesk session when VBoxService started. */ + !g_LAClientAttachedTS ? "already active" : g_LAClientInfo.fAttached ? "connected" : "disconnected", + uLAClientID, g_LAClientInfo.pszName, g_LAClientInfo.pszDomain); + + g_LAClientAttachedTS = g_LAClientInfo.uAttachedTS; + + /* Don't wait for event semaphore below anymore because we now know that the client + * changed. This means we need to iterate all VM information again immediately. */ + fWait = false; + } + else + { + static int s_iBitchedAboutLAClientInfo = 0; + if (s_iBitchedAboutLAClientInfo < 10) + { + s_iBitchedAboutLAClientInfo++; + VGSvcError("Error getting active location awareness client info, rc=%Rrc\n", rc2); + } + } + } + else if (RT_FAILURE(rc2)) + VGSvcError("Error getting attached value of location awareness client %RU32, rc=%Rrc\n", uLAClientID, rc2); + if (pszAttach) + RTStrFree(pszAttach); + } + else + { + VGSvcVerbose(1, "VRDP: UTTSC disconnected from VRDP server\n"); + vgsvcFreeLAClientInfo(&g_LAClientInfo); + } + + RTStrFree(pszLAClientID); + } + else + { + static int s_iBitchedAboutLAClient = 0; + if ( (rc2 != VERR_NOT_FOUND) /* No location awareness installed, skip. */ + && s_iBitchedAboutLAClient < 3) + { + s_iBitchedAboutLAClient++; + VGSvcError("VRDP: Querying connected location awareness client failed with rc=%Rrc\n", rc2); + } + } + + VGSvcVerbose(3, "VRDP: Handling location awareness done\n"); + + /* + * Flush all properties if we were restored. + */ + uint64_t idNewSession = g_idVMInfoSession; + VbglR3GetSessionId(&idNewSession); + if (idNewSession != g_idVMInfoSession) + { + VGSvcVerbose(3, "The VM session ID changed, flushing all properties\n"); + vgsvcVMInfoWriteFixedProperties(); + VGSvcPropCacheFlush(&g_VMInfoPropCache); + g_idVMInfoSession = idNewSession; + } + + /* + * Block for a while. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + if (fWait) + rc2 = RTSemEventMultiWait(g_hVMInfoEvent, g_cMsVMInfoInterval); + if (*pfShutdown) + break; + if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) + { + VGSvcError("RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); + rc = rc2; + break; + } + else if (RT_LIKELY(RT_SUCCESS(rc2))) + { + /* Reset event semaphore if it got triggered. */ + rc2 = RTSemEventMultiReset(g_hVMInfoEvent); + if (RT_FAILURE(rc2)) + rc2 = VGSvcError("RTSemEventMultiReset failed; rc2=%Rrc\n", rc2); + } + } + +#ifdef RT_OS_WINDOWS + if (g_pfnWSACleanup) + g_pfnWSACleanup(); +#endif + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vbsvcVMInfoStop(void) +{ + RTSemEventMultiSignal(g_hVMInfoEvent); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vbsvcVMInfoTerm(void) +{ + if (g_hVMInfoEvent != NIL_RTSEMEVENTMULTI) + { + /** @todo temporary solution: Zap all values which are not valid + * anymore when VM goes down (reboot/shutdown ). Needs to + * be replaced with "temporary properties" later. + * + * One idea is to introduce a (HGCM-)session guest property + * flag meaning that a guest property is only valid as long + * as the HGCM session isn't closed (e.g. guest application + * terminates). [don't remove till implemented] + */ + /** @todo r=bird: Drop the VbglR3GuestPropDelSet call here and use the cache + * since it remembers what we've written. */ + /* Delete the "../Net" branch. */ + const char *apszPat[1] = { "/VirtualBox/GuestInfo/Net/*" }; + int rc = VbglR3GuestPropDelSet(g_uVMInfoGuestPropSvcClientID, &apszPat[0], RT_ELEMENTS(apszPat)); + + /* Destroy LA client info. */ + vgsvcFreeLAClientInfo(&g_LAClientInfo); + + /* Destroy property cache. */ + VGSvcPropCacheDestroy(&g_VMInfoPropCache); + + /* Disconnect from guest properties service. */ + rc = VbglR3GuestPropDisconnect(g_uVMInfoGuestPropSvcClientID); + if (RT_FAILURE(rc)) + VGSvcError("Failed to disconnect from guest property service! Error: %Rrc\n", rc); + g_uVMInfoGuestPropSvcClientID = 0; + + RTSemEventMultiDestroy(g_hVMInfoEvent); + g_hVMInfoEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'vminfo' service description. + */ +VBOXSERVICE g_VMInfo = +{ + /* pszName. */ + "vminfo", + /* pszDescription. */ + "Virtual Machine Information", + /* pszUsage. */ + " [--vminfo-interval <ms>] [--vminfo-user-idle-threshold <ms>]" + , + /* pszOptions. */ + " --vminfo-interval Specifies the interval at which to retrieve the\n" + " VM information. The default is 10000 ms.\n" + " --vminfo-user-idle-threshold <ms>\n" + " Specifies the user idle threshold (in ms) for\n" + " considering a guest user as being idle. The default\n" + " is 5000 (5 seconds).\n" + , + /* methods */ + vbsvcVMInfoPreInit, + vbsvcVMInfoOption, + vbsvcVMInfoInit, + vbsvcVMInfoWorker, + vbsvcVMInfoStop, + vbsvcVMInfoTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h new file mode 100644 index 00000000..c603eaf9 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h @@ -0,0 +1,42 @@ +/* $Id: VBoxServiceVMInfo.h $ */ +/** @file + * VBoxServiceVMInfo.h - Internal VM info definitions. + */ + +/* + * Copyright (C) 2013-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +extern int VGSvcUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain, + const char *pszKey, const char *pszValueFormat, ...); + + +extern uint32_t g_uVMInfoUserIdleThresholdMS; + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h */ + diff --git a/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk b/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk new file mode 100644 index 00000000..d8ef415c --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk @@ -0,0 +1,42 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for VBoxServicec test cases. +# + +# +# Copyright (C) 2010-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# tstUserInfo +# +PROGRAMS.win += tstUserInfo +tstUserInfo_TEMPLATE = VBoxGuestR3Exe +tstUserInfo_SOURCES = \ + tstUserInfo.cpp +tstUserInfo_VBOX_IMPORT_CHECKER.win.x86 = xp + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp b/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp new file mode 100644 index 00000000..0e20f3df --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp @@ -0,0 +1,87 @@ +/* $Id: tstUserInfo.cpp $ */ +/** @file + * Test case for correct user environment. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +# include <iprt/win/shlobj.h> +#endif + +#include <iprt/initterm.h> +#include <iprt/path.h> +#include <iprt/env.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <VBox/log.h> +#include <VBox/version.h> +#include <VBox/VBoxGuestLib.h> + + +int main() +{ + /* + * Init globals and such. + */ + RTR3InitExeNoArguments(0); + + int rc = VbglR3Init(); + if (RT_FAILURE(rc)) + { + RTPrintf("VbglR3Init failed with rc=%Rrc.\n", rc); + return -1; + } +#ifdef RT_OS_WINDOWS + WCHAR wszPath[MAX_PATH]; + HRESULT hRes = SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, wszPath); + + if (SUCCEEDED(hRes)) + { + RTPrintf("SHGetFolderPathW (CSIDL_APPDATA) = %ls\n", wszPath); + hRes = SHGetFolderPathW(0, CSIDL_PERSONAL, 0, 0, wszPath); + if (SUCCEEDED(hRes)) + { + RTPrintf("SHGetFolderPathW (CSIDL_PERSONAL) = %ls\n", wszPath); + } + else + RTPrintf("SHGetFolderPathW (CSIDL_PERSONAL) returned error: 0x%x\n", hRes); + } + else + RTPrintf("SHGetFolderPathW (CSIDL_APPDATA) returned error: 0x%x\n", hRes); + + if (FAILED(hRes)) + rc = RTErrConvertFromWin32(hRes); + + /* Dump env bits. */ + RTPrintf("Environment:\n\n"); + RTPrintf("APPDATA = %s\n", RTEnvGet("APPDATA")); +#endif + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + |