diff options
Diffstat (limited to '')
133 files changed, 66754 insertions, 0 deletions
diff --git a/src/VBox/Additions/common/Makefile.kmk b/src/VBox/Additions/common/Makefile.kmk new file mode 100644 index 00000000..c1b38073 --- /dev/null +++ b/src/VBox/Additions/common/Makefile.kmk @@ -0,0 +1,39 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the common addition code. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile. +include $(PATH_SUB_CURRENT)/VBoxControl/Makefile.kmk +include $(PATH_SUB_CURRENT)/VBoxGuest/Makefile.kmk +include $(PATH_SUB_CURRENT)/VBoxService/Makefile.kmk +ifdef VBOX_WITH_PAM + include $(PATH_SUB_CURRENT)/pam/Makefile.kmk +endif + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/Additions/common/VBoxControl/Makefile.kmk b/src/VBox/Additions/common/VBoxControl/Makefile.kmk new file mode 100644 index 00000000..432ca1c0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/Makefile.kmk @@ -0,0 +1,61 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Guest Additions Command Line Management Interface. +# + +# +# Copyright (C) 2010-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile(s). +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# VBoxControl +# +PROGRAMS += VBoxControl +VBoxControl_TEMPLATE = VBoxGuestR3Exe +if "$(KBUILD_TARGET)" == "win" && defined(VBOX_SIGNING_MODE) && defined(VBOX_SIGN_ADDITIONS) # (See the main Windows Additions makefile.) + VBoxControl_INSTTYPE = none + VBoxControl_DEBUG_INSTTYPE = both +endif +VBoxControl_DEFS += \ + $(if $(VBOX_WITH_HGCM),VBOX_WITH_HGCM,) \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \ + $(if $(VBOX_WITH_SHARED_FOLDERS),VBOX_WITH_SHARED_FOLDERS,) +VBoxControl_DEFS.win += \ + $(if $(VBOX_WITH_DPC_LATENCY_CHECKER),VBOX_WITH_DPC_LATENCY_CHECKER,) +VBoxControl_SDKS = VBoxZlibStatic +VBoxControl_SOURCES = \ + VBoxControl.cpp +VBoxControl_SOURCES.win = \ + VBoxControl.rc +VBoxControl_LDFLAGS.darwin = -framework IOKit +VBoxControl_LIBS.netbsd = crypt +VBoxControl_USES.win += vboximportchecker +VBoxControl_VBOX_IMPORT_CHECKER.win.x86 = nt31 +VBoxControl_VBOX_IMPORT_CHECKER.win.amd64 = xp64 + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/VBoxControl/VBoxControl.cpp b/src/VBox/Additions/common/VBoxControl/VBoxControl.cpp new file mode 100644 index 00000000..6646e9d2 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/VBoxControl.cpp @@ -0,0 +1,2209 @@ +/* $Id: VBoxControl.cpp $ */ +/** @file + * VBoxControl - Guest Additions Command Line Management Interface. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/alloca.h> +#include <iprt/cpp/autores.h> +#include <iprt/buildconfig.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/zip.h> +#include <VBox/log.h> +#include <VBox/version.h> +#include <VBox/VBoxGuestLib.h> +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#endif +#ifdef VBOX_WITH_GUEST_PROPS +# include <VBox/HostServices/GuestPropertySvc.h> +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS +# include <VBox/shflsvc.h> +# ifdef RT_OS_OS2 +# define OS2EMX_PLAIN_CHAR +# define INCL_ERRORS +# define INCL_DOSFILEMGR +# include <os2emx.h> +# endif +#endif +#ifdef VBOX_WITH_DPC_LATENCY_CHECKER +# include <VBox/VBoxGuest.h> +# include "../VBoxGuest/lib/VBoxGuestR3LibInternal.h" /* HACK ALERT! Using vbglR3DoIOCtl directly!! */ +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The program name (derived from argv[0]). */ +char const *g_pszProgName = ""; +/** The current verbosity level. */ +int g_cVerbosity = 0; + + +/** @name Displays the program usage message. + * @{ + */ + +/** + * Helper function that does indentation. + * + * @param pszLine Text. + * @param pszName Program name. + * @param pszCommand Command/option syntax. + */ +static void doUsage(char const *pszLine, char const *pszName = "", char const *pszCommand = "") +{ + /* Allow for up to 15 characters command name length (VBoxControl.exe) with + * perfect column alignment. Beyond that there's at least one space between + * the command if there are command line parameters. */ + RTPrintf("%s %-*s%s%s\n", + pszName, + *pszLine ? 35 - strlen(pszName) : 1, pszCommand, + *pszLine ? " " : "", pszLine); +} + +/** Enumerate the different parts of the usage we might want to print out */ +enum VBoxControlUsage +{ +#ifdef RT_OS_WINDOWS + GET_VIDEO_ACCEL, + SET_VIDEO_ACCEL, + VIDEO_FLAGS, + LIST_CUST_MODES, + ADD_CUST_MODE, + REMOVE_CUST_MODE, + SET_VIDEO_MODE, +#endif +#ifdef VBOX_WITH_GUEST_PROPS + GUEST_PROP, +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS + GUEST_SHAREDFOLDERS, +#endif +#if !defined(VBOX_CONTROL_TEST) + WRITE_CORE_DUMP, +#endif + WRITE_LOG, + TAKE_SNAPSHOT, + SAVE_STATE, + SUSPEND, + POWER_OFF, + VERSION, + HELP, + USAGE_ALL = UINT32_MAX +}; + +static RTEXITCODE usage(enum VBoxControlUsage eWhich = USAGE_ALL) +{ + RTPrintf("Usage:\n\n"); + doUsage("print version number and exit", g_pszProgName, "[-V|--version]"); + doUsage("suppress the logo", g_pszProgName, "--nologo ..."); + RTPrintf("\n"); + + /* Exclude the Windows bits from the test version. Anyone who needs to + test them can fix this. */ +#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST) + if (eWhich == GET_VIDEO_ACCEL || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "getvideoacceleration"); + if (eWhich == SET_VIDEO_ACCEL || eWhich == USAGE_ALL) + doUsage("<on|off>", g_pszProgName, "setvideoacceleration"); + if (eWhich == VIDEO_FLAGS || eWhich == USAGE_ALL) + doUsage("<get|set|clear|delete> [hex mask]", g_pszProgName, "videoflags"); + if (eWhich == LIST_CUST_MODES || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "listcustommodes"); + if (eWhich == ADD_CUST_MODE || eWhich == USAGE_ALL) + doUsage("<width> <height> <bpp>", g_pszProgName, "addcustommode"); + if (eWhich == REMOVE_CUST_MODE || eWhich == USAGE_ALL) + doUsage("<width> <height> <bpp>", g_pszProgName, "removecustommode"); + if (eWhich == SET_VIDEO_MODE || eWhich == USAGE_ALL) + doUsage("<width> <height> <bpp> <screen>", g_pszProgName, "setvideomode"); +#endif +#ifdef VBOX_WITH_GUEST_PROPS + if (eWhich == GUEST_PROP || eWhich == USAGE_ALL) + { + doUsage("get <property> [--verbose]", g_pszProgName, "guestproperty"); + doUsage("set <property> [<value> [--flags <flags>]]", g_pszProgName, "guestproperty"); + doUsage("delete|unset <property>", g_pszProgName, "guestproperty"); + doUsage("enumerate [--patterns <patterns>]", g_pszProgName, "guestproperty"); + doUsage("wait <patterns>", g_pszProgName, "guestproperty"); + doUsage("[--timestamp <last timestamp>]"); + doUsage("[--timeout <timeout in ms>"); + } +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS + if (eWhich == GUEST_SHAREDFOLDERS || eWhich == USAGE_ALL) + { + doUsage("list [--automount]", g_pszProgName, "sharedfolder"); +# ifdef RT_OS_OS2 + doUsage("use <drive> <folder>", g_pszProgName, "sharedfolder"); + doUsage("unuse <drive>", g_pszProgName, "sharedfolder"); +# endif + } +#endif + +#if !defined(VBOX_CONTROL_TEST) + if (eWhich == WRITE_CORE_DUMP || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "writecoredump"); +#endif + if (eWhich == WRITE_LOG || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "writelog [-n|--no-newline] [--] <msg>"); + if (eWhich == TAKE_SNAPSHOT || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "takesnapshot"); + if (eWhich == SAVE_STATE || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "savestate"); + if (eWhich == SUSPEND || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "suspend"); + if (eWhich == POWER_OFF || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "poweroff"); + if (eWhich == HELP || eWhich == USAGE_ALL) + doUsage("[command]", g_pszProgName, "help"); + if (eWhich == VERSION || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "version"); + + return RTEXITCODE_SUCCESS; +} + +/** @} */ + + +/** + * Implementation of the '--version' option. + * + * @returns RTEXITCODE_SUCCESS + */ +static RTEXITCODE printVersion(void) +{ + RTPrintf("%sr%u\n", VBOX_VERSION_STRING, RTBldCfgRevision()); + return RTEXITCODE_SUCCESS; +} + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE. + * @param pszFormat The message text. No newline. + * @param ... Format arguments. + */ +static RTEXITCODE VBoxControlError(const char *pszFormat, ...) +{ + /** @todo prefix with current command. */ + va_list va; + va_start(va, pszFormat); + RTMsgErrorV(pszFormat, va); + va_end(va); + return RTEXITCODE_FAILURE; +} + + +/** + * Displays a getopt error. + * + * @returns RTEXITCODE_FAILURE. + * @param ch The RTGetOpt return value. + * @param pValueUnion The RTGetOpt return data. + */ +static RTEXITCODE VBoxCtrlGetOptError(int ch, PCRTGETOPTUNION pValueUnion) +{ + /** @todo prefix with current command. */ + return RTGetOptPrintError(ch, pValueUnion); +} + + +/** + * Displays an syntax error message. + * + * @returns RTEXITCODE_FAILURE. + * @param pszFormat The message text. No newline. + * @param ... Format arguments. + */ +static RTEXITCODE VBoxControlSyntaxError(const char *pszFormat, ...) +{ + /** @todo prefix with current command. */ + va_list va; + va_start(va, pszFormat); + RTMsgErrorV(pszFormat, va); + va_end(va); + return RTEXITCODE_SYNTAX; +} + +#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST) + +decltype(ChangeDisplaySettingsExA) *g_pfnChangeDisplaySettingsExA; +decltype(ChangeDisplaySettings) *g_pfnChangeDisplaySettingsA; +decltype(EnumDisplaySettingsA) *g_pfnEnumDisplaySettingsA; + +static unsigned nextAdjacentRectXP(RECTL const *paRects, unsigned cRects, unsigned iRect) +{ + for (unsigned i = 0; i < cRects; i++) + if (paRects[iRect].right == paRects[i].left) + return i; + return ~0U; +} + +static unsigned nextAdjacentRectXN(RECTL const *paRects, unsigned cRects, unsigned iRect) +{ + for (unsigned i = 0; i < cRects; i++) + if (paRects[iRect].left == paRects[i].right) + return i; + return ~0U; +} + +static unsigned nextAdjacentRectYP(RECTL const *paRects, unsigned cRects, unsigned iRect) +{ + for (unsigned i = 0; i < cRects; i++) + if (paRects[iRect].bottom == paRects[i].top) + return i; + return ~0U; +} + +unsigned nextAdjacentRectYN(RECTL const *paRects, unsigned cRects, unsigned iRect) +{ + for (unsigned i = 0; i < cRects; i++) + if (paRects[iRect].top == paRects[i].bottom) + return i; + return ~0U; +} + +void resizeRect(RECTL *paRects, unsigned cRects, unsigned iPrimary, unsigned iResized, int NewWidth, int NewHeight) +{ + RECTL *paNewRects = (RECTL *)alloca(sizeof (RECTL) * cRects); + memcpy (paNewRects, paRects, sizeof(RECTL) * cRects); + paNewRects[iResized].right += NewWidth - (paNewRects[iResized].right - paNewRects[iResized].left); + paNewRects[iResized].bottom += NewHeight - (paNewRects[iResized].bottom - paNewRects[iResized].top); + + /* Verify all pairs of originally adjacent rectangles for all 4 directions. + * If the pair has a "good" delta (that is the first rectangle intersects the second) + * at a direction and the second rectangle is not primary one (which can not be moved), + * move the second rectangle to make it adjacent to the first one. + */ + + /* X positive. */ + unsigned iRect; + for (iRect = 0; iRect < cRects; iRect++) + { + /* Find the next adjacent original rect in x positive direction. */ + unsigned iNextRect = nextAdjacentRectXP (paRects, cRects, iRect); + Log(("next %d -> %d\n", iRect, iNextRect)); + + if (iNextRect == ~0 || iNextRect == iPrimary) + { + continue; + } + + /* Check whether there is an X intersection between these adjacent rects in the new rectangles + * and fix the intersection if delta is "good". + */ + int delta = paNewRects[iRect].right - paNewRects[iNextRect].left; + + if (delta > 0) + { + Log(("XP intersection right %d left %d, diff %d\n", + paNewRects[iRect].right, paNewRects[iNextRect].left, + delta)); + + paNewRects[iNextRect].left += delta; + paNewRects[iNextRect].right += delta; + } + } + + /* X negative. */ + for (iRect = 0; iRect < cRects; iRect++) + { + /* Find the next adjacent original rect in x negative direction. */ + unsigned iNextRect = nextAdjacentRectXN (paRects, cRects, iRect); + Log(("next %d -> %d\n", iRect, iNextRect)); + + if (iNextRect == ~0 || iNextRect == iPrimary) + { + continue; + } + + /* Check whether there is an X intersection between these adjacent rects in the new rectangles + * and fix the intersection if delta is "good". + */ + int delta = paNewRects[iRect].left - paNewRects[iNextRect].right; + + if (delta < 0) + { + Log(("XN intersection left %d right %d, diff %d\n", + paNewRects[iRect].left, paNewRects[iNextRect].right, + delta)); + + paNewRects[iNextRect].left += delta; + paNewRects[iNextRect].right += delta; + } + } + + /* Y positive (in the computer sense, top->down). */ + for (iRect = 0; iRect < cRects; iRect++) + { + /* Find the next adjacent original rect in y positive direction. */ + unsigned iNextRect = nextAdjacentRectYP (paRects, cRects, iRect); + Log(("next %d -> %d\n", iRect, iNextRect)); + + if (iNextRect == ~0 || iNextRect == iPrimary) + { + continue; + } + + /* Check whether there is an Y intersection between these adjacent rects in the new rectangles + * and fix the intersection if delta is "good". + */ + int delta = paNewRects[iRect].bottom - paNewRects[iNextRect].top; + + if (delta > 0) + { + Log(("YP intersection bottom %d top %d, diff %d\n", + paNewRects[iRect].bottom, paNewRects[iNextRect].top, + delta)); + + paNewRects[iNextRect].top += delta; + paNewRects[iNextRect].bottom += delta; + } + } + + /* Y negative (in the computer sense, down->top). */ + for (iRect = 0; iRect < cRects; iRect++) + { + /* Find the next adjacent original rect in x negative direction. */ + unsigned iNextRect = nextAdjacentRectYN (paRects, cRects, iRect); + Log(("next %d -> %d\n", iRect, iNextRect)); + + if (iNextRect == ~0 || iNextRect == iPrimary) + { + continue; + } + + /* Check whether there is an Y intersection between these adjacent rects in the new rectangles + * and fix the intersection if delta is "good". + */ + int delta = paNewRects[iRect].top - paNewRects[iNextRect].bottom; + + if (delta < 0) + { + Log(("YN intersection top %d bottom %d, diff %d\n", + paNewRects[iRect].top, paNewRects[iNextRect].bottom, + delta)); + + paNewRects[iNextRect].top += delta; + paNewRects[iNextRect].bottom += delta; + } + } + + memcpy (paRects, paNewRects, sizeof (RECTL) * cRects); + return; +} + +/* Returns TRUE to try again. */ +static BOOL ResizeDisplayDevice(ULONG Id, DWORD Width, DWORD Height, DWORD BitsPerPixel) +{ + BOOL fModeReset = (Width == 0 && Height == 0 && BitsPerPixel == 0); + + DISPLAY_DEVICE DisplayDevice; + RT_ZERO(DisplayDevice); + DisplayDevice.cb = sizeof(DisplayDevice); + + /* Find out how many display devices the system has */ + DWORD NumDevices = 0; + DWORD i = 0; + while (EnumDisplayDevices(NULL, i, &DisplayDevice, 0)) + { + Log(("[%d] %s\n", i, DisplayDevice.DeviceName)); + + if (DisplayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) + { + Log(("Found primary device. err %d\n", GetLastError())); + NumDevices++; + } + else if (!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) + { + + Log(("Found secondary device. err %d\n", GetLastError())); + NumDevices++; + } + + RT_ZERO(DisplayDevice); + DisplayDevice.cb = sizeof(DisplayDevice); + i++; + } + + Log(("Found total %d devices. err %d\n", NumDevices, GetLastError())); + + if (NumDevices == 0 || Id >= NumDevices) + { + Log(("Requested identifier %d is invalid. err %d\n", Id, GetLastError())); + return FALSE; + } + + DISPLAY_DEVICE *paDisplayDevices = (DISPLAY_DEVICE *)alloca(sizeof (DISPLAY_DEVICE) * NumDevices); + DEVMODE *paDeviceModes = (DEVMODE *)alloca(sizeof (DEVMODE) * NumDevices); + RECTL *paRects = (RECTL *)alloca(sizeof (RECTL) * NumDevices); + + /* Fetch information about current devices and modes. */ + DWORD DevNum = 0; + DWORD DevPrimaryNum = 0; + + RT_ZERO(DisplayDevice); + DisplayDevice.cb = sizeof(DISPLAY_DEVICE); + + i = 0; + while (EnumDisplayDevices (NULL, i, &DisplayDevice, 0)) + { + Log(("[%d(%d)] %s\n", i, DevNum, DisplayDevice.DeviceName)); + + BOOL fFetchDevice = FALSE; + + if (DisplayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) + { + Log(("Found primary device. err %d\n", GetLastError())); + DevPrimaryNum = DevNum; + fFetchDevice = TRUE; + } + else if (!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) + { + + Log(("Found secondary device. err %d\n", GetLastError())); + fFetchDevice = TRUE; + } + + if (fFetchDevice) + { + if (DevNum >= NumDevices) + { + Log(("%d >= %d\n", NumDevices, DevNum)); + return FALSE; + } + + paDisplayDevices[DevNum] = DisplayDevice; + + RT_BZERO(&paDeviceModes[DevNum], sizeof(DEVMODE)); + paDeviceModes[DevNum].dmSize = sizeof(DEVMODE); + if (!g_pfnEnumDisplaySettingsA((LPSTR)DisplayDevice.DeviceName, ENUM_REGISTRY_SETTINGS, &paDeviceModes[DevNum])) + { + Log(("EnumDisplaySettings err %d\n", GetLastError())); + return FALSE; + } + + Log(("%dx%d at %d,%d\n", + paDeviceModes[DevNum].dmPelsWidth, + paDeviceModes[DevNum].dmPelsHeight, + paDeviceModes[DevNum].dmPosition.x, + paDeviceModes[DevNum].dmPosition.y)); + + paRects[DevNum].left = paDeviceModes[DevNum].dmPosition.x; + paRects[DevNum].top = paDeviceModes[DevNum].dmPosition.y; + paRects[DevNum].right = paDeviceModes[DevNum].dmPosition.x + paDeviceModes[DevNum].dmPelsWidth; + paRects[DevNum].bottom = paDeviceModes[DevNum].dmPosition.y + paDeviceModes[DevNum].dmPelsHeight; + DevNum++; + } + + RT_ZERO(DisplayDevice); + DisplayDevice.cb = sizeof(DISPLAY_DEVICE); + i++; + } + + if (Width == 0) + Width = paRects[Id].right - paRects[Id].left; + + if (Height == 0) + Height = paRects[Id].bottom - paRects[Id].top; + + /* Check whether a mode reset or a change is requested. */ + if ( !fModeReset + && paRects[Id].right - paRects[Id].left == (LONG)Width + && paRects[Id].bottom - paRects[Id].top == (LONG)Height + && paDeviceModes[Id].dmBitsPerPel == BitsPerPixel) + { + Log(("VBoxDisplayThread : already at desired resolution.\n")); + return FALSE; + } + + resizeRect(paRects, NumDevices, DevPrimaryNum, Id, Width, Height); +#ifdef LOG_ENABLED + for (i = 0; i < NumDevices; i++) + Log(("[%d]: %d,%d %dx%d\n", + i, paRects[i].left, paRects[i].top, + paRects[i].right - paRects[i].left, + paRects[i].bottom - paRects[i].top)); +#endif /* Log */ + + /* Without this, Windows will not ask the miniport for its + * mode table but uses an internal cache instead. + */ + DEVMODE tempDevMode; + RT_ZERO(tempDevMode); + tempDevMode.dmSize = sizeof(DEVMODE); + g_pfnEnumDisplaySettingsA(NULL, 0xffffff, &tempDevMode); + + /* Assign the new rectangles to displays. */ + for (i = 0; i < NumDevices; i++) + { + paDeviceModes[i].dmPosition.x = paRects[i].left; + paDeviceModes[i].dmPosition.y = paRects[i].top; + paDeviceModes[i].dmPelsWidth = paRects[i].right - paRects[i].left; + paDeviceModes[i].dmPelsHeight = paRects[i].bottom - paRects[i].top; + + paDeviceModes[i].dmFields = DM_POSITION | DM_PELSHEIGHT | DM_PELSWIDTH; + + if ( i == Id + && BitsPerPixel != 0) + { + paDeviceModes[i].dmFields |= DM_BITSPERPEL; + paDeviceModes[i].dmBitsPerPel = BitsPerPixel; + } + Log(("calling pfnChangeDisplaySettingsEx %p\n", RT_CB_LOG_CAST(g_pfnChangeDisplaySettingsExA))); + g_pfnChangeDisplaySettingsExA((LPSTR)paDisplayDevices[i].DeviceName, + &paDeviceModes[i], NULL, CDS_NORESET | CDS_UPDATEREGISTRY, NULL); + Log(("ChangeDisplaySettingsEx position err %d\n", GetLastError())); + } + + /* A second call to ChangeDisplaySettings updates the monitor. */ + LONG status = g_pfnChangeDisplaySettingsA(NULL, 0); + Log(("ChangeDisplaySettings update status %d\n", status)); + if (status == DISP_CHANGE_SUCCESSFUL || status == DISP_CHANGE_BADMODE) + { + /* Successfully set new video mode or our driver can not set the requested mode. Stop trying. */ + return FALSE; + } + + /* Retry the request. */ + return TRUE; +} + +static DECLCALLBACK(RTEXITCODE) handleSetVideoMode(int argc, char *argv[]) +{ + if (argc != 3 && argc != 4) + { + usage(SET_VIDEO_MODE); + return RTEXITCODE_FAILURE; + } + + DWORD xres = RTStrToUInt32(argv[0]); + DWORD yres = RTStrToUInt32(argv[1]); + DWORD bpp = RTStrToUInt32(argv[2]); + DWORD scr = 0; + if (argc == 4) + scr = RTStrToUInt32(argv[3]); + + HMODULE hmodUser = GetModuleHandle("user32.dll"); + if (hmodUser) + { + /* ChangeDisplaySettingsExA was probably added in W2K, whereas ChangeDisplaySettingsA + and EnumDisplaySettingsA was added in NT 3.51. */ + g_pfnChangeDisplaySettingsExA = (decltype(g_pfnChangeDisplaySettingsExA))GetProcAddress(hmodUser, "ChangeDisplaySettingsExA"); + g_pfnChangeDisplaySettingsA = (decltype(g_pfnChangeDisplaySettingsA)) GetProcAddress(hmodUser, "ChangeDisplaySettingsA"); + g_pfnEnumDisplaySettingsA = (decltype(g_pfnEnumDisplaySettingsA)) GetProcAddress(hmodUser, "EnumDisplaySettingsA"); + + Log(("VBoxService: g_pfnChangeDisplaySettingsExA=%p g_pfnChangeDisplaySettingsA=%p g_pfnEnumDisplaySettingsA=%p\n", + RT_CB_LOG_CAST(g_pfnChangeDisplaySettingsExA), RT_CB_LOG_CAST(g_pfnChangeDisplaySettingsA), + RT_CB_LOG_CAST(g_pfnEnumDisplaySettingsA))); + + if ( g_pfnChangeDisplaySettingsExA + && g_pfnChangeDisplaySettingsA + && g_pfnEnumDisplaySettingsA) + { + /* The screen index is 0 based in the ResizeDisplayDevice call. */ + scr = scr > 0 ? scr - 1 : 0; + + /* Horizontal resolution must be a multiple of 8, round down. */ + xres &= ~0x7; + + RTPrintf("Setting resolution of display %d to %dx%dx%d ...", scr, xres, yres, bpp); + ResizeDisplayDevice(scr, xres, yres, bpp); + RTPrintf("done.\n"); + } + else + VBoxControlError("Error retrieving API for display change!"); + } + else + VBoxControlError("Error retrieving handle to user32.dll!"); + + return RTEXITCODE_SUCCESS; +} + +static int checkVBoxVideoKey(HKEY hkeyVideo) +{ + RTUTF16 wszValue[128]; + DWORD cbValue = sizeof(wszValue); + DWORD dwKeyType; + LONG status = RegQueryValueExW(hkeyVideo, L"Device Description", NULL, &dwKeyType, (LPBYTE)wszValue, &cbValue); + if (status == ERROR_SUCCESS) + { + /* WDDM has additional chars after "Adapter" */ + static char s_szDeviceDescription[] = "VirtualBox Graphics Adapter"; + wszValue[sizeof(s_szDeviceDescription) - 1] = '\0'; + if (RTUtf16ICmpAscii(wszValue, s_szDeviceDescription) == 0) + return VINF_SUCCESS; + } + + return VERR_NOT_FOUND; +} + +static HKEY getVideoKey(bool writable) +{ + HKEY hkeyDeviceMap = 0; + LONG status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\VIDEO", 0, KEY_READ, &hkeyDeviceMap); + if (status != ERROR_SUCCESS || !hkeyDeviceMap) + { + VBoxControlError("Error opening video device map registry key!\n"); + return 0; + } + + HKEY hkeyVideo = 0; + ULONG iDevice; + DWORD dwKeyType; + + /* + * Scan all '\Device\VideoX' REG_SZ keys to find VBox video driver entry. + * 'ObjectNumberList' REG_BINARY is an array of 32 bit device indexes (X). + */ + + /* Get the 'ObjectNumberList' */ + ULONG cDevices = 0; + DWORD adwObjectNumberList[256]; + DWORD cbValue = sizeof(adwObjectNumberList); + status = RegQueryValueExA(hkeyDeviceMap, "ObjectNumberList", NULL, &dwKeyType, (LPBYTE)&adwObjectNumberList[0], &cbValue); + + if ( status == ERROR_SUCCESS + && dwKeyType == REG_BINARY) + cDevices = cbValue / sizeof(DWORD); + else + { + /* The list might not exists. Use 'MaxObjectNumber' REG_DWORD and build a list. */ + DWORD dwMaxObjectNumber = 0; + cbValue = sizeof(dwMaxObjectNumber); + status = RegQueryValueExA(hkeyDeviceMap, "MaxObjectNumber", NULL, &dwKeyType, (LPBYTE)&dwMaxObjectNumber, &cbValue); + if ( status == ERROR_SUCCESS + && dwKeyType == REG_DWORD) + { + /* 'MaxObjectNumber' is inclusive. */ + cDevices = RT_MIN(dwMaxObjectNumber + 1, RT_ELEMENTS(adwObjectNumberList)); + for (iDevice = 0; iDevice < cDevices; iDevice++) + adwObjectNumberList[iDevice] = iDevice; + } + } + + if (cDevices == 0) + { + /* Always try '\Device\Video0' as the old code did. Enum can be used in this case in principle. */ + adwObjectNumberList[0] = 0; + cDevices = 1; + } + + /* Scan device entries */ + for (iDevice = 0; iDevice < cDevices; iDevice++) + { + RTUTF16 wszValueName[64]; + RTUtf16Printf(wszValueName, RT_ELEMENTS(wszValueName), "\\Device\\Video%u", adwObjectNumberList[iDevice]); + + RTUTF16 wszVideoLocation[256]; + cbValue = sizeof(wszVideoLocation); + status = RegQueryValueExW(hkeyDeviceMap, wszValueName, NULL, &dwKeyType, (LPBYTE)&wszVideoLocation[0], &cbValue); + + /* This value starts with '\REGISTRY\Machine' */ + if ( status == ERROR_SUCCESS + && dwKeyType == REG_SZ + && RTUtf16NICmpAscii(wszVideoLocation, RT_STR_TUPLE("\\REGISTRY\\Machine")) == 0) + { + status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, &wszVideoLocation[18], 0, + KEY_READ | (writable ? KEY_WRITE : 0), &hkeyVideo); + if (status == ERROR_SUCCESS) + { + int rc = checkVBoxVideoKey(hkeyVideo); + if (RT_SUCCESS(rc)) + { + /* Found, return hkeyVideo to the caller. */ + break; + } + + RegCloseKey(hkeyVideo); + hkeyVideo = 0; + } + } + } + + if (hkeyVideo == 0) + { + VBoxControlError("Error opening video registry key!\n"); + } + + RegCloseKey(hkeyDeviceMap); + return hkeyVideo; +} + +static DECLCALLBACK(RTEXITCODE) handleGetVideoAcceleration(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); + ULONG status; + HKEY hkeyVideo = getVideoKey(false); + + if (hkeyVideo) + { + /* query the actual value */ + DWORD fAcceleration = 1; + DWORD cbValue = sizeof(fAcceleration); + DWORD dwKeyType; + status = RegQueryValueExA(hkeyVideo, "EnableVideoAccel", NULL, &dwKeyType, (LPBYTE)&fAcceleration, &cbValue); + if (status != ERROR_SUCCESS) + RTPrintf("Video acceleration: default\n"); + else + RTPrintf("Video acceleration: %s\n", fAcceleration ? "on" : "off"); + RegCloseKey(hkeyVideo); + } + return RTEXITCODE_SUCCESS; +} + +static DECLCALLBACK(RTEXITCODE) handleSetVideoAcceleration(int argc, char *argv[]) +{ + ULONG status; + HKEY hkeyVideo; + + /* must have exactly one argument: the new offset */ + if ( (argc != 1) + || ( RTStrICmp(argv[0], "on") + && RTStrICmp(argv[0], "off"))) + { + usage(SET_VIDEO_ACCEL); + return RTEXITCODE_FAILURE; + } + + hkeyVideo = getVideoKey(true); + + if (hkeyVideo) + { + int fAccel = 0; + if (RTStrICmp(argv[0], "on") == 0) + fAccel = 1; + /* set a new value */ + status = RegSetValueExA(hkeyVideo, "EnableVideoAccel", 0, REG_DWORD, (LPBYTE)&fAccel, sizeof(fAccel)); + if (status != ERROR_SUCCESS) + { + VBoxControlError("Error %d writing video acceleration status!\n", status); + } + RegCloseKey(hkeyVideo); + } + return RTEXITCODE_SUCCESS; +} + +static DECLCALLBACK(RTEXITCODE) videoFlagsGet(void) +{ + HKEY hkeyVideo = getVideoKey(false); + + if (hkeyVideo) + { + DWORD dwFlags = 0; + DWORD cbValue = sizeof(dwFlags); + DWORD dwKeyType; + ULONG status = RegQueryValueExA(hkeyVideo, "VBoxVideoFlags", NULL, &dwKeyType, (LPBYTE)&dwFlags, &cbValue); + if (status != ERROR_SUCCESS) + RTPrintf("Video flags: default\n"); + else + RTPrintf("Video flags: 0x%08X\n", dwFlags); + RegCloseKey(hkeyVideo); + return RTEXITCODE_SUCCESS; + } + + return RTEXITCODE_FAILURE; +} + +static DECLCALLBACK(RTEXITCODE) videoFlagsDelete(void) +{ + HKEY hkeyVideo = getVideoKey(true); + + if (hkeyVideo) + { + ULONG status = RegDeleteValueA(hkeyVideo, "VBoxVideoFlags"); + if (status != ERROR_SUCCESS) + VBoxControlError("Error %d deleting video flags.\n", status); + RegCloseKey(hkeyVideo); + return RTEXITCODE_SUCCESS; + } + + return RTEXITCODE_FAILURE; +} + +static DECLCALLBACK(RTEXITCODE) videoFlagsModify(bool fSet, int argc, char *argv[]) +{ + if (argc != 1) + { + VBoxControlError("Mask required.\n"); + return RTEXITCODE_FAILURE; + } + + uint32_t u32Mask = 0; + int rc = RTStrToUInt32Full(argv[0], 16, &u32Mask); + if (RT_FAILURE(rc)) + { + VBoxControlError("Invalid video flags mask.\n"); + return RTEXITCODE_FAILURE; + } + + RTEXITCODE exitCode = RTEXITCODE_SUCCESS; + + HKEY hkeyVideo = getVideoKey(true); + if (hkeyVideo) + { + DWORD dwFlags = 0; + DWORD cbValue = sizeof(dwFlags); + DWORD dwKeyType; + ULONG status = RegQueryValueExA(hkeyVideo, "VBoxVideoFlags", NULL, &dwKeyType, (LPBYTE)&dwFlags, &cbValue); + if (status != ERROR_SUCCESS) + dwFlags = 0; + + dwFlags = fSet ? dwFlags | u32Mask : dwFlags & ~u32Mask; + + status = RegSetValueExA(hkeyVideo, "VBoxVideoFlags", 0, REG_DWORD, (LPBYTE)&dwFlags, sizeof(dwFlags)); + if (status != ERROR_SUCCESS) + { + VBoxControlError("Error %d writing video flags.\n", status); + exitCode = RTEXITCODE_FAILURE; + } + + RegCloseKey(hkeyVideo); + } + else + { + exitCode = RTEXITCODE_FAILURE; + } + + return exitCode; +} + +static DECLCALLBACK(RTEXITCODE) handleVideoFlags(int argc, char *argv[]) +{ + /* Must have a keyword and optional value (32 bit hex string). */ + if (argc != 1 && argc != 2) + { + VBoxControlError("Invalid number of arguments.\n"); + usage(VIDEO_FLAGS); + return RTEXITCODE_FAILURE; + } + + RTEXITCODE exitCode = RTEXITCODE_SUCCESS; + + if (RTStrICmp(argv[0], "get") == 0) + { + exitCode = videoFlagsGet(); + } + else if (RTStrICmp(argv[0], "delete") == 0) + { + exitCode = videoFlagsDelete(); + } + else if (RTStrICmp(argv[0], "set") == 0) + { + exitCode = videoFlagsModify(true, argc - 1, &argv[1]); + } + else if (RTStrICmp(argv[0], "clear") == 0) + { + exitCode = videoFlagsModify(false, argc - 1, &argv[1]); + } + else + { + VBoxControlError("Invalid command.\n"); + exitCode = RTEXITCODE_FAILURE; + } + + if (exitCode != RTEXITCODE_SUCCESS) + { + usage(VIDEO_FLAGS); + } + + return exitCode; +} + +#define MAX_CUSTOM_MODES 128 + +/* the table of custom modes */ +struct +{ + DWORD xres; + DWORD yres; + DWORD bpp; +} customModes[MAX_CUSTOM_MODES] = {{0}}; + +void getCustomModes(HKEY hkeyVideo) +{ + ULONG status; + int curMode = 0; + + /* null out the table */ + RT_ZERO(customModes); + + do + { + char valueName[20]; + DWORD xres, yres, bpp = 0; + DWORD dwType; + DWORD dwLen = sizeof(DWORD); + + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", curMode); + status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&xres, &dwLen); + if (status != ERROR_SUCCESS) + break; + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", curMode); + status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&yres, &dwLen); + if (status != ERROR_SUCCESS) + break; + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", curMode); + status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&bpp, &dwLen); + if (status != ERROR_SUCCESS) + break; + + /* check if the mode is OK */ + if ( (xres > (1 << 16)) + || (yres > (1 << 16)) + || ( (bpp != 16) + && (bpp != 24) + && (bpp != 32))) + break; + + /* add mode to table */ + customModes[curMode].xres = xres; + customModes[curMode].yres = yres; + customModes[curMode].bpp = bpp; + + ++curMode; + + if (curMode >= MAX_CUSTOM_MODES) + break; + } while(1); +} + +void writeCustomModes(HKEY hkeyVideo) +{ + ULONG status; + int tableIndex = 0; + int modeIndex = 0; + + /* first remove all values */ + for (int i = 0; i < MAX_CUSTOM_MODES; i++) + { + char valueName[20]; + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", i); + RegDeleteValueA(hkeyVideo, valueName); + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", i); + RegDeleteValueA(hkeyVideo, valueName); + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", i); + RegDeleteValueA(hkeyVideo, valueName); + } + + do + { + if (tableIndex >= MAX_CUSTOM_MODES) + break; + + /* is the table entry present? */ + if ( (!customModes[tableIndex].xres) + || (!customModes[tableIndex].yres) + || (!customModes[tableIndex].bpp)) + { + tableIndex++; + continue; + } + + RTPrintf("writing mode %d (%dx%dx%d)\n", modeIndex, customModes[tableIndex].xres, customModes[tableIndex].yres, customModes[tableIndex].bpp); + char valueName[20]; + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", modeIndex); + status = RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].xres, + sizeof(customModes[tableIndex].xres)); + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", modeIndex); + RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].yres, + sizeof(customModes[tableIndex].yres)); + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", modeIndex); + RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].bpp, + sizeof(customModes[tableIndex].bpp)); + + modeIndex++; + tableIndex++; + + } while(1); + +} + +static DECLCALLBACK(RTEXITCODE) handleListCustomModes(int argc, char *argv[]) +{ + RT_NOREF1(argv); + if (argc != 0) + { + usage(LIST_CUST_MODES); + return RTEXITCODE_FAILURE; + } + + HKEY hkeyVideo = getVideoKey(false); + + if (hkeyVideo) + { + getCustomModes(hkeyVideo); + for (int i = 0; i < (sizeof(customModes) / sizeof(customModes[0])); i++) + { + if ( !customModes[i].xres + || !customModes[i].yres + || !customModes[i].bpp) + continue; + + RTPrintf("Mode: %d x %d x %d\n", + customModes[i].xres, customModes[i].yres, customModes[i].bpp); + } + RegCloseKey(hkeyVideo); + } + return RTEXITCODE_SUCCESS; +} + +static DECLCALLBACK(RTEXITCODE) handleAddCustomMode(int argc, char *argv[]) +{ + if (argc != 3) + { + usage(ADD_CUST_MODE); + return RTEXITCODE_FAILURE; + } + + DWORD xres = RTStrToUInt32(argv[0]); + DWORD yres = RTStrToUInt32(argv[1]); + DWORD bpp = RTStrToUInt32(argv[2]); + + /** @todo better check including xres mod 8 = 0! */ + if ( (xres > (1 << 16)) + || (yres > (1 << 16)) + || ( (bpp != 16) + && (bpp != 24) + && (bpp != 32))) + { + VBoxControlError("invalid mode specified!\n"); + return RTEXITCODE_FAILURE; + } + + HKEY hkeyVideo = getVideoKey(true); + + if (hkeyVideo) + { + int i; + int fModeExists = 0; + getCustomModes(hkeyVideo); + for (i = 0; i < MAX_CUSTOM_MODES; i++) + { + /* mode exists? */ + if ( customModes[i].xres == xres + && customModes[i].yres == yres + && customModes[i].bpp == bpp + ) + { + fModeExists = 1; + } + } + if (!fModeExists) + { + for (i = 0; i < MAX_CUSTOM_MODES; i++) + { + /* item free? */ + if (!customModes[i].xres) + { + customModes[i].xres = xres; + customModes[i].yres = yres; + customModes[i].bpp = bpp; + break; + } + } + writeCustomModes(hkeyVideo); + } + RegCloseKey(hkeyVideo); + } + return RTEXITCODE_SUCCESS; +} + +static DECLCALLBACK(RTEXITCODE) handleRemoveCustomMode(int argc, char *argv[]) +{ + if (argc != 3) + { + usage(REMOVE_CUST_MODE); + return RTEXITCODE_FAILURE; + } + + DWORD xres = RTStrToUInt32(argv[0]); + DWORD yres = RTStrToUInt32(argv[1]); + DWORD bpp = RTStrToUInt32(argv[2]); + + HKEY hkeyVideo = getVideoKey(true); + + if (hkeyVideo) + { + getCustomModes(hkeyVideo); + for (int i = 0; i < MAX_CUSTOM_MODES; i++) + { + /* correct item? */ + if ( (customModes[i].xres == xres) + && (customModes[i].yres == yres) + && (customModes[i].bpp == bpp)) + { + RTPrintf("found mode at index %d\n", i); + RT_ZERO(customModes[i]); + break; + } + } + writeCustomModes(hkeyVideo); + RegCloseKey(hkeyVideo); + } + + return RTEXITCODE_SUCCESS; +} + +#endif /* RT_OS_WINDOWS */ + +#ifdef VBOX_WITH_GUEST_PROPS +/** + * Retrieves a value from the guest property store. + * This is accessed through the "VBoxGuestPropSvc" HGCM service. + * + * @returns Command exit code. + * @note see the command line API description for parameters + */ +static RTEXITCODE getGuestProperty(int argc, char **argv) +{ + bool fVerbose = false; + if ( argc == 2 + && ( strcmp(argv[1], "-verbose") == 0 + || strcmp(argv[1], "--verbose") == 0) + ) + fVerbose = true; + else if (argc != 1) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + + uint32_t u32ClientId = 0; + int rc = VINF_SUCCESS; + + rc = VbglR3GuestPropConnect(&u32ClientId); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc); + + /* + * Here we actually retrieve the value from the host. + */ + const char *pszName = argv[0]; + char *pszValue = NULL; + uint64_t u64Timestamp = 0; + char *pszFlags = NULL; + /* The buffer for storing the data and its initial size. We leave a bit + * of space here in case the maximum values are raised. */ + void *pvBuf = NULL; + uint32_t cbBuf = GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN + 1024; + if (RT_SUCCESS(rc)) + { + /* Because there is a race condition between our reading the size of a + * property and the guest updating it, we loop a few times here and + * hope. Actually this should never go wrong, as we are generous + * enough with buffer space. */ + bool fFinished = false; + for (unsigned i = 0; i < 10 && !fFinished; ++i) + { + void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf); + if (NULL == pvTmpBuf) + { + rc = VERR_NO_MEMORY; + VBoxControlError("Out of memory\n"); + } + else + { + pvBuf = pvTmpBuf; + rc = VbglR3GuestPropRead(u32ClientId, pszName, pvBuf, cbBuf, + &pszValue, &u64Timestamp, &pszFlags, + &cbBuf); + } + if (VERR_BUFFER_OVERFLOW == rc) + /* Leave a bit of extra space to be safe */ + cbBuf += 1024; + else + fFinished = true; + } + if (VERR_TOO_MUCH_DATA == rc) + VBoxControlError("Temporarily unable to retrieve the property\n"); + else if (RT_FAILURE(rc) && rc != VERR_NOT_FOUND) + VBoxControlError("Failed to retrieve the property value, error %Rrc\n", rc); + } + + /* + * And display it on the guest console. + */ + if (VERR_NOT_FOUND == rc) + RTPrintf("No value set!\n"); + else if (RT_SUCCESS(rc)) + { + RTPrintf("Value: %s\n", pszValue); + if (fVerbose) + { + RTPrintf("Timestamp: %lld ns\n", u64Timestamp); + RTPrintf("Flags: %s\n", pszFlags); + } + } + + if (u32ClientId != 0) + VbglR3GuestPropDisconnect(u32ClientId); + RTMemFree(pvBuf); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Writes a value to the guest property store. + * This is accessed through the "VBoxGuestPropSvc" HGCM service. + * + * @returns Command exit code. + * @note see the command line API description for parameters + */ +static RTEXITCODE setGuestProperty(int argc, char *argv[]) +{ + /* + * Check the syntax. We can deduce the correct syntax from the number of + * arguments. + */ + bool fUsageOK = true; + const char *pszName = NULL; + const char *pszValue = NULL; + const char *pszFlags = NULL; + if (2 == argc) + { + pszValue = argv[1]; + } + else if (3 == argc) + fUsageOK = false; + else if (4 == argc) + { + pszValue = argv[1]; + if ( strcmp(argv[2], "-flags") != 0 + && strcmp(argv[2], "--flags") != 0) + fUsageOK = false; + pszFlags = argv[3]; + } + else if (argc != 1) + fUsageOK = false; + if (!fUsageOK) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + /* This is always needed. */ + pszName = argv[0]; + + /* + * Do the actual setting. + */ + uint32_t u32ClientId = 0; + int rc = VINF_SUCCESS; + rc = VbglR3GuestPropConnect(&u32ClientId); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc); + else + { + if (pszFlags != NULL) + rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, pszFlags); + else + rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, pszValue); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to store the property value, error %Rrc\n", rc); + } + + if (u32ClientId != 0) + VbglR3GuestPropDisconnect(u32ClientId); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Deletes a guest property from the guest property store. + * This is accessed through the "VBoxGuestPropSvc" HGCM service. + * + * @returns Command exit code. + * @note see the command line API description for parameters + */ +static RTEXITCODE deleteGuestProperty(int argc, char *argv[]) +{ + /* + * Check the syntax. We can deduce the correct syntax from the number of + * arguments. + */ + bool fUsageOK = true; + const char *pszName = NULL; + if (argc < 1) + fUsageOK = false; + if (!fUsageOK) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + /* This is always needed. */ + pszName = argv[0]; + + /* + * Do the actual setting. + */ + uint32_t u32ClientId = 0; + int rc = VbglR3GuestPropConnect(&u32ClientId); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc); + else + { + rc = VbglR3GuestPropDelete(u32ClientId, pszName); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to delete the property value, error %Rrc\n", rc); + } + + if (u32ClientId != 0) + VbglR3GuestPropDisconnect(u32ClientId); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Enumerates the properties in the guest property store. + * This is accessed through the "VBoxGuestPropSvc" HGCM service. + * + * @returns Command exit code. + * @note see the command line API description for parameters + */ +static RTEXITCODE enumGuestProperty(int argc, char *argv[]) +{ + /* + * Check the syntax. We can deduce the correct syntax from the number of + * arguments. + */ + char const * const *papszPatterns = NULL; + uint32_t cPatterns = 0; + if ( argc > 1 + && ( strcmp(argv[0], "-patterns") == 0 + || strcmp(argv[0], "--patterns") == 0)) + { + papszPatterns = (char const * const *)&argv[1]; + cPatterns = argc - 1; + } + else if (argc != 0) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + + /* + * Do the actual enumeration. + */ + uint32_t u32ClientId = 0; + int rc = VbglR3GuestPropConnect(&u32ClientId); + if (RT_SUCCESS(rc)) + { + PVBGLR3GUESTPROPENUM pHandle; + const char *pszName, *pszValue, *pszFlags; + uint64_t u64Timestamp; + + rc = VbglR3GuestPropEnum(u32ClientId, papszPatterns, cPatterns, &pHandle, + &pszName, &pszValue, &u64Timestamp, &pszFlags); + if (RT_SUCCESS(rc)) + { + while (RT_SUCCESS(rc) && pszName) + { + RTPrintf("Name: %s, value: %s, timestamp: %lld, flags: %s\n", + pszName, pszValue ? pszValue : "", u64Timestamp, pszFlags); + + rc = VbglR3GuestPropEnumNext(pHandle, &pszName, &pszValue, &u64Timestamp, &pszFlags); + if (RT_FAILURE(rc)) + VBoxControlError("Error while enumerating guest properties: %Rrc\n", rc); + } + + VbglR3GuestPropEnumFree(pHandle); + } + else if (VERR_NOT_FOUND == rc) + RTPrintf("No properties found.\n"); + else + VBoxControlError("Failed to enumerate the guest properties! Error: %Rrc\n", rc); + VbglR3GuestPropDisconnect(u32ClientId); + } + else + VBoxControlError("Failed to connect to the guest property service! Error: %Rrc\n", rc); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Waits for notifications of changes to guest properties. + * This is accessed through the "VBoxGuestPropSvc" HGCM service. + * + * @returns Command exit code. + * @note see the command line API description for parameters + */ +static RTEXITCODE waitGuestProperty(int argc, char **argv) +{ + /* + * Handle arguments + */ + const char *pszPatterns = NULL; + uint64_t u64TimestampIn = 0; + uint32_t u32Timeout = RT_INDEFINITE_WAIT; + bool fUsageOK = true; + if (argc < 1) + fUsageOK = false; + pszPatterns = argv[0]; + for (int i = 1; fUsageOK && i < argc; ++i) + { + if ( strcmp(argv[i], "-timeout") == 0 + || strcmp(argv[i], "--timeout") == 0) + { + if ( i + 1 >= argc + || RTStrToUInt32Full(argv[i + 1], 10, &u32Timeout) + != VINF_SUCCESS + ) + fUsageOK = false; + else + ++i; + } + else if ( strcmp(argv[i], "-timestamp") == 0 + || strcmp(argv[i], "--timestamp") == 0) + { + if ( i + 1 >= argc + || RTStrToUInt64Full(argv[i + 1], 10, &u64TimestampIn) + != VINF_SUCCESS + ) + fUsageOK = false; + else + ++i; + } + else + fUsageOK = false; + } + if (!fUsageOK) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + + /* + * Connect to the service + */ + uint32_t u32ClientId = 0; + int rc = VbglR3GuestPropConnect(&u32ClientId); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc); + + /* + * Retrieve the notification from the host + */ + char *pszName = NULL; + char *pszValue = NULL; + uint64_t u64TimestampOut = 0; + char *pszFlags = NULL; + bool fWasDeleted = false; + /* The buffer for storing the data and its initial size. We leave a bit + * of space here in case the maximum values are raised. */ + void *pvBuf = NULL; + uint32_t cbBuf = GUEST_PROP_MAX_NAME_LEN + GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN + _1K; + /* Because there is a race condition between our reading the size of a + * property and the guest updating it, we loop a few times here and + * hope. Actually this should never go wrong, as we are generous + * enough with buffer space. */ + for (unsigned iTry = 0; ; iTry++) + { + pvBuf = RTMemRealloc(pvBuf, cbBuf); + if (pvBuf != NULL) + { + rc = VbglR3GuestPropWait(u32ClientId, pszPatterns, pvBuf, cbBuf, + u64TimestampIn, u32Timeout, + &pszName, &pszValue, &u64TimestampOut, + &pszFlags, &cbBuf, &fWasDeleted); + if (rc == VERR_BUFFER_OVERFLOW && iTry < 10) + { + cbBuf += _1K; /* Add a bit of extra space to be on the safe side. */ + continue; + } + if (rc == VERR_TOO_MUCH_DATA) + VBoxControlError("Temporarily unable to get a notification\n"); + else if (rc == VERR_INTERRUPTED) + VBoxControlError("The request timed out or was interrupted\n"); + else if (RT_FAILURE(rc) && rc != VERR_NOT_FOUND) + VBoxControlError("Failed to get a notification, error %Rrc\n", rc); + } + else + { + VBoxControlError("Out of memory\n"); + rc = VERR_NO_MEMORY; + } + break; + } + + /* + * And display it on the guest console. + */ + if (VERR_NOT_FOUND == rc) + RTPrintf("No value set!\n"); + else if (rc == VERR_BUFFER_OVERFLOW) + RTPrintf("Internal error: unable to determine the size of the data!\n"); + else if (RT_SUCCESS(rc)) + { + if (fWasDeleted) + { + RTPrintf("Property %s was deleted\n", pszName); + } + else + { + RTPrintf("Name: %s\n", pszName); + RTPrintf("Value: %s\n", pszValue); + RTPrintf("Timestamp: %lld ns\n", u64TimestampOut); + RTPrintf("Flags: %s\n", pszFlags); + } + } + + if (u32ClientId != 0) + VbglR3GuestPropDisconnect(u32ClientId); + RTMemFree(pvBuf); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Access the guest property store through the "VBoxGuestPropSvc" HGCM + * service. + * + * @returns 0 on success, 1 on failure + * @note see the command line API description for parameters + */ +static DECLCALLBACK(RTEXITCODE) handleGuestProperty(int argc, char *argv[]) +{ + if (argc == 0) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + if (!strcmp(argv[0], "get")) + return getGuestProperty(argc - 1, argv + 1); + if (!strcmp(argv[0], "set")) + return setGuestProperty(argc - 1, argv + 1); + if (!strcmp(argv[0], "delete") || !strcmp(argv[0], "unset")) + return deleteGuestProperty(argc - 1, argv + 1); + if (!strcmp(argv[0], "enumerate")) + return enumGuestProperty(argc - 1, argv + 1); + if (!strcmp(argv[0], "wait")) + return waitGuestProperty(argc - 1, argv + 1); + /* unknown cmd */ + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; +} + +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS + +/** + * Lists the Shared Folders provided by the host. + */ +static RTEXITCODE sharedFolder_list(int argc, char **argv) +{ + bool fUsageOK = true; + bool fOnlyShowAutoMount = false; + if (argc == 1) + { + if (!strcmp(argv[0], "--automount")) + fOnlyShowAutoMount = true; + else + fUsageOK = false; + } + else if (argc > 1) + fUsageOK = false; + if (!fUsageOK) + { + usage(GUEST_SHAREDFOLDERS); + return RTEXITCODE_SYNTAX; + } + + uint32_t u32ClientId; + int rc = VbglR3SharedFolderConnect(&u32ClientId); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to connect to the shared folder service, error %Rrc\n", rc); + else + { + PVBGLR3SHAREDFOLDERMAPPING paMappings; + uint32_t cMappings; + rc = VbglR3SharedFolderGetMappings(u32ClientId, fOnlyShowAutoMount, &paMappings, &cMappings); + if (RT_SUCCESS(rc)) + { + if (fOnlyShowAutoMount) + RTPrintf("Auto-mounted Shared Folder mappings (%u):\n\n", cMappings); + else + RTPrintf("Shared Folder mappings (%u):\n\n", cMappings); + + for (uint32_t i = 0; i < cMappings; i++) + { + char *pszName; + char *pszMntPt; + uint64_t fFlags; + uint32_t uRootIdVer; + rc = VbglR3SharedFolderQueryFolderInfo(u32ClientId, paMappings[i].u32Root, 0, + &pszName, &pszMntPt, &fFlags, &uRootIdVer); + if (RT_SUCCESS(rc)) + { + RTPrintf("%02u - %s [idRoot=%u", i + 1, pszName, paMappings[i].u32Root); + if (fFlags & SHFL_MIF_WRITABLE) + RTPrintf(" writable"); + else + RTPrintf(" readonly"); + if (fFlags & SHFL_MIF_AUTO_MOUNT) + RTPrintf(" auto-mount"); + if (fFlags & SHFL_MIF_SYMLINK_CREATION) + RTPrintf(" create-symlink"); + if (fFlags & SHFL_MIF_HOST_ICASE) + RTPrintf(" host-icase"); + if (fFlags & SHFL_MIF_GUEST_ICASE) + RTPrintf(" guest-icase"); + if (*pszMntPt) + RTPrintf(" mnt-pt=%s", pszMntPt); + RTPrintf("]"); +# ifdef RT_OS_OS2 + /* Show drive letters: */ + const char *pszOn = " on"; + for (char chDrive = 'A'; chDrive <= 'Z'; chDrive++) + { + char szDrive[4] = { chDrive, ':', '\0', '\0' }; + union + { + FSQBUFFER2 FsQueryBuf; + char achPadding[512]; + } uBuf; + RT_ZERO(uBuf); + ULONG cbBuf = sizeof(uBuf) - 2; + APIRET rcOs2 = DosQueryFSAttach(szDrive, 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]; + if (RTStrICmp(pszMountedName, pszName) == 0) + { + const char *pszTag = pszMountedName + strlen(pszMountedName) + 1; /* safe */ + if (*pszTag != '\0') + RTPrintf("%s %s (%s)", pszOn, szDrive, pszTag); + else + RTPrintf("%s %s", pszOn, szDrive); + pszOn = ","; + } + } + } + } +# endif + RTPrintf("\n"); + + RTStrFree(pszName); + RTStrFree(pszMntPt); + } + else + VBoxControlError("Error while getting the shared folder name for root node = %u, rc = %Rrc\n", + paMappings[i].u32Root, rc); + } + if (!cMappings) + RTPrintf("No Shared Folders available.\n"); + VbglR3SharedFolderFreeMappings(paMappings); + } + else + VBoxControlError("Error while getting the shared folder mappings, rc = %Rrc\n", rc); + VbglR3SharedFolderDisconnect(u32ClientId); + } + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +# ifdef RT_OS_OS2 +/** + * Attaches a shared folder to a drive letter. + */ +static RTEXITCODE sharedFolder_use(int argc, char **argv) +{ + /* + * Takes a drive letter and a share name as arguments. + */ + if (argc != 2) + return VBoxControlSyntaxError("sharedfolder use: expected a drive letter and a shared folder name\n"); + + const char *pszDrive = argv[0]; + if (!RT_C_IS_ALPHA(pszDrive[0]) || pszDrive[1] != ':' || pszDrive[2] != '\0') + return VBoxControlSyntaxError("sharedfolder use: not a drive letter: %s\n", pszDrive); + + static const char s_szTag[] = "VBoxControl"; + char szzNameAndTag[256]; + const char *pszName = argv[1]; + size_t cchName = strlen(pszName); + if (cchName < 1) + return VBoxControlSyntaxError("sharedfolder use: shared folder name cannot be empty!\n"); + if (cchName + 1 + sizeof(s_szTag) >= sizeof(szzNameAndTag)) + return VBoxControlSyntaxError("sharedfolder use: shared folder name is too long! (%s)\n", pszName); + + /* + * Do the attaching. + */ + memcpy(szzNameAndTag, pszName, cchName); + szzNameAndTag[cchName] = '\0'; + memcpy(&szzNameAndTag[cchName + 1], s_szTag, sizeof(s_szTag)); + + APIRET rcOs2 = DosFSAttach(pszDrive, "VBOXSF", szzNameAndTag, cchName + 1 + sizeof(s_szTag), FS_ATTACH); + if (rcOs2 == NO_ERROR) + return RTEXITCODE_SUCCESS; + if (rcOs2 == ERROR_INVALID_FSD_NAME) + return VBoxControlError("Shared folders IFS not installed?\n"); + return VBoxControlError("DosFSAttach/FS_ATTACH failed to attach '%s' to '%s': %u\n", pszName, pszDrive, rcOs2); +} + +/** + * Detaches a shared folder from a drive letter. + */ +static RTEXITCODE sharedFolder_unuse(int argc, char **argv) +{ + /* + * Only takes a drive letter as argument. + */ + if (argc != 1) + return VBoxControlSyntaxError("sharedfolder unuse: expected drive letter\n"); + const char *pszDrive = argv[0]; + if (!RT_C_IS_ALPHA(pszDrive[0]) || pszDrive[1] != ':' || pszDrive[2] != '\0') + return VBoxControlSyntaxError("sharedfolder unuse: not a drive letter: %s\n", pszDrive); + + /* + * Do the detaching. + */ + APIRET rcOs2 = DosFSAttach(pszDrive, "VBOXSF", NULL, 0, FS_DETACH); + if (rcOs2 == NO_ERROR) + return RTEXITCODE_SUCCESS; + return VBoxControlError("DosFSAttach/FS_DETACH failed on '%s': %u\n", pszDrive, rcOs2); +} + +# endif /* RT_OS_OS2 */ + + +/** + * Handles Shared Folders control. + * + * @returns 0 on success, 1 on failure + * @note see the command line API description for parameters + * (r=bird: yeah, right. The API description contains nil about params) + */ +static DECLCALLBACK(RTEXITCODE) handleSharedFolder(int argc, char *argv[]) +{ + if (argc == 0) + { + usage(GUEST_SHAREDFOLDERS); + return RTEXITCODE_FAILURE; + } + if (!strcmp(argv[0], "list")) + return sharedFolder_list(argc - 1, argv + 1); +# ifdef RT_OS_OS2 + if (!strcmp(argv[0], "use")) + return sharedFolder_use(argc - 1, argv + 1); + if (!strcmp(argv[0], "unuse")) + return sharedFolder_unuse(argc - 1, argv + 1); +# endif + + usage(GUEST_SHAREDFOLDERS); + return RTEXITCODE_FAILURE; +} + +#endif +#if !defined(VBOX_CONTROL_TEST) + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: writecoredump} + */ +static DECLCALLBACK(RTEXITCODE) handleWriteCoreDump(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); + int rc = VbglR3WriteCoreDump(); + if (RT_SUCCESS(rc)) + { + RTPrintf("Guest core dump successful.\n"); + return RTEXITCODE_SUCCESS; + } + else + { + VBoxControlError("Error while taking guest core dump. rc=%Rrc\n", rc); + return RTEXITCODE_FAILURE; + } +} + +#endif +#ifdef VBOX_WITH_DPC_LATENCY_CHECKER + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: help} + */ +static DECLCALLBACK(RTEXITCODE) handleDpc(int argc, char *argv[]) +{ + RT_NOREF(argc, argv); + int rc = VERR_NOT_IMPLEMENTED; +# ifndef VBOX_CONTROL_TEST + for (int i = 0; i < 30; i++) + { + VBGLREQHDR Req; + VBGLREQHDR_INIT(&Req, DPC_LATENCY_CHECKER); + rc = vbglR3DoIOCtl(VBGL_IOCTL_DPC_LATENCY_CHECKER, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + RTPrintf("%d\n", i); + else + break; + } +# endif + if (RT_FAILURE(rc)) + return VBoxControlError("Error. rc=%Rrc\n", rc); + RTPrintf("Samples collection completed.\n"); + return RTEXITCODE_SUCCESS; +} +#endif /* VBOX_WITH_DPC_LATENCY_CHECKER */ + + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: writelog} + */ +static DECLCALLBACK(RTEXITCODE) handleWriteLog(int argc, char *argv[]) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--no-newline", 'n', RTGETOPT_REQ_NOTHING }, + }; + bool fNoNewline = false; + + RTGETOPTSTATE GetOptState; + int rc = RTGetOptInit(&GetOptState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), + 0 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + if (RT_SUCCESS(rc)) + { + RTGETOPTUNION ValueUnion; + int ch; + while ((ch = RTGetOpt(&GetOptState, &ValueUnion)) != 0) + { + switch (ch) + { + case VINF_GETOPT_NOT_OPTION: + { + size_t cch = strlen(ValueUnion.psz); + if ( fNoNewline + || (cch > 0 && ValueUnion.psz[cch - 1] == '\n') ) + rc = VbglR3WriteLog(ValueUnion.psz, cch); + else + { + char *pszDup = (char *)RTMemDupEx(ValueUnion.psz, cch, 2); + if (RT_SUCCESS(rc)) + { + pszDup[cch++] = '\n'; + pszDup[cch] = '\0'; + rc = VbglR3WriteLog(pszDup, cch); + RTMemFree(pszDup); + } + else + rc = VERR_NO_MEMORY; + } + if (RT_FAILURE(rc)) + return VBoxControlError("VbglR3WriteLog: %Rrc", rc); + break; + } + + case 'n': + fNoNewline = true; + break; + + case 'h': return usage(WRITE_LOG); + case 'V': return printVersion(); + default: + return VBoxCtrlGetOptError(ch, &ValueUnion); + } + } + } + else + return VBoxControlError("RTGetOptInit: %Rrc", rc); + return RTEXITCODE_SUCCESS; +} + + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: takesnapshot} + */ +static DECLCALLBACK(RTEXITCODE) handleTakeSnapshot(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); //VbglR3VmTakeSnapshot(argv[0], argv[1]); + return VBoxControlError("not implemented"); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: savestate} + */ +static DECLCALLBACK(RTEXITCODE) handleSaveState(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); //VbglR3VmSaveState(); + return VBoxControlError("not implemented"); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: suspend|pause} + */ +static DECLCALLBACK(RTEXITCODE) handleSuspend(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); //VbglR3VmSuspend(); + return VBoxControlError("not implemented"); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: poweroff|powerdown} + */ +static DECLCALLBACK(RTEXITCODE) handlePowerOff(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); //VbglR3VmPowerOff(); + return VBoxControlError("not implemented"); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: version} + */ +static DECLCALLBACK(RTEXITCODE) handleVersion(int argc, char *argv[]) +{ + RT_NOREF1(argv); + if (argc) + return VBoxControlSyntaxError("getversion does not take any arguments"); + return printVersion(); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: help} + */ +static DECLCALLBACK(RTEXITCODE) handleHelp(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); /* ignore arguments for now. */ + usage(); + return RTEXITCODE_SUCCESS; +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: ls} + */ +static DECLCALLBACK(RTEXITCODE) handleLs(int argc, char *argv[]) +{ + return RTFsCmdLs(argc + 1, argv - 1); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: tar} + */ +static DECLCALLBACK(RTEXITCODE) handleTar(int argc, char *argv[]) +{ + return RTZipTarCmd(argc + 1, argv - 1); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: tar} + */ +static DECLCALLBACK(RTEXITCODE) handleGzip(int argc, char *argv[]) +{ + return RTZipGzipCmd(argc + 1, argv - 1); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: unzip} + */ +static DECLCALLBACK(RTEXITCODE) handleUnzip(int argc, char *argv[]) +{ + return RTZipUnzipCmd(argc + 1, argv - 1); +} + + +/** command handler type */ +typedef DECLCALLBACKTYPE(RTEXITCODE, FNVBOXCTRLCMDHANDLER,(int argc, char *argv[])); +typedef FNVBOXCTRLCMDHANDLER *PFNVBOXCTRLCMDHANDLER; + +/** The table of all registered command handlers. */ +struct COMMANDHANDLER +{ + const char *pszCommand; + PFNVBOXCTRLCMDHANDLER pfnHandler; + bool fNeedDevice; +} g_aCommandHandlers[] = +{ +#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST) + { "getvideoacceleration", handleGetVideoAcceleration, true }, + { "setvideoacceleration", handleSetVideoAcceleration, true }, + { "videoflags", handleVideoFlags, true }, + { "listcustommodes", handleListCustomModes, true }, + { "addcustommode", handleAddCustomMode, true }, + { "removecustommode", handleRemoveCustomMode, true }, + { "setvideomode", handleSetVideoMode, true }, +#endif +#ifdef VBOX_WITH_GUEST_PROPS + { "guestproperty", handleGuestProperty, true }, +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS + { "sharedfolder", handleSharedFolder, true }, +#endif +#if !defined(VBOX_CONTROL_TEST) + { "writecoredump", handleWriteCoreDump, true }, +#endif +#ifdef VBOX_WITH_DPC_LATENCY_CHECKER + { "dpc", handleDpc, true }, +#endif + { "writelog", handleWriteLog, true }, + { "takesnapshot", handleTakeSnapshot, true }, + { "savestate", handleSaveState, true }, + { "suspend", handleSuspend, true }, + { "pause", handleSuspend, true }, + { "poweroff", handlePowerOff, true }, + { "powerdown", handlePowerOff, true }, + { "getversion", handleVersion, false }, + { "version", handleVersion, false }, + { "help", handleHelp, false }, + /* Hany tricks that doesn't cost much space: */ + { "gzip", handleGzip, false }, + { "ls", handleLs, false }, + { "tar", handleTar, false }, + { "unzip", handleUnzip, false }, +}; + +/** Main function */ +int main(int argc, char **argv) +{ + /** The application's global return code */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + /** An IPRT return code for local use */ + int rrc = VINF_SUCCESS; + /** The index of the command line argument we are currently processing */ + int iArg = 1; + /** Should we show the logo text? */ + bool fShowLogo = true; + /** Should we print the usage after the logo? For the -help switch. */ + bool fDoHelp = false; + /** Will we be executing a command or just printing information? */ + bool fOnlyInfo = false; + + rrc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rrc)) + return RTMsgInitFailure(rrc); + + /* + * Start by handling command line switches + */ + /** @todo RTGetOpt conversion of the whole file. */ + bool done = false; /**< Are we finished with handling switches? */ + while (!done && (iArg < argc)) + { + if ( !strcmp(argv[iArg], "-V") + || !strcmp(argv[iArg], "-v") + || !strcmp(argv[iArg], "--version") + || !strcmp(argv[iArg], "-version") + ) + { + /* Print version number, and do nothing else. */ + printVersion(); + fOnlyInfo = true; + fShowLogo = false; + done = true; + } + else if ( !strcmp(argv[iArg], "-nologo") + || !strcmp(argv[iArg], "--nologo")) + fShowLogo = false; + else if ( !strcmp(argv[iArg], "-help") + || !strcmp(argv[iArg], "--help")) + { + fOnlyInfo = true; + fDoHelp = true; + done = true; + } + else + /* We have found an argument which isn't a switch. Exit to the + * command processing bit. */ + done = true; + if (!done) + ++iArg; + } + + /* + * Find the application name, show our logo if the user hasn't suppressed it, + * and show the usage if the user asked us to + */ + g_pszProgName = RTPathFilename(argv[0]); + if (fShowLogo) + RTPrintf(VBOX_PRODUCT " Guest Additions Command Line Management Interface Version " + VBOX_VERSION_STRING "\n" + "Copyright (C) 2008-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n"); + if (fDoHelp) + usage(); + + /* + * Now look for an actual command in the argument list and handle it. + */ + if (!fOnlyInfo && rcExit == RTEXITCODE_SUCCESS) + { + if (argc > iArg) + { + /* + * Try locate the command and execute it, complain if not found. + */ + unsigned i; + for (i = 0; i < RT_ELEMENTS(g_aCommandHandlers); i++) + if (!strcmp(argv[iArg], g_aCommandHandlers[i].pszCommand)) + { + if (g_aCommandHandlers[i].fNeedDevice) + { + rrc = VbglR3Init(); + if (RT_FAILURE(rrc)) + { + VBoxControlError("Could not contact the host system. Make sure that you are running this\n" + "application inside a VirtualBox guest system, and that you have sufficient\n" + "user permissions.\n"); + rcExit = RTEXITCODE_FAILURE; + } + } + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = g_aCommandHandlers[i].pfnHandler(argc - iArg - 1, argv + iArg + 1); + break; + } + if (i >= RT_ELEMENTS(g_aCommandHandlers)) + { + usage(); + rcExit = RTEXITCODE_SYNTAX; + } + } + else + { + /* The user didn't specify a command. */ + usage(); + rcExit = RTEXITCODE_SYNTAX; + } + } + + /* + * And exit, returning the status + */ + return rcExit; +} + diff --git a/src/VBox/Additions/common/VBoxControl/VBoxControl.rc b/src/VBox/Additions/common/VBoxControl/VBoxControl.rc new file mode 100644 index 00000000..9a454882 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/VBoxControl.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxControl.rc $ */ +/** @file + * VBoxControl - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "VirtualBox Guest Additions Utility\0" + VALUE "InternalName", "VBoxControl\0" + VALUE "OriginalFilename", "VBoxControl.exe\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_GA_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/Additions/common/VBoxControl/testcase/Makefile.kmk b/src/VBox/Additions/common/VBoxControl/testcase/Makefile.kmk new file mode 100644 index 00000000..bee6c0a0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/testcase/Makefile.kmk @@ -0,0 +1,48 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the VBoxControl testcases. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) + + # + # Dummy CLI testcase. + # + PROGRAMS += tstVBoxControl + tstVBoxControl_TEMPLATE = VBoxR3TstExe + tstVBoxControl_DEFS = VBOX_CONTROL_TEST + tstVBoxControl_SOURCES = tstVBoxControl.cpp ../VBoxControl.cpp + tstVBoxControl_LIBS = $(LIB_RUNTIME) + tstVBoxControl_DEFS += \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS VBOX_WITH_HGCM,) + +endif # VBOX_WITH_TESTCASES + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/VBoxControl/testcase/tstVBoxControl.cpp b/src/VBox/Additions/common/VBoxControl/testcase/tstVBoxControl.cpp new file mode 100644 index 00000000..6211813e --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/testcase/tstVBoxControl.cpp @@ -0,0 +1,226 @@ +/* $Id: tstVBoxControl.cpp $ */ +/** @file + * VBoxControl - Guest Additions Command Line Management Interface, test case + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cpp/autores.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <VBox/log.h> +#include <VBox/version.h> +#include <VBox/VBoxGuestLib.h> +#ifdef VBOX_WITH_GUEST_PROPS +# include <VBox/HostServices/GuestPropertySvc.h> +#endif + +VBGLR3DECL(int) VbglR3Init(void) +{ + RTPrintf("Initialising guest library...\n"); + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropConnect(HGCMCLIENTID *pidClient) +{ + AssertPtrReturn(pidClient, VERR_INVALID_POINTER); + RTPrintf("Connect to guest property service...\n"); + *pidClient = 1; + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropDisconnect(HGCMCLIENTID idClient) +{ + RTPrintf("Disconnect client %d from guest property service...\n", idClient); + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropWrite(HGCMCLIENTID idClient, + const char *pszName, + const char *pszValue, + const char *pszFlags) +{ + RTPrintf("Called SET_PROP, client %d, name %s, value %s, flags %s...\n", + idClient, pszName, pszValue, pszFlags); + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropWriteValue(HGCMCLIENTID idClient, + const char *pszName, + const char *pszValue) +{ + RTPrintf("Called SET_PROP_VALUE, client %d, name %s, value %s...\n", + idClient, pszName, pszValue); + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_GUEST_PROPS +VBGLR3DECL(int) VbglR3GuestPropRead(HGCMCLIENTID idClient, + const char *pszName, + void *pvBuf, + uint32_t cbBuf, + char **ppszValue, + uint64_t *pu64Timestamp, + char **ppszFlags, + uint32_t *pcbBufActual) +{ + RT_NOREF2(pvBuf, cbBuf); + RTPrintf("Called GET_PROP, client %d, name %s...\n", + idClient, pszName); + static char szValue[] = "Value"; + static char szFlags[] = "TRANSIENT"; + if (ppszValue) + *ppszValue = szValue; + if (pu64Timestamp) + *pu64Timestamp = 12345; + if (ppszFlags) + *ppszFlags = szFlags; + if (pcbBufActual) + *pcbBufActual = 256; + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropDelete(HGCMCLIENTID idClient, + const char *pszName) +{ + RTPrintf("Called DEL_PROP, client %d, name %s...\n", + idClient, pszName); + return VINF_SUCCESS; +} + +struct VBGLR3GUESTPROPENUM +{ + uint32_t u32; +}; + +VBGLR3DECL(int) VbglR3GuestPropEnum(HGCMCLIENTID idClient, + char const * const *ppaszPatterns, + uint32_t cPatterns, + PVBGLR3GUESTPROPENUM *ppHandle, + char const **ppszName, + char const **ppszValue, + uint64_t *pu64Timestamp, + char const **ppszFlags) +{ + RT_NOREF2(ppaszPatterns, cPatterns); + RTPrintf("Called ENUM_PROPS, client %d...\n", idClient); + AssertPtrReturn(ppHandle, VERR_INVALID_POINTER); + static VBGLR3GUESTPROPENUM Handle = { 0 }; + static char szName[] = "Name"; + static char szValue[] = "Value"; + static char szFlags[] = "TRANSIENT"; + *ppHandle = &Handle; + if (ppszName) + *ppszName = szName; + if (ppszValue) + *ppszValue = szValue; + if (pu64Timestamp) + *pu64Timestamp = 12345; + if (ppszFlags) + *ppszFlags = szFlags; + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropEnumNext(PVBGLR3GUESTPROPENUM pHandle, + char const **ppszName, + char const **ppszValue, + uint64_t *pu64Timestamp, + char const **ppszFlags) +{ + RT_NOREF1(pHandle); + RTPrintf("Called enumerate next...\n"); + AssertReturn(RT_VALID_PTR(ppszName) || RT_VALID_PTR(ppszValue) || RT_VALID_PTR(ppszFlags), + VERR_INVALID_POINTER); + if (ppszName) + *ppszName = NULL; + if (ppszValue) + *ppszValue = NULL; + if (pu64Timestamp) + *pu64Timestamp = 0; + if (ppszFlags) + *ppszFlags = NULL; + return VINF_SUCCESS; +} + +VBGLR3DECL(void) VbglR3GuestPropEnumFree(PVBGLR3GUESTPROPENUM pHandle) +{ + RT_NOREF1(pHandle); + RTPrintf("Called enumerate free...\n"); +} + +VBGLR3DECL(int) VbglR3GuestPropWait(HGCMCLIENTID idClient, + const char *pszPatterns, + void *pvBuf, + uint32_t cbBuf, + uint64_t u64Timestamp, + uint32_t u32Timeout, + char ** ppszName, + char **ppszValue, + uint64_t *pu64Timestamp, + char **ppszFlags, + uint32_t *pcbBufActual, + bool *pfWasDeleted) +{ + RT_NOREF2(pvBuf, cbBuf); + if (u32Timeout == RT_INDEFINITE_WAIT) + RTPrintf("Called GET_NOTIFICATION, client %d, patterns %s, timestamp %llu,\n" + " timeout RT_INDEFINITE_WAIT...\n", + idClient, pszPatterns, u64Timestamp); + else + RTPrintf("Called GET_NOTIFICATION, client %d, patterns %s, timestamp %llu,\n" + " timeout %u...\n", + idClient, pszPatterns, u64Timestamp, u32Timeout); + static char szName[] = "Name"; + static char szValue[] = "Value"; + static char szFlags[] = "TRANSIENT"; + if (ppszName) + *ppszName = szName; + if (ppszValue) + *ppszValue = szValue; + if (pu64Timestamp) + *pu64Timestamp = 12345; + if (ppszFlags) + *ppszFlags = szFlags; + if (pcbBufActual) + *pcbBufActual = 256; + if (pfWasDeleted) + *pfWasDeleted = false; + return VINF_SUCCESS; +} + +#endif + +VBGLR3DECL(int) VbglR3WriteLog(const char *pch, size_t cch) +{ + NOREF(pch); NOREF(cch); + return VINF_SUCCESS; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/.scm-settings b/src/VBox/Additions/common/VBoxGuest/.scm-settings new file mode 100644 index 00000000..70bef493 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/.scm-settings @@ -0,0 +1,65 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for VBoxGuest +# + +# +# Copyright (C) 2010-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# Like the host drivers, this is dual licensed. +--license-ose-dual + +/VBoxGuest-solaris.conf: --treat-as .sh + +/netbsd/vboxguest.ioconf: --treat-as .sh --no-convert-tabs --no-convert-eol + +# a stub for a file that is normally autogenerated +/netbsd/locators.h: --external-copyright --no-fix-header-guards + +# And the R0 library is MIT to make two-way code exchange with the Linux kernel +# easier. +/lib/VBoxGuestR0LibCrOgl.cpp: --license-mit +/lib/VBoxGuestR0LibGenericRequest.cpp: --license-mit +/lib/VBoxGuestR0LibHGCM.cpp: --license-mit +/lib/VBoxGuestR0LibHGCMInternal.cpp: --license-mit +/lib/VBoxGuestR0LibIdc-os2.cpp: --license-mit +/lib/VBoxGuestR0LibIdc-solaris.cpp: --license-mit +/lib/VBoxGuestR0LibIdc-unix.cpp: --license-mit +/lib/VBoxGuestR0LibIdc-win.cpp: --license-mit +/lib/VBoxGuestR0LibIdc.cpp: --license-mit +/lib/VBoxGuestR0LibInit.cpp: --license-mit +/lib/VBoxGuestR0LibInternal.h: --license-mit +/lib/VBoxGuestR0LibMouse.cpp: --license-mit +/lib/VBoxGuestR0LibPhysHeap.cpp: --license-mit +/lib/VBoxGuestR0LibSharedFolders.c: --license-mit +/lib/VBoxGuestR0LibVMMDev.cpp: --license-mit +/lib/VbglR0CanUsePhysPageList.cpp: --license-mit + diff --git a/src/VBox/Additions/common/VBoxGuest/Makefile.kmk b/src/VBox/Additions/common/VBoxGuest/Makefile.kmk new file mode 100644 index 00000000..f619d04a --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/Makefile.kmk @@ -0,0 +1,287 @@ +# $Id: Makefile.kmk $ +## @file +# Makefile for the Cross Platform Guest Additions Driver. +# + +# +# Copyright (C) 2007-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile. +include $(PATH_SUB_CURRENT)/lib/Makefile.kmk + + +if defined(VBOX_WITH_ADDITION_DRIVERS) && "$(intersects $(KBUILD_TARGET), darwin freebsd haiku netbsd os2 solaris win)" != "" + # + # VBoxGuest - The Guest Additions Driver. + # + SYSMODS += VBoxGuest + VBoxGuest_TEMPLATE = VBoxGuestR0Drv + VBoxGuest_NAME.freebsd = vboxguest + VBoxGuest_NAME.haiku = vboxguest + VBoxGuest_NAME.netbsd = vboxguest + VBoxGuest_NAME.solaris = vboxguest + VBoxGuest_INST.darwin = $(INST_ADDITIONS)VBoxGuest.kext/Contents/MacOS/ + if defined(VBOX_SIGNING_MODE) && defined(VBOX_SIGN_ADDITIONS) # See Additions/WINNT/Makefile.kmk? + VBoxGuest_INSTTYPE.win = none + VBoxGuest_DEBUG_INSTTYPE.win = both + endif + VBoxGuest_DEFS.haiku = VBOX_SVN_REV=$(VBOX_SVN_REV) _KERNEL_MODE=1 + VBoxGuest_DEFS.solaris = VBOX_SVN_REV=$(VBOX_SVN_REV) + VBoxGuest_DEFS.win = VBOX_GUESTDRV_WITH_RELEASE_LOGGER + VBoxGuest_DEFS.win.x86 = TARGET_NT4 TARGET_NT3 RT_WITHOUT_NOCRT_WRAPPERS + VBoxGuest_DEFS.darwin = VBOX_GUESTDRV_WITH_RELEASE_LOGGER + ifeq ($(KBUILD_TYPE),release) + # Allow stopping/removing the driver without a reboot + # in debug mode; this is very useful for testing the shutdown stuff! + VBoxGuest_DEFS.win += VBOX_REBOOT_ON_UNINSTALL + endif + ifdef VBOX_WITH_GUEST_BUGCHECK_DETECTION + VBoxGuest_DEFS.win += VBOX_WITH_GUEST_BUGCHECK_DETECTION + endif + #VBoxGuest_DEFS.win += LOG_ENABLED LOG_TO_BACKDOOR + VBoxGuest_DEFS.win += \ + $(if $(VBOX_WITH_DPC_LATENCY_CHECKER),VBOX_WITH_DPC_LATENCY_CHECKER,) + VBoxGuest_DEPS.solaris += $(VBOX_SVN_REV_KMK) + VBoxGuest_DEPS.haiku += $(VBOX_SVN_REV_HEADER) + VBoxGuest_DEPS.freebsd += $(VBOX_SVN_REV_HEADER) + VBoxGuest_DEPS.netbsd += $(VBOX_SVN_REV_HEADER) + VBoxGuest_DEPS.darwin += $(VBOX_SVN_REV_HEADER) + VBoxGuest_DEFS = VBGL_VBOXGUEST VBOX_WITH_HGCM + VBoxGuest_INCS = . + VBoxGuest_INCS.freebsd = $(VBoxGuest_0_OUTDIR) $(PATH_STAGE)/gen-sys-hdrs + VBoxGuest_INCS.netbsd = $(VBoxGuest_0_OUTDIR) netbsd + ifeq ($(KBUILD_HOST),solaris) + VBoxGuest_LDFLAGS.solaris += -N misc/ctf + else + VBoxGuest_SOURCES.solaris = solaris/deps.asm + VBoxGuest_solaris/deps.asm_ASFLAGS = -f bin -g null + endif + ifneq ($(KBUILD_TARGET),os2) + ifeq ($(KBUILD_TARGET),win) + VBoxGuest_LDFLAGS.x86 = -Entry:DriverEntry@8 + VBoxGuest_LDFLAGS.amd64 = -Entry:DriverEntry + ifeq ($(KBUILD_TARGET_ARCH),x86) + VBoxGuest_SDKS = ReorderCompilerIncs $(VBOX_WINDDK_GST_NT4) + VBoxGuest_LIBS = \ + $(VBOX_LIB_VBGL_R0BASE) \ + $(VBOX_LIB_IPRT_GUEST_R0) \ + $(PATH_SDK_$(VBOX_WINDDK_GST_NT4)_LIB)/exsup.lib \ + $(PATH_SDK_$(VBOX_WINDDK_GST_NT4)_LIB)/int64.lib \ + $(PATH_SDK_$(VBOX_WINDDK_GST)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK_GST)_LIB)/hal.lib + ifneq ($(VBOX_VCC_CC_GUARD_CF),) + VBoxGuest_LIBS += \ + $(PATH_SDK_$(VBOX_WINDDK_GST)_LIB)/BufferOverflowK.lib + endif + else + VBoxGuest_LIBS = \ + $(PATH_SDK_$(VBOX_WINDDK_GST)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK_GST)_LIB)/hal.lib + endif + VBoxGuest_USES.win += vboximportchecker + VBoxGuest_VBOX_IMPORT_CHECKER.win.x86 = nt31/r0 + VBoxGuest_VBOX_IMPORT_CHECKER.win.amd64 = xp64/r0 + endif # win + ifn1of ($(KBUILD_TARGET), linux freebsd netbsd solaris haiku) + VBoxGuest_SOURCES = VBoxGuest-$(KBUILD_TARGET).cpp + else + VBoxGuest_SOURCES = VBoxGuest-$(KBUILD_TARGET).c + VBoxGuest_$(KBUILD_TARGET).c_DEPS = $(VBOX_SVN_REV_HEADER) + ifeq ($(KBUILD_TARGET),freebsd) + VBoxGuest-$(KBUILD_TARGET).c_CFLAGS = -Wno-sign-compare # /usr/src/sys/sys/vmmeter.h: In function 'vm_paging_needed' + endif + endif + VBoxGuest_SOURCES += \ + VBoxGuest.cpp + VBoxGuest_SOURCES.win += \ + win/VBoxGuest.rc + VBoxGuest_SOURCES.win.x86 += \ + ../../../Runtime/common/string/strcmp.asm \ + ../../../Runtime/common/string/strchr.asm \ + ../../../Runtime/r0drv/nt/nt3fakes-r0drv-nt.cpp \ + ../../../Runtime/r0drv/nt/nt3fakesA-r0drv-nt.asm + VBoxGuest_LIBS += \ + $(VBOX_LIB_VBGL_R0BASE) \ + $(VBOX_LIB_IPRT_GUEST_R0) + VBoxGuest_ORDERDEPS.freebsd = \ + $(PATH_STAGE)/gen-sys-hdrs/pci_if.h \ + $(PATH_STAGE)/gen-sys-hdrs/bus_if.h \ + $(PATH_STAGE)/gen-sys-hdrs/device_if.h + ifeq ($(KBUILD_TARGET),haiku) + # Haiku drivers cannot export symbols for other drivers, but modules can. + # Therefore vboxguest is a module containing the ring-0 guest lib, and vboxdev/vboxsf + # use this module to access the guest lib + SYSMODS += VBoxDev + VBoxDev_TEMPLATE = VBoxGuestR0Drv + VBoxDev_NAME = vboxdev + VBoxDev_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) _KERNEL_MODE=1 VBGL_VBOXGUEST VBOX_WITH_HGCM IN_RING0 + VBoxDev_SOURCES = VBoxDev-haiku.c VBoxGuest-haiku-stubs.c + endif + else # OS/2: + # The library order is crucial, so a bit of trickery is necessary. + # A library is used to make sure that VBoxGuestA-os2.asm is first in the link. (temporary hack?) + VBoxGuest_SOURCES = \ + VBoxGuestA-os2.asm + ifdef VBOX_USE_WATCOM_FOR_OS2 + VBoxGuest_LIBS = \ + $(VBoxGuestLibOs2Hack_1_TARGET) \ + $(VBOX_LIB_VBGL_R0BASE) \ + $(VBOX_LIB_IPRT_GUEST_R0) \ + $(PATH_IGCC)/lib/libend.lib + else + VBoxGuest_SOURCES += \ + VBoxGuest-os2.def + #VBoxGuest_LDFLAGS = -s -t -v + VBoxGuest_LIBS = \ + $(VBoxGuestLibOs2Hack_1_TARGET) \ + $(VBOX_LIB_VBGL_R0BASE) \ + $(VBOX_LIB_IPRT_GUEST_R0) \ + $(VBOX_GCC_LIBGCC) \ + end + endif + ## When debugging init with kDrvTest: + #VBoxGuest_NAME = VBoxGst + + # See above. + LIBRARIES += VBoxGuestLibOs2Hack + VBoxGuestLibOs2Hack_TEMPLATE = VBoxGuestR0DrvLib + VBoxGuestLibOs2Hack_INSTTYPE = none + VBoxGuestLibOs2Hack_DEFS = $(VBoxGuest_DEFS) + VBoxGuestLibOs2Hack_INCS = \ + . \ + $(PATH_ROOT)/src/VBox/Runtime/include # for the os2ddk + VBoxGuestLibOs2Hack_SOURCES = \ + VBoxGuest-os2.cpp \ + VBoxGuest.cpp + endif # OS/2 + + VBoxGuest.cpp_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) +endif # enabled + + +if defined(VBOX_WITH_ADDITION_DRIVERS) && "$(KBUILD_TARGET)" == "darwin" + # Files necessary to make a darwin kernel extension bundle. + INSTALLS += VBoxGuest.kext + VBoxGuest.kext_INST = $(INST_ADDITIONS)/VBoxGuest.kext/Contents/ + VBoxGuest.kext_SOURCES = $(VBoxGuest.kext_0_OUTDIR)/Contents/Info.plist + VBoxGuest.kext_CLEAN = $(VBoxGuest.kext_0_OUTDIR)/Contents/Info.plist + VBoxGuest.kext_BLDDIRS = $(VBoxGuest.kext_0_OUTDIR)/Contents/ + + $$(VBoxGuest.kext_0_OUTDIR)/Contents/Info.plist: \ + $(PATH_SUB_CURRENT)/darwin/Info.plist \ + $(VBOX_VERSION_MK) | $$(dir $$@) + $(call MSG_GENERATE,VBoxGuest,$@,$<) + $(QUIET)$(RM) -f $@ + $(QUIET)$(SED) \ + -e 's+@VBOX_VERSION_STRING@+$(VBOX_VERSION_STRING)+g' \ + -e 's+@VBOX_VERSION_MAJOR@+$(VBOX_VERSION_MAJOR)+g' \ + -e 's+@VBOX_VERSION_MINOR@+$(VBOX_VERSION_MINOR)+g' \ + -e 's+@VBOX_VERSION_BUILD@+$(VBOX_VERSION_BUILD)+g' \ + -e 's+@VBOX_VENDOR@+$(VBOX_VENDOR)+g' \ + -e 's+@VBOX_PRODUCT@+$(VBOX_PRODUCT)+g' \ + -e 's+@VBOX_C_YEAR@+$(VBOX_C_YEAR)+g' \ + --output $@ \ + $< + + $(evalcall2 VBOX_TEST_SIGN_KEXT,VBoxGuest) +endif # darwin + + +ifeq ($(KBUILD_TARGET),linux) + # + # Install the source files and script(s). + # + include $(PATH_SUB_CURRENT)/linux/files_vboxguest + # sources and stuff. + INSTALLS += vboxguest-src + vboxguest-src_INST = $(INST_ADDITIONS)src/vboxguest/ + vboxguest-src_MODE = a+r,u+w + vboxguest-src_SOURCES = $(subst ",,$(FILES_VBOXGUEST_NOBIN)) + + INSTALLS += vboxguest-scripts + vboxguest-scripts_INST = $(INST_ADDITIONS)src/ + vboxguest-scripts_MODE = a+rx,u+w + vboxguest-scripts_SOURCES = ../../../HostDrivers/linux/build_in_tmp + + # scripts. + INSTALLS += vboxguest-sh + vboxguest-sh_INST = $(INST_ADDITIONS)src/vboxguest/ + vboxguest-sh_MODE = a+rx,u+w + vboxguest-sh_SOURCES = $(subst ",,$(FILES_VBOXGUEST_BIN)) + + # + # Build test for the Guest Additions kernel module (kmk check). + # + $(evalcall2 VBOX_LINUX_KMOD_TEST_BUILD_RULE_FN,vboxguest-src,,save_symvers) +endif # Linux + +ifeq ($(KBUILD_TARGET),freebsd) + # + # Install the source files and script(s). + # + include $(PATH_SUB_CURRENT)/freebsd/files_vboxguest + # sources and stuff. + INSTALLS += vboxguest-src + vboxguest-src_INST = $(INST_ADDITIONS)src/vboxguest/ + vboxguest-src_MODE = a+r,u+w + vboxguest-src_SOURCES = $(subst ",,$(FILES_VBOXGUEST_NOBIN)) + +endif # FreeBSD + +ifeq ($(KBUILD_TARGET),win) + # + # VBoxGuestInst - The installer. + # + PROGRAMS.win.x86 += VBoxGuestInstNT + VBoxGuestInstNT_TEMPLATE = VBoxGuestR3Exe + ifndef VBOX_WITH_NOCRT_STATIC + VBoxGuestInstNT_LDFLAGS := -Include:_vcc100_kernel32_fakes_asm # Temporary kludge to deal with some link order issue. + endif + VBoxGuestInstNT_INCS = ../../WINNT/include + VBoxGuestInstNT_SOURCES = win/VBoxGuestInst.cpp + VBoxGuestInstNT_USES = vboximportchecker + VBoxGuestInstNT_VBOX_IMPORT_CHECKER.win.x86 = nt31 +endif + + +# +# Helper script. +# +INSTALLS.solaris += VBoxGuestLoad +VBoxGuestLoad_TEMPLATE = VBoxGuestR0Drv +VBoxGuestLoad_EXEC_SOURCES = solaris/load.sh + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxDev-haiku.c b/src/VBox/Additions/common/VBoxGuest/VBoxDev-haiku.c new file mode 100644 index 00000000..1fb8d1f0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxDev-haiku.c @@ -0,0 +1,446 @@ +/* $Id: VBoxDev-haiku.c $ */ +/** @file + * VBoxGuest kernel driver, Haiku Guest Additions, implementation. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/* + * This code is based on: + * + * VirtualBox Guest Additions for Haiku. + * Copyright (c) 2011 Mike Smith <mike@scgtrp.net> + * François Revol <revol@free.fr> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/param.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <OS.h> +#include <Drivers.h> +#include <KernelExport.h> +#include <PCI.h> + +#include "VBoxGuest-haiku.h" +#include "VBoxGuestInternal.h" +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/process.h> +#include <iprt/mem.h> +#include <iprt/asm.h> + +#define DRIVER_NAME "vboxdev" +#define DEVICE_NAME "misc/vboxguest" +#define MODULE_NAME "generic/vboxguest" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +int32 api_version = B_CUR_DRIVER_API_VERSION; + + +/** + * Driver open hook. + * + * @param name The name of the device as returned by publish_devices. + * @param flags Open flags. + * @param cookie Where to store the session pointer. + * + * @return Haiku status code. + */ +static status_t vgdrvHaikuOpen(const char *name, uint32 flags, void **cookie) +{ + int rc; + PVBOXGUESTSESSION pSession; + + LogFlow((DRIVER_NAME ":vgdrvHaikuOpen\n")); + + /* + * Create a new session. + */ + rc = VGDrvCommonCreateUserSession(&g_DevExt, VMMDEV_REQUESTOR_USERMODE, &pSession); + if (RT_SUCCESS(rc)) + { + Log((DRIVER_NAME ":vgdrvHaikuOpen success: g_DevExt=%p pSession=%p rc=%d pid=%d\n",&g_DevExt, pSession, rc,(int)RTProcSelf())); + ASMAtomicIncU32(&cUsers); + *cookie = pSession; + return B_OK; + } + + LogRel((DRIVER_NAME ":vgdrvHaikuOpen: failed. rc=%d\n", rc)); + return RTErrConvertToErrno(rc); +} + + +/** + * Driver close hook. + * @param cookie The session. + * + * @return Haiku status code. + */ +static status_t vgdrvHaikuClose(void *cookie) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie; + Log(("vgdrvHaikuClose: pSession=%p\n", pSession)); + + /** @todo r=ramshankar: should we really be using the session spinlock here? */ + RTSpinlockAcquire(g_DevExt.SessionSpinlock); + + /** @todo we don't know if it belongs to this session!! */ + if (sState.selectSync) + { + //dprintf(DRIVER_NAME "close: unblocking select %p %x\n", sState.selectSync, sState.selectEvent); + notify_select_event(sState.selectSync, sState.selectEvent); + sState.selectEvent = (uint8_t)0; + sState.selectRef = (uint32_t)0; + sState.selectSync = (void *)NULL; + } + + RTSpinlockRelease(g_DevExt.SessionSpinlock); + return B_OK; +} + + +/** + * Driver free hook. + * @param cookie The session. + * + * @return Haiku status code. + */ +static status_t vgdrvHaikuFree(void *cookie) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie; + Log(("vgdrvHaikuFree: pSession=%p\n", pSession)); + + /* + * Close the session if it's still hanging on to the device... + */ + if (RT_VALID_PTR(pSession)) + { + VGDrvCommonCloseSession(&g_DevExt, pSession); + ASMAtomicDecU32(&cUsers); + } + else + Log(("vgdrvHaikuFree: si_drv1=%p!\n", pSession)); + return B_OK; +} + + +/** + * Driver IOCtl entry. + * @param cookie The session. + * @param op The operation to perform. + * @param data The data associated with the operation. + * @param len Size of the data in bytes. + * + * @return Haiku status code. + */ +static status_t vgdrvHaikuIOCtl(void *cookie, uint32 op, void *data, size_t len) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie; + int rc; + Log(("vgdrvHaikuIOCtl: cookie=%p op=0x%08x data=%p len=%lu)\n", cookie, op, data, len)); + + /* + * Validate the input. + */ + if (RT_UNLIKELY(!RT_VALID_PTR(pSession))) + return EINVAL; + + /* + * Validate the request wrapper. + */ +#if 0 + if (IOCPARM_LEN(ulCmd) != sizeof(VBGLBIGREQ)) + { + Log((DRIVER_NAME ": vgdrvHaikuIOCtl: bad request %lu size=%lu expected=%d\n", ulCmd, IOCPARM_LEN(ulCmd), + sizeof(VBGLBIGREQ))); + return ENOTTY; + } +#endif + + if (RT_UNLIKELY(len > _1M * 16)) + { + dprintf(DRIVER_NAME ": vgdrvHaikuIOCtl: bad size %#x; pArg=%p Cmd=%lu.\n", (unsigned)len, data, op); + return EINVAL; + } + + /* + * Read the request. + */ + void *pvBuf = NULL; + if (RT_LIKELY(len > 0)) + { + pvBuf = RTMemTmpAlloc(len); + if (RT_UNLIKELY(!pvBuf)) + { + LogRel((DRIVER_NAME ":vgdrvHaikuIOCtl: RTMemTmpAlloc failed to alloc %d bytes.\n", len)); + return ENOMEM; + } + + /** @todo r=ramshankar: replace with RTR0MemUserCopyFrom() */ + rc = user_memcpy(pvBuf, data, len); + if (RT_UNLIKELY(rc < 0)) + { + RTMemTmpFree(pvBuf); + LogRel((DRIVER_NAME ":vgdrvHaikuIOCtl: user_memcpy failed; pvBuf=%p data=%p op=%d. rc=%d\n", pvBuf, data, op, rc)); + return EFAULT; + } + if (RT_UNLIKELY(!RT_VALID_PTR(pvBuf))) + { + RTMemTmpFree(pvBuf); + LogRel((DRIVER_NAME ":vgdrvHaikuIOCtl: pvBuf invalid pointer %p\n", pvBuf)); + return EINVAL; + } + } + Log(("vgdrvHaikuIOCtl: pSession=%p pid=%d.\n", pSession,(int)RTProcSelf())); + + /* + * Process the IOCtl. + */ + size_t cbDataReturned; + rc = VGDrvCommonIoCtl(op, &g_DevExt, pSession, pvBuf, len, &cbDataReturned); + if (RT_SUCCESS(rc)) + { + rc = 0; + if (RT_UNLIKELY(cbDataReturned > len)) + { + Log(("vgdrvHaikuIOCtl: too much output data %d expected %d\n", cbDataReturned, len)); + cbDataReturned = len; + } + if (cbDataReturned > 0) + { + rc = user_memcpy(data, pvBuf, cbDataReturned); + if (RT_UNLIKELY(rc < 0)) + { + Log(("vgdrvHaikuIOCtl: user_memcpy failed; pvBuf=%p pArg=%p Cmd=%lu. rc=%d\n", pvBuf, data, op, rc)); + rc = EFAULT; + } + } + } + else + { + Log(("vgdrvHaikuIOCtl: VGDrvCommonIoCtl failed. rc=%d\n", rc)); + rc = EFAULT; + } + RTMemTmpFree(pvBuf); + return rc; +} + + +/** + * Driver select hook. + * + * @param cookie The session. + * @param event The event. + * @param ref ??? + * @param sync ??? + * + * @return Haiku status code. + */ +static status_t vgdrvHaikuSelect(void *cookie, uint8 event, uint32 ref, selectsync *sync) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie; + status_t err = B_OK; + + switch (event) + { + case B_SELECT_READ: + break; + default: + return EINVAL; + } + + RTSpinlockAcquire(g_DevExt.SessionSpinlock); + + uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq); + if (pSession->u32MousePosChangedSeq != u32CurSeq) + { + pSession->u32MousePosChangedSeq = u32CurSeq; + notify_select_event(sync, event); + } + else if (sState.selectSync == NULL) + { + sState.selectEvent = (uint8_t)event; + sState.selectRef = (uint32_t)ref; + sState.selectSync = (void *)sync; + } + else + err = B_WOULD_BLOCK; + + RTSpinlockRelease(g_DevExt.SessionSpinlock); + + return err; +} + + +/** + * Driver deselect hook. + * @param cookie The session. + * @param event The event. + * @param sync ??? + * + * @return Haiku status code. + */ +static status_t vgdrvHaikuDeselect(void *cookie, uint8 event, selectsync *sync) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie; + status_t err = B_OK; + //dprintf(DRIVER_NAME "deselect(,%d,%p)\n", event, sync); + + RTSpinlockAcquire(g_DevExt.SessionSpinlock); + + if (sState.selectSync == sync) + { + //dprintf(DRIVER_NAME "deselect: dropping: %p %x\n", sState.selectSync, sState.selectEvent); + sState.selectEvent = (uint8_t)0; + sState.selectRef = (uint32_t)0; + sState.selectSync = NULL; + } + else + err = B_OK; + + RTSpinlockRelease(g_DevExt.SessionSpinlock); + return err; +} + + +/** + * Driver write hook. + * @param cookie The session. + * @param position The offset. + * @param data Pointer to the data. + * @param numBytes Where to store the number of bytes written. + * + * @return Haiku status code. + */ +static status_t vgdrvHaikuWrite(void *cookie, off_t position, const void *data, size_t *numBytes) +{ + *numBytes = 0; + return B_OK; +} + + +/** + * Driver read hook. + * @param cookie The session. + * @param position The offset. + * @param data Pointer to the data. + * @param numBytes Where to store the number of bytes read. + * + * @return Haiku status code. + */ +static status_t vgdrvHaikuRead(void *cookie, off_t position, void *data, size_t *numBytes) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie; + + if (*numBytes == 0) + return B_OK; + + uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq); + if (pSession->u32MousePosChangedSeq != u32CurSeq) + { + pSession->u32MousePosChangedSeq = u32CurSeq; + *numBytes = 1; + return B_OK; + } + + *numBytes = 0; + return B_OK; +} + + + +status_t init_hardware() +{ + return get_module(MODULE_NAME, (module_info **)&g_VBoxGuest); +} + +status_t init_driver() +{ + return B_OK; +} + +device_hooks *find_device(const char *name) +{ + static device_hooks s_vgdrvHaikuDeviceHooks = + { + vgdrvHaikuOpen, + vgdrvHaikuClose, + vgdrvHaikuFree, + vgdrvHaikuIOCtl, + vgdrvHaikuRead, + vgdrvHaikuWrite, + vgdrvHaikuSelect, + vgdrvHaikuDeselect, + }; + return &s_vgdrvHaikuDeviceHooks; +} + +const char **publish_devices() +{ + static const char *s_papszDevices[] = { DEVICE_NAME, NULL }; + return s_papszDevices; +} + +void uninit_driver() +{ + put_module(MODULE_NAME); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-darwin.cpp b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-darwin.cpp new file mode 100644 index 00000000..1ee85362 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-darwin.cpp @@ -0,0 +1,1393 @@ +/* $Id: VBoxGuest-darwin.cpp $ */ +/** @file + * VBoxGuest - Darwin Specifics. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_VGDRV +/* + * Deal with conflicts first. + * PVM - BSD mess, that FreeBSD has correct a long time ago. + * iprt/types.h before sys/param.h - prevents UINT32_C and friends. + */ +#include <iprt/types.h> +#include <sys/param.h> +#undef PVM + +#include <IOKit/IOLib.h> /* Assert as function */ + +#include <VBox/version.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/power.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/spinlock.h> +#include <iprt/string.h> +#include <VBox/err.h> +#include <VBox/log.h> + +#include <mach/kmod.h> +#include <miscfs/devfs/devfs.h> +#include <sys/conf.h> +#include <sys/errno.h> +#include <sys/ioccom.h> +#include <sys/malloc.h> +#include <sys/proc.h> +#include <sys/kauth.h> +#define _OS_OSUNSERIALIZE_H /* HACK ALERT! Block importing OSUnserialized.h as it causes compilation trouble with + newer clang versions and the 10.15 SDK, and we really don't need it. Sample error: + libkern/c++/OSUnserialize.h:72:2: error: use of OSPtr outside of a return type [-Werror,-Wossharedptr-misuse] */ +#include <IOKit/IOService.h> +#include <IOKit/IOUserClient.h> +#include <IOKit/pwr_mgt/RootDomain.h> +#include <IOKit/pci/IOPCIDevice.h> +#include <IOKit/IOBufferMemoryDescriptor.h> +#include <IOKit/IOFilterInterruptEventSource.h> +#include "VBoxGuestInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The system device node name. */ +#define DEVICE_NAME_SYS "vboxguest" +/** The user device node name. */ +#define DEVICE_NAME_USR "vboxguestu" + + +/** @name For debugging/whatever, now permanent. + * @{ */ +#define VBOX_PROC_SELFNAME_LEN 31 +#define VBOX_RETRIEVE_CUR_PROC_NAME(a_Name) char a_Name[VBOX_PROC_SELFNAME_LEN + 1]; \ + proc_selfname(a_Name, VBOX_PROC_SELFNAME_LEN) +/** @} */ + +#ifndef minor +/* The inlined C++ function version minor() takes the wrong parameter + type, uint32_t instead of dev_t. This kludge works around that. */ +# define minor(x) minor((uint32_t)(x)) +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +static kern_return_t vgdrvDarwinStart(struct kmod_info *pKModInfo, void *pvData); +static kern_return_t vgdrvDarwinStop(struct kmod_info *pKModInfo, void *pvData); +static int vgdrvDarwinCharDevRemove(void); + +static int vgdrvDarwinOpen(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess); +static int vgdrvDarwinClose(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess); +static int vgdrvDarwinIOCtlSlow(PVBOXGUESTSESSION pSession, u_long iCmd, caddr_t pData, struct proc *pProcess); +static int vgdrvDarwinIOCtl(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess); + +static int vgdrvDarwinErr2DarwinErr(int rc); + +static IOReturn vgdrvDarwinSleepHandler(void *pvTarget, void *pvRefCon, UInt32 uMessageType, IOService *pProvider, void *pvMessageArgument, vm_size_t argSize); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * The service class for handling the VMMDev PCI device. + * + * Instantiated when the module is loaded (and on PCI hotplugging?). + */ +class org_virtualbox_VBoxGuest : public IOService +{ + OSDeclareDefaultStructors(org_virtualbox_VBoxGuest); + +private: + IOPCIDevice *m_pIOPCIDevice; + IOMemoryMap *m_pMap; + IOFilterInterruptEventSource *m_pInterruptSrc; + + bool setupVmmDevInterrupts(IOService *pProvider); + bool disableVmmDevInterrupts(void); + bool isVmmDev(IOPCIDevice *pIOPCIDevice); + +protected: + /** Non-NULL if interrupts are registered. Probably same as getProvider(). */ + IOService *m_pInterruptProvider; + +public: + virtual bool init(OSDictionary *pDictionary = 0); + virtual void free(void); + virtual IOService *probe(IOService *pProvider, SInt32 *pi32Score); + virtual bool start(IOService *pProvider); + virtual void stop(IOService *pProvider); + virtual bool terminate(IOOptionBits fOptions); + static void vgdrvDarwinIrqHandler(OSObject *pTarget, void *pvRefCon, IOService *pNub, int iSrc); +}; + +OSDefineMetaClassAndStructors(org_virtualbox_VBoxGuest, IOService); + + +/** + * An attempt at getting that clientDied() notification. + * I don't think it'll work as I cannot figure out where/what creates the correct + * port right. + * + * Instantiated when userland does IOServiceOpen(). + */ +class org_virtualbox_VBoxGuestClient : public IOUserClient +{ + OSDeclareDefaultStructors(org_virtualbox_VBoxGuestClient); + +private: + /** Guard against the parent class growing and us using outdated headers. */ + uint8_t m_abSafetyPadding[256]; + + PVBOXGUESTSESSION m_pSession; /**< The session. */ + task_t m_Task; /**< The client task. */ + org_virtualbox_VBoxGuest *m_pProvider; /**< The service provider. */ + +public: + virtual bool initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type); + virtual bool start(IOService *pProvider); + static void sessionClose(RTPROCESS Process); + virtual IOReturn clientClose(void); + virtual IOReturn clientDied(void); + virtual bool terminate(IOOptionBits fOptions = 0); + virtual bool finalize(IOOptionBits fOptions); + virtual void stop(IOService *pProvider); + + RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT(); +}; + +OSDefineMetaClassAndStructors(org_virtualbox_VBoxGuestClient, IOUserClient); + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Declare the module stuff. + */ +RT_C_DECLS_BEGIN +extern kern_return_t _start(struct kmod_info *pKModInfo, void *pvData); +extern kern_return_t _stop(struct kmod_info *pKModInfo, void *pvData); + +KMOD_EXPLICIT_DECL(VBoxGuest, VBOX_VERSION_STRING, _start, _stop) +DECL_HIDDEN_DATA(kmod_start_func_t *) _realmain = vgdrvDarwinStart; +DECL_HIDDEN_DATA(kmod_stop_func_t *) _antimain = vgdrvDarwinStop; +DECL_HIDDEN_DATA(int) _kext_apple_cc = __APPLE_CC__; +RT_C_DECLS_END + + +/** + * Device extention & session data association structure. + */ +static VBOXGUESTDEVEXT g_DevExt; + +/** + * The character device switch table for the driver. + */ +static struct cdevsw g_DevCW = +{ + /*.d_open = */ vgdrvDarwinOpen, + /*.d_close = */ vgdrvDarwinClose, + /*.d_read = */ eno_rdwrt, + /*.d_write = */ eno_rdwrt, + /*.d_ioctl = */ vgdrvDarwinIOCtl, + /*.d_stop = */ eno_stop, + /*.d_reset = */ eno_reset, + /*.d_ttys = */ NULL, + /*.d_select = */ eno_select, + /*.d_mmap = */ eno_mmap, + /*.d_strategy = */ eno_strat, + /*.d_getc = */ (void *)(uintptr_t)&enodev, //eno_getc, + /*.d_putc = */ (void *)(uintptr_t)&enodev, //eno_putc, + /*.d_type = */ 0 +}; + +/** Major device number. */ +static int g_iMajorDeviceNo = -1; +/** Registered devfs device handle. */ +static void *g_hDevFsDeviceSys = NULL; +/** Registered devfs device handle for the user device. */ +static void *g_hDevFsDeviceUsr = NULL; /**< @todo 4 later */ + +/** Spinlock protecting g_apSessionHashTab. */ +static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK; +/** Hash table */ +static PVBOXGUESTSESSION g_apSessionHashTab[19]; +/** Calculates the index into g_apSessionHashTab.*/ +#define SESSION_HASH(pid) ((pid) % RT_ELEMENTS(g_apSessionHashTab)) +/** The number of open sessions. */ +static int32_t volatile g_cSessions = 0; +/** Makes sure there is only one org_virtualbox_VBoxGuest instance. */ +static bool volatile g_fInstantiated = 0; +/** The notifier handle for the sleep callback handler. */ +static IONotifier *g_pSleepNotifier = NULL; + + +/** + * Start the kernel module. + */ +static kern_return_t vgdrvDarwinStart(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); +#ifdef DEBUG + printf("vgdrvDarwinStart\n"); +#endif +#if 0 + gIOKitDebug |= 0x001 //kIOLogAttach + | 0x002 //kIOLogProbe + | 0x004 //kIOLogStart + | 0x008 //kIOLogRegister + | 0x010 //kIOLogMatch + | 0x020 //kIOLogConfig + ; +#endif + + /* + * Initialize IPRT. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + Log(("VBoxGuest: driver loaded\n")); + return KMOD_RETURN_SUCCESS; + } + + RTLogBackdoorPrintf("VBoxGuest: RTR0Init failed with rc=%Rrc\n", rc); + printf("VBoxGuest: RTR0Init failed with rc=%d\n", rc); + return KMOD_RETURN_FAILURE; +} + + +/** + * Stop the kernel module. + */ +static kern_return_t vgdrvDarwinStop(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + + /** @todo we need to check for VBoxSF clients? */ + + RTLogBackdoorPrintf("VBoxGuest: calling RTR0TermForced ...\n"); + RTR0TermForced(); + + RTLogBackdoorPrintf("VBoxGuest: vgdrvDarwinStop returns.\n"); + printf("VBoxGuest: driver unloaded\n"); + return KMOD_RETURN_SUCCESS; +} + + +/** + * Register VBoxGuest char device + */ +static int vgdrvDarwinCharDevInit(void) +{ + int rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxGuestDarwin"); + if (RT_SUCCESS(rc)) + { + /* + * Registering ourselves as a character device. + */ + g_iMajorDeviceNo = cdevsw_add(-1, &g_DevCW); + if (g_iMajorDeviceNo >= 0) + { + /** @todo limit /dev/vboxguest access. */ + g_hDevFsDeviceSys = devfs_make_node(makedev((uint32_t)g_iMajorDeviceNo, 0), DEVFS_CHAR, + UID_ROOT, GID_WHEEL, 0666, DEVICE_NAME_SYS); + if (g_hDevFsDeviceSys != NULL) + { + /* + * And a all-user device. + */ + g_hDevFsDeviceUsr = devfs_make_node(makedev((uint32_t)g_iMajorDeviceNo, 1), DEVFS_CHAR, + UID_ROOT, GID_WHEEL, 0666, DEVICE_NAME_USR); + if (g_hDevFsDeviceUsr != NULL) + { + /* + * Register a sleep/wakeup notification callback. + */ + g_pSleepNotifier = registerPrioritySleepWakeInterest(&vgdrvDarwinSleepHandler, &g_DevExt, NULL); + if (g_pSleepNotifier != NULL) + return KMOD_RETURN_SUCCESS; + } + } + } + vgdrvDarwinCharDevRemove(); + } + return KMOD_RETURN_FAILURE; +} + + +/** + * Unregister VBoxGuest char devices and associated session spinlock. + */ +static int vgdrvDarwinCharDevRemove(void) +{ + if (g_pSleepNotifier) + { + g_pSleepNotifier->remove(); + g_pSleepNotifier = NULL; + } + + if (g_hDevFsDeviceSys) + { + devfs_remove(g_hDevFsDeviceSys); + g_hDevFsDeviceSys = NULL; + } + + if (g_hDevFsDeviceUsr) + { + devfs_remove(g_hDevFsDeviceUsr); + g_hDevFsDeviceUsr = NULL; + } + + if (g_iMajorDeviceNo != -1) + { + int rc2 = cdevsw_remove(g_iMajorDeviceNo, &g_DevCW); + Assert(rc2 == g_iMajorDeviceNo); NOREF(rc2); + g_iMajorDeviceNo = -1; + } + + if (g_Spinlock != NIL_RTSPINLOCK) + { + int rc2 = RTSpinlockDestroy(g_Spinlock); AssertRC(rc2); + g_Spinlock = NIL_RTSPINLOCK; + } + + return KMOD_RETURN_SUCCESS; +} + + +/** + * Device open. Called on open /dev/vboxguest and (later) /dev/vboxguestu. + * + * @param Dev The device number. + * @param fFlags ???. + * @param fDevType ???. + * @param pProcess The process issuing this request. + */ +static int vgdrvDarwinOpen(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess) +{ + RT_NOREF(fFlags, fDevType); + + /* + * Only two minor devices numbers are allowed. + */ + if (minor(Dev) != 0 && minor(Dev) != 1) + return EACCES; + + /* + * The process issuing the request must be the current process. + */ + RTPROCESS Process = RTProcSelf(); + if ((int)Process != proc_pid(pProcess)) + return EIO; + + /* + * Find the session created by org_virtualbox_VBoxGuestClient, fail + * if no such session, and mark it as opened. We set the uid & gid + * here too, since that is more straight forward at this point. + */ + const bool fUnrestricted = minor(Dev) == 0; + int rc = VINF_SUCCESS; + PVBOXGUESTSESSION pSession = NULL; + kauth_cred_t pCred = kauth_cred_proc_ref(pProcess); + if (pCred) + { +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 + RTUID Uid = kauth_cred_getruid(pCred); + RTGID Gid = kauth_cred_getrgid(pCred); +#else + RTUID Uid = pCred->cr_ruid; + RTGID Gid = pCred->cr_rgid; +#endif + unsigned iHash = SESSION_HASH(Process); + RTSpinlockAcquire(g_Spinlock); + + pSession = g_apSessionHashTab[iHash]; + while (pSession && pSession->Process != Process) + pSession = pSession->pNextHash; + if (pSession) + { + if (!pSession->fOpened) + { + pSession->fOpened = true; + pSession->fUserSession = !fUnrestricted; + pSession->fRequestor = VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN; + if (Uid == 0) + pSession->fRequestor |= VMMDEV_REQUESTOR_USR_ROOT; + else + pSession->fRequestor |= VMMDEV_REQUESTOR_USR_USER; + if (Gid == 0) + pSession->fRequestor |= VMMDEV_REQUESTOR_GRP_WHEEL; + if (!fUnrestricted) + pSession->fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE; + pSession->fRequestor |= VMMDEV_REQUESTOR_CON_DONT_KNOW; /** @todo see if we can figure out console relationship of pProc. */ + } + else + rc = VERR_ALREADY_LOADED; + } + else + rc = VERR_GENERAL_FAILURE; + + RTSpinlockRelease(g_Spinlock); +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 + kauth_cred_unref(&pCred); +#else /* 10.4 */ + /* The 10.4u SDK headers and 10.4.11 kernel source have inconsistent definitions + of kauth_cred_unref(), so use the other (now deprecated) API for releasing it. */ + kauth_cred_rele(pCred); +#endif /* 10.4 */ + } + else + rc = VERR_INVALID_PARAMETER; + + Log(("vgdrvDarwinOpen: g_DevExt=%p pSession=%p rc=%d pid=%d\n", &g_DevExt, pSession, rc, proc_pid(pProcess))); + return vgdrvDarwinErr2DarwinErr(rc); +} + + +/** + * Close device. + */ +static int vgdrvDarwinClose(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess) +{ + RT_NOREF(Dev, fFlags, fDevType, pProcess); + Log(("vgdrvDarwinClose: pid=%d\n", (int)RTProcSelf())); + Assert(proc_pid(pProcess) == (int)RTProcSelf()); + + /* + * Hand the session closing to org_virtualbox_VBoxGuestClient. + */ + org_virtualbox_VBoxGuestClient::sessionClose(RTProcSelf()); + return 0; +} + + +/** + * Device I/O Control entry point. + * + * @returns Darwin for slow IOCtls and VBox status code for the fast ones. + * @param Dev The device number (major+minor). + * @param iCmd The IOCtl command. + * @param pData Pointer to the request data. + * @param fFlags Flag saying we're a character device (like we didn't know already). + * @param pProcess The process issuing this request. + */ +static int vgdrvDarwinIOCtl(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess) +{ + RT_NOREF(Dev, fFlags); + const bool fUnrestricted = minor(Dev) == 0; + const RTPROCESS Process = (RTPROCESS)proc_pid(pProcess); + const unsigned iHash = SESSION_HASH(Process); + PVBOXGUESTSESSION pSession; + + /* + * Find the session. + */ + RTSpinlockAcquire(g_Spinlock); + pSession = g_apSessionHashTab[iHash]; + while (pSession && (pSession->Process != Process || pSession->fUserSession == fUnrestricted || !pSession->fOpened)) + pSession = pSession->pNextHash; + + //if (RT_LIKELY(pSession)) + // supdrvSessionRetain(pSession); + + RTSpinlockRelease(g_Spinlock); + if (!pSession) + { + Log(("VBoxDrvDarwinIOCtl: WHAT?!? pSession == NULL! This must be a mistake... pid=%d iCmd=%#lx\n", + (int)Process, iCmd)); + return EINVAL; + } + + /* + * Deal with the high-speed IOCtl. + */ + int rc; + if (VBGL_IOCTL_IS_FAST(iCmd)) + rc = VGDrvCommonIoCtlFast(iCmd, &g_DevExt, pSession); + else + rc = vgdrvDarwinIOCtlSlow(pSession, iCmd, pData, pProcess); + + //supdrvSessionRelease(pSession); + return rc; +} + + +/** + * Worker for VBoxDrvDarwinIOCtl that takes the slow IOCtl functions. + * + * @returns Darwin errno. + * + * @param pSession The session. + * @param iCmd The IOCtl command. + * @param pData Pointer to the request data. + * @param pProcess The calling process. + */ +static int vgdrvDarwinIOCtlSlow(PVBOXGUESTSESSION pSession, u_long iCmd, caddr_t pData, struct proc *pProcess) +{ + RT_NOREF(pProcess); + LogFlow(("vgdrvDarwinIOCtlSlow: pSession=%p iCmd=%p pData=%p pProcess=%p\n", pSession, iCmd, pData, pProcess)); + + + /* + * Buffered or unbuffered? + */ + PVBGLREQHDR pHdr; + user_addr_t pUser = 0; + void *pvPageBuf = NULL; + uint32_t cbReq = IOCPARM_LEN(iCmd); + if ((IOC_DIRMASK & iCmd) == IOC_INOUT) + { + pHdr = (PVBGLREQHDR)pData; + if (RT_UNLIKELY(cbReq < sizeof(*pHdr))) + { + LogRel(("vgdrvDarwinIOCtlSlow: cbReq=%#x < %#x; iCmd=%#lx\n", cbReq, (int)sizeof(*pHdr), iCmd)); + return EINVAL; + } + if (RT_UNLIKELY(pHdr->uVersion != VBGLREQHDR_VERSION)) + { + LogRel(("vgdrvDarwinIOCtlSlow: bad uVersion=%#x; iCmd=%#lx\n", pHdr->uVersion, iCmd)); + return EINVAL; + } + if (RT_UNLIKELY( RT_MAX(pHdr->cbIn, pHdr->cbOut) != cbReq + || pHdr->cbIn < sizeof(*pHdr) + || (pHdr->cbOut < sizeof(*pHdr) && pHdr->cbOut != 0))) + { + LogRel(("vgdrvDarwinIOCtlSlow: max(%#x,%#x) != %#x; iCmd=%#lx\n", pHdr->cbIn, pHdr->cbOut, cbReq, iCmd)); + return EINVAL; + } + } + else if ((IOC_DIRMASK & iCmd) == IOC_VOID && !cbReq) + { + /* + * Get the header and figure out how much we're gonna have to read. + */ + VBGLREQHDR Hdr; + pUser = (user_addr_t)*(void **)pData; + int rc = copyin(pUser, &Hdr, sizeof(Hdr)); + if (RT_UNLIKELY(rc)) + { + LogRel(("vgdrvDarwinIOCtlSlow: copyin(%llx,Hdr,) -> %#x; iCmd=%#lx\n", (unsigned long long)pUser, rc, iCmd)); + return rc; + } + if (RT_UNLIKELY(Hdr.uVersion != VBGLREQHDR_VERSION)) + { + LogRel(("vgdrvDarwinIOCtlSlow: bad uVersion=%#x; iCmd=%#lx\n", Hdr.uVersion, iCmd)); + return EINVAL; + } + cbReq = RT_MAX(Hdr.cbIn, Hdr.cbOut); + if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr) + || (Hdr.cbOut < sizeof(Hdr) && Hdr.cbOut != 0) + || cbReq > _1M*16)) + { + LogRel(("vgdrvDarwinIOCtlSlow: max(%#x,%#x); iCmd=%#lx\n", Hdr.cbIn, Hdr.cbOut, iCmd)); + return EINVAL; + } + + /* + * Allocate buffer and copy in the data. + */ + pHdr = (PVBGLREQHDR)RTMemTmpAlloc(cbReq); + if (!pHdr) + pvPageBuf = pHdr = (PVBGLREQHDR)IOMallocAligned(RT_ALIGN_Z(cbReq, PAGE_SIZE), 8); + if (RT_UNLIKELY(!pHdr)) + { + LogRel(("vgdrvDarwinIOCtlSlow: failed to allocate buffer of %d bytes; iCmd=%#lx\n", cbReq, iCmd)); + return ENOMEM; + } + rc = copyin(pUser, pHdr, Hdr.cbIn); + if (RT_UNLIKELY(rc)) + { + LogRel(("vgdrvDarwinIOCtlSlow: copyin(%llx,%p,%#x) -> %#x; iCmd=%#lx\n", + (unsigned long long)pUser, pHdr, Hdr.cbIn, rc, iCmd)); + if (pvPageBuf) + IOFreeAligned(pvPageBuf, RT_ALIGN_Z(cbReq, PAGE_SIZE)); + else + RTMemTmpFree(pHdr); + return rc; + } + if (Hdr.cbIn < cbReq) + RT_BZERO((uint8_t *)pHdr + Hdr.cbIn, cbReq - Hdr.cbIn); + } + else + { + Log(("vgdrvDarwinIOCtlSlow: huh? cbReq=%#x iCmd=%#lx\n", cbReq, iCmd)); + return EINVAL; + } + + /* + * Process the IOCtl. + */ + int rc = VGDrvCommonIoCtl(iCmd, &g_DevExt, pSession, pHdr, cbReq); + if (RT_LIKELY(!rc)) + { + /* + * If not buffered, copy back the buffer before returning. + */ + if (pUser) + { + uint32_t cbOut = pHdr->cbOut; + if (cbOut > cbReq) + { + LogRel(("vgdrvDarwinIOCtlSlow: too much output! %#x > %#x; uCmd=%#lx!\n", cbOut, cbReq, iCmd)); + cbOut = cbReq; + } + rc = copyout(pHdr, pUser, cbOut); + if (RT_UNLIKELY(rc)) + LogRel(("vgdrvDarwinIOCtlSlow: copyout(%p,%llx,%#x) -> %d; uCmd=%#lx!\n", + pHdr, (unsigned long long)pUser, cbOut, rc, iCmd)); + + /* cleanup */ + if (pvPageBuf) + IOFreeAligned(pvPageBuf, RT_ALIGN_Z(cbReq, PAGE_SIZE)); + else + RTMemTmpFree(pHdr); + } + } + else + { + /* + * The request failed, just clean up. + */ + if (pUser) + { + if (pvPageBuf) + IOFreeAligned(pvPageBuf, RT_ALIGN_Z(cbReq, PAGE_SIZE)); + else + RTMemTmpFree(pHdr); + } + + Log(("vgdrvDarwinIOCtlSlow: pid=%d iCmd=%lx pData=%p failed, rc=%d\n", proc_pid(pProcess), iCmd, (void *)pData, rc)); + rc = EINVAL; + } + + Log2(("vgdrvDarwinIOCtlSlow: returns %d\n", rc)); + return rc; +} + + +/** + * @note This code is duplicated on other platforms with variations, so please + * keep them all up to date when making changes! + */ +int VBOXCALL VBoxGuestIDC(void *pvSession, uintptr_t uReq, PVBGLREQHDR pReqHdr, size_t cbReq) +{ + /* + * Simple request validation (common code does the rest). + */ + int rc; + if ( RT_VALID_PTR(pReqHdr) + && cbReq >= sizeof(*pReqHdr)) + { + /* + * All requests except the connect one requires a valid session. + */ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pvSession; + if (pSession) + { + if ( RT_VALID_PTR(pSession) + && pSession->pDevExt == &g_DevExt) + rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); + else + rc = VERR_INVALID_HANDLE; + } + else if (uReq == VBGL_IOCTL_IDC_CONNECT) + { + rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession); + if (RT_SUCCESS(rc)) + { + rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); + if (RT_FAILURE(rc)) + VGDrvCommonCloseSession(&g_DevExt, pSession); + } + } + else + rc = VERR_INVALID_HANDLE; + } + else + rc = VERR_INVALID_POINTER; + return rc; +} + + +void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt) +{ + NOREF(pDevExt); +} + + +bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue) +{ + RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue); + return false; +} + + +/** + * Callback for blah blah blah. + * + * @todo move to IPRT. + */ +static IOReturn vgdrvDarwinSleepHandler(void *pvTarget, void *pvRefCon, UInt32 uMessageType, + IOService *pProvider, void *pvMsgArg, vm_size_t cbMsgArg) +{ + RT_NOREF(pvTarget, pProvider, pvMsgArg, cbMsgArg); + LogFlow(("VBoxGuest: Got sleep/wake notice. Message type was %x\n", uMessageType)); + + if (uMessageType == kIOMessageSystemWillSleep) + RTPowerSignalEvent(RTPOWEREVENT_SUSPEND); + else if (uMessageType == kIOMessageSystemHasPoweredOn) + RTPowerSignalEvent(RTPOWEREVENT_RESUME); + + acknowledgeSleepWakeNotification(pvRefCon); + + return 0; +} + + +/** + * Converts an IPRT error code to a darwin error code. + * + * @returns corresponding darwin error code. + * @param rc IPRT status code. + */ +static int vgdrvDarwinErr2DarwinErr(int rc) +{ + switch (rc) + { + case VINF_SUCCESS: return 0; + case VERR_GENERAL_FAILURE: return EACCES; + case VERR_INVALID_PARAMETER: return EINVAL; + case VERR_INVALID_MAGIC: return EILSEQ; + case VERR_INVALID_HANDLE: return ENXIO; + case VERR_INVALID_POINTER: return EFAULT; + case VERR_LOCK_FAILED: return ENOLCK; + case VERR_ALREADY_LOADED: return EEXIST; + case VERR_PERMISSION_DENIED: return EPERM; + case VERR_VERSION_MISMATCH: return ENOSYS; + } + + return EPERM; +} + + +/* + * + * org_virtualbox_VBoxGuest + * + * - IOService diff resync - + * - IOService diff resync - + * - IOService diff resync - + * + */ + + +/** + * Initialize the object. + */ +bool org_virtualbox_VBoxGuest::init(OSDictionary *pDictionary) +{ + LogFlow(("IOService::init([%p], %p)\n", this, pDictionary)); + if (IOService::init(pDictionary)) + { + /* init members. */ + return true; + } + return false; +} + + +/** + * Free the object. + */ +void org_virtualbox_VBoxGuest::free(void) +{ + RTLogBackdoorPrintf("IOService::free([%p])\n", this); /* might go sideways if we use LogFlow() here. weird. */ + IOService::free(); +} + + +/** + * Check if it's ok to start this service. + * It's always ok by us, so it's up to IOService to decide really. + */ +IOService *org_virtualbox_VBoxGuest::probe(IOService *pProvider, SInt32 *pi32Score) +{ + LogFlow(("IOService::probe([%p])\n", this)); + IOService *pRet = IOService::probe(pProvider, pi32Score); + LogFlow(("IOService::probe([%p]) returns %p *pi32Score=%d\n", this, pRet, pi32Score ? *pi32Score : -1)); + return pRet; +} + + +/** + * Start this service. + */ +bool org_virtualbox_VBoxGuest::start(IOService *pProvider) +{ + LogFlow(("IOService::start([%p])\n", this)); + + /* + * Low level initialization / device initialization should be performed only once. + */ + if (ASMAtomicCmpXchgBool(&g_fInstantiated, true, false)) + { + /* + * Make sure it's a PCI device. + */ + m_pIOPCIDevice = OSDynamicCast(IOPCIDevice, pProvider); + if (m_pIOPCIDevice) + { + /* + * Call parent. + */ + if (IOService::start(pProvider)) + { + /* + * Is it the VMM device? + */ + if (isVmmDev(m_pIOPCIDevice)) + { + /* + * Enable I/O port and memory regions on the device. + */ + m_pIOPCIDevice->setMemoryEnable(true); + m_pIOPCIDevice->setIOEnable(true); + + /* + * Region #0: I/O ports. Mandatory. + */ + IOMemoryDescriptor *pMem = m_pIOPCIDevice->getDeviceMemoryWithIndex(0); + if (pMem) + { + IOPhysicalAddress IOPortBasePhys = pMem->getPhysicalAddress(); + if ((IOPortBasePhys >> 16) == 0) + { + RTIOPORT IOPortBase = (RTIOPORT)IOPortBasePhys; + void *pvMMIOBase = NULL; + uint32_t cbMMIO = 0; + + /* + * Region #1: Shared Memory. Technically optional. + */ + m_pMap = m_pIOPCIDevice->mapDeviceMemoryWithIndex(1); + if (m_pMap) + { + pvMMIOBase = (void *)m_pMap->getVirtualAddress(); + cbMMIO = (uint32_t)m_pMap->getLength(); + } + + /* + * Initialize the device extension. + */ + int rc = VGDrvCommonInitDevExt(&g_DevExt, IOPortBase, pvMMIOBase, cbMMIO, + ARCH_BITS == 64 ? VBOXOSTYPE_MacOS_x64 : VBOXOSTYPE_MacOS, 0); + if (RT_SUCCESS(rc)) + { + /* + * Register the device nodes and enable interrupts. + */ + rc = vgdrvDarwinCharDevInit(); + if (rc == KMOD_RETURN_SUCCESS) + { + if (setupVmmDevInterrupts(pProvider)) + { + /* + * Read host configuration. + */ + VGDrvCommonProcessOptionsFromHost(&g_DevExt); + + /* + * Just register the service and we're done! + */ + registerService(); + + LogRel(("VBoxGuest: IOService started\n")); + return true; + } + + LogRel(("VBoxGuest: Failed to set up interrupts\n")); + vgdrvDarwinCharDevRemove(); + } + else + LogRel(("VBoxGuest: Failed to initialize character devices (rc=%#x).\n", rc)); + + VGDrvCommonDeleteDevExt(&g_DevExt); + } + else + LogRel(("VBoxGuest: Failed to initialize common code (rc=%Rrc).\n", rc)); + + if (m_pMap) + { + m_pMap->release(); + m_pMap = NULL; + } + } + else + LogRel(("VBoxGuest: Bad I/O port address: %#RX64\n", (uint64_t)IOPortBasePhys)); + } + else + LogRel(("VBoxGuest: The device missing is the I/O port range (#0).\n")); + } + else + LogRel(("VBoxGuest: Not the VMMDev (%#x:%#x).\n", + m_pIOPCIDevice->configRead16(kIOPCIConfigVendorID), m_pIOPCIDevice->configRead16(kIOPCIConfigDeviceID))); + + IOService::stop(pProvider); + } + } + else + LogRel(("VBoxGuest: Provider is not an instance of IOPCIDevice.\n")); + + ASMAtomicXchgBool(&g_fInstantiated, false); + } + return false; +} + + +/** + * Stop this service. + */ +void org_virtualbox_VBoxGuest::stop(IOService *pProvider) +{ +#ifdef LOG_ENABLED + RTLogBackdoorPrintf("org_virtualbox_VBoxGuest::stop([%p], %p)\n", this, pProvider); /* Being cautious here, no Log(). */ +#endif + AssertReturnVoid(ASMAtomicReadBool(&g_fInstantiated)); + + /* Low level termination should be performed only once */ + if (!disableVmmDevInterrupts()) + printf("VBoxGuest: unable to unregister interrupt handler\n"); + + vgdrvDarwinCharDevRemove(); + VGDrvCommonDeleteDevExt(&g_DevExt); + + if (m_pMap) + { + m_pMap->release(); + m_pMap = NULL; + } + + IOService::stop(pProvider); + + ASMAtomicWriteBool(&g_fInstantiated, false); + + printf("VBoxGuest: IOService stopped\n"); + RTLogBackdoorPrintf("org_virtualbox_VBoxGuest::stop: returning\n"); /* Being cautious here, no Log(). */ +} + + +/** + * Termination request. + * + * @return true if we're ok with shutting down now, false if we're not. + * @param fOptions Flags. + */ +bool org_virtualbox_VBoxGuest::terminate(IOOptionBits fOptions) +{ +#ifdef LOG_ENABLED + RTLogBackdoorPrintf("org_virtualbox_VBoxGuest::terminate: reference_count=%d g_cSessions=%d (fOptions=%#x)\n", + KMOD_INFO_NAME.reference_count, ASMAtomicUoReadS32(&g_cSessions), fOptions); /* Being cautious here, no Log(). */ +#endif + + bool fRc; + if ( KMOD_INFO_NAME.reference_count != 0 + || ASMAtomicUoReadS32(&g_cSessions)) + fRc = false; + else + fRc = IOService::terminate(fOptions); + +#ifdef LOG_ENABLED + RTLogBackdoorPrintf("org_virtualbox_SupDrv::terminate: returns %d\n", fRc); /* Being cautious here, no Log(). */ +#endif + return fRc; +} + + +/** + * Implementes a IOInterruptHandler, called by provider when an interrupt occurs. + */ +/*static*/ void org_virtualbox_VBoxGuest::vgdrvDarwinIrqHandler(OSObject *pTarget, void *pvRefCon, IOService *pNub, int iSrc) +{ +#ifdef LOG_ENABLED + RTLogBackdoorPrintf("vgdrvDarwinIrqHandler: %p %p %p %d\n", pTarget, pvRefCon, pNub, iSrc); +#endif + RT_NOREF(pTarget, pvRefCon, pNub, iSrc); + + VGDrvCommonISR(&g_DevExt); + /* There is in fact no way of indicating that this is our interrupt, other + than making the device lower it. So, the return code is ignored. */ +} + + +/** + * Sets up and enables interrupts on the device. + * + * Interrupts are handled directly, no messing around with workloops. The + * rational here is is that the main job of our interrupt handler is waking up + * other threads currently sitting in HGCM calls, i.e. little more effort than + * waking up the workloop thread. + * + * @returns success indicator. Failures are fully logged. + */ +bool org_virtualbox_VBoxGuest::setupVmmDevInterrupts(IOService *pProvider) +{ + AssertReturn(pProvider, false); + + if (m_pInterruptProvider != pProvider) + { + pProvider->retain(); + if (m_pInterruptProvider) + m_pInterruptProvider->release(); + m_pInterruptProvider = pProvider; + } + + IOReturn rc = pProvider->registerInterrupt(0 /*intIndex*/, this, vgdrvDarwinIrqHandler, this); + if (rc == kIOReturnSuccess) + { + rc = pProvider->enableInterrupt(0 /*intIndex*/); + if (rc == kIOReturnSuccess) + return true; + + LogRel(("VBoxGuest: Failed to enable interrupt: %#x\n", rc)); + m_pInterruptProvider->unregisterInterrupt(0 /*intIndex*/); + } + else + LogRel(("VBoxGuest: Failed to register interrupt: %#x\n", rc)); + return false; +} + + +/** + * Counterpart to setupVmmDevInterrupts(). + */ +bool org_virtualbox_VBoxGuest::disableVmmDevInterrupts(void) +{ + if (m_pInterruptProvider) + { + IOReturn rc = m_pInterruptProvider->disableInterrupt(0 /*intIndex*/); + AssertMsg(rc == kIOReturnSuccess, ("%#x\n", rc)); + rc = m_pInterruptProvider->unregisterInterrupt(0 /*intIndex*/); + AssertMsg(rc == kIOReturnSuccess, ("%#x\n", rc)); + RT_NOREF_PV(rc); + + m_pInterruptProvider->release(); + m_pInterruptProvider = NULL; + } + + return true; +} + + +/** + * Checks if it's the VMM device. + * + * @returns true if it is, false if it isn't. + * @param pIOPCIDevice The PCI device we think might be the VMM device. + */ +bool org_virtualbox_VBoxGuest::isVmmDev(IOPCIDevice *pIOPCIDevice) +{ + if (pIOPCIDevice) + { + uint16_t idVendor = m_pIOPCIDevice->configRead16(kIOPCIConfigVendorID); + if (idVendor == VMMDEV_VENDORID) + { + uint16_t idDevice = m_pIOPCIDevice->configRead16(kIOPCIConfigDeviceID); + if (idDevice == VMMDEV_DEVICEID) + return true; + } + } + return false; +} + + + +/* + * + * org_virtualbox_VBoxGuestClient + * + */ + + +/** + * Initializer called when the client opens the service. + */ +bool org_virtualbox_VBoxGuestClient::initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type) +{ + LogFlow(("org_virtualbox_VBoxGuestClient::initWithTask([%p], %#x, %p, %#x) (cur pid=%d proc=%p)\n", + this, OwningTask, pvSecurityId, u32Type, RTProcSelf(), RTR0ProcHandleSelf())); + AssertMsg((RTR0PROCESS)OwningTask == RTR0ProcHandleSelf(), ("%p %p\n", OwningTask, RTR0ProcHandleSelf())); + + if (!OwningTask) + return false; + + if (u32Type != VBOXGUEST_DARWIN_IOSERVICE_COOKIE) + { + VBOX_RETRIEVE_CUR_PROC_NAME(szProcName); + LogRelMax(10, ("org_virtualbox_VBoxGuestClient::initWithTask: Bad cookie %#x (%s)\n", u32Type, szProcName)); + return false; + } + + if (IOUserClient::initWithTask(OwningTask, pvSecurityId , u32Type)) + { + /* + * In theory we have to call task_reference() to make sure that the task is + * valid during the lifetime of this object. The pointer is only used to check + * for the context this object is called in though and never dereferenced + * or passed to anything which might, so we just skip this step. + */ + m_Task = OwningTask; + m_pSession = NULL; + m_pProvider = NULL; + return true; + } + return false; +} + + +/** + * Start the client service. + */ +bool org_virtualbox_VBoxGuestClient::start(IOService *pProvider) +{ + LogFlow(("org_virtualbox_VBoxGuestClient::start([%p], %p) (cur pid=%d proc=%p)\n", + this, pProvider, RTProcSelf(), RTR0ProcHandleSelf() )); + AssertMsgReturn((RTR0PROCESS)m_Task == RTR0ProcHandleSelf(), + ("%p %p\n", m_Task, RTR0ProcHandleSelf()), + false); + + if (IOUserClient::start(pProvider)) + { + m_pProvider = OSDynamicCast(org_virtualbox_VBoxGuest, pProvider); + if (m_pProvider) + { + Assert(!m_pSession); + + /* + * Create a new session. + * Note! We complete the requestor stuff in the open method. + */ + int rc = VGDrvCommonCreateUserSession(&g_DevExt, VMMDEV_REQUESTOR_USERMODE, &m_pSession); + if (RT_SUCCESS(rc)) + { + m_pSession->fOpened = false; + /* The Uid, Gid and fUnrestricted fields are set on open. */ + + /* + * Insert it into the hash table, checking that there isn't + * already one for this process first. (One session per proc!) + */ + unsigned iHash = SESSION_HASH(m_pSession->Process); + RTSpinlockAcquire(g_Spinlock); + + PVBOXGUESTSESSION pCur = g_apSessionHashTab[iHash]; + while (pCur && pCur->Process != m_pSession->Process) + pCur = pCur->pNextHash; + if (!pCur) + { + m_pSession->pNextHash = g_apSessionHashTab[iHash]; + g_apSessionHashTab[iHash] = m_pSession; + m_pSession->pvVBoxGuestClient = this; + ASMAtomicIncS32(&g_cSessions); + rc = VINF_SUCCESS; + } + else + rc = VERR_ALREADY_LOADED; + + RTSpinlockRelease(g_Spinlock); + if (RT_SUCCESS(rc)) + { + Log(("org_virtualbox_VBoxGuestClient::start: created session %p for pid %d\n", m_pSession, (int)RTProcSelf())); + return true; + } + + LogFlow(("org_virtualbox_VBoxGuestClient::start: already got a session for this process (%p)\n", pCur)); + VGDrvCommonCloseSession(&g_DevExt, m_pSession); //supdrvSessionRelease(m_pSession); + } + + m_pSession = NULL; + LogFlow(("org_virtualbox_VBoxGuestClient::start: rc=%Rrc from supdrvCreateSession\n", rc)); + } + else + LogFlow(("org_virtualbox_VBoxGuestClient::start: %p isn't org_virtualbox_VBoxGuest\n", pProvider)); + } + return false; +} + + +/** + * Common worker for clientClose and VBoxDrvDarwinClose. + */ +/* static */ void org_virtualbox_VBoxGuestClient::sessionClose(RTPROCESS Process) +{ + /* + * Find the session and remove it from the hash table. + * + * Note! Only one session per process. (Both start() and + * vgdrvDarwinOpen makes sure this is so.) + */ + const unsigned iHash = SESSION_HASH(Process); + RTSpinlockAcquire(g_Spinlock); + PVBOXGUESTSESSION pSession = g_apSessionHashTab[iHash]; + if (pSession) + { + if (pSession->Process == Process) + { + g_apSessionHashTab[iHash] = pSession->pNextHash; + pSession->pNextHash = NULL; + ASMAtomicDecS32(&g_cSessions); + } + else + { + PVBOXGUESTSESSION pPrev = pSession; + pSession = pSession->pNextHash; + while (pSession) + { + if (pSession->Process == Process) + { + pPrev->pNextHash = pSession->pNextHash; + pSession->pNextHash = NULL; + ASMAtomicDecS32(&g_cSessions); + break; + } + + /* next */ + pPrev = pSession; + pSession = pSession->pNextHash; + } + } + } + RTSpinlockRelease(g_Spinlock); + if (!pSession) + { + Log(("VBoxGuestClient::sessionClose: pSession == NULL, pid=%d; freed already?\n", (int)Process)); + return; + } + + /* + * Remove it from the client object. + */ + org_virtualbox_VBoxGuestClient *pThis = (org_virtualbox_VBoxGuestClient *)pSession->pvVBoxGuestClient; + pSession->pvVBoxGuestClient = NULL; + if (pThis) + { + Assert(pThis->m_pSession == pSession); + pThis->m_pSession = NULL; + } + + /* + * Close the session. + */ + VGDrvCommonCloseSession(&g_DevExt, pSession); // supdrvSessionRelease(m_pSession); +} + + +/** + * Client exits normally. + */ +IOReturn org_virtualbox_VBoxGuestClient::clientClose(void) +{ + LogFlow(("org_virtualbox_VBoxGuestClient::clientClose([%p]) (cur pid=%d proc=%p)\n", this, RTProcSelf(), RTR0ProcHandleSelf())); + AssertMsg((RTR0PROCESS)m_Task == RTR0ProcHandleSelf(), ("%p %p\n", m_Task, RTR0ProcHandleSelf())); + + /* + * Clean up the session if it's still around. + * + * We cannot rely 100% on close, and in the case of a dead client + * we'll end up hanging inside vm_map_remove() if we postpone it. + */ + if (m_pSession) + { + sessionClose(RTProcSelf()); + Assert(!m_pSession); + } + + m_pProvider = NULL; + terminate(); + + return kIOReturnSuccess; +} + + +/** + * The client exits abnormally / forgets to do cleanups. (logging) + */ +IOReturn org_virtualbox_VBoxGuestClient::clientDied(void) +{ + LogFlow(("IOService::clientDied([%p]) m_Task=%p R0Process=%p Process=%d\n", this, m_Task, RTR0ProcHandleSelf(), RTProcSelf())); + + /* IOUserClient::clientDied() calls clientClose, so we'll just do the work there. */ + return IOUserClient::clientDied(); +} + + +/** + * Terminate the service (initiate the destruction). (logging) + */ +bool org_virtualbox_VBoxGuestClient::terminate(IOOptionBits fOptions) +{ + LogFlow(("IOService::terminate([%p], %#x)\n", this, fOptions)); + return IOUserClient::terminate(fOptions); +} + + +/** + * The final stage of the client service destruction. (logging) + */ +bool org_virtualbox_VBoxGuestClient::finalize(IOOptionBits fOptions) +{ + LogFlow(("IOService::finalize([%p], %#x)\n", this, fOptions)); + return IOUserClient::finalize(fOptions); +} + + +/** + * Stop the client service. (logging) + */ +void org_virtualbox_VBoxGuestClient::stop(IOService *pProvider) +{ + LogFlow(("IOService::stop([%p])\n", this)); + IOUserClient::stop(pProvider); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-freebsd.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-freebsd.c new file mode 100644 index 00000000..99228388 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-freebsd.c @@ -0,0 +1,799 @@ +/* $Id: VBoxGuest-freebsd.c $ */ +/** @file + * VirtualBox Guest Additions Driver for FreeBSD. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/** @todo r=bird: This must merge with SUPDrv-freebsd.c before long. The two + * source files should only differ on prefixes and the extra bits wrt to the + * pci device. I.e. it should be diffable so that fixes to one can easily be + * applied to the other. */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/param.h> +#undef PVM +#include <sys/types.h> +#include <sys/module.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/kernel.h> +#include <sys/fcntl.h> +#include <sys/conf.h> +#include <sys/uio.h> +#include <sys/bus.h> +#include <sys/poll.h> +#include <sys/selinfo.h> +#include <sys/queue.h> +#include <sys/lock.h> +#include <sys/lockmgr.h> +#include <sys/malloc.h> +#include <sys/file.h> +#include <sys/rman.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pcireg.h> + +#include "VBoxGuestInternal.h" +#include <VBox/version.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/mem.h> +#include <iprt/asm.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The module name. */ +#define DEVICE_NAME "vboxguest" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +struct VBoxGuestDeviceState +{ + /** Resource ID of the I/O port */ + int iIOPortResId; + /** Pointer to the I/O port resource. */ + struct resource *pIOPortRes; + /** Start address of the IO Port. */ + uint16_t uIOPortBase; + /** Resource ID of the MMIO area */ + int iVMMDevMemResId; + /** Pointer to the MMIO resource. */ + struct resource *pVMMDevMemRes; + /** Handle of the MMIO resource. */ + bus_space_handle_t VMMDevMemHandle; + /** Size of the memory area. */ + bus_size_t VMMDevMemSize; + /** Mapping of the register space */ + void *pMMIOBase; + /** IRQ number */ + int iIrqResId; + /** IRQ resource handle. */ + struct resource *pIrqRes; + /** Pointer to the IRQ handler. */ + void *pfnIrqHandler; + /** VMMDev version */ + uint32_t u32Version; +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/* + * Character device file handlers. + */ +static d_fdopen_t vgdrvFreeBSDOpen; +static d_close_t vgdrvFreeBSDClose; +static d_ioctl_t vgdrvFreeBSDIOCtl; +static int vgdrvFreeBSDIOCtlSlow(PVBOXGUESTSESSION pSession, u_long ulCmd, caddr_t pvData, struct thread *pTd); +static d_write_t vgdrvFreeBSDWrite; +static d_read_t vgdrvFreeBSDRead; +static d_poll_t vgdrvFreeBSDPoll; + +/* + * IRQ related functions. + */ +static void vgdrvFreeBSDRemoveIRQ(device_t pDevice, void *pvState); +static int vgdrvFreeBSDAddIRQ(device_t pDevice, void *pvState); +static int vgdrvFreeBSDISR(void *pvState); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static MALLOC_DEFINE(M_VBOXGUEST, "vboxguest", "VirtualBox Guest Device Driver"); + +#ifndef D_NEEDMINOR +# define D_NEEDMINOR 0 +#endif + +/* + * The /dev/vboxguest character device entry points. + */ +static struct cdevsw g_vgdrvFreeBSDChrDevSW = +{ + .d_version = D_VERSION, + .d_flags = D_TRACKCLOSE | D_NEEDMINOR, + .d_fdopen = vgdrvFreeBSDOpen, + .d_close = vgdrvFreeBSDClose, + .d_ioctl = vgdrvFreeBSDIOCtl, + .d_read = vgdrvFreeBSDRead, + .d_write = vgdrvFreeBSDWrite, + .d_poll = vgdrvFreeBSDPoll, + .d_name = "vboxguest" +}; + +/** Device extention & session data association structure. */ +static VBOXGUESTDEVEXT g_DevExt; + +/** List of cloned device. Managed by the kernel. */ +static struct clonedevs *g_pvgdrvFreeBSDClones; +/** The dev_clone event handler tag. */ +static eventhandler_tag g_vgdrvFreeBSDEHTag; +/** Reference counter */ +static volatile uint32_t cUsers; +/** selinfo structure used for polling. */ +static struct selinfo g_SelInfo; + +/** + * DEVFS event handler. + */ +static void vgdrvFreeBSDClone(void *pvArg, struct ucred *pCred, char *pszName, int cchName, struct cdev **ppDev) +{ + int iUnit; + int rc; + + Log(("vgdrvFreeBSDClone: pszName=%s ppDev=%p\n", pszName, ppDev)); + + /* + * One device node per user, si_drv1 points to the session. + * /dev/vboxguest<N> where N = {0...255}. + */ + if (!ppDev) + return; + if (strcmp(pszName, "vboxguest") == 0) + iUnit = -1; + else if (dev_stdclone(pszName, NULL, "vboxguest", &iUnit) != 1) + return; + if (iUnit >= 256) + { + Log(("vgdrvFreeBSDClone: iUnit=%d >= 256 - rejected\n", iUnit)); + return; + } + + Log(("vgdrvFreeBSDClone: pszName=%s iUnit=%d\n", pszName, iUnit)); + + rc = clone_create(&g_pvgdrvFreeBSDClones, &g_vgdrvFreeBSDChrDevSW, &iUnit, ppDev, 0); + Log(("vgdrvFreeBSDClone: clone_create -> %d; iUnit=%d\n", rc, iUnit)); + if (rc) + { + *ppDev = make_dev(&g_vgdrvFreeBSDChrDevSW, + iUnit, + UID_ROOT, + GID_WHEEL, + 0664, + "vboxguest%d", iUnit); + if (*ppDev) + { + dev_ref(*ppDev); + (*ppDev)->si_flags |= SI_CHEAPCLONE; + Log(("vgdrvFreeBSDClone: Created *ppDev=%p iUnit=%d si_drv1=%p si_drv2=%p\n", + *ppDev, iUnit, (*ppDev)->si_drv1, (*ppDev)->si_drv2)); + (*ppDev)->si_drv1 = (*ppDev)->si_drv2 = NULL; + } + else + Log(("vgdrvFreeBSDClone: make_dev iUnit=%d failed\n", iUnit)); + } + else + Log(("vgdrvFreeBSDClone: Existing *ppDev=%p iUnit=%d si_drv1=%p si_drv2=%p\n", + *ppDev, iUnit, (*ppDev)->si_drv1, (*ppDev)->si_drv2)); +} + +/** + * File open handler + * + */ +#if __FreeBSD_version >= 700000 +static int vgdrvFreeBSDOpen(struct cdev *pDev, int fOpen, struct thread *pTd, struct file *pFd) +#else +static int vgdrvFreeBSDOpen(struct cdev *pDev, int fOpen, struct thread *pTd) +#endif +{ + int rc; + PVBOXGUESTSESSION pSession; + uint32_t fRequestor; + struct ucred *pCred = curthread->td_ucred; + if (!pCred) + pCred = curproc->p_ucred; + + LogFlow(("vgdrvFreeBSDOpen:\n")); + + /* + * Try grab it (we don't grab the giant, remember). + */ + if (!ASMAtomicCmpXchgPtr(&pDev->si_drv1, (void *)0x42, NULL)) + return EBUSY; + + /* + * Create a new session. + */ + fRequestor = VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN; + if (pCred && pCred->cr_uid == 0) + fRequestor |= VMMDEV_REQUESTOR_USR_ROOT; + else + fRequestor |= VMMDEV_REQUESTOR_USR_USER; + if (pCred && groupmember(0, pCred)) + fRequestor |= VMMDEV_REQUESTOR_GRP_WHEEL; + fRequestor |= VMMDEV_REQUESTOR_NO_USER_DEVICE; /** @todo implement /dev/vboxuser + if (!fUnrestricted) + fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE; */ + fRequestor |= VMMDEV_REQUESTOR_CON_DONT_KNOW; /** @todo see if we can figure out console relationship of pProc. */ + rc = VGDrvCommonCreateUserSession(&g_DevExt, fRequestor, &pSession); + if (RT_SUCCESS(rc)) + { + if (ASMAtomicCmpXchgPtr(&pDev->si_drv1, pSession, (void *)0x42)) + { + Log(("vgdrvFreeBSDOpen: success - g_DevExt=%p pSession=%p rc=%d pid=%d\n", &g_DevExt, pSession, rc, (int)RTProcSelf())); + ASMAtomicIncU32(&cUsers); + return 0; + } + + VGDrvCommonCloseSession(&g_DevExt, pSession); + } + + LogRel(("vgdrvFreeBSDOpen: failed. rc=%d\n", rc)); + return RTErrConvertToErrno(rc); +} + +/** + * File close handler + * + */ +static int vgdrvFreeBSDClose(struct cdev *pDev, int fFile, int DevType, struct thread *pTd) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pDev->si_drv1; + Log(("vgdrvFreeBSDClose: fFile=%#x pSession=%p\n", fFile, pSession)); + + /* + * Close the session if it's still hanging on to the device... + */ + if (RT_VALID_PTR(pSession)) + { + VGDrvCommonCloseSession(&g_DevExt, pSession); + if (!ASMAtomicCmpXchgPtr(&pDev->si_drv1, NULL, pSession)) + Log(("vgdrvFreeBSDClose: si_drv1=%p expected %p!\n", pDev->si_drv1, pSession)); + ASMAtomicDecU32(&cUsers); + /* Don't use destroy_dev here because it may sleep resulting in a hanging user process. */ + destroy_dev_sched(pDev); + } + else + Log(("vgdrvFreeBSDClose: si_drv1=%p!\n", pSession)); + return 0; +} + + +/** + * I/O control request. + * + * @returns depends... + * @param pDev The device. + * @param ulCmd The command. + * @param pvData Pointer to the data. + * @param fFile The file descriptor flags. + * @param pTd The calling thread. + */ +static int vgdrvFreeBSDIOCtl(struct cdev *pDev, u_long ulCmd, caddr_t pvData, int fFile, struct thread *pTd) +{ + PVBOXGUESTSESSION pSession; + devfs_get_cdevpriv((void **)&pSession); + + /* + * Deal with the fast ioctl path first. + */ + if (VBGL_IOCTL_IS_FAST(ulCmd)) + return VGDrvCommonIoCtlFast(ulCmd, &g_DevExt, pSession); + + return vgdrvFreeBSDIOCtlSlow(pSession, ulCmd, pvData, pTd); +} + + +/** + * Deal with the 'slow' I/O control requests. + * + * @returns 0 on success, appropriate errno on failure. + * @param pSession The session. + * @param ulCmd The command. + * @param pvData The request data. + * @param pTd The calling thread. + */ +static int vgdrvFreeBSDIOCtlSlow(PVBOXGUESTSESSION pSession, u_long ulCmd, caddr_t pvData, struct thread *pTd) +{ + PVBGLREQHDR pHdr; + uint32_t cbReq = IOCPARM_LEN(ulCmd); + void *pvUser = NULL; + + /* + * Buffered request? + */ + if ((IOC_DIRMASK & ulCmd) == IOC_INOUT) + { + pHdr = (PVBGLREQHDR)pvData; + if (RT_UNLIKELY(cbReq < sizeof(*pHdr))) + { + LogRel(("vgdrvFreeBSDIOCtlSlow: cbReq=%#x < %#x; ulCmd=%#lx\n", cbReq, (int)sizeof(*pHdr), ulCmd)); + return EINVAL; + } + if (RT_UNLIKELY(pHdr->uVersion != VBGLREQHDR_VERSION)) + { + LogRel(("vgdrvFreeBSDIOCtlSlow: bad uVersion=%#x; ulCmd=%#lx\n", pHdr->uVersion, ulCmd)); + return EINVAL; + } + if (RT_UNLIKELY( RT_MAX(pHdr->cbIn, pHdr->cbOut) != cbReq + || pHdr->cbIn < sizeof(*pHdr) + || (pHdr->cbOut < sizeof(*pHdr) && pHdr->cbOut != 0))) + { + LogRel(("vgdrvFreeBSDIOCtlSlow: max(%#x,%#x) != %#x; ulCmd=%#lx\n", pHdr->cbIn, pHdr->cbOut, cbReq, ulCmd)); + return EINVAL; + } + } + /* + * Big unbuffered request? + */ + else if ((IOC_DIRMASK & ulCmd) == IOC_VOID && !cbReq) + { + /* + * Read the header, validate it and figure out how much that needs to be buffered. + */ + VBGLREQHDR Hdr; + pvUser = *(void **)pvData; + int rc = copyin(pvUser, &Hdr, sizeof(Hdr)); + if (RT_UNLIKELY(rc)) + { + LogRel(("vgdrvFreeBSDIOCtlSlow: copyin(%p,Hdr,) -> %#x; ulCmd=%#lx\n", pvUser, rc, ulCmd)); + return rc; + } + if (RT_UNLIKELY(Hdr.uVersion != VBGLREQHDR_VERSION)) + { + LogRel(("vgdrvFreeBSDIOCtlSlow: bad uVersion=%#x; ulCmd=%#lx\n", Hdr.uVersion, ulCmd)); + return EINVAL; + } + cbReq = RT_MAX(Hdr.cbIn, Hdr.cbOut); + if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr) + || (Hdr.cbOut < sizeof(Hdr) && Hdr.cbOut != 0) + || cbReq > _1M*16)) + { + LogRel(("vgdrvFreeBSDIOCtlSlow: max(%#x,%#x); ulCmd=%#lx\n", Hdr.cbIn, Hdr.cbOut, ulCmd)); + return EINVAL; + } + + /* + * Allocate buffer and copy in the data. + */ + pHdr = (PVBGLREQHDR)RTMemTmpAlloc(cbReq); + if (RT_UNLIKELY(!pHdr)) + { + LogRel(("vgdrvFreeBSDIOCtlSlow: failed to allocate buffer of %d bytes; ulCmd=%#lx\n", cbReq, ulCmd)); + return ENOMEM; + } + rc = copyin(pvUser, pHdr, Hdr.cbIn); + if (RT_UNLIKELY(rc)) + { + LogRel(("vgdrvFreeBSDIOCtlSlow: copyin(%p,%p,%#x) -> %#x; ulCmd=%#lx\n", + pvUser, pHdr, Hdr.cbIn, rc, ulCmd)); + RTMemTmpFree(pHdr); + return rc; + } + if (Hdr.cbIn < cbReq) + RT_BZERO((uint8_t *)pHdr + Hdr.cbIn, cbReq - Hdr.cbIn); + } + else + { + Log(("vgdrvFreeBSDIOCtlSlow: huh? cbReq=%#x ulCmd=%#lx\n", cbReq, ulCmd)); + return EINVAL; + } + + /* + * Process the IOCtl. + */ + int rc = VGDrvCommonIoCtl(ulCmd, &g_DevExt, pSession, pHdr, cbReq); + if (RT_LIKELY(!rc)) + { + /* + * If unbuffered, copy back the result before returning. + */ + if (pvUser) + { + uint32_t cbOut = pHdr->cbOut; + if (cbOut > cbReq) + { + LogRel(("vgdrvFreeBSDIOCtlSlow: too much output! %#x > %#x; uCmd=%#lx!\n", cbOut, cbReq, ulCmd)); + cbOut = cbReq; + } + rc = copyout(pHdr, pvUser, cbOut); + if (RT_UNLIKELY(rc)) + LogRel(("vgdrvFreeBSDIOCtlSlow: copyout(%p,%p,%#x) -> %d; uCmd=%#lx!\n", pHdr, pvUser, cbOut, rc, ulCmd)); + + Log(("vgdrvFreeBSDIOCtlSlow: returns %d / %d ulCmd=%lx\n", 0, pHdr->rc, ulCmd)); + + /* cleanup */ + RTMemTmpFree(pHdr); + } + } + else + { + /* + * The request failed, just clean up. + */ + if (pvUser) + RTMemTmpFree(pHdr); + + Log(("vgdrvFreeBSDIOCtlSlow: ulCmd=%lx pData=%p failed, rc=%d\n", ulCmd, pvData, rc)); + rc = EINVAL; + } + + return rc; +} + + +/** + * @note This code is duplicated on other platforms with variations, so please + * keep them all up to date when making changes! + */ +int VBOXCALL VBoxGuestIDC(void *pvSession, uintptr_t uReq, PVBGLREQHDR pReqHdr, size_t cbReq) +{ + /* + * Simple request validation (common code does the rest). + */ + int rc; + if ( RT_VALID_PTR(pReqHdr) + && cbReq >= sizeof(*pReqHdr)) + { + /* + * All requests except the connect one requires a valid session. + */ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pvSession; + if (pSession) + { + if ( RT_VALID_PTR(pSession) + && pSession->pDevExt == &g_DevExt) + rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); + else + rc = VERR_INVALID_HANDLE; + } + else if (uReq == VBGL_IOCTL_IDC_CONNECT) + { + rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession); + if (RT_SUCCESS(rc)) + { + rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); + if (RT_FAILURE(rc)) + VGDrvCommonCloseSession(&g_DevExt, pSession); + } + } + else + rc = VERR_INVALID_HANDLE; + } + else + rc = VERR_INVALID_POINTER; + return rc; +} + + +static int vgdrvFreeBSDPoll(struct cdev *pDev, int fEvents, struct thread *td) +{ + int fEventsProcessed; + + LogFlow(("vgdrvFreeBSDPoll: fEvents=%d\n", fEvents)); + + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pDev->si_drv1; + if (RT_UNLIKELY(!RT_VALID_PTR(pSession))) { + Log(("vgdrvFreeBSDPoll: no state data for %s\n", devtoname(pDev))); + return (fEvents & (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM)); + } + + uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq); + if (pSession->u32MousePosChangedSeq != u32CurSeq) + { + fEventsProcessed = fEvents & (POLLIN | POLLRDNORM); + pSession->u32MousePosChangedSeq = u32CurSeq; + } + else + { + fEventsProcessed = 0; + + selrecord(td, &g_SelInfo); + } + + return fEventsProcessed; +} + +static int vgdrvFreeBSDWrite(struct cdev *pDev, struct uio *pUio, int fIo) +{ + return 0; +} + +static int vgdrvFreeBSDRead(struct cdev *pDev, struct uio *pUio, int fIo) +{ + return 0; +} + +static int vgdrvFreeBSDDetach(device_t pDevice) +{ + struct VBoxGuestDeviceState *pState = device_get_softc(pDevice); + + if (cUsers > 0) + return EBUSY; + + /* + * Reverse what we did in vgdrvFreeBSDAttach. + */ + if (g_vgdrvFreeBSDEHTag != NULL) + EVENTHANDLER_DEREGISTER(dev_clone, g_vgdrvFreeBSDEHTag); + + clone_cleanup(&g_pvgdrvFreeBSDClones); + + vgdrvFreeBSDRemoveIRQ(pDevice, pState); + + if (pState->pVMMDevMemRes) + bus_release_resource(pDevice, SYS_RES_MEMORY, pState->iVMMDevMemResId, pState->pVMMDevMemRes); + if (pState->pIOPortRes) + bus_release_resource(pDevice, SYS_RES_IOPORT, pState->iIOPortResId, pState->pIOPortRes); + + VGDrvCommonDeleteDevExt(&g_DevExt); + + RTR0Term(); + + return 0; +} + + +/** + * Interrupt service routine. + * + * @returns Whether the interrupt was from VMMDev. + * @param pvState Opaque pointer to the device state. + */ +static int vgdrvFreeBSDISR(void *pvState) +{ + LogFlow(("vgdrvFreeBSDISR: pvState=%p\n", pvState)); + + bool fOurIRQ = VGDrvCommonISR(&g_DevExt); + + return fOurIRQ ? 0 : 1; +} + +void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt) +{ + LogFlow(("VGDrvNativeISRMousePollEvent:\n")); + + /* + * Wake up poll waiters. + */ + selwakeup(&g_SelInfo); +} + + +bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue) +{ + RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue); + return false; +} + + +/** + * Sets IRQ for VMMDev. + * + * @returns FreeBSD error code. + * @param pDevice Pointer to the device info structure. + * @param pvState Pointer to the state info structure. + */ +static int vgdrvFreeBSDAddIRQ(device_t pDevice, void *pvState) +{ + int iResId = 0; + int rc = 0; + struct VBoxGuestDeviceState *pState = (struct VBoxGuestDeviceState *)pvState; + + pState->pIrqRes = bus_alloc_resource_any(pDevice, SYS_RES_IRQ, &iResId, RF_SHAREABLE | RF_ACTIVE); + +#if __FreeBSD_version >= 700000 + rc = bus_setup_intr(pDevice, pState->pIrqRes, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)vgdrvFreeBSDISR, pState, + &pState->pfnIrqHandler); +#else + rc = bus_setup_intr(pDevice, pState->pIrqRes, INTR_TYPE_BIO, (driver_intr_t *)vgdrvFreeBSDISR, pState, &pState->pfnIrqHandler); +#endif + + if (rc) + { + pState->pfnIrqHandler = NULL; + return VERR_DEV_IO_ERROR; + } + + pState->iIrqResId = iResId; + + return VINF_SUCCESS; +} + +/** + * Removes IRQ for VMMDev. + * + * @param pDevice Pointer to the device info structure. + * @param pvState Opaque pointer to the state info structure. + */ +static void vgdrvFreeBSDRemoveIRQ(device_t pDevice, void *pvState) +{ + struct VBoxGuestDeviceState *pState = (struct VBoxGuestDeviceState *)pvState; + + if (pState->pIrqRes) + { + bus_teardown_intr(pDevice, pState->pIrqRes, pState->pfnIrqHandler); + bus_release_resource(pDevice, SYS_RES_IRQ, 0, pState->pIrqRes); + } +} + +static int vgdrvFreeBSDAttach(device_t pDevice) +{ + int rc; + int iResId; + struct VBoxGuestDeviceState *pState; + + cUsers = 0; + + /* + * Initialize IPRT R0 driver, which internally calls OS-specific r0 init. + */ + rc = RTR0Init(0); + if (RT_FAILURE(rc)) + { + LogFunc(("RTR0Init failed.\n")); + return ENXIO; + } + + pState = device_get_softc(pDevice); + + /* + * Allocate I/O port resource. + */ + iResId = PCIR_BAR(0); + pState->pIOPortRes = bus_alloc_resource_any(pDevice, SYS_RES_IOPORT, &iResId, RF_ACTIVE); + pState->uIOPortBase = rman_get_start(pState->pIOPortRes); + pState->iIOPortResId = iResId; + if (pState->uIOPortBase) + { + /* + * Map the MMIO region. + */ + iResId = PCIR_BAR(1); + pState->pVMMDevMemRes = bus_alloc_resource_any(pDevice, SYS_RES_MEMORY, &iResId, RF_ACTIVE); + pState->VMMDevMemHandle = rman_get_bushandle(pState->pVMMDevMemRes); + pState->VMMDevMemSize = rman_get_size(pState->pVMMDevMemRes); + + pState->pMMIOBase = rman_get_virtual(pState->pVMMDevMemRes); + pState->iVMMDevMemResId = iResId; + if (pState->pMMIOBase) + { + /* + * Call the common device extension initializer. + */ + rc = VGDrvCommonInitDevExt(&g_DevExt, pState->uIOPortBase, + pState->pMMIOBase, pState->VMMDevMemSize, +#if ARCH_BITS == 64 + VBOXOSTYPE_FreeBSD_x64, +#else + VBOXOSTYPE_FreeBSD, +#endif + VMMDEV_EVENT_MOUSE_POSITION_CHANGED); + if (RT_SUCCESS(rc)) + { + /* + * Add IRQ of VMMDev. + */ + rc = vgdrvFreeBSDAddIRQ(pDevice, pState); + if (RT_SUCCESS(rc)) + { + /* + * Read host configuration. + */ + VGDrvCommonProcessOptionsFromHost(&g_DevExt); + + /* + * Configure device cloning. + */ + clone_setup(&g_pvgdrvFreeBSDClones); + g_vgdrvFreeBSDEHTag = EVENTHANDLER_REGISTER(dev_clone, vgdrvFreeBSDClone, 0, 1000); + if (g_vgdrvFreeBSDEHTag) + { + printf(DEVICE_NAME ": loaded successfully\n"); + return 0; + } + + printf(DEVICE_NAME ": EVENTHANDLER_REGISTER(dev_clone,,,) failed\n"); + clone_cleanup(&g_pvgdrvFreeBSDClones); + vgdrvFreeBSDRemoveIRQ(pDevice, pState); + } + else + printf((DEVICE_NAME ": VGDrvCommonInitDevExt failed.\n")); + VGDrvCommonDeleteDevExt(&g_DevExt); + } + else + printf((DEVICE_NAME ": vgdrvFreeBSDAddIRQ failed.\n")); + } + else + printf((DEVICE_NAME ": MMIO region setup failed.\n")); + } + else + printf((DEVICE_NAME ": IOport setup failed.\n")); + + RTR0Term(); + return ENXIO; +} + +static int vgdrvFreeBSDProbe(device_t pDevice) +{ + if ((pci_get_vendor(pDevice) == VMMDEV_VENDORID) && (pci_get_device(pDevice) == VMMDEV_DEVICEID)) + return 0; + + return ENXIO; +} + +static device_method_t vgdrvFreeBSDMethods[] = +{ + /* Device interface. */ + DEVMETHOD(device_probe, vgdrvFreeBSDProbe), + DEVMETHOD(device_attach, vgdrvFreeBSDAttach), + DEVMETHOD(device_detach, vgdrvFreeBSDDetach), + {0,0} +}; + +static driver_t vgdrvFreeBSDDriver = +{ + DEVICE_NAME, + vgdrvFreeBSDMethods, + sizeof(struct VBoxGuestDeviceState), +}; + +static devclass_t vgdrvFreeBSDClass; + +DRIVER_MODULE(vboxguest, pci, vgdrvFreeBSDDriver, vgdrvFreeBSDClass, 0, 0); +MODULE_VERSION(vboxguest, 1); + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku-stubs.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku-stubs.c new file mode 100644 index 00000000..8981a5e9 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku-stubs.c @@ -0,0 +1,471 @@ +/* $Id: VBoxGuest-haiku-stubs.c $ */ +/** @file + * VBoxGuest kernel module, Haiku Guest Additions, stubs. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/* + * This code is based on: + * + * VirtualBox Guest Additions for Haiku. + * Copyright (c) 2011 Mike Smith <mike@scgtrp.net> + * François Revol <revol@free.fr> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/* + * This file provides stubs for calling VBox runtime functions through the vboxguest module. + * It should be linked into any driver or module that uses the VBox runtime, except vboxguest + * itself (which contains the actual library and therefore doesn't need stubs to call it). + */ + +#include "VBoxGuest-haiku.h" +#include "VBoxGuestInternal.h" +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/process.h> +#include <iprt/mem.h> +#include <iprt/asm.h> +#include <iprt/mp.h> +#include <iprt/power.h> +#include <iprt/thread.h> + +// >>> file('/tmp/stubs.c', 'w').writelines([re.sub(r'^(?P<returntype>[^(]+) \(\*_(?P<functionname>[A-Za-z0-9_]+)\)\((?P<params>[^)]+)\);', lambda m: '%s %s(%s)\n{\n %sg_VBoxGuest->_%s(%s);\n}\n' % (m.group(1), m.group(2), m.group(3), ('return ' if m.group(1) != 'void' else ''), m.group(2), (', '.join(a.split(' ')[-1].replace('*', '') for a in m.group(3).split(',')) if m.group(3) != 'void' else '')), f) for f in functions]) + +struct vboxguest_module_info *g_VBoxGuest; + +RTDECL(size_t) RTLogBackdoorPrintf(const char *pszFormat, ...) +{ + va_list args; + size_t cb; + + va_start(args, pszFormat); + cb = g_VBoxGuest->_RTLogBackdoorPrintf(pszFormat, args); + va_end(args); + + return cb; +} +RTDECL(size_t) RTLogBackdoorPrintfV(const char *pszFormat, va_list args) +{ + return g_VBoxGuest->_RTLogBackdoorPrintfV(pszFormat, args); +} +RTDECL(int) RTLogSetDefaultInstanceThread(PRTLOGGER pLogger, uintptr_t uKey) +{ + return g_VBoxGuest->_RTLogSetDefaultInstanceThread(pLogger, uKey); +} +RTDECL(int) RTMemAllocExTag(size_t cb, size_t cbAlignment, uint32_t fFlags, const char *pszTag, void **ppv) +{ + return g_VBoxGuest->_RTMemAllocExTag(cb, cbAlignment, fFlags, pszTag, ppv); +} +RTR0DECL(void*) RTMemContAlloc(PRTCCPHYS pPhys, size_t cb) +{ + return g_VBoxGuest->_RTMemContAlloc(pPhys, cb); +} +RTR0DECL(void) RTMemContFree(void *pv, size_t cb) +{ + g_VBoxGuest->_RTMemContFree(pv, cb); +} +RTDECL(void) RTMemFreeEx(void *pv, size_t cb) +{ + g_VBoxGuest->_RTMemFreeEx(pv, cb); +} +RTDECL(bool) RTMpIsCpuPossible(RTCPUID idCpu) +{ + return g_VBoxGuest->_RTMpIsCpuPossible(idCpu); +} +RTDECL(int) RTMpNotificationDeregister(PFNRTMPNOTIFICATION pfnCallback, void *pvUser) +{ + return g_VBoxGuest->_RTMpNotificationDeregister(pfnCallback, pvUser); +} +RTDECL(int) RTMpNotificationRegister(PFNRTMPNOTIFICATION pfnCallback, void *pvUser) +{ + return g_VBoxGuest->_RTMpNotificationRegister(pfnCallback, pvUser); +} +RTDECL(int) RTMpOnAll(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2) +{ + return g_VBoxGuest->_RTMpOnAll(pfnWorker, pvUser1, pvUser2); +} +RTDECL(int) RTMpOnOthers(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2) +{ + return g_VBoxGuest->_RTMpOnOthers(pfnWorker, pvUser1, pvUser2); +} +RTDECL(int) RTMpOnSpecific(RTCPUID idCpu, PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2) +{ + return g_VBoxGuest->_RTMpOnSpecific(idCpu, pfnWorker, pvUser1, pvUser2); +} +RTDECL(int) RTPowerNotificationDeregister(PFNRTPOWERNOTIFICATION pfnCallback, void *pvUser) +{ + return g_VBoxGuest->_RTPowerNotificationDeregister(pfnCallback, pvUser); +} +RTDECL(int) RTPowerNotificationRegister(PFNRTPOWERNOTIFICATION pfnCallback, void *pvUser) +{ + return g_VBoxGuest->_RTPowerNotificationRegister(pfnCallback, pvUser); +} +RTDECL(int) RTPowerSignalEvent(RTPOWEREVENT enmEvent) +{ + return g_VBoxGuest->_RTPowerSignalEvent(enmEvent); +} +RTR0DECL(void) RTR0AssertPanicSystem(void) +{ + g_VBoxGuest->_RTR0AssertPanicSystem(); +} +RTR0DECL(int) RTR0Init(unsigned fReserved) +{ + return g_VBoxGuest->_RTR0Init(fReserved); +} +RTR0DECL(void*) RTR0MemObjAddress(RTR0MEMOBJ MemObj) +{ + return g_VBoxGuest->_RTR0MemObjAddress(MemObj); +} +RTR0DECL(RTR3PTR) RTR0MemObjAddressR3(RTR0MEMOBJ MemObj) +{ + return g_VBoxGuest->_RTR0MemObjAddressR3(MemObj); +} +RTR0DECL(int) RTR0MemObjAllocContTag(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjAllocContTag(pMemObj, cb, fExecutable, pszTag); +} +RTR0DECL(int) RTR0MemObjAllocLowTag(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjAllocLowTag(pMemObj, cb, fExecutable, pszTag); +} +RTR0DECL(int) RTR0MemObjAllocPageTag(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjAllocPageTag(pMemObj, cb, fExecutable, pszTag); +} +RTR0DECL(int) RTR0MemObjAllocPhysExTag(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, size_t uAlignment, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjAllocPhysExTag(pMemObj, cb, PhysHighest, uAlignment, pszTag); +} +RTR0DECL(int) RTR0MemObjAllocPhysNCTag(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjAllocPhysNCTag(pMemObj, cb, PhysHighest, pszTag); +} +RTR0DECL(int) RTR0MemObjAllocPhysTag(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjAllocPhysTag(pMemObj, cb, PhysHighest, pszTag); +} +RTR0DECL(int) RTR0MemObjEnterPhysTag(PRTR0MEMOBJ pMemObj, RTHCPHYS Phys, size_t cb, uint32_t uCachePolicy, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjEnterPhysTag(pMemObj, Phys, cb, uCachePolicy, pszTag); +} +RTR0DECL(int) RTR0MemObjFree(RTR0MEMOBJ MemObj, bool fFreeMappings) +{ + return g_VBoxGuest->_RTR0MemObjFree(MemObj, fFreeMappings); +} +RTR0DECL(RTHCPHYS) RTR0MemObjGetPagePhysAddr(RTR0MEMOBJ MemObj, size_t iPage) +{ + return g_VBoxGuest->_RTR0MemObjGetPagePhysAddr(MemObj, iPage); +} +RTR0DECL(bool) RTR0MemObjIsMapping(RTR0MEMOBJ MemObj) +{ + return g_VBoxGuest->_RTR0MemObjIsMapping(MemObj); +} +RTR0DECL(int) RTR0MemObjLockKernelTag(PRTR0MEMOBJ pMemObj, void *pv, size_t cb, uint32_t fAccess, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjLockKernelTag(pMemObj, pv, cb, fAccess, pszTag); +} +RTR0DECL(int) RTR0MemObjLockUserTag(PRTR0MEMOBJ pMemObj, RTR3PTR R3Ptr, size_t cb, uint32_t fAccess, RTR0PROCESS R0Process, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjLockUserTag(pMemObj, R3Ptr, cb, fAccess, R0Process, pszTag); +} +RTR0DECL(int) RTR0MemObjMapKernelExTag(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, void *pvFixed, size_t uAlignment, unsigned fProt, size_t offSub, size_t cbSub, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjMapKernelExTag(pMemObj, MemObjToMap, pvFixed, uAlignment, fProt, offSub, cbSub, pszTag); +} +RTR0DECL(int) RTR0MemObjMapKernelTag(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, void *pvFixed, size_t uAlignment, unsigned fProt, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjMapKernelTag(pMemObj, MemObjToMap, pvFixed, uAlignment, fProt, pszTag); +} +RTR0DECL(int) RTR0MemObjMapUserTag(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, RTR3PTR R3PtrFixed, size_t uAlignment, unsigned fProt, RTR0PROCESS R0Process, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjMapUserTag(pMemObj, MemObjToMap, R3PtrFixed, uAlignment, fProt, R0Process, pszTag); +} +RTR0DECL(int) RTR0MemObjProtect(RTR0MEMOBJ hMemObj, size_t offSub, size_t cbSub, uint32_t fProt) +{ + return g_VBoxGuest->_RTR0MemObjProtect(hMemObj, offSub, cbSub, fProt); +} +RTR0DECL(int) RTR0MemObjReserveKernelTag(PRTR0MEMOBJ pMemObj, void *pvFixed, size_t cb, size_t uAlignment, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjReserveKernelTag(pMemObj, pvFixed, cb, uAlignment, pszTag); +} +RTR0DECL(int) RTR0MemObjReserveUserTag(PRTR0MEMOBJ pMemObj, RTR3PTR R3PtrFixed, size_t cb, size_t uAlignment, RTR0PROCESS R0Process, const char *pszTag) +{ + return g_VBoxGuest->_RTR0MemObjReserveUserTag(pMemObj, R3PtrFixed, cb, uAlignment, R0Process, pszTag); +} +RTR0DECL(size_t) RTR0MemObjSize(RTR0MEMOBJ MemObj) +{ + return g_VBoxGuest->_RTR0MemObjSize(MemObj); +} +RTR0DECL(RTR0PROCESS) RTR0ProcHandleSelf(void) +{ + return g_VBoxGuest->_RTR0ProcHandleSelf(); +} +RTR0DECL(void) RTR0Term(void) +{ + g_VBoxGuest->_RTR0Term(); +} +RTR0DECL(void) RTR0TermForced(void) +{ + g_VBoxGuest->_RTR0TermForced(); +} +RTDECL(RTPROCESS) RTProcSelf(void) +{ + return g_VBoxGuest->_RTProcSelf(); +} +RTDECL(uint32_t) RTSemEventGetResolution(void) +{ + return g_VBoxGuest->_RTSemEventGetResolution(); +} +RTDECL(uint32_t) RTSemEventMultiGetResolution(void) +{ + return g_VBoxGuest->_RTSemEventMultiGetResolution(); +} +RTDECL(int) RTSemEventMultiWaitEx(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout) +{ + return g_VBoxGuest->_RTSemEventMultiWaitEx(hEventMultiSem, fFlags, uTimeout); +} +RTDECL(int) RTSemEventMultiWaitExDebug(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + return g_VBoxGuest->_RTSemEventMultiWaitExDebug(hEventMultiSem, fFlags, uTimeout, uId, pszFile, iLine, pszFunction); +} +RTDECL(int) RTSemEventWaitEx(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout) +{ + return g_VBoxGuest->_RTSemEventWaitEx(hEventSem, fFlags, uTimeout); +} +RTDECL(int) RTSemEventWaitExDebug(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout, RTHCUINTPTR uId, RT_SRC_POS_DECL) +{ + return g_VBoxGuest->_RTSemEventWaitExDebug(hEventSem, fFlags, uTimeout, uId, pszFile, iLine, pszFunction); +} +RTDECL(bool) RTThreadIsInInterrupt(RTTHREAD hThread) +{ + return g_VBoxGuest->_RTThreadIsInInterrupt(hThread); +} +RTDECL(void) RTThreadPreemptDisable(PRTTHREADPREEMPTSTATE pState) +{ + g_VBoxGuest->_RTThreadPreemptDisable(pState); +} +RTDECL(bool) RTThreadPreemptIsEnabled(RTTHREAD hThread) +{ + return g_VBoxGuest->_RTThreadPreemptIsEnabled(hThread); +} +RTDECL(bool) RTThreadPreemptIsPending(RTTHREAD hThread) +{ + return g_VBoxGuest->_RTThreadPreemptIsPending(hThread); +} +RTDECL(bool) RTThreadPreemptIsPendingTrusty(void) +{ + return g_VBoxGuest->_RTThreadPreemptIsPendingTrusty(); +} +RTDECL(bool) RTThreadPreemptIsPossible(void) +{ + return g_VBoxGuest->_RTThreadPreemptIsPossible(); +} +RTDECL(void) RTThreadPreemptRestore(PRTTHREADPREEMPTSTATE pState) +{ + g_VBoxGuest->_RTThreadPreemptRestore(pState); +} +RTDECL(uint32_t) RTTimerGetSystemGranularity(void) +{ + return g_VBoxGuest->_RTTimerGetSystemGranularity(); +} +RTDECL(int) RTTimerReleaseSystemGranularity(uint32_t u32Granted) +{ + return g_VBoxGuest->_RTTimerReleaseSystemGranularity(u32Granted); +} +RTDECL(int) RTTimerRequestSystemGranularity(uint32_t u32Request, uint32_t *pu32Granted) +{ + return g_VBoxGuest->_RTTimerRequestSystemGranularity(u32Request, pu32Granted); +} +RTDECL(void) RTSpinlockAcquire(RTSPINLOCK Spinlock) +{ + g_VBoxGuest->_RTSpinlockAcquire(Spinlock); +} +RTDECL(void) RTSpinlockRelease(RTSPINLOCK Spinlock) +{ + g_VBoxGuest->_RTSpinlockRelease(Spinlock); +} +RTDECL(void*) RTMemTmpAllocTag(size_t cb, const char *pszTag) +{ + return g_VBoxGuest->_RTMemTmpAllocTag(cb, pszTag); +} +RTDECL(void) RTMemTmpFree(void *pv) +{ + g_VBoxGuest->_RTMemTmpFree(pv); +} +RTDECL(PRTLOGGER) RTLogDefaultInstance(void) +{ + return g_VBoxGuest->_RTLogDefaultInstance(); +} +RTDECL(PRTLOGGER) RTLogDefaultInstanceEx(uint32_t fFlagsAndGroup) +{ + return g_VBoxGuest->_RTLogDefaultInstanceEx(fFlagsAndGroup); +} +RTDECL(PRTLOGGER) RTLogRelGetDefaultInstance(void) +{ + return g_VBoxGuest->_RTLogRelGetDefaultInstance(); +} +RTDECL(PRTLOGGER) RTLogRelGetDefaultInstance(uint32_t fFlags, uint32_t iGroup) +{ + return g_VBoxGuest->_RTLogRelGetDefaultInstanceEx(fFlags, iGroup); +} +RTDECL(int) RTErrConvertToErrno(int iErr) +{ + return g_VBoxGuest->_RTErrConvertToErrno(iErr); +} +int VGDrvCommonIoCtl(unsigned iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, void *pvData, size_t cbData, size_t *pcbDataReturned) +{ + return g_VBoxGuest->_VGDrvCommonIoCtl(iFunction, pDevExt, pSession, pvData, cbData, pcbDataReturned); +} +int VGDrvCommonCreateUserSession(PVBOXGUESTDEVEXT pDevExt, uint32_t fRequestor, PVBOXGUESTSESSION *ppSession) +{ + return g_VBoxGuest->_VGDrvCommonCreateUserSession(pDevExt, fRequestor, ppSession); +} +void VGDrvCommonCloseSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession) +{ + g_VBoxGuest->_VGDrvCommonCloseSession(pDevExt, pSession); +} +void* VBoxGuestIDCOpen(uint32_t *pu32Version) +{ + return g_VBoxGuest->_VBoxGuestIDCOpen(pu32Version); +} +int VBoxGuestIDCClose(void *pvSession) +{ + return g_VBoxGuest->_VBoxGuestIDCClose(pvSession); +} +int VBoxGuestIDCCall(void *pvSession, unsigned iCmd, void *pvData, size_t cbData, size_t *pcbDataReturned) +{ + return g_VBoxGuest->_VBoxGuestIDCCall(pvSession, iCmd, pvData, cbData, pcbDataReturned); +} +RTDECL(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction) +{ + g_VBoxGuest->_RTAssertMsg1Weak(pszExpr, uLine, pszFile, pszFunction); +} +RTDECL(void) RTAssertMsg2Weak(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + RTAssertMsg2WeakV(pszFormat, va); + va_end(va); +} +RTDECL(void) RTAssertMsg2WeakV(const char *pszFormat, va_list va) +{ + g_VBoxGuest->_RTAssertMsg2WeakV(pszFormat, va); +} +RTDECL(bool) RTAssertShouldPanic(void) +{ + return g_VBoxGuest->_RTAssertShouldPanic(); +} +RTDECL(int) RTSemFastMutexCreate(PRTSEMFASTMUTEX phFastMtx) +{ + return g_VBoxGuest->_RTSemFastMutexCreate(phFastMtx); +} +RTDECL(int) RTSemFastMutexDestroy(RTSEMFASTMUTEX hFastMtx) +{ + return g_VBoxGuest->_RTSemFastMutexDestroy(hFastMtx); +} +RTDECL(int) RTSemFastMutexRelease(RTSEMFASTMUTEX hFastMtx) +{ + return g_VBoxGuest->_RTSemFastMutexRelease(hFastMtx); +} +RTDECL(int) RTSemFastMutexRequest(RTSEMFASTMUTEX hFastMtx) +{ + return g_VBoxGuest->_RTSemFastMutexRequest(hFastMtx); +} +RTDECL(int) RTSemMutexCreate(PRTSEMMUTEX phFastMtx) +{ + return g_VBoxGuest->_RTSemMutexCreate(phFastMtx); +} +RTDECL(int) RTSemMutexDestroy(RTSEMMUTEX hFastMtx) +{ + return g_VBoxGuest->_RTSemMutexDestroy(hFastMtx); +} +RTDECL(int) RTSemMutexRelease(RTSEMMUTEX hFastMtx) +{ + return g_VBoxGuest->_RTSemMutexRelease(hFastMtx); +} +RTDECL(int) RTSemMutexRequest(RTSEMMUTEX hFastMtx, RTMSINTERVAL cMillies) +{ + return g_VBoxGuest->_RTSemMutexRequest(hFastMtx, cMillies); +} +int RTHeapSimpleRelocate(RTHEAPSIMPLE hHeap, uintptr_t offDelta) +{ + return g_VBoxGuest->_RTHeapSimpleRelocate(hHeap, offDelta); +} +int RTHeapOffsetInit(PRTHEAPOFFSET phHeap, void *pvMemory, size_t cbMemory) +{ + return g_VBoxGuest->_RTHeapOffsetInit(phHeap, pvMemory, cbMemory); +} +int RTHeapSimpleInit(PRTHEAPSIMPLE pHeap, void *pvMemory, size_t cbMemory) +{ + return g_VBoxGuest->_RTHeapSimpleInit(pHeap, pvMemory, cbMemory); +} +void* RTHeapOffsetAlloc(RTHEAPOFFSET hHeap, size_t cb, size_t cbAlignment) +{ + return g_VBoxGuest->_RTHeapOffsetAlloc(hHeap, cb, cbAlignment); +} +void* RTHeapSimpleAlloc(RTHEAPSIMPLE Heap, size_t cb, size_t cbAlignment) +{ + return g_VBoxGuest->_RTHeapSimpleAlloc(Heap, cb, cbAlignment); +} +void RTHeapOffsetFree(RTHEAPOFFSET hHeap, void *pv) +{ + g_VBoxGuest->_RTHeapOffsetFree(hHeap, pv); +} +void RTHeapSimpleFree(RTHEAPSIMPLE Heap, void *pv) +{ + g_VBoxGuest->_RTHeapSimpleFree(Heap, pv); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.c new file mode 100644 index 00000000..2a872032 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.c @@ -0,0 +1,588 @@ +/* $Id: VBoxGuest-haiku.c $ */ +/** @file + * VBoxGuest kernel module, Haiku Guest Additions, implementation. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/* + * This code is based on: + * + * VirtualBox Guest Additions for Haiku. + * Copyright (c) 2011 Mike Smith <mike@scgtrp.net> + * François Revol <revol@free.fr> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IN_VBOXGUEST +#include <sys/param.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <OS.h> +#include <Drivers.h> +#include <KernelExport.h> +#include <PCI.h> + +#include "VBoxGuest-haiku.h" +#include "VBoxGuestInternal.h" +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/process.h> +#include <iprt/mem.h> +#include <iprt/memobj.h> +#include <iprt/asm.h> +#include <iprt/timer.h> +#include <iprt/heap.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define MODULE_NAME VBOXGUEST_MODULE_NAME + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/* + * IRQ related functions. + */ +static void vgdrvHaikuRemoveIRQ(void *pvState); +static int vgdrvHaikuAddIRQ(void *pvState); +static int32 vgdrvHaikuISR(void *pvState); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static status_t std_ops(int32 op, ...); + +static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK; + +int32 api_version = B_CUR_DRIVER_API_VERSION; + +/** List of cloned device. Managed by the kernel. */ +//static struct clonedevs *g_pvgdrvHaikuClones; +/** The dev_clone event handler tag. */ +//static eventhandler_tag g_vgdrvHaikuEHTag; +/** selinfo structure used for polling. */ +//static struct selinfo g_SelInfo; +/** PCI Bus Manager Module */ +static pci_module_info *gPCI; + +static struct vboxguest_module_info g_VBoxGuest = +{ + { + MODULE_NAME, + 0, + std_ops + }, + { 0 }, + { 0 }, + 0, + RTLogBackdoorPrintf, + RTLogBackdoorPrintfV, + RTLogSetDefaultInstanceThread, + RTMemAllocExTag, + RTMemContAlloc, + RTMemContFree, + RTMemFreeEx, + RTMpIsCpuPossible, + RTMpNotificationDeregister, + RTMpNotificationRegister, + RTMpOnAll, + RTMpOnOthers, + RTMpOnSpecific, + RTPowerNotificationDeregister, + RTPowerNotificationRegister, + RTPowerSignalEvent, + RTR0AssertPanicSystem, + RTR0Init, + RTR0MemObjAddress, + RTR0MemObjAddressR3, + RTR0MemObjAllocContTag, + RTR0MemObjAllocLowTag, + RTR0MemObjAllocPageTag, + RTR0MemObjAllocPhysExTag, + RTR0MemObjAllocPhysNCTag, + RTR0MemObjAllocPhysTag, + RTR0MemObjEnterPhysTag, + RTR0MemObjFree, + RTR0MemObjGetPagePhysAddr, + RTR0MemObjIsMapping, + RTR0MemObjLockKernelTag, + RTR0MemObjLockUserTag, + RTR0MemObjMapKernelExTag, + RTR0MemObjMapKernelTag, + RTR0MemObjMapUserTag, + RTR0MemObjProtect, + RTR0MemObjReserveKernelTag, + RTR0MemObjReserveUserTag, + RTR0MemObjSize, + RTR0ProcHandleSelf, + RTR0Term, + RTR0TermForced, + RTProcSelf, + RTSemEventGetResolution, + RTSemEventMultiGetResolution, + RTSemEventMultiWaitEx, + RTSemEventMultiWaitExDebug, + RTSemEventWaitEx, + RTSemEventWaitExDebug, + RTThreadIsInInterrupt, + RTThreadPreemptDisable, + RTThreadPreemptIsEnabled, + RTThreadPreemptIsPending, + RTThreadPreemptIsPendingTrusty, + RTThreadPreemptIsPossible, + RTThreadPreemptRestore, + RTTimerGetSystemGranularity, + RTTimerReleaseSystemGranularity, + RTTimerRequestSystemGranularity, + RTSpinlockAcquire, + RTSpinlockRelease, + RTMemTmpAllocTag, + RTMemTmpFree, + RTLogDefaultInstance, + RTLogDefaultInstanceEx, + RTLogRelGetDefaultInstance, + RTLogRelGetDefaultInstanceEx, + RTErrConvertToErrno, + VGDrvCommonIoCtl, + VGDrvCommonCreateUserSession, + VGDrvCommonCloseSession, + VBoxGuestIDCOpen, + VBoxGuestIDCClose, + VBoxGuestIDCCall, + RTAssertMsg1Weak, + RTAssertMsg2Weak, + RTAssertMsg2WeakV, + RTAssertShouldPanic, + RTSemFastMutexCreate, + RTSemFastMutexDestroy, + RTSemFastMutexRelease, + RTSemFastMutexRequest, + RTSemMutexCreate, + RTSemMutexDestroy, + RTSemMutexRelease, + RTSemMutexRequest, + RTHeapSimpleRelocate, + RTHeapOffsetInit, + RTHeapSimpleInit, + RTHeapOffsetAlloc, + RTHeapSimpleAlloc, + RTHeapOffsetFree, + RTHeapSimpleFree +}; + +#if 0 +/** + * DEVFS event handler. + */ +static void vgdrvHaikuClone(void *pvArg, struct ucred *pCred, char *pszName, int cchName, struct cdev **ppDev) +{ + int iUnit; + int rc; + + Log(("vgdrvHaikuClone: pszName=%s ppDev=%p\n", pszName, ppDev)); + + /* + * One device node per user, si_drv1 points to the session. + * /dev/vboxguest<N> where N = {0...255}. + */ + if (!ppDev) + return; + if (strcmp(pszName, "vboxguest") == 0) + iUnit = -1; + else if (dev_stdclone(pszName, NULL, "vboxguest", &iUnit) != 1) + return; + if (iUnit >= 256) + { + Log(("vgdrvHaikuClone: iUnit=%d >= 256 - rejected\n", iUnit)); + return; + } + + Log(("vgdrvHaikuClone: pszName=%s iUnit=%d\n", pszName, iUnit)); + + rc = clone_create(&g_pvgdrvHaikuClones, &g_vgdrvHaikuDeviceHooks, &iUnit, ppDev, 0); + Log(("vgdrvHaikuClone: clone_create -> %d; iUnit=%d\n", rc, iUnit)); + if (rc) + { + *ppDev = make_dev(&g_vgdrvHaikuDeviceHooks, + iUnit, + UID_ROOT, + GID_WHEEL, + 0644, + "vboxguest%d", iUnit); + if (*ppDev) + { + dev_ref(*ppDev); + (*ppDev)->si_flags |= SI_CHEAPCLONE; + Log(("vgdrvHaikuClone: Created *ppDev=%p iUnit=%d si_drv1=%p si_drv2=%p\n", + *ppDev, iUnit, (*ppDev)->si_drv1, (*ppDev)->si_drv2)); + (*ppDev)->si_drv1 = (*ppDev)->si_drv2 = NULL; + } + else + Log(("vgdrvHaikuClone: make_dev iUnit=%d failed\n", iUnit)); + } + else + Log(("vgdrvHaikuClone: Existing *ppDev=%p iUnit=%d si_drv1=%p si_drv2=%p\n", + *ppDev, iUnit, (*ppDev)->si_drv1, (*ppDev)->si_drv2)); +} +#endif + + +static status_t vgdrvHaikuDetach(void) +{ + struct VBoxGuestDeviceState *pState = &sState; + + if (cUsers > 0) + return EBUSY; + + /* + * Reverse what we did in vgdrvHaikuAttach. + */ + vgdrvHaikuRemoveIRQ(pState); + + if (pState->iVMMDevMemAreaId) + delete_area(pState->iVMMDevMemAreaId); + + VGDrvCommonDeleteDevExt(&g_DevExt); + +#ifdef DO_LOG + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogSetDefaultInstance(NULL); +// RTLogDestroy(RTLogSetDefaultInstance(NULL)); +#endif + + RTSpinlockDestroy(g_Spinlock); + g_Spinlock = NIL_RTSPINLOCK; + + RTR0Term(); + return B_OK; +} + + +/** + * Interrupt service routine. + * + * @returns Whether the interrupt was from VMMDev. + * @param pvState Opaque pointer to the device state. + */ +static int32 vgdrvHaikuISR(void *pvState) +{ + LogFlow((MODULE_NAME ":vgdrvHaikuISR pvState=%p\n", pvState)); + + bool fOurIRQ = VGDrvCommonISR(&g_DevExt); + if (fOurIRQ) + return B_HANDLED_INTERRUPT; + return B_UNHANDLED_INTERRUPT; +} + + +void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt) +{ + LogFlow(("VGDrvNativeISRMousePollEvent:\n")); + + status_t err = B_OK; + //dprintf(MODULE_NAME ": isr mouse\n"); + + /* + * Wake up poll waiters. + */ + //selwakeup(&g_SelInfo); + //XXX:notify_select_event(); + RTSpinlockAcquire(g_Spinlock); + + if (sState.selectSync) + { + //dprintf(MODULE_NAME ": isr mouse: notify\n"); + notify_select_event(sState.selectSync, sState.selectEvent); + sState.selectEvent = (uint8_t)0; + sState.selectRef = (uint32_t)0; + sState.selectSync = NULL; + } + else + err = B_ERROR; + + RTSpinlockRelease(g_Spinlock); +} + + +bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue) +{ + RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue); + return false; +} + + +/** + * Sets IRQ for VMMDev. + * + * @returns Haiku error code. + * @param pvState Pointer to the state info structure. + */ +static int vgdrvHaikuAddIRQ(void *pvState) +{ + status_t err; + struct VBoxGuestDeviceState *pState = (struct VBoxGuestDeviceState *)pvState; + + AssertReturn(pState, VERR_INVALID_PARAMETER); + + err = install_io_interrupt_handler(pState->iIrqResId, vgdrvHaikuISR, pState, 0); + if (err == B_OK) + return VINF_SUCCESS; + return VERR_DEV_IO_ERROR; +} + + +/** + * Removes IRQ for VMMDev. + * + * @param pvState Opaque pointer to the state info structure. + */ +static void vgdrvHaikuRemoveIRQ(void *pvState) +{ + struct VBoxGuestDeviceState *pState = (struct VBoxGuestDeviceState *)pvState; + AssertPtr(pState); + + remove_io_interrupt_handler(pState->iIrqResId, vgdrvHaikuISR, pState); +} + + +static status_t vgdrvHaikuAttach(const pci_info *pDevice) +{ + status_t status; + int rc; + int iResId; + struct VBoxGuestDeviceState *pState = &sState; + static const char *const s_apszGroups[] = VBOX_LOGGROUP_NAMES; + PRTLOGGER pRelLogger; + + AssertReturn(pDevice, B_BAD_VALUE); + + cUsers = 0; + + /* + * Initialize IPRT R0 driver, which internally calls OS-specific r0 init. + */ + rc = RTR0Init(0); + if (RT_FAILURE(rc)) + { + dprintf(MODULE_NAME ": RTR0Init failed: %d\n", rc); + return ENXIO; + } + + rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "vgdrvHaiku"); + if (RT_FAILURE(rc)) + { + LogRel(("vgdrvHaikuAttach: RTSpinlock create failed. rc=%Rrc\n", rc)); + return ENXIO; + } + +#ifdef DO_LOG + /* + * Create the release log. + * (We do that here instead of common code because we want to log + * early failures using the LogRel macro.) + */ + rc = RTLogCreate(&pRelLogger, 0 | RTLOGFLAGS_PREFIX_THREAD /* fFlags */, "all", + "VBOX_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups, + RTLOGDEST_STDOUT | RTLOGDEST_DEBUGGER | RTLOGDEST_USER, NULL); + dprintf(MODULE_NAME ": RTLogCreate: %d\n", rc); + if (RT_SUCCESS(rc)) + { + //RTLogGroupSettings(pRelLogger, g_szLogGrp); + //RTLogFlags(pRelLogger, g_szLogFlags); + //RTLogDestinations(pRelLogger, "/var/log/vboxguest.log"); + RTLogRelSetDefaultInstance(pRelLogger); + RTLogSetDefaultInstance(pRelLogger); //XXX + } +#endif + + /* + * Allocate I/O port resource. + */ + pState->uIOPortBase = pDevice->u.h0.base_registers[0]; + /** @todo check flags for IO? */ + if (pState->uIOPortBase) + { + /* + * Map the MMIO region. + */ + uint32 phys = pDevice->u.h0.base_registers[1]; + /** @todo Check flags for mem? */ + pState->VMMDevMemSize = pDevice->u.h0.base_register_sizes[1]; + pState->iVMMDevMemAreaId = map_physical_memory("VirtualBox Guest MMIO", phys, pState->VMMDevMemSize, + B_ANY_KERNEL_BLOCK_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, + &pState->pMMIOBase); + if (pState->iVMMDevMemAreaId > 0 && pState->pMMIOBase) + { + /* + * Call the common device extension initializer. + */ + rc = VGDrvCommonInitDevExt(&g_DevExt, pState->uIOPortBase, pState->pMMIOBase, pState->VMMDevMemSize, +#if ARCH_BITS == 64 + VBOXOSTYPE_Haiku_x64, +#else + VBOXOSTYPE_Haiku, +#endif + VMMDEV_EVENT_MOUSE_POSITION_CHANGED); + if (RT_SUCCESS(rc)) + { + /* + * Add IRQ of VMMDev. + */ + pState->iIrqResId = pDevice->u.h0.interrupt_line; + rc = vgdrvHaikuAddIRQ(pState); + if (RT_SUCCESS(rc)) + { + /* + * Read host configuration. + */ + VGDrvCommonProcessOptionsFromHost(&g_DevExt); + + LogRel((MODULE_NAME ": loaded successfully\n")); + return B_OK; + } + + LogRel((MODULE_NAME ": VGDrvCommonInitDevExt failed.\n")); + VGDrvCommonDeleteDevExt(&g_DevExt); + } + else + LogRel((MODULE_NAME ": vgdrvHaikuAddIRQ failed.\n")); + } + else + LogRel((MODULE_NAME ": MMIO region setup failed.\n")); + } + else + LogRel((MODULE_NAME ": IOport setup failed.\n")); + + RTR0Term(); + return ENXIO; +} + + +static status_t vgdrvHaikuProbe(pci_info *pDevice) +{ + if ( pDevice->vendor_id == VMMDEV_VENDORID + && pDevice->device_id == VMMDEV_DEVICEID) + return B_OK; + + return ENXIO; +} + + +status_t init_module(void) +{ + status_t err = B_ENTRY_NOT_FOUND; + pci_info info; + int ix = 0; + + err = get_module(B_PCI_MODULE_NAME, (module_info **)&gPCI); + if (err != B_OK) + return err; + + while ((*gPCI->get_nth_pci_info)(ix++, &info) == B_OK) + { + if (vgdrvHaikuProbe(&info) == 0) + { + /* We found it */ + err = vgdrvHaikuAttach(&info); + return err; + } + } + + return B_ENTRY_NOT_FOUND; +} + + +void uninit_module(void) +{ + vgdrvHaikuDetach(); + put_module(B_PCI_MODULE_NAME); +} + + +static status_t std_ops(int32 op, ...) +{ + switch (op) + { + case B_MODULE_INIT: + return init_module(); + + case B_MODULE_UNINIT: + { + uninit_module(); + return B_OK; + } + + default: + return B_ERROR; + } +} + + +_EXPORT module_info *modules[] = +{ + (module_info *)&g_VBoxGuest, + NULL +}; + +/* Common code that depend on g_DevExt. */ +#include "VBoxGuestIDC-unix.c.h" + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.h b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.h new file mode 100644 index 00000000..a5fa58d6 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.h @@ -0,0 +1,248 @@ +/* $Id: VBoxGuest-haiku.h $ */ +/** @file + * VBoxGuest kernel module, Haiku Guest Additions, header. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/* + * This code is based on: + * + * VirtualBox Guest Additions for Haiku. + * Copyright (c) 2011 Mike Smith <mike@scgtrp.net> + * François Revol <revol@free.fr> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxGuest_VBoxGuest_haiku_h +#define GA_INCLUDED_SRC_common_VBoxGuest_VBoxGuest_haiku_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <OS.h> +#include <Drivers.h> +#include <drivers/module.h> + +#include "VBoxGuestInternal.h" +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/process.h> +#include <iprt/mem.h> +#include <iprt/asm.h> +#include <iprt/mp.h> +#include <iprt/power.h> +#include <iprt/thread.h> + +/** The module name. */ +#define VBOXGUEST_MODULE_NAME "generic/vboxguest" + +struct VBoxGuestDeviceState +{ + /** Resource ID of the I/O port */ + int iIOPortResId; + /** Pointer to the I/O port resource. */ +// struct resource *pIOPortRes; + /** Start address of the IO Port. */ + uint16_t uIOPortBase; + /** Resource ID of the MMIO area */ + area_id iVMMDevMemAreaId; + /** Pointer to the MMIO resource. */ +// struct resource *pVMMDevMemRes; + /** Handle of the MMIO resource. */ +// bus_space_handle_t VMMDevMemHandle; + /** Size of the memory area. */ + size_t VMMDevMemSize; + /** Mapping of the register space */ + void *pMMIOBase; + /** IRQ number */ + int iIrqResId; + /** IRQ resource handle. */ +// struct resource *pIrqRes; + /** Pointer to the IRQ handler. */ +// void *pfnIrqHandler; + /** VMMDev version */ + uint32_t u32Version; + + /** The (only) select data we wait on. */ + //XXX: should leave in pSession ? + uint8_t selectEvent; + uint32_t selectRef; + void *selectSync; +}; + +struct vboxguest_module_info +{ + module_info module; + + VBOXGUESTDEVEXT devExt; + struct VBoxGuestDeviceState _sState; + volatile uint32_t _cUsers; + + size_t(*_RTLogBackdoorPrintf)(const char *pszFormat, ...); + size_t(*_RTLogBackdoorPrintfV)(const char *pszFormat, va_list args); + int (*_RTLogSetDefaultInstanceThread)(PRTLOGGER pLogger, uintptr_t uKey); + int (*_RTMemAllocExTag)(size_t cb, size_t cbAlignment, uint32_t fFlags, const char *pszTag, void **ppv); + void* (*_RTMemContAlloc)(PRTCCPHYS pPhys, size_t cb); + void (*_RTMemContFree)(void *pv, size_t cb); + void (*_RTMemFreeEx)(void *pv, size_t cb); + bool (*_RTMpIsCpuPossible)(RTCPUID idCpu); + int (*_RTMpNotificationDeregister)(PFNRTMPNOTIFICATION pfnCallback, void *pvUser); + int (*_RTMpNotificationRegister)(PFNRTMPNOTIFICATION pfnCallback, void *pvUser); + int (*_RTMpOnAll)(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2); + int (*_RTMpOnOthers)(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2); + int (*_RTMpOnSpecific)(RTCPUID idCpu, PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2); + int (*_RTPowerNotificationDeregister)(PFNRTPOWERNOTIFICATION pfnCallback, void *pvUser); + int (*_RTPowerNotificationRegister)(PFNRTPOWERNOTIFICATION pfnCallback, void *pvUser); + int (*_RTPowerSignalEvent)(RTPOWEREVENT enmEvent); + void (*_RTR0AssertPanicSystem)(void); + int (*_RTR0Init)(unsigned fReserved); + void* (*_RTR0MemObjAddress)(RTR0MEMOBJ MemObj); + RTR3PTR(*_RTR0MemObjAddressR3)(RTR0MEMOBJ MemObj); + int (*_RTR0MemObjAllocContTag)(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag); + int (*_RTR0MemObjAllocLowTag)(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag); + int (*_RTR0MemObjAllocPageTag)(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag); + int (*_RTR0MemObjAllocPhysExTag)(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, size_t uAlignment, const char *pszTag); + int (*_RTR0MemObjAllocPhysNCTag)(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, const char *pszTag); + int (*_RTR0MemObjAllocPhysTag)(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, const char *pszTag); + int (*_RTR0MemObjEnterPhysTag)(PRTR0MEMOBJ pMemObj, RTHCPHYS Phys, size_t cb, uint32_t uCachePolicy, const char *pszTag); + int (*_RTR0MemObjFree)(RTR0MEMOBJ MemObj, bool fFreeMappings); + RTHCPHYS(*_RTR0MemObjGetPagePhysAddr)(RTR0MEMOBJ MemObj, size_t iPage); + bool (*_RTR0MemObjIsMapping)(RTR0MEMOBJ MemObj); + int (*_RTR0MemObjLockKernelTag)(PRTR0MEMOBJ pMemObj, void *pv, size_t cb, uint32_t fAccess, const char *pszTag); + int (*_RTR0MemObjLockUserTag)(PRTR0MEMOBJ pMemObj, RTR3PTR R3Ptr, size_t cb, uint32_t fAccess, + RTR0PROCESS R0Process, const char *pszTag); + int (*_RTR0MemObjMapKernelExTag)(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, void *pvFixed, size_t uAlignment, + unsigned fProt, size_t offSub, size_t cbSub, const char *pszTag); + int (*_RTR0MemObjMapKernelTag)(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, void *pvFixed, + size_t uAlignment, unsigned fProt, const char *pszTag); + int (*_RTR0MemObjMapUserTag)(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, RTR3PTR R3PtrFixed, + size_t uAlignment, unsigned fProt, RTR0PROCESS R0Process, const char *pszTag); + int (*_RTR0MemObjProtect)(RTR0MEMOBJ hMemObj, size_t offSub, size_t cbSub, uint32_t fProt); + int (*_RTR0MemObjReserveKernelTag)(PRTR0MEMOBJ pMemObj, void *pvFixed, size_t cb, size_t uAlignment, const char *pszTag); + int (*_RTR0MemObjReserveUserTag)(PRTR0MEMOBJ pMemObj, RTR3PTR R3PtrFixed, size_t cb, size_t uAlignment, + RTR0PROCESS R0Process, const char *pszTag); + size_t(*_RTR0MemObjSize)(RTR0MEMOBJ MemObj); + RTR0PROCESS(*_RTR0ProcHandleSelf)(void); + void (*_RTR0Term)(void); + void (*_RTR0TermForced)(void); + RTPROCESS(*_RTProcSelf)(void); + uint32_t(*_RTSemEventGetResolution)(void); + uint32_t(*_RTSemEventMultiGetResolution)(void); + int (*_RTSemEventMultiWaitEx)(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout); + int (*_RTSemEventMultiWaitExDebug)(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout, + RTHCUINTPTR uId, RT_SRC_POS_DECL); + int (*_RTSemEventWaitEx)(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout); + int (*_RTSemEventWaitExDebug)(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout, + RTHCUINTPTR uId, RT_SRC_POS_DECL); + bool (*_RTThreadIsInInterrupt)(RTTHREAD hThread); + void (*_RTThreadPreemptDisable)(PRTTHREADPREEMPTSTATE pState); + bool (*_RTThreadPreemptIsEnabled)(RTTHREAD hThread); + bool (*_RTThreadPreemptIsPending)(RTTHREAD hThread); + bool (*_RTThreadPreemptIsPendingTrusty)(void); + bool (*_RTThreadPreemptIsPossible)(void); + void (*_RTThreadPreemptRestore)(PRTTHREADPREEMPTSTATE pState); + uint32_t(*_RTTimerGetSystemGranularity)(void); + int (*_RTTimerReleaseSystemGranularity)(uint32_t u32Granted); + int (*_RTTimerRequestSystemGranularity)(uint32_t u32Request, uint32_t *pu32Granted); + void (*_RTSpinlockAcquire)(RTSPINLOCK Spinlock); + void (*_RTSpinlockRelease)(RTSPINLOCK Spinlock); + void* (*_RTMemTmpAllocTag)(size_t cb, const char *pszTag); + void (*_RTMemTmpFree)(void *pv); + PRTLOGGER(*_RTLogDefaultInstance)(void); + PRTLOGGER(*_RTLogDefaultInstanceEx)(uint32_t fFlagsAndGroup); + PRTLOGGER(*_RTLogRelGetDefaultInstance)(void); + PRTLOGGER(*_RTLogRelGetDefaultInstanceEx)(uint32_t fFlagsAndGroup); + int (*_RTErrConvertToErrno)(int iErr); + int (*_VGDrvCommonIoCtl)(unsigned iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + void *pvData, size_t cbData, size_t *pcbDataReturned); + int (*_VGDrvCommonCreateUserSession)(PVBOXGUESTDEVEXT pDevExt, uint32_t fRequestor, PVBOXGUESTSESSION *ppSession); + void (*_VGDrvCommonCloseSession)(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession); + void* (*_VBoxGuestIDCOpen)(uint32_t *pu32Version); + int (*_VBoxGuestIDCClose)(void *pvSession); + int (*_VBoxGuestIDCCall)(void *pvSession, unsigned iCmd, void *pvData, size_t cbData, size_t *pcbDataReturned); + void (*_RTAssertMsg1Weak)(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction); + void (*_RTAssertMsg2Weak)(const char *pszFormat, ...); + void (*_RTAssertMsg2WeakV)(const char *pszFormat, va_list va); + bool (*_RTAssertShouldPanic)(void); + int (*_RTSemFastMutexCreate)(PRTSEMFASTMUTEX phFastMtx); + int (*_RTSemFastMutexDestroy)(RTSEMFASTMUTEX hFastMtx); + int (*_RTSemFastMutexRelease)(RTSEMFASTMUTEX hFastMtx); + int (*_RTSemFastMutexRequest)(RTSEMFASTMUTEX hFastMtx); + int (*_RTSemMutexCreate)(PRTSEMMUTEX phFastMtx); + int (*_RTSemMutexDestroy)(RTSEMMUTEX hFastMtx); + int (*_RTSemMutexRelease)(RTSEMMUTEX hFastMtx); + int (*_RTSemMutexRequest)(RTSEMMUTEX hFastMtx, RTMSINTERVAL cMillies); + int (*_RTHeapSimpleRelocate)(RTHEAPSIMPLE hHeap, uintptr_t offDelta); + int (*_RTHeapOffsetInit)(PRTHEAPOFFSET phHeap, void *pvMemory, size_t cbMemory); + int (*_RTHeapSimpleInit)(PRTHEAPSIMPLE pHeap, void *pvMemory, size_t cbMemory); + void* (*_RTHeapOffsetAlloc)(RTHEAPOFFSET hHeap, size_t cb, size_t cbAlignment); + void* (*_RTHeapSimpleAlloc)(RTHEAPSIMPLE Heap, size_t cb, size_t cbAlignment); + void (*_RTHeapOffsetFree)(RTHEAPOFFSET hHeap, void *pv); + void (*_RTHeapSimpleFree)(RTHEAPSIMPLE Heap, void *pv); +}; + + +#ifdef IN_VBOXGUEST +#define g_DevExt (g_VBoxGuest.devExt) +#define cUsers (g_VBoxGuest._cUsers) +#define sState (g_VBoxGuest._sState) +#else +#define g_DevExt (g_VBoxGuest->devExt) +#define cUsers (g_VBoxGuest->_cUsers) +#define sState (g_VBoxGuest->_sState) +extern struct vboxguest_module_info *g_VBoxGuest; +#endif + +#endif /* !GA_INCLUDED_SRC_common_VBoxGuest_VBoxGuest_haiku_h */ + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c new file mode 100644 index 00000000..538a0bc4 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c @@ -0,0 +1,1470 @@ +/* $Id: VBoxGuest-linux.c $ */ +/** @file + * VBoxGuest - Linux specifics. + * + * Note. Unfortunately, the difference between this and SUPDrv-linux.c is + * a little bit too big to be helpful. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV + +#include "the-linux-kernel.h" + +#if RTLNX_VER_MIN(2,6,15) +# define VBOXGUEST_WITH_INPUT_DRIVER +#endif + +#if RTLNX_VER_MIN(4,15,0) +# define CONST_4_15 const +#else +# define CONST_4_15 +#endif + +#include "VBoxGuestInternal.h" +#ifdef VBOXGUEST_WITH_INPUT_DRIVER +# include <linux/input.h> +#endif +#include <linux/miscdevice.h> +#include <linux/poll.h> +#if RTLNX_VER_MIN(2,6,28) +# include <linux/tty.h> +#endif +#include <VBox/version.h> +#include "revision-generated.h" + +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/mp.h> +#include <iprt/process.h> +#include <iprt/spinlock.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <VBox/err.h> +#include <VBox/log.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The device name. */ +#define DEVICE_NAME "vboxguest" +/** The device name for the device node open to everyone. */ +#define DEVICE_NAME_USER "vboxuser" +/** The name of the PCI driver */ +#define DRIVER_NAME DEVICE_NAME + + +/* 2.4.x compatibility macros that may or may not be defined. */ +#ifndef IRQ_RETVAL +# define irqreturn_t void +# define IRQ_RETVAL(n) +#endif + +/* uidgid.h was introduced in 3.5.0. */ +#if RTLNX_VER_MAX(3,5,0) +# define kgid_t gid_t +# define kuid_t uid_t +#endif + +#ifdef VBOXGUEST_WITH_INPUT_DRIVER +/** The name of input pointing device for mouse integration. */ +# define VBOX_INPUT_DEVICE_NAME "VirtualBox mouse integration" +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void vgdrvLinuxTermPci(struct pci_dev *pPciDev); +static int vgdrvLinuxProbePci(struct pci_dev *pPciDev, const struct pci_device_id *id); +static int __init vgdrvLinuxModInit(void); +static void __exit vgdrvLinuxModExit(void); +static int vgdrvLinuxOpen(struct inode *pInode, struct file *pFilp); +static int vgdrvLinuxRelease(struct inode *pInode, struct file *pFilp); +#ifdef HAVE_UNLOCKED_IOCTL +static long vgdrvLinuxIOCtl(struct file *pFilp, unsigned int uCmd, unsigned long ulArg); +#else +static int vgdrvLinuxIOCtl(struct inode *pInode, struct file *pFilp, unsigned int uCmd, unsigned long ulArg); +#endif +static int vgdrvLinuxIOCtlSlow(struct file *pFilp, unsigned int uCmd, unsigned long ulArg, PVBOXGUESTSESSION pSession); +static int vgdrvLinuxFAsync(int fd, struct file *pFile, int fOn); +static unsigned int vgdrvLinuxPoll(struct file *pFile, poll_table *pPt); +static ssize_t vgdrvLinuxRead(struct file *pFile, char *pbBuf, size_t cbRead, loff_t *poff); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Device extention & session data association structure. + */ +static VBOXGUESTDEVEXT g_DevExt; +/** The PCI device. */ +static struct pci_dev *g_pPciDev = NULL; +/** The base of the I/O port range. */ +static RTIOPORT g_IOPortBase; +/** The base of the MMIO range. */ +static RTHCPHYS g_MMIOPhysAddr = NIL_RTHCPHYS; +/** The size of the MMIO range as seen by PCI. */ +static uint32_t g_cbMMIO; +/** The pointer to the mapping of the MMIO range. */ +static void *g_pvMMIOBase; +/** Wait queue used by polling. */ +static wait_queue_head_t g_PollEventQueue; +/** Asynchronous notification stuff. */ +static struct fasync_struct *g_pFAsyncQueue; +#ifdef VBOXGUEST_WITH_INPUT_DRIVER +/** Pre-allocated mouse status VMMDev requests for use in the IRQ handler. */ +static VMMDevReqMouseStatusEx *g_pMouseStatusReqEx; +#endif +#if RTLNX_VER_MIN(2,6,0) +/** Whether we've create the logger or not. */ +static volatile bool g_fLoggerCreated; +/** Release logger group settings. */ +static char g_szLogGrp[128]; +/** Release logger flags settings. */ +static char g_szLogFlags[128]; +/** Release logger destination settings. */ +static char g_szLogDst[128]; +# if 0 +/** Debug logger group settings. */ +static char g_szDbgLogGrp[128]; +/** Debug logger flags settings. */ +static char g_szDbgLogFlags[128]; +/** Debug logger destination settings. */ +static char g_szDbgLogDst[128]; +# endif +#endif + +/** The input device handle */ +#ifdef VBOXGUEST_WITH_INPUT_DRIVER +static struct input_dev *g_pInputDevice = NULL; +#endif + +/** The file_operations structure. */ +static struct file_operations g_FileOps = +{ + owner: THIS_MODULE, + open: vgdrvLinuxOpen, + release: vgdrvLinuxRelease, +#ifdef HAVE_UNLOCKED_IOCTL + unlocked_ioctl: vgdrvLinuxIOCtl, +#else + ioctl: vgdrvLinuxIOCtl, +#endif + fasync: vgdrvLinuxFAsync, + read: vgdrvLinuxRead, + poll: vgdrvLinuxPoll, + llseek: no_llseek, +}; + +/** The miscdevice structure. */ +static struct miscdevice g_MiscDevice = +{ + minor: MISC_DYNAMIC_MINOR, + name: DEVICE_NAME, + fops: &g_FileOps, +}; + +/** The file_operations structure for the user device. + * @remarks For the time being we'll be using the same implementation as + * /dev/vboxguest here. */ +static struct file_operations g_FileOpsUser = +{ + owner: THIS_MODULE, + open: vgdrvLinuxOpen, + release: vgdrvLinuxRelease, +#ifdef HAVE_UNLOCKED_IOCTL + unlocked_ioctl: vgdrvLinuxIOCtl, +#else + ioctl: vgdrvLinuxIOCtl, +#endif +}; + +/** The miscdevice structure for the user device. */ +static struct miscdevice g_MiscDeviceUser = +{ + minor: MISC_DYNAMIC_MINOR, + name: DEVICE_NAME_USER, + fops: &g_FileOpsUser, +}; + + +/** PCI hotplug structure. */ +static const struct pci_device_id g_VBoxGuestPciId[] = +{ + { + vendor: VMMDEV_VENDORID, + device: VMMDEV_DEVICEID + }, + { + /* empty entry */ + } +}; + +MODULE_DEVICE_TABLE(pci, g_VBoxGuestPciId); + +/** Structure for registering the PCI driver. */ +static struct pci_driver g_PciDriver = +{ + name: DRIVER_NAME, + id_table: g_VBoxGuestPciId, + probe: vgdrvLinuxProbePci, + remove: vgdrvLinuxTermPci +}; + +#ifdef VBOXGUEST_WITH_INPUT_DRIVER +/** Kernel IDC session to ourselves for use with the mouse events. */ +static PVBOXGUESTSESSION g_pKernelSession = NULL; +#endif + + + +/** + * Converts a VBox status code to a linux error code. + * + * @returns corresponding negative linux error code. + * @param rc supdrv error code (SUPDRV_ERR_* defines). + */ +static int vgdrvLinuxConvertToNegErrno(int rc) +{ + if ( rc > -1000 + && rc < 1000) + return -RTErrConvertToErrno(rc); + switch (rc) + { + case VERR_HGCM_SERVICE_NOT_FOUND: return -ESRCH; + case VINF_HGCM_CLIENT_REJECTED: return 0; + case VERR_HGCM_INVALID_CMD_ADDRESS: return -EFAULT; + case VINF_HGCM_ASYNC_EXECUTE: return 0; + case VERR_HGCM_INTERNAL: return -EPROTO; + case VERR_HGCM_INVALID_CLIENT_ID: return -EINVAL; + case VINF_HGCM_SAVE_STATE: return 0; + /* No reason to return this to a guest */ + // case VERR_HGCM_SERVICE_EXISTS: return -EEXIST; + default: + AssertMsgFailed(("Unhandled error code %Rrc\n", rc)); + return -EPROTO; + } +} + + +/** + * Does the PCI detection and init of the device. + * + * @returns 0 on success, negated errno on failure. + */ +static int vgdrvLinuxProbePci(struct pci_dev *pPciDev, const struct pci_device_id *id) +{ + int rc; + + NOREF(id); + AssertReturn(!g_pPciDev, -EINVAL); + rc = pci_enable_device(pPciDev); + if (rc >= 0) + { + /* I/O Ports are mandatory, the MMIO bit is not. */ + g_IOPortBase = pci_resource_start(pPciDev, 0); + if (g_IOPortBase != 0) + { + /* + * Map the register address space. + */ + g_MMIOPhysAddr = pci_resource_start(pPciDev, 1); + g_cbMMIO = pci_resource_len(pPciDev, 1); + if (request_mem_region(g_MMIOPhysAddr, g_cbMMIO, DEVICE_NAME) != NULL) + { + g_pvMMIOBase = ioremap(g_MMIOPhysAddr, g_cbMMIO); + if (g_pvMMIOBase) + { + /** @todo why aren't we requesting ownership of the I/O ports as well? */ + g_pPciDev = pPciDev; + return 0; + } + + /* failure cleanup path */ + LogRel((DEVICE_NAME ": ioremap failed; MMIO Addr=%RHp cb=%#x\n", g_MMIOPhysAddr, g_cbMMIO)); + rc = -ENOMEM; + release_mem_region(g_MMIOPhysAddr, g_cbMMIO); + } + else + { + LogRel((DEVICE_NAME ": failed to obtain adapter memory\n")); + rc = -EBUSY; + } + g_MMIOPhysAddr = NIL_RTHCPHYS; + g_cbMMIO = 0; + g_IOPortBase = 0; + } + else + { + LogRel((DEVICE_NAME ": did not find expected hardware resources\n")); + rc = -ENXIO; + } + pci_disable_device(pPciDev); + } + else + LogRel((DEVICE_NAME ": could not enable device: %d\n", rc)); + return rc; +} + + +/** + * Clean up the usage of the PCI device. + */ +static void vgdrvLinuxTermPci(struct pci_dev *pPciDev) +{ + g_pPciDev = NULL; + if (pPciDev) + { + iounmap(g_pvMMIOBase); + g_pvMMIOBase = NULL; + + release_mem_region(g_MMIOPhysAddr, g_cbMMIO); + g_MMIOPhysAddr = NIL_RTHCPHYS; + g_cbMMIO = 0; + + pci_disable_device(pPciDev); + } +} + + +/** + * Interrupt service routine. + * + * @returns In 2.4 it returns void. + * In 2.6 we indicate whether we've handled the IRQ or not. + * + * @param iIrq The IRQ number. + * @param pvDevId The device ID, a pointer to g_DevExt. + * @param pRegs Register set. Removed in 2.6.19. + */ +#if RTLNX_VER_MIN(2,6,19) && !defined(DOXYGEN_RUNNING) +static irqreturn_t vgdrvLinuxISR(int iIrq, void *pvDevId) +#else +static irqreturn_t vgdrvLinuxISR(int iIrq, void *pvDevId, struct pt_regs *pRegs) +#endif +{ + bool fTaken = VGDrvCommonISR(&g_DevExt); + return IRQ_RETVAL(fTaken); +} + + +/** + * Registers the ISR and initializes the poll wait queue. + */ +static int __init vgdrvLinuxInitISR(void) +{ + int rc; + + init_waitqueue_head(&g_PollEventQueue); + rc = request_irq(g_pPciDev->irq, + vgdrvLinuxISR, +#if RTLNX_VER_MIN(2,6,20) + IRQF_SHARED, +#else + SA_SHIRQ, +#endif + DEVICE_NAME, + &g_DevExt); + if (rc) + { + LogRel((DEVICE_NAME ": could not request IRQ %d: err=%d\n", g_pPciDev->irq, rc)); + return rc; + } + return 0; +} + + +/** + * Deregisters the ISR. + */ +static void vgdrvLinuxTermISR(void) +{ + free_irq(g_pPciDev->irq, &g_DevExt); +} + + +#ifdef VBOXGUEST_WITH_INPUT_DRIVER +/** + * Check if extended mouse pointer state request protocol is currently used by driver. + * + * @returns True if extended protocol is used, False otherwise. + */ +static bool vgdrvLinuxUsesMouseStatusEx(void) +{ + return g_pMouseStatusReqEx->Core.header.requestType == VMMDevReq_GetMouseStatusEx; +} + +/** + * Reports the mouse integration status to the host. + * + * Calls the kernel IOCtl to report mouse status to the host on behalf of + * our kernel session. + * + * @param fStatus The mouse status to report. + */ +static int vgdrvLinuxSetMouseStatus(uint32_t fStatus) +{ + int rc; + VBGLIOCSETMOUSESTATUS Req; + VBGLREQHDR_INIT(&Req.Hdr, SET_MOUSE_STATUS); + Req.u.In.fStatus = fStatus; + rc = VGDrvCommonIoCtl(VBGL_IOCTL_SET_MOUSE_STATUS, &g_DevExt, g_pKernelSession, &Req.Hdr, sizeof(Req)); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + return rc; +} + + +/** + * Called when the input device is first opened. + * + * Sets up absolute mouse reporting. + */ +static int vboxguestOpenInputDevice(struct input_dev *pDev) +{ + int rc = vgdrvLinuxSetMouseStatus( VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE + | VMMDEV_MOUSE_NEW_PROTOCOL + | (vgdrvLinuxUsesMouseStatusEx() ? VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL : 0)); + if (RT_FAILURE(rc)) + return ENODEV; + NOREF(pDev); + return 0; +} + + +/** + * Called if all open handles to the input device are closed. + * + * Disables absolute reporting. + */ +static void vboxguestCloseInputDevice(struct input_dev *pDev) +{ + NOREF(pDev); + vgdrvLinuxSetMouseStatus(0); +} + + +/** + * Free corresponding mouse status request structure. + */ +static void vgdrvLinuxFreeMouseStatusReq(void) +{ + VbglR0GRFree(&g_pMouseStatusReqEx->Core.header); + g_pMouseStatusReqEx = NULL; +} + +/** + * Creates the kernel input device. + */ +static int __init vgdrvLinuxCreateInputDevice(void) +{ + /* Try to allocate legacy request data first, and check if host supports extended protocol. */ + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&g_pMouseStatusReqEx, sizeof(VMMDevReqMouseStatus), VMMDevReq_GetMouseStatus); + if (RT_SUCCESS(rc)) + { + /* Check if host supports extended mouse state reporting. */ + g_pMouseStatusReqEx->Core.mouseFeatures = 0; + rc = VbglR0GRPerform(&g_pMouseStatusReqEx->Core.header); + if (RT_SUCCESS(rc)) + { + if (g_pMouseStatusReqEx->Core.mouseFeatures & VMMDEV_MOUSE_HOST_USES_FULL_STATE_PROTOCOL) + { + VMMDevReqMouseStatusEx *pReqEx = NULL; + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReqEx, sizeof(*pReqEx), VMMDevReq_GetMouseStatusEx); + if (RT_SUCCESS(rc)) + { + /* De-allocate legacy request data, */ + VbglR0GRFree(&g_pMouseStatusReqEx->Core.header); + /* ..and switch to extended requests. */ + g_pMouseStatusReqEx = pReqEx; + LogRel(("Host supports full mouse state reporting, switching to extended mouse integration protocol\n")); + } + else + LogRel(("Host supports full mouse state reporting, but feature cannot be initialized, switching to legacy mouse integration protocol\n")); + } + else + LogRel(("Host does not support full mouse state reporting, switching to legacy mouse integration protocol\n")); + } + else + LogRel(("Unable to get host mouse capabilities, switching to legacy mouse integration protocol\n")); + } + else + rc = -ENOMEM; + + if (RT_SUCCESS(rc)) + { + g_pInputDevice = input_allocate_device(); + if (g_pInputDevice) + { + g_pInputDevice->name = VBOX_INPUT_DEVICE_NAME; + g_pInputDevice->id.bustype = BUS_PCI; + g_pInputDevice->id.vendor = VMMDEV_VENDORID; + g_pInputDevice->id.product = VMMDEV_DEVICEID; + g_pInputDevice->id.version = VBOX_SHORT_VERSION; + g_pInputDevice->open = vboxguestOpenInputDevice; + g_pInputDevice->close = vboxguestCloseInputDevice; +# if RTLNX_VER_MAX(2,6,22) + g_pInputDevice->cdev.dev = &g_pPciDev->dev; +# else + g_pInputDevice->dev.parent = &g_pPciDev->dev; +# endif + /* Set up input device capabilities. */ + ASMBitSet(g_pInputDevice->evbit, EV_ABS); + ASMBitSet(g_pInputDevice->evbit, EV_KEY); +# ifdef EV_SYN + ASMBitSet(g_pInputDevice->evbit, EV_SYN); +# endif + ASMBitSet(g_pInputDevice->absbit, ABS_X); + ASMBitSet(g_pInputDevice->absbit, ABS_Y); + + input_set_abs_params(g_pInputDevice, ABS_X, VMMDEV_MOUSE_RANGE_MIN, VMMDEV_MOUSE_RANGE_MAX, 0, 0); + input_set_abs_params(g_pInputDevice, ABS_Y, VMMDEV_MOUSE_RANGE_MIN, VMMDEV_MOUSE_RANGE_MAX, 0, 0); + + ASMBitSet(g_pInputDevice->keybit, BTN_MOUSE); + + /* Report extra capabilities to input layer if extended mouse state protocol + * will be used in communication with host. */ + if (vgdrvLinuxUsesMouseStatusEx()) + { + ASMBitSet(g_pInputDevice->evbit, EV_REL); + ASMBitSet(g_pInputDevice->evbit, EV_KEY); + + ASMBitSet(g_pInputDevice->relbit, REL_WHEEL); + ASMBitSet(g_pInputDevice->relbit, REL_HWHEEL); + + ASMBitSet(g_pInputDevice->keybit, BTN_LEFT); + ASMBitSet(g_pInputDevice->keybit, BTN_RIGHT); + ASMBitSet(g_pInputDevice->keybit, BTN_MIDDLE); + ASMBitSet(g_pInputDevice->keybit, BTN_SIDE); + ASMBitSet(g_pInputDevice->keybit, BTN_EXTRA); + } + + rc = input_register_device(g_pInputDevice); + if (rc == 0) + return 0; + + input_free_device(g_pInputDevice); + } + else + rc = -ENOMEM; + + vgdrvLinuxFreeMouseStatusReq(); + } + + return rc; +} + + +/** + * Terminates the kernel input device. + */ +static void vgdrvLinuxTermInputDevice(void) +{ + /* Notify host that mouse integration is no longer available. */ + vgdrvLinuxSetMouseStatus(0); + + vgdrvLinuxFreeMouseStatusReq(); + + /* See documentation of input_register_device(): input_free_device() + * should not be called after a device has been registered. */ + input_unregister_device(g_pInputDevice); +} + +#endif /* VBOXGUEST_WITH_INPUT_DRIVER */ + +/** + * Creates the device nodes. + * + * @returns 0 on success, negated errno on failure. + */ +static int __init vgdrvLinuxInitDeviceNodes(void) +{ + /* + * The full feature device node. + */ + int rc = misc_register(&g_MiscDevice); + if (!rc) + { + /* + * The device node intended to be accessible by all users. + */ + rc = misc_register(&g_MiscDeviceUser); + if (!rc) + return 0; + LogRel((DEVICE_NAME ": misc_register failed for %s (rc=%d)\n", DEVICE_NAME_USER, rc)); + misc_deregister(&g_MiscDevice); + } + else + LogRel((DEVICE_NAME ": misc_register failed for %s (rc=%d)\n", DEVICE_NAME, rc)); + return rc; +} + + +/** + * Deregisters the device nodes. + */ +static void vgdrvLinuxTermDeviceNodes(void) +{ + misc_deregister(&g_MiscDevice); + misc_deregister(&g_MiscDeviceUser); +} + + +/** + * Initialize module. + * + * @returns appropriate status code. + */ +static int __init vgdrvLinuxModInit(void) +{ + static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; + PRTLOGGER pRelLogger; + int rc; + + /* + * Initialize IPRT first. + */ + rc = RTR0Init(0); + if (RT_FAILURE(rc)) + { + printk(KERN_ERR DEVICE_NAME ": RTR0Init failed, rc=%d.\n", rc); + return -EINVAL; + } + + /* + * Create the release log. + * (We do that here instead of common code because we want to log + * early failures using the LogRel macro.) + */ + rc = RTLogCreate(&pRelLogger, 0 /* fFlags */, "all", + "VBOX_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups, + RTLOGDEST_STDOUT | RTLOGDEST_DEBUGGER | RTLOGDEST_USER, NULL); + if (RT_SUCCESS(rc)) + { +#if RTLNX_VER_MIN(2,6,0) + RTLogGroupSettings(pRelLogger, g_szLogGrp); + RTLogFlags(pRelLogger, g_szLogFlags); + RTLogDestinations(pRelLogger, g_szLogDst); +#endif + RTLogRelSetDefaultInstance(pRelLogger); + } +#if RTLNX_VER_MIN(2,6,0) + g_fLoggerCreated = true; +#endif + + /* + * Locate and initialize the PCI device. + */ + rc = pci_register_driver(&g_PciDriver); + if (rc >= 0 && g_pPciDev) + { + /* + * Call the common device extension initializer. + */ +#if RTLNX_VER_MIN(2,6,0) && defined(RT_ARCH_X86) + VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux26; +#elif RTLNX_VER_MIN(2,6,0) && defined(RT_ARCH_AMD64) + VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux26_x64; +#elif RTLNX_VER_MIN(2,4,0) && defined(RT_ARCH_X86) + VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux24; +#elif RTLNX_VER_MIN(2,4,0) && defined(RT_ARCH_AMD64) + VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux24_x64; +#else +# warning "huh? which arch + version is this?" + VBOXOSTYPE enmOsType = VBOXOSTYPE_Linux; +#endif + rc = VGDrvCommonInitDevExt(&g_DevExt, + g_IOPortBase, + g_pvMMIOBase, + g_cbMMIO, + enmOSType, + VMMDEV_EVENT_MOUSE_POSITION_CHANGED); + if (RT_SUCCESS(rc)) + { + /* + * Register the interrupt service routine for it now that g_DevExt can handle IRQs. + */ + rc = vgdrvLinuxInitISR(); + if (rc >= 0) /** @todo r=bird: status check differs from that inside vgdrvLinuxInitISR. */ + { +#ifdef VBOXGUEST_WITH_INPUT_DRIVER + /* + * Create the kernel session for this driver. + */ + rc = VGDrvCommonCreateKernelSession(&g_DevExt, &g_pKernelSession); + if (RT_SUCCESS(rc)) + { + /* + * Create the kernel input device. + */ + rc = vgdrvLinuxCreateInputDevice(); + if (rc >= 0) + { +#endif + /* + * Read host configuration. + */ + VGDrvCommonProcessOptionsFromHost(&g_DevExt); + + /* + * Finally, create the device nodes. + */ + rc = vgdrvLinuxInitDeviceNodes(); + if (rc >= 0) + { + /* some useful information for the user but don't show this on the console */ + LogRel((DEVICE_NAME ": Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) "\n")); + LogRel((DEVICE_NAME ": misc device minor %d, IRQ %d, I/O port %RTiop, MMIO at %RHp (size 0x%x)\n", + g_MiscDevice.minor, g_pPciDev->irq, g_IOPortBase, g_MMIOPhysAddr, g_cbMMIO)); + printk(KERN_DEBUG DEVICE_NAME ": Successfully loaded version " + VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " (interface " RT_XSTR(VMMDEV_VERSION) ")\n"); + return rc; + } + + /* bail out */ +#ifdef VBOXGUEST_WITH_INPUT_DRIVER + vgdrvLinuxTermInputDevice(); + } + else + { + LogRel((DEVICE_NAME ": vboxguestCreateInputDevice failed with rc=%Rrc\n", rc)); + rc = RTErrConvertFromErrno(rc); + } + VGDrvCommonCloseSession(&g_DevExt, g_pKernelSession); + } +#endif + vgdrvLinuxTermISR(); + } + VGDrvCommonDeleteDevExt(&g_DevExt); + } + else + { + LogRel((DEVICE_NAME ": VGDrvCommonInitDevExt failed with rc=%Rrc\n", rc)); + rc = RTErrConvertFromErrno(rc); + } + } + else + { + LogRel((DEVICE_NAME ": PCI device not found, probably running on physical hardware.\n")); + rc = -ENODEV; + } + pci_unregister_driver(&g_PciDriver); + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogDestroy(RTLogSetDefaultInstance(NULL)); + RTR0Term(); + return rc; +} + + +/** + * Unload the module. + */ +static void __exit vgdrvLinuxModExit(void) +{ + /* + * Inverse order of init. + */ + vgdrvLinuxTermDeviceNodes(); +#ifdef VBOXGUEST_WITH_INPUT_DRIVER + vgdrvLinuxTermInputDevice(); + VGDrvCommonCloseSession(&g_DevExt, g_pKernelSession); +#endif + vgdrvLinuxTermISR(); + VGDrvCommonDeleteDevExt(&g_DevExt); + pci_unregister_driver(&g_PciDriver); + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogDestroy(RTLogSetDefaultInstance(NULL)); + RTR0Term(); +} + + +/** + * Get the process user ID. + * + * @returns UID. + */ +DECLINLINE(RTUID) vgdrvLinuxGetUid(void) +{ +#if RTLNX_VER_MIN(2,6,29) +# if RTLNX_VER_MIN(3,5,0) + return from_kuid(current_user_ns(), current->cred->uid); +# else + return current->cred->uid; +# endif +#else + return current->uid; +#endif +} + + +/** + * Checks if the given group number is zero or not. + * + * @returns true / false. + * @param gid The group to check for. + */ +DECLINLINE(bool) vgdrvLinuxIsGroupZero(kgid_t gid) +{ +#if RTLNX_VER_MIN(3,5,0) + return from_kgid(current_user_ns(), gid); +#else + return gid == 0; +#endif +} + + +/** + * Searches the effective group and supplementary groups for @a gid. + * + * @returns true if member, false if not. + * @param gid The group to check for. + */ +DECLINLINE(RTGID) vgdrvLinuxIsInGroupEff(kgid_t gid) +{ + return in_egroup_p(gid) != 0; +} + + +/** + * Check if we can positively or negatively determine that the process is + * running under a login on the physical machine console. + * + * Havne't found a good way to figure this out for graphical sessions, so this + * is mostly pointless. But let us try do what we can do. + * + * @returns VMMDEV_REQUESTOR_CON_XXX. + */ +static uint32_t vgdrvLinuxRequestorOnConsole(void) +{ + uint32_t fRet = VMMDEV_REQUESTOR_CON_DONT_KNOW; + +#if RTLNX_VER_MIN(2,6,28) /* First with tty_kref_put(). */ + /* + * Check for tty0..63, ASSUMING that these are only used for the physical console. + */ + struct tty_struct *pTty = get_current_tty(); + if (pTty) + { +# if RTLNX_VER_MIN(4,2,0) + const char *pszName = tty_name(pTty); +# else + char szBuf[64]; + const char *pszName = tty_name(pTty, szBuf); +# endif + if ( pszName + && pszName[0] == 't' + && pszName[1] == 't' + && pszName[2] == 'y' + && RT_C_IS_DIGIT(pszName[3]) + && ( pszName[4] == '\0' + || ( RT_C_IS_DIGIT(pszName[4]) + && pszName[5] == '\0' + && (pszName[3] - '0') * 10 + (pszName[4] - '0') <= 63)) ) + fRet = VMMDEV_REQUESTOR_CON_YES; + tty_kref_put(pTty); + } +#endif + + return fRet; +} + + +/** + * Device open. Called on open /dev/vboxdrv + * + * @param pInode Pointer to inode info structure. + * @param pFilp Associated file pointer. + */ +static int vgdrvLinuxOpen(struct inode *pInode, struct file *pFilp) +{ + int rc; + PVBOXGUESTSESSION pSession; + uint32_t fRequestor; + Log((DEVICE_NAME ": pFilp=%p pid=%d/%d %s\n", pFilp, RTProcSelf(), current->pid, current->comm)); + + /* + * Figure out the requestor flags. + * ASSUMES that the gid of /dev/vboxuser is what we should consider the special vbox group. + */ + fRequestor = VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN; + if (vgdrvLinuxGetUid() == 0) + fRequestor |= VMMDEV_REQUESTOR_USR_ROOT; + else + fRequestor |= VMMDEV_REQUESTOR_USR_USER; + if (MINOR(pInode->i_rdev) == g_MiscDeviceUser.minor) + { + fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE; + if (!vgdrvLinuxIsGroupZero(pInode->i_gid) && vgdrvLinuxIsInGroupEff(pInode->i_gid)) + fRequestor |= VMMDEV_REQUESTOR_GRP_VBOX; + } + fRequestor |= vgdrvLinuxRequestorOnConsole(); + + /* + * Call common code to create the user session. Associate it with + * the file so we can access it in the other methods. + */ + rc = VGDrvCommonCreateUserSession(&g_DevExt, fRequestor, &pSession); + if (RT_SUCCESS(rc)) + pFilp->private_data = pSession; + + Log(("vgdrvLinuxOpen: g_DevExt=%p pSession=%p rc=%d/%d (pid=%d/%d %s)\n", + &g_DevExt, pSession, rc, vgdrvLinuxConvertToNegErrno(rc), RTProcSelf(), current->pid, current->comm)); + return vgdrvLinuxConvertToNegErrno(rc); +} + + +/** + * Close device. + * + * @param pInode Pointer to inode info structure. + * @param pFilp Associated file pointer. + */ +static int vgdrvLinuxRelease(struct inode *pInode, struct file *pFilp) +{ + Log(("vgdrvLinuxRelease: pFilp=%p pSession=%p pid=%d/%d %s\n", + pFilp, pFilp->private_data, RTProcSelf(), current->pid, current->comm)); + +#if RTLNX_VER_MAX(2,6,28) + /* This housekeeping was needed in older kernel versions to ensure that + * the file pointer didn't get left on the polling queue. */ + vgdrvLinuxFAsync(-1, pFilp, 0); +#endif + VGDrvCommonCloseSession(&g_DevExt, (PVBOXGUESTSESSION)pFilp->private_data); + pFilp->private_data = NULL; + return 0; +} + + +/** + * Device I/O Control entry point. + * + * @param pFilp Associated file pointer. + * @param uCmd The function specified to ioctl(). + * @param ulArg The argument specified to ioctl(). + */ +#if defined(HAVE_UNLOCKED_IOCTL) || defined(DOXYGEN_RUNNING) +static long vgdrvLinuxIOCtl(struct file *pFilp, unsigned int uCmd, unsigned long ulArg) +#else +static int vgdrvLinuxIOCtl(struct inode *pInode, struct file *pFilp, unsigned int uCmd, unsigned long ulArg) +#endif +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pFilp->private_data; + int rc; +#ifndef HAVE_UNLOCKED_IOCTL + unlock_kernel(); +#endif + +#if 0 /* no fast I/O controls defined atm. */ + if (RT_LIKELY( ( uCmd == SUP_IOCTL_FAST_DO_RAW_RUN + || uCmd == SUP_IOCTL_FAST_DO_HM_RUN + || uCmd == SUP_IOCTL_FAST_DO_NOP) + && pSession->fUnrestricted == true)) + rc = VGDrvCommonIoCtlFast(uCmd, ulArg, &g_DevExt, pSession); + else +#endif + rc = vgdrvLinuxIOCtlSlow(pFilp, uCmd, ulArg, pSession); + +#ifndef HAVE_UNLOCKED_IOCTL + lock_kernel(); +#endif + return rc; +} + + +/** + * Device I/O Control entry point, slow variant. + * + * @param pFilp Associated file pointer. + * @param uCmd The function specified to ioctl(). + * @param ulArg The argument specified to ioctl(). + * @param pSession The session instance. + */ +static int vgdrvLinuxIOCtlSlow(struct file *pFilp, unsigned int uCmd, unsigned long ulArg, PVBOXGUESTSESSION pSession) +{ + int rc; + VBGLREQHDR Hdr; + PVBGLREQHDR pHdr; + uint32_t cbBuf; + + Log6(("vgdrvLinuxIOCtlSlow: pFilp=%p uCmd=%#x ulArg=%p pid=%d/%d\n", pFilp, uCmd, (void *)ulArg, RTProcSelf(), current->pid)); + + /* + * Read the header. + */ + if (RT_FAILURE(RTR0MemUserCopyFrom(&Hdr, ulArg, sizeof(Hdr)))) + { + Log(("vgdrvLinuxIOCtlSlow: copy_from_user(,%#lx,) failed; uCmd=%#x\n", ulArg, uCmd)); + return -EFAULT; + } + if (RT_UNLIKELY(Hdr.uVersion != VBGLREQHDR_VERSION)) + { + Log(("vgdrvLinuxIOCtlSlow: bad header version %#x; uCmd=%#x\n", Hdr.uVersion, uCmd)); + return -EINVAL; + } + + /* + * Buffer the request. + * Note! The header is revalidated by the common code. + */ + cbBuf = RT_MAX(Hdr.cbIn, Hdr.cbOut); + if (RT_UNLIKELY(cbBuf > _1M*16)) + { + Log(("vgdrvLinuxIOCtlSlow: too big cbBuf=%#x; uCmd=%#x\n", cbBuf, uCmd)); + return -E2BIG; + } + if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr) + || (cbBuf != _IOC_SIZE(uCmd) && _IOC_SIZE(uCmd) != 0))) + { + Log(("vgdrvLinuxIOCtlSlow: bad ioctl cbBuf=%#x _IOC_SIZE=%#x; uCmd=%#x\n", cbBuf, _IOC_SIZE(uCmd), uCmd)); + return -EINVAL; + } + pHdr = RTMemAlloc(cbBuf); + if (RT_UNLIKELY(!pHdr)) + { + LogRel(("vgdrvLinuxIOCtlSlow: failed to allocate buffer of %d bytes for uCmd=%#x\n", cbBuf, uCmd)); + return -ENOMEM; + } + if (RT_FAILURE(RTR0MemUserCopyFrom(pHdr, ulArg, Hdr.cbIn))) + { + Log(("vgdrvLinuxIOCtlSlow: copy_from_user(,%#lx, %#x) failed; uCmd=%#x\n", ulArg, Hdr.cbIn, uCmd)); + RTMemFree(pHdr); + return -EFAULT; + } + if (Hdr.cbIn < cbBuf) + RT_BZERO((uint8_t *)pHdr + Hdr.cbIn, cbBuf - Hdr.cbIn); + + /* + * Process the IOCtl. + */ + rc = VGDrvCommonIoCtl(uCmd, &g_DevExt, pSession, pHdr, cbBuf); + + /* + * Copy ioctl data and output buffer back to user space. + */ + if (RT_SUCCESS(rc)) + { + uint32_t cbOut = pHdr->cbOut; + if (RT_UNLIKELY(cbOut > cbBuf)) + { + LogRel(("vgdrvLinuxIOCtlSlow: too much output! %#x > %#x; uCmd=%#x!\n", cbOut, cbBuf, uCmd)); + cbOut = cbBuf; + } + if (RT_FAILURE(RTR0MemUserCopyTo(ulArg, pHdr, cbOut))) + { + /* this is really bad! */ + LogRel(("vgdrvLinuxIOCtlSlow: copy_to_user(%#lx,,%#x); uCmd=%#x!\n", ulArg, cbOut, uCmd)); + rc = -EFAULT; + } + } + else + { + Log(("vgdrvLinuxIOCtlSlow: pFilp=%p uCmd=%#x ulArg=%p failed, rc=%d\n", pFilp, uCmd, (void *)ulArg, rc)); + rc = -EINVAL; + } + RTMemFree(pHdr); + + Log6(("vgdrvLinuxIOCtlSlow: returns %d (pid=%d/%d)\n", rc, RTProcSelf(), current->pid)); + return rc; +} + + +/** + * @note This code is duplicated on other platforms with variations, so please + * keep them all up to date when making changes! + */ +int VBOXCALL VBoxGuestIDC(void *pvSession, uintptr_t uReq, PVBGLREQHDR pReqHdr, size_t cbReq) +{ + /* + * Simple request validation (common code does the rest). + */ + int rc; + if ( RT_VALID_PTR(pReqHdr) + && cbReq >= sizeof(*pReqHdr)) + { + /* + * All requests except the connect one requires a valid session. + */ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pvSession; + if (pSession) + { + if ( RT_VALID_PTR(pSession) + && pSession->pDevExt == &g_DevExt) + rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); + else + rc = VERR_INVALID_HANDLE; + } + else if (uReq == VBGL_IOCTL_IDC_CONNECT) + { + rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession); + if (RT_SUCCESS(rc)) + { + rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); + if (RT_FAILURE(rc)) + VGDrvCommonCloseSession(&g_DevExt, pSession); + } + } + else + rc = VERR_INVALID_HANDLE; + } + else + rc = VERR_INVALID_POINTER; + return rc; +} +EXPORT_SYMBOL(VBoxGuestIDC); + + +/** + * Asynchronous notification activation method. + * + * @returns 0 on success, negative errno on failure. + * + * @param fd The file descriptor. + * @param pFile The file structure. + * @param fOn On/off indicator. + */ +static int vgdrvLinuxFAsync(int fd, struct file *pFile, int fOn) +{ + return fasync_helper(fd, pFile, fOn, &g_pFAsyncQueue); +} + + +/** + * Poll function. + * + * This returns ready to read if the mouse pointer mode or the pointer position + * has changed since last call to read. + * + * @returns 0 if no changes, POLLIN | POLLRDNORM if there are unseen changes. + * + * @param pFile The file structure. + * @param pPt The poll table. + * + * @remarks This is probably not really used, X11 is said to use the fasync + * interface instead. + */ +static unsigned int vgdrvLinuxPoll(struct file *pFile, poll_table *pPt) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pFile->private_data; + uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq); + unsigned int fMask = pSession->u32MousePosChangedSeq != u32CurSeq + ? POLLIN | POLLRDNORM + : 0; + poll_wait(pFile, &g_PollEventQueue, pPt); + return fMask; +} + + +/** + * Read to go with our poll/fasync response. + * + * @returns 1 or -EINVAL. + * + * @param pFile The file structure. + * @param pbBuf The buffer to read into. + * @param cbRead The max number of bytes to read. + * @param poff The current file position. + * + * @remarks This is probably not really used as X11 lets the driver do its own + * event reading. The poll condition is therefore also cleared when we + * see VMMDevReq_GetMouseStatus in vgdrvIoCtl_VMMRequest. + */ +static ssize_t vgdrvLinuxRead(struct file *pFile, char *pbBuf, size_t cbRead, loff_t *poff) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pFile->private_data; + uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq); + + if (*poff != 0) + return -EINVAL; + + /* + * Fake a single byte read if we're not up to date with the current mouse position. + */ + if ( pSession->u32MousePosChangedSeq != u32CurSeq + && cbRead > 0) + { + pSession->u32MousePosChangedSeq = u32CurSeq; + pbBuf[0] = 0; + return 1; + } + return 0; +} + + +#ifdef VBOXGUEST_WITH_INPUT_DRIVER +/** + * Get host mouse state. + * + * @returns IPRT status code. + * @param pfMouseFeatures Where to store host mouse capabilities. + * @param pX Where to store X axis coordinate. + * @param pY Where to store Y axis coordinate. + * @param pDz Where to store vertical wheel movement offset (only set if in case of VMMDevReq_GetMouseStatusEx request). + * @param pDw Where to store horizontal wheel movement offset (only set if in case of VMMDevReq_GetMouseStatusEx request). + * @param pfButtons Where to store mouse buttons state (only set if in case of VMMDevReq_GetMouseStatusEx request). + */ +static int vgdrvLinuxGetHostMouseState(uint32_t *pfMouseFeatures, int32_t *pX, int32_t *pY, int32_t *pDz, int32_t *pDw, uint32_t *pfButtons) +{ + int rc = VERR_INVALID_PARAMETER; + + Assert(pfMouseFeatures); + Assert(pX); + Assert(pY); + Assert(pDz); + Assert(pDw); + Assert(pfButtons); + + /* Initialize legacy request data. */ + g_pMouseStatusReqEx->Core.mouseFeatures = 0; + g_pMouseStatusReqEx->Core.pointerXPos = 0; + g_pMouseStatusReqEx->Core.pointerYPos = 0; + + /* Initialize extended request data if VMMDevReq_GetMouseStatusEx is used. */ + if (vgdrvLinuxUsesMouseStatusEx()) + { + g_pMouseStatusReqEx->dz = 0; + g_pMouseStatusReqEx->dw = 0; + g_pMouseStatusReqEx->fButtons = 0; + } + + /* Get host mouse state - either lagacy or extended version. */ + rc = VbglR0GRPerform(&g_pMouseStatusReqEx->Core.header); + if (RT_SUCCESS(rc)) + { + *pfMouseFeatures = g_pMouseStatusReqEx->Core.mouseFeatures; + *pX = g_pMouseStatusReqEx->Core.pointerXPos; + *pY = g_pMouseStatusReqEx->Core.pointerYPos; + + /* Get extended mouse state data in case of VMMDevReq_GetMouseStatusEx. */ + if (vgdrvLinuxUsesMouseStatusEx()) + { + *pDz = g_pMouseStatusReqEx->dz; + *pDw = g_pMouseStatusReqEx->dw; + *pfButtons = g_pMouseStatusReqEx->fButtons; + } + } + + return rc; +} +#endif /* VBOXGUEST_WITH_INPUT_DRIVER */ + + +void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt) +{ +#ifdef VBOXGUEST_WITH_INPUT_DRIVER + int rc; + uint32_t fMouseFeatures = 0; + int32_t x = 0; + int32_t y = 0; + int32_t dz = 0; + int32_t dw = 0; + uint32_t fButtons = 0; +#endif + NOREF(pDevExt); + + /* + * Wake up everyone that's in a poll() and post anyone that has + * subscribed to async notifications. + */ + Log3(("VGDrvNativeISRMousePollEvent: wake_up_all\n")); + wake_up_all(&g_PollEventQueue); + Log3(("VGDrvNativeISRMousePollEvent: kill_fasync\n")); + kill_fasync(&g_pFAsyncQueue, SIGIO, POLL_IN); +#ifdef VBOXGUEST_WITH_INPUT_DRIVER + rc = vgdrvLinuxGetHostMouseState(&fMouseFeatures, &x, &y, &dz, &dw, &fButtons); + if (RT_SUCCESS(rc)) + { + input_report_abs(g_pInputDevice, ABS_X, x); + input_report_abs(g_pInputDevice, ABS_Y, y); + + if ( vgdrvLinuxUsesMouseStatusEx() + && fMouseFeatures & VMMDEV_MOUSE_HOST_USES_FULL_STATE_PROTOCOL + && fMouseFeatures & VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL) + { + /* Vertical and horizontal scroll values come as-is from GUI. + * Invert values here as it is done in PS/2 mouse driver, so + * scrolling direction will be exectly the same. */ + input_report_rel(g_pInputDevice, REL_WHEEL, -dz); + input_report_rel(g_pInputDevice, REL_HWHEEL, -dw); + + input_report_key(g_pInputDevice, BTN_LEFT, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_LEFT)); + input_report_key(g_pInputDevice, BTN_RIGHT, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_RIGHT)); + input_report_key(g_pInputDevice, BTN_MIDDLE, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_MIDDLE)); + input_report_key(g_pInputDevice, BTN_SIDE, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_X1)); + input_report_key(g_pInputDevice, BTN_EXTRA, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_X2)); + } + +# ifdef EV_SYN + input_sync(g_pInputDevice); +# endif + } +#endif + Log3(("VGDrvNativeISRMousePollEvent: done\n")); +} + + +bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue) +{ + RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue); + return false; +} + + +#if RTLNX_VER_MIN(2,6,0) + +/** log and dbg_log parameter setter. */ +static int vgdrvLinuxParamLogGrpSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam) +{ + if (g_fLoggerCreated) + { + PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); + if (pLogger) + RTLogGroupSettings(pLogger, pszValue); + } + else if (pParam->name[0] != 'd') + strlcpy(&g_szLogGrp[0], pszValue, sizeof(g_szLogGrp)); + + return 0; +} + +/** log and dbg_log parameter getter. */ +static int vgdrvLinuxParamLogGrpGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam) +{ + PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); + *pszBuf = '\0'; + if (pLogger) + RTLogQueryGroupSettings(pLogger, pszBuf, _4K); + return strlen(pszBuf); +} + + +/** log and dbg_log_flags parameter setter. */ +static int vgdrvLinuxParamLogFlagsSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam) +{ + if (g_fLoggerCreated) + { + PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); + if (pLogger) + RTLogFlags(pLogger, pszValue); + } + else if (pParam->name[0] != 'd') + strlcpy(&g_szLogFlags[0], pszValue, sizeof(g_szLogFlags)); + return 0; +} + +/** log and dbg_log_flags parameter getter. */ +static int vgdrvLinuxParamLogFlagsGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam) +{ + PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); + *pszBuf = '\0'; + if (pLogger) + RTLogQueryFlags(pLogger, pszBuf, _4K); + return strlen(pszBuf); +} + + +/** log and dbg_log_dest parameter setter. */ +static int vgdrvLinuxParamLogDstSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam) +{ + if (g_fLoggerCreated) + { + PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); + if (pLogger) + RTLogDestinations(pLogger, pszValue); + } + else if (pParam->name[0] != 'd') + strlcpy(&g_szLogDst[0], pszValue, sizeof(g_szLogDst)); + return 0; +} + +/** log and dbg_log_dest parameter getter. */ +static int vgdrvLinuxParamLogDstGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam) +{ + PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance(); + *pszBuf = '\0'; + if (pLogger) + RTLogQueryDestinations(pLogger, pszBuf, _4K); + return strlen(pszBuf); +} + + +/** r3_log_to_host parameter setter. */ +static int vgdrvLinuxParamR3LogToHostSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam) +{ + g_DevExt.fLoggingEnabled = VBDrvCommonIsOptionValueTrue(pszValue); + return 0; +} + +/** r3_log_to_host parameter getter. */ +static int vgdrvLinuxParamR3LogToHostGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam) +{ + strcpy(pszBuf, g_DevExt.fLoggingEnabled ? "enabled" : "disabled"); + return strlen(pszBuf); +} + + +/* + * Define module parameters. + */ +module_param_call(log, vgdrvLinuxParamLogGrpSet, vgdrvLinuxParamLogGrpGet, NULL, 0664); +module_param_call(log_flags, vgdrvLinuxParamLogFlagsSet, vgdrvLinuxParamLogFlagsGet, NULL, 0664); +module_param_call(log_dest, vgdrvLinuxParamLogDstSet, vgdrvLinuxParamLogDstGet, NULL, 0664); +# ifdef LOG_ENABLED +module_param_call(dbg_log, vgdrvLinuxParamLogGrpSet, vgdrvLinuxParamLogGrpGet, NULL, 0664); +module_param_call(dbg_log_flags, vgdrvLinuxParamLogFlagsSet, vgdrvLinuxParamLogFlagsGet, NULL, 0664); +module_param_call(dbg_log_dest, vgdrvLinuxParamLogDstSet, vgdrvLinuxParamLogDstGet, NULL, 0664); +# endif +module_param_call(r3_log_to_host, vgdrvLinuxParamR3LogToHostSet, vgdrvLinuxParamR3LogToHostGet, NULL, 0664); + +#endif /* 2.6.0 and later */ + + +module_init(vgdrvLinuxModInit); +module_exit(vgdrvLinuxModExit); + +MODULE_AUTHOR(VBOX_VENDOR); +MODULE_DESCRIPTION(VBOX_PRODUCT " Guest Additions for Linux Module"); +MODULE_LICENSE("GPL"); +#ifdef MODULE_VERSION +MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV)); +#endif + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c new file mode 100644 index 00000000..a7c8c028 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c @@ -0,0 +1,1094 @@ +/* $Id: VBoxGuest-netbsd.c $ */ +/** @file + * VirtualBox Guest Additions Driver for NetBSD. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/select.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/kmem.h> +#include <sys/module.h> +#include <sys/device.h> +#include <sys/bus.h> +#include <sys/poll.h> +#include <sys/proc.h> +#include <sys/kauth.h> +#include <sys/stat.h> +#include <sys/selinfo.h> +#include <sys/queue.h> +#include <sys/lock.h> +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/malloc.h> +#include <sys/uio.h> +#include <sys/file.h> +#include <sys/filedesc.h> +#include <sys/vfs_syscalls.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pcireg.h> +#include <dev/pci/pcidevs.h> + +#include <dev/wscons/wsconsio.h> +#include <dev/wscons/wsmousevar.h> +#include <dev/wscons/tpcalibvar.h> + +#ifdef PVM +# undef PVM +#endif +#include "VBoxGuestInternal.h" +#include <VBox/log.h> +#include <iprt/err.h> +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/process.h> +#include <iprt/mem.h> +#include <iprt/asm.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The module name. */ +#define DEVICE_NAME "vboxguest" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct VBoxGuestDeviceState +{ + device_t sc_dev; + pci_chipset_tag_t sc_pc; + + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + bus_addr_t sc_iobase; + bus_size_t sc_iosize; + + bus_space_tag_t sc_memt; + bus_space_handle_t sc_memh; + + /** Size of the memory area. */ + bus_size_t sc_memsize; + + /** IRQ resource handle. */ + pci_intr_handle_t ih; + /** Pointer to the IRQ handler. */ + void *pfnIrqHandler; + + /** Controller features, limits and status. */ + u_int vboxguest_state; + + device_t sc_wsmousedev; + VMMDevReqMouseStatus *sc_vmmmousereq; + PVBOXGUESTSESSION sc_session; + struct tpcalib_softc sc_tpcalib; +} vboxguest_softc; + + +struct vboxguest_fdata +{ + vboxguest_softc *sc; + PVBOXGUESTSESSION session; +}; + +#define VBOXGUEST_STATE_INITOK 1 << 0 + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/* + * Driver(9) autoconf machinery. + */ +static int VBoxGuestNetBSDMatch(device_t parent, cfdata_t match, void *aux); +static void VBoxGuestNetBSDAttach(device_t parent, device_t self, void *aux); +static void VBoxGuestNetBSDWsmAttach(vboxguest_softc *sc); +static int VBoxGuestNetBSDDetach(device_t self, int flags); + +/* + * IRQ related functions. + */ +static int VBoxGuestNetBSDAddIRQ(vboxguest_softc *sc, struct pci_attach_args *pa); +static void VBoxGuestNetBSDRemoveIRQ(vboxguest_softc *sc); +static int VBoxGuestNetBSDISR(void *pvState); + +/* + * Character device file handlers. + */ +static int VBoxGuestNetBSDOpen(dev_t device, int flags, int fmt, struct lwp *process); +static int VBoxGuestNetBSDClose(struct file *fp); +static int VBoxGuestNetBSDIOCtl(struct file *fp, u_long cmd, void *addr); +static int VBoxGuestNetBSDIOCtlSlow(struct vboxguest_fdata *fdata, u_long command, void *data); +static int VBoxGuestNetBSDPoll(struct file *fp, int events); + +/* + * wsmouse(4) accessops + */ +static int VBoxGuestNetBSDWsmEnable(void *cookie); +static void VBoxGuestNetBSDWsmDisable(void *cookie); +static int VBoxGuestNetBSDWsmIOCtl(void *cookie, u_long cmd, void *data, int flag, struct lwp *l); + +static int VBoxGuestNetBSDSetMouseStatus(vboxguest_softc *sc, uint32_t fStatus); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +extern struct cfdriver vboxguest_cd; /* CFDRIVER_DECL */ +extern struct cfattach vboxguest_ca; /* CFATTACH_DECL */ + +/* + * The /dev/vboxguest character device entry points. + */ +static struct cdevsw g_VBoxGuestNetBSDChrDevSW = +{ + .d_open = VBoxGuestNetBSDOpen, + .d_close = noclose, + .d_read = noread, + .d_write = nowrite, + .d_ioctl = noioctl, + .d_stop = nostop, + .d_tty = notty, + .d_poll = nopoll, + .d_mmap = nommap, + .d_kqfilter = nokqfilter, +}; + +static const struct fileops vboxguest_fileops = { + .fo_read = fbadop_read, + .fo_write = fbadop_write, + .fo_ioctl = VBoxGuestNetBSDIOCtl, + .fo_fcntl = fnullop_fcntl, + .fo_poll = VBoxGuestNetBSDPoll, + .fo_stat = fbadop_stat, + .fo_close = VBoxGuestNetBSDClose, + .fo_kqfilter = fnullop_kqfilter, + .fo_restart = fnullop_restart +}; + + +const struct wsmouse_accessops vboxguest_wsm_accessops = { + VBoxGuestNetBSDWsmEnable, + VBoxGuestNetBSDWsmIOCtl, + VBoxGuestNetBSDWsmDisable, +}; + + +/* + * XXX: wsmux(4) doesn't properly handle the case when two mice with + * absolute position events but different calibration data are being + * multiplexed. Without GAs the absolute events will be reported + * through the tablet ums(4) device with the range of 32k, but with + * GAs the absolute events will be reported through the VMM device + * (wsmouse at vboxguest) and VMM uses the range of 64k. Which one + * responds to the calibration ioctl depends on the order of + * attachment. On boot kernel attaches ums first and GAs later, so + * it's VMM (this driver) that gets the ioctl. After save/restore the + * ums will be detached and re-attached and after that it's ums that + * will get the ioctl, but the events (with a wider range) will still + * come via the VMM, confusing X, wsmoused, etc. Hack around that by + * forcing the range here to match the tablet's range. + * + * We force VMM range into the ums range and rely on the fact that no + * actual calibration is done and both devices are used in the raw + * mode. See tpcalib_trans call below. + * + * Cf. src/VBox/Devices/Input/UsbMouse.cpp + */ +#define USB_TABLET_RANGE_MIN 0 +#define USB_TABLET_RANGE_MAX 0x7fff + +static struct wsmouse_calibcoords vboxguest_wsm_default_calib = { + .minx = USB_TABLET_RANGE_MIN, // VMMDEV_MOUSE_RANGE_MIN, + .miny = USB_TABLET_RANGE_MIN, // VMMDEV_MOUSE_RANGE_MIN, + .maxx = USB_TABLET_RANGE_MAX, // VMMDEV_MOUSE_RANGE_MAX, + .maxy = USB_TABLET_RANGE_MAX, // VMMDEV_MOUSE_RANGE_MAX, + .samplelen = WSMOUSE_CALIBCOORDS_RESET, +}; + +/** Device extention & session data association structure. */ +static VBOXGUESTDEVEXT g_DevExt; + +static vboxguest_softc *g_SC; + +/** Reference counter */ +static volatile uint32_t cUsers; +/** selinfo structure used for polling. */ +static struct selinfo g_SelInfo; + + +CFATTACH_DECL_NEW(vboxguest, sizeof(vboxguest_softc), + VBoxGuestNetBSDMatch, VBoxGuestNetBSDAttach, VBoxGuestNetBSDDetach, NULL); + + +static int VBoxGuestNetBSDMatch(device_t parent, cfdata_t match, void *aux) +{ + const struct pci_attach_args *pa = aux; + + if (RT_UNLIKELY(g_SC != NULL)) /* should not happen */ + return 0; + + if ( PCI_VENDOR(pa->pa_id) == VMMDEV_VENDORID + && PCI_PRODUCT(pa->pa_id) == VMMDEV_DEVICEID) + { + return 1; + } + + return 0; +} + + +static void VBoxGuestNetBSDAttach(device_t parent, device_t self, void *aux) +{ + int rc = VINF_SUCCESS; + int iResId = 0; + vboxguest_softc *sc; + struct pci_attach_args *pa = aux; + bus_space_tag_t iot, memt; + bus_space_handle_t ioh, memh; + bus_dma_segment_t seg; + int ioh_valid, memh_valid; + + KASSERT(g_SC == NULL); + + cUsers = 0; + + aprint_normal(": VirtualBox Guest\n"); + + sc = device_private(self); + sc->sc_dev = self; + + /* + * Initialize IPRT R0 driver, which internally calls OS-specific r0 init. + */ + rc = RTR0Init(0); + if (RT_FAILURE(rc)) + { + LogFunc(("RTR0Init failed.\n")); + aprint_error_dev(sc->sc_dev, "RTR0Init failed\n"); + return; + } + + sc->sc_pc = pa->pa_pc; + + /* + * Allocate I/O port resource. + */ + ioh_valid = (pci_mapreg_map(pa, PCI_BAR0, + PCI_MAPREG_TYPE_IO, 0, + &sc->sc_iot, &sc->sc_ioh, + &sc->sc_iobase, &sc->sc_iosize) == 0); + + if (ioh_valid) + { + + /* + * Map the MMIO region. + */ + memh_valid = (pci_mapreg_map(pa, PCI_BAR1, + PCI_MAPREG_TYPE_MEM, BUS_SPACE_MAP_LINEAR, + &sc->sc_memt, &sc->sc_memh, + NULL, &sc->sc_memsize) == 0); + if (memh_valid) + { + /* + * Call the common device extension initializer. + */ + rc = VGDrvCommonInitDevExt(&g_DevExt, sc->sc_iobase, + bus_space_vaddr(sc->sc_memt, sc->sc_memh), + sc->sc_memsize, +#if ARCH_BITS == 64 + VBOXOSTYPE_NetBSD_x64, +#else + VBOXOSTYPE_NetBSD, +#endif + VMMDEV_EVENT_MOUSE_POSITION_CHANGED); + if (RT_SUCCESS(rc)) + { + /* + * Add IRQ of VMMDev. + */ + rc = VBoxGuestNetBSDAddIRQ(sc, pa); + if (RT_SUCCESS(rc)) + { + sc->vboxguest_state |= VBOXGUEST_STATE_INITOK; + + /* + * Read host configuration. + */ + VGDrvCommonProcessOptionsFromHost(&g_DevExt); + + /* + * Attach wsmouse. + */ + VBoxGuestNetBSDWsmAttach(sc); + + g_SC = sc; + return; + } + VGDrvCommonDeleteDevExt(&g_DevExt); + } + else + { + aprint_error_dev(sc->sc_dev, "init failed\n"); + } + bus_space_unmap(sc->sc_memt, sc->sc_memh, sc->sc_memsize); + } + else + { + aprint_error_dev(sc->sc_dev, "MMIO mapping failed\n"); + } + bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_iosize); + } + else + { + aprint_error_dev(sc->sc_dev, "IO mapping failed\n"); + } + + RTR0Term(); + return; +} + + +/** + * Sets IRQ for VMMDev. + * + * @returns NetBSD error code. + * @param sc Pointer to the state info structure. + * @param pa Pointer to the PCI attach arguments. + */ +static int VBoxGuestNetBSDAddIRQ(vboxguest_softc *sc, struct pci_attach_args *pa) +{ + int iResId = 0; + int rc = 0; + const char *intrstr; +#if __NetBSD_Prereq__(6, 99, 39) + char intstrbuf[100]; +#endif + + LogFlow((DEVICE_NAME ": %s\n", __func__)); + + if (pci_intr_map(pa, &sc->ih)) + { + aprint_error_dev(sc->sc_dev, "couldn't map interrupt.\n"); + return VERR_DEV_IO_ERROR; + } + + intrstr = pci_intr_string(sc->sc_pc, sc->ih +#if __NetBSD_Prereq__(6, 99, 39) + , intstrbuf, sizeof(intstrbuf) +#endif + ); + aprint_normal_dev(sc->sc_dev, "interrupting at %s\n", intrstr); + + sc->pfnIrqHandler = pci_intr_establish(sc->sc_pc, sc->ih, IPL_BIO, VBoxGuestNetBSDISR, sc); + if (sc->pfnIrqHandler == NULL) + { + aprint_error_dev(sc->sc_dev, "couldn't establish interrupt\n"); + return VERR_DEV_IO_ERROR; + } + + return VINF_SUCCESS; +} + + +/* + * Optionally attach wsmouse(4) device as a child. + */ +static void VBoxGuestNetBSDWsmAttach(vboxguest_softc *sc) +{ + struct wsmousedev_attach_args am = { &vboxguest_wsm_accessops, sc }; + + PVBOXGUESTSESSION session = NULL; + VMMDevReqMouseStatus *req = NULL; + int rc; + + rc = VGDrvCommonCreateKernelSession(&g_DevExt, &session); + if (RT_FAILURE(rc)) + goto fail; + + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&req, sizeof(*req), + VMMDevReq_GetMouseStatus); + if (RT_FAILURE(rc)) + goto fail; + +#if __NetBSD_Prereq__(9,99,88) + sc->sc_wsmousedev = config_found(sc->sc_dev, &am, wsmousedevprint, + CFARGS(.iattr = "wsmousedev")); +#elif __NetBSD_Prereq__(9,99,82) + sc->sc_wsmousedev = config_found(sc->sc_dev, &am, wsmousedevprint, + CFARG_IATTR, "wsmousedev", + CFARG_EOL); +#else + sc->sc_wsmousedev = config_found_ia(sc->sc_dev, "wsmousedev", + &am, wsmousedevprint); +#endif + + if (sc->sc_wsmousedev == NULL) + goto fail; + + sc->sc_session = session; + sc->sc_vmmmousereq = req; + + tpcalib_init(&sc->sc_tpcalib); + tpcalib_ioctl(&sc->sc_tpcalib, WSMOUSEIO_SCALIBCOORDS, + &vboxguest_wsm_default_calib, 0, 0); + return; + + fail: + if (session != NULL) + VGDrvCommonCloseSession(&g_DevExt, session); + if (req != NULL) + VbglR0GRFree((VMMDevRequestHeader *)req); +} + + +static int VBoxGuestNetBSDDetach(device_t self, int flags) +{ + vboxguest_softc *sc; + sc = device_private(self); + + LogFlow((DEVICE_NAME ": %s\n", __func__)); + + if (cUsers > 0) + return EBUSY; + + if ((sc->vboxguest_state & VBOXGUEST_STATE_INITOK) == 0) + return 0; + + /* + * Reverse what we did in VBoxGuestNetBSDAttach. + */ + if (sc->sc_vmmmousereq != NULL) + VbglR0GRFree((VMMDevRequestHeader *)sc->sc_vmmmousereq); + + VBoxGuestNetBSDRemoveIRQ(sc); + + VGDrvCommonDeleteDevExt(&g_DevExt); + + bus_space_unmap(sc->sc_memt, sc->sc_memh, sc->sc_memsize); + bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_iosize); + + RTR0Term(); + + return config_detach_children(self, flags); +} + + +/** + * Removes IRQ for VMMDev. + * + * @param sc Opaque pointer to the state info structure. + */ +static void VBoxGuestNetBSDRemoveIRQ(vboxguest_softc *sc) +{ + LogFlow((DEVICE_NAME ": %s\n", __func__)); + + if (sc->pfnIrqHandler) + { + pci_intr_disestablish(sc->sc_pc, sc->pfnIrqHandler); + } +} + + +/** + * Interrupt service routine. + * + * @returns Whether the interrupt was from VMMDev. + * @param pvState Opaque pointer to the device state. + */ +static int VBoxGuestNetBSDISR(void *pvState) +{ + LogFlow((DEVICE_NAME ": %s: pvState=%p\n", __func__, pvState)); + + bool fOurIRQ = VGDrvCommonISR(&g_DevExt); + + return fOurIRQ ? 1 : 0; +} + + +/* + * Called by VGDrvCommonISR() if mouse position changed + */ +void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt) +{ + vboxguest_softc *sc = g_SC; + + LogFlow((DEVICE_NAME ": %s\n", __func__)); + + /* + * Wake up poll waiters. + */ + selnotify(&g_SelInfo, 0, 0); + + if (sc->sc_vmmmousereq != NULL) { + int x, y; + int rc; + + sc->sc_vmmmousereq->mouseFeatures = 0; + sc->sc_vmmmousereq->pointerXPos = 0; + sc->sc_vmmmousereq->pointerYPos = 0; + + rc = VbglR0GRPerform(&sc->sc_vmmmousereq->header); + if (RT_FAILURE(rc)) + return; + + /* XXX: see the comment for vboxguest_wsm_default_calib */ + int rawx = (unsigned)sc->sc_vmmmousereq->pointerXPos >> 1; + int rawy = (unsigned)sc->sc_vmmmousereq->pointerYPos >> 1; + tpcalib_trans(&sc->sc_tpcalib, rawx, rawy, &x, &y); + + wsmouse_input(sc->sc_wsmousedev, + 0, /* buttons */ + x, y, + 0, 0, /* z, w */ + WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y); + } +} + + +bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue) +{ + RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue); + return false; +} + + +static int VBoxGuestNetBSDSetMouseStatus(vboxguest_softc *sc, uint32_t fStatus) +{ + VBGLIOCSETMOUSESTATUS Req; + int rc; + + VBGLREQHDR_INIT(&Req.Hdr, SET_MOUSE_STATUS); + Req.u.In.fStatus = fStatus; + rc = VGDrvCommonIoCtl(VBGL_IOCTL_SET_MOUSE_STATUS, + &g_DevExt, + sc->sc_session, + &Req.Hdr, sizeof(Req)); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + + return rc; +} + + +static int +VBoxGuestNetBSDWsmEnable(void *cookie) +{ + vboxguest_softc *sc = cookie; + int rc; + + rc = VBoxGuestNetBSDSetMouseStatus(sc, VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE + | VMMDEV_MOUSE_NEW_PROTOCOL); + if (RT_FAILURE(rc)) + return RTErrConvertToErrno(rc); + + return 0; +} + + +static void +VBoxGuestNetBSDWsmDisable(void *cookie) +{ + vboxguest_softc *sc = cookie; + VBoxGuestNetBSDSetMouseStatus(sc, 0); +} + + +static int +VBoxGuestNetBSDWsmIOCtl(void *cookie, u_long cmd, void *data, int flag, struct lwp *l) +{ + vboxguest_softc *sc = cookie; + + switch (cmd) { + case WSMOUSEIO_GTYPE: + *(u_int *)data = WSMOUSE_TYPE_TPANEL; + break; + + case WSMOUSEIO_SCALIBCOORDS: + case WSMOUSEIO_GCALIBCOORDS: + return tpcalib_ioctl(&sc->sc_tpcalib, cmd, data, flag, l); + + default: + return EPASSTHROUGH; + } + return 0; +} + + +/** + * File open handler + * + */ +static int VBoxGuestNetBSDOpen(dev_t device, int flags, int fmt, struct lwp *pLwp) +{ + vboxguest_softc *sc; + struct vboxguest_fdata *fdata; + file_t *fp; + int fd, error; + + LogFlow((DEVICE_NAME ": %s\n", __func__)); + + if ((sc = device_lookup_private(&vboxguest_cd, minor(device))) == NULL) + { + printf("device_lookup_private failed\n"); + return (ENXIO); + } + + if ((sc->vboxguest_state & VBOXGUEST_STATE_INITOK) == 0) + { + aprint_error_dev(sc->sc_dev, "device not configured\n"); + return (ENXIO); + } + + fdata = kmem_alloc(sizeof(*fdata), KM_SLEEP); + if (fdata != NULL) + { + fdata->sc = sc; + + error = fd_allocfile(&fp, &fd); + if (error == 0) + { + /* + * Create a new session. + */ + struct kauth_cred *pCred = pLwp->l_cred; + int fHaveCred = (pCred != NULL && pCred != NOCRED && pCred != FSCRED); + uint32_t fRequestor; + int fIsWheel; + int rc; + + fRequestor = VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN; + + /* uid */ + if (fHaveCred && kauth_cred_geteuid(pCred) == (uid_t)0) + fRequestor |= VMMDEV_REQUESTOR_USR_ROOT; + else + fRequestor |= VMMDEV_REQUESTOR_USR_USER; + + /* gid */ + if (fHaveCred + && (kauth_cred_getegid(pCred) == (gid_t)0 + || (kauth_cred_ismember_gid(pCred, 0, &fIsWheel) == 0 + && fIsWheel))) + fRequestor |= VMMDEV_REQUESTOR_GRP_WHEEL; + +#if 0 /** @todo implement /dev/vboxuser */ + if (!fUnrestricted) + fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE; +#else + fRequestor |= VMMDEV_REQUESTOR_NO_USER_DEVICE; +#endif + + /** @todo can we find out if pLwp is on the console? */ + fRequestor |= VMMDEV_REQUESTOR_CON_DONT_KNOW; + + rc = VGDrvCommonCreateUserSession(&g_DevExt, fRequestor, &fdata->session); + if (RT_SUCCESS(rc)) + { + ASMAtomicIncU32(&cUsers); + return fd_clone(fp, fd, flags, &vboxguest_fileops, fdata); + } + + aprint_error_dev(sc->sc_dev, "VBox session creation failed\n"); + closef(fp); /* ??? */ + error = RTErrConvertToErrno(rc); + } + kmem_free(fdata, sizeof(*fdata)); + } + else + error = ENOMEM; + return error; +} + +/** + * File close handler + * + */ +static int VBoxGuestNetBSDClose(struct file *fp) +{ + struct vboxguest_fdata *fdata = fp->f_data; + vboxguest_softc *sc = fdata->sc; + + LogFlow((DEVICE_NAME ": %s\n", __func__)); + + VGDrvCommonCloseSession(&g_DevExt, fdata->session); + ASMAtomicDecU32(&cUsers); + + kmem_free(fdata, sizeof(*fdata)); + + return 0; +} + +/** + * IOCTL handler + * + */ +static int VBoxGuestNetBSDIOCtl(struct file *fp, u_long command, void *data) +{ + struct vboxguest_fdata *fdata = fp->f_data; + + if (VBGL_IOCTL_IS_FAST(command)) + return VGDrvCommonIoCtlFast(command, &g_DevExt, fdata->session); + + return VBoxGuestNetBSDIOCtlSlow(fdata, command, data); +} + +static int VBoxGuestNetBSDIOCtlSlow(struct vboxguest_fdata *fdata, u_long command, void *data) +{ + vboxguest_softc *sc = fdata->sc; + size_t cbReq = IOCPARM_LEN(command); + PVBGLREQHDR pHdr = NULL; + void *pvUser = NULL; + int err, rc; + + LogFlow(("%s: command=%#lx data=%p\n", __func__, command, data)); + + /* + * Buffered request? + */ + if ((command & IOC_DIRMASK) == IOC_INOUT) + { + /* will be validated by VGDrvCommonIoCtl() */ + pHdr = (PVBGLREQHDR)data; + } + + /* + * Big unbuffered request? "data" is the userland pointer. + */ + else if ((command & IOC_DIRMASK) == IOC_VOID && cbReq != 0) + { + /* + * Read the header, validate it and figure out how much that + * needs to be buffered. + */ + VBGLREQHDR Hdr; + + if (RT_UNLIKELY(cbReq < sizeof(Hdr))) + return ENOTTY; + + pvUser = data; + err = copyin(pvUser, &Hdr, sizeof(Hdr)); + if (RT_UNLIKELY(err != 0)) + return err; + + if (RT_UNLIKELY(Hdr.uVersion != VBGLREQHDR_VERSION)) + return ENOTTY; + + if (cbReq > 16 * _1M) + return EINVAL; + + if (Hdr.cbOut == 0) + Hdr.cbOut = Hdr.cbIn; + + if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr) || Hdr.cbIn > cbReq + || Hdr.cbOut < sizeof(Hdr) || Hdr.cbOut > cbReq)) + return EINVAL; + + /* + * Allocate buffer and copy in the data. + */ + cbReq = RT_MAX(Hdr.cbIn, Hdr.cbOut); + + pHdr = (PVBGLREQHDR)RTMemTmpAlloc(cbReq); + if (RT_UNLIKELY(pHdr == NULL)) + { + LogRel(("%s: command=%#lx data=%p: unable to allocate %zu bytes\n", + __func__, command, data, cbReq)); + return ENOMEM; + } + + err = copyin(pvUser, pHdr, Hdr.cbIn); + if (err != 0) + { + RTMemTmpFree(pHdr); + return err; + } + + if (Hdr.cbIn < cbReq) + memset((uint8_t *)pHdr + Hdr.cbIn, '\0', cbReq - Hdr.cbIn); + } + + /* + * Process the IOCtl. + */ + rc = VGDrvCommonIoCtl(command, &g_DevExt, fdata->session, pHdr, cbReq); + if (RT_SUCCESS(rc)) + { + err = 0; + + /* + * If unbuffered, copy back the result before returning. + */ + if (pvUser != NULL) + { + size_t cbOut = pHdr->cbOut; + if (cbOut > cbReq) + { + LogRel(("%s: command=%#lx data=%p: too much output: %zu > %zu\n", + __func__, command, data, cbOut, cbReq)); + cbOut = cbReq; + } + + err = copyout(pHdr, pvUser, cbOut); + RTMemTmpFree(pHdr); + } + } + else + { + LogRel(("%s: command=%#lx data=%p: error %Rrc\n", + __func__, command, data, rc)); + + if (pvUser != NULL) + RTMemTmpFree(pHdr); + + err = RTErrConvertToErrno(rc); + } + + return err; +} + +static int VBoxGuestNetBSDPoll(struct file *fp, int events) +{ + struct vboxguest_fdata *fdata = fp->f_data; + vboxguest_softc *sc = fdata->sc; + + int rc = 0; + int events_processed; + + uint32_t u32CurSeq; + + LogFlow((DEVICE_NAME ": %s\n", __func__)); + + u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq); + if (fdata->session->u32MousePosChangedSeq != u32CurSeq) + { + events_processed = events & (POLLIN | POLLRDNORM); + fdata->session->u32MousePosChangedSeq = u32CurSeq; + } + else + { + events_processed = 0; + + selrecord(curlwp, &g_SelInfo); + } + + return events_processed; +} + + +/** + * @note This code is duplicated on other platforms with variations, so please + * keep them all up to date when making changes! + */ +int VBOXCALL VBoxGuestIDC(void *pvSession, uintptr_t uReq, PVBGLREQHDR pReqHdr, size_t cbReq) +{ + /* + * Simple request validation (common code does the rest). + */ + int rc; + if ( RT_VALID_PTR(pReqHdr) + && cbReq >= sizeof(*pReqHdr)) + { + /* + * All requests except the connect one requires a valid session. + */ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pvSession; + if (pSession) + { + if ( RT_VALID_PTR(pSession) + && pSession->pDevExt == &g_DevExt) + rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); + else + rc = VERR_INVALID_HANDLE; + } + else if (uReq == VBGL_IOCTL_IDC_CONNECT) + { + rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession); + if (RT_SUCCESS(rc)) + { + rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); + if (RT_FAILURE(rc)) + VGDrvCommonCloseSession(&g_DevExt, pSession); + } + } + else + rc = VERR_INVALID_HANDLE; + } + else + rc = VERR_INVALID_POINTER; + return rc; +} + + +MODULE(MODULE_CLASS_DRIVER, vboxguest, "pci"); + +/* + * XXX: See netbsd/vboxguest.ioconf for the details. +*/ +#if 0 +#include "ioconf.c" +#else + +static const struct cfiattrdata wsmousedevcf_iattrdata = { + "wsmousedev", 1, { + { "mux", "0", 0 }, + } +}; + +/* device vboxguest: wsmousedev */ +static const struct cfiattrdata * const vboxguest_attrs[] = { &wsmousedevcf_iattrdata, NULL }; +CFDRIVER_DECL(vboxguest, DV_DULL, vboxguest_attrs); + +static struct cfdriver * const cfdriver_ioconf_vboxguest[] = { + &vboxguest_cd, NULL +}; + + +static const struct cfparent vboxguest_pspec = { + "pci", "pci", DVUNIT_ANY +}; +static int vboxguest_loc[] = { -1, -1 }; + + +static const struct cfparent wsmousedev_pspec = { + "wsmousedev", "vboxguest", DVUNIT_ANY +}; +static int wsmousedev_loc[] = { 0 }; + + +static struct cfdata cfdata_ioconf_vboxguest[] = { + /* vboxguest0 at pci? dev ? function ? */ + { + .cf_name = "vboxguest", + .cf_atname = "vboxguest", + .cf_unit = 0, /* Only unit 0 is ever used */ + .cf_fstate = FSTATE_NOTFOUND, + .cf_loc = vboxguest_loc, + .cf_flags = 0, + .cf_pspec = &vboxguest_pspec, + }, + + /* wsmouse* at vboxguest? */ + { "wsmouse", "wsmouse", 0, FSTATE_STAR, wsmousedev_loc, 0, &wsmousedev_pspec }, + + { NULL, NULL, 0, 0, NULL, 0, NULL } +}; + +static struct cfattach * const vboxguest_cfattachinit[] = { + &vboxguest_ca, NULL +}; + +static const struct cfattachinit cfattach_ioconf_vboxguest[] = { + { "vboxguest", vboxguest_cfattachinit }, + { NULL, NULL } +}; +#endif + + +static int +vboxguest_modcmd(modcmd_t cmd, void *opaque) +{ + devmajor_t bmajor, cmajor; +#if !__NetBSD_Prereq__(8,99,46) + register_t retval; +#endif + int error; + + LogFlow((DEVICE_NAME ": %s\n", __func__)); + + switch (cmd) + { + case MODULE_CMD_INIT: + error = config_init_component(cfdriver_ioconf_vboxguest, + cfattach_ioconf_vboxguest, + cfdata_ioconf_vboxguest); + if (error) + break; + + bmajor = cmajor = NODEVMAJOR; + error = devsw_attach("vboxguest", + NULL, &bmajor, + &g_VBoxGuestNetBSDChrDevSW, &cmajor); + if (error) + { + if (error == EEXIST) + error = 0; /* maybe built-in ... improve eventually */ + else + break; + } + + error = do_sys_mknod(curlwp, "/dev/vboxguest", + 0666|S_IFCHR, makedev(cmajor, 0), +#if !__NetBSD_Prereq__(8,99,46) + &retval, +#endif + UIO_SYSSPACE); + if (error == EEXIST) { + error = 0; + + /* + * Since NetBSD doesn't yet have a major reserved for + * vboxguest, the (first free) major we get will + * change when new devices are added, so an existing + * /dev/vboxguest may now point to some other device, + * creating confusion (tripped me up a few times). + */ + aprint_normal("vboxguest: major %d:" + " check existing /dev/vboxguest\n", cmajor); + } + break; + + case MODULE_CMD_FINI: + error = config_fini_component(cfdriver_ioconf_vboxguest, + cfattach_ioconf_vboxguest, + cfdata_ioconf_vboxguest); + if (error) + break; + + devsw_detach(NULL, &g_VBoxGuestNetBSDChrDevSW); + break; + + default: + return ENOTTY; + } + return error; +} diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.cpp b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.cpp new file mode 100644 index 00000000..10363a65 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.cpp @@ -0,0 +1,698 @@ +/* $Id: VBoxGuest-os2.cpp $ */ +/** @file + * VBoxGuest - OS/2 specifics. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + * --------------------------------------------------------------------------- + * This code is based on: + * + * VBoxDrv - OS/2 specifics. + * + * Copyright (c) 2007-2012 knut st. osmundsen <bird-src-spam@anduin.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <os2ddk/bsekee.h> + +#include "VBoxGuestInternal.h" +#include <VBox/version.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/initterm.h> +#include <iprt/log.h> +#include <iprt/memobj.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/process.h> +#include <iprt/spinlock.h> +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Device extention & session data association structure. + */ +static VBOXGUESTDEVEXT g_DevExt; +/** The memory object for the MMIO memory. */ +static RTR0MEMOBJ g_MemObjMMIO = NIL_RTR0MEMOBJ; +/** The memory mapping object the MMIO memory. */ +static RTR0MEMOBJ g_MemMapMMIO = NIL_RTR0MEMOBJ; + +/** Spinlock protecting g_apSessionHashTab. */ +static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK; +/** Hash table */ +static PVBOXGUESTSESSION g_apSessionHashTab[19]; +/** Calculates the index into g_apSessionHashTab.*/ +#define SESSION_HASH(sfn) ((sfn) % RT_ELEMENTS(g_apSessionHashTab)) + +RT_C_DECLS_BEGIN +/* Defined in VBoxGuestA-os2.asm */ +extern uint32_t g_PhysMMIOBase; +extern uint32_t g_cbMMIO; /* 0 currently not set. */ +extern uint16_t g_IOPortBase; +extern uint8_t g_bInterruptLine; +extern uint8_t g_bPciBusNo; +extern uint8_t g_bPciDevFunNo; +extern RTFAR16 g_fpfnVBoxGuestOs2IDCService16; +extern RTFAR16 g_fpfnVBoxGuestOs2IDCService16Asm; +#ifdef DEBUG_READ +/* (debugging) */ +extern uint16_t g_offLogHead; +extern uint16_t volatile g_offLogTail; +extern uint16_t const g_cchLogMax; +extern char g_szLog[]; +#endif +/* (init only:) */ +extern char g_szInitText[]; +extern uint16_t g_cchInitText; +extern uint16_t g_cchInitTextMax; +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vgdrvOS2MapMemory(void); +static VBOXOSTYPE vgdrvOS2DetectVersion(void); + +/* in VBoxGuestA-os2.asm */ +DECLASM(int) vgdrvOS2DevHlpSetIRQ(uint8_t bIRQ); + + +/** + * 32-bit Ring-0 initialization. + * + * This is called from VBoxGuestA-os2.asm upon the first open call to the vboxgst$ device. + * + * @returns 0 on success, non-zero on failure. + * @param pszArgs Pointer to the device arguments. + */ +DECLASM(int) vgdrvOS2Init(const char *pszArgs) +{ + //Log(("vgdrvOS2Init: pszArgs='%s' MMIO=0x%RX32 IOPort=0x%RX16 Int=%#x Bus=%#x Dev=%#x Fun=%d\n", + // pszArgs, g_PhysMMIOBase, g_IOPortBase, g_bInterruptLine, g_bPciBusNo, g_bPciDevFunNo >> 3, g_bPciDevFunNo & 7)); + + /* + * Initialize the runtime. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + /* + * Process the command line. + */ + bool fVerbose = true; + if (pszArgs) + { + char ch; + while ((ch = *pszArgs++) != '\0') + if (ch == '-' || ch == '/') + { + ch = *pszArgs++; + if (ch == 'Q' || ch == 'q') + fVerbose = false; + else if (ch == 'V' || ch == 'v') + fVerbose = true; + else if (ch == '\0') + break; + /*else: ignore stuff we don't know what is */ + } + /* else: skip spaces and unknown stuff */ + } + + /* + * Map the MMIO memory if found. + */ + rc = vgdrvOS2MapMemory(); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the device extension. + */ + if (g_MemMapMMIO != NIL_RTR0MEMOBJ) + rc = VGDrvCommonInitDevExt(&g_DevExt, g_IOPortBase, + RTR0MemObjAddress(g_MemMapMMIO), + RTR0MemObjSize(g_MemMapMMIO), + vgdrvOS2DetectVersion(), + 0); + else + rc = VGDrvCommonInitDevExt(&g_DevExt, g_IOPortBase, NULL, 0, vgdrvOS2DetectVersion(), 0); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the session hash table. + */ + rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxGuestOS2"); + if (RT_SUCCESS(rc)) + { + /* + * Configure the interrupt handler. + */ + if (g_bInterruptLine) + { + rc = vgdrvOS2DevHlpSetIRQ(g_bInterruptLine); + if (rc) + { + Log(("vgdrvOS2DevHlpSetIRQ(%d) -> %d\n", g_bInterruptLine, rc)); + rc = RTErrConvertFromOS2(rc); + } + } + if (RT_SUCCESS(rc)) + { + /* + * Read host configuration. + */ + VGDrvCommonProcessOptionsFromHost(&g_DevExt); + + /* + * Success + */ + if (fVerbose) + { + strcpy(&g_szInitText[0], + "\r\n" + "VirtualBox Guest Additions Driver for OS/2 version " VBOX_VERSION_STRING "\r\n" + "Copyright (C) 2008-" VBOX_C_YEAR " " VBOX_VENDOR "\r\n"); + g_cchInitText = strlen(&g_szInitText[0]); + } + Log(("vgdrvOS2Init: Successfully loaded\n%s", g_szInitText)); + return VINF_SUCCESS; + } + + g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxGuest.sys: SetIrq failed for IRQ %#d, rc=%Rrc\n", + g_bInterruptLine, rc); + } + else + g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxGuest.sys: RTSpinlockCreate failed, rc=%Rrc\n", rc); + VGDrvCommonDeleteDevExt(&g_DevExt); + } + else + g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxGuest.sys: vgdrvOS2InitDevExt failed, rc=%Rrc\n", rc); + + int rc2 = RTR0MemObjFree(g_MemObjMMIO, true /* fFreeMappings */); AssertRC(rc2); + g_MemObjMMIO = g_MemMapMMIO = NIL_RTR0MEMOBJ; + } + else + g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxGuest.sys: VBoxGuestOS2MapMMIO failed, rc=%Rrc\n", rc); + RTR0Term(); + } + else + g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxGuest.sys: RTR0Init failed, rc=%Rrc\n", rc); + + RTLogBackdoorPrintf("vgdrvOS2Init: failed rc=%Rrc - %s", rc, &g_szInitText[0]); + return rc; +} + + +/** + * Maps the VMMDev memory. + * + * @returns VBox status code. + * @retval VERR_VERSION_MISMATCH The VMMDev memory didn't meet our expectations. + */ +static int vgdrvOS2MapMemory(void) +{ + const RTCCPHYS PhysMMIOBase = g_PhysMMIOBase; + + /* + * Did we find any MMIO region (0 or NIL)? + */ + if ( !PhysMMIOBase + || PhysMMIOBase == NIL_RTCCPHYS) + { + Assert(g_MemMapMMIO != NIL_RTR0MEMOBJ); + return VINF_SUCCESS; + } + + /* + * Create a physical memory object for it. + * + * Since we don't know the actual size (OS/2 doesn't at least), we make + * a qualified guess using the VMMDEV_RAM_SIZE. + */ + size_t cb = RT_ALIGN_Z(VMMDEV_RAM_SIZE, PAGE_SIZE); + int rc = RTR0MemObjEnterPhys(&g_MemObjMMIO, PhysMMIOBase, cb, RTMEM_CACHE_POLICY_DONT_CARE); + if (RT_FAILURE(rc)) + { + cb = _4K; + rc = RTR0MemObjEnterPhys(&g_MemObjMMIO, PhysMMIOBase, cb, RTMEM_CACHE_POLICY_DONT_CARE); + } + if (RT_FAILURE(rc)) + { + Log(("vgdrvOS2MapMemory: RTR0MemObjEnterPhys(,%RCp,%zx) -> %Rrc\n", PhysMMIOBase, cb, rc)); + return rc; + } + + /* + * Map the object into kernel space. + * + * We want a normal mapping with normal caching, which good in two ways. First + * since the API doesn't have any flags indicating how the mapping should be cached. + * And second, because PGM doesn't necessarily respect the cache/writethru bits + * anyway for normal RAM. + */ + rc = RTR0MemObjMapKernel(&g_MemMapMMIO, g_MemObjMMIO, (void *)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + if (RT_SUCCESS(rc)) + { + /* + * Validate the VMM memory. + */ + VMMDevMemory *pVMMDev = (VMMDevMemory *)RTR0MemObjAddress(g_MemMapMMIO); + Assert(pVMMDev); + if ( pVMMDev->u32Version == VMMDEV_MEMORY_VERSION + && pVMMDev->u32Size >= 32 /* just for checking sanity */) + { + /* + * Did we hit the correct size? If not we'll have to + * redo the mapping using the correct size. + */ + if (RT_ALIGN_32(pVMMDev->u32Size, PAGE_SIZE) == cb) + return VINF_SUCCESS; + + Log(("vgdrvOS2MapMemory: Actual size %#RX32 (tried %#zx)\n", pVMMDev->u32Size, cb)); + cb = RT_ALIGN_32(pVMMDev->u32Size, PAGE_SIZE); + + rc = RTR0MemObjFree(g_MemObjMMIO, true); AssertRC(rc); + g_MemObjMMIO = g_MemMapMMIO = NIL_RTR0MEMOBJ; + + rc = RTR0MemObjEnterPhys(&g_MemObjMMIO, PhysMMIOBase, cb, RTMEM_CACHE_POLICY_DONT_CARE); + if (RT_SUCCESS(rc)) + { + rc = RTR0MemObjMapKernel(&g_MemMapMMIO, g_MemObjMMIO, (void *)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + Log(("vgdrvOS2MapMemory: RTR0MemObjMapKernel [%RCp,%zx] -> %Rrc (2nd)\n", PhysMMIOBase, cb, rc)); + } + else + Log(("vgdrvOS2MapMemory: RTR0MemObjEnterPhys(,%RCp,%zx) -> %Rrc (2nd)\n", PhysMMIOBase, cb, rc)); + } + else + { + rc = VERR_VERSION_MISMATCH; + LogRel(("vgdrvOS2MapMemory: Bogus VMMDev memory; u32Version=%RX32 (expected %RX32) u32Size=%RX32\n", + pVMMDev->u32Version, VMMDEV_MEMORY_VERSION, pVMMDev->u32Size)); + } + } + else + Log(("vgdrvOS2MapMemory: RTR0MemObjMapKernel [%RCp,%zx] -> %Rrc\n", PhysMMIOBase, cb, rc)); + + int rc2 = RTR0MemObjFree(g_MemObjMMIO, true /* fFreeMappings */); AssertRC(rc2); + g_MemObjMMIO = g_MemMapMMIO = NIL_RTR0MEMOBJ; + return rc; +} + + +/** + * Called fromn vgdrvOS2Init to determine which OS/2 version this is. + * + * @returns VBox OS/2 type. + */ +static VBOXOSTYPE vgdrvOS2DetectVersion(void) +{ + VBOXOSTYPE enmOSType = VBOXOSTYPE_OS2; + +#if 0 /** @todo dig up the version stuff from GIS later and verify that the numbers are actually decimal. */ + unsigned uMajor, uMinor; + if (uMajor == 2) + { + if (uMinor >= 30 && uMinor < 40) + enmOSType = VBOXOSTYPE_OS2Warp3; + else if (uMinor >= 40 && uMinor < 45) + enmOSType = VBOXOSTYPE_OS2Warp4; + else if (uMinor >= 45 && uMinor < 50) + enmOSType = VBOXOSTYPE_OS2Warp45; + } +#endif + return enmOSType; +} + + +DECLASM(int) vgdrvOS2Open(uint16_t sfn) +{ + int rc; + PVBOXGUESTSESSION pSession; + + /* + * Create a new session. + */ + uint32_t fRequestor = VMMDEV_REQUESTOR_USERMODE + | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN + | VMMDEV_REQUESTOR_USR_ROOT /* everyone is root on OS/2 */ + | VMMDEV_REQUESTOR_GRP_WHEEL /* and their admins */ + | VMMDEV_REQUESTOR_NO_USER_DEVICE /** @todo implement /dev/vboxuser? */ + | VMMDEV_REQUESTOR_CON_DONT_KNOW; /** @todo check screen group/whatever of process to see if console */ + rc = VGDrvCommonCreateUserSession(&g_DevExt, fRequestor, &pSession); + if (RT_SUCCESS(rc)) + { + pSession->sfn = sfn; + + /* + * Insert it into the hash table. + */ + unsigned iHash = SESSION_HASH(sfn); + RTSpinlockAcquire(g_Spinlock); + pSession->pNextHash = g_apSessionHashTab[iHash]; + g_apSessionHashTab[iHash] = pSession; + RTSpinlockRelease(g_Spinlock); + } + + Log(("vgdrvOS2Open: g_DevExt=%p pSession=%p rc=%d pid=%d\n", &g_DevExt, pSession, rc, (int)RTProcSelf())); + return rc; +} + + +DECLASM(int) vgdrvOS2Close(uint16_t sfn) +{ + Log(("vgdrvOS2Close: pid=%d sfn=%d\n", (int)RTProcSelf(), sfn)); + + /* + * Remove from the hash table. + */ + PVBOXGUESTSESSION pSession; + const RTPROCESS Process = RTProcSelf(); + const unsigned iHash = SESSION_HASH(sfn); + RTSpinlockAcquire(g_Spinlock); + + pSession = g_apSessionHashTab[iHash]; + if (pSession) + { + if ( pSession->sfn == sfn + && pSession->Process == Process) + { + g_apSessionHashTab[iHash] = pSession->pNextHash; + pSession->pNextHash = NULL; + } + else + { + PVBOXGUESTSESSION pPrev = pSession; + pSession = pSession->pNextHash; + while (pSession) + { + if ( pSession->sfn == sfn + && pSession->Process == Process) + { + pPrev->pNextHash = pSession->pNextHash; + pSession->pNextHash = NULL; + break; + } + + /* next */ + pPrev = pSession; + pSession = pSession->pNextHash; + } + } + } + RTSpinlockRelease(g_Spinlock); + if (!pSession) + { + Log(("VBoxGuestIoctl: WHUT?!? pSession == NULL! This must be a mistake... pid=%d sfn=%d\n", (int)Process, sfn)); + return VERR_INVALID_PARAMETER; + } + + /* + * Close the session. + */ + VGDrvCommonCloseSession(&g_DevExt, pSession); + return 0; +} + + +DECLASM(int) vgdrvOS2IOCtlFast(uint16_t sfn, uint8_t iFunction, int32_t *prc) +{ + /* + * Find the session. + */ + const RTPROCESS Process = RTProcSelf(); + const unsigned iHash = SESSION_HASH(sfn); + PVBOXGUESTSESSION pSession; + + RTSpinlockAcquire(g_Spinlock); + pSession = g_apSessionHashTab[iHash]; + if (pSession && pSession->Process != Process) + { + do pSession = pSession->pNextHash; + while ( pSession + && ( pSession->sfn != sfn + || pSession->Process != Process)); + } + RTSpinlockRelease(g_Spinlock); + if (RT_UNLIKELY(!pSession)) + { + Log(("VBoxGuestIoctl: WHAT?!? pSession == NULL! This must be a mistake... pid=%d\n", (int)Process)); + return VERR_INVALID_PARAMETER; + } + + /* + * Dispatch the fast IOCtl. + */ + *prc = VGDrvCommonIoCtlFast(iFunction, &g_DevExt, pSession); + return 0; +} + + +/** + * 32-bit IDC service routine. + * + * @returns VBox status code. + * @param u32Session The session handle (PVBOXGUESTSESSION). + * @param iFunction The requested function. + * @param pReqHdr The input/output data buffer. The caller + * ensures that this cannot be swapped out, or that + * it's acceptable to take a page in fault in the + * current context. If the request doesn't take + * input or produces output, apssing NULL is okay. + * @param cbReq The size of the data buffer. + * + * @remark This is called from the 16-bit thunker as well as directly from the 32-bit clients. + */ +DECLASM(int) VGDrvOS2IDCService(uint32_t u32Session, unsigned iFunction, PVBGLREQHDR pReqHdr, size_t cbReq) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)u32Session; + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertMsgReturn(pSession->sfn == 0xffff, ("%RX16\n", pSession->sfn), VERR_INVALID_HANDLE); + AssertMsgReturn(pSession->pDevExt == &g_DevExt, ("%p != %p\n", pSession->pDevExt, &g_DevExt), VERR_INVALID_HANDLE); + + return VGDrvCommonIoCtl(iFunction, &g_DevExt, pSession, pReqHdr, cbReq); +} + + +/** + * Worker for VBoxGuestOS2IDC, it creates the kernel session. + * + * @returns Pointer to the session. + */ +DECLASM(PVBOXGUESTSESSION) vgdrvOS2IDCConnect(void) +{ + PVBOXGUESTSESSION pSession; + int rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession); + if (RT_SUCCESS(rc)) + { + pSession->sfn = 0xffff; + return pSession; + } + return NULL; +} + + +DECLASM(int) vgdrvOS2IOCtl(uint16_t sfn, uint8_t iCat, uint8_t iFunction, void *pvParm, void *pvData, + uint16_t *pcbParm, uint16_t *pcbData) +{ + /* + * Find the session. + */ + const RTPROCESS Process = RTProcSelf(); + const unsigned iHash = SESSION_HASH(sfn); + PVBOXGUESTSESSION pSession; + + RTSpinlockAcquire(g_Spinlock); + pSession = g_apSessionHashTab[iHash]; + if (pSession && pSession->Process != Process) + { + do pSession = pSession->pNextHash; + while ( pSession + && ( pSession->sfn != sfn + || pSession->Process != Process)); + } + RTSpinlockRelease(g_Spinlock); + if (!pSession) + { + Log(("VBoxGuestIoctl: WHAT?!? pSession == NULL! This must be a mistake... pid=%d\n", (int)Process)); + return VERR_INVALID_PARAMETER; + } + + /* + * Verify the category and dispatch the IOCtl. + * + * The IOCtl call uses the parameter buffer as generic data input/output + * buffer similar to the one unix ioctl buffer argument. While the data + * buffer is not used. + */ + if (RT_LIKELY(iCat == VBGL_IOCTL_CATEGORY)) + { + Log(("vgdrvOS2IOCtl: pSession=%p iFunction=%#x pvParm=%p pvData=%p *pcbParm=%d *pcbData=%d\n", pSession, iFunction, pvParm, pvData, *pcbParm, *pcbData)); + if ( pvParm + && *pcbParm >= sizeof(VBGLREQHDR) + && *pcbData == 0) + { + /* + * Lock the buffer. + */ + KernVMLock_t ParmLock; + int32_t rc = KernVMLock(VMDHL_WRITE, pvParm, *pcbParm, &ParmLock, (KernPageList_t *)-1, NULL); + if (rc == 0) + { + /* + * Process the IOCtl. + */ + PVBGLREQHDR pReqHdr = (PVBGLREQHDR)pvParm; + rc = VGDrvCommonIoCtl(iFunction, &g_DevExt, pSession, pReqHdr, *pcbParm); + + /* + * Unlock the buffer. + */ + *pcbParm = RT_SUCCESS(rc) ? pReqHdr->cbOut : sizeof(*pReqHdr); + int rc2 = KernVMUnlock(&ParmLock); + AssertMsg(rc2 == 0, ("rc2=%d\n", rc2)); NOREF(rc2); + + Log2(("vgdrvOS2IOCtl: returns %d\n", rc)); + return rc; + } + AssertMsgFailed(("KernVMLock(VMDHL_WRITE, %p, %#x, &p, NULL, NULL) -> %d\n", pvParm, *pcbParm, &ParmLock, rc)); + return VERR_LOCK_FAILED; + } + Log2(("vgdrvOS2IOCtl: returns VERR_INVALID_PARAMETER (iFunction=%#x)\n", iFunction)); + return VERR_INVALID_PARAMETER; + } + return VERR_NOT_SUPPORTED; +} + + +/** + * 32-bit ISR, called by 16-bit assembly thunker in VBoxGuestA-os2.asm. + * + * @returns true if it's our interrupt, false it isn't. + */ +DECLASM(bool) vgdrvOS2ISR(void) +{ + Log(("vgdrvOS2ISR\n")); + + return VGDrvCommonISR(&g_DevExt); +} + + +void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt) +{ + /* No polling on OS/2 */ + NOREF(pDevExt); +} + + +bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue) +{ + RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue); + return false; +} + + +#ifdef DEBUG_READ /** @todo figure out this one once and for all... */ + +/** + * Callback for writing to the log buffer. + * + * @returns number of bytes written. + * @param pvArg Unused. + * @param pachChars Pointer to an array of utf-8 characters. + * @param cbChars Number of bytes in the character array pointed to by pachChars. + */ +static DECLCALLBACK(size_t) vgdrvOS2LogOutput(void *pvArg, const char *pachChars, size_t cbChars) +{ + size_t cchWritten = 0; + while (cbChars-- > 0) + { + const uint16_t offLogHead = g_offLogHead; + const uint16_t offLogHeadNext = (offLogHead + 1) & (g_cchLogMax - 1); + if (offLogHeadNext == g_offLogTail) + break; /* no */ + g_szLog[offLogHead] = *pachChars++; + g_offLogHead = offLogHeadNext; + cchWritten++; + } + return cchWritten; +} + + +int SUPR0Printf(const char *pszFormat, ...) +{ + va_list va; + +#if 0 //def DEBUG_bird + va_start(va, pszFormat); + RTLogComPrintfV(pszFormat, va); + va_end(va); +#endif + + va_start(va, pszFormat); + int cch = RTLogFormatV(vgdrvOS2LogOutput, NULL, pszFormat, va); + va_end(va); + + return cch; +} + +#endif /* DEBUG_READ */ + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.def b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.def new file mode 100644 index 00000000..dde341fe --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.def @@ -0,0 +1,55 @@ +; $Id: VBoxGuest-os2.def $ +;; @file +; VBoxGuest - OS/2 definition file. +; + +; +; Copyright (C) 2007-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +PHYSICAL DEVICE VBoxGst +DESCRIPTION 'VirtualBox Guest Additions Driver for OS/2.' +CODE PRELOAD EXECUTEREAD +DATA PRELOAD +; We're using wlink.exe, so this doesn't work. +;SEGMENTS +; DATA16 class 'FAR_DATA' +; DATA16_INIT class 'FAR_DATA' +; +; CODE16 class 'CODE' +; CODE16_INIT class 'CODE' +; +; CODE32 class 'CODE' +; TEXT32 class 'CODE' +; +; DATA32 class 'DATA' +; BSS32 class 'BSS' + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c new file mode 100644 index 00000000..b28f9382 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c @@ -0,0 +1,1138 @@ +/* $Id: VBoxGuest-solaris.c $ */ +/** @file + * VirtualBox Guest Additions Driver for Solaris. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/conf.h> +#include <sys/modctl.h> +#include <sys/mutex.h> +#include <sys/pci.h> +#include <sys/stat.h> +#include <sys/ddi.h> +#include <sys/ddi_intr.h> +#include <sys/sunddi.h> +#include <sys/open.h> +#include <sys/sunldi.h> +#include <sys/policy.h> +#include <sys/file.h> +#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */ + +#include "VBoxGuestInternal.h" +#include <VBox/log.h> +#include <VBox/version.h> +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/process.h> +#include <iprt/mem.h> +#include <iprt/cdefs.h> +#include <iprt/asm.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The module name. */ +#define DEVICE_NAME "vboxguest" +/** The module description as seen in 'modinfo'. */ +#define DEVICE_DESC "VirtualBox GstDrv" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vgdrvSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred); +static int vgdrvSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred); +static int vgdrvSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred); +static int vgdrvSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred); +static int vgdrvSolarisIOCtl(dev_t Dev, int iCmd, intptr_t pArg, int Mode, cred_t *pCred, int *pVal); +static int vgdrvSolarisIOCtlSlow(PVBOXGUESTSESSION pSession, int iCmd, int Mode, intptr_t iArgs); +static int vgdrvSolarisPoll(dev_t Dev, short fEvents, int fAnyYet, short *pReqEvents, struct pollhead **ppPollHead); + +static int vgdrvSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pArg, void **ppResult); +static int vgdrvSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd); +static int vgdrvSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); +static int vgdrvSolarisQuiesce(dev_info_t *pDip); + +static int vgdrvSolarisAddIRQ(dev_info_t *pDip); +static void vgdrvSolarisRemoveIRQ(dev_info_t *pDip); +static uint_t vgdrvSolarisHighLevelISR(caddr_t Arg); +static uint_t vgdrvSolarisISR(caddr_t Arg); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * cb_ops: for drivers that support char/block entry points + */ +static struct cb_ops g_vgdrvSolarisCbOps = +{ + vgdrvSolarisOpen, + vgdrvSolarisClose, + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + vgdrvSolarisRead, + vgdrvSolarisWrite, + vgdrvSolarisIOCtl, + nodev, /* c devmap */ + nodev, /* c mmap */ + nodev, /* c segmap */ + vgdrvSolarisPoll, + ddi_prop_op, /* property ops */ + NULL, /* streamtab */ + D_NEW | D_MP, /* compat. flag */ + CB_REV /* revision */ +}; + +/** + * dev_ops: for driver device operations + */ +static struct dev_ops g_vgdrvSolarisDevOps = +{ + DEVO_REV, /* driver build revision */ + 0, /* ref count */ + vgdrvSolarisGetInfo, + nulldev, /* identify */ + nulldev, /* probe */ + vgdrvSolarisAttach, + vgdrvSolarisDetach, + nodev, /* reset */ + &g_vgdrvSolarisCbOps, + (struct bus_ops *)0, + nodev, /* power */ + vgdrvSolarisQuiesce +}; + +/** + * modldrv: export driver specifics to the kernel + */ +static struct modldrv g_vgdrvSolarisModule = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_vgdrvSolarisDevOps +}; + +/** + * modlinkage: export install/remove/info to the kernel + */ +static struct modlinkage g_vgdrvSolarisModLinkage = +{ + MODREV_1, /* loadable module system revision */ + &g_vgdrvSolarisModule, + NULL /* terminate array of linkage structures */ +}; + +/** + * State info for each open file handle. + */ +typedef struct +{ + /** Pointer to the session handle. */ + PVBOXGUESTSESSION pSession; + /** The process reference for posting signals */ + void *pvProcRef; +} vboxguest_state_t; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Device handle (we support only one instance). */ +static dev_info_t *g_pDip = NULL; +/** Opaque pointer to file-descriptor states */ +static void *g_pvgdrvSolarisState = NULL; +/** Device extention & session data association structure. */ +static VBOXGUESTDEVEXT g_DevExt; +/** IO port handle. */ +static ddi_acc_handle_t g_PciIOHandle; +/** MMIO handle. */ +static ddi_acc_handle_t g_PciMMIOHandle; +/** IO Port. */ +static uint16_t g_uIOPortBase; +/** Address of the MMIO region.*/ +static caddr_t g_pMMIOBase; +/** Size of the MMIO region. */ +static off_t g_cbMMIO; +/** Pointer to an array of interrupt handles. */ +static ddi_intr_handle_t *g_pahIntrs; +/** Handle to the soft interrupt. */ +static ddi_softint_handle_t g_hSoftIntr; +/** The pollhead structure */ +static pollhead_t g_PollHead; +/** The IRQ Mutex */ +static kmutex_t g_IrqMtx; +/** The IRQ high-level Mutex. */ +static kmutex_t g_HighLevelIrqMtx; +/** Whether soft-ints are setup. */ +static bool g_fSoftIntRegistered = false; + +/** Additional IPRT function we need to drag in for vboxfs. */ +PFNRT g_Deps[] = +{ + (PFNRT)RTErrConvertToErrno, +}; + + +/** + * Kernel entry points + */ +int _init(void) +{ + /* + * Initialize IPRT R0 driver, which internally calls OS-specific r0 init. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + PRTLOGGER pRelLogger; + static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; + rc = RTLogCreate(&pRelLogger, 0 /* fFlags */, "all", + "VBOX_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups, + RTLOGDEST_STDOUT | RTLOGDEST_DEBUGGER, NULL); + if (RT_SUCCESS(rc)) + RTLogRelSetDefaultInstance(pRelLogger); + else + cmn_err(CE_NOTE, "failed to initialize driver logging rc=%d!\n", rc); + + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_vgdrvSolarisModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + LogRel((DEVICE_NAME ": failed to disable autounloading!\n")); + + rc = ddi_soft_state_init(&g_pvgdrvSolarisState, sizeof(vboxguest_state_t), 1); + if (!rc) + { + rc = mod_install(&g_vgdrvSolarisModLinkage); + if (rc) + ddi_soft_state_fini(&g_pvgdrvSolarisState); + } + } + else + { + cmn_err(CE_NOTE, "_init: RTR0Init failed. rc=%d\n", rc); + return EINVAL; + } + + return rc; +} + + +int _fini(void) +{ + LogFlow((DEVICE_NAME ":_fini\n")); + int rc = mod_remove(&g_vgdrvSolarisModLinkage); + if (!rc) + ddi_soft_state_fini(&g_pvgdrvSolarisState); + + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogDestroy(RTLogSetDefaultInstance(NULL)); + + if (!rc) + RTR0Term(); + return rc; +} + + +int _info(struct modinfo *pModInfo) +{ + /* LogFlow((DEVICE_NAME ":_info\n")); - Called too early, causing RTThreadPreemtIsEnabled warning. */ + return mod_info(&g_vgdrvSolarisModLinkage, pModInfo); +} + + +/** + * Attach entry point, to attach a device to the system or resume it. + * + * @param pDip The module structure instance. + * @param enmCmd Attach type (ddi_attach_cmd_t) + * + * @return corresponding solaris error code. + */ +static int vgdrvSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) +{ + LogFlow(("vgdrvSolarisAttach:\n")); + switch (enmCmd) + { + case DDI_ATTACH: + { + if (g_pDip) + { + LogRel(("vgdrvSolarisAttach: Only one instance supported.\n")); + return DDI_FAILURE; + } + + /* + * Enable resources for PCI access. + */ + ddi_acc_handle_t PciHandle; + int rc = pci_config_setup(pDip, &PciHandle); + if (rc == DDI_SUCCESS) + { + /* + * Map the register address space. + */ + caddr_t baseAddr; + ddi_device_acc_attr_t deviceAttr; + deviceAttr.devacc_attr_version = DDI_DEVICE_ATTR_V0; + deviceAttr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC; + deviceAttr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; + deviceAttr.devacc_attr_access = DDI_DEFAULT_ACC; + rc = ddi_regs_map_setup(pDip, 1, &baseAddr, 0, 0, &deviceAttr, &g_PciIOHandle); + if (rc == DDI_SUCCESS) + { + /* + * Read size of the MMIO region. + */ + g_uIOPortBase = (uintptr_t)baseAddr; + rc = ddi_dev_regsize(pDip, 2, &g_cbMMIO); + if (rc == DDI_SUCCESS) + { + rc = ddi_regs_map_setup(pDip, 2, &g_pMMIOBase, 0, g_cbMMIO, &deviceAttr, &g_PciMMIOHandle); + if (rc == DDI_SUCCESS) + { + /* + * Call the common device extension initializer. + */ + rc = VGDrvCommonInitDevExt(&g_DevExt, g_uIOPortBase, g_pMMIOBase, g_cbMMIO, +#if ARCH_BITS == 64 + VBOXOSTYPE_Solaris_x64, +#else + VBOXOSTYPE_Solaris, +#endif + VMMDEV_EVENT_MOUSE_POSITION_CHANGED); + if (RT_SUCCESS(rc)) + { + /* + * Add IRQ of VMMDev. + */ + rc = vgdrvSolarisAddIRQ(pDip); + if (rc == DDI_SUCCESS) + { + /* + * Read host configuration. + */ + VGDrvCommonProcessOptionsFromHost(&g_DevExt); + + rc = ddi_create_minor_node(pDip, DEVICE_NAME, S_IFCHR, 0 /* instance */, DDI_PSEUDO, + 0 /* fFlags */); + if (rc == DDI_SUCCESS) + { + g_pDip = pDip; + pci_config_teardown(&PciHandle); + return DDI_SUCCESS; + } + + LogRel((DEVICE_NAME "::Attach: ddi_create_minor_node failed.\n")); + vgdrvSolarisRemoveIRQ(pDip); + } + else + LogRel((DEVICE_NAME "::Attach: vgdrvSolarisAddIRQ failed.\n")); + VGDrvCommonDeleteDevExt(&g_DevExt); + } + else + LogRel((DEVICE_NAME "::Attach: VGDrvCommonInitDevExt failed.\n")); + ddi_regs_map_free(&g_PciMMIOHandle); + } + else + LogRel((DEVICE_NAME "::Attach: ddi_regs_map_setup for MMIO region failed.\n")); + } + else + LogRel((DEVICE_NAME "::Attach: ddi_dev_regsize for MMIO region failed.\n")); + ddi_regs_map_free(&g_PciIOHandle); + } + else + LogRel((DEVICE_NAME "::Attach: ddi_regs_map_setup for IOport failed.\n")); + pci_config_teardown(&PciHandle); + } + else + LogRel((DEVICE_NAME "::Attach: pci_config_setup failed rc=%d.\n", rc)); + return DDI_FAILURE; + } + + case DDI_RESUME: + { + /** @todo implement resume for guest driver. */ + return DDI_SUCCESS; + } + + default: + return DDI_FAILURE; + } +} + + +/** + * Detach entry point, to detach a device to the system or suspend it. + * + * @param pDip The module structure instance. + * @param enmCmd Attach type (ddi_attach_cmd_t) + * + * @return corresponding solaris error code. + */ +static int vgdrvSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + LogFlow(("vgdrvSolarisDetach:\n")); + switch (enmCmd) + { + case DDI_DETACH: + { + vgdrvSolarisRemoveIRQ(pDip); + ddi_regs_map_free(&g_PciIOHandle); + ddi_regs_map_free(&g_PciMMIOHandle); + ddi_remove_minor_node(pDip, NULL); + VGDrvCommonDeleteDevExt(&g_DevExt); + g_pDip = NULL; + return DDI_SUCCESS; + } + + case DDI_SUSPEND: + { + /** @todo implement suspend for guest driver. */ + return DDI_SUCCESS; + } + + default: + return DDI_FAILURE; + } +} + + +/** + * Quiesce entry point, called by solaris kernel for disabling the device from + * generating any interrupts or doing in-bound DMA. + * + * @param pDip The module structure instance. + * + * @return corresponding solaris error code. + */ +static int vgdrvSolarisQuiesce(dev_info_t *pDip) +{ + int rc = ddi_intr_disable(g_pahIntrs[0]); + if (rc != DDI_SUCCESS) + return DDI_FAILURE; + + /** @todo What about HGCM/HGSMI touching guest-memory? */ + + return DDI_SUCCESS; +} + + +/** + * Info entry point, called by solaris kernel for obtaining driver info. + * + * @param pDip The module structure instance (do not use). + * @param enmCmd Information request type. + * @param pvArg Type specific argument. + * @param ppvResult Where to store the requested info. + * + * @return corresponding solaris error code. + */ +static int vgdrvSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult) +{ + LogFlow(("vgdrvSolarisGetInfo:\n")); + + int rc = DDI_SUCCESS; + switch (enmCmd) + { + case DDI_INFO_DEVT2DEVINFO: + { + *ppvResult = (void *)g_pDip; + if (!*ppvResult) + rc = DDI_FAILURE; + break; + } + + case DDI_INFO_DEVT2INSTANCE: + { + /* There can only be a single-instance of this driver and thus its instance number is 0. */ + *ppvResult = (void *)0; + break; + } + + default: + rc = DDI_FAILURE; + break; + } + + NOREF(pvArg); + return rc; +} + + +/** + * User context entry points + * + * @remarks fFlags are the flags passed to open() or to ldi_open_by_name. In + * the latter case the FKLYR flag is added to indicate that the caller + * is a kernel component rather than user land. + */ +static int vgdrvSolarisOpen(dev_t *pDev, int fFlags, int fType, cred_t *pCred) +{ + int rc; + PVBOXGUESTSESSION pSession = NULL; + + LogFlow(("vgdrvSolarisOpen:\n")); + + /* + * Verify we are being opened as a character device. + */ + if (fType != OTYP_CHR) + return EINVAL; + + vboxguest_state_t *pState = NULL; + unsigned iOpenInstance; + for (iOpenInstance = 0; iOpenInstance < 4096; iOpenInstance++) + { + if ( !ddi_get_soft_state(g_pvgdrvSolarisState, iOpenInstance) /* faster */ + && ddi_soft_state_zalloc(g_pvgdrvSolarisState, iOpenInstance) == DDI_SUCCESS) + { + pState = ddi_get_soft_state(g_pvgdrvSolarisState, iOpenInstance); + break; + } + } + if (!pState) + { + Log(("vgdrvSolarisOpen: too many open instances.")); + return ENXIO; + } + + /* + * Create a new session. + * + * Note! The devfs inode with the gid isn't readily available here, so we cannot easily + * to the vbox group detection like on linux. Read config instead? + */ + if (!(fFlags & FKLYR)) + { + uint32_t fRequestor = VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN; + if (crgetruid(pCred) == 0) + fRequestor |= VMMDEV_REQUESTOR_USR_ROOT; + else + fRequestor |= VMMDEV_REQUESTOR_USR_USER; + if (secpolicy_coreadm(pCred) == 0) + fRequestor |= VMMDEV_REQUESTOR_GRP_WHEEL; + /** @todo is there any way of detecting that the process belongs to someone on the physical console? + * secpolicy_console() [== PRIV_SYS_DEVICES] doesn't look quite right, or does it? */ + fRequestor |= VMMDEV_REQUESTOR_CON_DONT_KNOW; + fRequestor |= VMMDEV_REQUESTOR_NO_USER_DEVICE; /** @todo implement vboxuser device node. */ + + rc = VGDrvCommonCreateUserSession(&g_DevExt, fRequestor, &pSession); + } + else + rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession); + if (RT_SUCCESS(rc)) + { + if (!(fFlags & FKLYR)) + pState->pvProcRef = proc_ref(); + else + pState->pvProcRef = NULL; + pState->pSession = pSession; + *pDev = makedevice(getmajor(*pDev), iOpenInstance); + Log(("vgdrvSolarisOpen: pSession=%p pState=%p pid=%d\n", pSession, pState, (int)RTProcSelf())); + return 0; + } + + /* Failed, clean up. */ + ddi_soft_state_free(g_pvgdrvSolarisState, iOpenInstance); + + LogRel((DEVICE_NAME "::Open: VGDrvCommonCreateUserSession failed. rc=%d\n", rc)); + return EFAULT; +} + + +static int vgdrvSolarisClose(dev_t Dev, int flag, int fType, cred_t *pCred) +{ + LogFlow(("vgdrvSolarisClose: pid=%d\n", (int)RTProcSelf())); + + PVBOXGUESTSESSION pSession = NULL; + vboxguest_state_t *pState = ddi_get_soft_state(g_pvgdrvSolarisState, getminor(Dev)); + if (!pState) + { + Log(("vgdrvSolarisClose: failed to get pState.\n")); + return EFAULT; + } + + if (pState->pvProcRef != NULL) + { + proc_unref(pState->pvProcRef); + pState->pvProcRef = NULL; + } + pSession = pState->pSession; + pState->pSession = NULL; + Log(("vgdrvSolarisClose: pSession=%p pState=%p\n", pSession, pState)); + ddi_soft_state_free(g_pvgdrvSolarisState, getminor(Dev)); + if (!pSession) + { + Log(("vgdrvSolarisClose: failed to get pSession.\n")); + return EFAULT; + } + + /* + * Close the session. + */ + if (pSession) + VGDrvCommonCloseSession(&g_DevExt, pSession); + return 0; +} + + +static int vgdrvSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFlow((DEVICE_NAME "::Read\n")); + + vboxguest_state_t *pState = ddi_get_soft_state(g_pvgdrvSolarisState, getminor(Dev)); + if (!pState) + { + Log((DEVICE_NAME "::Close: failed to get pState.\n")); + return EFAULT; + } + + PVBOXGUESTSESSION pSession = pState->pSession; + uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq); + if (pSession->u32MousePosChangedSeq != u32CurSeq) + pSession->u32MousePosChangedSeq = u32CurSeq; + + return 0; +} + + +static int vgdrvSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFlow(("vgdrvSolarisWrite:\n")); + return 0; +} + + +/** @def IOCPARM_LEN + * Gets the length from the ioctl number. + * This is normally defined by sys/ioccom.h on BSD systems... + */ +#ifndef IOCPARM_LEN +# define IOCPARM_LEN(x) ( ((x) >> 16) & IOCPARM_MASK ) +#endif + + +/** + * Driver ioctl, an alternate entry point for this character driver. + * + * @param Dev Device number + * @param iCmd Operation identifier + * @param iArgs Arguments from user to driver + * @param Mode Information bitfield (read/write, address space etc.) + * @param pCred User credentials + * @param pVal Return value for calling process. + * + * @return corresponding solaris error code. + */ +static int vgdrvSolarisIOCtl(dev_t Dev, int iCmd, intptr_t iArgs, int Mode, cred_t *pCred, int *pVal) +{ + /* + * Get the session from the soft state item. + */ + vboxguest_state_t *pState = ddi_get_soft_state(g_pvgdrvSolarisState, getminor(Dev)); + if (!pState) + { + LogRel(("vgdrvSolarisIOCtl: no state data for %#x (%d)\n", Dev, getminor(Dev))); + return EINVAL; + } + + PVBOXGUESTSESSION pSession = pState->pSession; + if (!pSession) + { + LogRel(("vgdrvSolarisIOCtl: no session in state data for %#x (%d)\n", Dev, getminor(Dev))); + return DDI_SUCCESS; + } + + /* + * Deal with fast requests. + */ + if (VBGL_IOCTL_IS_FAST(iCmd)) + { + *pVal = VGDrvCommonIoCtlFast(iCmd, &g_DevExt, pSession); + return 0; + } + + /* + * It's kind of simple if this is a kernel session, take slow path if user land. + */ + if (pSession->R0Process == NIL_RTR0PROCESS) + { + if (IOCPARM_LEN(iCmd) == sizeof(VBGLREQHDR)) + { + PVBGLREQHDR pHdr = (PVBGLREQHDR)iArgs; + int rc; + if (iCmd != VBGL_IOCTL_IDC_DISCONNECT) + rc =VGDrvCommonIoCtl(iCmd, &g_DevExt, pSession, pHdr, RT_MAX(pHdr->cbIn, pHdr->cbOut)); + else + { + pState->pSession = NULL; + rc = VGDrvCommonIoCtl(iCmd, &g_DevExt, pSession, pHdr, RT_MAX(pHdr->cbIn, pHdr->cbOut)); + if (RT_FAILURE(rc)) + pState->pSession = pSession; + } + return rc; + } + } + + return vgdrvSolarisIOCtlSlow(pSession, iCmd, Mode, iArgs); +} + + +/** + * Worker for VBoxSupDrvIOCtl that takes the slow IOCtl functions. + * + * @returns Solaris errno. + * + * @param pSession The session. + * @param iCmd The IOCtl command. + * @param Mode Information bitfield (for specifying ownership of data) + * @param iArg User space address of the request buffer. + */ +static int vgdrvSolarisIOCtlSlow(PVBOXGUESTSESSION pSession, int iCmd, int Mode, intptr_t iArg) +{ + int rc; + uint32_t cbBuf = 0; + union + { + VBGLREQHDR Hdr; + uint8_t abBuf[64]; + } StackBuf; + PVBGLREQHDR pHdr; + + + /* + * Read the header. + */ + if (RT_UNLIKELY(IOCPARM_LEN(iCmd) != sizeof(StackBuf.Hdr))) + { + LogRel(("vgdrvSolarisIOCtlSlow: iCmd=%#x len %d expected %d\n", iCmd, IOCPARM_LEN(iCmd), sizeof(StackBuf.Hdr))); + return EINVAL; + } + rc = ddi_copyin((void *)iArg, &StackBuf.Hdr, sizeof(StackBuf.Hdr), Mode); + if (RT_UNLIKELY(rc)) + { + LogRel(("vgdrvSolarisIOCtlSlow: ddi_copyin(,%#lx,) failed; iCmd=%#x. rc=%d\n", iArg, iCmd, rc)); + return EFAULT; + } + if (RT_UNLIKELY(StackBuf.Hdr.uVersion != VBGLREQHDR_VERSION)) + { + LogRel(("vgdrvSolarisIOCtlSlow: bad header version %#x; iCmd=%#x\n", StackBuf.Hdr.uVersion, iCmd)); + return EINVAL; + } + cbBuf = RT_MAX(StackBuf.Hdr.cbIn, StackBuf.Hdr.cbOut); + if (RT_UNLIKELY( StackBuf.Hdr.cbIn < sizeof(StackBuf.Hdr) + || (StackBuf.Hdr.cbOut < sizeof(StackBuf.Hdr) && StackBuf.Hdr.cbOut != 0) + || cbBuf > _1M*16)) + { + LogRel(("vgdrvSolarisIOCtlSlow: max(%#x,%#x); iCmd=%#x\n", StackBuf.Hdr.cbIn, StackBuf.Hdr.cbOut, iCmd)); + return EINVAL; + } + + /* + * Buffer the request. + * + * Note! Common code revalidates the header sizes and version. So it's + * fine to read it once more. + */ + if (cbBuf <= sizeof(StackBuf)) + pHdr = &StackBuf.Hdr; + else + { + pHdr = RTMemTmpAlloc(cbBuf); + if (RT_UNLIKELY(!pHdr)) + { + LogRel(("vgdrvSolarisIOCtlSlow: failed to allocate buffer of %d bytes for iCmd=%#x.\n", cbBuf, iCmd)); + return ENOMEM; + } + } + rc = ddi_copyin((void *)iArg, pHdr, cbBuf, Mode); + if (RT_UNLIKELY(rc)) + { + LogRel(("vgdrvSolarisIOCtlSlow: copy_from_user(,%#lx, %#x) failed; iCmd=%#x. rc=%d\n", iArg, cbBuf, iCmd, rc)); + if (pHdr != &StackBuf.Hdr) + RTMemFree(pHdr); + return EFAULT; + } + + /* + * Process the IOCtl. + */ + rc = VGDrvCommonIoCtl(iCmd, &g_DevExt, pSession, pHdr, cbBuf); + + /* + * Copy ioctl data and output buffer back to user space. + */ + if (RT_SUCCESS(rc)) + { + uint32_t cbOut = pHdr->cbOut; + if (RT_UNLIKELY(cbOut > cbBuf)) + { + LogRel(("vgdrvSolarisIOCtlSlow: too much output! %#x > %#x; iCmd=%#x!\n", cbOut, cbBuf, iCmd)); + cbOut = cbBuf; + } + rc = ddi_copyout(pHdr, (void *)iArg, cbOut, Mode); + if (RT_UNLIKELY(rc != 0)) + { + /* this is really bad */ + LogRel(("vgdrvSolarisIOCtlSlow: ddi_copyout(,%p,%d) failed. rc=%d\n", (void *)iArg, cbBuf, rc)); + rc = EFAULT; + } + } + else + rc = EINVAL; + + if (pHdr != &StackBuf.Hdr) + RTMemTmpFree(pHdr); + return rc; +} + + +#if 0 +/** + * @note This code is duplicated on other platforms with variations, so please + * keep them all up to date when making changes! + */ +int VBOXCALL VBoxGuestIDC(void *pvSession, uintptr_t uReq, PVBGLREQHDR pReqHdr, size_t cbReq) +{ + /* + * Simple request validation (common code does the rest). + */ + int rc; + if ( RT_VALID_PTR(pReqHdr) + && cbReq >= sizeof(*pReqHdr)) + { + /* + * All requests except the connect one requires a valid session. + */ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pvSession; + if (pSession) + { + if ( RT_VALID_PTR(pSession) + && pSession->pDevExt == &g_DevExt) + rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); + else + rc = VERR_INVALID_HANDLE; + } + else if (uReq == VBGL_IOCTL_IDC_CONNECT) + { + rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession); + if (RT_SUCCESS(rc)) + { + rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq); + if (RT_FAILURE(rc)) + VGDrvCommonCloseSession(&g_DevExt, pSession); + } + } + else + rc = VERR_INVALID_HANDLE; + } + else + rc = VERR_INVALID_POINTER; + return rc; +} +#endif + + +static int vgdrvSolarisPoll(dev_t Dev, short fEvents, int fAnyYet, short *pReqEvents, struct pollhead **ppPollHead) +{ + LogFlow(("vgdrvSolarisPoll: fEvents=%d fAnyYet=%d\n", fEvents, fAnyYet)); + + vboxguest_state_t *pState = ddi_get_soft_state(g_pvgdrvSolarisState, getminor(Dev)); + if (RT_LIKELY(pState)) + { + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pState->pSession; + uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq); + if (pSession->u32MousePosChangedSeq != u32CurSeq) + { + *pReqEvents |= (POLLIN | POLLRDNORM); + pSession->u32MousePosChangedSeq = u32CurSeq; + } + else + { + *pReqEvents = 0; + if (!fAnyYet) + *ppPollHead = &g_PollHead; + } + + return 0; + } + + Log(("vgdrvSolarisPoll: no state data for %d\n", getminor(Dev))); + return EINVAL; +} + + +/** + * Sets IRQ for VMMDev. + * + * @returns Solaris error code. + * @param pDip Pointer to the device info structure. + */ +static int vgdrvSolarisAddIRQ(dev_info_t *pDip) +{ + LogFlow(("vgdrvSolarisAddIRQ: pDip=%p\n", pDip)); + + /* Get the types of interrupt supported for this hardware. */ + int fIntrType = 0; + int rc = ddi_intr_get_supported_types(pDip, &fIntrType); + if (rc == DDI_SUCCESS) + { + /* We only support fixed interrupts at this point, not MSIs. */ + if (fIntrType & DDI_INTR_TYPE_FIXED) + { + /* Verify the number of interrupts supported by this device. There can only be one fixed interrupt. */ + int cIntrCount = 0; + rc = ddi_intr_get_nintrs(pDip, fIntrType, &cIntrCount); + if ( rc == DDI_SUCCESS + && cIntrCount == 1) + { + /* Allocated kernel memory for the interrupt handle. The allocation size is stored internally. */ + g_pahIntrs = RTMemAllocZ(cIntrCount * sizeof(ddi_intr_handle_t)); + if (g_pahIntrs) + { + /* Allocate the interrupt for this device and verify the allocation. */ + int cIntrAllocated; + rc = ddi_intr_alloc(pDip, g_pahIntrs, fIntrType, 0 /* interrupt number */, cIntrCount, &cIntrAllocated, + DDI_INTR_ALLOC_NORMAL); + if ( rc == DDI_SUCCESS + && cIntrAllocated == 1) + { + /* Get the interrupt priority assigned by the system. */ + uint_t uIntrPriority; + rc = ddi_intr_get_pri(g_pahIntrs[0], &uIntrPriority); + if (rc == DDI_SUCCESS) + { + /* Check if the interrupt priority is scheduler level or above, if so we need to use a high-level + and low-level interrupt handlers with corresponding mutexes. */ + cmn_err(CE_CONT, "!vboxguest: uIntrPriority=%d hilevel_pri=%d\n", uIntrPriority, ddi_intr_get_hilevel_pri()); + if (uIntrPriority >= ddi_intr_get_hilevel_pri()) + { + /* Initialize the high-level mutex. */ + mutex_init(&g_HighLevelIrqMtx, NULL /* pszDesc */, MUTEX_DRIVER, DDI_INTR_PRI(uIntrPriority)); + + /* Assign interrupt handler function to the interrupt handle. */ + rc = ddi_intr_add_handler(g_pahIntrs[0], (ddi_intr_handler_t *)&vgdrvSolarisHighLevelISR, + NULL /* pvArg1 */, NULL /* pvArg2 */); + + if (rc == DDI_SUCCESS) + { + /* Add the low-level interrupt handler. */ + rc = ddi_intr_add_softint(pDip, &g_hSoftIntr, DDI_INTR_SOFTPRI_MAX, + (ddi_intr_handler_t *)&vgdrvSolarisISR, NULL /* pvArg1 */); + if (rc == DDI_SUCCESS) + { + /* Initialize the low-level mutex at the corresponding level. */ + mutex_init(&g_IrqMtx, NULL /* pszDesc */, MUTEX_DRIVER, + DDI_INTR_PRI(DDI_INTR_SOFTPRI_MAX)); + + g_fSoftIntRegistered = true; + /* Enable the high-level interrupt. */ + rc = ddi_intr_enable(g_pahIntrs[0]); + if (rc == DDI_SUCCESS) + return rc; + + LogRel((DEVICE_NAME "::AddIRQ: failed to enable interrupt. rc=%d\n", rc)); + mutex_destroy(&g_IrqMtx); + } + else + LogRel((DEVICE_NAME "::AddIRQ: failed to add soft interrupt handler. rc=%d\n", rc)); + + ddi_intr_remove_handler(g_pahIntrs[0]); + } + else + LogRel((DEVICE_NAME "::AddIRQ: failed to add high-level interrupt handler. rc=%d\n", rc)); + + mutex_destroy(&g_HighLevelIrqMtx); + } + else + { + /* Interrupt handler runs at reschedulable level, initialize the mutex at the given priority. */ + mutex_init(&g_IrqMtx, NULL /* pszDesc */, MUTEX_DRIVER, DDI_INTR_PRI(uIntrPriority)); + + /* Assign interrupt handler function to the interrupt handle. */ + rc = ddi_intr_add_handler(g_pahIntrs[0], (ddi_intr_handler_t *)vgdrvSolarisISR, + NULL /* pvArg1 */, NULL /* pvArg2 */); + if (rc == DDI_SUCCESS) + { + /* Enable the interrupt. */ + rc = ddi_intr_enable(g_pahIntrs[0]); + if (rc == DDI_SUCCESS) + return rc; + + LogRel((DEVICE_NAME "::AddIRQ: failed to enable interrupt. rc=%d\n", rc)); + mutex_destroy(&g_IrqMtx); + } + } + } + else + LogRel((DEVICE_NAME "::AddIRQ: failed to get priority of interrupt. rc=%d\n", rc)); + + Assert(cIntrAllocated == 1); + ddi_intr_free(g_pahIntrs[0]); + } + else + LogRel((DEVICE_NAME "::AddIRQ: failed to allocated IRQs. count=%d\n", cIntrCount)); + RTMemFree(g_pahIntrs); + } + else + LogRel((DEVICE_NAME "::AddIRQ: failed to allocated IRQs. count=%d\n", cIntrCount)); + } + else + LogRel((DEVICE_NAME "::AddIRQ: failed to get or insufficient number of IRQs. rc=%d cIntrCount=%d\n", rc, cIntrCount)); + } + else + LogRel((DEVICE_NAME "::AddIRQ: fixed-type interrupts not supported. IntrType=%#x\n", fIntrType)); + } + else + LogRel((DEVICE_NAME "::AddIRQ: failed to get supported interrupt types. rc=%d\n", rc)); + return rc; +} + + +/** + * Removes IRQ for VMMDev. + * + * @param pDip Pointer to the device info structure. + */ +static void vgdrvSolarisRemoveIRQ(dev_info_t *pDip) +{ + LogFlow(("vgdrvSolarisRemoveIRQ:\n")); + + int rc = ddi_intr_disable(g_pahIntrs[0]); + if (rc == DDI_SUCCESS) + { + rc = ddi_intr_remove_handler(g_pahIntrs[0]); + if (rc == DDI_SUCCESS) + ddi_intr_free(g_pahIntrs[0]); + } + + if (g_fSoftIntRegistered) + { + ddi_intr_remove_softint(g_hSoftIntr); + mutex_destroy(&g_HighLevelIrqMtx); + g_fSoftIntRegistered = false; + } + + mutex_destroy(&g_IrqMtx); + RTMemFree(g_pahIntrs); +} + + +/** + * High-level Interrupt Service Routine for VMMDev. + * + * This routine simply dispatches a soft-interrupt at an acceptable IPL as + * VGDrvCommonISR() cannot be called at a high IPL (scheduler level or higher) + * due to pollwakeup() in VGDrvNativeISRMousePollEvent(). + * + * @param Arg Private data (unused, will be NULL). + * @returns DDI_INTR_CLAIMED if it's our interrupt, DDI_INTR_UNCLAIMED if it isn't. + */ +static uint_t vgdrvSolarisHighLevelISR(caddr_t Arg) +{ + bool const fOurIrq = VGDrvCommonIsOurIRQ(&g_DevExt); + if (fOurIrq) + { + ddi_intr_trigger_softint(g_hSoftIntr, NULL /* Arg */); + return DDI_INTR_CLAIMED; + } + return DDI_INTR_UNCLAIMED; +} + + +/** + * Interrupt Service Routine for VMMDev. + * + * @param Arg Private data (unused, will be NULL). + * @returns DDI_INTR_CLAIMED if it's our interrupt, DDI_INTR_UNCLAIMED if it isn't. + */ +static uint_t vgdrvSolarisISR(caddr_t Arg) +{ + LogFlow(("vgdrvSolarisISR:\n")); + + /* The mutex is required to protect against parallel executions (if possible?) and also the + mouse notify registeration race between VGDrvNativeSetMouseNotifyCallback() and VGDrvCommonISR(). */ + mutex_enter(&g_IrqMtx); + bool fOurIRQ = VGDrvCommonISR(&g_DevExt); + mutex_exit(&g_IrqMtx); + + return fOurIRQ ? DDI_INTR_CLAIMED : DDI_INTR_UNCLAIMED; +} + + +void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt) +{ + LogFlow(("VGDrvNativeISRMousePollEvent:\n")); + + /* + * Wake up poll waiters. + */ + pollwakeup(&g_PollHead, POLLIN | POLLRDNORM); +} + + +bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue) +{ + RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue); + return false; +} + + +/** + * Sets the mouse notification callback. + * + * @returns VBox status code. + * @param pDevExt Pointer to the device extension. + * @param pNotify Pointer to the mouse notify struct. + */ +int VGDrvNativeSetMouseNotifyCallback(PVBOXGUESTDEVEXT pDevExt, PVBGLIOCSETMOUSENOTIFYCALLBACK pNotify) +{ + /* Take the mutex here so as to not race with VGDrvCommonISR() which invokes the mouse notify callback. */ + mutex_enter(&g_IrqMtx); + pDevExt->pfnMouseNotifyCallback = pNotify->u.In.pfnNotify; + pDevExt->pvMouseNotifyCallbackArg = pNotify->u.In.pvUser; + mutex_exit(&g_IrqMtx); + return VINF_SUCCESS; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.conf b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.conf new file mode 100644 index 00000000..91a245bf --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.conf @@ -0,0 +1,41 @@ +# $Id: VBoxGuest-solaris.conf $ +## @file +# OpenSolaris Guest Driver Configuration + +# +# Copyright (C) 2007-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# This needs to go into /platform/i86pc/kernel/drv, +# while the 64-bit driver object goes into the amd64 +# subdirectory (32-bit drivers goes into the same +# directory). +# +name="vboxguest" parent="/pci@0,0/pci80ee,cafe" instance=0; diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp new file mode 100644 index 00000000..c9e180af --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp @@ -0,0 +1,3503 @@ +/* $Id: VBoxGuest-win.cpp $ */ +/** @file + * VBoxGuest - Windows specifics. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +#include <iprt/nt/nt.h> + +#include "VBoxGuestInternal.h" +#include <VBox/VBoxGuestLib.h> +#include <VBox/log.h> + +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/critsect.h> +#include <iprt/dbg.h> +#include <iprt/err.h> +#include <iprt/initterm.h> +#include <iprt/memobj.h> +#include <iprt/mem.h> +#include <iprt/mp.h> +#include <iprt/spinlock.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#ifdef TARGET_NT4 +# include <VBox/pci.h> +# define PIMAGE_NT_HEADERS32 PIMAGE_NT_HEADERS32_IPRT +# include <iprt/formats/mz.h> +# include <iprt/formats/pecoff.h> +extern "C" IMAGE_DOS_HEADER __ImageBase; +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#undef ExFreePool + +#ifndef PCI_MAX_BUSES +# define PCI_MAX_BUSES 256 +#endif + +/** CM_RESOURCE_MEMORY_* flags which were used on XP or earlier. */ +#define VBOX_CM_PRE_VISTA_MASK (0x3f) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Possible device states for our state machine. + */ +typedef enum VGDRVNTDEVSTATE +{ + /** @name Stable states + * @{ */ + VGDRVNTDEVSTATE_REMOVED = 0, + VGDRVNTDEVSTATE_STOPPED, + VGDRVNTDEVSTATE_OPERATIONAL, + /** @} */ + + /** @name Transitional states + * @{ */ + VGDRVNTDEVSTATE_PENDINGSTOP, + VGDRVNTDEVSTATE_PENDINGREMOVE, + VGDRVNTDEVSTATE_SURPRISEREMOVED + /** @} */ +} VGDRVNTDEVSTATE; + + +/** + * Subclassing the device extension for adding windows-specific bits. + */ +typedef struct VBOXGUESTDEVEXTWIN +{ + /** The common device extension core. */ + VBOXGUESTDEVEXT Core; + + /** Our functional driver object. */ + PDEVICE_OBJECT pDeviceObject; + /** Top of the stack. */ + PDEVICE_OBJECT pNextLowerDriver; + + /** @name PCI bus and slot (device+function) set by for legacy NT only. + * @{ */ + /** Bus number where the device is located. */ + ULONG uBus; + /** Slot number where the device is located (PCI_SLOT_NUMBER). */ + ULONG uSlot; + /** @} */ + + /** @name Interrupt stuff. + * @{ */ + /** Interrupt object pointer. */ + PKINTERRUPT pInterruptObject; + /** Device interrupt level. */ + ULONG uInterruptLevel; + /** Device interrupt vector. */ + ULONG uInterruptVector; + /** Affinity mask. */ + KAFFINITY fInterruptAffinity; + /** LevelSensitive or Latched. */ + KINTERRUPT_MODE enmInterruptMode; + /** @} */ + + /** Physical address and length of VMMDev memory. */ + PHYSICAL_ADDRESS uVmmDevMemoryPhysAddr; + /** Length of VMMDev memory. */ + ULONG cbVmmDevMemory; + + /** Device state. */ + VGDRVNTDEVSTATE volatile enmDevState; + /** The previous stable device state. */ + VGDRVNTDEVSTATE enmPrevDevState; + + /** Last system power action set (see VBoxGuestPower). */ + POWER_ACTION enmLastSystemPowerAction; + /** Preallocated generic request for shutdown. */ + VMMDevPowerStateRequest *pPowerStateRequest; + + /** Spinlock protecting MouseNotifyCallback. Required since the consumer is + * in a DPC callback and not the ISR. */ + KSPIN_LOCK MouseEventAccessSpinLock; + + /** Read/write critical section for handling race between checking for idle + * driver (in IRP_MN_QUERY_REMOVE_DEVICE & IRP_MN_QUERY_STOP_DEVICE) and + * creating new sessions. The session creation code enteres the critical + * section in read (shared) access mode, whereas the idle checking code + * enteres is in write (exclusive) access mode. */ + RTCRITSECTRW SessionCreateCritSect; +} VBOXGUESTDEVEXTWIN; +typedef VBOXGUESTDEVEXTWIN *PVBOXGUESTDEVEXTWIN; + + +/** NT (windows) version identifier. */ +typedef enum VGDRVNTVER +{ + VGDRVNTVER_INVALID = 0, + VGDRVNTVER_WINNT310, + VGDRVNTVER_WINNT350, + VGDRVNTVER_WINNT351, + VGDRVNTVER_WINNT4, + VGDRVNTVER_WIN2K, + VGDRVNTVER_WINXP, + VGDRVNTVER_WIN2K3, + VGDRVNTVER_WINVISTA, + VGDRVNTVER_WIN7, + VGDRVNTVER_WIN8, + VGDRVNTVER_WIN81, + VGDRVNTVER_WIN10, + VGDRVNTVER_WIN11 +} VGDRVNTVER; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +static NTSTATUS NTAPI vgdrvNtNt5PlusAddDevice(PDRIVER_OBJECT pDrvObj, PDEVICE_OBJECT pDevObj); +static NTSTATUS NTAPI vgdrvNtNt5PlusPnP(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS NTAPI vgdrvNtNt5PlusPower(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS NTAPI vgdrvNtNt5PlusSystemControl(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static void NTAPI vgdrvNtUnload(PDRIVER_OBJECT pDrvObj); +static NTSTATUS NTAPI vgdrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS NTAPI vgdrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS NTAPI vgdrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS vgdrvNtDeviceControlSlow(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + PIRP pIrp, PIO_STACK_LOCATION pStack); +static NTSTATUS NTAPI vgdrvNtInternalIOCtl(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static void vgdrvNtReadConfiguration(PVBOXGUESTDEVEXTWIN pDevExt); +static NTSTATUS NTAPI vgdrvNtShutdown(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS NTAPI vgdrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static VOID NTAPI vgdrvNtBugCheckCallback(PVOID pvBuffer, ULONG cbBuffer); +static VOID NTAPI vgdrvNtDpcHandler(PKDPC pDPC, PDEVICE_OBJECT pDevObj, PIRP pIrp, PVOID pContext); +static BOOLEAN NTAPI vgdrvNtIsrHandler(PKINTERRUPT interrupt, PVOID serviceContext); +#ifdef VBOX_STRICT +static void vgdrvNtDoTests(void); +#endif +#ifdef TARGET_NT4 +static ULONG NTAPI vgdrvNt31GetBusDataByOffset(BUS_DATA_TYPE enmBusDataType, ULONG idxBus, ULONG uSlot, + void *pvData, ULONG offData, ULONG cbData); +static ULONG NTAPI vgdrvNt31SetBusDataByOffset(BUS_DATA_TYPE enmBusDataType, ULONG idxBus, ULONG uSlot, + void *pvData, ULONG offData, ULONG cbData); +#endif + +/* + * We only do INIT allocations. PAGE is too much work and risk for little gain. + */ +#ifdef ALLOC_PRAGMA +NTSTATUS DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath); +# pragma alloc_text(INIT, DriverEntry) +# ifdef TARGET_NT4 +static NTSTATUS vgdrvNt4CreateDevice(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath); +# pragma alloc_text(INIT, vgdrvNt4CreateDevice) +static NTSTATUS vgdrvNt4FindPciDevice(PULONG puluBusNumber, PPCI_SLOT_NUMBER puSlotNumber); +# pragma alloc_text(INIT, vgdrvNt4FindPciDevice) +# endif +#endif +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The detected NT (windows) version. */ +static VGDRVNTVER g_enmVGDrvNtVer = VGDRVNTVER_INVALID; +/** Pointer to the PoStartNextPowerIrp routine (in the NT kernel). + * Introduced in Windows 2000. */ +static decltype(PoStartNextPowerIrp) *g_pfnPoStartNextPowerIrp = NULL; +/** Pointer to the PoCallDriver routine (in the NT kernel). + * Introduced in Windows 2000. */ +static decltype(PoCallDriver) *g_pfnPoCallDriver = NULL; +#ifdef TARGET_NT4 +/** Pointer to the HalAssignSlotResources routine (in the HAL). + * Introduced in NT 3.50. */ +static decltype(HalAssignSlotResources) *g_pfnHalAssignSlotResources= NULL; +/** Pointer to the HalGetBusDataByOffset routine (in the HAL). + * Introduced in NT 3.50. */ +static decltype(HalGetBusDataByOffset) *g_pfnHalGetBusDataByOffset = NULL; +/** Pointer to the HalSetBusDataByOffset routine (in the HAL). + * Introduced in NT 3.50 (we provide fallback and use it only for NT 3.1). */ +static decltype(HalSetBusDataByOffset) *g_pfnHalSetBusDataByOffset = NULL; +#endif +/** Pointer to the KeRegisterBugCheckCallback routine (in the NT kernel). + * Introduced in Windows 3.50. */ +static decltype(KeRegisterBugCheckCallback) *g_pfnKeRegisterBugCheckCallback = NULL; +/** Pointer to the KeRegisterBugCheckCallback routine (in the NT kernel). + * Introduced in Windows 3.50. */ +static decltype(KeDeregisterBugCheckCallback) *g_pfnKeDeregisterBugCheckCallback = NULL; +/** Pointer to the KiBugCheckData array (in the NT kernel). + * Introduced in Windows 4. */ +static uintptr_t const *g_pauKiBugCheckData = NULL; +/** Set if the callback was successfully registered and needs deregistering. */ +static bool g_fBugCheckCallbackRegistered = false; +/** The bugcheck callback record. */ +static KBUGCHECK_CALLBACK_RECORD g_BugCheckCallbackRec; + + + +/** + * Driver entry point. + * + * @returns appropriate status code. + * @param pDrvObj Pointer to driver object. + * @param pRegPath Registry base path. + */ +NTSTATUS DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath) +{ + RT_NOREF1(pRegPath); +#ifdef TARGET_NT4 + /* + * Looks like NT 3.1 doesn't necessarily zero our uninitialized data segments + * (like ".bss"), at least not when loading at runtime, so do that. + */ + PIMAGE_DOS_HEADER pMzHdr = &__ImageBase; + PIMAGE_NT_HEADERS32 pNtHdrs = (PIMAGE_NT_HEADERS32)((uint8_t *)pMzHdr + pMzHdr->e_lfanew); + if ( pNtHdrs->Signature == IMAGE_NT_SIGNATURE + && pNtHdrs->FileHeader.NumberOfSections > 2 + && pNtHdrs->FileHeader.NumberOfSections < 64) + { + uint32_t iShdr = pNtHdrs->FileHeader.NumberOfSections; + uint32_t uRvaEnd = pNtHdrs->OptionalHeader.SizeOfImage; /* (may be changed to exclude tail sections) */ + PIMAGE_SECTION_HEADER paShdrs; + paShdrs = (PIMAGE_SECTION_HEADER)&pNtHdrs->OptionalHeader.DataDirectory[pNtHdrs->OptionalHeader.NumberOfRvaAndSizes]; + while (iShdr-- > 0) + { + if ( !(paShdrs[iShdr].Characteristics & IMAGE_SCN_TYPE_NOLOAD) + && paShdrs[iShdr].VirtualAddress < uRvaEnd) + { + uint32_t const cbSection = uRvaEnd - paShdrs[iShdr].VirtualAddress; + uint32_t const offUninitialized = paShdrs[iShdr].SizeOfRawData; + //RTLogBackdoorPrintf("section #%u: rva=%#x size=%#x calcsize=%#x) rawsize=%#x\n", iShdr, + // paShdrs[iShdr].VirtualAddress, paShdrs[iShdr].Misc.VirtualSize, cbSection, offUninitialized); + if ( offUninitialized < cbSection + && (paShdrs[iShdr].Characteristics & IMAGE_SCN_MEM_WRITE)) + memset((uint8_t *)pMzHdr + paShdrs[iShdr].VirtualAddress + offUninitialized, 0, cbSection - offUninitialized); + uRvaEnd = paShdrs[iShdr].VirtualAddress; + } + } + } + else + RTLogBackdoorPrintf("VBoxGuest: Bad pNtHdrs=%p: %#x\n", pNtHdrs, pNtHdrs->Signature); +#endif + + /* + * Start by initializing IPRT. + */ + int rc = RTR0Init(0); + if (RT_FAILURE(rc)) + { + RTLogBackdoorPrintf("VBoxGuest: RTR0Init failed: %Rrc!\n", rc); + return STATUS_UNSUCCESSFUL; + } + VGDrvCommonInitLoggers(); + + LogFunc(("Driver built: %s %s\n", __DATE__, __TIME__)); + + /* + * Check if the NT version is supported and initialize g_enmVGDrvNtVer. + */ + ULONG ulMajorVer; + ULONG ulMinorVer; + ULONG ulBuildNo; + BOOLEAN fCheckedBuild = PsGetVersion(&ulMajorVer, &ulMinorVer, &ulBuildNo, NULL); + + /* Use RTLogBackdoorPrintf to make sure that this goes to VBox.log on the host. */ + RTLogBackdoorPrintf("VBoxGuest: Windows version %u.%u, build %u\n", ulMajorVer, ulMinorVer, ulBuildNo); + if (fCheckedBuild) + RTLogBackdoorPrintf("VBoxGuest: Windows checked build\n"); + +#ifdef VBOX_STRICT + vgdrvNtDoTests(); +#endif + NTSTATUS rcNt = STATUS_SUCCESS; + switch (ulMajorVer) + { + case 10: + /* Windows 10 Preview builds starting with 9926. */ + g_enmVGDrvNtVer = VGDRVNTVER_WIN10; + /* Windows 11 Preview builds starting with 22000. */ + if (ulBuildNo >= 22000) + g_enmVGDrvNtVer = VGDRVNTVER_WIN11; + break; + case 6: /* Windows Vista or Windows 7 (based on minor ver) */ + switch (ulMinorVer) + { + case 0: /* Note: Also could be Windows 2008 Server! */ + g_enmVGDrvNtVer = VGDRVNTVER_WINVISTA; + break; + case 1: /* Note: Also could be Windows 2008 Server R2! */ + g_enmVGDrvNtVer = VGDRVNTVER_WIN7; + break; + case 2: + g_enmVGDrvNtVer = VGDRVNTVER_WIN8; + break; + case 3: + g_enmVGDrvNtVer = VGDRVNTVER_WIN81; + break; + case 4: /* Windows 10 Preview builds. */ + default: + g_enmVGDrvNtVer = VGDRVNTVER_WIN10; + break; + } + break; + case 5: + switch (ulMinorVer) + { + default: + case 2: + g_enmVGDrvNtVer = VGDRVNTVER_WIN2K3; + break; + case 1: + g_enmVGDrvNtVer = VGDRVNTVER_WINXP; + break; + case 0: + g_enmVGDrvNtVer = VGDRVNTVER_WIN2K; + break; + } + break; + case 4: + g_enmVGDrvNtVer = VGDRVNTVER_WINNT4; + break; + case 3: + if (ulMinorVer > 50) + g_enmVGDrvNtVer = VGDRVNTVER_WINNT351; + else if (ulMinorVer >= 50) + g_enmVGDrvNtVer = VGDRVNTVER_WINNT350; + else + g_enmVGDrvNtVer = VGDRVNTVER_WINNT310; + break; + default: + /* Major versions above 6 gets classified as windows 10. */ + if (ulMajorVer > 6) + g_enmVGDrvNtVer = VGDRVNTVER_WIN10; + else + { + RTLogBackdoorPrintf("At least Windows NT 3.10 required! Found %u.%u!\n", ulMajorVer, ulMinorVer); + rcNt = STATUS_DRIVER_UNABLE_TO_LOAD; + } + break; + } + if (NT_SUCCESS(rcNt)) + { + /* + * Dynamically resolve symbols not present in NT4. + */ + RTDBGKRNLINFO hKrnlInfo; + rc = RTR0DbgKrnlInfoOpen(&hKrnlInfo, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + g_pfnKeRegisterBugCheckCallback = (decltype(KeRegisterBugCheckCallback) *) RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "KeRegisterBugCheckCallback"); + g_pfnKeDeregisterBugCheckCallback = (decltype(KeDeregisterBugCheckCallback) *)RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "KeDeregisterBugCheckCallback"); + g_pauKiBugCheckData = (uintptr_t const *) RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "KiBugCheckData"); + g_pfnPoCallDriver = (decltype(PoCallDriver) *) RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "PoCallDriver"); + g_pfnPoStartNextPowerIrp = (decltype(PoStartNextPowerIrp) *) RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "PoStartNextPowerIrp"); +#ifdef TARGET_NT4 + if (g_enmVGDrvNtVer > VGDRVNTVER_WINNT4) +#endif + { + if (!g_pfnPoCallDriver) { LogRelFunc(("Missing PoCallDriver!\n")); rc = VERR_SYMBOL_NOT_FOUND; } + if (!g_pfnPoStartNextPowerIrp) { LogRelFunc(("Missing PoStartNextPowerIrp!\n")); rc = VERR_SYMBOL_NOT_FOUND; } + } + +#ifdef TARGET_NT4 + g_pfnHalAssignSlotResources = (decltype(HalAssignSlotResources) *)RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "HalAssignSlotResources"); + if (!g_pfnHalAssignSlotResources && g_enmVGDrvNtVer >= VGDRVNTVER_WINNT350 && g_enmVGDrvNtVer < VGDRVNTVER_WIN2K) + { + RTLogBackdoorPrintf("VBoxGuest: Missing HalAssignSlotResources!\n"); + rc = VERR_SYMBOL_NOT_FOUND; + } + + g_pfnHalGetBusDataByOffset = (decltype(HalGetBusDataByOffset) *)RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "HalGetBusDataByOffset"); + if (!g_pfnHalGetBusDataByOffset && g_enmVGDrvNtVer >= VGDRVNTVER_WINNT350 && g_enmVGDrvNtVer < VGDRVNTVER_WIN2K) + { + RTLogBackdoorPrintf("VBoxGuest: Missing HalGetBusDataByOffset!\n"); + rc = VERR_SYMBOL_NOT_FOUND; + } + if (!g_pfnHalGetBusDataByOffset) + g_pfnHalGetBusDataByOffset = vgdrvNt31GetBusDataByOffset; + + g_pfnHalSetBusDataByOffset = (decltype(HalSetBusDataByOffset) *)RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "HalSetBusDataByOffset"); + if (!g_pfnHalSetBusDataByOffset && g_enmVGDrvNtVer >= VGDRVNTVER_WINNT350 && g_enmVGDrvNtVer < VGDRVNTVER_WIN2K) + { + RTLogBackdoorPrintf("VBoxGuest: Missing HalSetBusDataByOffset!\n"); + rc = VERR_SYMBOL_NOT_FOUND; + } + if (!g_pfnHalSetBusDataByOffset) + g_pfnHalSetBusDataByOffset = vgdrvNt31SetBusDataByOffset; +#endif + RTR0DbgKrnlInfoRelease(hKrnlInfo); + } + if (RT_SUCCESS(rc)) + { + /* + * Setup the driver entry points in pDrvObj. + */ + pDrvObj->DriverUnload = vgdrvNtUnload; + pDrvObj->MajorFunction[IRP_MJ_CREATE] = vgdrvNtCreate; + pDrvObj->MajorFunction[IRP_MJ_CLOSE] = vgdrvNtClose; + pDrvObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = vgdrvNtDeviceControl; + pDrvObj->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = vgdrvNtInternalIOCtl; + /** @todo Need to call IoRegisterShutdownNotification or + * IoRegisterLastChanceShutdownNotification, possibly hooking the + * HalReturnToFirmware import in NTOSKRNL on older systems (<= ~NT4) and + * check for power off requests. */ + pDrvObj->MajorFunction[IRP_MJ_SHUTDOWN] = vgdrvNtShutdown; + pDrvObj->MajorFunction[IRP_MJ_READ] = vgdrvNtNotSupportedStub; + pDrvObj->MajorFunction[IRP_MJ_WRITE] = vgdrvNtNotSupportedStub; +#ifdef TARGET_NT4 + if (g_enmVGDrvNtVer <= VGDRVNTVER_WINNT4) + rcNt = vgdrvNt4CreateDevice(pDrvObj, pRegPath); + else +#endif + { + pDrvObj->MajorFunction[IRP_MJ_PNP] = vgdrvNtNt5PlusPnP; + pDrvObj->MajorFunction[IRP_MJ_POWER] = vgdrvNtNt5PlusPower; + pDrvObj->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = vgdrvNtNt5PlusSystemControl; + pDrvObj->DriverExtension->AddDevice = vgdrvNtNt5PlusAddDevice; + } + if (NT_SUCCESS(rcNt)) + { + /* + * Try register the bugcheck callback (non-fatal). + */ + if ( g_pfnKeRegisterBugCheckCallback + && g_pfnKeDeregisterBugCheckCallback) + { + AssertCompile(BufferEmpty == 0); + KeInitializeCallbackRecord(&g_BugCheckCallbackRec); + if (g_pfnKeRegisterBugCheckCallback(&g_BugCheckCallbackRec, vgdrvNtBugCheckCallback, + NULL, 0, (PUCHAR)"VBoxGuest")) + g_fBugCheckCallbackRegistered = true; + else + g_fBugCheckCallbackRegistered = false; + } + else + Assert(g_pfnKeRegisterBugCheckCallback == NULL && g_pfnKeDeregisterBugCheckCallback == NULL); + + LogFlowFunc(("Returning %#x\n", rcNt)); + return rcNt; + } + } + else + rcNt = STATUS_PROCEDURE_NOT_FOUND; + } + + /* + * Failed. + */ + LogRelFunc(("Failed! rcNt=%#x\n", rcNt)); + VGDrvCommonDestroyLoggers(); + RTR0Term(); + return rcNt; +} + + +/** + * Translates our internal NT version enum to VBox OS. + * + * @returns VBox OS type. + * @param enmNtVer The NT version. + */ +static VBOXOSTYPE vgdrvNtVersionToOSType(VGDRVNTVER enmNtVer) +{ + VBOXOSTYPE enmOsType; + switch (enmNtVer) + { + case VGDRVNTVER_WINNT310: enmOsType = VBOXOSTYPE_WinNT3x; break; + case VGDRVNTVER_WINNT350: enmOsType = VBOXOSTYPE_WinNT3x; break; + case VGDRVNTVER_WINNT351: enmOsType = VBOXOSTYPE_WinNT3x; break; + case VGDRVNTVER_WINNT4: enmOsType = VBOXOSTYPE_WinNT4; break; + case VGDRVNTVER_WIN2K: enmOsType = VBOXOSTYPE_Win2k; break; + case VGDRVNTVER_WINXP: enmOsType = VBOXOSTYPE_WinXP; break; + case VGDRVNTVER_WIN2K3: enmOsType = VBOXOSTYPE_Win2k3; break; + case VGDRVNTVER_WINVISTA: enmOsType = VBOXOSTYPE_WinVista; break; + case VGDRVNTVER_WIN7: enmOsType = VBOXOSTYPE_Win7; break; + case VGDRVNTVER_WIN8: enmOsType = VBOXOSTYPE_Win8; break; + case VGDRVNTVER_WIN81: enmOsType = VBOXOSTYPE_Win81; break; + case VGDRVNTVER_WIN10: enmOsType = VBOXOSTYPE_Win10; break; + case VGDRVNTVER_WIN11: enmOsType = VBOXOSTYPE_Win11_x64; break; + + default: + /* We don't know, therefore NT family. */ + enmOsType = VBOXOSTYPE_WinNT; + break; + } +#if ARCH_BITS == 64 + enmOsType = (VBOXOSTYPE)((int)enmOsType | VBOXOSTYPE_x64); +#endif + return enmOsType; +} + + +/** + * Does the fundamental device extension initialization. + * + * @returns NT status. + * @param pDevExt The device extension. + * @param pDevObj The device object. + */ +static NTSTATUS vgdrvNtInitDevExtFundament(PVBOXGUESTDEVEXTWIN pDevExt, PDEVICE_OBJECT pDevObj) +{ + RT_ZERO(*pDevExt); + + KeInitializeSpinLock(&pDevExt->MouseEventAccessSpinLock); + pDevExt->pDeviceObject = pDevObj; + pDevExt->enmPrevDevState = VGDRVNTDEVSTATE_STOPPED; + pDevExt->enmDevState = VGDRVNTDEVSTATE_STOPPED; + + int rc = RTCritSectRwInit(&pDevExt->SessionCreateCritSect); + if (RT_SUCCESS(rc)) + { + rc = VGDrvCommonInitDevExtFundament(&pDevExt->Core); + if (RT_SUCCESS(rc)) + { + LogFlow(("vgdrvNtInitDevExtFundament: returning success\n")); + return STATUS_SUCCESS; + } + + RTCritSectRwDelete(&pDevExt->SessionCreateCritSect); + } + Log(("vgdrvNtInitDevExtFundament: failed: rc=%Rrc\n", rc)); + return STATUS_UNSUCCESSFUL; +} + + +/** + * Counter part to vgdrvNtInitDevExtFundament. + * + * @param pDevExt The device extension. + */ +static void vgdrvNtDeleteDevExtFundament(PVBOXGUESTDEVEXTWIN pDevExt) +{ + LogFlow(("vgdrvNtDeleteDevExtFundament:\n")); + VGDrvCommonDeleteDevExtFundament(&pDevExt->Core); + RTCritSectRwDelete(&pDevExt->SessionCreateCritSect); +} + + +#ifdef LOG_ENABLED +/** + * Debug helper to dump a device resource list. + * + * @param pResourceList list of device resources. + */ +static void vgdrvNtShowDeviceResources(PCM_RESOURCE_LIST pRsrcList) +{ + for (uint32_t iList = 0; iList < pRsrcList->Count; iList++) + { + PCM_FULL_RESOURCE_DESCRIPTOR pList = &pRsrcList->List[iList]; + LogFunc(("List #%u: InterfaceType=%#x BusNumber=%#x ListCount=%u ListRev=%#x ListVer=%#x\n", + iList, pList->InterfaceType, pList->BusNumber, pList->PartialResourceList.Count, + pList->PartialResourceList.Revision, pList->PartialResourceList.Version )); + + PCM_PARTIAL_RESOURCE_DESCRIPTOR pResource = pList->PartialResourceList.PartialDescriptors; + for (ULONG i = 0; i < pList->PartialResourceList.Count; ++i, ++pResource) + { + ULONG uType = pResource->Type; + static char const * const s_apszName[] = + { + "CmResourceTypeNull", + "CmResourceTypePort", + "CmResourceTypeInterrupt", + "CmResourceTypeMemory", + "CmResourceTypeDma", + "CmResourceTypeDeviceSpecific", + "CmResourceTypeuBusNumber", + "CmResourceTypeDevicePrivate", + "CmResourceTypeAssignedResource", + "CmResourceTypeSubAllocateFrom", + }; + + if (uType < RT_ELEMENTS(s_apszName)) + LogFunc((" %.30s Flags=%#x Share=%#x", s_apszName[uType], pResource->Flags, pResource->ShareDisposition)); + else + LogFunc((" Type=%#x Flags=%#x Share=%#x", uType, pResource->Flags, pResource->ShareDisposition)); + switch (uType) + { + case CmResourceTypePort: + case CmResourceTypeMemory: + Log((" Start %#RX64, length=%#x\n", pResource->u.Port.Start.QuadPart, pResource->u.Port.Length)); + break; + + case CmResourceTypeInterrupt: + Log((" Level=%X, vector=%#x, affinity=%#x\n", + pResource->u.Interrupt.Level, pResource->u.Interrupt.Vector, pResource->u.Interrupt.Affinity)); + break; + + case CmResourceTypeDma: + Log((" Channel %d, Port %#x\n", pResource->u.Dma.Channel, pResource->u.Dma.Port)); + break; + + default: + Log(("\n")); + break; + } + } + } +} +#endif /* LOG_ENABLED */ + + +/** + * Helper to scan the PCI resource list and remember stuff. + * + * @param pDevExt The device extension. + * @param pResList Resource list + * @param fTranslated Whether the addresses are translated or not. + */ +static NTSTATUS vgdrvNtScanPCIResourceList(PVBOXGUESTDEVEXTWIN pDevExt, PCM_RESOURCE_LIST pResList, bool fTranslated) +{ + LogFlowFunc(("Found %d resources\n", pResList->List->PartialResourceList.Count)); + PCM_PARTIAL_RESOURCE_DESCRIPTOR pPartialData = NULL; + bool fGotIrq = false; + bool fGotMmio = false; + bool fGotIoPorts = false; + NTSTATUS rc = STATUS_SUCCESS; + for (ULONG i = 0; i < pResList->List->PartialResourceList.Count; i++) + { + pPartialData = &pResList->List->PartialResourceList.PartialDescriptors[i]; + switch (pPartialData->Type) + { + case CmResourceTypePort: + LogFlowFunc(("I/O range: Base=%#RX64, length=%08x\n", + pPartialData->u.Port.Start.QuadPart, pPartialData->u.Port.Length)); + /* Save the first I/O port base. */ + if (!fGotIoPorts) + { + pDevExt->Core.IOPortBase = (RTIOPORT)pPartialData->u.Port.Start.LowPart; + fGotIoPorts = true; + LogFunc(("I/O range for VMMDev found! Base=%#RX64, length=%08x\n", + pPartialData->u.Port.Start.QuadPart, pPartialData->u.Port.Length)); + } + else + LogRelFunc(("More than one I/O port range?!?\n")); + break; + + case CmResourceTypeInterrupt: + LogFunc(("Interrupt: Level=%x, vector=%x, mode=%x\n", + pPartialData->u.Interrupt.Level, pPartialData->u.Interrupt.Vector, pPartialData->Flags)); + if (!fGotIrq) + { + /* Save information. */ + pDevExt->uInterruptLevel = pPartialData->u.Interrupt.Level; + pDevExt->uInterruptVector = pPartialData->u.Interrupt.Vector; + pDevExt->fInterruptAffinity = pPartialData->u.Interrupt.Affinity; + + /* Check interrupt mode. */ + if (pPartialData->Flags & CM_RESOURCE_INTERRUPT_LATCHED) + pDevExt->enmInterruptMode = Latched; + else + pDevExt->enmInterruptMode = LevelSensitive; + fGotIrq = true; + LogFunc(("Interrupt for VMMDev found! Vector=%#x Level=%#x Affinity=%zx Mode=%d\n", pDevExt->uInterruptVector, + pDevExt->uInterruptLevel, pDevExt->fInterruptAffinity, pDevExt->enmInterruptMode)); + } + else + LogFunc(("More than one IRQ resource!\n")); + break; + + case CmResourceTypeMemory: + LogFlowFunc(("Memory range: Base=%#RX64, length=%08x\n", + pPartialData->u.Memory.Start.QuadPart, pPartialData->u.Memory.Length)); + /* We only care about the first read/write memory range. */ + if ( !fGotMmio + && (pPartialData->Flags & CM_RESOURCE_MEMORY_WRITEABILITY_MASK) == CM_RESOURCE_MEMORY_READ_WRITE) + { + /* Save physical MMIO base + length for VMMDev. */ + pDevExt->uVmmDevMemoryPhysAddr = pPartialData->u.Memory.Start; + pDevExt->cbVmmDevMemory = (ULONG)pPartialData->u.Memory.Length; + + if (!fTranslated) + { + /* Technically we need to make the HAL translate the address. since we + didn't used to do this and it probably just returns the input address, + we allow ourselves to ignore failures. */ + ULONG uAddressSpace = 0; + PHYSICAL_ADDRESS PhysAddr = pPartialData->u.Memory.Start; + if (HalTranslateBusAddress(pResList->List->InterfaceType, pResList->List->BusNumber, PhysAddr, + &uAddressSpace, &PhysAddr)) + { + Log(("HalTranslateBusAddress(%#RX64) -> %RX64, type %#x\n", + pPartialData->u.Memory.Start.QuadPart, PhysAddr.QuadPart, uAddressSpace)); + if (pPartialData->u.Memory.Start.QuadPart != PhysAddr.QuadPart) + pDevExt->uVmmDevMemoryPhysAddr = PhysAddr; + } + else + Log(("HalTranslateBusAddress(%#RX64) -> failed!\n", pPartialData->u.Memory.Start.QuadPart)); + } + + fGotMmio = true; + LogFunc(("Found memory range for VMMDev! Base = %#RX64, Length = %08x\n", + pPartialData->u.Memory.Start.QuadPart, pPartialData->u.Memory.Length)); + } + else + LogFunc(("Ignoring memory: Flags=%08x Base=%#RX64\n", + pPartialData->Flags, pPartialData->u.Memory.Start.QuadPart)); + break; + + default: + LogFunc(("Unhandled resource found, type=%d\n", pPartialData->Type)); + break; + } + } + return rc; +} + + +#ifdef TARGET_NT4 + +/** + * Scans the PCI resources on NT 3.1. + * + * @returns STATUS_SUCCESS or STATUS_DEVICE_CONFIGURATION_ERROR. + * @param pDevExt The device extension. + * @param uBus The bus number. + * @param uSlot The PCI slot to scan. + */ +static NTSTATUS vgdrvNt31ScanSlotResources(PVBOXGUESTDEVEXTWIN pDevExt, ULONG uBus, ULONG uSlot) +{ + /* + * Disable memory mappings so we can determin the BAR lengths + * without upsetting other mappings. + */ + uint16_t fCmd = 0; + g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, uSlot, &fCmd, VBOX_PCI_COMMAND, sizeof(fCmd)); + if (fCmd & VBOX_PCI_COMMAND_MEMORY) + { + uint16_t fCmdTmp = fCmd & ~VBOX_PCI_COMMAND_MEMORY; + g_pfnHalSetBusDataByOffset(PCIConfiguration, uBus, uSlot, &fCmdTmp, VBOX_PCI_COMMAND, sizeof(fCmdTmp)); + } + + /* + * Scan the address resources first. + */ + uint32_t aBars[6] = { UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX }; + g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, uSlot, &aBars, VBOX_PCI_BASE_ADDRESS_0, sizeof(aBars)); + + bool fGotMmio = false; + bool fGotIoPorts = false; + for (uint32_t i = 0; i < RT_ELEMENTS(aBars); i++) + { + uint32_t uBar = aBars[i]; + if (uBar == UINT32_MAX) + continue; + if ((uBar & 1) == PCI_ADDRESS_SPACE_IO) + { + uint32_t uAddr = uBar & UINT32_C(0xfffffffc); + if (!uAddr) + continue; + if (!fGotIoPorts) + { + pDevExt->Core.IOPortBase = (uint16_t)uAddr & UINT16_C(0xfffc); + fGotIoPorts = true; + LogFunc(("I/O range for VMMDev found in BAR%u! %#x\n", i, pDevExt->Core.IOPortBase)); + } + else + LogRelFunc(("More than one I/O port range?!? BAR%u=%#x\n", i, uBar)); + } + else + { + uint32_t uAddr = uBar & UINT32_C(0xfffffff0); + if (!uAddr) + continue; + + if (!fGotMmio) + { + /* Figure the length by trying to set all address bits and seeing + how many we're allowed to set. */ + uint32_t iBit = 4; + while (!(uAddr & RT_BIT_32(iBit))) + iBit++; + + uint32_t const offPciBar = VBOX_PCI_BASE_ADDRESS_0 + i * 4; + uint32_t uTmpBar = uBar | ((RT_BIT_32(iBit) - 1) & UINT32_C(0xfffffff0)); + g_pfnHalSetBusDataByOffset(PCIConfiguration, uBus, uSlot, &uTmpBar, offPciBar, sizeof(uTmpBar)); + uTmpBar = uBar; + g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, uSlot, &uTmpBar, offPciBar, sizeof(uTmpBar)); + g_pfnHalSetBusDataByOffset(PCIConfiguration, uBus, uSlot, &uBar, offPciBar, sizeof(uBar)); + + while (iBit > 4 && (uTmpBar & RT_BIT_32(iBit - 1))) + iBit--; + + /* got it */ + pDevExt->cbVmmDevMemory = RT_BIT_32(iBit); + pDevExt->uVmmDevMemoryPhysAddr.QuadPart = uAddr; + fGotMmio = true; + LogFunc(("Found memory range for VMMDev in BAR%u! %#RX64 LB %#x (raw %#x)\n", + i, pDevExt->uVmmDevMemoryPhysAddr.QuadPart, pDevExt->cbVmmDevMemory, uBar)); + } + else + LogFunc(("Ignoring memory: BAR%u=%#x\n", i, uBar)); + } + } + + /* + * Get the IRQ + */ + struct + { + uint8_t bInterruptLine; + uint8_t bInterruptPin; + } Buf = { 0, 0 }; + g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, uSlot, &Buf, VBOX_PCI_INTERRUPT_LINE, sizeof(Buf)); + if (Buf.bInterruptPin != 0) + { + pDevExt->uInterruptVector = Buf.bInterruptLine; + pDevExt->uInterruptLevel = Buf.bInterruptLine; + pDevExt->enmInterruptMode = LevelSensitive; + pDevExt->fInterruptAffinity = RT_BIT_32(RTMpGetCount()) - 1; + LogFunc(("Interrupt for VMMDev found! Vector=%#x Level=%#x Affinity=%zx Mode=%d\n", + pDevExt->uInterruptVector, pDevExt->uInterruptLevel, pDevExt->fInterruptAffinity, pDevExt->enmInterruptMode)); + } + + /* + * Got what we need? + */ + if (fGotIoPorts && (!fGotMmio || Buf.bInterruptPin != 0)) + { + /* + * Enable both MMIO, I/O space and busmastering so we can use the device. + */ + uint16_t fCmdNew = fCmd | VBOX_PCI_COMMAND_IO | VBOX_PCI_COMMAND_MEMORY | VBOX_PCI_COMMAND_MASTER; + g_pfnHalSetBusDataByOffset(PCIConfiguration, uBus, uSlot, &fCmdNew, VBOX_PCI_COMMAND, sizeof(fCmdNew)); + + return STATUS_SUCCESS; + } + + /* No. Complain, restore device command value and return failure. */ + if (!fGotIoPorts) + LogRel(("VBoxGuest: Did not find I/O port range: %#x %#x %#x %#x %#x %#x\n", + aBars[0], aBars[1], aBars[2], aBars[3], aBars[4], aBars[5])); + if (!fGotMmio || Buf.bInterruptPin != 0) + LogRel(("VBoxGuest: Got MMIO but no interrupts!\n")); + + g_pfnHalSetBusDataByOffset(PCIConfiguration, uBus, uSlot, &fCmd, VBOX_PCI_COMMAND, sizeof(fCmd)); + return STATUS_DEVICE_CONFIGURATION_ERROR; +} + +#endif /* TARGET_NT4 */ + +/** + * Unmaps the VMMDev I/O range from kernel space. + * + * @param pDevExt The device extension. + */ +static void vgdrvNtUnmapVMMDevMemory(PVBOXGUESTDEVEXTWIN pDevExt) +{ + LogFlowFunc(("pVMMDevMemory = %#x\n", pDevExt->Core.pVMMDevMemory)); + if (pDevExt->Core.pVMMDevMemory) + { + MmUnmapIoSpace((void*)pDevExt->Core.pVMMDevMemory, pDevExt->cbVmmDevMemory); + pDevExt->Core.pVMMDevMemory = NULL; + } + + pDevExt->uVmmDevMemoryPhysAddr.QuadPart = 0; + pDevExt->cbVmmDevMemory = 0; +} + + +/** + * Maps the I/O space from VMMDev to virtual kernel address space. + * + * @return NTSTATUS + * + * @param pDevExt The device extension. + * @param PhysAddr Physical address to map. + * @param cbToMap Number of bytes to map. + * @param ppvMMIOBase Pointer of mapped I/O base. + * @param pcbMMIO Length of mapped I/O base. + */ +static NTSTATUS vgdrvNtMapVMMDevMemory(PVBOXGUESTDEVEXTWIN pDevExt, PHYSICAL_ADDRESS PhysAddr, ULONG cbToMap, + void **ppvMMIOBase, uint32_t *pcbMMIO) +{ + AssertPtrReturn(pDevExt, VERR_INVALID_POINTER); + AssertPtrReturn(ppvMMIOBase, VERR_INVALID_POINTER); + /* pcbMMIO is optional. */ + + NTSTATUS rc = STATUS_SUCCESS; + if (PhysAddr.LowPart > 0) /* We're mapping below 4GB. */ + { + VMMDevMemory *pVMMDevMemory = (VMMDevMemory *)MmMapIoSpace(PhysAddr, cbToMap, MmNonCached); + LogFlowFunc(("pVMMDevMemory = %#x\n", pVMMDevMemory)); + if (pVMMDevMemory) + { + LogFunc(("VMMDevMemory: Version = %#x, Size = %d\n", pVMMDevMemory->u32Version, pVMMDevMemory->u32Size)); + + /* Check version of the structure; do we have the right memory version? */ + if (pVMMDevMemory->u32Version == VMMDEV_MEMORY_VERSION) + { + /* Save results. */ + *ppvMMIOBase = pVMMDevMemory; + if (pcbMMIO) /* Optional. */ + *pcbMMIO = pVMMDevMemory->u32Size; + + LogFlowFunc(("VMMDevMemory found and mapped! pvMMIOBase = 0x%p\n", *ppvMMIOBase)); + } + else + { + /* Not our version, refuse operation and unmap the memory. */ + LogFunc(("Wrong version (%u), refusing operation!\n", pVMMDevMemory->u32Version)); + + vgdrvNtUnmapVMMDevMemory(pDevExt); + rc = STATUS_UNSUCCESSFUL; + } + } + else + rc = STATUS_UNSUCCESSFUL; + } + return rc; +} + + +/** + * Sets up the device and its resources. + * + * @param pDevExt Our device extension data. + * @param pDevObj The device object. + * @param pIrp The request packet if NT5+, NULL for NT4 and earlier. + * @param pDrvObj The driver object for NT4, NULL for NT5+. + * @param pRegPath The registry path for NT4, NULL for NT5+. + */ +static NTSTATUS vgdrvNtSetupDevice(PVBOXGUESTDEVEXTWIN pDevExt, PDEVICE_OBJECT pDevObj, + PIRP pIrp, PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath) +{ + LogFlowFunc(("ENTER: pDevExt=%p pDevObj=%p pIrq=%p pDrvObj=%p pRegPath=%p\n", pDevExt, pDevObj, pIrp, pDrvObj, pRegPath)); + + NTSTATUS rcNt; + if (!pIrp) + { +#ifdef TARGET_NT4 + /* + * NT4, NT3.x: Let's have a look at what our PCI adapter offers. + */ + LogFlowFunc(("Starting to scan PCI resources of VBoxGuest ...\n")); + + /* Assign the PCI resources. */ + UNICODE_STRING ClassName; + RtlInitUnicodeString(&ClassName, L"VBoxGuestAdapter"); + PCM_RESOURCE_LIST pResourceList = NULL; + if (g_pfnHalAssignSlotResources) + { + rcNt = g_pfnHalAssignSlotResources(pRegPath, &ClassName, pDrvObj, pDevObj, PCIBus, pDevExt->uBus, pDevExt->uSlot, + &pResourceList); +# ifdef LOG_ENABLED + if (pResourceList) + vgdrvNtShowDeviceResources(pResourceList); +# endif + if (NT_SUCCESS(rcNt)) + { + rcNt = vgdrvNtScanPCIResourceList(pDevExt, pResourceList, false /*fTranslated*/); + ExFreePool(pResourceList); + } + } + else + rcNt = vgdrvNt31ScanSlotResources(pDevExt, pDevExt->uBus, pDevExt->uSlot); + +# else /* !TARGET_NT4 */ + AssertFailed(); + RT_NOREF(pDevObj, pDrvObj, pRegPath); + rcNt = STATUS_INTERNAL_ERROR; +# endif /* !TARGET_NT4 */ + } + else + { + /* + * NT5+: Scan the PCI resource list from the IRP. + */ + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); +# ifdef LOG_ENABLED + vgdrvNtShowDeviceResources(pStack->Parameters.StartDevice.AllocatedResourcesTranslated); +# endif + rcNt = vgdrvNtScanPCIResourceList(pDevExt, pStack->Parameters.StartDevice.AllocatedResourcesTranslated, + true /*fTranslated*/); + } + if (NT_SUCCESS(rcNt)) + { + /* + * Map physical address of VMMDev memory into MMIO region + * and init the common device extension bits. + */ + void *pvMMIOBase = NULL; + uint32_t cbMMIO = 0; + rcNt = vgdrvNtMapVMMDevMemory(pDevExt, + pDevExt->uVmmDevMemoryPhysAddr, + pDevExt->cbVmmDevMemory, + &pvMMIOBase, + &cbMMIO); + if (NT_SUCCESS(rcNt)) + { + pDevExt->Core.pVMMDevMemory = (VMMDevMemory *)pvMMIOBase; + + LogFunc(("pvMMIOBase=0x%p, pDevExt=0x%p, pDevExt->Core.pVMMDevMemory=0x%p\n", + pvMMIOBase, pDevExt, pDevExt ? pDevExt->Core.pVMMDevMemory : NULL)); + + int vrc = VGDrvCommonInitDevExtResources(&pDevExt->Core, + pDevExt->Core.IOPortBase, + pvMMIOBase, cbMMIO, + vgdrvNtVersionToOSType(g_enmVGDrvNtVer), + VMMDEV_EVENT_MOUSE_POSITION_CHANGED); + if (RT_SUCCESS(vrc)) + { + + vrc = VbglR0GRAlloc((VMMDevRequestHeader **)&pDevExt->pPowerStateRequest, + sizeof(VMMDevPowerStateRequest), VMMDevReq_SetPowerStatus); + if (RT_SUCCESS(vrc)) + { + /* + * Register DPC and ISR. + */ + LogFlowFunc(("Initializing DPC/ISR (pDevObj=%p)...\n", pDevExt->pDeviceObject)); + IoInitializeDpcRequest(pDevExt->pDeviceObject, vgdrvNtDpcHandler); + + ULONG uInterruptVector = pDevExt->uInterruptVector; + KIRQL uHandlerIrql = (KIRQL)pDevExt->uInterruptLevel; +#ifdef TARGET_NT4 + if (!pIrp) + { + /* NT4: Get an interrupt vector. Only proceed if the device provides an interrupt. */ + if ( uInterruptVector + || pDevExt->uInterruptLevel) + { + LogFlowFunc(("Getting interrupt vector (HAL): Bus=%u, IRQL=%u, Vector=%u\n", + pDevExt->uBus, pDevExt->uInterruptLevel, pDevExt->uInterruptVector)); + uInterruptVector = HalGetInterruptVector(g_enmVGDrvNtVer == VGDRVNTVER_WINNT310 ? Isa : PCIBus, + pDevExt->uBus, + pDevExt->uInterruptLevel, + pDevExt->uInterruptVector, + &uHandlerIrql, + &pDevExt->fInterruptAffinity); + LogFlowFunc(("HalGetInterruptVector returns vector=%u\n", uInterruptVector)); + } + else + LogFunc(("Device does not provide an interrupt!\n")); + } +#endif + if (uInterruptVector) + { + LogFlowFunc(("Connecting interrupt (IntVector=%#u), uHandlerIrql=%u) ...\n", + uInterruptVector, uHandlerIrql)); + + rcNt = IoConnectInterrupt(&pDevExt->pInterruptObject, /* Out: interrupt object. */ + vgdrvNtIsrHandler, /* Our ISR handler. */ + pDevExt, /* Device context. */ + NULL, /* Optional spinlock. */ + uInterruptVector, /* Interrupt vector. */ + uHandlerIrql, /* Irql. */ + uHandlerIrql, /* SynchronizeIrql. */ + pDevExt->enmInterruptMode, /* LevelSensitive or Latched. */ + TRUE, /* Shareable interrupt. */ + pDevExt->fInterruptAffinity, /* CPU affinity. */ + FALSE); /* Don't save FPU stack. */ + if (NT_ERROR(rcNt)) + LogFunc(("Could not connect interrupt: rcNt=%#x!\n", rcNt)); + } + else + LogFunc(("No interrupt vector found!\n")); + if (NT_SUCCESS(rcNt)) + { + /* + * Once we've read configuration from register and host, we're finally read. + */ + /** @todo clean up guest ring-3 logging, keeping it separate from the kernel to avoid sharing limits with it. */ + pDevExt->Core.fLoggingEnabled = true; + vgdrvNtReadConfiguration(pDevExt); + + /* Ready to rumble! */ + LogRelFunc(("Device is ready!\n")); + pDevExt->enmDevState = VGDRVNTDEVSTATE_OPERATIONAL; + pDevExt->enmPrevDevState = VGDRVNTDEVSTATE_OPERATIONAL; + return STATUS_SUCCESS; + } + + pDevExt->pInterruptObject = NULL; + + VbglR0GRFree(&pDevExt->pPowerStateRequest->header); + pDevExt->pPowerStateRequest = NULL; + } + else + { + LogFunc(("Alloc for pPowerStateRequest failed, vrc=%Rrc\n", vrc)); + rcNt = STATUS_UNSUCCESSFUL; + } + + VGDrvCommonDeleteDevExtResources(&pDevExt->Core); + } + else + { + LogFunc(("Could not init device extension resources: vrc=%Rrc\n", vrc)); + rcNt = STATUS_DEVICE_CONFIGURATION_ERROR; + } + vgdrvNtUnmapVMMDevMemory(pDevExt); + } + else + LogFunc(("Could not map physical address of VMMDev, rcNt=%#x\n", rcNt)); + } + + LogFunc(("Returned with rcNt=%#x\n", rcNt)); + return rcNt; +} + + + + +#ifdef TARGET_NT4 +# define PCI_CFG_ADDR 0xcf8 +# define PCI_CFG_DATA 0xcfc + +/** + * NT 3.1 doesn't do PCI nor HalSetBusDataByOffset, this is our fallback. + */ +static ULONG NTAPI vgdrvNt31SetBusDataByOffset(BUS_DATA_TYPE enmBusDataType, ULONG idxBus, ULONG uSlot, + void *pvData, ULONG offData, ULONG cbData) +{ + /* + * Validate input a little bit. + */ + RT_NOREF(enmBusDataType); + Assert(idxBus <= 255); + Assert(uSlot <= 255); + Assert(offData <= 255); + Assert(cbData > 0); + + PCI_SLOT_NUMBER PciSlot; + PciSlot.u.AsULONG = uSlot; + uint32_t const idxAddrTop = UINT32_C(0x80000000) + | (idxBus << 16) + | (PciSlot.u.bits.DeviceNumber << 11) + | (PciSlot.u.bits.FunctionNumber << 8); + + /* + * Write the given bytes. + */ + uint8_t const *pbData = (uint8_t const *)pvData; + uint32_t off = offData; + uint32_t cbRet = 0; + + /* Unaligned start. */ + if (off & 3) + { + ASMOutU32(PCI_CFG_ADDR, idxAddrTop | (off & ~3)); + switch (off & 3) + { + case 1: + ASMOutU8(PCI_CFG_DATA + 1, pbData[cbRet++]); + if (cbRet >= cbData) + break; + RT_FALL_THRU(); + case 2: + ASMOutU8(PCI_CFG_DATA + 2, pbData[cbRet++]); + if (cbRet >= cbData) + break; + RT_FALL_THRU(); + case 3: + ASMOutU8(PCI_CFG_DATA + 3, pbData[cbRet++]); + break; + } + off = (off | 3) + 1; + } + + /* Bulk. */ + while (off < 256 && cbRet < cbData) + { + ASMOutU32(PCI_CFG_ADDR, idxAddrTop | off); + switch (cbData - cbRet) + { + case 1: + ASMOutU8(PCI_CFG_DATA, pbData[cbRet]); + cbRet += 1; + break; + case 2: + ASMOutU16(PCI_CFG_DATA, RT_MAKE_U16(pbData[cbRet], pbData[cbRet + 1])); + cbRet += 2; + break; + case 3: + ASMOutU16(PCI_CFG_DATA, RT_MAKE_U16(pbData[cbRet], pbData[cbRet + 1])); + ASMOutU8(PCI_CFG_DATA + 2, pbData[cbRet + 2]); + cbRet += 3; + break; + default: + ASMOutU32(PCI_CFG_DATA, RT_MAKE_U32_FROM_U8(pbData[cbRet], pbData[cbRet + 1], + pbData[cbRet + 2], pbData[cbRet + 3])); + cbRet += 4; + break; + } + off += 4; + } + + return cbRet; +} + + +/** + * NT 3.1 doesn't do PCI nor HalGetBusDataByOffset, this is our fallback. + */ +static ULONG NTAPI vgdrvNt31GetBusDataByOffset(BUS_DATA_TYPE enmBusDataType, ULONG idxBus, ULONG uSlot, + void *pvData, ULONG offData, ULONG cbData) +{ + /* + * Validate input a little bit. + */ + RT_NOREF(enmBusDataType); + Assert(idxBus <= 255); + Assert(uSlot <= 255); + Assert(offData <= 255); + Assert(cbData > 0); + + PCI_SLOT_NUMBER PciSlot; + PciSlot.u.AsULONG = uSlot; + uint32_t const idxAddrTop = UINT32_C(0x80000000) + | (idxBus << 16) + | (PciSlot.u.bits.DeviceNumber << 11) + | (PciSlot.u.bits.FunctionNumber << 8); + + /* + * Read the header type. + */ + ASMOutU32(PCI_CFG_ADDR, idxAddrTop | (VBOX_PCI_HEADER_TYPE & ~3)); + uint8_t bHdrType = ASMInU8(PCI_CFG_DATA + (VBOX_PCI_HEADER_TYPE & 3)); + if (bHdrType == 0xff) + return idxBus < 8 ? 2 : 0; /* No device here */ + if ( offData == VBOX_PCI_HEADER_TYPE + && cbData == 1) + { + *(uint8_t *)pvData = bHdrType; + /*Log("vgdrvNt31GetBusDataByOffset: PCI %#x/%#x -> %02x\n", idxAddrTop, offData, bHdrType);*/ + return 1; + } + + /* + * Read the requested bytes. + */ + uint8_t *pbData = (uint8_t *)pvData; + uint32_t off = offData; + uint32_t cbRet = 0; + + /* Unaligned start. */ + if (off & 3) + { + ASMOutU32(PCI_CFG_ADDR, idxAddrTop | (off & ~3)); + uint32_t uValue = ASMInU32(PCI_CFG_DATA); + switch (off & 3) + { + case 1: + pbData[cbRet++] = (uint8_t)(uValue >> 8); + if (cbRet >= cbData) + break; + RT_FALL_THRU(); + case 2: + pbData[cbRet++] = (uint8_t)(uValue >> 16); + if (cbRet >= cbData) + break; + RT_FALL_THRU(); + case 3: + pbData[cbRet++] = (uint8_t)(uValue >> 24); + break; + } + off = (off | 3) + 1; + } + + /* Bulk. */ + while (off < 256 && cbRet < cbData) + { + ASMOutU32(PCI_CFG_ADDR, idxAddrTop | off); + uint32_t uValue = ASMInU32(PCI_CFG_DATA); + switch (cbData - cbRet) + { + case 1: + pbData[cbRet++] = (uint8_t)uValue; + break; + case 2: + pbData[cbRet++] = (uint8_t)uValue; + pbData[cbRet++] = (uint8_t)(uValue >> 8); + break; + case 3: + pbData[cbRet++] = (uint8_t)uValue; + pbData[cbRet++] = (uint8_t)(uValue >> 8); + pbData[cbRet++] = (uint8_t)(uValue >> 16); + break; + default: + pbData[cbRet++] = (uint8_t)uValue; + pbData[cbRet++] = (uint8_t)(uValue >> 8); + pbData[cbRet++] = (uint8_t)(uValue >> 16); + pbData[cbRet++] = (uint8_t)(uValue >> 24); + break; + } + off += 4; + } + + Log(("vgdrvNt31GetBusDataByOffset: PCI %#x/%#x -> %.*Rhxs\n", idxAddrTop, offData, cbRet, pvData)); + return cbRet; +} + + +/** + * Helper function to handle the PCI device lookup. + * + * @returns NT status code. + * + * @param puBus Where to return the bus number on success. + * @param pSlot Where to return the slot number on success. + */ +static NTSTATUS vgdrvNt4FindPciDevice(PULONG puBus, PPCI_SLOT_NUMBER pSlot) +{ + Log(("vgdrvNt4FindPciDevice\n")); + + PCI_SLOT_NUMBER Slot; + Slot.u.AsULONG = 0; + + /* Scan each bus. */ + for (ULONG uBus = 0; uBus < PCI_MAX_BUSES; uBus++) + { + /* Scan each device. */ + for (ULONG idxDevice = 0; idxDevice < PCI_MAX_DEVICES; idxDevice++) + { + Slot.u.bits.DeviceNumber = idxDevice; + Slot.u.bits.FunctionNumber = 0; + + /* Check the device header. */ + uint8_t bHeaderType = 0xff; + ULONG cbRet = g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, Slot.u.AsULONG, + &bHeaderType, VBOX_PCI_HEADER_TYPE, sizeof(bHeaderType)); + if (cbRet == 0) + break; + if (cbRet == 2 || bHeaderType == 0xff) + continue; + + /* Scan functions. */ + uint32_t const cFunctionStep = bHeaderType & 0x80 ? 1 : 8; + Log(("vgdrvNt4FindPciDevice: %#x:%#x cFunctionStep=%d bHeaderType=%#x\n", uBus, idxDevice, cFunctionStep, bHeaderType)); + for (ULONG idxFunction = 0; idxFunction < PCI_MAX_FUNCTION; idxFunction += cFunctionStep) + { + Slot.u.bits.FunctionNumber = idxFunction; + + /* Read the vendor and device IDs of this device and compare with the VMMDev. */ + struct + { + uint16_t idVendor; + uint16_t idDevice; + } Buf = { PCI_INVALID_VENDORID, PCI_INVALID_VENDORID }; + cbRet = g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, Slot.u.AsULONG, &Buf, VBOX_PCI_VENDOR_ID, sizeof(Buf)); + if ( cbRet == sizeof(Buf) + && Buf.idVendor == VMMDEV_VENDORID + && Buf.idDevice == VMMDEV_DEVICEID) + { + /* Hooray, we've found it! */ + Log(("vgdrvNt4FindPciDevice: Device found! Bus=%#x Slot=%#u (dev %#x, fun %#x, rvd %#x)\n", + uBus, Slot.u.AsULONG, Slot.u.bits.DeviceNumber, Slot.u.bits.FunctionNumber, Slot.u.bits.Reserved)); + + *puBus = uBus; + *pSlot = Slot; + return STATUS_SUCCESS; + } + } + } + } + + return STATUS_DEVICE_DOES_NOT_EXIST; +} + + +/** + * Legacy helper function to create the device object. + * + * @returns NT status code. + * + * @param pDrvObj The driver object. + * @param pRegPath The driver registry path. + */ +static NTSTATUS vgdrvNt4CreateDevice(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath) +{ + Log(("vgdrvNt4CreateDevice: pDrvObj=%p, pRegPath=%p\n", pDrvObj, pRegPath)); + + /* + * Find our virtual PCI device + */ + ULONG uBus; + PCI_SLOT_NUMBER uSlot; + NTSTATUS rc = vgdrvNt4FindPciDevice(&uBus, &uSlot); + if (NT_ERROR(rc)) + { + Log(("vgdrvNt4CreateDevice: Device not found!\n")); + return rc; + } + + /* + * Create device. + */ + UNICODE_STRING DevName; + RtlInitUnicodeString(&DevName, VBOXGUEST_DEVICE_NAME_NT); + PDEVICE_OBJECT pDeviceObject = NULL; + rc = IoCreateDevice(pDrvObj, sizeof(VBOXGUESTDEVEXTWIN), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject); + if (NT_SUCCESS(rc)) + { + Log(("vgdrvNt4CreateDevice: Device created\n")); + + UNICODE_STRING DosName; + RtlInitUnicodeString(&DosName, VBOXGUEST_DEVICE_NAME_DOS); + rc = IoCreateSymbolicLink(&DosName, &DevName); + if (NT_SUCCESS(rc)) + { + Log(("vgdrvNt4CreateDevice: Symlink created\n")); + + /* + * Setup the device extension. + */ + Log(("vgdrvNt4CreateDevice: Setting up device extension ...\n")); + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDeviceObject->DeviceExtension; + int vrc = vgdrvNtInitDevExtFundament(pDevExt, pDeviceObject); + if (RT_SUCCESS(vrc)) + { + /* Store bus and slot number we've queried before. */ + pDevExt->uBus = uBus; + pDevExt->uSlot = uSlot.u.AsULONG; + + Log(("vgdrvNt4CreateDevice: Device extension created\n")); + + /* Do the actual VBox init ... */ + rc = vgdrvNtSetupDevice(pDevExt, pDeviceObject, NULL /*pIrp*/, pDrvObj, pRegPath); + if (NT_SUCCESS(rc)) + { + Log(("vgdrvNt4CreateDevice: Returning rc = 0x%x (success)\n", rc)); + return rc; + } + + /* bail out */ + vgdrvNtDeleteDevExtFundament(pDevExt); + } + IoDeleteSymbolicLink(&DosName); + } + else + Log(("vgdrvNt4CreateDevice: IoCreateSymbolicLink failed with rc = %#x\n", rc)); + IoDeleteDevice(pDeviceObject); + } + else + Log(("vgdrvNt4CreateDevice: IoCreateDevice failed with rc = %#x\n", rc)); + Log(("vgdrvNt4CreateDevice: Returning rc = 0x%x\n", rc)); + return rc; +} + +#endif /* TARGET_NT4 */ + +/** + * Handle request from the Plug & Play subsystem. + * + * @returns NT status code + * @param pDrvObj Driver object + * @param pDevObj Device object + * + * @remarks Parts of this is duplicated in VBoxGuest-win-legacy.cpp. + */ +static NTSTATUS NTAPI vgdrvNtNt5PlusAddDevice(PDRIVER_OBJECT pDrvObj, PDEVICE_OBJECT pDevObj) +{ + LogFlowFuncEnter(); + + /* + * Create device. + */ + UNICODE_STRING DevName; + RtlInitUnicodeString(&DevName, VBOXGUEST_DEVICE_NAME_NT); + PDEVICE_OBJECT pDeviceObject = NULL; + NTSTATUS rcNt = IoCreateDevice(pDrvObj, sizeof(VBOXGUESTDEVEXTWIN), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject); + if (NT_SUCCESS(rcNt)) + { + /* + * Create symbolic link (DOS devices). + */ + UNICODE_STRING DosName; + RtlInitUnicodeString(&DosName, VBOXGUEST_DEVICE_NAME_DOS); + rcNt = IoCreateSymbolicLink(&DosName, &DevName); + if (NT_SUCCESS(rcNt)) + { + /* + * Setup the device extension. + */ + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDeviceObject->DeviceExtension; + rcNt = vgdrvNtInitDevExtFundament(pDevExt, pDeviceObject); + if (NT_SUCCESS(rcNt)) + { + pDevExt->pNextLowerDriver = IoAttachDeviceToDeviceStack(pDeviceObject, pDevObj); + if (pDevExt->pNextLowerDriver != NULL) + { + /* Ensure we are not called at elevated IRQL, even if our code isn't pagable any more. */ + pDeviceObject->Flags |= DO_POWER_PAGABLE; + + /* Driver is ready now. */ + pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; + LogFlowFunc(("Returning with rcNt=%#x (success)\n", rcNt)); + return rcNt; + } + LogFunc(("IoAttachDeviceToDeviceStack did not give a nextLowerDriver!\n")); + rcNt = STATUS_DEVICE_NOT_CONNECTED; + vgdrvNtDeleteDevExtFundament(pDevExt); + } + + IoDeleteSymbolicLink(&DosName); + } + else + LogFunc(("IoCreateSymbolicLink failed with rcNt=%#x!\n", rcNt)); + IoDeleteDevice(pDeviceObject); + } + else + LogFunc(("IoCreateDevice failed with rcNt=%#x!\n", rcNt)); + + LogFunc(("Returning with rcNt=%#x\n", rcNt)); + return rcNt; +} + + +/** + * Irp completion routine for PnP Irps we send. + * + * @returns NT status code. + * @param pDevObj Device object. + * @param pIrp Request packet. + * @param pEvent Semaphore. + */ +static NTSTATUS vgdrvNt5PlusPnpIrpComplete(PDEVICE_OBJECT pDevObj, PIRP pIrp, PKEVENT pEvent) +{ + RT_NOREF2(pDevObj, pIrp); + KeSetEvent(pEvent, 0, FALSE); + return STATUS_MORE_PROCESSING_REQUIRED; +} + + +/** + * Helper to send a PnP IRP and wait until it's done. + * + * @returns NT status code. + * @param pDevObj Device object. + * @param pIrp Request packet. + * @param fStrict When set, returns an error if the IRP gives an error. + */ +static NTSTATUS vgdrvNt5PlusPnPSendIrpSynchronously(PDEVICE_OBJECT pDevObj, PIRP pIrp, BOOLEAN fStrict) +{ + KEVENT Event; + + KeInitializeEvent(&Event, SynchronizationEvent, FALSE); + + IoCopyCurrentIrpStackLocationToNext(pIrp); + IoSetCompletionRoutine(pIrp, (PIO_COMPLETION_ROUTINE)vgdrvNt5PlusPnpIrpComplete, &Event, TRUE, TRUE, TRUE); + + NTSTATUS rcNt = IoCallDriver(pDevObj, pIrp); + if (rcNt == STATUS_PENDING) + { + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + rcNt = pIrp->IoStatus.Status; + } + + if ( !fStrict + && (rcNt == STATUS_NOT_SUPPORTED || rcNt == STATUS_INVALID_DEVICE_REQUEST)) + { + rcNt = STATUS_SUCCESS; + } + + Log(("vgdrvNt5PlusPnPSendIrpSynchronously: Returning %#x\n", rcNt)); + return rcNt; +} + + +/** + * Deletes the device hardware resources. + * + * Used during removal, stopping and legacy module unloading. + * + * @param pDevExt The device extension. + */ +static void vgdrvNtDeleteDeviceResources(PVBOXGUESTDEVEXTWIN pDevExt) +{ + if (pDevExt->pInterruptObject) + { + IoDisconnectInterrupt(pDevExt->pInterruptObject); + pDevExt->pInterruptObject = NULL; + } + pDevExt->pPowerStateRequest = NULL; /* Will be deleted by the following call. */ + if (pDevExt->Core.uInitState == VBOXGUESTDEVEXT_INIT_STATE_RESOURCES) + VGDrvCommonDeleteDevExtResources(&pDevExt->Core); + vgdrvNtUnmapVMMDevMemory(pDevExt); +} + + +/** + * Deletes the device extension fundament and unlinks the device + * + * Used during removal and legacy module unloading. Must have called + * vgdrvNtDeleteDeviceResources. + * + * @param pDevObj Device object. + * @param pDevExt The device extension. + */ +static void vgdrvNtDeleteDeviceFundamentAndUnlink(PDEVICE_OBJECT pDevObj, PVBOXGUESTDEVEXTWIN pDevExt) +{ + /* + * Delete the remainder of the device extension. + */ + vgdrvNtDeleteDevExtFundament(pDevExt); + + /* + * Delete the DOS symlink to the device and finally the device itself. + */ + UNICODE_STRING DosName; + RtlInitUnicodeString(&DosName, VBOXGUEST_DEVICE_NAME_DOS); + IoDeleteSymbolicLink(&DosName); + + Log(("vgdrvNtDeleteDeviceFundamentAndUnlink: Deleting device ...\n")); + IoDeleteDevice(pDevObj); +} + + +/** + * Checks if the device is idle. + * @returns STATUS_SUCCESS if idle, STATUS_UNSUCCESSFUL if busy. + * @param pDevExt The device extension. + * @param pszQueryNm The query name. + */ +static NTSTATUS vgdrvNtCheckIdle(PVBOXGUESTDEVEXTWIN pDevExt, const char *pszQueryNm) +{ + uint32_t cSessions = pDevExt->Core.cSessions; + if (cSessions == 0) + return STATUS_SUCCESS; + LogRel(("vgdrvNtCheckIdle/%s: cSessions=%d\n", pszQueryNm, cSessions)); + return STATUS_UNSUCCESSFUL; +} + + +/** + * PnP Request handler. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +static NTSTATUS NTAPI vgdrvNtNt5PlusPnP(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension; + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + +#ifdef LOG_ENABLED + static char const * const s_apszFnctName[] = + { + "IRP_MN_START_DEVICE", + "IRP_MN_QUERY_REMOVE_DEVICE", + "IRP_MN_REMOVE_DEVICE", + "IRP_MN_CANCEL_REMOVE_DEVICE", + "IRP_MN_STOP_DEVICE", + "IRP_MN_QUERY_STOP_DEVICE", + "IRP_MN_CANCEL_STOP_DEVICE", + "IRP_MN_QUERY_DEVICE_RELATIONS", + "IRP_MN_QUERY_INTERFACE", + "IRP_MN_QUERY_CAPABILITIES", + "IRP_MN_QUERY_RESOURCES", + "IRP_MN_QUERY_RESOURCE_REQUIREMENTS", + "IRP_MN_QUERY_DEVICE_TEXT", + "IRP_MN_FILTER_RESOURCE_REQUIREMENTS", + "IRP_MN_0xE", + "IRP_MN_READ_CONFIG", + "IRP_MN_WRITE_CONFIG", + "IRP_MN_EJECT", + "IRP_MN_SET_LOCK", + "IRP_MN_QUERY_ID", + "IRP_MN_QUERY_PNP_DEVICE_STATE", + "IRP_MN_QUERY_BUS_INFORMATION", + "IRP_MN_DEVICE_USAGE_NOTIFICATION", + "IRP_MN_SURPRISE_REMOVAL", + }; + Log(("vgdrvNtNt5PlusPnP: MinorFunction: %s\n", + pStack->MinorFunction < RT_ELEMENTS(s_apszFnctName) ? s_apszFnctName[pStack->MinorFunction] : "Unknown")); +#endif + + NTSTATUS rc = STATUS_SUCCESS; + uint8_t bMinorFunction = pStack->MinorFunction; + switch (bMinorFunction) + { + case IRP_MN_START_DEVICE: + { + Log(("vgdrvNtNt5PlusPnP: START_DEVICE\n")); + + /* This must be handled first by the lower driver. */ + rc = vgdrvNt5PlusPnPSendIrpSynchronously(pDevExt->pNextLowerDriver, pIrp, TRUE); + if ( NT_SUCCESS(rc) + && NT_SUCCESS(pIrp->IoStatus.Status)) + { + Log(("vgdrvNtNt5PlusPnP: START_DEVICE: pStack->Parameters.StartDevice.AllocatedResources = %p\n", + pStack->Parameters.StartDevice.AllocatedResources)); + if (pStack->Parameters.StartDevice.AllocatedResources) + { + rc = vgdrvNtSetupDevice(pDevExt, pDevObj, pIrp, NULL, NULL); + if (NT_SUCCESS(rc)) + Log(("vgdrvNtNt5PlusPnP: START_DEVICE: success\n")); + else + Log(("vgdrvNtNt5PlusPnP: START_DEVICE: vgdrvNtSetupDevice failed: %#x\n", rc)); + } + else + { + Log(("vgdrvNtNt5PlusPnP: START_DEVICE: No resources, pDevExt = %p, nextLowerDriver = %p!\n", + pDevExt, pDevExt ? pDevExt->pNextLowerDriver : NULL)); + rc = STATUS_UNSUCCESSFUL; + } + } + else + Log(("vgdrvNtNt5PlusPnP: START_DEVICE: vgdrvNt5PlusPnPSendIrpSynchronously failed: %#x + %#x\n", + rc, pIrp->IoStatus.Status)); + + pIrp->IoStatus.Status = rc; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rc; + } + + + /* + * Sent before removing the device and/or driver. + */ + case IRP_MN_QUERY_REMOVE_DEVICE: + { + Log(("vgdrvNtNt5PlusPnP: QUERY_REMOVE_DEVICE\n")); + + RTCritSectRwEnterExcl(&pDevExt->SessionCreateCritSect); +#ifdef VBOX_REBOOT_ON_UNINSTALL + Log(("vgdrvNtNt5PlusPnP: QUERY_REMOVE_DEVICE: Device cannot be removed without a reboot.\n")); + rc = STATUS_UNSUCCESSFUL; +#endif + if (NT_SUCCESS(rc)) + rc = vgdrvNtCheckIdle(pDevExt, "QUERY_REMOVE_DEVICE"); + if (NT_SUCCESS(rc)) + { + pDevExt->enmDevState = VGDRVNTDEVSTATE_PENDINGREMOVE; + RTCritSectRwLeaveExcl(&pDevExt->SessionCreateCritSect); + + /* This IRP passed down to lower driver. */ + pIrp->IoStatus.Status = STATUS_SUCCESS; + + IoSkipCurrentIrpStackLocation(pIrp); + rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp); + Log(("vgdrvNtNt5PlusPnP: QUERY_REMOVE_DEVICE: Next lower driver replied rc = 0x%x\n", rc)); + + /* We must not do anything the IRP after doing IoSkip & CallDriver + since the driver below us will complete (or already have completed) the IRP. + I.e. just return the status we got from IoCallDriver */ + } + else + { + RTCritSectRwLeaveExcl(&pDevExt->SessionCreateCritSect); + pIrp->IoStatus.Status = rc; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + } + + Log(("vgdrvNtNt5PlusPnP: QUERY_REMOVE_DEVICE: Returning with rc = 0x%x\n", rc)); + return rc; + } + + /* + * Cancels a pending remove, IRP_MN_QUERY_REMOVE_DEVICE. + * We only have to revert the state. + */ + case IRP_MN_CANCEL_REMOVE_DEVICE: + { + Log(("vgdrvNtNt5PlusPnP: CANCEL_REMOVE_DEVICE\n")); + + /* This must be handled first by the lower driver. */ + rc = vgdrvNt5PlusPnPSendIrpSynchronously(pDevExt->pNextLowerDriver, pIrp, TRUE); + if ( NT_SUCCESS(rc) + && pDevExt->enmDevState == VGDRVNTDEVSTATE_PENDINGREMOVE) + { + /* Return to the state prior to receiving the IRP_MN_QUERY_REMOVE_DEVICE request. */ + pDevExt->enmDevState = pDevExt->enmPrevDevState; + } + + /* Complete the IRP. */ + pIrp->IoStatus.Status = rc; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rc; + } + + /* + * We do nothing here actually, esp. since this request is not expected for VBoxGuest. + * The cleanup will be done in IRP_MN_REMOVE_DEVICE, which follows this call. + */ + case IRP_MN_SURPRISE_REMOVAL: + { + Log(("vgdrvNtNt5PlusPnP: IRP_MN_SURPRISE_REMOVAL\n")); + pDevExt->enmDevState = VGDRVNTDEVSTATE_SURPRISEREMOVED; + LogRel(("VBoxGuest: unexpected device removal\n")); + + /* Pass to the lower driver. */ + pIrp->IoStatus.Status = STATUS_SUCCESS; + + IoSkipCurrentIrpStackLocation(pIrp); + rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp); + + /* Do not complete the IRP. */ + return rc; + } + + /* + * Device and/or driver removal. Destroy everything. + */ + case IRP_MN_REMOVE_DEVICE: + { + Log(("vgdrvNtNt5PlusPnP: REMOVE_DEVICE\n")); + pDevExt->enmDevState = VGDRVNTDEVSTATE_REMOVED; + + /* + * Disconnect interrupts and delete all hardware resources. + * Note! This may already have been done if we're STOPPED already, if that's a possibility. + */ + vgdrvNtDeleteDeviceResources(pDevExt); + + /* + * We need to send the remove down the stack before we detach, but we don't need + * to wait for the completion of this operation (nor register a completion routine). + */ + pIrp->IoStatus.Status = STATUS_SUCCESS; + + IoSkipCurrentIrpStackLocation(pIrp); + rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp); + Log(("vgdrvNtNt5PlusPnP: REMOVE_DEVICE: Next lower driver replied rc = 0x%x\n", rc)); + + IoDetachDevice(pDevExt->pNextLowerDriver); + Log(("vgdrvNtNt5PlusPnP: REMOVE_DEVICE: Removing device ...\n")); + + /* + * Delete the remainder of the device extension data, unlink it from the namespace and delete it. + */ + vgdrvNtDeleteDeviceFundamentAndUnlink(pDevObj, pDevExt); + + pDevObj = NULL; /* invalid */ + pDevExt = NULL; /* invalid */ + + Log(("vgdrvNtNt5PlusPnP: REMOVE_DEVICE: Device removed!\n")); + return rc; /* Propagating rc from IoCallDriver. */ + } + + + /* + * Sent before stopping the device/driver to check whether it is okay to do so. + */ + case IRP_MN_QUERY_STOP_DEVICE: + { + Log(("vgdrvNtNt5PlusPnP: QUERY_STOP_DEVICE\n")); + RTCritSectRwEnterExcl(&pDevExt->SessionCreateCritSect); + rc = vgdrvNtCheckIdle(pDevExt, "QUERY_STOP_DEVICE"); + if (NT_SUCCESS(rc)) + { + pDevExt->enmPrevDevState = pDevExt->enmDevState; + pDevExt->enmDevState = VGDRVNTDEVSTATE_PENDINGSTOP; + RTCritSectRwLeaveExcl(&pDevExt->SessionCreateCritSect); + + /* This IRP passed down to lower driver. */ + pIrp->IoStatus.Status = STATUS_SUCCESS; + + IoSkipCurrentIrpStackLocation(pIrp); + + rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp); + Log(("vgdrvNtNt5PlusPnP: QUERY_STOP_DEVICE: Next lower driver replied rc = 0x%x\n", rc)); + + /* we must not do anything with the IRP after doing IoSkip & CallDriver since the + driver below us will complete (or already have completed) the IRP. I.e. just + return the status we got from IoCallDriver. */ + } + else + { + RTCritSectRwLeaveExcl(&pDevExt->SessionCreateCritSect); + pIrp->IoStatus.Status = rc; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + } + + Log(("vgdrvNtNt5PlusPnP: QUERY_STOP_DEVICE: Returning with rc = 0x%x\n", rc)); + return rc; + } + + /* + * Cancels a pending remove, IRP_MN_QUERY_STOP_DEVICE. + * We only have to revert the state. + */ + case IRP_MN_CANCEL_STOP_DEVICE: + { + Log(("vgdrvNtNt5PlusPnP: CANCEL_STOP_DEVICE\n")); + + /* This must be handled first by the lower driver. */ + rc = vgdrvNt5PlusPnPSendIrpSynchronously(pDevExt->pNextLowerDriver, pIrp, TRUE); + if ( NT_SUCCESS(rc) + && pDevExt->enmDevState == VGDRVNTDEVSTATE_PENDINGSTOP) + { + /* Return to the state prior to receiving the IRP_MN_QUERY_STOP_DEVICE request. */ + pDevExt->enmDevState = pDevExt->enmPrevDevState; + } + + /* Complete the IRP. */ + pIrp->IoStatus.Status = rc; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rc; + } + + /* + * Stop the device. + */ + case IRP_MN_STOP_DEVICE: + { + Log(("vgdrvNtNt5PlusPnP: STOP_DEVICE\n")); + pDevExt->enmDevState = VGDRVNTDEVSTATE_STOPPED; + + /* + * Release the hardware resources. + */ + vgdrvNtDeleteDeviceResources(pDevExt); + + /* + * Pass the request to the lower driver. + */ + pIrp->IoStatus.Status = STATUS_SUCCESS; + IoSkipCurrentIrpStackLocation(pIrp); + rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp); + Log(("vgdrvNtNt5PlusPnP: STOP_DEVICE: Next lower driver replied rc = 0x%x\n", rc)); + return rc; + } + + default: + { + IoSkipCurrentIrpStackLocation(pIrp); + rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp); + Log(("vgdrvNtNt5PlusPnP: Unknown request %#x: Lower driver replied: %x\n", bMinorFunction, rc)); + return rc; + } + } +} + + +/** + * Handle the power completion event. + * + * @returns NT status code. + * @param pDevObj Targetted device object. + * @param pIrp IO request packet. + * @param pContext Context value passed to IoSetCompletionRoutine in VBoxGuestPower. + */ +static NTSTATUS vgdrvNtNt5PlusPowerComplete(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp, IN PVOID pContext) +{ +#ifdef VBOX_STRICT + RT_NOREF1(pDevObj); + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pContext; + PIO_STACK_LOCATION pIrpSp = IoGetCurrentIrpStackLocation(pIrp); + + Assert(pDevExt); + + if (pIrpSp) + { + Assert(pIrpSp->MajorFunction == IRP_MJ_POWER); + if (NT_SUCCESS(pIrp->IoStatus.Status)) + { + switch (pIrpSp->MinorFunction) + { + case IRP_MN_SET_POWER: + switch (pIrpSp->Parameters.Power.Type) + { + case DevicePowerState: + switch (pIrpSp->Parameters.Power.State.DeviceState) + { + case PowerDeviceD0: + break; + default: /* Shut up MSC */ + break; + } + break; + default: /* Shut up MSC */ + break; + } + break; + } + } + } +#else + RT_NOREF3(pDevObj, pIrp, pContext); +#endif + + return STATUS_SUCCESS; +} + + +/** + * Handle the Power requests. + * + * @returns NT status code + * @param pDevObj device object + * @param pIrp IRP + */ +static NTSTATUS NTAPI vgdrvNtNt5PlusPower(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension; + POWER_STATE_TYPE enmPowerType = pStack->Parameters.Power.Type; + POWER_STATE PowerState = pStack->Parameters.Power.State; + POWER_ACTION enmPowerAction = pStack->Parameters.Power.ShutdownType; + + Log(("vgdrvNtNt5PlusPower:\n")); + + switch (pStack->MinorFunction) + { + case IRP_MN_SET_POWER: + { + Log(("vgdrvNtNt5PlusPower: IRP_MN_SET_POWER, type= %d\n", enmPowerType)); + switch (enmPowerType) + { + case SystemPowerState: + { + Log(("vgdrvNtNt5PlusPower: SystemPowerState, action = %d, state = %d/%d\n", + enmPowerAction, PowerState.SystemState, PowerState.DeviceState)); + + switch (enmPowerAction) + { + case PowerActionSleep: + + /* System now is in a working state. */ + if (PowerState.SystemState == PowerSystemWorking) + { + if ( pDevExt + && pDevExt->enmLastSystemPowerAction == PowerActionHibernate) + { + Log(("vgdrvNtNt5PlusPower: Returning from hibernation!\n")); + int rc = VGDrvCommonReinitDevExtAfterHibernation(&pDevExt->Core, + vgdrvNtVersionToOSType(g_enmVGDrvNtVer)); + if (RT_FAILURE(rc)) + Log(("vgdrvNtNt5PlusPower: Cannot re-init VMMDev chain, rc = %d!\n", rc)); + } + } + break; + + case PowerActionShutdownReset: + { + Log(("vgdrvNtNt5PlusPower: Power action reset!\n")); + + /* Tell the VMM that we no longer support mouse pointer integration. */ + VMMDevReqMouseStatus *pReq = NULL; + int vrc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof (VMMDevReqMouseStatus), + VMMDevReq_SetMouseStatus); + if (RT_SUCCESS(vrc)) + { + pReq->mouseFeatures = 0; + pReq->pointerXPos = 0; + pReq->pointerYPos = 0; + + vrc = VbglR0GRPerform(&pReq->header); + if (RT_FAILURE(vrc)) + { + Log(("vgdrvNtNt5PlusPower: error communicating new power status to VMMDev. vrc = %Rrc\n", vrc)); + } + + VbglR0GRFree(&pReq->header); + } + + /* Don't do any cleanup here; there might be still coming in some IOCtls after we got this + * power action and would assert/crash when we already cleaned up all the stuff! */ + break; + } + + case PowerActionShutdown: + case PowerActionShutdownOff: + { + Log(("vgdrvNtNt5PlusPower: Power action shutdown!\n")); + if (PowerState.SystemState >= PowerSystemShutdown) + { + Log(("vgdrvNtNt5PlusPower: Telling the VMMDev to close the VM ...\n")); + + VMMDevPowerStateRequest *pReq = pDevExt->pPowerStateRequest; + int vrc = VERR_NOT_IMPLEMENTED; + if (pReq) + { + pReq->header.requestType = VMMDevReq_SetPowerStatus; + pReq->powerState = VMMDevPowerState_PowerOff; + + vrc = VbglR0GRPerform(&pReq->header); + } + if (RT_FAILURE(vrc)) + Log(("vgdrvNtNt5PlusPower: Error communicating new power status to VMMDev. vrc = %Rrc\n", vrc)); + + /* No need to do cleanup here; at this point we should've been + * turned off by VMMDev already! */ + } + break; + } + + case PowerActionHibernate: + Log(("vgdrvNtNt5PlusPower: Power action hibernate!\n")); + break; + + case PowerActionWarmEject: + Log(("vgdrvNtNt5PlusPower: PowerActionWarmEject!\n")); + break; + + default: + Log(("vgdrvNtNt5PlusPower: %d\n", enmPowerAction)); + break; + } + + /* + * Save the current system power action for later use. + * This becomes handy when we return from hibernation for example. + */ + if (pDevExt) + pDevExt->enmLastSystemPowerAction = enmPowerAction; + + break; + } + default: + break; + } + break; + } + default: + break; + } + + /* + * Whether we are completing or relaying this power IRP, + * we must call PoStartNextPowerIrp. + */ + g_pfnPoStartNextPowerIrp(pIrp); + + /* + * Send the IRP down the driver stack, using PoCallDriver + * (not IoCallDriver, as for non-power irps). + */ + IoCopyCurrentIrpStackLocationToNext(pIrp); + IoSetCompletionRoutine(pIrp, + vgdrvNtNt5PlusPowerComplete, + (PVOID)pDevExt, + TRUE, + TRUE, + TRUE); + return g_pfnPoCallDriver(pDevExt->pNextLowerDriver, pIrp); +} + + +/** + * IRP_MJ_SYSTEM_CONTROL handler. + * + * @returns NT status code + * @param pDevObj Device object. + * @param pIrp IRP. + */ +static NTSTATUS NTAPI vgdrvNtNt5PlusSystemControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension; + + LogFlowFuncEnter(); + + /* Always pass it on to the next driver. */ + IoSkipCurrentIrpStackLocation(pIrp); + + return IoCallDriver(pDevExt->pNextLowerDriver, pIrp); +} + + +/** + * Unload the driver. + * + * @param pDrvObj Driver object. + */ +static void NTAPI vgdrvNtUnload(PDRIVER_OBJECT pDrvObj) +{ + LogFlowFuncEnter(); + +#ifdef TARGET_NT4 + /* + * We need to destroy the device object here on NT4 and earlier. + */ + PDEVICE_OBJECT pDevObj = pDrvObj->DeviceObject; + if (pDevObj) + { + if (g_enmVGDrvNtVer <= VGDRVNTVER_WINNT4) + { + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension; + AssertPtr(pDevExt); + AssertMsg(pDevExt->Core.uInitState == VBOXGUESTDEVEXT_INIT_STATE_RESOURCES, + ("uInitState=%#x\n", pDevExt->Core.uInitState)); + + vgdrvNtDeleteDeviceResources(pDevExt); + vgdrvNtDeleteDeviceFundamentAndUnlink(pDevObj, pDevExt); + } + } +#else /* !TARGET_NT4 */ + /* + * On a PnP driver this routine will be called after IRP_MN_REMOVE_DEVICE + * where we already did the cleanup, so don't do anything here (yet). + */ + RT_NOREF1(pDrvObj); +#endif /* !TARGET_NT4 */ + + VGDrvCommonDestroyLoggers(); + RTR0Term(); + + /* + * Finally deregister the bugcheck callback. Do it late to catch trouble in RTR0Term. + */ + if (g_fBugCheckCallbackRegistered) + { + g_pfnKeDeregisterBugCheckCallback(&g_BugCheckCallbackRec); + g_fBugCheckCallbackRegistered = false; + } +} + + +/** + * For simplifying request completion into a simple return statement, extended + * version. + * + * @returns rcNt + * @param rcNt The status code. + * @param uInfo Extra info value. + * @param pIrp The IRP. + */ +DECLINLINE(NTSTATUS) vgdrvNtCompleteRequestEx(NTSTATUS rcNt, ULONG_PTR uInfo, PIRP pIrp) +{ + pIrp->IoStatus.Status = rcNt; + pIrp->IoStatus.Information = uInfo; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * For simplifying request completion into a simple return statement. + * + * @returns rcNt + * @param rcNt The status code. + * @param pIrp The IRP. + */ +DECLINLINE(NTSTATUS) vgdrvNtCompleteRequest(NTSTATUS rcNt, PIRP pIrp) +{ + return vgdrvNtCompleteRequestEx(rcNt, 0 /*uInfo*/, pIrp); +} + + +/** + * Checks if NT authority rev 1 SID (SECURITY_NT_AUTHORITY). + * + * @returns true / false. + * @param pSid The SID to check. + */ +DECLINLINE(bool) vgdrvNtIsSidNtAuth(struct _SID const *pSid) +{ + return pSid != NULL + && pSid->Revision == 1 + && pSid->IdentifierAuthority.Value[5] == 5 + && pSid->IdentifierAuthority.Value[4] == 0 + && pSid->IdentifierAuthority.Value[3] == 0 + && pSid->IdentifierAuthority.Value[2] == 0 + && pSid->IdentifierAuthority.Value[1] == 0 + && pSid->IdentifierAuthority.Value[0] == 0; +} + + +/** + * Matches SID with local system user (S-1-5-18 / SECURITY_LOCAL_SYSTEM_RID). + */ +DECLINLINE(bool) vgdrvNtIsSidLocalSystemUser(SID const *pSid) +{ + return vgdrvNtIsSidNtAuth(pSid) + && pSid->SubAuthorityCount == 1 + && pSid->SubAuthority[0] == SECURITY_LOCAL_SYSTEM_RID; +} + + +/** + * Matches SID with NT system admin user (S-1-5-*-500 / DOMAIN_USER_RID_ADMIN). + */ +DECLINLINE(bool) vgdrvNtIsSidAdminUser(SID const *pSid) +{ + /** @todo restrict to SECURITY_NT_NON_UNIQUE? */ + return vgdrvNtIsSidNtAuth(pSid) + && pSid->SubAuthorityCount >= 2 + && pSid->SubAuthorityCount <= SID_MAX_SUB_AUTHORITIES + && pSid->SubAuthority[pSid->SubAuthorityCount - 1] == DOMAIN_USER_RID_ADMIN; +} + + +/** + * Matches SID with NT system guest user (S-1-5-*-501 / DOMAIN_USER_RID_GUEST). + */ +DECLINLINE(bool) vgdrvNtIsSidGuestUser(SID const *pSid) +{ + /** @todo restrict to SECURITY_NT_NON_UNIQUE? */ + return vgdrvNtIsSidNtAuth(pSid) + && pSid->SubAuthorityCount >= 2 + && pSid->SubAuthorityCount <= SID_MAX_SUB_AUTHORITIES + && pSid->SubAuthority[pSid->SubAuthorityCount - 1] == DOMAIN_USER_RID_GUEST; +} + + +/** + * Matches SID with NT system admins group (S-1-5-32-544, S-1-5-*-512). + */ +DECLINLINE(bool) vgdrvNtIsSidAdminsGroup(SID const *pSid) +{ + return vgdrvNtIsSidNtAuth(pSid) + && ( ( pSid->SubAuthorityCount == 2 + && pSid->SubAuthority[0] == SECURITY_BUILTIN_DOMAIN_RID + && pSid->SubAuthority[1] == DOMAIN_ALIAS_RID_ADMINS) +#if 0 + /** @todo restrict to SECURITY_NT_NON_UNIQUE? */ + || ( pSid->SubAuthorityCount >= 2 + && pSid->SubAuthorityCount <= SID_MAX_SUB_AUTHORITIES + && pSid->SubAuthority[pSid->SubAuthorityCount - 1] == DOMAIN_GROUP_RID_ADMINS) +#endif + ); +} + + +/** + * Matches SID with NT system users group (S-1-5-32-545, S-1-5-32-547, S-1-5-*-512). + */ +DECLINLINE(bool) vgdrvNtIsSidUsersGroup(SID const *pSid) +{ + return vgdrvNtIsSidNtAuth(pSid) + && ( ( pSid->SubAuthorityCount == 2 + && pSid->SubAuthority[0] == SECURITY_BUILTIN_DOMAIN_RID + && ( pSid->SubAuthority[1] == DOMAIN_ALIAS_RID_USERS + || pSid->SubAuthority[1] == DOMAIN_ALIAS_RID_POWER_USERS) ) +#if 0 + /** @todo restrict to SECURITY_NT_NON_UNIQUE? */ + || ( pSid->SubAuthorityCount >= 2 + && pSid->SubAuthorityCount <= SID_MAX_SUB_AUTHORITIES + && pSid->SubAuthority[pSid->SubAuthorityCount - 1] == DOMAIN_GROUP_RID_USERS) +#endif + ); +} + + +/** + * Matches SID with NT system guests group (S-1-5-32-546, S-1-5-*-512). + */ +DECLINLINE(bool) vgdrvNtIsSidGuestsGroup(SID const *pSid) +{ + return vgdrvNtIsSidNtAuth(pSid) + && ( ( pSid->SubAuthorityCount == 2 + && pSid->SubAuthority[0] == SECURITY_BUILTIN_DOMAIN_RID + && pSid->SubAuthority[1] == DOMAIN_ALIAS_RID_GUESTS) +#if 0 + /** @todo restrict to SECURITY_NT_NON_UNIQUE? */ + || ( pSid->SubAuthorityCount >= 2 + && pSid->SubAuthorityCount <= SID_MAX_SUB_AUTHORITIES + && pSid->SubAuthority[pSid->SubAuthorityCount - 1] == DOMAIN_GROUP_RID_GUESTS) +#endif + ); +} + + +/** + * Checks if local authority rev 1 SID (SECURITY_LOCAL_SID_AUTHORITY). + * + * @returns true / false. + * @param pSid The SID to check. + */ +DECLINLINE(bool) vgdrvNtIsSidLocalAuth(struct _SID const *pSid) +{ + return pSid != NULL + && pSid->Revision == 1 + && pSid->IdentifierAuthority.Value[5] == 2 + && pSid->IdentifierAuthority.Value[4] == 0 + && pSid->IdentifierAuthority.Value[3] == 0 + && pSid->IdentifierAuthority.Value[2] == 0 + && pSid->IdentifierAuthority.Value[1] == 0 + && pSid->IdentifierAuthority.Value[0] == 0; +} + + +/** + * Matches SID with console logon group (S-1-2-1 / SECURITY_LOCAL_LOGON_RID). + */ +DECLINLINE(bool) vgdrvNtIsSidConsoleLogonGroup(SID const *pSid) +{ + return vgdrvNtIsSidLocalAuth(pSid) + && pSid->SubAuthorityCount == 1 + && pSid->SubAuthority[0] == SECURITY_LOCAL_LOGON_RID; +} + + +/** + * Checks if mandatory label authority rev 1 SID (SECURITY_MANDATORY_LABEL_AUTHORITY). + * + * @returns true / false. + * @param pSid The SID to check. + */ +DECLINLINE(bool) vgdrvNtIsSidMandatoryLabelAuth(struct _SID const *pSid) +{ + return pSid != NULL + && pSid->Revision == 1 + && pSid->IdentifierAuthority.Value[5] == 16 + && pSid->IdentifierAuthority.Value[4] == 0 + && pSid->IdentifierAuthority.Value[3] == 0 + && pSid->IdentifierAuthority.Value[2] == 0 + && pSid->IdentifierAuthority.Value[1] == 0 + && pSid->IdentifierAuthority.Value[0] == 0; +} + + +#ifdef LOG_ENABLED +/** Format an SID for logging. */ +static const char *vgdrvNtFormatSid(char *pszBuf, size_t cbBuf, struct _SID const *pSid) +{ + uint64_t uAuth = RT_MAKE_U64_FROM_U8(pSid->IdentifierAuthority.Value[5], pSid->IdentifierAuthority.Value[4], + pSid->IdentifierAuthority.Value[3], pSid->IdentifierAuthority.Value[2], + pSid->IdentifierAuthority.Value[1], pSid->IdentifierAuthority.Value[0], + 0, 0); + ssize_t offCur = RTStrPrintf2(pszBuf, cbBuf, "S-%u-%RU64", pSid->Revision, uAuth); + ULONG const *puSubAuth = &pSid->SubAuthority[0]; + unsigned cSubAuths = pSid->SubAuthorityCount; + while (cSubAuths > 0 && (size_t)offCur < cbBuf) + { + ssize_t cchThis = RTStrPrintf2(&pszBuf[offCur], cbBuf - (size_t)offCur, "-%u", *puSubAuth); + if (cchThis > 0) + { + offCur += cchThis; + puSubAuth++; + cSubAuths--; + } + else + { + Assert(cbBuf >= 5); + pszBuf[cbBuf - 4] = '.'; + pszBuf[cbBuf - 3] = '.'; + pszBuf[cbBuf - 2] = '.'; + pszBuf[cbBuf - 1] = '\0'; + break; + } + } + return pszBuf; +} +#endif + + +/** + * Calculate requestor flags for the current process. + * + * ASSUMES vgdrvNtCreate is executed in the context of the process and thread + * doing the NtOpenFile call. + * + * @returns VMMDEV_REQUESTOR_XXX + */ +static uint32_t vgdrvNtCalcRequestorFlags(void) +{ + uint32_t fRequestor = VMMDEV_REQUESTOR_USERMODE + | VMMDEV_REQUESTOR_USR_NOT_GIVEN + | VMMDEV_REQUESTOR_CON_DONT_KNOW + | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN + | VMMDEV_REQUESTOR_NO_USER_DEVICE; + HANDLE hToken = NULL; + NTSTATUS rcNt = ZwOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken); + if (NT_SUCCESS(rcNt)) + { + union + { + TOKEN_USER CurUser; + TOKEN_GROUPS CurGroups; + uint8_t abPadding[256]; + } Buf; +#ifdef LOG_ENABLED + char szSid[200]; +#endif + + /* + * Get the user SID and see if it's a standard one. + */ + RT_ZERO(Buf.CurUser); + ULONG cbReturned = 0; + rcNt = ZwQueryInformationToken(hToken, TokenUser, &Buf.CurUser, sizeof(Buf), &cbReturned); + if (NT_SUCCESS(rcNt)) + { + struct _SID const *pSid = (struct _SID const *)Buf.CurUser.User.Sid; + Log5(("vgdrvNtCalcRequestorFlags: TokenUser: %#010x %s\n", + Buf.CurUser.User.Attributes, vgdrvNtFormatSid(szSid, sizeof(szSid), pSid))); + + if (vgdrvNtIsSidLocalSystemUser(pSid)) + fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_USR_MASK) | VMMDEV_REQUESTOR_USR_SYSTEM; + else if (vgdrvNtIsSidAdminUser(pSid)) + fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_USR_MASK) | VMMDEV_REQUESTOR_USR_ROOT; + else if (vgdrvNtIsSidGuestUser(pSid)) + fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_USR_MASK) | VMMDEV_REQUESTOR_USR_GUEST; + } + else + LogRel(("vgdrvNtCalcRequestorFlags: TokenUser query failed: %#x\n", rcNt)); + + /* + * Get the groups. + */ + TOKEN_GROUPS *pCurGroupsFree = NULL; + TOKEN_GROUPS *pCurGroups = &Buf.CurGroups; + uint32_t cbCurGroups = sizeof(Buf); + cbReturned = 0; + RT_ZERO(Buf); + rcNt = ZwQueryInformationToken(hToken, TokenGroups, pCurGroups, cbCurGroups, &cbReturned); + if (rcNt == STATUS_BUFFER_TOO_SMALL) + { + uint32_t cTries = 8; + do + { + RTMemTmpFree(pCurGroupsFree); + if (cbCurGroups < cbReturned) + cbCurGroups = RT_ALIGN_32(cbCurGroups + 32, 64); + else + cbCurGroups += 64; + pCurGroupsFree = pCurGroups = (TOKEN_GROUPS *)RTMemTmpAllocZ(cbCurGroups); + if (pCurGroupsFree) + rcNt = ZwQueryInformationToken(hToken, TokenGroups, pCurGroups, cbCurGroups, &cbReturned); + else + rcNt = STATUS_NO_MEMORY; + } while (rcNt == STATUS_BUFFER_TOO_SMALL && cTries-- > 0); + } + if (NT_SUCCESS(rcNt)) + { + bool fGuestsMember = false; + bool fUsersMember = false; + if (g_enmVGDrvNtVer >= VGDRVNTVER_WIN7) + fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_CON_MASK) | VMMDEV_REQUESTOR_CON_NO; + + for (uint32_t iGrp = 0; iGrp < pCurGroups->GroupCount; iGrp++) + { + uint32_t const fAttribs = pCurGroups->Groups[iGrp].Attributes; + struct _SID const *pSid = (struct _SID const *)pCurGroups->Groups[iGrp].Sid; + Log5(("vgdrvNtCalcRequestorFlags: TokenGroups[%u]: %#10x %s\n", + iGrp, fAttribs, vgdrvNtFormatSid(szSid, sizeof(szSid), pSid))); + + if ( (fAttribs & SE_GROUP_INTEGRITY_ENABLED) + && vgdrvNtIsSidMandatoryLabelAuth(pSid) + && pSid->SubAuthorityCount == 1 + && (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) == VMMDEV_REQUESTOR_TRUST_NOT_GIVEN) + { + fRequestor &= ~VMMDEV_REQUESTOR_TRUST_MASK; + if (pSid->SubAuthority[0] < SECURITY_MANDATORY_LOW_RID) + fRequestor |= VMMDEV_REQUESTOR_TRUST_UNTRUSTED; + else if (pSid->SubAuthority[0] < SECURITY_MANDATORY_MEDIUM_RID) + fRequestor |= VMMDEV_REQUESTOR_TRUST_LOW; + else if (pSid->SubAuthority[0] < SECURITY_MANDATORY_MEDIUM_PLUS_RID) + fRequestor |= VMMDEV_REQUESTOR_TRUST_MEDIUM; + else if (pSid->SubAuthority[0] < SECURITY_MANDATORY_HIGH_RID) + fRequestor |= VMMDEV_REQUESTOR_TRUST_MEDIUM_PLUS; + else if (pSid->SubAuthority[0] < SECURITY_MANDATORY_SYSTEM_RID) + fRequestor |= VMMDEV_REQUESTOR_TRUST_HIGH; + else if (pSid->SubAuthority[0] < SECURITY_MANDATORY_PROTECTED_PROCESS_RID) + fRequestor |= VMMDEV_REQUESTOR_TRUST_SYSTEM; + else + fRequestor |= VMMDEV_REQUESTOR_TRUST_PROTECTED; + Log5(("vgdrvNtCalcRequestorFlags: mandatory label %u: => %#x\n", pSid->SubAuthority[0], fRequestor)); + } + else if ( (fAttribs & (SE_GROUP_ENABLED | SE_GROUP_MANDATORY | SE_GROUP_USE_FOR_DENY_ONLY)) + == (SE_GROUP_ENABLED | SE_GROUP_MANDATORY) + && vgdrvNtIsSidConsoleLogonGroup(pSid)) + { + fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_CON_MASK) | VMMDEV_REQUESTOR_CON_YES; + Log5(("vgdrvNtCalcRequestorFlags: console: => %#x\n", fRequestor)); + } + else if ( (fAttribs & (SE_GROUP_ENABLED | SE_GROUP_MANDATORY | SE_GROUP_USE_FOR_DENY_ONLY)) + == (SE_GROUP_ENABLED | SE_GROUP_MANDATORY) + && vgdrvNtIsSidNtAuth(pSid)) + { + if (vgdrvNtIsSidAdminsGroup(pSid)) + { + fRequestor |= VMMDEV_REQUESTOR_GRP_WHEEL; + Log5(("vgdrvNtCalcRequestorFlags: admins group: => %#x\n", fRequestor)); + } + else if (vgdrvNtIsSidUsersGroup(pSid)) + { + Log5(("vgdrvNtCalcRequestorFlags: users group\n")); + fUsersMember = true; + } + else if (vgdrvNtIsSidGuestsGroup(pSid)) + { + Log5(("vgdrvNtCalcRequestorFlags: guests group\n")); + fGuestsMember = true; + } + } + } + if ((fRequestor & VMMDEV_REQUESTOR_USR_MASK) == VMMDEV_REQUESTOR_USR_NOT_GIVEN) + { + if (fUsersMember) + fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_USR_MASK) | VMMDEV_REQUESTOR_USR_USER; + else if (fGuestsMember) + fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_USR_MASK) | VMMDEV_REQUESTOR_USR_GUEST; + } + } + else + LogRel(("vgdrvNtCalcRequestorFlags: TokenGroups query failed: %#x\n", rcNt)); + + RTMemTmpFree(pCurGroupsFree); + ZwClose(hToken); + + /* + * Determine whether we should set VMMDEV_REQUESTOR_USER_DEVICE or not. + * + * The purpose here is to differentiate VBoxService accesses + * from VBoxTray and VBoxControl, as VBoxService should be allowed to + * do more than the latter two. VBoxService normally runs under the + * system account which is easily detected, but for debugging and + * similar purposes we also allow an elevated admin to run it as well. + */ + if ( (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) == VMMDEV_REQUESTOR_TRUST_UNTRUSTED /* general paranoia wrt system account */ + || (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) == VMMDEV_REQUESTOR_TRUST_LOW /* ditto */ + || (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) == VMMDEV_REQUESTOR_TRUST_MEDIUM /* ditto */ + || !( (fRequestor & VMMDEV_REQUESTOR_USR_MASK) == VMMDEV_REQUESTOR_USR_SYSTEM + || ( ( (fRequestor & VMMDEV_REQUESTOR_GRP_WHEEL) + || (fRequestor & VMMDEV_REQUESTOR_USR_MASK) == VMMDEV_REQUESTOR_USR_ROOT) + && ( (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) >= VMMDEV_REQUESTOR_TRUST_HIGH + || (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) == VMMDEV_REQUESTOR_TRUST_NOT_GIVEN)) )) + fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE; + } + else + { + LogRel(("vgdrvNtCalcRequestorFlags: NtOpenProcessToken query failed: %#x\n", rcNt)); + fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE; + } + + Log5(("vgdrvNtCalcRequestorFlags: returns %#x\n", fRequestor)); + return fRequestor; +} + + +/** + * Create (i.e. Open) file entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +static NTSTATUS NTAPI vgdrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + Log(("vgdrvNtCreate: RequestorMode=%d\n", pIrp->RequestorMode)); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension; + + Assert(pFileObj->FsContext == NULL); + + /* + * We are not remotely similar to a directory... + */ + NTSTATUS rcNt; + if (!(pStack->Parameters.Create.Options & FILE_DIRECTORY_FILE)) + { + /* + * Check the device state. We enter the critsect in shared mode to + * prevent race with PnP system requests checking whether we're idle. + */ + RTCritSectRwEnterShared(&pDevExt->SessionCreateCritSect); + VGDRVNTDEVSTATE const enmDevState = pDevExt->enmDevState; + if (enmDevState == VGDRVNTDEVSTATE_OPERATIONAL) + { + /* + * Create a client session. + */ + int rc; + PVBOXGUESTSESSION pSession; + if (pIrp->RequestorMode == KernelMode) + rc = VGDrvCommonCreateKernelSession(&pDevExt->Core, &pSession); + else + rc = VGDrvCommonCreateUserSession(&pDevExt->Core, vgdrvNtCalcRequestorFlags(), &pSession); + RTCritSectRwLeaveShared(&pDevExt->SessionCreateCritSect); + if (RT_SUCCESS(rc)) + { + pFileObj->FsContext = pSession; + Log(("vgdrvNtCreate: Successfully created %s session %p (fRequestor=%#x)\n", + pIrp->RequestorMode == KernelMode ? "kernel" : "user", pSession, pSession->fRequestor)); + + return vgdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } + + /* Note. the IoStatus is completely ignored on error. */ + Log(("vgdrvNtCreate: Failed to create session: rc=%Rrc\n", rc)); + if (rc == VERR_NO_MEMORY) + rcNt = STATUS_NO_MEMORY; + else + rcNt = STATUS_UNSUCCESSFUL; + } + else + { + RTCritSectRwLeaveShared(&pDevExt->SessionCreateCritSect); + LogFlow(("vgdrvNtCreate: Failed. Device is not in 'working' state: %d\n", enmDevState)); + rcNt = STATUS_DEVICE_NOT_READY; + } + } + else + { + LogFlow(("vgdrvNtCreate: Failed. FILE_DIRECTORY_FILE set\n")); + rcNt = STATUS_NOT_A_DIRECTORY; + } + return vgdrvNtCompleteRequest(rcNt, pIrp); +} + + +/** + * Close file entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +static NTSTATUS NTAPI vgdrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension; + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + + LogFlowFunc(("pDevExt=0x%p, pFileObj=0x%p, FsContext=0x%p\n", pDevExt, pFileObj, pFileObj->FsContext)); + +#ifdef VBOX_WITH_HGCM + /* Close both, R0 and R3 sessions. */ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pFileObj->FsContext; + if (pSession) + VGDrvCommonCloseSession(&pDevExt->Core, pSession); +#endif + + pFileObj->FsContext = NULL; + pIrp->IoStatus.Information = 0; + pIrp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + + return STATUS_SUCCESS; +} + + +/** + * Device I/O Control entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS NTAPI vgdrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension; + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PVBOXGUESTSESSION pSession = pStack->FileObject ? (PVBOXGUESTSESSION)pStack->FileObject->FsContext : NULL; + + if (!RT_VALID_PTR(pSession)) + return vgdrvNtCompleteRequest(STATUS_TRUST_FAILURE, pIrp); + +#if 0 /* No fast I/O controls defined yet. */ + /* + * Deal with the 2-3 high-speed IOCtl that takes their arguments from + * the session and iCmd, and does not return anything. + */ + if (pSession->fUnrestricted) + { + ULONG ulCmd = pStack->Parameters.DeviceIoControl.IoControlCode; + if ( ulCmd == SUP_IOCTL_FAST_DO_RAW_RUN + || ulCmd == SUP_IOCTL_FAST_DO_HM_RUN + || ulCmd == SUP_IOCTL_FAST_DO_NOP) + { + int rc = supdrvIOCtlFast(ulCmd, (unsigned)(uintptr_t)pIrp->UserBuffer /* VMCPU id */, pDevExt, pSession); + + /* Complete the I/O request. */ + supdrvSessionRelease(pSession); + return vgdrvNtCompleteRequest(RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER, pIrp); + } + } +#endif + + return vgdrvNtDeviceControlSlow(&pDevExt->Core, pSession, pIrp, pStack); +} + + +/** + * Device I/O Control entry point. + * + * @param pDevExt The device extension. + * @param pSession The session. + * @param pIrp Request packet. + * @param pStack The request stack pointer. + */ +static NTSTATUS vgdrvNtDeviceControlSlow(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + PIRP pIrp, PIO_STACK_LOCATION pStack) +{ + NTSTATUS rcNt; + uint32_t cbOut = 0; + int rc = 0; + Log2(("vgdrvNtDeviceControlSlow(%p,%p): ioctl=%#x pBuf=%p cbIn=%#x cbOut=%#x pSession=%p\n", + pDevExt, pIrp, pStack->Parameters.DeviceIoControl.IoControlCode, + pIrp->AssociatedIrp.SystemBuffer, pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength, pSession)); + +#if 0 /*def RT_ARCH_AMD64*/ + /* Don't allow 32-bit processes to do any I/O controls. */ + if (!IoIs32bitProcess(pIrp)) +#endif + { + /* Verify that it's a buffered CTL. */ + if ((pStack->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED) + { + /* Verify that the sizes in the request header are correct. */ + PVBGLREQHDR pHdr = (PVBGLREQHDR)pIrp->AssociatedIrp.SystemBuffer; + if ( pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) + && pStack->Parameters.DeviceIoControl.InputBufferLength == pHdr->cbIn + && pStack->Parameters.DeviceIoControl.OutputBufferLength == pHdr->cbOut) + { + /* Zero extra output bytes to make sure we don't leak anything. */ + if (pHdr->cbIn < pHdr->cbOut) + RtlZeroMemory((uint8_t *)pHdr + pHdr->cbIn, pHdr->cbOut - pHdr->cbIn); + + /* + * Do the job. + */ + rc = VGDrvCommonIoCtl(pStack->Parameters.DeviceIoControl.IoControlCode, pDevExt, pSession, pHdr, + RT_MAX(pHdr->cbIn, pHdr->cbOut)); + if (RT_SUCCESS(rc)) + { + rcNt = STATUS_SUCCESS; + cbOut = pHdr->cbOut; + if (cbOut > pStack->Parameters.DeviceIoControl.OutputBufferLength) + { + cbOut = pStack->Parameters.DeviceIoControl.OutputBufferLength; + LogRel(("vgdrvNtDeviceControlSlow: too much output! %#x > %#x; uCmd=%#x!\n", + pHdr->cbOut, cbOut, pStack->Parameters.DeviceIoControl.IoControlCode)); + } + + /* If IDC successful disconnect request, we must set the context pointer to NULL. */ + if ( pStack->Parameters.DeviceIoControl.IoControlCode == VBGL_IOCTL_IDC_DISCONNECT + && RT_SUCCESS(pHdr->rc)) + pStack->FileObject->FsContext = NULL; + } + else if (rc == VERR_NOT_SUPPORTED) + rcNt = STATUS_NOT_SUPPORTED; + else + rcNt = STATUS_INVALID_PARAMETER; + Log2(("vgdrvNtDeviceControlSlow: returns %#x cbOut=%d rc=%#x\n", rcNt, cbOut, rc)); + } + else + { + Log(("vgdrvNtDeviceControlSlow: Mismatching sizes (%#x) - Hdr=%#lx/%#lx Irp=%#lx/%#lx!\n", + pStack->Parameters.DeviceIoControl.IoControlCode, + pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbIn : 0, + pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbOut : 0, + pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + { + Log(("vgdrvNtDeviceControlSlow: not buffered request (%#x) - not supported\n", + pStack->Parameters.DeviceIoControl.IoControlCode)); + rcNt = STATUS_NOT_SUPPORTED; + } + } +#if 0 /*def RT_ARCH_AMD64*/ + else + { + Log(("VBoxDrvNtDeviceControlSlow: WOW64 req - not supported\n")); + rcNt = STATUS_NOT_SUPPORTED; + } +#endif + + return vgdrvNtCompleteRequestEx(rcNt, cbOut, pIrp); +} + + +/** + * Internal Device I/O Control entry point (for IDC). + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +static NTSTATUS NTAPI vgdrvNtInternalIOCtl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + /* Currently no special code here. */ + return vgdrvNtDeviceControl(pDevObj, pIrp); +} + + +/** + * IRP_MJ_SHUTDOWN handler. + * + * @returns NT status code + * @param pDevObj Device object. + * @param pIrp IRP. + */ +static NTSTATUS NTAPI vgdrvNtShutdown(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension; + LogFlowFuncEnter(); + + VMMDevPowerStateRequest *pReq = pDevExt->pPowerStateRequest; + if (pReq) + { + pReq->header.requestType = VMMDevReq_SetPowerStatus; + pReq->powerState = VMMDevPowerState_PowerOff; + + int rc = VbglR0GRPerform(&pReq->header); + if (RT_FAILURE(rc)) + LogFunc(("Error performing request to VMMDev, rc=%Rrc\n", rc)); + } + + /* just in case, since we shouldn't normally get here. */ + pIrp->IoStatus.Information = 0; + pIrp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return STATUS_SUCCESS; +} + + +/** + * Stub function for functions we don't implemented. + * + * @returns STATUS_NOT_SUPPORTED + * @param pDevObj Device object. + * @param pIrp IRP. + */ +static NTSTATUS vgdrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + RT_NOREF1(pDevObj); + LogFlowFuncEnter(); + + pIrp->IoStatus.Information = 0; + pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + + return STATUS_NOT_SUPPORTED; +} + + +/** + * Bug check callback (KBUGCHECK_CALLBACK_ROUTINE). + * + * This adds a log entry on the host, in case Hyper-V isn't active or the guest + * is too old for reporting it itself via the crash MSRs. + * + * @param pvBuffer Not used. + * @param cbBuffer Not used. + */ +static VOID NTAPI vgdrvNtBugCheckCallback(PVOID pvBuffer, ULONG cbBuffer) +{ + if (g_pauKiBugCheckData) + { + RTLogBackdoorPrintf("VBoxGuest: BugCheck! P0=%#zx P1=%#zx P2=%#zx P3=%#zx P4=%#zx\n", g_pauKiBugCheckData[0], + g_pauKiBugCheckData[1], g_pauKiBugCheckData[2], g_pauKiBugCheckData[3], g_pauKiBugCheckData[4]); + + VMMDevReqNtBugCheck *pReq = NULL; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_NtBugCheck); + if (RT_SUCCESS(rc)) + { + pReq->uBugCheck = g_pauKiBugCheckData[0]; + pReq->auParameters[0] = g_pauKiBugCheckData[1]; + pReq->auParameters[1] = g_pauKiBugCheckData[2]; + pReq->auParameters[2] = g_pauKiBugCheckData[3]; + pReq->auParameters[3] = g_pauKiBugCheckData[4]; + VbglR0GRPerform(&pReq->header); + VbglR0GRFree(&pReq->header); + } + } + else + { + RTLogBackdoorPrintf("VBoxGuest: BugCheck!\n"); + + VMMDevRequestHeader *pReqHdr = NULL; + int rc = VbglR0GRAlloc(&pReqHdr, sizeof(*pReqHdr), VMMDevReq_NtBugCheck); + if (RT_SUCCESS(rc)) + { + VbglR0GRPerform(pReqHdr); + VbglR0GRFree(pReqHdr); + } + } + + RT_NOREF(pvBuffer, cbBuffer); +} + + +/** + * Sets the mouse notification callback. + * + * @returns VBox status code. + * @param pDevExt Pointer to the device extension. + * @param pNotify Pointer to the mouse notify struct. + */ +int VGDrvNativeSetMouseNotifyCallback(PVBOXGUESTDEVEXT pDevExt, PVBGLIOCSETMOUSENOTIFYCALLBACK pNotify) +{ + PVBOXGUESTDEVEXTWIN pDevExtWin = (PVBOXGUESTDEVEXTWIN)pDevExt; + /* we need a lock here to avoid concurrency with the set event functionality */ + KIRQL OldIrql; + KeAcquireSpinLock(&pDevExtWin->MouseEventAccessSpinLock, &OldIrql); + pDevExtWin->Core.pfnMouseNotifyCallback = pNotify->u.In.pfnNotify; + pDevExtWin->Core.pvMouseNotifyCallbackArg = pNotify->u.In.pvUser; + KeReleaseSpinLock(&pDevExtWin->MouseEventAccessSpinLock, OldIrql); + return VINF_SUCCESS; +} + + +/** + * DPC handler. + * + * @param pDPC DPC descriptor. + * @param pDevObj Device object. + * @param pIrp Interrupt request packet. + * @param pContext Context specific pointer. + */ +static void NTAPI vgdrvNtDpcHandler(PKDPC pDPC, PDEVICE_OBJECT pDevObj, PIRP pIrp, PVOID pContext) +{ + RT_NOREF3(pDPC, pIrp, pContext); + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension; + Log3Func(("pDevExt=0x%p\n", pDevExt)); + + /* Test & reset the counter. */ + if (ASMAtomicXchgU32(&pDevExt->Core.u32MousePosChangedSeq, 0)) + { + /* we need a lock here to avoid concurrency with the set event ioctl handler thread, + * i.e. to prevent the event from destroyed while we're using it */ + Assert(KeGetCurrentIrql() == DISPATCH_LEVEL); + KeAcquireSpinLockAtDpcLevel(&pDevExt->MouseEventAccessSpinLock); + + if (pDevExt->Core.pfnMouseNotifyCallback) + pDevExt->Core.pfnMouseNotifyCallback(pDevExt->Core.pvMouseNotifyCallbackArg); + + KeReleaseSpinLockFromDpcLevel(&pDevExt->MouseEventAccessSpinLock); + } + + /* Process the wake-up list we were asked by the scheduling a DPC + * in vgdrvNtIsrHandler(). */ + VGDrvCommonWaitDoWakeUps(&pDevExt->Core); +} + + +/** + * ISR handler. + * + * @return BOOLEAN Indicates whether the IRQ came from us (TRUE) or not (FALSE). + * @param pInterrupt Interrupt that was triggered. + * @param pServiceContext Context specific pointer. + */ +static BOOLEAN NTAPI vgdrvNtIsrHandler(PKINTERRUPT pInterrupt, PVOID pServiceContext) +{ + RT_NOREF1(pInterrupt); + PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pServiceContext; + if (pDevExt == NULL) + return FALSE; + + /*Log3Func(("pDevExt=0x%p, pVMMDevMemory=0x%p\n", pDevExt, pDevExt ? pDevExt->pVMMDevMemory : NULL));*/ + + /* Enter the common ISR routine and do the actual work. */ + BOOLEAN fIRQTaken = VGDrvCommonISR(&pDevExt->Core); + + /* If we need to wake up some events we do that in a DPC to make + * sure we're called at the right IRQL. */ + if (fIRQTaken) + { + Log3Func(("IRQ was taken! pInterrupt=0x%p, pDevExt=0x%p\n", pInterrupt, pDevExt)); + if (ASMAtomicUoReadU32( &pDevExt->Core.u32MousePosChangedSeq) + || !RTListIsEmpty(&pDevExt->Core.WakeUpList)) + { + Log3Func(("Requesting DPC...\n")); + IoRequestDpc(pDevExt->pDeviceObject, NULL /*pIrp*/, NULL /*pvContext*/); + } + } + return fIRQTaken; +} + + +void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt) +{ + NOREF(pDevExt); + /* nothing to do here - i.e. since we can not KeSetEvent from ISR level, + * we rely on the pDevExt->u32MousePosChangedSeq to be set to a non-zero value on a mouse event + * and queue the DPC in our ISR routine in that case doing KeSetEvent from the DPC routine */ +} + + +/** + * Hook for handling OS specfic options from the host. + * + * @returns true if handled, false if not. + * @param pDevExt The device extension. + * @param pszName The option name. + * @param pszValue The option value. + */ +bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue) +{ + RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue); + return false; +} + + +/** + * Implements RTL_QUERY_REGISTRY_ROUTINE for enumerating our registry key. + */ +static NTSTATUS NTAPI vgdrvNtRegistryEnumCallback(PWSTR pwszValueName, ULONG uValueType, + PVOID pvValue, ULONG cbValue, PVOID pvUser, PVOID pvEntryCtx) +{ + Log4(("vgdrvNtRegistryEnumCallback: pwszValueName=%ls uValueType=%#x Value=%.*Rhxs\n", pwszValueName, uValueType, cbValue, pvValue)); + + /* + * Filter out general service config values. + */ + if ( RTUtf16ICmpAscii(pwszValueName, "Type") == 0 + || RTUtf16ICmpAscii(pwszValueName, "Start") == 0 + || RTUtf16ICmpAscii(pwszValueName, "ErrorControl") == 0 + || RTUtf16ICmpAscii(pwszValueName, "Tag") == 0 + || RTUtf16ICmpAscii(pwszValueName, "ImagePath") == 0 + || RTUtf16ICmpAscii(pwszValueName, "DisplayName") == 0 + || RTUtf16ICmpAscii(pwszValueName, "Group") == 0 + || RTUtf16ICmpAscii(pwszValueName, "DependOnGroup") == 0 + || RTUtf16ICmpAscii(pwszValueName, "DependOnService") == 0 + ) + { + return STATUS_SUCCESS; + } + + /* + * Convert the value name. + */ + size_t cch = RTUtf16CalcUtf8Len(pwszValueName); + if (cch < 64 && cch > 0) + { + char szValueName[72]; + char *pszTmp = szValueName; + int rc = RTUtf16ToUtf8Ex(pwszValueName, RTSTR_MAX, &pszTmp, sizeof(szValueName), NULL); + if (RT_SUCCESS(rc)) + { + /* + * Convert the value. + */ + char szValue[72]; + char *pszFree = NULL; + char *pszValue = NULL; + szValue[0] = '\0'; + switch (uValueType) + { + case REG_SZ: + case REG_EXPAND_SZ: + rc = RTUtf16CalcUtf8LenEx((PCRTUTF16)pvValue, cbValue / sizeof(RTUTF16), &cch); + if (RT_SUCCESS(rc) && cch < _1K) + { + if (cch < sizeof(szValue)) + { + pszValue = szValue; + rc = RTUtf16ToUtf8Ex((PCRTUTF16)pvValue, cbValue / sizeof(RTUTF16), &pszValue, sizeof(szValue), NULL); + } + else + { + rc = RTUtf16ToUtf8Ex((PCRTUTF16)pvValue, cbValue / sizeof(RTUTF16), &pszValue, sizeof(szValue), NULL); + if (RT_SUCCESS(rc)) + pszFree = pszValue; + } + if (RT_FAILURE(rc)) + { + LogRel(("VBoxGuest: Failed to convert registry value '%ls' string data to UTF-8: %Rrc\n", + pwszValueName, rc)); + pszValue = NULL; + } + } + else if (RT_SUCCESS(rc)) + LogRel(("VBoxGuest: Registry value '%ls' has a too long value: %#x (uvalueType=%#x)\n", + pwszValueName, cbValue, uValueType)); + else + LogRel(("VBoxGuest: Registry value '%ls' has an invalid string value (cbValue=%#x, uvalueType=%#x)\n", + pwszValueName, cbValue, uValueType)); + break; + + case REG_DWORD: + if (cbValue == sizeof(uint32_t)) + { + RTStrFormatU32(szValue, sizeof(szValue), *(uint32_t const *)pvValue, 10, 0, 0, 0); + pszValue = szValue; + } + else + LogRel(("VBoxGuest: Registry value '%ls' has wrong length for REG_DWORD: %#x\n", pwszValueName, cbValue)); + break; + + case REG_QWORD: + if (cbValue == sizeof(uint64_t)) + { + RTStrFormatU32(szValue, sizeof(szValue), *(uint32_t const *)pvValue, 10, 0, 0, 0); + pszValue = szValue; + } + else + LogRel(("VBoxGuest: Registry value '%ls' has wrong length for REG_DWORD: %#x\n", pwszValueName, cbValue)); + break; + + default: + LogRel(("VBoxGuest: Ignoring registry value '%ls': Unsupported type %#x\n", pwszValueName, uValueType)); + break; + } + if (pszValue) + { + /* + * Process it. + */ + PVBOXGUESTDEVEXT pDevExt = (PVBOXGUESTDEVEXT)pvUser; + VGDrvCommonProcessOption(pDevExt, szValueName, pszValue); + if (pszFree) + RTStrFree(pszFree); + } + } + } + else if (cch > 0) + LogRel(("VBoxGuest: Ignoring registery value '%ls': name too long\n", pwszValueName)); + else + LogRel(("VBoxGuest: Ignoring registery value with bad name\n", pwszValueName)); + NOREF(pvEntryCtx); + return STATUS_SUCCESS; +} + + +/** + * Reads configuration from the registry and guest properties. + * + * We ignore failures and instead preserve existing configuration values. + * + * Thie routine will block. + * + * @param pDevExt The device extension. + */ +static void vgdrvNtReadConfiguration(PVBOXGUESTDEVEXTWIN pDevExt) +{ + /* + * First the registry. + * + * Note! RTL_QUERY_REGISTRY_NOEXPAND is sensible (no environment) and also necessary to + * avoid crash on NT 3.1 because RtlExpandEnvironmentStrings_U thinks its in ring-3 + * and tries to get the default heap from the PEB via the TEB. No TEB in ring-0. + */ + RTL_QUERY_REGISTRY_TABLE aQuery[2]; + RT_ZERO(aQuery); + aQuery[0].QueryRoutine = vgdrvNtRegistryEnumCallback; + aQuery[0].Flags = RTL_QUERY_REGISTRY_NOEXPAND; + aQuery[0].Name = NULL; + aQuery[0].EntryContext = NULL; + aQuery[0].DefaultType = REG_NONE; + NTSTATUS rcNt = RtlQueryRegistryValues(RTL_REGISTRY_SERVICES, L"VBoxGuest", &aQuery[0], pDevExt, NULL /*pwszzEnv*/); + if (!NT_SUCCESS(rcNt)) + LogRel(("VBoxGuest: RtlQueryRegistryValues failed: %#x\n", rcNt)); + + /* + * Read configuration from the host. + */ + VGDrvCommonProcessOptionsFromHost(&pDevExt->Core); +} + +#ifdef VBOX_STRICT + +/** + * A quick implementation of AtomicTestAndClear for uint32_t and multiple bits. + */ +static uint32_t vgdrvNtAtomicBitsTestAndClear(void *pu32Bits, uint32_t u32Mask) +{ + AssertPtrReturn(pu32Bits, 0); + LogFlowFunc(("*pu32Bits=%#x, u32Mask=%#x\n", *(uint32_t *)pu32Bits, u32Mask)); + uint32_t u32Result = 0; + uint32_t u32WorkingMask = u32Mask; + int iBitOffset = ASMBitFirstSetU32 (u32WorkingMask); + + while (iBitOffset > 0) + { + bool fSet = ASMAtomicBitTestAndClear(pu32Bits, iBitOffset - 1); + if (fSet) + u32Result |= 1 << (iBitOffset - 1); + u32WorkingMask &= ~(1 << (iBitOffset - 1)); + iBitOffset = ASMBitFirstSetU32 (u32WorkingMask); + } + LogFlowFunc(("Returning %#x\n", u32Result)); + return u32Result; +} + + +static void vgdrvNtTestAtomicTestAndClearBitsU32(uint32_t u32Mask, uint32_t u32Bits, uint32_t u32Exp) +{ + ULONG u32Bits2 = u32Bits; + uint32_t u32Result = vgdrvNtAtomicBitsTestAndClear(&u32Bits2, u32Mask); + if ( u32Result != u32Exp + || (u32Bits2 & u32Mask) + || (u32Bits2 & u32Result) + || ((u32Bits2 | u32Result) != u32Bits) + ) + AssertLogRelMsgFailed(("TEST FAILED: u32Mask=%#x, u32Bits (before)=%#x, u32Bits (after)=%#x, u32Result=%#x, u32Exp=%#x\n", + u32Mask, u32Bits, u32Bits2, u32Result)); +} + + +static void vgdrvNtDoTests(void) +{ + vgdrvNtTestAtomicTestAndClearBitsU32(0x00, 0x23, 0); + vgdrvNtTestAtomicTestAndClearBitsU32(0x11, 0, 0); + vgdrvNtTestAtomicTestAndClearBitsU32(0x11, 0x22, 0); + vgdrvNtTestAtomicTestAndClearBitsU32(0x11, 0x23, 0x1); + vgdrvNtTestAtomicTestAndClearBitsU32(0x11, 0x32, 0x10); + vgdrvNtTestAtomicTestAndClearBitsU32(0x22, 0x23, 0x22); +} + +#endif /* VBOX_STRICT */ + +#ifdef VBOX_WITH_DPC_LATENCY_CHECKER + +/* + * DPC latency checker. + */ + +/** + * One DPC latency sample. + */ +typedef struct DPCSAMPLE +{ + LARGE_INTEGER PerfDelta; + LARGE_INTEGER PerfCounter; + LARGE_INTEGER PerfFrequency; + uint64_t u64TSC; +} DPCSAMPLE; +AssertCompileSize(DPCSAMPLE, 4*8); + +/** + * The DPC latency measurement workset. + */ +typedef struct DPCDATA +{ + KDPC Dpc; + KTIMER Timer; + KSPIN_LOCK SpinLock; + + ULONG ulTimerRes; + + bool volatile fFinished; + + /** The timer interval (relative). */ + LARGE_INTEGER DueTime; + + LARGE_INTEGER PerfCounterPrev; + + /** Align the sample array on a 64 byte boundrary just for the off chance + * that we'll get cache line aligned memory backing this structure. */ + uint32_t auPadding[ARCH_BITS == 32 ? 5 : 7]; + + int cSamples; + DPCSAMPLE aSamples[8192]; +} DPCDATA; + +AssertCompileMemberAlignment(DPCDATA, aSamples, 64); + +/** + * DPC callback routine for the DPC latency measurement code. + * + * @param pDpc The DPC, not used. + * @param pvDeferredContext Pointer to the DPCDATA. + * @param SystemArgument1 System use, ignored. + * @param SystemArgument2 System use, ignored. + */ +static VOID vgdrvNtDpcLatencyCallback(PKDPC pDpc, PVOID pvDeferredContext, PVOID SystemArgument1, PVOID SystemArgument2) +{ + DPCDATA *pData = (DPCDATA *)pvDeferredContext; + RT_NOREF(pDpc, SystemArgument1, SystemArgument2); + + KeAcquireSpinLockAtDpcLevel(&pData->SpinLock); + + if (pData->cSamples >= RT_ELEMENTS(pData->aSamples)) + pData->fFinished = true; + else + { + DPCSAMPLE *pSample = &pData->aSamples[pData->cSamples++]; + + pSample->u64TSC = ASMReadTSC(); + pSample->PerfCounter = KeQueryPerformanceCounter(&pSample->PerfFrequency); + pSample->PerfDelta.QuadPart = pSample->PerfCounter.QuadPart - pData->PerfCounterPrev.QuadPart; + + pData->PerfCounterPrev.QuadPart = pSample->PerfCounter.QuadPart; + + KeSetTimer(&pData->Timer, pData->DueTime, &pData->Dpc); + } + + KeReleaseSpinLockFromDpcLevel(&pData->SpinLock); +} + + +/** + * Handles the DPC latency checker request. + * + * @returns VBox status code. + */ +int VGDrvNtIOCtl_DpcLatencyChecker(void) +{ + /* + * Allocate a block of non paged memory for samples and related data. + */ + DPCDATA *pData = (DPCDATA *)RTMemAlloc(sizeof(DPCDATA)); + if (!pData) + { + RTLogBackdoorPrintf("VBoxGuest: DPC: DPCDATA allocation failed.\n"); + return VERR_NO_MEMORY; + } + + /* + * Initialize the data. + */ + KeInitializeDpc(&pData->Dpc, vgdrvNtDpcLatencyCallback, pData); + KeInitializeTimer(&pData->Timer); + KeInitializeSpinLock(&pData->SpinLock); + + pData->fFinished = false; + pData->cSamples = 0; + pData->PerfCounterPrev.QuadPart = 0; + + pData->ulTimerRes = ExSetTimerResolution(1000 * 10, 1); + pData->DueTime.QuadPart = -(int64_t)pData->ulTimerRes / 10; + + /* + * Start the DPC measurements and wait for a full set. + */ + KeSetTimer(&pData->Timer, pData->DueTime, &pData->Dpc); + + while (!pData->fFinished) + { + LARGE_INTEGER Interval; + Interval.QuadPart = -100 * 1000 * 10; + KeDelayExecutionThread(KernelMode, TRUE, &Interval); + } + + ExSetTimerResolution(0, 0); + + /* + * Log everything to the host. + */ + RTLogBackdoorPrintf("DPC: ulTimerRes = %d\n", pData->ulTimerRes); + for (int i = 0; i < pData->cSamples; i++) + { + DPCSAMPLE *pSample = &pData->aSamples[i]; + + RTLogBackdoorPrintf("[%d] pd %lld pc %lld pf %lld t %lld\n", + i, + pSample->PerfDelta.QuadPart, + pSample->PerfCounter.QuadPart, + pSample->PerfFrequency.QuadPart, + pSample->u64TSC); + } + + RTMemFree(pData); + return VINF_SUCCESS; +} + +#endif /* VBOX_WITH_DPC_LATENCY_CHECKER */ + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp b/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp new file mode 100644 index 00000000..e6485c0b --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp @@ -0,0 +1,4516 @@ +/* $Id: VBoxGuest.cpp $ */ +/** @file + * VBoxGuest - Guest Additions Driver, Common Code. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/** @page pg_vbdrv VBoxGuest + * + * VBoxGuest is the device driver for VMMDev. + * + * The device driver is shipped as part of the guest additions. It has roots in + * the host VMM support driver (usually known as VBoxDrv), so fixes in platform + * specific code may apply to both drivers. + * + * The common code lives in VBoxGuest.cpp and is compiled both as C++ and C. + * The VBoxGuest.cpp source file shall not contain platform specific code, + * though it must occationally do a few \#ifdef RT_OS_XXX tests to cater for + * platform differences. Though, in those cases, it is common that more than + * one platform needs special handling. + * + * On most platforms the device driver should create two device nodes, one for + * full (unrestricted) access to the feature set, and one which only provides a + * restrict set of functions. These are generally referred to as 'vboxguest' + * and 'vboxuser' respectively. Currently, this two device approach is only + * implemented on Linux! + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEFAULT +#include "VBoxGuestInternal.h" +#include <VBox/VMMDev.h> /* for VMMDEV_RAM_SIZE */ +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/HostServices/GuestPropertySvc.h> +#include <iprt/ctype.h> +#include <iprt/mem.h> +#include <iprt/time.h> +#include <iprt/memobj.h> +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/string.h> +#include <iprt/process.h> +#include <iprt/assert.h> +#include <iprt/param.h> +#include <iprt/timer.h> +#ifdef VBOX_WITH_HGCM +# include <iprt/thread.h> +#endif +#include "version-generated.h" +#if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) +# include "revision-generated.h" +#endif +#if defined(RT_OS_SOLARIS) || defined(RT_OS_DARWIN) +# include <iprt/rand.h> +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOXGUEST_ACQUIRE_STYLE_EVENTS (VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST | VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST) + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifdef VBOX_WITH_HGCM +static DECLCALLBACK(int) vgdrvHgcmAsyncWaitCallback(VMMDevHGCMRequestHeader *pHdrNonVolatile, void *pvUser, uint32_t u32User); +#endif +static int vgdrvIoCtl_CancelAllWaitEvents(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession); +static void vgdrvBitUsageTrackerClear(PVBOXGUESTBITUSAGETRACER pTracker); +static uint32_t vgdrvGetAllowedEventMaskForSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession); +static int vgdrvResetEventFilterOnHost(PVBOXGUESTDEVEXT pDevExt, uint32_t fFixedEvents); +static int vgdrvResetMouseStatusOnHost(PVBOXGUESTDEVEXT pDevExt); +static int vgdrvResetCapabilitiesOnHost(PVBOXGUESTDEVEXT pDevExt); +static int vgdrvSetSessionEventFilter(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + uint32_t fOrMask, uint32_t fNotMask, bool fSessionTermination); +static int vgdrvSetSessionMouseStatus(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + uint32_t fOrMask, uint32_t fNotMask, bool fSessionTermination); +static int vgdrvSetSessionCapabilities(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + uint32_t fOrMask, uint32_t fNoMask, + uint32_t *pfSessionCaps, uint32_t *pfGlobalCaps, bool fSessionTermination); +static int vgdrvAcquireSessionCapabilities(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + uint32_t fOrMask, uint32_t fNotMask, uint32_t fFlags, bool fSessionTermination); +static int vgdrvDispatchEventsLocked(PVBOXGUESTDEVEXT pDevExt, uint32_t fEvents); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const uint32_t g_cbChangeMemBalloonReq = RT_UOFFSETOF(VMMDevChangeMemBalloon, aPhysPage[VMMDEV_MEMORY_BALLOON_CHUNK_PAGES]); + +#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) +/** + * Drag in the rest of IRPT since we share it with the + * rest of the kernel modules on Solaris. + */ +struct CLANG11WEIRDNESS { PFNRT pfn; } g_apfnVBoxGuestIPRTDeps[] = +{ + /* VirtioNet */ + { (PFNRT)RTRandBytes }, + /* RTSemMutex* */ + { (PFNRT)RTSemMutexCreate }, + { (PFNRT)RTSemMutexDestroy }, + { (PFNRT)RTSemMutexRequest }, + { (PFNRT)RTSemMutexRequestNoResume }, + { (PFNRT)RTSemMutexRequestDebug }, + { (PFNRT)RTSemMutexRequestNoResumeDebug }, + { (PFNRT)RTSemMutexRelease }, + { (PFNRT)RTSemMutexIsOwned }, + { NULL } +}; +#endif /* RT_OS_DARWIN || RT_OS_SOLARIS */ + + +/** + * Reserves memory in which the VMM can relocate any guest mappings + * that are floating around. + * + * This operation is a little bit tricky since the VMM might not accept + * just any address because of address clashes between the three contexts + * it operates in, so use a small stack to perform this operation. + * + * @returns VBox status code (ignored). + * @param pDevExt The device extension. + */ +static int vgdrvInitFixateGuestMappings(PVBOXGUESTDEVEXT pDevExt) +{ + /* + * Query the required space. + */ + VMMDevReqHypervisorInfo *pReq; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(VMMDevReqHypervisorInfo), VMMDevReq_GetHypervisorInfo); + if (RT_FAILURE(rc)) + return rc; + pReq->hypervisorStart = 0; + pReq->hypervisorSize = 0; + rc = VbglR0GRPerform(&pReq->header); + if (RT_FAILURE(rc)) /* this shouldn't happen! */ + { + VbglR0GRFree(&pReq->header); + return rc; + } + + /* + * The VMM will report back if there is nothing it wants to map, like for + * instance in VT-x and AMD-V mode. + */ + if (pReq->hypervisorSize == 0) + Log(("vgdrvInitFixateGuestMappings: nothing to do\n")); + else + { + /* + * We have to try several times since the host can be picky + * about certain addresses. + */ + RTR0MEMOBJ hFictive = NIL_RTR0MEMOBJ; + uint32_t cbHypervisor = pReq->hypervisorSize; + RTR0MEMOBJ ahTries[5]; + uint32_t iTry; + bool fBitched = false; + Log(("vgdrvInitFixateGuestMappings: cbHypervisor=%#x\n", cbHypervisor)); + for (iTry = 0; iTry < RT_ELEMENTS(ahTries); iTry++) + { + /* + * Reserve space, or if that isn't supported, create a object for + * some fictive physical memory and map that in to kernel space. + * + * To make the code a bit uglier, most systems cannot help with + * 4MB alignment, so we have to deal with that in addition to + * having two ways of getting the memory. + */ + uint32_t uAlignment = _4M; + RTR0MEMOBJ hObj; + rc = RTR0MemObjReserveKernel(&hObj, (void *)-1, RT_ALIGN_32(cbHypervisor, _4M), uAlignment); + if (rc == VERR_NOT_SUPPORTED) + { + uAlignment = PAGE_SIZE; + rc = RTR0MemObjReserveKernel(&hObj, (void *)-1, RT_ALIGN_32(cbHypervisor, _4M) + _4M, uAlignment); + } + /* + * If both RTR0MemObjReserveKernel calls above failed because either not supported or + * not implemented at all at the current platform, try to map the memory object into the + * virtual kernel space. + */ + if (rc == VERR_NOT_SUPPORTED) + { + if (hFictive == NIL_RTR0MEMOBJ) + { + rc = RTR0MemObjEnterPhys(&hObj, VBOXGUEST_HYPERVISOR_PHYSICAL_START, cbHypervisor + _4M, RTMEM_CACHE_POLICY_DONT_CARE); + if (RT_FAILURE(rc)) + break; + hFictive = hObj; + } + uAlignment = _4M; + rc = RTR0MemObjMapKernel(&hObj, hFictive, (void *)-1, uAlignment, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + if (rc == VERR_NOT_SUPPORTED) + { + uAlignment = PAGE_SIZE; + rc = RTR0MemObjMapKernel(&hObj, hFictive, (void *)-1, uAlignment, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + } + } + if (RT_FAILURE(rc)) + { + LogRel(("VBoxGuest: Failed to reserve memory for the hypervisor: rc=%Rrc (cbHypervisor=%#x uAlignment=%#x iTry=%u)\n", + rc, cbHypervisor, uAlignment, iTry)); + fBitched = true; + break; + } + + /* + * Try set it. + */ + pReq->header.requestType = VMMDevReq_SetHypervisorInfo; + pReq->header.rc = VERR_INTERNAL_ERROR; + pReq->hypervisorSize = cbHypervisor; + pReq->hypervisorStart = (RTGCPTR32)(uintptr_t)RTR0MemObjAddress(hObj); + if ( uAlignment == PAGE_SIZE + && pReq->hypervisorStart & (_4M - 1)) + pReq->hypervisorStart = RT_ALIGN_32(pReq->hypervisorStart, _4M); + AssertMsg(RT_ALIGN_32(pReq->hypervisorStart, _4M) == pReq->hypervisorStart, ("%#x\n", pReq->hypervisorStart)); + + rc = VbglR0GRPerform(&pReq->header); + if (RT_SUCCESS(rc)) + { + pDevExt->hGuestMappings = hFictive != NIL_RTR0MEMOBJ ? hFictive : hObj; + Log(("VBoxGuest: %p LB %#x; uAlignment=%#x iTry=%u hGuestMappings=%p (%s)\n", + RTR0MemObjAddress(pDevExt->hGuestMappings), + RTR0MemObjSize(pDevExt->hGuestMappings), + uAlignment, iTry, pDevExt->hGuestMappings, hFictive != NIL_RTR0PTR ? "fictive" : "reservation")); + break; + } + ahTries[iTry] = hObj; + } + + /* + * Cleanup failed attempts. + */ + while (iTry-- > 0) + RTR0MemObjFree(ahTries[iTry], false /* fFreeMappings */); + if ( RT_FAILURE(rc) + && hFictive != NIL_RTR0PTR) + RTR0MemObjFree(hFictive, false /* fFreeMappings */); + if (RT_FAILURE(rc) && !fBitched) + LogRel(("VBoxGuest: Warning: failed to reserve %#d of memory for guest mappings.\n", cbHypervisor)); + } + VbglR0GRFree(&pReq->header); + + /* + * We ignore failed attempts for now. + */ + return VINF_SUCCESS; +} + + +/** + * Undo what vgdrvInitFixateGuestMappings did. + * + * @param pDevExt The device extension. + */ +static void vgdrvTermUnfixGuestMappings(PVBOXGUESTDEVEXT pDevExt) +{ + if (pDevExt->hGuestMappings != NIL_RTR0PTR) + { + /* + * Tell the host that we're going to free the memory we reserved for + * it, the free it up. (Leak the memory if anything goes wrong here.) + */ + VMMDevReqHypervisorInfo *pReq; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(VMMDevReqHypervisorInfo), VMMDevReq_SetHypervisorInfo); + if (RT_SUCCESS(rc)) + { + pReq->hypervisorStart = 0; + pReq->hypervisorSize = 0; + rc = VbglR0GRPerform(&pReq->header); + VbglR0GRFree(&pReq->header); + } + if (RT_SUCCESS(rc)) + { + rc = RTR0MemObjFree(pDevExt->hGuestMappings, true /* fFreeMappings */); + AssertRC(rc); + } + else + LogRel(("vgdrvTermUnfixGuestMappings: Failed to unfix the guest mappings! rc=%Rrc\n", rc)); + + pDevExt->hGuestMappings = NIL_RTR0MEMOBJ; + } +} + + + +/** + * Report the guest information to the host. + * + * @returns IPRT status code. + * @param enmOSType The OS type to report. + */ +static int vgdrvReportGuestInfo(VBOXOSTYPE enmOSType) +{ + /* + * Allocate and fill in the two guest info reports. + */ + VMMDevReportGuestInfo2 *pReqInfo2 = NULL; + VMMDevReportGuestInfo *pReqInfo1 = NULL; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReqInfo2, sizeof (VMMDevReportGuestInfo2), VMMDevReq_ReportGuestInfo2); + Log(("vgdrvReportGuestInfo: VbglR0GRAlloc VMMDevReportGuestInfo2 completed with rc=%Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + pReqInfo2->guestInfo.additionsMajor = VBOX_VERSION_MAJOR; + pReqInfo2->guestInfo.additionsMinor = VBOX_VERSION_MINOR; + pReqInfo2->guestInfo.additionsBuild = VBOX_VERSION_BUILD; + pReqInfo2->guestInfo.additionsRevision = VBOX_SVN_REV; + pReqInfo2->guestInfo.additionsFeatures = VBOXGSTINFO2_F_REQUESTOR_INFO; + RTStrCopy(pReqInfo2->guestInfo.szName, sizeof(pReqInfo2->guestInfo.szName), VBOX_VERSION_STRING); + + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReqInfo1, sizeof (VMMDevReportGuestInfo), VMMDevReq_ReportGuestInfo); + Log(("vgdrvReportGuestInfo: VbglR0GRAlloc VMMDevReportGuestInfo completed with rc=%Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + pReqInfo1->guestInfo.interfaceVersion = VMMDEV_VERSION; + pReqInfo1->guestInfo.osType = enmOSType; + + /* + * There are two protocols here: + * 1. Info2 + Info1. Supported by >=3.2.51. + * 2. Info1 and optionally Info2. The old protocol. + * + * We try protocol 1 first. It will fail with VERR_NOT_SUPPORTED + * if not supported by the VMMDev (message ordering requirement). + */ + rc = VbglR0GRPerform(&pReqInfo2->header); + Log(("vgdrvReportGuestInfo: VbglR0GRPerform VMMDevReportGuestInfo2 completed with rc=%Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + rc = VbglR0GRPerform(&pReqInfo1->header); + Log(("vgdrvReportGuestInfo: VbglR0GRPerform VMMDevReportGuestInfo completed with rc=%Rrc\n", rc)); + } + else if ( rc == VERR_NOT_SUPPORTED + || rc == VERR_NOT_IMPLEMENTED) + { + rc = VbglR0GRPerform(&pReqInfo1->header); + Log(("vgdrvReportGuestInfo: VbglR0GRPerform VMMDevReportGuestInfo completed with rc=%Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + rc = VbglR0GRPerform(&pReqInfo2->header); + Log(("vgdrvReportGuestInfo: VbglR0GRPerform VMMDevReportGuestInfo2 completed with rc=%Rrc\n", rc)); + if (rc == VERR_NOT_IMPLEMENTED) + rc = VINF_SUCCESS; + } + } + VbglR0GRFree(&pReqInfo1->header); + } + VbglR0GRFree(&pReqInfo2->header); + } + + return rc; +} + + +/** + * Report the guest driver status to the host. + * + * @returns IPRT status code. + * @param fActive Flag whether the driver is now active or not. + */ +static int vgdrvReportDriverStatus(bool fActive) +{ + /* + * Report guest status of the VBox driver to the host. + */ + VMMDevReportGuestStatus *pReq2 = NULL; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq2, sizeof(*pReq2), VMMDevReq_ReportGuestStatus); + Log(("vgdrvReportDriverStatus: VbglR0GRAlloc VMMDevReportGuestStatus completed with rc=%Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + pReq2->guestStatus.facility = VBoxGuestFacilityType_VBoxGuestDriver; + pReq2->guestStatus.status = fActive ? + VBoxGuestFacilityStatus_Active + : VBoxGuestFacilityStatus_Inactive; + pReq2->guestStatus.flags = 0; + rc = VbglR0GRPerform(&pReq2->header); + Log(("vgdrvReportDriverStatus: VbglR0GRPerform VMMDevReportGuestStatus completed with fActive=%d, rc=%Rrc\n", + fActive ? 1 : 0, rc)); + if (rc == VERR_NOT_IMPLEMENTED) /* Compatibility with older hosts. */ + rc = VINF_SUCCESS; + VbglR0GRFree(&pReq2->header); + } + + return rc; +} + + +/** @name Memory Ballooning + * @{ + */ + +/** + * Inflate the balloon by one chunk represented by an R0 memory object. + * + * The caller owns the balloon mutex. + * + * @returns IPRT status code. + * @param pMemObj Pointer to the R0 memory object. + * @param pReq The pre-allocated request for performing the VMMDev call. + */ +static int vgdrvBalloonInflate(PRTR0MEMOBJ pMemObj, VMMDevChangeMemBalloon *pReq) +{ + uint32_t iPage; + int rc; + + for (iPage = 0; iPage < VMMDEV_MEMORY_BALLOON_CHUNK_PAGES; iPage++) + { + RTHCPHYS phys = RTR0MemObjGetPagePhysAddr(*pMemObj, iPage); + pReq->aPhysPage[iPage] = phys; + } + + pReq->fInflate = true; + pReq->header.size = g_cbChangeMemBalloonReq; + pReq->cPages = VMMDEV_MEMORY_BALLOON_CHUNK_PAGES; + + rc = VbglR0GRPerform(&pReq->header); + if (RT_FAILURE(rc)) + LogRel(("vgdrvBalloonInflate: VbglR0GRPerform failed. rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Deflate the balloon by one chunk - info the host and free the memory object. + * + * The caller owns the balloon mutex. + * + * @returns IPRT status code. + * @param pMemObj Pointer to the R0 memory object. + * The memory object will be freed afterwards. + * @param pReq The pre-allocated request for performing the VMMDev call. + */ +static int vgdrvBalloonDeflate(PRTR0MEMOBJ pMemObj, VMMDevChangeMemBalloon *pReq) +{ + uint32_t iPage; + int rc; + + for (iPage = 0; iPage < VMMDEV_MEMORY_BALLOON_CHUNK_PAGES; iPage++) + { + RTHCPHYS phys = RTR0MemObjGetPagePhysAddr(*pMemObj, iPage); + pReq->aPhysPage[iPage] = phys; + } + + pReq->fInflate = false; + pReq->header.size = g_cbChangeMemBalloonReq; + pReq->cPages = VMMDEV_MEMORY_BALLOON_CHUNK_PAGES; + + rc = VbglR0GRPerform(&pReq->header); + if (RT_FAILURE(rc)) + { + LogRel(("vgdrvBalloonDeflate: VbglR0GRPerform failed. rc=%Rrc\n", rc)); + return rc; + } + + rc = RTR0MemObjFree(*pMemObj, true); + if (RT_FAILURE(rc)) + { + LogRel(("vgdrvBalloonDeflate: RTR0MemObjFree(%p,true) -> %Rrc; this is *BAD*!\n", *pMemObj, rc)); + return rc; + } + + *pMemObj = NIL_RTR0MEMOBJ; + return VINF_SUCCESS; +} + + +/** + * Inflate/deflate the memory balloon and notify the host. + * + * This is a worker used by vgdrvIoCtl_CheckMemoryBalloon - it takes the mutex. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param cBalloonChunks The new size of the balloon in chunks of 1MB. + * @param pfHandleInR3 Where to return the handle-in-ring3 indicator + * (VINF_SUCCESS if set). + */ +static int vgdrvSetBalloonSizeKernel(PVBOXGUESTDEVEXT pDevExt, uint32_t cBalloonChunks, bool *pfHandleInR3) +{ + int rc = VINF_SUCCESS; + + if (pDevExt->MemBalloon.fUseKernelAPI) + { + VMMDevChangeMemBalloon *pReq; + uint32_t i; + + if (cBalloonChunks > pDevExt->MemBalloon.cMaxChunks) + { + LogRel(("vgdrvSetBalloonSizeKernel: illegal balloon size %u (max=%u)\n", + cBalloonChunks, pDevExt->MemBalloon.cMaxChunks)); + return VERR_INVALID_PARAMETER; + } + + if (cBalloonChunks == pDevExt->MemBalloon.cMaxChunks) + return VINF_SUCCESS; /* nothing to do */ + + if ( cBalloonChunks > pDevExt->MemBalloon.cChunks + && !pDevExt->MemBalloon.paMemObj) + { + pDevExt->MemBalloon.paMemObj = (PRTR0MEMOBJ)RTMemAllocZ(sizeof(RTR0MEMOBJ) * pDevExt->MemBalloon.cMaxChunks); + if (!pDevExt->MemBalloon.paMemObj) + { + LogRel(("vgdrvSetBalloonSizeKernel: no memory for paMemObj!\n")); + return VERR_NO_MEMORY; + } + } + + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, g_cbChangeMemBalloonReq, VMMDevReq_ChangeMemBalloon); + if (RT_FAILURE(rc)) + return rc; + + if (cBalloonChunks > pDevExt->MemBalloon.cChunks) + { + /* inflate */ + for (i = pDevExt->MemBalloon.cChunks; i < cBalloonChunks; i++) + { + rc = RTR0MemObjAllocPhysNC(&pDevExt->MemBalloon.paMemObj[i], + VMMDEV_MEMORY_BALLOON_CHUNK_SIZE, NIL_RTHCPHYS); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NOT_SUPPORTED) + { + /* not supported -- fall back to the R3-allocated memory. */ + rc = VINF_SUCCESS; + pDevExt->MemBalloon.fUseKernelAPI = false; + Assert(pDevExt->MemBalloon.cChunks == 0); + Log(("VBoxGuestSetBalloonSizeKernel: PhysNC allocs not supported, falling back to R3 allocs.\n")); + } + /* else if (rc == VERR_NO_MEMORY || rc == VERR_NO_PHYS_MEMORY): + * cannot allocate more memory => don't try further, just stop here */ + /* else: XXX what else can fail? VERR_MEMOBJ_INIT_FAILED for instance. just stop. */ + break; + } + + rc = vgdrvBalloonInflate(&pDevExt->MemBalloon.paMemObj[i], pReq); + if (RT_FAILURE(rc)) + { + Log(("vboxGuestSetBalloonSize(inflate): failed, rc=%Rrc!\n", rc)); + RTR0MemObjFree(pDevExt->MemBalloon.paMemObj[i], true); + pDevExt->MemBalloon.paMemObj[i] = NIL_RTR0MEMOBJ; + break; + } + pDevExt->MemBalloon.cChunks++; + } + } + else + { + /* deflate */ + for (i = pDevExt->MemBalloon.cChunks; i-- > cBalloonChunks;) + { + rc = vgdrvBalloonDeflate(&pDevExt->MemBalloon.paMemObj[i], pReq); + if (RT_FAILURE(rc)) + { + Log(("vboxGuestSetBalloonSize(deflate): failed, rc=%Rrc!\n", rc)); + break; + } + pDevExt->MemBalloon.cChunks--; + } + } + + VbglR0GRFree(&pReq->header); + } + + /* + * Set the handle-in-ring3 indicator. When set Ring-3 will have to work + * the balloon changes via the other API. + */ + *pfHandleInR3 = pDevExt->MemBalloon.fUseKernelAPI ? false : true; + + return rc; +} + + +/** + * Inflate/deflate the balloon by one chunk. + * + * Worker for vgdrvIoCtl_ChangeMemoryBalloon - it takes the mutex. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param pSession The session. + * @param pvChunk The address of the chunk to add to / remove from the + * balloon. (user space address) + * @param fInflate Inflate if true, deflate if false. + */ +static int vgdrvSetBalloonSizeFromUser(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, RTR3PTR pvChunk, bool fInflate) +{ + VMMDevChangeMemBalloon *pReq; + PRTR0MEMOBJ pMemObj = NULL; + int rc = VINF_SUCCESS; + uint32_t i; + RT_NOREF1(pSession); + + if (fInflate) + { + if ( pDevExt->MemBalloon.cChunks > pDevExt->MemBalloon.cMaxChunks - 1 + || pDevExt->MemBalloon.cMaxChunks == 0 /* If called without first querying. */) + { + LogRel(("vgdrvSetBalloonSizeFromUser: cannot inflate balloon, already have %u chunks (max=%u)\n", + pDevExt->MemBalloon.cChunks, pDevExt->MemBalloon.cMaxChunks)); + return VERR_INVALID_PARAMETER; + } + + if (!pDevExt->MemBalloon.paMemObj) + { + pDevExt->MemBalloon.paMemObj = (PRTR0MEMOBJ)RTMemAlloc(sizeof(RTR0MEMOBJ) * pDevExt->MemBalloon.cMaxChunks); + if (!pDevExt->MemBalloon.paMemObj) + { + LogRel(("vgdrvSetBalloonSizeFromUser: no memory for paMemObj!\n")); + return VERR_NO_MEMORY; + } + for (i = 0; i < pDevExt->MemBalloon.cMaxChunks; i++) + pDevExt->MemBalloon.paMemObj[i] = NIL_RTR0MEMOBJ; + } + } + else + { + if (pDevExt->MemBalloon.cChunks == 0) + { + AssertMsgFailed(("vgdrvSetBalloonSizeFromUser: cannot decrease balloon, already at size 0\n")); + return VERR_INVALID_PARAMETER; + } + } + + /* + * Enumerate all memory objects and check if the object is already registered. + */ + for (i = 0; i < pDevExt->MemBalloon.cMaxChunks; i++) + { + if ( fInflate + && !pMemObj + && pDevExt->MemBalloon.paMemObj[i] == NIL_RTR0MEMOBJ) + pMemObj = &pDevExt->MemBalloon.paMemObj[i]; /* found free object pointer */ + if (RTR0MemObjAddressR3(pDevExt->MemBalloon.paMemObj[i]) == pvChunk) + { + if (fInflate) + return VERR_ALREADY_EXISTS; /* don't provide the same memory twice */ + pMemObj = &pDevExt->MemBalloon.paMemObj[i]; + break; + } + } + if (!pMemObj) + { + if (fInflate) + { + /* no free object pointer found -- should not happen */ + return VERR_NO_MEMORY; + } + + /* cannot free this memory as it wasn't provided before */ + return VERR_NOT_FOUND; + } + + /* + * Try inflate / default the balloon as requested. + */ + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, g_cbChangeMemBalloonReq, VMMDevReq_ChangeMemBalloon); + if (RT_FAILURE(rc)) + return rc; + pReq->header.fRequestor = pSession->fRequestor; + + if (fInflate) + { + rc = RTR0MemObjLockUser(pMemObj, pvChunk, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE, + RTMEM_PROT_READ | RTMEM_PROT_WRITE, NIL_RTR0PROCESS); + if (RT_SUCCESS(rc)) + { + rc = vgdrvBalloonInflate(pMemObj, pReq); + if (RT_SUCCESS(rc)) + pDevExt->MemBalloon.cChunks++; + else + { + Log(("vgdrvSetBalloonSizeFromUser(inflate): failed, rc=%Rrc!\n", rc)); + RTR0MemObjFree(*pMemObj, true); + *pMemObj = NIL_RTR0MEMOBJ; + } + } + } + else + { + rc = vgdrvBalloonDeflate(pMemObj, pReq); + if (RT_SUCCESS(rc)) + pDevExt->MemBalloon.cChunks--; + else + Log(("vgdrvSetBalloonSizeFromUser(deflate): failed, rc=%Rrc!\n", rc)); + } + + VbglR0GRFree(&pReq->header); + return rc; +} + + +/** + * Cleanup the memory balloon of a session. + * + * Will request the balloon mutex, so it must be valid and the caller must not + * own it already. + * + * @param pDevExt The device extension. + * @param pSession The session. Can be NULL at unload. + */ +static void vgdrvCloseMemBalloon(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession) +{ + RTSemFastMutexRequest(pDevExt->MemBalloon.hMtx); + if ( pDevExt->MemBalloon.pOwner == pSession + || pSession == NULL /*unload*/) + { + if (pDevExt->MemBalloon.paMemObj) + { + VMMDevChangeMemBalloon *pReq; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, g_cbChangeMemBalloonReq, VMMDevReq_ChangeMemBalloon); + if (RT_SUCCESS(rc)) + { + /* fRequestor is kernel here, as we're cleaning up. */ + + uint32_t i; + for (i = pDevExt->MemBalloon.cChunks; i-- > 0;) + { + rc = vgdrvBalloonDeflate(&pDevExt->MemBalloon.paMemObj[i], pReq); + if (RT_FAILURE(rc)) + { + LogRel(("vgdrvCloseMemBalloon: Deflate failed with rc=%Rrc. Will leak %u chunks.\n", + rc, pDevExt->MemBalloon.cChunks)); + break; + } + pDevExt->MemBalloon.paMemObj[i] = NIL_RTR0MEMOBJ; + pDevExt->MemBalloon.cChunks--; + } + VbglR0GRFree(&pReq->header); + } + else + LogRel(("vgdrvCloseMemBalloon: Failed to allocate VMMDev request buffer (rc=%Rrc). Will leak %u chunks.\n", + rc, pDevExt->MemBalloon.cChunks)); + RTMemFree(pDevExt->MemBalloon.paMemObj); + pDevExt->MemBalloon.paMemObj = NULL; + } + + pDevExt->MemBalloon.pOwner = NULL; + } + RTSemFastMutexRelease(pDevExt->MemBalloon.hMtx); +} + +/** @} */ + + + +/** @name Heartbeat + * @{ + */ + +/** + * Sends heartbeat to host. + * + * @returns VBox status code. + */ +static int vgdrvHeartbeatSend(PVBOXGUESTDEVEXT pDevExt) +{ + int rc; + if (pDevExt->pReqGuestHeartbeat) + { + rc = VbglR0GRPerform(pDevExt->pReqGuestHeartbeat); + Log3(("vgdrvHeartbeatSend: VbglR0GRPerform vgdrvHeartbeatSend completed with rc=%Rrc\n", rc)); + } + else + rc = VERR_INVALID_STATE; + return rc; +} + + +/** + * Callback for heartbeat timer. + */ +static DECLCALLBACK(void) vgdrvHeartbeatTimerHandler(PRTTIMER hTimer, void *pvUser, uint64_t iTick) +{ + PVBOXGUESTDEVEXT pDevExt = (PVBOXGUESTDEVEXT)pvUser; + int rc; + AssertReturnVoid(pDevExt); + + rc = vgdrvHeartbeatSend(pDevExt); + if (RT_FAILURE(rc)) + Log(("HB Timer: vgdrvHeartbeatSend failed: rc=%Rrc\n", rc)); + + NOREF(hTimer); NOREF(iTick); +} + + +/** + * Configure the host to check guest's heartbeat + * and get heartbeat interval from the host. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param fEnabled Set true to enable guest heartbeat checks on host. + */ +static int vgdrvHeartbeatHostConfigure(PVBOXGUESTDEVEXT pDevExt, bool fEnabled) +{ + VMMDevReqHeartbeat *pReq; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_HeartbeatConfigure); + Log(("vgdrvHeartbeatHostConfigure: VbglR0GRAlloc vgdrvHeartbeatHostConfigure completed with rc=%Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + pReq->fEnabled = fEnabled; + pReq->cNsInterval = 0; + rc = VbglR0GRPerform(&pReq->header); + Log(("vgdrvHeartbeatHostConfigure: VbglR0GRPerform vgdrvHeartbeatHostConfigure completed with rc=%Rrc\n", rc)); + pDevExt->cNsHeartbeatInterval = pReq->cNsInterval; + VbglR0GRFree(&pReq->header); + } + return rc; +} + + +/** + * Initializes the heartbeat timer. + * + * This feature may be disabled by the host. + * + * @returns VBox status (ignored). + * @param pDevExt The device extension. + */ +static int vgdrvHeartbeatInit(PVBOXGUESTDEVEXT pDevExt) +{ + /* + * Make sure that heartbeat checking is disabled. + */ + int rc = vgdrvHeartbeatHostConfigure(pDevExt, false); + if (RT_SUCCESS(rc)) + { + rc = vgdrvHeartbeatHostConfigure(pDevExt, true); + if (RT_SUCCESS(rc)) + { + /* + * Preallocate the request to use it from the timer callback because: + * 1) on Windows VbglR0GRAlloc must be called at IRQL <= APC_LEVEL + * and the timer callback runs at DISPATCH_LEVEL; + * 2) avoid repeated allocations. + */ + rc = VbglR0GRAlloc(&pDevExt->pReqGuestHeartbeat, sizeof(*pDevExt->pReqGuestHeartbeat), VMMDevReq_GuestHeartbeat); + if (RT_SUCCESS(rc)) + { + LogRel(("vgdrvHeartbeatInit: Setting up heartbeat to trigger every %RU64 milliseconds\n", + pDevExt->cNsHeartbeatInterval / RT_NS_1MS)); + rc = RTTimerCreateEx(&pDevExt->pHeartbeatTimer, pDevExt->cNsHeartbeatInterval, 0 /*fFlags*/, + (PFNRTTIMER)vgdrvHeartbeatTimerHandler, pDevExt); + if (RT_SUCCESS(rc)) + { + rc = RTTimerStart(pDevExt->pHeartbeatTimer, 0); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + LogRel(("vgdrvHeartbeatInit: Heartbeat timer failed to start, rc=%Rrc\n", rc)); + } + else + LogRel(("vgdrvHeartbeatInit: Failed to create heartbeat timer: %Rrc\n", rc)); + + VbglR0GRFree(pDevExt->pReqGuestHeartbeat); + pDevExt->pReqGuestHeartbeat = NULL; + } + else + LogRel(("vgdrvHeartbeatInit: VbglR0GRAlloc(VMMDevReq_GuestHeartbeat): %Rrc\n", rc)); + + LogRel(("vgdrvHeartbeatInit: Failed to set up the timer, guest heartbeat is disabled\n")); + vgdrvHeartbeatHostConfigure(pDevExt, false); + } + else + LogRel(("vgdrvHeartbeatInit: Failed to configure host for heartbeat checking: rc=%Rrc\n", rc)); + } + return rc; +} + +/** @} */ + + +/** + * Helper to reinit the VMMDev communication after hibernation. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param enmOSType The OS type. + * + * @todo Call this on all platforms, not just windows. + */ +int VGDrvCommonReinitDevExtAfterHibernation(PVBOXGUESTDEVEXT pDevExt, VBOXOSTYPE enmOSType) +{ + int rc = vgdrvReportGuestInfo(enmOSType); + if (RT_SUCCESS(rc)) + { + rc = vgdrvReportDriverStatus(true /* Driver is active */); + if (RT_FAILURE(rc)) + Log(("VGDrvCommonReinitDevExtAfterHibernation: could not report guest driver status, rc=%Rrc\n", rc)); + } + else + Log(("VGDrvCommonReinitDevExtAfterHibernation: could not report guest information to host, rc=%Rrc\n", rc)); + LogFlow(("VGDrvCommonReinitDevExtAfterHibernation: returned with rc=%Rrc\n", rc)); + RT_NOREF1(pDevExt); + return rc; +} + + +/** + * Initializes the release logger (debug is implicit), if configured. + * + * @returns IPRT status code. + */ +int VGDrvCommonInitLoggers(void) +{ +#ifdef VBOX_GUESTDRV_WITH_RELEASE_LOGGER + /* + * Create the release log. + */ + static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; + PRTLOGGER pRelLogger; + int rc = RTLogCreate(&pRelLogger, 0 /*fFlags*/, "all", "VBOXGUEST_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups, + RTLOGDEST_STDOUT | RTLOGDEST_DEBUGGER, NULL); + if (RT_SUCCESS(rc)) + RTLogRelSetDefaultInstance(pRelLogger); + /** @todo Add native hook for getting logger config parameters and setting + * them. On linux we should use the module parameter stuff... */ + return rc; +#else + return VINF_SUCCESS; +#endif +} + + +/** + * Destroys the loggers. + */ +void VGDrvCommonDestroyLoggers(void) +{ +#ifdef VBOX_GUESTDRV_WITH_RELEASE_LOGGER + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogDestroy(RTLogSetDefaultInstance(NULL)); +#endif +} + + +/** + * Initialize the device extension fundament. + * + * There are no device resources at this point, VGDrvCommonInitDevExtResources + * should be called when they are available. + * + * @returns VBox status code. + * @param pDevExt The device extension to init. + */ +int VGDrvCommonInitDevExtFundament(PVBOXGUESTDEVEXT pDevExt) +{ + int rc; + AssertMsg( pDevExt->uInitState != VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT + && pDevExt->uInitState != VBOXGUESTDEVEXT_INIT_STATE_RESOURCES, ("uInitState=%#x\n", pDevExt->uInitState)); + + /* + * Initialize the data. + */ + pDevExt->IOPortBase = UINT16_MAX; + pDevExt->pVMMDevMemory = NULL; + pDevExt->hGuestMappings = NIL_RTR0MEMOBJ; + pDevExt->EventSpinlock = NIL_RTSPINLOCK; + pDevExt->fHostFeatures = 0; + pDevExt->pIrqAckEvents = NULL; + pDevExt->PhysIrqAckEvents = NIL_RTCCPHYS; + RTListInit(&pDevExt->WaitList); +#ifdef VBOX_WITH_HGCM + RTListInit(&pDevExt->HGCMWaitList); +#endif +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP + RTListInit(&pDevExt->WakeUpList); +#endif + RTListInit(&pDevExt->WokenUpList); + RTListInit(&pDevExt->FreeList); + RTListInit(&pDevExt->SessionList); + pDevExt->cSessions = 0; + pDevExt->fLoggingEnabled = false; + pDevExt->f32PendingEvents = 0; + pDevExt->u32MousePosChangedSeq = 0; + pDevExt->SessionSpinlock = NIL_RTSPINLOCK; + pDevExt->MemBalloon.hMtx = NIL_RTSEMFASTMUTEX; + pDevExt->MemBalloon.cChunks = 0; + pDevExt->MemBalloon.cMaxChunks = 0; + pDevExt->MemBalloon.fUseKernelAPI = true; + pDevExt->MemBalloon.paMemObj = NULL; + pDevExt->MemBalloon.pOwner = NULL; + pDevExt->pfnMouseNotifyCallback = NULL; + pDevExt->pvMouseNotifyCallbackArg = NULL; + pDevExt->pReqGuestHeartbeat = NULL; + + pDevExt->fFixedEvents = 0; + vgdrvBitUsageTrackerClear(&pDevExt->EventFilterTracker); + pDevExt->fEventFilterHost = UINT32_MAX; /* forces a report */ + + vgdrvBitUsageTrackerClear(&pDevExt->MouseStatusTracker); + pDevExt->fMouseStatusHost = UINT32_MAX; /* forces a report */ + + pDevExt->fAcquireModeGuestCaps = 0; + pDevExt->fSetModeGuestCaps = 0; + pDevExt->fAcquiredGuestCaps = 0; + vgdrvBitUsageTrackerClear(&pDevExt->SetGuestCapsTracker); + pDevExt->fGuestCapsHost = UINT32_MAX; /* forces a report */ + + /* + * Create the wait and session spinlocks as well as the ballooning mutex. + */ + rc = RTSpinlockCreate(&pDevExt->EventSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxGuestEvent"); + if (RT_SUCCESS(rc)) + { + rc = RTSpinlockCreate(&pDevExt->SessionSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxGuestSession"); + if (RT_SUCCESS(rc)) + { + rc = RTSemFastMutexCreate(&pDevExt->MemBalloon.hMtx); + if (RT_SUCCESS(rc)) + { + pDevExt->uInitState = VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT; + return VINF_SUCCESS; + } + + LogRel(("VGDrvCommonInitDevExt: failed to create mutex, rc=%Rrc!\n", rc)); + RTSpinlockDestroy(pDevExt->SessionSpinlock); + } + else + LogRel(("VGDrvCommonInitDevExt: failed to create spinlock, rc=%Rrc!\n", rc)); + RTSpinlockDestroy(pDevExt->EventSpinlock); + } + else + LogRel(("VGDrvCommonInitDevExt: failed to create spinlock, rc=%Rrc!\n", rc)); + + pDevExt->uInitState = 0; + return rc; +} + + +/** + * Counter to VGDrvCommonInitDevExtFundament. + * + * @param pDevExt The device extension. + */ +void VGDrvCommonDeleteDevExtFundament(PVBOXGUESTDEVEXT pDevExt) +{ + int rc2; + AssertMsgReturnVoid(pDevExt->uInitState == VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT, ("uInitState=%#x\n", pDevExt->uInitState)); + pDevExt->uInitState = VBOXGUESTDEVEXT_INIT_STATE_DELETED; + + rc2 = RTSemFastMutexDestroy(pDevExt->MemBalloon.hMtx); AssertRC(rc2); + rc2 = RTSpinlockDestroy(pDevExt->EventSpinlock); AssertRC(rc2); + rc2 = RTSpinlockDestroy(pDevExt->SessionSpinlock); AssertRC(rc2); +} + + +/** + * Initializes the VBoxGuest device extension resource parts. + * + * The native code locates the VMMDev on the PCI bus and retrieve the MMIO and + * I/O port ranges, this function will take care of mapping the MMIO memory (if + * present). Upon successful return the native code should set up the interrupt + * handler. + * + * @returns VBox status code. + * + * @param pDevExt The device extension. Allocated by the native code. + * @param IOPortBase The base of the I/O port range. + * @param pvMMIOBase The base of the MMIO memory mapping. + * This is optional, pass NULL if not present. + * @param cbMMIO The size of the MMIO memory mapping. + * This is optional, pass 0 if not present. + * @param enmOSType The guest OS type to report to the VMMDev. + * @param fFixedEvents Events that will be enabled upon init and no client + * will ever be allowed to mask. + */ +int VGDrvCommonInitDevExtResources(PVBOXGUESTDEVEXT pDevExt, uint16_t IOPortBase, + void *pvMMIOBase, uint32_t cbMMIO, VBOXOSTYPE enmOSType, uint32_t fFixedEvents) +{ + int rc; + AssertMsgReturn(pDevExt->uInitState == VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT, ("uInitState=%#x\n", pDevExt->uInitState), + VERR_INVALID_STATE); + + /* + * If there is an MMIO region validate the version and size. + */ + if (pvMMIOBase) + { + VMMDevMemory *pVMMDev = (VMMDevMemory *)pvMMIOBase; + Assert(cbMMIO); + if ( pVMMDev->u32Version == VMMDEV_MEMORY_VERSION + && pVMMDev->u32Size >= 32 + && pVMMDev->u32Size <= cbMMIO) + { + pDevExt->pVMMDevMemory = pVMMDev; + Log(("VGDrvCommonInitDevExtResources: VMMDevMemory: mapping=%p size=%#RX32 (%#RX32) version=%#RX32\n", + pVMMDev, pVMMDev->u32Size, cbMMIO, pVMMDev->u32Version)); + } + else /* try live without it. */ + LogRel(("VGDrvCommonInitDevExtResources: Bogus VMMDev memory; u32Version=%RX32 (expected %RX32) u32Size=%RX32 (expected <= %RX32)\n", + pVMMDev->u32Version, VMMDEV_MEMORY_VERSION, pVMMDev->u32Size, cbMMIO)); + } + + /* + * Initialize the guest library and report the guest info back to VMMDev, + * set the interrupt control filter mask, and fixate the guest mappings + * made by the VMM. + */ + pDevExt->IOPortBase = IOPortBase; + rc = VbglR0InitPrimary(pDevExt->IOPortBase, (VMMDevMemory *)pDevExt->pVMMDevMemory, &pDevExt->fHostFeatures); + if (RT_SUCCESS(rc)) + { + VMMDevRequestHeader *pAckReq = NULL; + rc = VbglR0GRAlloc(&pAckReq, sizeof(VMMDevEvents), VMMDevReq_AcknowledgeEvents); + if (RT_SUCCESS(rc)) + { + pDevExt->PhysIrqAckEvents = VbglR0PhysHeapGetPhysAddr(pAckReq); + Assert(pDevExt->PhysIrqAckEvents != 0); + ASMCompilerBarrier(); /* linux + solaris already have IRQs hooked up at this point, so take care. */ + pDevExt->pIrqAckEvents = (VMMDevEvents *)pAckReq; + + rc = vgdrvReportGuestInfo(enmOSType); + if (RT_SUCCESS(rc)) + { + /* + * Set the fixed event and make sure the host doesn't have any lingering + * the guest capabilities or mouse status bits set. + */ +#ifdef VBOX_WITH_HGCM + fFixedEvents |= VMMDEV_EVENT_HGCM; +#endif + pDevExt->fFixedEvents = fFixedEvents; + rc = vgdrvResetEventFilterOnHost(pDevExt, fFixedEvents); + if (RT_SUCCESS(rc)) + { + rc = vgdrvResetCapabilitiesOnHost(pDevExt); + if (RT_SUCCESS(rc)) + { + rc = vgdrvResetMouseStatusOnHost(pDevExt); + if (RT_SUCCESS(rc)) + { + /* + * Initialize stuff which may fail without requiring the driver init to fail. + */ + vgdrvInitFixateGuestMappings(pDevExt); + vgdrvHeartbeatInit(pDevExt); + + /* + * Done! + */ + rc = vgdrvReportDriverStatus(true /* Driver is active */); + if (RT_FAILURE(rc)) + LogRel(("VGDrvCommonInitDevExtResources: VBoxReportGuestDriverStatus failed, rc=%Rrc\n", rc)); + + pDevExt->uInitState = VBOXGUESTDEVEXT_INIT_STATE_RESOURCES; + LogFlowFunc(("VGDrvCommonInitDevExtResources: returns success\n")); + return VINF_SUCCESS; + } + LogRel(("VGDrvCommonInitDevExtResources: failed to clear mouse status: rc=%Rrc\n", rc)); + } + else + LogRel(("VGDrvCommonInitDevExtResources: failed to clear guest capabilities: rc=%Rrc\n", rc)); + } + else + LogRel(("VGDrvCommonInitDevExtResources: failed to set fixed event filter: rc=%Rrc\n", rc)); + pDevExt->fFixedEvents = 0; + } + else + LogRel(("VGDrvCommonInitDevExtResources: vgdrvReportGuestInfo failed: rc=%Rrc\n", rc)); + VbglR0GRFree((VMMDevRequestHeader *)pDevExt->pIrqAckEvents); + } + else + LogRel(("VGDrvCommonInitDevExtResources: VbglR0GRAlloc failed: rc=%Rrc\n", rc)); + + VbglR0TerminatePrimary(); + } + else + LogRel(("VGDrvCommonInitDevExtResources: VbglR0InitPrimary failed: rc=%Rrc\n", rc)); + pDevExt->IOPortBase = UINT16_MAX; + return rc; +} + + +/** + * Deletes all the items in a wait chain. + * @param pList The head of the chain. + */ +static void vgdrvDeleteWaitList(PRTLISTNODE pList) +{ + while (!RTListIsEmpty(pList)) + { + int rc2; + PVBOXGUESTWAIT pWait = RTListGetFirst(pList, VBOXGUESTWAIT, ListNode); + RTListNodeRemove(&pWait->ListNode); + + rc2 = RTSemEventMultiDestroy(pWait->Event); AssertRC(rc2); + pWait->Event = NIL_RTSEMEVENTMULTI; + pWait->pSession = NULL; + RTMemFree(pWait); + } +} + + +/** + * Counter to VGDrvCommonInitDevExtResources. + * + * @param pDevExt The device extension. + */ +void VGDrvCommonDeleteDevExtResources(PVBOXGUESTDEVEXT pDevExt) +{ + Log(("VGDrvCommonDeleteDevExtResources:\n")); + AssertMsgReturnVoid(pDevExt->uInitState == VBOXGUESTDEVEXT_INIT_STATE_RESOURCES, ("uInitState=%#x\n", pDevExt->uInitState)); + pDevExt->uInitState = VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT; + + /* + * Stop and destroy HB timer and disable host heartbeat checking. + */ + if (pDevExt->pHeartbeatTimer) + { + RTTimerDestroy(pDevExt->pHeartbeatTimer); + vgdrvHeartbeatHostConfigure(pDevExt, false); + } + + VbglR0GRFree(pDevExt->pReqGuestHeartbeat); + pDevExt->pReqGuestHeartbeat = NULL; + + /* + * Clean up the bits that involves the host first. + */ + vgdrvTermUnfixGuestMappings(pDevExt); + if (!RTListIsEmpty(&pDevExt->SessionList)) + { + LogRelFunc(("session list not empty!\n")); + RTListInit(&pDevExt->SessionList); + } + + /* + * Update the host flags (mouse status etc) not to reflect this session. + */ + pDevExt->fFixedEvents = 0; + vgdrvResetEventFilterOnHost(pDevExt, 0 /*fFixedEvents*/); + vgdrvResetCapabilitiesOnHost(pDevExt); + vgdrvResetMouseStatusOnHost(pDevExt); + + vgdrvCloseMemBalloon(pDevExt, (PVBOXGUESTSESSION)NULL); + + /* + * No more IRQs. + */ + pDevExt->pIrqAckEvents = NULL; /* Will be freed by VbglR0TerminatePrimary. */ + ASMAtomicWriteU32(&pDevExt->fHostFeatures, 0); + + /* + * Cleanup all the other resources. + */ + vgdrvDeleteWaitList(&pDevExt->WaitList); +#ifdef VBOX_WITH_HGCM + vgdrvDeleteWaitList(&pDevExt->HGCMWaitList); +#endif +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP + vgdrvDeleteWaitList(&pDevExt->WakeUpList); +#endif + vgdrvDeleteWaitList(&pDevExt->WokenUpList); + vgdrvDeleteWaitList(&pDevExt->FreeList); + + VbglR0TerminatePrimary(); + + + pDevExt->pVMMDevMemory = NULL; + pDevExt->IOPortBase = 0; +} + + +/** + * Initializes the VBoxGuest device extension when the device driver is loaded. + * + * The native code locates the VMMDev on the PCI bus and retrieve the MMIO and + * I/O port ranges, this function will take care of mapping the MMIO memory (if + * present). Upon successful return the native code should set up the interrupt + * handler. + * + * Instead of calling this method, the host specific code choose to perform a + * more granular initialization using: + * 1. VGDrvCommonInitLoggers + * 2. VGDrvCommonInitDevExtFundament + * 3. VGDrvCommonInitDevExtResources + * + * @returns VBox status code. + * + * @param pDevExt The device extension. Allocated by the native code. + * @param IOPortBase The base of the I/O port range. + * @param pvMMIOBase The base of the MMIO memory mapping. + * This is optional, pass NULL if not present. + * @param cbMMIO The size of the MMIO memory mapping. + * This is optional, pass 0 if not present. + * @param enmOSType The guest OS type to report to the VMMDev. + * @param fFixedEvents Events that will be enabled upon init and no client + * will ever be allowed to mask. + */ +int VGDrvCommonInitDevExt(PVBOXGUESTDEVEXT pDevExt, uint16_t IOPortBase, + void *pvMMIOBase, uint32_t cbMMIO, VBOXOSTYPE enmOSType, uint32_t fFixedEvents) +{ + int rc; + VGDrvCommonInitLoggers(); + + rc = VGDrvCommonInitDevExtFundament(pDevExt); + if (RT_SUCCESS(rc)) + { + rc = VGDrvCommonInitDevExtResources(pDevExt, IOPortBase, pvMMIOBase, cbMMIO, enmOSType, fFixedEvents); + if (RT_SUCCESS(rc)) + return rc; + + VGDrvCommonDeleteDevExtFundament(pDevExt); + } + VGDrvCommonDestroyLoggers(); + return rc; /* (failed) */ +} + + +/** + * Checks if the given option can be taken to not mean 'false'. + * + * @returns true or false accordingly. + * @param pszValue The value to consider. + */ +bool VBDrvCommonIsOptionValueTrue(const char *pszValue) +{ + if (pszValue) + { + char ch; + while ( (ch = *pszValue) != '\0' + && RT_C_IS_SPACE(ch)) + pszValue++; + + return ch != '\0' + && ch != 'n' /* no */ + && ch != 'N' /* NO */ + && ch != 'd' /* disabled */ + && ch != 'f' /* false*/ + && ch != 'F' /* FALSE */ + && ch != 'D' /* DISABLED */ + && ( (ch != 'o' && ch != 'O') /* off, OFF, Off */ + || (pszValue[1] != 'f' && pszValue[1] != 'F') ) + && (ch != '0' || pszValue[1] != '\0') /* '0' */ + ; + } + return false; +} + + +/** + * Processes a option. + * + * This will let the OS specific code have a go at it too. + * + * @param pDevExt The device extension. + * @param pszName The option name, sans prefix. + * @param pszValue The option value. + */ +void VGDrvCommonProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue) +{ + Log(("VGDrvCommonProcessOption: pszName='%s' pszValue='%s'\n", pszName, pszValue)); + + if ( RTStrICmpAscii(pszName, "r3_log_to_host") == 0 + || RTStrICmpAscii(pszName, "LoggingEnabled") == 0 /*legacy*/ ) + pDevExt->fLoggingEnabled = VBDrvCommonIsOptionValueTrue(pszValue); + else if ( RTStrNICmpAscii(pszName, RT_STR_TUPLE("log")) == 0 + || RTStrNICmpAscii(pszName, RT_STR_TUPLE("dbg_log")) == 0) + { + bool const fDbgRel = *pszName == 'd' || *pszName == 'D'; + const char *pszSubName = &pszName[fDbgRel ? 4 + 3 : 3]; + if ( !*pszSubName + || RTStrICmpAscii(pszSubName, "_flags") == 0 + || RTStrICmpAscii(pszSubName, "_dest") == 0) + { + PRTLOGGER pLogger = !fDbgRel ? RTLogRelGetDefaultInstance() : RTLogDefaultInstance(); + if (pLogger) + { + if (!*pszSubName) + RTLogGroupSettings(pLogger, pszValue); + else if (RTStrICmpAscii(pszSubName, "_flags")) + RTLogFlags(pLogger, pszValue); + else + RTLogDestinations(pLogger, pszValue); + } + } + else if (!VGDrvNativeProcessOption(pDevExt, pszName, pszValue)) + LogRel(("VBoxGuest: Ignoring unknown option '%s' (value '%s')\n", pszName, pszValue)); + } + else if (!VGDrvNativeProcessOption(pDevExt, pszName, pszValue)) + LogRel(("VBoxGuest: Ignoring unknown option '%s' (value '%s')\n", pszName, pszValue)); +} + + +/** + * Read driver configuration from the host. + * + * This involves connecting to the guest properties service, which means that + * interrupts needs to work and that the calling thread must be able to block. + * + * @param pDevExt The device extension. + */ +void VGDrvCommonProcessOptionsFromHost(PVBOXGUESTDEVEXT pDevExt) +{ + /* + * Create a kernel session without our selves, then connect to the HGCM service. + */ + PVBOXGUESTSESSION pSession; + int rc = VGDrvCommonCreateKernelSession(pDevExt, &pSession); + if (RT_SUCCESS(rc)) + { + union + { + VBGLIOCHGCMCONNECT Connect; + VBGLIOCHGCMDISCONNECT Disconnect; + GuestPropMsgEnumProperties EnumMsg; + } uBuf; + + RT_ZERO(uBuf.Connect); + VBGLREQHDR_INIT(&uBuf.Connect.Hdr, HGCM_CONNECT); + uBuf.Connect.u.In.Loc.type = VMMDevHGCMLoc_LocalHost_Existing; + RTStrCopy(uBuf.Connect.u.In.Loc.u.host.achName, sizeof(uBuf.Connect.u.In.Loc.u.host.achName), + "VBoxGuestPropSvc"); /** @todo Add a define to the header for the name. */ + rc = VGDrvCommonIoCtl(VBGL_IOCTL_HGCM_CONNECT, pDevExt, pSession, &uBuf.Connect.Hdr, sizeof(uBuf.Connect)); + if (RT_SUCCESS(rc)) + { + static const char g_szzPattern[] = "/VirtualBox/GuestAdd/VBoxGuest/*\0"; + uint32_t const idClient = uBuf.Connect.u.Out.idClient; + char *pszzStrings = NULL; + uint32_t cbStrings; + + /* + * Enumerate all the relevant properties. We try with a 1KB buffer, but + * will double it until we get what we want or go beyond 16KB. + */ + for (cbStrings = _1K; cbStrings <= _16K; cbStrings *= 2) + { + pszzStrings = (char *)RTMemAllocZ(cbStrings); + if (pszzStrings) + { + VBGL_HGCM_HDR_INIT(&uBuf.EnumMsg.hdr, idClient, GUEST_PROP_FN_ENUM_PROPS, 3); + + uBuf.EnumMsg.patterns.type = VMMDevHGCMParmType_LinAddr; + uBuf.EnumMsg.patterns.u.Pointer.size = sizeof(g_szzPattern); + uBuf.EnumMsg.patterns.u.Pointer.u.linearAddr = (uintptr_t)g_szzPattern; + + uBuf.EnumMsg.strings.type = VMMDevHGCMParmType_LinAddr; + uBuf.EnumMsg.strings.u.Pointer.size = cbStrings; + uBuf.EnumMsg.strings.u.Pointer.u.linearAddr = (uintptr_t)pszzStrings; + + uBuf.EnumMsg.size.type = VMMDevHGCMParmType_32bit; + uBuf.EnumMsg.size.u.value32 = 0; + + rc = VGDrvCommonIoCtl(VBGL_IOCTL_HGCM_CALL(sizeof(uBuf.EnumMsg)), pDevExt, pSession, + &uBuf.EnumMsg.hdr.Hdr, sizeof(uBuf.EnumMsg)); + if (RT_SUCCESS(rc)) + { + if ( uBuf.EnumMsg.size.type == VMMDevHGCMParmType_32bit + && uBuf.EnumMsg.size.u.value32 <= cbStrings + && uBuf.EnumMsg.size.u.value32 > 0) + cbStrings = uBuf.EnumMsg.size.u.value32; + Log(("VGDrvCommonReadConfigurationFromHost: GUEST_PROP_FN_ENUM_PROPS -> %#x bytes (cbStrings=%#x)\n", + uBuf.EnumMsg.size.u.value32, cbStrings)); + break; + } + + RTMemFree(pszzStrings); + pszzStrings = NULL; + } + else + { + LogRel(("VGDrvCommonReadConfigurationFromHost: failed to allocate %#x bytes\n", cbStrings)); + break; + } + } + + /* + * Disconnect and destroy the session. + */ + VBGLREQHDR_INIT(&uBuf.Disconnect.Hdr, HGCM_DISCONNECT); + uBuf.Disconnect.u.In.idClient = idClient; + VGDrvCommonIoCtl(VBGL_IOCTL_HGCM_DISCONNECT, pDevExt, pSession, &uBuf.Disconnect.Hdr, sizeof(uBuf.Disconnect)); + + VGDrvCommonCloseSession(pDevExt, pSession); + + /* + * Process the properties if we got any. + * + * The string buffer contains packed strings in groups of four - name, value, + * timestamp (as a decimal string) and flags. It is terminated by four empty + * strings. Layout: + * Name\0Value\0Timestamp\0Flags\0 + */ + if (pszzStrings) + { + uint32_t off; + for (off = 0; off < cbStrings; off++) + { + /* + * Parse the four fields, checking that it's all plain ASCII w/o any control characters. + */ + const char *apszFields[4] = { NULL, NULL, NULL, NULL }; + bool fValidFields = true; + unsigned iField; + for (iField = 0; iField < RT_ELEMENTS(apszFields); iField++) + { + apszFields[0] = &pszzStrings[off]; + while (off < cbStrings) + { + char ch = pszzStrings[off++]; + if ((unsigned)ch < 0x20U || (unsigned)ch > 0x7fU) + { + if (!ch) + break; + if (fValidFields) + Log(("VGDrvCommonReadConfigurationFromHost: Invalid char %#x at %#x (field %u)\n", + ch, off - 1, iField)); + fValidFields = false; + } + } + } + if ( off <= cbStrings + && fValidFields + && *apszFields[0] != '\0') + { + /* + * Validate and convert the flags to integer, then process the option. + */ + uint32_t fFlags = 0; + rc = GuestPropValidateFlags(apszFields[3], &fFlags); + if (RT_SUCCESS(rc)) + { + if (fFlags & GUEST_PROP_F_RDONLYGUEST) + { + apszFields[0] += sizeof(g_szzPattern) - 2; + VGDrvCommonProcessOption(pDevExt, apszFields[0], apszFields[1]); + } + else + LogRel(("VBoxGuest: Ignoring '%s' as it does not have RDONLYGUEST set\n", apszFields[0])); + } + else + LogRel(("VBoxGuest: Invalid flags '%s' for '%s': %Rrc\n", apszFields[2], apszFields[0], rc)); + } + else if (off < cbStrings) + { + LogRel(("VBoxGuest: Malformed guest properties enum result!\n")); + Log(("VBoxGuest: off=%#x cbStrings=%#x\n%.*Rhxd\n", off, cbStrings, cbStrings, pszzStrings)); + break; + } + else if (!fValidFields) + LogRel(("VBoxGuest: Ignoring %.*Rhxs as it has invalid characters in one or more fields\n", + (int)strlen(apszFields[0]), apszFields[0])); + else + break; + } + + RTMemFree(pszzStrings); + } + else + LogRel(("VGDrvCommonReadConfigurationFromHost: failed to enumerate '%s': %Rrc\n", g_szzPattern, rc)); + + } + else + LogRel(("VGDrvCommonReadConfigurationFromHost: failed to connect: %Rrc\n", rc)); + } + else + LogRel(("VGDrvCommonReadConfigurationFromHost: failed to connect: %Rrc\n", rc)); +} + + +/** + * Destroys the VBoxGuest device extension. + * + * The native code should call this before the driver is unloaded, + * but don't call this on shutdown. + * + * @param pDevExt The device extension. + */ +void VGDrvCommonDeleteDevExt(PVBOXGUESTDEVEXT pDevExt) +{ + Log(("VGDrvCommonDeleteDevExt:\n")); + Log(("VBoxGuest: The additions driver is terminating.\n")); + VGDrvCommonDeleteDevExtResources(pDevExt); + VGDrvCommonDeleteDevExtFundament(pDevExt); + VGDrvCommonDestroyLoggers(); +} + + +/** + * Creates a VBoxGuest user session. + * + * The native code calls this when a ring-3 client opens the device. + * Use VGDrvCommonCreateKernelSession when a ring-0 client connects. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param fRequestor VMMDEV_REQUESTOR_XXX. + * @param ppSession Where to store the session on success. + */ +int VGDrvCommonCreateUserSession(PVBOXGUESTDEVEXT pDevExt, uint32_t fRequestor, PVBOXGUESTSESSION *ppSession) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)RTMemAllocZ(sizeof(*pSession)); + if (RT_UNLIKELY(!pSession)) + { + LogRel(("VGDrvCommonCreateUserSession: no memory!\n")); + return VERR_NO_MEMORY; + } + + pSession->Process = RTProcSelf(); + pSession->R0Process = RTR0ProcHandleSelf(); + pSession->pDevExt = pDevExt; + pSession->fRequestor = fRequestor; + pSession->fUserSession = RT_BOOL(fRequestor & VMMDEV_REQUESTOR_USER_DEVICE); + RTSpinlockAcquire(pDevExt->SessionSpinlock); + RTListAppend(&pDevExt->SessionList, &pSession->ListNode); + pDevExt->cSessions++; + RTSpinlockRelease(pDevExt->SessionSpinlock); + + *ppSession = pSession; + LogFlow(("VGDrvCommonCreateUserSession: pSession=%p proc=%RTproc (%d) r0proc=%p\n", + pSession, pSession->Process, (int)pSession->Process, (uintptr_t)pSession->R0Process)); /** @todo %RTr0proc */ + return VINF_SUCCESS; +} + + +/** + * Creates a VBoxGuest kernel session. + * + * The native code calls this when a ring-0 client connects to the device. + * Use VGDrvCommonCreateUserSession when a ring-3 client opens the device. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param ppSession Where to store the session on success. + */ +int VGDrvCommonCreateKernelSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION *ppSession) +{ + PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)RTMemAllocZ(sizeof(*pSession)); + if (RT_UNLIKELY(!pSession)) + { + LogRel(("VGDrvCommonCreateKernelSession: no memory!\n")); + return VERR_NO_MEMORY; + } + + pSession->Process = NIL_RTPROCESS; + pSession->R0Process = NIL_RTR0PROCESS; + pSession->pDevExt = pDevExt; + pSession->fRequestor = VMMDEV_REQUESTOR_KERNEL | VMMDEV_REQUESTOR_USR_DRV_OTHER + | VMMDEV_REQUESTOR_CON_DONT_KNOW | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN; + RTSpinlockAcquire(pDevExt->SessionSpinlock); + RTListAppend(&pDevExt->SessionList, &pSession->ListNode); + pDevExt->cSessions++; + RTSpinlockRelease(pDevExt->SessionSpinlock); + + *ppSession = pSession; + LogFlow(("VGDrvCommonCreateKernelSession: pSession=%p proc=%RTproc (%d) r0proc=%p\n", + pSession, pSession->Process, (int)pSession->Process, (uintptr_t)pSession->R0Process)); /** @todo %RTr0proc */ + return VINF_SUCCESS; +} + + +/** + * Closes a VBoxGuest session. + * + * @param pDevExt The device extension. + * @param pSession The session to close (and free). + */ +void VGDrvCommonCloseSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession) +{ +#ifdef VBOX_WITH_HGCM + unsigned i; +#endif + LogFlow(("VGDrvCommonCloseSession: pSession=%p proc=%RTproc (%d) r0proc=%p\n", + pSession, pSession->Process, (int)pSession->Process, (uintptr_t)pSession->R0Process)); /** @todo %RTr0proc */ + + RTSpinlockAcquire(pDevExt->SessionSpinlock); + RTListNodeRemove(&pSession->ListNode); + pDevExt->cSessions--; + RTSpinlockRelease(pDevExt->SessionSpinlock); + vgdrvAcquireSessionCapabilities(pDevExt, pSession, 0, UINT32_MAX, VBGL_IOC_AGC_FLAGS_DEFAULT, true /*fSessionTermination*/); + vgdrvSetSessionCapabilities(pDevExt, pSession, 0 /*fOrMask*/, UINT32_MAX /*fNotMask*/, + NULL /*pfSessionCaps*/, NULL /*pfGlobalCaps*/, true /*fSessionTermination*/); + vgdrvSetSessionEventFilter(pDevExt, pSession, 0 /*fOrMask*/, UINT32_MAX /*fNotMask*/, true /*fSessionTermination*/); + vgdrvSetSessionMouseStatus(pDevExt, pSession, 0 /*fOrMask*/, UINT32_MAX /*fNotMask*/, true /*fSessionTermination*/); + + vgdrvIoCtl_CancelAllWaitEvents(pDevExt, pSession); + +#ifdef VBOX_WITH_HGCM + for (i = 0; i < RT_ELEMENTS(pSession->aHGCMClientIds); i++) + if (pSession->aHGCMClientIds[i]) + { + uint32_t idClient = pSession->aHGCMClientIds[i]; + pSession->aHGCMClientIds[i] = 0; + Log(("VGDrvCommonCloseSession: disconnecting client id %#RX32\n", idClient)); + VbglR0HGCMInternalDisconnect(idClient, VMMDEV_REQUESTOR_KERNEL | VMMDEV_REQUESTOR_USR_DRV, + vgdrvHgcmAsyncWaitCallback, pDevExt, RT_INDEFINITE_WAIT); + } +#endif + + pSession->pDevExt = NULL; + pSession->Process = NIL_RTPROCESS; + pSession->R0Process = NIL_RTR0PROCESS; + vgdrvCloseMemBalloon(pDevExt, pSession); + RTMemFree(pSession); +} + + +/** + * Allocates a wait-for-event entry. + * + * @returns The wait-for-event entry. + * @param pDevExt The device extension. + * @param pSession The session that's allocating this. Can be NULL. + */ +static PVBOXGUESTWAIT vgdrvWaitAlloc(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession) +{ + /* + * Allocate it one way or the other. + */ + PVBOXGUESTWAIT pWait = RTListGetFirst(&pDevExt->FreeList, VBOXGUESTWAIT, ListNode); + if (pWait) + { + RTSpinlockAcquire(pDevExt->EventSpinlock); + + pWait = RTListGetFirst(&pDevExt->FreeList, VBOXGUESTWAIT, ListNode); + if (pWait) + RTListNodeRemove(&pWait->ListNode); + + RTSpinlockRelease(pDevExt->EventSpinlock); + } + if (!pWait) + { + int rc; + + pWait = (PVBOXGUESTWAIT)RTMemAlloc(sizeof(*pWait)); + if (!pWait) + { + LogRelMax(32, ("vgdrvWaitAlloc: out-of-memory!\n")); + return NULL; + } + + rc = RTSemEventMultiCreate(&pWait->Event); + if (RT_FAILURE(rc)) + { + LogRelMax(32, ("vgdrvWaitAlloc: RTSemEventMultiCreate failed with rc=%Rrc!\n", rc)); + RTMemFree(pWait); + return NULL; + } + + pWait->ListNode.pNext = NULL; + pWait->ListNode.pPrev = NULL; + } + + /* + * Zero members just as an precaution. + */ + pWait->fReqEvents = 0; + pWait->fResEvents = 0; +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP + pWait->fPendingWakeUp = false; + pWait->fFreeMe = false; +#endif + pWait->pSession = pSession; +#ifdef VBOX_WITH_HGCM + pWait->pHGCMReq = NULL; +#endif + RTSemEventMultiReset(pWait->Event); + return pWait; +} + + +/** + * Frees the wait-for-event entry. + * + * The caller must own the wait spinlock ! + * The entry must be in a list! + * + * @param pDevExt The device extension. + * @param pWait The wait-for-event entry to free. + */ +static void vgdrvWaitFreeLocked(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTWAIT pWait) +{ + pWait->fReqEvents = 0; + pWait->fResEvents = 0; +#ifdef VBOX_WITH_HGCM + pWait->pHGCMReq = NULL; +#endif +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP + Assert(!pWait->fFreeMe); + if (pWait->fPendingWakeUp) + pWait->fFreeMe = true; + else +#endif + { + RTListNodeRemove(&pWait->ListNode); + RTListAppend(&pDevExt->FreeList, &pWait->ListNode); + } +} + + +/** + * Frees the wait-for-event entry. + * + * @param pDevExt The device extension. + * @param pWait The wait-for-event entry to free. + */ +static void vgdrvWaitFreeUnlocked(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTWAIT pWait) +{ + RTSpinlockAcquire(pDevExt->EventSpinlock); + vgdrvWaitFreeLocked(pDevExt, pWait); + RTSpinlockRelease(pDevExt->EventSpinlock); +} + + +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP +/** + * Processes the wake-up list. + * + * All entries in the wake-up list gets signalled and moved to the woken-up + * list. + * At least on Windows this function can be invoked concurrently from + * different VCPUs. So, be thread-safe. + * + * @param pDevExt The device extension. + */ +void VGDrvCommonWaitDoWakeUps(PVBOXGUESTDEVEXT pDevExt) +{ + if (!RTListIsEmpty(&pDevExt->WakeUpList)) + { + RTSpinlockAcquire(pDevExt->EventSpinlock); + for (;;) + { + int rc; + PVBOXGUESTWAIT pWait = RTListGetFirst(&pDevExt->WakeUpList, VBOXGUESTWAIT, ListNode); + if (!pWait) + break; + /* Prevent other threads from accessing pWait when spinlock is released. */ + RTListNodeRemove(&pWait->ListNode); + + pWait->fPendingWakeUp = true; + RTSpinlockRelease(pDevExt->EventSpinlock); + + rc = RTSemEventMultiSignal(pWait->Event); + AssertRC(rc); + + RTSpinlockAcquire(pDevExt->EventSpinlock); + Assert(pWait->ListNode.pNext == NULL && pWait->ListNode.pPrev == NULL); + RTListAppend(&pDevExt->WokenUpList, &pWait->ListNode); + pWait->fPendingWakeUp = false; + if (RT_LIKELY(!pWait->fFreeMe)) + { /* likely */ } + else + { + pWait->fFreeMe = false; + vgdrvWaitFreeLocked(pDevExt, pWait); + } + } + RTSpinlockRelease(pDevExt->EventSpinlock); + } +} +#endif /* VBOXGUEST_USE_DEFERRED_WAKE_UP */ + + +/** + * Implements the fast (no input or output) type of IOCtls. + * + * This is currently just a placeholder stub inherited from the support driver code. + * + * @returns VBox status code. + * @param iFunction The IOCtl function number. + * @param pDevExt The device extension. + * @param pSession The session. + */ +int VGDrvCommonIoCtlFast(uintptr_t iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession) +{ + LogFlow(("VGDrvCommonIoCtlFast: iFunction=%#x pDevExt=%p pSession=%p\n", iFunction, pDevExt, pSession)); + + NOREF(iFunction); + NOREF(pDevExt); + NOREF(pSession); + return VERR_NOT_SUPPORTED; +} + + +/** + * Gets the driver I/O control interface version, maybe adjusting it for + * backwards compatibility. + * + * The adjusting is currently not implemented as we only have one major I/O + * control interface version out there to support. This is something we will + * implement as needed. + * + * returns IPRT status code. + * @param pDevExt The device extension. + * @param pSession The session. + * @param pReq The request info. + */ +static int vgdrvIoCtl_DriverVersionInfo(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCDRIVERVERSIONINFO pReq) +{ + int rc; + LogFlow(("VBGL_IOCTL_DRIVER_VERSION_INFO: uReqVersion=%#x uMinVersion=%#x uReserved1=%#x uReserved2=%#x\n", + pReq->u.In.uReqVersion, pReq->u.In.uMinVersion, pReq->u.In.uReserved1, pReq->u.In.uReserved2)); + RT_NOREF2(pDevExt, pSession); + + /* + * Input validation. + */ + if ( pReq->u.In.uMinVersion <= pReq->u.In.uReqVersion + && RT_HI_U16(pReq->u.In.uMinVersion) == RT_HI_U16(pReq->u.In.uReqVersion)) + { + /* + * Match the version. + * The current logic is very simple, match the major interface version. + */ + if ( pReq->u.In.uMinVersion <= VBGL_IOC_VERSION + && RT_HI_U16(pReq->u.In.uMinVersion) == RT_HI_U16(VBGL_IOC_VERSION)) + rc = VINF_SUCCESS; + else + { + LogRel(("VBGL_IOCTL_DRIVER_VERSION_INFO: Version mismatch. Requested: %#x Min: %#x Current: %#x\n", + pReq->u.In.uReqVersion, pReq->u.In.uMinVersion, VBGL_IOC_VERSION)); + rc = VERR_VERSION_MISMATCH; + } + } + else + { + LogRel(("VBGL_IOCTL_DRIVER_VERSION_INFO: uMinVersion=%#x uMaxVersion=%#x doesn't match!\n", + pReq->u.In.uMinVersion, pReq->u.In.uReqVersion)); + rc = VERR_INVALID_PARAMETER; + } + + pReq->u.Out.uSessionVersion = RT_SUCCESS(rc) ? VBGL_IOC_VERSION : UINT32_MAX; + pReq->u.Out.uDriverVersion = VBGL_IOC_VERSION; + pReq->u.Out.uDriverRevision = VBOX_SVN_REV; + pReq->u.Out.uReserved1 = 0; + pReq->u.Out.uReserved2 = 0; + return rc; +} + + +/** + * Similar to vgdrvIoCtl_DriverVersionInfo, except its for IDC. + * + * returns IPRT status code. + * @param pDevExt The device extension. + * @param pSession The session. + * @param pReq The request info. + */ +static int vgdrvIoCtl_IdcConnect(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCIDCCONNECT pReq) +{ + int rc; + LogFlow(("VBGL_IOCTL_IDC_CONNECT: u32MagicCookie=%#x uReqVersion=%#x uMinVersion=%#x uReserved=%#x\n", + pReq->u.In.u32MagicCookie, pReq->u.In.uReqVersion, pReq->u.In.uMinVersion, pReq->u.In.uReserved)); + Assert(pSession != NULL); + RT_NOREF(pDevExt); + + /* + * Input validation. + */ + if (pReq->u.In.u32MagicCookie == VBGL_IOCTL_IDC_CONNECT_MAGIC_COOKIE) + { + if ( pReq->u.In.uMinVersion <= pReq->u.In.uReqVersion + && RT_HI_U16(pReq->u.In.uMinVersion) == RT_HI_U16(pReq->u.In.uReqVersion)) + { + /* + * Match the version. + * The current logic is very simple, match the major interface version. + */ + if ( pReq->u.In.uMinVersion <= VBGL_IOC_VERSION + && RT_HI_U16(pReq->u.In.uMinVersion) == RT_HI_U16(VBGL_IOC_VERSION)) + { + pReq->u.Out.pvSession = pSession; + pReq->u.Out.uSessionVersion = VBGL_IOC_VERSION; + pReq->u.Out.uDriverVersion = VBGL_IOC_VERSION; + pReq->u.Out.uDriverRevision = VBOX_SVN_REV; + pReq->u.Out.uReserved1 = 0; + pReq->u.Out.pvReserved2 = NULL; + return VINF_SUCCESS; + + } + LogRel(("VBGL_IOCTL_IDC_CONNECT: Version mismatch. Requested: %#x Min: %#x Current: %#x\n", + pReq->u.In.uReqVersion, pReq->u.In.uMinVersion, VBGL_IOC_VERSION)); + rc = VERR_VERSION_MISMATCH; + } + else + { + LogRel(("VBGL_IOCTL_IDC_CONNECT: uMinVersion=%#x uMaxVersion=%#x doesn't match!\n", + pReq->u.In.uMinVersion, pReq->u.In.uReqVersion)); + rc = VERR_INVALID_PARAMETER; + } + + pReq->u.Out.pvSession = NULL; + pReq->u.Out.uSessionVersion = UINT32_MAX; + pReq->u.Out.uDriverVersion = VBGL_IOC_VERSION; + pReq->u.Out.uDriverRevision = VBOX_SVN_REV; + pReq->u.Out.uReserved1 = 0; + pReq->u.Out.pvReserved2 = NULL; + } + else + { + LogRel(("VBGL_IOCTL_IDC_CONNECT: u32MagicCookie=%#x expected %#x!\n", + pReq->u.In.u32MagicCookie, VBGL_IOCTL_IDC_CONNECT_MAGIC_COOKIE)); + rc = VERR_INVALID_PARAMETER; + } + return rc; +} + + +/** + * Counterpart to vgdrvIoCtl_IdcConnect, destroys the session. + * + * returns IPRT status code. + * @param pDevExt The device extension. + * @param pSession The session. + * @param pReq The request info. + */ +static int vgdrvIoCtl_IdcDisconnect(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCIDCDISCONNECT pReq) +{ + LogFlow(("VBGL_IOCTL_IDC_DISCONNECT: pvSession=%p vs pSession=%p\n", pReq->u.In.pvSession, pSession)); + RT_NOREF(pDevExt); + Assert(pSession != NULL); + + if (pReq->u.In.pvSession == pSession) + { + VGDrvCommonCloseSession(pDevExt, pSession); + return VINF_SUCCESS; + } + LogRel(("VBGL_IOCTL_IDC_DISCONNECT: In.pvSession=%p is not equal to pSession=%p!\n", pReq->u.In.pvSession, pSession)); + return VERR_INVALID_PARAMETER; +} + + +/** + * Return the VMM device I/O info. + * + * returns IPRT status code. + * @param pDevExt The device extension. + * @param pInfo The request info. + * @note Ring-0 only, caller checked. + */ +static int vgdrvIoCtl_GetVMMDevIoInfo(PVBOXGUESTDEVEXT pDevExt, PVBGLIOCGETVMMDEVIOINFO pInfo) +{ + LogFlow(("VBGL_IOCTL_GET_VMMDEV_IO_INFO\n")); + + pInfo->u.Out.IoPort = pDevExt->IOPortBase; + pInfo->u.Out.pvVmmDevMapping = pDevExt->pVMMDevMemory; + pInfo->u.Out.auPadding[0] = 0; +#if HC_ARCH_BITS != 32 + pInfo->u.Out.auPadding[1] = 0; + pInfo->u.Out.auPadding[2] = 0; +#endif + return VINF_SUCCESS; +} + + +/** + * Set the callback for the kernel mouse handler. + * + * returns IPRT status code. + * @param pDevExt The device extension. + * @param pNotify The new callback information. + */ +static int vgdrvIoCtl_SetMouseNotifyCallback(PVBOXGUESTDEVEXT pDevExt, PVBGLIOCSETMOUSENOTIFYCALLBACK pNotify) +{ + LogFlow(("VBOXGUEST_IOCTL_SET_MOUSE_NOTIFY_CALLBACK: pfnNotify=%p pvUser=%p\n", pNotify->u.In.pfnNotify, pNotify->u.In.pvUser)); + +#ifdef VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT + VGDrvNativeSetMouseNotifyCallback(pDevExt, pNotify); +#else + RTSpinlockAcquire(pDevExt->EventSpinlock); + pDevExt->pfnMouseNotifyCallback = pNotify->u.In.pfnNotify; + pDevExt->pvMouseNotifyCallbackArg = pNotify->u.In.pvUser; + RTSpinlockRelease(pDevExt->EventSpinlock); +#endif + return VINF_SUCCESS; +} + + +/** + * Worker vgdrvIoCtl_WaitEvent. + * + * The caller enters the spinlock, we leave it. + * + * @returns VINF_SUCCESS if we've left the spinlock and can return immediately. + */ +DECLINLINE(int) vbdgCheckWaitEventCondition(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + PVBGLIOCWAITFOREVENTS pInfo, int iEvent, const uint32_t fReqEvents) +{ + uint32_t fMatches = pDevExt->f32PendingEvents & fReqEvents; + if (fMatches & VBOXGUEST_ACQUIRE_STYLE_EVENTS) + fMatches &= vgdrvGetAllowedEventMaskForSession(pDevExt, pSession); + if (fMatches || pSession->fPendingCancelWaitEvents) + { + ASMAtomicAndU32(&pDevExt->f32PendingEvents, ~fMatches); + RTSpinlockRelease(pDevExt->EventSpinlock); + + pInfo->u.Out.fEvents = fMatches; + if (fReqEvents & ~((uint32_t)1 << iEvent)) + LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns %#x\n", pInfo->u.Out.fEvents)); + else + LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns %#x/%d\n", pInfo->u.Out.fEvents, iEvent)); + pSession->fPendingCancelWaitEvents = false; + return VINF_SUCCESS; + } + + RTSpinlockRelease(pDevExt->EventSpinlock); + return VERR_TIMEOUT; +} + + +static int vgdrvIoCtl_WaitForEvents(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + PVBGLIOCWAITFOREVENTS pInfo, bool fInterruptible) +{ + uint32_t const cMsTimeout = pInfo->u.In.cMsTimeOut; + const uint32_t fReqEvents = pInfo->u.In.fEvents; + uint32_t fResEvents; + int iEvent; + PVBOXGUESTWAIT pWait; + int rc; + + pInfo->u.Out.fEvents = 0; /* Note! This overwrites pInfo->u.In.* fields! */ + + /* + * Copy and verify the input mask. + */ + iEvent = ASMBitFirstSetU32(fReqEvents) - 1; + if (RT_UNLIKELY(iEvent < 0)) + { + LogRel(("VBOXGUEST_IOCTL_WAITEVENT: Invalid input mask %#x!!\n", fReqEvents)); + return VERR_INVALID_PARAMETER; + } + + /* + * Check the condition up front, before doing the wait-for-event allocations. + */ + RTSpinlockAcquire(pDevExt->EventSpinlock); + rc = vbdgCheckWaitEventCondition(pDevExt, pSession, pInfo, iEvent, fReqEvents); + if (rc == VINF_SUCCESS) + return rc; + + if (!cMsTimeout) + { + LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns VERR_TIMEOUT\n")); + return VERR_TIMEOUT; + } + + pWait = vgdrvWaitAlloc(pDevExt, pSession); + if (!pWait) + return VERR_NO_MEMORY; + pWait->fReqEvents = fReqEvents; + + /* + * We've got the wait entry now, re-enter the spinlock and check for the condition. + * If the wait condition is met, return. + * Otherwise enter into the list and go to sleep waiting for the ISR to signal us. + */ + RTSpinlockAcquire(pDevExt->EventSpinlock); + RTListAppend(&pDevExt->WaitList, &pWait->ListNode); + rc = vbdgCheckWaitEventCondition(pDevExt, pSession, pInfo, iEvent, fReqEvents); + if (rc == VINF_SUCCESS) + { + vgdrvWaitFreeUnlocked(pDevExt, pWait); + return rc; + } + + if (fInterruptible) + rc = RTSemEventMultiWaitNoResume(pWait->Event, cMsTimeout == UINT32_MAX ? RT_INDEFINITE_WAIT : cMsTimeout); + else + rc = RTSemEventMultiWait(pWait->Event, cMsTimeout == UINT32_MAX ? RT_INDEFINITE_WAIT : cMsTimeout); + + /* + * There is one special case here and that's when the semaphore is + * destroyed upon device driver unload. This shouldn't happen of course, + * but in case it does, just get out of here ASAP. + */ + if (rc == VERR_SEM_DESTROYED) + return rc; + + /* + * Unlink the wait item and dispose of it. + */ + RTSpinlockAcquire(pDevExt->EventSpinlock); + fResEvents = pWait->fResEvents; + vgdrvWaitFreeLocked(pDevExt, pWait); + RTSpinlockRelease(pDevExt->EventSpinlock); + + /* + * Now deal with the return code. + */ + if ( fResEvents + && fResEvents != UINT32_MAX) + { + pInfo->u.Out.fEvents = fResEvents; + if (fReqEvents & ~((uint32_t)1 << iEvent)) + LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns %#x\n", pInfo->u.Out.fEvents)); + else + LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns %#x/%d\n", pInfo->u.Out.fEvents, iEvent)); + rc = VINF_SUCCESS; + } + else if ( fResEvents == UINT32_MAX + || rc == VERR_INTERRUPTED) + { + rc = VERR_INTERRUPTED; + LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns VERR_INTERRUPTED\n")); + } + else if (rc == VERR_TIMEOUT) + LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns VERR_TIMEOUT (2)\n")); + else + { + if (RT_SUCCESS(rc)) + { + LogRelMax(32, ("VBOXGUEST_IOCTL_WAITEVENT: returns %Rrc but no events!\n", rc)); + rc = VERR_INTERNAL_ERROR; + } + LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns %Rrc\n", rc)); + } + + return rc; +} + + +/** @todo the semantics of this IoCtl have been tightened, so that no calls to + * VBOXGUEST_IOCTL_WAITEVENT are allowed in a session after it has been + * called. Change the code to make calls to VBOXGUEST_IOCTL_WAITEVENT made + * after that to return VERR_INTERRUPTED or something appropriate. */ +static int vgdrvIoCtl_CancelAllWaitEvents(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession) +{ + PVBOXGUESTWAIT pWait; + PVBOXGUESTWAIT pSafe; + int rc = 0; + /* Was as least one WAITEVENT in process for this session? If not we + * set a flag that the next call should be interrupted immediately. This + * is needed so that a user thread can reliably interrupt another one in a + * WAITEVENT loop. */ + bool fCancelledOne = false; + + LogFlow(("VBOXGUEST_IOCTL_CANCEL_ALL_WAITEVENTS\n")); + + /* + * Walk the event list and wake up anyone with a matching session. + */ + RTSpinlockAcquire(pDevExt->EventSpinlock); + RTListForEachSafe(&pDevExt->WaitList, pWait, pSafe, VBOXGUESTWAIT, ListNode) + { + if (pWait->pSession == pSession) + { + fCancelledOne = true; + pWait->fResEvents = UINT32_MAX; + RTListNodeRemove(&pWait->ListNode); +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP + RTListAppend(&pDevExt->WakeUpList, &pWait->ListNode); +#else + rc |= RTSemEventMultiSignal(pWait->Event); + RTListAppend(&pDevExt->WokenUpList, &pWait->ListNode); +#endif + } + } + if (!fCancelledOne) + pSession->fPendingCancelWaitEvents = true; + RTSpinlockRelease(pDevExt->EventSpinlock); + Assert(rc == 0); + NOREF(rc); + +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP + VGDrvCommonWaitDoWakeUps(pDevExt); +#endif + + return VINF_SUCCESS; +} + + +/** + * Checks if the VMM request is allowed in the context of the given session. + * + * @returns VINF_SUCCESS or VERR_PERMISSION_DENIED. + * @param pDevExt The device extension. + * @param pSession The calling session. + * @param enmType The request type. + * @param pReqHdr The request. + */ +static int vgdrvCheckIfVmmReqIsAllowed(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, VMMDevRequestType enmType, + VMMDevRequestHeader const *pReqHdr) +{ + /* + * Categorize the request being made. + */ + /** @todo This need quite some more work! */ + enum + { + kLevel_Invalid, kLevel_NoOne, kLevel_OnlyVBoxGuest, kLevel_OnlyKernel, kLevel_TrustedUsers, kLevel_AllUsers + } enmRequired; + RT_NOREF1(pDevExt); + + switch (enmType) + { + /* + * Deny access to anything we don't know or provide specialized I/O controls for. + */ +#ifdef VBOX_WITH_HGCM + case VMMDevReq_HGCMConnect: + case VMMDevReq_HGCMDisconnect: +# ifdef VBOX_WITH_64_BITS_GUESTS + case VMMDevReq_HGCMCall64: +# endif + case VMMDevReq_HGCMCall32: + case VMMDevReq_HGCMCancel: + case VMMDevReq_HGCMCancel2: +#endif /* VBOX_WITH_HGCM */ + case VMMDevReq_SetGuestCapabilities: + default: + enmRequired = kLevel_NoOne; + break; + + /* + * There are a few things only this driver can do (and it doesn't use + * the VMMRequst I/O control route anyway, but whatever). + */ + case VMMDevReq_ReportGuestInfo: + case VMMDevReq_ReportGuestInfo2: + case VMMDevReq_GetHypervisorInfo: + case VMMDevReq_SetHypervisorInfo: + case VMMDevReq_RegisterPatchMemory: + case VMMDevReq_DeregisterPatchMemory: + case VMMDevReq_GetMemBalloonChangeRequest: + case VMMDevReq_ChangeMemBalloon: + enmRequired = kLevel_OnlyVBoxGuest; + break; + + /* + * Trusted users apps only. + */ + case VMMDevReq_QueryCredentials: + case VMMDevReq_ReportCredentialsJudgement: + case VMMDevReq_RegisterSharedModule: + case VMMDevReq_UnregisterSharedModule: + case VMMDevReq_WriteCoreDump: + case VMMDevReq_GetCpuHotPlugRequest: + case VMMDevReq_SetCpuHotPlugStatus: + case VMMDevReq_CheckSharedModules: + case VMMDevReq_GetPageSharingStatus: + case VMMDevReq_DebugIsPageShared: + case VMMDevReq_ReportGuestStats: + case VMMDevReq_ReportGuestUserState: + case VMMDevReq_GetStatisticsChangeRequest: + enmRequired = kLevel_TrustedUsers; + break; + + /* + * Anyone. + */ + case VMMDevReq_GetMouseStatus: + case VMMDevReq_SetMouseStatus: + case VMMDevReq_SetPointerShape: + case VMMDevReq_GetHostVersion: + case VMMDevReq_Idle: + case VMMDevReq_GetHostTime: + case VMMDevReq_SetPowerStatus: + case VMMDevReq_AcknowledgeEvents: + case VMMDevReq_CtlGuestFilterMask: + case VMMDevReq_ReportGuestStatus: + case VMMDevReq_GetDisplayChangeRequest: + case VMMDevReq_VideoModeSupported: + case VMMDevReq_GetHeightReduction: + case VMMDevReq_GetDisplayChangeRequest2: + case VMMDevReq_VideoModeSupported2: + case VMMDevReq_VideoAccelEnable: + case VMMDevReq_VideoAccelFlush: + case VMMDevReq_VideoSetVisibleRegion: + case VMMDevReq_VideoUpdateMonitorPositions: + case VMMDevReq_GetDisplayChangeRequestEx: + case VMMDevReq_GetDisplayChangeRequestMulti: + case VMMDevReq_GetSeamlessChangeRequest: + case VMMDevReq_GetVRDPChangeRequest: + case VMMDevReq_LogString: + case VMMDevReq_GetSessionId: + enmRequired = kLevel_AllUsers; + break; + + /* + * Depends on the request parameters... + */ + /** @todo this have to be changed into an I/O control and the facilities + * tracked in the session so they can automatically be failed when the + * session terminates without reporting the new status. + * + * The information presented by IGuest is not reliable without this! */ + case VMMDevReq_ReportGuestCapabilities: + switch (((VMMDevReportGuestStatus const *)pReqHdr)->guestStatus.facility) + { + case VBoxGuestFacilityType_All: + case VBoxGuestFacilityType_VBoxGuestDriver: + enmRequired = kLevel_OnlyVBoxGuest; + break; + case VBoxGuestFacilityType_VBoxService: + enmRequired = kLevel_TrustedUsers; + break; + case VBoxGuestFacilityType_VBoxTrayClient: + case VBoxGuestFacilityType_Seamless: + case VBoxGuestFacilityType_Graphics: + default: + enmRequired = kLevel_AllUsers; + break; + } + break; + } + + /* + * Check against the session. + */ + switch (enmRequired) + { + default: + case kLevel_NoOne: + break; + case kLevel_OnlyVBoxGuest: + case kLevel_OnlyKernel: + if (pSession->R0Process == NIL_RTR0PROCESS) + return VINF_SUCCESS; + break; + case kLevel_TrustedUsers: + if (pSession->fUserSession) + break; + RT_FALL_THRU(); + case kLevel_AllUsers: + return VINF_SUCCESS; + } + + return VERR_PERMISSION_DENIED; +} + +static int vgdrvIoCtl_VMMDevRequest(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + VMMDevRequestHeader *pReqHdr, size_t cbData) +{ + int rc; + VMMDevRequestHeader *pReqCopy; + + /* + * Validate the header and request size. + */ + const VMMDevRequestType enmType = pReqHdr->requestType; + const uint32_t cbReq = pReqHdr->size; + const uint32_t cbMinSize = (uint32_t)vmmdevGetRequestSize(enmType); + + LogFlow(("VBOXGUEST_IOCTL_VMMREQUEST: type %d\n", pReqHdr->requestType)); + + if (cbReq < cbMinSize) + { + LogRel(("VBOXGUEST_IOCTL_VMMREQUEST: invalid hdr size %#x, expected >= %#x; type=%#x!!\n", + cbReq, cbMinSize, enmType)); + return VERR_INVALID_PARAMETER; + } + if (cbReq > cbData) + { + LogRel(("VBOXGUEST_IOCTL_VMMREQUEST: invalid size %#x, expected >= %#x (hdr); type=%#x!!\n", + cbData, cbReq, enmType)); + return VERR_INVALID_PARAMETER; + } + rc = VbglGR0Verify(pReqHdr, cbData); + if (RT_FAILURE(rc)) + { + Log(("VBOXGUEST_IOCTL_VMMREQUEST: invalid header: size %#x, expected >= %#x (hdr); type=%#x; rc=%Rrc!!\n", + cbData, cbReq, enmType, rc)); + return rc; + } + + rc = vgdrvCheckIfVmmReqIsAllowed(pDevExt, pSession, enmType, pReqHdr); + if (RT_FAILURE(rc)) + { + Log(("VBOXGUEST_IOCTL_VMMREQUEST: Operation not allowed! type=%#x rc=%Rrc\n", enmType, rc)); + return rc; + } + + /* + * Make a copy of the request in the physical memory heap so + * the VBoxGuestLibrary can more easily deal with the request. + * (This is really a waste of time since the OS or the OS specific + * code has already buffered or locked the input/output buffer, but + * it does makes things a bit simpler wrt to phys address.) + */ + rc = VbglR0GRAlloc(&pReqCopy, cbReq, enmType); + if (RT_FAILURE(rc)) + { + Log(("VBOXGUEST_IOCTL_VMMREQUEST: failed to allocate %u (%#x) bytes to cache the request. rc=%Rrc!!\n", + cbReq, cbReq, rc)); + return rc; + } + memcpy(pReqCopy, pReqHdr, cbReq); + Assert(pReqCopy->reserved1 == cbReq); + pReqCopy->reserved1 = 0; /* VGDrvCommonIoCtl or caller sets cbOut, so clear it. */ + pReqCopy->fRequestor = pSession->fRequestor; + + if (enmType == VMMDevReq_GetMouseStatus) /* clear poll condition. */ + pSession->u32MousePosChangedSeq = ASMAtomicUoReadU32(&pDevExt->u32MousePosChangedSeq); + + rc = VbglR0GRPerform(pReqCopy); + if ( RT_SUCCESS(rc) + && RT_SUCCESS(pReqCopy->rc)) + { + Assert(rc != VINF_HGCM_ASYNC_EXECUTE); + Assert(pReqCopy->rc != VINF_HGCM_ASYNC_EXECUTE); + + memcpy(pReqHdr, pReqCopy, cbReq); + pReqHdr->reserved1 = cbReq; /* preserve cbOut */ + } + else if (RT_FAILURE(rc)) + Log(("VBOXGUEST_IOCTL_VMMREQUEST: VbglR0GRPerform - rc=%Rrc!\n", rc)); + else + { + Log(("VBOXGUEST_IOCTL_VMMREQUEST: request execution failed; VMMDev rc=%Rrc!\n", pReqCopy->rc)); + rc = pReqCopy->rc; + } + + VbglR0GRFree(pReqCopy); + return rc; +} + + +#ifdef VBOX_WITH_HGCM + +AssertCompile(RT_INDEFINITE_WAIT == (uint32_t)RT_INDEFINITE_WAIT); /* assumed by code below */ + +/** Worker for vgdrvHgcmAsyncWaitCallback*. */ +static int vgdrvHgcmAsyncWaitCallbackWorker(VMMDevHGCMRequestHeader volatile *pHdr, PVBOXGUESTDEVEXT pDevExt, + bool fInterruptible, uint32_t cMillies) +{ + int rc; + + /* + * Check to see if the condition was met by the time we got here. + * + * We create a simple poll loop here for dealing with out-of-memory + * conditions since the caller isn't necessarily able to deal with + * us returning too early. + */ + PVBOXGUESTWAIT pWait; + for (;;) + { + RTSpinlockAcquire(pDevExt->EventSpinlock); + if ((pHdr->fu32Flags & VBOX_HGCM_REQ_DONE) != 0) + { + RTSpinlockRelease(pDevExt->EventSpinlock); + return VINF_SUCCESS; + } + RTSpinlockRelease(pDevExt->EventSpinlock); + + pWait = vgdrvWaitAlloc(pDevExt, NULL); + if (pWait) + break; + if (fInterruptible) + return VERR_INTERRUPTED; + RTThreadSleep(1); + } + pWait->fReqEvents = VMMDEV_EVENT_HGCM; + pWait->pHGCMReq = pHdr; + + /* + * Re-enter the spinlock and re-check for the condition. + * If the condition is met, return. + * Otherwise link us into the HGCM wait list and go to sleep. + */ + RTSpinlockAcquire(pDevExt->EventSpinlock); + RTListAppend(&pDevExt->HGCMWaitList, &pWait->ListNode); + if ((pHdr->fu32Flags & VBOX_HGCM_REQ_DONE) != 0) + { + vgdrvWaitFreeLocked(pDevExt, pWait); + RTSpinlockRelease(pDevExt->EventSpinlock); + return VINF_SUCCESS; + } + RTSpinlockRelease(pDevExt->EventSpinlock); + + if (fInterruptible) + rc = RTSemEventMultiWaitNoResume(pWait->Event, cMillies); + else + rc = RTSemEventMultiWait(pWait->Event, cMillies); + if (rc == VERR_SEM_DESTROYED) + return rc; + + /* + * Unlink, free and return. + */ + if ( RT_FAILURE(rc) + && rc != VERR_TIMEOUT + && ( !fInterruptible + || rc != VERR_INTERRUPTED)) + LogRel(("vgdrvHgcmAsyncWaitCallback: wait failed! %Rrc\n", rc)); + + vgdrvWaitFreeUnlocked(pDevExt, pWait); + return rc; +} + + +/** + * This is a callback for dealing with async waits. + * + * It operates in a manner similar to vgdrvIoCtl_WaitEvent. + */ +static DECLCALLBACK(int) vgdrvHgcmAsyncWaitCallback(VMMDevHGCMRequestHeader *pHdr, void *pvUser, uint32_t u32User) +{ + PVBOXGUESTDEVEXT pDevExt = (PVBOXGUESTDEVEXT)pvUser; + LogFlow(("vgdrvHgcmAsyncWaitCallback: requestType=%d\n", pHdr->header.requestType)); + return vgdrvHgcmAsyncWaitCallbackWorker((VMMDevHGCMRequestHeader volatile *)pHdr, pDevExt, + false /* fInterruptible */, u32User /* cMillies */); +} + + +/** + * This is a callback for dealing with async waits with a timeout. + * + * It operates in a manner similar to vgdrvIoCtl_WaitEvent. + */ +static DECLCALLBACK(int) vgdrvHgcmAsyncWaitCallbackInterruptible(VMMDevHGCMRequestHeader *pHdr, void *pvUser, uint32_t u32User) +{ + PVBOXGUESTDEVEXT pDevExt = (PVBOXGUESTDEVEXT)pvUser; + LogFlow(("vgdrvHgcmAsyncWaitCallbackInterruptible: requestType=%d\n", pHdr->header.requestType)); + return vgdrvHgcmAsyncWaitCallbackWorker((VMMDevHGCMRequestHeader volatile *)pHdr, pDevExt, + true /* fInterruptible */, u32User /* cMillies */); +} + + +static int vgdrvIoCtl_HGCMConnect(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCHGCMCONNECT pInfo) +{ + int rc; + HGCMCLIENTID idClient = 0; + + /* + * The VbglHGCMConnect call will invoke the callback if the HGCM + * call is performed in an ASYNC fashion. The function is not able + * to deal with cancelled requests. + */ + Log(("VBOXGUEST_IOCTL_HGCM_CONNECT: %.128s\n", + pInfo->u.In.Loc.type == VMMDevHGCMLoc_LocalHost || pInfo->u.In.Loc.type == VMMDevHGCMLoc_LocalHost_Existing + ? pInfo->u.In.Loc.u.host.achName : "<not local host>")); + + rc = VbglR0HGCMInternalConnect(&pInfo->u.In.Loc, pSession->fRequestor, &idClient, + vgdrvHgcmAsyncWaitCallback, pDevExt, RT_INDEFINITE_WAIT); + Log(("VBOXGUEST_IOCTL_HGCM_CONNECT: idClient=%RX32 (rc=%Rrc)\n", idClient, rc)); + if (RT_SUCCESS(rc)) + { + /* + * Append the client id to the client id table. + * If the table has somehow become filled up, we'll disconnect the session. + */ + unsigned i; + RTSpinlockAcquire(pDevExt->SessionSpinlock); + for (i = 0; i < RT_ELEMENTS(pSession->aHGCMClientIds); i++) + if (!pSession->aHGCMClientIds[i]) + { + pSession->aHGCMClientIds[i] = idClient; + break; + } + RTSpinlockRelease(pDevExt->SessionSpinlock); + if (i >= RT_ELEMENTS(pSession->aHGCMClientIds)) + { + LogRelMax(32, ("VBOXGUEST_IOCTL_HGCM_CONNECT: too many HGCMConnect calls for one session!\n")); + VbglR0HGCMInternalDisconnect(idClient, pSession->fRequestor, vgdrvHgcmAsyncWaitCallback, pDevExt, RT_INDEFINITE_WAIT); + + pInfo->u.Out.idClient = 0; + return VERR_TOO_MANY_OPEN_FILES; + } + } + pInfo->u.Out.idClient = idClient; + return rc; +} + + +static int vgdrvIoCtl_HGCMDisconnect(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCHGCMDISCONNECT pInfo) +{ + /* + * Validate the client id and invalidate its entry while we're in the call. + */ + int rc; + const uint32_t idClient = pInfo->u.In.idClient; + unsigned i; + RTSpinlockAcquire(pDevExt->SessionSpinlock); + for (i = 0; i < RT_ELEMENTS(pSession->aHGCMClientIds); i++) + if (pSession->aHGCMClientIds[i] == idClient) + { + pSession->aHGCMClientIds[i] = UINT32_MAX; + break; + } + RTSpinlockRelease(pDevExt->SessionSpinlock); + if (i >= RT_ELEMENTS(pSession->aHGCMClientIds)) + { + LogRelMax(32, ("VBOXGUEST_IOCTL_HGCM_DISCONNECT: idClient=%RX32\n", idClient)); + return VERR_INVALID_HANDLE; + } + + /* + * The VbglHGCMConnect call will invoke the callback if the HGCM + * call is performed in an ASYNC fashion. The function is not able + * to deal with cancelled requests. + */ + Log(("VBOXGUEST_IOCTL_HGCM_DISCONNECT: idClient=%RX32\n", idClient)); + rc = VbglR0HGCMInternalDisconnect(idClient, pSession->fRequestor, vgdrvHgcmAsyncWaitCallback, pDevExt, RT_INDEFINITE_WAIT); + LogFlow(("VBOXGUEST_IOCTL_HGCM_DISCONNECT: rc=%Rrc\n", rc)); + + /* Update the client id array according to the result. */ + RTSpinlockAcquire(pDevExt->SessionSpinlock); + if (pSession->aHGCMClientIds[i] == UINT32_MAX) + pSession->aHGCMClientIds[i] = RT_SUCCESS(rc) ? 0 : idClient; + RTSpinlockRelease(pDevExt->SessionSpinlock); + + return rc; +} + + +static int vgdrvIoCtl_HGCMCallInner(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCHGCMCALL pInfo, + uint32_t cMillies, bool fInterruptible, bool f32bit, bool fUserData, + size_t cbExtra, size_t cbData) +{ + const uint32_t u32ClientId = pInfo->u32ClientID; + uint32_t fFlags; + size_t cbActual; + unsigned i; + int rc; + + /* + * Some more validations. + */ + if (RT_LIKELY(pInfo->cParms <= VMMDEV_MAX_HGCM_PARMS)) /* (Just make sure it doesn't overflow the next check.) */ + { /* likely */} + else + { + LogRel(("VBOXGUEST_IOCTL_HGCM_CALL: cParm=%RX32 is not sane\n", pInfo->cParms)); + return VERR_INVALID_PARAMETER; + } + + cbActual = cbExtra + sizeof(*pInfo); +#ifdef RT_ARCH_AMD64 + if (f32bit) + cbActual += pInfo->cParms * sizeof(HGCMFunctionParameter32); + else +#endif + cbActual += pInfo->cParms * sizeof(HGCMFunctionParameter); + if (RT_LIKELY(cbData >= cbActual)) + { /* likely */} + else + { + LogRel(("VBOXGUEST_IOCTL_HGCM_CALL: cbData=%#zx (%zu) required size is %#zx (%zu)\n", + cbData, cbData, cbActual, cbActual)); + return VERR_INVALID_PARAMETER; + } + pInfo->Hdr.cbOut = (uint32_t)cbActual; + + /* + * Validate the client id. + */ + RTSpinlockAcquire(pDevExt->SessionSpinlock); + for (i = 0; i < RT_ELEMENTS(pSession->aHGCMClientIds); i++) + if (pSession->aHGCMClientIds[i] == u32ClientId) + break; + RTSpinlockRelease(pDevExt->SessionSpinlock); + if (RT_LIKELY(i < RT_ELEMENTS(pSession->aHGCMClientIds))) + { /* likely */} + else + { + LogRelMax(32, ("VBOXGUEST_IOCTL_HGCM_CALL: Invalid handle. u32Client=%RX32\n", u32ClientId)); + return VERR_INVALID_HANDLE; + } + + /* + * The VbglHGCMCall call will invoke the callback if the HGCM + * call is performed in an ASYNC fashion. This function can + * deal with cancelled requests, so we let user more requests + * be interruptible (should add a flag for this later I guess). + */ + LogFlow(("VBOXGUEST_IOCTL_HGCM_CALL: u32Client=%RX32\n", pInfo->u32ClientID)); + fFlags = !fUserData && pSession->R0Process == NIL_RTR0PROCESS ? VBGLR0_HGCMCALL_F_KERNEL : VBGLR0_HGCMCALL_F_USER; + uint32_t cbInfo = (uint32_t)(cbData - cbExtra); +#ifdef RT_ARCH_AMD64 + if (f32bit) + { + if (fInterruptible) + rc = VbglR0HGCMInternalCall32(pInfo, cbInfo, fFlags, pSession->fRequestor, + vgdrvHgcmAsyncWaitCallbackInterruptible, pDevExt, cMillies); + else + rc = VbglR0HGCMInternalCall32(pInfo, cbInfo, fFlags, pSession->fRequestor, + vgdrvHgcmAsyncWaitCallback, pDevExt, cMillies); + } + else +#endif + { + if (fInterruptible) + rc = VbglR0HGCMInternalCall(pInfo, cbInfo, fFlags, pSession->fRequestor, + vgdrvHgcmAsyncWaitCallbackInterruptible, pDevExt, cMillies); + else + rc = VbglR0HGCMInternalCall(pInfo, cbInfo, fFlags, pSession->fRequestor, + vgdrvHgcmAsyncWaitCallback, pDevExt, cMillies); + } + if (RT_SUCCESS(rc)) + { + rc = pInfo->Hdr.rc; + LogFlow(("VBOXGUEST_IOCTL_HGCM_CALL: result=%Rrc\n", rc)); + } + else + { + if ( rc != VERR_INTERRUPTED + && rc != VERR_TIMEOUT) + LogRelMax(32, ("VBOXGUEST_IOCTL_HGCM_CALL: %s Failed. rc=%Rrc (Hdr.rc=%Rrc).\n", f32bit ? "32" : "64", rc, pInfo->Hdr.rc)); + else + Log(("VBOXGUEST_IOCTL_HGCM_CALL: %s Failed. rc=%Rrc (Hdr.rc=%Rrc).\n", f32bit ? "32" : "64", rc, pInfo->Hdr.rc)); + } + return rc; +} + + +static int vgdrvIoCtl_HGCMCallWrapper(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCHGCMCALL pInfo, + bool f32bit, bool fUserData, size_t cbData) +{ + return vgdrvIoCtl_HGCMCallInner(pDevExt, pSession, pInfo, pInfo->cMsTimeout, + pInfo->fInterruptible || pSession->R0Process != NIL_RTR0PROCESS, + f32bit, fUserData, 0 /*cbExtra*/, cbData); +} + + +/** + * Handles a fast HGCM call from another driver. + * + * The driver has provided a fully assembled HGCM call request and all we need + * to do is send it to the host and do the wait processing. + * + * @returns VBox status code of the request submission part. + * @param pDevExt The device extension. + * @param pCallReq The call request. + */ +static int vgdrvIoCtl_HGCMFastCall(PVBOXGUESTDEVEXT pDevExt, VBGLIOCIDCHGCMFASTCALL volatile *pCallReq) +{ + VMMDevHGCMCall volatile *pHgcmCall = (VMMDevHGCMCall volatile *)(pCallReq + 1); + int rc; + + /* + * Check out the physical address. + */ + Assert((pCallReq->GCPhysReq & PAGE_OFFSET_MASK) == ((uintptr_t)pHgcmCall & PAGE_OFFSET_MASK)); + + AssertReturn(!pCallReq->fInterruptible, VERR_NOT_IMPLEMENTED); + + /* + * Submit the request. + */ + Log(("vgdrvIoCtl_HGCMFastCall -> host\n")); + ASMOutU32(pDevExt->IOPortBase + VMMDEV_PORT_OFF_REQUEST, (uint32_t)pCallReq->GCPhysReq); + + /* Make the compiler aware that the host has changed memory. */ + ASMCompilerBarrier(); + + rc = pHgcmCall->header.header.rc; + Log(("vgdrvIoCtl_HGCMFastCall -> %Rrc (header rc=%Rrc)\n", rc, pHgcmCall->header.result)); + + /* + * The host is likely to engage in asynchronous execution of HGCM, unless it fails. + */ + if (rc == VINF_HGCM_ASYNC_EXECUTE) + { + rc = vgdrvHgcmAsyncWaitCallbackWorker(&pHgcmCall->header, pDevExt, false /* fInterruptible */, RT_INDEFINITE_WAIT); + if (pHgcmCall->header.fu32Flags & VBOX_HGCM_REQ_DONE) + { + Assert(!(pHgcmCall->header.fu32Flags & VBOX_HGCM_REQ_CANCELLED)); + rc = VINF_SUCCESS; + } + else + { + /* + * Timeout and interrupt scenarios are messy and requires + * cancelation, so implement later. + */ + AssertReleaseMsgFailed(("rc=%Rrc\n", rc)); + } + } + else + Assert((pHgcmCall->header.fu32Flags & VBOX_HGCM_REQ_DONE) || RT_FAILURE_NP(rc)); + + Log(("vgdrvIoCtl_HGCMFastCall: rc=%Rrc result=%Rrc fu32Flags=%#x\n", rc, pHgcmCall->header.result, pHgcmCall->header.fu32Flags)); + return rc; + +} + +#endif /* VBOX_WITH_HGCM */ + +/** + * Handle VBGL_IOCTL_CHECK_BALLOON from R3. + * + * Ask the host for the size of the balloon and try to set it accordingly. If + * this approach fails because it's not supported, return with fHandleInR3 set + * and let the user land supply memory we can lock via the other ioctl. + * + * @returns VBox status code. + * + * @param pDevExt The device extension. + * @param pSession The session. + * @param pInfo The output buffer. + */ +static int vgdrvIoCtl_CheckMemoryBalloon(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCCHECKBALLOON pInfo) +{ + VMMDevGetMemBalloonChangeRequest *pReq; + int rc; + + LogFlow(("VBGL_IOCTL_CHECK_BALLOON:\n")); + rc = RTSemFastMutexRequest(pDevExt->MemBalloon.hMtx); + AssertRCReturn(rc, rc); + + /* + * The first user trying to query/change the balloon becomes the + * owner and owns it until the session is closed (vgdrvCloseMemBalloon). + */ + if ( pDevExt->MemBalloon.pOwner != pSession + && pDevExt->MemBalloon.pOwner == NULL) + pDevExt->MemBalloon.pOwner = pSession; + + if (pDevExt->MemBalloon.pOwner == pSession) + { + /* + * This is a response to that event. Setting this bit means that + * we request the value from the host and change the guest memory + * balloon according to this value. + */ + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(VMMDevGetMemBalloonChangeRequest), VMMDevReq_GetMemBalloonChangeRequest); + if (RT_SUCCESS(rc)) + { + pReq->header.fRequestor = pSession->fRequestor; + pReq->eventAck = VMMDEV_EVENT_BALLOON_CHANGE_REQUEST; + rc = VbglR0GRPerform(&pReq->header); + if (RT_SUCCESS(rc)) + { + Assert(pDevExt->MemBalloon.cMaxChunks == pReq->cPhysMemChunks || pDevExt->MemBalloon.cMaxChunks == 0); + pDevExt->MemBalloon.cMaxChunks = pReq->cPhysMemChunks; + + pInfo->u.Out.cBalloonChunks = pReq->cBalloonChunks; + pInfo->u.Out.fHandleInR3 = false; + pInfo->u.Out.afPadding[0] = false; + pInfo->u.Out.afPadding[1] = false; + pInfo->u.Out.afPadding[2] = false; + + rc = vgdrvSetBalloonSizeKernel(pDevExt, pReq->cBalloonChunks, &pInfo->u.Out.fHandleInR3); + /* Ignore various out of memory failures. */ + if ( rc == VERR_NO_MEMORY + || rc == VERR_NO_PHYS_MEMORY + || rc == VERR_NO_CONT_MEMORY) + rc = VINF_SUCCESS; + } + else + LogRel(("VBGL_IOCTL_CHECK_BALLOON: VbglR0GRPerform failed. rc=%Rrc\n", rc)); + VbglR0GRFree(&pReq->header); + } + } + else + rc = VERR_PERMISSION_DENIED; + + RTSemFastMutexRelease(pDevExt->MemBalloon.hMtx); + LogFlow(("VBGL_IOCTL_CHECK_BALLOON returns %Rrc\n", rc)); + return rc; +} + + +/** + * Handle a request for changing the memory balloon. + * + * @returns VBox status code. + * + * @param pDevExt The device extention. + * @param pSession The session. + * @param pInfo The change request structure (input). + */ +static int vgdrvIoCtl_ChangeMemoryBalloon(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCCHANGEBALLOON pInfo) +{ + int rc; + LogFlow(("VBGL_IOCTL_CHANGE_BALLOON: fInflate=%RTbool u64ChunkAddr=%p\n", pInfo->u.In.fInflate, pInfo->u.In.pvChunk)); + if ( pInfo->u.In.abPadding[0] + || pInfo->u.In.abPadding[1] + || pInfo->u.In.abPadding[2] + || pInfo->u.In.abPadding[3] + || pInfo->u.In.abPadding[4] + || pInfo->u.In.abPadding[5] + || pInfo->u.In.abPadding[6] +#if ARCH_BITS == 32 + || pInfo->u.In.abPadding[7] + || pInfo->u.In.abPadding[8] + || pInfo->u.In.abPadding[9] +#endif + ) + { + Log(("VBGL_IOCTL_CHANGE_BALLOON: Padding isn't all zero: %.*Rhxs\n", sizeof(pInfo->u.In.abPadding), pInfo->u.In.abPadding)); + return VERR_INVALID_PARAMETER; + } + + rc = RTSemFastMutexRequest(pDevExt->MemBalloon.hMtx); + AssertRCReturn(rc, rc); + + if (!pDevExt->MemBalloon.fUseKernelAPI) + { + /* + * The first user trying to query/change the balloon becomes the + * owner and owns it until the session is closed (vgdrvCloseMemBalloon). + */ + if ( pDevExt->MemBalloon.pOwner != pSession + && pDevExt->MemBalloon.pOwner == NULL) + pDevExt->MemBalloon.pOwner = pSession; + + if (pDevExt->MemBalloon.pOwner == pSession) + rc = vgdrvSetBalloonSizeFromUser(pDevExt, pSession, pInfo->u.In.pvChunk, pInfo->u.In.fInflate != false); + else + rc = VERR_PERMISSION_DENIED; + } + else + rc = VERR_PERMISSION_DENIED; + + RTSemFastMutexRelease(pDevExt->MemBalloon.hMtx); + return rc; +} + + +/** + * Handle a request for writing a core dump of the guest on the host. + * + * @returns VBox status code. + * + * @param pDevExt The device extension. + * @param pSession The session. + * @param pInfo The output buffer. + */ +static int vgdrvIoCtl_WriteCoreDump(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCWRITECOREDUMP pInfo) +{ + VMMDevReqWriteCoreDump *pReq = NULL; + int rc; + LogFlow(("VBOXGUEST_IOCTL_WRITE_CORE_DUMP\n")); + RT_NOREF1(pDevExt); + + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_WriteCoreDump); + if (RT_SUCCESS(rc)) + { + pReq->header.fRequestor = pSession->fRequestor; + pReq->fFlags = pInfo->u.In.fFlags; + rc = VbglR0GRPerform(&pReq->header); + if (RT_FAILURE(rc)) + Log(("VBOXGUEST_IOCTL_WRITE_CORE_DUMP: VbglR0GRPerform failed, rc=%Rrc!\n", rc)); + + VbglR0GRFree(&pReq->header); + } + else + Log(("VBOXGUEST_IOCTL_WRITE_CORE_DUMP: failed to allocate %u (%#x) bytes to cache the request. rc=%Rrc!!\n", + sizeof(*pReq), sizeof(*pReq), rc)); + return rc; +} + + +/** + * Guest backdoor logging. + * + * @returns VBox status code. + * + * @param pDevExt The device extension. + * @param pch The log message (need not be NULL terminated). + * @param cbData Size of the buffer. + * @param fUserSession Copy of VBOXGUESTSESSION::fUserSession for the + * call. True normal user, false root user. + */ +static int vgdrvIoCtl_Log(PVBOXGUESTDEVEXT pDevExt, const char *pch, size_t cbData, bool fUserSession) +{ + if (pDevExt->fLoggingEnabled) + RTLogBackdoorPrintf("%.*s", cbData, pch); + else if (!fUserSession) + LogRel(("%.*s", cbData, pch)); + else + Log(("%.*s", cbData, pch)); + return VINF_SUCCESS; +} + + +/** @name Guest Capabilities, Mouse Status and Event Filter + * @{ + */ + +/** + * Clears a bit usage tracker (init time). + * + * @param pTracker The tracker to clear. + */ +static void vgdrvBitUsageTrackerClear(PVBOXGUESTBITUSAGETRACER pTracker) +{ + uint32_t iBit; + AssertCompile(sizeof(pTracker->acPerBitUsage) == 32 * sizeof(uint32_t)); + + for (iBit = 0; iBit < 32; iBit++) + pTracker->acPerBitUsage[iBit] = 0; + pTracker->fMask = 0; +} + + +#ifdef VBOX_STRICT +/** + * Checks that pTracker->fMask is correct and that the usage values are within + * the valid range. + * + * @param pTracker The tracker. + * @param cMax Max valid usage value. + * @param pszWhat Identifies the tracker in assertions. + */ +static void vgdrvBitUsageTrackerCheckMask(PCVBOXGUESTBITUSAGETRACER pTracker, uint32_t cMax, const char *pszWhat) +{ + uint32_t fMask = 0; + uint32_t iBit; + AssertCompile(sizeof(pTracker->acPerBitUsage) == 32 * sizeof(uint32_t)); + + for (iBit = 0; iBit < 32; iBit++) + if (pTracker->acPerBitUsage[iBit]) + { + fMask |= RT_BIT_32(iBit); + AssertMsg(pTracker->acPerBitUsage[iBit] <= cMax, + ("%s: acPerBitUsage[%u]=%#x cMax=%#x\n", pszWhat, iBit, pTracker->acPerBitUsage[iBit], cMax)); + } + + AssertMsg(fMask == pTracker->fMask, ("%s: %#x vs %#x\n", pszWhat, fMask, pTracker->fMask)); +} +#endif + + +/** + * Applies a change to the bit usage tracker. + * + * + * @returns true if the mask changed, false if not. + * @param pTracker The bit usage tracker. + * @param fChanged The bits to change. + * @param fPrevious The previous value of the bits. + * @param cMax The max valid usage value for assertions. + * @param pszWhat Identifies the tracker in assertions. + */ +static bool vgdrvBitUsageTrackerChange(PVBOXGUESTBITUSAGETRACER pTracker, uint32_t fChanged, uint32_t fPrevious, + uint32_t cMax, const char *pszWhat) +{ + bool fGlobalChange = false; + AssertCompile(sizeof(pTracker->acPerBitUsage) == 32 * sizeof(uint32_t)); + + while (fChanged) + { + uint32_t const iBit = ASMBitFirstSetU32(fChanged) - 1; + uint32_t const fBitMask = RT_BIT_32(iBit); + Assert(iBit < 32); Assert(fBitMask & fChanged); + + if (fBitMask & fPrevious) + { + pTracker->acPerBitUsage[iBit] -= 1; + AssertMsg(pTracker->acPerBitUsage[iBit] <= cMax, + ("%s: acPerBitUsage[%u]=%#x cMax=%#x\n", pszWhat, iBit, pTracker->acPerBitUsage[iBit], cMax)); + if (pTracker->acPerBitUsage[iBit] == 0) + { + fGlobalChange = true; + pTracker->fMask &= ~fBitMask; + } + } + else + { + pTracker->acPerBitUsage[iBit] += 1; + AssertMsg(pTracker->acPerBitUsage[iBit] > 0 && pTracker->acPerBitUsage[iBit] <= cMax, + ("pTracker->acPerBitUsage[%u]=%#x cMax=%#x\n", pszWhat, iBit, pTracker->acPerBitUsage[iBit], cMax)); + if (pTracker->acPerBitUsage[iBit] == 1) + { + fGlobalChange = true; + pTracker->fMask |= fBitMask; + } + } + + fChanged &= ~fBitMask; + } + +#ifdef VBOX_STRICT + vgdrvBitUsageTrackerCheckMask(pTracker, cMax, pszWhat); +#endif + NOREF(pszWhat); NOREF(cMax); + return fGlobalChange; +} + + +/** + * Init and termination worker for resetting the (host) event filter on the host + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param fFixedEvents Fixed events (init time). + */ +static int vgdrvResetEventFilterOnHost(PVBOXGUESTDEVEXT pDevExt, uint32_t fFixedEvents) +{ + VMMDevCtlGuestFilterMask *pReq; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_CtlGuestFilterMask); + if (RT_SUCCESS(rc)) + { + pReq->u32NotMask = UINT32_MAX & ~fFixedEvents; + pReq->u32OrMask = fFixedEvents; + rc = VbglR0GRPerform(&pReq->header); + if (RT_FAILURE(rc)) + LogRelFunc(("failed with rc=%Rrc\n", rc)); + VbglR0GRFree(&pReq->header); + } + RT_NOREF1(pDevExt); + return rc; +} + + +/** + * Changes the event filter mask for the given session. + * + * This is called in response to VBGL_IOCTL_CHANGE_FILTER_MASK as well as to do + * session cleanup. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param pSession The session. + * @param fOrMask The events to add. + * @param fNotMask The events to remove. + * @param fSessionTermination Set if we're called by the session cleanup code. + * This tweaks the error handling so we perform + * proper session cleanup even if the host + * misbehaves. + * + * @remarks Takes the session spinlock. + */ +static int vgdrvSetSessionEventFilter(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + uint32_t fOrMask, uint32_t fNotMask, bool fSessionTermination) +{ + VMMDevCtlGuestFilterMask *pReq; + uint32_t fChanged; + uint32_t fPrevious; + int rc; + + /* + * Preallocate a request buffer so we can do all in one go without leaving the spinlock. + */ + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_CtlGuestFilterMask); + if (RT_SUCCESS(rc)) + { /* nothing */ } + else if (!fSessionTermination) + { + LogRel(("vgdrvSetSessionFilterMask: VbglR0GRAlloc failure: %Rrc\n", rc)); + return rc; + } + else + pReq = NULL; /* Ignore failure, we must do session cleanup. */ + + + RTSpinlockAcquire(pDevExt->SessionSpinlock); + + /* + * Apply the changes to the session mask. + */ + fPrevious = pSession->fEventFilter; + pSession->fEventFilter |= fOrMask; + pSession->fEventFilter &= ~fNotMask; + + /* + * If anything actually changed, update the global usage counters. + */ + fChanged = fPrevious ^ pSession->fEventFilter; + LogFlow(("vgdrvSetSessionEventFilter: Session->fEventFilter: %#x -> %#x (changed %#x)\n", + fPrevious, pSession->fEventFilter, fChanged)); + if (fChanged) + { + bool fGlobalChange = vgdrvBitUsageTrackerChange(&pDevExt->EventFilterTracker, fChanged, fPrevious, + pDevExt->cSessions, "EventFilterTracker"); + + /* + * If there are global changes, update the event filter on the host. + */ + if (fGlobalChange || pDevExt->fEventFilterHost == UINT32_MAX) + { + Assert(pReq || fSessionTermination); + if (pReq) + { + pReq->u32OrMask = pDevExt->fFixedEvents | pDevExt->EventFilterTracker.fMask; + if (pReq->u32OrMask == pDevExt->fEventFilterHost) + rc = VINF_SUCCESS; + else + { + pDevExt->fEventFilterHost = pReq->u32OrMask; + pReq->u32NotMask = ~pReq->u32OrMask; + rc = VbglR0GRPerform(&pReq->header); + if (RT_FAILURE(rc)) + { + /* + * Failed, roll back (unless it's session termination time). + */ + pDevExt->fEventFilterHost = UINT32_MAX; + if (!fSessionTermination) + { + vgdrvBitUsageTrackerChange(&pDevExt->EventFilterTracker, fChanged, pSession->fEventFilter, + pDevExt->cSessions, "EventFilterTracker"); + pSession->fEventFilter = fPrevious; + } + } + } + } + else + rc = VINF_SUCCESS; + } + } + + RTSpinlockRelease(pDevExt->SessionSpinlock); + if (pReq) + VbglR0GRFree(&pReq->header); + return rc; +} + + +/** + * Handle VBGL_IOCTL_CHANGE_FILTER_MASK. + * + * @returns VBox status code. + * + * @param pDevExt The device extension. + * @param pSession The session. + * @param pInfo The request. + */ +static int vgdrvIoCtl_ChangeFilterMask(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCCHANGEFILTERMASK pInfo) +{ + LogFlow(("VBGL_IOCTL_CHANGE_FILTER_MASK: or=%#x not=%#x\n", pInfo->u.In.fOrMask, pInfo->u.In.fNotMask)); + + if ((pInfo->u.In.fOrMask | pInfo->u.In.fNotMask) & ~VMMDEV_EVENT_VALID_EVENT_MASK) + { + Log(("VBGL_IOCTL_CHANGE_FILTER_MASK: or=%#x not=%#x: Invalid masks!\n", pInfo->u.In.fOrMask, pInfo->u.In.fNotMask)); + return VERR_INVALID_PARAMETER; + } + + return vgdrvSetSessionEventFilter(pDevExt, pSession, pInfo->u.In.fOrMask, pInfo->u.In.fNotMask, false /*fSessionTermination*/); +} + + +/** + * Init and termination worker for set mouse feature status to zero on the host. + * + * @returns VBox status code. + * @param pDevExt The device extension. + */ +static int vgdrvResetMouseStatusOnHost(PVBOXGUESTDEVEXT pDevExt) +{ + VMMDevReqMouseStatus *pReq; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_SetMouseStatus); + if (RT_SUCCESS(rc)) + { + pReq->mouseFeatures = 0; + pReq->pointerXPos = 0; + pReq->pointerYPos = 0; + rc = VbglR0GRPerform(&pReq->header); + if (RT_FAILURE(rc)) + LogRelFunc(("failed with rc=%Rrc\n", rc)); + VbglR0GRFree(&pReq->header); + } + RT_NOREF1(pDevExt); + return rc; +} + + +/** + * Changes the mouse status mask for the given session. + * + * This is called in response to VBOXGUEST_IOCTL_SET_MOUSE_STATUS as well as to + * do session cleanup. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param pSession The session. + * @param fOrMask The status flags to add. + * @param fNotMask The status flags to remove. + * @param fSessionTermination Set if we're called by the session cleanup code. + * This tweaks the error handling so we perform + * proper session cleanup even if the host + * misbehaves. + * + * @remarks Takes the session spinlock. + */ +static int vgdrvSetSessionMouseStatus(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + uint32_t fOrMask, uint32_t fNotMask, bool fSessionTermination) +{ + VMMDevReqMouseStatus *pReq; + uint32_t fChanged; + uint32_t fPrevious; + int rc; + + /* + * Preallocate a request buffer so we can do all in one go without leaving the spinlock. + */ + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_SetMouseStatus); + if (RT_SUCCESS(rc)) + { + if (!fSessionTermination) + pReq->header.fRequestor = pSession->fRequestor; + } + else if (!fSessionTermination) + { + LogRel(("vgdrvSetSessionMouseStatus: VbglR0GRAlloc failure: %Rrc\n", rc)); + return rc; + } + else + pReq = NULL; /* Ignore failure, we must do session cleanup. */ + + + RTSpinlockAcquire(pDevExt->SessionSpinlock); + + /* + * Apply the changes to the session mask. + */ + fPrevious = pSession->fMouseStatus; + pSession->fMouseStatus |= fOrMask; + pSession->fMouseStatus &= ~fNotMask; + + /* + * If anything actually changed, update the global usage counters. + */ + fChanged = fPrevious ^ pSession->fMouseStatus; + if (fChanged) + { + bool fGlobalChange = vgdrvBitUsageTrackerChange(&pDevExt->MouseStatusTracker, fChanged, fPrevious, + pDevExt->cSessions, "MouseStatusTracker"); + + /* + * If there are global changes, update the event filter on the host. + */ + if (fGlobalChange || pDevExt->fMouseStatusHost == UINT32_MAX) + { + Assert(pReq || fSessionTermination); + if (pReq) + { + pReq->mouseFeatures = pDevExt->MouseStatusTracker.fMask; + if (pReq->mouseFeatures == pDevExt->fMouseStatusHost) + rc = VINF_SUCCESS; + else + { + pDevExt->fMouseStatusHost = pReq->mouseFeatures; + pReq->pointerXPos = 0; + pReq->pointerYPos = 0; + rc = VbglR0GRPerform(&pReq->header); + if (RT_FAILURE(rc)) + { + /* + * Failed, roll back (unless it's session termination time). + */ + pDevExt->fMouseStatusHost = UINT32_MAX; + if (!fSessionTermination) + { + vgdrvBitUsageTrackerChange(&pDevExt->MouseStatusTracker, fChanged, pSession->fMouseStatus, + pDevExt->cSessions, "MouseStatusTracker"); + pSession->fMouseStatus = fPrevious; + } + } + } + } + else + rc = VINF_SUCCESS; + } + } + + RTSpinlockRelease(pDevExt->SessionSpinlock); + if (pReq) + VbglR0GRFree(&pReq->header); + return rc; +} + + +/** + * Sets the mouse status features for this session and updates them globally. + * + * @returns VBox status code. + * + * @param pDevExt The device extention. + * @param pSession The session. + * @param fFeatures New bitmap of enabled features. + */ +static int vgdrvIoCtl_SetMouseStatus(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, uint32_t fFeatures) +{ + LogFlow(("VBGL_IOCTL_SET_MOUSE_STATUS: features=%#x\n", fFeatures)); + + if (fFeatures & ~VMMDEV_MOUSE_GUEST_MASK) + return VERR_INVALID_PARAMETER; + + return vgdrvSetSessionMouseStatus(pDevExt, pSession, fFeatures, ~fFeatures, false /*fSessionTermination*/); +} + + +/** + * Return the mask of VMM device events that this session is allowed to see (wrt + * to "acquire" mode guest capabilities). + * + * The events associated with guest capabilities in "acquire" mode will be + * restricted to sessions which has acquired the respective capabilities. + * If someone else tries to wait for acquired events, they won't be woken up + * when the event becomes pending. Should some other thread in the session + * acquire the capability while the corresponding event is pending, the waiting + * thread will woken up. + * + * @returns Mask of events valid for the given session. + * @param pDevExt The device extension. + * @param pSession The session. + * + * @remarks Needs only be called when dispatching events in the + * VBOXGUEST_ACQUIRE_STYLE_EVENTS mask. + */ +static uint32_t vgdrvGetAllowedEventMaskForSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession) +{ + uint32_t fAcquireModeGuestCaps; + uint32_t fAcquiredGuestCaps; + uint32_t fAllowedEvents; + + /* + * Note! Reads pSession->fAcquiredGuestCaps and pDevExt->fAcquireModeGuestCaps + * WITHOUT holding VBOXGUESTDEVEXT::SessionSpinlock. + */ + fAcquireModeGuestCaps = ASMAtomicUoReadU32(&pDevExt->fAcquireModeGuestCaps); + if (fAcquireModeGuestCaps == 0) + return VMMDEV_EVENT_VALID_EVENT_MASK; + fAcquiredGuestCaps = ASMAtomicUoReadU32(&pSession->fAcquiredGuestCaps); + + /* + * Calculate which events to allow according to the cap config and caps + * acquired by the session. + */ + fAllowedEvents = VMMDEV_EVENT_VALID_EVENT_MASK; + if ( !(fAcquiredGuestCaps & VMMDEV_GUEST_SUPPORTS_GRAPHICS) + && (fAcquireModeGuestCaps & VMMDEV_GUEST_SUPPORTS_GRAPHICS)) + fAllowedEvents &= ~VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST; + + if ( !(fAcquiredGuestCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS) + && (fAcquireModeGuestCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS)) + fAllowedEvents &= ~VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST; + + return fAllowedEvents; +} + + +/** + * Init and termination worker for set guest capabilities to zero on the host. + * + * @returns VBox status code. + * @param pDevExt The device extension. + */ +static int vgdrvResetCapabilitiesOnHost(PVBOXGUESTDEVEXT pDevExt) +{ + VMMDevReqGuestCapabilities2 *pReq; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_SetGuestCapabilities); + if (RT_SUCCESS(rc)) + { + pReq->u32NotMask = UINT32_MAX; + pReq->u32OrMask = 0; + rc = VbglR0GRPerform(&pReq->header); + + if (RT_FAILURE(rc)) + LogRelFunc(("failed with rc=%Rrc\n", rc)); + VbglR0GRFree(&pReq->header); + } + RT_NOREF1(pDevExt); + return rc; +} + + +/** + * Sets the guest capabilities to the host while holding the lock. + * + * This will ASSUME that we're the ones in charge of the mask, so + * we'll simply clear all bits we don't set. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param pReq The request. + */ +static int vgdrvUpdateCapabilitiesOnHostWithReqAndLock(PVBOXGUESTDEVEXT pDevExt, VMMDevReqGuestCapabilities2 *pReq) +{ + int rc; + + pReq->u32OrMask = pDevExt->fAcquiredGuestCaps | pDevExt->SetGuestCapsTracker.fMask; + if (pReq->u32OrMask == pDevExt->fGuestCapsHost) + rc = VINF_SUCCESS; + else + { + pDevExt->fGuestCapsHost = pReq->u32OrMask; + pReq->u32NotMask = ~pReq->u32OrMask; + rc = VbglR0GRPerform(&pReq->header); + if (RT_FAILURE(rc)) + pDevExt->fGuestCapsHost = UINT32_MAX; + } + + return rc; +} + + +/** + * Switch a set of capabilities into "acquire" mode and (maybe) acquire them for + * the given session. + * + * This is called in response to VBOXGUEST_IOCTL_GUEST_CAPS_ACQUIRE as well as + * to do session cleanup. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param pSession The session. + * @param fOrMask The capabilities to add . + * @param fNotMask The capabilities to remove. Ignored in + * VBOXGUESTCAPSACQUIRE_FLAGS_CONFIG_ACQUIRE_MODE. + * @param fFlags Confusing operation modifier. + * VBOXGUESTCAPSACQUIRE_FLAGS_NONE means to both + * configure and acquire/release the capabilities. + * VBOXGUESTCAPSACQUIRE_FLAGS_CONFIG_ACQUIRE_MODE + * means only configure capabilities in the + * @a fOrMask capabilities for "acquire" mode. + * @param fSessionTermination Set if we're called by the session cleanup code. + * This tweaks the error handling so we perform + * proper session cleanup even if the host + * misbehaves. + * + * @remarks Takes both the session and event spinlocks. + */ +static int vgdrvAcquireSessionCapabilities(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + uint32_t fOrMask, uint32_t fNotMask, uint32_t fFlags, + bool fSessionTermination) +{ + uint32_t fCurrentOwnedCaps; + uint32_t fSessionRemovedCaps; + uint32_t fSessionAddedCaps; + uint32_t fOtherConflictingCaps; + VMMDevReqGuestCapabilities2 *pReq = NULL; + int rc; + + + /* + * Validate and adjust input. + */ + if (fOrMask & ~( VMMDEV_GUEST_SUPPORTS_SEAMLESS + | VMMDEV_GUEST_SUPPORTS_GUEST_HOST_WINDOW_MAPPING + | VMMDEV_GUEST_SUPPORTS_GRAPHICS ) ) + { + LogRel(("vgdrvAcquireSessionCapabilities: invalid fOrMask=%#x (pSession=%p fNotMask=%#x fFlags=%#x)\n", + fOrMask, pSession, fNotMask, fFlags)); + return VERR_INVALID_PARAMETER; + } + + if ((fFlags & ~VBGL_IOC_AGC_FLAGS_VALID_MASK) != 0) + { + LogRel(("vgdrvAcquireSessionCapabilities: invalid fFlags=%#x (pSession=%p fOrMask=%#x fNotMask=%#x)\n", + fFlags, pSession, fOrMask, fNotMask)); + return VERR_INVALID_PARAMETER; + } + Assert(!fOrMask || !fSessionTermination); + + /* The fNotMask no need to have all values valid, invalid ones will simply be ignored. */ + fNotMask &= ~fOrMask; + + /* + * Preallocate a update request if we're about to do more than just configure + * the capability mode. + */ + if (!(fFlags & VBGL_IOC_AGC_FLAGS_CONFIG_ACQUIRE_MODE)) + { + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_SetGuestCapabilities); + if (RT_SUCCESS(rc)) + { + if (!fSessionTermination) + pReq->header.fRequestor = pSession->fRequestor; + } + else if (!fSessionTermination) + { + LogRel(("vgdrvAcquireSessionCapabilities: pSession=%p fOrMask=%#x fNotMask=%#x fFlags=%#x: VbglR0GRAlloc failure: %Rrc\n", + pSession, fOrMask, fNotMask, fFlags, rc)); + return rc; + } + else + pReq = NULL; /* Ignore failure, we must do session cleanup. */ + } + + /* + * Try switch the capabilities in the OR mask into "acquire" mode. + * + * Note! We currently ignore anyone which may already have "set" the capabilities + * in fOrMask. Perhaps not the best way to handle it, but it's simple... + */ + RTSpinlockAcquire(pDevExt->EventSpinlock); + + if (!(pDevExt->fSetModeGuestCaps & fOrMask)) + pDevExt->fAcquireModeGuestCaps |= fOrMask; + else + { + RTSpinlockRelease(pDevExt->EventSpinlock); + + if (pReq) + VbglR0GRFree(&pReq->header); + AssertMsgFailed(("Trying to change caps mode: %#x\n", fOrMask)); + LogRel(("vgdrvAcquireSessionCapabilities: pSession=%p fOrMask=%#x fNotMask=%#x fFlags=%#x: calling caps acquire for set caps\n", + pSession, fOrMask, fNotMask, fFlags)); + return VERR_INVALID_STATE; + } + + /* + * If we only wanted to switch the capabilities into "acquire" mode, we're done now. + */ + if (fFlags & VBGL_IOC_AGC_FLAGS_CONFIG_ACQUIRE_MODE) + { + RTSpinlockRelease(pDevExt->EventSpinlock); + + Assert(!pReq); + Log(("vgdrvAcquireSessionCapabilities: pSession=%p fOrMask=%#x fNotMask=%#x fFlags=%#x: configured acquire caps: 0x%x\n", + pSession, fOrMask, fNotMask, fFlags)); + return VINF_SUCCESS; + } + Assert(pReq || fSessionTermination); + + /* + * Caller wants to acquire/release the capabilities too. + * + * Note! The mode change of the capabilities above won't be reverted on + * failure, this is intentional. + */ + fCurrentOwnedCaps = pSession->fAcquiredGuestCaps; + fSessionRemovedCaps = fCurrentOwnedCaps & fNotMask; + fSessionAddedCaps = fOrMask & ~fCurrentOwnedCaps; + fOtherConflictingCaps = pDevExt->fAcquiredGuestCaps & ~fCurrentOwnedCaps; + fOtherConflictingCaps &= fSessionAddedCaps; + + if (!fOtherConflictingCaps) + { + if (fSessionAddedCaps) + { + pSession->fAcquiredGuestCaps |= fSessionAddedCaps; + pDevExt->fAcquiredGuestCaps |= fSessionAddedCaps; + } + + if (fSessionRemovedCaps) + { + pSession->fAcquiredGuestCaps &= ~fSessionRemovedCaps; + pDevExt->fAcquiredGuestCaps &= ~fSessionRemovedCaps; + } + + /* + * If something changes (which is very likely), tell the host. + */ + if (fSessionAddedCaps || fSessionRemovedCaps || pDevExt->fGuestCapsHost == UINT32_MAX) + { + Assert(pReq || fSessionTermination); + if (pReq) + { + rc = vgdrvUpdateCapabilitiesOnHostWithReqAndLock(pDevExt, pReq); + if (RT_FAILURE(rc) && !fSessionTermination) + { + /* Failed, roll back. */ + if (fSessionAddedCaps) + { + pSession->fAcquiredGuestCaps &= ~fSessionAddedCaps; + pDevExt->fAcquiredGuestCaps &= ~fSessionAddedCaps; + } + if (fSessionRemovedCaps) + { + pSession->fAcquiredGuestCaps |= fSessionRemovedCaps; + pDevExt->fAcquiredGuestCaps |= fSessionRemovedCaps; + } + + RTSpinlockRelease(pDevExt->EventSpinlock); + LogRel(("vgdrvAcquireSessionCapabilities: vgdrvUpdateCapabilitiesOnHostWithReqAndLock failed: rc=%Rrc\n", rc)); + VbglR0GRFree(&pReq->header); + return rc; + } + } + } + } + else + { + RTSpinlockRelease(pDevExt->EventSpinlock); + + Log(("vgdrvAcquireSessionCapabilities: Caps %#x were busy\n", fOtherConflictingCaps)); + VbglR0GRFree(&pReq->header); + return VERR_RESOURCE_BUSY; + } + + RTSpinlockRelease(pDevExt->EventSpinlock); + if (pReq) + VbglR0GRFree(&pReq->header); + + /* + * If we added a capability, check if that means some other thread in our + * session should be unblocked because there are events pending. + * + * HACK ALERT! When the seamless support capability is added we generate a + * seamless change event so that the ring-3 client can sync with + * the seamless state. Although this introduces a spurious + * wakeups of the ring-3 client, it solves the problem of client + * state inconsistency in multiuser environment (on Windows). + */ + if (fSessionAddedCaps) + { + uint32_t fGenFakeEvents = 0; + if (fSessionAddedCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS) + fGenFakeEvents |= VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST; + + RTSpinlockAcquire(pDevExt->EventSpinlock); + if (fGenFakeEvents || pDevExt->f32PendingEvents) + vgdrvDispatchEventsLocked(pDevExt, fGenFakeEvents); + RTSpinlockRelease(pDevExt->EventSpinlock); + +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP + VGDrvCommonWaitDoWakeUps(pDevExt); +#endif + } + + return VINF_SUCCESS; +} + + +/** + * Handle VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES. + * + * @returns VBox status code. + * + * @param pDevExt The device extension. + * @param pSession The session. + * @param pAcquire The request. + */ +static int vgdrvIoCtl_GuestCapsAcquire(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCACQUIREGUESTCAPS pAcquire) +{ + int rc; + LogFlow(("VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES: or=%#x not=%#x flags=%#x\n", + pAcquire->u.In.fOrMask, pAcquire->u.In.fNotMask, pAcquire->u.In.fFlags)); + + rc = vgdrvAcquireSessionCapabilities(pDevExt, pSession, pAcquire->u.In.fOrMask, pAcquire->u.In.fNotMask, + pAcquire->u.In.fFlags, false /*fSessionTermination*/); + if (RT_FAILURE(rc)) + LogRel(("VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES failed rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Sets the guest capabilities for a session. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param pSession The session. + * @param fOrMask The capabilities to add. + * @param fNotMask The capabilities to remove. + * @param pfSessionCaps Where to return the guest capabilities reported + * for this session. Optional. + * @param pfGlobalCaps Where to return the guest capabilities reported + * for all the sessions. Optional. + * + * @param fSessionTermination Set if we're called by the session cleanup code. + * This tweaks the error handling so we perform + * proper session cleanup even if the host + * misbehaves. + * + * @remarks Takes the session spinlock. + */ +static int vgdrvSetSessionCapabilities(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + uint32_t fOrMask, uint32_t fNotMask, uint32_t *pfSessionCaps, uint32_t *pfGlobalCaps, + bool fSessionTermination) +{ + /* + * Preallocate a request buffer so we can do all in one go without leaving the spinlock. + */ + VMMDevReqGuestCapabilities2 *pReq; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_SetGuestCapabilities); + if (RT_SUCCESS(rc)) + { + if (!fSessionTermination) + pReq->header.fRequestor = pSession->fRequestor; + } + else if (!fSessionTermination) + { + if (pfSessionCaps) + *pfSessionCaps = UINT32_MAX; + if (pfGlobalCaps) + *pfGlobalCaps = UINT32_MAX; + LogRel(("vgdrvSetSessionCapabilities: VbglR0GRAlloc failure: %Rrc\n", rc)); + return rc; + } + else + pReq = NULL; /* Ignore failure, we must do session cleanup. */ + + + RTSpinlockAcquire(pDevExt->SessionSpinlock); + +#ifndef VBOXGUEST_DISREGARD_ACQUIRE_MODE_GUEST_CAPS + /* + * Capabilities in "acquire" mode cannot be set via this API. + * (Acquire mode is only used on windows at the time of writing.) + */ + if (!(fOrMask & pDevExt->fAcquireModeGuestCaps)) +#endif + { + /* + * Apply the changes to the session mask. + */ + uint32_t fChanged; + uint32_t fPrevious = pSession->fCapabilities; + pSession->fCapabilities |= fOrMask; + pSession->fCapabilities &= ~fNotMask; + + /* + * If anything actually changed, update the global usage counters. + */ + fChanged = fPrevious ^ pSession->fCapabilities; + if (fChanged) + { + bool fGlobalChange = vgdrvBitUsageTrackerChange(&pDevExt->SetGuestCapsTracker, fChanged, fPrevious, + pDevExt->cSessions, "SetGuestCapsTracker"); + + /* + * If there are global changes, update the capabilities on the host. + */ + if (fGlobalChange || pDevExt->fGuestCapsHost == UINT32_MAX) + { + Assert(pReq || fSessionTermination); + if (pReq) + { + rc = vgdrvUpdateCapabilitiesOnHostWithReqAndLock(pDevExt, pReq); + + /* On failure, roll back (unless it's session termination time). */ + if (RT_FAILURE(rc) && !fSessionTermination) + { + vgdrvBitUsageTrackerChange(&pDevExt->SetGuestCapsTracker, fChanged, pSession->fCapabilities, + pDevExt->cSessions, "SetGuestCapsTracker"); + pSession->fCapabilities = fPrevious; + } + } + } + } + } +#ifndef VBOXGUEST_DISREGARD_ACQUIRE_MODE_GUEST_CAPS + else + rc = VERR_RESOURCE_BUSY; +#endif + + if (pfSessionCaps) + *pfSessionCaps = pSession->fCapabilities; + if (pfGlobalCaps) + *pfGlobalCaps = pDevExt->fAcquiredGuestCaps | pDevExt->SetGuestCapsTracker.fMask; + + RTSpinlockRelease(pDevExt->SessionSpinlock); + if (pReq) + VbglR0GRFree(&pReq->header); + return rc; +} + + +/** + * Handle VBGL_IOCTL_CHANGE_GUEST_CAPABILITIES. + * + * @returns VBox status code. + * + * @param pDevExt The device extension. + * @param pSession The session. + * @param pInfo The request. + */ +static int vgdrvIoCtl_SetCapabilities(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCSETGUESTCAPS pInfo) +{ + int rc; + LogFlow(("VBGL_IOCTL_CHANGE_GUEST_CAPABILITIES: or=%#x not=%#x\n", pInfo->u.In.fOrMask, pInfo->u.In.fNotMask)); + + if (!((pInfo->u.In.fOrMask | pInfo->u.In.fNotMask) & ~VMMDEV_GUEST_CAPABILITIES_MASK)) + rc = vgdrvSetSessionCapabilities(pDevExt, pSession, pInfo->u.In.fOrMask, pInfo->u.In.fNotMask, + &pInfo->u.Out.fSessionCaps, &pInfo->u.Out.fGlobalCaps, false /*fSessionTermination*/); + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + +/** @} */ + + +/** + * Common IOCtl for user to kernel and kernel to kernel communication. + * + * This function only does the basic validation and then invokes + * worker functions that takes care of each specific function. + * + * @returns VBox status code. + * + * @param iFunction The requested function. + * @param pDevExt The device extension. + * @param pSession The client session. + * @param pReqHdr Pointer to the request. This always starts with + * a request common header. + * @param cbReq The max size of the request buffer. + */ +int VGDrvCommonIoCtl(uintptr_t iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLREQHDR pReqHdr, size_t cbReq) +{ + uintptr_t const iFunctionStripped = VBGL_IOCTL_CODE_STRIPPED(iFunction); + int rc; + + LogFlow(("VGDrvCommonIoCtl: iFunction=%#x pDevExt=%p pSession=%p pReqHdr=%p cbReq=%zu\n", + iFunction, pDevExt, pSession, pReqHdr, cbReq)); + + /* + * Define some helper macros to simplify validation. + */ +#define REQ_CHECK_SIZES_EX(Name, cbInExpect, cbOutExpect) \ + do { \ + if (RT_LIKELY( pReqHdr->cbIn == (cbInExpect) \ + && ( pReqHdr->cbOut == (cbOutExpect) \ + || ((cbInExpect) == (cbOutExpect) && pReqHdr->cbOut == 0) ) )) \ + { /* likely */ } \ + else \ + { \ + Log(( #Name ": Invalid input/output sizes. cbIn=%ld expected %ld. cbOut=%ld expected %ld.\n", \ + (long)pReqHdr->cbIn, (long)(cbInExpect), (long)pReqHdr->cbOut, (long)(cbOutExpect))); \ + return pReqHdr->rc = VERR_INVALID_PARAMETER; \ + } \ + } while (0) + +#define REQ_CHECK_SIZES(Name) REQ_CHECK_SIZES_EX(Name, Name ## _SIZE_IN, Name ## _SIZE_OUT) + +#define REQ_CHECK_SIZE_IN(Name, cbInExpect) \ + do { \ + if (RT_LIKELY(pReqHdr->cbIn == (cbInExpect))) \ + { /* likely */ } \ + else \ + { \ + Log(( #Name ": Invalid input/output sizes. cbIn=%ld expected %ld.\n", \ + (long)pReqHdr->cbIn, (long)(cbInExpect))); \ + return pReqHdr->rc = VERR_INVALID_PARAMETER; \ + } \ + } while (0) + +#define REQ_CHECK_SIZE_OUT(Name, cbOutExpect) \ + do { \ + if (RT_LIKELY( pReqHdr->cbOut == (cbOutExpect) \ + || (pReqHdr->cbOut == 0 && pReqHdr->cbIn == (cbOutExpect)))) \ + { /* likely */ } \ + else \ + { \ + Log(( #Name ": Invalid input/output sizes. cbOut=%ld (%ld) expected %ld.\n", \ + (long)pReqHdr->cbOut, (long)pReqHdr->cbIn, (long)(cbOutExpect))); \ + return pReqHdr->rc = VERR_INVALID_PARAMETER; \ + } \ + } while (0) + +#define REQ_CHECK_EXPR(Name, expr) \ + do { \ + if (RT_LIKELY(!!(expr))) \ + { /* likely */ } \ + else \ + { \ + Log(( #Name ": %s\n", #expr)); \ + return pReqHdr->rc = VERR_INVALID_PARAMETER; \ + } \ + } while (0) + +#define REQ_CHECK_EXPR_FMT(expr, fmt) \ + do { \ + if (RT_LIKELY(!!(expr))) \ + { /* likely */ } \ + else \ + { \ + Log( fmt ); \ + return pReqHdr->rc = VERR_INVALID_PARAMETER; \ + } \ + } while (0) + +#define REQ_CHECK_RING0(mnemonic) \ + do { \ + if (pSession->R0Process != NIL_RTR0PROCESS) \ + { \ + LogFunc((mnemonic ": Ring-0 only, caller is %RTproc/%p\n", \ + pSession->Process, (uintptr_t)pSession->R0Process)); \ + return pReqHdr->rc = VERR_PERMISSION_DENIED; \ + } \ + } while (0) + + + /* + * Validate the request. + */ + if (RT_LIKELY(cbReq >= sizeof(*pReqHdr))) + { /* likely */ } + else + { + Log(("VGDrvCommonIoCtl: Bad ioctl request size; cbReq=%#lx\n", (long)cbReq)); + return VERR_INVALID_PARAMETER; + } + + if (pReqHdr->cbOut == 0) + pReqHdr->cbOut = pReqHdr->cbIn; + + if (RT_LIKELY( pReqHdr->uVersion == VBGLREQHDR_VERSION + && pReqHdr->cbIn >= sizeof(*pReqHdr) + && pReqHdr->cbIn <= cbReq + && pReqHdr->cbOut >= sizeof(*pReqHdr) + && pReqHdr->cbOut <= cbReq)) + { /* likely */ } + else + { + Log(("VGDrvCommonIoCtl: Bad ioctl request header; cbIn=%#lx cbOut=%#lx version=%#lx\n", + (long)pReqHdr->cbIn, (long)pReqHdr->cbOut, (long)pReqHdr->uVersion)); + return VERR_INVALID_PARAMETER; + } + + if (RT_LIKELY(RT_VALID_PTR(pSession))) + { /* likely */ } + else + { + Log(("VGDrvCommonIoCtl: Invalid pSession value %p (ioctl=%#x)\n", pSession, iFunction)); + return VERR_INVALID_PARAMETER; + } + + + /* + * Deal with variably sized requests first. + */ + rc = VINF_SUCCESS; + if ( iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_VMMDEV_REQUEST(0)) + || iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_VMMDEV_REQUEST_BIG) ) + { + REQ_CHECK_EXPR(VBGL_IOCTL_VMMDEV_REQUEST, pReqHdr->uType != VBGLREQHDR_TYPE_DEFAULT); + REQ_CHECK_EXPR_FMT(pReqHdr->cbIn == pReqHdr->cbOut, + ("VBGL_IOCTL_VMMDEV_REQUEST: cbIn=%ld != cbOut=%ld\n", (long)pReqHdr->cbIn, (long)pReqHdr->cbOut)); + pReqHdr->rc = vgdrvIoCtl_VMMDevRequest(pDevExt, pSession, (VMMDevRequestHeader *)pReqHdr, cbReq); + } + else if (RT_LIKELY(pReqHdr->uType == VBGLREQHDR_TYPE_DEFAULT)) + { + if (iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_LOG(0))) + { + REQ_CHECK_SIZE_OUT(VBGL_IOCTL_LOG, VBGL_IOCTL_LOG_SIZE_OUT); + pReqHdr->rc = vgdrvIoCtl_Log(pDevExt, &((PVBGLIOCLOG)pReqHdr)->u.In.szMsg[0], pReqHdr->cbIn - sizeof(VBGLREQHDR), + pSession->fUserSession); + } +#ifdef VBOX_WITH_HGCM + else if (iFunction == VBGL_IOCTL_IDC_HGCM_FAST_CALL) /* (is variable size, but we don't bother encoding it) */ + { + REQ_CHECK_RING0("VBGL_IOCTL_IDC_HGCM_FAST_CALL"); + REQ_CHECK_EXPR(VBGL_IOCTL_IDC_HGCM_FAST_CALL, cbReq >= sizeof(VBGLIOCIDCHGCMFASTCALL) + sizeof(VMMDevHGCMCall)); + pReqHdr->rc = vgdrvIoCtl_HGCMFastCall(pDevExt, (VBGLIOCIDCHGCMFASTCALL volatile *)pReqHdr); + } + else if ( iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_HGCM_CALL(0)) +# if ARCH_BITS == 64 + || iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_HGCM_CALL_32(0)) +# endif + ) + { + REQ_CHECK_EXPR(VBGL_IOCTL_HGCM_CALL, pReqHdr->cbIn >= sizeof(VBGLIOCHGCMCALL)); + REQ_CHECK_EXPR(VBGL_IOCTL_HGCM_CALL, pReqHdr->cbIn == pReqHdr->cbOut); + pReqHdr->rc = vgdrvIoCtl_HGCMCallWrapper(pDevExt, pSession, (PVBGLIOCHGCMCALL)pReqHdr, + iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_HGCM_CALL_32(0)), + false /*fUserData*/, cbReq); + } + else if (iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_HGCM_CALL_WITH_USER_DATA(0))) + { + REQ_CHECK_RING0("VBGL_IOCTL_HGCM_CALL_WITH_USER_DATA"); + REQ_CHECK_EXPR(VBGL_IOCTL_HGCM_CALL, pReqHdr->cbIn >= sizeof(VBGLIOCHGCMCALL)); + REQ_CHECK_EXPR(VBGL_IOCTL_HGCM_CALL, pReqHdr->cbIn == pReqHdr->cbOut); + pReqHdr->rc = vgdrvIoCtl_HGCMCallWrapper(pDevExt, pSession, (PVBGLIOCHGCMCALL)pReqHdr, + ARCH_BITS == 32, true /*fUserData*/, cbReq); + } +#endif /* VBOX_WITH_HGCM */ + else + { + switch (iFunction) + { + /* + * Ring-0 only: + */ + case VBGL_IOCTL_IDC_CONNECT: + REQ_CHECK_RING0("VBGL_IOCL_IDC_CONNECT"); + REQ_CHECK_SIZES(VBGL_IOCTL_IDC_CONNECT); + pReqHdr->rc = vgdrvIoCtl_IdcConnect(pDevExt, pSession, (PVBGLIOCIDCCONNECT)pReqHdr); + break; + + case VBGL_IOCTL_IDC_DISCONNECT: + REQ_CHECK_RING0("VBGL_IOCTL_IDC_DISCONNECT"); + REQ_CHECK_SIZES(VBGL_IOCTL_IDC_DISCONNECT); + pReqHdr->rc = vgdrvIoCtl_IdcDisconnect(pDevExt, pSession, (PVBGLIOCIDCDISCONNECT)pReqHdr); + break; + + case VBGL_IOCTL_GET_VMMDEV_IO_INFO: + REQ_CHECK_RING0("GET_VMMDEV_IO_INFO"); + REQ_CHECK_SIZES(VBGL_IOCTL_GET_VMMDEV_IO_INFO); + pReqHdr->rc = vgdrvIoCtl_GetVMMDevIoInfo(pDevExt, (PVBGLIOCGETVMMDEVIOINFO)pReqHdr); + break; + + case VBGL_IOCTL_SET_MOUSE_NOTIFY_CALLBACK: + REQ_CHECK_RING0("SET_MOUSE_NOTIFY_CALLBACK"); + REQ_CHECK_SIZES(VBGL_IOCTL_SET_MOUSE_NOTIFY_CALLBACK); + pReqHdr->rc = vgdrvIoCtl_SetMouseNotifyCallback(pDevExt, (PVBGLIOCSETMOUSENOTIFYCALLBACK)pReqHdr); + break; + + /* + * Ring-3 only: + */ + case VBGL_IOCTL_DRIVER_VERSION_INFO: + REQ_CHECK_SIZES(VBGL_IOCTL_DRIVER_VERSION_INFO); + pReqHdr->rc = vgdrvIoCtl_DriverVersionInfo(pDevExt, pSession, (PVBGLIOCDRIVERVERSIONINFO)pReqHdr); + break; + + /* + * Both ring-3 and ring-0: + */ + case VBGL_IOCTL_WAIT_FOR_EVENTS: + REQ_CHECK_SIZES(VBGL_IOCTL_WAIT_FOR_EVENTS); + pReqHdr->rc = vgdrvIoCtl_WaitForEvents(pDevExt, pSession, (VBGLIOCWAITFOREVENTS *)pReqHdr, + pSession->R0Process != NIL_RTR0PROCESS); + break; + + case VBGL_IOCTL_INTERRUPT_ALL_WAIT_FOR_EVENTS: + REQ_CHECK_SIZES(VBGL_IOCTL_INTERRUPT_ALL_WAIT_FOR_EVENTS); + pReqHdr->rc = vgdrvIoCtl_CancelAllWaitEvents(pDevExt, pSession); + break; + + case VBGL_IOCTL_CHANGE_FILTER_MASK: + REQ_CHECK_SIZES(VBGL_IOCTL_CHANGE_FILTER_MASK); + pReqHdr->rc = vgdrvIoCtl_ChangeFilterMask(pDevExt, pSession, (PVBGLIOCCHANGEFILTERMASK)pReqHdr); + break; + +#ifdef VBOX_WITH_HGCM + case VBGL_IOCTL_HGCM_CONNECT: + REQ_CHECK_SIZES(VBGL_IOCTL_HGCM_CONNECT); + pReqHdr->rc = vgdrvIoCtl_HGCMConnect(pDevExt, pSession, (PVBGLIOCHGCMCONNECT)pReqHdr); + break; + + case VBGL_IOCTL_HGCM_DISCONNECT: + REQ_CHECK_SIZES(VBGL_IOCTL_HGCM_DISCONNECT); + pReqHdr->rc = vgdrvIoCtl_HGCMDisconnect(pDevExt, pSession, (PVBGLIOCHGCMDISCONNECT)pReqHdr); + break; +#endif + + case VBGL_IOCTL_CHECK_BALLOON: + REQ_CHECK_SIZES(VBGL_IOCTL_CHECK_BALLOON); + pReqHdr->rc = vgdrvIoCtl_CheckMemoryBalloon(pDevExt, pSession, (PVBGLIOCCHECKBALLOON)pReqHdr); + break; + + case VBGL_IOCTL_CHANGE_BALLOON: + REQ_CHECK_SIZES(VBGL_IOCTL_CHANGE_BALLOON); + pReqHdr->rc = vgdrvIoCtl_ChangeMemoryBalloon(pDevExt, pSession, (PVBGLIOCCHANGEBALLOON)pReqHdr); + break; + + case VBGL_IOCTL_WRITE_CORE_DUMP: + REQ_CHECK_SIZES(VBGL_IOCTL_WRITE_CORE_DUMP); + pReqHdr->rc = vgdrvIoCtl_WriteCoreDump(pDevExt, pSession, (PVBGLIOCWRITECOREDUMP)pReqHdr); + break; + + case VBGL_IOCTL_SET_MOUSE_STATUS: + REQ_CHECK_SIZES(VBGL_IOCTL_SET_MOUSE_STATUS); + pReqHdr->rc = vgdrvIoCtl_SetMouseStatus(pDevExt, pSession, ((PVBGLIOCSETMOUSESTATUS)pReqHdr)->u.In.fStatus); + break; + + case VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES: + REQ_CHECK_SIZES(VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES); + pReqHdr->rc = vgdrvIoCtl_GuestCapsAcquire(pDevExt, pSession, (PVBGLIOCACQUIREGUESTCAPS)pReqHdr); + break; + + case VBGL_IOCTL_CHANGE_GUEST_CAPABILITIES: + REQ_CHECK_SIZES(VBGL_IOCTL_CHANGE_GUEST_CAPABILITIES); + pReqHdr->rc = vgdrvIoCtl_SetCapabilities(pDevExt, pSession, (PVBGLIOCSETGUESTCAPS)pReqHdr); + break; + +#ifdef VBOX_WITH_DPC_LATENCY_CHECKER + case VBGL_IOCTL_DPC_LATENCY_CHECKER: + REQ_CHECK_SIZES(VBGL_IOCTL_DPC_LATENCY_CHECKER); + pReqHdr->rc = VGDrvNtIOCtl_DpcLatencyChecker(); + break; +#endif + + default: + { + LogRel(("VGDrvCommonIoCtl: Unknown request iFunction=%#x (stripped %#x) cbReq=%#x\n", + iFunction, iFunctionStripped, cbReq)); + pReqHdr->rc = rc = VERR_NOT_SUPPORTED; + break; + } + } + } + } + else + { + Log(("VGDrvCommonIoCtl: uType=%#x, expected default (ioctl=%#x)\n", pReqHdr->uType, iFunction)); + return VERR_INVALID_PARAMETER; + } + + LogFlow(("VGDrvCommonIoCtl: returns %Rrc (req: rc=%Rrc cbOut=%#x)\n", rc, pReqHdr->rc, pReqHdr->cbOut)); + return rc; +} + + +/** + * Used by VGDrvCommonISR as well as the acquire guest capability code. + * + * @returns VINF_SUCCESS on success. On failure, ORed together + * RTSemEventMultiSignal errors (completes processing despite errors). + * @param pDevExt The VBoxGuest device extension. + * @param fEvents The events to dispatch. + */ +static int vgdrvDispatchEventsLocked(PVBOXGUESTDEVEXT pDevExt, uint32_t fEvents) +{ + PVBOXGUESTWAIT pWait; + PVBOXGUESTWAIT pSafe; + int rc = VINF_SUCCESS; + + fEvents |= pDevExt->f32PendingEvents; + + RTListForEachSafe(&pDevExt->WaitList, pWait, pSafe, VBOXGUESTWAIT, ListNode) + { + uint32_t fHandledEvents = pWait->fReqEvents & fEvents; + if ( fHandledEvents != 0 + && !pWait->fResEvents) + { + /* Does this one wait on any of the events we're dispatching? We do a quick + check first, then deal with VBOXGUEST_ACQUIRE_STYLE_EVENTS as applicable. */ + if (fHandledEvents & VBOXGUEST_ACQUIRE_STYLE_EVENTS) + fHandledEvents &= vgdrvGetAllowedEventMaskForSession(pDevExt, pWait->pSession); + if (fHandledEvents) + { + pWait->fResEvents = pWait->fReqEvents & fEvents & fHandledEvents; + fEvents &= ~pWait->fResEvents; + RTListNodeRemove(&pWait->ListNode); +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP + RTListAppend(&pDevExt->WakeUpList, &pWait->ListNode); +#else + RTListAppend(&pDevExt->WokenUpList, &pWait->ListNode); + rc |= RTSemEventMultiSignal(pWait->Event); +#endif + if (!fEvents) + break; + } + } + } + + ASMAtomicWriteU32(&pDevExt->f32PendingEvents, fEvents); + return rc; +} + + +/** + * Simply checks whether the IRQ is ours or not, does not do any interrupt + * procesing. + * + * @returns true if it was our interrupt, false if it wasn't. + * @param pDevExt The VBoxGuest device extension. + */ +bool VGDrvCommonIsOurIRQ(PVBOXGUESTDEVEXT pDevExt) +{ + VMMDevMemory volatile *pVMMDevMemory; + bool fOurIrq; + + RTSpinlockAcquire(pDevExt->EventSpinlock); + pVMMDevMemory = pDevExt->pVMMDevMemory; + fOurIrq = pVMMDevMemory ? pVMMDevMemory->V.V1_04.fHaveEvents : false; + RTSpinlockRelease(pDevExt->EventSpinlock); + + return fOurIrq; +} + + +/** + * Common interrupt service routine. + * + * This deals with events and with waking up thread waiting for those events. + * + * @returns true if it was our interrupt, false if it wasn't. + * @param pDevExt The VBoxGuest device extension. + */ +bool VGDrvCommonISR(PVBOXGUESTDEVEXT pDevExt) +{ + VMMDevEvents volatile *pReq; + bool fMousePositionChanged = false; + int rc = 0; + VMMDevMemory volatile *pVMMDevMemory; + bool fOurIrq; + + /* + * Make sure we've initialized the device extension. + */ + if (RT_LIKELY(pDevExt->fHostFeatures & VMMDEV_HVF_FAST_IRQ_ACK)) + pReq = NULL; + else if (RT_LIKELY((pReq = pDevExt->pIrqAckEvents) != NULL)) + { /* likely */ } + else + return false; + + /* + * Enter the spinlock and check if it's our IRQ or not. + */ + RTSpinlockAcquire(pDevExt->EventSpinlock); + pVMMDevMemory = pDevExt->pVMMDevMemory; + fOurIrq = pVMMDevMemory ? pVMMDevMemory->V.V1_04.fHaveEvents : false; + if (fOurIrq) + { + /* + * Acknowledge events. + * We don't use VbglR0GRPerform here as it may take another spinlocks. + */ + uint32_t fEvents; + if (!pReq) + { + fEvents = ASMInU32(pDevExt->IOPortBase + VMMDEV_PORT_OFF_REQUEST_FAST); + ASMCompilerBarrier(); /* paranoia */ + rc = fEvents != UINT32_MAX ? VINF_SUCCESS : VERR_INTERNAL_ERROR; + } + else + { + pReq->header.rc = VERR_INTERNAL_ERROR; + pReq->events = 0; + ASMCompilerBarrier(); + ASMOutU32(pDevExt->IOPortBase + VMMDEV_PORT_OFF_REQUEST, (uint32_t)pDevExt->PhysIrqAckEvents); + ASMCompilerBarrier(); /* paranoia */ + fEvents = pReq->events; + rc = pReq->header.rc; + } + if (RT_SUCCESS(rc)) + { + Log3(("VGDrvCommonISR: acknowledge events succeeded %#RX32\n", fEvents)); + + /* + * VMMDEV_EVENT_MOUSE_POSITION_CHANGED can only be polled for. + */ + if (fEvents & VMMDEV_EVENT_MOUSE_POSITION_CHANGED) + { + fMousePositionChanged = true; + fEvents &= ~VMMDEV_EVENT_MOUSE_POSITION_CHANGED; +#if !defined(VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT) + if (pDevExt->pfnMouseNotifyCallback) + pDevExt->pfnMouseNotifyCallback(pDevExt->pvMouseNotifyCallbackArg); +#endif + } + +#ifdef VBOX_WITH_HGCM + /* + * The HGCM event/list is kind of different in that we evaluate all entries. + */ + if (fEvents & VMMDEV_EVENT_HGCM) + { + PVBOXGUESTWAIT pWait; + PVBOXGUESTWAIT pSafe; + RTListForEachSafe(&pDevExt->HGCMWaitList, pWait, pSafe, VBOXGUESTWAIT, ListNode) + { + if (pWait->pHGCMReq->fu32Flags & VBOX_HGCM_REQ_DONE) + { + pWait->fResEvents = VMMDEV_EVENT_HGCM; + RTListNodeRemove(&pWait->ListNode); +# ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP + RTListAppend(&pDevExt->WakeUpList, &pWait->ListNode); +# else + RTListAppend(&pDevExt->WokenUpList, &pWait->ListNode); + rc |= RTSemEventMultiSignal(pWait->Event); +# endif + } + } + fEvents &= ~VMMDEV_EVENT_HGCM; + } +#endif + + /* + * Normal FIFO waiter evaluation. + */ + rc |= vgdrvDispatchEventsLocked(pDevExt, fEvents); + } + else /* something is serious wrong... */ + Log(("VGDrvCommonISR: acknowledge events failed rc=%Rrc (events=%#x)!!\n", rc, fEvents)); + } + else + Log3(("VGDrvCommonISR: not ours\n")); + + RTSpinlockRelease(pDevExt->EventSpinlock); + + /* + * Execute the mouse notification callback here if it cannot be executed while + * holding the interrupt safe spinlock, see @bugref{8639}. + */ +#if defined(VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT) && !defined(RT_OS_WINDOWS) /* (Windows does this in the Dpc callback) */ + if ( fMousePositionChanged + && pDevExt->pfnMouseNotifyCallback) + pDevExt->pfnMouseNotifyCallback(pDevExt->pvMouseNotifyCallbackArg); +#endif + +#if defined(VBOXGUEST_USE_DEFERRED_WAKE_UP) && !defined(RT_OS_WINDOWS) + /* + * Do wake-ups. + * Note. On Windows this isn't possible at this IRQL, so a DPC will take + * care of it. Same on darwin, doing it in the work loop callback. + */ + VGDrvCommonWaitDoWakeUps(pDevExt); +#endif + + /* + * Work the poll and async notification queues on OSes that implements that. + * (Do this outside the spinlock to prevent some recursive spinlocking.) + */ + if (fMousePositionChanged) + { + ASMAtomicIncU32(&pDevExt->u32MousePosChangedSeq); + VGDrvNativeISRMousePollEvent(pDevExt); + } + + AssertMsg(rc == 0, ("rc=%#x (%d)\n", rc, rc)); + return fOurIrq; +} diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuestA-os2.asm b/src/VBox/Additions/common/VBoxGuest/VBoxGuestA-os2.asm new file mode 100644 index 00000000..12df6b42 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuestA-os2.asm @@ -0,0 +1,1679 @@ +; $Id: VBoxGuestA-os2.asm $ +;; @file +; VBoxGuest - OS/2 assembly file, the first file in the link. +; + +; +; Copyright (C) 2007-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +;----------------------------------------------------------------------------- +; This code is based on: +; +; VBoxDrv - OS/2 assembly file, the first file in the link. +; +; Copyright (c) 2007-2010 knut st. osmundsen <bird-src-spam@anduin.net> +; +; Permission is hereby granted, free of charge, to any person +; obtaining a copy of this software and associated documentation +; files (the "Software"), to deal in the Software without +; restriction, including without limitation the rights to use, +; copy, modify, merge, publish, distribute, sublicense, and/or sell +; copies of the Software, and to permit persons to whom the +; Software is furnished to do so, subject to the following +; conditions: +; +; The above copyright notice and this permission notice shall be +; included in all copies or substantial portions of the Software. +; +; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +; OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +; OTHER DEALINGS IN THE SOFTWARE. +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%define RT_INCL_16BIT_SEGMENTS +%include "iprt/asmdefs.mac" +%include "iprt/err.mac" +%include "VBox/VBoxGuest.mac" + + +;******************************************************************************* +;* Structures and Typedefs * +;******************************************************************************* +;; +; Request packet header. +struc PKTHDR + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 +endstruc + + +;; +; Init request packet - input. +struc PKTINITIN + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + + .data_1 resb 1 + .fpfnDevHlp resd 1 + .fpszArgs resd 1 + .data_2 resb 1 +endstruc + +;; +; Init request packet - output. +struc PKTINITOUT + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + + .cUnits resb 1 ; block devs only. + .cbCode16 resw 1 + .cbData16 resw 1 + .fpaBPBs resd 1 ; block devs only. + .data_2 resb 1 +endstruc + +;; +; Open request packet. +struc PKTOPEN + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + .sfn resw 1 +endstruc + +;; +; Close request packet. +struc PKTCLOSE + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + .sfn resw 1 +endstruc + +;; +; IOCtl request packet. +struc PKTIOCTL + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + + .cat resb 1 + .fun resb 1 + .pParm resd 1 + .pData resd 1 + .sfn resw 1 + .cbParm resw 1 + .cbData resw 1 +endstruc + +;; +; Read/Write request packet +struc PKTRW + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + + .media resb 1 + .PhysTrans resd 1 + .cbTrans resw 1 + .start resd 1 + .sfn resw 1 +endstruc + + +;******************************************************************************* +;* Defined Constants And Macros * +;******************************************************************************* +; Some devhdr.inc stuff. +%define DEVLEV_3 0180h +%define DEV_30 0800h +%define DEV_IOCTL 4000h +%define DEV_CHAR_DEV 8000h + +%define DEV_16MB 0002h +%define DEV_IOCTL2 0001h + +; Some dhcalls.h stuff. +%define DevHlp_VirtToLin 05bh +%define DevHlp_SAVE_MESSAGE 03dh +%define DevHlp_EOI 031h +%define DevHlp_SetIRQ 01bh +%define DevHlp_PhysToVirt 015h + +; Fast IOCtl category, also defined in VBoxGuest.h +%define VBGL_IOCTL_CATEGORY_FAST 0c3h + +;; +; Got some nasm/link trouble, so emit the stuff ourselves. +; @param %1 Must be a GLOBALNAME. +%macro JMP32TO16 1 + ;jmp far dword NAME(%1) wrt CODE16 + db 066h + db 0eah + dw NAME(%1) wrt CODE16 + dw CODE16 +%endmacro + +;; +; Got some nasm/link trouble, so emit the stuff ourselves. +; @param %1 Must be a GLOBALNAME. +%macro JMP16TO32 1 + ;jmp far dword NAME(%1) wrt FLAT + db 066h + db 0eah + dd NAME(%1) ;wrt FLAT + dw TEXT32 wrt FLAT +%endmacro + + +;******************************************************************************* +;* External Symbols * +;******************************************************************************* +segment CODE16 +extern DOS16OPEN +extern DOS16CLOSE +extern DOS16WRITE +extern DOS16DEVIOCTL2 +segment TEXT32 +extern KernThunkStackTo32 +extern KernThunkStackTo16 + +extern NAME(vgdrvOS2Init) +extern NAME(vgdrvOS2Open) +extern NAME(vgdrvOS2Close) +extern NAME(vgdrvOS2IOCtl) +extern NAME(vgdrvOS2IOCtlFast) +extern NAME(vgdrvOS2IDCConnect) +extern NAME(VGDrvOS2IDCService) +extern NAME(vgdrvOS2ISR) + + +segment DATA16 + +;; +; Device headers. The first one is the one we'll be opening and the +; latter is only used for 32-bit initialization. +GLOBALNAME g_VBoxGuestHdr1 + dw NAME(g_VBoxGuestHdr2) wrt DATA16 ; NextHeader.off + dw DATA16 ; NextHeader.sel + dw DEVLEV_3 | DEV_30 | DEV_CHAR_DEV | DEV_IOCTL; SDevAtt + dw NAME(VGDrvOS2Entrypoint) wrt CODE16 ; StrategyEP + dw NAME(VGDrvOS2IDC) wrt CODE16 ; IDCEP + db 'vboxgst$' ; DevName + dw 0 ; SDevProtCS + dw 0 ; SDevProtDS + dw 0 ; SDevRealCS + dw 0 ; SDevRealDS + dd DEV_16MB | DEV_IOCTL2 ; SDevCaps + +align 4 +GLOBALNAME g_VBoxGuestHdr2 + dd 0ffffffffh ; NextHeader (NIL) + dw DEVLEV_3 | DEV_30 | DEV_CHAR_DEV ; SDevAtt + dw NAME(vgdrvOS2InitEntrypoint) wrt CODE16 ; StrategyEP + dw 0 ; IDCEP + db 'vboxgs1$' ; DevName + dw 0 ; SDevProtCS + dw 0 ; SDevProtDS + dw 0 ; SDevRealCS + dw 0 ; SDevRealDS + dd DEV_16MB | DEV_IOCTL2 ; SDevCaps + + +;; Tristate 32-bit initialization indicator [0 = need init, -1 = init failed, 1 init succeeded]. +; Check in the open path of the primary driver. The secondary driver will +; open the primary one during it's init and thereby trigger the 32-bit init. +GLOBALNAME g_fInitialized + db 0 + +align 4 +;; Pointer to the device helper service routine +; This is set during the initialization of the 2nd device driver. +GLOBALNAME g_fpfnDevHlp + dd 0 + + +;; vgdrvFindAdapter Output +; @{ + +;; The MMIO base of the VMMDev. +GLOBALNAME g_PhysMMIOBase + dd 0 +;; The size of the MMIO memory of the VMMDev. +GLOBALNAME g_cbMMIO + dd 0 +;; The I/O port base of the VMMDev. +GLOBALNAME g_IOPortBase + dw 0 +;; The VMMDev Interrupt Line. +GLOBALNAME g_bInterruptLine + db 0 +;; The PCI bus number returned by Find PCI Device. +GLOBALNAME g_bPciBusNo + db 0 +;; The PCI Device No / Function Number returned by Find PCI Device. +; (The upper 5 bits is the number, and the lower 3 the function.) +GLOBALNAME g_bPciDevFunNo + db 0 +;; Flag that is set by the vboxgst$ init routine if VMMDev was found. +; Both init routines must refuse loading the driver if the +; device cannot be located. +GLOBALNAME g_fFoundAdapter + db 0 +;; @} + + +%ifdef DEBUG_READ +;; Where we write to the log. +GLOBALNAME g_offLogHead + dw 0 +;; Where we read from the log. +GLOBALNAME g_offLogTail + dw 0 +;; The size of the log. (power of two!) +%define LOG_SIZE 16384 +GLOBALNAME g_cchLogMax + dw LOG_SIZE +;; The log buffer. +GLOBALNAME g_szLog + times LOG_SIZE db 0 +%endif ; DEBUG_READ + + +; +; The init data. +; +segment DATA16_INIT +GLOBALNAME g_InitDataStart + +;; Far pointer to the device argument. +g_fpszArgs: + dd 0 + +%if 0 +;; Message table for the Save_Message device helper. +GLOBALNAME g_MsgTab + dw 1178 ; MsgId - 'MSG_REPLACEMENT_STRING'. + dw 1 ; cMsgStrings + dw NAME(g_szInitText) ; MsgStrings[0] + dw seg NAME(g_szInitText) +%else +;; Far pointer to DOS16WRITE (corrected set before called). +; Just a temporary hack to work around a wlink issue. +GLOBALNAME g_fpfnDos16Write + dw DOS16WRITE + dw seg DOS16WRITE +%endif + +;; Size of the text currently in the g_szInitText buffer. +GLOBALNAME g_cchInitText + dw 0 +;; The max size of text that can fit into the g_szInitText buffer. +GLOBALNAME g_cchInitTextMax + dw 512 +;; The init text buffer. +GLOBALNAME g_szInitText + times 512 db 0 + +;; Message string that's written on failure. +g_achLoadFailureMsg1: + db 0dh,0ah,'VBoxGuest: load failure no. ' +g_cchLoadFailureMsg1 EQU $ - g_achLoadFailureMsg1 +g_achLoadFailureMsg2: + db '!',0dh,0ah +g_cchLoadFailureMsg2 EQU $ - g_achLoadFailureMsg2 + + +; +; The 16-bit code segment. +; +segment CODE16 + + +;; +; The strategy entry point (vboxdrv$). +; +; ss:bx -> request packet +; ds:si -> device header +; +; Can clobber any registers it likes except SP. +; +BEGINPROC VGDrvOS2Entrypoint + push ebp + mov ebp, esp + push es ; bp - 2 + push bx ; bp - 4 + and sp, 0fffch + + ; + ; Check for the most frequent first. + ; + cmp byte [es:bx + PKTHDR.cmd], 10h ; Generic IOCtl + jne near vgdrvOS2EP_NotGenIOCtl + + + ; + ; Generic I/O Control Request. + ; +vgdrvOS2EP_GenIOCtl: + + ; Fast IOCtl? + cmp byte [es:bx + PKTIOCTL.cat], VBGL_IOCTL_CATEGORY_FAST + jne vgdrvOS2EP_GenIOCtl_Other + + ; + ; Fast IOCtl. + ; DECLASM(int) vgdrvOS2IOCtlFast(uint16_t sfn, uint8_t iFunction, uint16_t *pcbParm) + ; +vgdrvOS2EP_GenIOCtl_Fast: + mov ax, [es:bx + PKTIOCTL.pData + 2] ; LDT selector to flat address. + shr ax, 3 + shl eax, 16 + mov ax, [es:bx + PKTIOCTL.pData] + push eax ; 08h - pointer to the rc buffer. + + ; function. + movzx edx, byte [es:bx + PKTIOCTL.fun] + push edx ; 04h + + ; system file number. + movzx eax, word [es:bx + PKTIOCTL.sfn] + push eax ; 00h + + JMP16TO32 vgdrvOS2EP_GenIOCtl_Fast_32 +segment TEXT32 +GLOBALNAME vgdrvOS2EP_GenIOCtl_Fast_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; call the C code (don't cleanup the stack). + call NAME(vgdrvOS2IOCtlFast) + + ; switch back the stack. + push eax + call KernThunkStackTo16 + pop eax + + JMP32TO16 vgdrvOS2EP_GenIOCtl_Fast_32 +segment CODE16 +GLOBALNAME vgdrvOS2EP_GenIOCtl_Fast_16 + + les bx, [bp - 4] ; Reload the packet pointer. + or eax, eax + jnz near vgdrvOS2EP_GeneralFailure + + ; setup output stuff. + mov edx, esp + mov eax, [ss:edx + 0ch] ; output sizes. + mov [es:bx + PKTIOCTL.cbParm], eax ; update cbParm and cbData. + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + + mov sp, bp + pop ebp + retf + + ; + ; Other IOCtl (slow) + ; +vgdrvOS2EP_GenIOCtl_Other: + mov eax, [es:bx + PKTIOCTL.cbParm] ; Load cbParm and cbData + push eax ; 1eh - in/out data size. + ; 1ch - in/out parameter size. + push edx ; 18h - pointer to data size (filled in later). + push ecx ; 14h - pointer to param size (filled in later). + + ; pData (convert to flat 32-bit) + mov ax, word [es:bx + PKTIOCTL.pData + 2] ; selector + cmp ax, 3 ; <= 3 -> nil selector... + jbe .no_data + movzx esi, word [es:bx + PKTIOCTL.pData] ; offset + mov dl, DevHlp_VirtToLin + call far [NAME(g_fpfnDevHlp)] + jc near vgdrvOS2EP_GeneralFailure + jmp .finish_data +.no_data: + xor eax, eax +.finish_data: + push eax ; 10h + + ; pParm (convert to flat 32-bit) + mov ax, word [es:bx + PKTIOCTL.pParm + 2] ; selector + cmp ax, 3 ; <= 3 -> nil selector... + jbe .no_parm + movzx esi, word [es:bx + PKTIOCTL.pParm] ; offset + mov dl, DevHlp_VirtToLin + call far [NAME(g_fpfnDevHlp)] + jc near vgdrvOS2EP_GeneralFailure + jmp .finish_parm +.no_parm: + xor eax, eax +.finish_parm: + push eax ; 0ch + + ; function. + movzx edx, byte [es:bx + PKTIOCTL.fun] + push edx ; 08h + + ; category. + movzx ecx, byte [es:bx + PKTIOCTL.cat] + push ecx ; 04h + + ; system file number. + movzx eax, word [es:bx + PKTIOCTL.sfn] + push eax ; 00h + + JMP16TO32 vgdrvOS2EP_GenIOCtl_Other_32 +segment TEXT32 +GLOBALNAME vgdrvOS2EP_GenIOCtl_Other_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; update in/out parameter pointers + lea eax, [esp + 1ch] + mov [esp + 14h], eax + lea edx, [esp + 1eh] + mov [esp + 18h], edx + + ; call the C code (don't cleanup the stack). + call NAME(vgdrvOS2IOCtl) + + ; switch back the stack. + push eax + call KernThunkStackTo16 + pop eax + + JMP32TO16 vgdrvOS2EP_GenIOCtl_Other_16 +segment CODE16 +GLOBALNAME vgdrvOS2EP_GenIOCtl_Other_16 + + les bx, [bp - 4] ; Reload the packet pointer. + or eax, eax + jnz near vgdrvOS2EP_GeneralFailure + + ; setup output stuff. + mov edx, esp + mov eax, [ss:edx + 1ch] ; output sizes. + mov [es:bx + PKTIOCTL.cbParm], eax ; update cbParm and cbData. + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + + mov sp, bp + pop ebp + retf + + + ; + ; Less Performance Critical Requests. + ; +vgdrvOS2EP_NotGenIOCtl: + cmp byte [es:bx + PKTHDR.cmd], 0dh ; Open + je vgdrvOS2EP_Open + cmp byte [es:bx + PKTHDR.cmd], 0eh ; Close + je vgdrvOS2EP_Close + cmp byte [es:bx + PKTHDR.cmd], 00h ; Init + je vgdrvOS2EP_Init +%ifdef DEBUG_READ + cmp byte [es:bx + PKTHDR.cmd], 04h ; Read + je near vgdrvOS2EP_Read +%endif + jmp near vgdrvOS2EP_NotSupported + + + ; + ; Open Request. w/ ring-0 init. + ; +vgdrvOS2EP_Open: + cmp byte [NAME(g_fInitialized)], 1 + jne vgdrvOS2EP_OpenOther + + ; First argument, the system file number. + movzx eax, word [es:bx + PKTOPEN.sfn] + push eax + + JMP16TO32 vgdrvOS2EP_Open_32 +segment TEXT32 +GLOBALNAME vgdrvOS2EP_Open_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; call the C code. + call NAME(vgdrvOS2Open) + + ; switch back the stack. + push eax + call KernThunkStackTo16 + pop eax + + JMP32TO16 vgdrvOS2EP_Open_16 +segment CODE16 +GLOBALNAME vgdrvOS2EP_Open_16 + + les bx, [bp - 4] ; Reload the packet pointer. + or eax, eax + jnz near vgdrvOS2EP_GeneralFailure + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + jmp near vgdrvOS2EP_Done + + ; Initializing or failed init? +vgdrvOS2EP_OpenOther: + cmp byte [NAME(g_fInitialized)], 0 + jne vgdrvOS2EP_OpenFailed + + mov byte [NAME(g_fInitialized)], -1 + call NAME(vgdrvRing0Init) + cmp byte [NAME(g_fInitialized)], 1 + je vgdrvOS2EP_Open + +vgdrvOS2EP_OpenFailed: + mov word [es:bx + PKTHDR.status], 0810fh ; error, done, init failed. + jmp near vgdrvOS2EP_Done + + + ; + ; Close Request. + ; +vgdrvOS2EP_Close: + ; First argument, the system file number. + movzx eax, word [es:bx + PKTOPEN.sfn] + push eax + + JMP16TO32 vgdrvOS2EP_Close_32 +segment TEXT32 +GLOBALNAME vgdrvOS2EP_Close_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; call the C code. + call NAME(vgdrvOS2Close) + + ; switch back the stack. + push eax + call KernThunkStackTo16 + pop eax + + JMP32TO16 vgdrvOS2EP_Close_16 +segment CODE16 +GLOBALNAME vgdrvOS2EP_Close_16 + + les bx, [bp - 4] ; Reload the packet pointer. + or eax, eax + jnz near vgdrvOS2EP_GeneralFailure + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + jmp near vgdrvOS2EP_Done + + + ; + ; Init Request. + ; Find the VMMDev adapter so we can unload the driver (and avoid trouble) if not found. + ; +vgdrvOS2EP_Init: + call NAME(vgdrvFindAdapter) + test ax, ax + jz .ok + mov word [es:bx + PKTHDR.status], 0810fh ; error, done, init failed. + call NAME(vgdrvOS2InitFlushText) + jmp .next +.ok: + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. +.next: + mov byte [es:bx + PKTINITOUT.cUnits], 0 + mov word [es:bx + PKTINITOUT.cbCode16], NAME(g_InitCodeStart) wrt CODE16 + mov word [es:bx + PKTINITOUT.cbData16], NAME(g_InitDataStart) wrt DATA16 + mov dword [es:bx + PKTINITOUT.fpaBPBs], 0 + jmp near vgdrvOS2EP_Done + + +%ifdef DEBUG_READ + ; + ; Read Request. + ; Return log data. + ; +vgdrvOS2EP_Read: + ; Any log data available? + xor dx, dx + mov ax, [NAME(g_offLogTail)] + cmp ax, [NAME(g_offLogHead)] + jz near .log_done + + ; create a temporary mapping of the physical buffer. Docs claims it trashes nearly everything... + push ebp + mov cx, [es:bx + PKTRW.cbTrans] + push cx + mov ax, [es:bx + PKTRW.PhysTrans + 2] + mov bx, [es:bx + PKTRW.PhysTrans] + mov dh, 1 + mov dl, DevHlp_PhysToVirt + call far [NAME(g_fpfnDevHlp)] + pop bx ; bx = cbTrans + pop ebp + jc near .log_phystovirt_failed + ; es:di -> the output buffer. + + ; setup the copy operation. + mov ax, [NAME(g_offLogTail)] + xor dx, dx ; dx tracks the number of bytes copied. +.log_loop: + mov cx, [NAME(g_offLogHead)] + cmp ax, cx + je .log_done + jb .log_loop_before + mov cx, LOG_SIZE +.log_loop_before: ; cx = end offset + sub cx, ax ; cx = sequential bytes to copy. + cmp cx, bx + jbe .log_loop_min + mov cx, bx ; output buffer is smaller than available data. +.log_loop_min: + mov si, NAME(g_szLog) + add si, ax ; ds:si -> the log buffer. + add dx, cx ; update output counter + add ax, cx ; calc new offLogTail + and ax, LOG_SIZE - 1 + rep movsb ; do the copy + mov [NAME(g_offLogTail)], ax ; commit the read. + jmp .log_loop + +.log_done: + les bx, [bp - 4] ; Reload the packet pointer. + mov word [es:bx + PKTRW.cbTrans], dx + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + jmp near vgdrvOS2EP_Done + +.log_phystovirt_failed: + les bx, [bp - 4] ; Reload the packet pointer. + jmp vgdrvOS2EP_GeneralFailure +%endif ; DEBUG_READ + + + ; + ; Return 'unknown command' error. + ; +vgdrvOS2EP_NotSupported: + mov word [es:bx + PKTHDR.status], 08103h ; error, done, unknown command. + jmp vgdrvOS2EP_Done + + ; + ; Return 'general failure' error. + ; +vgdrvOS2EP_GeneralFailure: + mov word [es:bx + PKTHDR.status], 0810ch ; error, done, general failure. + jmp vgdrvOS2EP_Done + + ; + ; Non-optimized return path. + ; +vgdrvOS2EP_Done: + mov sp, bp + pop ebp + retf +ENDPROC VGDrvOS2Entrypoint + + +;; +; The helper device entry point. +; +; This is only used to do the DosOpen on the main driver so we can +; do ring-3 init and report failures. +; +GLOBALNAME vgdrvOS2InitEntrypoint + ; The only request we're servicing is the 'init' one. + cmp word [es:bx + PKTHDR.cmd], 0 + je near NAME(vgdrvOS2InitEntrypointServiceInitReq) + + ; Ok, it's not the init request, just fail it. + mov word [es:bx + PKTHDR.status], 08103h ; error, done, unknown command. + retf + + +;; +; The OS/2 IDC entry point. +; +; This is only used to setup connection, the returned structure +; will provide the entry points that we'll be using. +; +; @cproto void far __cdecl VGDrvOS2IDC(VBOXGUESTOS2IDCCONNECT far *fpConnectInfo); +; +; @param fpConnectInfo [bp + 8] Pointer to an VBOXGUESTOS2IDCCONNECT structure. +; +GLOBALNAME VGDrvOS2IDC + push ebp ; bp - 0h + mov ebp, esp + ; save everything we might touch. + push es ; bp - 2h + push ds ; bp - 4h + push eax ; bp - 8h + push ebx ; bp - 0ch + push ecx ; bp - 10h + push edx ; bp - 14h + and sp, 0fffch + + JMP16TO32 VGDrvOS2IDC_32 +segment TEXT32 +GLOBALNAME VGDrvOS2IDC_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; call the C code. + call NAME(vgdrvOS2IDCConnect) + + ; + ; Load the return buffer address into ds:ebx and setup the buffer. + ; (eax == u32Session) + ; + mov cx, [ebp + 08h + 2] + mov ds, cx + movzx ebx, word [ebp + 08h] + + mov dword [ebx + VBGLOS2ATTACHDD.u32Version ], VBGL_IOC_VERSION + mov dword [ebx + VBGLOS2ATTACHDD.u32Session ], eax + mov dword [ebx + VBGLOS2ATTACHDD.pfnServiceEP ], NAME(VGDrvOS2IDCService) + mov word [ebx + VBGLOS2ATTACHDD.fpfnServiceEP ], NAME(VGDrvOS2IDCService16) wrt CODE16 + mov word [ebx + VBGLOS2ATTACHDD.fpfnServiceEP + 2], CODE16 + mov word [ebx + VBGLOS2ATTACHDD.fpfnServiceAsmEP ], NAME(VGDrvOS2IDCService16Asm) wrt CODE16 + mov word [ebx + VBGLOS2ATTACHDD.fpfnServiceAsmEP+2],CODE16 + + mov ax, DATA32 wrt FLAT + mov ds, ax + + ; switch back the stack. + call KernThunkStackTo16 + + JMP32TO16 VGDrvOS2IDC_16 +segment CODE16 +GLOBALNAME VGDrvOS2IDC_16 + + ; restore. + lea sp, [bp - 14h] + pop edx + pop ecx + pop ebx + pop eax + pop ds + pop es + pop ebp + retf +ENDPROC VGDrvOS2IDC + + +;; +; The 16-bit IDC entry point, cdecl. +; +; All this does is thunking the request into something that fits +; the 32-bit IDC service routine. +; +; +; @returns VBox status code. +; @param u32Session bp + 8h - The above session handle. +; @param iFunction bp + 0ch - The requested function. +; @param fpReqHdr bp + 0eh - The input/output data buffer. The caller ensures that this +; cannot be swapped out, or that it's acceptable to take a +; page in fault in the current context. If the request doesn't +; take input or produces output, passing NULL is okay. +; @param cbReq bp + 12h - The size of the data buffer. +; +; @cproto long far __cdecl VGDrvOS2IDCService16(uint32_t u32Session, uint16_t iFunction, void far *fpReqHdr, uint16_t cbReq); +; +GLOBALNAME VGDrvOS2IDCService16 + push ebp ; bp - 0h + mov ebp, esp + push es ; bp - 2h + push ds ; bp - 4h + push ecx ; bp - 8h + push edx ; bp - 0ch + push esi ; bp - 10h + and sp, 0fffch ; align the stack. + + ; locals + push dword 0 ; esp + 18h (dd): cbDataReturned + + ; load our ds (for g_fpfnDevHlp). + mov ax, DATA16 + mov ds, ax + + ; + ; Create the call frame before switching. + ; + movzx ecx, word [bp + 12h] + push ecx ; esp + 10h: cbData + + ; thunk data argument if present. + mov ax, [bp + 0eh + 2] ; selector + cmp ax, 3 ; <= 3 -> nil selector... + jbe .no_data + movzx esi, word [bp + 0eh] ; offset + mov dl, DevHlp_VirtToLin + call far [NAME(g_fpfnDevHlp)] + jc near VGDrvOS2IDCService16_InvalidPointer + jmp .finish_data +.no_data: + xor eax, eax +.finish_data: + push eax ; esp + 08h: pvData + movzx edx, word [bp + 0ch] + push edx ; esp + 04h: iFunction + mov ecx, [bp + 08h] + push ecx ; esp + 00h: u32Session + + JMP16TO32 VGDrvOS2IDCService16_32 +segment TEXT32 +GLOBALNAME VGDrvOS2IDCService16_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; call the C code (don't cleanup the stack). + call NAME(VGDrvOS2IDCService) + + ; switch back the stack. + push eax + call KernThunkStackTo16 + pop eax + + JMP32TO16 VGDrvOS2IDCService16_16 +segment CODE16 +GLOBALNAME VGDrvOS2IDCService16_16 + +VGDrvOS2IDCService16_Done: + lea sp, [bp - 10h] + pop esi + pop edx + pop ecx + pop ds + pop es + pop ebp + retf + +VGDrvOS2IDCService16_InvalidPointer: + mov ax, VERR_INVALID_POINTER + jmp VGDrvOS2IDCService16_Done +ENDPROC VGDrvOS2IDCService16 + + +;; +; The 16-bit IDC entry point, register based. +; +; This is just a wrapper around VGDrvOS2IDCService16 to simplify +; calls from 16-bit assembly code. +; +; @returns ax: VBox status code; cx: The amount of data returned. +; +; @param u32Session eax - The above session handle. +; @param iFunction dl - The requested function. +; @param pvData es:bx - The input/output data buffer. +; @param cbData cx - The size of the data buffer. +; +GLOBALNAME VGDrvOS2IDCService16Asm + push ebp ; bp - 0h + mov ebp, esp + push edx ; bp - 4h + + push cx ; cbData + push es + xor dh, dh + push dx + push eax + call NAME(VGDrvOS2IDCService16) + + mov cx, [es:bx + VBGLREQHDR.cbOut] + + mov edx, [bp - 4] + mov esp, ebp + pop ebp + retf +ENDPROC VGDrvOS2IDCService16Asm + + + +;; +; The 16-bit interrupt service routine. +; +; OS/2 saves all registers according to the docs, although it doesn't say whether +; this includes the 32-bit parts. Since it doesn't cost much to be careful, save +; everything. +; +; @returns CF=0 if it's our interrupt, CF=1 it it isn't. +; +; +GLOBALNAME vgdrvOS2ISR16 + push ebp + mov ebp, esp + pushf ; bp - 02h + cli + push eax ; bp - 06h + push edx ; bp - 0ah + push ebx ; bp - 0eh + push ds ; bp - 10h + push es ; bp - 12h + push ecx ; bp - 16h + push esi ; bp - 1ah + push edi ; bp - 1eh + + and sp, 0fff0h ; align the stack (16-bytes make GCC extremely happy). + + JMP16TO32 vgdrvOS2ISR16_32 +segment TEXT32 +GLOBALNAME vgdrvOS2ISR16_32 + + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + + call KernThunkStackTo32 + + call NAME(vgdrvOS2ISR) + mov ebx, eax + + call KernThunkStackTo16 + + JMP32TO16 vgdrvOS2ISR16_16 +segment CODE16 +GLOBALNAME vgdrvOS2ISR16_16 + + lea sp, [bp - 1eh] + pop edi + pop esi + pop ecx + pop es + pop ds + test bl, 0ffh + jnz .our + pop ebx + pop edx + pop eax + popf + pop ebp + stc + retf + + ; + ; Do EIO. + ; +.our: + mov al, [NAME(g_bInterruptLine)] + mov dl, DevHlp_EOI + call far [NAME(g_fpfnDevHlp)] + + pop ebx + pop edx + pop eax + popf + pop ebp + clc + retf +ENDPROC vgdrvOS2ISR16 + + + + + + +; +; The 32-bit text segment. +; +segment TEXT32 +;; +; 32-bit worker for registering the ISR. +; +; @returns 0 on success, some non-zero OS/2 error code on failure. +; @param bIrq [ebp + 8] The IRQ number. (uint8_t) +; +GLOBALNAME vgdrvOS2DevHlpSetIRQ + push ebp + mov ebp, esp + push ebx + push ds + + call KernThunkStackTo16 + + movzx ebx, byte [ebp + 8] ; load bIrq into BX. + + JMP32TO16 vgdrvOS2DevHlpSetIRQ_16 +segment CODE16 +GLOBALNAME vgdrvOS2DevHlpSetIRQ_16 + + mov ax, DATA16 ; for g_fpfnDevHlp. + mov ds, ax + mov ax, NAME(vgdrvOS2ISR16) ; The devhlp assume it's relative to DS. + mov dh, 1 ; 1 = shared + mov dl, DevHlp_SetIRQ + call far [NAME(g_fpfnDevHlp)] + jnc .ok + movzx eax, ax + or eax, eax + jnz .go_back + or eax, 6 + jmp .go_back +.ok: + xor eax, eax + +.go_back: + JMP16TO32 vgdrvOS2DevHlpSetIRQ_32 +segment TEXT32 +GLOBALNAME vgdrvOS2DevHlpSetIRQ_32 + + pop ds ; KernThunkStackTo32 ASSUMES flat DS and ES. + + mov ebx, eax + call KernThunkStackTo32 + mov eax, ebx + + pop ebx + pop ebp + ret +ENDPROC vgdrvOS2DevHlpSetIRQ + + + + +; +; The 16-bit init code. +; +segment CODE16_INIT +GLOBALNAME g_InitCodeStart + +;; The device name for DosOpen. +g_szDeviceName: + db '\DEV\vboxgst$', 0 + +; icsdebug can't see where stuff starts otherwise. (kDevTest) +int3 +int3 +int3 +int3 +int3 +int3 + +;; +; The Ring-3 init code. +; +BEGINPROC vgdrvOS2InitEntrypointServiceInitReq + push ebp + mov ebp, esp + push es ; bp - 2 + push sp ; bp - 4 + push -1 ; bp - 6: hfOpen + push 0 ; bp - 8: usAction + and sp, 0fffch + + ; check for the init package. + cmp word [es:bx + PKTHDR.cmd], 0 + jne near .not_init + + ; check that we found the VMMDev. + test byte [NAME(g_fFoundAdapter)], 1 + jz near .done_err + + ; + ; Copy the data out of the init packet. + ; + mov eax, [es:bx + PKTINITIN.fpfnDevHlp] + mov [NAME(g_fpfnDevHlp)], eax + mov edx, [es:bx + PKTINITIN.fpszArgs] + mov [g_fpszArgs], edx + + ; + ; Open the first driver, close it, and check status. + ; + + ; APIRET _Pascal DosOpen(PSZ pszFname, PHFILE phfOpen, PUSHORT pusAction, + ; ULONG ulFSize, USHORT usAttr, USHORT fsOpenFlags, + ; USHORT fsOpenMode, ULONG ulReserved); + push seg g_szDeviceName ; pszFname + push g_szDeviceName + push ss ; phfOpen + lea dx, [bp - 6] + push dx + push ss ; pusAction + lea dx, [bp - 8] + push dx + push dword 0 ; ulFSize + push 0 ; usAttr = FILE_NORMAL + push 1 ; fsOpenFlags = FILE_OPEN + push 00040h ; fsOpenMode = OPEN_SHARE_DENYNONE | OPEN_ACCESS_READONLY + push dword 0 ; ulReserved + call far DOS16OPEN + + push ax ; Quickly flush any text. + call NAME(vgdrvOS2InitFlushText) + pop ax + + or ax, ax + jnz .done_err + + ; APIRET APIENTRY DosClose(HFILE hf); + mov cx, [bp - 6] + push cx + call far DOS16CLOSE + or ax, ax + jnz .done_err ; This can't happen (I hope). + + ; + ; Ok, we're good. + ; + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + mov byte [es:bx + PKTINITOUT.cUnits], 0 + mov word [es:bx + PKTINITOUT.cbCode16], NAME(g_InitCodeStart) wrt CODE16 + mov word [es:bx + PKTINITOUT.cbData16], NAME(g_InitDataStart) wrt DATA16 + mov dword [es:bx + PKTINITOUT.fpaBPBs], 0 + jmp .done + + ; + ; Init failure. + ; +.done_err: + mov word [es:bx + PKTHDR.status], 0810fh ; error, done, init failed. + mov byte [es:bx + PKTINITOUT.cUnits], 0 + mov word [es:bx + PKTINITOUT.cbCode16], 0 + mov word [es:bx + PKTINITOUT.cbData16], 0 + mov dword [es:bx + PKTINITOUT.fpaBPBs], 0 + jmp .done + + ; + ; Not init, return 'unknown command'. + ; +.not_init: + mov word [es:bx + PKTHDR.status], 08103h ; error, done, unknown command. + jmp .done + + ; + ; Request done. + ; +.done: + mov sp, bp + pop ebp + retf +ENDPROC vgdrvOS2InitEntrypointServiceInitReq + + +;; +; The Ring-0 init code. +; +BEGINPROC vgdrvRing0Init + push es + push esi + push ebp + mov ebp, esp + and sp, 0fffch + + ; + ; Thunk the argument string pointer first. + ; + movzx esi, word [g_fpszArgs] ; offset + mov ax, [g_fpszArgs + 2] ; selector + mov dl, DevHlp_VirtToLin + call far [NAME(g_fpfnDevHlp)] + jc near vgdrvRing0Init_done ; eax is non-zero on failure (can't happen) + push eax ; 00h - pszArgs (for vgdrvOS2Init). + + ; + ; Do 16-bit init? + ; + + + ; + ; Do 32-bit init + ; + JMP16TO32 vgdrvRing0Init_32 +segment TEXT32 +GLOBALNAME vgdrvRing0Init_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; call the C code. + call NAME(vgdrvOS2Init) + + ; switch back the stack and reload ds. + push eax + call KernThunkStackTo16 + pop eax + + mov dx, seg NAME(g_fInitialized) + mov ds, dx + + JMP32TO16 vgdrvRing0Init_16 +segment CODE16_INIT +GLOBALNAME vgdrvRing0Init_16 + + ; check the result and set g_fInitialized on success. + or eax, eax + jnz vgdrvRing0Init_done + mov byte [NAME(g_fInitialized)], 1 + +vgdrvRing0Init_done: + mov sp, bp + pop ebp + pop esi + pop es + ret +ENDPROC vgdrvRing0Init + + +;; +; Flush any text in the text buffer. +; +BEGINPROC vgdrvOS2InitFlushText + push bp + mov bp, sp + + ; Anything in the buffer? + mov ax, [NAME(g_cchInitText)] + or ax, ax + jz .done + +%if 1 + ; Write it to STDOUT. + ; APIRET _Pascal DosWrite(HFILE hf, PVOID pvBuf, USHORT cbBuf, PUSHORT pcbBytesWritten); + push ax ; bp - 2 : cbBytesWritten + mov cx, sp + push 1 ; STDOUT + push seg NAME(g_szInitText) ; pvBuf + push NAME(g_szInitText) + push ax ; cbBuf + push ss ; pcbBytesWritten + push cx +%if 0 ; wlink generates a non-aliased fixup here which results in 16-bit offset with the flat 32-bit selector. + call far DOS16WRITE +%else + ; convert flat pointer to a far pointer using the tiled algorithm. + push ds + mov ax, DATA32 wrt FLAT + mov ds, ax + mov eax, g_pfnDos16Write wrt FLAT + movzx eax, word [eax + 2] ; High word of the flat address (in DATA32). + shl ax, 3 + or ax, 0007h + pop ds + mov [NAME(g_fpfnDos16Write) + 2], ax ; Update the selector (in DATA16_INIT). + ; do the call + call far [NAME(g_fpfnDos16Write)] +%endif + +%else ; alternative workaround for the wlink issue. + ; Use the save message devhlp. + push esi + push ebx + xor bx, bx + mov si, NAME(g_MsgTab) + mov dx, seg NAME(g_MsgTab) + mov ds, dx + mov dl, DevHlp_SAVE_MESSAGE + call far [NAME(g_fpfnDevHlp)] + pop ebx + pop esi +%endif + + ; Empty the buffer. + mov word [NAME(g_cchInitText)], 0 + mov byte [NAME(g_szInitText)], 0 + +.done: + mov sp, bp + pop bp + ret +ENDPROC vgdrvOS2InitFlushText + + +;; The device name for DosOpen. +g_szOemHlpDevName: + db '\DEV\OEMHLP$', 0 + + +;; +; Talks to OEMHLP$ about finding the VMMDev PCI adapter. +; +; On success g_fFoundAdapter is set to 1, and g_cbMMIO, +; g_PhysMMIOBase and g_IOPortBase are initialized with +; the PCI data. +; +; @returns 0 on success, non-zero on failure. (eax) +; +; @remark ASSUMES DS:DATA16. +; @uses nothing. +; +BEGINPROC vgdrvFindAdapter + push ebx + push ecx + push edx + push esi + push edi + push ebp + mov ebp, esp + push -1 ; bp - 2: hfOpen +%define hfOpen bp - 2 + push 0 ; bp - 4: usAction +%define usAction bp - 4 + sub sp, 20h ; bp - 44: 32 byte parameter buffer. +%define abParm bp - 24h + sub sp, 40h ; bp - c4: 32 byte data buffer. +%define abData bp - 44h + +;; VBox stuff +%define VBOX_PCI_VENDORID 080eeh +%define VMMDEV_DEVICEID 0cafeh + +;; OEMHLP$ stuff. +%define IOCTL_OEMHLP 80h +%define OEMHLP_PCI 0bh +%define PCI_FIND_DEVICE 1 +%define PCI_READ_CONFIG 3 + +;; PCI stuff +%define PCI_INTERRUPT_LINE 03ch ;;< 8-bit RW - Interrupt line. +%define PCI_BASE_ADDRESS_0 010h ;;< 32-bit RW */ +%define PCI_BASE_ADDRESS_1 014h ;;< 32-bit RW */ + + +%macro CallIOCtl 2 + ; APIRET _Pascal DosDevIOCtl2(PVOID pData, USHORT cbData, PVOID pParm, + ; USHORT cbParm, USHORT usFun, USHORT usCategory, + ; HFILE hDev); + push ss ; pData + lea dx, [abData] + push dx + push %2 ; cbData + + push ss ; pParm + lea dx, [abParm] + push dx + push %1 ; cbParm + push OEMHLP_PCI ; usFun + push IOCTL_OEMHLP ; usCategory + + mov ax, [hfOpen] ; hDev + push ax + call far DOS16DEVIOCTL2 + + ; check for error. + test ax, ax + jnz near .done_err_close + cmp [abData + 0], byte 0 + jne near .done_err_close +%endmacro + + + ; + ; Open the OEMHLP$ driver. + ; + + ; APIRET _Pascal DosOpen(PSZ pszFname, PHFILE phfOpen, PUSHORT pusAction, + ; ULONG ulFSize, USHORT usAttr, USHORT fsOpenFlags, + ; USHORT fsOpenMode, ULONG ulReserved); + mov di, '0' + push seg g_szOemHlpDevName ; pszFname + push g_szOemHlpDevName + push ss ; phfOpen + lea dx, [hfOpen] + push dx + push ss ; pusAction + lea dx, [usAction] + push dx + push dword 0 ; ulFSize + push 0 ; usAttr = FILE_NORMAL + push 1 ; fsOpenFlags = FILE_OPEN + push 00040h ; fsOpenMode = OPEN_SHARE_DENYNONE | OPEN_ACCESS_READONLY + push dword 0 ; ulReserved + call far DOS16OPEN + or ax, ax + jnz near .done + + + ; + ; Send a PCI_FIND_DEVICE request. + ; + + ; Initialize the parameter packet. + mov [abParm + 0], byte PCI_FIND_DEVICE ; 0 - db - SubFunction Number + mov [abParm + 1], word VMMDEV_DEVICEID ; 1 - dw - Device ID + mov [abParm + 3], word VBOX_PCI_VENDORID ; 3 - dw - Vendor ID + mov [abParm + 5], byte 0 ; 5 - db - (Device) Index + + ; Zero padd the data packet. + mov [abData + 0], dword 0 + + mov di, '1' + CallIOCtl 6, 3 + + mov al, [abData + 1] ; 1 - db - Bus Number. + mov [NAME(g_bPciBusNo)], al + mov al, [abData + 2] ; 2 - db - DevFunc Number. + mov [NAME(g_bPciDevFunNo)], al + + ; + ; Read the interrupt register (byte). + ; + mov di, '2' + mov ax, PCI_INTERRUPT_LINE | 0100h + call .NestedReadReg + mov [NAME(g_bInterruptLine)], al + + ; + ; Read the first base address (dword), this shall must be in I/O space. + ; + mov di, '3' + mov ax, PCI_BASE_ADDRESS_0 | 0400h + call .NestedReadReg + mov di, '4' + test al, 1h ; Test that it's an I/O space address. + jz .done_err_close + mov di, '5' + test eax, 0ffff0002h ; These shall all be 0 according to the specs. + jnz .done_err_close + and ax, 0fffeh + mov [NAME(g_IOPortBase)], ax + + ; + ; Read the second base address (dword), this shall be in memory space if present. + ; + mov di, '6' + mov ax, PCI_BASE_ADDRESS_1 | 0400h + call .NestedReadReg + mov di, '7' + test al, 1h ; Test that it's a memory space address. + jnz .done_err_close + and eax, 0fffffff0h + mov [NAME(g_PhysMMIOBase)], eax + + ;or eax, eax + ;jz .done_success ; No memory region. + ;; @todo If there is a simple way of determining the size do that, if + ; not we can easily handle it the code that does the actual mapping. + + + ; + ; Ok, we're good! + ; +.done_success: + or [NAME(g_fFoundAdapter)], byte 1 + jmp .done_close + + ; + ; Close the OEMHLP$ driver. + ; +.done_err_close: + or ax, 80h +.done_close: + ; APIRET APIENTRY DosClose(HFILE hf); + push ax ; Save result + mov cx, [hfOpen] + push cx + call far DOS16CLOSE + or ax, ax + jnz .bitch ; This can't happen (I hope). + pop ax + or ax, ax + jnz .bitch + + ; + ; Return to vgdrvOS2EP_Init. + ; +.done: + mov esp, ebp + pop ebp + pop edi + pop esi + pop edx + pop ecx + pop ebx + ret + + + ; + ; Puts the reason for failure in message buffer. + ; The caller will flush this. + ; +.bitch: + push es + + mov ax, ds + mov es, ax + mov ax, di ; save the reason. + mov di, NAME(g_szInitText) + add di, [NAME(g_cchInitText)] + + mov si, g_achLoadFailureMsg1 + mov cx, g_cchLoadFailureMsg1 + rep movsb + + stosb + + mov si, g_achLoadFailureMsg2 + mov cx, g_cchLoadFailureMsg2 + rep movsb + + mov [di], byte 0 + sub di, NAME(g_szInitText) + mov [NAME(g_cchInitText)], di + + pop es + jmp .done + + + ; + ; Nested function which reads a PCI config register. + ; (This operates on the vgdrvFindAdapter stack frame.) + ; + ; Input: + ; al - register to read + ; ah - register size. + ; + ; Output: + ; eax - the value. + ; + ; Uses: + ; dx + ; +.NestedReadReg: + ; Fill in the request packet. + mov [abParm + 0], byte PCI_READ_CONFIG ; 0 - db - SubFunction Number + mov dl, [NAME(g_bPciBusNo)] + mov [abParm + 1], dl ; 1 - db - Bus Number + mov dl, [NAME(g_bPciDevFunNo)] + mov [abParm + 2], dl ; 2 - db - DevFunc Number + mov [abParm + 3], al ; 3 - db - Configuration Register + mov [abParm + 4], ah ; 4 - db - (Register) Size + + ; Pad the data packet. + mov [abData + 0], dword 0 + mov [abData + 4], dword 0 + + CallIOCtl 5, 5 + + mov eax, [abData + 1] ; 1 - dd - Data + + ret + +ENDPROC vgdrvFindAdapter + + + +;; +; This must be present +segment DATA32 +g_pfnDos16Write: + dd DOS16WRITE ; flat + diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h b/src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h new file mode 100644 index 00000000..282c835f --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h @@ -0,0 +1,415 @@ +/* $Id: VBoxGuestInternal.h $ */ +/** @file + * VBoxGuest - Guest Additions Driver, Internal Header. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxGuest_VBoxGuestInternal_h +#define GA_INCLUDED_SRC_common_VBoxGuest_VBoxGuestInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> +#include <iprt/list.h> +#include <iprt/semaphore.h> +#include <iprt/spinlock.h> +#include <iprt/timer.h> +#include <VBox/VMMDev.h> +#include <VBox/VBoxGuest.h> +#include <VBox/VBoxGuestLib.h> + +/** @def VBOXGUEST_USE_DEFERRED_WAKE_UP + * Defer wake-up of waiting thread when defined. */ +#if defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING) +# define VBOXGUEST_USE_DEFERRED_WAKE_UP +#endif + +/** @def VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT + * The mouse notification callback can cause preemption and must not be invoked + * while holding a high-level spinlock. + */ +#if defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING) +# define VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT +#endif + +/** Pointer to the VBoxGuest per session data. */ +typedef struct VBOXGUESTSESSION *PVBOXGUESTSESSION; + +/** Pointer to a wait-for-event entry. */ +typedef struct VBOXGUESTWAIT *PVBOXGUESTWAIT; + +/** + * VBox guest wait for event entry. + * + * Each waiting thread allocates one of these items and adds + * it to the wait list before going to sleep on the event sem. + */ +typedef struct VBOXGUESTWAIT +{ + /** The list node. */ + RTLISTNODE ListNode; + /** The events we are waiting on. */ + uint32_t fReqEvents; + /** The events we received. */ + uint32_t volatile fResEvents; +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP + /** Set by VGDrvCommonWaitDoWakeUps before leaving the spinlock to call + * RTSemEventMultiSignal. */ + bool volatile fPendingWakeUp; + /** Set by the requestor thread if it got the spinlock before the + * signaller. Deals with the race in VGDrvCommonWaitDoWakeUps. */ + bool volatile fFreeMe; +#endif + /** The event semaphore. */ + RTSEMEVENTMULTI Event; + /** The session that's waiting. */ + PVBOXGUESTSESSION pSession; +#ifdef VBOX_WITH_HGCM + /** The HGCM request we're waiting for to complete. */ + VMMDevHGCMRequestHeader volatile *pHGCMReq; +#endif +} VBOXGUESTWAIT; + + +/** + * VBox guest memory balloon. + */ +typedef struct VBOXGUESTMEMBALLOON +{ + /** Mutex protecting the members below from concurrent access. */ + RTSEMFASTMUTEX hMtx; + /** The current number of chunks in the balloon. */ + uint32_t cChunks; + /** The maximum number of chunks in the balloon (typically the amount of guest + * memory / chunksize). */ + uint32_t cMaxChunks; + /** This is true if we are using RTR0MemObjAllocPhysNC() / RTR0MemObjGetPagePhysAddr() + * and false otherwise. */ + bool fUseKernelAPI; + /** The current owner of the balloon. + * This is automatically assigned to the first session using the ballooning + * API and first released when the session closes. */ + PVBOXGUESTSESSION pOwner; + /** The pointer to the array of memory objects holding the chunks of the + * balloon. This array is cMaxChunks in size when present. */ + PRTR0MEMOBJ paMemObj; +} VBOXGUESTMEMBALLOON; +/** Pointer to a memory balloon. */ +typedef VBOXGUESTMEMBALLOON *PVBOXGUESTMEMBALLOON; + + +/** + * Per bit usage tracker for a uint32_t mask. + * + * Used for optimal handling of guest properties, mouse status and event filter. + */ +typedef struct VBOXGUESTBITUSAGETRACER +{ + /** Per bit usage counters. */ + uint32_t acPerBitUsage[32]; + /** The current mask according to acPerBitUsage. */ + uint32_t fMask; +} VBOXGUESTBITUSAGETRACER; +/** Pointer to a per bit usage tracker. */ +typedef VBOXGUESTBITUSAGETRACER *PVBOXGUESTBITUSAGETRACER; +/** Pointer to a const per bit usage tracker. */ +typedef VBOXGUESTBITUSAGETRACER const *PCVBOXGUESTBITUSAGETRACER; + + +/** + * VBox guest device (data) extension. + */ +typedef struct VBOXGUESTDEVEXT +{ + /** VBOXGUESTDEVEXT_INIT_STATE_XXX. */ + uint32_t uInitState; + /** The base of the adapter I/O ports. */ + RTIOPORT IOPortBase; + /** Pointer to the mapping of the VMMDev adapter memory. */ + VMMDevMemory volatile *pVMMDevMemory; + /** The memory object reserving space for the guest mappings. */ + RTR0MEMOBJ hGuestMappings; + /** Spinlock protecting the signaling and resetting of the wait-for-event + * semaphores as well as the event acking in the ISR. */ + RTSPINLOCK EventSpinlock; + /** Host feature flags (VMMDEV_HVF_XXX). */ + uint32_t fHostFeatures; + /** Preallocated VMMDevEvents for the IRQ handler. */ + VMMDevEvents *pIrqAckEvents; + /** The physical address of pIrqAckEvents. */ + RTCCPHYS PhysIrqAckEvents; + /** Wait-for-event list for threads waiting for multiple events + * (VBOXGUESTWAIT). */ + RTLISTANCHOR WaitList; +#ifdef VBOX_WITH_HGCM + /** Wait-for-event list for threads waiting on HGCM async completion + * (VBOXGUESTWAIT). + * + * The entire list is evaluated upon the arrival of an HGCM event, unlike + * the other lists which are only evaluated till the first thread has + * been woken up. */ + RTLISTANCHOR HGCMWaitList; +#endif +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP + /** List of wait-for-event entries that needs waking up + * (VBOXGUESTWAIT). */ + RTLISTANCHOR WakeUpList; +#endif + /** List of wait-for-event entries that has been woken up + * (VBOXGUESTWAIT). */ + RTLISTANCHOR WokenUpList; + /** List of free wait-for-event entries (VBOXGUESTWAIT). */ + RTLISTANCHOR FreeList; + /** Mask of pending events. */ + uint32_t volatile f32PendingEvents; + /** Current VMMDEV_EVENT_MOUSE_POSITION_CHANGED sequence number. + * Used to implement polling. */ + uint32_t volatile u32MousePosChangedSeq; + + /** Spinlock various items in the VBOXGUESTSESSION. */ + RTSPINLOCK SessionSpinlock; + /** List of guest sessions (VBOXGUESTSESSION). We currently traverse this + * but do not search it, so a list data type should be fine. Use under the + * #SessionSpinlock lock. */ + RTLISTANCHOR SessionList; + /** Number of session. */ + uint32_t cSessions; + /** Flag indicating whether logging to the release log + * is enabled. */ + bool fLoggingEnabled; + /** Memory balloon information for RTR0MemObjAllocPhysNC(). */ + VBOXGUESTMEMBALLOON MemBalloon; + /** Mouse notification callback function. */ + PFNVBOXGUESTMOUSENOTIFY pfnMouseNotifyCallback; + /** The callback argument for the mouse ntofication callback. */ + void *pvMouseNotifyCallbackArg; + + /** @name Host Event Filtering + * @{ */ + /** Events we won't permit anyone to filter out. */ + uint32_t fFixedEvents; + /** Usage counters for the host events. (Fixed events are not included.) */ + VBOXGUESTBITUSAGETRACER EventFilterTracker; + /** The event filter last reported to the host (UINT32_MAX on failure). */ + uint32_t fEventFilterHost; + /** @} */ + + /** @name Mouse Status + * @{ */ + /** Usage counters for the mouse statuses (VMMDEV_MOUSE_XXX). */ + VBOXGUESTBITUSAGETRACER MouseStatusTracker; + /** The mouse status last reported to the host (UINT32_MAX on failure). */ + uint32_t fMouseStatusHost; + /** @} */ + + /** @name Guest Capabilities + * @{ */ + /** Guest capabilities which have been set to "acquire" mode. This means + * that only one session can use them at a time, and that they will be + * automatically cleaned up if that session exits without doing so. + * + * Protected by VBOXGUESTDEVEXT::SessionSpinlock, but is unfortunately read + * without holding the lock in a couple of places. */ + uint32_t volatile fAcquireModeGuestCaps; + /** Guest capabilities which have been set to "set" mode. This just means + * that they have been blocked from ever being set to "acquire" mode. */ + uint32_t fSetModeGuestCaps; + /** Mask of all capabilities which are currently acquired by some session + * and as such reported to the host. */ + uint32_t fAcquiredGuestCaps; + /** Usage counters for guest capabilities in "set" mode. Indexed by + * capability bit number, one count per session using a capability. */ + VBOXGUESTBITUSAGETRACER SetGuestCapsTracker; + /** The guest capabilities last reported to the host (UINT32_MAX on failure). */ + uint32_t fGuestCapsHost; + /** @} */ + + /** Heartbeat timer which fires with interval + * cNsHearbeatInterval and its handler sends + * VMMDevReq_GuestHeartbeat to VMMDev. */ + PRTTIMER pHeartbeatTimer; + /** Heartbeat timer interval in nanoseconds. */ + uint64_t cNsHeartbeatInterval; + /** Preallocated VMMDevReq_GuestHeartbeat request. */ + VMMDevRequestHeader *pReqGuestHeartbeat; +} VBOXGUESTDEVEXT; +/** Pointer to the VBoxGuest driver data. */ +typedef VBOXGUESTDEVEXT *PVBOXGUESTDEVEXT; + +/** @name VBOXGUESTDEVEXT_INIT_STATE_XXX - magic values for validating init + * state of the device extension structur. + * @{ */ +#define VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT UINT32_C(0x0badcafe) +#define VBOXGUESTDEVEXT_INIT_STATE_RESOURCES UINT32_C(0xcafebabe) +#define VBOXGUESTDEVEXT_INIT_STATE_DELETED UINT32_C(0xdeadd0d0) +/** @} */ + +/** + * The VBoxGuest per session data. + */ +typedef struct VBOXGUESTSESSION +{ + /** The list node. */ + RTLISTNODE ListNode; +#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_OS2) || defined(RT_OS_SOLARIS) + /** Pointer to the next session with the same hash. */ + PVBOXGUESTSESSION pNextHash; +#endif +#if defined(RT_OS_OS2) + /** The system file number of this session. */ + uint16_t sfn; + uint16_t Alignment; /**< Alignment */ +#endif + /** The requestor information to pass to the host for this session. + * @sa VMMDevRequestHeader::fRequestor */ + uint32_t fRequestor; + /** The process (id) of the session. + * This is NIL if it's a kernel session. */ + RTPROCESS Process; + /** Which process this session is associated with. + * This is NIL if it's a kernel session. */ + RTR0PROCESS R0Process; + /** Pointer to the device extension. */ + PVBOXGUESTDEVEXT pDevExt; + +#ifdef VBOX_WITH_HGCM + /** Array containing HGCM client IDs associated with this session. + * This will be automatically disconnected when the session is closed. */ + uint32_t volatile aHGCMClientIds[64]; +#endif + /** The last consumed VMMDEV_EVENT_MOUSE_POSITION_CHANGED sequence number. + * Used to implement polling. */ + uint32_t volatile u32MousePosChangedSeq; + /** Host events requested by the session. + * An event type requested in any guest session will be added to the host + * filter. Protected by VBOXGUESTDEVEXT::SessionSpinlock. */ + uint32_t fEventFilter; + /** Guest capabilities held in "acquired" by this session. + * Protected by VBOXGUESTDEVEXT::SessionSpinlock, but is unfortunately read + * without holding the lock in a couple of places. */ + uint32_t volatile fAcquiredGuestCaps; + /** Guest capabilities in "set" mode for this session. + * These accumulated for sessions via VBOXGUESTDEVEXT::acGuestCapsSet and + * reported to the host. Protected by VBOXGUESTDEVEXT::SessionSpinlock. */ + uint32_t fCapabilities; + /** Mouse features supported. A feature enabled in any guest session will + * be enabled for the host. + * @note We invert the VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR feature in this + * bitmap. The logic of this is that the real feature is when the host + * cursor is not needed, and we tell the host it is not needed if any + * session explicitly fails to assert it. Storing it inverted simplifies + * the checks. + * Use under the VBOXGUESTDEVEXT#SessionSpinlock lock. */ + uint32_t fMouseStatus; +#ifdef RT_OS_DARWIN + /** Pointer to the associated org_virtualbox_VBoxGuestClient object. */ + void *pvVBoxGuestClient; + /** Whether this session has been opened or not. */ + bool fOpened; +#endif + /** Whether a CANCEL_ALL_WAITEVENTS is pending. This happens when + * CANCEL_ALL_WAITEVENTS is called, but no call to WAITEVENT is in process + * in the current session. In that case the next call will be interrupted + * at once. */ + bool volatile fPendingCancelWaitEvents; + /** Does this session belong to a root process or a user one? */ + bool fUserSession; +} VBOXGUESTSESSION; + +RT_C_DECLS_BEGIN + +int VGDrvCommonInitDevExt(PVBOXGUESTDEVEXT pDevExt, uint16_t IOPortBase, void *pvMMIOBase, uint32_t cbMMIO, + VBOXOSTYPE enmOSType, uint32_t fEvents); +void VGDrvCommonDeleteDevExt(PVBOXGUESTDEVEXT pDevExt); + +int VGDrvCommonInitLoggers(void); +void VGDrvCommonDestroyLoggers(void); +int VGDrvCommonInitDevExtFundament(PVBOXGUESTDEVEXT pDevExt); +void VGDrvCommonDeleteDevExtFundament(PVBOXGUESTDEVEXT pDevExt); +int VGDrvCommonInitDevExtResources(PVBOXGUESTDEVEXT pDevExt, uint16_t IOPortBase, + void *pvMMIOBase, uint32_t cbMMIO, VBOXOSTYPE enmOSType, uint32_t fFixedEvents); +void VGDrvCommonDeleteDevExtResources(PVBOXGUESTDEVEXT pDevExt); +int VGDrvCommonReinitDevExtAfterHibernation(PVBOXGUESTDEVEXT pDevExt, VBOXOSTYPE enmOSType); + +bool VBDrvCommonIsOptionValueTrue(const char *pszValue); +void VGDrvCommonProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue); +void VGDrvCommonProcessOptionsFromHost(PVBOXGUESTDEVEXT pDevExt); +bool VGDrvCommonIsOurIRQ(PVBOXGUESTDEVEXT pDevExt); +bool VGDrvCommonISR(PVBOXGUESTDEVEXT pDevExt); + +#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP +void VGDrvCommonWaitDoWakeUps(PVBOXGUESTDEVEXT pDevExt); +#endif + +int VGDrvCommonCreateUserSession(PVBOXGUESTDEVEXT pDevExt, uint32_t fRequestor, PVBOXGUESTSESSION *ppSession); +int VGDrvCommonCreateKernelSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION *ppSession); +void VGDrvCommonCloseSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession); + +int VGDrvCommonIoCtlFast(uintptr_t iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession); +int VGDrvCommonIoCtl(uintptr_t iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, + PVBGLREQHDR pReqHdr, size_t cbReq); + +/** + * ISR callback for notifying threads polling for mouse events. + * + * This is called at the end of the ISR, after leaving the event spinlock, if + * VMMDEV_EVENT_MOUSE_POSITION_CHANGED was raised by the host. + * + * @param pDevExt The device extension. + */ +void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt); + +/** + * Hook for handling OS specfic options from the host. + * + * @returns true if handled, false if not. + * @param pDevExt The device extension. + * @param pszName The option name. + * @param pszValue The option value. + */ +bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue); + + +#ifdef VBOX_WITH_DPC_LATENCY_CHECKER +int VGDrvNtIOCtl_DpcLatencyChecker(void); +#endif + +#ifdef VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT +int VGDrvNativeSetMouseNotifyCallback(PVBOXGUESTDEVEXT pDevExt, PVBGLIOCSETMOUSENOTIFYCALLBACK pNotify); +#endif + +RT_C_DECLS_END + +#endif /* !GA_INCLUDED_SRC_common_VBoxGuest_VBoxGuestInternal_h */ + diff --git a/src/VBox/Additions/common/VBoxGuest/darwin/Info.plist b/src/VBox/Additions/common/VBoxGuest/darwin/Info.plist new file mode 100644 index 00000000..f4384535 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/darwin/Info.plist @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> <string>English</string> + <key>CFBundleExecutable</key> <string>VBoxGuest</string> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxGuest</string> + <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> + <key>CFBundleName</key> <string>VBoxGuest</string> + <key>CFBundlePackageType</key> <string>KEXT</string> + <key>CFBundleSignature</key> <string>????</string> + <key>NSHumanReadableCopyright</key> <string>Copyright © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string> + <key>CFBundleGetInfoString</key> <string>@VBOX_PRODUCT@ @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string> + <key>CFBundleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleShortVersionString</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>OSBundleCompatibleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>IOKitPersonalities</key> + <dict> + <key>VBoxGuest</key> + <dict> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxGuest</string> + <key>IOClass</key> <string>org_virtualbox_VBoxGuest</string> + <key>IOMatchCategory</key> <string>org_virtualbox_VBoxGuest</string> + <key>IOUserClientClass</key> <string>org_virtualbox_VBoxGuestClient</string> + <key>IOKitDebug</key> <integer>65535</integer> + <key>IOProviderClass</key> <string>IOPCIDevice</string> + <key>IOPCIPrimaryMatch</key> <string>0xcafe80ee</string> + <!-- <key>IONameMatch</key> <string>pci80ee,cafe</string> --> + </dict> + </dict> + <key>OSBundleLibraries</key> + <dict> + <key>com.apple.iokit.IOPCIFamily</key> <string>2.5</string> <!-- TODO: Figure the version in mac os x 10.4. --> + <key>com.apple.kpi.bsd</key> <string>8.0.0</string> + <key>com.apple.kpi.mach</key> <string>8.0.0</string> + <key>com.apple.kpi.libkern</key> <string>8.0.0</string> + <key>com.apple.kpi.unsupported</key> <string>8.0.0</string> + <key>com.apple.kpi.iokit</key> <string>8.0.0</string> + </dict> + <key>OSBundleLibraries_x86_64</key> + <dict> + <key>com.apple.iokit.IOPCIFamily</key> <string>2.6</string> + <key>com.apple.kpi.bsd</key> <string>10.0.0d4</string> + <key>com.apple.kpi.mach</key> <string>10.0.0d3</string> + <key>com.apple.kpi.libkern</key> <string>10.0.0d3</string> + <key>com.apple.kpi.iokit</key> <string>10.0.0d3</string> + <key>com.apple.kpi.unsupported</key> <string>10.0.0d3</string> + </dict> +</dict> +</plist> + diff --git a/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile b/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile new file mode 100644 index 00000000..5344dbc0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile @@ -0,0 +1,202 @@ +# $Id: Makefile $ +## @file +# VirtualBox Guest Additions Module Makefile. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# +KMOD = vboxguest + +CFLAGS += -DRT_OS_FREEBSD -DIN_RING0 -DIN_RT_R0 -DIN_SUP_R0 -DVBOX -DRT_WITH_VBOX -Iinclude -I. -Ir0drv -w -DVBGL_VBOXGUEST -DVBOX_WITH_HGCM -DVBOX_WITH_64_BITS_GUESTS + +.if (${MACHINE_ARCH} == "i386") + CFLAGS += -DRT_ARCH_X86 +.elif (${MACHINE_ARCH} == "amd64") + CFLAGS += -DRT_ARCH_AMD64 +.endif + +SRCS = \ + VBoxGuest.c \ + VBoxGuest-freebsd.c \ + VBoxGuestR0LibGenericRequest.c \ + VBoxGuestR0LibHGCMInternal.c \ + VBoxGuestR0LibInit.c \ + VBoxGuestR0LibPhysHeap.c \ + VBoxGuestR0LibVMMDev.c + +# Include needed interface headers so they are created during build +SRCS += \ + device_if.h \ + bus_if.h \ + pci_if.h \ + +.PATH: ${.CURDIR}/alloc +SRCS += \ + heapsimple.c + +.PATH: ${.CURDIR}/common/err +SRCS += \ + RTErrConvertFromErrno.c \ + RTErrConvertToErrno.c \ + errinfo.c + +.PATH: ${.CURDIR}/common/log +SRCS += \ + log.c \ + logellipsis.c \ + logrel.c \ + logrelellipsis.c \ + logcom.c \ + logformat.c \ + RTLogCreateEx.c + +.PATH: ${.CURDIR}/common/misc +SRCS += \ + RTAssertMsg1Weak.c \ + RTAssertMsg2.c \ + RTAssertMsg2Add.c \ + RTAssertMsg2AddWeak.c \ + RTAssertMsg2AddWeakV.c \ + RTAssertMsg2Weak.c \ + RTAssertMsg2WeakV.c \ + assert.c \ + handletable.c \ + handletablectx.c \ + once.c \ + thread.c + +.PATH: ${.CURDIR}/common/string +SRCS += \ + RTStrCat.c \ + RTStrCmp.c \ + RTStrCopy.c \ + RTStrCopyEx.c \ + RTStrCopyP.c \ + RTStrEnd.c \ + RTStrICmpAscii.c \ + RTStrNICmpAscii.c \ + RTStrNCmp.c \ + RTStrNLen.c \ + stringalloc.c \ + strformat.c \ + RTStrFormat.c \ + strformatnum.c \ + strformatrt.c \ + strformattype.c \ + strprintf.c \ + strprintf-ellipsis.c \ + strprintf2.c \ + strprintf2-ellipsis.c \ + strtonum.c \ + memchr.c \ + utf-8.c + +.PATH: ${.CURDIR}/common/rand +SRCS += \ + rand.c \ + randadv.c \ + randparkmiller.c + +.PATH: ${.CURDIR}/common/path +SRCS += \ + RTPathStripFilename.c + +.PATH: ${.CURDIR}/common/checksum +SRCS += \ + crc32.c \ + ipv4.c + +.PATH: ${.CURDIR}/common/table +SRCS += \ + avlpv.c + +.PATH: ${.CURDIR}/common/time +SRCS += \ + time.c + +.PATH: ${.CURDIR}/generic +SRCS += \ + uuid-generic.c \ + RTAssertShouldPanic-generic.c \ + RTLogWriteDebugger-generic.c \ + RTLogWriteStdOut-stub-generic.c \ + RTLogWriteStdErr-stub-generic.c \ + RTRandAdvCreateSystemFaster-generic.c \ + RTRandAdvCreateSystemTruer-generic.c \ + RTSemEventWait-2-ex-generic.c \ + RTSemEventWaitNoResume-2-ex-generic.c \ + RTSemEventMultiWait-2-ex-generic.c \ + RTSemEventMultiWaitNoResume-2-ex-generic.c \ + RTTimerCreate-generic.c \ + rtStrFormatKernelAddress-generic.c \ + timer-generic.c \ + errvars-generic.c \ + mppresent-generic.c + +.PATH: ${.CURDIR}/r0drv +SRCS += \ + alloc-r0drv.c \ + initterm-r0drv.c \ + memobj-r0drv.c \ + powernotification-r0drv.c + +.PATH: ${.CURDIR}/r0drv/freebsd +SRCS += \ + assert-r0drv-freebsd.c \ + alloc-r0drv-freebsd.c \ + initterm-r0drv-freebsd.c \ + memobj-r0drv-freebsd.c \ + memuserkernel-r0drv-freebsd.c \ + mp-r0drv-freebsd.c \ + process-r0drv-freebsd.c \ + semevent-r0drv-freebsd.c \ + semeventmulti-r0drv-freebsd.c \ + semfastmutex-r0drv-freebsd.c \ + semmutex-r0drv-freebsd.c \ + spinlock-r0drv-freebsd.c \ + thread-r0drv-freebsd.c \ + thread2-r0drv-freebsd.c \ + time-r0drv-freebsd.c + +.PATH: ${.CURDIR}/r0drv/generic +SRCS += \ + semspinmutex-r0drv-generic.c \ + mpnotification-r0drv-generic.c \ + RTMpIsCpuWorkPending-r0drv-generic.c + +.PATH: ${.CURDIR}/VBox +SRCS += \ + log-vbox.c \ + logbackdoor.c \ + RTLogWriteVmm-amd64-x86. + +.include <bsd.kmod.mk> + diff --git a/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile.kup b/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile.kup diff --git a/src/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest b/src/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest new file mode 100755 index 00000000..3f8e8f94 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest @@ -0,0 +1,240 @@ +#!/bin/sh +# $Id: files_vboxguest $ +## @file +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# Copyright (C) 2007-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +FILES_VBOXGUEST_NOBIN=" \ + ${PATH_ROOT}/include/iprt/alloca.h=>include/iprt/alloca.h \ + ${PATH_ROOT}/include/iprt/alloc.h=>include/iprt/alloc.h \ + ${PATH_ROOT}/include/iprt/asm.h=>include/iprt/asm.h \ + ${PATH_ROOT}/include/iprt/asm-amd64-x86.h=>include/iprt/asm-amd64-x86.h \ + ${PATH_ROOT}/include/iprt/asm-math.h=>include/iprt/asm-math.h \ + ${PATH_ROOT}/include/iprt/assert.h=>include/iprt/assert.h \ + ${PATH_ROOT}/include/iprt/assertcompile.h=>include/iprt/assertcompile.h \ + ${PATH_ROOT}/include/iprt/avl.h=>include/iprt/avl.h \ + ${PATH_ROOT}/include/iprt/cdefs.h=>include/iprt/cdefs.h \ + ${PATH_ROOT}/include/iprt/cpuset.h=>include/iprt/cpuset.h \ + ${PATH_ROOT}/include/iprt/ctype.h=>include/iprt/ctype.h \ + ${PATH_ROOT}/include/iprt/err.h=>include/iprt/err.h \ + ${PATH_ROOT}/include/iprt/errcore.h=>include/iprt/errcore.h \ + ${PATH_ROOT}/include/iprt/errno.h=>include/iprt/errno.h \ + ${PATH_ROOT}/include/iprt/heap.h=>include/iprt/heap.h \ + ${PATH_ROOT}/include/iprt/handletable.h=>include/iprt/handletable.h \ + ${PATH_ROOT}/include/iprt/initterm.h=>include/iprt/initterm.h \ + ${PATH_ROOT}/include/iprt/latin1.h=>include/iprt/latin1.h \ + ${PATH_ROOT}/include/iprt/list.h=>include/iprt/list.h \ + ${PATH_ROOT}/include/iprt/lockvalidator.h=>include/iprt/lockvalidator.h \ + ${PATH_ROOT}/include/iprt/log.h=>include/iprt/log.h \ + ${PATH_ROOT}/include/iprt/mangling.h=>include/iprt/mangling.h \ + ${PATH_ROOT}/include/iprt/mem.h=>include/iprt/mem.h \ + ${PATH_ROOT}/include/iprt/memobj.h=>include/iprt/memobj.h \ + ${PATH_ROOT}/include/iprt/mp.h=>include/iprt/mp.h \ + ${PATH_ROOT}/include/iprt/param.h=>include/iprt/param.h \ + ${PATH_ROOT}/include/iprt/power.h=>include/iprt/power.h \ + ${PATH_ROOT}/include/iprt/process.h=>include/iprt/process.h \ + ${PATH_ROOT}/include/iprt/semaphore.h=>include/iprt/semaphore.h \ + ${PATH_ROOT}/include/iprt/spinlock.h=>include/iprt/spinlock.h \ + ${PATH_ROOT}/include/iprt/stdarg.h=>include/iprt/stdarg.h \ + ${PATH_ROOT}/include/iprt/stdint.h=>include/iprt/stdint.h \ + ${PATH_ROOT}/include/iprt/string.h=>include/iprt/string.h \ + ${PATH_ROOT}/include/iprt/thread.h=>include/iprt/thread.h \ + ${PATH_ROOT}/include/iprt/time.h=>include/iprt/time.h \ + ${PATH_ROOT}/include/iprt/timer.h=>include/iprt/timer.h \ + ${PATH_ROOT}/include/iprt/types.h=>include/iprt/types.h \ + ${PATH_ROOT}/include/iprt/utf16.h=>include/iprt/utf16.h \ + ${PATH_ROOT}/include/iprt/uuid.h=>include/iprt/uuid.h \ + ${PATH_ROOT}/include/iprt/crc.h=>include/iprt/crc.h \ + ${PATH_ROOT}/include/iprt/net.h=>include/iprt/net.h \ + ${PATH_ROOT}/include/iprt/rand.h=>include/iprt/rand.h \ + ${PATH_ROOT}/include/iprt/path.h=>include/iprt/path.h \ + ${PATH_ROOT}/include/iprt/once.h=>include/iprt/once.h \ + ${PATH_ROOT}/include/iprt/critsect.h=>include/iprt/critsect.h \ + ${PATH_ROOT}/include/iprt/x86.h=>include/iprt/x86.h \ + ${PATH_ROOT}/include/iprt/x86-helpers.h=>include/iprt/x86-helpers.h \ + ${PATH_ROOT}/include/iprt/nocrt/limits.h=>include/iprt/nocrt/limits.h \ + ${PATH_ROOT}/include/VBox/cdefs.h=>include/VBox/cdefs.h \ + ${PATH_ROOT}/include/VBox/err.h=>include/VBox/err.h \ + ${PATH_ROOT}/include/VBox/log.h=>include/VBox/log.h \ + ${PATH_ROOT}/include/VBox/param.h=>include/VBox/param.h \ + ${PATH_ROOT}/include/VBox/types.h=>include/VBox/types.h \ + ${PATH_ROOT}/include/VBox/ostypes.h=>include/VBox/ostypes.h \ + ${PATH_ROOT}/include/VBox/VMMDev.h=>include/VBox/VMMDev.h \ + ${PATH_ROOT}/include/VBox/VMMDevCoreTypes.h=>include/VBox/VMMDevCoreTypes.h \ + ${PATH_ROOT}/include/VBox/VBoxGuest.h=>include/VBox/VBoxGuest.h \ + ${PATH_ROOT}/include/VBox/VBoxGuestCoreTypes.h=>include/VBox/VBoxGuestCoreTypes.h \ + ${PATH_ROOT}/include/VBox/VBoxGuestLib.h=>include/VBox/VBoxGuestLib.h \ + ${PATH_ROOT}/include/VBox/VBoxGuestMangling.h=>include/VBox/VBoxGuestMangling.h \ + ${PATH_ROOT}/include/VBox/version.h=>include/VBox/version.h \ + ${PATH_ROOT}/include/VBox/HostServices/GuestPropertySvc.h=>include/VBox/HostServices/GuestPropertySvc.h \ + ${PATH_ROOT}/include/VBox/vmm/cpuidcall.h=>include/VBox/vmm/cpuidcall.h \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp=>VBoxGuest.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuest-freebsd.c=>VBoxGuest-freebsd.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h=>VBoxGuestInternal.h \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile=>Makefile \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h=>VBoxGuestR0LibInternal.h \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp=>VBoxGuestR0LibGenericRequest.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCMInternal.cpp=>VBoxGuestR0LibHGCMInternal.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp=>VBoxGuestR0LibInit.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp=>VBoxGuestR0LibPhysHeap.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibVMMDev.cpp=>VBoxGuestR0LibVMMDev.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/alloc/heapsimple.cpp=>alloc/heapsimple.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertFromErrno.cpp=>common/err/RTErrConvertFromErrno.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertToErrno.cpp=>common/err/RTErrConvertToErrno.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/errinfo.cpp=>common/err/errinfo.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/log.cpp=>common/log/log.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logellipsis.cpp=>common/log/logellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logrel.cpp=>common/log/logrel.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logrelellipsis.cpp=>common/log/logrelellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logcom.cpp=>common/log/logcom.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logformat.cpp=>common/log/logformat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/RTLogCreateEx.cpp=>common/log/RTLogCreateEx.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletable.cpp=>common/misc/handletable.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletable.h=>common/misc/handletable.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletablectx.cpp=>common/misc/handletablectx.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/once.cpp=>common/misc/once.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/thread.cpp=>common/misc/thread.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp=>common/misc/RTAssertMsg1Weak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp=>common/misc/RTAssertMsg2.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp=>common/misc/RTAssertMsg2Add.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp=>common/misc/RTAssertMsg2AddWeak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp=>common/misc/RTAssertMsg2AddWeakV.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp=>common/misc/RTAssertMsg2Weak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp=>common/misc/RTAssertMsg2WeakV.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/assert.cpp=>common/misc/assert.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCat.cpp=>common/string/RTStrCat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCmp.cpp=>common/string/RTStrCmp.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopy.cpp=>common/string/RTStrCopy.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyEx.cpp=>common/string/RTStrCopyEx.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyP.cpp=>common/string/RTStrCopyP.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrEnd.cpp=>common/string/RTStrEnd.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrICmpAscii.cpp=>common/string/RTStrICmpAscii.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNICmpAscii.cpp=>common/string/RTStrNICmpAscii.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNCmp.cpp=>common/string/RTStrNCmp.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNLen.cpp=>common/string/RTStrNLen.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/stringalloc.cpp=>common/string/stringalloc.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformat.cpp=>common/string/strformat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrFormat.cpp=>common/string/RTStrFormat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatnum.cpp=>common/string/strformatnum.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatrt.cpp=>common/string/strformatrt.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformattype.cpp=>common/string/strformattype.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf.cpp=>common/string/strprintf.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf-ellipsis.cpp=>common/string/strprintf-ellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2.cpp=>common/string/strprintf2.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2-ellipsis.cpp=>common/string/strprintf2-ellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strtonum.cpp=>common/string/strtonum.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/memchr.cpp=>common/string/memchr.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/utf-8.cpp=>common/string/utf-8.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/rand/rand.cpp=>common/rand/rand.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/rand/randadv.cpp=>common/rand/randadv.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/rand/randparkmiller.cpp=>common/rand/randparkmiller.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/path/RTPathStripFilename.cpp=>common/path/RTPathStripFilename.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/checksum/crc32.cpp=>common/checksum/crc32.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/checksum/ipv4.cpp=>common/checksum/ipv4.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avlpv.cpp=>common/table/avlpv.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Base.cpp.h=>common/table/avl_Base.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Get.cpp.h=>common/table/avl_Get.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h=>common/table/avl_GetBestFit.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_RemoveBestFit.cpp.h=>common/table/avl_RemoveBestFit.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h=>common/table/avl_DoWithAll.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Destroy.cpp.h=>common/table/avl_Destroy.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/time/time.cpp=>common/time/time.c \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/assert.h=>include/internal/assert.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/initterm.h=>include/internal/initterm.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/iprt.h=>include/internal/iprt.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/lockvalidator.h=>include/internal/lockvalidator.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/magics.h=>include/internal/magics.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/mem.h=>include/internal/mem.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/memobj.h=>include/internal/memobj.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/string.h=>include/internal/string.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/thread.h=>include/internal/thread.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/time.h=>include/internal/time.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/rand.h=>include/internal/rand.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/sched.h=>include/internal/sched.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/process.h=>include/internal/process.h \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTAssertShouldPanic-generic.cpp=>generic/RTAssertShouldPanic-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdErr-stub-generic.cpp=>generic/RTLogWriteStdErr-stub-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdOut-stub-generic.cpp=>generic/RTLogWriteStdOut-stub-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteDebugger-generic.cpp=>generic/RTLogWriteDebugger-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTRandAdvCreateSystemFaster-generic.cpp=>generic/RTRandAdvCreateSystemFaster-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTRandAdvCreateSystemTruer-generic.cpp=>generic/RTRandAdvCreateSystemTruer-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/uuid-generic.cpp=>generic/uuid-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWait-2-ex-generic.cpp=>generic/RTSemEventWait-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventWaitNoResume-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWait-2-ex-generic.cpp=>generic/RTSemEventMultiWait-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventMultiWaitNoResume-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTTimerCreate-generic.cpp=>generic/RTTimerCreate-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/rtStrFormatKernelAddress-generic.cpp=>generic/rtStrFormatKernelAddress-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/errvars-generic.cpp=>generic/errvars-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/timer-generic.cpp=>generic/timer-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/mppresent-generic.cpp=>generic/mppresent-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.cpp=>r0drv/alloc-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.h=>r0drv/alloc-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/initterm-r0drv.cpp=>r0drv/initterm-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/mp-r0drv.h=>r0drv/mp-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/power-r0drv.h=>r0drv/power-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/powernotification-r0drv.c=>r0drv/powernotification-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/assert-r0drv-freebsd.c=>r0drv/freebsd/assert-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/alloc-r0drv-freebsd.c=>r0drv/freebsd/alloc-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/initterm-r0drv-freebsd.c=>r0drv/freebsd/initterm-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/memobj-r0drv-freebsd.c=>r0drv/freebsd/memobj-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/memuserkernel-r0drv-freebsd.c=>r0drv/freebsd/memuserkernel-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/mp-r0drv-freebsd.c=>r0drv/freebsd/mp-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/process-r0drv-freebsd.c=>r0drv/freebsd/process-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semevent-r0drv-freebsd.c=>r0drv/freebsd/semevent-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semeventmulti-r0drv-freebsd.c=>r0drv/freebsd/semeventmulti-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semfastmutex-r0drv-freebsd.c=>r0drv/freebsd/semfastmutex-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semmutex-r0drv-freebsd.c=>r0drv/freebsd/semmutex-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/spinlock-r0drv-freebsd.c=>r0drv/freebsd/spinlock-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/the-freebsd-kernel.h=>r0drv/freebsd/the-freebsd-kernel.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/thread-r0drv-freebsd.c=>r0drv/freebsd/thread-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/thread2-r0drv-freebsd.c=>r0drv/freebsd/thread2-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/time-r0drv-freebsd.c=>r0drv/freebsd/time-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/timer-r0drv-freebsd.c=>r0drv/freebsd/timer-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/sleepqueue-r0drv-freebsd.h=>r0drv/freebsd/sleepqueue-r0drv-freebsd.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/semspinmutex-r0drv-generic.c=>r0drv/generic/semspinmutex-r0drv-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/mpnotification-r0drv-generic.cpp=>r0drv/generic/mpnotification-r0drv-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/RTMpIsCpuWorkPending-r0drv-generic.cpp=>r0drv/generic/RTMpIsCpuWorkPending-r0drv-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/memobj-r0drv.cpp=>r0drv/memobj-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/VBox/log-vbox.cpp=>VBox/log-vbox.c \ + ${PATH_ROOT}/src/VBox/Runtime/VBox/logbackdoor.cpp=>VBox/logbackdoor.c \ + ${PATH_ROOT}/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.cpp=>VBox/RTLogWriteVmm-amd64-x86.c \ + ${PATH_OUT}/version-generated.h=>version-generated.h \ + ${PATH_OUT}/product-generated.h=>product-generated.h \ + ${PATH_OUT}/revision-generated.h=>revision-generated.h \ +" + +FILES_VBOXGUEST_BIN=" \ +" + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk b/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk new file mode 100644 index 00000000..eead95e5 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk @@ -0,0 +1,254 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the common guest addition code library. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# Target config. +# +if defined(VBOX_WITH_ADDITION_DRIVERS) && !defined(VBOX_ONLY_VALIDATIONKIT) + LIBRARIES += \ + VBoxGuestR0Lib \ + VBoxGuestR0LibBase +endif +LIBRARIES += \ + VBoxGuestR3Lib \ + VBoxGuestR3LibShared +ifndef VBOX_ONLY_VALIDATIONKIT + if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd) + ifndef VBOX_USE_SYSTEM_XORG_HEADERS + LIBRARIES += \ + VBoxGuestR3LibXFree86 + endif + endif + if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) + LIBRARIES += \ + VBoxGuestR3LibXOrg + endif +endif +LIBRARIES.win.amd64 += VBoxGuestR3Lib-x86 VBoxGuestR3LibShared-x86 + + +# +# VBoxGuestR0Lib +# +VBoxGuestR0Lib_TEMPLATE = VBoxGuestR0DrvLib +VBoxGuestR0Lib_DEFS = VBOX_WITH_HGCM \ + $(if $(VBOX_WITH_DRAG_AND_DROP),VBOX_WITH_DRAG_AND_DROP,) \ + $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,) +VBoxGuestR0Lib_INCS = \ + $(VBoxGuestR0Lib_0_OUTDIR) +VBoxGuestR0Lib_SOURCES = \ + VBoxGuestR0LibInit.cpp \ + VBoxGuestR0LibPhysHeap.cpp \ + VBoxGuestR0LibGenericRequest.cpp \ + VBoxGuestR0LibVMMDev.cpp \ + VBoxGuestR0LibHGCM.cpp \ + VbglR0CanUsePhysPageList.cpp \ + \ + VBoxGuestR0LibIdc.cpp \ + VBoxGuestR0LibSharedFolders.c \ + VBoxGuestR0LibCrOgl.cpp \ + VBoxGuestR0LibMouse.cpp +VBoxGuestR0Lib_SOURCES.os2 = VBoxGuestR0LibIdc-os2.cpp +VBoxGuestR0Lib_SOURCES.solaris = VBoxGuestR0LibIdc-solaris.cpp +VBoxGuestR0Lib_SOURCES.win = VBoxGuestR0LibIdc-win.cpp +ifn1of ($(KBUILD_TARGET), os2 solaris win) + VBoxGuestR0Lib_SOURCES += VBoxGuestR0LibIdc-unix.cpp +endif + +# +# VBoxGuestR0LibBase +# +VBoxGuestR0LibBase_TEMPLATE = VBoxGuestR0DrvLib +VBoxGuestR0LibBase_DEFS = VBOX_WITH_HGCM VBGL_VBOXGUEST \ + $(if $(VBOX_WITH_DRAG_AND_DROP),VBOX_WITH_DRAG_AND_DROP,) \ + $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,) +VBoxGuestR0LibBase_INCS = $(VBoxGuestR0Lib_INCS) +VBoxGuestR0LibBase_INCS.win = $(VBoxGuestR0Lib_INCS.win) +VBoxGuestR0LibBase_SOURCES = \ + VBoxGuestR0LibInit.cpp \ + VBoxGuestR0LibPhysHeap.cpp \ + VBoxGuestR0LibGenericRequest.cpp \ + VBoxGuestR0LibVMMDev.cpp \ + VBoxGuestR0LibHGCMInternal.cpp \ + VbglR0CanUsePhysPageList.cpp + +# +# VBoxGuestR3Lib +# +VBoxGuestR3Lib_TEMPLATE := VBoxGuestR3Lib +VBoxGuestR3Lib_DEFS = \ + VBOX_WITH_HGCM \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \ + $(if $(VBOX_WITH_SHARED_CLIPBOARD),VBOX_WITH_SHARED_CLIPBOARD,) \ + $(if $(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS),VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS,) \ + $(if $(VBOX_WITH_SHARED_FOLDERS),VBOX_WITH_SHARED_FOLDERS,) \ + $(if $(VBOX_WITH_GUEST_CONTROL),VBOX_WITH_GUEST_CONTROL,) +VBoxGuestR3Lib_SOURCES = \ + VBoxGuestR3Lib.cpp \ + VBoxGuestR3LibAdditions.cpp \ + VBoxGuestR3LibAutoLogon.cpp \ + VBoxGuestR3LibBalloon.cpp \ + VBoxGuestR3LibCoreDump.cpp \ + VBoxGuestR3LibCpuHotPlug.cpp \ + VBoxGuestR3LibCredentials.cpp \ + VBoxGuestR3LibDrmClient.cpp \ + VBoxGuestR3LibEvent.cpp \ + VBoxGuestR3LibGuestUser.cpp \ + VBoxGuestR3LibGR.cpp \ + VBoxGuestR3LibHGCM.cpp \ + VBoxGuestR3LibHostChannel.cpp \ + VBoxGuestR3LibLog.cpp \ + VBoxGuestR3LibMisc.cpp \ + VBoxGuestR3LibStat.cpp \ + VBoxGuestR3LibTime.cpp \ + VBoxGuestR3LibModule.cpp \ + VBoxGuestR3LibPidFile.cpp \ + VBoxGuestR3LibVrdp.cpp \ + VBoxGuestR3LibMouse.cpp \ + VBoxGuestR3LibSeamless.cpp \ + VBoxGuestR3LibVideo.cpp +ifneq ($(KBUILD_TARGET),win) + VBoxGuestR3Lib_SOURCES += \ + VBoxGuestR3LibDaemonize.cpp +endif +ifdef VBOX_WITH_GUEST_PROPS + VBoxGuestR3Lib_SOURCES += \ + VBoxGuestR3LibGuestProp.cpp \ + VBoxGuestR3LibHostVersion.cpp +endif +ifdef VBOX_WITH_SHARED_CLIPBOARD + VBoxGuestR3Lib_DEFS += VBOX_WITH_SHARED_CLIPBOARD_GUEST + VBoxGuestR3Lib_SOURCES += \ + VBoxGuestR3LibClipboard.cpp + ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + VBoxGuestR3Lib_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardMIME.cpp + endif +endif +ifdef VBOX_WITH_SHARED_FOLDERS + VBoxGuestR3Lib_SOURCES += \ + VBoxGuestR3LibSharedFolders.cpp +endif +ifdef VBOX_WITH_GUEST_CONTROL + VBoxGuestR3Lib_SOURCES += \ + VBoxGuestR3LibGuestCtrl.cpp +endif +ifdef VBOX_WITH_DRAG_AND_DROP + VBoxGuestR3Lib_DEFS += \ + VBOX_WITH_DRAG_AND_DROP \ + $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,) + VBoxGuestR3Lib_SOURCES += \ + VBoxGuestR3LibDragAndDrop.cpp +endif + +VBoxGuestR3LibAdditions.cpp_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) + +# +# VBoxGuestR3LibShared - a PIC variant of VBoxGuestR3Lib for linking into .so/.dll/.dylib. +# +VBoxGuestR3LibShared_TEMPLATE := VBoxGuestR3Dll +VBoxGuestR3LibShared_DEFS := $(VBoxGuestR3Lib_DEFS) +VBoxGuestR3LibShared_SOURCES := $(VBoxGuestR3Lib_SOURCES) +VBoxGuestR3LibShared_INST := $(INST_ADDITIONS_LIB) + + +# +# VBoxGuestR3Lib-x86 - an x86 (32-bit) variant of VBoxGuestR3Lib for 64-bit Windows. +# +VBoxGuestR3Lib-x86_EXTENDS := VBoxGuestR3Lib +VBoxGuestR3Lib-x86_BLD_TRG_ARCH := x86 + + +# +# VBoxGuestR3LibShared-x86 - an x86 (32-bit) variant of VBoxGuestR3LibShared for 64-bit Windows. +# +VBoxGuestR3LibShared-x86_EXTENDS := VBoxGuestR3LibShared +VBoxGuestR3LibShared-x86_BLD_TRG_ARCH := x86 + + +# +# VBoxGuestR3LibXFree86 - a reduced version of the guest library which uses +# the X server runtime instead of IPRT, for use with old servers where the +# C library is not available. +# +VBoxGuestR3LibXFree86_TEMPLATE = VBoxGuestR3XFree86Lib +VBoxGuestR3LibXFree86_DEFS = \ + VBOX_WITH_HGCM \ + VBOX_VBGLR3_XFREE86 \ + RTMEM_NO_WRAP_TO_EF_APIS \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \ + $(if $(VBOX_WITH_DRAG_AND_DROP),VBOX_WITH_DRAG_AND_DROP,) \ + $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,) \ + $(if $(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS),VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS,) +VBoxGuestR3LibXFree86_SOURCES = \ + VBoxGuestR3Lib.cpp \ + VBoxGuestR3LibGR.cpp \ + $(if $(VBOX_WITH_GUEST_PROPS),VBoxGuestR3LibGuestProp.cpp,) \ + VBoxGuestR3LibMouse.cpp \ + VBoxGuestR3LibMisc.cpp \ + VBoxGuestR3LibSeamless.cpp \ + VBoxGuestR3LibVideo.cpp \ + VBoxGuestR3LibRuntimeXF86.cpp +VBoxGuestR3LibXFree86_INCS = \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3/Xserver \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3 \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11 + +# +# VBoxGuestR3LibXOrg - a reduced version of the guest library which uses +# the C server runtime instead of IPRT. +# +VBoxGuestR3LibXOrg_TEMPLATE = VBoxGuestR3XOrgLib +VBoxGuestR3LibXOrg_DEFS = \ + VBOX_WITH_HGCM \ + VBOX_VBGLR3_XORG \ + RTMEM_NO_WRAP_TO_EF_APIS \ + IN_RT_STATIC \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \ + $(if $(VBOX_WITH_DRAG_AND_DROP),VBOX_WITH_DRAG_AND_DROP,) \ + $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,) \ + $(if $(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS),VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS,) +VBoxGuestR3LibXOrg_SOURCES = $(VBoxGuestR3LibXFree86_SOURCES) + +VBoxGuestR3LibRuntimeXF86.cpp_CXXFLAGS = -Wno-shadow + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibCrOgl.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibCrOgl.cpp new file mode 100644 index 00000000..a6638dc0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibCrOgl.cpp @@ -0,0 +1,134 @@ +/* $Id: VBoxGuestR0LibCrOgl.cpp $ */ +/** @file + * VBoxGuestLib - Ring-3 Support Library for VirtualBox guest additions, Chromium OpenGL Service. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/string.h> +#include "VBoxGuestR0LibInternal.h" + +#ifdef VBGL_VBOXGUEST +# error "This file shouldn't be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest. It's client code." +#endif + + +DECLR0VBGL(int) VbglR0CrCtlCreate(VBGLCRCTLHANDLE *phCtl) +{ + int rc; + + if (phCtl) + { + struct VBGLHGCMHANDLEDATA *pHandleData = vbglR0HGCMHandleAlloc(); + if (pHandleData) + { + rc = VbglR0IdcOpen(&pHandleData->IdcHandle, + VBGL_IOC_VERSION /*uReqVersion*/, + VBGL_IOC_VERSION & UINT32_C(0xffff0000) /*uMinVersion*/, + NULL /*puSessionVersion*/, NULL /*puDriverVersion*/, NULL /*uDriverRevision*/); + if (RT_SUCCESS(rc)) + { + *phCtl = pHandleData; + return VINF_SUCCESS; + } + + vbglR0HGCMHandleFree(pHandleData); + } + else + rc = VERR_NO_MEMORY; + + *phCtl = NULL; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + +DECLR0VBGL(int) VbglR0CrCtlDestroy(VBGLCRCTLHANDLE hCtl) +{ + VbglR0IdcClose(&hCtl->IdcHandle); + + vbglR0HGCMHandleFree(hCtl); + + return VINF_SUCCESS; +} + +DECLR0VBGL(int) VbglR0CrCtlConConnect(VBGLCRCTLHANDLE hCtl, HGCMCLIENTID *pidClient) +{ + VBGLIOCHGCMCONNECT info; + int rc; + + if (!hCtl || !pidClient) + return VERR_INVALID_PARAMETER; + + RT_ZERO(info); + VBGLREQHDR_INIT(&info.Hdr, HGCM_CONNECT); + info.u.In.Loc.type = VMMDevHGCMLoc_LocalHost_Existing; + RTStrCopy(info.u.In.Loc.u.host.achName, sizeof(info.u.In.Loc.u.host.achName), "VBoxSharedCrOpenGL"); + rc = VbglR0IdcCall(&hCtl->IdcHandle, VBGL_IOCTL_HGCM_CONNECT, &info.Hdr, sizeof(info)); + if (RT_SUCCESS(rc)) + { + Assert(info.u.Out.idClient); + *pidClient = info.u.Out.idClient; + return rc; + } + + AssertRC(rc); + *pidClient = 0; + return rc; +} + +DECLR0VBGL(int) VbglR0CrCtlConDisconnect(VBGLCRCTLHANDLE hCtl, HGCMCLIENTID idClient) +{ + VBGLIOCHGCMDISCONNECT info; + VBGLREQHDR_INIT(&info.Hdr, HGCM_DISCONNECT); + info.u.In.idClient = idClient; + return VbglR0IdcCall(&hCtl->IdcHandle, VBGL_IOCTL_HGCM_DISCONNECT, &info.Hdr, sizeof(info)); +} + +DECLR0VBGL(int) VbglR0CrCtlConCallRaw(VBGLCRCTLHANDLE hCtl, PVBGLIOCHGCMCALL pCallInfo, int cbCallInfo) +{ + return VbglR0IdcCallRaw(&hCtl->IdcHandle, VBGL_IOCTL_HGCM_CALL(cbCallInfo), &pCallInfo->Hdr, cbCallInfo); +} + +DECLR0VBGL(int) VbglR0CrCtlConCall(VBGLCRCTLHANDLE hCtl, PVBGLIOCHGCMCALL pCallInfo, int cbCallInfo) +{ + int rc = VbglR0IdcCallRaw(&hCtl->IdcHandle, VBGL_IOCTL_HGCM_CALL(cbCallInfo), &pCallInfo->Hdr, cbCallInfo); + if (RT_SUCCESS(rc)) + rc = pCallInfo->Hdr.rc; + return rc; +} + +DECLR0VBGL(int) VbglR0CrCtlConCallUserDataRaw(VBGLCRCTLHANDLE hCtl, PVBGLIOCHGCMCALL pCallInfo, int cbCallInfo) +{ + return VbglR0IdcCallRaw(&hCtl->IdcHandle, VBGL_IOCTL_HGCM_CALL_WITH_USER_DATA(cbCallInfo), &pCallInfo->Hdr, cbCallInfo); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp new file mode 100644 index 00000000..72518a3e --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp @@ -0,0 +1,183 @@ +/* $Id: VBoxGuestR0LibGenericRequest.cpp $ */ +/** @file + * VBoxGuestLibR0 - Generic VMMDev request management. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR0LibInternal.h" +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <VBox/err.h> + + +DECLR0VBGL(int) VbglGR0Verify(const VMMDevRequestHeader *pReq, size_t cbReq) +{ + size_t cbReqExpected; + + if (RT_UNLIKELY(!pReq || cbReq < sizeof(VMMDevRequestHeader))) + { + dprintf(("VbglGR0Verify: Invalid parameter: pReq = %p, cbReq = %zu\n", pReq, cbReq)); + return VERR_INVALID_PARAMETER; + } + + if (RT_UNLIKELY(pReq->size > cbReq)) + { + dprintf(("VbglGR0Verify: request size %u > buffer size %zu\n", pReq->size, cbReq)); + return VERR_INVALID_PARAMETER; + } + + /* The request size must correspond to the request type. */ + cbReqExpected = vmmdevGetRequestSize(pReq->requestType); + if (RT_UNLIKELY(cbReq < cbReqExpected)) + { + dprintf(("VbglGR0Verify: buffer size %zu < expected size %zu\n", cbReq, cbReqExpected)); + return VERR_INVALID_PARAMETER; + } + + if (cbReqExpected == cbReq) + { + /* + * This is most likely a fixed size request, and in this case the + * request size must be also equal to the expected size. + */ + if (RT_UNLIKELY(pReq->size != cbReqExpected)) + { + dprintf(("VbglGR0Verify: request size %u != expected size %zu\n", pReq->size, cbReqExpected)); + return VERR_INVALID_PARAMETER; + } + + return VINF_SUCCESS; + } + + /* + * This can be a variable size request. Check the request type and limit the size + * to VMMDEV_MAX_VMMDEVREQ_SIZE, which is max size supported by the host. + * + * Note: Keep this list sorted for easier human lookup! + */ + if ( pReq->requestType == VMMDevReq_ChangeMemBalloon + || pReq->requestType == VMMDevReq_GetDisplayChangeRequestMulti +#ifdef VBOX_WITH_64_BITS_GUESTS + || pReq->requestType == VMMDevReq_HGCMCall64 +#endif + || pReq->requestType == VMMDevReq_HGCMCall32 + || pReq->requestType == VMMDevReq_RegisterSharedModule + || pReq->requestType == VMMDevReq_ReportGuestUserState + || pReq->requestType == VMMDevReq_LogString + || pReq->requestType == VMMDevReq_SetPointerShape + || pReq->requestType == VMMDevReq_VideoSetVisibleRegion + || pReq->requestType == VMMDevReq_VideoUpdateMonitorPositions) + { + if (RT_UNLIKELY(cbReq > VMMDEV_MAX_VMMDEVREQ_SIZE)) + { + dprintf(("VbglGR0Verify: VMMDevReq_LogString: buffer size %zu too big\n", cbReq)); + return VERR_BUFFER_OVERFLOW; /** @todo is this error code ok? */ + } + } + else + { + dprintf(("VbglGR0Verify: request size %u > buffer size %zu\n", pReq->size, cbReq)); + return VERR_IO_BAD_LENGTH; /** @todo is this error code ok? */ + } + + return VINF_SUCCESS; +} + +DECLR0VBGL(int) VbglR0GRAlloc(VMMDevRequestHeader **ppReq, size_t cbReq, VMMDevRequestType enmReqType) +{ + int rc = vbglR0Enter(); + if (RT_SUCCESS(rc)) + { + if ( ppReq + && cbReq >= sizeof(VMMDevRequestHeader) + && cbReq == (uint32_t)cbReq) + { + VMMDevRequestHeader *pReq = (VMMDevRequestHeader *)VbglR0PhysHeapAlloc((uint32_t)cbReq); + AssertMsgReturn(pReq, ("VbglR0GRAlloc: no memory (cbReq=%u)\n", cbReq), VERR_NO_MEMORY); + memset(pReq, 0xAA, cbReq); + + pReq->size = (uint32_t)cbReq; + pReq->version = VMMDEV_REQUEST_HEADER_VERSION; + pReq->requestType = enmReqType; + pReq->rc = VERR_GENERAL_FAILURE; + pReq->reserved1 = 0; +#ifdef VBGL_VBOXGUEST + pReq->fRequestor = VMMDEV_REQUESTOR_KERNEL | VMMDEV_REQUESTOR_USR_DRV +#else + pReq->fRequestor = VMMDEV_REQUESTOR_KERNEL | VMMDEV_REQUESTOR_USR_DRV_OTHER +#endif + + | VMMDEV_REQUESTOR_CON_DONT_KNOW | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN; + *ppReq = pReq; + rc = VINF_SUCCESS; + } + else + { + dprintf(("VbglR0GRAlloc: Invalid parameter: ppReq=%p cbReq=%u\n", ppReq, cbReq)); + rc = VERR_INVALID_PARAMETER; + } + } + return rc; +} + +DECLR0VBGL(int) VbglR0GRPerform(VMMDevRequestHeader *pReq) +{ + int rc = vbglR0Enter(); + if (RT_SUCCESS(rc)) + { + if (pReq) + { + RTCCPHYS PhysAddr = VbglR0PhysHeapGetPhysAddr(pReq); + if ( PhysAddr != 0 + && PhysAddr < _4G) /* Port IO is 32 bit. */ + { + ASMOutU32(g_vbgldata.portVMMDev + VMMDEV_PORT_OFF_REQUEST, (uint32_t)PhysAddr); + /* Make the compiler aware that the host has changed memory. */ + ASMCompilerBarrier(); + rc = pReq->rc; + } + else + rc = VERR_VBGL_INVALID_ADDR; + } + else + rc = VERR_INVALID_PARAMETER; + } + return rc; +} + +DECLR0VBGL(void) VbglR0GRFree(VMMDevRequestHeader *pReq) +{ + int rc = vbglR0Enter(); + if (RT_SUCCESS(rc)) + VbglR0PhysHeapFree(pReq); +} diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCM.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCM.cpp new file mode 100644 index 00000000..e2e28ebb --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCM.cpp @@ -0,0 +1,240 @@ +/* $Id: VBoxGuestR0LibHGCM.cpp $ */ +/** @file + * VBoxGuestLib - Host-Guest Communication Manager, ring-0 client drivers. + * + * These public functions can be only used by other drivers. They all + * do an IOCTL to VBoxGuest via IDC. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR0LibInternal.h" + +#include <iprt/assert.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <VBox/err.h> + +#ifdef VBGL_VBOXGUEST +# error "This file shouldn't be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest. It's client code." +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBGL_HGCM_ASSERT_MSG AssertReleaseMsg + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Fast heap for HGCM handles data. + * @{ + */ +static RTSEMFASTMUTEX g_hMtxHGCMHandleData; +static struct VBGLHGCMHANDLEDATA g_aHGCMHandleData[64]; +/** @} */ + + +/** + * Initializes the HGCM VBGL bits. + * + * @return VBox status code. + */ +DECLR0VBGL(int) VbglR0HGCMInit(void) +{ + AssertReturn(g_hMtxHGCMHandleData == NIL_RTSEMFASTMUTEX, VINF_ALREADY_INITIALIZED); + return RTSemFastMutexCreate(&g_hMtxHGCMHandleData); +} + +/** + * Initializes the HGCM VBGL bits. + * + * @return VBox status code. + */ +DECLR0VBGL(int) VbglR0HGCMTerminate(void) +{ + RTSemFastMutexDestroy(g_hMtxHGCMHandleData); + g_hMtxHGCMHandleData = NIL_RTSEMFASTMUTEX; + + return VINF_SUCCESS; +} + +DECLINLINE(int) vbglR0HandleHeapEnter(void) +{ + int rc = RTSemFastMutexRequest(g_hMtxHGCMHandleData); + + VBGL_HGCM_ASSERT_MSG(RT_SUCCESS(rc), ("Failed to request handle heap mutex, rc = %Rrc\n", rc)); + + return rc; +} + +DECLINLINE(void) vbglR0HandleHeapLeave(void) +{ + RTSemFastMutexRelease(g_hMtxHGCMHandleData); +} + +struct VBGLHGCMHANDLEDATA *vbglR0HGCMHandleAlloc(void) +{ + struct VBGLHGCMHANDLEDATA *p = NULL; + int rc = vbglR0HandleHeapEnter(); + if (RT_SUCCESS(rc)) + { + uint32_t i; + + /* Simple linear search in array. This will be called not so often, only connect/disconnect. */ + /** @todo bitmap for faster search and other obvious optimizations. */ + for (i = 0; i < RT_ELEMENTS(g_aHGCMHandleData); i++) + { + if (!g_aHGCMHandleData[i].fAllocated) + { + p = &g_aHGCMHandleData[i]; + p->fAllocated = 1; + break; + } + } + + vbglR0HandleHeapLeave(); + + VBGL_HGCM_ASSERT_MSG(p != NULL, ("Not enough HGCM handles.\n")); + } + return p; +} + +void vbglR0HGCMHandleFree(struct VBGLHGCMHANDLEDATA *pHandle) +{ + if (pHandle) + { + int rc = vbglR0HandleHeapEnter(); + if (RT_SUCCESS(rc)) + { + VBGL_HGCM_ASSERT_MSG(pHandle->fAllocated, ("Freeing not allocated handle.\n")); + + RT_ZERO(*pHandle); + vbglR0HandleHeapLeave(); + } + } +} + +DECLR0VBGL(int) VbglR0HGCMConnect(VBGLHGCMHANDLE *pHandle, const char *pszServiceName, HGCMCLIENTID *pidClient) +{ + int rc; + if (pHandle && pszServiceName && pidClient) + { + struct VBGLHGCMHANDLEDATA *pHandleData = vbglR0HGCMHandleAlloc(); + if (pHandleData) + { + rc = VbglR0IdcOpen(&pHandleData->IdcHandle, + VBGL_IOC_VERSION /*uReqVersion*/, + VBGL_IOC_VERSION & UINT32_C(0xffff0000) /*uMinVersion*/, + NULL /*puSessionVersion*/, NULL /*puDriverVersion*/, NULL /*uDriverRevision*/); + if (RT_SUCCESS(rc)) + { + VBGLIOCHGCMCONNECT Info; + RT_ZERO(Info); + VBGLREQHDR_INIT(&Info.Hdr, HGCM_CONNECT); + Info.u.In.Loc.type = VMMDevHGCMLoc_LocalHost_Existing; + rc = RTStrCopy(Info.u.In.Loc.u.host.achName, sizeof(Info.u.In.Loc.u.host.achName), pszServiceName); + if (RT_SUCCESS(rc)) + { + rc = VbglR0IdcCall(&pHandleData->IdcHandle, VBGL_IOCTL_HGCM_CONNECT, &Info.Hdr, sizeof(Info)); + if (RT_SUCCESS(rc)) + { + *pidClient = Info.u.Out.idClient; + *pHandle = pHandleData; + return rc; + } + } + + VbglR0IdcClose(&pHandleData->IdcHandle); + } + + vbglR0HGCMHandleFree(pHandleData); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + return rc; +} + +DECLR0VBGL(int) VbglR0HGCMDisconnect(VBGLHGCMHANDLE handle, HGCMCLIENTID idClient) +{ + int rc; + VBGLIOCHGCMDISCONNECT Info; + + RT_ZERO(Info); + VBGLREQHDR_INIT(&Info.Hdr, HGCM_DISCONNECT); + Info.u.In.idClient = idClient; + rc = VbglR0IdcCall(&handle->IdcHandle, VBGL_IOCTL_HGCM_DISCONNECT, &Info.Hdr, sizeof(Info)); + + VbglR0IdcClose(&handle->IdcHandle); + + vbglR0HGCMHandleFree(handle); + + return rc; +} + +DECLR0VBGL(int) VbglR0HGCMCallRaw(VBGLHGCMHANDLE handle, PVBGLIOCHGCMCALL pData, uint32_t cbData) +{ + VBGL_HGCM_ASSERT_MSG(cbData >= sizeof(VBGLIOCHGCMCALL) + pData->cParms * sizeof(HGCMFunctionParameter), + ("cbData = %d, cParms = %d (calculated size %d)\n", cbData, pData->cParms, + sizeof(VBGLIOCHGCMCALL) + pData->cParms * sizeof(VBGLIOCHGCMCALL))); + + return VbglR0IdcCallRaw(&handle->IdcHandle, VBGL_IOCTL_HGCM_CALL(cbData), &pData->Hdr, cbData); +} + +DECLR0VBGL(int) VbglR0HGCMCall(VBGLHGCMHANDLE handle, PVBGLIOCHGCMCALL pData, uint32_t cbData) +{ + int rc = VbglR0HGCMCallRaw(handle, pData, cbData); + if (RT_SUCCESS(rc)) + rc = pData->Hdr.rc; + return rc; +} + +DECLR0VBGL(int) VbglR0HGCMCallUserDataRaw(VBGLHGCMHANDLE handle, PVBGLIOCHGCMCALL pData, uint32_t cbData) +{ + VBGL_HGCM_ASSERT_MSG(cbData >= sizeof(VBGLIOCHGCMCALL) + pData->cParms * sizeof(HGCMFunctionParameter), + ("cbData = %d, cParms = %d (calculated size %d)\n", cbData, pData->cParms, + sizeof(VBGLIOCHGCMCALL) + pData->cParms * sizeof(VBGLIOCHGCMCALL))); + + return VbglR0IdcCallRaw(&handle->IdcHandle, VBGL_IOCTL_HGCM_CALL_WITH_USER_DATA(cbData), &pData->Hdr, cbData); +} + + +DECLR0VBGL(int) VbglR0HGCMFastCall(VBGLHGCMHANDLE hHandle, PVBGLIOCIDCHGCMFASTCALL pCallReq, uint32_t cbCallReq) +{ + /* pCallReq->Hdr.rc and pCallReq->HgcmCallReq.header.header.rc; are not used by this IDC. */ + return VbglR0IdcCallRaw(&hHandle->IdcHandle, VBGL_IOCTL_IDC_HGCM_FAST_CALL, &pCallReq->Hdr, cbCallReq); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCMInternal.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCMInternal.cpp new file mode 100644 index 00000000..2a237db1 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCMInternal.cpp @@ -0,0 +1,1175 @@ +/* $Id: VBoxGuestR0LibHGCMInternal.cpp $ */ +/** @file + * VBoxGuestLib - Host-Guest Communication Manager internal functions, implemented by VBoxGuest + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_HGCM + +#include "VBoxGuestR0LibInternal.h" +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/memobj.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <VBox/err.h> + +#ifndef VBGL_VBOXGUEST +# error "This file should only be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest." +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The max parameter buffer size for a user request. */ +#define VBGLR0_MAX_HGCM_USER_PARM (24*_1M) +/** The max parameter buffer size for a kernel request. */ +#define VBGLR0_MAX_HGCM_KERNEL_PARM (16*_1M) +/** The max embedded buffer size. */ +#define VBGLR0_MAX_HGCM_EMBEDDED_BUFFER _64K + +#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) +/** Linux needs to use bounce buffers since RTR0MemObjLockUser has unwanted + * side effects. + * Darwin 32bit & 64bit also needs this because of 4GB/4GB user/kernel space. */ +# define USE_BOUNCE_BUFFERS +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Lock info structure used by VbglR0HGCMInternalCall and its helpers. + */ +struct VbglR0ParmInfo +{ + uint32_t cLockBufs; + struct + { + uint32_t iParm; + RTR0MEMOBJ hObj; +#ifdef USE_BOUNCE_BUFFERS + void *pvSmallBuf; +#endif + } aLockBufs[10]; +}; + + + +/* These functions can be only used by VBoxGuest. */ + +DECLR0VBGL(int) VbglR0HGCMInternalConnect(HGCMServiceLocation const *pLoc, uint32_t fRequestor, HGCMCLIENTID *pidClient, + PFNVBGLHGCMCALLBACK pfnAsyncCallback, void *pvAsyncData, uint32_t u32AsyncData) +{ + int rc; + if ( RT_VALID_PTR(pLoc) + && RT_VALID_PTR(pidClient) + && RT_VALID_PTR(pfnAsyncCallback)) + { + /* Allocate request */ + VMMDevHGCMConnect *pHGCMConnect = NULL; + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pHGCMConnect, sizeof(VMMDevHGCMConnect), VMMDevReq_HGCMConnect); + if (RT_SUCCESS(rc)) + { + /* Initialize request memory */ + pHGCMConnect->header.header.fRequestor = fRequestor; + + pHGCMConnect->header.fu32Flags = 0; + + memcpy(&pHGCMConnect->loc, pLoc, sizeof(pHGCMConnect->loc)); + pHGCMConnect->u32ClientID = 0; + + /* Issue request */ + rc = VbglR0GRPerform (&pHGCMConnect->header.header); + if (RT_SUCCESS(rc)) + { + /* Check if host decides to process the request asynchronously. */ + if (rc == VINF_HGCM_ASYNC_EXECUTE) + { + /* Wait for request completion interrupt notification from host */ + pfnAsyncCallback(&pHGCMConnect->header, pvAsyncData, u32AsyncData); + } + + rc = pHGCMConnect->header.result; + if (RT_SUCCESS(rc)) + *pidClient = pHGCMConnect->u32ClientID; + } + VbglR0GRFree(&pHGCMConnect->header.header); + } + } + else + rc = VERR_INVALID_PARAMETER; + return rc; +} + + +DECLR0VBGL(int) VbglR0HGCMInternalDisconnect(HGCMCLIENTID idClient, uint32_t fRequestor, + PFNVBGLHGCMCALLBACK pfnAsyncCallback, void *pvAsyncData, uint32_t u32AsyncData) +{ + int rc; + if ( idClient != 0 + && pfnAsyncCallback) + { + /* Allocate request */ + VMMDevHGCMDisconnect *pHGCMDisconnect = NULL; + rc = VbglR0GRAlloc ((VMMDevRequestHeader **)&pHGCMDisconnect, sizeof (VMMDevHGCMDisconnect), VMMDevReq_HGCMDisconnect); + if (RT_SUCCESS(rc)) + { + /* Initialize request memory */ + pHGCMDisconnect->header.header.fRequestor = fRequestor; + + pHGCMDisconnect->header.fu32Flags = 0; + + pHGCMDisconnect->u32ClientID = idClient; + + /* Issue request */ + rc = VbglR0GRPerform(&pHGCMDisconnect->header.header); + if (RT_SUCCESS(rc)) + { + /* Check if host decides to process the request asynchronously. */ + if (rc == VINF_HGCM_ASYNC_EXECUTE) + { + /* Wait for request completion interrupt notification from host */ + pfnAsyncCallback(&pHGCMDisconnect->header, pvAsyncData, u32AsyncData); + } + + rc = pHGCMDisconnect->header.result; + } + + VbglR0GRFree(&pHGCMDisconnect->header.header); + } + } + else + rc = VERR_INVALID_PARAMETER; + return rc; +} + + +/** + * Preprocesses the HGCM call, validating and locking/buffering parameters. + * + * @returns VBox status code. + * + * @param pCallInfo The call info. + * @param cbCallInfo The size of the call info structure. + * @param fIsUser Is it a user request or kernel request. + * @param pcbExtra Where to return the extra request space needed for + * physical page lists. + */ +static int vbglR0HGCMInternalPreprocessCall(PCVBGLIOCHGCMCALL pCallInfo, uint32_t cbCallInfo, + bool fIsUser, struct VbglR0ParmInfo *pParmInfo, size_t *pcbExtra) +{ + HGCMFunctionParameter const *pSrcParm = VBGL_HGCM_GET_CALL_PARMS(pCallInfo); + uint32_t const cParms = pCallInfo->cParms; + uint32_t iParm; + uint32_t cb; + + /* + * Lock down the any linear buffers so we can get their addresses + * and figure out how much extra storage we need for page lists. + * + * Note! With kernel mode users we can be assertive. For user mode users + * we should just (debug) log it and fail without any fanfare. + */ + *pcbExtra = 0; + pParmInfo->cLockBufs = 0; + for (iParm = 0; iParm < cParms; iParm++, pSrcParm++) + { + switch (pSrcParm->type) + { + case VMMDevHGCMParmType_32bit: + Log4(("GstHGCMCall: parm=%u type=32bit: %#010x\n", iParm, pSrcParm->u.value32)); + break; + + case VMMDevHGCMParmType_64bit: + Log4(("GstHGCMCall: parm=%u type=64bit: %#018RX64\n", iParm, pSrcParm->u.value64)); + break; + + case VMMDevHGCMParmType_PageList: + case VMMDevHGCMParmType_ContiguousPageList: + if (fIsUser) + return VERR_INVALID_PARAMETER; + cb = pSrcParm->u.PageList.size; + if (cb) + { + uint32_t off = pSrcParm->u.PageList.offset; + HGCMPageListInfo *pPgLst; + uint32_t cPages; + uint32_t u32; + + AssertMsgReturn(cb <= VBGLR0_MAX_HGCM_KERNEL_PARM, ("%#x > %#x\n", cb, VBGLR0_MAX_HGCM_KERNEL_PARM), + VERR_OUT_OF_RANGE); + AssertMsgReturn( off >= cParms * sizeof(HGCMFunctionParameter) + && off <= cbCallInfo - sizeof(HGCMPageListInfo), + ("offset=%#x cParms=%#x cbCallInfo=%#x\n", off, cParms, cbCallInfo), + VERR_INVALID_PARAMETER); + + pPgLst = (HGCMPageListInfo *)((uint8_t *)pCallInfo + off); + cPages = pPgLst->cPages; + u32 = RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages]) + off; + AssertMsgReturn(u32 <= cbCallInfo, + ("u32=%#x (cPages=%#x offset=%#x) cbCallInfo=%#x\n", u32, cPages, off, cbCallInfo), + VERR_INVALID_PARAMETER); + AssertMsgReturn(pPgLst->offFirstPage < PAGE_SIZE, ("#x\n", pPgLst->offFirstPage), VERR_INVALID_PARAMETER); + u32 = RT_ALIGN_32(pPgLst->offFirstPage + cb, PAGE_SIZE) >> PAGE_SHIFT; + AssertMsgReturn(cPages == u32, ("cPages=%#x u32=%#x\n", cPages, u32), VERR_INVALID_PARAMETER); + AssertMsgReturn(VBOX_HGCM_F_PARM_ARE_VALID(pPgLst->flags), ("%#x\n", pPgLst->flags), VERR_INVALID_PARAMETER); + Log4(("GstHGCMCall: parm=%u type=pglst: cb=%#010x cPgs=%u offPg0=%#x flags=%#x\n", + iParm, cb, cPages, pPgLst->offFirstPage, pPgLst->flags)); + u32 = cPages; + while (u32-- > 0) + { + Log4(("GstHGCMCall: pg#%u=%RHp\n", u32, pPgLst->aPages[u32])); + AssertMsgReturn(!(pPgLst->aPages[u32] & (PAGE_OFFSET_MASK | UINT64_C(0xfff0000000000000))), + ("pg#%u=%RHp\n", u32, pPgLst->aPages[u32]), + VERR_INVALID_PARAMETER); + } + + *pcbExtra += RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[pPgLst->cPages]); + } + else + Log4(("GstHGCMCall: parm=%u type=pglst: cb=0\n", iParm)); + break; + + case VMMDevHGCMParmType_Embedded: + if (fIsUser) /// @todo relax this. + return VERR_INVALID_PARAMETER; + cb = pSrcParm->u.Embedded.cbData; + if (cb) + { + uint32_t off = pSrcParm->u.Embedded.offData; + AssertMsgReturn(cb <= VBGLR0_MAX_HGCM_EMBEDDED_BUFFER, ("%#x > %#x\n", cb, VBGLR0_MAX_HGCM_EMBEDDED_BUFFER), + VERR_INVALID_PARAMETER); + AssertMsgReturn(cb <= cbCallInfo - cParms * sizeof(HGCMFunctionParameter), + ("cb=%#x cParms=%#x cbCallInfo=%3x\n", cb, cParms, cbCallInfo), + VERR_INVALID_PARAMETER); + AssertMsgReturn( off >= cParms * sizeof(HGCMFunctionParameter) + && off <= cbCallInfo - cb, + ("offData=%#x cParms=%#x cbCallInfo=%#x\n", off, cParms, cbCallInfo), + VERR_INVALID_PARAMETER); + AssertMsgReturn(VBOX_HGCM_F_PARM_ARE_VALID(pSrcParm->u.Embedded.fFlags), + ("%#x\n", pSrcParm->u.Embedded.fFlags), VERR_INVALID_PARAMETER); + + *pcbExtra += RT_ALIGN_32(cb, 8); + } + else + Log4(("GstHGCMCall: parm=%u type=embed: cb=0\n", iParm)); + break; + + + case VMMDevHGCMParmType_LinAddr_Locked_In: + case VMMDevHGCMParmType_LinAddr_Locked_Out: + case VMMDevHGCMParmType_LinAddr_Locked: + if (fIsUser) + return VERR_INVALID_PARAMETER; + if (!VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ true)) + { + cb = pSrcParm->u.Pointer.size; + AssertMsgReturn(cb <= VBGLR0_MAX_HGCM_KERNEL_PARM, ("%#x > %#x\n", cb, VBGLR0_MAX_HGCM_KERNEL_PARM), + VERR_OUT_OF_RANGE); + if (cb != 0) + Log4(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p\n", + iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr)); + else + Log4(("GstHGCMCall: parm=%u type=%#x: cb=0\n", iParm, pSrcParm->type)); + break; + } + RT_FALL_THRU(); + + case VMMDevHGCMParmType_LinAddr_In: + case VMMDevHGCMParmType_LinAddr_Out: + case VMMDevHGCMParmType_LinAddr: + cb = pSrcParm->u.Pointer.size; + if (cb != 0) + { +#ifdef USE_BOUNCE_BUFFERS + void *pvSmallBuf = NULL; +#endif + uint32_t iLockBuf = pParmInfo->cLockBufs; + RTR0MEMOBJ hObj; + int rc; + uint32_t fAccess = pSrcParm->type == VMMDevHGCMParmType_LinAddr_In + || pSrcParm->type == VMMDevHGCMParmType_LinAddr_Locked_In + ? RTMEM_PROT_READ + : RTMEM_PROT_READ | RTMEM_PROT_WRITE; + + AssertReturn(iLockBuf < RT_ELEMENTS(pParmInfo->aLockBufs), VERR_INVALID_PARAMETER); + if (!fIsUser) + { + AssertMsgReturn(cb <= VBGLR0_MAX_HGCM_KERNEL_PARM, ("%#x > %#x\n", cb, VBGLR0_MAX_HGCM_KERNEL_PARM), + VERR_OUT_OF_RANGE); + rc = RTR0MemObjLockKernel(&hObj, (void *)pSrcParm->u.Pointer.u.linearAddr, cb, fAccess); + if (RT_FAILURE(rc)) + { + Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemObjLockKernel(,%p,%#x) -> %Rrc\n", + pCallInfo->u32ClientID, pCallInfo->u32Function, iParm, pSrcParm->u.Pointer.u.linearAddr, cb, rc)); + return rc; + } + Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p locked kernel -> %p\n", + iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, hObj)); + } + else if (cb > VBGLR0_MAX_HGCM_USER_PARM) + { + Log(("GstHGCMCall: id=%#x fn=%u parm=%u pv=%p cb=%#x > %#x -> out of range\n", + pCallInfo->u32ClientID, pCallInfo->u32Function, iParm, pSrcParm->u.Pointer.u.linearAddr, + cb, VBGLR0_MAX_HGCM_USER_PARM)); + return VERR_OUT_OF_RANGE; + } + else + { +#ifndef USE_BOUNCE_BUFFERS + rc = RTR0MemObjLockUser(&hObj, (RTR3PTR)pSrcParm->u.Pointer.u.linearAddr, cb, fAccess, NIL_RTR0PROCESS); + if (RT_FAILURE(rc)) + { + Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemObjLockUser(,%p,%#x,nil) -> %Rrc\n", + pCallInfo->u32ClientID, pCallInfo->u32Function, iParm, pSrcParm->u.Pointer.u.linearAddr, cb, rc)); + return rc; + } + Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p locked user -> %p\n", + iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, hObj)); + +#else /* USE_BOUNCE_BUFFERS */ + /* + * This is a bit massive, but we don't want to waste a + * whole page for a 3 byte string buffer (guest props). + * + * The threshold is ASSUMING sizeof(RTMEMHDR) == 16 and + * the system is using some power of two allocator. + */ + /** @todo A more efficient strategy would be to combine buffers. However it + * is probably going to be more massive than the current code, so + * it can wait till later. */ + bool fCopyIn = pSrcParm->type != VMMDevHGCMParmType_LinAddr_Out + && pSrcParm->type != VMMDevHGCMParmType_LinAddr_Locked_Out; + if (cb <= PAGE_SIZE / 2 - 16) + { + pvSmallBuf = fCopyIn ? RTMemTmpAlloc(cb) : RTMemTmpAllocZ(cb); + if (RT_UNLIKELY(!pvSmallBuf)) + return VERR_NO_MEMORY; + if (fCopyIn) + { + rc = RTR0MemUserCopyFrom(pvSmallBuf, pSrcParm->u.Pointer.u.linearAddr, cb); + if (RT_FAILURE(rc)) + { + RTMemTmpFree(pvSmallBuf); + Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemUserCopyFrom(,%p,%#x) -> %Rrc\n", + pCallInfo->u32ClientID, pCallInfo->u32Function, iParm, + pSrcParm->u.Pointer.u.linearAddr, cb, rc)); + return rc; + } + } + rc = RTR0MemObjLockKernel(&hObj, pvSmallBuf, cb, fAccess); + if (RT_FAILURE(rc)) + { + RTMemTmpFree(pvSmallBuf); + Log(("GstHGCMCall: RTR0MemObjLockKernel failed for small buffer: rc=%Rrc pvSmallBuf=%p cb=%#x\n", + rc, pvSmallBuf, cb)); + return rc; + } + Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p small buffer %p -> %p\n", + iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, pvSmallBuf, hObj)); + } + else + { + rc = RTR0MemObjAllocPage(&hObj, cb, false /*fExecutable*/); + if (RT_FAILURE(rc)) + return rc; + if (!fCopyIn) + memset(RTR0MemObjAddress(hObj), '\0', cb); + else + { + rc = RTR0MemUserCopyFrom(RTR0MemObjAddress(hObj), pSrcParm->u.Pointer.u.linearAddr, cb); + if (RT_FAILURE(rc)) + { + RTR0MemObjFree(hObj, false /*fFreeMappings*/); + Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemUserCopyFrom(,%p,%#x) -> %Rrc\n", + pCallInfo->u32ClientID, pCallInfo->u32Function, iParm, + pSrcParm->u.Pointer.u.linearAddr, cb, rc)); + return rc; + } + } + Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p big buffer -> %p\n", + iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, hObj)); + } +#endif /* USE_BOUNCE_BUFFERS */ + } + + pParmInfo->aLockBufs[iLockBuf].iParm = iParm; + pParmInfo->aLockBufs[iLockBuf].hObj = hObj; +#ifdef USE_BOUNCE_BUFFERS + pParmInfo->aLockBufs[iLockBuf].pvSmallBuf = pvSmallBuf; +#endif + pParmInfo->cLockBufs = iLockBuf + 1; + + if (VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ false)) + { + size_t const cPages = RTR0MemObjSize(hObj) >> PAGE_SHIFT; + *pcbExtra += RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages]); + } + } + else + Log4(("GstHGCMCall: parm=%u type=%#x: cb=0\n", iParm, pSrcParm->type)); + break; + + default: + return VERR_INVALID_PARAMETER; + } + } + + return VINF_SUCCESS; +} + + +/** + * Translates locked linear address to the normal type. + * The locked types are only for the guest side and not handled by the host. + * + * @returns normal linear address type. + * @param enmType The type. + */ +static HGCMFunctionParameterType vbglR0HGCMInternalConvertLinAddrType(HGCMFunctionParameterType enmType) +{ + switch (enmType) + { + case VMMDevHGCMParmType_LinAddr_Locked_In: + return VMMDevHGCMParmType_LinAddr_In; + case VMMDevHGCMParmType_LinAddr_Locked_Out: + return VMMDevHGCMParmType_LinAddr_Out; + case VMMDevHGCMParmType_LinAddr_Locked: + return VMMDevHGCMParmType_LinAddr; + default: + return enmType; + } +} + + +/** + * Translates linear address types to page list direction flags. + * + * @returns page list flags. + * @param enmType The type. + */ +static uint32_t vbglR0HGCMInternalLinAddrTypeToPageListFlags(HGCMFunctionParameterType enmType) +{ + switch (enmType) + { + case VMMDevHGCMParmType_LinAddr_In: + case VMMDevHGCMParmType_LinAddr_Locked_In: + return VBOX_HGCM_F_PARM_DIRECTION_TO_HOST; + + case VMMDevHGCMParmType_LinAddr_Out: + case VMMDevHGCMParmType_LinAddr_Locked_Out: + return VBOX_HGCM_F_PARM_DIRECTION_FROM_HOST; + + default: AssertFailed(); RT_FALL_THRU(); + case VMMDevHGCMParmType_LinAddr: + case VMMDevHGCMParmType_LinAddr_Locked: + return VBOX_HGCM_F_PARM_DIRECTION_BOTH; + } +} + + +/** + * Initializes the call request that we're sending to the host. + * + * @returns VBox status code. + * + * @param pCallInfo The call info. + * @param cbCallInfo The size of the call info structure. + * @param fRequestor VMMDEV_REQUESTOR_XXX. + * @param fIsUser Is it a user request or kernel request. + * @param pcbExtra Where to return the extra request space needed for + * physical page lists. + */ +static void vbglR0HGCMInternalInitCall(VMMDevHGCMCall *pHGCMCall, PCVBGLIOCHGCMCALL pCallInfo, + uint32_t cbCallInfo, uint32_t fRequestor, bool fIsUser, struct VbglR0ParmInfo *pParmInfo) +{ + HGCMFunctionParameter const *pSrcParm = VBGL_HGCM_GET_CALL_PARMS(pCallInfo); + HGCMFunctionParameter *pDstParm = VMMDEV_HGCM_CALL_PARMS(pHGCMCall); + uint32_t const cParms = pCallInfo->cParms; + uint32_t offExtra = (uint32_t)((uintptr_t)(pDstParm + cParms) - (uintptr_t)pHGCMCall); + uint32_t iLockBuf = 0; + uint32_t iParm; + RT_NOREF1(cbCallInfo); +#ifndef USE_BOUNCE_BUFFERS + RT_NOREF1(fIsUser); +#endif + + /* + * The call request headers. + */ + pHGCMCall->header.header.fRequestor = !fIsUser || (fRequestor & VMMDEV_REQUESTOR_USERMODE) ? fRequestor + : VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_USR_NOT_GIVEN + | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN | VMMDEV_REQUESTOR_CON_DONT_KNOW; + + pHGCMCall->header.fu32Flags = 0; + pHGCMCall->header.result = VINF_SUCCESS; + + pHGCMCall->u32ClientID = pCallInfo->u32ClientID; + pHGCMCall->u32Function = pCallInfo->u32Function; + pHGCMCall->cParms = cParms; + + /* + * The parameters. + */ + for (iParm = 0; iParm < cParms; iParm++, pSrcParm++, pDstParm++) + { + switch (pSrcParm->type) + { + case VMMDevHGCMParmType_32bit: + case VMMDevHGCMParmType_64bit: + *pDstParm = *pSrcParm; + break; + + case VMMDevHGCMParmType_PageList: + case VMMDevHGCMParmType_ContiguousPageList: + pDstParm->type = pSrcParm->type; + pDstParm->u.PageList.size = pSrcParm->u.PageList.size; + if (pSrcParm->u.PageList.size) + { + HGCMPageListInfo const *pSrcPgLst = (HGCMPageListInfo *)((uint8_t *)pCallInfo + pSrcParm->u.PageList.offset); + HGCMPageListInfo *pDstPgLst = (HGCMPageListInfo *)((uint8_t *)pHGCMCall + offExtra); + uint32_t const cPages = pSrcPgLst->cPages; + uint32_t iPage; + + pDstParm->u.PageList.offset = offExtra; + pDstPgLst->flags = pSrcPgLst->flags; + pDstPgLst->offFirstPage = pSrcPgLst->offFirstPage; + pDstPgLst->cPages = (uint16_t)cPages; + for (iPage = 0; iPage < cPages; iPage++) + pDstPgLst->aPages[iPage] = pSrcPgLst->aPages[iPage]; + + offExtra += RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages]); + } + else + pDstParm->u.PageList.offset = 0; /** @todo will fail on the host side now */ + break; + + case VMMDevHGCMParmType_Embedded: + { + uint32_t const cb = pSrcParm->u.Embedded.cbData; + pDstParm->type = VMMDevHGCMParmType_Embedded; + pDstParm->u.Embedded.cbData = cb; + pDstParm->u.Embedded.offData = offExtra; + if (cb > 0) + { + uint8_t *pbDst = (uint8_t *)pHGCMCall + offExtra; + if (pSrcParm->u.Embedded.fFlags & VBOX_HGCM_F_PARM_DIRECTION_TO_HOST) + { + memcpy(pbDst, (uint8_t const *)pCallInfo + pSrcParm->u.Embedded.offData, cb); + if (RT_ALIGN(cb, 8) != cb) + memset(pbDst + cb, 0, RT_ALIGN(cb, 8) - cb); + } + else + RT_BZERO(pbDst, RT_ALIGN(cb, 8)); + offExtra += RT_ALIGN(cb, 8); + } + break; + } + + case VMMDevHGCMParmType_LinAddr_Locked_In: + case VMMDevHGCMParmType_LinAddr_Locked_Out: + case VMMDevHGCMParmType_LinAddr_Locked: + if (!VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ true)) + { + *pDstParm = *pSrcParm; + pDstParm->type = vbglR0HGCMInternalConvertLinAddrType(pSrcParm->type); + break; + } + RT_FALL_THRU(); + + case VMMDevHGCMParmType_LinAddr_In: + case VMMDevHGCMParmType_LinAddr_Out: + case VMMDevHGCMParmType_LinAddr: + if (pSrcParm->u.Pointer.size != 0) + { +#ifdef USE_BOUNCE_BUFFERS + void *pvSmallBuf = pParmInfo->aLockBufs[iLockBuf].pvSmallBuf; +#endif + RTR0MEMOBJ hObj = pParmInfo->aLockBufs[iLockBuf].hObj; + Assert(iParm == pParmInfo->aLockBufs[iLockBuf].iParm); + + if (VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ false)) + { + HGCMPageListInfo *pDstPgLst = (HGCMPageListInfo *)((uint8_t *)pHGCMCall + offExtra); + size_t const cPages = RTR0MemObjSize(hObj) >> PAGE_SHIFT; + size_t iPage; + + pDstParm->type = VMMDevHGCMParmType_PageList; + pDstParm->u.PageList.size = pSrcParm->u.Pointer.size; + pDstParm->u.PageList.offset = offExtra; + pDstPgLst->flags = vbglR0HGCMInternalLinAddrTypeToPageListFlags(pSrcParm->type); +#ifdef USE_BOUNCE_BUFFERS + if (fIsUser) + pDstPgLst->offFirstPage = (uintptr_t)pvSmallBuf & PAGE_OFFSET_MASK; + else +#endif + pDstPgLst->offFirstPage = (uint16_t)(pSrcParm->u.Pointer.u.linearAddr & PAGE_OFFSET_MASK); + pDstPgLst->cPages = (uint16_t)cPages; Assert(pDstPgLst->cPages == cPages); + for (iPage = 0; iPage < cPages; iPage++) + { + pDstPgLst->aPages[iPage] = RTR0MemObjGetPagePhysAddr(hObj, iPage); + Assert(pDstPgLst->aPages[iPage] != NIL_RTHCPHYS); + } + + offExtra += RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages]); + } + else + { + pDstParm->type = vbglR0HGCMInternalConvertLinAddrType(pSrcParm->type); + pDstParm->u.Pointer.size = pSrcParm->u.Pointer.size; +#ifdef USE_BOUNCE_BUFFERS + if (fIsUser) + pDstParm->u.Pointer.u.linearAddr = pvSmallBuf + ? (uintptr_t)pvSmallBuf + : (uintptr_t)RTR0MemObjAddress(hObj); + else +#endif + pDstParm->u.Pointer.u.linearAddr = pSrcParm->u.Pointer.u.linearAddr; + } + iLockBuf++; + } + else + { + pDstParm->type = vbglR0HGCMInternalConvertLinAddrType(pSrcParm->type); + pDstParm->u.Pointer.size = 0; + pDstParm->u.Pointer.u.linearAddr = 0; + } + break; + + default: + AssertFailed(); + pDstParm->type = VMMDevHGCMParmType_Invalid; + break; + } + } +} + + +/** + * Performs the call and completion wait. + * + * @returns VBox status code of this operation, not necessarily the call. + * + * @param pHGCMCall The HGCM call info. + * @param pfnAsyncCallback The async callback that will wait for the call + * to complete. + * @param pvAsyncData Argument for the callback. + * @param u32AsyncData Argument for the callback. + * @param pfLeakIt Where to return the leak it / free it, + * indicator. Cancellation fun. + */ +static int vbglR0HGCMInternalDoCall(VMMDevHGCMCall *pHGCMCall, PFNVBGLHGCMCALLBACK pfnAsyncCallback, + void *pvAsyncData, uint32_t u32AsyncData, bool *pfLeakIt) +{ + int rc; + + Log(("calling VbglR0GRPerform\n")); + rc = VbglR0GRPerform(&pHGCMCall->header.header); + Log(("VbglR0GRPerform rc = %Rrc (header rc=%d)\n", rc, pHGCMCall->header.result)); + + /* + * If the call failed, but as a result of the request itself, then pretend + * success. Upper layers will interpret the result code in the packet. + */ + if ( RT_FAILURE(rc) + && rc == pHGCMCall->header.result) + { + Assert(pHGCMCall->header.fu32Flags & VBOX_HGCM_REQ_DONE); + rc = VINF_SUCCESS; + } + + /* + * Check if host decides to process the request asynchronously, + * if so, we wait for it to complete using the caller supplied callback. + */ + *pfLeakIt = false; + if (rc == VINF_HGCM_ASYNC_EXECUTE) + { + Log(("Processing HGCM call asynchronously\n")); + rc = pfnAsyncCallback(&pHGCMCall->header, pvAsyncData, u32AsyncData); + if (pHGCMCall->header.fu32Flags & VBOX_HGCM_REQ_DONE) + { + Assert(!(pHGCMCall->header.fu32Flags & VBOX_HGCM_REQ_CANCELLED)); + rc = VINF_SUCCESS; + } + else + { + /* + * The request didn't complete in time or the call was interrupted, + * the RC from the callback indicates which. Try cancel the request. + * + * This is a bit messy because we're racing request completion. Sorry. + */ + /** @todo It would be nice if we could use the waiter callback to do further + * waiting in case of a completion race. If it wasn't for WINNT having its own + * version of all that stuff, I would've done it already. */ + VMMDevHGCMCancel2 *pCancelReq; + int rc2 = VbglR0GRAlloc((VMMDevRequestHeader **)&pCancelReq, sizeof(*pCancelReq), VMMDevReq_HGCMCancel2); + if (RT_SUCCESS(rc2)) + { + pCancelReq->physReqToCancel = VbglR0PhysHeapGetPhysAddr(pHGCMCall); + rc2 = VbglR0GRPerform(&pCancelReq->header); + VbglR0GRFree(&pCancelReq->header); + } +#if 1 /** @todo ADDVER: Remove this on next minor version change. */ + if (rc2 == VERR_NOT_IMPLEMENTED) + { + /* host is too old, or we're out of heap. */ + pHGCMCall->header.fu32Flags |= VBOX_HGCM_REQ_CANCELLED; + pHGCMCall->header.header.requestType = VMMDevReq_HGCMCancel; + rc2 = VbglR0GRPerform(&pHGCMCall->header.header); + if (rc2 == VERR_INVALID_PARAMETER) + rc2 = VERR_NOT_FOUND; + else if (RT_SUCCESS(rc)) + RTThreadSleep(1); + } +#endif + if (RT_SUCCESS(rc)) rc = VERR_INTERRUPTED; /** @todo weed this out from the WINNT VBoxGuest code. */ + if (RT_SUCCESS(rc2)) + { + Log(("vbglR0HGCMInternalDoCall: successfully cancelled\n")); + pHGCMCall->header.fu32Flags |= VBOX_HGCM_REQ_CANCELLED; + } + else + { + /* + * Wait for a bit while the host (hopefully) completes it. + */ + uint64_t u64Start = RTTimeSystemMilliTS(); + uint32_t cMilliesToWait = rc2 == VERR_NOT_FOUND || rc2 == VERR_SEM_DESTROYED ? 500 : 2000; + uint64_t cElapsed = 0; + if (rc2 != VERR_NOT_FOUND) + { + static unsigned s_cErrors = 0; + if (s_cErrors++ < 32) + LogRel(("vbglR0HGCMInternalDoCall: Failed to cancel the HGCM call on %Rrc: rc2=%Rrc\n", rc, rc2)); + } + else + Log(("vbglR0HGCMInternalDoCall: Cancel race rc=%Rrc rc2=%Rrc\n", rc, rc2)); + + do + { + ASMCompilerBarrier(); /* paranoia */ + if (pHGCMCall->header.fu32Flags & VBOX_HGCM_REQ_DONE) + break; + RTThreadSleep(1); + cElapsed = RTTimeSystemMilliTS() - u64Start; + } while (cElapsed < cMilliesToWait); + + ASMCompilerBarrier(); /* paranoia^2 */ + if (pHGCMCall->header.fu32Flags & VBOX_HGCM_REQ_DONE) + rc = VINF_SUCCESS; + else + { + LogRel(("vbglR0HGCMInternalDoCall: Leaking %u bytes. Pending call to %u with %u parms. (rc2=%Rrc)\n", + pHGCMCall->header.header.size, pHGCMCall->u32Function, pHGCMCall->cParms, rc2)); + *pfLeakIt = true; + } + Log(("vbglR0HGCMInternalDoCall: Cancel race ended with rc=%Rrc (rc2=%Rrc) after %llu ms\n", rc, rc2, cElapsed)); + } + } + } + + Log(("GstHGCMCall: rc=%Rrc result=%Rrc fu32Flags=%#x fLeakIt=%d\n", + rc, pHGCMCall->header.result, pHGCMCall->header.fu32Flags, *pfLeakIt)); + return rc; +} + + +/** + * Copies the result of the call back to the caller info structure and user + * buffers (if using bounce buffers). + * + * @returns rc, unless RTR0MemUserCopyTo fails. + * @param pCallInfo Call info structure to update. + * @param cbCallInfo The size of the client request. + * @param pHGCMCall HGCM call request. + * @param cbHGCMCall The size of the HGCM call request. + * @param pParmInfo Parameter locking/buffering info. + * @param fIsUser Is it a user (true) or kernel request. + * @param rc The current result code. Passed along to + * preserve informational status codes. + */ +static int vbglR0HGCMInternalCopyBackResult(PVBGLIOCHGCMCALL pCallInfo, uint32_t cbCallInfo, + VMMDevHGCMCall const *pHGCMCall, uint32_t cbHGCMCall, + struct VbglR0ParmInfo *pParmInfo, bool fIsUser, int rc) +{ + HGCMFunctionParameter const *pSrcParm = VMMDEV_HGCM_CALL_PARMS(pHGCMCall); + HGCMFunctionParameter *pDstParm = VBGL_HGCM_GET_CALL_PARMS(pCallInfo); + uint32_t const cParms = pCallInfo->cParms; +#ifdef USE_BOUNCE_BUFFERS + uint32_t iLockBuf = 0; +#endif + uint32_t iParm; + RT_NOREF1(pParmInfo); +#ifndef USE_BOUNCE_BUFFERS + RT_NOREF1(fIsUser); +#endif + + /* + * The call result. + */ + pCallInfo->Hdr.rc = pHGCMCall->header.result; + + /* + * Copy back parameters. + */ + /** @todo This is assuming user data (pDstParm) is buffered. Not true + * on OS/2, though I'm not sure we care... */ + for (iParm = 0; iParm < cParms; iParm++, pSrcParm++, pDstParm++) + { + switch (pDstParm->type) + { + case VMMDevHGCMParmType_32bit: + case VMMDevHGCMParmType_64bit: + *pDstParm = *pSrcParm; + break; + + case VMMDevHGCMParmType_PageList: + case VMMDevHGCMParmType_ContiguousPageList: + pDstParm->u.PageList.size = pSrcParm->u.PageList.size; + break; + + case VMMDevHGCMParmType_Embedded: + { + uint32_t const cbDst = pDstParm->u.Embedded.cbData; + uint32_t cbSrc; + pDstParm->u.Embedded.cbData = cbSrc = pSrcParm->u.Embedded.cbData; + if ( cbSrc > 0 + && (pDstParm->u.Embedded.fFlags & VBOX_HGCM_F_PARM_DIRECTION_FROM_HOST)) + { + uint32_t const offDst = pDstParm->u.Embedded.offData; + uint32_t const offSrc = pSrcParm->u.Embedded.offData; + + AssertReturn(offDst < cbCallInfo, VERR_INTERNAL_ERROR_2); + AssertReturn(offDst >= sizeof(*pCallInfo) + cParms * sizeof(*pDstParm), VERR_INTERNAL_ERROR_2); + AssertReturn(cbDst <= cbCallInfo - offDst , VERR_INTERNAL_ERROR_2); + + AssertReturn(offSrc < cbCallInfo, VERR_INTERNAL_ERROR_2); + AssertReturn(offSrc >= sizeof(*pHGCMCall) + cParms * sizeof(*pSrcParm), VERR_INTERNAL_ERROR_2); + if (cbSrc <= cbHGCMCall - offSrc) + { /* likely */ } + else + { + /* Special case: Buffer overflow w/ correct size given. */ + AssertReturn(RT_FAILURE_NP(rc), VERR_INTERNAL_ERROR_2); + cbSrc = cbHGCMCall - offSrc; + } + memcpy((uint8_t *)pCallInfo + offDst, (uint8_t const *)pHGCMCall + offSrc, RT_MIN(cbSrc, cbDst)); + } + break; + } + + case VMMDevHGCMParmType_LinAddr_Locked_In: + case VMMDevHGCMParmType_LinAddr_In: +#ifdef USE_BOUNCE_BUFFERS + if ( fIsUser + && iLockBuf < pParmInfo->cLockBufs + && iParm == pParmInfo->aLockBufs[iLockBuf].iParm) + iLockBuf++; +#endif + pDstParm->u.Pointer.size = pSrcParm->u.Pointer.size; + break; + + case VMMDevHGCMParmType_LinAddr_Locked_Out: + case VMMDevHGCMParmType_LinAddr_Locked: + if (!VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ true)) + { + pDstParm->u.Pointer.size = pSrcParm->u.Pointer.size; + break; + } + RT_FALL_THRU(); + + case VMMDevHGCMParmType_LinAddr_Out: + case VMMDevHGCMParmType_LinAddr: + { +#ifdef USE_BOUNCE_BUFFERS + if (fIsUser) + { + size_t cbOut = RT_MIN(pSrcParm->u.Pointer.size, pDstParm->u.Pointer.size); + if (cbOut) + { + int rc2; + Assert(pParmInfo->aLockBufs[iLockBuf].iParm == iParm); + rc2 = RTR0MemUserCopyTo((RTR3PTR)pDstParm->u.Pointer.u.linearAddr, + pParmInfo->aLockBufs[iLockBuf].pvSmallBuf + ? pParmInfo->aLockBufs[iLockBuf].pvSmallBuf + : RTR0MemObjAddress(pParmInfo->aLockBufs[iLockBuf].hObj), + cbOut); + if (RT_FAILURE(rc2)) + return rc2; + iLockBuf++; + } + else if ( iLockBuf < pParmInfo->cLockBufs + && iParm == pParmInfo->aLockBufs[iLockBuf].iParm) + iLockBuf++; + } +#endif + pDstParm->u.Pointer.size = pSrcParm->u.Pointer.size; + break; + } + + default: + AssertFailed(); + rc = VERR_INTERNAL_ERROR_4; + break; + } + } + +#ifdef USE_BOUNCE_BUFFERS + Assert(!fIsUser || pParmInfo->cLockBufs == iLockBuf); +#endif + return rc; +} + + +DECLR0VBGL(int) VbglR0HGCMInternalCall(PVBGLIOCHGCMCALL pCallInfo, uint32_t cbCallInfo, uint32_t fFlags, uint32_t fRequestor, + PFNVBGLHGCMCALLBACK pfnAsyncCallback, void *pvAsyncData, uint32_t u32AsyncData) +{ + bool fIsUser = (fFlags & VBGLR0_HGCMCALL_F_MODE_MASK) == VBGLR0_HGCMCALL_F_USER; + struct VbglR0ParmInfo ParmInfo; + size_t cbExtra; + int rc; + + /* + * Basic validation. + */ + AssertMsgReturn( !pCallInfo + || !pfnAsyncCallback + || pCallInfo->cParms > VBOX_HGCM_MAX_PARMS + || !(fFlags & ~VBGLR0_HGCMCALL_F_MODE_MASK), + ("pCallInfo=%p pfnAsyncCallback=%p fFlags=%#x\n", pCallInfo, pfnAsyncCallback, fFlags), + VERR_INVALID_PARAMETER); + AssertReturn( cbCallInfo >= sizeof(VBGLIOCHGCMCALL) + || cbCallInfo >= pCallInfo->cParms * sizeof(HGCMFunctionParameter), + VERR_INVALID_PARAMETER); + + Log(("GstHGCMCall: u32ClientID=%#x u32Function=%u cParms=%u cbCallInfo=%#x fFlags=%#x\n", + pCallInfo->u32ClientID, pCallInfo->u32ClientID, pCallInfo->u32Function, pCallInfo->cParms, cbCallInfo, fFlags)); + + /* + * Validate, lock and buffer the parameters for the call. + * This will calculate the amount of extra space for physical page list. + */ + rc = vbglR0HGCMInternalPreprocessCall(pCallInfo, cbCallInfo, fIsUser, &ParmInfo, &cbExtra); + if (RT_SUCCESS(rc)) + { + /* + * Allocate the request buffer and recreate the call request. + */ + VMMDevHGCMCall *pHGCMCall; + uint32_t const cbHGCMCall = sizeof(VMMDevHGCMCall) + pCallInfo->cParms * sizeof(HGCMFunctionParameter) + (uint32_t)cbExtra; + rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pHGCMCall, cbHGCMCall, VMMDevReq_HGCMCall); + if (RT_SUCCESS(rc)) + { + bool fLeakIt; + vbglR0HGCMInternalInitCall(pHGCMCall, pCallInfo, cbCallInfo, fRequestor, fIsUser, &ParmInfo); + + /* + * Perform the call. + */ + rc = vbglR0HGCMInternalDoCall(pHGCMCall, pfnAsyncCallback, pvAsyncData, u32AsyncData, &fLeakIt); + if (RT_SUCCESS(rc)) + { + /* + * Copy back the result (parameters and buffers that changed). + */ + rc = vbglR0HGCMInternalCopyBackResult(pCallInfo, cbCallInfo, pHGCMCall, cbHGCMCall, &ParmInfo, fIsUser, rc); + } + else + { + if ( rc != VERR_INTERRUPTED + && rc != VERR_TIMEOUT) + { + static unsigned s_cErrors = 0; + if (s_cErrors++ < 32) + LogRel(("VbglR0HGCMInternalCall: vbglR0HGCMInternalDoCall failed. rc=%Rrc\n", rc)); + } + } + + if (!fLeakIt) + VbglR0GRFree(&pHGCMCall->header.header); + } + } + else + LogRel(("VbglR0HGCMInternalCall: vbglR0HGCMInternalPreprocessCall failed. rc=%Rrc\n", rc)); + + /* + * Release locks and free bounce buffers. + */ + if (ParmInfo.cLockBufs) + while (ParmInfo.cLockBufs-- > 0) + { + RTR0MemObjFree(ParmInfo.aLockBufs[ParmInfo.cLockBufs].hObj, false /*fFreeMappings*/); +#ifdef USE_BOUNCE_BUFFERS + RTMemTmpFree(ParmInfo.aLockBufs[ParmInfo.cLockBufs].pvSmallBuf); +#endif + } + + return rc; +} + + +#if ARCH_BITS == 64 +DECLR0VBGL(int) VbglR0HGCMInternalCall32(PVBGLIOCHGCMCALL pCallInfo, uint32_t cbCallInfo, uint32_t fFlags, uint32_t fRequestor, + PFNVBGLHGCMCALLBACK pfnAsyncCallback, void *pvAsyncData, uint32_t u32AsyncData) +{ + PVBGLIOCHGCMCALL pCallInfo64 = NULL; + HGCMFunctionParameter *pParm64 = NULL; + HGCMFunctionParameter32 *pParm32 = NULL; + uint32_t cParms = 0; + uint32_t iParm = 0; + int rc = VINF_SUCCESS; + + /* + * Input validation. + */ + AssertMsgReturn( !pCallInfo + || !pfnAsyncCallback + || pCallInfo->cParms > VBOX_HGCM_MAX_PARMS + || !(fFlags & ~VBGLR0_HGCMCALL_F_MODE_MASK), + ("pCallInfo=%p pfnAsyncCallback=%p fFlags=%#x\n", pCallInfo, pfnAsyncCallback, fFlags), + VERR_INVALID_PARAMETER); + AssertReturn( cbCallInfo >= sizeof(VBGLIOCHGCMCALL) + || cbCallInfo >= pCallInfo->cParms * sizeof(HGCMFunctionParameter32), + VERR_INVALID_PARAMETER); + + /* This Assert does not work on Solaris/Windows 64/32 mixed mode, not sure why, skipping for now */ +#if !defined(RT_OS_SOLARIS) && !defined(RT_OS_WINDOWS) + AssertReturn((fFlags & VBGLR0_HGCMCALL_F_MODE_MASK) == VBGLR0_HGCMCALL_F_KERNEL, VERR_WRONG_ORDER); +#endif + + cParms = pCallInfo->cParms; + Log(("VbglR0HGCMInternalCall32: cParms=%d, u32Function=%d, fFlags=%#x\n", cParms, pCallInfo->u32Function, fFlags)); + + /* + * The simple approach, allocate a temporary request and convert the parameters. + */ + pCallInfo64 = (PVBGLIOCHGCMCALL)RTMemTmpAllocZ(sizeof(*pCallInfo64) + cParms * sizeof(HGCMFunctionParameter)); + if (!pCallInfo64) + return VERR_NO_TMP_MEMORY; + + *pCallInfo64 = *pCallInfo; + pParm32 = VBGL_HGCM_GET_CALL_PARMS32(pCallInfo); + pParm64 = VBGL_HGCM_GET_CALL_PARMS(pCallInfo64); + for (iParm = 0; iParm < cParms; iParm++, pParm32++, pParm64++) + { + switch (pParm32->type) + { + case VMMDevHGCMParmType_32bit: + pParm64->type = VMMDevHGCMParmType_32bit; + pParm64->u.value32 = pParm32->u.value32; + break; + + case VMMDevHGCMParmType_64bit: + pParm64->type = VMMDevHGCMParmType_64bit; + pParm64->u.value64 = pParm32->u.value64; + break; + + case VMMDevHGCMParmType_LinAddr_Out: + case VMMDevHGCMParmType_LinAddr: + case VMMDevHGCMParmType_LinAddr_In: + pParm64->type = pParm32->type; + pParm64->u.Pointer.size = pParm32->u.Pointer.size; + pParm64->u.Pointer.u.linearAddr = pParm32->u.Pointer.u.linearAddr; + break; + + default: + rc = VERR_INVALID_PARAMETER; + LogRel(("VbglR0HGCMInternalCall32: pParm32 type %#x invalid.\n", pParm32->type)); + break; + } + if (RT_FAILURE(rc)) + break; + } + if (RT_SUCCESS(rc)) + { + rc = VbglR0HGCMInternalCall(pCallInfo64, sizeof(*pCallInfo64) + cParms * sizeof(HGCMFunctionParameter), fFlags, + fRequestor, pfnAsyncCallback, pvAsyncData, u32AsyncData); + + if (RT_SUCCESS(rc)) + { + *pCallInfo = *pCallInfo64; + + /* + * Copy back. + */ + pParm32 = VBGL_HGCM_GET_CALL_PARMS32(pCallInfo); + pParm64 = VBGL_HGCM_GET_CALL_PARMS(pCallInfo64); + for (iParm = 0; iParm < cParms; iParm++, pParm32++, pParm64++) + { + switch (pParm64->type) + { + case VMMDevHGCMParmType_32bit: + pParm32->u.value32 = pParm64->u.value32; + break; + + case VMMDevHGCMParmType_64bit: + pParm32->u.value64 = pParm64->u.value64; + break; + + case VMMDevHGCMParmType_LinAddr_Out: + case VMMDevHGCMParmType_LinAddr: + case VMMDevHGCMParmType_LinAddr_In: + pParm32->u.Pointer.size = pParm64->u.Pointer.size; + break; + + default: + LogRel(("VbglR0HGCMInternalCall32: failed invalid pParm32 type %d\n", pParm32->type)); + rc = VERR_INTERNAL_ERROR_3; + break; + } + } + } + else + { + static unsigned s_cErrors = 0; + if (s_cErrors++ < 32) + LogRel(("VbglR0HGCMInternalCall32: VbglR0HGCMInternalCall failed. rc=%Rrc\n", rc)); + } + } + else + { + static unsigned s_cErrors = 0; + if (s_cErrors++ < 32) + LogRel(("VbglR0HGCMInternalCall32: failed. rc=%Rrc\n", rc)); + } + + RTMemTmpFree(pCallInfo64); + return rc; +} +#endif /* ARCH_BITS == 64 */ + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-os2.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-os2.cpp new file mode 100644 index 00000000..828dea2e --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-os2.cpp @@ -0,0 +1,85 @@ +/* $Id: VBoxGuestR0LibIdc-os2.cpp $ */ +/** @file + * VBoxGuestLib - Ring-0 Support Library for VBoxGuest, IDC, OS/2 specific. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR0LibInternal.h" +#include <VBox/err.h> +#include <VBox/log.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN /* for watcom */ +/* This is defined in some assembly file. The AttachDD operation + is done in the driver init code. */ +extern VBGLOS2ATTACHDD g_VBoxGuestIDC; +RT_C_DECLS_END + + + +int VBOXCALL vbglR0IdcNativeOpen(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCCONNECT pReq) +{ + /* + * Just check whether the connection was made or not. + */ + if ( g_VBoxGuestIDC.u32Version == VBGL_IOC_VERSION + && RT_VALID_PTR(g_VBoxGuestIDC.u32Session) + && RT_VALID_PTR(g_VBoxGuestIDC.pfnServiceEP)) + return g_VBoxGuestIDC.pfnServiceEP(g_VBoxGuestIDC.u32Session, VBGL_IOCTL_IDC_CONNECT, &pReq->Hdr, sizeof(*pReq)); + Log(("vbglDriverOpen: failed\n")); + RT_NOREF(pHandle); + return VERR_FILE_NOT_FOUND; +} + + +int VBOXCALL vbglR0IdcNativeClose(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCDISCONNECT pReq) +{ + return g_VBoxGuestIDC.pfnServiceEP((uintptr_t)pHandle->s.pvSession, VBGL_IOCTL_IDC_DISCONNECT, &pReq->Hdr, sizeof(*pReq)); +} + + +/** + * Makes an IDC call, returning only the I/O control status code. + * + * @returns VBox status code (the I/O control failure status). + * @param pHandle The IDC handle. + * @param uReq The request number. + * @param pReqHdr The request header. + * @param cbReq The request size. + */ +DECLR0VBGL(int) VbglR0IdcCallRaw(PVBGLIDCHANDLE pHandle, uintptr_t uReq, PVBGLREQHDR pReqHdr, uint32_t cbReq) +{ + return g_VBoxGuestIDC.pfnServiceEP((uintptr_t)pHandle->s.pvSession, uReq, pReqHdr, cbReq); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-solaris.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-solaris.cpp new file mode 100644 index 00000000..760f060c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-solaris.cpp @@ -0,0 +1,97 @@ +/* $Id: VBoxGuestR0LibIdc-solaris.cpp $ */ +/** @file + * VBoxGuestLib - Ring-0 Support Library for VBoxGuest, IDC, Solaris specific. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/conf.h> +#include <sys/sunldi.h> +#include <sys/file.h> +#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */ +#include "VBoxGuestR0LibInternal.h" +#include <VBox/err.h> + + +int VBOXCALL vbglR0IdcNativeOpen(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCCONNECT pReq) +{ + ldi_handle_t hDev = NULL; + ldi_ident_t hIdent = ldi_ident_from_anon(); + int rc = ldi_open_by_name((char *)VBOXGUEST_DEVICE_NAME, FREAD, kcred, &hDev, hIdent); + ldi_ident_release(hIdent); + if (rc == 0) + { + pHandle->s.hDev = hDev; + rc = VbglR0IdcCallRaw(pHandle, VBGL_IOCTL_IDC_CONNECT, &pReq->Hdr, sizeof(*pReq)); + if (RT_SUCCESS(rc) && RT_SUCCESS(pReq->Hdr.rc)) + return VINF_SUCCESS; + ldi_close(hDev, FREAD, kcred); + } + else + rc = VERR_OPEN_FAILED; + pHandle->s.hDev = NULL; + return rc; +} + + +int VBOXCALL vbglR0IdcNativeClose(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCDISCONNECT pReq) +{ + int rc = VbglR0IdcCallRaw(pHandle, VBGL_IOCTL_IDC_DISCONNECT, &pReq->Hdr, sizeof(*pReq)); + if (RT_SUCCESS(rc) && RT_SUCCESS(pReq->Hdr.rc)) + { + ldi_close(pHandle->s.hDev, FREAD, kcred); + pHandle->s.hDev = NULL; + } + return rc; +} + + +/** + * Makes an IDC call, returning only the I/O control status code. + * + * @returns VBox status code (the I/O control failure status). + * @param pHandle The IDC handle. + * @param uReq The request number. + * @param pReqHdr The request header. + * @param cbReq The request size. + */ +DECLR0VBGL(int) VbglR0IdcCallRaw(PVBGLIDCHANDLE pHandle, uintptr_t uReq, PVBGLREQHDR pReqHdr, uint32_t cbReq) +{ +#if 0 + return VBoxGuestIDC(pHandle->s.pvSession, uReq, pReqHdr, cbReq); +#else + int iIgn; + int rc = ldi_ioctl(pHandle->s.hDev, uReq, (intptr_t)pReqHdr, FKIOCTL | FNATIVE, kcred, &iIgn); + if (rc == 0) + return VINF_SUCCESS; + return RTErrConvertFromErrno(rc); +#endif +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-unix.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-unix.cpp new file mode 100644 index 00000000..8aefb725 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-unix.cpp @@ -0,0 +1,64 @@ +/* $Id: VBoxGuestR0LibIdc-unix.cpp $ */ +/** @file + * VBoxGuestLib - Ring-0 Support Library for VBoxGuest, IDC, UNIX-like OSes. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR0LibInternal.h" + + +int VBOXCALL vbglR0IdcNativeOpen(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCCONNECT pReq) +{ + RT_NOREF(pHandle); + return VBoxGuestIDC(NULL /*pvSession*/, VBGL_IOCTL_IDC_CONNECT, &pReq->Hdr, sizeof(*pReq)); +} + + +int VBOXCALL vbglR0IdcNativeClose(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCDISCONNECT pReq) +{ + return VBoxGuestIDC(pHandle->s.pvSession, VBGL_IOCTL_IDC_DISCONNECT, &pReq->Hdr, sizeof(*pReq)); +} + + +/** + * Makes an IDC call, returning only the I/O control status code. + * + * @returns VBox status code (the I/O control failure status). + * @param pHandle The IDC handle. + * @param uReq The request number. + * @param pReqHdr The request header. + * @param cbReq The request size. + */ +DECLR0VBGL(int) VbglR0IdcCallRaw(PVBGLIDCHANDLE pHandle, uintptr_t uReq, PVBGLREQHDR pReqHdr, uint32_t cbReq) +{ + return VBoxGuestIDC(pHandle->s.pvSession, uReq, pReqHdr, cbReq); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-win.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-win.cpp new file mode 100644 index 00000000..395667e0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-win.cpp @@ -0,0 +1,208 @@ +/* $Id: VBoxGuestR0LibIdc-win.cpp $ */ +/** @file + * VBoxGuestLib - Ring-0 Support Library for VBoxGuest, IDC, Windows specific. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt.h> +#include "VBoxGuestR0LibInternal.h" +#include <VBox/VBoxGuest.h> +#include <iprt/errcore.h> +#include <VBox/log.h> + + +/** + * Internal I/O Control call worker. + * + * @returns VBox status code. + * @param pDeviceObject The device object to call. + * @param pFileObject The file object for the connection. + * @param uReq The request. + * @param pReq The request packet. + */ +static int vbglR0IdcNtCallInternal(PDEVICE_OBJECT pDeviceObject, PFILE_OBJECT pFileObject, uint32_t uReq, PVBGLREQHDR pReq) +{ + int rc; + NTSTATUS rcNt; + + /* + * Build the request. + * + * We want to avoid double buffering of the request, therefore we don't + * specify any request pointers or sizes when asking the kernel to build + * the IRP for us, but instead do that part our selves. + * + * See https://www.osr.com/blog/2018/02/14/beware-iobuilddeviceiocontrolrequest/ + * for how fun this is when we're not at IRQL PASSIVE (HACK ALERT futher down). + * Ran into this little issue when LoadLibraryEx on a .NET DLL using the + * LOAD_LIBRARY_AS_DATAFILE and LOAD_LIBRARY_AS_IMAGE_RESOURCE flags. + */ + KEVENT Event; + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + IO_STATUS_BLOCK IoStatusBlock = RTNT_IO_STATUS_BLOCK_INITIALIZER; +#if 0 + PIRP pIrp = IoBuildDeviceIoControlRequest(uReq, /* IoControlCode */ + pDeviceObject, + pReq, /* InputBuffer */ + pReq->cbIn, /* InputBufferLength */ + pReq, /* OutputBuffer */ + pReq->cbOut, /* OutputBufferLength */ + TRUE, /* InternalDeviceIoControl (=> IRP_MJ_INTERNAL_DEVICE_CONTROL) */ + &Event, /* Event */ + &IoStatusBlock); /* IoStatusBlock */ +#else + PIRP pIrp = IoBuildDeviceIoControlRequest(uReq, /* IoControlCode */ + pDeviceObject, + NULL, /* InputBuffer */ + 0, /* InputBufferLength */ + NULL, /* OutputBuffer */ + 0, /* OutputBufferLength */ + TRUE, /* InternalDeviceIoControl (=> IRP_MJ_INTERNAL_DEVICE_CONTROL) */ + &Event, /* Event */ + &IoStatusBlock); /* IoStatusBlock */ +#endif + if (pIrp) + { +#if 0 + IoGetNextIrpStackLocation(pIrp)->FileObject = pFileObject; +#else + //w2k3sp1+: if (!KeAreAllApcsDisabled()) + //w2k3sp1+: pIrp->Flags |= IRP_SYNCHRONOUS_API; + //w2k3sp1+: else + { + /* HACK ALERT! Causes IoCompleteRequest to update UserIosb and free the IRP w/o any APC happening. */ + pIrp->Flags |= IRP_SYNCHRONOUS_API | IRP_PAGING_IO | IRP_SYNCHRONOUS_PAGING_IO; + KIRQL bIrqlSaved; + KeRaiseIrql(APC_LEVEL, &bIrqlSaved); + RemoveEntryList(&pIrp->ThreadListEntry); + InitializeListHead(&pIrp->ThreadListEntry); + KeLowerIrql(bIrqlSaved); + } + pIrp->UserBuffer = pReq; + pIrp->AssociatedIrp.SystemBuffer = pReq; + PIO_STACK_LOCATION pStack = IoGetNextIrpStackLocation(pIrp); + pStack->FileObject = pFileObject; + pStack->Parameters.DeviceIoControl.OutputBufferLength = pReq->cbOut; + pStack->Parameters.DeviceIoControl.InputBufferLength = pReq->cbIn; +#endif + + /* + * Call the driver, wait for an async request to complete (should never happen). + */ + rcNt = IoCallDriver(pDeviceObject, pIrp); + if (rcNt == STATUS_PENDING) + rcNt = KeWaitForSingleObject(&Event, /* Object */ + Executive, /* WaitReason */ + KernelMode, /* WaitMode */ + FALSE, /* Alertable */ + NULL); /* TimeOut */ + if (NT_SUCCESS(rcNt)) + rcNt = IoStatusBlock.Status; + if (NT_SUCCESS(rcNt)) + rc = pReq->rc; + else + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +int VBOXCALL vbglR0IdcNativeOpen(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCCONNECT pReq) +{ + PDEVICE_OBJECT pDeviceObject = NULL; + PFILE_OBJECT pFileObject = NULL; + UNICODE_STRING wszDeviceName; + NTSTATUS rcNt; + int rc; + + /* + * Get the device object pointer. + */ + RtlInitUnicodeString(&wszDeviceName, VBOXGUEST_DEVICE_NAME_NT); + rcNt = IoGetDeviceObjectPointer(&wszDeviceName, FILE_ALL_ACCESS, &pFileObject, &pDeviceObject); + if (NT_SUCCESS(rcNt)) + { + /* + * Make the connection call. + */ + rc = vbglR0IdcNtCallInternal(pDeviceObject, pFileObject, VBGL_IOCTL_IDC_CONNECT, &pReq->Hdr); + if (RT_SUCCESS(rc) && RT_SUCCESS(pReq->Hdr.rc)) + { + pHandle->s.pDeviceObject = pDeviceObject; + pHandle->s.pFileObject = pFileObject; + return rc; + } + + /* only the file object. */ + ObDereferenceObject(pFileObject); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + pHandle->s.pDeviceObject = NULL; + pHandle->s.pFileObject = NULL; + return rc; +} + + +int VBOXCALL vbglR0IdcNativeClose(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCDISCONNECT pReq) +{ + PFILE_OBJECT pFileObject = pHandle->s.pFileObject; + int rc = vbglR0IdcNtCallInternal(pHandle->s.pDeviceObject, pFileObject, VBGL_IOCTL_IDC_DISCONNECT, &pReq->Hdr); + if (RT_SUCCESS(rc) && RT_SUCCESS(pReq->Hdr.rc)) + { + pHandle->s.pDeviceObject = NULL; + pHandle->s.pFileObject = NULL; + ObDereferenceObject(pFileObject); + } + + return rc; +} + + +/** + * Makes an IDC call, returning only the I/O control status code. + * + * @returns VBox status code (the I/O control failure status). + * @param pHandle The IDC handle. + * @param uReq The request number. + * @param pReqHdr The request header. + * @param cbReq The request size. + */ +DECLR0VBGL(int) VbglR0IdcCallRaw(PVBGLIDCHANDLE pHandle, uintptr_t uReq, PVBGLREQHDR pReqHdr, uint32_t cbReq) +{ + NOREF(cbReq); + return vbglR0IdcNtCallInternal(pHandle->s.pDeviceObject, pHandle->s.pFileObject, (uint32_t)uReq, pReqHdr); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc.cpp new file mode 100644 index 00000000..c0b0ac53 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc.cpp @@ -0,0 +1,205 @@ +/* $Id: VBoxGuestR0LibIdc.cpp $ */ +/** @file + * VBoxGuestLib - Ring-0 Support Library for VBoxGuest, IDC. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR0LibInternal.h" +#include <iprt/errcore.h> +#include <VBox/VBoxGuest.h> +/*#include <iprt/asm.h>*/ + +#ifdef VBGL_VBOXGUEST +# error "This file shouldn't be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest. It's client code." +#endif + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/*static PVBGLIDCHANDLE volatile g_pMainHandle = NULL;*/ + + +/** + * Opens the IDC interface of the support driver. + * + * This will perform basic version negotiations and fail if the + * minimum requirements aren't met. + * + * @returns VBox status code. + * @param pHandle The handle structure (output). + * @param uReqVersion The requested version. Pass 0 for default. + * @param uMinVersion The minimum required version. Pass 0 for default. + * @param puSessionVersion Where to store the session version. Optional. + * @param puDriverVersion Where to store the session version. Optional. + * @param puDriverRevision Where to store the SVN revision of the driver. Optional. + */ +DECLR0VBGL(int) VbglR0IdcOpen(PVBGLIDCHANDLE pHandle, uint32_t uReqVersion, uint32_t uMinVersion, + uint32_t *puSessionVersion, uint32_t *puDriverVersion, uint32_t *puDriverRevision) +{ + unsigned uDefaultMinVersion; + VBGLIOCIDCCONNECT Req; + int rc; + + /* + * Validate and set failure return values. + */ + AssertPtrReturn(pHandle, VERR_INVALID_POINTER); + pHandle->s.pvSession = NULL; + + AssertPtrNullReturn(puSessionVersion, VERR_INVALID_POINTER); + if (puSessionVersion) + *puSessionVersion = 0; + + AssertPtrNullReturn(puDriverVersion, VERR_INVALID_POINTER); + if (puDriverVersion) + *puDriverVersion = 0; + + AssertPtrNullReturn(puDriverRevision, VERR_INVALID_POINTER); + if (puDriverRevision) + *puDriverRevision = 0; + + AssertReturn(!uMinVersion || (uMinVersion & UINT32_C(0xffff0000)) == (VBGL_IOC_VERSION & UINT32_C(0xffff0000)), VERR_INVALID_PARAMETER); + AssertReturn(!uReqVersion || (uReqVersion & UINT32_C(0xffff0000)) == (VBGL_IOC_VERSION & UINT32_C(0xffff0000)), VERR_INVALID_PARAMETER); + + /* + * Handle default version input and enforce minimum requirements made + * by this library. + * + * The clients will pass defaults (0), and only in the case that some + * special API feature was just added will they set an actual version. + * So, this is the place where can easily enforce a minimum IDC version + * on bugs and similar. It corresponds a bit to what SUPR3Init is + * responsible for. + */ + uDefaultMinVersion = VBGL_IOC_VERSION & UINT32_C(0xffff0000); + if (!uMinVersion || uMinVersion < uDefaultMinVersion) + uMinVersion = uDefaultMinVersion; + if (!uReqVersion || uReqVersion < uDefaultMinVersion) + uReqVersion = uDefaultMinVersion; + + /* + * Setup the connect request packet and call the OS specific function. + */ + VBGLREQHDR_INIT(&Req.Hdr, IDC_CONNECT); + Req.u.In.u32MagicCookie = VBGL_IOCTL_IDC_CONNECT_MAGIC_COOKIE; + Req.u.In.uMinVersion = uMinVersion; + Req.u.In.uReqVersion = uReqVersion; + Req.u.In.uReserved = 0; + rc = vbglR0IdcNativeOpen(pHandle, &Req); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + if (RT_SUCCESS(rc)) + { + pHandle->s.pvSession = Req.u.Out.pvSession; + if (puSessionVersion) + *puSessionVersion = Req.u.Out.uSessionVersion; + if (puDriverVersion) + *puDriverVersion = Req.u.Out.uDriverVersion; + if (puDriverRevision) + *puDriverRevision = Req.u.Out.uDriverRevision; + + /* + * We don't really trust anyone, make sure the returned + * session and version values actually makes sense. + */ + if ( RT_VALID_PTR(Req.u.Out.pvSession) + && Req.u.Out.uSessionVersion >= uMinVersion + && (Req.u.Out.uSessionVersion & UINT32_C(0xffff0000)) == (VBGL_IOC_VERSION & UINT32_C(0xffff0000))) + { + /*ASMAtomicCmpXchgPtr(&g_pMainHandle, pHandle, NULL);*/ + return rc; + } + + AssertMsgFailed(("pSession=%p uSessionVersion=0x%x (r%u)\n", Req.u.Out.pvSession, Req.u.Out.uSessionVersion, Req.u.Out.uDriverRevision)); + rc = VERR_VERSION_MISMATCH; + VbglR0IdcClose(pHandle); + } + + return rc; +} + + +/** + * Closes a IDC connection established by VbglR0IdcOpen. + * + * @returns VBox status code. + * @param pHandle The IDC handle. + */ +DECLR0VBGL(int) VbglR0IdcClose(PVBGLIDCHANDLE pHandle) +{ + VBGLIOCIDCDISCONNECT Req; + int rc; + + /* + * Catch closed handles and check that the session is valid. + */ + AssertPtrReturn(pHandle, VERR_INVALID_POINTER); + if (!pHandle->s.pvSession) + return VERR_INVALID_HANDLE; + AssertPtrReturn(pHandle->s.pvSession, VERR_INVALID_HANDLE); + + /* + * Create the request and hand it to the OS specific code. + */ + VBGLREQHDR_INIT(&Req.Hdr, IDC_DISCONNECT); + Req.u.In.pvSession = pHandle->s.pvSession; + rc = vbglR0IdcNativeClose(pHandle, &Req); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + if (RT_SUCCESS(rc)) + { + pHandle->s.pvSession = NULL; + /*ASMAtomicCmpXchgPtr(&g_pMainHandle, NULL, pHandle);*/ + } + return rc; +} + + +/** + * Makes an IDC call, returning the request status. + * + * @returns VBox status code. Request status if the I/O control succeeds, + * otherwise the I/O control failure status. + * @param pHandle The IDC handle. + * @param uReq The request number. + * @param pReqHdr The request header. + * @param cbReq The request size. + */ +DECLR0VBGL(int) VbglR0IdcCall(PVBGLIDCHANDLE pHandle, uintptr_t uReq, PVBGLREQHDR pReqHdr, uint32_t cbReq) +{ + int rc = VbglR0IdcCallRaw(pHandle, uReq, pReqHdr, cbReq); + if (RT_SUCCESS(rc)) + rc = pReqHdr->rc; + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp new file mode 100644 index 00000000..0ed6c35f --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp @@ -0,0 +1,333 @@ +/* $Id: VBoxGuestR0LibInit.cpp $ */ +/** @file + * VBoxGuestLibR0 - Library initialization. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR0LibInternal.h" + +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/semaphore.h> +#include <VBox/err.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The global VBGL instance data. */ +VBGLDATA g_vbgldata; + + +/** + * Used by vbglR0QueryDriverInfo and VbglInit to try get the host feature mask + * and version information (g_vbgldata::hostVersion). + * + * This was first implemented by the host in 3.1 and we quietly ignore failures + * for that reason. + */ +static void vbglR0QueryHostVersion(void) +{ + VMMDevReqHostVersion *pReq; + int rc = VbglR0GRAlloc((VMMDevRequestHeader **) &pReq, sizeof (*pReq), VMMDevReq_GetHostVersion); + if (RT_SUCCESS(rc)) + { + rc = VbglR0GRPerform(&pReq->header); + if (RT_SUCCESS(rc)) + { + g_vbgldata.hostVersion = *pReq; + Log(("vbglR0QueryHostVersion: %u.%u.%ur%u %#x\n", + pReq->major, pReq->minor, pReq->build, pReq->revision, pReq->features)); + } + + VbglR0GRFree(&pReq->header); + } +} + + +#ifndef VBGL_VBOXGUEST +/** + * The guest library uses lazy initialization for VMMDev port and memory, + * because these values are provided by the VBoxGuest driver and it might + * be loaded later than other drivers. + * + * The VbglEnter checks the current library status, tries to retrieve these + * values and fails if they are unavailable. + */ +static int vbglR0QueryDriverInfo(void) +{ +# ifdef VBGLDATA_USE_FAST_MUTEX + int rc = RTSemFastMutexRequest(g_vbgldata.hMtxIdcSetup); +# else + int rc = RTSemMutexRequest(g_vbgldata.hMtxIdcSetup, RT_INDEFINITE_WAIT); +# endif + if (RT_SUCCESS(rc)) + { + if (g_vbgldata.status == VbglStatusReady) + { /* likely */ } + else + { + rc = VbglR0IdcOpen(&g_vbgldata.IdcHandle, + VBGL_IOC_VERSION /*uReqVersion*/, + VBGL_IOC_VERSION & UINT32_C(0xffff0000) /*uMinVersion*/, + NULL /*puSessionVersion*/, NULL /*puDriverVersion*/, NULL /*puDriverRevision*/); + if (RT_SUCCESS(rc)) + { + /* + * Try query the port info. + */ + VBGLIOCGETVMMDEVIOINFO PortInfo; + RT_ZERO(PortInfo); + VBGLREQHDR_INIT(&PortInfo.Hdr, GET_VMMDEV_IO_INFO); + rc = VbglR0IdcCall(&g_vbgldata.IdcHandle, VBGL_IOCTL_GET_VMMDEV_IO_INFO, &PortInfo.Hdr, sizeof(PortInfo)); + if (RT_SUCCESS(rc)) + { + dprintf(("Port I/O = 0x%04x, MMIO = %p\n", PortInfo.u.Out.IoPort, PortInfo.u.Out.pvVmmDevMapping)); + + g_vbgldata.portVMMDev = PortInfo.u.Out.IoPort; + g_vbgldata.pVMMDevMemory = (VMMDevMemory *)PortInfo.u.Out.pvVmmDevMapping; + g_vbgldata.status = VbglStatusReady; + + vbglR0QueryHostVersion(); + } + } + + dprintf(("vbglQueryDriverInfo rc = %Rrc\n", rc)); + } + +# ifdef VBGLDATA_USE_FAST_MUTEX + RTSemFastMutexRelease(g_vbgldata.hMtxIdcSetup); +# else + RTSemMutexRelease(g_vbgldata.hMtxIdcSetup); +# endif + } + return rc; +} +#endif /* !VBGL_VBOXGUEST */ + +/** + * Checks if VBGL has been initialized. + * + * The client library, this will lazily complete the initialization. + * + * @return VINF_SUCCESS or VERR_VBGL_NOT_INITIALIZED. + */ +int vbglR0Enter(void) +{ + if (g_vbgldata.status == VbglStatusReady) + return VINF_SUCCESS; + +#ifndef VBGL_VBOXGUEST + if (g_vbgldata.status == VbglStatusInitializing) + { + vbglR0QueryDriverInfo(); + if (g_vbgldata.status == VbglStatusReady) + return VINF_SUCCESS; + } +#endif + return VERR_VBGL_NOT_INITIALIZED; +} + + +static int vbglR0InitCommon(void) +{ + int rc; + + RT_ZERO(g_vbgldata); + g_vbgldata.status = VbglStatusInitializing; + + rc = VbglR0PhysHeapInit(); + if (RT_SUCCESS(rc)) + { + dprintf(("vbglR0InitCommon: returns rc = %d\n", rc)); + return rc; + } + + LogRel(("vbglR0InitCommon: VbglR0PhysHeapInit failed: rc=%Rrc\n", rc)); + g_vbgldata.status = VbglStatusNotInitialized; + return rc; +} + + +static void vbglR0TerminateCommon(void) +{ + VbglR0PhysHeapTerminate(); + g_vbgldata.status = VbglStatusNotInitialized; +} + +#ifdef VBGL_VBOXGUEST + +DECLR0VBGL(int) VbglR0InitPrimary(RTIOPORT portVMMDev, VMMDevMemory *pVMMDevMemory, uint32_t *pfFeatures) +{ + int rc; + +# ifdef RT_OS_WINDOWS /** @todo r=bird: this doesn't make sense. Is there something special going on on windows? */ + dprintf(("vbglInit: starts g_vbgldata.status %d\n", g_vbgldata.status)); + + if ( g_vbgldata.status == VbglStatusInitializing + || g_vbgldata.status == VbglStatusReady) + { + /* Initialization is already in process. */ + return VINF_SUCCESS; + } +# else + dprintf(("vbglInit: starts\n")); +# endif + + rc = vbglR0InitCommon(); + if (RT_SUCCESS(rc)) + { + g_vbgldata.portVMMDev = portVMMDev; + g_vbgldata.pVMMDevMemory = pVMMDevMemory; + g_vbgldata.status = VbglStatusReady; + + vbglR0QueryHostVersion(); + *pfFeatures = g_vbgldata.hostVersion.features; + return VINF_SUCCESS; + } + + g_vbgldata.status = VbglStatusNotInitialized; + return rc; +} + +DECLR0VBGL(void) VbglR0TerminatePrimary(void) +{ + vbglR0TerminateCommon(); +} + + +#else /* !VBGL_VBOXGUEST */ + +DECLR0VBGL(int) VbglR0InitClient(void) +{ + int rc; + + /** @todo r=bird: explain why we need to be doing this, please... */ + if ( g_vbgldata.status == VbglStatusInitializing + || g_vbgldata.status == VbglStatusReady) + { + /* Initialization is already in process. */ + return VINF_SUCCESS; + } + + rc = vbglR0InitCommon(); + if (RT_SUCCESS(rc)) + { +# ifdef VBGLDATA_USE_FAST_MUTEX + rc = RTSemFastMutexCreate(&g_vbgldata.hMtxIdcSetup); +# else + rc = RTSemMutexCreate(&g_vbgldata.hMtxIdcSetup); +# endif + if (RT_SUCCESS(rc)) + { + /* Try to obtain VMMDev port via IOCTL to VBoxGuest main driver. */ + vbglR0QueryDriverInfo(); + +# ifdef VBOX_WITH_HGCM + rc = VbglR0HGCMInit(); +# endif + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + +# ifdef VBGLDATA_USE_FAST_MUTEX + RTSemFastMutexDestroy(g_vbgldata.hMtxIdcSetup); + g_vbgldata.hMtxIdcSetup = NIL_RTSEMFASTMUTEX; +# else + RTSemMutexDestroy(g_vbgldata.hMtxIdcSetup); + g_vbgldata.hMtxIdcSetup = NIL_RTSEMMUTEX; +# endif + } + vbglR0TerminateCommon(); + } + + return rc; +} + +DECLR0VBGL(void) VbglR0TerminateClient(void) +{ +# ifdef VBOX_WITH_HGCM + VbglR0HGCMTerminate(); +# endif + + /* driver open could fail, which does not prevent VbglInit from succeeding, + * close the driver only if it is opened */ + VbglR0IdcClose(&g_vbgldata.IdcHandle); +# ifdef VBGLDATA_USE_FAST_MUTEX + RTSemFastMutexDestroy(g_vbgldata.hMtxIdcSetup); + g_vbgldata.hMtxIdcSetup = NIL_RTSEMFASTMUTEX; +# else + RTSemMutexDestroy(g_vbgldata.hMtxIdcSetup); + g_vbgldata.hMtxIdcSetup = NIL_RTSEMMUTEX; +# endif + + /* note: do vbglR0TerminateCommon as a last step since it zeroez up the g_vbgldata + * conceptually, doing vbglR0TerminateCommon last is correct + * since this is the reverse order to how init is done */ + vbglR0TerminateCommon(); +} + + +int VBOXCALL vbglR0QueryIdcHandle(PVBGLIDCHANDLE *ppIdcHandle) +{ + if (g_vbgldata.status == VbglStatusReady) + { /* likely */ } + else + { + vbglR0QueryDriverInfo(); + if (g_vbgldata.status != VbglStatusReady) + { + *ppIdcHandle = NULL; + return VERR_TRY_AGAIN; + } + } + + *ppIdcHandle = &g_vbgldata.IdcHandle; + return VINF_SUCCESS; +} + + +DECLR0VBGL(int) VbglR0QueryHostFeatures(uint32_t *pfHostFeatures) +{ + if (g_vbgldata.status == VbglStatusReady) + *pfHostFeatures = g_vbgldata.hostVersion.features; + else + { + int rc = vbglR0QueryDriverInfo(); + if (g_vbgldata.status != VbglStatusReady) + return rc; + *pfHostFeatures = g_vbgldata.hostVersion.features; + } + + return VINF_SUCCESS; +} + +#endif /* !VBGL_VBOXGUEST */ + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h new file mode 100644 index 00000000..81657a8e --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h @@ -0,0 +1,212 @@ +/* $Id: VBoxGuestR0LibInternal.h $ */ +/** @file + * VBoxGuestLibR0 - Internal header. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR0LibInternal_h +#define GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR0LibInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* + * Define the private IDC handle structure before we include the VBoxGuestLib.h header. + */ +#include <iprt/types.h> +#include <iprt/assert.h> +RT_C_DECLS_BEGIN + +# ifndef VBGL_VBOXGUEST +/** + * The hidden part of VBGLIDCHANDLE. + */ +struct VBGLIDCHANDLEPRIVATE +{ + /** Pointer to the session handle. */ + void *pvSession; +# if defined(RT_OS_WINDOWS) && (defined(IPRT_INCLUDED_nt_ntddk_h) || defined(IPRT_INCLUDED_nt_nt_h)) + /** Pointer to the NT device object. */ + PDEVICE_OBJECT pDeviceObject; + /** Pointer to the NT file object. */ + PFILE_OBJECT pFileObject; +# elif defined(RT_OS_SOLARIS) && defined(_SYS_SUNLDI_H) + /** LDI device handle to keep the device attached. */ + ldi_handle_t hDev; +# endif +}; +/** Indicate that the VBGLIDCHANDLEPRIVATE structure is present. */ +# define VBGLIDCHANDLEPRIVATE_DECLARED 1 +#endif + +#include <VBox/VMMDev.h> +#include <VBox/VBoxGuest.h> +#include <VBox/VBoxGuestLib.h> + +#ifdef VBGLIDCHANDLEPRIVATE_DECLARED +AssertCompile(RT_SIZEOFMEMB(VBGLIDCHANDLE, apvPadding) >= sizeof(struct VBGLIDCHANDLEPRIVATE)); +#endif + + +/* + * Native IDC functions. + */ +int VBOXCALL vbglR0IdcNativeOpen(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCCONNECT pReq); +int VBOXCALL vbglR0IdcNativeClose(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCDISCONNECT pReq); + + +/* + * Deprecated logging macro + */ +#include <VBox/log.h> +#ifdef RT_OS_WINDOWS /** @todo dprintf() -> Log() */ +# if (defined(DEBUG) && !defined(NO_LOGGING)) || defined(LOG_ENABLED) +# define dprintf(a) RTLogBackdoorPrintf a +# else +# define dprintf(a) do {} while (0) +# endif +#else +# define dprintf(a) Log(a) +#endif + +/* + * Lazy bird: OS/2 doesn't currently implement the RTSemMutex API in ring-0, so + * use a fast mutex instead. Unlike Windows, the OS/2 implementation + * doesn't have any nasty side effects on IRQL-like context properties, so the + * fast mutexes on OS/2 are identical to normal mutexes except for the missing + * timeout aspec. Fortunately we don't need timeouts here. + */ +#ifdef RT_OS_OS2 +# define VBGLDATA_USE_FAST_MUTEX +#endif + +struct VBGLPHYSHEAPBLOCK; +typedef struct VBGLPHYSHEAPBLOCK VBGLPHYSHEAPBLOCK; +struct VBGLPHYSHEAPFREEBLOCK; +typedef struct VBGLPHYSHEAPFREEBLOCK VBGLPHYSHEAPFREEBLOCK; +struct VBGLPHYSHEAPCHUNK; +typedef struct VBGLPHYSHEAPCHUNK VBGLPHYSHEAPCHUNK; + +enum VbglLibStatus +{ + VbglStatusNotInitialized = 0, + VbglStatusInitializing, + VbglStatusReady +}; + +/** + * Global VBGL ring-0 data. + * Lives in VBoxGuestR0LibInit.cpp. + */ +typedef struct VBGLDATA +{ + enum VbglLibStatus status; + + RTIOPORT portVMMDev; + + VMMDevMemory *pVMMDevMemory; + + /** Physical memory heap data. + * @{ + */ + RTSEMFASTMUTEX hMtxHeap; + /** Head of the block list (both free and allocated blocks). + * This runs parallel to the chunk list and is sorted by address within each + * chunk. This allows merging with blocks both after and before when + * freeing. */ + VBGLPHYSHEAPBLOCK *pBlockHead; + /** Head of the free list. + * This is not sorted and more on the most recently freed approach. */ + VBGLPHYSHEAPFREEBLOCK *pFreeHead; + /** Number of block of any kind. */ + int32_t cBlocks; + /** Number of free blocks. */ + int32_t cFreeBlocks; + /** Head of the chunk list. */ + VBGLPHYSHEAPCHUNK *pChunkHead; + /** @} */ + + /** + * The host version data. + */ + VMMDevReqHostVersion hostVersion; + + +#ifndef VBGL_VBOXGUEST + /** The IDC handle. This is used for talking to the main driver. */ + VBGLIDCHANDLE IdcHandle; + /** Mutex used to serialize IDC setup. */ +# ifdef VBGLDATA_USE_FAST_MUTEX + RTSEMFASTMUTEX hMtxIdcSetup; +# else + RTSEMMUTEX hMtxIdcSetup; +# endif +#endif +} VBGLDATA; + + +extern VBGLDATA g_vbgldata; + +/** + * Internal macro for checking whether we can pass physical page lists to the + * host. + * + * ASSUMES that vbglR0Enter has been called already. + * + * @param a_fLocked For the windows shared folders workarounds. + * + * @remarks Disabled the PageList feature for locked memory on Windows, + * because a new MDL is created by VBGL to get the page addresses + * and the pages from the MDL are marked as dirty when they should not. + */ +#if defined(RT_OS_WINDOWS) +# define VBGLR0_CAN_USE_PHYS_PAGE_LIST(a_fLocked) \ + ( !(a_fLocked) && (g_vbgldata.hostVersion.features & VMMDEV_HVF_HGCM_PHYS_PAGE_LIST) ) +#else +# define VBGLR0_CAN_USE_PHYS_PAGE_LIST(a_fLocked) \ + ( !!(g_vbgldata.hostVersion.features & VMMDEV_HVF_HGCM_PHYS_PAGE_LIST) ) +#endif + +int vbglR0Enter (void); + +#ifdef VBOX_WITH_HGCM +struct VBGLHGCMHANDLEDATA *vbglR0HGCMHandleAlloc(void); +void vbglR0HGCMHandleFree(struct VBGLHGCMHANDLEDATA *pHandle); +#endif /* VBOX_WITH_HGCM */ + +#ifndef VBGL_VBOXGUEST +/** + * Get the IDC handle to the main VBoxGuest driver. + * @returns VERR_TRY_AGAIN if the main driver has not yet been loaded. + */ +int VBOXCALL vbglR0QueryIdcHandle(PVBGLIDCHANDLE *ppIdcHandle); +#endif + +RT_C_DECLS_END + +#endif /* !GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR0LibInternal_h */ + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibMouse.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibMouse.cpp new file mode 100644 index 00000000..a0c9e1c5 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibMouse.cpp @@ -0,0 +1,130 @@ +/* $Id: VBoxGuestR0LibMouse.cpp $ */ +/** @file + * VBoxGuestLibR0 - Mouse Integration. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR0LibInternal.h" +#ifdef VBGL_VBOXGUEST +# error "This file shouldn't be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest. It's client code." +#endif + + +/** + * Sets the function which is called back on each mouse pointer event. Only + * one callback can be active at once, so if you need several for any reason + * you must multiplex yourself. Call backs can be disabled by passing NULL + * as the function pointer. + * + * @remarks Ring-0. + * @returns iprt status code. + * @returns VERR_TRY_AGAIN if the main guest driver hasn't finished + * initialising. + * + * @param pfnNotify the function to call back. NULL to disable call backs. + * @param pvUser user supplied data/cookie to be passed to the function. + */ +DECLR0VBGL(int) VbglR0SetMouseNotifyCallback(PFNVBOXGUESTMOUSENOTIFY pfnNotify, void *pvUser) +{ + PVBGLIDCHANDLE pIdcHandle; + int rc = vbglR0QueryIdcHandle(&pIdcHandle); + if (RT_SUCCESS(rc)) + { + VBGLIOCSETMOUSENOTIFYCALLBACK NotifyCallback; + VBGLREQHDR_INIT(&NotifyCallback.Hdr, SET_MOUSE_NOTIFY_CALLBACK); + NotifyCallback.u.In.pfnNotify = pfnNotify; + NotifyCallback.u.In.pvUser = pvUser; + rc = VbglR0IdcCall(pIdcHandle, VBGL_IOCTL_SET_MOUSE_NOTIFY_CALLBACK, &NotifyCallback.Hdr, sizeof(NotifyCallback)); + } + return rc; +} + + +/** + * Retrieve mouse coordinates and features from the host. + * + * @remarks Ring-0. + * @returns VBox status code. + * + * @param pfFeatures Where to store the mouse features. + * @param px Where to store the X co-ordinate. + * @param py Where to store the Y co-ordinate. + */ +DECLR0VBGL(int) VbglR0GetMouseStatus(uint32_t *pfFeatures, uint32_t *px, uint32_t *py) +{ + PVBGLIDCHANDLE pIdcHandle; + int rc = vbglR0QueryIdcHandle(&pIdcHandle); + if (RT_SUCCESS(rc)) + { + VMMDevReqMouseStatus Req; + VMMDEV_REQ_HDR_INIT(&Req.header, sizeof(Req), VMMDevReq_GetMouseStatus); + Req.mouseFeatures = 0; + Req.pointerXPos = 0; + Req.pointerYPos = 0; + rc = VbglR0IdcCall(pIdcHandle, VBGL_IOCTL_VMMDEV_REQUEST(sizeof(Req)), (PVBGLREQHDR)&Req.header, sizeof(Req)); + if (RT_SUCCESS(rc)) + { + if (pfFeatures) + *pfFeatures = Req.mouseFeatures; + if (px) + *px = Req.pointerXPos; + if (py) + *py = Req.pointerYPos; + } + } + return rc; +} + + +/** + * Send mouse features to the host. + * + * @remarks Ring-0. + * @returns VBox status code. + * + * @param fFeatures Supported mouse pointer features. The main guest driver + * will mediate different callers and show the host any + * feature enabled by any guest caller. + */ +DECLR0VBGL(int) VbglR0SetMouseStatus(uint32_t fFeatures) +{ + PVBGLIDCHANDLE pIdcHandle; + int rc = vbglR0QueryIdcHandle(&pIdcHandle); + if (RT_SUCCESS(rc)) + { + VBGLIOCSETMOUSESTATUS Req; + VBGLREQHDR_INIT(&Req.Hdr, SET_MOUSE_STATUS); + Req.u.In.fStatus = fFeatures; + rc = VbglR0IdcCall(pIdcHandle, VBGL_IOCTL_SET_MOUSE_STATUS, &Req.Hdr, sizeof(Req)); + } + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp new file mode 100644 index 00000000..b99f51ad --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp @@ -0,0 +1,1197 @@ +/* $Id: VBoxGuestR0LibPhysHeap.cpp $ */ +/** @file + * VBoxGuestLibR0 - Physical memory heap. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @page pg_vbglr0_phys_heap VBoxGuestLibR0 - Physical memory heap. + * + * Traditional heap implementation keeping all blocks in a ordered list and + * keeping free blocks on additional list via pointers in the user area. This + * is similar to @ref grp_rt_heap_simple "RTHeapSimple" and + * @ref grp_rt_heap_offset "RTHeapOffset" in IPRT, except that this code handles + * mutiple chunks and has a physical address associated with each chunk and + * block. The alignment is fixed (VBGL_PH_ALLOC_ALIGN). + * + * When allocating memory, a free block is found that satisfies the request, + * extending the heap with another chunk if needed. The block is split if it's + * too large, and the tail end is put on the free list. + * + * When freeing memory, the block being freed is put back on the free list and + * we use the block list to check whether it can be merged with adjacent blocks. + * + * @note The original code managed the blocks in two separate lists for free and + * allocated blocks, which had the disadvantage only allowing merging with + * the block after the block being freed. On the plus side, it had the + * potential for slightly better locality when examining the free list, + * since the next pointer and block size members were closer to one + * another. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR0LibInternal.h" + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/memobj.h> +#include <iprt/semaphore.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Enables heap dumping. */ +#if defined(DOXYGEN_RUNNING) || 0 +# define VBGL_PH_DUMPHEAP +#endif + +#ifdef VBGL_PH_DUMPHEAP +# define VBGL_PH_DPRINTF(a) RTAssertMsg2Weak a +#else +# define VBGL_PH_DPRINTF(a) do { } while (0) +#endif + +/** Heap chunk signature */ +#define VBGL_PH_CHUNKSIGNATURE UINT32_C(0xADDCCCCC) +/** Heap chunk allocation unit */ +#define VBGL_PH_CHUNKSIZE (0x10000) + +/** Heap block signature */ +#define VBGL_PH_BLOCKSIGNATURE UINT32_C(0xADDBBBBB) + +/** The allocation block alignment. + * + * This cannot be larger than VBGLPHYSHEAPBLOCK. + */ +#define VBGL_PH_ALLOC_ALIGN (sizeof(void *)) + +/** Max number of free nodes to search before just using the best fit. + * + * This is used to limit the free list walking during allocation and just get + * on with the job. A low number should reduce the cache trashing at the + * possible cost of heap fragmentation. + * + * Picked 16 after comparing the tstVbglR0PhysHeap-1 results w/ uRandSeed=42 for + * different max values. + */ +#define VBGL_PH_MAX_FREE_SEARCH 16 + +/** Threshold to stop the block search if a free block is at least this much too big. + * + * May cause more fragmation (depending on usage pattern), but should speed up + * allocation and hopefully reduce cache trashing. + * + * Since we merge adjacent free blocks when we can, free blocks should typically + * be a lot larger that what's requested. So, it is probably a good idea to + * just chop up a large block rather than keep searching for a perfect-ish + * match. + * + * Undefine this to disable this trick. + */ +#if defined(DOXYGEN_RUNNING) || 1 +# define VBGL_PH_STOP_SEARCH_AT_EXCESS _4K +#endif + +/** Threshold at which to split out a tail free block when allocating. + * + * The value gives the amount of user space, i.e. excluding the header. + * + * Using 32 bytes based on VMMDev.h request sizes. The smallest requests are 24 + * bytes, i.e. only the header, at least 4 of these. There are at least 10 with + * size 28 bytes and at least 11 with size 32 bytes. So, 32 bytes would fit + * some 25 requests out of about 60, which is reasonable. + */ +#define VBGL_PH_MIN_SPLIT_FREE_BLOCK 32 + + +/** The smallest amount of user data that can be allocated. + * + * This is to ensure that the block can be converted into a + * VBGLPHYSHEAPFREEBLOCK structure when freed. This must be smaller or equal + * to VBGL_PH_MIN_SPLIT_FREE_BLOCK. + */ +#define VBGL_PH_SMALLEST_ALLOC_SIZE 16 + +/** The maximum allocation request size. */ +#define VBGL_PH_LARGEST_ALLOC_SIZE RT_ALIGN_32( _128M \ + - sizeof(VBGLPHYSHEAPBLOCK) \ + - sizeof(VBGLPHYSHEAPCHUNK) \ + - VBGL_PH_ALLOC_ALIGN, \ + VBGL_PH_ALLOC_ALIGN) + +/** + * Whether to use the RTR0MemObjAllocCont API or RTMemContAlloc for + * allocating chunks. + * + * This can be enabled on hosts where RTMemContAlloc is more complicated than + * RTR0MemObjAllocCont. This can also be done if we wish to save code space, as + * the latter is typically always dragged into the link on guests where the + * linker cannot eliminiate functions within objects. Only drawback is that + * RTR0MemObjAllocCont requires another heap allocation for the handle. + */ +#if defined(DOXYGEN_RUNNING) || (!defined(IN_TESTCASE) && 0) +# define VBGL_PH_USE_MEMOBJ +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * A heap block (within a chunk). + * + * This is used to track a part of a heap chunk that's either free or + * allocated. The VBGLPHYSHEAPBLOCK::fAllocated member indicates which it is. + */ +struct VBGLPHYSHEAPBLOCK +{ + /** Magic value (VBGL_PH_BLOCKSIGNATURE). */ + uint32_t u32Signature; + + /** Size of user data in the block. Does not include this block header. */ + uint32_t cbUser : 31; + /** The top bit indicates whether it's allocated or free. */ + uint32_t fAllocated : 1; + + /** Pointer to the next block on the list. */ + VBGLPHYSHEAPBLOCK *pNext; + /** Pointer to the previous block on the list. */ + VBGLPHYSHEAPBLOCK *pPrev; + /** Pointer back to the chunk. */ + VBGLPHYSHEAPCHUNK *pChunk; +}; + +/** + * A free block. + */ +struct VBGLPHYSHEAPFREEBLOCK +{ + /** Core block data. */ + VBGLPHYSHEAPBLOCK Core; + /** Pointer to the next free list entry. */ + VBGLPHYSHEAPFREEBLOCK *pNextFree; + /** Pointer to the previous free list entry. */ + VBGLPHYSHEAPFREEBLOCK *pPrevFree; +}; +AssertCompile(VBGL_PH_SMALLEST_ALLOC_SIZE >= sizeof(VBGLPHYSHEAPFREEBLOCK) - sizeof(VBGLPHYSHEAPBLOCK)); +AssertCompile(VBGL_PH_MIN_SPLIT_FREE_BLOCK >= sizeof(VBGLPHYSHEAPFREEBLOCK) - sizeof(VBGLPHYSHEAPBLOCK)); +AssertCompile(VBGL_PH_MIN_SPLIT_FREE_BLOCK >= VBGL_PH_SMALLEST_ALLOC_SIZE); + +/** + * A chunk of memory used by the heap for sub-allocations. + * + * There is a list of these. + */ +struct VBGLPHYSHEAPCHUNK +{ + /** Magic value (VBGL_PH_CHUNKSIGNATURE). */ + uint32_t u32Signature; + + /** Size of the chunk. Includes the chunk header. */ + uint32_t cbChunk; + + /** Physical address of the chunk (contiguous). */ + uint32_t physAddr; + +#if !defined(VBGL_PH_USE_MEMOBJ) || ARCH_BITS != 32 + uint32_t uPadding1; +#endif + + /** Number of block of any kind. */ + int32_t cBlocks; + /** Number of free blocks. */ + int32_t cFreeBlocks; + + /** Pointer to the next chunk. */ + VBGLPHYSHEAPCHUNK *pNext; + /** Pointer to the previous chunk. */ + VBGLPHYSHEAPCHUNK *pPrev; + +#if defined(VBGL_PH_USE_MEMOBJ) + /** The allocation handle. */ + RTR0MEMOBJ hMemObj; +#endif + +#if ARCH_BITS == 64 + /** Pad the size up to 64 bytes. */ +# ifdef VBGL_PH_USE_MEMOBJ + uintptr_t auPadding2[2]; +# else + uintptr_t auPadding2[3]; +# endif +#endif +}; +#if ARCH_BITS == 64 +AssertCompileSize(VBGLPHYSHEAPCHUNK, 64); +#endif + + +/** + * Debug function that dumps the heap. + */ +#ifndef VBGL_PH_DUMPHEAP +# define dumpheap(pszWhere) do { } while (0) +#else +static void dumpheap(const char *pszWhere) +{ + VBGL_PH_DPRINTF(("VBGL_PH dump at '%s'\n", pszWhere)); + + VBGL_PH_DPRINTF(("Chunks:\n")); + for (VBGLPHYSHEAPCHUNK *pChunk = g_vbgldata.pChunkHead; pChunk; pChunk = pChunk->pNext) + VBGL_PH_DPRINTF(("%p: pNext = %p, pPrev = %p, sign = %08X, size = %8d, cBlocks = %8d, cFreeBlocks=%8d, phys = %08X\n", + pChunk, pChunk->pNext, pChunk->pPrev, pChunk->u32Signature, pChunk->cbChunk, + pChunk->cBlocks, pChunk->cFreeBlocks, pChunk->physAddr)); + + VBGL_PH_DPRINTF(("Allocated blocks:\n")); + for (VBGLPHYSHEAPBLOCK *pBlock = g_vbgldata.pBlockHead; pBlock; pBlock = pBlock->pNext) + VBGL_PH_DPRINTF(("%p: pNext = %p, pPrev = %p, size = %05x, sign = %08X, %s, pChunk = %p\n", + pBlock, pBlock->pNext, pBlock->pPrev, pBlock->cbUser, + pBlock->u32Signature, pBlock->fAllocated ? "allocated" : " free", pBlock->pChunk)); + + VBGL_PH_DPRINTF(("Free blocks:\n")); + for (VBGLPHYSHEAPFREEBLOCK *pBlock = g_vbgldata.pFreeHead; pBlock; pBlock = pBlock->pNextFree) + VBGL_PH_DPRINTF(("%p: pNextFree = %p, pPrevFree = %p, size = %05x, sign = %08X, pChunk = %p%s\n", + pBlock, pBlock->pNextFree, pBlock->pPrevFree, pBlock->Core.cbUser, + pBlock->Core.u32Signature, pBlock->Core.pChunk, + !pBlock->Core.fAllocated ? "" : " !!allocated-block-on-freelist!!")); + + VBGL_PH_DPRINTF(("VBGL_PH dump at '%s' done\n", pszWhere)); +} +#endif + + +/** + * Initialize a free block + */ +static void vbglPhysHeapInitFreeBlock(VBGLPHYSHEAPFREEBLOCK *pBlock, VBGLPHYSHEAPCHUNK *pChunk, uint32_t cbUser) +{ + Assert(pBlock != NULL); + Assert(pChunk != NULL); + + pBlock->Core.u32Signature = VBGL_PH_BLOCKSIGNATURE; + pBlock->Core.cbUser = cbUser; + pBlock->Core.fAllocated = false; + pBlock->Core.pNext = NULL; + pBlock->Core.pPrev = NULL; + pBlock->Core.pChunk = pChunk; + pBlock->pNextFree = NULL; + pBlock->pPrevFree = NULL; +} + + +/** + * Updates block statistics when a block is added. + */ +DECLINLINE(void) vbglPhysHeapStatsBlockAdded(VBGLPHYSHEAPBLOCK *pBlock) +{ + g_vbgldata.cBlocks += 1; + pBlock->pChunk->cBlocks += 1; + AssertMsg((uint32_t)pBlock->pChunk->cBlocks <= pBlock->pChunk->cbChunk / sizeof(VBGLPHYSHEAPFREEBLOCK), + ("pChunk=%p: cbChunk=%#x cBlocks=%d\n", pBlock->pChunk, pBlock->pChunk->cbChunk, pBlock->pChunk->cBlocks)); +} + + +/** + * Links @a pBlock onto the head of block list. + * + * This also update the per-chunk block counts. + */ +static void vbglPhysHeapInsertBlock(VBGLPHYSHEAPBLOCK *pBlock) +{ + AssertMsg(pBlock->pNext == NULL, ("pBlock->pNext = %p\n", pBlock->pNext)); + AssertMsg(pBlock->pPrev == NULL, ("pBlock->pPrev = %p\n", pBlock->pPrev)); + + /* inserting to head of list */ + VBGLPHYSHEAPBLOCK *pOldHead = g_vbgldata.pBlockHead; + + pBlock->pNext = pOldHead; + pBlock->pPrev = NULL; + + if (pOldHead) + pOldHead->pPrev = pBlock; + g_vbgldata.pBlockHead = pBlock; + + /* Update the stats: */ + vbglPhysHeapStatsBlockAdded(pBlock); +} + + +/** + * Links @a pBlock onto the block list after @a pInsertAfter. + * + * This also update the per-chunk block counts. + */ +static void vbglPhysHeapInsertBlockAfter(VBGLPHYSHEAPBLOCK *pBlock, VBGLPHYSHEAPBLOCK *pInsertAfter) +{ + AssertMsg(pBlock->pNext == NULL, ("pBlock->pNext = %p\n", pBlock->pNext)); + AssertMsg(pBlock->pPrev == NULL, ("pBlock->pPrev = %p\n", pBlock->pPrev)); + + pBlock->pNext = pInsertAfter->pNext; + pBlock->pPrev = pInsertAfter; + + if (pInsertAfter->pNext) + pInsertAfter->pNext->pPrev = pBlock; + + pInsertAfter->pNext = pBlock; + + /* Update the stats: */ + vbglPhysHeapStatsBlockAdded(pBlock); +} + + +/** + * Unlinks @a pBlock from the block list. + * + * This also update the per-chunk block counts. + */ +static void vbglPhysHeapUnlinkBlock(VBGLPHYSHEAPBLOCK *pBlock) +{ + VBGLPHYSHEAPBLOCK *pOtherBlock = pBlock->pNext; + if (pOtherBlock) + pOtherBlock->pPrev = pBlock->pPrev; + /* else: this is tail of list but we do not maintain tails of block lists. so nothing to do. */ + + pOtherBlock = pBlock->pPrev; + if (pOtherBlock) + pOtherBlock->pNext = pBlock->pNext; + else + { + Assert(g_vbgldata.pBlockHead == pBlock); + g_vbgldata.pBlockHead = pBlock->pNext; + } + + pBlock->pNext = NULL; + pBlock->pPrev = NULL; + + /* Update the stats: */ + g_vbgldata.cBlocks -= 1; + pBlock->pChunk->cBlocks -= 1; + AssertMsg(pBlock->pChunk->cBlocks >= 0, + ("pChunk=%p: cbChunk=%#x cBlocks=%d\n", pBlock->pChunk, pBlock->pChunk->cbChunk, pBlock->pChunk->cBlocks)); + Assert(g_vbgldata.cBlocks >= 0); +} + + + +/** + * Updates statistics after adding a free block. + */ +DECLINLINE(void) vbglPhysHeapStatsFreeBlockAdded(VBGLPHYSHEAPFREEBLOCK *pBlock) +{ + g_vbgldata.cFreeBlocks += 1; + pBlock->Core.pChunk->cFreeBlocks += 1; +} + + +/** + * Links @a pBlock onto head of the free chain. + * + * This is used during block freeing and when adding a new chunk. + * + * This also update the per-chunk block counts. + */ +static void vbglPhysHeapInsertFreeBlock(VBGLPHYSHEAPFREEBLOCK *pBlock) +{ + Assert(!pBlock->Core.fAllocated); + AssertMsg(pBlock->pNextFree == NULL, ("pBlock->pNextFree = %p\n", pBlock->pNextFree)); + AssertMsg(pBlock->pPrevFree == NULL, ("pBlock->pPrevFree = %p\n", pBlock->pPrevFree)); + + /* inserting to head of list */ + VBGLPHYSHEAPFREEBLOCK *pOldHead = g_vbgldata.pFreeHead; + + pBlock->pNextFree = pOldHead; + pBlock->pPrevFree = NULL; + + if (pOldHead) + pOldHead->pPrevFree = pBlock; + g_vbgldata.pFreeHead = pBlock; + + /* Update the stats: */ + vbglPhysHeapStatsFreeBlockAdded(pBlock); +} + + +/** + * Links @a pBlock after @a pInsertAfter. + * + * This is used when splitting a free block during allocation to preserve the + * place in the free list. + * + * This also update the per-chunk block counts. + */ +static void vbglPhysHeapInsertFreeBlockAfter(VBGLPHYSHEAPFREEBLOCK *pBlock, VBGLPHYSHEAPFREEBLOCK *pInsertAfter) +{ + Assert(!pBlock->Core.fAllocated); + AssertMsg(pBlock->pNextFree == NULL, ("pBlock->pNextFree = %p\n", pBlock->pNextFree)); + AssertMsg(pBlock->pPrevFree == NULL, ("pBlock->pPrevFree = %p\n", pBlock->pPrevFree)); + + /* inserting after the tiven node */ + pBlock->pNextFree = pInsertAfter->pNextFree; + pBlock->pPrevFree = pInsertAfter; + + if (pInsertAfter->pNextFree) + pInsertAfter->pNextFree->pPrevFree = pBlock; + + pInsertAfter->pNextFree = pBlock; + + /* Update the stats: */ + vbglPhysHeapStatsFreeBlockAdded(pBlock); +} + + +/** + * Unlinks @a pBlock from the free list. + * + * This also update the per-chunk block counts. + */ +static void vbglPhysHeapUnlinkFreeBlock(VBGLPHYSHEAPFREEBLOCK *pBlock) +{ + Assert(!pBlock->Core.fAllocated); + + VBGLPHYSHEAPFREEBLOCK *pOtherBlock = pBlock->pNextFree; + if (pOtherBlock) + pOtherBlock->pPrevFree = pBlock->pPrevFree; + /* else: this is tail of list but we do not maintain tails of block lists. so nothing to do. */ + + pOtherBlock = pBlock->pPrevFree; + if (pOtherBlock) + pOtherBlock->pNextFree = pBlock->pNextFree; + else + { + Assert(g_vbgldata.pFreeHead == pBlock); + g_vbgldata.pFreeHead = pBlock->pNextFree; + } + + pBlock->pNextFree = NULL; + pBlock->pPrevFree = NULL; + + /* Update the stats: */ + g_vbgldata.cFreeBlocks -= 1; + pBlock->Core.pChunk->cFreeBlocks -= 1; + AssertMsg(pBlock->Core.pChunk->cFreeBlocks >= 0, + ("pChunk=%p: cbChunk=%#x cFreeBlocks=%d\n", + pBlock->Core.pChunk, pBlock->Core.pChunk->cbChunk, pBlock->Core.pChunk->cFreeBlocks)); + Assert(g_vbgldata.cFreeBlocks >= 0); +} + + +/** + * Allocate another chunk and add it to the heap. + * + * @returns Pointer to the free block in the new chunk on success, NULL on + * allocation failure. + * @param cbMinBlock The size of the user block we need this chunk for. + */ +static VBGLPHYSHEAPFREEBLOCK *vbglPhysHeapChunkAlloc(uint32_t cbMinBlock) +{ + RTCCPHYS PhysAddr = NIL_RTHCPHYS; + VBGLPHYSHEAPCHUNK *pChunk; + uint32_t cbChunk; +#ifdef VBGL_PH_USE_MEMOBJ + RTR0MEMOBJ hMemObj = NIL_RTR0MEMOBJ; + int rc; +#endif + + VBGL_PH_DPRINTF(("Allocating new chunk for %#x byte allocation\n", cbMinBlock)); + AssertReturn(cbMinBlock <= VBGL_PH_LARGEST_ALLOC_SIZE, NULL); /* paranoia */ + + /* + * Compute the size of the new chunk, rounding up to next chunk size, + * which must be power of 2. + * + * Note! Using VBGLPHYSHEAPFREEBLOCK here means the minimum block size is + * 8 or 16 bytes too high, but safer this way since cbMinBlock is + * zero during the init code call. + */ + Assert(RT_IS_POWER_OF_TWO(VBGL_PH_CHUNKSIZE)); + cbChunk = cbMinBlock + sizeof(VBGLPHYSHEAPCHUNK) + sizeof(VBGLPHYSHEAPFREEBLOCK); + cbChunk = RT_ALIGN_32(cbChunk, VBGL_PH_CHUNKSIZE); + + /* + * This function allocates physical contiguous memory below 4 GB. This 4GB + * limitation stems from using a 32-bit OUT instruction to pass a block + * physical address to the host. + */ +#ifdef VBGL_PH_USE_MEMOBJ + rc = RTR0MemObjAllocCont(&hMemObj, cbChunk, false /*fExecutable*/); + pChunk = (VBGLPHYSHEAPCHUNK *)(RT_SUCCESS(rc) ? RTR0MemObjAddress(hMemObj) : NULL); +#else + pChunk = (VBGLPHYSHEAPCHUNK *)RTMemContAlloc(&PhysAddr, cbChunk); +#endif + if (!pChunk) + { + /* If the allocation fail, halv the size till and try again. */ + uint32_t cbMinChunk = RT_MAX(cbMinBlock, PAGE_SIZE / 2) + sizeof(VBGLPHYSHEAPCHUNK) + sizeof(VBGLPHYSHEAPFREEBLOCK); + cbMinChunk = RT_ALIGN_32(cbMinChunk, PAGE_SIZE); + if (cbChunk > cbMinChunk) + do + { + cbChunk >>= 2; + cbChunk = RT_ALIGN_32(cbChunk, PAGE_SIZE); +#ifdef VBGL_PH_USE_MEMOBJ + rc = RTR0MemObjAllocCont(&hMemObj, cbChunk, false /*fExecutable*/); + pChunk = (VBGLPHYSHEAPCHUNK *)(RT_SUCCESS(rc) ? RTR0MemObjAddress(hMemObj) : NULL); +#else + pChunk = (VBGLPHYSHEAPCHUNK *)RTMemContAlloc(&PhysAddr, cbChunk); +#endif + } while (!pChunk && cbChunk > cbMinChunk); + } + if (pChunk) + { + VBGLPHYSHEAPCHUNK *pOldHeadChunk; + VBGLPHYSHEAPFREEBLOCK *pBlock; + AssertRelease(PhysAddr < _4G && PhysAddr + cbChunk <= _4G); + + /* + * Init the new chunk. + */ + pChunk->u32Signature = VBGL_PH_CHUNKSIGNATURE; + pChunk->cbChunk = cbChunk; + pChunk->physAddr = (uint32_t)PhysAddr; + pChunk->cBlocks = 0; + pChunk->cFreeBlocks = 0; + pChunk->pNext = NULL; + pChunk->pPrev = NULL; +#ifdef VBGL_PH_USE_MEMOBJ + pChunk->hMemObj = hMemObj; +#endif + + /* Initialize the padding too: */ +#if !defined(VBGL_PH_USE_MEMOBJ) || ARCH_BITS != 32 + pChunk->uPadding1 = UINT32_C(0xADDCAAA1); +#endif +#if ARCH_BITS == 64 + pChunk->auPadding2[0] = UINT64_C(0xADDCAAA3ADDCAAA2); + pChunk->auPadding2[1] = UINT64_C(0xADDCAAA5ADDCAAA4); +# ifndef VBGL_PH_USE_MEMOBJ + pChunk->auPadding2[2] = UINT64_C(0xADDCAAA7ADDCAAA6); +# endif +#endif + + /* + * Initialize the free block, which now occupies entire chunk. + */ + pBlock = (VBGLPHYSHEAPFREEBLOCK *)(pChunk + 1); + vbglPhysHeapInitFreeBlock(pBlock, pChunk, cbChunk - sizeof(VBGLPHYSHEAPCHUNK) - sizeof(VBGLPHYSHEAPBLOCK)); + vbglPhysHeapInsertBlock(&pBlock->Core); + vbglPhysHeapInsertFreeBlock(pBlock); + + /* + * Add the chunk to the list. + */ + pOldHeadChunk = g_vbgldata.pChunkHead; + pChunk->pNext = pOldHeadChunk; + if (pOldHeadChunk) + pOldHeadChunk->pPrev = pChunk; + g_vbgldata.pChunkHead = pChunk; + + VBGL_PH_DPRINTF(("Allocated chunk %p LB %#x, block %p LB %#x\n", pChunk, cbChunk, pBlock, pBlock->Core.cbUser)); + return pBlock; + } + LogRel(("vbglPhysHeapChunkAlloc: failed to alloc %u (%#x) contiguous bytes.\n", cbChunk, cbChunk)); + return NULL; +} + + +/** + * Deletes a chunk: Unlinking all its blocks and freeing its memory. + */ +static void vbglPhysHeapChunkDelete(VBGLPHYSHEAPCHUNK *pChunk) +{ + uintptr_t uEnd, uCur; + Assert(pChunk != NULL); + AssertMsg(pChunk->u32Signature == VBGL_PH_CHUNKSIGNATURE, ("pChunk->u32Signature = %08X\n", pChunk->u32Signature)); + + VBGL_PH_DPRINTF(("Deleting chunk %p size %x\n", pChunk, pChunk->cbChunk)); + + /* + * First scan the chunk and unlink all blocks from the lists. + * + * Note! We could do this by finding the first and last block list entries + * and just drop the whole chain relating to this chunk, rather than + * doing it one by one. But doing it one by one is simpler and will + * continue to work if the block list ends in an unsorted state. + */ + uEnd = (uintptr_t)pChunk + pChunk->cbChunk; + uCur = (uintptr_t)(pChunk + 1); + + while (uCur < uEnd) + { + VBGLPHYSHEAPBLOCK *pBlock = (VBGLPHYSHEAPBLOCK *)uCur; + Assert(pBlock->u32Signature == VBGL_PH_BLOCKSIGNATURE); + Assert(pBlock->pChunk == pChunk); + + uCur += pBlock->cbUser + sizeof(VBGLPHYSHEAPBLOCK); + Assert(uCur == (uintptr_t)pBlock->pNext || uCur >= uEnd); + + if (!pBlock->fAllocated) + vbglPhysHeapUnlinkFreeBlock((VBGLPHYSHEAPFREEBLOCK *)pBlock); + vbglPhysHeapUnlinkBlock(pBlock); + } + + AssertMsg(uCur == uEnd, ("uCur = %p, uEnd = %p, pChunk->cbChunk = %08X\n", uCur, uEnd, pChunk->cbChunk)); + + /* + * Unlink the chunk from the chunk list. + */ + if (pChunk->pNext) + pChunk->pNext->pPrev = pChunk->pPrev; + /* else: we do not maintain tail pointer. */ + + if (pChunk->pPrev) + pChunk->pPrev->pNext = pChunk->pNext; + else + { + Assert(g_vbgldata.pChunkHead == pChunk); + g_vbgldata.pChunkHead = pChunk->pNext; + } + + /* + * Finally, free the chunk memory. + */ +#ifdef VBGL_PH_USE_MEMOBJ + RTR0MemObjFree(pChunk->hMemObj, true /*fFreeMappings*/); +#else + RTMemContFree(pChunk, pChunk->cbChunk); +#endif +} + + +DECLR0VBGL(void *) VbglR0PhysHeapAlloc(uint32_t cb) +{ + VBGLPHYSHEAPFREEBLOCK *pBlock; + VBGLPHYSHEAPFREEBLOCK *pIter; + int32_t cLeft; +#ifdef VBGL_PH_STOP_SEARCH_AT_EXCESS + uint32_t cbAlwaysSplit; +#endif + int rc; + + /* + * Make sure we don't allocate anything too small to turn into a free node + * and align the size to prevent pointer misalignment and whatnot. + */ + cb = RT_MAX(cb, VBGL_PH_SMALLEST_ALLOC_SIZE); + cb = RT_ALIGN_32(cb, VBGL_PH_ALLOC_ALIGN); + AssertCompile(VBGL_PH_ALLOC_ALIGN <= sizeof(pBlock->Core)); + + rc = RTSemFastMutexRequest(g_vbgldata.hMtxHeap); + AssertRCReturn(rc, NULL); + + dumpheap("pre alloc"); + + /* + * Search the free list. We do this in linear fashion as we don't expect + * there to be many blocks in the heap. + */ +#ifdef VBGL_PH_STOP_SEARCH_AT_EXCESS + cbAlwaysSplit = cb + VBGL_PH_STOP_SEARCH_AT_EXCESS; +#endif + cLeft = VBGL_PH_MAX_FREE_SEARCH; + pBlock = NULL; + if (cb <= PAGE_SIZE / 4 * 3) + { + /* Smaller than 3/4 page: Prefer a free block that can keep the request within a single page, + so HGCM processing in VMMDev can use page locks instead of several reads and writes. */ + VBGLPHYSHEAPFREEBLOCK *pFallback = NULL; + for (pIter = g_vbgldata.pFreeHead; pIter != NULL; pIter = pIter->pNextFree, cLeft--) + { + AssertBreak(pIter->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE); + if (pIter->Core.cbUser >= cb) + { + if (pIter->Core.cbUser == cb) + { + if (PAGE_SIZE - ((uintptr_t)(pIter + 1) & PAGE_OFFSET_MASK) >= cb) + { + pBlock = pIter; + break; + } + pFallback = pIter; + } + else + { + if (!pFallback || pIter->Core.cbUser < pFallback->Core.cbUser) + pFallback = pIter; + if (PAGE_SIZE - ((uintptr_t)(pIter + 1) & PAGE_OFFSET_MASK) >= cb) + { + if (!pBlock || pIter->Core.cbUser < pBlock->Core.cbUser) + pBlock = pIter; +#ifdef VBGL_PH_STOP_SEARCH_AT_EXCESS + else if (pIter->Core.cbUser >= cbAlwaysSplit) + { + pBlock = pIter; + break; + } +#endif + } + } + + if (cLeft > 0) + { /* likely */ } + else + break; + } + } + + if (!pBlock) + pBlock = pFallback; + } + else + { + /* Large than 3/4 page: Find closest free list match. */ + for (pIter = g_vbgldata.pFreeHead; pIter != NULL; pIter = pIter->pNextFree, cLeft--) + { + AssertBreak(pIter->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE); + if (pIter->Core.cbUser >= cb) + { + if (pIter->Core.cbUser == cb) + { + /* Exact match - we're done! */ + pBlock = pIter; + break; + } + +#ifdef VBGL_PH_STOP_SEARCH_AT_EXCESS + if (pIter->Core.cbUser >= cbAlwaysSplit) + { + /* Really big block - no point continue searching! */ + pBlock = pIter; + break; + } +#endif + /* Looking for a free block with nearest size. */ + if (!pBlock || pIter->Core.cbUser < pBlock->Core.cbUser) + pBlock = pIter; + + if (cLeft > 0) + { /* likely */ } + else + break; + } + } + } + + if (!pBlock) + { + /* No free blocks, allocate a new chunk, the only free block of the + chunk will be returned. */ + pBlock = vbglPhysHeapChunkAlloc(cb); + } + + if (pBlock) + { + /* We have a free block, either found or allocated. */ + AssertMsg(pBlock->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE, + ("pBlock = %p, pBlock->u32Signature = %08X\n", pBlock, pBlock->Core.u32Signature)); + AssertMsg(!pBlock->Core.fAllocated, ("pBlock = %p\n", pBlock)); + + /* + * If the block is too large, split off a free block with the unused space. + * + * We do this before unlinking the block so we can preserve the location + * in the free list. + * + * Note! We cannot split off and return the tail end here, because that may + * violate the same page requirements for requests smaller than 3/4 page. + */ + AssertCompile(VBGL_PH_MIN_SPLIT_FREE_BLOCK >= sizeof(*pBlock) - sizeof(pBlock->Core)); + if (pBlock->Core.cbUser >= sizeof(VBGLPHYSHEAPBLOCK) * 2 + VBGL_PH_MIN_SPLIT_FREE_BLOCK + cb) + { + pIter = (VBGLPHYSHEAPFREEBLOCK *)((uintptr_t)(&pBlock->Core + 1) + cb); + vbglPhysHeapInitFreeBlock(pIter, pBlock->Core.pChunk, pBlock->Core.cbUser - cb - sizeof(VBGLPHYSHEAPBLOCK)); + + pBlock->Core.cbUser = cb; + + /* Insert the new 'pIter' block after the 'pBlock' in the block list + and in the free list. */ + vbglPhysHeapInsertBlockAfter(&pIter->Core, &pBlock->Core); + vbglPhysHeapInsertFreeBlockAfter(pIter, pBlock); + } + + /* + * Unlink the block from the free list and mark it as allocated. + */ + vbglPhysHeapUnlinkFreeBlock(pBlock); + pBlock->Core.fAllocated = true; + + dumpheap("post alloc"); + + /* + * Return success. + */ + rc = RTSemFastMutexRelease(g_vbgldata.hMtxHeap); + + VBGL_PH_DPRINTF(("VbglR0PhysHeapAlloc: returns %p size %x\n", pBlock + 1, pBlock->Core.cbUser)); + return &pBlock->Core + 1; + } + + /* + * Return failure. + */ + rc = RTSemFastMutexRelease(g_vbgldata.hMtxHeap); + AssertRC(rc); + + VBGL_PH_DPRINTF(("VbglR0PhysHeapAlloc: returns NULL (requested %#x bytes)\n", cb)); + return NULL; +} + + +DECLR0VBGL(uint32_t) VbglR0PhysHeapGetPhysAddr(void *pv) +{ + /* + * Validate the incoming pointer. + */ + if (pv != NULL) + { + VBGLPHYSHEAPBLOCK *pBlock = (VBGLPHYSHEAPBLOCK *)pv - 1; + if ( pBlock->u32Signature == VBGL_PH_BLOCKSIGNATURE + && pBlock->fAllocated) + { + /* + * Calculate and return its physical address. + */ + VBGLPHYSHEAPCHUNK *pChunk = pBlock->pChunk; + return pChunk->physAddr + (uint32_t)((uintptr_t)pv - (uintptr_t)pChunk); + } + + AssertMsgFailed(("Use after free or corrupt pointer variable: pv=%p pBlock=%p: u32Signature=%#x cb=%#x fAllocated=%d\n", + pv, pBlock, pBlock->u32Signature, pBlock->cbUser, pBlock->fAllocated)); + } + else + AssertMsgFailed(("Unexpected NULL pointer\n")); + return 0; +} + + +DECLR0VBGL(void) VbglR0PhysHeapFree(void *pv) +{ + if (pv != NULL) + { + VBGLPHYSHEAPFREEBLOCK *pBlock; + + int rc = RTSemFastMutexRequest(g_vbgldata.hMtxHeap); + AssertRCReturnVoid(rc); + + dumpheap("pre free"); + + /* + * Validate the block header. + */ + pBlock = (VBGLPHYSHEAPFREEBLOCK *)((VBGLPHYSHEAPBLOCK *)pv - 1); + if ( pBlock->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE + && pBlock->Core.fAllocated + && pBlock->Core.cbUser >= VBGL_PH_SMALLEST_ALLOC_SIZE) + { + VBGLPHYSHEAPCHUNK *pChunk; + VBGLPHYSHEAPBLOCK *pNeighbour; + + /* + * Change the block status to freeed. + */ + VBGL_PH_DPRINTF(("VbglR0PhysHeapFree: %p size %#x\n", pv, pBlock->Core.cbUser)); + + pBlock->Core.fAllocated = false; + pBlock->pNextFree = pBlock->pPrevFree = NULL; + vbglPhysHeapInsertFreeBlock(pBlock); + + dumpheap("post insert"); + + /* + * Check if the block after this one is also free and we can merge it into this one. + */ + pChunk = pBlock->Core.pChunk; + + pNeighbour = pBlock->Core.pNext; + if ( pNeighbour + && !pNeighbour->fAllocated + && pNeighbour->pChunk == pChunk) + { + Assert((uintptr_t)pBlock + sizeof(pBlock->Core) + pBlock->Core.cbUser == (uintptr_t)pNeighbour); + + /* Adjust size of current memory block */ + pBlock->Core.cbUser += pNeighbour->cbUser + sizeof(VBGLPHYSHEAPBLOCK); + + /* Unlink the following node and invalid it. */ + vbglPhysHeapUnlinkFreeBlock((VBGLPHYSHEAPFREEBLOCK *)pNeighbour); + vbglPhysHeapUnlinkBlock(pNeighbour); + + pNeighbour->u32Signature = ~VBGL_PH_BLOCKSIGNATURE; + pNeighbour->cbUser = UINT32_MAX / 4; + + dumpheap("post merge after"); + } + + /* + * Same check for the block before us. This invalidates pBlock. + */ + pNeighbour = pBlock->Core.pPrev; + if ( pNeighbour + && !pNeighbour->fAllocated + && pNeighbour->pChunk == pChunk) + { + Assert((uintptr_t)pNeighbour + sizeof(*pNeighbour) + pNeighbour->cbUser == (uintptr_t)pBlock); + + /* Adjust size of the block before us */ + pNeighbour->cbUser += pBlock->Core.cbUser + sizeof(VBGLPHYSHEAPBLOCK); + + /* Unlink this node and invalid it. */ + vbglPhysHeapUnlinkFreeBlock(pBlock); + vbglPhysHeapUnlinkBlock(&pBlock->Core); + + pBlock->Core.u32Signature = ~VBGL_PH_BLOCKSIGNATURE; + pBlock->Core.cbUser = UINT32_MAX / 8; + + pBlock = NULL; /* invalid */ + + dumpheap("post merge before"); + } + + /* + * If this chunk is now completely unused, delete it if there are + * more completely free ones. + */ + if ( pChunk->cFreeBlocks == pChunk->cBlocks + && (pChunk->pPrev || pChunk->pNext)) + { + VBGLPHYSHEAPCHUNK *pCurChunk; + uint32_t cUnusedChunks = 0; + for (pCurChunk = g_vbgldata.pChunkHead; pCurChunk; pCurChunk = pCurChunk->pNext) + { + AssertBreak(pCurChunk->u32Signature == VBGL_PH_CHUNKSIGNATURE); + if (pCurChunk->cFreeBlocks == pCurChunk->cBlocks) + { + cUnusedChunks++; + if (cUnusedChunks > 1) + { + /* Delete current chunk, it will also unlink all free blocks + * remaining in the chunk from the free list, so the pBlock + * will also be invalid after this. + */ + vbglPhysHeapChunkDelete(pChunk); + pBlock = NULL; /* invalid */ + pChunk = NULL; + pNeighbour = NULL; + break; + } + } + } + } + + dumpheap("post free"); + } + else + AssertMsgFailed(("pBlock: %p: u32Signature=%#x cb=%#x fAllocated=%d - double free?\n", + pBlock, pBlock->Core.u32Signature, pBlock->Core.cbUser, pBlock->Core.fAllocated)); + + rc = RTSemFastMutexRelease(g_vbgldata.hMtxHeap); + AssertRC(rc); + } +} + +#ifdef IN_TESTCASE /* For the testcase only */ + +/** + * Returns the sum of all free heap blocks. + * + * This is the amount of memory you can theoretically allocate if you do + * allocations exactly matching the free blocks. + * + * @returns The size of the free blocks. + * @returns 0 if heap was safely detected as being bad. + */ +DECLVBGL(size_t) VbglR0PhysHeapGetFreeSize(void) +{ + int rc = RTSemFastMutexRequest(g_vbgldata.hMtxHeap); + AssertRCReturn(rc, 0); + + size_t cbTotal = 0; + for (VBGLPHYSHEAPFREEBLOCK *pCurBlock = g_vbgldata.pFreeHead; pCurBlock; pCurBlock = pCurBlock->pNextFree) + { + Assert(pCurBlock->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE); + Assert(!pCurBlock->Core.fAllocated); + cbTotal += pCurBlock->Core.cbUser; + } + + RTSemFastMutexRelease(g_vbgldata.hMtxHeap); + return cbTotal; +} + + +/** + * Checks the heap, caller responsible for locking. + * + * @returns VINF_SUCCESS if okay, error status if not. + * @param pErrInfo Where to return more error details, optional. + */ +static int vbglR0PhysHeapCheckLocked(PRTERRINFO pErrInfo) +{ + /* + * Scan the blocks in each chunk, walking the block list in parallel. + */ + const VBGLPHYSHEAPBLOCK *pPrevBlockListEntry = NULL; + const VBGLPHYSHEAPBLOCK *pCurBlockListEntry = g_vbgldata.pBlockHead; + unsigned acTotalBlocks[2] = { 0, 0 }; + for (VBGLPHYSHEAPCHUNK *pCurChunk = g_vbgldata.pChunkHead, *pPrevChunk = NULL; pCurChunk; pCurChunk = pCurChunk->pNext) + { + AssertReturn(pCurChunk->u32Signature == VBGL_PH_CHUNKSIGNATURE, + RTErrInfoSetF(pErrInfo, VERR_INVALID_MAGIC, "pCurChunk=%p: magic=%#x", pCurChunk, pCurChunk->u32Signature)); + AssertReturn(pCurChunk->pPrev == pPrevChunk, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_2, + "pCurChunk=%p: pPrev=%p, expected %p", pCurChunk, pCurChunk->pPrev, pPrevChunk)); + + const VBGLPHYSHEAPBLOCK *pCurBlock = (const VBGLPHYSHEAPBLOCK *)(pCurChunk + 1); + uintptr_t const uEnd = (uintptr_t)pCurChunk + pCurChunk->cbChunk; + unsigned acBlocks[2] = { 0, 0 }; + while ((uintptr_t)pCurBlock < uEnd) + { + AssertReturn(pCurBlock->u32Signature == VBGL_PH_BLOCKSIGNATURE, + RTErrInfoSetF(pErrInfo, VERR_INVALID_MAGIC, + "pCurBlock=%p: magic=%#x", pCurBlock, pCurBlock->u32Signature)); + AssertReturn(pCurBlock->pChunk == pCurChunk, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_2, + "pCurBlock=%p: pChunk=%p, expected %p", pCurBlock, pCurBlock->pChunk, pCurChunk)); + AssertReturn( pCurBlock->cbUser >= VBGL_PH_SMALLEST_ALLOC_SIZE + && pCurBlock->cbUser <= VBGL_PH_LARGEST_ALLOC_SIZE + && RT_ALIGN_32(pCurBlock->cbUser, VBGL_PH_ALLOC_ALIGN) == pCurBlock->cbUser, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_3, + "pCurBlock=%p: cbUser=%#x", pCurBlock, pCurBlock->cbUser)); + AssertReturn(pCurBlock == pCurBlockListEntry, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_4, + "pCurChunk=%p: pCurBlock=%p, pCurBlockListEntry=%p\n", + pCurChunk, pCurBlock, pCurBlockListEntry)); + AssertReturn(pCurBlock->pPrev == pPrevBlockListEntry, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_5, + "pCurChunk=%p: pCurBlock->pPrev=%p, pPrevBlockListEntry=%p\n", + pCurChunk, pCurBlock->pPrev, pPrevBlockListEntry)); + + acBlocks[pCurBlock->fAllocated] += 1; + + /* advance */ + pPrevBlockListEntry = pCurBlock; + pCurBlockListEntry = pCurBlock->pNext; + pCurBlock = (const VBGLPHYSHEAPBLOCK *)((uintptr_t)(pCurBlock + 1) + pCurBlock->cbUser); + } + AssertReturn((uintptr_t)pCurBlock == uEnd, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_4, + "pCurBlock=%p uEnd=%p", pCurBlock, uEnd)); + + acTotalBlocks[1] += acBlocks[1]; + AssertReturn(acBlocks[0] + acBlocks[1] == (uint32_t)pCurChunk->cBlocks, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_4, + "pCurChunk=%p: cBlocks=%u, expected %u", + pCurChunk, pCurChunk->cBlocks, acBlocks[0] + acBlocks[1])); + + acTotalBlocks[0] += acBlocks[0]; + AssertReturn(acBlocks[0] == (uint32_t)pCurChunk->cFreeBlocks, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_5, + "pCurChunk=%p: cFreeBlocks=%u, expected %u", + pCurChunk, pCurChunk->cFreeBlocks, acBlocks[0])); + + pPrevChunk = pCurChunk; + } + + AssertReturn(acTotalBlocks[0] == (uint32_t)g_vbgldata.cFreeBlocks, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR, + "g_vbgldata.cFreeBlocks=%u, expected %u", g_vbgldata.cFreeBlocks, acTotalBlocks[0])); + AssertReturn(acTotalBlocks[0] + acTotalBlocks[1] == (uint32_t)g_vbgldata.cBlocks, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR, + "g_vbgldata.cBlocks=%u, expected %u", g_vbgldata.cBlocks, acTotalBlocks[0] + acTotalBlocks[1])); + + /* + * Check that the free list contains the same number of blocks as we + * encountered during the above scan. + */ + { + unsigned cFreeListBlocks = 0; + for (const VBGLPHYSHEAPFREEBLOCK *pCurBlock = g_vbgldata.pFreeHead, *pPrevBlock = NULL; + pCurBlock; + pCurBlock = pCurBlock->pNextFree) + { + AssertReturn(pCurBlock->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE, + RTErrInfoSetF(pErrInfo, VERR_INVALID_MAGIC, + "pCurBlock=%p/free: magic=%#x", pCurBlock, pCurBlock->Core.u32Signature)); + AssertReturn(pCurBlock->pPrevFree == pPrevBlock, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_2, + "pCurBlock=%p/free: pPrev=%p, expected %p", pCurBlock, pCurBlock->pPrevFree, pPrevBlock)); + AssertReturn(pCurBlock->Core.pChunk->u32Signature == VBGL_PH_CHUNKSIGNATURE, + RTErrInfoSetF(pErrInfo, VERR_INVALID_MAGIC, "pCurBlock=%p/free: chunk (%p) magic=%#x", + pCurBlock, pCurBlock->Core.pChunk, pCurBlock->Core.pChunk->u32Signature)); + cFreeListBlocks++; + pPrevBlock = pCurBlock; + } + + AssertReturn(cFreeListBlocks == acTotalBlocks[0], + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_3, + "Found %u in free list, expected %u", cFreeListBlocks, acTotalBlocks[0])); + } + return VINF_SUCCESS; +} + + +/** + * Performs a heap check. + * + * @returns Problem description on failure, NULL on success. + * @param pErrInfo Where to return more error details, optional. + */ +DECLVBGL(int) VbglR0PhysHeapCheck(PRTERRINFO pErrInfo) +{ + int rc = RTSemFastMutexRequest(g_vbgldata.hMtxHeap); + AssertRCReturn(rc, 0); + + rc = vbglR0PhysHeapCheckLocked(pErrInfo); + + RTSemFastMutexRelease(g_vbgldata.hMtxHeap); + return rc; +} + +#endif /* IN_TESTCASE */ + +DECLR0VBGL(int) VbglR0PhysHeapInit(void) +{ + g_vbgldata.hMtxHeap = NIL_RTSEMFASTMUTEX; + + /* Allocate the first chunk of the heap. */ + VBGLPHYSHEAPFREEBLOCK *pBlock = vbglPhysHeapChunkAlloc(0); + if (pBlock) + return RTSemFastMutexCreate(&g_vbgldata.hMtxHeap); + return VERR_NO_CONT_MEMORY; +} + +DECLR0VBGL(void) VbglR0PhysHeapTerminate(void) +{ + while (g_vbgldata.pChunkHead) + vbglPhysHeapChunkDelete(g_vbgldata.pChunkHead); + + RTSemFastMutexDestroy(g_vbgldata.hMtxHeap); + g_vbgldata.hMtxHeap = NIL_RTSEMFASTMUTEX; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibSharedFolders.c b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibSharedFolders.c new file mode 100644 index 00000000..02a5c470 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibSharedFolders.c @@ -0,0 +1,716 @@ +/* $Id: VBoxGuestR0LibSharedFolders.c $ */ +/** @file + * VBoxGuestR0LibSharedFolders - Ring 0 Shared Folders calls. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#include "VBoxGuestR0LibInternal.h" +#include <VBox/VBoxGuestLibSharedFolders.h> +#include <VBox/log.h> +#include <iprt/err.h> +#include <iprt/time.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/string.h> + +#ifdef VBGL_VBOXGUEST +# error "This file shouldn't be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest. It's client code." +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOX_INIT_CALL(a, b, c) \ + LogFunc(("%s, idClient=%d\n", "SHFL_FN_" # b, (c)->idClient)); \ + VBGL_HGCM_HDR_INIT(a, (c)->idClient, SHFL_FN_##b, SHFL_CPARMS_##b); \ + (a)->fInterruptible = false /* Currently we do like nfs with -o hard (default). */ + +#define VBOX_INIT_CALL_EX(a, b, c, a_cbReq) \ + LogFunc(("%s, idClient=%d\n", "SHFL_FN_" # b, (c)->idClient)); \ + VBGL_HGCM_HDR_INIT_EX(a, (c)->idClient, SHFL_FN_##b, SHFL_CPARMS_##b, a_cbReq); \ + (a)->fInterruptible = false /* Currently we do like nfs with -o hard (default). */ + + + +DECLVBGL(int) VbglR0SfInit(void) +{ + return VbglR0InitClient(); +} + +DECLVBGL(void) VbglR0SfTerm(void) +{ + VbglR0TerminateClient(); +} + +DECLVBGL(int) VbglR0SfConnect(PVBGLSFCLIENT pClient) +{ + int rc = VbglR0HGCMConnect(&pClient->handle, "VBoxSharedFolders", &pClient->idClient); + if (RT_SUCCESS(rc)) + LogFunc(("idClient=%d\n", pClient->idClient)); + else + LogFunc(("VbglR0HGCMConnect failed -> rc=%Rrc\n", rc)); + return rc; +} + +DECLVBGL(void) VbglR0SfDisconnect(PVBGLSFCLIENT pClient) +{ + int rc; + LogFunc(("u32ClientID=%d\n", pClient->idClient)); + if (pClient->handle == NULL) + return; /* not connected */ + + rc = VbglR0HGCMDisconnect(pClient->handle, pClient->idClient); + NOREF(rc); +/* Log(("VBOXSF: VbglR0SfDisconnect: VbglR0HGCMDisconnect -> %#x\n", rc)); */ + pClient->idClient = 0; + pClient->handle = NULL; + return; +} + +#if !defined(RT_OS_LINUX) + +# ifndef RT_OS_WINDOWS + +DECLVBGL(int) VbglR0SfSetUtf8(PVBGLSFCLIENT pClient) +{ + int rc; + VBGLIOCHGCMCALL callInfo; + + VBOX_INIT_CALL(&callInfo, SET_UTF8, pClient); + rc = VbglR0HGCMCall(pClient->handle, &callInfo, sizeof(callInfo)); +/* Log(("VBOXSF: VbglR0SfSetUtf8: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + +# endif /* !RT_OS_WINDOWS */ + +/** @name Deprecated VBGL shared folder helpers. + * + * @deprecated These are all use the slow VbglR0HGCMCall interface, that + * basically treat ring-0 and user land callers much the same. + * Since 6.0 there is VbglR0HGCMFastCall() that does not bother with + * repacking the request and locking/duplicating parameter buffers, + * but just passes it along to the host and handles the waiting. + * Also new in 6.0 is embedded buffers which saves a bit time on + * guest and host by embedding parameter buffers into the request. + * + * @{ + */ + +DECLVBGL(int) VbglR0SfQueryMappings(PVBGLSFCLIENT pClient, SHFLMAPPING paMappings[], uint32_t *pcMappings) +{ + int rc; + VBoxSFQueryMappings data; + + VBOX_INIT_CALL(&data.callInfo, QUERY_MAPPINGS, pClient); + + data.flags.type = VMMDevHGCMParmType_32bit; + data.flags.u.value32 = SHFL_MF_UCS2; + + data.numberOfMappings.type = VMMDevHGCMParmType_32bit; + data.numberOfMappings.u.value32 = *pcMappings; + + data.mappings.type = VMMDevHGCMParmType_LinAddr; + data.mappings.u.Pointer.size = sizeof(SHFLMAPPING) * *pcMappings; + data.mappings.u.Pointer.u.linearAddr = (uintptr_t)&paMappings[0]; + +/* Log(("VBOXSF: in ifs difference %d\n", (char *)&data.flags.type - (char *)&data.callInfo.cParms)); */ + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfQueryMappings: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.result)); */ + if (RT_SUCCESS(rc)) + *pcMappings = data.numberOfMappings.u.value32; + + return rc; +} + +DECLVBGL(int) VbglR0SfQueryMapName(PVBGLSFCLIENT pClient, SHFLROOT root, SHFLSTRING *pString, uint32_t size) +{ + int rc; + VBoxSFQueryMapName data; + + VBOX_INIT_CALL(&data.callInfo, QUERY_MAP_NAME, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = root; + + data.name.type = VMMDevHGCMParmType_LinAddr; + data.name.u.Pointer.size = size; + data.name.u.Pointer.u.linearAddr = (uintptr_t)pString; + + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfQueryMapName: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + +DECLVBGL(int) VbglR0SfMapFolder(PVBGLSFCLIENT pClient, PSHFLSTRING szFolderName, PVBGLSFMAP pMap) +{ + int rc; + VBoxSFMapFolder data; + + VBOX_INIT_CALL(&data.callInfo, MAP_FOLDER, pClient); + + data.path.type = VMMDevHGCMParmType_LinAddr; + data.path.u.Pointer.size = ShflStringSizeOfBuffer(szFolderName); + data.path.u.Pointer.u.linearAddr = (uintptr_t)szFolderName; + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = 0; + + data.delimiter.type = VMMDevHGCMParmType_32bit; + data.delimiter.u.value32 = RTPATH_DELIMITER; + + data.fCaseSensitive.type = VMMDevHGCMParmType_32bit; +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + data.fCaseSensitive.u.value32 = 0; +#else + data.fCaseSensitive.u.value32 = 1; +#endif + + rc = VbglR0HGCMCallRaw(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfMapFolder: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + if (RT_SUCCESS(rc)) + { + pMap->root = data.root.u.value32; + rc = data.callInfo.Hdr.rc; + } + return rc; +} + +DECLVBGL(int) VbglR0SfUnmapFolder(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap) +{ + int rc; + VBoxSFUnmapFolder data; + + VBOX_INIT_CALL(&data.callInfo, UNMAP_FOLDER, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfUnmapFolder: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + +# if !defined(RT_OS_WINDOWS) + +DECLVBGL(int) VbglR0SfCreate(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, PSHFLSTRING pParsedPath, PSHFLCREATEPARMS pCreateParms) +{ + /** @todo copy buffers to physical or mapped memory. */ + int rc; + VBoxSFCreate data; + + VBOX_INIT_CALL(&data.callInfo, CREATE, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.path.type = VMMDevHGCMParmType_LinAddr; + data.path.u.Pointer.size = ShflStringSizeOfBuffer (pParsedPath); + data.path.u.Pointer.u.linearAddr = (uintptr_t)pParsedPath; + + data.parms.type = VMMDevHGCMParmType_LinAddr; + data.parms.u.Pointer.size = sizeof(SHFLCREATEPARMS); + data.parms.u.Pointer.u.linearAddr = (uintptr_t)pCreateParms; + + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfCreate: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + +DECLVBGL(int) VbglR0SfClose(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE Handle) +{ + int rc; + VBoxSFClose data; + + VBOX_INIT_CALL(&data.callInfo, CLOSE, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.handle.type = VMMDevHGCMParmType_64bit; + data.handle.u.value64 = Handle; + + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfClose: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + + +DECLVBGL(int) VbglR0SfRemove(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, PSHFLSTRING pParsedPath, uint32_t flags) +{ + int rc = VINF_SUCCESS; + + VBoxSFRemove data; + + VBOX_INIT_CALL(&data.callInfo, REMOVE, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.path.type = VMMDevHGCMParmType_LinAddr_In; + data.path.u.Pointer.size = ShflStringSizeOfBuffer(pParsedPath); + data.path.u.Pointer.u.linearAddr = (uintptr_t)pParsedPath; + + data.flags.type = VMMDevHGCMParmType_32bit; + data.flags.u.value32 = flags; + + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfRemove: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + +DECLVBGL(int) VbglR0SfRename(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, PSHFLSTRING pSrcPath, PSHFLSTRING pDestPath, uint32_t flags) +{ + int rc; + VBoxSFRename data; + + VBOX_INIT_CALL(&data.callInfo, RENAME, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.src.type = VMMDevHGCMParmType_LinAddr_In; + data.src.u.Pointer.size = ShflStringSizeOfBuffer(pSrcPath); + data.src.u.Pointer.u.linearAddr = (uintptr_t)pSrcPath; + + data.dest.type = VMMDevHGCMParmType_LinAddr_In; + data.dest.u.Pointer.size = ShflStringSizeOfBuffer(pDestPath); + data.dest.u.Pointer.u.linearAddr = (uintptr_t)pDestPath; + + data.flags.type = VMMDevHGCMParmType_32bit; + data.flags.u.value32 = flags; + + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfRename: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + +DECLVBGL(int) VbglR0SfRead(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile, + uint64_t offset, uint32_t *pcbBuffer, uint8_t *pBuffer, bool fLocked) +{ + int rc; + VBoxSFRead data; + + VBOX_INIT_CALL(&data.callInfo, READ, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.handle.type = VMMDevHGCMParmType_64bit; + data.handle.u.value64 = hFile; + data.offset.type = VMMDevHGCMParmType_64bit; + data.offset.u.value64 = offset; + data.cb.type = VMMDevHGCMParmType_32bit; + data.cb.u.value32 = *pcbBuffer; + data.buffer.type = (fLocked) ? VMMDevHGCMParmType_LinAddr_Locked_Out : VMMDevHGCMParmType_LinAddr_Out; + data.buffer.u.Pointer.size = *pcbBuffer; + data.buffer.u.Pointer.u.linearAddr = (uintptr_t)pBuffer; + + rc = VbglR0HGCMCallRaw(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfRead: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + if (RT_SUCCESS(rc)) + { + rc = data.callInfo.Hdr.rc; + *pcbBuffer = data.cb.u.value32; + } + return rc; +} + +DECLVBGL(int) VbglR0SfReadPageList(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile, uint64_t offset, uint32_t *pcbBuffer, + uint16_t offFirstPage, uint16_t cPages, RTGCPHYS64 *paPages) +{ + uint32_t cbToRead = *pcbBuffer; + uint32_t cbData = (uint32_t)(sizeof(VBoxSFRead) + RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages])); + VBoxSFRead *pData = (VBoxSFRead *)RTMemTmpAlloc(cbData); + HGCMPageListInfo *pPgLst = (HGCMPageListInfo *)(pData + 1); + uint16_t iPage; + int rc; + + if (RT_UNLIKELY(!pData)) + return VERR_NO_TMP_MEMORY; + + VBOX_INIT_CALL_EX(&pData->callInfo, READ, pClient, cbData); + + pData->root.type = VMMDevHGCMParmType_32bit; + pData->root.u.value32 = pMap->root; + + pData->handle.type = VMMDevHGCMParmType_64bit; + pData->handle.u.value64 = hFile; + pData->offset.type = VMMDevHGCMParmType_64bit; + pData->offset.u.value64 = offset; + pData->cb.type = VMMDevHGCMParmType_32bit; + pData->cb.u.value32 = cbToRead; + pData->buffer.type = VMMDevHGCMParmType_PageList; + pData->buffer.u.PageList.size = cbToRead; + pData->buffer.u.PageList.offset = sizeof(VBoxSFRead); + + pPgLst->flags = VBOX_HGCM_F_PARM_DIRECTION_FROM_HOST; + pPgLst->offFirstPage = offFirstPage; + pPgLst->cPages = cPages; + for (iPage = 0; iPage < cPages; iPage++) + pPgLst->aPages[iPage] = paPages[iPage]; + + rc = VbglR0HGCMCallRaw(pClient->handle, &pData->callInfo, cbData); +/* Log(("VBOXSF: VbglR0SfReadPageList: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + if (RT_SUCCESS(rc)) + { + rc = pData->callInfo.Hdr.rc; + *pcbBuffer = pData->cb.u.value32; + } + + RTMemTmpFree(pData); + return rc; +} + +DECLVBGL(int) VbglR0SfWrite(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile, + uint64_t offset, uint32_t *pcbBuffer, uint8_t *pBuffer, bool fLocked) +{ + int rc; + VBoxSFWrite data; + + VBOX_INIT_CALL(&data.callInfo, WRITE, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.handle.type = VMMDevHGCMParmType_64bit; + data.handle.u.value64 = hFile; + data.offset.type = VMMDevHGCMParmType_64bit; + data.offset.u.value64 = offset; + data.cb.type = VMMDevHGCMParmType_32bit; + data.cb.u.value32 = *pcbBuffer; + data.buffer.type = fLocked ? VMMDevHGCMParmType_LinAddr_Locked_In : VMMDevHGCMParmType_LinAddr_In; + data.buffer.u.Pointer.size = *pcbBuffer; + data.buffer.u.Pointer.u.linearAddr = (uintptr_t)pBuffer; + + rc = VbglR0HGCMCallRaw(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfWrite: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + if (RT_SUCCESS(rc)) + { + rc = data.callInfo.Hdr.rc; + *pcbBuffer = data.cb.u.value32; + } + return rc; +} + +DECLVBGL(int) VbglR0SfWritePhysCont(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile, uint64_t offset, + uint32_t *pcbBuffer, RTCCPHYS PhysBuffer) +{ + uint32_t cbToWrite = *pcbBuffer; + uint32_t cPages = RT_ALIGN_32((PhysBuffer & PAGE_OFFSET_MASK) + cbToWrite, PAGE_SIZE) >> PAGE_SHIFT; + uint32_t cbData = (uint32_t)(sizeof(VBoxSFWrite) + RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages])); + VBoxSFWrite *pData = (VBoxSFWrite *)RTMemTmpAlloc(cbData); + HGCMPageListInfo *pPgLst = (HGCMPageListInfo *)(pData + 1); + uint32_t iPage; + int rc; + + if (RT_UNLIKELY(!pData)) + return VERR_NO_TMP_MEMORY; + + VBOX_INIT_CALL_EX(&pData->callInfo, WRITE, pClient, cbData); + + pData->root.type = VMMDevHGCMParmType_32bit; + pData->root.u.value32 = pMap->root; + + pData->handle.type = VMMDevHGCMParmType_64bit; + pData->handle.u.value64 = hFile; + pData->offset.type = VMMDevHGCMParmType_64bit; + pData->offset.u.value64 = offset; + pData->cb.type = VMMDevHGCMParmType_32bit; + pData->cb.u.value32 = cbToWrite; + pData->buffer.type = VMMDevHGCMParmType_PageList; + pData->buffer.u.PageList.size = cbToWrite; + pData->buffer.u.PageList.offset = sizeof(VBoxSFWrite); + + pPgLst->flags = VBOX_HGCM_F_PARM_DIRECTION_TO_HOST; + pPgLst->offFirstPage = (uint16_t)(PhysBuffer & PAGE_OFFSET_MASK); + pPgLst->cPages = cPages; + PhysBuffer &= ~(RTCCPHYS)PAGE_OFFSET_MASK; + for (iPage = 0; iPage < cPages; iPage++, PhysBuffer += PAGE_SIZE) + pPgLst->aPages[iPage] = PhysBuffer; + + rc = VbglR0HGCMCallRaw(pClient->handle, &pData->callInfo, cbData); +/* Log(("VBOXSF: VbglR0SfWritePhysCont: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + if (RT_SUCCESS(rc)) + { + rc = pData->callInfo.Hdr.rc; + *pcbBuffer = pData->cb.u.value32; + } + + RTMemTmpFree(pData); + return rc; + +} + +DECLVBGL(int) VbglR0SfWritePageList(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile, uint64_t offset, uint32_t *pcbBuffer, + uint16_t offFirstPage, uint16_t cPages, RTGCPHYS64 *paPages) +{ + uint32_t cbToWrite = *pcbBuffer; + uint32_t cbData = (uint32_t)(sizeof(VBoxSFWrite) + RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages])); + VBoxSFWrite *pData = (VBoxSFWrite *)RTMemTmpAlloc(cbData); + HGCMPageListInfo *pPgLst = (HGCMPageListInfo *)(pData + 1); + uint16_t iPage; + int rc; + + if (RT_UNLIKELY(!pData)) + return VERR_NO_TMP_MEMORY; + + VBOX_INIT_CALL_EX(&pData->callInfo, WRITE, pClient, cbData); + + pData->root.type = VMMDevHGCMParmType_32bit; + pData->root.u.value32 = pMap->root; + + pData->handle.type = VMMDevHGCMParmType_64bit; + pData->handle.u.value64 = hFile; + pData->offset.type = VMMDevHGCMParmType_64bit; + pData->offset.u.value64 = offset; + pData->cb.type = VMMDevHGCMParmType_32bit; + pData->cb.u.value32 = cbToWrite; + pData->buffer.type = VMMDevHGCMParmType_PageList; + pData->buffer.u.PageList.size = cbToWrite; + pData->buffer.u.PageList.offset = sizeof(VBoxSFWrite); + + pPgLst->flags = VBOX_HGCM_F_PARM_DIRECTION_TO_HOST; + pPgLst->offFirstPage = offFirstPage; + pPgLst->cPages = cPages; + for (iPage = 0; iPage < cPages; iPage++) + pPgLst->aPages[iPage] = paPages[iPage]; + + rc = VbglR0HGCMCallRaw(pClient->handle, &pData->callInfo, cbData); +/* Log(("VBOXSF: VbglR0SfWritePageList: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + if (RT_SUCCESS(rc)) + { + rc = pData->callInfo.Hdr.rc; + *pcbBuffer = pData->cb.u.value32; + } + + RTMemTmpFree(pData); + return rc; +} + +# endif /* !RT_OS_WINDOWS */ + +DECLVBGL(int) VbglR0SfFlush(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile) +{ + int rc; + VBoxSFFlush data; + + VBOX_INIT_CALL(&data.callInfo, FLUSH, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.handle.type = VMMDevHGCMParmType_64bit; + data.handle.u.value64 = hFile; + + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfFlush: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + +DECLVBGL(int) VbglR0SfDirInfo( + PVBGLSFCLIENT pClient, + PVBGLSFMAP pMap, + SHFLHANDLE hFile, + PSHFLSTRING ParsedPath, + uint32_t flags, + uint32_t index, + uint32_t *pcbBuffer, + PSHFLDIRINFO pBuffer, + uint32_t *pcFiles) +{ + int rc; + VBoxSFList data; + + VBOX_INIT_CALL(&data.callInfo, LIST, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.handle.type = VMMDevHGCMParmType_64bit; + data.handle.u.value64 = hFile; + data.flags.type = VMMDevHGCMParmType_32bit; + data.flags.u.value32 = flags; + data.cb.type = VMMDevHGCMParmType_32bit; + data.cb.u.value32 = *pcbBuffer; + data.path.type = VMMDevHGCMParmType_LinAddr_In; + data.path.u.Pointer.size = ParsedPath ? ShflStringSizeOfBuffer(ParsedPath) : 0; + data.path.u.Pointer.u.linearAddr = (uintptr_t) ParsedPath; + + data.buffer.type = VMMDevHGCMParmType_LinAddr_Out; + data.buffer.u.Pointer.size = *pcbBuffer; + data.buffer.u.Pointer.u.linearAddr = (uintptr_t)pBuffer; + + data.resumePoint.type = VMMDevHGCMParmType_32bit; + data.resumePoint.u.value32 = index; + data.cFiles.type = VMMDevHGCMParmType_32bit; + data.cFiles.u.value32 = 0; /* out parameters only */ + + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfDirInfo: rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + *pcbBuffer = data.cb.u.value32; + *pcFiles = data.cFiles.u.value32; + return rc; +} + +# ifndef RT_OS_WINDOWS + +DECLVBGL(int) VbglR0SfFsInfo(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile, + uint32_t flags, uint32_t *pcbBuffer, PSHFLDIRINFO pBuffer) +{ + int rc; + VBoxSFInformation data; + + VBOX_INIT_CALL(&data.callInfo, INFORMATION, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.handle.type = VMMDevHGCMParmType_64bit; + data.handle.u.value64 = hFile; + data.flags.type = VMMDevHGCMParmType_32bit; + data.flags.u.value32 = flags; + data.cb.type = VMMDevHGCMParmType_32bit; + data.cb.u.value32 = *pcbBuffer; + data.info.type = VMMDevHGCMParmType_LinAddr; + data.info.u.Pointer.size = *pcbBuffer; + data.info.u.Pointer.u.linearAddr = (uintptr_t)pBuffer; + + rc = VbglR0HGCMCallRaw(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfFsInfo: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + if (RT_SUCCESS(rc)) + { + rc = data.callInfo.Hdr.rc; + *pcbBuffer = data.cb.u.value32; + } + return rc; +} + +# endif /* !RT_OS_WINDOWS */ + +DECLVBGL(int) VbglR0SfLock(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile, + uint64_t offset, uint64_t cbSize, uint32_t fLock) +{ + int rc; + VBoxSFLock data; + + VBOX_INIT_CALL(&data.callInfo, LOCK, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.handle.type = VMMDevHGCMParmType_64bit; + data.handle.u.value64 = hFile; + data.offset.type = VMMDevHGCMParmType_64bit; + data.offset.u.value64 = offset; + data.length.type = VMMDevHGCMParmType_64bit; + data.length.u.value64 = cbSize; + + data.flags.type = VMMDevHGCMParmType_32bit; + data.flags.u.value32 = fLock; + + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfLock: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + +# ifndef RT_OS_WINDOWS + +DECLVBGL(int) VbglR0SfReadLink(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, PSHFLSTRING pParsedPath, uint32_t cbBuffer, uint8_t *pBuffer) +{ + int rc; + VBoxSFReadLink data; + + VBOX_INIT_CALL(&data.callInfo, READLINK, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.path.type = VMMDevHGCMParmType_LinAddr_In; + data.path.u.Pointer.size = ShflStringSizeOfBuffer (pParsedPath); + data.path.u.Pointer.u.linearAddr = (uintptr_t)pParsedPath; + + data.buffer.type = VMMDevHGCMParmType_LinAddr_Out; + data.buffer.u.Pointer.size = cbBuffer; + data.buffer.u.Pointer.u.linearAddr = (uintptr_t)pBuffer; + + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfReadLink: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + +DECLVBGL(int) VbglR0SfSymlink(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, PSHFLSTRING pNewPath, PSHFLSTRING pOldPath, + PSHFLFSOBJINFO pBuffer) +{ + int rc; + VBoxSFSymlink data; + + VBOX_INIT_CALL(&data.callInfo, SYMLINK, pClient); + + data.root.type = VMMDevHGCMParmType_32bit; + data.root.u.value32 = pMap->root; + + data.newPath.type = VMMDevHGCMParmType_LinAddr_In; + data.newPath.u.Pointer.size = ShflStringSizeOfBuffer (pNewPath); + data.newPath.u.Pointer.u.linearAddr = (uintptr_t)pNewPath; + + data.oldPath.type = VMMDevHGCMParmType_LinAddr_In; + data.oldPath.u.Pointer.size = ShflStringSizeOfBuffer (pOldPath); + data.oldPath.u.Pointer.u.linearAddr = (uintptr_t)pOldPath; + + data.info.type = VMMDevHGCMParmType_LinAddr_Out; + data.info.u.Pointer.size = sizeof(SHFLFSOBJINFO); + data.info.u.Pointer.u.linearAddr = (uintptr_t)pBuffer; + + rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data)); +/* Log(("VBOXSF: VbglR0SfSymlink: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + +DECLVBGL(int) VbglR0SfSetSymlinks(PVBGLSFCLIENT pClient) +{ + int rc; + VBGLIOCHGCMCALL callInfo; + + VBOX_INIT_CALL(&callInfo, SET_SYMLINKS, pClient); + rc = VbglR0HGCMCall(pClient->handle, &callInfo, sizeof(callInfo)); +/* Log(("VBOXSF: VbglR0SfSetSymlinks: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */ + return rc; +} + +# endif /* !RT_OS_WINDOWS */ + +#endif /* !RT_OS_LINUX */ + +/** @} */ + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibVMMDev.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibVMMDev.cpp new file mode 100644 index 00000000..5909fd2c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibVMMDev.cpp @@ -0,0 +1,51 @@ +/* $Id: VBoxGuestR0LibVMMDev.cpp $ */ +/** @file + * VBoxGuestLibR0 - VMMDev device related functions. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR0LibInternal.h" + + +DECLVBGL(int) VbglR0QueryVMMDevMemory(VMMDevMemory **ppVMMDevMemory) +{ + int rc = vbglR0Enter(); + if (RT_FAILURE(rc)) + return rc; + + /* If the memory was not found, return an error. */ + if (!g_vbgldata.pVMMDevMemory) + return VERR_NOT_SUPPORTED; + + *ppVMMDevMemory = g_vbgldata.pVMMDevMemory; + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3Lib.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3Lib.cpp new file mode 100644 index 00000000..4298b742 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3Lib.cpp @@ -0,0 +1,485 @@ +/* $Id: VBoxGuestR3Lib.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Core. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if defined(RT_OS_WINDOWS) +# include <iprt/nt/nt-and-windows.h> + +#elif defined(RT_OS_OS2) +# define INCL_BASE +# define INCL_ERRORS +# include <os2.h> + +#elif defined(RT_OS_DARWIN) \ + || defined(RT_OS_FREEBSD) \ + || defined(RT_OS_HAIKU) \ + || defined(RT_OS_LINUX) \ + || defined(RT_OS_NETBSD) \ + || defined(RT_OS_SOLARIS) +# include <sys/types.h> +# include <sys/stat.h> +# if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined(RT_OS_NETBSD) + /** @todo check this on solaris+freebsd as well. */ +# include <sys/ioctl.h> +# endif +# if defined(RT_OS_DARWIN) +# include <mach/mach_port.h> +# include <IOKit/IOKitLib.h> +# endif +# include <errno.h> +# include <unistd.h> +#endif + +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/time.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <VBox/log.h> +#include "VBoxGuestR3LibInternal.h" + +#ifdef VBOX_VBGLR3_XFREE86 +/* Rather than try to resolve all the header file conflicts, I will just + prototype what we need here. */ +# define XF86_O_RDWR 0x0002 +typedef void *pointer; +extern "C" int xf86open(const char *, int, ...); +extern "C" int xf86close(int); +extern "C" int xf86ioctl(int, unsigned long, pointer); +# define VBOX_VBGLR3_XSERVER +#elif defined(VBOX_VBGLR3_XORG) +# include <sys/stat.h> +# include <fcntl.h> +# include <unistd.h> +# include <sys/ioctl.h> +# define xf86open open +# define xf86close close +# define xf86ioctl ioctl +# define XF86_O_RDWR O_RDWR +# define VBOX_VBGLR3_XSERVER +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The VBoxGuest device handle. */ +#ifdef VBOX_VBGLR3_XSERVER +static int g_File = -1; +#elif defined(RT_OS_WINDOWS) +static HANDLE g_hFile = INVALID_HANDLE_VALUE; +#else +static RTFILE g_File = NIL_RTFILE; +#endif +/** User counter. + * A counter of the number of times the library has been initialised, for use with + * X.org drivers, where the library may be shared by multiple independent modules + * inside a single process space. + */ +static uint32_t volatile g_cInits = 0; +#ifdef RT_OS_DARWIN +/** I/O Kit connection handle. */ +static io_connect_t g_uConnection = 0; +#endif + + + +/** + * Implementation of VbglR3Init and VbglR3InitUser + */ +static int vbglR3Init(const char *pszDeviceName) +{ + int rc2; + uint32_t cInits = ASMAtomicIncU32(&g_cInits); + Assert(cInits > 0); + if (cInits > 1) + { + /* + * This will fail if two (or more) threads race each other calling VbglR3Init. + * However it will work fine for single threaded or otherwise serialized + * processed calling us more than once. + */ +#ifdef RT_OS_WINDOWS + if (g_hFile == INVALID_HANDLE_VALUE) +#elif !defined (VBOX_VBGLR3_XSERVER) + if (g_File == NIL_RTFILE) +#else + if (g_File == -1) +#endif + return VERR_INTERNAL_ERROR; + return VINF_SUCCESS; + } +#if defined(RT_OS_WINDOWS) + if (g_hFile != INVALID_HANDLE_VALUE) +#elif !defined(VBOX_VBGLR3_XSERVER) + if (g_File != NIL_RTFILE) +#else + if (g_File != -1) +#endif + return VERR_INTERNAL_ERROR; + +#if defined(RT_OS_WINDOWS) + /* + * Have to use CreateFile here as we want to specify FILE_FLAG_OVERLAPPED + * and possible some other bits not available thru iprt/file.h. + */ + HANDLE hFile = CreateFile(pszDeviceName, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, + NULL); + + if (hFile == INVALID_HANDLE_VALUE) + return VERR_OPEN_FAILED; + g_hFile = hFile; + +#elif defined(RT_OS_OS2) + /* + * We might wish to compile this with Watcom, so stick to + * the OS/2 APIs all the way. And in any case we have to use + * DosDevIOCtl for the requests, why not use Dos* for everything. + */ + HFILE hf = NULLHANDLE; + ULONG ulAction = 0; + APIRET rc = DosOpen((PCSZ)pszDeviceName, &hf, &ulAction, 0, FILE_NORMAL, + OPEN_ACTION_OPEN_IF_EXISTS, + OPEN_FLAGS_FAIL_ON_ERROR | OPEN_FLAGS_NOINHERIT | OPEN_SHARE_DENYNONE | OPEN_ACCESS_READWRITE, + NULL); + if (rc) + return RTErrConvertFromOS2(rc); + + if (hf < 16) + { + HFILE ahfs[16]; + unsigned i; + for (i = 0; i < RT_ELEMENTS(ahfs); i++) + { + ahfs[i] = 0xffffffff; + rc = DosDupHandle(hf, &ahfs[i]); + if (rc) + break; + } + + if (i-- > 1) + { + ULONG fulState = 0; + rc = DosQueryFHState(ahfs[i], &fulState); + if (!rc) + { + fulState |= OPEN_FLAGS_NOINHERIT; + fulState &= OPEN_FLAGS_WRITE_THROUGH | OPEN_FLAGS_FAIL_ON_ERROR | OPEN_FLAGS_NO_CACHE | OPEN_FLAGS_NOINHERIT; /* Turn off non-participating bits. */ + rc = DosSetFHState(ahfs[i], fulState); + } + if (!rc) + { + rc = DosClose(hf); + AssertMsg(!rc, ("%ld\n", rc)); + hf = ahfs[i]; + } + else + i++; + while (i-- > 0) + DosClose(ahfs[i]); + } + } + g_File = (RTFILE)hf; + +#elif defined(RT_OS_DARWIN) + /* + * Darwin is kind of special we need to engage the device via I/O first + * before we open it via the BSD device node. + */ + /* IOKit */ + mach_port_t MasterPort; + kern_return_t kr = IOMasterPort(MACH_PORT_NULL, &MasterPort); + if (kr != kIOReturnSuccess) + { + LogRel(("IOMasterPort -> %d\n", kr)); + return VERR_GENERAL_FAILURE; + } + + CFDictionaryRef ClassToMatch = IOServiceMatching("org_virtualbox_VBoxGuest"); + if (!ClassToMatch) + { + LogRel(("IOServiceMatching(\"org_virtualbox_VBoxGuest\") failed.\n")); + return VERR_GENERAL_FAILURE; + } + + io_service_t ServiceObject = IOServiceGetMatchingService(kIOMasterPortDefault, ClassToMatch); + if (!ServiceObject) + { + LogRel(("IOServiceGetMatchingService returned NULL\n")); + return VERR_NOT_FOUND; + } + + io_connect_t uConnection; + kr = IOServiceOpen(ServiceObject, mach_task_self(), VBOXGUEST_DARWIN_IOSERVICE_COOKIE, &uConnection); + IOObjectRelease(ServiceObject); + if (kr != kIOReturnSuccess) + { + LogRel(("IOServiceOpen returned %d. Driver open failed.\n", kr)); + return VERR_OPEN_FAILED; + } + + /* Regular unix FD. */ + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszDeviceName, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + { + LogRel(("RTFileOpen(%s) returned %Rrc. Driver open failed.\n", pszDeviceName, rc)); + IOServiceClose(uConnection); + return rc; + } + g_File = hFile; + g_uConnection = uConnection; + +#elif defined(VBOX_VBGLR3_XSERVER) + int File = xf86open(pszDeviceName, XF86_O_RDWR); + if (File == -1) + return VERR_OPEN_FAILED; + g_File = File; + +#else + + /* The default implementation. (linux, solaris, freebsd, netbsd, haiku) */ + RTFILE File; + int rc = RTFileOpen(&File, pszDeviceName, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return rc; + g_File = File; + +#endif + + /* + * Adjust the I/O control interface version. + */ + { + VBGLIOCDRIVERVERSIONINFO VerInfo; + VBGLREQHDR_INIT(&VerInfo.Hdr, DRIVER_VERSION_INFO); + VerInfo.u.In.uMinVersion = VBGL_IOC_VERSION & UINT32_C(0xffff0000); + VerInfo.u.In.uReqVersion = VBGL_IOC_VERSION; + VerInfo.u.In.uReserved1 = 0; + VerInfo.u.In.uReserved2 = 0; + rc2 = vbglR3DoIOCtl(VBGL_IOCTL_DRIVER_VERSION_INFO, &VerInfo.Hdr, sizeof(VerInfo)); +#ifndef VBOX_VBGLR3_XSERVER + AssertRC(rc2); /* otherwise ignored for now*/ +#endif + } + + +#ifndef VBOX_VBGLR3_XSERVER + /* + * Create release logger + */ + PRTLOGGER pReleaseLogger; + static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; + rc2 = RTLogCreate(&pReleaseLogger, 0, "all", "VBOX_RELEASE_LOG", + RT_ELEMENTS(s_apszGroups), &s_apszGroups[0], RTLOGDEST_USER, NULL); + /* This may legitimately fail if we are using the mini-runtime. */ + if (RT_SUCCESS(rc2)) + RTLogRelSetDefaultInstance(pReleaseLogger); +#endif + + return VINF_SUCCESS; +} + + +/** + * Open the VBox R3 Guest Library. This should be called by system daemons + * and processes. + */ +VBGLR3DECL(int) VbglR3Init(void) +{ + return vbglR3Init(VBOXGUEST_DEVICE_NAME); +} + + +/** + * Open the VBox R3 Guest Library. Equivalent to VbglR3Init, but for user + * session processes. + */ +VBGLR3DECL(int) VbglR3InitUser(void) +{ + return vbglR3Init(VBOXGUEST_USER_DEVICE_NAME); +} + + +VBGLR3DECL(void) VbglR3Term(void) +{ + /* + * Decrement the reference count and see if we're the last one out. + */ + uint32_t cInits = ASMAtomicDecU32(&g_cInits); + if (cInits > 0) + return; +#if !defined(VBOX_VBGLR3_XSERVER) + AssertReturnVoid(!cInits); + +# if defined(RT_OS_WINDOWS) + HANDLE hFile = g_hFile; + g_hFile = INVALID_HANDLE_VALUE; + AssertReturnVoid(hFile != INVALID_HANDLE_VALUE); + BOOL fRc = CloseHandle(hFile); + Assert(fRc); NOREF(fRc); + +# elif defined(RT_OS_OS2) + RTFILE File = g_File; + g_File = NIL_RTFILE; + AssertReturnVoid(File != NIL_RTFILE); + APIRET rc = DosClose((uintptr_t)File); + AssertMsg(!rc, ("%ld\n", rc)); + +#elif defined(RT_OS_DARWIN) + io_connect_t uConnection = g_uConnection; + RTFILE hFile = g_File; + g_uConnection = 0; + g_File = NIL_RTFILE; + kern_return_t kr = IOServiceClose(uConnection); + AssertMsg(kr == kIOReturnSuccess, ("%#x (%d)\n", kr, kr)); NOREF(kr); + int rc = RTFileClose(hFile); + AssertRC(rc); + +# else /* The IPRT case. */ + RTFILE File = g_File; + g_File = NIL_RTFILE; + AssertReturnVoid(File != NIL_RTFILE); + int rc = RTFileClose(File); + AssertRC(rc); +# endif + +#else /* VBOX_VBGLR3_XSERVER */ + int File = g_File; + g_File = -1; + if (File == -1) + return; + xf86close(File); +#endif /* VBOX_VBGLR3_XSERVER */ +} + + +/** + * Internal wrapper around various OS specific ioctl implementations. + * + * @returns VBox status code as returned by VBoxGuestCommonIOCtl, or + * an failure returned by the OS specific ioctl APIs. + * + * @param uFunction The requested function. + * @param pHdr The input and output request buffer. + * @param cbReq The size of the request buffer. + */ +int vbglR3DoIOCtlRaw(uintptr_t uFunction, PVBGLREQHDR pHdr, size_t cbReq) +{ + Assert(cbReq == RT_MAX(pHdr->cbIn, pHdr->cbOut)); RT_NOREF1(cbReq); + Assert(pHdr->cbOut != 0); + +#if defined(RT_OS_WINDOWS) +# if 0 /*def USE_NT_DEVICE_IO_CONTROL_FILE*/ + IO_STATUS_BLOCK Ios; + Ios.Status = -1; + Ios.Information = 0; + NTSTATUS rcNt = NtDeviceIoControlFile(g_hFile, NULL /*hEvent*/, NULL /*pfnApc*/, NULL /*pvApcCtx*/, &Ios, + (ULONG)uFunction, + pHdr /*pvInput */, pHdr->cbIn /* cbInput */, + pHdr /*pvOutput*/, pHdr->cbOut /* cbOutput */); + if (NT_SUCCESS(rcNt)) + { + if (NT_SUCCESS(Ios.Status)) + return VINF_SUCCESS; + rcNt = Ios.Status; + } + return RTErrConvertFromNtStatus(rcNt); + +# else + DWORD cbReturned = (ULONG)pHdr->cbOut; + if (DeviceIoControl(g_hFile, uFunction, pHdr, pHdr->cbIn, pHdr, cbReturned, &cbReturned, NULL)) + return 0; + return RTErrConvertFromWin32(GetLastError()); +# endif + +#elif defined(RT_OS_OS2) + ULONG cbOS2Parm = cbReq; + APIRET rc = DosDevIOCtl((uintptr_t)g_File, VBGL_IOCTL_CATEGORY, uFunction, pHdr, cbReq, &cbOS2Parm, NULL, 0, NULL); + if (RT_LIKELY(rc == NO_ERROR)) + return VINF_SUCCESS; + return RTErrConvertFromOS2(rc); + +#elif defined(VBOX_VBGLR3_XSERVER) + if (g_File != -1) + { + if (RT_LIKELY(xf86ioctl((int)g_File, uFunction, pHdr) >= 0)) + return VINF_SUCCESS; + return VERR_FILE_IO_ERROR; + } + return VERR_INVALID_HANDLE; + +#else + if (g_File != NIL_RTFILE) + { + if (RT_LIKELY(ioctl((int)(intptr_t)g_File, uFunction, pHdr) >= 0)) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); + } + return VERR_INVALID_HANDLE; +#endif +} + + +/** + * Internal wrapper around various OS specific ioctl implementations, that + * returns the status from the header. + * + * @returns VBox status code as returned by VBoxGuestCommonIOCtl, or + * an failure returned by the OS specific ioctl APIs. + * + * @param uFunction The requested function. + * @param pHdr The input and output request buffer. + * @param cbReq The size of the request buffer. + */ +int vbglR3DoIOCtl(uintptr_t uFunction, PVBGLREQHDR pHdr, size_t cbReq) +{ + int rc = vbglR3DoIOCtlRaw(uFunction, pHdr, cbReq); + if (RT_SUCCESS(rc)) + rc = pHdr->rc; + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAdditions.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAdditions.cpp new file mode 100644 index 00000000..2ee88509 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAdditions.cpp @@ -0,0 +1,363 @@ +/* $Id: VBoxGuestR3LibAdditions.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Additions Info. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/string.h> +#ifdef RT_OS_WINDOWS +# include <iprt/utf16.h> +#endif +#include <VBox/log.h> +#include <VBox/version.h> +#include "VBoxGuestR3LibInternal.h" + + + +#ifdef RT_OS_WINDOWS + +/** + * Opens the "VirtualBox Guest Additions" registry key. + * + * @returns IPRT status code + * @param phKey Receives key handle on success. The returned handle must + * be closed by calling vbglR3WinCloseRegKey. + */ +static int vbglR3WinOpenAdditionRegisterKey(PHKEY phKey) +{ + /* + * Current vendor first. We keep the older ones just for the case that + * the caller isn't actually installed yet (no real use case AFAIK). + */ + static PCRTUTF16 s_apwszKeys[] = + { + L"SOFTWARE\\" RT_LSTR(VBOX_VENDOR_SHORT) L"\\VirtualBox Guest Additions", +#ifdef RT_ARCH_AMD64 + L"SOFTWARE\\Wow6432Node\\" RT_LSTR(VBOX_VENDOR_SHORT) L"\\VirtualBox Guest Additions", +#endif + L"SOFTWARE\\Sun\\VirtualBox Guest Additions", +#ifdef RT_ARCH_AMD64 + L"SOFTWARE\\Wow6432Node\\Sun\\VirtualBox Guest Additions", +#endif + L"SOFTWARE\\Sun\\xVM VirtualBox Guest Additions", +#ifdef RT_ARCH_AMD64 + L"SOFTWARE\\Wow6432Node\\Sun\\xVM VirtualBox Guest Additions", +#endif + }; + int rc = VERR_NOT_FOUND; + for (uint32_t i = 0; i < RT_ELEMENTS(s_apwszKeys); i++) + { + LSTATUS lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, s_apwszKeys[i], 0 /* ulOptions*/, KEY_READ, phKey); + if (lrc == ERROR_SUCCESS) + return VINF_SUCCESS; + if (i == 0) + rc = RTErrConvertFromWin32(lrc); + } + return rc; +} + + +/** + * Closes the registry handle returned by vbglR3WinOpenAdditionRegisterKey(). + * + * @returns @a rc or IPRT failure status. + * @param hKey Handle to close. + * @param rc The current IPRT status of the operation. Error + * condition takes precedence over errors from this call. + */ +static int vbglR3WinCloseRegKey(HKEY hKey, int rc) +{ + LSTATUS lrc = RegCloseKey(hKey); + if ( lrc == ERROR_SUCCESS + || RT_FAILURE(rc)) + return rc; + return RTErrConvertFromWin32(lrc); +} + + +/** + * Queries a string value from a specified registry key. + * + * @return IPRT status code. + * @param hKey Handle of registry key to use. + * @param pwszValueName The name of the value to query. + * @param cbHint Size hint. + * @param ppszValue Where to return value string on success. Free + * with RTStrFree. + */ +static int vbglR3QueryRegistryString(HKEY hKey, PCRTUTF16 pwszValueName, uint32_t cbHint, char **ppszValue) +{ + AssertPtr(pwszValueName); + AssertPtrReturn(ppszValue, VERR_INVALID_POINTER); + + /* + * First try. + */ + int rc; + DWORD dwType; + DWORD cbTmp = cbHint; + PRTUTF16 pwszTmp = (PRTUTF16)RTMemTmpAllocZ(cbTmp + sizeof(RTUTF16)); + if (pwszTmp) + { + LSTATUS lrc = RegQueryValueExW(hKey, pwszValueName, NULL, &dwType, (BYTE *)pwszTmp, &cbTmp); + if (lrc == ERROR_MORE_DATA) + { + /* + * Allocate larger buffer and try again. + */ + RTMemTmpFree(pwszTmp); + cbTmp += 16; + pwszTmp = (PRTUTF16)RTMemTmpAllocZ(cbTmp + sizeof(RTUTF16)); + if (!pwszTmp) + { + *ppszValue = NULL; + return VERR_NO_TMP_MEMORY; + } + lrc = RegQueryValueExW(hKey, pwszValueName, NULL, &dwType, (BYTE *)pwszTmp, &cbTmp); + } + if (lrc == ERROR_SUCCESS) + { + /* + * Check the type and convert to UTF-8. + */ + if (dwType == REG_SZ) + rc = RTUtf16ToUtf8(pwszTmp, ppszValue); + else + rc = VERR_WRONG_TYPE; + } + else + rc = RTErrConvertFromWin32(lrc); + RTMemTmpFree(pwszTmp); + } + else + rc = VERR_NO_TMP_MEMORY; + if (RT_SUCCESS(rc)) + return rc; + *ppszValue = NULL; + return rc; +} + +#endif /* RT_OS_WINDOWS */ + + +/** + * Fallback for VbglR3GetAdditionsVersion. + * + * @copydoc VbglR3GetAdditionsVersion + */ +static int vbglR3GetAdditionsCompileTimeVersion(char **ppszVer, char **ppszVerExt, char **ppszRev) +{ + int rc = VINF_SUCCESS; + if (ppszVer) + rc = RTStrDupEx(ppszVer, VBOX_VERSION_STRING_RAW); + if (RT_SUCCESS(rc)) + { + if (ppszVerExt) + rc = RTStrDupEx(ppszVerExt, VBOX_VERSION_STRING); + if (RT_SUCCESS(rc)) + { + if (ppszRev) + rc = RTStrDupEx(ppszRev, RT_XSTR(VBOX_SVN_REV)); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + /* bail out: */ + } + if (ppszVerExt) + { + RTStrFree(*ppszVerExt); + *ppszVerExt = NULL; + } + } + if (ppszVer) + { + RTStrFree(*ppszVer); + *ppszVer = NULL; + } + return rc; +} + + +/** + * Retrieves the installed Guest Additions version and/or revision. + * + * @returns IPRT status code + * @param ppszVer Receives pointer of allocated raw version string + * (major.minor.build). NULL is accepted. The returned + * pointer must be freed using RTStrFree(). + * @param ppszVerExt Receives pointer of allocated full version string + * (raw version + vendor suffix(es)). NULL is + * accepted. The returned pointer must be freed using + * RTStrFree(). + * @param ppszRev Receives pointer of allocated revision string. NULL is + * accepted. The returned pointer must be freed using + * RTStrFree(). + */ +VBGLR3DECL(int) VbglR3GetAdditionsVersion(char **ppszVer, char **ppszVerExt, char **ppszRev) +{ + /* + * Zap the return value up front. + */ + if (ppszVer) + *ppszVer = NULL; + if (ppszVerExt) + *ppszVerExt = NULL; + if (ppszRev) + *ppszRev = NULL; + +#ifdef RT_OS_WINDOWS + HKEY hKey; + int rc = vbglR3WinOpenAdditionRegisterKey(&hKey); + if (RT_SUCCESS(rc)) + { + /* + * Version. + */ + if (ppszVer) + rc = vbglR3QueryRegistryString(hKey, L"Version", 64, ppszVer); + + if ( RT_SUCCESS(rc) + && ppszVerExt) + rc = vbglR3QueryRegistryString(hKey, L"VersionExt", 128, ppszVerExt); + + /* + * Revision. + */ + if ( RT_SUCCESS(rc) + && ppszRev) + rc = vbglR3QueryRegistryString(hKey, L"Revision", 64, ppszRev); + + rc = vbglR3WinCloseRegKey(hKey, rc); + + /* Clean up allocated strings on error. */ + if (RT_FAILURE(rc)) + { + if (ppszVer) + { + RTStrFree(*ppszVer); + *ppszVer = NULL; + } + if (ppszVerExt) + { + RTStrFree(*ppszVerExt); + *ppszVerExt = NULL; + } + if (ppszRev) + { + RTStrFree(*ppszRev); + *ppszRev = NULL; + } + } + } + /* + * No registry entries found, return the version string compiled into this binary. + */ + else + rc = vbglR3GetAdditionsCompileTimeVersion(ppszVer, ppszVerExt, ppszRev); + return rc; + +#else /* !RT_OS_WINDOWS */ + /* + * On non-Windows platforms just return the compile-time version string. + */ + return vbglR3GetAdditionsCompileTimeVersion(ppszVer, ppszVerExt, ppszRev); +#endif /* !RT_OS_WINDOWS */ +} + + +/** + * Retrieves the installation path of Guest Additions. + * + * @returns IPRT status code + * @param ppszPath Receives pointer of allocated installation path string. + * The returned pointer must be freed using + * RTStrFree(). + */ +VBGLR3DECL(int) VbglR3GetAdditionsInstallationPath(char **ppszPath) +{ + int rc; + +#ifdef RT_OS_WINDOWS + /* + * Get it from the registry. + */ + HKEY hKey; + rc = vbglR3WinOpenAdditionRegisterKey(&hKey); + if (RT_SUCCESS(rc)) + { + rc = vbglR3QueryRegistryString(hKey, L"InstallDir", MAX_PATH * sizeof(RTUTF16), ppszPath); + if (RT_SUCCESS(rc)) + RTPathChangeToUnixSlashes(*ppszPath, true /*fForce*/); + rc = vbglR3WinCloseRegKey(hKey, rc); + } +#else + /** @todo implement me */ + rc = VERR_NOT_IMPLEMENTED; + RT_NOREF1(ppszPath); +#endif + return rc; +} + + +/** + * Reports the Guest Additions status of a certain facility to the host. + * + * @returns IPRT status code + * @param enmFacility The facility to report the status on. + * @param enmStatus The new status of the facility. + * @param fReserved Flags reserved for future hacks. + */ +VBGLR3DECL(int) VbglR3ReportAdditionsStatus(VBoxGuestFacilityType enmFacility, VBoxGuestFacilityStatus enmStatus, + uint32_t fReserved) +{ + VMMDevReportGuestStatus Report; + RT_ZERO(Report); + int rc = vmmdevInitRequest(&Report.header, VMMDevReq_ReportGuestStatus); + if (RT_SUCCESS(rc)) + { + Report.guestStatus.facility = enmFacility; + Report.guestStatus.status = enmStatus; + Report.guestStatus.flags = fReserved; + + rc = vbglR3GRPerform(&Report.header); + } + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAutoLogon.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAutoLogon.cpp new file mode 100644 index 00000000..680e6a5c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAutoLogon.cpp @@ -0,0 +1,130 @@ +/* $Id: VBoxGuestR3LibAutoLogon.cpp $ */ +/** @file + * VBoxGuestR3LibAutoLogon - Ring-3 utility functions for auto-logon modules + * (VBoxGINA / VBoxCredProv / pam_vbox). + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#endif + +#include "VBoxGuestR3LibInternal.h" +#include <iprt/errcore.h> + + +/** + * Reports the current auto-logon 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. + */ +VBGLR3DECL(int) VbglR3AutoLogonReportStatus(VBoxGuestFacilityStatus enmStatus) +{ + /* + * VBoxGuestFacilityStatus_Failed is sticky. + */ + static VBoxGuestFacilityStatus s_enmLastStatus = VBoxGuestFacilityStatus_Inactive; + if (s_enmLastStatus != VBoxGuestFacilityStatus_Failed) + { + int rc = VbglR3ReportAdditionsStatus(VBoxGuestFacilityType_AutoLogon, enmStatus, 0 /* Flags */); + if (rc == VERR_NOT_SUPPORTED) + { + /* + * To maintain backwards compatibility to older hosts which don't have + * VMMDevReportGuestStatus implemented we set the appropriate status via + * guest property to have at least something. + */ +#ifdef VBOX_WITH_GUEST_PROPS + HGCMCLIENTID idClient = 0; + rc = VbglR3GuestPropConnect(&idClient); + if (RT_SUCCESS(rc)) + { + const char *pszStatus; + switch (enmStatus) + { + case VBoxGuestFacilityStatus_Inactive: pszStatus = "Inactive"; break; + case VBoxGuestFacilityStatus_Paused: pszStatus = "Disabled"; break; + case VBoxGuestFacilityStatus_PreInit: pszStatus = "PreInit"; break; + case VBoxGuestFacilityStatus_Init: pszStatus = "Init"; break; + case VBoxGuestFacilityStatus_Active: pszStatus = "Active"; break; + case VBoxGuestFacilityStatus_Terminating: pszStatus = "Terminating"; break; + case VBoxGuestFacilityStatus_Terminated: pszStatus = "Terminated"; break; + case VBoxGuestFacilityStatus_Failed: pszStatus = "Failed"; break; + default: pszStatus = NULL; + } + if (pszStatus) + { + /* + * Use TRANSRESET when possible, fall back to TRANSIENT + * (generally sufficient unless the guest misbehaves). + */ + static const char s_szPath[] = "/VirtualBox/GuestInfo/OS/AutoLogonStatus"; + rc = VbglR3GuestPropWrite(idClient, s_szPath, pszStatus, "TRANSRESET"); + if (rc == VERR_PARSE_ERROR) + rc = VbglR3GuestPropWrite(idClient, s_szPath, pszStatus, "TRANSIENT"); + } + else + rc = VERR_INVALID_PARAMETER; + + VbglR3GuestPropDisconnect(idClient); + } +#endif + } + + s_enmLastStatus = enmStatus; + } + return VINF_SUCCESS; +} + + +/** + * Detects whether our process is running in a remote session or not. + * + * @return bool true if running in a remote session, false if not. + */ +VBGLR3DECL(bool) VbglR3AutoLogonIsRemoteSession(void) +{ +#ifdef RT_OS_WINDOWS + return GetSystemMetrics(SM_REMOTESESSION) != 0 ? true : false; +#else + return false; /* Not implemented. */ +#endif +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibBalloon.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibBalloon.cpp new file mode 100644 index 00000000..b7b2a8f8 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibBalloon.cpp @@ -0,0 +1,83 @@ +/* $Id: VBoxGuestR3LibBalloon.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Ballooning. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR3LibInternal.h" +#include <iprt/string.h> + + +/** + * Refresh the memory balloon after a change. + * + * @returns IPRT status code. + * @param pcChunks The size of the balloon in chunks of 1MB (out). + * @param pfHandleInR3 Allocating of memory in R3 required (out). + */ +VBGLR3DECL(int) VbglR3MemBalloonRefresh(uint32_t *pcChunks, bool *pfHandleInR3) +{ + VBGLIOCCHECKBALLOON Info; + VBGLREQHDR_INIT(&Info.Hdr, CHECK_BALLOON); + int rc = vbglR3DoIOCtl(VBGL_IOCTL_CHECK_BALLOON, &Info.Hdr, sizeof(Info)); + if (RT_SUCCESS(rc)) + { + *pcChunks = Info.u.Out.cBalloonChunks; + *pfHandleInR3 = Info.u.Out.fHandleInR3 != false; + } + return rc; +} + + +/** + * Change the memory by granting/reclaiming memory to/from R0. + * + * @returns IPRT status code. + * @param pv Memory chunk (1MB). + * @param fInflate true = inflate balloon (grant memory). + * false = deflate balloon (reclaim memory). + */ +VBGLR3DECL(int) VbglR3MemBalloonChange(void *pv, bool fInflate) +{ + VBGLIOCCHANGEBALLOON Info; + VBGLREQHDR_INIT(&Info.Hdr, CHANGE_BALLOON); + Info.u.In.pvChunk = pv; + Info.u.In.fInflate = fInflate; + RT_ZERO(Info.u.In.abPadding); + return vbglR3DoIOCtl(VBGL_IOCTL_CHANGE_BALLOON, &Info.Hdr, sizeof(Info)); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp new file mode 100644 index 00000000..f44b923a --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp @@ -0,0 +1,2609 @@ +/* $Id: VBoxGuestR3LibClipboard.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared Clipboard. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/GuestHost/clipboard-helper.h> +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include <VBox/GuestHost/SharedClipboard-transfers.h> +#endif +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/err.h> +#include <iprt/assert.h> +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include <iprt/dir.h> +# include <iprt/file.h> +# include <iprt/path.h> +#endif +#include <iprt/string.h> +#include <iprt/cpp/ministring.h> + +#include "VBoxGuestR3LibInternal.h" + + +/* + * Function naming convention: + * + * FunctionNameRecv = Receives a host message (request). + * FunctionNameReply = Replies to a host message (request). + * FunctionNameSend = Sends a guest message to the host. + */ + + +/********************************************************************************************************************************* +* Prototypes * +*********************************************************************************************************************************/ + + +/** + * Connects to the Shared Clipboard service, legacy version, do not use anymore. + * + * @returns VBox status code + * @param pidClient Where to put the client id on success. The client id + * must be passed to all the other clipboard calls. + */ +VBGLR3DECL(int) VbglR3ClipboardConnect(HGCMCLIENTID *pidClient) +{ + int rc = VbglR3HGCMConnect("VBoxSharedClipboard", pidClient); + if (RT_FAILURE(rc)) + { + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) + LogRel(("Shared Clipboard: Unabled to connect, as host service was not found, skipping\n")); + else + LogRel(("Shared Clipboard: Unabled to connect to host service, rc=%Rrc\n", rc)); + } + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Connects to the Shared Clipboard service, extended version. + * + * @returns VBox status code. + * @param pCtx Command context. This will be initialized by this + * call. + * @param fGuestFeatures The guest features supported by this client, + * VBOX_SHCL_GF_0_XXX. + */ +VBGLR3DECL(int) VbglR3ClipboardConnectEx(PVBGLR3SHCLCMDCTX pCtx, uint64_t fGuestFeatures) +{ + /* + * Intialize the context structure. + */ + pCtx->idClient = 0; + pCtx->fGuestFeatures = fGuestFeatures; + pCtx->fHostFeatures = 0; + pCtx->fUseLegacyProtocol = true; + pCtx->cParmsRecived = 0; + pCtx->idContext = 0; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* Init callback table. */ + RT_ZERO(pCtx->Transfers.Callbacks); + /* Indicate that this guest supports Shared Clipboard file transfers. */ + pCtx->fGuestFeatures |= VBOX_SHCL_GF_0_TRANSFERS; +# ifdef RT_OS_WINDOWS + /* Indicate that on Windows guest OSes we have our own IDataObject implementation which + * integrates nicely into the guest's Windows Explorer showing / handling the Shared Clipboard file transfers. */ + pCtx->fGuestFeatures |= VBOX_SHCL_GF_0_TRANSFERS_FRONTEND; +# endif + pCtx->Transfers.cbChunkSize = VBOX_SHCL_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */ + pCtx->Transfers.cbMaxChunkSize = VBOX_SHCL_MAX_CHUNK_SIZE; /** @todo Ditto. */ +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + /* + * First step is connecting to the HGCM service. + */ + int rc = VbglR3ClipboardConnect(&pCtx->idClient); + if (RT_SUCCESS(rc)) + { + /* + * Next is reporting our features. If this fails, assume older host. + */ + rc = VbglR3ClipboardReportFeatures(pCtx->idClient, pCtx->fGuestFeatures, &pCtx->fHostFeatures); + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: Guest features: %#RX64 - Host features: %#RX64\n", + pCtx->fGuestFeatures, pCtx->fHostFeatures)); + + if ( (pCtx->fHostFeatures & VBOX_SHCL_HF_0_CONTEXT_ID) + && (pCtx->fGuestFeatures & VBOX_SHCL_GF_0_CONTEXT_ID) ) + { + pCtx->fUseLegacyProtocol = false; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if ( (pCtx->fHostFeatures & VBOX_SHCL_HF_0_TRANSFERS) + && (pCtx->fGuestFeatures & VBOX_SHCL_GF_0_TRANSFERS) ) + { + VBoxShClParmNegotiateChunkSize MsgChunkSize; + do + { + VBGL_HGCM_HDR_INIT(&MsgChunkSize.hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE, + VBOX_SHCL_CPARMS_NEGOTIATE_CHUNK_SIZE); + MsgChunkSize.cb32MaxChunkSize.SetUInt32(pCtx->Transfers.cbMaxChunkSize); + MsgChunkSize.cb32ChunkSize.SetUInt32(0); /* If set to 0, let the host choose. */ + rc = VbglR3HGCMCall(&MsgChunkSize.hdr, sizeof(MsgChunkSize)); + } while (rc == VERR_INTERRUPTED); + if (RT_SUCCESS(rc)) + { + Assert(MsgChunkSize.cb32ChunkSize.type == VMMDevHGCMParmType_32bit); + pCtx->Transfers.cbChunkSize = RT_MIN(MsgChunkSize.cb32ChunkSize.u.value32, pCtx->Transfers.cbChunkSize); + Assert(MsgChunkSize.cb32MaxChunkSize.type == VMMDevHGCMParmType_32bit); + pCtx->Transfers.cbMaxChunkSize = RT_MIN(MsgChunkSize.cb32MaxChunkSize.u.value32, pCtx->Transfers.cbMaxChunkSize); + + LogRel2(("Shared Clipboard: Using chunk size %RU32 (maximum is %RU32)\n", + pCtx->Transfers.cbChunkSize, pCtx->Transfers.cbMaxChunkSize)); + } + } + else + { + if (!(pCtx->fHostFeatures & VBOX_SHCL_HF_0_TRANSFERS)) + LogRel2(("Shared Clipboard: Host does not support transfers\n")); + } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + } + else + { + if (!(pCtx->fHostFeatures & VBOX_SHCL_HF_0_CONTEXT_ID)) + LogRel(("Shared Clipboard: Host does not support context IDs, using legacy protocol\n")); + + pCtx->fUseLegacyProtocol = true; + } + } + else + { + AssertLogRelMsg(rc == VERR_NOT_SUPPORTED || rc == VERR_NOT_IMPLEMENTED, + ("Reporting features failed: %Rrc\n", rc)); + pCtx->fUseLegacyProtocol = true; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Reports features to the host and retrieve host feature set. + * + * @returns VBox status code. + * @param idClient The client ID returned by VbglR3ClipboardConnect(). + * @param fGuestFeatures Features to report, VBOX_SHCL_GF_XXX. + * @param pfHostFeatures Where to store the features VBOX_SHCL_HF_XXX. + */ +VBGLR3DECL(int) VbglR3ClipboardReportFeatures(uint32_t idClient, uint64_t fGuestFeatures, uint64_t *pfHostFeatures) +{ + int rc; + do + { + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter f64Features0; + HGCMFunctionParameter f64Features1; + } Msg; + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, 2); + VbglHGCMParmUInt64Set(&Msg.f64Features0, fGuestFeatures); + VbglHGCMParmUInt64Set(&Msg.f64Features1, VBOX_SHCL_GF_1_MUST_BE_ONE); + + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Assert(Msg.f64Features0.type == VMMDevHGCMParmType_64bit); + Assert(Msg.f64Features1.type == VMMDevHGCMParmType_64bit); + if (Msg.f64Features1.u.value64 & VBOX_SHCL_GF_1_MUST_BE_ONE) + rc = VERR_NOT_SUPPORTED; + else if (pfHostFeatures) + *pfHostFeatures = Msg.f64Features0.u.value64; + break; + } + } while (rc == VERR_INTERRUPTED); + return rc; + +} + + +/** + * Disconnects from the Shared Clipboard service, legacy version, do not use anymore. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3ClipboardConnect(). + */ +VBGLR3DECL(int) VbglR3ClipboardDisconnect(HGCMCLIENTID idClient) +{ + return VbglR3HGCMDisconnect(idClient); +} + + +/** + * Disconnects from the Shared Clipboard service, extended version. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + */ +VBGLR3DECL(int) VbglR3ClipboardDisconnectEx(PVBGLR3SHCLCMDCTX pCtx) +{ + int rc = VbglR3ClipboardDisconnect(pCtx->idClient); + if (RT_SUCCESS(rc)) + { + pCtx->idClient = 0; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Receives reported formats from the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the + * connection. + * @param pfFormats Where to store the received formats from the host. + */ +static int vbglR3ClipboardFormatsReportRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLFORMATS pfFormats) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pfFormats, VERR_INVALID_POINTER); + + *pfFormats = 0; + + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter id64Context; + HGCMFunctionParameter f32Formats; + } Msg; + + VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_MSG_GET, 2); + Msg.id64Context.SetUInt32(VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + Msg.f32Formats.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.f32Formats.GetUInt32(pfFormats); + AssertRC(rc); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Fetches a VBOX_SHCL_HOST_MSG_READ_DATA_CID message. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pfFormat Where to return the requested format. + */ +static int vbglR3ClipboardFetchReadDataCid(PVBGLR3SHCLCMDCTX pCtx, PSHCLFORMAT pfFormat) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pfFormat, VERR_INVALID_POINTER); + + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter id64Context; + HGCMFunctionParameter f32Format; + } Msg; + + VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_MSG_GET, 2); + Msg.id64Context.SetUInt64(VBOX_SHCL_HOST_MSG_READ_DATA_CID); + Msg.f32Format.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.id64Context.GetUInt64(&pCtx->idContext); + AssertRC(rc); + int rc2 = Msg.f32Format.GetUInt32(pfFormat); + AssertRCStmt(rc2, rc = rc2); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Fetches a VBOX_SHCL_HOST_MSG_READ_DATA message. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pfFormat Where to return the requested format. + */ +static int vbglR3ClipboardFetchReadData(PVBGLR3SHCLCMDCTX pCtx, PSHCLFORMAT pfFormat) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pfFormat, VERR_INVALID_POINTER); + + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter id32Msg; + HGCMFunctionParameter f32Format; + } Msg; + + VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_MSG_GET, 2); + Msg.id32Msg.SetUInt32(VBOX_SHCL_HOST_MSG_READ_DATA); + Msg.f32Format.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.f32Format.GetUInt32(pfFormat); + AssertRC(rc); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Get a host message, legacy version (which does not have VBOX_SHCL_GUEST_FN_MSG_GET). Do not use anymore. + * + * Note: This is the old message which still is being used for the non-URI Shared Clipboard transfers, + * to not break compatibility with older additions / VBox versions. + * + * This will block until a message becomes available. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3ClipboardConnect(). + * @param pidMsg Where to store the message id. + * @param pfFormats Where to store the format(s) the message applies to. + */ +VBGLR3DECL(int) VbglR3ClipboardGetHostMsgOld(HGCMCLIENTID idClient, uint32_t *pidMsg, uint32_t *pfFormats) +{ + VBoxShClGetHostMsgOld Msg; + + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, VBOX_SHCL_CPARMS_GET_HOST_MSG_OLD); + VbglHGCMParmUInt32Set(&Msg.msg, 0); + VbglHGCMParmUInt32Set(&Msg.formats, 0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + int rc2 = VbglHGCMParmUInt32Get(&Msg.msg, pidMsg); + if (RT_SUCCESS(rc)) + { + rc2 = VbglHGCMParmUInt32Get(&Msg.formats, pfFormats); + if (RT_SUCCESS(rc2)) + return rc; + } + rc = rc2; + } + *pidMsg = UINT32_MAX - 1; + *pfFormats = UINT32_MAX; + return rc; +} + + +/** + * Reads data from the host clipboard. + * + * Legacy function, do not use anymore. + * + * @returns VBox status code. + * @retval VINF_BUFFER_OVERFLOW If there is more data available than the caller provided buffer space for. + * + * @param idClient The client id returned by VbglR3ClipboardConnect(). + * @param fFormat The format we're requesting the data in. + * @param pvData Where to store the data. + * @param cbData The size of the buffer pointed to by \a pvData. + * @param pcbRead The actual size of the host clipboard data. May be larger than \a cbData. + */ +VBGLR3DECL(int) VbglR3ClipboardReadData(HGCMCLIENTID idClient, uint32_t fFormat, void *pvData, uint32_t cbData, + uint32_t *pcbRead) +{ + LogFlowFuncEnter(); + + struct + { + VBGLIOCHGCMCALL Hdr; + VBoxShClParmDataRead Parms; + } Msg; + + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, VBOX_SHCL_GUEST_FN_DATA_READ, VBOX_SHCL_CPARMS_DATA_READ); + VbglHGCMParmUInt32Set(&Msg.Parms.f32Format, fFormat); + VbglHGCMParmPtrSet( &Msg.Parms.pData, pvData, cbData); + VbglHGCMParmUInt32Set(&Msg.Parms.cb32Needed, 0); + + int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + uint32_t cbRead; + rc = VbglHGCMParmUInt32Get(&Msg.Parms.cb32Needed, &cbRead); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("cbRead=%RU32\n", cbRead)); + + if (cbRead > cbData) + rc = VINF_BUFFER_OVERFLOW; + + *pcbRead = cbRead; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Reads clipboard data from the host clipboard. + * + * @returns VBox status code. + * @retval VINF_BUFFER_OVERFLOW If there is more data available than the caller provided buffer space for. + * + * @param pCtx The command context returned by VbglR3ClipboardConnectEx(). + * @param uFormat Clipboard format of clipboard data to be read. + * @param pvData Buffer where to store the read data. + * @param cbData Size (in bytes) of data buffer where to store the read data. + * @param pcbRead The actual size of the host clipboard data. + */ +VBGLR3DECL(int) VbglR3ClipboardReadDataEx(PVBGLR3SHCLCMDCTX pCtx, + SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbRead) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + return VbglR3ClipboardReadData(pCtx->idClient, uFormat, pvData, cbData, pcbRead); +} + + +/** + * Query the host features. + * + * @returns VBox status code. + * @param idClient The client ID returned by VbglR3ClipboardConnect(). + * @param pfHostFeatures Where to store the host feature, VBOX_SHCL_HF_XXX. + */ +VBGLR3DECL(int) VbglR3ClipboardQueryFeatures(uint32_t idClient, uint64_t *pfHostFeatures) +{ + int rc; + do + { + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter f64Features0; + HGCMFunctionParameter f64Features1; + } Msg; + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, VBOX_SHCL_GUEST_FN_QUERY_FEATURES, 2); + VbglHGCMParmUInt64Set(&Msg.f64Features0, 0); + VbglHGCMParmUInt64Set(&Msg.f64Features1, RT_BIT_64(63)); + + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Assert(Msg.f64Features0.type == VMMDevHGCMParmType_64bit); + Assert(Msg.f64Features1.type == VMMDevHGCMParmType_64bit); + if (Msg.f64Features1.u.value64 & RT_BIT_64(63)) + rc = VERR_NOT_SUPPORTED; + else if (pfHostFeatures) + *pfHostFeatures = Msg.f64Features0.u.value64; + break; + } + } while (rc == VERR_INTERRUPTED); + return rc; + +} + +/** + * Peeks at the next host message, waiting for one to turn up. + * + * This glosses over the difference between new (6.1) and old (1.3.2) host + * service versions, however it does so by abusing @a pcParameters, so don't use + * it directly when in legacy mode, always pass it on to + * VbglR3ClipboardEventGetNext() or VbglR3ClipboardEventGetNextEx(). + * + * @returns VBox status code. + * @retval VERR_INTERRUPTED if interrupted. Does the necessary cleanup, so + * caller just have to repeat this call. + * @retval VERR_VM_RESTORED if the VM has been restored (idRestoreCheck). + * + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pidMsg Where to store the message id. + * @param pcParameters Where to store the number of parameters which will + * be received in a second call to the host. + * @param pidRestoreCheck Pointer to the VbglR3GetSessionId() variable to use + * for the VM restore check. Optional. + * + * @note Restore check is only performed optimally with a 6.0 host. + */ +VBGLR3DECL(int) VbglR3ClipboardMsgPeekWait(PVBGLR3SHCLCMDCTX pCtx, uint32_t *pidMsg, + uint32_t *pcParameters, uint64_t *pidRestoreCheck) +{ + AssertPtrReturn(pidMsg, VERR_INVALID_POINTER); + AssertPtrReturn(pcParameters, VERR_INVALID_POINTER); + + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter idMsg; /* Doubles as restore check on input. */ + HGCMFunctionParameter cParameters; + } Msg; + int rc; + if (!pCtx->fUseLegacyProtocol) + { + VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT, 2); + VbglHGCMParmUInt64Set(&Msg.idMsg, pidRestoreCheck ? *pidRestoreCheck : 0); + VbglHGCMParmUInt32Set(&Msg.cParameters, 0); + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + LogFlowFunc(("VbglR3HGCMCall -> %Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + AssertMsgReturn( Msg.idMsg.type == VMMDevHGCMParmType_64bit + && Msg.cParameters.type == VMMDevHGCMParmType_32bit, + ("msg.type=%d num_parms.type=%d\n", Msg.idMsg.type, Msg.cParameters.type), + VERR_INTERNAL_ERROR_3); + + *pidMsg = (uint32_t)Msg.idMsg.u.value64; + *pcParameters = Msg.cParameters.u.value32; + return rc; + } + + /* + * If restored, update pidRestoreCheck. + */ + if (rc == VERR_VM_RESTORED && pidRestoreCheck) + *pidRestoreCheck = Msg.idMsg.u.value64; + } + else + { + /* + * We do some crude stuff here by putting the 2nd parameter (foramts) in the parameter count, + * however it's supposed to be passed directly to VbglR3ClipboardEventGetNext or + * VbglR3ClipboardEventGetNextEx, so that's fine... + */ + rc = VbglR3ClipboardGetHostMsgOld(pCtx->idClient, pidMsg, pcParameters); + if (RT_SUCCESS(rc)) + return rc; + } + + /* + * If interrupted we must cancel the call so it doesn't prevent us from making another one. + */ + if (rc == VERR_INTERRUPTED) + { + VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_MSG_CANCEL, 0); + int rc2 = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg.Hdr)); + AssertRC(rc2); + } + + *pidMsg = UINT32_MAX - 1; + *pcParameters = UINT32_MAX - 2; + return rc; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + +/** + * Reads a root list header from the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pRootListHdr Where to store the received root list header. + */ +static int vbglR3ClipboardRootListHdrRead(PVBGLR3SHCLCMDCTX pCtx, PSHCLROOTLISTHDR pRootListHdr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pRootListHdr, VERR_INVALID_POINTER); + + VBoxShClRootListHdrMsg Msg; + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ, VBOX_SHCL_CPARMS_ROOT_LIST_HDR_READ); + + Msg.ReqParms.uContext.SetUInt64(pCtx->idContext); + Msg.ReqParms.fRoots.SetUInt32(0); + + Msg.cRoots.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.ReqParms.fRoots.GetUInt32(&pRootListHdr->fRoots); AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = Msg.cRoots.GetUInt32(&pRootListHdr->cRoots); + AssertRC(rc); + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Reads a root list entry from the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param uIndex Index of root list entry to read. + * @param pRootListEntry Where to store the root list entry read from the host. + */ +static int vbglR3ClipboardRootListEntryRead(PVBGLR3SHCLCMDCTX pCtx, uint32_t uIndex, PSHCLROOTLISTENTRY pRootListEntry) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pRootListEntry, VERR_INVALID_POINTER); + + VBoxShClRootListEntryMsg Msg; + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ, VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_READ); + + Msg.Parms.uContext.SetUInt64(pCtx->idContext); + Msg.Parms.fInfo.SetUInt32(pRootListEntry->fInfo); + Msg.Parms.uIndex.SetUInt32(uIndex); + + Msg.szName.SetPtr(pRootListEntry->pszName, pRootListEntry->cbName); + Msg.cbInfo.SetUInt32(pRootListEntry->cbInfo); + Msg.pvInfo.SetPtr(pRootListEntry->pvInfo, pRootListEntry->cbInfo); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.Parms.fInfo.GetUInt32(&pRootListEntry->fInfo); AssertRC(rc); + if (RT_SUCCESS(rc)) + { + uint32_t cbInfo = 0; + rc = Msg.cbInfo.GetUInt32(&cbInfo); AssertRC(rc); + if (pRootListEntry->cbInfo != cbInfo) + rc = VERR_INVALID_PARAMETER; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Reads the root list from the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param ppRootList Where to store the (allocated) root list. Must be free'd by the caller with + * SharedClipboardTransferRootListFree(). + */ +VBGLR3DECL(int) VbglR3ClipboardRootListRead(PVBGLR3SHCLCMDCTX pCtx, PSHCLROOTLIST *ppRootList) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(ppRootList, VERR_INVALID_POINTER); + + int rc; + + PSHCLROOTLIST pRootList = ShClTransferRootListAlloc(); + if (pRootList) + { + SHCLROOTLISTHDR srcRootListHdr; + rc = vbglR3ClipboardRootListHdrRead(pCtx, &srcRootListHdr); + if (RT_SUCCESS(rc)) + { + pRootList->Hdr.cRoots = srcRootListHdr.cRoots; + pRootList->Hdr.fRoots = 0; /** @todo Implement this. */ + + if (srcRootListHdr.cRoots) + { + pRootList->paEntries = + (PSHCLROOTLISTENTRY)RTMemAllocZ(srcRootListHdr.cRoots * sizeof(SHCLROOTLISTENTRY)); + if (pRootList->paEntries) + { + for (uint32_t i = 0; i < srcRootListHdr.cRoots; i++) + { + SHCLROOTLISTENTRY *pEntry = &pRootList->paEntries[i]; + AssertPtr(pEntry); + + rc = ShClTransferRootListEntryInit(pEntry); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardRootListEntryRead(pCtx, i, pEntry); + + if (RT_FAILURE(rc)) + break; + } + } + else + rc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(rc)) + { + *ppRootList = pRootList; + } + else + ShClTransferRootListFree(pRootList); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives a transfer status from the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pEnmDir Where to store the transfer direction for the reported transfer. + * @param pReport Where to store the transfer (status) report. + */ +VBGLR3DECL(int) VbglR3ClipboarTransferStatusRecv(PVBGLR3SHCLCMDCTX pCtx, + PSHCLTRANSFERDIR pEnmDir, PSHCLTRANSFERREPORT pReport) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pReport, VERR_INVALID_POINTER); + AssertPtrReturn(pEnmDir, VERR_INVALID_POINTER); + + VBoxShClTransferStatusMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_TRANSFER_STATUS); + + Msg.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + Msg.enmDir.SetUInt32(0); + Msg.enmStatus.SetUInt32(0); + Msg.rc.SetUInt32(0); + Msg.fFlags.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.uContext.GetUInt64(&pCtx->idContext); AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = Msg.enmDir.GetUInt32((uint32_t *)pEnmDir); + AssertRC(rc); + } + if (RT_SUCCESS(rc)) + { + rc = Msg.enmStatus.GetUInt32(&pReport->uStatus); + AssertRC(rc); + } + if (RT_SUCCESS(rc)) + { + rc = Msg.rc.GetUInt32((uint32_t *)&pReport->rc); + AssertRC(rc); + } + if (RT_SUCCESS(rc)) + { + rc = Msg.fFlags.GetUInt32(&pReport->fFlags); + AssertRC(rc); + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Replies to a transfer report from the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pTransfer Transfer of report to reply to. + * @param uStatus Tranfer status to reply. + * @param rcTransfer Result code (rc) to reply. + */ +VBGLR3DECL(int) VbglR3ClipboardTransferStatusReply(PVBGLR3SHCLCMDCTX pCtx, PSHCLTRANSFER pTransfer, + SHCLTRANSFERSTATUS uStatus, int rcTransfer) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + + RT_NOREF(pTransfer); + + VBoxShClReplyMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN + 1); + + Msg.uContext.SetUInt64(pCtx->idContext); + Msg.enmType.SetUInt32(VBOX_SHCL_REPLYMSGTYPE_TRANSFER_STATUS); + Msg.rc.SetUInt32((uint32_t )rcTransfer); /* int vs. uint32_t */ + Msg.pvPayload.SetPtr(NULL, 0); + + Msg.u.TransferStatus.enmStatus.SetUInt32((uint32_t)uStatus); + + LogFlowFunc(("%s\n", ShClTransferStatusToStr(uStatus))); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives a host request to read a root list header from the guest. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pfRoots Where to store the root list header flags to use, requested by the host. + */ +VBGLR3DECL(int) VbglR3ClipboardRootListHdrReadReq(PVBGLR3SHCLCMDCTX pCtx, uint32_t *pfRoots) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pfRoots, VERR_INVALID_POINTER); + + VBoxShClRootListReadReqMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_ROOT_LIST_HDR_READ_REQ); + + Msg.ReqParms.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ); + Msg.ReqParms.fRoots.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.ReqParms.uContext.GetUInt64(&pCtx->idContext); AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = Msg.ReqParms.fRoots.GetUInt32(pfRoots); + AssertRC(rc); + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Replies to a root list header request. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pRootListHdr Root lsit header to reply to the host. + */ +VBGLR3DECL(int) VbglR3ClipboardRootListHdrReadReply(PVBGLR3SHCLCMDCTX pCtx, PSHCLROOTLISTHDR pRootListHdr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pRootListHdr, VERR_INVALID_POINTER); + + VBoxShClRootListHdrMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE, VBOX_SHCL_CPARMS_ROOT_LIST_HDR_WRITE); + + Msg.ReqParms.uContext.SetUInt64(pCtx->idContext); + Msg.ReqParms.fRoots.SetUInt32(pRootListHdr->fRoots); + + Msg.cRoots.SetUInt32(pRootListHdr->cRoots); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives a host request to read a root list entry from the guest. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param puIndex Where to return the index of the root list entry the host wants to read. + * @param pfInfo Where to return the read flags the host wants to use. + */ +VBGLR3DECL(int) VbglR3ClipboardRootListEntryReadReq(PVBGLR3SHCLCMDCTX pCtx, uint32_t *puIndex, uint32_t *pfInfo) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(puIndex, VERR_INVALID_POINTER); + AssertPtrReturn(pfInfo, VERR_INVALID_POINTER); + + VBoxShClRootListEntryReadReqMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_READ_REQ); + + Msg.Parms.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ); + Msg.Parms.fInfo.SetUInt32(0); + Msg.Parms.uIndex.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.Parms.uContext.GetUInt64(&pCtx->idContext); AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = Msg.Parms.fInfo.GetUInt32(pfInfo); + AssertRC(rc); + } + if (RT_SUCCESS(rc)) + { + rc = Msg.Parms.uIndex.GetUInt32(puIndex); + AssertRC(rc); + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Replies to a root list entry read request from the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param uIndex Index of root list entry to reply. + * @param pEntry Actual root list entry to reply. + */ +VBGLR3DECL(int) VbglR3ClipboardRootListEntryReadReply(PVBGLR3SHCLCMDCTX pCtx, uint32_t uIndex, PSHCLROOTLISTENTRY pEntry) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pEntry, VERR_INVALID_POINTER); + + VBoxShClRootListEntryMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE, VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_WRITE); + + Msg.Parms.uContext.SetUInt64(pCtx->idContext); + Msg.Parms.fInfo.SetUInt32(0); + Msg.Parms.uIndex.SetUInt32(uIndex); + + Msg.szName.SetPtr(pEntry->pszName, pEntry->cbName); + Msg.cbInfo.SetUInt32(pEntry->cbInfo); + Msg.pvInfo.SetPtr(pEntry->pvInfo, pEntry->cbInfo); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends a request to open a list handle to the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pOpenParms List open parameters to use for the open request. + * @param phList Where to return the list handle received from the host. + */ +VBGLR3DECL(int) VbglR3ClipboardListOpenSend(PVBGLR3SHCLCMDCTX pCtx, PSHCLLISTOPENPARMS pOpenParms, + PSHCLLISTHANDLE phList) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pOpenParms, VERR_INVALID_POINTER); + AssertPtrReturn(phList, VERR_INVALID_POINTER); + + VBoxShClListOpenMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_LIST_OPEN, VBOX_SHCL_CPARMS_LIST_OPEN); + + Msg.uContext.SetUInt64(pCtx->idContext); + Msg.fList.SetUInt32(0); + Msg.pvFilter.SetPtr(pOpenParms->pszFilter, pOpenParms->cbFilter); + Msg.pvPath.SetPtr(pOpenParms->pszPath, pOpenParms->cbPath); + Msg.uHandle.SetUInt64(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.uHandle.GetUInt64(phList); AssertRC(rc); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives a host request to open a list handle on the guest. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pOpenParms Where to store the open parameters the host wants to use for opening the list handle. + */ +VBGLR3DECL(int) VbglR3ClipboardListOpenRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLLISTOPENPARMS pOpenParms) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pOpenParms, VERR_INVALID_POINTER); + + VBoxShClListOpenMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_LIST_OPEN); + + Msg.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN); + Msg.fList.SetUInt32(0); + Msg.pvPath.SetPtr(pOpenParms->pszPath, pOpenParms->cbPath); + Msg.pvFilter.SetPtr(pOpenParms->pszFilter, pOpenParms->cbFilter); + Msg.uHandle.SetUInt64(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.uContext.GetUInt64(&pCtx->idContext); + if (RT_SUCCESS(rc)) + rc = Msg.fList.GetUInt32(&pOpenParms->fList); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Replies to a list open request from the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param rcReply Return code to reply to the host. + * @param hList List handle of (guest) list to reply to the host. + */ +VBGLR3DECL(int) VbglR3ClipboardListOpenReply(PVBGLR3SHCLCMDCTX pCtx, int rcReply, SHCLLISTHANDLE hList) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBoxShClReplyMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN + 1); + + Msg.uContext.SetUInt64(pCtx->idContext); + Msg.enmType.SetUInt32(VBOX_SHCL_REPLYMSGTYPE_LIST_OPEN); + Msg.rc.SetUInt32((uint32_t)rcReply); /** int vs. uint32_t */ + Msg.pvPayload.SetPtr(NULL, 0); + + Msg.u.ListOpen.uHandle.SetUInt64(hList); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives a host request to close a list handle on the guest. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param phList Where to store the list handle to close, received from the host. + */ +VBGLR3DECL(int) VbglR3ClipboardListCloseRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLLISTHANDLE phList) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(phList, VERR_INVALID_POINTER); + + VBoxShClListCloseMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_LIST_CLOSE); + + Msg.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE); + Msg.uHandle.SetUInt64(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.uContext.GetUInt64(&pCtx->idContext); + if (RT_SUCCESS(rc)) + { + rc = Msg.uHandle.GetUInt64(phList); + AssertRC(rc); + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Replies to a list handle close request from the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param rcReply Return code to reply to the host. + * @param hList List handle the send the close reply for. + */ +VBGLR3DECL(int) VbglR3ClipboardListCloseReply(PVBGLR3SHCLCMDCTX pCtx, int rcReply, SHCLLISTHANDLE hList) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBoxShClReplyMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN + 1); + + Msg.uContext.SetUInt64(pCtx->idContext); + Msg.enmType.SetUInt32(VBOX_SHCL_REPLYMSGTYPE_LIST_CLOSE); + Msg.rc.SetUInt32((uint32_t)rcReply); /** int vs. uint32_t */ + Msg.pvPayload.SetPtr(NULL, 0); + + Msg.u.ListOpen.uHandle.SetUInt64(hList); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends a request to close a list handle to the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param hList List handle to request for closing on the host. + */ +VBGLR3DECL(int) VbglR3ClipboardListCloseSend(PVBGLR3SHCLCMDCTX pCtx, SHCLLISTHANDLE hList) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBoxShClListCloseMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_LIST_CLOSE, VBOX_SHCL_CPARMS_LIST_CLOSE); + + Msg.uContext.SetUInt64(pCtx->idContext); + Msg.uHandle.SetUInt64(hList); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends a request to read a list header to the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param hList List handle to read list header for. + * @param fFlags List header read flags to use. + * @param pListHdr Where to return the list header received from the host. + */ +VBGLR3DECL(int) VbglR3ClipboardListHdrRead(PVBGLR3SHCLCMDCTX pCtx, SHCLLISTHANDLE hList, uint32_t fFlags, + PSHCLLISTHDR pListHdr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pListHdr, VERR_INVALID_POINTER); + + VBoxShClListHdrMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_LIST_HDR_READ, VBOX_SHCL_CPARMS_LIST_HDR); + + Msg.ReqParms.uContext.SetUInt64(pCtx->idContext); + Msg.ReqParms.uHandle.SetUInt64(hList); + Msg.ReqParms.fFlags.SetUInt32(fFlags); + + Msg.fFeatures.SetUInt32(0); + Msg.cbTotalSize.SetUInt32(0); + Msg.cTotalObjects.SetUInt64(0); + Msg.cbTotalSize.SetUInt64(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.fFeatures.GetUInt32(&pListHdr->fFeatures); + if (RT_SUCCESS(rc)) + rc = Msg.cTotalObjects.GetUInt64(&pListHdr->cTotalObjects); + if (RT_SUCCESS(rc)) + rc = Msg.cbTotalSize.GetUInt64(&pListHdr->cbTotalSize); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives a host request to read a list header on the guest. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param phList Where to return the list handle to read list header for. + * @param pfFlags Where to return the List header read flags to use. + */ +VBGLR3DECL(int) VbglR3ClipboardListHdrReadRecvReq(PVBGLR3SHCLCMDCTX pCtx, PSHCLLISTHANDLE phList, uint32_t *pfFlags) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(phList, VERR_INVALID_POINTER); + AssertPtrReturn(pfFlags, VERR_INVALID_POINTER); + + VBoxShClListHdrReadReqMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_LIST_HDR_READ_REQ); + + Msg.ReqParms.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ); + Msg.ReqParms.uHandle.SetUInt64(0); + Msg.ReqParms.fFlags.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.ReqParms.uContext.GetUInt64(&pCtx->idContext); + if (RT_SUCCESS(rc)) + rc = Msg.ReqParms.uHandle.GetUInt64(phList); + if (RT_SUCCESS(rc)) + rc = Msg.ReqParms.fFlags.GetUInt32(pfFlags); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends (writes) a list header to the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param hList List handle to write list header for. + * @param pListHdr List header to write. + */ +VBGLR3DECL(int) VbglR3ClipboardListHdrWrite(PVBGLR3SHCLCMDCTX pCtx, SHCLLISTHANDLE hList, + PSHCLLISTHDR pListHdr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pListHdr, VERR_INVALID_POINTER); + + VBoxShClListHdrMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE, VBOX_SHCL_CPARMS_LIST_HDR); + + Msg.ReqParms.uContext.SetUInt64(pCtx->idContext); + Msg.ReqParms.uHandle.SetUInt64(hList); + Msg.ReqParms.fFlags.SetUInt32(0); + + Msg.fFeatures.SetUInt32(0); + Msg.cbTotalSize.SetUInt32(pListHdr->fFeatures); + Msg.cTotalObjects.SetUInt64(pListHdr->cTotalObjects); + Msg.cbTotalSize.SetUInt64(pListHdr->cbTotalSize); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends a request to read a list entry from the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param hList List handle to request to read a list entry for. + * @param pListEntry Where to return the list entry read from the host. + */ +VBGLR3DECL(int) VbglR3ClipboardListEntryRead(PVBGLR3SHCLCMDCTX pCtx, SHCLLISTHANDLE hList, + PSHCLLISTENTRY pListEntry) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pListEntry, VERR_INVALID_POINTER); + + VBoxShClListEntryMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ, VBOX_SHCL_CPARMS_LIST_ENTRY); + + Msg.ReqParms.uContext.SetUInt64(pCtx->idContext); + Msg.ReqParms.uHandle.SetUInt64(hList); + Msg.ReqParms.fInfo.SetUInt32(0); + + Msg.szName.SetPtr(pListEntry->pszName, pListEntry->cbName); + Msg.cbInfo.SetUInt32(pListEntry->cbInfo); + Msg.pvInfo.SetPtr(pListEntry->pvInfo, pListEntry->cbInfo); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.cbInfo.GetUInt32(&pListEntry->cbInfo); AssertRC(rc); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives a host request to read a list entry from the guest. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param phList Where to return the list handle to read a list entry for. + * @param pfInfo Where to return the list read flags. + */ +VBGLR3DECL(int) VbglR3ClipboardListEntryReadRecvReq(PVBGLR3SHCLCMDCTX pCtx, PSHCLLISTHANDLE phList, uint32_t *pfInfo) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(phList, VERR_INVALID_POINTER); + AssertPtrReturn(pfInfo, VERR_INVALID_POINTER); + + VBoxShClListEntryReadReqMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_LIST_ENTRY_READ); + + Msg.ReqParms.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ); + Msg.ReqParms.uHandle.SetUInt64(0); + Msg.ReqParms.fInfo.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.ReqParms.uContext.GetUInt64(&pCtx->idContext); + if (RT_SUCCESS(rc)) + { + rc = Msg.ReqParms.uHandle.GetUInt64(phList); + AssertRC(rc); + } + if (RT_SUCCESS(rc)) + { + rc = Msg.ReqParms.fInfo.GetUInt32(pfInfo); + AssertRC(rc); + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends (writes) a list entry to the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param hList List handle to write a list etnry for. + * @param pListEntry List entry to write. + */ +VBGLR3DECL(int) VbglR3ClipboardListEntryWrite(PVBGLR3SHCLCMDCTX pCtx, SHCLLISTHANDLE hList, + PSHCLLISTENTRY pListEntry) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pListEntry, VERR_INVALID_POINTER); + + VBoxShClListEntryMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE, VBOX_SHCL_CPARMS_LIST_ENTRY); + + Msg.ReqParms.uContext.SetUInt64(pCtx->idContext); + Msg.ReqParms.uHandle.SetUInt64(hList); + Msg.ReqParms.fInfo.SetUInt32(pListEntry->fInfo); + + Msg.szName.SetPtr(pListEntry->pszName, pListEntry->cbName); + Msg.cbInfo.SetUInt32(pListEntry->cbInfo); + Msg.pvInfo.SetPtr(pListEntry->pvInfo, pListEntry->cbInfo); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives a host request to open an object on the guest. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pCreateParms Where to store the object open/create parameters received from the host. + */ +VBGLR3DECL(int) VbglR3ClipboardObjOpenRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCreateParms, VERR_INVALID_POINTER); + + VBoxShClObjOpenMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_OBJ_OPEN); + + Msg.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN); + Msg.uHandle.SetUInt64(0); + Msg.szPath.SetPtr(pCreateParms->pszPath, pCreateParms->cbPath); + Msg.fCreate.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.uContext.GetUInt64(&pCtx->idContext); + if (RT_SUCCESS(rc)) + rc = Msg.fCreate.GetUInt32(&pCreateParms->fCreate); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Replies a host request to open an object. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param rcReply Return code to reply to the host. + * @param hObj Object handle of opened object to reply to the host. + */ +VBGLR3DECL(int) VbglR3ClipboardObjOpenReply(PVBGLR3SHCLCMDCTX pCtx, int rcReply, SHCLOBJHANDLE hObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBoxShClReplyMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN + 1); + + Msg.uContext.SetUInt64(pCtx->idContext); + Msg.enmType.SetUInt32(VBOX_SHCL_REPLYMSGTYPE_OBJ_OPEN); + Msg.rc.SetUInt32((uint32_t)rcReply); /** int vs. uint32_t */ + Msg.pvPayload.SetPtr(NULL, 0); + + Msg.u.ObjOpen.uHandle.SetUInt64(hObj); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends an object open request to the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param pCreateParms Object open/create parameters to use for opening the object on the host. + * @param phObj Where to return the object handle from the host. + */ +VBGLR3DECL(int) VbglR3ClipboardObjOpenSend(PVBGLR3SHCLCMDCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms, + PSHCLOBJHANDLE phObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCreateParms, VERR_INVALID_POINTER); + AssertPtrReturn(phObj, VERR_INVALID_POINTER); + + VBoxShClObjOpenMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_OBJ_OPEN, VBOX_SHCL_CPARMS_OBJ_OPEN); + + Msg.uContext.SetUInt64(pCtx->idContext); + Msg.uHandle.SetUInt64(0); + Msg.szPath.SetPtr((void *)pCreateParms->pszPath, pCreateParms->cbPath); + Msg.fCreate.SetUInt32(pCreateParms->fCreate); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.uHandle.GetUInt64(phObj); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives a host request to close an object on the guest. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param phObj Where to return the object handle to close from the host. + */ +VBGLR3DECL(int) VbglR3ClipboardObjCloseRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLOBJHANDLE phObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(phObj, VERR_INVALID_POINTER); + + VBoxShClObjCloseMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_OBJ_CLOSE); + + Msg.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE); + Msg.uHandle.SetUInt64(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.uContext.GetUInt64(&pCtx->idContext); + if (RT_SUCCESS(rc)) + rc = Msg.uHandle.GetUInt64(phObj); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Replies to an object open request from the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param rcReply Return code to reply to the host. + * @param hObj Object handle to reply to the host. + */ +VBGLR3DECL(int) VbglR3ClipboardObjCloseReply(PVBGLR3SHCLCMDCTX pCtx, int rcReply, SHCLOBJHANDLE hObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBoxShClReplyMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN + 1); + + Msg.uContext.SetUInt64(pCtx->idContext); + Msg.enmType.SetUInt32(VBOX_SHCL_REPLYMSGTYPE_OBJ_CLOSE); + Msg.rc.SetUInt32((uint32_t)rcReply); /** int vs. uint32_t */ + Msg.pvPayload.SetPtr(NULL, 0); + + Msg.u.ObjClose.uHandle.SetUInt64(hObj); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends a request to close an object to the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param hObj Object handle to close on the host. + */ +VBGLR3DECL(int) VbglR3ClipboardObjCloseSend(PVBGLR3SHCLCMDCTX pCtx, SHCLOBJHANDLE hObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBoxShClObjCloseMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_OBJ_CLOSE, VBOX_SHCL_CPARMS_OBJ_CLOSE); + + Msg.uContext.SetUInt64(pCtx->idContext); + Msg.uHandle.SetUInt64(hObj); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Receives a host request to read from an object on the guest. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param phObj Where to return the object handle to read from. + * @param pcbToRead Where to return the amount (in bytes) to read. + * @param pfFlags Where to return the read flags. + */ +VBGLR3DECL(int) VbglR3ClipboardObjReadRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLOBJHANDLE phObj, uint32_t *pcbToRead, + uint32_t *pfFlags) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(phObj, VERR_INVALID_POINTER); + AssertPtrReturn(pcbToRead, VERR_INVALID_POINTER); + AssertPtrReturn(pfFlags, VERR_INVALID_POINTER); + + VBoxShClObjReadReqMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_OBJ_READ_REQ); + + Msg.ReqParms.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ); + Msg.ReqParms.uHandle.SetUInt64(0); + Msg.ReqParms.cbToRead.SetUInt32(0); + Msg.ReqParms.fRead.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.ReqParms.uContext.GetUInt64(&pCtx->idContext); + if (RT_SUCCESS(rc)) + rc = Msg.ReqParms.uHandle.GetUInt64(phObj); + if (RT_SUCCESS(rc)) + rc = Msg.ReqParms.cbToRead.GetUInt32(pcbToRead); + if (RT_SUCCESS(rc)) + rc = Msg.ReqParms.fRead.GetUInt32(pfFlags); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends a request to read from an object to the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param hObj Object handle of object to read from. + * @param pvData Buffer where to store the read object data. + * @param cbData Size (in bytes) of buffer. + * @param pcbRead Where to store the amount (in bytes) read from the object. + */ +VBGLR3DECL(int) VbglR3ClipboardObjReadSend(PVBGLR3SHCLCMDCTX pCtx, SHCLOBJHANDLE hObj, + void *pvData, uint32_t cbData, uint32_t *pcbRead) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + /* pcbRead is optional. */ + + VBoxShClObjReadWriteMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_OBJ_READ, VBOX_SHCL_CPARMS_OBJ_READ); + + Msg.uContext.SetUInt64(pCtx->idContext); + Msg.uHandle.SetUInt64(hObj); + Msg.cbData.SetUInt32(cbData); + Msg.pvData.SetPtr(pvData, cbData); + Msg.cbChecksum.SetUInt32(0); + Msg.pvChecksum.SetPtr(NULL, 0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Add checksum support. */ + + if (pcbRead) + { + rc = Msg.cbData.GetUInt32(pcbRead); AssertRC(rc); + AssertReturn(cbData >= *pcbRead, VERR_TOO_MUCH_DATA); + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends a request to write to an object to the host. + * + * @returns VBox status code. + * @param pCtx Shared Clipboard command context to use for the connection. + * @param hObj Object handle of object to write to. + * @param pvData Buffer of data to write to object. + * @param cbData Size (in bytes) of buffer. + * @param pcbWritten Where to store the amount (in bytes) written to the object. + */ +VBGLR3DECL(int) VbglR3ClipboardObjWriteSend(PVBGLR3SHCLCMDCTX pCtx, SHCLOBJHANDLE hObj, + void *pvData, uint32_t cbData, uint32_t *pcbWritten) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + /* cbData can be 0. */ + /* pcbWritten is optional. */ + + VBoxShClObjReadWriteMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient, + VBOX_SHCL_GUEST_FN_OBJ_WRITE, VBOX_SHCL_CPARMS_OBJ_WRITE); + + Msg.uContext.SetUInt64(pCtx->idContext); + Msg.uHandle.SetUInt64(hObj); + Msg.pvData.SetPtr(pvData, cbData); + Msg.pvChecksum.SetPtr(NULL, 0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Add checksum support. */ + + if (pcbWritten) + *pcbWritten = cbData; /** @todo For now return all as being written. */ + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/********************************************************************************************************************************* +* Transfer interface implementations * +*********************************************************************************************************************************/ + +static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceGetRoots(PSHCLTXPROVIDERCTX pCtx, PSHCLROOTLIST *ppRootList) +{ + LogFlowFuncEnter(); + + PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser; + AssertPtr(pCmdCtx); + + int rc = VbglR3ClipboardRootListRead(pCmdCtx, ppRootList); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceListOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLLISTOPENPARMS pOpenParms, + PSHCLLISTHANDLE phList) +{ + LogFlowFuncEnter(); + + PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser; + AssertPtr(pCmdCtx); + + int rc = VbglR3ClipboardListOpenSend(pCmdCtx, pOpenParms, phList); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceListClose(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList) +{ + LogFlowFuncEnter(); + + PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser; + AssertPtr(pCmdCtx); + + int rc = VbglR3ClipboardListCloseSend(pCmdCtx, hList); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceListHdrRead(PSHCLTXPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr) +{ + LogFlowFuncEnter(); + + PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser; + AssertPtr(pCmdCtx); + + int rc = ShClTransferListHdrInit(pListHdr); + if (RT_SUCCESS(rc)) + { + if (RT_SUCCESS(rc)) + { + rc = VbglR3ClipboardListHdrRead(pCmdCtx, hList, 0 /* fFlags */, pListHdr); + } + else + ShClTransferListHdrDestroy(pListHdr); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceListEntryRead(PSHCLTXPROVIDERCTX pCtx, + SHCLLISTHANDLE hList, PSHCLLISTENTRY pEntry) +{ + LogFlowFuncEnter(); + + PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser; + AssertPtr(pCmdCtx); + + int rc = VbglR3ClipboardListEntryRead(pCmdCtx, hList, pEntry); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceObjOpen(PSHCLTXPROVIDERCTX pCtx, + PSHCLOBJOPENCREATEPARMS pCreateParms, PSHCLOBJHANDLE phObj) +{ + LogFlowFuncEnter(); + + PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser; + AssertPtr(pCmdCtx); + + int rc = VbglR3ClipboardObjOpenSend(pCmdCtx, pCreateParms, phObj); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceObjClose(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj) +{ + LogFlowFuncEnter(); + + PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser; + AssertPtr(pCmdCtx); + + int rc = VbglR3ClipboardObjCloseSend(pCmdCtx, hObj); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceObjRead(PSHCLTXPROVIDERCTX pCtx, + SHCLOBJHANDLE hObj, void *pvData, uint32_t cbData, + uint32_t fFlags, uint32_t *pcbRead) +{ + LogFlowFuncEnter(); + + PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser; + AssertPtr(pCmdCtx); + + RT_NOREF(fFlags); /* Not used yet. */ + + int rc = VbglR3ClipboardObjReadSend(pCmdCtx, hObj, pvData, cbData, pcbRead); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Starts a transfer on the guest side. + * + * @returns VBox status code. + * @param pCmdCtx Command context to use. + * @param pTransferCtx Transfer context to create transfer for. + * @param uTransferID ID to use for transfer to start. + * @param enmDir Direction of transfer to start. + * @param enmSource Source of transfer to start. + * @param ppTransfer Where to return the transfer object on success. Optional. + */ +static int vbglR3ClipboardTransferStart(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFERCTX pTransferCtx, + SHCLTRANSFERID uTransferID, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, + PSHCLTRANSFER *ppTransfer) +{ + PSHCLTRANSFER pTransfer; + int rc = ShClTransferCreate(&pTransfer); + if (RT_SUCCESS(rc)) + { + ShClTransferSetCallbacks(pTransfer, &pCmdCtx->Transfers.Callbacks); + + rc = ShClTransferInit(pTransfer, enmDir, enmSource); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCtxTransferRegisterById(pTransferCtx, pTransfer, uTransferID); + if (RT_SUCCESS(rc)) + { + /* If this is a read transfer (reading data from host), set the interface to use + * our VbglR3 routines here. */ + if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) + { + SHCLTXPROVIDERCREATIONCTX creationCtx; + RT_ZERO(creationCtx); + + creationCtx.Interface.pfnRootsGet = vbglR3ClipboardTransferIfaceGetRoots; + + creationCtx.Interface.pfnListOpen = vbglR3ClipboardTransferIfaceListOpen; + creationCtx.Interface.pfnListClose = vbglR3ClipboardTransferIfaceListClose; + creationCtx.Interface.pfnListHdrRead = vbglR3ClipboardTransferIfaceListHdrRead; + creationCtx.Interface.pfnListEntryRead = vbglR3ClipboardTransferIfaceListEntryRead; + + creationCtx.Interface.pfnObjOpen = vbglR3ClipboardTransferIfaceObjOpen; + creationCtx.Interface.pfnObjClose = vbglR3ClipboardTransferIfaceObjClose; + creationCtx.Interface.pfnObjRead = vbglR3ClipboardTransferIfaceObjRead; + + creationCtx.pvUser = pCmdCtx; + + rc = ShClTransferSetProviderIface(pTransfer, &creationCtx); + } + + if (RT_SUCCESS(rc)) + rc = ShClTransferStart(pTransfer); + } + + if (RT_FAILURE(rc)) + ShClTransferCtxTransferUnregister(pTransferCtx, uTransferID); + } + } + + if (RT_SUCCESS(rc)) + { + if (ppTransfer) + *ppTransfer = pTransfer; + + LogRel2(("Shared Clipboard: Transfer ID=%RU32 (%s %s) successfully started\n", + uTransferID, + enmDir == SHCLTRANSFERDIR_FROM_REMOTE ? "reading from" : "writing to", + enmSource == SHCLSOURCE_LOCAL ? "local" : "remote")); + } + else + LogRel(("Shared Clipboard: Unable to start transfer ID=%RU32, rc=%Rrc\n", uTransferID, rc)); + + /* Send a reply in any case. */ + int rc2 = VbglR3ClipboardTransferStatusReply(pCmdCtx, pTransfer, + RT_SUCCESS(rc) + ? SHCLTRANSFERSTATUS_STARTED : SHCLTRANSFERSTATUS_ERROR, rc); + if (RT_SUCCESS(rc)) + rc = rc2; + + if (RT_FAILURE(rc)) + { + ShClTransferDestroy(pTransfer); + pTransfer = NULL; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Stops a transfer on the guest side. + * + * @returns VBox status code, or VERR_NOT_FOUND if transfer has not been found. + * @param pCmdCtx Command context to use. + * @param pTransferCtx Transfer context to stop transfer for. + * @param uTransferID ID of transfer to stop. + */ +static int vbglR3ClipboardTransferStop(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFERCTX pTransferCtx, + SHCLTRANSFERID uTransferID) +{ + int rc; + + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, uTransferID); + if (pTransfer) + { + rc = ShClTransferCtxTransferUnregister(pTransferCtx, uTransferID); + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: Transfer ID=%RU32 successfully stopped\n", uTransferID)); + } + else + LogRel(("Shared Clipboard: Unable to stop transfer ID=%RU32, rc=%Rrc\n", uTransferID, rc)); + + /* Send a reply in any case. */ + int rc2 = VbglR3ClipboardTransferStatusReply(pCmdCtx, pTransfer, + RT_SUCCESS(rc) + ? SHCLTRANSFERSTATUS_STOPPED : SHCLTRANSFERSTATUS_ERROR, rc); + AssertRC(rc2); + } + else + rc = VERR_NOT_FOUND; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sets transfer callbacks of a Shared Clipboard command context. + * + * @param pCmdCtx Command context to set callbacks for. + * @param pCallbacks Pointer to callback table to set. + */ +VBGLR3DECL(void) VbglR3ClipboardTransferSetCallbacks(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFERCALLBACKTABLE pCallbacks) +{ + AssertPtrReturnVoid(pCmdCtx); + AssertPtrReturnVoid(pCallbacks); + + ShClTransferCopyCallbacks(&pCmdCtx->Transfers.Callbacks, pCallbacks); +} + +VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, + PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFERCTX pTransferCtx, + PVBGLR3CLIPBOARDEVENT pEvent) +{ + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + + LogFunc(("Handling idMsg=%RU32 (%s), cParms=%RU32\n", idMsg, ShClHostMsgToStr(idMsg), cParms)); + + int rc; + if (!pCmdCtx->fUseLegacyProtocol) + { + bool fErrorSent = false; /* Whether an error has been reported back to the host already. */ + + switch (idMsg) + { + case VBOX_SHCL_HOST_MSG_TRANSFER_STATUS: + { + SHCLTRANSFERDIR enmDir; + SHCLTRANSFERREPORT transferReport; + rc = VbglR3ClipboarTransferStatusRecv(pCmdCtx, &enmDir, &transferReport); + if (RT_SUCCESS(rc)) + { + const SHCLTRANSFERID uTransferID = VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext); + + LogFlowFunc(("[Transfer %RU32] enmDir=%RU32, status=%s\n", + uTransferID, enmDir, ShClTransferStatusToStr(transferReport.uStatus))); + + switch (transferReport.uStatus) + { + case SHCLTRANSFERSTATUS_INITIALIZED: + RT_FALL_THROUGH(); + case SHCLTRANSFERSTATUS_STARTED: + { + SHCLSOURCE enmSource = SHCLSOURCE_INVALID; + + /* The host announces the transfer direction from its point of view, so inverse the direction here. */ + if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) + { + enmDir = SHCLTRANSFERDIR_FROM_REMOTE; + enmSource = SHCLSOURCE_REMOTE; + } + else if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) + { + enmDir = SHCLTRANSFERDIR_TO_REMOTE; + enmSource = SHCLSOURCE_LOCAL; + } + else + AssertFailedBreakStmt(rc = VERR_INVALID_PARAMETER); + + rc = vbglR3ClipboardTransferStart(pCmdCtx, pTransferCtx, uTransferID, + enmDir, enmSource, NULL /* ppTransfer */); + break; + } + + case SHCLTRANSFERSTATUS_STOPPED: + RT_FALL_THROUGH(); + case SHCLTRANSFERSTATUS_CANCELED: + RT_FALL_THROUGH(); + case SHCLTRANSFERSTATUS_KILLED: + RT_FALL_THROUGH(); + case SHCLTRANSFERSTATUS_ERROR: + { + rc = vbglR3ClipboardTransferStop(pCmdCtx, pTransferCtx, + VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext)); + break; + } + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + if (RT_SUCCESS(rc)) + { + pEvent->u.TransferStatus.enmDir = enmDir; + pEvent->u.TransferStatus.Report = transferReport; + pEvent->u.TransferStatus.uID = VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext); + + pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS; + + LogRel2(("Shared Clipboard: Received status %s (rc=%Rrc) for transfer ID=%RU32\n", + ShClTransferStatusToStr(pEvent->u.TransferStatus.Report.uStatus), pEvent->u.TransferStatus.Report.rc, + pEvent->u.TransferStatus.uID)); + } + } + break; + } + + case VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ: + { + uint32_t fRoots; + rc = VbglR3ClipboardRootListHdrReadReq(pCmdCtx, &fRoots); + + /** @todo Validate / handle fRoots. */ + + if (RT_SUCCESS(rc)) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, + VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext)); + AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND); + + SHCLROOTLISTHDR rootListHdr; + RT_ZERO(rootListHdr); + + rootListHdr.cRoots = ShClTransferRootsCount(pTransfer); + + LogFlowFunc(("cRoots=%RU32\n", rootListHdr.cRoots)); + + rc = VbglR3ClipboardRootListHdrReadReply(pCmdCtx, &rootListHdr); + } + break; + } + + case VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ: + { + uint32_t uIndex; + uint32_t fInfo; + rc = VbglR3ClipboardRootListEntryReadReq(pCmdCtx, &uIndex, &fInfo); + if (RT_SUCCESS(rc)) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, + VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext)); + AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND); + + SHCLROOTLISTENTRY rootListEntry; + rc = ShClTransferRootsEntry(pTransfer, uIndex, &rootListEntry); + if (RT_SUCCESS(rc)) + rc = VbglR3ClipboardRootListEntryReadReply(pCmdCtx, uIndex, &rootListEntry); + } + break; + } + + case VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN: + { + SHCLLISTOPENPARMS openParmsList; + rc = ShClTransferListOpenParmsInit(&openParmsList); + if (RT_SUCCESS(rc)) + { + rc = VbglR3ClipboardListOpenRecv(pCmdCtx, &openParmsList); + if (RT_SUCCESS(rc)) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, + VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext)); + AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND); + + LogFlowFunc(("pszPath=%s\n", openParmsList.pszPath)); + + SHCLLISTHANDLE hList = SHCLLISTHANDLE_INVALID; + rc = ShClTransferListOpen(pTransfer, &openParmsList, &hList); + + /* Reply in any case. */ + int rc2 = VbglR3ClipboardListOpenReply(pCmdCtx, rc, hList); + AssertRC(rc2); + } + + ShClTransferListOpenParmsDestroy(&openParmsList); + } + + break; + } + + case VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE: + { + SHCLLISTHANDLE hList; + rc = VbglR3ClipboardListCloseRecv(pCmdCtx, &hList); + if (RT_SUCCESS(rc)) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, + VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext)); + AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND); + + rc = ShClTransferListClose(pTransfer, hList); + + /* Reply in any case. */ + int rc2 = VbglR3ClipboardListCloseReply(pCmdCtx, rc, hList); + AssertRC(rc2); + } + + break; + } + + case VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ: + { + /** @todo Handle filter + list features. */ + + SHCLLISTHANDLE hList = SHCLLISTHANDLE_INVALID; + uint32_t fFlags = 0; + rc = VbglR3ClipboardListHdrReadRecvReq(pCmdCtx, &hList, &fFlags); + if (RT_SUCCESS(rc)) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, + VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext)); + AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND); + + SHCLLISTHDR hdrList; + rc = ShClTransferListGetHeader(pTransfer, hList, &hdrList); + if (RT_SUCCESS(rc)) + { + rc = VbglR3ClipboardListHdrWrite(pCmdCtx, hList, &hdrList); + + ShClTransferListHdrDestroy(&hdrList); + } + } + + break; + } + + case VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ: + { + LogFlowFunc(("VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ\n")); + + SHCLLISTENTRY entryList; + rc = ShClTransferListEntryInit(&entryList); + if (RT_SUCCESS(rc)) + { + SHCLLISTHANDLE hList; + uint32_t fInfo; + rc = VbglR3ClipboardListEntryReadRecvReq(pCmdCtx, &hList, &fInfo); + if (RT_SUCCESS(rc)) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, + VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext)); + AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND); + + rc = ShClTransferListRead(pTransfer, hList, &entryList); + if (RT_SUCCESS(rc)) + { + PSHCLFSOBJINFO pObjInfo = (PSHCLFSOBJINFO)entryList.pvInfo; + Assert(entryList.cbInfo == sizeof(SHCLFSOBJINFO)); + + RT_NOREF(pObjInfo); + + LogFlowFunc(("\t%s (%RU64 bytes)\n", entryList.pszName, pObjInfo->cbObject)); + + rc = VbglR3ClipboardListEntryWrite(pCmdCtx, hList, &entryList); + } + } + + ShClTransferListEntryDestroy(&entryList); + } + + break; + } + + case VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN: + { + SHCLOBJOPENCREATEPARMS openParms; + rc = ShClTransferObjOpenParmsInit(&openParms); + if (RT_SUCCESS(rc)) + { + rc = VbglR3ClipboardObjOpenRecv(pCmdCtx, &openParms); + if (RT_SUCCESS(rc)) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, + VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext)); + AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND); + + SHCLOBJHANDLE hObj; + rc = ShClTransferObjOpen(pTransfer, &openParms, &hObj); + + /* Reply in any case. */ + int rc2 = VbglR3ClipboardObjOpenReply(pCmdCtx, rc, hObj); + AssertRC(rc2); + } + + ShClTransferObjOpenParmsDestroy(&openParms); + } + + break; + } + + case VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE: + { + SHCLOBJHANDLE hObj; + rc = VbglR3ClipboardObjCloseRecv(pCmdCtx, &hObj); + if (RT_SUCCESS(rc)) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, + VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext)); + AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND); + + rc = ShClTransferObjClose(pTransfer, hObj); + + /* Reply in any case. */ + int rc2 = VbglR3ClipboardObjCloseReply(pCmdCtx, rc, hObj); + AssertRC(rc2); + } + + break; + } + + case VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ: + { + SHCLOBJHANDLE hObj; + uint32_t cbBuf; + uint32_t fFlags; + rc = VbglR3ClipboardObjReadRecv(pCmdCtx, &hObj, &cbBuf, &fFlags); + if (RT_SUCCESS(rc)) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, + VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext)); + AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND); + + AssertBreakStmt(pCmdCtx->Transfers.cbChunkSize, rc = VERR_INVALID_PARAMETER); + + const uint32_t cbToRead = RT_MIN(cbBuf, pCmdCtx->Transfers.cbChunkSize); + + LogFlowFunc(("hObj=%RU64, cbBuf=%RU32, fFlags=0x%x -> cbChunkSize=%RU32, cbToRead=%RU32\n", + hObj, cbBuf, fFlags, pCmdCtx->Transfers.cbChunkSize, cbToRead)); + + void *pvBuf = RTMemAlloc(cbToRead); + if (pvBuf) + { + uint32_t cbRead; + rc = ShClTransferObjRead(pTransfer, hObj, pvBuf, cbToRead, fFlags, &cbRead); + if (RT_SUCCESS(rc)) + rc = VbglR3ClipboardObjWriteSend(pCmdCtx, hObj, pvBuf, cbRead, NULL /* pcbWritten */); + + RTMemFree(pvBuf); + } + else + rc = VERR_NO_MEMORY; + } + + break; + } + + default: + { + rc = VbglR3ClipboardEventGetNext(idMsg, cParms, pCmdCtx, pEvent); + if (RT_FAILURE(rc)) + fErrorSent = true; + break; + } + } + + if ( !fErrorSent + && RT_FAILURE(rc)) + { + /* Report error back to the host. */ + int rc2 = VbglR3ClipboardWriteError(pCmdCtx->idClient, rc); + AssertRC(rc2); + } + } + else + { + /* + * This builds on what we did in VbglR3ClipboardMsgPeekWait, so + * !HACK ALERT! cParms is the format flag or flags. + */ + rc = VINF_SUCCESS; + switch (idMsg) + { + case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: + pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS; + pEvent->u.fReportedFormats = cParms; + break; + + case VBOX_SHCL_HOST_MSG_READ_DATA: + pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_READ_DATA; + pEvent->u.fReadData = cParms; + break; + + case VBOX_SHCL_HOST_MSG_QUIT: + pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_QUIT; + break; + + default: + AssertMsgFailed(("%u (%#x)\n", idMsg, idMsg)); + rc = VERR_NOT_SUPPORTED; + break; + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +VBGLR3DECL(int) VbglR3ClipboardEventGetNext(uint32_t idMsg, uint32_t cParms, PVBGLR3SHCLCMDCTX pCtx, PVBGLR3CLIPBOARDEVENT pEvent) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + + RT_NOREF(cParms); + + int rc; + if (!pCtx->fUseLegacyProtocol) + { + LogFunc(("Handling idMsg=%RU32 (%s)\n", idMsg, ShClHostMsgToStr(idMsg))); + switch (idMsg) + { + case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: + { + rc = vbglR3ClipboardFormatsReportRecv(pCtx, &pEvent->u.fReportedFormats); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS; + break; + } + + case VBOX_SHCL_HOST_MSG_READ_DATA_CID: + { + rc = vbglR3ClipboardFetchReadDataCid(pCtx, &pEvent->u.fReadData); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_READ_DATA; + break; + } + + case VBOX_SHCL_HOST_MSG_READ_DATA: + { + rc = vbglR3ClipboardFetchReadData(pCtx, &pEvent->u.fReadData); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_READ_DATA; + break; + } + + case VBOX_SHCL_HOST_MSG_QUIT: + { + pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_QUIT; + rc = VINF_SUCCESS; + break; + } + + default: + { + /** @todo r=bird: BUGBUG - need a skip command here! */ + rc = VERR_NOT_SUPPORTED; + break; + } + } + + if (RT_SUCCESS(rc)) + { + /* Copy over our command context to the event. */ + pEvent->cmdCtx = *pCtx; + } + else + { + /* Report error back to the host. */ + int rc2 = VbglR3ClipboardWriteError(pCtx->idClient, rc); + AssertRC(rc2); + } + } + else + { + /* + * This builds on what we did in VbglR3ClipboardMsgPeekWait, so + * !HACK ALERT! cParms is the format flag or flags. + */ + rc = VINF_SUCCESS; + switch (idMsg) + { + case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: + pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS; + pEvent->u.fReportedFormats = cParms; + break; + + case VBOX_SHCL_HOST_MSG_READ_DATA: + pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_READ_DATA; + pEvent->u.fReadData = cParms; + break; + + case VBOX_SHCL_HOST_MSG_QUIT: + pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_QUIT; + break; + + default: + AssertMsgFailed(("%u (%#x)\n", idMsg, idMsg)); + rc = VERR_NOT_SUPPORTED; + break; + } + pEvent->cmdCtx = *pCtx; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Frees (destroys) a formerly allocated Shared Clipboard event. + * + * @returns IPRT status code. + * @param pEvent Event to free (destroy). + */ +VBGLR3DECL(void) VbglR3ClipboardEventFree(PVBGLR3CLIPBOARDEVENT pEvent) +{ + if (!pEvent) + return; + + /* Some messages require additional cleanup. */ + switch (pEvent->enmType) + { + default: + break; + } + + RTMemFree(pEvent); + pEvent = NULL; +} + +/** + * Reports (advertises) guest clipboard formats to the host. + * + * Legacy function, do not use anymore. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3ClipboardConnect(). + * @param fFormats The formats to report. + */ +VBGLR3DECL(int) VbglR3ClipboardReportFormats(HGCMCLIENTID idClient, uint32_t fFormats) +{ + struct + { + VBGLIOCHGCMCALL Hdr; + VBoxShClParmReportFormats Parms; + } Msg; + + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, VBOX_SHCL_CPARMS_REPORT_FORMATS); + VbglHGCMParmUInt32Set(&Msg.Parms.f32Formats, fFormats); + + int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends guest clipboard data to the host. + * + * Legacy function kept for compatibility, do not use anymore. + * + * This is usually called in reply to a VBOX_SHCL_HOST_MSG_READ_DATA message + * from the host. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3ClipboardConnect(). + * @param fFormat The format of the data. + * @param pvData Pointer to the data to send. Can be NULL if @a cbData + * is zero. + * @param cbData Number of bytes of data to send. Zero is valid. + */ +VBGLR3DECL(int) VbglR3ClipboardWriteData(HGCMCLIENTID idClient, uint32_t fFormat, void *pv, uint32_t cb) +{ + LogFlowFuncEnter(); + + struct + { + VBGLIOCHGCMCALL Hdr; + VBoxShClParmDataWriteOld Parms; + } Msg; + + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, VBOX_SHCL_CPARMS_DATA_WRITE_OLD); + VbglHGCMParmUInt32Set(&Msg.Parms.f32Format, fFormat); + VbglHGCMParmPtrSet(&Msg.Parms.pData, pv, cb); + + int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Sends guest clipboard data to the host. + * + * This is usually called in reply to a VBOX_SHCL_HOST_MSG_READ_DATA message + * from the host. + * + * @returns VBox status code. + * @param pCtx The command context returned by VbglR3ClipboardConnectEx(). + * @param fFormat Clipboard format to send. + * @param pvData Pointer to the data to send. Can be NULL if @a cbData + * is zero. + * @param cbData Number of bytes of data to send. Zero is valid. + */ +VBGLR3DECL(int) VbglR3ClipboardWriteDataEx(PVBGLR3SHCLCMDCTX pCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData) +{ + LogFlowFunc(("ENTER: fFormat=%#x pvData=%p cbData=%#x\n", fFormat, pvData, cbData)); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + if (cbData > 0) + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + + int rc; + if (pCtx->fUseLegacyProtocol) + rc = VbglR3ClipboardWriteData(pCtx->idClient, fFormat, pvData, cbData); + else + { + struct + { + VBGLIOCHGCMCALL Hdr; + VBoxShClParmDataWrite Parms; + } Msg; + + VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, VBOX_SHCL_CPARMS_DATA_WRITE); + Msg.Parms.id64Context.SetUInt64(pCtx->idContext); + Msg.Parms.f32Format.SetUInt32(fFormat); + Msg.Parms.pData.SetPtr(pvData, cbData); + + LogFlowFunc(("CID=%RU32\n", pCtx->idContext)); + + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Writes an error to the host. + * + * @returns IPRT status code. + * @param idClient The client id returned by VbglR3ClipboardConnect(). + * @param rcErr Error (IPRT-style) to send. + */ +VBGLR3DECL(int) VbglR3ClipboardWriteError(HGCMCLIENTID idClient, int rcErr) +{ + AssertReturn(idClient, VERR_INVALID_PARAMETER); + + VBoxShClWriteErrorMsg Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, VBOX_SHCL_GUEST_FN_ERROR, VBOX_SHCL_CPARMS_ERROR); + + /** @todo Context ID not used yet. */ + Msg.uContext.SetUInt64(0); + Msg.rc.SetUInt32((uint32_t)rcErr); /* uint32_t vs. int. */ + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + if (RT_FAILURE(rc)) + LogFlowFunc(("Sending error %Rrc failed with rc=%Rrc\n", rcErr, rc)); + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Reporting error %Rrc to the host failed with %Rrc\n", rcErr, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCoreDump.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCoreDump.cpp new file mode 100644 index 00000000..35971006 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCoreDump.cpp @@ -0,0 +1,56 @@ +/* $Id: VBoxGuestR3LibCoreDump.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Core Dumps. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR3LibInternal.h" + + +/** + * Write guest core dump. + * + * @returns IPRT status code. + */ +VBGLR3DECL(int) VbglR3WriteCoreDump(void) +{ + VBGLIOCWRITECOREDUMP Req; + VBGLREQHDR_INIT(&Req.Hdr, WRITE_CORE_DUMP); + Req.u.In.fFlags = 0; + return vbglR3DoIOCtl(VBGL_IOCTL_WRITE_CORE_DUMP, &Req.Hdr, sizeof(Req)); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCpuHotPlug.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCpuHotPlug.cpp new file mode 100644 index 00000000..00aa0257 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCpuHotPlug.cpp @@ -0,0 +1,133 @@ +/* $Id: VBoxGuestR3LibCpuHotPlug.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, CPU Hot Plugging. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR3LibInternal.h" +#include <iprt/assert.h> +#include <iprt/errcore.h> + + +/** + * Initialize CPU hot plugging. + * + * This will enable the CPU hot plugging events. + * + * @returns VBox status code. + */ +VBGLR3DECL(int) VbglR3CpuHotPlugInit(void) +{ + int rc = VbglR3CtlFilterMask(VMMDEV_EVENT_CPU_HOTPLUG, 0); + if (RT_FAILURE(rc)) + return rc; + + VMMDevCpuHotPlugStatusRequest Req; + vmmdevInitRequest(&Req.header, VMMDevReq_SetCpuHotPlugStatus); + Req.enmStatusType = VMMDevCpuStatusType_Enable; + rc = vbglR3GRPerform(&Req.header); + if (RT_FAILURE(rc)) + VbglR3CtlFilterMask(0, VMMDEV_EVENT_CPU_HOTPLUG); + + return rc; +} + + +/** + * Terminate CPU hot plugging. + * + * This will disable the CPU hot plugging events. + * + * @returns VBox status code. + */ +VBGLR3DECL(int) VbglR3CpuHotPlugTerm(void) +{ + /* Clear the events. */ + VbglR3CtlFilterMask(0, VMMDEV_EVENT_CPU_HOTPLUG); + + VMMDevCpuHotPlugStatusRequest Req; + vmmdevInitRequest(&Req.header, VMMDevReq_SetCpuHotPlugStatus); + Req.enmStatusType = VMMDevCpuStatusType_Disable; + return vbglR3GRPerform(&Req.header); +} + + +/** + * Waits for a CPU hot plugging event and retrieve the data associated with it. + * + * @returns VBox status code. + * @param penmEventType Where to store the event type on success. + * @param pidCpuCore Where to store the CPU core ID on success. + * @param pidCpuPackage Where to store the CPU package ID on success. + */ +VBGLR3DECL(int) VbglR3CpuHotPlugWaitForEvent(VMMDevCpuEventType *penmEventType, uint32_t *pidCpuCore, uint32_t *pidCpuPackage) +{ + AssertPtrReturn(penmEventType, VERR_INVALID_POINTER); + AssertPtrReturn(pidCpuCore, VERR_INVALID_POINTER); + AssertPtrReturn(pidCpuPackage, VERR_INVALID_POINTER); + + uint32_t fEvents = 0; + int rc = VbglR3WaitEvent(VMMDEV_EVENT_CPU_HOTPLUG, RT_INDEFINITE_WAIT, &fEvents); + if (RT_SUCCESS(rc)) + { + /* did we get the right event? */ + if (fEvents & VMMDEV_EVENT_CPU_HOTPLUG) + { + VMMDevGetCpuHotPlugRequest Req; + + /* get the CPU hot plugging request */ + vmmdevInitRequest(&Req.header, VMMDevReq_GetCpuHotPlugRequest); + Req.idCpuCore = UINT32_MAX; + Req.idCpuPackage = UINT32_MAX; + Req.enmEventType = VMMDevCpuEventType_None; + rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + { + *penmEventType = Req.enmEventType; + *pidCpuCore = Req.idCpuCore; + *pidCpuPackage = Req.idCpuPackage; + return VINF_SUCCESS; + } + } + else + rc = VERR_TRY_AGAIN; + } + else if (rc == VERR_TIMEOUT) /* just in case */ + rc = VERR_TRY_AGAIN; + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCredentials.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCredentials.cpp new file mode 100644 index 00000000..b4d795f7 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCredentials.cpp @@ -0,0 +1,222 @@ +/* $Id: VBoxGuestR3LibCredentials.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, user credentials. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/mem.h> +#include <iprt/rand.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include <VBox/log.h> + +#include "VBoxGuestR3LibInternal.h" + + +/** + * Checks whether user credentials are available to the guest or not. + * + * @returns IPRT status value; VINF_SUCCESS if credentials are available, + * VERR_NOT_FOUND if not. Otherwise an error is occurred. + */ +VBGLR3DECL(int) VbglR3CredentialsQueryAvailability(void) +{ + VMMDevCredentials Req; + RT_ZERO(Req); + vmmdevInitRequest((VMMDevRequestHeader*)&Req, VMMDevReq_QueryCredentials); + Req.u32Flags |= VMMDEV_CREDENTIALS_QUERYPRESENCE; + + int rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + { + if ((Req.u32Flags & VMMDEV_CREDENTIALS_PRESENT) == 0) + rc = VERR_NOT_FOUND; + } + return rc; +} + + +/** + * Retrieves and clears the user credentials for logging into the guest OS. + * + * @returns IPRT status value + * @param ppszUser Receives pointer of allocated user name string. + * The returned pointer must be freed using VbglR3CredentialsDestroy(). + * @param ppszPassword Receives pointer of allocated user password string. + * The returned pointer must be freed using VbglR3CredentialsDestroy(). + * @param ppszDomain Receives pointer of allocated domain name string. + * The returned pointer must be freed using VbglR3CredentialsDestroy(). + */ +VBGLR3DECL(int) VbglR3CredentialsRetrieve(char **ppszUser, char **ppszPassword, char **ppszDomain) +{ + AssertPtrReturn(ppszUser, VERR_INVALID_POINTER); + AssertPtrReturn(ppszPassword, VERR_INVALID_POINTER); + AssertPtrReturn(ppszDomain, VERR_INVALID_POINTER); + + VMMDevCredentials Req; + RT_ZERO(Req); + vmmdevInitRequest((VMMDevRequestHeader*)&Req, VMMDevReq_QueryCredentials); + Req.u32Flags |= VMMDEV_CREDENTIALS_READ | VMMDEV_CREDENTIALS_CLEAR; + + int rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + { + rc = RTStrDupEx(ppszUser, Req.szUserName); + if (RT_SUCCESS(rc)) + { + rc = RTStrDupEx(ppszPassword, Req.szPassword); + if (RT_SUCCESS(rc)) + { + rc = RTStrDupEx(ppszDomain, Req.szDomain); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + RTStrFree(*ppszPassword); + } + RTStrFree(*ppszUser); + } + } + return rc; +} + + +/** + * Retrieves and clears the user credentials for logging into the guest OS. + * UTF-16 version. + * + * @returns IPRT status value + * @param ppwszUser Receives pointer of allocated user name string. + * The returned pointer must be freed using VbglR3CredentialsDestroyUtf16(). + * @param ppwszPassword Receives pointer of allocated user password string. + * The returned pointer must be freed using VbglR3CredentialsDestroyUtf16(). + * @param ppwszDomain Receives pointer of allocated domain name string. + * The returned pointer must be freed using VbglR3CredentialsDestroyUtf16(). + */ +VBGLR3DECL(int) VbglR3CredentialsRetrieveUtf16(PRTUTF16 *ppwszUser, PRTUTF16 *ppwszPassword, PRTUTF16 *ppwszDomain) +{ + AssertPtrReturn(ppwszUser, VERR_INVALID_POINTER); + AssertPtrReturn(ppwszPassword, VERR_INVALID_POINTER); + AssertPtrReturn(ppwszDomain, VERR_INVALID_POINTER); + + char *pszUser, *pszPassword, *pszDomain; + int rc = VbglR3CredentialsRetrieve(&pszUser, &pszPassword, &pszDomain); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwszUser = NULL; + PRTUTF16 pwszPassword = NULL; + PRTUTF16 pwszDomain = NULL; + + rc = RTStrToUtf16(pszUser, &pwszUser); + if (RT_SUCCESS(rc)) + { + rc = RTStrToUtf16(pszPassword, &pwszPassword); + if (RT_SUCCESS(rc)) + rc = RTStrToUtf16(pszDomain, &pwszDomain); + } + + if (RT_SUCCESS(rc)) + { + *ppwszUser = pwszUser; + *ppwszPassword = pwszPassword; + *ppwszDomain = pwszDomain; + } + else + VbglR3CredentialsDestroyUtf16(pwszUser, pwszPassword, pwszDomain, 3 /* Passes */); + VbglR3CredentialsDestroy(pszUser, pszPassword, pszDomain, 3 /* Passes */); + } + + return rc; +} + + +/** + * Clears and frees the three strings. + * + * @param pszUser Receives pointer of the user name string to destroy. + * Optional. + * @param pszPassword Receives pointer of the password string to destroy. + * Optional. + * @param pszDomain Receives pointer of allocated domain name string. + * Optional. + * @param cPasses Number of wipe passes. The more the better + slower. + */ +VBGLR3DECL(void) VbglR3CredentialsDestroy(char *pszUser, char *pszPassword, char *pszDomain, uint32_t cPasses) +{ + /* wipe first */ + if (pszUser) + RTMemWipeThoroughly(pszUser, strlen(pszUser) + 1, cPasses); + if (pszPassword) + RTMemWipeThoroughly(pszPassword, strlen(pszPassword) + 1, cPasses); + if (pszDomain) + RTMemWipeThoroughly(pszDomain, strlen(pszDomain) + 1, cPasses); + + /* then free. */ + RTStrFree(pszUser); + RTStrFree(pszPassword); + RTStrFree(pszDomain); +} + + +/** + * Clears and frees the three strings. UTF-16 version. + * + * @param pwszUser Receives pointer of the user name string to destroy. + * Optional. + * @param pwszPassword Receives pointer of the password string to destroy. + * Optional. + * @param pwszDomain Receives pointer of allocated domain name string. + * Optional. + * @param cPasses Number of wipe passes. The more the better + slower. + */ +VBGLR3DECL(void) VbglR3CredentialsDestroyUtf16(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszDomain, + uint32_t cPasses) +{ + /* wipe first */ + if (pwszUser) + RTMemWipeThoroughly(pwszUser, (RTUtf16Len(pwszUser) + 1) * sizeof(RTUTF16), cPasses); + if (pwszPassword) + RTMemWipeThoroughly(pwszPassword, (RTUtf16Len(pwszPassword) + 1) * sizeof(RTUTF16), cPasses); + if (pwszDomain) + RTMemWipeThoroughly(pwszDomain, (RTUtf16Len(pwszDomain) + 1) * sizeof(RTUTF16), cPasses); + + /* then free. */ + RTUtf16Free(pwszUser); + RTUtf16Free(pwszPassword); + RTUtf16Free(pwszDomain); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp new file mode 100644 index 00000000..2aad8c5c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp @@ -0,0 +1,326 @@ +/** $Id: VBoxGuestR3LibDaemonize.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, daemonize a process. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if defined(RT_OS_OS2) +# define INCL_BASE +# define INCL_ERRORS +# include <os2.h> + +# include <iprt/alloca.h> +# include <iprt/string.h> + +#elif defined(RT_OS_WINDOWS) +# error "PORTME" + +#else /* the unices */ +# include <sys/types.h> +# include <sys/stat.h> +# include <sys/wait.h> +# include <stdio.h> +# include <fcntl.h> +# include <stdlib.h> +# include <unistd.h> +# include <signal.h> +# include <errno.h> +#endif + +#include <iprt/process.h> +#include <iprt/string.h> +#include <VBox/err.h> +#include <VBox/log.h> + +#include "VBoxGuestR3LibInternal.h" + + +/** + * Daemonize the process for running in the background. + * + * This is supposed to do the same job as the BSD daemon() call. + * + * @returns 0 on success + * + * @param fNoChDir Pass false to change working directory to root. + * @param fNoClose Pass false to redirect standard file streams to /dev/null. + * @param fRespawn Restart the daemonised process after five seconds if it + * terminates abnormally. + * @param pcRespawn Where to store a count of how often we have respawned, + * intended for avoiding error spamming. Optional. + * @param fReturnOnUpdate If True, this function will return control to caller when + * child process will terminate with exit code of VBGLR3EXITCODERELOAD, + * indicating that Guest Additions update has been started and this running + * process will be asked to be restarted by arrival of the next SIGUSR1 + * signal (caller should wait for SIGUSR1). If False, this functions will + * never return, but rather exit() when child process terminates with + * exit code 0. + * @param pfUpdateStarted A flag which passed to caller if fReturnOnUpdate is True (can be NULL). + * @param szPidfile Optional path to parent process' pidfile (can be NULL). + * @param phPidfile Optional path to parent process' pidfile handle (can not be NULL if + * szPidfile was specified). + * + * @todo Use RTProcDaemonize instead of this. + * @todo Implement fRespawn on OS/2. + * @todo Make the respawn interval configurable. But not until someone + * actually needs that. + */ +VBGLR3DECL(int) VbglR3DaemonizeEx(bool fNoChDir, bool fNoClose, bool fRespawn, unsigned *pcRespawn, + bool fReturnOnUpdate, bool *pfUpdateStarted, const char *szPidfile, + RTFILE *phPidfile) +{ +#if defined(RT_OS_OS2) + PPIB pPib; + PTIB pTib; + DosGetInfoBlocks(&pTib, &pPib); + + RT_NOREF(fReturnOnUpdate); + RT_NOREF(pfUpdateStarted); + RT_NOREF(szPidfile); + RT_NOREF(phPidfile); + + AssertRelease(!fRespawn); + /* Get the full path to the executable. */ + char szExe[CCHMAXPATH]; + APIRET rc = DosQueryModuleName(pPib->pib_hmte, sizeof(szExe), szExe); + if (rc) + return RTErrConvertFromOS2(rc); + + /* calc the length of the command line. */ + char *pch = pPib->pib_pchcmd; + size_t cch0 = strlen(pch); + pch += cch0 + 1; + size_t cch1 = strlen(pch); + pch += cch1 + 1; + char *pchArgs; + if (cch1 && *pch) + { + do pch = strchr(pch, '\0') + 1; + while (*pch); + + size_t cchTotal = pch - pPib->pib_pchcmd; + pchArgs = (char *)alloca(cchTotal + sizeof("--daemonized\0\0")); + memcpy(pchArgs, pPib->pib_pchcmd, cchTotal - 1); + memcpy(pchArgs + cchTotal - 1, "--daemonized\0\0", sizeof("--daemonized\0\0")); + } + else + { + size_t cchTotal = pch - pPib->pib_pchcmd + 1; + pchArgs = (char *)alloca(cchTotal + sizeof(" --daemonized ")); + memcpy(pchArgs, pPib->pib_pchcmd, cch0 + 1); + pch = pchArgs + cch0 + 1; + memcpy(pch, " --daemonized ", sizeof(" --daemonized ") - 1); + pch += sizeof(" --daemonized ") - 1; + if (cch1) + memcpy(pch, pPib->pib_pchcmd + cch0 + 1, cch1 + 2); + else + pch[0] = pch[1] = '\0'; + } + + /* spawn a detach process */ + char szObj[128]; + RESULTCODES ResCodes = { 0, 0 }; + szObj[0] = '\0'; + rc = DosExecPgm(szObj, sizeof(szObj), EXEC_BACKGROUND, (PCSZ)pchArgs, NULL, &ResCodes, (PCSZ)szExe); + if (rc) + { + /** @todo Change this to some standard log/print error?? */ + /* VBoxServiceError("DosExecPgm failed with rc=%d and szObj='%s'\n", rc, szObj); */ + return RTErrConvertFromOS2(rc); + } + DosExit(EXIT_PROCESS, 0); + return VERR_GENERAL_FAILURE; + +#elif defined(RT_OS_WINDOWS) +# error "PORTME" + +#else /* the unices */ + /* + * Fork the child process in a new session and quit the parent. + * + * - fork once and create a new session (setsid). This will detach us + * from the controlling tty meaning that we won't receive the SIGHUP + * (or any other signal) sent to that session. + * - The SIGHUP signal is ignored because the session/parent may throw + * us one before we get to the setsid. + * - When the parent exit(0) we will become an orphan and re-parented to + * the init process. + * - Because of the Linux / System V semantics of assigning the controlling + * tty automagically when a session leader first opens a tty, we will + * fork() once more on Linux to get rid of the session leadership role. + */ + + struct sigaction OldSigAct; + struct sigaction SigAct; + RT_ZERO(SigAct); + SigAct.sa_handler = SIG_IGN; + int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct); + + pid_t pid = fork(); + if (pid == -1) + return RTErrConvertFromErrno(errno); + if (pid != 0) + exit(0); + + /* + * The orphaned child becomes is reparented to the init process. + * We create a new session for it (setsid), point the standard + * file descriptors to /dev/null, and change to the root directory. + */ + pid_t newpgid = setsid(); + int SavedErrno = errno; + if (rcSigAct != -1) + sigaction(SIGHUP, &OldSigAct, NULL); + if (newpgid == -1) + return RTErrConvertFromErrno(SavedErrno); + + if (!fNoClose) + { + /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */ + int fd = open("/dev/null", O_RDWR); + if (fd == -1) /* paranoia */ + { + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + fd = open("/dev/null", O_RDWR); + } + if (fd != -1) + { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > 2) + close(fd); + } + } + + if (!fNoChDir) + { + int rcShutUpGcc = chdir("/"); + RT_NOREF_PV(rcShutUpGcc); + } + + /* + * Change the umask - this is non-standard daemon() behavior. + */ + umask(027); + +# ifdef RT_OS_LINUX + /* + * And fork again to lose session leader status (non-standard daemon() + * behaviour). + */ + pid = fork(); + if (pid == -1) + return RTErrConvertFromErrno(errno); + if (pid != 0) + exit(0); +# endif /* RT_OS_LINUX */ + + /* Check if another instance is already running. */ + if (szPidfile != NULL) + { + if (phPidfile != NULL) + { + int rc = VbglR3PidfileWait(szPidfile, phPidfile, 5000); + + /* Another instance of process is already running. */ + if (rc == VERR_FILE_LOCK_VIOLATION) + { + LogRel(("cannot aquire pidfile %s, exitting\n", szPidfile)); + exit(1); + } + + /* Unable to lock on pidfile. */ + if (RT_FAILURE(rc)) + exit(1); + } + else + return VERR_INVALID_PARAMETER; + } + + if (fRespawn) + { + /* We implement re-spawning as a third fork(), with the parent process + * monitoring the child and re-starting it after a delay if it exits + * abnormally. */ + unsigned cRespawn = 0; + for (;;) + { + int iStatus, rcWait; + + if (pcRespawn != NULL) + *pcRespawn = cRespawn; + pid = fork(); + if (pid == -1) + return RTErrConvertFromErrno(errno); + if (pid == 0) + return VINF_SUCCESS; + do + rcWait = waitpid(pid, &iStatus, 0); + while (rcWait == -1 && errno == EINTR); + if (rcWait == -1) + exit(1); + if (WIFEXITED(iStatus)) + { + if (WEXITSTATUS(iStatus) == 0) + exit(0); + else if (fReturnOnUpdate && WEXITSTATUS(iStatus) == VBGLR3EXITCODERELOAD) + { + /* Tell caller that update has been started. */ + if (pfUpdateStarted != NULL) + *pfUpdateStarted = true; + + return VINF_SUCCESS; + } + } + sleep(5); + ++cRespawn; + } + } + return VINF_SUCCESS; +#endif +} + +/** + * A wrapper function for VbglR3DaemonizeEx. + */ +VBGLR3DECL(int) VbglR3Daemonize(bool fNoChDir, bool fNoClose, bool fRespawn, unsigned *pcRespawn) +{ + return VbglR3DaemonizeEx(fNoChDir, fNoClose, fRespawn, pcRespawn, false, NULL, NULL, NULL); +} diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp new file mode 100644 index 00000000..68ba038b --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp @@ -0,0 +1,1948 @@ +/* $Id: VBoxGuestR3LibDragAndDrop.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Drag & Drop. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/path.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/uri.h> +#include <iprt/thread.h> + +#include <iprt/cpp/list.h> +#include <iprt/cpp/ministring.h> + +#ifdef LOG_GROUP + #undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_GUEST_DND +#include <VBox/log.h> + +#include <VBox/VBoxGuestLib.h> +#include <VBox/GuestHost/DragAndDrop.h> +#include <VBox/HostServices/DragAndDropSvc.h> + +using namespace DragAndDropSvc; + +#include "VBoxGuestR3LibInternal.h" + + +/********************************************************************************************************************************* +* Private internal functions * +*********************************************************************************************************************************/ + +/** + * Receives the next upcoming message for a given DnD context. + * + * @returns IPRT status code. + * Will return VERR_CANCELLED (implemented by the host service) if we need to bail out. + * @param pCtx DnD context to use. + * @param puMsg Where to store the message type. + * @param pcParms Where to store the number of parameters required for receiving the message. + * @param fWait Whether to wait (block) for a new message to arrive or not. + */ +static int vbglR3DnDGetNextMsgType(PVBGLR3GUESTDNDCMDCTX pCtx, uint32_t *puMsg, uint32_t *pcParms, bool fWait) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(puMsg, VERR_INVALID_POINTER); + AssertPtrReturn(pcParms, VERR_INVALID_POINTER); + + int rc; + + do + { + HGCMMsgGetNext Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_GET_NEXT_HOST_MSG, 3); + Msg.uMsg.SetUInt32(0); + Msg.cParms.SetUInt32(0); + Msg.fBlock.SetUInt32(fWait ? 1 : 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.uMsg.GetUInt32(puMsg); AssertRC(rc); + rc = Msg.cParms.GetUInt32(pcParms); AssertRC(rc); + } + + LogRel(("DnD: Received message %s (%#x) from host\n", DnDHostMsgToStr(*puMsg), *puMsg)); + + } while (rc == VERR_INTERRUPTED); + + return rc; +} + + +/** + * Sends a DnD error back to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param rcErr Error (IPRT-style) to send. + */ +VBGLR3DECL(int) VbglR3DnDSendError(PVBGLR3GUESTDNDCMDCTX pCtx, int rcErr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMMsgGHError Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_EVT_ERROR, 2); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.rc.SetUInt32((uint32_t)rcErr); /* uint32_t vs. int. */ + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + /* + * Never return an error if the host did not accept the error at the current + * time. This can be due to the host not having any appropriate callbacks + * set which would handle that error. + * + * bird: Looks like VERR_NOT_SUPPORTED is what the host will return if it + * doesn't an appropriate callback. The code used to ignore ALL errors + * the host would return, also relevant ones. + */ + if (RT_FAILURE(rc)) + LogFlowFunc(("Sending error %Rrc failed with rc=%Rrc\n", rcErr, rc)); + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; + + return rc; +} + +/** + * Host -> Guest + * Utility function to receive a so-called "action message" from the host. + * Certain DnD messages use the same amount / sort of parameters and grouped as "action messages". + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param uMsg Which kind of message to receive. + * @param puScreenID Where to store the host screen ID the message is bound to. Optional. + * @param puX Where to store the absolute X coordinates. Optional. + * @param puY Where to store the absolute Y coordinates. Optional. + * @param puDefAction Where to store the default action to perform. Optional. + * @param puAllActions Where to store the available actions. Optional. + * @param ppszFormats Where to store List of formats. Optional. + * @param pcbFormats Size (in bytes) of where to store the list of formats. Optional. + * + * @todo r=andy Get rid of this function as soon as we resolved the protocol TODO #1. + * This was part of the initial protocol and needs to go. + */ +static int vbglR3DnDHGRecvAction(PVBGLR3GUESTDNDCMDCTX pCtx, + uint32_t uMsg, + uint32_t *puScreenID, + uint32_t *puX, + uint32_t *puY, + uint32_t *puDefAction, + uint32_t *puAllActions, + char **ppszFormats, + uint32_t *pcbFormats) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + /* The rest is optional. */ + + const uint32_t cbFormatsTmp = pCtx->cbMaxChunkSize; + + char *pszFormatsTmp = static_cast<char *>(RTMemAlloc(cbFormatsTmp)); + if (!pszFormatsTmp) + return VERR_NO_MEMORY; + + HGCMMsgHGAction Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, uMsg, 8); + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uScreenId.SetUInt32(0); + Msg.u.v3.uX.SetUInt32(0); + Msg.u.v3.uY.SetUInt32(0); + Msg.u.v3.uDefAction.SetUInt32(0); + Msg.u.v3.uAllActions.SetUInt32(0); + Msg.u.v3.pvFormats.SetPtr(pszFormatsTmp, cbFormatsTmp); + Msg.u.v3.cbFormats.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Context ID not used yet. */ + if (RT_SUCCESS(rc) && puScreenID) + rc = Msg.u.v3.uScreenId.GetUInt32(puScreenID); + if (RT_SUCCESS(rc) && puX) + rc = Msg.u.v3.uX.GetUInt32(puX); + if (RT_SUCCESS(rc) && puY) + rc = Msg.u.v3.uY.GetUInt32(puY); + if (RT_SUCCESS(rc) && puDefAction) + rc = Msg.u.v3.uDefAction.GetUInt32(puDefAction); + if (RT_SUCCESS(rc) && puAllActions) + rc = Msg.u.v3.uAllActions.GetUInt32(puAllActions); + if (RT_SUCCESS(rc) && pcbFormats) + rc = Msg.u.v3.cbFormats.GetUInt32(pcbFormats); + + if (RT_SUCCESS(rc)) + { + if (ppszFormats) + { + *ppszFormats = RTStrDup(pszFormatsTmp); + if (!*ppszFormats) + rc = VERR_NO_MEMORY; + } + } + } + + RTStrFree(pszFormatsTmp); + + return rc; +} + +/** + * Host -> Guest + * Utility function to receive a HOST_DND_FN_HG_EVT_LEAVE message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + */ +static int vbglR3DnDHGRecvLeave(PVBGLR3GUESTDNDCMDCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMMsgHGLeave Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_EVT_LEAVE, 1); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Host -> Guest + * Utility function to receive a HOST_DND_FN_HG_EVT_CANCEL message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + */ +static int vbglR3DnDHGRecvCancel(PVBGLR3GUESTDNDCMDCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMMsgHGCancel Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_CANCEL, 1); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Host -> Guest + * Utility function to receive a HOST_DND_FN_HG_SND_DIR message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pszDirname Where to store the directory name of the directory being created. + * @param cbDirname Size (in bytes) of where to store the directory name of the directory being created. + * @param pcbDirnameRecv Size (in bytes) of the actual directory name received. + * @param pfMode Where to store the directory creation mode. + */ +static int vbglR3DnDHGRecvDir(PVBGLR3GUESTDNDCMDCTX pCtx, + char *pszDirname, + uint32_t cbDirname, + uint32_t *pcbDirnameRecv, + uint32_t *pfMode) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pszDirname, VERR_INVALID_POINTER); + AssertReturn(cbDirname, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbDirnameRecv, VERR_INVALID_POINTER); + AssertPtrReturn(pfMode, VERR_INVALID_POINTER); + + HGCMMsgHGSendDir Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_SND_DIR, 4); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvName.SetPtr(pszDirname, cbDirname); + Msg.u.v3.cbName.SetUInt32(cbDirname); + Msg.u.v3.fMode.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Context ID not used yet. */ + rc = Msg.u.v3.cbName.GetUInt32(pcbDirnameRecv); AssertRC(rc); + rc = Msg.u.v3.fMode.GetUInt32(pfMode); AssertRC(rc); + + AssertReturn(cbDirname >= *pcbDirnameRecv, VERR_TOO_MUCH_DATA); + } + + return rc; +} + +/** + * Host -> Guest + * Utility function to receive a HOST_DND_FN_HG_SND_FILE_DATA message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pvData Where to store the file data chunk. + * @param cbData Size (in bytes) of where to store the data chunk. + * @param pcbDataRecv Size (in bytes) of the actual data chunk size received. + */ +static int vbglR3DnDHGRecvFileData(PVBGLR3GUESTDNDCMDCTX pCtx, + void *pvData, + uint32_t cbData, + uint32_t *pcbDataRecv) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbDataRecv, VERR_INVALID_POINTER); + + HGCMMsgHGSendFileData Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_SND_FILE_DATA, 5); + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvData.SetPtr(pvData, cbData); + Msg.u.v3.cbData.SetUInt32(0); + Msg.u.v3.pvChecksum.SetPtr(NULL, 0); + Msg.u.v3.cbChecksum.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Context ID not used yet. */ + rc = Msg.u.v3.cbData.GetUInt32(pcbDataRecv); AssertRC(rc); + AssertReturn(cbData >= *pcbDataRecv, VERR_TOO_MUCH_DATA); + /** @todo Add checksum support. */ + } + + return rc; +} + +/** + * Host -> Guest + * Utility function to receive the HOST_DND_FN_HG_SND_FILE_HDR message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pszFilename Where to store the file name of the file being transferred. + * @param cbFilename Size (in bytes) of where to store the file name of the file being transferred. + * @param puFlags File transfer flags. Currently not being used. + * @param pfMode Where to store the file creation mode. + * @param pcbTotal Where to store the file size (in bytes). + */ +static int vbglR3DnDHGRecvFileHdr(PVBGLR3GUESTDNDCMDCTX pCtx, + char *pszFilename, + uint32_t cbFilename, + uint32_t *puFlags, + uint32_t *pfMode, + uint64_t *pcbTotal) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertReturn(cbFilename, VERR_INVALID_PARAMETER); + AssertPtrReturn(puFlags, VERR_INVALID_POINTER); + AssertPtrReturn(pfMode, VERR_INVALID_POINTER); + AssertReturn(pcbTotal, VERR_INVALID_POINTER); + + HGCMMsgHGSendFileHdr Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_SND_FILE_HDR, 6); + Msg.uContext.SetUInt32(0); /** @todo Not used yet. */ + Msg.pvName.SetPtr(pszFilename, cbFilename); + Msg.cbName.SetUInt32(cbFilename); + Msg.uFlags.SetUInt32(0); + Msg.fMode.SetUInt32(0); + Msg.cbTotal.SetUInt64(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Get context ID. */ + rc = Msg.uFlags.GetUInt32(puFlags); AssertRC(rc); + rc = Msg.fMode.GetUInt32(pfMode); AssertRC(rc); + rc = Msg.cbTotal.GetUInt64(pcbTotal); AssertRC(rc); + } + + return rc; +} + +/** + * Host -> Guest + * Helper function for receiving URI data from the host. Do not call directly. + * This function also will take care of the file creation / locking on the guest. + * + * @returns IPRT status code. + * @retval VERR_CANCELLED if the transfer was cancelled by the host. + * @param pCtx DnD context to use. + * @param pDataHdr DnD data header to use. Needed for accounting. + * @param pDroppedFiles Dropped files object to use for maintaining the file creation / locking. + */ +static int vbglR3DnDHGRecvURIData(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr, PDNDDROPPEDFILES pDroppedFiles) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER); + AssertPtrReturn(pDroppedFiles, VERR_INVALID_POINTER); + + /* Only count the raw data minus the already received meta data. */ + Assert(pDataHdr->cbTotal >= pDataHdr->cbMeta); + uint64_t cbToRecvBytes = pDataHdr->cbTotal - pDataHdr->cbMeta; + uint64_t cToRecvObjs = pDataHdr->cObjects; + + LogFlowFunc(("cbToRecvBytes=%RU64, cToRecvObjs=%RU64, (cbTotal=%RU64, cbMeta=%RU32)\n", + cbToRecvBytes, cToRecvObjs, pDataHdr->cbTotal, pDataHdr->cbMeta)); + + /* Anything to do at all? */ + /* Note: Do not check for cbToRecvBytes == 0 here, as this might be just + * a bunch of 0-byte files to be transferred. */ + if (!cToRecvObjs) + return VINF_SUCCESS; + + LogRel2(("DnD: Receiving URI data started\n")); + + /* + * Allocate temporary chunk buffer. + */ + uint32_t cbChunkMax = pCtx->cbMaxChunkSize; + void *pvChunk = RTMemAlloc(cbChunkMax); + if (!pvChunk) + return VERR_NO_MEMORY; + uint32_t cbChunkRead = 0; + + uint64_t cbFileSize = 0; /* Total file size (in bytes). */ + uint64_t cbFileWritten = 0; /* Written bytes. */ + + const char *pszDropDir = DnDDroppedFilesGetDirAbs(pDroppedFiles); + AssertPtr(pszDropDir); + + int rc; + + /* + * Enter the main loop of retieving files + directories. + */ + DNDTRANSFEROBJECT objCur; + RT_ZERO(objCur); + + char szPathName[RTPATH_MAX] = { 0 }; + uint32_t cbPathName = 0; + uint32_t fFlags = 0; + uint32_t fMode = 0; + + do + { + LogFlowFunc(("Waiting for new message ...\n")); + + uint32_t uNextMsg; + uint32_t cNextParms; + rc = vbglR3DnDGetNextMsgType(pCtx, &uNextMsg, &cNextParms, true /* fWait */); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("uNextMsg=%RU32, cNextParms=%RU32\n", uNextMsg, cNextParms)); + + switch (uNextMsg) + { + case HOST_DND_FN_HG_SND_DIR: + { + rc = vbglR3DnDHGRecvDir(pCtx, + szPathName, + sizeof(szPathName), + &cbPathName, + &fMode); + LogFlowFunc(("HOST_DND_FN_HG_SND_DIR: " + "pszPathName=%s, cbPathName=%RU32, fMode=0x%x, rc=%Rrc\n", + szPathName, cbPathName, fMode, rc)); + + char *pszPathAbs = RTPathJoinA(pszDropDir, szPathName); + if (pszPathAbs) + { +#ifdef RT_OS_WINDOWS + uint32_t fCreationMode = (fMode & RTFS_DOS_MASK) | RTFS_DOS_NT_NORMAL; +#else + uint32_t fCreationMode = (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRWXU; +#endif + rc = RTDirCreate(pszPathAbs, fCreationMode, 0); + if (RT_SUCCESS(rc)) + rc = DnDDroppedFilesAddDir(pDroppedFiles, pszPathAbs); + + if (RT_SUCCESS(rc)) + { + Assert(cToRecvObjs); + cToRecvObjs--; + } + + RTStrFree(pszPathAbs); + } + else + rc = VERR_NO_MEMORY; + break; + } + case HOST_DND_FN_HG_SND_FILE_HDR: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_SND_FILE_DATA: + { + if (uNextMsg == HOST_DND_FN_HG_SND_FILE_HDR) + { + rc = vbglR3DnDHGRecvFileHdr(pCtx, + szPathName, + sizeof(szPathName), + &fFlags, + &fMode, + &cbFileSize); + LogFlowFunc(("HOST_DND_FN_HG_SND_FILE_HDR: " + "szPathName=%s, fFlags=0x%x, fMode=0x%x, cbFileSize=%RU64, rc=%Rrc\n", + szPathName, fFlags, fMode, cbFileSize, rc)); + } + else + { + rc = vbglR3DnDHGRecvFileData(pCtx, + pvChunk, + cbChunkMax, + &cbChunkRead); + LogFlowFunc(("HOST_DND_FN_HG_SND_FILE_DATA: " + "cbChunkRead=%RU32, rc=%Rrc\n", cbChunkRead, rc)); + } + + if ( RT_SUCCESS(rc) + && uNextMsg == HOST_DND_FN_HG_SND_FILE_HDR) + { + char *pszPathAbs = RTPathJoinA(pszDropDir, szPathName); + if (pszPathAbs) + { + LogFlowFunc(("Opening pszPathName=%s, cbPathName=%RU32, fMode=0x%x, cbFileSize=%zu\n", + szPathName, cbPathName, fMode, cbFileSize)); + + uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE + | RTFILE_O_CREATE_REPLACE; + + /* Is there already a file open, e.g. in transfer? */ + if (!DnDTransferObjectIsOpen(&objCur)) + { +#ifdef RT_OS_WINDOWS + uint32_t fCreationMode = (fMode & RTFS_DOS_MASK) | RTFS_DOS_NT_NORMAL; +#else + uint32_t fCreationMode = (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR; +#endif + rc = DnDTransferObjectInitEx(&objCur, DNDTRANSFEROBJTYPE_FILE, + pszDropDir /* Source (base) path */, szPathName /* Destination path */); + if (RT_SUCCESS(rc)) + { + rc = DnDTransferObjectOpen(&objCur, fOpen, fCreationMode, DNDTRANSFEROBJECT_FLAGS_NONE); + if (RT_SUCCESS(rc)) + { + rc = DnDDroppedFilesAddFile(pDroppedFiles, pszPathAbs); + if (RT_SUCCESS(rc)) + { + cbFileWritten = 0; + DnDTransferObjectSetSize(&objCur, cbFileSize); + } + } + } + } + else + { + AssertMsgFailed(("ObjType=%RU32\n", DnDTransferObjectGetType(&objCur))); + rc = VERR_WRONG_ORDER; + } + + RTStrFree(pszPathAbs); + } + else + rc = VERR_NO_MEMORY; + } + + if ( RT_SUCCESS(rc) + && uNextMsg == HOST_DND_FN_HG_SND_FILE_DATA + && cbChunkRead) + { + uint32_t cbChunkWritten; + rc = DnDTransferObjectWrite(&objCur, pvChunk, cbChunkRead, &cbChunkWritten); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("HOST_DND_FN_HG_SND_FILE_DATA: " + "cbChunkRead=%RU32, cbChunkWritten=%RU32, cbFileWritten=%RU64 cbFileSize=%RU64\n", + cbChunkRead, cbChunkWritten, cbFileWritten + cbChunkWritten, cbFileSize)); + + cbFileWritten += cbChunkWritten; + + Assert(cbChunkRead <= cbToRecvBytes); + cbToRecvBytes -= cbChunkRead; + } + } + + /* Data transfer complete? Close the file. */ + bool fClose = DnDTransferObjectIsComplete(&objCur); + if (fClose) + { + Assert(cToRecvObjs); + cToRecvObjs--; + } + + /* Only since protocol v2 we know the file size upfront. */ + Assert(cbFileWritten <= cbFileSize); + + if (fClose) + { + LogFlowFunc(("Closing file\n")); + DnDTransferObjectDestroy(&objCur); + } + + break; + } + case HOST_DND_FN_CANCEL: + { + rc = vbglR3DnDHGRecvCancel(pCtx); + if (RT_SUCCESS(rc)) + rc = VERR_CANCELLED; + break; + } + default: + { + LogRel(("DnD: Warning: Message %s (%#x) from host not supported or in wrong order\n", DnDHostMsgToStr(uNextMsg), uNextMsg)); + rc = VERR_NOT_SUPPORTED; + break; + } + } + } + + if (RT_FAILURE(rc)) + break; + + LogFlowFunc(("cbToRecvBytes=%RU64, cToRecvObjs=%RU64\n", cbToRecvBytes, cToRecvObjs)); + if ( !cbToRecvBytes + && !cToRecvObjs) + { + break; + } + + } while (RT_SUCCESS(rc)); + + LogFlowFunc(("Loop ended with %Rrc\n", rc)); + + /* All URI data processed? */ + if (rc == VERR_NO_DATA) + rc = VINF_SUCCESS; + + /* Delete temp buffer again. */ + if (pvChunk) + RTMemFree(pvChunk); + + /* Cleanup on failure or if the user has canceled the operation or + * something else went wrong. */ + if (RT_FAILURE(rc)) + { + if (rc == VERR_CANCELLED) + LogRel2(("DnD: Receiving URI data was cancelled by the host\n")); + else + LogRel(("DnD: Receiving URI data failed with %Rrc\n", rc)); + + DnDTransferObjectDestroy(&objCur); + DnDDroppedFilesRollback(pDroppedFiles); + } + else + { + LogRel2(("DnD: Receiving URI data finished\n")); + + /** @todo Compare the transfer list with the dirs/files we really transferred. */ + /** @todo Implement checksum verification, if any. */ + } + + /* + * Close the dropped files directory. + * Don't try to remove it here, however, as the files are being needed + * by the client's drag'n drop operation lateron. + */ + int rc2 = DnDDroppedFilesReset(pDroppedFiles, false /* fRemoveDropDir */); + if (RT_FAILURE(rc2)) /* Not fatal, don't report back to host. */ + LogFlowFunc(("Closing dropped files directory failed with %Rrc\n", rc2)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest + * Utility function to receive the HOST_DND_FN_HG_SND_DATA message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pDataHdr DnD data header to use. Need for accounting and stuff. + * @param pvData Where to store the received data from the host. + * @param cbData Size (in bytes) of where to store the received data. + * @param pcbDataRecv Where to store the received amount of data (in bytes). + */ +static int vbglR3DnDHGRecvDataRaw(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr, + void *pvData, uint32_t cbData, uint32_t *pcbDataRecv) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pcbDataRecv, VERR_INVALID_POINTER); + + LogFlowFunc(("pvDate=%p, cbData=%RU32\n", pvData, cbData)); + + HGCMMsgHGSendData Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_SND_DATA, 5); + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvData.SetPtr(pvData, cbData); + Msg.u.v3.cbData.SetUInt32(0); + Msg.u.v3.pvChecksum.SetPtr(NULL, 0); + Msg.u.v3.cbChecksum.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + uint32_t cbDataRecv; + rc = Msg.u.v3.cbData.GetUInt32(&cbDataRecv); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + /** @todo Use checksum for validating the received data. */ + if (pcbDataRecv) + *pcbDataRecv = cbDataRecv; + LogFlowFuncLeaveRC(rc); + return rc; + } + } + + /* failure */ + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest + * Utility function to receive the HOST_DND_FN_HG_SND_DATA_HDR message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pDataHdr Where to store the receivd DnD data header. + */ +static int vbglR3DnDHGRecvDataHdr(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER); + + Assert(pCtx->uProtocolDeprecated >= 3); /* Only for protocol v3 and up. */ + + HGCMMsgHGSendDataHdr Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_SND_DATA_HDR, 12); + Msg.uContext.SetUInt32(0); + Msg.uFlags.SetUInt32(0); + Msg.uScreenId.SetUInt32(0); + Msg.cbTotal.SetUInt64(0); + Msg.cbMeta.SetUInt32(0); + Msg.pvMetaFmt.SetPtr(pDataHdr->pvMetaFmt, pDataHdr->cbMetaFmt); + Msg.cbMetaFmt.SetUInt32(0); + Msg.cObjects.SetUInt64(0); + Msg.enmCompression.SetUInt32(0); + Msg.enmChecksumType.SetUInt32(0); + Msg.pvChecksum.SetPtr(pDataHdr->pvChecksum, pDataHdr->cbChecksum); + Msg.cbChecksum.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /* Msg.uContext not needed here. */ + Msg.uFlags.GetUInt32(&pDataHdr->uFlags); + Msg.uScreenId.GetUInt32(&pDataHdr->uScreenId); + Msg.cbTotal.GetUInt64(&pDataHdr->cbTotal); + Msg.cbMeta.GetUInt32(&pDataHdr->cbMeta); + Msg.cbMetaFmt.GetUInt32(&pDataHdr->cbMetaFmt); + Msg.cObjects.GetUInt64(&pDataHdr->cObjects); + Msg.enmCompression.GetUInt32(&pDataHdr->enmCompression); + Msg.enmChecksumType.GetUInt32((uint32_t *)&pDataHdr->enmChecksumType); + Msg.cbChecksum.GetUInt32(&pDataHdr->cbChecksum); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest + * Helper function for receiving the actual DnD data from the host. Do not call directly. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pDataHdr Where to store the data header data. + * @param ppvData Returns the received meta data. Needs to be free'd by the caller. + * @param pcbData Where to store the size (in bytes) of the received meta data. + */ +static int vbglR3DnDHGRecvDataLoop(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr, + void **ppvData, uint64_t *pcbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER); + AssertPtrReturn(ppvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbData, VERR_INVALID_POINTER); + + int rc; + uint32_t cbDataRecv; + + LogFlowFuncEnter(); + + rc = vbglR3DnDHGRecvDataHdr(pCtx, pDataHdr); + if (RT_FAILURE(rc)) + return rc; + + LogFlowFunc(("cbTotal=%RU64, cbMeta=%RU32, cObjects=%RU32\n", pDataHdr->cbTotal, pDataHdr->cbMeta, pDataHdr->cObjects)); + if (pDataHdr->cbMeta) + { + uint64_t cbDataTmp = 0; + void *pvDataTmp = RTMemAlloc(pDataHdr->cbMeta); + if (!pvDataTmp) + rc = VERR_NO_MEMORY; + + if (RT_SUCCESS(rc)) + { + uint8_t *pvDataOff = (uint8_t *)pvDataTmp; + while (cbDataTmp < pDataHdr->cbMeta) + { + rc = vbglR3DnDHGRecvDataRaw(pCtx, pDataHdr, + pvDataOff, RT_MIN(pDataHdr->cbMeta - cbDataTmp, pCtx->cbMaxChunkSize), + &cbDataRecv); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("cbDataRecv=%RU32, cbDataTmp=%RU64\n", cbDataRecv, cbDataTmp)); + Assert(cbDataTmp + cbDataRecv <= pDataHdr->cbMeta); + cbDataTmp += cbDataRecv; + pvDataOff += cbDataRecv; + } + else + break; + } + + if (RT_SUCCESS(rc)) + { + Assert(cbDataTmp == pDataHdr->cbMeta); + + LogFlowFunc(("Received %RU64 bytes of data\n", cbDataTmp)); + + *ppvData = pvDataTmp; + *pcbData = cbDataTmp; + } + else + RTMemFree(pvDataTmp); + } + } + else + { + *ppvData = NULL; + *pcbData = 0; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest + * Main function for receiving the actual DnD data from the host. + * + * @returns VBox status code. + * @retval VERR_CANCELLED if cancelled by the host. + * @param pCtx DnD context to use. + * @param pMeta Where to store the actual meta data received from the host. + */ +static int vbglR3DnDHGRecvDataMain(PVBGLR3GUESTDNDCMDCTX pCtx, + PVBGLR3GUESTDNDMETADATA pMeta) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pMeta, VERR_INVALID_POINTER); + + AssertMsgReturn(pCtx->cbMaxChunkSize, ("Maximum chunk size must not be 0\n"), VERR_INVALID_PARAMETER); + + VBOXDNDDATAHDR dataHdr; + RT_ZERO(dataHdr); + dataHdr.cbMetaFmt = pCtx->cbMaxChunkSize; + dataHdr.pvMetaFmt = RTMemAlloc(dataHdr.cbMetaFmt); + if (!dataHdr.pvMetaFmt) + return VERR_NO_MEMORY; + + void *pvData = NULL; + uint64_t cbData = 0; + int rc = vbglR3DnDHGRecvDataLoop(pCtx, &dataHdr, &pvData, &cbData); + if (RT_SUCCESS(rc)) + { + LogRel2(("DnD: Received %RU64 bytes meta data in format '%s'\n", cbData, (char *)dataHdr.pvMetaFmt)); + + /** + * Check if this is an URI event. If so, let VbglR3 do all the actual + * data transfer + file/directory creation internally without letting + * the caller know. + * + * This keeps the actual (guest OS-)dependent client (like VBoxClient / + * VBoxTray) small by not having too much redundant code. + */ + Assert(dataHdr.cbMetaFmt); + AssertPtr(dataHdr.pvMetaFmt); + if (DnDMIMEHasFileURLs((char *)dataHdr.pvMetaFmt, dataHdr.cbMetaFmt)) /* URI data. */ + { + DNDDROPPEDFILES droppedFiles; + RT_ZERO(droppedFiles); + + rc = DnDDroppedFilesInit(&droppedFiles); + if (RT_SUCCESS(rc)) + rc = DnDDroppedFilesOpenTemp(&droppedFiles, DNDURIDROPPEDFILE_FLAGS_NONE); + + if (RT_FAILURE(rc)) + { + LogRel(("DnD: Initializing dropped files directory failed with %Rrc\n", rc)); + } + else + { + AssertPtr(pvData); + Assert(cbData); + + /* Use the dropped files directory as the root directory for the current transfer. */ + rc = DnDTransferListInitEx(&pMeta->u.URI.Transfer, DnDDroppedFilesGetDirAbs(&droppedFiles), + DNDTRANSFERLISTFMT_NATIVE); + if (RT_SUCCESS(rc)) + { + rc = DnDTransferListAppendRootsFromBuffer(&pMeta->u.URI.Transfer, DNDTRANSFERLISTFMT_URI, (const char *)pvData, cbData, + DND_PATH_SEPARATOR_STR, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + { + rc = vbglR3DnDHGRecvURIData(pCtx, &dataHdr, &droppedFiles); + if (RT_SUCCESS(rc)) + { + pMeta->enmType = VBGLR3GUESTDNDMETADATATYPE_URI_LIST; + } + } + } + } + } + else /* Raw data. */ + { + pMeta->u.Raw.cbMeta = cbData; + pMeta->u.Raw.pvMeta = pvData; + + pMeta->enmType = VBGLR3GUESTDNDMETADATATYPE_RAW; + } + + if (pvData) + RTMemFree(pvData); + } + + if (dataHdr.pvMetaFmt) + RTMemFree(dataHdr.pvMetaFmt); + + if (RT_FAILURE(rc)) + { + if (rc != VERR_CANCELLED) + { + LogRel(("DnD: Receiving data failed with %Rrc\n", rc)); + + int rc2 = VbglR3DnDHGSendProgress(pCtx, DND_PROGRESS_ERROR, 100 /* Percent */, rc); + if (RT_FAILURE(rc2)) + LogRel(("DnD: Unable to send progress error %Rrc to host: %Rrc\n", rc, rc2)); + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH +/** + * Guest -> Host + * Utility function to receive the HOST_DND_FN_GH_REQ_PENDING message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param puScreenID For which screen on the host the request is for. Optional. + */ +static int vbglR3DnDGHRecvPending(PVBGLR3GUESTDNDCMDCTX pCtx, uint32_t *puScreenID) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + /* pScreenID is optional. */ + + HGCMMsgGHReqPending Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_GH_REQ_PENDING, 2); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uScreenId.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Context ID not used yet. */ + if (puScreenID) + rc = Msg.u.v3.uContext.GetUInt32(puScreenID); + } + + return rc; +} + +/** + * Guest -> Host + * Utility function to receive the HOST_DND_FN_GH_EVT_DROPPED message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param ppszFormat Requested data format from the host. Optional. + * @param pcbFormat Size of requested data format (in bytes). Optional. + * @param puAction Requested action from the host. Optional. + */ +static int vbglR3DnDGHRecvDropped(PVBGLR3GUESTDNDCMDCTX pCtx, + char **ppszFormat, + uint32_t *pcbFormat, + uint32_t *puAction) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + /* The rest is optional. */ + + const uint32_t cbFormatTmp = pCtx->cbMaxChunkSize; + + char *pszFormatTmp = static_cast<char *>(RTMemAlloc(cbFormatTmp)); + if (!pszFormatTmp) + return VERR_NO_MEMORY; + + HGCMMsgGHDropped Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_GH_EVT_DROPPED, 4); + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvFormat.SetPtr(pszFormatTmp, cbFormatTmp); + Msg.u.v3.cbFormat.SetUInt32(0); + Msg.u.v3.uAction.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Context ID not used yet. */ + if (pcbFormat) + rc = Msg.u.v3.cbFormat.GetUInt32(pcbFormat); + if (RT_SUCCESS(rc) && puAction) + rc = Msg.u.v3.uAction.GetUInt32(puAction); + + if (RT_SUCCESS(rc)) + { + *ppszFormat = RTStrDup(pszFormatTmp); + if (!*ppszFormat) + rc = VERR_NO_MEMORY; + } + } + + RTMemFree(pszFormatTmp); + + return rc; +} +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + + +/********************************************************************************************************************************* +* Public functions * +*********************************************************************************************************************************/ + +/** + * Connects a DnD context to the DnD host service. + * + * @returns IPRT status code. + * @param pCtx DnD context to connect. + */ +VBGLR3DECL(int) VbglR3DnDConnect(PVBGLR3GUESTDNDCMDCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + /* Initialize header */ + int rc = VbglR3HGCMConnect("VBoxDragAndDropSvc", &pCtx->uClientID); + if (RT_FAILURE(rc)) + return rc; + Assert(pCtx->uClientID); + + /* Set the default protocol version we would like to use. + * Deprecated since VBox 6.1.x, but let this set to 3 to (hopefully) not break things. */ + pCtx->uProtocolDeprecated = 3; + + pCtx->fHostFeatures = VBOX_DND_HF_NONE; + pCtx->fGuestFeatures = VBOX_DND_GF_NONE; + + /* + * Get the VM's session ID. + * This is not fatal in case we're running with an ancient VBox version. + */ + pCtx->uSessionID = 0; + int rc2 = VbglR3GetSessionId(&pCtx->uSessionID); RT_NOREF(rc2); + LogFlowFunc(("uSessionID=%RU64, rc=%Rrc\n", pCtx->uSessionID, rc2)); + + /* + * Try sending the connect message to tell the protocol version to use. + * Note: This might fail when the Guest Additions run on an older VBox host (< VBox 5.0) which + * does not implement this command. + */ + HGCMMsgConnect Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_CONNECT, 3); + Msg.u.v3.uContext.SetUInt32(0); /** @todo Context ID not used yet. */ + Msg.u.v3.uProtocol.SetUInt32(pCtx->uProtocolDeprecated); /* Deprecated since VBox 6.1.x. */ + Msg.u.v3.uFlags.SetUInt32(0); /* Unused at the moment. */ + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /* Set the protocol version we're going to use as told by the host. */ + rc = Msg.u.v3.uProtocol.GetUInt32(&pCtx->uProtocolDeprecated); AssertRC(rc); + + /* + * Next is reporting our features. If this fails, assume older host. + */ + rc2 = VbglR3DnDReportFeatures(pCtx->uClientID, pCtx->fGuestFeatures, &pCtx->fHostFeatures); + if (RT_SUCCESS(rc2)) + { + LogRel2(("DnD: Guest features: %#RX64 - Host features: %#RX64\n", + pCtx->fGuestFeatures, pCtx->fHostFeatures)); + } + else /* Failing here is not fatal; might be running with an older host. */ + { + AssertLogRelMsg(rc2 == VERR_NOT_SUPPORTED || rc2 == VERR_NOT_IMPLEMENTED, + ("Reporting features failed: %Rrc\n", rc2)); + } + + pCtx->cbMaxChunkSize = DND_DEFAULT_CHUNK_SIZE; /** @todo Use a scratch buffer on the heap? */ + } + else + pCtx->uProtocolDeprecated = 0; /* We're using protocol v0 (initial draft) as a fallback. */ + + LogFlowFunc(("uClient=%RU32, uProtocol=%RU32, rc=%Rrc\n", pCtx->uClientID, pCtx->uProtocolDeprecated, rc)); + return rc; +} + +/** + * Disconnects a given DnD context from the DnD host service. + * + * @returns IPRT status code. + * @param pCtx DnD context to disconnect. + * The context is invalid afterwards on successful disconnection. + */ +VBGLR3DECL(int) VbglR3DnDDisconnect(PVBGLR3GUESTDNDCMDCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + if (!pCtx->uClientID) /* Already disconnected? Bail out early. */ + return VINF_SUCCESS; + + int rc = VbglR3HGCMDisconnect(pCtx->uClientID); + if (RT_SUCCESS(rc)) + pCtx->uClientID = 0; + + return rc; +} + +/** + * Reports features to the host and retrieve host feature set. + * + * @returns VBox status code. + * @param idClient The client ID returned by VbglR3DnDConnect(). + * @param fGuestFeatures Features to report, VBOX_DND_GF_XXX. + * @param pfHostFeatures Where to store the features VBOX_DND_HF_XXX. + */ +VBGLR3DECL(int) VbglR3DnDReportFeatures(uint32_t idClient, uint64_t fGuestFeatures, uint64_t *pfHostFeatures) +{ + int rc; + do + { + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter f64Features0; + HGCMFunctionParameter f64Features1; + } Msg; + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_DND_FN_REPORT_FEATURES, 2); + VbglHGCMParmUInt64Set(&Msg.f64Features0, fGuestFeatures); + VbglHGCMParmUInt64Set(&Msg.f64Features1, VBOX_DND_GF_1_MUST_BE_ONE); + + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Assert(Msg.f64Features0.type == VMMDevHGCMParmType_64bit); + Assert(Msg.f64Features1.type == VMMDevHGCMParmType_64bit); + if (Msg.f64Features1.u.value64 & VBOX_DND_GF_1_MUST_BE_ONE) + rc = VERR_NOT_SUPPORTED; + else if (pfHostFeatures) + *pfHostFeatures = Msg.f64Features0.u.value64; + break; + } + } while (rc == VERR_INTERRUPTED); + return rc; + +} + +/** + * Receives the next upcoming DnD event. + * + * This is the main function DnD clients call in order to implement any DnD functionality. + * The purpose of it is to abstract the actual DnD protocol handling as much as possible from + * the clients -- those only need to react to certain events, regardless of how the underlying + * protocol actually is working. + * + * @returns VBox status code. + * @param pCtx DnD context to work with. + * @param ppEvent Next DnD event received on success; needs to be free'd by the client calling + * VbglR3DnDEventFree() when done. + */ +VBGLR3DECL(int) VbglR3DnDEventGetNext(PVBGLR3GUESTDNDCMDCTX pCtx, PVBGLR3DNDEVENT *ppEvent) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(ppEvent, VERR_INVALID_POINTER); + + PVBGLR3DNDEVENT pEvent = (PVBGLR3DNDEVENT)RTMemAllocZ(sizeof(VBGLR3DNDEVENT)); + if (!pEvent) + return VERR_NO_MEMORY; + + uint32_t uMsg = 0; + uint32_t cParms = 0; + int rc = vbglR3DnDGetNextMsgType(pCtx, &uMsg, &cParms, true /* fWait */); + if (RT_SUCCESS(rc)) + { + /* Check for VM session change. */ + uint64_t uSessionID; + int rc2 = VbglR3GetSessionId(&uSessionID); + if ( RT_SUCCESS(rc2) + && (uSessionID != pCtx->uSessionID)) + { + LogRel2(("DnD: VM session ID changed to %RU64\n", uSessionID)); + rc = VbglR3DnDDisconnect(pCtx); + if (RT_SUCCESS(rc)) + rc = VbglR3DnDConnect(pCtx); + } + } + + if (rc == VERR_CANCELLED) /* Host service told us that we have to bail out. */ + { + LogRel2(("DnD: Host service requested termination\n")); + + pEvent->enmType = VBGLR3DNDEVENTTYPE_QUIT; + *ppEvent = pEvent; + + return VINF_SUCCESS; + } + + if (RT_SUCCESS(rc)) + { + LogFunc(("Handling uMsg=%RU32\n", uMsg)); + + switch(uMsg) + { + case HOST_DND_FN_HG_EVT_ENTER: + { + rc = vbglR3DnDHGRecvAction(pCtx, + uMsg, + &pEvent->u.HG_Enter.uScreenID, + NULL /* puXPos */, + NULL /* puYPos */, + NULL /* uDefAction */, + &pEvent->u.HG_Enter.dndLstActionsAllowed, + &pEvent->u.HG_Enter.pszFormats, + &pEvent->u.HG_Enter.cbFormats); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_ENTER; + break; + } + case HOST_DND_FN_HG_EVT_MOVE: + { + rc = vbglR3DnDHGRecvAction(pCtx, + uMsg, + NULL /* puScreenId */, + &pEvent->u.HG_Move.uXpos, + &pEvent->u.HG_Move.uYpos, + &pEvent->u.HG_Move.dndActionDefault, + NULL /* puAllActions */, + NULL /* pszFormats */, + NULL /* pcbFormats */); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_MOVE; + break; + } + case HOST_DND_FN_HG_EVT_DROPPED: + { + rc = vbglR3DnDHGRecvAction(pCtx, + uMsg, + NULL /* puScreenId */, + &pEvent->u.HG_Drop.uXpos, + &pEvent->u.HG_Drop.uYpos, + &pEvent->u.HG_Drop.dndActionDefault, + NULL /* puAllActions */, + NULL /* pszFormats */, + NULL /* pcbFormats */); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_DROP; + break; + } + case HOST_DND_FN_HG_EVT_LEAVE: + { + rc = vbglR3DnDHGRecvLeave(pCtx); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_LEAVE; + break; + } + case HOST_DND_FN_HG_SND_DATA_HDR: + { + rc = vbglR3DnDHGRecvDataMain(pCtx, &pEvent->u.HG_Received.Meta); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_RECEIVE; + break; + } + case HOST_DND_FN_HG_SND_DIR: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_SND_FILE_HDR: + RT_FALL_THROUGH(); + case HOST_DND_FN_HG_SND_FILE_DATA: + { + /* + * All messages for this block are handled internally + * by vbglR3DnDHGRecvDataMain(), see above. + * + * So if we land here our code is buggy. + */ + rc = VERR_WRONG_ORDER; + break; + } + case HOST_DND_FN_CANCEL: + { + rc = vbglR3DnDHGRecvCancel(pCtx); + if (RT_SUCCESS(rc)) + rc = VERR_CANCELLED; /* Will emit a cancel event below. */ + break; + } +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case HOST_DND_FN_GH_REQ_PENDING: + { + rc = vbglR3DnDGHRecvPending(pCtx, &pEvent->u.GH_IsPending.uScreenID); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_GH_REQ_PENDING; + break; + } + case HOST_DND_FN_GH_EVT_DROPPED: + { + rc = vbglR3DnDGHRecvDropped(pCtx, + &pEvent->u.GH_Drop.pszFormat, + &pEvent->u.GH_Drop.cbFormat, + &pEvent->u.GH_Drop.dndActionRequested); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_GH_DROP; + break; + } +#endif + default: + { + rc = VERR_NOT_SUPPORTED; + break; + } + } + } + + if (RT_FAILURE(rc)) + { + /* Current operation cancelled? Set / overwrite event type and tell the caller. */ + if (rc == VERR_CANCELLED) + { + pEvent->enmType = VBGLR3DNDEVENTTYPE_CANCEL; + rc = VINF_SUCCESS; /* Deliver the event to the caller. */ + } + else + { + VbglR3DnDEventFree(pEvent); + LogRel(("DnD: Handling message %s (%#x) failed with %Rrc\n", DnDHostMsgToStr(uMsg), uMsg, rc)); + } + } + + if (RT_SUCCESS(rc)) + *ppEvent = pEvent; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Frees (destroys) a formerly allocated DnD event. + * + * @returns IPRT status code. + * @param pEvent Event to free (destroy). + */ +VBGLR3DECL(void) VbglR3DnDEventFree(PVBGLR3DNDEVENT pEvent) +{ + if (!pEvent) + return; + + /* Some messages require additional cleanup. */ + switch (pEvent->enmType) + { + case VBGLR3DNDEVENTTYPE_HG_ENTER: + { + if (pEvent->u.HG_Enter.pszFormats) + RTStrFree(pEvent->u.HG_Enter.pszFormats); + break; + } + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case VBGLR3DNDEVENTTYPE_GH_DROP: + { + if (pEvent->u.GH_Drop.pszFormat) + RTStrFree(pEvent->u.GH_Drop.pszFormat); + break; + } +#endif + case VBGLR3DNDEVENTTYPE_HG_RECEIVE: + { + PVBGLR3GUESTDNDMETADATA pMeta = &pEvent->u.HG_Received.Meta; + switch (pMeta->enmType) + { + case VBGLR3GUESTDNDMETADATATYPE_RAW: + { + if (pMeta->u.Raw.pvMeta) + { + Assert(pMeta->u.Raw.cbMeta); + RTMemFree(pMeta->u.Raw.pvMeta); + pMeta->u.Raw.cbMeta = 0; + } + break; + } + + case VBGLR3GUESTDNDMETADATATYPE_URI_LIST: + { + DnDTransferListDestroy(&pMeta->u.URI.Transfer); + break; + } + + default: + break; + } + break; + } + + default: + break; + } + + RTMemFree(pEvent); + pEvent = NULL; +} + +VBGLR3DECL(int) VbglR3DnDHGSendAckOp(PVBGLR3GUESTDNDCMDCTX pCtx, VBOXDNDACTION dndAction) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMMsgHGAck Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_HG_ACK_OP, 2); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uAction.SetUInt32(dndAction); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Host -> Guest + * Requests the actual DnD data to be sent from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pcszFormat Format to request the data from the host in. + */ +VBGLR3DECL(int) VbglR3DnDHGSendReqData(PVBGLR3GUESTDNDCMDCTX pCtx, const char* pcszFormat) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pcszFormat, VERR_INVALID_POINTER); + if (!RTStrIsValidEncoding(pcszFormat)) + return VERR_INVALID_PARAMETER; + + const uint32_t cbFormat = (uint32_t)strlen(pcszFormat) + 1; /* Include termination */ + + HGCMMsgHGReqData Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_HG_REQ_DATA, 3); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvFormat.SetPtr((void*)pcszFormat, cbFormat); + Msg.u.v3.cbFormat.SetUInt32(cbFormat); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Host -> Guest + * Reports back its progress back to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param uStatus DnD status to report. + * @param uPercent Overall progress (in percent) to report. + * @param rcErr Error code (IPRT-style) to report. + */ +VBGLR3DECL(int) VbglR3DnDHGSendProgress(PVBGLR3GUESTDNDCMDCTX pCtx, uint32_t uStatus, uint8_t uPercent, int rcErr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(uStatus > DND_PROGRESS_UNKNOWN, VERR_INVALID_PARAMETER); + + HGCMMsgHGProgress Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_HG_EVT_PROGRESS, 4); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uStatus.SetUInt32(uStatus); + Msg.u.v3.uPercent.SetUInt32(uPercent); + Msg.u.v3.rc.SetUInt32((uint32_t)rcErr); /* uint32_t vs. int. */ + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH +/** + * Guest -> Host + * Acknowledges that there currently is a drag'n drop operation in progress on the guest, + * which eventually could be dragged over to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param dndActionDefault Default action for the operation to report. + * @param dndLstActionsAllowed All available actions for the operation to report. + * @param pcszFormats Available formats for the operation to report. + * @param cbFormats Size (in bytes) of formats to report. + */ +VBGLR3DECL(int) VbglR3DnDGHSendAckPending(PVBGLR3GUESTDNDCMDCTX pCtx, + VBOXDNDACTION dndActionDefault, VBOXDNDACTIONLIST dndLstActionsAllowed, + const char* pcszFormats, uint32_t cbFormats) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pcszFormats, VERR_INVALID_POINTER); + AssertReturn(cbFormats, VERR_INVALID_PARAMETER); + + if (!RTStrIsValidEncoding(pcszFormats)) + return VERR_INVALID_UTF8_ENCODING; + + HGCMMsgGHAckPending Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_GH_ACK_PENDING, 5); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uDefAction.SetUInt32(dndActionDefault); + Msg.u.v3.uAllActions.SetUInt32(dndLstActionsAllowed); + Msg.u.v3.pvFormats.SetPtr((void*)pcszFormats, cbFormats); + Msg.u.v3.cbFormats.SetUInt32(cbFormats); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Guest -> Host + * Utility function to send DnD data from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pvData Data block to send. + * @param cbData Size (in bytes) of data block to send. + * @param pDataHdr Data header to use -- needed for accounting. + */ +static int vbglR3DnDGHSendDataInternal(PVBGLR3GUESTDNDCMDCTX pCtx, + void *pvData, uint64_t cbData, PVBOXDNDSNDDATAHDR pDataHdr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER); + + HGCMMsgGHSendDataHdr MsgHdr; + VBGL_HGCM_HDR_INIT(&MsgHdr.hdr, pCtx->uClientID, GUEST_DND_FN_GH_SND_DATA_HDR, 12); + MsgHdr.uContext.SetUInt32(0); /** @todo Not used yet. */ + MsgHdr.uFlags.SetUInt32(0); /** @todo Not used yet. */ + MsgHdr.uScreenId.SetUInt32(0); /** @todo Not used for guest->host (yet). */ + MsgHdr.cbTotal.SetUInt64(pDataHdr->cbTotal); + MsgHdr.cbMeta.SetUInt32(pDataHdr->cbMeta); + MsgHdr.pvMetaFmt.SetPtr(pDataHdr->pvMetaFmt, pDataHdr->cbMetaFmt); + MsgHdr.cbMetaFmt.SetUInt32(pDataHdr->cbMetaFmt); + MsgHdr.cObjects.SetUInt64(pDataHdr->cObjects); + MsgHdr.enmCompression.SetUInt32(0); /** @todo Not used yet. */ + MsgHdr.enmChecksumType.SetUInt32(RTDIGESTTYPE_INVALID); /** @todo Not used yet. */ + MsgHdr.pvChecksum.SetPtr(NULL, 0); /** @todo Not used yet. */ + MsgHdr.cbChecksum.SetUInt32(0); /** @todo Not used yet. */ + + int rc = VbglR3HGCMCall(&MsgHdr.hdr, sizeof(MsgHdr)); + + LogFlowFunc(("cbTotal=%RU64, cbMeta=%RU32, cObjects=%RU64, rc=%Rrc\n", + pDataHdr->cbTotal, pDataHdr->cbMeta, pDataHdr->cObjects, rc)); + + if (RT_SUCCESS(rc)) + { + HGCMMsgGHSendData MsgData; + VBGL_HGCM_HDR_INIT(&MsgData.hdr, pCtx->uClientID, GUEST_DND_FN_GH_SND_DATA, 5); + MsgData.u.v3.uContext.SetUInt32(0); /** @todo Not used yet. */ + MsgData.u.v3.pvChecksum.SetPtr(NULL, 0); /** @todo Not used yet. */ + MsgData.u.v3.cbChecksum.SetUInt32(0); /** @todo Not used yet. */ + + uint32_t cbCurChunk; + const uint32_t cbMaxChunk = pCtx->cbMaxChunkSize; + uint32_t cbSent = 0; + + while (cbSent < cbData) + { + cbCurChunk = RT_MIN(cbData - cbSent, cbMaxChunk); + MsgData.u.v3.pvData.SetPtr(static_cast<uint8_t *>(pvData) + cbSent, cbCurChunk); + MsgData.u.v3.cbData.SetUInt32(cbCurChunk); + + rc = VbglR3HGCMCall(&MsgData.hdr, sizeof(MsgData)); + if (RT_FAILURE(rc)) + break; + + cbSent += cbCurChunk; + } + + LogFlowFunc(("cbMaxChunk=%RU32, cbData=%RU64, cbSent=%RU32, rc=%Rrc\n", + cbMaxChunk, cbData, cbSent, rc)); + + if (RT_SUCCESS(rc)) + Assert(cbSent == cbData); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Guest -> Host + * Utility function to send a guest directory to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pObj transfer object containing the directory to send. + */ +static int vbglR3DnDGHSendDir(PVBGLR3GUESTDNDCMDCTX pCtx, DNDTRANSFEROBJECT *pObj) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(DnDTransferObjectGetType(pObj) == DNDTRANSFEROBJTYPE_DIRECTORY, VERR_INVALID_PARAMETER); + + const char *pcszPath = DnDTransferObjectGetDestPath(pObj); + const size_t cbPath = RTStrNLen(pcszPath, RTPATH_MAX) + 1 /* Include termination. */; + const RTFMODE fMode = DnDTransferObjectGetMode(pObj); + + LogFlowFunc(("strDir=%s (%zu bytes), fMode=0x%x\n", pcszPath, cbPath, fMode)); + + if (cbPath > RTPATH_MAX + 1) /* Can't happen, but check anyway. */ + return VERR_INVALID_PARAMETER; + + HGCMMsgGHSendDir Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_GH_SND_DIR, 4); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvName.SetPtr((void *)pcszPath, (uint32_t)cbPath); + Msg.u.v3.cbName.SetUInt32((uint32_t)cbPath); + Msg.u.v3.fMode.SetUInt32(fMode); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Guest -> Host + * Utility function to send a file from the guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pObj Transfer object containing the file to send. + */ +static int vbglR3DnDGHSendFile(PVBGLR3GUESTDNDCMDCTX pCtx, PDNDTRANSFEROBJECT pObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertReturn(DnDTransferObjectIsOpen(pObj) == false, VERR_INVALID_STATE); + AssertReturn(DnDTransferObjectGetType(pObj) == DNDTRANSFEROBJTYPE_FILE, VERR_INVALID_PARAMETER); + + uint64_t fOpen = RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE; + + int rc = DnDTransferObjectOpen(pObj, fOpen, 0 /* fMode */, DNDTRANSFEROBJECT_FLAGS_NONE); + if (RT_FAILURE(rc)) + return rc; + + uint32_t cbBuf = pCtx->cbMaxChunkSize; + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + void *pvBuf = RTMemAlloc(cbBuf); /** @todo Make this buffer part of PVBGLR3GUESTDNDCMDCTX? */ + if (!pvBuf) + { + int rc2 = DnDTransferObjectClose(pObj); + AssertRC(rc2); + return VERR_NO_MEMORY; + } + + const char *pcszPath = DnDTransferObjectGetDestPath(pObj); + const size_t cchPath = RTStrNLen(pcszPath, RTPATH_MAX); + const uint64_t cbSize = DnDTransferObjectGetSize(pObj); + const RTFMODE fMode = DnDTransferObjectGetMode(pObj); + + LogFlowFunc(("strFile=%s (%zu), cbSize=%RU64, fMode=0x%x\n", pcszPath, cchPath, cbSize, fMode)); + + HGCMMsgGHSendFileHdr MsgHdr; + VBGL_HGCM_HDR_INIT(&MsgHdr.hdr, pCtx->uClientID, GUEST_DND_FN_GH_SND_FILE_HDR, 6); + MsgHdr.uContext.SetUInt32(0); /* Context ID; unused at the moment. */ + MsgHdr.pvName.SetPtr((void *)pcszPath, (uint32_t)(cchPath + 1)); /* Include termination. */ + MsgHdr.cbName.SetUInt32((uint32_t)(cchPath + 1)); /* Ditto. */ + MsgHdr.uFlags.SetUInt32(0); /* Flags; unused at the moment. */ + MsgHdr.fMode.SetUInt32(fMode); /* File mode */ + MsgHdr.cbTotal.SetUInt64(cbSize); /* File size (in bytes). */ + + rc = VbglR3HGCMCall(&MsgHdr.hdr, sizeof(MsgHdr)); + + LogFlowFunc(("Sending file header resulted in %Rrc\n", rc)); + + if (RT_SUCCESS(rc)) + { + /* + * Send the actual file data, chunk by chunk. + */ + HGCMMsgGHSendFileData Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_GH_SND_FILE_DATA, 5); + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvChecksum.SetPtr(NULL, 0); + Msg.u.v3.cbChecksum.SetUInt32(0); + + uint64_t cbToReadTotal = cbSize; + uint64_t cbWrittenTotal = 0; + while (cbToReadTotal) + { + uint32_t cbToRead = RT_MIN(cbToReadTotal, cbBuf); + uint32_t cbRead = 0; + if (cbToRead) + rc = DnDTransferObjectRead(pObj, pvBuf, cbToRead, &cbRead); + + LogFlowFunc(("cbToReadTotal=%RU64, cbToRead=%RU32, cbRead=%RU32, rc=%Rrc\n", + cbToReadTotal, cbToRead, cbRead, rc)); + + if ( RT_SUCCESS(rc) + && cbRead) + { + Msg.u.v3.pvData.SetPtr(pvBuf, cbRead); + Msg.u.v3.cbData.SetUInt32(cbRead); + /** @todo Calculate + set checksums. */ + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + } + + if (RT_FAILURE(rc)) + { + LogFlowFunc(("Reading failed with rc=%Rrc\n", rc)); + break; + } + + Assert(cbRead <= cbToReadTotal); + cbToReadTotal -= cbRead; + cbWrittenTotal += cbRead; + + LogFlowFunc(("%RU64/%RU64 -- %RU8%%\n", cbWrittenTotal, cbSize, cbWrittenTotal * 100 / cbSize)); + }; + } + + RTMemFree(pvBuf); + int rc2 = DnDTransferObjectClose(pObj); + if (RT_SUCCESS(rc)) + rc = rc2; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Guest -> Host + * Utility function to send a transfer object from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pObj Transfer object to send from guest to the host. + */ +static int vbglR3DnDGHSendURIObject(PVBGLR3GUESTDNDCMDCTX pCtx, PDNDTRANSFEROBJECT pObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + + int rc; + + const DNDTRANSFEROBJTYPE enmType = DnDTransferObjectGetType(pObj); + + switch (enmType) + { + case DNDTRANSFEROBJTYPE_DIRECTORY: + rc = vbglR3DnDGHSendDir(pCtx, pObj); + break; + + case DNDTRANSFEROBJTYPE_FILE: + rc = vbglR3DnDGHSendFile(pCtx, pObj); + break; + + default: + AssertMsgFailed(("Object type %ld not implemented\n", enmType)); + rc = VERR_NOT_IMPLEMENTED; + break; + } + + return rc; +} + +/** + * Guest -> Host + * Utility function to send raw data from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pvData Block to raw data to send. + * @param cbData Size (in bytes) of raw data to send. + */ +static int vbglR3DnDGHSendRawData(PVBGLR3GUESTDNDCMDCTX pCtx, void *pvData, size_t cbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + /* cbData can be 0. */ + + VBOXDNDDATAHDR dataHdr; + RT_ZERO(dataHdr); + + dataHdr.cbMeta = (uint32_t)cbData; + dataHdr.cbTotal = cbData; + + return vbglR3DnDGHSendDataInternal(pCtx, pvData, cbData, &dataHdr); +} + +/** + * Guest -> Host + * Utility function to send transfer data from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pTransferList Dnd transfer list to send. + */ +static int vbglR3DnDGHSendTransferData(PVBGLR3GUESTDNDCMDCTX pCtx, PDNDTRANSFERLIST pTransferList) +{ + AssertPtrReturn(pCtx,VERR_INVALID_POINTER); + AssertPtrReturn(pTransferList, VERR_INVALID_POINTER); + + /* + * Send the (meta) data; in case of URIs it's the root entries of a + * transfer list the host needs to know upfront to set up the drag'n drop operation. + */ + char *pszList = NULL; + size_t cbList; + int rc = DnDTransferListGetRoots(pTransferList, DNDTRANSFERLISTFMT_URI, &pszList, &cbList); + if (RT_FAILURE(rc)) + return rc; + + void *pvURIList = (void *)pszList; + uint32_t cbURLIist = (uint32_t)cbList; + + /* The total size also contains the size of the meta data. */ + uint64_t cbTotal = cbURLIist; + cbTotal += DnDTransferListObjTotalBytes(pTransferList); + + /* We're going to send a transfer list in text format. */ + const char szMetaFmt[] = "text/uri-list"; + const uint32_t cbMetaFmt = (uint32_t)strlen(szMetaFmt) + 1; /* Include termination. */ + + VBOXDNDDATAHDR dataHdr; + dataHdr.uFlags = 0; /* Flags not used yet. */ + dataHdr.cbTotal = cbTotal; + dataHdr.cbMeta = cbURLIist; + dataHdr.pvMetaFmt = (void *)szMetaFmt; + dataHdr.cbMetaFmt = cbMetaFmt; + dataHdr.cObjects = DnDTransferListObjCount(pTransferList); + + rc = vbglR3DnDGHSendDataInternal(pCtx, pvURIList, cbURLIist, &dataHdr); + + if (RT_SUCCESS(rc)) + { + while (DnDTransferListObjCount(pTransferList)) + { + PDNDTRANSFEROBJECT pObj = DnDTransferListObjGetFirst(pTransferList); + + rc = vbglR3DnDGHSendURIObject(pCtx, pObj); + if (RT_FAILURE(rc)) + break; + + DnDTransferListObjRemoveFirst(pTransferList); + } + + Assert(DnDTransferListObjCount(pTransferList) == 0); + } + + return rc; +} + +/** + * Guest -> Host + * Sends data, which either can be raw or URI data, from guest to the host. This function + * initiates the actual data transfer from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pszFormat In which format the data will be sent. + * @param pvData Data block to send. + * For URI data this must contain the absolute local URI paths, separated by DND_PATH_SEPARATOR_STR. + * @param cbData Size (in bytes) of data block to send. + */ +VBGLR3DECL(int) VbglR3DnDGHSendData(PVBGLR3GUESTDNDCMDCTX pCtx, const char *pszFormat, void *pvData, uint32_t cbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pszFormat, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + LogFlowFunc(("pszFormat=%s, pvData=%p, cbData=%RU32\n", pszFormat, pvData, cbData)); + + LogRel2(("DnD: Sending %RU32 bytes meta data in format '%s'\n", cbData, pszFormat)); + + int rc; + if (DnDMIMEHasFileURLs(pszFormat, strlen(pszFormat))) + { + DNDTRANSFERLIST lstTransfer; + RT_ZERO(lstTransfer); + + rc = DnDTransferListInit(&lstTransfer); + if (RT_SUCCESS(rc)) + { + /** @todo Add symlink support (DNDTRANSFERLIST_FLAGS_RESOLVE_SYMLINKS) here. */ + /** @todo Add lazy loading (DNDTRANSFERLIST_FLAGS_LAZY) here. */ + const DNDTRANSFERLISTFLAGS fFlags = DNDTRANSFERLIST_FLAGS_RECURSIVE; + + rc = DnDTransferListAppendPathsFromBuffer(&lstTransfer, DNDTRANSFERLISTFMT_URI, (const char *)pvData, cbData, + DND_PATH_SEPARATOR_STR, fFlags); + if (RT_SUCCESS(rc)) + rc = vbglR3DnDGHSendTransferData(pCtx, &lstTransfer); + DnDTransferListDestroy(&lstTransfer); + } + } + else + rc = vbglR3DnDGHSendRawData(pCtx, pvData, cbData); + + if (RT_FAILURE(rc)) + { + LogRel(("DnD: Sending data failed with rc=%Rrc\n", rc)); + + if (rc != VERR_CANCELLED) + { + int rc2 = VbglR3DnDSendError(pCtx, rc); + if (RT_FAILURE(rc2)) + LogFlowFunc(("Unable to send error (%Rrc) to host, rc=%Rrc\n", rc, rc2)); + } + } + + return rc; +} +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDrmClient.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDrmClient.cpp new file mode 100644 index 00000000..66a8bca7 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDrmClient.cpp @@ -0,0 +1,199 @@ +/* $Id: VBoxGuestR3LibDrmClient.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, DRM client handling. + */ + +/* + * Copyright (C) 2020-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR3LibInternal.h" + +#include <iprt/env.h> +#include <iprt/path.h> +#include <iprt/process.h> + +#if defined(RT_OS_LINUX) +# include <VBox/HostServices/GuestPropertySvc.h> + +/** Defines the DRM client executable (image). */ +# define VBOX_DRMCLIENT_EXECUTABLE "/usr/bin/VBoxDRMClient" +# define VBOX_DRMCLIENT_LEGACY_EXECUTABLE "/usr/bin/VBoxClient" +/** Defines the guest property that defines if the DRM resizing client needs to be active or not. */ +# define VBOX_DRMCLIENT_GUEST_PROP_RESIZE "/VirtualBox/GuestAdd/DRMResize" + +/** + * Check if specified guest property exist. + * + * @returns \c true if the property exists and its flags do match, \c false otherwise. + * @param pszPropName Guest property name. + * @param fPropFlags Guest property flags mask to verify if property exist. + * If \p fPropFlags is 0, flags verification is omitted. + */ +static bool vbglR3DrmClientCheckProp(const char *pszPropName, uint32_t fPropFlags) +{ + bool fExist = false; +# if defined(VBOX_WITH_GUEST_PROPS) + uint32_t idClient; + + int rc = VbglR3GuestPropConnect(&idClient); + if (RT_SUCCESS(rc)) + { + char *pcszFlags = NULL; + + rc = VbglR3GuestPropReadEx(idClient, pszPropName, NULL /* ppszValue */, &pcszFlags, NULL); + if (RT_SUCCESS(rc)) + { + /* Check property flags match. */ + if (fPropFlags) + { + uint32_t fFlags = 0; + + rc = GuestPropValidateFlags(pcszFlags, &fFlags); + fExist = RT_SUCCESS(rc) && (fFlags == fPropFlags); + } + else + fExist = true; + + RTStrFree(pcszFlags); + } + + VbglR3GuestPropDisconnect(idClient); + } +# endif /* VBOX_WITH_GUEST_PROPS */ + return fExist; +} +#endif /* RT_OS_LINUX */ + +/** + * Returns true if the DRM resizing client is needed. + * This is achieved by querying existence of a guest property. + * + * @returns \c true if the DRM resizing client is needed, \c false if not. + */ +VBGLR3DECL(bool) VbglR3DrmClientIsNeeded(void) +{ +#if defined(RT_OS_LINUX) + return vbglR3DrmClientCheckProp(VBOX_DRMCLIENT_GUEST_PROP_RESIZE, 0); +#else + return false; +#endif +} + +/** + * Returns true if the DRM IPC server socket access should be restricted. + * + * Restricted access means that only users from a certain group should + * be granted with read and write access permission to IPC socket. Check + * is done by examining \c VBGLR3DRMIPCPROPRESTRICT guest property. Property + * is only considered valid if is read-only for guest. I.e., the following + * property should be set on the host side: + * + * VBoxManage guestproperty set <VM> /VirtualBox/GuestAdd/DRMIpcRestricted 1 --flags RDONLYGUEST + * + * @returns \c true if restricted socket access is required, \c false otherwise. + */ +VBGLR3DECL(bool) VbglR3DrmRestrictedIpcAccessIsNeeded(void) +{ +#if defined(RT_OS_LINUX) + return vbglR3DrmClientCheckProp(VBGLR3DRMIPCPROPRESTRICT, GUEST_PROP_F_RDONLYGUEST); +#else + return false; +#endif +} + +/** + * Returns true if the DRM resizing client already is running. + * This is achieved by querying existence of a guest property. + * + * @returns \c true if the DRM resizing client is running, \c false if not. + */ +VBGLR3DECL(bool) VbglR3DrmClientIsRunning(void) +{ + return VbglR3DrmClientIsNeeded(); +} + +#if defined(RT_OS_LINUX) +static int VbglR3DrmStart(const char *pszCmd, const char **apszArgs) +{ + return RTProcCreate(pszCmd, apszArgs, RTENV_DEFAULT, + RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_SEARCH_PATH, NULL); +} +#endif + +/** + * Starts (executes) the DRM resizing client process ("VBoxDRMClient"). + * + * @returns VBox status code. + */ +VBGLR3DECL(int) VbglR3DrmClientStart(void) +{ +#if defined(RT_OS_LINUX) + const char *apszArgs[2] = { VBOX_DRMCLIENT_EXECUTABLE, NULL }; /** @todo r=andy Pass path + process name as argv0? */ + return VbglR3DrmStart(VBOX_DRMCLIENT_EXECUTABLE, apszArgs); +#else + return VERR_NOT_SUPPORTED; +#endif +} + +/** + * Starts (executes) the legacy DRM resizing client process ("VBoxClient --vmsvga"). + * + * @returns VBox status code. + */ +VBGLR3DECL(int) VbglR3DrmLegacyClientStart(void) +{ +#if defined(RT_OS_LINUX) + const char *apszArgs[3] = { VBOX_DRMCLIENT_LEGACY_EXECUTABLE, "--vmsvga", NULL }; + return VbglR3DrmStart(VBOX_DRMCLIENT_LEGACY_EXECUTABLE, apszArgs); +#else + return VERR_NOT_SUPPORTED; +#endif +} + +/** + * Starts (executes) the legacy X11 resizing agent process ("VBoxClient --display"). + * + * @returns VBox status code. + */ +VBGLR3DECL(int) VbglR3DrmLegacyX11AgentStart(void) +{ +#if defined(RT_OS_LINUX) + const char *apszArgs[3] = { VBOX_DRMCLIENT_LEGACY_EXECUTABLE, "--display", NULL }; + return VbglR3DrmStart(VBOX_DRMCLIENT_LEGACY_EXECUTABLE, apszArgs); +#else + return VERR_NOT_SUPPORTED; +#endif +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibEvent.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibEvent.cpp new file mode 100644 index 00000000..f459f607 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibEvent.cpp @@ -0,0 +1,103 @@ +/* $Id: VBoxGuestR3LibEvent.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Events. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/log.h> +#include <iprt/assert.h> +#include "VBoxGuestR3LibInternal.h" + + +/** + * Wait for the host to signal one or more events and return which. + * + * The events will only be delivered by the host if they have been enabled + * previously using @a VbglR3CtlFilterMask. If one or several of the events + * have already been signalled but not yet waited for, this function will return + * immediately and return those events. + * + * @returns IPRT status code. + * + * @param fMask The events we want to wait for, or-ed together. + * @param cMillies How long to wait before giving up and returning + * (VERR_TIMEOUT). Use RT_INDEFINITE_WAIT to wait until we + * are interrupted or one of the events is signalled. + * @param pfEvents Where to store the events signalled. Optional. + */ +VBGLR3DECL(int) VbglR3WaitEvent(uint32_t fMask, uint32_t cMillies, uint32_t *pfEvents) +{ + LogFlow(("VbglR3WaitEvent: fMask=%#x, cMillies=%u, pfEvents=%p\n", fMask, cMillies, pfEvents)); + AssertReturn((fMask & ~VMMDEV_EVENT_VALID_EVENT_MASK) == 0, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pfEvents, VERR_INVALID_POINTER); + + VBGLIOCWAITFOREVENTS WaitEvents; + VBGLREQHDR_INIT(&WaitEvents.Hdr, WAIT_FOR_EVENTS); + WaitEvents.u.In.fEvents = fMask; + WaitEvents.u.In.cMsTimeOut = cMillies; + int rc = vbglR3DoIOCtl(VBGL_IOCTL_WAIT_FOR_EVENTS, &WaitEvents.Hdr, sizeof(WaitEvents)); + if (pfEvents) + { + if (RT_SUCCESS(rc)) + *pfEvents = WaitEvents.u.Out.fEvents; + else + *pfEvents = 0; + } + + LogFlow(("VbglR3WaitEvent: rc=%Rrc fEvents=%#x\n", rc, WaitEvents.u.Out.fEvents)); + return rc; +} + + +/** + * Causes any pending VbglR3WaitEvent calls (VBGL_IOCTL_WAIT_FOR_EVENTS) to + * return with a VERR_INTERRUPTED status. + * + * Can be used in combination with a termination flag variable for interrupting + * event loops. After calling this, VBGL_IOCTL_WAIT_FOR_EVENTS should no longer + * be called in the same session. At the time of writing this is not enforced; + * at the time of reading it may be. + * + * @returns IPRT status code. + */ +VBGLR3DECL(int) VbglR3InterruptEventWaits(void) +{ + VBGLREQHDR Req; + VBGLREQHDR_INIT(&Req, INTERRUPT_ALL_WAIT_FOR_EVENTS); + return vbglR3DoIOCtl(VBGL_IOCTL_INTERRUPT_ALL_WAIT_FOR_EVENTS, &Req, sizeof(Req)); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGR.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGR.cpp new file mode 100644 index 00000000..e80f1f55 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGR.cpp @@ -0,0 +1,90 @@ +/* $Id: VBoxGuestR3LibGR.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, GR. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/mem.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/errcore.h> +#include "VBoxGuestR3LibInternal.h" + + +int vbglR3GRAlloc(VMMDevRequestHeader **ppReq, size_t cb, VMMDevRequestType enmReqType) +{ + VMMDevRequestHeader *pReq; + + AssertPtrReturn(ppReq, VERR_INVALID_PARAMETER); + AssertMsgReturn(cb >= sizeof(VMMDevRequestHeader) && cb < _1G, ("%#zx vs %#zx\n", cb, sizeof(VMMDevRequestHeader)), + VERR_INVALID_PARAMETER); + + pReq = (VMMDevRequestHeader *)RTMemTmpAlloc(cb); + if (RT_LIKELY(pReq)) + { + pReq->size = (uint32_t)cb; + pReq->version = VMMDEV_REQUEST_HEADER_VERSION; + pReq->requestType = enmReqType; + pReq->rc = VERR_GENERAL_FAILURE; + pReq->reserved1 = 0; + pReq->fRequestor = 0; + + *ppReq = pReq; + + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + + +int vbglR3GRPerform(VMMDevRequestHeader *pReq) +{ + PVBGLREQHDR pReqHdr = (PVBGLREQHDR)pReq; + uint32_t const cbReq = pReqHdr->cbIn; + Assert(pReqHdr->cbOut == 0 || pReqHdr->cbOut == cbReq); + pReqHdr->cbOut = cbReq; + if (pReq->size < _1K) + return vbglR3DoIOCtl(VBGL_IOCTL_VMMDEV_REQUEST(cbReq), pReqHdr, cbReq); + return vbglR3DoIOCtl(VBGL_IOCTL_VMMDEV_REQUEST_BIG, pReqHdr, cbReq); +} + + +void vbglR3GRFree(VMMDevRequestHeader *pReq) +{ + RTMemTmpFree(pReq); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestCtrl.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestCtrl.cpp new file mode 100644 index 00000000..fc29936c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestCtrl.cpp @@ -0,0 +1,2214 @@ +/* $Id: VBoxGuestR3LibGuestCtrl.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, guest control. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/string.h> +#include <iprt/mem.h> +#include <iprt/assert.h> +#include <iprt/cpp/autores.h> +#include <iprt/stdarg.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/GuestHost/GuestControl.h> +#include <VBox/HostServices/GuestControlSvc.h> + +#ifndef RT_OS_WINDOWS +# include <signal.h> +# ifdef RT_OS_DARWIN +# include <pthread.h> +# define sigprocmask pthread_sigmask /* On xnu sigprocmask works on the process, not the calling thread as elsewhere. */ +# endif +#endif + +#include "VBoxGuestR3LibInternal.h" + +using namespace guestControl; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Set if GUEST_MSG_PEEK_WAIT and friends are supported. */ +static int g_fVbglR3GuestCtrlHavePeekGetCancel = -1; + + +/** + * Connects to the guest control service. + * + * @returns VBox status code + * @param pidClient Where to put The client ID on success. The client ID + * must be passed to all the other calls to the service. + */ +VBGLR3DECL(int) VbglR3GuestCtrlConnect(uint32_t *pidClient) +{ + return VbglR3HGCMConnect("VBoxGuestControlSvc", pidClient); +} + + +/** + * Disconnect from the guest control service. + * + * @returns VBox status code. + * @param idClient The client ID returned by VbglR3GuestCtrlConnect(). + */ +VBGLR3DECL(int) VbglR3GuestCtrlDisconnect(uint32_t idClient) +{ + return VbglR3HGCMDisconnect(idClient); +} + + +/** + * Waits until a new host message arrives. + * This will block until a message becomes available. + * + * @returns VBox status code. + * @param idClient The client ID returned by VbglR3GuestCtrlConnect(). + * @param pidMsg Where to store the message id. + * @param pcParameters Where to store the number of parameters which will + * be received in a second call to the host. + */ +static int vbglR3GuestCtrlMsgWaitFor(uint32_t idClient, uint32_t *pidMsg, uint32_t *pcParameters) +{ + AssertPtrReturn(pidMsg, VERR_INVALID_POINTER); + AssertPtrReturn(pcParameters, VERR_INVALID_POINTER); + + HGCMMsgWaitFor Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, + GUEST_MSG_WAIT, /* Tell the host we want our next message. */ + 2); /* Just peek for the next message! */ + VbglHGCMParmUInt32Set(&Msg.msg, 0); + VbglHGCMParmUInt32Set(&Msg.num_parms, 0); + + /* + * We should always get a VERR_TOO_MUCH_DATA response here, see + * guestControl::HostMessage::Peek() and its caller ClientState::SendReply(). + * We accept success too here, in case someone decide to make the protocol + * slightly more sane. + * + * Note! A really sane protocol design would have a separate call for getting + * info about a pending message (returning VINF_SUCCESS), and a separate + * one for retriving the actual message parameters. Not this weird + * stuff, to put it rather bluntly. + * + * Note! As a result of this weird design, we are not able to correctly + * retrieve message if we're interrupted by a signal, like SIGCHLD. + * Because IPRT wants to use waitpid(), we're forced to have a handler + * installed for SIGCHLD, so when working with child processes there + * will be signals in the air and we will get VERR_INTERRUPTED returns. + * The way HGCM handles interrupted calls is to silently (?) drop them + * as they complete (see VMMDev), so the server knows little about it + * and just goes on to the next message inline. + * + * So, as a "temporary" mesasure, we block SIGCHLD here while waiting, + * because it will otherwise be impossible do simple stuff like 'mkdir' + * on a mac os x guest, and probably most other unix guests. + */ +#ifdef RT_OS_WINDOWS + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +#else + sigset_t SigSet; + sigemptyset(&SigSet); + sigaddset(&SigSet, SIGCHLD); + sigprocmask(SIG_BLOCK, &SigSet, NULL); + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + sigprocmask(SIG_UNBLOCK, &SigSet, NULL); +#endif + if ( rc == VERR_TOO_MUCH_DATA + || RT_SUCCESS(rc)) + { + int rc2 = VbglHGCMParmUInt32Get(&Msg.msg, pidMsg); + if (RT_SUCCESS(rc2)) + { + rc2 = VbglHGCMParmUInt32Get(&Msg.num_parms, pcParameters); + if (RT_SUCCESS(rc2)) + { + /* Ok, so now we know what message type and how much parameters there are. */ + return rc; + } + } + rc = rc2; + } + *pidMsg = UINT32_MAX - 1; + *pcParameters = UINT32_MAX - 2; + return rc; +} + + +/** + * Determins the value of g_fVbglR3GuestCtrlHavePeekGetCancel. + * + * @returns true if supported, false if not. + * @param idClient The client ID to use for the testing. + */ +DECL_NO_INLINE(static, bool) vbglR3GuestCtrlDetectPeekGetCancelSupport(uint32_t idClient) +{ + /* + * Seems we get VINF_SUCCESS back from the host if we try unsupported + * guest control messages, so we need to supply some random message + * parameters and check that they change. + */ + uint32_t const idDummyMsg = UINT32_C(0x8350bdca); + uint32_t const cDummyParmeters = UINT32_C(0x7439604f); + uint32_t const cbDummyMask = UINT32_C(0xc0ffe000); + Assert(cDummyParmeters > VMMDEV_MAX_HGCM_PARMS); + + int rc; + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter idMsg; + HGCMFunctionParameter cParams; + HGCMFunctionParameter acbParams[14]; + } PeekCall; + Assert(RT_ELEMENTS(PeekCall.acbParams) + 2 < VMMDEV_MAX_HGCM_PARMS); + + do + { + memset(&PeekCall, 0xf6, sizeof(PeekCall)); + VBGL_HGCM_HDR_INIT(&PeekCall.Hdr, idClient, GUEST_MSG_PEEK_NOWAIT, 16); + VbglHGCMParmUInt32Set(&PeekCall.idMsg, idDummyMsg); + VbglHGCMParmUInt32Set(&PeekCall.cParams, cDummyParmeters); + for (uint32_t i = 0; i < RT_ELEMENTS(PeekCall.acbParams); i++) + VbglHGCMParmUInt32Set(&PeekCall.acbParams[i], i | cbDummyMask); + + rc = VbglR3HGCMCall(&PeekCall.Hdr, sizeof(PeekCall)); + } while (rc == VERR_INTERRUPTED); + + LogRel2(("vbglR3GuestCtrlDetectPeekGetCancelSupport: rc=%Rrc %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x\n", + rc, PeekCall.idMsg.u.value32, PeekCall.cParams.u.value32, + PeekCall.acbParams[ 0].u.value32, PeekCall.acbParams[ 1].u.value32, + PeekCall.acbParams[ 2].u.value32, PeekCall.acbParams[ 3].u.value32, + PeekCall.acbParams[ 4].u.value32, PeekCall.acbParams[ 5].u.value32, + PeekCall.acbParams[ 6].u.value32, PeekCall.acbParams[ 7].u.value32, + PeekCall.acbParams[ 8].u.value32, PeekCall.acbParams[ 9].u.value32, + PeekCall.acbParams[10].u.value32, PeekCall.acbParams[11].u.value32, + PeekCall.acbParams[12].u.value32, PeekCall.acbParams[13].u.value32)); + + /* + * VERR_TRY_AGAIN is likely and easy. + */ + if ( rc == VERR_TRY_AGAIN + && PeekCall.idMsg.u.value32 == 0 + && PeekCall.cParams.u.value32 == 0 + && PeekCall.acbParams[0].u.value32 == 0 + && PeekCall.acbParams[1].u.value32 == 0 + && PeekCall.acbParams[2].u.value32 == 0 + && PeekCall.acbParams[3].u.value32 == 0) + { + g_fVbglR3GuestCtrlHavePeekGetCancel = 1; + LogRel(("vbglR3GuestCtrlDetectPeekGetCancelSupport: Supported (#1)\n")); + return true; + } + + /* + * VINF_SUCCESS is annoying but with 16 parameters we've got plenty to check. + */ + if ( rc == VINF_SUCCESS + && PeekCall.idMsg.u.value32 != idDummyMsg + && PeekCall.idMsg.u.value32 != 0 + && PeekCall.cParams.u.value32 <= VMMDEV_MAX_HGCM_PARMS) + { + for (uint32_t i = 0; i < RT_ELEMENTS(PeekCall.acbParams); i++) + if (PeekCall.acbParams[i].u.value32 != (i | cbDummyMask)) + { + g_fVbglR3GuestCtrlHavePeekGetCancel = 0; + LogRel(("vbglR3GuestCtrlDetectPeekGetCancelSupport: Not supported (#1)\n")); + return false; + } + g_fVbglR3GuestCtrlHavePeekGetCancel = 1; + LogRel(("vbglR3GuestCtrlDetectPeekGetCancelSupport: Supported (#2)\n")); + return true; + } + + /* + * Okay, pretty sure it's not supported then. + */ + LogRel(("vbglR3GuestCtrlDetectPeekGetCancelSupport: Not supported (#3)\n")); + g_fVbglR3GuestCtrlHavePeekGetCancel = 0; + return false; +} + + +/** + * Reads g_fVbglR3GuestCtrlHavePeekGetCancel and resolved -1. + * + * @returns true if supported, false if not. + * @param idClient The client ID to use for the testing. + */ +DECLINLINE(bool) vbglR3GuestCtrlSupportsPeekGetCancel(uint32_t idClient) +{ + int fState = g_fVbglR3GuestCtrlHavePeekGetCancel; + if (RT_LIKELY(fState != -1)) + return fState != 0; + return vbglR3GuestCtrlDetectPeekGetCancelSupport(idClient); +} + + +/** + * Figures which getter function to use to retrieve the message. + */ +DECLINLINE(uint32_t) vbglR3GuestCtrlGetMsgFunctionNo(uint32_t idClient) +{ + return vbglR3GuestCtrlSupportsPeekGetCancel(idClient) ? GUEST_MSG_GET : GUEST_MSG_WAIT; +} + + +/** + * Checks if the host supports the optimizes message and session functions. + * + * @returns true / false. + * @param idClient The client ID returned by VbglR3GuestCtrlConnect(). + * We may need to use this for checking. + * @since 6.0 + */ +VBGLR3DECL(bool) VbglR3GuestCtrlSupportsOptimizations(uint32_t idClient) +{ + return vbglR3GuestCtrlSupportsPeekGetCancel(idClient); +} + + +/** + * Make us the guest control master client. + * + * @returns VBox status code. + * @param idClient The client ID returned by VbglR3GuestCtrlConnect(). + */ +VBGLR3DECL(int) VbglR3GuestCtrlMakeMeMaster(uint32_t idClient) +{ + int rc; + do + { + VBGLIOCHGCMCALL Hdr; + VBGL_HGCM_HDR_INIT(&Hdr, idClient, GUEST_MSG_MAKE_ME_MASTER, 0); + rc = VbglR3HGCMCall(&Hdr, sizeof(Hdr)); + } while (rc == VERR_INTERRUPTED); + return rc; +} + + +/** + * Reports features to the host and retrieve host feature set. + * + * @returns VBox status code. + * @param idClient The client ID returned by VbglR3GuestCtrlConnect(). + * @param fGuestFeatures Features to report, VBOX_GUESTCTRL_GF_XXX. + * @param pfHostFeatures Where to store the features VBOX_GUESTCTRL_HF_XXX. + */ +VBGLR3DECL(int) VbglR3GuestCtrlReportFeatures(uint32_t idClient, uint64_t fGuestFeatures, uint64_t *pfHostFeatures) +{ + int rc; + do + { + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter f64Features0; + HGCMFunctionParameter f64Features1; + } Msg; + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_REPORT_FEATURES, 2); + VbglHGCMParmUInt64Set(&Msg.f64Features0, fGuestFeatures); + VbglHGCMParmUInt64Set(&Msg.f64Features1, VBOX_GUESTCTRL_GF_1_MUST_BE_ONE); + + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Assert(Msg.f64Features0.type == VMMDevHGCMParmType_64bit); + Assert(Msg.f64Features1.type == VMMDevHGCMParmType_64bit); + if (Msg.f64Features1.u.value64 & VBOX_GUESTCTRL_GF_1_MUST_BE_ONE) + rc = VERR_NOT_SUPPORTED; + else if (pfHostFeatures) + *pfHostFeatures = Msg.f64Features0.u.value64; + break; + } + } while (rc == VERR_INTERRUPTED); + return rc; + +} + + +/** + * Query the host features. + * + * @returns VBox status code. + * @param idClient The client ID returned by VbglR3GuestCtrlConnect(). + * @param pfHostFeatures Where to store the host feature, VBOX_GUESTCTRL_HF_XXX. + */ +VBGLR3DECL(int) VbglR3GuestCtrlQueryFeatures(uint32_t idClient, uint64_t *pfHostFeatures) +{ + int rc; + do + { + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter f64Features0; + HGCMFunctionParameter f64Features1; + } Msg; + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_QUERY_FEATURES, 2); + VbglHGCMParmUInt64Set(&Msg.f64Features0, 0); + VbglHGCMParmUInt64Set(&Msg.f64Features1, RT_BIT_64(63)); + + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Assert(Msg.f64Features0.type == VMMDevHGCMParmType_64bit); + Assert(Msg.f64Features1.type == VMMDevHGCMParmType_64bit); + if (Msg.f64Features1.u.value64 & RT_BIT_64(63)) + rc = VERR_NOT_SUPPORTED; + else if (pfHostFeatures) + *pfHostFeatures = Msg.f64Features0.u.value64; + break; + } + } while (rc == VERR_INTERRUPTED); + return rc; + +} + + +/** + * Peeks at the next host message, waiting for one to turn up. + * + * @returns VBox status code. + * @retval VERR_INTERRUPTED if interrupted. Does the necessary cleanup, so + * caller just have to repeat this call. + * @retval VERR_VM_RESTORED if the VM has been restored (idRestoreCheck). + * + * @param idClient The client ID returned by VbglR3GuestCtrlConnect(). + * @param pidMsg Where to store the message id. + * @param pcParameters Where to store the number of parameters which will + * be received in a second call to the host. + * @param pidRestoreCheck Pointer to the VbglR3GetSessionId() variable to use + * for the VM restore check. Optional. + * + * @note Restore check is only performed optimally with a 6.0 host. + */ +VBGLR3DECL(int) VbglR3GuestCtrlMsgPeekWait(uint32_t idClient, uint32_t *pidMsg, uint32_t *pcParameters, uint64_t *pidRestoreCheck) +{ + AssertPtrReturn(pidMsg, VERR_INVALID_POINTER); + AssertPtrReturn(pcParameters, VERR_INVALID_POINTER); + + int rc; + if (vbglR3GuestCtrlSupportsPeekGetCancel(idClient)) + { + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter idMsg; /* Doubles as restore check on input. */ + HGCMFunctionParameter cParameters; + } Msg; + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_PEEK_WAIT, 2); + VbglHGCMParmUInt64Set(&Msg.idMsg, pidRestoreCheck ? *pidRestoreCheck : 0); + VbglHGCMParmUInt32Set(&Msg.cParameters, 0); + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + LogRel2(("VbglR3GuestCtrlMsgPeekWait -> %Rrc\n", rc)); + if (RT_SUCCESS(rc)) + { + AssertMsgReturn( Msg.idMsg.type == VMMDevHGCMParmType_64bit + && Msg.cParameters.type == VMMDevHGCMParmType_32bit, + ("msg.type=%d num_parms.type=%d\n", Msg.idMsg.type, Msg.cParameters.type), + VERR_INTERNAL_ERROR_3); + + *pidMsg = (uint32_t)Msg.idMsg.u.value64; + *pcParameters = Msg.cParameters.u.value32; + return rc; + } + + /* + * If interrupted we must cancel the call so it doesn't prevent us from making another one. + */ + if (rc == VERR_INTERRUPTED) + { + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_CANCEL, 0); + int rc2 = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg.Hdr)); + AssertRC(rc2); + } + + /* + * If restored, update pidRestoreCheck. + */ + if (rc == VERR_VM_RESTORED && pidRestoreCheck) + *pidRestoreCheck = Msg.idMsg.u.value64; + + *pidMsg = UINT32_MAX - 1; + *pcParameters = UINT32_MAX - 2; + return rc; + } + + /* + * Fallback if host < v6.0. + * + * Note! The restore check isn't perfect. Would require checking afterwards + * and stash the result if we were restored during the call. Too much + * hazzle for a downgrade scenario. + */ + if (pidRestoreCheck) + { + uint64_t idRestoreCur = *pidRestoreCheck; + rc = VbglR3GetSessionId(&idRestoreCur); + if (RT_SUCCESS(rc) && idRestoreCur != *pidRestoreCheck) + { + *pidRestoreCheck = idRestoreCur; + return VERR_VM_RESTORED; + } + } + + rc = vbglR3GuestCtrlMsgWaitFor(idClient, pidMsg, pcParameters); + if (rc == VERR_TOO_MUCH_DATA) + rc = VINF_SUCCESS; + return rc; +} + + +/** + * Asks the host guest control service to set a message filter to this + * client so that it only will receive certain messages in the future. + * The filter(s) are a bitmask for the context IDs, served from the host. + * + * @return IPRT status code. + * @param idClient The client ID returned by VbglR3GuestCtrlConnect(). + * @param uValue The value to filter messages for. + * @param uMaskAdd Filter mask to add. + * @param uMaskRemove Filter mask to remove. + */ +VBGLR3DECL(int) VbglR3GuestCtrlMsgFilterSet(uint32_t idClient, uint32_t uValue, uint32_t uMaskAdd, uint32_t uMaskRemove) +{ + HGCMMsgFilterSet Msg; + + /* Tell the host we want to set a filter. */ + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_MSG_FILTER_SET, 4); + VbglHGCMParmUInt32Set(&Msg.value, uValue); + VbglHGCMParmUInt32Set(&Msg.mask_add, uMaskAdd); + VbglHGCMParmUInt32Set(&Msg.mask_remove, uMaskRemove); + VbglHGCMParmUInt32Set(&Msg.flags, 0 /* Flags, unused */); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + + +/** + * Replies to a message from the host. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param rc Guest rc to reply. + */ +VBGLR3DECL(int) VbglR3GuestCtrlMsgReply(PVBGLR3GUESTCTRLCMDCTX pCtx, + int rc) +{ + return VbglR3GuestCtrlMsgReplyEx(pCtx, rc, 0 /* uType */, + NULL /* pvPayload */, 0 /* cbPayload */); +} + + +/** + * Replies to a message from the host, extended version. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param rc Guest rc to reply. + * @param uType Reply type; not used yet and must be 0. + * @param pvPayload Pointer to data payload to reply. Optional. + * @param cbPayload Size of data payload (in bytes) to reply. + */ +VBGLR3DECL(int) VbglR3GuestCtrlMsgReplyEx(PVBGLR3GUESTCTRLCMDCTX pCtx, + int rc, uint32_t uType, + void *pvPayload, uint32_t cbPayload) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + /* Everything else is optional. */ + + HGCMMsgReply Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_REPLY, 4); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, uType); + VbglHGCMParmUInt32Set(&Msg.rc, (uint32_t)rc); /* int vs. uint32_t */ + VbglHGCMParmPtrSet(&Msg.payload, pvPayload, cbPayload); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Tell the host to skip the current message replying VERR_NOT_SUPPORTED + * + * @return IPRT status code. + * @param idClient The client ID returned by VbglR3GuestCtrlConnect(). + * @param rcSkip The status code to pass back to Main when skipping. + * @param idMsg The message ID to skip, pass UINT32_MAX to pass any. + */ +VBGLR3DECL(int) VbglR3GuestCtrlMsgSkip(uint32_t idClient, int rcSkip, uint32_t idMsg) +{ + if (vbglR3GuestCtrlSupportsPeekGetCancel(idClient)) + { + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter rcSkip; + HGCMFunctionParameter idMsg; + } Msg; + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_SKIP, 2); + VbglHGCMParmUInt32Set(&Msg.rcSkip, (uint32_t)rcSkip); + VbglHGCMParmUInt32Set(&Msg.idMsg, idMsg); + return VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + } + + /* This is generally better than nothing... */ + return VbglR3GuestCtrlMsgSkipOld(idClient); +} + + +/** + * Tells the host service to skip the current message returned by + * VbglR3GuestCtrlMsgWaitFor(). + * + * @return IPRT status code. + * @param idClient The client ID returned by VbglR3GuestCtrlConnect(). + */ +VBGLR3DECL(int) VbglR3GuestCtrlMsgSkipOld(uint32_t idClient) +{ + HGCMMsgSkip Msg; + + /* Tell the host we want to skip the current assigned message. */ + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_MSG_SKIP_OLD, 1); + VbglHGCMParmUInt32Set(&Msg.flags, 0 /* Flags, unused */); + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + + +/** + * Asks the host to cancel (release) all pending waits which were deferred. + * + * @returns VBox status code. + * @param idClient The client ID returned by VbglR3GuestCtrlConnect(). + */ +VBGLR3DECL(int) VbglR3GuestCtrlCancelPendingWaits(uint32_t idClient) +{ + HGCMMsgCancelPendingWaits Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_MSG_CANCEL, 0); + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + + +/** + * Prepares a session. + * @since 6.0 + * @sa GUEST_SESSION_PREPARE + */ +VBGLR3DECL(int) VbglR3GuestCtrlSessionPrepare(uint32_t idClient, uint32_t idSession, void const *pvKey, uint32_t cbKey) +{ + int rc; + do + { + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter idSession; + HGCMFunctionParameter pKey; + } Msg; + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_SESSION_PREPARE, 2); + VbglHGCMParmUInt32Set(&Msg.idSession, idSession); + VbglHGCMParmPtrSet(&Msg.pKey, (void *)pvKey, cbKey); + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + } while (rc == VERR_INTERRUPTED); + return rc; +} + + +/** + * Accepts a session. + * @since 6.0 + * @sa GUEST_SESSION_ACCEPT + */ +VBGLR3DECL(int) VbglR3GuestCtrlSessionAccept(uint32_t idClient, uint32_t idSession, void const *pvKey, uint32_t cbKey) +{ + int rc; + do + { + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter idSession; + HGCMFunctionParameter pKey; + } Msg; + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_SESSION_ACCEPT, 2); + VbglHGCMParmUInt32Set(&Msg.idSession, idSession); + VbglHGCMParmPtrSet(&Msg.pKey, (void *)pvKey, cbKey); + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + } while (rc == VERR_INTERRUPTED); + return rc; +} + + +/** + * Cancels a prepared session. + * @since 6.0 + * @sa GUEST_SESSION_CANCEL_PREPARED + */ +VBGLR3DECL(int) VbglR3GuestCtrlSessionCancelPrepared(uint32_t idClient, uint32_t idSession) +{ + int rc; + do + { + struct + { + VBGLIOCHGCMCALL Hdr; + HGCMFunctionParameter idSession; + } Msg; + VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_SESSION_CANCEL_PREPARED, 1); + VbglHGCMParmUInt32Set(&Msg.idSession, idSession); + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + } while (rc == VERR_INTERRUPTED); + return rc; +} + + +/** + * Invalidates the internal state because the (VM) session has been changed (i.e. restored). + * + * @returns VBox status code. + * @param idClient Client ID to use for invalidating state. + * @param idNewControlSession New control session ID. Currently unused. + */ +VBGLR3DECL(int) VbglR3GuestCtrlSessionHasChanged(uint32_t idClient, uint64_t idNewControlSession) +{ + RT_NOREF(idNewControlSession); + + vbglR3GuestCtrlDetectPeekGetCancelSupport(idClient); + + return VINF_SUCCESS; +} + + +/** + * Asks a specific guest session to close. + * + * @return IPRT status code. + * @param pCtx Guest control command context to use. + * @param fFlags Some kind of flag. Figure it out yourself. + */ +VBGLR3DECL(int) VbglR3GuestCtrlSessionClose(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t fFlags) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER); + + HGCMMsgSessionClose Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_SESSION_CLOSE, pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.flags, fFlags); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + + +/** + * Notifies a guest session. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uType Notification type of type GUEST_SESSION_NOTIFYTYPE_XXX. + * @param iResult Result code (rc) to notify. + */ +VBGLR3DECL(int) VbglR3GuestCtrlSessionNotify(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uType, int32_t iResult) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMMsgSessionNotify Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_SESSION_NOTIFY, 3); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, uType); + VbglHGCMParmUInt32Set(&Msg.result, (uint32_t)iResult); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Initializes a session startup info, extended version. + * + * @returns VBox status code. + * @param pStartupInfo Session startup info to initializes. + * @param cbUser Size (in bytes) to use for the user name buffer. + * @param cbPassword Size (in bytes) to use for the password buffer. + * @param cbDomain Size (in bytes) to use for the domain name buffer. + */ +VBGLR3DECL(int) VbglR3GuestCtrlSessionStartupInfoInitEx(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo, + size_t cbUser, size_t cbPassword, size_t cbDomain) +{ + AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER); + + RT_BZERO(pStartupInfo, sizeof(VBGLR3GUESTCTRLSESSIONSTARTUPINFO)); + +#define ALLOC_STR(a_Str, a_cb) \ + if ((a_cb) > 0) \ + { \ + pStartupInfo->psz##a_Str = RTStrAlloc(a_cb); \ + AssertPtrBreak(pStartupInfo->psz##a_Str); \ + pStartupInfo->cb##a_Str = (uint32_t)a_cb; \ + } + + do + { + ALLOC_STR(User, cbUser); + ALLOC_STR(Password, cbPassword); + ALLOC_STR(Domain, cbDomain); + + return VINF_SUCCESS; + + } while (0); + +#undef ALLOC_STR + + VbglR3GuestCtrlSessionStartupInfoDestroy(pStartupInfo); + return VERR_NO_MEMORY; +} + +/** + * Initializes a session startup info. + * + * @returns VBox status code. + * @param pStartupInfo Session startup info to initializes. + */ +VBGLR3DECL(int) VbglR3GuestCtrlSessionStartupInfoInit(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo) +{ + return VbglR3GuestCtrlSessionStartupInfoInitEx(pStartupInfo, + GUEST_PROC_DEF_USER_LEN, GUEST_PROC_DEF_PASSWORD_LEN, + GUEST_PROC_DEF_DOMAIN_LEN); +} + +/** + * Destroys a session startup info. + * + * @param pStartupInfo Session startup info to destroy. + */ +VBGLR3DECL(void) VbglR3GuestCtrlSessionStartupInfoDestroy(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo) +{ + if (!pStartupInfo) + return; + + RTStrFree(pStartupInfo->pszUser); + RTStrFree(pStartupInfo->pszPassword); + RTStrFree(pStartupInfo->pszDomain); + + RT_BZERO(pStartupInfo, sizeof(VBGLR3GUESTCTRLSESSIONSTARTUPINFO)); +} + +/** + * Free's a session startup info. + * + * @param pStartupInfo Session startup info to free. + * The pointer will not be valid anymore after return. + */ +VBGLR3DECL(void) VbglR3GuestCtrlSessionStartupInfoFree(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo) +{ + if (!pStartupInfo) + return; + + VbglR3GuestCtrlSessionStartupInfoDestroy(pStartupInfo); + + RTMemFree(pStartupInfo); + pStartupInfo = NULL; +} + +/** + * Duplicates a session startup info. + * + * @returns Duplicated session startup info on success, or NULL on error. + * @param pStartupInfo Session startup info to duplicate. + */ +VBGLR3DECL(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO) VbglR3GuestCtrlSessionStartupInfoDup(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo) +{ + AssertPtrReturn(pStartupInfo, NULL); + + PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfoDup = (PVBGLR3GUESTCTRLSESSIONSTARTUPINFO) + RTMemDup(pStartupInfo, sizeof(VBGLR3GUESTCTRLSESSIONSTARTUPINFO)); + if (pStartupInfoDup) + { + do + { + pStartupInfoDup->pszUser = NULL; + pStartupInfoDup->pszPassword = NULL; + pStartupInfoDup->pszDomain = NULL; + +#define DUP_STR(a_Str) \ + if (pStartupInfo->cb##a_Str) \ + { \ + pStartupInfoDup->psz##a_Str = (char *)RTStrDup(pStartupInfo->psz##a_Str); \ + AssertPtrBreak(pStartupInfoDup->psz##a_Str); \ + pStartupInfoDup->cb##a_Str = (uint32_t)strlen(pStartupInfoDup->psz##a_Str) + 1 /* Include terminator */; \ + } + DUP_STR(User); + DUP_STR(Password); + DUP_STR(Domain); + +#undef DUP_STR + + return pStartupInfoDup; + + } while (0); /* To use break macros above. */ + + VbglR3GuestCtrlSessionStartupInfoFree(pStartupInfoDup); + } + + return NULL; +} + +/** + * Retrieves a HOST_SESSION_CREATE message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param ppStartupInfo Where to store the allocated session startup info. + * Needs to be free'd by VbglR3GuestCtrlSessionStartupInfoFree(). + */ +VBGLR3DECL(int) VbglR3GuestCtrlSessionGetOpen(PVBGLR3GUESTCTRLCMDCTX pCtx, PVBGLR3GUESTCTRLSESSIONSTARTUPINFO *ppStartupInfo) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 6, VERR_INVALID_PARAMETER); + AssertPtrReturn(ppStartupInfo, VERR_INVALID_POINTER); + + PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo + = (PVBGLR3GUESTCTRLSESSIONSTARTUPINFO)RTMemAlloc(sizeof(VBGLR3GUESTCTRLSESSIONSTARTUPINFO)); + if (!pStartupInfo) + return VERR_NO_MEMORY; + + int rc = VbglR3GuestCtrlSessionStartupInfoInit(pStartupInfo); + if (RT_FAILURE(rc)) + { + VbglR3GuestCtrlSessionStartupInfoFree(pStartupInfo); + return rc; + } + + do + { + HGCMMsgSessionOpen Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_SESSION_CREATE); + VbglHGCMParmUInt32Set(&Msg.protocol, 0); + VbglHGCMParmPtrSet(&Msg.username, pStartupInfo->pszUser, pStartupInfo->cbUser); + VbglHGCMParmPtrSet(&Msg.password, pStartupInfo->pszPassword, pStartupInfo->cbPassword); + VbglHGCMParmPtrSet(&Msg.domain, pStartupInfo->pszDomain, pStartupInfo->cbDomain); + VbglHGCMParmUInt32Set(&Msg.flags, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.protocol.GetUInt32(&pStartupInfo->uProtocol); + Msg.flags.GetUInt32(&pStartupInfo->fFlags); + + pStartupInfo->uSessionID = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(pCtx->uContextID); + } + + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + + if (RT_SUCCESS(rc)) + { + *ppStartupInfo = pStartupInfo; + } + else + VbglR3GuestCtrlSessionStartupInfoFree(pStartupInfo); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Retrieves a HOST_SESSION_CLOSE message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlSessionGetClose(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *pfFlags, uint32_t *pidSession) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER); + + AssertPtrReturn(pfFlags, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgSessionClose Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_SESSION_CLOSE); + VbglHGCMParmUInt32Set(&Msg.flags, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.flags.GetUInt32(pfFlags); + + if (pidSession) + *pidSession = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(pCtx->uContextID); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_PATH_RENAME message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlPathGetRename(PVBGLR3GUESTCTRLCMDCTX pCtx, + char *pszSource, uint32_t cbSource, + char *pszDest, uint32_t cbDest, + uint32_t *pfFlags) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 4, VERR_INVALID_PARAMETER); + + AssertPtrReturn(pszSource, VERR_INVALID_POINTER); + AssertReturn(cbSource, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszDest, VERR_INVALID_POINTER); + AssertReturn(cbDest, VERR_INVALID_PARAMETER); + AssertPtrReturn(pfFlags, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgPathRename Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_PATH_RENAME); + VbglHGCMParmPtrSet(&Msg.source, pszSource, cbSource); + VbglHGCMParmPtrSet(&Msg.dest, pszDest, cbDest); + VbglHGCMParmUInt32Set(&Msg.flags, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.flags.GetUInt32(pfFlags); + } + + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_PATH_USER_DOCUMENTS message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlPathGetUserDocuments(PVBGLR3GUESTCTRLCMDCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 1, VERR_INVALID_PARAMETER); + + int rc; + do + { + HGCMMsgPathUserDocuments Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_PATH_USER_DOCUMENTS); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + Msg.context.GetUInt32(&pCtx->uContextID); + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_PATH_USER_HOME message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlPathGetUserHome(PVBGLR3GUESTCTRLCMDCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 1, VERR_INVALID_PARAMETER); + + int rc; + do + { + HGCMMsgPathUserHome Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_PATH_USER_HOME); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + Msg.context.GetUInt32(&pCtx->uContextID); + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + +/** + * Retrieves a HOST_MSG_SHUTDOWN message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param pfAction Where to store the action flags on success. + */ +VBGLR3DECL(int) VbglR3GuestCtrlGetShutdown(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *pfAction) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER); + AssertPtrReturn(pfAction, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgShutdown Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_SHUTDOWN); + VbglHGCMParmUInt32Set(&Msg.action, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.action.GetUInt32(pfAction); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + +/** + * Initializes a process startup info, extended version. + * + * @returns VBox status code. + * @param pStartupInfo Process startup info to initializes. + * @param cbCmd Size (in bytes) to use for the command buffer. + * @param cbUser Size (in bytes) to use for the user name buffer. + * @param cbPassword Size (in bytes) to use for the password buffer. + * @param cbDomain Size (in bytes) to use for the domain buffer. + * @param cbArgs Size (in bytes) to use for the arguments buffer. + * @param cbEnv Size (in bytes) to use for the environment buffer. + */ +VBGLR3DECL(int) VbglR3GuestCtrlProcStartupInfoInitEx(PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo, + size_t cbCmd, + size_t cbUser, size_t cbPassword, size_t cbDomain, + size_t cbArgs, size_t cbEnv) +{ + AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER); + AssertReturn(cbCmd, VERR_INVALID_PARAMETER); + AssertReturn(cbUser, VERR_INVALID_PARAMETER); + AssertReturn(cbPassword, VERR_INVALID_PARAMETER); + AssertReturn(cbDomain, VERR_INVALID_PARAMETER); + AssertReturn(cbArgs, VERR_INVALID_PARAMETER); + AssertReturn(cbEnv, VERR_INVALID_PARAMETER); + + RT_BZERO(pStartupInfo, sizeof(VBGLR3GUESTCTRLPROCSTARTUPINFO)); + +#define ALLOC_STR(a_Str, a_cb) \ + if ((a_cb) > 0) \ + { \ + pStartupInfo->psz##a_Str = RTStrAlloc(a_cb); \ + AssertPtrBreak(pStartupInfo->psz##a_Str); \ + pStartupInfo->cb##a_Str = (uint32_t)a_cb; \ + } + + do + { + ALLOC_STR(Cmd, cbCmd); + ALLOC_STR(Args, cbArgs); + ALLOC_STR(Env, cbEnv); + ALLOC_STR(User, cbUser); + ALLOC_STR(Password, cbPassword); + ALLOC_STR(Domain, cbDomain); + + return VINF_SUCCESS; + + } while (0); + +#undef ALLOC_STR + + VbglR3GuestCtrlProcStartupInfoDestroy(pStartupInfo); + return VERR_NO_MEMORY; +} + +/** + * Initializes a process startup info with default values. + * + * @param pStartupInfo Process startup info to initializes. + */ +VBGLR3DECL(int) VbglR3GuestCtrlProcStartupInfoInit(PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo) +{ + return VbglR3GuestCtrlProcStartupInfoInitEx(pStartupInfo, + GUEST_PROC_DEF_CMD_LEN, + GUEST_PROC_DEF_USER_LEN /* Deprecated, now handled via session creation. */, + GUEST_PROC_DEF_PASSWORD_LEN /* Ditto. */, + GUEST_PROC_DEF_DOMAIN_LEN /* Ditto. */, + GUEST_PROC_DEF_ARGS_LEN, GUEST_PROC_DEF_ENV_LEN); +} + +/** + * Destroys a process startup info. + * + * @param pStartupInfo Process startup info to destroy. + */ +VBGLR3DECL(void) VbglR3GuestCtrlProcStartupInfoDestroy(PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo) +{ + if (!pStartupInfo) + return; + + RTStrFree(pStartupInfo->pszCmd); + RTStrFree(pStartupInfo->pszArgs); + RTStrFree(pStartupInfo->pszEnv); + RTStrFree(pStartupInfo->pszUser); + RTStrFree(pStartupInfo->pszPassword); + RTStrFree(pStartupInfo->pszDomain); + + RT_BZERO(pStartupInfo, sizeof(VBGLR3GUESTCTRLPROCSTARTUPINFO)); +} + +/** + * Free's a process startup info. + * + * @param pStartupInfo Process startup info to free. + * The pointer will not be valid anymore after return. + */ +VBGLR3DECL(void) VbglR3GuestCtrlProcStartupInfoFree(PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo) +{ + if (!pStartupInfo) + return; + + VbglR3GuestCtrlProcStartupInfoDestroy(pStartupInfo); + + RTMemFree(pStartupInfo); + pStartupInfo = NULL; +} + +/** + * Duplicates a process startup info. + * + * @returns Duplicated process startup info on success, or NULL on error. + * @param pStartupInfo Process startup info to duplicate. + */ +VBGLR3DECL(PVBGLR3GUESTCTRLPROCSTARTUPINFO) VbglR3GuestCtrlProcStartupInfoDup(PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo) +{ + AssertPtrReturn(pStartupInfo, NULL); + + PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfoDup = (PVBGLR3GUESTCTRLPROCSTARTUPINFO) + RTMemDup(pStartupInfo, sizeof(VBGLR3GUESTCTRLPROCSTARTUPINFO)); + if (pStartupInfoDup) + { + do + { + pStartupInfoDup->pszCmd = NULL; + pStartupInfoDup->pszArgs = NULL; + pStartupInfoDup->pszEnv = NULL; + pStartupInfoDup->pszUser = NULL; + pStartupInfoDup->pszPassword = NULL; + pStartupInfoDup->pszDomain = NULL; + +#define DUP_STR(a_Str) \ + if (pStartupInfo->cb##a_Str) \ + { \ + pStartupInfoDup->psz##a_Str = (char *)RTStrDup(pStartupInfo->psz##a_Str); \ + AssertPtrBreak(pStartupInfoDup->psz##a_Str); \ + pStartupInfoDup->cb##a_Str = (uint32_t)strlen(pStartupInfoDup->psz##a_Str) + 1 /* Include terminator */; \ + } + +#define DUP_MEM(a_Str) \ + if (pStartupInfo->cb##a_Str) \ + { \ + pStartupInfoDup->psz##a_Str = (char *)RTMemDup(pStartupInfo->psz##a_Str, pStartupInfo->cb##a_Str); \ + AssertPtrBreak(pStartupInfoDup->psz##a_Str); \ + pStartupInfoDup->cb##a_Str = (uint32_t)pStartupInfo->cb##a_Str; \ + } + + DUP_STR(Cmd); + DUP_MEM(Args); + DUP_MEM(Env); + DUP_STR(User); + DUP_STR(Password); + DUP_STR(Domain); + +#undef DUP_STR +#undef DUP_MEM + + return pStartupInfoDup; + + } while (0); /* To use break macros above. */ + + VbglR3GuestCtrlProcStartupInfoFree(pStartupInfoDup); + } + + return NULL; +} + +/** + * Retrieves a HOST_EXEC_CMD message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param ppStartupInfo Where to store the allocated session startup info. + * Needs to be free'd by VbglR3GuestCtrlProcStartupInfoFree(). + */ +VBGLR3DECL(int) VbglR3GuestCtrlProcGetStart(PVBGLR3GUESTCTRLCMDCTX pCtx, PVBGLR3GUESTCTRLPROCSTARTUPINFO *ppStartupInfo) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(ppStartupInfo, VERR_INVALID_POINTER); + + PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo + = (PVBGLR3GUESTCTRLPROCSTARTUPINFO)RTMemAlloc(sizeof(VBGLR3GUESTCTRLPROCSTARTUPINFO)); + if (!pStartupInfo) + return VERR_NO_MEMORY; + + int rc = VbglR3GuestCtrlProcStartupInfoInit(pStartupInfo); + if (RT_FAILURE(rc)) + { + VbglR3GuestCtrlProcStartupInfoFree(pStartupInfo); + return rc; + } + + unsigned cRetries = 0; + const unsigned cMaxRetries = 32; /* Should be enough for now. */ + const unsigned cGrowthFactor = 2; /* By how much the buffers will grow if they're too small yet. */ + + do + { + LogRel(("VbglR3GuestCtrlProcGetStart: Retrieving\n")); + + HGCMMsgProcExec Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_EXEC_CMD); + VbglHGCMParmPtrSet(&Msg.cmd, pStartupInfo->pszCmd, pStartupInfo->cbCmd); + VbglHGCMParmUInt32Set(&Msg.flags, 0); + VbglHGCMParmUInt32Set(&Msg.num_args, 0); + VbglHGCMParmPtrSet(&Msg.args, pStartupInfo->pszArgs, pStartupInfo->cbArgs); + VbglHGCMParmUInt32Set(&Msg.num_env, 0); + VbglHGCMParmUInt32Set(&Msg.cb_env, 0); + VbglHGCMParmPtrSet(&Msg.env, pStartupInfo->pszEnv, pStartupInfo->cbEnv); + if (pCtx->uProtocol < 2) + { + VbglHGCMParmPtrSet(&Msg.u.v1.username, pStartupInfo->pszUser, pStartupInfo->cbUser); + VbglHGCMParmPtrSet(&Msg.u.v1.password, pStartupInfo->pszPassword, pStartupInfo->cbPassword); + VbglHGCMParmUInt32Set(&Msg.u.v1.timeout, 0); + } + else + { + VbglHGCMParmUInt32Set(&Msg.u.v2.timeout, 0); + VbglHGCMParmUInt32Set(&Msg.u.v2.priority, 0); + VbglHGCMParmUInt32Set(&Msg.u.v2.num_affinity, 0); + VbglHGCMParmPtrSet(&Msg.u.v2.affinity, pStartupInfo->uAffinity, sizeof(pStartupInfo->uAffinity)); + } + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_FAILURE(rc)) + { + LogRel(("VbglR3GuestCtrlProcGetStart: 1 - %Rrc (retry %u, cbCmd=%RU32, cbArgs=%RU32, cbEnv=%RU32)\n", + rc, cRetries, pStartupInfo->cbCmd, pStartupInfo->cbArgs, pStartupInfo->cbEnv)); + + if ( rc == VERR_BUFFER_OVERFLOW + && cRetries++ < cMaxRetries) + { +#define GROW_STR(a_Str, a_cbMax) \ + pStartupInfo->psz##a_Str = (char *)RTMemRealloc(pStartupInfo->psz##a_Str, \ + RT_MIN(pStartupInfo->cb##a_Str * cGrowthFactor, a_cbMax)); \ + AssertPtrBreakStmt(pStartupInfo->psz##a_Str, VERR_NO_MEMORY); \ + pStartupInfo->cb##a_Str = RT_MIN(pStartupInfo->cb##a_Str * cGrowthFactor, a_cbMax); + + /* We can't tell which parameter doesn't fit, so we have to resize all. */ + GROW_STR(Cmd , GUEST_PROC_MAX_CMD_LEN); + GROW_STR(Args, GUEST_PROC_MAX_ARGS_LEN); + GROW_STR(Env, GUEST_PROC_MAX_ENV_LEN); + +#undef GROW_STR + LogRel(("VbglR3GuestCtrlProcGetStart: 2 - %Rrc (retry %u, cbCmd=%RU32, cbArgs=%RU32, cbEnv=%RU32)\n", + rc, cRetries, pStartupInfo->cbCmd, pStartupInfo->cbArgs, pStartupInfo->cbEnv)); + LogRel(("g_fVbglR3GuestCtrlHavePeekGetCancel=%RTbool\n", RT_BOOL(g_fVbglR3GuestCtrlHavePeekGetCancel))); + } + else + break; + } + else + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.flags.GetUInt32(&pStartupInfo->fFlags); + Msg.num_args.GetUInt32(&pStartupInfo->cArgs); + Msg.num_env.GetUInt32(&pStartupInfo->cEnvVars); + Msg.cb_env.GetUInt32(&pStartupInfo->cbEnv); + if (pCtx->uProtocol < 2) + Msg.u.v1.timeout.GetUInt32(&pStartupInfo->uTimeLimitMS); + else + { + Msg.u.v2.timeout.GetUInt32(&pStartupInfo->uTimeLimitMS); + Msg.u.v2.priority.GetUInt32(&pStartupInfo->uPriority); + Msg.u.v2.num_affinity.GetUInt32(&pStartupInfo->cAffinity); + } + } + } while (( rc == VERR_INTERRUPTED + || rc == VERR_BUFFER_OVERFLOW) && g_fVbglR3GuestCtrlHavePeekGetCancel); + + if (RT_SUCCESS(rc)) + { + *ppStartupInfo = pStartupInfo; + } + else + VbglR3GuestCtrlProcStartupInfoFree(pStartupInfo); + + LogRel(("VbglR3GuestCtrlProcGetStart: Returning %Rrc (retry %u, cbCmd=%RU32, cbArgs=%RU32, cbEnv=%RU32)\n", + rc, cRetries, pStartupInfo->cbCmd, pStartupInfo->cbArgs, pStartupInfo->cbEnv)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Allocates and gets host data, based on the message ID. + * + * This will block until data becomes available. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param puPID Where to return the guest PID to retrieve output from on success. + * @param puHandle Where to return the guest process handle to retrieve output from on success. + * @param pfFlags Where to return the output flags on success. + */ +VBGLR3DECL(int) VbglR3GuestCtrlProcGetOutput(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t *puPID, uint32_t *puHandle, uint32_t *pfFlags) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 4, VERR_INVALID_PARAMETER); + + AssertPtrReturn(puPID, VERR_INVALID_POINTER); + AssertPtrReturn(puHandle, VERR_INVALID_POINTER); + AssertPtrReturn(pfFlags, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgProcOutput Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_EXEC_GET_OUTPUT); + VbglHGCMParmUInt32Set(&Msg.pid, 0); + VbglHGCMParmUInt32Set(&Msg.handle, 0); + VbglHGCMParmUInt32Set(&Msg.flags, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, RT_UOFFSETOF(HGCMMsgProcOutput, data)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.pid.GetUInt32(puPID); + Msg.handle.GetUInt32(puHandle); + Msg.flags.GetUInt32(pfFlags); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves the input data from host which then gets sent to the started + * process (HOST_EXEC_SET_INPUT). + * + * This will block until data becomes available. + */ +VBGLR3DECL(int) VbglR3GuestCtrlProcGetInput(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t *puPID, uint32_t *pfFlags, + void *pvData, uint32_t cbData, + uint32_t *pcbSize) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 5, VERR_INVALID_PARAMETER); + + AssertPtrReturn(puPID, VERR_INVALID_POINTER); + AssertPtrReturn(pfFlags, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbSize, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgProcInput Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_EXEC_SET_INPUT); + VbglHGCMParmUInt32Set(&Msg.pid, 0); + VbglHGCMParmUInt32Set(&Msg.flags, 0); + VbglHGCMParmPtrSet(&Msg.data, pvData, cbData); + VbglHGCMParmUInt32Set(&Msg.size, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.pid.GetUInt32(puPID); + Msg.flags.GetUInt32(pfFlags); + Msg.size.GetUInt32(pcbSize); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + + if ( rc != VERR_TOO_MUCH_DATA + || g_fVbglR3GuestCtrlHavePeekGetCancel) + return rc; + return VERR_BUFFER_OVERFLOW; +} + + +/** + * Retrieves a HOST_DIR_REMOVE message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlDirGetRemove(PVBGLR3GUESTCTRLCMDCTX pCtx, + char *pszPath, uint32_t cbPath, + uint32_t *pfFlags) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 3, VERR_INVALID_PARAMETER); + + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(cbPath, VERR_INVALID_PARAMETER); + AssertPtrReturn(pfFlags, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgDirRemove Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_DIR_REMOVE); + VbglHGCMParmPtrSet(&Msg.path, pszPath, cbPath); + VbglHGCMParmUInt32Set(&Msg.flags, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.flags.GetUInt32(pfFlags); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_FILE_OPEN message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileGetOpen(PVBGLR3GUESTCTRLCMDCTX pCtx, + char *pszFileName, uint32_t cbFileName, + char *pszAccess, uint32_t cbAccess, + char *pszDisposition, uint32_t cbDisposition, + char *pszSharing, uint32_t cbSharing, + uint32_t *puCreationMode, + uint64_t *poffAt) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 7, VERR_INVALID_PARAMETER); + + AssertPtrReturn(pszFileName, VERR_INVALID_POINTER); + AssertReturn(cbFileName, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszAccess, VERR_INVALID_POINTER); + AssertReturn(cbAccess, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszDisposition, VERR_INVALID_POINTER); + AssertReturn(cbDisposition, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszSharing, VERR_INVALID_POINTER); + AssertReturn(cbSharing, VERR_INVALID_PARAMETER); + AssertPtrReturn(puCreationMode, VERR_INVALID_POINTER); + AssertPtrReturn(poffAt, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgFileOpen Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_OPEN); + VbglHGCMParmPtrSet(&Msg.filename, pszFileName, cbFileName); + VbglHGCMParmPtrSet(&Msg.openmode, pszAccess, cbAccess); + VbglHGCMParmPtrSet(&Msg.disposition, pszDisposition, cbDisposition); + VbglHGCMParmPtrSet(&Msg.sharing, pszSharing, cbSharing); + VbglHGCMParmUInt32Set(&Msg.creationmode, 0); + VbglHGCMParmUInt64Set(&Msg.offset, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.creationmode.GetUInt32(puCreationMode); + Msg.offset.GetUInt64(poffAt); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_FILE_CLOSE message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileGetClose(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER); + AssertPtrReturn(puHandle, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgFileClose Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_CLOSE); + VbglHGCMParmUInt32Set(&Msg.handle, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.handle.GetUInt32(puHandle); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_FILE_READ message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileGetRead(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle, uint32_t *puToRead) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + AssertReturn(pCtx->uNumParms == 3, VERR_INVALID_PARAMETER); + AssertPtrReturn(puHandle, VERR_INVALID_POINTER); + AssertPtrReturn(puToRead, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgFileRead Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_READ); + VbglHGCMParmUInt32Set(&Msg.handle, 0); + VbglHGCMParmUInt32Set(&Msg.size, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.handle.GetUInt32(puHandle); + Msg.size.GetUInt32(puToRead); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_FILE_READ_AT message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileGetReadAt(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t *puHandle, uint32_t *puToRead, uint64_t *poffAt) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + AssertReturn(pCtx->uNumParms == 4, VERR_INVALID_PARAMETER); + AssertPtrReturn(puHandle, VERR_INVALID_POINTER); + AssertPtrReturn(puToRead, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgFileReadAt Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_READ_AT); + VbglHGCMParmUInt32Set(&Msg.handle, 0); + VbglHGCMParmUInt64Set(&Msg.offset, 0); + VbglHGCMParmUInt32Set(&Msg.size, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.handle.GetUInt32(puHandle); + Msg.offset.GetUInt64(poffAt); + Msg.size.GetUInt32(puToRead); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_FILE_WRITE message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileGetWrite(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle, + void *pvData, uint32_t cbData, uint32_t *pcbSize) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + AssertReturn(pCtx->uNumParms == 4, VERR_INVALID_PARAMETER); + AssertPtrReturn(puHandle, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbSize, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgFileWrite Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_WRITE); + VbglHGCMParmUInt32Set(&Msg.handle, 0); + VbglHGCMParmPtrSet(&Msg.data, pvData, cbData); + VbglHGCMParmUInt32Set(&Msg.size, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.handle.GetUInt32(puHandle); + Msg.size.GetUInt32(pcbSize); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + + if ( rc != VERR_TOO_MUCH_DATA + || g_fVbglR3GuestCtrlHavePeekGetCancel) + return rc; + return VERR_BUFFER_OVERFLOW; +} + + +/** + * Retrieves a HOST_FILE_WRITE_AT message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileGetWriteAt(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle, + void *pvData, uint32_t cbData, uint32_t *pcbSize, uint64_t *poffAt) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + AssertReturn(pCtx->uNumParms == 5, VERR_INVALID_PARAMETER); + AssertPtrReturn(puHandle, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbSize, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgFileWriteAt Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_WRITE_AT); + VbglHGCMParmUInt32Set(&Msg.handle, 0); + VbglHGCMParmPtrSet(&Msg.data, pvData, cbData); + VbglHGCMParmUInt32Set(&Msg.size, 0); + VbglHGCMParmUInt64Set(&Msg.offset, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.handle.GetUInt32(puHandle); + Msg.size.GetUInt32(pcbSize); + Msg.offset.GetUInt64(poffAt); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + + if ( rc != VERR_TOO_MUCH_DATA + || g_fVbglR3GuestCtrlHavePeekGetCancel) + return rc; + return VERR_BUFFER_OVERFLOW; +} + + +/** + * Retrieves a HOST_FILE_SEEK message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileGetSeek(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t *puHandle, uint32_t *puSeekMethod, uint64_t *poffAt) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + AssertReturn(pCtx->uNumParms == 4, VERR_INVALID_PARAMETER); + AssertPtrReturn(puHandle, VERR_INVALID_POINTER); + AssertPtrReturn(puSeekMethod, VERR_INVALID_POINTER); + AssertPtrReturn(poffAt, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgFileSeek Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_SEEK); + VbglHGCMParmUInt32Set(&Msg.handle, 0); + VbglHGCMParmUInt32Set(&Msg.method, 0); + VbglHGCMParmUInt64Set(&Msg.offset, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.handle.GetUInt32(puHandle); + Msg.method.GetUInt32(puSeekMethod); + Msg.offset.GetUInt64(poffAt); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_FILE_TELL message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileGetTell(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER); + AssertPtrReturn(puHandle, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgFileTell Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_TELL); + VbglHGCMParmUInt32Set(&Msg.handle, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.handle.GetUInt32(puHandle); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_FILE_SET_SIZE message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileGetSetSize(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle, uint64_t *pcbNew) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + AssertReturn(pCtx->uNumParms == 3, VERR_INVALID_PARAMETER); + AssertPtrReturn(puHandle, VERR_INVALID_POINTER); + AssertPtrReturn(pcbNew, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgFileSetSize Msg; + VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.id32Context, HOST_MSG_FILE_SET_SIZE); + VbglHGCMParmUInt32Set(&Msg.id32Handle, 0); + VbglHGCMParmUInt64Set(&Msg.cb64NewSize, 0); + + rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.id32Context.GetUInt32(&pCtx->uContextID); + Msg.id32Handle.GetUInt32(puHandle); + Msg.cb64NewSize.GetUInt64(pcbNew); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_EXEC_TERMINATE message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlProcGetTerminate(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puPID) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER); + AssertPtrReturn(puPID, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgProcTerminate Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_EXEC_TERMINATE); + VbglHGCMParmUInt32Set(&Msg.pid, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.pid.GetUInt32(puPID); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Retrieves a HOST_EXEC_WAIT_FOR message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlProcGetWaitFor(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t *puPID, uint32_t *puWaitFlags, uint32_t *puTimeoutMS) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + AssertReturn(pCtx->uNumParms == 5, VERR_INVALID_PARAMETER); + AssertPtrReturn(puPID, VERR_INVALID_POINTER); + + int rc; + do + { + HGCMMsgProcWaitFor Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms); + VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_EXEC_WAIT_FOR); + VbglHGCMParmUInt32Set(&Msg.pid, 0); + VbglHGCMParmUInt32Set(&Msg.flags, 0); + VbglHGCMParmUInt32Set(&Msg.timeout, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.pid.GetUInt32(puPID); + Msg.flags.GetUInt32(puWaitFlags); + Msg.timeout.GetUInt32(puTimeoutMS); + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Replies to a HOST_MSG_FILE_OPEN message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uRc Guest rc of operation (note: IPRT-style signed int). + * @param uFileHandle File handle of opened file on success. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileCbOpen(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t uRc, uint32_t uFileHandle) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMReplyFileNotify Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_OPEN); + VbglHGCMParmUInt32Set(&Msg.rc, uRc); + VbglHGCMParmUInt32Set(&Msg.u.open.handle, uFileHandle); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.open)); +} + + +/** + * Replies to a HOST_MSG_FILE_CLOSE message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uRc Guest rc of operation (note: IPRT-style signed int). + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileCbClose(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t uRc) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMReplyFileNotify Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 3); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_CLOSE); + VbglHGCMParmUInt32Set(&Msg.rc, uRc); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSETOF(HGCMReplyFileNotify, u)); +} + + +/** + * Sends an unexpected file handling error to the host. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uRc Guest rc of operation (note: IPRT-style signed int). + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileCbError(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMReplyFileNotify Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 3); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_ERROR); + VbglHGCMParmUInt32Set(&Msg.rc, uRc); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSETOF(HGCMReplyFileNotify, u)); +} + + +/** + * Replies to a HOST_MSG_FILE_READ message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uRc Guest rc of operation (note: IPRT-style signed int). + * @param pvData Pointer to read file data from guest on success. + * @param cbData Size (in bytes) of read file data from guest on success. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileCbRead(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t uRc, + void *pvData, uint32_t cbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMReplyFileNotify Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_READ); + VbglHGCMParmUInt32Set(&Msg.rc, uRc); + VbglHGCMParmPtrSet(&Msg.u.read.data, pvData, cbData); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.read)); +} + + +/** + * Replies to a HOST_MSG_FILE_READ_AT message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uRc Guest rc of operation (note: IPRT-style signed int). + * @param pvData Pointer to read file data from guest on success. + * @param cbData Size (in bytes) of read file data from guest on success. + * @param offNew New offset (in bytes) the guest file pointer points at on success. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileCbReadOffset(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc, + void *pvData, uint32_t cbData, int64_t offNew) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMReplyFileNotify Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 5); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_READ_OFFSET); + VbglHGCMParmUInt32Set(&Msg.rc, uRc); + VbglHGCMParmPtrSet(&Msg.u.ReadOffset.pvData, pvData, cbData); + VbglHGCMParmUInt64Set(&Msg.u.ReadOffset.off64New, (uint64_t)offNew); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.ReadOffset)); +} + + +/** + * Replies to a HOST_MSG_FILE_WRITE message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uRc Guest rc of operation (note: IPRT-style signed int). + * @param cbWritten Size (in bytes) of file data successfully written to guest file. Can be partial. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileCbWrite(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc, uint32_t cbWritten) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMReplyFileNotify Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_WRITE); + VbglHGCMParmUInt32Set(&Msg.rc, uRc); + VbglHGCMParmUInt32Set(&Msg.u.write.written, cbWritten); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.write)); +} + + +/** + * Replies to a HOST_MSG_FILE_WRITE_AT message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uRc Guest rc of operation (note: IPRT-style signed int). + * @param cbWritten Size (in bytes) of file data successfully written to guest file. Can be partial. + * @param offNew New offset (in bytes) the guest file pointer points at on success. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileCbWriteOffset(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc, uint32_t cbWritten, int64_t offNew) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMReplyFileNotify Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 5); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_WRITE_OFFSET); + VbglHGCMParmUInt32Set(&Msg.rc, uRc); + VbglHGCMParmUInt32Set(&Msg.u.WriteOffset.cb32Written, cbWritten); + VbglHGCMParmUInt64Set(&Msg.u.WriteOffset.off64New, (uint64_t)offNew); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.WriteOffset)); +} + + +/** + * Replies to a HOST_MSG_FILE_SEEK message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uRc Guest rc of operation (note: IPRT-style signed int). + * @param offCurrent New offset (in bytes) the guest file pointer points at on success. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileCbSeek(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc, uint64_t offCurrent) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMReplyFileNotify Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_SEEK); + VbglHGCMParmUInt32Set(&Msg.rc, uRc); + VbglHGCMParmUInt64Set(&Msg.u.seek.offset, offCurrent); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.seek)); +} + + +/** + * Replies to a HOST_MSG_FILE_TELL message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uRc Guest rc of operation (note: IPRT-style signed int). + * @param offCurrent Current offset (in bytes) the guest file pointer points at on success. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileCbTell(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc, uint64_t offCurrent) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMReplyFileNotify Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_TELL); + VbglHGCMParmUInt32Set(&Msg.rc, uRc); + VbglHGCMParmUInt64Set(&Msg.u.tell.offset, offCurrent); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.tell)); +} + + +/** + * Replies to a HOST_MSG_FILE_SET_SIZE message. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uRc Guest rc of operation (note: IPRT-style signed int). + * @param cbNew New file size (in bytes) of the guest file on success. + */ +VBGLR3DECL(int) VbglR3GuestCtrlFileCbSetSize(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc, uint64_t cbNew) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMReplyFileNotify Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_SET_SIZE); + VbglHGCMParmUInt32Set(&Msg.rc, uRc); + VbglHGCMParmUInt64Set(&Msg.u.SetSize.cb64Size, cbNew); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.SetSize)); +} + + +/** + * Callback for reporting a guest process status (along with some other stuff) to the host. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uPID Guest process PID to report status for. + * @param uStatus Status to report. Of type PROC_STS_XXX. + * @param fFlags Additional status flags, depending on the reported status. See RTPROCSTATUS. + * @param pvData Pointer to additional status data. Optional. + * @param cbData Size (in bytes) of additional status data. + */ +VBGLR3DECL(int) VbglR3GuestCtrlProcCbStatus(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t uPID, uint32_t uStatus, uint32_t fFlags, + void *pvData, uint32_t cbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMMsgProcStatus Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_EXEC_STATUS, 5); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.pid, uPID); + VbglHGCMParmUInt32Set(&Msg.status, uStatus); + VbglHGCMParmUInt32Set(&Msg.flags, fFlags); + VbglHGCMParmPtrSet(&Msg.data, pvData, cbData); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + + +/** + * Sends output (from stdout/stderr) from a running process. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uPID Guest process PID to report status for. + * @param uHandle Guest process handle the output belong to. + * @param fFlags Additional output flags. + * @param pvData Pointer to actual output data. + * @param cbData Size (in bytes) of output data. + */ +VBGLR3DECL(int) VbglR3GuestCtrlProcCbOutput(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t uPID,uint32_t uHandle, uint32_t fFlags, + void *pvData, uint32_t cbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMMsgProcOutput Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_EXEC_OUTPUT, 5); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.pid, uPID); + VbglHGCMParmUInt32Set(&Msg.handle, uHandle); + VbglHGCMParmUInt32Set(&Msg.flags, fFlags); + VbglHGCMParmPtrSet(&Msg.data, pvData, cbData); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + + +/** + * Callback for reporting back the input status of a guest process to the host. + * + * @returns VBox status code. + * @param pCtx Guest control command context to use. + * @param uPID Guest process PID to report status for. + * @param uStatus Status to report. Of type INPUT_STS_XXX. + * @param fFlags Additional input flags. + * @param cbWritten Size (in bytes) of input data handled. + */ +VBGLR3DECL(int) VbglR3GuestCtrlProcCbStatusInput(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t uPID, uint32_t uStatus, + uint32_t fFlags, uint32_t cbWritten) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + HGCMMsgProcStatusInput Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_EXEC_INPUT_STATUS, 5); + VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID); + VbglHGCMParmUInt32Set(&Msg.pid, uPID); + VbglHGCMParmUInt32Set(&Msg.status, uStatus); + VbglHGCMParmUInt32Set(&Msg.flags, fFlags); + VbglHGCMParmUInt32Set(&Msg.written, cbWritten); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestProp.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestProp.cpp new file mode 100644 index 00000000..106be26e --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestProp.cpp @@ -0,0 +1,1032 @@ +/* $Id: VBoxGuestR3LibGuestProp.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, guest properties. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#if defined(VBOX_VBGLR3_XFREE86) || defined(VBOX_VBGLR3_XORG) +# define VBOX_VBGLR3_XSERVER +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/string.h> +#ifndef VBOX_VBGLR3_XSERVER +# include <iprt/mem.h> +#endif +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/stdarg.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/HostServices/GuestPropertySvc.h> + +#include "VBoxGuestR3LibInternal.h" + +#ifdef VBOX_VBGLR3_XFREE86 +/* Rather than try to resolve all the header file conflicts, I will just + prototype what we need here. */ +extern "C" char* xf86strcpy(char*,const char*); +# undef strcpy +# define strcpy xf86strcpy +extern "C" void* xf86memchr(const void*,int,xf86size_t); +# undef memchr +# define memchr xf86memchr +extern "C" void* xf86memset(const void*,int,xf86size_t); +# undef memset +# define memset xf86memset + +#endif /* VBOX_VBGLR3_XFREE86 */ + +#ifdef VBOX_VBGLR3_XSERVER + +# undef RTStrEnd +# define RTStrEnd xf86RTStrEnd + +DECLINLINE(char const *) RTStrEnd(char const *pszString, size_t cchMax) +{ + /* Avoid potential issues with memchr seen in glibc. + * See sysdeps/x86_64/memchr.S in glibc versions older than 2.11 */ + while (cchMax > RTSTR_MEMCHR_MAX) + { + char const *pszRet = (char const *)memchr(pszString, '\0', RTSTR_MEMCHR_MAX); + if (RT_LIKELY(pszRet)) + return pszRet; + pszString += RTSTR_MEMCHR_MAX; + cchMax -= RTSTR_MEMCHR_MAX; + } + return (char const *)memchr(pszString, '\0', cchMax); +} + +DECLINLINE(char *) RTStrEnd(char *pszString, size_t cchMax) +{ + /* Avoid potential issues with memchr seen in glibc. + * See sysdeps/x86_64/memchr.S in glibc versions older than 2.11 */ + while (cchMax > RTSTR_MEMCHR_MAX) + { + char *pszRet = (char *)memchr(pszString, '\0', RTSTR_MEMCHR_MAX); + if (RT_LIKELY(pszRet)) + return pszRet; + pszString += RTSTR_MEMCHR_MAX; + cchMax -= RTSTR_MEMCHR_MAX; + } + return (char *)memchr(pszString, '\0', cchMax); +} + +#endif /* VBOX_VBGLR3_XSERVER */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Structure containing information needed to enumerate through guest + * properties. + * + * @remarks typedef in VBoxGuestLib.h. + */ +struct VBGLR3GUESTPROPENUM +{ + /** @todo add a magic and validate the handle. */ + /** The buffer containing the raw enumeration data */ + char *pchBuf; + /** The end of the buffer */ + char *pchBufEnd; + /** Pointer to the next entry to enumerate inside the buffer */ + char *pchNext; +}; + + + +/** + * Connects to the guest property service. + * + * @returns VBox status code + * @returns VERR_NOT_SUPPORTED if guest properties are not available on the host. + * @param pidClient Where to put the client ID on success. The client ID + * must be passed to all the other calls to the service. + */ +VBGLR3DECL(int) VbglR3GuestPropConnect(HGCMCLIENTID *pidClient) +{ + int rc = VbglR3HGCMConnect("VBoxGuestPropSvc", pidClient); + if (rc == VERR_NOT_IMPLEMENTED || rc == VERR_HGCM_SERVICE_NOT_FOUND) + rc = VERR_NOT_SUPPORTED; + return rc; +} + + +/** + * Disconnect from the guest property service. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3InfoSvcConnect(). + */ +VBGLR3DECL(int) VbglR3GuestPropDisconnect(HGCMCLIENTID idClient) +{ + return VbglR3HGCMDisconnect(idClient); +} + + +/** + * Checks if @a pszPropName exists. + * + * @returns \c true if the guest property exists, \c false if not. + * @param idClient The HGCM client ID for the guest property session. + * @param pszPropName The property name. + */ +VBGLR3DECL(bool) VbglR3GuestPropExist(uint32_t idClient, const char *pszPropName) +{ + return RT_SUCCESS(VbglR3GuestPropReadEx(idClient, pszPropName, NULL /*ppszValue*/, NULL /* ppszFlags */, NULL /* puTimestamp */)); +} + + +/** + * Write a property value. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3InvsSvcConnect(). + * @param pszName The property to save to. Utf8 + * @param pszValue The value to store. Utf8. If this is NULL then + * the property will be removed. + * @param pszFlags The flags for the property + */ +VBGLR3DECL(int) VbglR3GuestPropWrite(HGCMCLIENTID idClient, const char *pszName, const char *pszValue, const char *pszFlags) +{ + int rc; + + if (pszValue != NULL) + { + GuestPropMsgSetProperty Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_SET_PROP_VALUE, 3); + VbglHGCMParmPtrSetString(&Msg.name, pszName); + VbglHGCMParmPtrSetString(&Msg.value, pszValue); + VbglHGCMParmPtrSetString(&Msg.flags, pszFlags); + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + } + else + { + GuestPropMsgDelProperty Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_DEL_PROP, 1); + VbglHGCMParmPtrSetString(&Msg.name, pszName); + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + } + return rc; +} + + +/** + * Write a property value. + * + * @returns VBox status code. + * + * @param idClient The client id returned by VbglR3InvsSvcConnect(). + * @param pszName The property to save to. Must be valid UTF-8. + * @param pszValue The value to store. Must be valid UTF-8. + * If this is NULL then the property will be removed. + * + * @note if the property already exists and pszValue is not NULL then the + * property's flags field will be left unchanged + */ +VBGLR3DECL(int) VbglR3GuestPropWriteValue(HGCMCLIENTID idClient, const char *pszName, const char *pszValue) +{ + int rc; + + if (pszValue != NULL) + { + GuestPropMsgSetPropertyValue Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_SET_PROP_VALUE, 2); + VbglHGCMParmPtrSetString(&Msg.name, pszName); + VbglHGCMParmPtrSetString(&Msg.value, pszValue); + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + } + else + { + GuestPropMsgDelProperty Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_DEL_PROP, 1); + VbglHGCMParmPtrSetString(&Msg.name, pszName); + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + } + return rc; +} + +#ifndef VBOX_VBGLR3_XSERVER +/** + * Write a property value where the value is formatted in RTStrPrintfV fashion. + * + * @returns The same as VbglR3GuestPropWriteValue with the addition of VERR_NO_STR_MEMORY. + * + * @param idClient The client ID returned by VbglR3InvsSvcConnect(). + * @param pszName The property to save to. Must be valid UTF-8. + * @param pszValueFormat The value format. This must be valid UTF-8 when fully formatted. + * @param va The format arguments. + */ +VBGLR3DECL(int) VbglR3GuestPropWriteValueV(HGCMCLIENTID idClient, const char *pszName, const char *pszValueFormat, va_list va) +{ + /* + * Format the value and pass it on to the setter. + */ + int rc = VERR_NO_STR_MEMORY; + char *pszValue; + if (RTStrAPrintfV(&pszValue, pszValueFormat, va) >= 0) + { + rc = VbglR3GuestPropWriteValue(idClient, pszName, pszValue); + RTStrFree(pszValue); + } + return rc; +} + + +/** + * Write a property value where the value is formatted in RTStrPrintf fashion. + * + * @returns The same as VbglR3GuestPropWriteValue with the addition of VERR_NO_STR_MEMORY. + * + * @param idClient The client ID returned by VbglR3InvsSvcConnect(). + * @param pszName The property to save to. Must be valid UTF-8. + * @param pszValueFormat The value format. This must be valid UTF-8 when fully formatted. + * @param ... The format arguments. + */ +VBGLR3DECL(int) VbglR3GuestPropWriteValueF(HGCMCLIENTID idClient, const char *pszName, const char *pszValueFormat, ...) +{ + va_list va; + va_start(va, pszValueFormat); + int rc = VbglR3GuestPropWriteValueV(idClient, pszName, pszValueFormat, va); + va_end(va); + return rc; +} +#endif /* VBOX_VBGLR3_XSERVER */ + +/** + * Retrieve a property. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success, pszValue, pu64Timestamp and pszFlags + * containing valid data. + * @retval VERR_BUFFER_OVERFLOW if the scratch buffer @a pcBuf is not large + * enough. In this case the size needed will be placed in + * @a pcbBufActual if it is not NULL. + * @retval VERR_NOT_FOUND if the key wasn't found. + * + * @param idClient The client id returned by VbglR3GuestPropConnect(). + * @param pszName The value to read. Utf8 + * @param pvBuf A scratch buffer to store the data retrieved into. + * The returned data is only valid for it's lifetime. + * @a ppszValue will point to the start of this buffer. + * @param cbBuf The size of @a pcBuf + * @param ppszValue Where to store the pointer to the value retrieved. + * Optional. + * @param pu64Timestamp Where to store the timestamp. Optional. + * @param ppszFlags Where to store the pointer to the flags. Optional. + * @param pcbBufActual If @a pcBuf is not large enough, the size needed. + * Optional. + */ +VBGLR3DECL(int) VbglR3GuestPropRead(HGCMCLIENTID idClient, const char *pszName, + void *pvBuf, uint32_t cbBuf, + char **ppszValue, uint64_t *pu64Timestamp, + char **ppszFlags, + uint32_t *pcbBufActual) +{ + /* + * Create the GET_PROP message and call the host. + */ + GuestPropMsgGetProperty Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_GET_PROP, 4); + VbglHGCMParmPtrSetString(&Msg.name, pszName); + VbglHGCMParmPtrSet(&Msg.buffer, pvBuf, cbBuf); + VbglHGCMParmUInt64Set(&Msg.timestamp, 0); + VbglHGCMParmUInt32Set(&Msg.size, 0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + /* + * The cbBufActual parameter is also returned on overflow so the call can + * adjust his/her buffer. + */ + if ( rc == VERR_BUFFER_OVERFLOW + || pcbBufActual != NULL) + { + int rc2 = VbglHGCMParmUInt32Get(&Msg.size, pcbBufActual); + AssertRCReturn(rc2, RT_FAILURE(rc) ? rc : rc2); + } + if (RT_FAILURE(rc)) + return rc; + + /* + * Buffer layout: Value\0Flags\0. + * + * If the caller cares about any of these strings, make sure things are + * properly terminated (paranoia). + */ + if ( RT_SUCCESS(rc) + && (ppszValue != NULL || ppszFlags != NULL)) + { + /* Validate / skip 'Name'. */ + char *pszFlags = RTStrEnd((char *)pvBuf, cbBuf) + 1; + AssertPtrReturn(pszFlags, VERR_TOO_MUCH_DATA); + if (ppszValue) + *ppszValue = (char *)pvBuf; + + if (ppszFlags) + { + /* Validate 'Flags'. */ + char *pszEos = RTStrEnd(pszFlags, cbBuf - (pszFlags - (char *)pvBuf)); + AssertPtrReturn(pszEos, VERR_TOO_MUCH_DATA); + *ppszFlags = pszFlags; + } + } + + /* And the timestamp, if requested. */ + if (pu64Timestamp != NULL) + { + rc = VbglHGCMParmUInt64Get(&Msg.timestamp, pu64Timestamp); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} + +/** + * Reads a guest property by returning allocated values. + * + * @returns VBox status code, fully bitched. + * + * @param idClient The HGCM client ID for the guest property session. + * @param pszPropName The property name. + * @param ppszValue Where to return the value. This is always set + * to NULL. Needs to be free'd using RTStrFree(). Optional. + * @param ppszFlags Where to return the value flags. + * Needs to be free'd using RTStrFree(). Optional. + * @param puTimestamp Where to return the timestamp. This is only set + * on success. Optional. + */ +VBGLR3DECL(int) VbglR3GuestPropReadEx(uint32_t idClient, + const char *pszPropName, char **ppszValue, char **ppszFlags, uint64_t *puTimestamp) +{ + AssertPtrReturn(pszPropName, VERR_INVALID_POINTER); + + uint32_t cbBuf = _1K; + void *pvBuf = NULL; + int rc = VINF_SUCCESS; /* MSC can't figure out the loop */ + + if (ppszValue) + *ppszValue = NULL; + + for (unsigned cTries = 0; cTries < 10; cTries++) + { + /* + * (Re-)Allocate the buffer and try read the property. + */ + RTMemFree(pvBuf); + pvBuf = RTMemAlloc(cbBuf); + if (!pvBuf) + { + rc = VERR_NO_MEMORY; + break; + } + char *pszValue; + char *pszFlags; + uint64_t uTimestamp; + rc = VbglR3GuestPropRead(idClient, pszPropName, pvBuf, cbBuf, &pszValue, &uTimestamp, &pszFlags, NULL); + if (RT_FAILURE(rc)) + { + if (rc == VERR_BUFFER_OVERFLOW) + { + /* try again with a bigger buffer. */ + cbBuf *= 2; + continue; + } + break; + } + + if (ppszValue) + { + *ppszValue = RTStrDup(pszValue); + if (!*ppszValue) + { + rc = VERR_NO_MEMORY; + break; + } + } + + if (puTimestamp) + *puTimestamp = uTimestamp; + if (ppszFlags) + *ppszFlags = RTStrDup(pszFlags); + break; /* done */ + } + + if (pvBuf) + RTMemFree(pvBuf); + return rc; +} + +#ifndef VBOX_VBGLR3_XSERVER +/** + * Retrieve a property value, allocating space for it. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success, *ppszValue containing valid data. + * @retval VERR_NOT_FOUND if the key wasn't found. + * @retval VERR_TOO_MUCH_DATA if we were unable to determine the right size + * to allocate for the buffer. This can happen as the result of a + * race between our allocating space and the host changing the + * property value. + * + * @param idClient The client id returned by VbglR3GuestPropConnect(). + * @param pszName The value to read. Must be valid UTF-8. + * @param ppszValue Where to store the pointer to the value returned. + * This is always set to NULL or to the result, even + * on failure. + */ +VBGLR3DECL(int) VbglR3GuestPropReadValueAlloc(HGCMCLIENTID idClient, const char *pszName, char **ppszValue) +{ + /* + * Quick input validation. + */ + AssertPtr(ppszValue); + *ppszValue = NULL; + AssertPtrReturn(pszName, VERR_INVALID_PARAMETER); + + /* + * There is a race here between our reading the property size and the + * host changing the value before we read it. Try up to ten times and + * report the problem if that fails. + */ + char *pszValue = NULL; + void *pvBuf = NULL; + uint32_t cbBuf = GUEST_PROP_MAX_VALUE_LEN; + int rc = VERR_BUFFER_OVERFLOW; + for (unsigned i = 0; i < 10 && rc == VERR_BUFFER_OVERFLOW; ++i) + { + /* We leave a bit of space here in case the maximum value is raised. */ + cbBuf += 1024; + void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf); + if (pvTmpBuf) + { + pvBuf = pvTmpBuf; + rc = VbglR3GuestPropRead(idClient, pszName, pvBuf, cbBuf, &pszValue, NULL, NULL, &cbBuf); + } + else + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + Assert(pszValue == (char *)pvBuf); + *ppszValue = pszValue; + } + else + { + RTMemFree(pvBuf); + if (rc == VERR_BUFFER_OVERFLOW) + /* VERR_BUFFER_OVERFLOW has a different meaning here as a + * return code, but we need to report the race. */ + rc = VERR_TOO_MUCH_DATA; + } + + return rc; +} + + +/** + * Free the memory used by VbglR3GuestPropReadValueAlloc for returning a + * value. + * + * @param pszValue the memory to be freed. NULL pointers will be ignored. + */ +VBGLR3DECL(void) VbglR3GuestPropReadValueFree(char *pszValue) +{ + RTMemFree(pszValue); +} +#endif /* VBOX_VBGLR3_XSERVER */ + +/** + * Retrieve a property value, using a user-provided buffer to store it. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success, pszValue containing valid data. + * @retval VERR_BUFFER_OVERFLOW and the size needed in pcchValueActual if the + * buffer provided was too small + * @retval VERR_NOT_FOUND if the key wasn't found. + * + * @note There is a race here between obtaining the size of the buffer + * needed to hold the value and the value being updated. + * + * @param idClient The client id returned by VbglR3GuestPropConnect(). + * @param pszName The value to read. Utf8 + * @param pszValue Where to store the value retrieved. + * @param cchValue The size of the buffer pointed to by @a pszValue + * @param pcchValueActual Where to store the size of the buffer needed if + * the buffer supplied is too small. Optional. + */ +VBGLR3DECL(int) VbglR3GuestPropReadValue(HGCMCLIENTID idClient, const char *pszName, + char *pszValue, uint32_t cchValue, + uint32_t *pcchValueActual) +{ + void *pvBuf = pszValue; + uint32_t cchValueActual; + int rc = VbglR3GuestPropRead(idClient, pszName, pvBuf, cchValue, &pszValue, NULL, NULL, &cchValueActual); + if (pcchValueActual != NULL) + *pcchValueActual = cchValueActual; + return rc; +} + + +#ifndef VBOX_VBGLR3_XSERVER +/** + * Raw API for enumerating guest properties which match a given pattern. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success and pcBuf points to a packed array + * of the form \<name\>, \<value\>, \<timestamp string\>, \<flags\>, + * terminated by four empty strings. pcbBufActual will contain the + * total size of the array. + * @retval VERR_BUFFER_OVERFLOW if the buffer provided was too small. In + * this case pcbBufActual will contain the size of the buffer needed. + * @returns IPRT error code in other cases, and pchBufActual is undefined. + * + * @param idClient The client ID returned by VbglR3GuestPropConnect + * @param pszzPatterns A packed array of zero terminated strings, terminated + * by an empty string. + * @param pcBuf The buffer to store the results to. + * @param cbBuf The size of the buffer + * @param pcbBufActual Where to store the size of the returned data on + * success or the buffer size needed if @a pcBuf is too + * small. + */ +VBGLR3DECL(int) VbglR3GuestPropEnumRaw(HGCMCLIENTID idClient, + const char *pszzPatterns, + char *pcBuf, + uint32_t cbBuf, + uint32_t *pcbBufActual) +{ + GuestPropMsgEnumProperties Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_ENUM_PROPS, 3); + + /* Get the length of the patterns array... */ + size_t cchPatterns = 0; + for (size_t cchCurrent = strlen(pszzPatterns); cchCurrent != 0; + cchCurrent = strlen(pszzPatterns + cchPatterns)) + cchPatterns += cchCurrent + 1; + /* ...including the terminator. */ + ++cchPatterns; + VbglHGCMParmPtrSet(&Msg.patterns, (char *)pszzPatterns, (uint32_t)cchPatterns); + VbglHGCMParmPtrSet(&Msg.strings, pcBuf, cbBuf); + VbglHGCMParmUInt32Set(&Msg.size, 0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if ( pcbBufActual + && ( RT_SUCCESS(rc) + || rc == VERR_BUFFER_OVERFLOW)) + { + int rc2 = VbglHGCMParmUInt32Get(&Msg.size, pcbBufActual); + if (RT_FAILURE(rc2)) + rc = rc2; + } + return rc; +} + + +/** + * Start enumerating guest properties which match a given pattern. + * + * This function creates a handle which can be used to continue enumerating. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success, *ppHandle points to a handle for continuing + * the enumeration and *ppszName, *ppszValue, *pu64Timestamp and + * *ppszFlags are set. + * @retval VERR_TOO_MUCH_DATA if it was not possible to determine the amount + * of local space needed to store all the enumeration data. This is + * due to a race between allocating space and the host adding new + * data, so retrying may help here. Other parameters are left + * uninitialised + * + * @param idClient The client id returned by VbglR3InfoSvcConnect(). + * @param papszPatterns The patterns against which the properties are + * matched. Pass NULL if everything should be matched. + * @param cPatterns The number of patterns in @a papszPatterns. 0 means + * match everything. + * @param ppHandle where the handle for continued enumeration is stored + * on success. This must be freed with + * VbglR3GuestPropEnumFree when it is no longer needed. + * @param ppszName Where to store the next property name. This will be + * set to NULL if there are no more properties to + * enumerate. This pointer should not be freed. Optional. + * @param ppszValue Where to store the next property value. This will be + * set to NULL if there are no more properties to + * enumerate. This pointer should not be freed. Optional. + * @param pu64Timestamp Where to store the next property timestamp. This + * will be set to zero if there are no more properties + * to enumerate. Optional. + * @param ppszFlags Where to store the next property flags. This will be + * set to NULL if there are no more properties to + * enumerate. This pointer should not be freed. Optional. + * + * @remarks While all output parameters are optional, you need at least one to + * figure out when to stop. + */ +VBGLR3DECL(int) VbglR3GuestPropEnum(HGCMCLIENTID idClient, + char const * const *papszPatterns, + uint32_t cPatterns, + PVBGLR3GUESTPROPENUM *ppHandle, + char const **ppszName, + char const **ppszValue, + uint64_t *pu64Timestamp, + char const **ppszFlags) +{ + /* Create the handle. */ + PVBGLR3GUESTPROPENUM pHandle = (PVBGLR3GUESTPROPENUM)RTMemAllocZ(sizeof(VBGLR3GUESTPROPENUM)); + if (RT_LIKELY(pHandle)) + {/* likely */} + else + return VERR_NO_MEMORY; + + /* Get the length of the pattern string, including the final terminator. */ + size_t cbPatterns = 1; + for (uint32_t i = 0; i < cPatterns; ++i) + cbPatterns += strlen(papszPatterns[i]) + 1; + + /* Pack the pattern array. */ + char *pszzPatterns = (char *)RTMemAlloc(cbPatterns); + size_t off = 0; + for (uint32_t i = 0; i < cPatterns; ++i) + { + size_t cb = strlen(papszPatterns[i]) + 1; + memcpy(&pszzPatterns[off], papszPatterns[i], cb); + off += cb; + } + pszzPatterns[off] = '\0'; + + /* In reading the guest property data we are racing against the host + * adding more of it, so loop a few times and retry on overflow. */ + uint32_t cbBuf = 4096; /* picked out of thin air */ + char *pchBuf = NULL; + int rc = VINF_SUCCESS; + for (int i = 0; i < 10; ++i) + { + void *pvNew = RTMemRealloc(pchBuf, cbBuf); + if (pvNew) + pchBuf = (char *)pvNew; + else + { + rc = VERR_NO_MEMORY; + break; + } + rc = VbglR3GuestPropEnumRaw(idClient, pszzPatterns, pchBuf, cbBuf, &cbBuf); + if (rc != VERR_BUFFER_OVERFLOW) + break; + cbBuf += 4096; /* Just to increase our chances */ + } + RTMemFree(pszzPatterns); + if (RT_SUCCESS(rc)) + { + /* + * Complete the handle and call VbglR3GuestPropEnumNext to retrieve the first entry. + */ + pHandle->pchNext = pchBuf; + pHandle->pchBuf = pchBuf; + pHandle->pchBufEnd = pchBuf + cbBuf; + + const char *pszNameTmp; + if (!ppszName) + ppszName = &pszNameTmp; + rc = VbglR3GuestPropEnumNext(pHandle, ppszName, ppszValue, pu64Timestamp, ppszFlags); + if (RT_SUCCESS(rc)) + { + *ppHandle = pHandle; + return rc; + } + } + else if (rc == VERR_BUFFER_OVERFLOW) + rc = VERR_TOO_MUCH_DATA; + RTMemFree(pchBuf); + RTMemFree(pHandle); + return rc; +} + + +/** + * Get the next guest property. + * + * See @a VbglR3GuestPropEnum. + * + * @returns VBox status code. + * + * @param pHandle Handle obtained from @a VbglR3GuestPropEnum. + * @param ppszName Where to store the next property name. This will be + * set to NULL if there are no more properties to + * enumerate. This pointer should not be freed. Optional. + * @param ppszValue Where to store the next property value. This will be + * set to NULL if there are no more properties to + * enumerate. This pointer should not be freed. Optional. + * @param pu64Timestamp Where to store the next property timestamp. This + * will be set to zero if there are no more properties + * to enumerate. Optional. + * @param ppszFlags Where to store the next property flags. This will be + * set to NULL if there are no more properties to + * enumerate. This pointer should not be freed. Optional. + * + * @remarks While all output parameters are optional, you need at least one to + * figure out when to stop. + */ +VBGLR3DECL(int) VbglR3GuestPropEnumNext(PVBGLR3GUESTPROPENUM pHandle, + char const **ppszName, + char const **ppszValue, + uint64_t *pu64Timestamp, + char const **ppszFlags) +{ + /* + * The VBGLR3GUESTPROPENUM structure contains a buffer containing the raw + * properties data and a pointer into the buffer which tracks how far we + * have parsed so far. The buffer contains packed strings in groups of + * four - name, value, timestamp (as a decimal string) and flags. It is + * terminated by four empty strings. We can rely on this layout unless + * the caller has been poking about in the structure internals, in which + * case they must take responsibility for the results. + * + * Layout: + * Name\0Value\0Timestamp\0Flags\0 + */ + char *pchNext = pHandle->pchNext; /* The cursor. */ + char *pchEnd = pHandle->pchBufEnd; /* End of buffer, for size calculations. */ + + char *pszName = pchNext; + char *pszValue = pchNext = RTStrEnd(pchNext, pchEnd - pchNext) + 1; + AssertPtrReturn(pchNext, VERR_PARSE_ERROR); /* 0x1 is also an invalid pointer :) */ + + char *pszTimestamp = pchNext = RTStrEnd(pchNext, pchEnd - pchNext) + 1; + AssertPtrReturn(pchNext, VERR_PARSE_ERROR); + + char *pszFlags = pchNext = RTStrEnd(pchNext, pchEnd - pchNext) + 1; + AssertPtrReturn(pchNext, VERR_PARSE_ERROR); + + /* + * Don't move the index pointer if we found the terminating "\0\0\0\0" entry. + * Don't try convert the timestamp either. + */ + uint64_t u64Timestamp; + if (*pszName != '\0') + { + pchNext = RTStrEnd(pchNext, pchEnd - pchNext) + 1; + AssertPtrReturn(pchNext, VERR_PARSE_ERROR); + + /* Convert the timestamp string into a number. */ + int rc = RTStrToUInt64Full(pszTimestamp, 0, &u64Timestamp); + AssertRCSuccessReturn(rc, VERR_PARSE_ERROR); + + pHandle->pchNext = pchNext; + AssertPtr(pchNext); + } + else + { + u64Timestamp = 0; + AssertMsgReturn(!*pszValue && !*pszTimestamp && !*pszFlags, + ("'%s' '%s' '%s'\n", pszValue, pszTimestamp, pszFlags), + VERR_PARSE_ERROR); + } + + /* + * Everything is fine, set the return values. + */ + if (ppszName) + *ppszName = *pszName != '\0' ? pszName : NULL; + if (ppszValue) + *ppszValue = *pszValue != '\0' ? pszValue : NULL; + if (pu64Timestamp) + *pu64Timestamp = u64Timestamp; + if (ppszFlags) + *ppszFlags = *pszFlags != '\0' ? pszFlags : NULL; + return VINF_SUCCESS; +} + + +/** + * Free an enumeration handle returned by @a VbglR3GuestPropEnum. + * @param pHandle the handle to free + */ +VBGLR3DECL(void) VbglR3GuestPropEnumFree(PVBGLR3GUESTPROPENUM pHandle) +{ + if (!pHandle) + return; + RTMemFree(pHandle->pchBuf); + RTMemFree(pHandle); +} + + +/** + * Deletes a guest property. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3InvsSvcConnect(). + * @param pszName The property to delete. Utf8 + */ +VBGLR3DECL(int) VbglR3GuestPropDelete(HGCMCLIENTID idClient, const char *pszName) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + + GuestPropMsgDelProperty Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_DEL_PROP, 1); + VbglHGCMParmPtrSetString(&Msg.name, pszName); + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + + +/** + * Deletes a set of keys. + * + * The set is specified in the same way as for VbglR3GuestPropEnum. + * + * @returns VBox status code. Stops on first failure. + * See also VbglR3GuestPropEnum. + * + * @param idClient The client id returned by VbglR3InfoSvcConnect(). + * @param papszPatterns The patterns against which the properties are + * matched. Pass NULL if everything should be matched. + * @param cPatterns The number of patterns in @a papszPatterns. 0 means + * match everything. + */ +VBGLR3DECL(int) VbglR3GuestPropDelSet(HGCMCLIENTID idClient, + const char * const *papszPatterns, + uint32_t cPatterns) +{ + PVBGLR3GUESTPROPENUM pHandle; + char const *pszName, *pszValue, *pszFlags; + uint64_t pu64Timestamp; + int rc = VbglR3GuestPropEnum(idClient, + (char **)papszPatterns, /** @todo fix this cast. */ + cPatterns, + &pHandle, + &pszName, + &pszValue, + &pu64Timestamp, + &pszFlags); + + while (RT_SUCCESS(rc) && pszName) + { + rc = VbglR3GuestPropWriteValue(idClient, pszName, NULL); + if (RT_FAILURE(rc)) + break; + + rc = VbglR3GuestPropEnumNext(pHandle, + &pszName, + &pszValue, + &pu64Timestamp, + &pszFlags); + } + + VbglR3GuestPropEnumFree(pHandle); + return rc; +} + + +/** + * Wait for notification of changes to a guest property. If this is called in + * a loop, the timestamp of the last notification seen can be passed as a + * parameter to be sure that no notifications are missed. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success, @a ppszName, @a ppszValue, + * @a pu64Timestamp and @a ppszFlags containing valid data. + * @retval VINF_NOT_FOUND if no previous notification could be found with the + * timestamp supplied. This will normally mean that a large number + * of notifications occurred in between. + * @retval VERR_BUFFER_OVERFLOW if the scratch buffer @a pvBuf is not large + * enough. In this case the size needed will be placed in + * @a pcbBufActual if it is not NULL. + * @retval VERR_TIMEOUT if a timeout occurred before a notification was seen. + * + * @param idClient The client id returned by VbglR3GuestPropConnect(). + * @param pszPatterns The patterns that the property names must matchfor + * the change to be reported. + * @param pvBuf A scratch buffer to store the data retrieved into. + * The returned data is only valid for it's lifetime. + * @a ppszValue will point to the start of this buffer. + * @param cbBuf The size of @a pvBuf + * @param u64Timestamp The timestamp of the last event seen. Pass zero + * to wait for the next event. + * @param cMillies Timeout in milliseconds. Use RT_INDEFINITE_WAIT + * to wait indefinitely. + * @param ppszName Where to store the pointer to the name retrieved. + * Optional. + * @param ppszValue Where to store the pointer to the value retrieved. + * Optional. + * @param pu64Timestamp Where to store the timestamp. Optional. + * @param ppszFlags Where to store the pointer to the flags. Optional. + * @param pcbBufActual If @a pcBuf is not large enough, the size needed. + * Optional. + * @param pfWasDeleted A flag which indicates that property was deleted. + * Optional. + */ +VBGLR3DECL(int) VbglR3GuestPropWait(HGCMCLIENTID idClient, + const char *pszPatterns, + void *pvBuf, uint32_t cbBuf, + uint64_t u64Timestamp, uint32_t cMillies, + char ** ppszName, char **ppszValue, + uint64_t *pu64Timestamp, char **ppszFlags, + uint32_t *pcbBufActual, bool *pfWasDeleted) +{ + /* + * Create the GET_NOTIFICATION message and call the host. + */ + GuestPropMsgGetNotification Msg; + VBGL_HGCM_HDR_INIT_TIMED(&Msg.hdr, idClient, GUEST_PROP_FN_GET_NOTIFICATION, 4, cMillies); + + VbglHGCMParmPtrSetString(&Msg.patterns, pszPatterns); + RT_BZERO(pvBuf, cbBuf); + VbglHGCMParmPtrSet(&Msg.buffer, pvBuf, cbBuf); + VbglHGCMParmUInt64Set(&Msg.timestamp, u64Timestamp); + VbglHGCMParmUInt32Set(&Msg.size, 0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + /* + * The cbBufActual parameter is also returned on overflow so the caller can + * adjust their buffer. + */ + if ( rc == VERR_BUFFER_OVERFLOW + && pcbBufActual != NULL) + { + int rc2 = Msg.size.GetUInt32(pcbBufActual); + AssertRCReturn(rc2, RT_FAILURE(rc) ? rc : rc2); + } + if (RT_FAILURE(rc)) + return rc; + + /* + * Buffer layout: Name\0Value\0Flags\0fWasDeleted\0. + * + * If the caller cares about any of these strings, make sure things are + * properly terminated (paranoia). + */ + if ( RT_SUCCESS(rc) + && (ppszName != NULL || ppszValue != NULL || ppszFlags != NULL || pfWasDeleted != NULL)) + { + /* Validate / skip 'Name'. */ + char *pszValue = RTStrEnd((char *)pvBuf, cbBuf) + 1; + AssertPtrReturn(pszValue, VERR_TOO_MUCH_DATA); + if (ppszName) + *ppszName = (char *)pvBuf; + + /* Validate / skip 'Value'. */ + char *pszFlags = RTStrEnd(pszValue, cbBuf - (pszValue - (char *)pvBuf)) + 1; + AssertPtrReturn(pszFlags, VERR_TOO_MUCH_DATA); + if (ppszValue) + *ppszValue = pszValue; + + if (ppszFlags) + *ppszFlags = pszFlags; + + /* Skip 'Flags' and deal with 'fWasDeleted' if it's present. */ + char *pszWasDeleted = RTStrEnd(pszFlags, cbBuf - (pszFlags - (char *)pvBuf)) + 1; + AssertPtrReturn(pszWasDeleted, VERR_TOO_MUCH_DATA); + char chWasDeleted = 0; + if ( (size_t)pszWasDeleted - (size_t)pvBuf < cbBuf + && (chWasDeleted = *pszWasDeleted) != '\0') + AssertMsgReturn((chWasDeleted == '0' || chWasDeleted == '1') && pszWasDeleted[1] == '\0', + ("'%s'\n", pszWasDeleted), VERR_PARSE_ERROR); + if (pfWasDeleted) + *pfWasDeleted = chWasDeleted == '1'; + } + + /* And the timestamp, if requested. */ + if (pu64Timestamp != NULL) + { + rc = Msg.timestamp.GetUInt64(pu64Timestamp); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} +#endif /* VBOX_VBGLR3_XSERVER */ diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestUser.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestUser.cpp new file mode 100644 index 00000000..afa280fa --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestUser.cpp @@ -0,0 +1,119 @@ +/* $Id: VBoxGuestR3LibGuestUser.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, + * guest user reporting / utility functions. + */ + +/* + * Copyright (C) 2013-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <VBox/log.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "VBoxGuestR3LibInternal.h" + + +/** + * Reports a state change of a specific guest user. + * + * @returns IPRT status value + * @param pszUser Guest user name to report state for. + * @param pszDomain Domain the guest user's account is bound to. + * @param enmState Guest user state to report. + * @param puDetails Pointer to state details. Optional. + * @param cbDetails Size (in bytes) of state details. Pass 0 + * if puDetails is NULL. + */ +VBGLR3DECL(int) VbglR3GuestUserReportState(const char *pszUser, const char *pszDomain, VBoxGuestUserState enmState, + uint8_t *puDetails, uint32_t cbDetails) +{ + AssertPtrReturn(pszUser, VERR_INVALID_POINTER); + /* pszDomain is optional. */ + /* puDetails is optional. */ + AssertReturn(cbDetails == 0 || puDetails != NULL, VERR_INVALID_PARAMETER); + AssertReturn(cbDetails < 16U*_1M, VERR_OUT_OF_RANGE); + + uint32_t cbBase = sizeof(VMMDevReportGuestUserState); + uint32_t cbUser = (uint32_t)strlen(pszUser) + 1; /* Include terminating zero */ + uint32_t cbDomain = pszDomain ? (uint32_t)strlen(pszDomain) + 1 /* Ditto */ : 0; + + /* Allocate enough space for all fields. */ + uint32_t cbSize = cbBase + + cbUser + + cbDomain + + cbDetails; + VMMDevReportGuestUserState *pReport = (VMMDevReportGuestUserState *)RTMemAllocZ(cbSize); + if (!pReport) + return VERR_NO_MEMORY; + + int rc = vmmdevInitRequest(&pReport->header, VMMDevReq_ReportGuestUserState); + if (RT_SUCCESS(rc)) + { + pReport->header.size = cbSize; + + pReport->status.state = enmState; + pReport->status.cbUser = cbUser; + pReport->status.cbDomain = cbDomain; + pReport->status.cbDetails = cbDetails; + + /* + * Note: cbOffDynamic contains the first dynamic array entry within + * VBoxGuestUserStatus. + * Therefore it's vital to *not* change the order of the struct members + * without altering this code. Don't try this at home. + */ + uint32_t cbOffDynamic = RT_UOFFSETOF(VBoxGuestUserStatus, szUser); + + /* pDynamic marks the beginning for the dynamically allocated areas. */ + uint8_t *pDynamic = (uint8_t *)&pReport->status; + pDynamic += cbOffDynamic; + AssertPtr(pDynamic); + + memcpy(pDynamic, pszUser, cbUser); + if (cbDomain) + memcpy(pDynamic + cbUser, pszDomain, cbDomain); + if (cbDetails) + memcpy(pDynamic + cbUser + cbDomain, puDetails, cbDetails); + + rc = vbglR3GRPerform(&pReport->header); + } + + RTMemFree(pReport); + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp new file mode 100644 index 00000000..8053f0be --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp @@ -0,0 +1,108 @@ +/* $Id: VBoxGuestR3LibHGCM.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, + * generic HGCM. + */ + +/* + * Copyright (C) 2015-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR3LibInternal.h" +#include <VBox/VBoxGuestLib.h> +#include <iprt/string.h> + + +/** + * Connects to an HGCM service. + * + * @returns VBox status code + * @param pszServiceName Name of the host service. + * @param pidClient Where to put the client ID on success. The client ID + * must be passed to all the other calls to the service. + */ +VBGLR3DECL(int) VbglR3HGCMConnect(const char *pszServiceName, HGCMCLIENTID *pidClient) +{ + AssertPtrReturn(pszServiceName, VERR_INVALID_POINTER); + AssertPtrReturn(pidClient, VERR_INVALID_POINTER); + + VBGLIOCHGCMCONNECT Info; + RT_ZERO(Info); + VBGLREQHDR_INIT(&Info.Hdr, HGCM_CONNECT); + Info.u.In.Loc.type = VMMDevHGCMLoc_LocalHost_Existing; + int rc = RTStrCopy(Info.u.In.Loc.u.host.achName, sizeof(Info.u.In.Loc.u.host.achName), pszServiceName); + if (RT_FAILURE(rc)) + return rc; + rc = vbglR3DoIOCtl(VBGL_IOCTL_HGCM_CONNECT, &Info.Hdr, sizeof(Info)); + if (RT_SUCCESS(rc)) + *pidClient = Info.u.Out.idClient; + return rc; +} + + +/** + * Disconnect from an HGCM service. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3HGCMConnect(). + */ +VBGLR3DECL(int) VbglR3HGCMDisconnect(HGCMCLIENTID idClient) +{ + VBGLIOCHGCMDISCONNECT Info; + VBGLREQHDR_INIT(&Info.Hdr, HGCM_DISCONNECT); + Info.u.In.idClient = idClient; + + return vbglR3DoIOCtl(VBGL_IOCTL_HGCM_DISCONNECT, &Info.Hdr, sizeof(Info)); +} + + +/** + * Makes a fully prepared HGCM call. + * + * @returns VBox status code. + * @param pInfo Fully prepared HGCM call info. + * @param cbInfo Size of the info. This may sometimes be larger than + * what the parameter count indicates because of + * parameter changes between versions and such. + */ +VBGLR3DECL(int) VbglR3HGCMCall(PVBGLIOCHGCMCALL pInfo, size_t cbInfo) +{ + /* Expect caller to have filled in pInfo. */ + AssertMsg(pInfo->Hdr.cbIn == cbInfo, ("cbIn=%#x cbInfo=%#zx\n", pInfo->Hdr.cbIn, cbInfo)); + AssertMsg(pInfo->Hdr.cbOut == cbInfo, ("cbOut=%#x cbInfo=%#zx\n", pInfo->Hdr.cbOut, cbInfo)); + Assert(sizeof(*pInfo) + pInfo->cParms * sizeof(HGCMFunctionParameter) <= cbInfo); + Assert(pInfo->u32ClientID != 0); + + return vbglR3DoIOCtl(VBGL_IOCTL_HGCM_CALL(cbInfo), &pInfo->Hdr, cbInfo); +} diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostChannel.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostChannel.cpp new file mode 100644 index 00000000..9c9e377e --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostChannel.cpp @@ -0,0 +1,235 @@ +/* $Id: VBoxGuestR3LibHostChannel.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Host Channel. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +#include <iprt/mem.h> + +#include <VBox/HostServices/VBoxHostChannel.h> + +#include "VBoxGuestR3LibInternal.h" + + +VBGLR3DECL(int) VbglR3HostChannelInit(uint32_t *pidClient) +{ + return VbglR3HGCMConnect("VBoxHostChannel", pidClient); +} + +VBGLR3DECL(void) VbglR3HostChannelTerm(uint32_t idClient) +{ + VbglR3HGCMDisconnect(idClient); +} + +VBGLR3DECL(int) VbglR3HostChannelAttach(uint32_t *pu32ChannelHandle, + uint32_t u32HGCMClientId, + const char *pszName, + uint32_t u32Flags) +{ + /* Make a heap copy of the name, because HGCM can not use some of other memory types. */ + size_t cbName = strlen(pszName) + 1; + char *pszCopy = (char *)RTMemAlloc(cbName); + if (pszCopy == NULL) + { + return VERR_NO_MEMORY; + } + + memcpy(pszCopy, pszName, cbName); + + VBoxHostChannelAttach parms; + VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_ATTACH, 3); + VbglHGCMParmPtrSet(&parms.name, pszCopy, (uint32_t)cbName); + VbglHGCMParmUInt32Set(&parms.flags, u32Flags); + VbglHGCMParmUInt32Set(&parms.handle, 0); + + int rc = VbglR3HGCMCall(&parms.hdr, sizeof(parms)); + + if (RT_SUCCESS(rc)) + *pu32ChannelHandle = parms.handle.u.value32; + + RTMemFree(pszCopy); + + return rc; +} + +VBGLR3DECL(void) VbglR3HostChannelDetach(uint32_t u32ChannelHandle, + uint32_t u32HGCMClientId) +{ + VBoxHostChannelDetach parms; + VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_DETACH, 1); + VbglHGCMParmUInt32Set(&parms.handle, u32ChannelHandle); + + VbglR3HGCMCall(&parms.hdr, sizeof(parms)); +} + +VBGLR3DECL(int) VbglR3HostChannelSend(uint32_t u32ChannelHandle, + uint32_t u32HGCMClientId, + void *pvData, + uint32_t cbData) +{ + VBoxHostChannelSend parms; + VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_SEND, 2); + VbglHGCMParmUInt32Set(&parms.handle, u32ChannelHandle); + VbglHGCMParmPtrSet(&parms.data, pvData, cbData); + + return VbglR3HGCMCall(&parms.hdr, sizeof(parms)); +} + +VBGLR3DECL(int) VbglR3HostChannelRecv(uint32_t u32ChannelHandle, + uint32_t u32HGCMClientId, + void *pvData, + uint32_t cbData, + uint32_t *pu32SizeReceived, + uint32_t *pu32SizeRemaining) +{ + VBoxHostChannelRecv parms; + VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_RECV, 4); + VbglHGCMParmUInt32Set(&parms.handle, u32ChannelHandle); + VbglHGCMParmPtrSet(&parms.data, pvData, cbData); + VbglHGCMParmUInt32Set(&parms.sizeReceived, 0); + VbglHGCMParmUInt32Set(&parms.sizeRemaining, 0); + + int rc = VbglR3HGCMCall(&parms.hdr, sizeof(parms)); + + if (RT_SUCCESS(rc)) + { + *pu32SizeReceived = parms.sizeReceived.u.value32; + *pu32SizeRemaining = parms.sizeRemaining.u.value32; + } + + return rc; +} + +VBGLR3DECL(int) VbglR3HostChannelControl(uint32_t u32ChannelHandle, + uint32_t u32HGCMClientId, + uint32_t u32Code, + void *pvParm, + uint32_t cbParm, + void *pvData, + uint32_t cbData, + uint32_t *pu32SizeDataReturned) +{ + VBoxHostChannelControl parms; + VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_CONTROL, 5); + VbglHGCMParmUInt32Set(&parms.handle, u32ChannelHandle); + VbglHGCMParmUInt32Set(&parms.code, u32Code); + VbglHGCMParmPtrSet(&parms.parm, pvParm, cbParm); + VbglHGCMParmPtrSet(&parms.data, pvData, cbData); + VbglHGCMParmUInt32Set(&parms.sizeDataReturned, 0); + + int rc = VbglR3HGCMCall(&parms.hdr, sizeof(parms)); + + if (RT_SUCCESS(rc)) + { + *pu32SizeDataReturned = parms.sizeDataReturned.u.value32; + } + + return rc; +} + +VBGLR3DECL(int) VbglR3HostChannelEventWait(uint32_t *pu32ChannelHandle, + uint32_t u32HGCMClientId, + uint32_t *pu32EventId, + void *pvParm, + uint32_t cbParm, + uint32_t *pu32SizeReturned) +{ + VBoxHostChannelEventWait parms; + VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_EVENT_WAIT, 4); + VbglHGCMParmUInt32Set(&parms.handle, 0); + VbglHGCMParmUInt32Set(&parms.id, 0); + VbglHGCMParmPtrSet(&parms.parm, pvParm, cbParm); + VbglHGCMParmUInt32Set(&parms.sizeReturned, 0); + + int rc = VbglR3HGCMCall(&parms.hdr, sizeof(parms)); + + if (RT_SUCCESS(rc)) + { + *pu32ChannelHandle = parms.handle.u.value32; + *pu32EventId = parms.id.u.value32; + *pu32SizeReturned = parms.sizeReturned.u.value32; + } + + return rc; +} + +VBGLR3DECL(int) VbglR3HostChannelEventCancel(uint32_t u32ChannelHandle, + uint32_t u32HGCMClientId) +{ + RT_NOREF1(u32ChannelHandle); + + VBoxHostChannelEventCancel parms; + VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_EVENT_CANCEL, 0); + + return VbglR3HGCMCall(&parms.hdr, sizeof(parms)); +} + +VBGLR3DECL(int) VbglR3HostChannelQuery(const char *pszName, + uint32_t u32HGCMClientId, + uint32_t u32Code, + void *pvParm, + uint32_t cbParm, + void *pvData, + uint32_t cbData, + uint32_t *pu32SizeDataReturned) +{ + /* Make a heap copy of the name, because HGCM can not use some of other memory types. */ + size_t cbName = strlen(pszName) + 1; + char *pszCopy = (char *)RTMemAlloc(cbName); + if (pszCopy == NULL) + { + return VERR_NO_MEMORY; + } + + memcpy(pszCopy, pszName, cbName); + + VBoxHostChannelQuery parms; + VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_QUERY, 5); + VbglHGCMParmPtrSet(&parms.name, pszCopy, (uint32_t)cbName); + VbglHGCMParmUInt32Set(&parms.code, u32Code); + VbglHGCMParmPtrSet(&parms.parm, pvParm, cbParm); + VbglHGCMParmPtrSet(&parms.data, pvData, cbData); + VbglHGCMParmUInt32Set(&parms.sizeDataReturned, 0); + + int rc = VbglR3HGCMCall(&parms.hdr, sizeof(parms)); + + if (RT_SUCCESS(rc)) + { + *pu32SizeDataReturned = parms.sizeDataReturned.u.value32; + } + + RTMemFree(pszCopy); + + return rc; +} diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostVersion.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostVersion.cpp new file mode 100644 index 00000000..f3d7740f --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostVersion.cpp @@ -0,0 +1,226 @@ +/* $Id: */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, host version check. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/string.h> +#include <VBox/log.h> + +#ifdef RT_OS_WINDOWS + #define WIN32_LEAN_AND_MEAN + #include <iprt/win/windows.h> +#endif + +#include "VBoxGuestR3LibInternal.h" + + +/** + * Checks for a Guest Additions update by comparing the installed version on the + * guest and the reported host version. + * + * @returns VBox status code + * + * @param idClient The client id returned by + * VbglR3InfoSvcConnect(). + * @param pfUpdate Receives pointer to boolean flag indicating + * whether an update was found or not. + * @param ppszHostVersion Receives pointer of allocated version string. + * The returned pointer must be freed using + * VbglR3GuestPropReadValueFree(). Always set to + * NULL. + * @param ppszGuestVersion Receives pointer of allocated revision string. + * The returned pointer must be freed using + * VbglR3GuestPropReadValueFree(). Always set to + * NULL. + */ +VBGLR3DECL(int) VbglR3HostVersionCheckForUpdate(HGCMCLIENTID idClient, bool *pfUpdate, char **ppszHostVersion, char **ppszGuestVersion) +{ +#ifdef VBOX_WITH_GUEST_PROPS + Assert(idClient > 0); + AssertPtr(pfUpdate); + AssertPtr(ppszHostVersion); + AssertPtr(ppszGuestVersion); + + *ppszHostVersion = NULL; + *ppszGuestVersion = NULL; + + /* We assume we have an update initially. + Every block down below is allowed to veto */ + *pfUpdate = true; + + /* Do we need to do all this stuff? */ + char *pszCheckHostVersion; + int rc = VbglR3GuestPropReadValueAlloc(idClient, "/VirtualBox/GuestAdd/CheckHostVersion", &pszCheckHostVersion); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NOT_FOUND) + rc = VINF_SUCCESS; /* If we don't find the value above we do the check by default */ + else + LogFlow(("Could not read check host version flag! rc = %Rrc\n", rc)); + } + else + { + /* Only don't do the check if we have a valid "0" in it */ + if (!strcmp(pszCheckHostVersion, "0")) + { + LogRel(("No host version update check performed (disabled).\n")); + *pfUpdate = false; + } + VbglR3GuestPropReadValueFree(pszCheckHostVersion); + } + + /* Collect all needed information */ + /* Make sure we only notify the user once by comparing the host version with + * the last checked host version (if any) */ + if (RT_SUCCESS(rc) && *pfUpdate) + { + /* Look up host version */ + rc = VbglR3GuestPropReadValueAlloc(idClient, "/VirtualBox/HostInfo/VBoxVer", ppszHostVersion); + if (RT_FAILURE(rc)) + { + LogFlow(("Could not read VBox host version! rc = %Rrc\n", rc)); + } + else + { + LogFlow(("Host version: %s\n", *ppszHostVersion)); + + /* Get last checked host version */ + char *pszLastCheckedHostVersion; + rc = VbglR3HostVersionLastCheckedLoad(idClient, &pszLastCheckedHostVersion); + if (RT_SUCCESS(rc)) + { + LogFlow(("Last checked host version: %s\n", pszLastCheckedHostVersion)); + if (strcmp(*ppszHostVersion, pszLastCheckedHostVersion) == 0) + *pfUpdate = false; /* We already notified this version, skip */ + VbglR3GuestPropReadValueFree(pszLastCheckedHostVersion); + } + else if (rc == VERR_NOT_FOUND) /* Never wrote a last checked host version before */ + { + LogFlow(("Never checked a host version before.\n")); + rc = VINF_SUCCESS; + } + } + + /* Look up guest version */ + if (RT_SUCCESS(rc)) + { + rc = VbglR3GetAdditionsVersion(ppszGuestVersion, NULL /* Extended version not needed here */, + NULL /* Revision not needed here */); + if (RT_FAILURE(rc)) + LogFlow(("Could not read VBox guest version! rc = %Rrc\n", rc)); + } + } + + /* Do the actual version comparison (if needed, see block(s) above) */ + if (RT_SUCCESS(rc) && *pfUpdate) + { + if (RTStrVersionCompare(*ppszHostVersion, *ppszGuestVersion) > 0) /* Is host version greater than guest add version? */ + { + /* Yay, we have an update! */ + LogRel(("Guest Additions update found! Please upgrade this machine to the latest Guest Additions.\n")); + } + else + { + /* How sad ... */ + *pfUpdate = false; + } + } + + /* Cleanup on failure */ + if (RT_FAILURE(rc)) + { + if (*ppszHostVersion) + { + VbglR3GuestPropReadValueFree(*ppszHostVersion); + *ppszHostVersion = NULL; + } + if (*ppszGuestVersion) + { + VbglR3GuestPropReadValueFree(*ppszGuestVersion); + *ppszGuestVersion = NULL; + } + } + return rc; +#else /* !VBOX_WITH_GUEST_PROPS */ + RT_NOREF(idClient, pfUpdate, ppszHostVersion, ppszGuestVersion); + return VERR_NOT_SUPPORTED; +#endif +} + + +/** Retrieves the last checked host version. + * + * @returns VBox status code. + * + * @param idClient The client id returned by VbglR3InfoSvcConnect(). + * @param ppszVer Receives pointer of allocated version string. + * The returned pointer must be freed using RTStrFree() on VINF_SUCCESS. + */ +VBGLR3DECL(int) VbglR3HostVersionLastCheckedLoad(HGCMCLIENTID idClient, char **ppszVer) +{ +#ifdef VBOX_WITH_GUEST_PROPS + Assert(idClient > 0); + AssertPtr(ppszVer); + return VbglR3GuestPropReadValueAlloc(idClient, "/VirtualBox/GuestAdd/HostVerLastChecked", ppszVer); +#else /* !VBOX_WITH_GUEST_PROPS */ + RT_NOREF(idClient, ppszVer); + return VERR_NOT_SUPPORTED; +#endif +} + + +/** Stores the last checked host version for later lookup. + * Requires strings in form of "majorVer.minorVer.build". + * + * @returns VBox status code. + * + * @param idClient The client id returned by VbglR3InfoSvcConnect(). + * @param pszVer Pointer to version string to store. + */ +VBGLR3DECL(int) VbglR3HostVersionLastCheckedStore(HGCMCLIENTID idClient, const char *pszVer) +{ +#ifdef VBOX_WITH_GUEST_PROPS + Assert(idClient > 0); + AssertPtr(pszVer); + return VbglR3GuestPropWriteValue(idClient, "/VirtualBox/GuestAdd/HostVerLastChecked", pszVer); +#else /* !VBOX_WITH_GUEST_PROPS */ + RT_NOREF(idClient, pszVer); + return VERR_NOT_SUPPORTED; +#endif +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibInternal.h b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibInternal.h new file mode 100644 index 00000000..fb97ace3 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibInternal.h @@ -0,0 +1,131 @@ +/* $Id: VBoxGuestR3LibInternal.h $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 support library for the guest additions, Internal header. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR3LibInternal_h +#define GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR3LibInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/VMMDev.h> +#include <VBox/VBoxGuest.h> +#include <VBox/VBoxGuestLib.h> + +#ifdef VBOX_VBGLR3_XFREE86 +/* Rather than try to resolve all the header file conflicts, I will just + prototype what we need here. */ +typedef unsigned long xf86size_t; +extern "C" xf86size_t xf86strlen(const char*); +# undef strlen +# define strlen xf86strlen +#endif /* VBOX_VBGLR3_XFREE86 */ + +RT_C_DECLS_BEGIN + +int vbglR3DoIOCtl(uintptr_t uFunction, PVBGLREQHDR pReq, size_t cbReq); +int vbglR3DoIOCtlRaw(uintptr_t uFunction, PVBGLREQHDR pReq, size_t cbReq); +int vbglR3GRAlloc(VMMDevRequestHeader **ppReq, size_t cb, VMMDevRequestType enmReqType); +int vbglR3GRPerform(VMMDevRequestHeader *pReq); +void vbglR3GRFree(VMMDevRequestHeader *pReq); + + + +DECLINLINE(void) VbglHGCMParmUInt32Set(HGCMFunctionParameter *pParm, uint32_t u32) +{ + pParm->type = VMMDevHGCMParmType_32bit; + pParm->u.value64 = 0; /* init unused bits to 0 */ + pParm->u.value32 = u32; +} + + +DECLINLINE(int) VbglHGCMParmUInt32Get(HGCMFunctionParameter *pParm, uint32_t *pu32) +{ + if (pParm->type == VMMDevHGCMParmType_32bit) + { + *pu32 = pParm->u.value32; + return VINF_SUCCESS; + } + *pu32 = UINT32_MAX; /* shut up gcc */ + return VERR_WRONG_PARAMETER_TYPE; +} + + +DECLINLINE(void) VbglHGCMParmUInt64Set(HGCMFunctionParameter *pParm, uint64_t u64) +{ + pParm->type = VMMDevHGCMParmType_64bit; + pParm->u.value64 = u64; +} + + +DECLINLINE(int) VbglHGCMParmUInt64Get(HGCMFunctionParameter *pParm, uint64_t *pu64) +{ + if (pParm->type == VMMDevHGCMParmType_64bit) + { + *pu64 = pParm->u.value64; + return VINF_SUCCESS; + } + *pu64 = UINT64_MAX; /* shut up gcc */ + return VERR_WRONG_PARAMETER_TYPE; +} + + +DECLINLINE(void) VbglHGCMParmPtrSet(HGCMFunctionParameter *pParm, void *pv, uint32_t cb) +{ + pParm->type = VMMDevHGCMParmType_LinAddr; + pParm->u.Pointer.size = cb; + pParm->u.Pointer.u.linearAddr = (uintptr_t)pv; +} + + +#ifdef IPRT_INCLUDED_string_h + +DECLINLINE(void) VbglHGCMParmPtrSetString(HGCMFunctionParameter *pParm, const char *psz) +{ + pParm->type = VMMDevHGCMParmType_LinAddr_In; + pParm->u.Pointer.size = (uint32_t)strlen(psz) + 1; + pParm->u.Pointer.u.linearAddr = (uintptr_t)psz; +} + +#endif /* IPRT_INCLUDED_string_h */ + +#ifdef VBOX_VBGLR3_XFREE86 +# undef strlen +#endif /* VBOX_VBGLR3_XFREE86 */ + +RT_C_DECLS_END + +#endif /* !GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR3LibInternal_h */ + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibLog.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibLog.cpp new file mode 100644 index 00000000..04a167e0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibLog.cpp @@ -0,0 +1,94 @@ +/* $Id: VBoxGuestR3LibLog.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Logging. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include "VBoxGuestR3LibInternal.h" + + +/** + * Write to the backdoor logger from ring 3 guest code. + * + * @returns IPRT status code. + * + * @param pch The string to log. Does not need to be terminated. + * @param cch The number of chars (bytes) to log. + * + * @remarks This currently does not accept more than 255 bytes of data at + * one time. It should probably be rewritten to use pass a pointer + * in the IOCtl. + */ +VBGLR3DECL(int) VbglR3WriteLog(const char *pch, size_t cch) +{ + /* + * Quietly skip empty strings. + * (Happens in the RTLogBackdoorPrintf case.) + */ + int rc; + if (cch > 0) + { + if (RT_VALID_PTR(pch)) + { + /* + * We need to repackage the string for ring-0. + */ + size_t cbMsg = VBGL_IOCTL_LOG_SIZE(cch); + PVBGLIOCLOG pMsg = (PVBGLIOCLOG)RTMemTmpAlloc(cbMsg); + if (pMsg) + { + VBGLREQHDR_INIT_EX(&pMsg->Hdr, VBGL_IOCTL_LOG_SIZE_IN(cch), VBGL_IOCTL_LOG_SIZE_OUT); + memcpy(pMsg->u.In.szMsg, pch, cch); + pMsg->u.In.szMsg[cch] = '\0'; + rc = vbglR3DoIOCtl(VBGL_IOCTL_LOG(cch), &pMsg->Hdr, cbMsg); + + RTMemTmpFree(pMsg); + } + else + rc = VERR_NO_TMP_MEMORY; + } + else + rc = VERR_INVALID_POINTER; + } + else + rc = VINF_SUCCESS; + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMisc.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMisc.cpp new file mode 100644 index 00000000..81e7b272 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMisc.cpp @@ -0,0 +1,135 @@ +/* $Id: VBoxGuestR3LibMisc.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Misc. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/log.h> +#include "VBoxGuestR3LibInternal.h" + + +/** + * Change the IRQ filter mask. + * + * @returns IPRT status code. + * @param fOr The OR mask. + * @param fNot The NOT mask. + */ +VBGLR3DECL(int) VbglR3CtlFilterMask(uint32_t fOr, uint32_t fNot) +{ + VBGLIOCCHANGEFILTERMASK Info; + VBGLREQHDR_INIT(&Info.Hdr, CHANGE_FILTER_MASK); + Info.u.In.fOrMask = fOr; + Info.u.In.fNotMask = fNot; + return vbglR3DoIOCtl(VBGL_IOCTL_CHANGE_FILTER_MASK, &Info.Hdr, sizeof(Info)); +} + + +/** + * Report a change in the capabilities that we support to the host. + * + * @returns IPRT status code. + * @param fOr Capabilities which have been added. + * @param fNot Capabilities which have been removed. + * + * @todo Move to a different file. + */ +VBGLR3DECL(int) VbglR3SetGuestCaps(uint32_t fOr, uint32_t fNot) +{ + VBGLIOCSETGUESTCAPS Info; + VBGLREQHDR_INIT(&Info.Hdr, CHANGE_GUEST_CAPABILITIES); + Info.u.In.fOrMask = fOr; + Info.u.In.fNotMask = fNot; + return vbglR3DoIOCtl(VBGL_IOCTL_CHANGE_GUEST_CAPABILITIES, &Info.Hdr, sizeof(Info)); +} + + +/** + * Acquire capabilities to report to the host. + * + * The capabilities which can be acquired are the same as those reported by + * VbglR3SetGuestCaps, and once a capability has been acquired once is is + * switched to "acquire mode" and can no longer be set using VbglR3SetGuestCaps. + * Capabilities can also be switched to acquire mode without actually being + * acquired. A client can not acquire a capability which has been acquired and + * not released by another client. Capabilities acquired are automatically + * released on session termination. + * + * @returns IPRT status code + * @returns VERR_RESOURCE_BUSY and acquires nothing if another client has + * acquired and not released at least one of the @a fOr capabilities + * @param fOr Capabilities to acquire or to switch to acquire mode + * @param fNot Capabilities to release + * @param fConfig if set, capabilities in @a fOr are switched to acquire mode + * but not acquired, and @a fNot is ignored. See + * VBGL_IOC_AGC_FLAGS_CONFIG_ACQUIRE_MODE for details. + */ +VBGLR3DECL(int) VbglR3AcquireGuestCaps(uint32_t fOr, uint32_t fNot, bool fConfig) +{ + VBGLIOCACQUIREGUESTCAPS Info; + VBGLREQHDR_INIT(&Info.Hdr, ACQUIRE_GUEST_CAPABILITIES); + Info.u.In.fFlags = fConfig ? VBGL_IOC_AGC_FLAGS_CONFIG_ACQUIRE_MODE : VBGL_IOC_AGC_FLAGS_DEFAULT; + Info.u.In.fOrMask = fOr; + Info.u.In.fNotMask = fNot; + return vbglR3DoIOCtl(VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES, &Info.Hdr, sizeof(Info)); +} + + +/** + * Query the session ID of this VM. + * + * The session id is an unique identifier that gets changed for each VM start, + * reset or restore. Useful for detection a VM restore. + * + * @returns IPRT status code. + * @param pu64IdSession Session id (out). This is NOT changed on + * failure, so the caller can depend on this to + * deal with backward compatibility (see + * VBoxServiceVMInfoWorker() for an example.) + */ +VBGLR3DECL(int) VbglR3GetSessionId(uint64_t *pu64IdSession) +{ + VMMDevReqSessionId Req; + + vmmdevInitRequest(&Req.header, VMMDevReq_GetSessionId); + Req.idSession = 0; + int rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + *pu64IdSession = Req.idSession; + + return rc; +} diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibModule.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibModule.cpp new file mode 100644 index 00000000..371b42d7 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibModule.cpp @@ -0,0 +1,180 @@ +/* $Id: VBoxGuestR3LibModule.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared modules. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR3LibInternal.h" +#include <iprt/mem.h> +#include <iprt/string.h> + +/** + * Registers a new shared module for the VM + * + * @returns IPRT status code. + * @param pszModuleName Module name + * @param pszVersion Module version + * @param GCBaseAddr Module base address + * @param cbModule Module size + * @param cRegions Number of shared region descriptors + * @param pRegions Shared region(s) + */ +VBGLR3DECL(int) VbglR3RegisterSharedModule(char *pszModuleName, char *pszVersion, + RTGCPTR64 GCBaseAddr, uint32_t cbModule, + unsigned cRegions, VMMDEVSHAREDREGIONDESC *pRegions) +{ + VMMDevSharedModuleRegistrationRequest *pReq; + int rc; + + /* Sanity check. */ + AssertReturn(cRegions < VMMDEVSHAREDREGIONDESC_MAX, VERR_INVALID_PARAMETER); + + pReq = (VMMDevSharedModuleRegistrationRequest *)RTMemAllocZ(RT_UOFFSETOF_DYN(VMMDevSharedModuleRegistrationRequest, + aRegions[cRegions])); + AssertReturn(pReq, VERR_NO_MEMORY); + + vmmdevInitRequest(&pReq->header, VMMDevReq_RegisterSharedModule); + pReq->header.size = RT_UOFFSETOF_DYN(VMMDevSharedModuleRegistrationRequest, aRegions[cRegions]); + pReq->GCBaseAddr = GCBaseAddr; + pReq->cbModule = cbModule; + pReq->cRegions = cRegions; +#ifdef RT_OS_WINDOWS +# if ARCH_BITS == 32 + pReq->enmGuestOS = VBOXOSFAMILY_Windows32; +# else + pReq->enmGuestOS = VBOXOSFAMILY_Windows64; +# endif +#else + /** @todo */ + pReq->enmGuestOS = VBOXOSFAMILY_Unknown; +#endif + for (unsigned i = 0; i < cRegions; i++) + pReq->aRegions[i] = pRegions[i]; + + if ( RTStrCopy(pReq->szName, sizeof(pReq->szName), pszModuleName) != VINF_SUCCESS + || RTStrCopy(pReq->szVersion, sizeof(pReq->szVersion), pszVersion) != VINF_SUCCESS) + { + rc = VERR_BUFFER_OVERFLOW; + goto end; + } + + rc = vbglR3GRPerform(&pReq->header); + +end: + RTMemFree(pReq); + return rc; + +} + +/** + * Unregisters a shared module for the VM + * + * @returns IPRT status code. + * @param pszModuleName Module name + * @param pszVersion Module version + * @param GCBaseAddr Module base address + * @param cbModule Module size + */ +VBGLR3DECL(int) VbglR3UnregisterSharedModule(char *pszModuleName, char *pszVersion, RTGCPTR64 GCBaseAddr, uint32_t cbModule) +{ + VMMDevSharedModuleUnregistrationRequest Req; + + vmmdevInitRequest(&Req.header, VMMDevReq_UnregisterSharedModule); + Req.GCBaseAddr = GCBaseAddr; + Req.cbModule = cbModule; + + if ( RTStrCopy(Req.szName, sizeof(Req.szName), pszModuleName) != VINF_SUCCESS + || RTStrCopy(Req.szVersion, sizeof(Req.szVersion), pszVersion) != VINF_SUCCESS) + { + return VERR_BUFFER_OVERFLOW; + } + return vbglR3GRPerform(&Req.header); +} + +/** + * Checks registered modules for shared pages + * + * @returns IPRT status code. + */ +VBGLR3DECL(int) VbglR3CheckSharedModules() +{ + VMMDevSharedModuleCheckRequest Req; + + vmmdevInitRequest(&Req.header, VMMDevReq_CheckSharedModules); + return vbglR3GRPerform(&Req.header); +} + +/** + * Checks if page sharing is enabled. + * + * @returns true/false enabled/disabled + */ +VBGLR3DECL(bool) VbglR3PageSharingIsEnabled() +{ + VMMDevPageSharingStatusRequest Req; + + vmmdevInitRequest(&Req.header, VMMDevReq_GetPageSharingStatus); + int rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + return Req.fEnabled; + return false; +} + +/** + * Checks if page sharing is enabled. + * + * @returns true/false enabled/disabled + */ +VBGLR3DECL(int) VbglR3PageIsShared(RTGCPTR pPage, bool *pfShared, uint64_t *puPageFlags) +{ +#ifdef DEBUG + VMMDevPageIsSharedRequest Req; + + vmmdevInitRequest(&Req.header, VMMDevReq_DebugIsPageShared); + Req.GCPtrPage = pPage; + int rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + { + *pfShared = Req.fShared; + *puPageFlags = Req.uPageFlags; + } + return rc; +#else + RT_NOREF3(pPage, pfShared, puPageFlags); + return VERR_NOT_IMPLEMENTED; +#endif +} diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMouse.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMouse.cpp new file mode 100644 index 00000000..b93872d4 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMouse.cpp @@ -0,0 +1,90 @@ +/* $Id: VBoxGuestR3LibMouse.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Mouse. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR3LibInternal.h" + + +/** + * Retrieve mouse coordinates and features from the host. + * + * @returns VBox status code. + * + * @param pfFeatures Where to store the mouse features. + * @param px Where to store the X co-ordinate. + * @param py Where to store the Y co-ordinate. + */ +VBGLR3DECL(int) VbglR3GetMouseStatus(uint32_t *pfFeatures, uint32_t *px, uint32_t *py) +{ + VMMDevReqMouseStatus Req; + vmmdevInitRequest(&Req.header, VMMDevReq_GetMouseStatus); + Req.mouseFeatures = 0; + Req.pointerXPos = 0; + Req.pointerYPos = 0; + int rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + { + if (pfFeatures) + *pfFeatures = Req.mouseFeatures; + if (px) + *px = Req.pointerXPos; + if (py) + *py = Req.pointerYPos; + } + return rc; +} + + +/** + * Send mouse features to the host. + * + * @returns VBox status code. + * + * @param fFeatures Supported mouse pointer features. The main guest driver + * will mediate different callers and show the host any + * feature enabled by any guest caller. + */ +VBGLR3DECL(int) VbglR3SetMouseStatus(uint32_t fFeatures) +{ + VBGLIOCSETMOUSESTATUS Req; + VBGLREQHDR_INIT(&Req.Hdr, SET_MOUSE_STATUS); + Req.u.In.fStatus = fFeatures; + return vbglR3DoIOCtl(VBGL_IOCTL_SET_MOUSE_STATUS, &Req.Hdr, sizeof(Req)); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibPidFile.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibPidFile.cpp new file mode 100644 index 00000000..ff7c312d --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibPidFile.cpp @@ -0,0 +1,151 @@ +/** $Id: VBoxGuestR3LibPidFile.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, + * Create a PID file. + */ + +/* + * Copyright (C) 2015-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/file.h> +#include <iprt/string.h> +#include <iprt/process.h> +#include <iprt/thread.h> +#include <iprt/err.h> +#include "VBoxGuestR3LibInternal.h" + +/* A time to wait before starting the next attempt to check a pidfile. */ +#define VBGL_PIDFILE_WAIT_RELAX_TIME_MS (250) + +/** + * Creates a PID File and returns the open file descriptor. + * + * On DOS based system, file sharing (deny write) is used for locking the PID + * file. + * + * On Unix-y systems, an exclusive advisory lock is used for locking the PID + * file since the file sharing support is usually missing there. + * + * This API will overwrite any existing PID Files without a lock on them, on the + * assumption that they are stale files which an old process did not properly + * clean up. + * + * @returns IPRT status code. + * @param pszPath The path and filename to create the PID File under + * @param phFile Where to store the file descriptor of the open (and locked + * on Unix-y systems) PID File. On failure, or if another + * process owns the PID File, this will be set to NIL_RTFILE. + */ +VBGLR3DECL(int) VbglR3PidFile(const char *pszPath, PRTFILE phFile) +{ + AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER); + AssertPtrReturn(phFile, VERR_INVALID_PARAMETER); + *phFile = NIL_RTFILE; + + RTFILE hPidFile; + int rc = RTFileOpen(&hPidFile, pszPath, + RTFILE_O_READWRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_WRITE + | (0644 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { +#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) + /** @todo using size 0 for locking means lock all on Posix. + * We should adopt this as our convention too, or something + * similar. */ + rc = RTFileLock(hPidFile, RTFILE_LOCK_WRITE, 0, 0); + if (RT_FAILURE(rc)) + RTFileClose(hPidFile); + else +#endif + { + char szBuf[256]; + size_t cbPid = RTStrPrintf(szBuf, sizeof(szBuf), "%d\n", + RTProcSelf()); + RTFileWrite(hPidFile, szBuf, cbPid, NULL); + *phFile = hPidFile; + } + } + return rc; +} + + +/** + * Close and remove an open PID File. + * + * @param pszPath The path to the PID File, + * @param hFile The handle for the file. NIL_RTFILE is ignored as usual. + */ +VBGLR3DECL(void) VbglR3ClosePidFile(const char *pszPath, RTFILE hFile) +{ + AssertPtrReturnVoid(pszPath); + if (hFile != NIL_RTFILE) + { +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + RTFileWriteAt(hFile, 0, "-1", 2, NULL); +#else + RTFileDelete(pszPath); +#endif + RTFileClose(hFile); + } +} + + +/** + * Wait for other process to release pidfile. + * + * This function is a wrapper to VbglR3PidFile(). + * + * @returns IPRT status code. + * @param szPidfile Path to pidfile. + * @param phPidfile Handle to pidfile. + * @param u64TimeoutMs A timeout value in milliseconds to wait for + * other process to release pidfile. + */ +VBGLR3DECL(int) VbglR3PidfileWait(const char *szPidfile, RTFILE *phPidfile, uint64_t u64TimeoutMs) +{ + int rc = VERR_FILE_LOCK_VIOLATION; + uint64_t u64Start = RTTimeSystemMilliTS(); + + AssertPtrReturn(szPidfile, VERR_INVALID_PARAMETER); + AssertPtrReturn(phPidfile, VERR_INVALID_PARAMETER); + + while ( !RT_SUCCESS((rc = VbglR3PidFile(szPidfile, phPidfile))) + && (RTTimeSystemMilliTS() - u64Start < u64TimeoutMs)) + { + RTThreadSleep(VBGL_PIDFILE_WAIT_RELAX_TIME_MS); + } + + return rc; +} diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibRuntimeXF86.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibRuntimeXF86.cpp new file mode 100644 index 00000000..8a9d15c0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibRuntimeXF86.cpp @@ -0,0 +1,108 @@ +/* $Id: VBoxGuestR3LibRuntimeXF86.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, + * implements the minimum of runtime functions needed for + * XFree86 driver code. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#if defined(VBOX_VBGLR3_XFREE86) +extern "C" { +# define XFree86LOADER +# include <xf86_ansic.h> +# undef size_t +} +#else +# include <stdarg.h> +# include <stdlib.h> +# define xalloc malloc +# define xfree free +extern "C" void ErrorF(const char *f, ...); +#endif + +RTDECL(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction) +{ + ErrorF("Assertion failed! Expression: %s at %s in\n", pszExpr, + pszFunction); + ErrorF("%s:%u\n", pszFile, uLine); +} + +RTDECL(void) RTAssertMsg2Weak(const char *pszFormat, ...) +{ + NOREF(pszFormat); +} + +RTDECL(bool) RTAssertShouldPanic(void) +{ + return false; +} + +RTDECL(PRTLOGGER) RTLogDefaultInstanceEx(uint32_t fFlagsAndGroup) +{ + NOREF(fFlagsAndGroup); + return NULL; +} + +RTDECL(PRTLOGGER) RTLogRelGetDefaultInstance(void) +{ + return NULL; +} + +RTDECL(PRTLOGGER) RTLogRelGetDefaultInstanceEx(uint32_t fFlagsAndGroup) +{ + NOREF(fFlagsAndGroup); + return NULL; +} + +RTDECL(void) RTLogLoggerEx(PRTLOGGER, unsigned, unsigned, const char *pszFormat, ...) +{ + NOREF(pszFormat); +} + +RTDECL(void *) RTMemTmpAllocTag(size_t cb, const char *pszTag) +{ + NOREF(pszTag); + return xalloc(cb); +} + +RTDECL(void) RTMemTmpFree(void *pv) +{ + xfree(pv); +} diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSeamless.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSeamless.cpp new file mode 100644 index 00000000..a13a77b9 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSeamless.cpp @@ -0,0 +1,209 @@ +/* $Id: VBoxGuestR3LibSeamless.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Seamless mode. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/string.h> + +#include <VBox/log.h> + +#include "VBoxGuestR3LibInternal.h" + +#ifdef VBOX_VBGLR3_XFREE86 +/* Rather than try to resolve all the header file conflicts, I will just + prototype what we need here. */ +extern "C" void* xf86memcpy(void*,const void*,xf86size_t); +# undef memcpy +# define memcpy xf86memcpy +#endif /* VBOX_VBGLR3_XFREE86 */ + +/** + * Tell the host that we support (or no longer support) seamless mode. + * + * @returns IPRT status value + * @param fState whether or not we support seamless mode + */ +VBGLR3DECL(int) VbglR3SeamlessSetCap(bool fState) +{ + if (fState) + return VbglR3SetGuestCaps(VMMDEV_GUEST_SUPPORTS_SEAMLESS, 0); + return VbglR3SetGuestCaps(0, VMMDEV_GUEST_SUPPORTS_SEAMLESS); +} + +/** + * Wait for a seamless mode change event. + * + * @returns IPRT status value. + * @param[out] pMode On success, the seamless mode to switch into (i.e. + * disabled, visible region or host window). + */ +VBGLR3DECL(int) VbglR3SeamlessWaitEvent(VMMDevSeamlessMode *pMode) +{ + AssertPtrReturn(pMode, VERR_INVALID_POINTER); + + /** @todo r=andy The (similar / duplicate) Windows code does similar waiting. Merge / fix this. */ + uint32_t fEvent = 0; + int rc = VbglR3WaitEvent(VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST, 1000 /* ms */, &fEvent); + if (RT_SUCCESS(rc)) + { + /* did we get the right event? */ + if (fEvent & VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST) + { + VMMDevSeamlessChangeRequest seamlessChangeRequest; + + /* get the seamless change request */ + vmmdevInitRequest(&seamlessChangeRequest.header, VMMDevReq_GetSeamlessChangeRequest); + seamlessChangeRequest.mode = (VMMDevSeamlessMode)-1; + seamlessChangeRequest.eventAck = VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST; + rc = vbglR3GRPerform(&seamlessChangeRequest.header); + if (RT_SUCCESS(rc)) + { + *pMode = seamlessChangeRequest.mode; + return VINF_SUCCESS; + } + } + else + rc = VERR_TRY_AGAIN; + } + else if ( rc == VERR_INTERRUPTED + || rc == VERR_TIMEOUT /* just in case */) + rc = VERR_TRY_AGAIN; + return rc; +} + +/** + * Request the last seamless mode switch from the host again. + * + * @returns IPRT status value. + * @param[out] pMode On success, the seamless mode that was switched + * into (i.e. disabled, visible region or host window). + */ +VBGLR3DECL(int) VbglR3SeamlessGetLastEvent(VMMDevSeamlessMode *pMode) +{ + VMMDevSeamlessChangeRequest seamlessChangeRequest; + int rc; + + AssertPtrReturn(pMode, VERR_INVALID_PARAMETER); + + /* get the seamless change request */ + vmmdevInitRequest(&seamlessChangeRequest.header, VMMDevReq_GetSeamlessChangeRequest); + seamlessChangeRequest.mode = (VMMDevSeamlessMode)-1; + seamlessChangeRequest.eventAck = VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST; + rc = vbglR3GRPerform(&seamlessChangeRequest.header); + if (RT_SUCCESS(rc)) + { + *pMode = seamlessChangeRequest.mode; + return VINF_SUCCESS; + } + return rc; +} + +/** + * Inform the host about the visible region + * + * @returns IPRT status code + * @param cRects number of rectangles in the list of visible rectangles + * @param pRects list of visible rectangles on the guest display + * + * @todo A scatter-gather version of vbglR3GRPerform would be nice, so that we don't have + * to copy our rectangle and header data into a single structure and perform an + * additional allocation. + * @todo Would that really gain us much, given that the rectangles may not + * be grouped at all, or in the format we need? Keeping the memory + * for our "single structure" around (re-alloc-ing it if necessary) + * sounds like a simpler optimisation if we need it. + */ +VBGLR3DECL(int) VbglR3SeamlessSendRects(uint32_t cRects, PRTRECT pRects) +{ + VMMDevVideoSetVisibleRegion *pReq; + int rc; + + AssertReturn(pRects || cRects == 0, VERR_INVALID_PARAMETER); + AssertMsgReturn(cRects <= _1M, ("%u\n", cRects), VERR_OUT_OF_RANGE); + + rc = vbglR3GRAlloc((VMMDevRequestHeader **)&pReq, + sizeof(VMMDevVideoSetVisibleRegion) + + cRects * sizeof(RTRECT) + - sizeof(RTRECT), + VMMDevReq_VideoSetVisibleRegion); + if (RT_SUCCESS(rc)) + { + pReq->cRect = cRects; + if (cRects) + memcpy(&pReq->Rect, pRects, cRects * sizeof(RTRECT)); + /* This will fail harmlessly for cRect == 0 and older host code */ + rc = vbglR3GRPerform(&pReq->header); + LogFunc(("Visible region request returned %Rrc, internal %Rrc.\n", + rc, pReq->header.rc)); + if (RT_SUCCESS(rc)) + rc = pReq->header.rc; + vbglR3GRFree(&pReq->header); + } + LogFunc(("Sending %u rectangles to the host: %Rrc\n", cRects, rc)); + return rc; +} + +VBGLR3DECL(int) VbglR3SeamlessSendMonitorPositions(uint32_t cPositions, PRTPOINT pPositions) +{ + if (!pPositions || cPositions <= 0) + return VERR_INVALID_PARAMETER; + + VMMDevVideoUpdateMonitorPositions *pReq; + int rc; + + rc = vbglR3GRAlloc((VMMDevRequestHeader **)&pReq, + sizeof(VMMDevVideoUpdateMonitorPositions) + + (cPositions - 1) * sizeof(RTPOINT), + VMMDevReq_VideoUpdateMonitorPositions); + if (RT_SUCCESS(rc)) + { + pReq->cPositions = cPositions; + if (cPositions) + memcpy(&pReq->aPositions, pPositions, cPositions * sizeof(RTPOINT)); + rc = vbglR3GRPerform(&pReq->header); + LogFunc(("Monitor position update request returned %Rrc, internal %Rrc.\n", + rc, pReq->header.rc)); + if (RT_SUCCESS(rc)) + rc = pReq->header.rc; + vbglR3GRFree(&pReq->header); + } + LogFunc(("Sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc)); + return rc; +} diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSharedFolders.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSharedFolders.cpp new file mode 100644 index 00000000..47137fd2 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSharedFolders.cpp @@ -0,0 +1,432 @@ +/* $Id: VBoxGuestR3LibSharedFolders.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, shared folders. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/string.h> +#include <iprt/mem.h> +#include <iprt/assert.h> +#include <iprt/cpp/autores.h> +#include <iprt/stdarg.h> +#include <VBox/log.h> +#include <VBox/shflsvc.h> /** @todo File should be moved to VBox/HostServices/SharedFolderSvc.h */ + +#include "VBoxGuestR3LibInternal.h" + + +/** + * Connects to the shared folder service. + * + * @returns VBox status code + * @param pidClient Where to put the client id on success. The client id + * must be passed to all the other calls to the service. + */ +VBGLR3DECL(int) VbglR3SharedFolderConnect(HGCMCLIENTID *pidClient) +{ + return VbglR3HGCMConnect("VBoxSharedFolders", pidClient); +} + + +/** + * Disconnect from the shared folder service. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3InfoSvcConnect(). + */ +VBGLR3DECL(int) VbglR3SharedFolderDisconnect(HGCMCLIENTID idClient) +{ + return VbglR3HGCMDisconnect(idClient); +} + + +/** + * Checks whether a shared folder share exists or not. + * + * @returns True if shared folder exists, false if not. + * @param idClient The client id returned by VbglR3InfoSvcConnect(). + * @param pszShareName Shared folder name to check. + */ +VBGLR3DECL(bool) VbglR3SharedFolderExists(HGCMCLIENTID idClient, const char *pszShareName) +{ + AssertPtr(pszShareName); + + uint32_t cMappings; + VBGLR3SHAREDFOLDERMAPPING *paMappings; + + /** @todo Use some caching here? */ + bool fFound = false; + int rc = VbglR3SharedFolderGetMappings(idClient, true /* Only process auto-mounted folders */, &paMappings, &cMappings); + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < cMappings && !fFound; i++) + { + char *pszName = NULL; + rc = VbglR3SharedFolderGetName(idClient, paMappings[i].u32Root, &pszName); + if ( RT_SUCCESS(rc) + && *pszName) + { + if (RTStrICmp(pszName, pszShareName) == 0) + fFound = true; + RTStrFree(pszName); + } + } + VbglR3SharedFolderFreeMappings(paMappings); + } + return fFound; +} + + +/** + * Get the list of available shared folders. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3SharedFolderConnect(). + * @param fAutoMountOnly Flag whether only auto-mounted shared folders + * should be reported. + * @param ppaMappings Allocated array which will retrieve the mapping info. Needs + * to be freed with VbglR3SharedFolderFreeMappings() later. + * @param pcMappings The number of mappings returned in @a ppaMappings. + */ +VBGLR3DECL(int) VbglR3SharedFolderGetMappings(HGCMCLIENTID idClient, bool fAutoMountOnly, + PVBGLR3SHAREDFOLDERMAPPING *ppaMappings, uint32_t *pcMappings) +{ + AssertPtrReturn(pcMappings, VERR_INVALID_PARAMETER); + AssertPtrReturn(ppaMappings, VERR_INVALID_PARAMETER); + + *pcMappings = 0; + *ppaMappings = NULL; + + VBoxSFQueryMappings Msg; + VBGL_HGCM_HDR_INIT(&Msg.callInfo, idClient, SHFL_FN_QUERY_MAPPINGS, 3); + + /* Set the mapping flags. */ + uint32_t u32Flags = 0; /** @todo SHFL_MF_UTF8 is not implemented yet. */ + if (fAutoMountOnly) /* We only want the mappings which get auto-mounted. */ + u32Flags |= SHFL_MF_AUTOMOUNT; + VbglHGCMParmUInt32Set(&Msg.flags, u32Flags); + + /* + * Prepare and get the actual mappings from the host service. + */ + int rc = VINF_SUCCESS; + uint32_t cMappings = 8; /* Should be a good default value. */ + uint32_t cbSize = cMappings * sizeof(VBGLR3SHAREDFOLDERMAPPING); + VBGLR3SHAREDFOLDERMAPPING *ppaMappingsTemp = (PVBGLR3SHAREDFOLDERMAPPING)RTMemAllocZ(cbSize); + if (!ppaMappingsTemp) + return VERR_NO_MEMORY; + + do + { + VbglHGCMParmUInt32Set(&Msg.numberOfMappings, cMappings); + VbglHGCMParmPtrSet(&Msg.mappings, ppaMappingsTemp, cbSize); + + rc = VbglR3HGCMCall(&Msg.callInfo, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + VbglHGCMParmUInt32Get(&Msg.numberOfMappings, pcMappings); + + /* Do we have more mappings than we have allocated space for? */ + if (rc == VINF_BUFFER_OVERFLOW) + { + cMappings = *pcMappings; + cbSize = cMappings * sizeof(VBGLR3SHAREDFOLDERMAPPING); + void *pvNew = RTMemRealloc(ppaMappingsTemp, cbSize); + AssertPtrBreakStmt(pvNew, rc = VERR_NO_MEMORY); + ppaMappingsTemp = (PVBGLR3SHAREDFOLDERMAPPING)pvNew; + } + } + } while (rc == VINF_BUFFER_OVERFLOW); /** @todo r=bird: This won't happen because the weird host code never returns it. */ + + if ( RT_FAILURE(rc) + || !*pcMappings) + { + RTMemFree(ppaMappingsTemp); + ppaMappingsTemp = NULL; + } + + /* In this case, just return success with 0 mappings */ + if ( rc == VERR_INVALID_PARAMETER + && fAutoMountOnly) + rc = VINF_SUCCESS; + + *ppaMappings = ppaMappingsTemp; + + return rc; +} + + +/** + * Frees the shared folder mappings allocated by + * VbglR3SharedFolderGetMappings() before. + * + * @param paMappings What + */ +VBGLR3DECL(void) VbglR3SharedFolderFreeMappings(PVBGLR3SHAREDFOLDERMAPPING paMappings) +{ + if (paMappings) + RTMemFree(paMappings); +} + + +/** + * Get the real name of a shared folder. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3InvsSvcConnect(). + * @param u32Root Root ID of shared folder to get the name for. + * @param ppszName Where to return the name string. This shall be + * freed by calling RTStrFree. + */ +VBGLR3DECL(int) VbglR3SharedFolderGetName(HGCMCLIENTID idClient, uint32_t u32Root, char **ppszName) +{ + AssertPtr(ppszName); + + VBoxSFQueryMapName Msg; + VBGL_HGCM_HDR_INIT(&Msg.callInfo, idClient, SHFL_FN_QUERY_MAP_NAME, 2); + + int rc; + uint32_t cbString = SHFLSTRING_HEADER_SIZE + SHFL_MAX_LEN * sizeof(RTUTF16); + PSHFLSTRING pString = (PSHFLSTRING)RTMemAlloc(cbString); + if (pString) + { + if (!ShflStringInitBuffer(pString, cbString)) + { + RTMemFree(pString); + return VERR_INVALID_PARAMETER; + } + + VbglHGCMParmUInt32Set(&Msg.root, u32Root); + VbglHGCMParmPtrSet(&Msg.name, pString, cbString); + + rc = VbglR3HGCMCall(&Msg.callInfo, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + *ppszName = NULL; + rc = RTUtf16ToUtf8(&pString->String.ucs2[0], ppszName); + } + RTMemFree(pString); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Queries information about a shared folder. + * + * @returns VBox status code. + * + * @param idClient The client ID. + * @param idRoot The root ID of the folder to query information for. + * @param fQueryFlags SHFL_MIQF_XXX. + * @param ppszName Where to return the pointer to the name. + * Free using RTStrFree. Optional. + * @param ppszMountPoint Where to return the pointer to the auto mount point. + * Free using RTStrFree. Optional. + * @param pfFlags Where to return the flags (SHFL_MIF_XXX). Optional. + * @param puRootIdVersion where to return the root ID version. Optional. + * This helps detecting root-id reuse. + * + * @remarks ASSUMES UTF-16 connection to host. + */ +VBGLR3DECL(int) VbglR3SharedFolderQueryFolderInfo(HGCMCLIENTID idClient, uint32_t idRoot, uint64_t fQueryFlags, + char **ppszName, char **ppszMountPoint, + uint64_t *pfFlags, uint32_t *puRootIdVersion) +{ + AssertReturn(!(fQueryFlags & ~(SHFL_MIQF_DRIVE_LETTER | SHFL_MIQF_PATH)), VERR_INVALID_FLAGS); + + /* + * Allocate string buffers first. + */ + int rc; + PSHFLSTRING pNameBuf = (PSHFLSTRING)RTMemAlloc(SHFLSTRING_HEADER_SIZE + (SHFL_MAX_LEN + 1) * sizeof(RTUTF16)); + PSHFLSTRING pMountPoint = (PSHFLSTRING)RTMemAlloc(SHFLSTRING_HEADER_SIZE + (260 + 1) * sizeof(RTUTF16)); + if (pNameBuf && pMountPoint) + { + ShflStringInitBuffer(pNameBuf, SHFLSTRING_HEADER_SIZE + (SHFL_MAX_LEN + 1) * sizeof(RTUTF16)); + ShflStringInitBuffer(pMountPoint, SHFLSTRING_HEADER_SIZE + (260 + 1) * sizeof(RTUTF16)); + + /* + * Make the call. + */ + VBoxSFQueryMapInfo Msg; + VBGL_HGCM_HDR_INIT(&Msg.callInfo, idClient, SHFL_FN_QUERY_MAP_INFO, 5); + VbglHGCMParmUInt32Set(&Msg.root, idRoot); + VbglHGCMParmPtrSet(&Msg.name, pNameBuf, SHFLSTRING_HEADER_SIZE + pNameBuf->u16Size); + VbglHGCMParmPtrSet(&Msg.mountPoint, pMountPoint, SHFLSTRING_HEADER_SIZE + pMountPoint->u16Size); + VbglHGCMParmUInt64Set(&Msg.flags, fQueryFlags); + VbglHGCMParmUInt32Set(&Msg.rootIdVersion, 0); + + rc = VbglR3HGCMCall(&Msg.callInfo, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /* + * Copy out the results. + */ + if (puRootIdVersion) + *puRootIdVersion = Msg.rootIdVersion.u.value64; + + if (pfFlags) + *pfFlags = Msg.flags.u.value64; + + if (ppszName) + { + *ppszName = NULL; + rc = RTUtf16ToUtf8Ex(pNameBuf->String.utf16, pNameBuf->u16Length / sizeof(RTUTF16), ppszName, 0, NULL); + } + + if (ppszMountPoint && RT_SUCCESS(rc)) + { + *ppszMountPoint = NULL; + rc = RTUtf16ToUtf8Ex(pMountPoint->String.utf16, pMountPoint->u16Length / sizeof(RTUTF16), ppszMountPoint, 0, NULL); + if (RT_FAILURE(rc) && ppszName) + { + RTStrFree(*ppszName); + *ppszName = NULL; + } + } + } + } + else + rc = VERR_NO_MEMORY; + RTMemFree(pMountPoint); + RTMemFree(pNameBuf); + return rc; +} + + +/** + * Waits for changes to the mappings (add, remove, restore). + * + * @returns VBox status code. + * @retval VINF_SUCCESS on change + * @retval VINF_TRY_AGAIN on restore. + * @retval VERR_OUT_OF_RESOURCES if there are too many guys waiting. + * + * @param idClient The client ID. + * @param uPrevVersion The mappings config version number returned the last + * time around. Use UINT32_MAX for the first call. + * @param puCurVersion Where to return the current mappings config version. + */ +VBGLR3DECL(int) VbglR3SharedFolderWaitForMappingsChanges(HGCMCLIENTID idClient, uint32_t uPrevVersion, uint32_t *puCurVersion) +{ + VBoxSFWaitForMappingsChanges Msg; + VBGL_HGCM_HDR_INIT(&Msg.callInfo, idClient, SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES, 1); + VbglHGCMParmUInt32Set(&Msg.version, uPrevVersion); + + int rc = VbglR3HGCMCall(&Msg.callInfo, sizeof(Msg)); + + *puCurVersion = Msg.version.u.value32; + return rc; +} + + +/** + * Cancels all threads currently waiting for changes for this client. + * + * @returns VBox status code. + * @param idClient The client ID. + */ +VBGLR3DECL(int) VbglR3SharedFolderCancelMappingsChangesWaits(HGCMCLIENTID idClient) +{ + VBGLIOCHGCMCALL CallInfo; + VBGL_HGCM_HDR_INIT(&CallInfo, idClient, SHFL_FN_CANCEL_MAPPINGS_CHANGES_WAITS, 0); + + return VbglR3HGCMCall(&CallInfo, sizeof(CallInfo)); +} + + +/** + * Retrieves the prefix for a shared folder mount point. If no prefix + * is set in the guest properties "sf_" is returned. + * + * @returns VBox status code. + * @param ppszPrefix Where to return the prefix string. This shall be + * freed by calling RTStrFree. + */ +VBGLR3DECL(int) VbglR3SharedFolderGetMountPrefix(char **ppszPrefix) +{ + AssertPtrReturn(ppszPrefix, VERR_INVALID_POINTER); + int rc; +#ifdef VBOX_WITH_GUEST_PROPS + HGCMCLIENTID idClientGuestProp; + rc = VbglR3GuestPropConnect(&idClientGuestProp); + if (RT_SUCCESS(rc)) + { + rc = VbglR3GuestPropReadValueAlloc(idClientGuestProp, "/VirtualBox/GuestAdd/SharedFolders/MountPrefix", ppszPrefix); + if (rc == VERR_NOT_FOUND) /* No prefix set? Then set the default. */ + { +#endif +/** @todo r=bird: Inconsistent! VbglR3SharedFolderGetMountDir does not return a default. */ + rc = RTStrDupEx(ppszPrefix, "sf_"); +#ifdef VBOX_WITH_GUEST_PROPS + } + VbglR3GuestPropDisconnect(idClientGuestProp); + } +#endif + return rc; +} + + +/** + * Retrieves the mount root directory for auto-mounted shared + * folders. mount point. If no string is set (VERR_NOT_FOUND) + * it's up on the caller (guest) to decide where to mount. + * + * @returns VBox status code. + * @param ppszDir Where to return the directory + * string. This shall be freed by + * calling RTStrFree. + */ +VBGLR3DECL(int) VbglR3SharedFolderGetMountDir(char **ppszDir) +{ + AssertPtrReturn(ppszDir, VERR_INVALID_POINTER); + int rc = VERR_NOT_FOUND; +#ifdef VBOX_WITH_GUEST_PROPS + HGCMCLIENTID idClientGuestProp; + rc = VbglR3GuestPropConnect(&idClientGuestProp); + if (RT_SUCCESS(rc)) + { + rc = VbglR3GuestPropReadValueAlloc(idClientGuestProp, "/VirtualBox/GuestAdd/SharedFolders/MountDir", ppszDir); + VbglR3GuestPropDisconnect(idClientGuestProp); + } +#endif + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibStat.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibStat.cpp new file mode 100644 index 00000000..30bc4631 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibStat.cpp @@ -0,0 +1,79 @@ +/* $Id: VBoxGuestR3LibStat.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Statistics. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR3LibInternal.h" + + +/** + * Query the current statistics update interval. + * + * @returns IPRT status code. + * @param pcMsInterval Update interval in ms (out). + */ +VBGLR3DECL(int) VbglR3StatQueryInterval(PRTMSINTERVAL pcMsInterval) +{ + VMMDevGetStatisticsChangeRequest Req; + + vmmdevInitRequest(&Req.header, VMMDevReq_GetStatisticsChangeRequest); + Req.eventAck = VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST; + Req.u32StatInterval = 1; + int rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + { + *pcMsInterval = Req.u32StatInterval * 1000; + if (*pcMsInterval / 1000 != Req.u32StatInterval) + *pcMsInterval = ~(RTMSINTERVAL)0; + } + return rc; +} + + +/** + * Report guest statistics. + * + * @returns IPRT status code. + * @param pReq Request packet with statistics. + */ +VBGLR3DECL(int) VbglR3StatReport(VMMDevReportGuestStats *pReq) +{ + vmmdevInitRequest(&pReq->header, VMMDevReq_ReportGuestStats); + return vbglR3GRPerform(&pReq->header); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibTime.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibTime.cpp new file mode 100644 index 00000000..603e83f7 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibTime.cpp @@ -0,0 +1,55 @@ +/* $Id: VBoxGuestR3LibTime.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Time. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/time.h> +#include "VBoxGuestR3LibInternal.h" + + +VBGLR3DECL(int) VbglR3GetHostTime(PRTTIMESPEC pTime) +{ + VMMDevReqHostTime Req; + vmmdevInitRequest(&Req.header, VMMDevReq_GetHostTime); + Req.time = UINT64_MAX; + int rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + RTTimeSpecSetMilli(pTime, (int64_t)Req.time); + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVideo.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVideo.cpp new file mode 100644 index 00000000..38a79c58 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVideo.cpp @@ -0,0 +1,618 @@ +/* $Id: VBoxGuestR3LibVideo.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Video. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR3LibInternal.h" + +#include <VBox/log.h> +#include <VBox/HostServices/GuestPropertySvc.h> /* For Save and RetrieveVideoMode */ +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#ifdef VBOX_VBGLR3_XFREE86 +/* Rather than try to resolve all the header file conflicts, I will just + prototype what we need here. */ +extern "C" void* xf86memcpy(void*,const void*,xf86size_t); +# undef memcpy +# define memcpy xf86memcpy +extern "C" void* xf86memset(const void*,int,xf86size_t); +# undef memset +# define memset xf86memset +#endif /* VBOX_VBGLR3_XFREE86 */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VIDEO_PROP_PREFIX "/VirtualBox/GuestAdd/Vbgl/Video/" + + +/** + * Enable or disable video acceleration. + * + * @returns VBox status code. + * + * @param fEnable Pass zero to disable, any other value to enable. + */ +VBGLR3DECL(int) VbglR3VideoAccelEnable(bool fEnable) +{ + VMMDevVideoAccelEnable Req; + vmmdevInitRequest(&Req.header, VMMDevReq_VideoAccelEnable); + Req.u32Enable = fEnable; + Req.cbRingBuffer = VMMDEV_VBVA_RING_BUFFER_SIZE; + Req.fu32Status = 0; + return vbglR3GRPerform(&Req.header); +} + + +/** + * Flush the video buffer. + * + * @returns VBox status code. + */ +VBGLR3DECL(int) VbglR3VideoAccelFlush(void) +{ + VMMDevVideoAccelFlush Req; + vmmdevInitRequest(&Req.header, VMMDevReq_VideoAccelFlush); + return vbglR3GRPerform(&Req.header); +} + + +/** + * Send mouse pointer shape information to the host. + * + * @returns VBox status code. + * + * @param fFlags Mouse pointer flags. + * @param xHot X coordinate of hot spot. + * @param yHot Y coordinate of hot spot. + * @param cx Pointer width. + * @param cy Pointer height. + * @param pvImg Pointer to the image data (can be NULL). + * @param cbImg Size of the image data pointed to by pvImg. + */ +VBGLR3DECL(int) VbglR3SetPointerShape(uint32_t fFlags, uint32_t xHot, uint32_t yHot, uint32_t cx, uint32_t cy, + const void *pvImg, size_t cbImg) +{ + VMMDevReqMousePointer *pReq; + size_t cbReq = vmmdevGetMousePointerReqSize(cx, cy); + AssertReturn( !pvImg + || cbReq == RT_UOFFSETOF(VMMDevReqMousePointer, pointerData) + cbImg, + VERR_INVALID_PARAMETER); + int rc = vbglR3GRAlloc((VMMDevRequestHeader **)&pReq, cbReq, VMMDevReq_SetPointerShape); + if (RT_SUCCESS(rc)) + { + pReq->fFlags = fFlags; + pReq->xHot = xHot; + pReq->yHot = yHot; + pReq->width = cx; + pReq->height = cy; + if (pvImg) + memcpy(pReq->pointerData, pvImg, cbImg); + + rc = vbglR3GRPerform(&pReq->header); + if (RT_SUCCESS(rc)) + rc = pReq->header.rc; + vbglR3GRFree(&pReq->header); + } + return rc; +} + + +/** + * Send mouse pointer shape information to the host. + * This version of the function accepts a request for clients that + * already allocate and manipulate the request structure directly. + * + * @returns VBox status code. + * + * @param pReq Pointer to the VMMDevReqMousePointer structure. + */ +VBGLR3DECL(int) VbglR3SetPointerShapeReq(VMMDevReqMousePointer *pReq) +{ + int rc = vbglR3GRPerform(&pReq->header); + if (RT_SUCCESS(rc)) + rc = pReq->header.rc; + return rc; +} + + +/** + * Query the last display change request sent from the host to the guest. + * + * @returns iprt status value + * @param pcx Where to store the horizontal pixel resolution + * @param pcy Where to store the vertical pixel resolution + * requested (a value of zero means do not change). + * @param pcBits Where to store the bits per pixel requested (a value + * of zero means do not change). + * @param piDisplay Where to store the display number the request was for + * - 0 for the primary display, 1 for the first + * secondary display, etc. + * @param fAck whether or not to acknowledge the newest request sent by + * the host. If this is set, the function will return the + * most recent host request, otherwise it will return the + * last request to be acknowledged. + * + */ +static int getDisplayChangeRequest2(uint32_t *pcx, uint32_t *pcy, + uint32_t *pcBits, uint32_t *piDisplay, + bool fAck) +{ + VMMDevDisplayChangeRequest2 Req; + + AssertPtrReturn(pcx, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcy, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcBits, VERR_INVALID_PARAMETER); + AssertPtrReturn(piDisplay, VERR_INVALID_PARAMETER); + RT_ZERO(Req); + vmmdevInitRequest(&Req.header, VMMDevReq_GetDisplayChangeRequest2); + if (fAck) + Req.eventAck = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST; + int rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + rc = Req.header.rc; + if (RT_SUCCESS(rc)) + { + *pcx = Req.xres; + *pcy = Req.yres; + *pcBits = Req.bpp; + *piDisplay = Req.display; + } + return rc; +} + + +/** + * Query the last display change request sent from the host to the guest. + * + * @returns iprt status value + * @param pcx Where to store the horizontal pixel resolution + * requested (a value of zero means do not change). + * @param pcy Where to store the vertical pixel resolution + * requested (a value of zero means do not change). + * @param pcBits Where to store the bits per pixel requested (a value + * of zero means do not change). + * @param piDisplay Where to store the display number the request was for + * - 0 for the primary display, 1 for the first + * secondary display, etc. + * @param fAck whether or not to acknowledge the newest request sent by + * the host. If this is set, the function will return the + * most recent host request, otherwise it will return the + * last request to be acknowledged. + * + * @param pdx New horizontal position of the secondary monitor. + * Optional. + * @param pdy New vertical position of the secondary monitor. + * Optional. + * @param pfEnabled Secondary monitor is enabled or not. Optional. + * @param pfChangeOrigin Whether the mode hint retrieved included + * information about origin/display offset inside the + * frame-buffer. Optional. + * + */ +VBGLR3DECL(int) VbglR3GetDisplayChangeRequest(uint32_t *pcx, uint32_t *pcy, + uint32_t *pcBits, + uint32_t *piDisplay, + uint32_t *pdx, uint32_t *pdy, + bool *pfEnabled, + bool *pfChangeOrigin, + bool fAck) +{ + VMMDevDisplayChangeRequestEx Req; + int rc = VINF_SUCCESS; + + AssertPtrReturn(pcx, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcy, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcBits, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pdx, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pdy, VERR_INVALID_PARAMETER); + AssertPtrReturn(piDisplay, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pfEnabled, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pfChangeOrigin, VERR_INVALID_PARAMETER); + + RT_ZERO(Req); + rc = vmmdevInitRequest(&Req.header, VMMDevReq_GetDisplayChangeRequestEx); + AssertRCReturn(rc, rc); + if (fAck) + Req.eventAck = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST; + rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + rc = Req.header.rc; + if (RT_SUCCESS(rc)) + { + *pcx = Req.xres; + *pcy = Req.yres; + *pcBits = Req.bpp; + *piDisplay = Req.display; + if (pdx) + *pdx = Req.cxOrigin; + if (pdy) + *pdy = Req.cyOrigin; + if (pfEnabled) + *pfEnabled = Req.fEnabled; + if (pfChangeOrigin) + *pfChangeOrigin = Req.fChangeOrigin; + return VINF_SUCCESS; + } + + /* NEEDS TESTING: test below with current Additions on VBox 4.1 or older. */ + /** @todo Can we find some standard grep-able string for "NEEDS TESTING"? */ + if (rc == VERR_NOT_IMPLEMENTED) /* Fall back to the old API. */ + { + if (pfEnabled) + *pfEnabled = true; + if (pfChangeOrigin) + *pfChangeOrigin = false; + return getDisplayChangeRequest2(pcx, pcy, pcBits, piDisplay, fAck); + } + return rc; +} + + +/** + * Query the last display change request sent from the host to the guest. + * + * @returns iprt status value + * @param cDisplaysIn How many elements in the paDisplays array. + * @param pcDisplaysOut How many elements were returned. + * @param paDisplays Display information. + * @param fAck Whether or not to acknowledge the newest request sent by + * the host. If this is set, the function will return the + * most recent host request, otherwise it will return the + * last request to be acknowledged. + */ +VBGLR3DECL(int) VbglR3GetDisplayChangeRequestMulti(uint32_t cDisplaysIn, + uint32_t *pcDisplaysOut, + VMMDevDisplayDef *paDisplays, + bool fAck) +{ + VMMDevDisplayChangeRequestMulti *pReq; + size_t cbDisplays; + size_t cbAlloc; + int rc = VINF_SUCCESS; + + AssertReturn(cDisplaysIn > 0 && cDisplaysIn <= 64 /* VBOX_VIDEO_MAX_SCREENS */, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcDisplaysOut, VERR_INVALID_PARAMETER); + AssertPtrReturn(paDisplays, VERR_INVALID_PARAMETER); + + cbDisplays = cDisplaysIn * sizeof(VMMDevDisplayDef); + cbAlloc = RT_UOFFSETOF(VMMDevDisplayChangeRequestMulti, aDisplays) + cbDisplays; + pReq = (VMMDevDisplayChangeRequestMulti *)RTMemTmpAlloc(cbAlloc); + AssertPtrReturn(pReq, VERR_NO_MEMORY); + + memset(pReq, 0, cbAlloc); + rc = vmmdevInitRequest(&pReq->header, VMMDevReq_GetDisplayChangeRequestMulti); + AssertRCReturnStmt(rc, RTMemTmpFree(pReq), rc); + + pReq->header.size += (uint32_t)cbDisplays; + pReq->cDisplays = cDisplaysIn; + if (fAck) + pReq->eventAck = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST; + + rc = vbglR3GRPerform(&pReq->header); + AssertRCReturnStmt(rc, RTMemTmpFree(pReq), rc); + + rc = pReq->header.rc; + if (RT_SUCCESS(rc)) + { + memcpy(paDisplays, pReq->aDisplays, pReq->cDisplays * sizeof(VMMDevDisplayDef)); + *pcDisplaysOut = pReq->cDisplays; + } + + RTMemTmpFree(pReq); + return rc; +} + + +/** + * Query the host as to whether it likes a specific video mode. + * + * @returns the result of the query + * @param cx the width of the mode being queried + * @param cy the height of the mode being queried + * @param cBits the bpp of the mode being queried + */ +VBGLR3DECL(bool) VbglR3HostLikesVideoMode(uint32_t cx, uint32_t cy, uint32_t cBits) +{ + bool fRc = true; /* If for some reason we can't contact the host then + * we like everything. */ + int rc; + VMMDevVideoModeSupportedRequest req; + + vmmdevInitRequest(&req.header, VMMDevReq_VideoModeSupported); + req.width = cx; + req.height = cy; + req.bpp = cBits; + req.fSupported = true; + rc = vbglR3GRPerform(&req.header); + if (RT_SUCCESS(rc) && RT_SUCCESS(req.header.rc)) + fRc = req.fSupported; + return fRc; +} + +/** + * Get the highest screen number for which there is a saved video mode or "0" + * if there are no saved modes. + * + * @returns iprt status value + * @returns VERR_NOT_SUPPORTED if the guest property service is not available. + * @param pcScreen where to store the virtual screen number + */ +VBGLR3DECL(int) VbglR3VideoModeGetHighestSavedScreen(unsigned *pcScreen) +{ +#if defined(VBOX_WITH_GUEST_PROPS) + int rc; + HGCMCLIENTID idClient = 0; + PVBGLR3GUESTPROPENUM pHandle = NULL; + const char *pszName = NULL; + unsigned cHighestScreen = 0; + + /* Validate input. */ + AssertPtrReturn(pcScreen, VERR_INVALID_POINTER); + + /* Query the data. */ + rc = VbglR3GuestPropConnect(&idClient); + if (RT_SUCCESS(rc)) + { + const char *pszPattern = VIDEO_PROP_PREFIX"*"; + rc = VbglR3GuestPropEnum(idClient, &pszPattern, 1, &pHandle, &pszName, NULL, NULL, NULL); + int rc2 = VbglR3GuestPropDisconnect(idClient); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + } + + /* Process the data. */ + while (RT_SUCCESS(rc) && pszName != NULL) + { + uint32_t cScreen; + + rc = RTStrToUInt32Full(pszName + sizeof(VIDEO_PROP_PREFIX) - 1, 10, &cScreen); + if (RT_SUCCESS(rc)) /* There may be similar properties with text. */ + cHighestScreen = RT_MAX(cHighestScreen, cScreen); + rc = VbglR3GuestPropEnumNext(pHandle, &pszName, NULL, NULL, NULL); + } + + VbglR3GuestPropEnumFree(pHandle); + + /* Return result. */ + if (RT_SUCCESS(rc)) + *pcScreen = cHighestScreen; + return rc; +#else /* !VBOX_WITH_GUEST_PROPS */ + RT_NOREF(pcScreen); + return VERR_NOT_SUPPORTED; +#endif /* !VBOX_WITH_GUEST_PROPS */ +} + +/** + * Save video mode parameters to the guest property store. + * + * @returns iprt status value + * @param idScreen The virtual screen number. + * @param cx mode width + * @param cy mode height + * @param cBits bits per pixel for the mode + * @param x virtual screen X offset + * @param y virtual screen Y offset + * @param fEnabled is this virtual screen enabled? + */ +VBGLR3DECL(int) VbglR3SaveVideoMode(unsigned idScreen, unsigned cx, unsigned cy, unsigned cBits, + unsigned x, unsigned y, bool fEnabled) +{ +#ifdef VBOX_WITH_GUEST_PROPS + unsigned cHighestScreen = 0; + int rc = VbglR3VideoModeGetHighestSavedScreen(&cHighestScreen); + if (RT_SUCCESS(rc)) + { + HGCMCLIENTID idClient = 0; + rc = VbglR3GuestPropConnect(&idClient); + if (RT_SUCCESS(rc)) + { + int rc2; + char szModeName[GUEST_PROP_MAX_NAME_LEN]; + char szModeParms[GUEST_PROP_MAX_VALUE_LEN]; + RTStrPrintf(szModeName, sizeof(szModeName), VIDEO_PROP_PREFIX "%u", idScreen); + RTStrPrintf(szModeParms, sizeof(szModeParms), "%ux%ux%u,%ux%u,%u", cx, cy, cBits, x, y, (unsigned) fEnabled); + + rc = VbglR3GuestPropWriteValue(idClient, szModeName, szModeParms); + /* Write out the mode using the legacy name too, in case the user + * re-installs older Additions. */ + if (idScreen == 0) + { + RTStrPrintf(szModeParms, sizeof(szModeParms), "%ux%ux%u", cx, cy, cBits); + VbglR3GuestPropWriteValue(idClient, VIDEO_PROP_PREFIX "SavedMode", szModeParms); + } + + rc2 = VbglR3GuestPropDisconnect(idClient); + if (rc != VINF_PERMISSION_DENIED) + { + if (RT_SUCCESS(rc)) + rc = rc2; + if (RT_SUCCESS(rc)) + { + /* Sanity check 1. We do not try to make allowance for someone else + * changing saved settings at the same time as us. */ + bool fEnabled2 = false; + unsigned cx2 = 0; + unsigned cy2 = 0; + unsigned cBits2 = 0; + unsigned x2 = 0; + unsigned y2 = 0; + rc = VbglR3RetrieveVideoMode(idScreen, &cx2, &cy2, &cBits2, &x2, &y2, &fEnabled2); + if ( RT_SUCCESS(rc) + && (cx != cx2 || cy != cy2 || cBits != cBits2 || x != x2 || y != y2 || fEnabled != fEnabled2)) + rc = VERR_WRITE_ERROR; + /* Sanity check 2. Same comment. */ + else if (RT_SUCCESS(rc)) + { + unsigned cHighestScreen2 = 0; + rc = VbglR3VideoModeGetHighestSavedScreen(&cHighestScreen2); + if (RT_SUCCESS(rc)) + if (cHighestScreen2 != RT_MAX(cHighestScreen, idScreen)) + rc = VERR_INTERNAL_ERROR; + } + } + } + } + } + return rc; +#else /* !VBOX_WITH_GUEST_PROPS */ + RT_NOREF(idScreen, cx, cy, cBits, x, y, fEnabled); + return VERR_NOT_SUPPORTED; +#endif /* !VBOX_WITH_GUEST_PROPS */ +} + + +/** + * Retrieve video mode parameters from the guest property store. + * + * @returns iprt status value + * @param idScreen The virtual screen number. + * @param pcx where to store the mode width + * @param pcy where to store the mode height + * @param pcBits where to store the bits per pixel for the mode + * @param px where to store the virtual screen X offset + * @param py where to store the virtual screen Y offset + * @param pfEnabled where to store whether this virtual screen is enabled + */ +VBGLR3DECL(int) VbglR3RetrieveVideoMode(unsigned idScreen, + unsigned *pcx, unsigned *pcy, + unsigned *pcBits, + unsigned *px, unsigned *py, + bool *pfEnabled) +{ +#ifdef VBOX_WITH_GUEST_PROPS + /* + * First we retrieve the video mode which is saved as a string in the + * guest property store. + */ + HGCMCLIENTID idClient = 0; + int rc = VbglR3GuestPropConnect(&idClient); + if (RT_SUCCESS(rc)) + { + int rc2; + /* The buffer for VbglR3GuestPropReadValue. If this is too small then + * something is wrong with the data stored in the property. */ + char szModeParms[1024]; + char szModeName[GUEST_PROP_MAX_NAME_LEN]; /** @todo add a VbglR3GuestPropReadValueF/FV that does the RTStrPrintf for you. */ + RTStrPrintf(szModeName, sizeof(szModeName), VIDEO_PROP_PREFIX "%u", idScreen); + rc = VbglR3GuestPropReadValue(idClient, szModeName, szModeParms, sizeof(szModeParms), NULL); + /* Try legacy single screen name. */ + if (rc == VERR_NOT_FOUND && idScreen == 0) + rc = VbglR3GuestPropReadValue(idClient, + VIDEO_PROP_PREFIX"SavedMode", + szModeParms, sizeof(szModeParms), + NULL); + rc2 = VbglR3GuestPropDisconnect(idClient); + if (RT_SUCCESS(rc)) + rc = rc2; + + /* + * Now we convert the string returned to numeric values. + */ + if (RT_SUCCESS(rc)) + { + /* Mandatory chunk: 640x480x32 */ + char *pszNext; + uint32_t cx = 0; + rc = VERR_PARSE_ERROR; + rc2 = RTStrToUInt32Ex(szModeParms, &pszNext, 10, &cx); + if (rc2 == VWRN_TRAILING_CHARS && *pszNext == 'x') + { + uint32_t cy = 0; + rc2 = RTStrToUInt32Ex(pszNext + 1, &pszNext, 10, &cy); + if (rc2 == VWRN_TRAILING_CHARS && *pszNext == 'x') + { + uint8_t cBits = 0; + rc2 = RTStrToUInt8Ex(pszNext + 1, &pszNext, 10, &cBits); + if (rc2 == VINF_SUCCESS || rc2 == VWRN_TRAILING_CHARS) + { + /* Optional chunk: ,32x64,1 (we fail if this is partially there) */ + uint32_t x = 0; + uint32_t y = 0; + uint8_t fEnabled = 1; + if (rc2 == VINF_SUCCESS) + rc = VINF_SUCCESS; + else if (*pszNext == ',') + { + rc2 = RTStrToUInt32Ex(pszNext + 1, &pszNext, 10, &x); + if (rc2 == VWRN_TRAILING_CHARS && *pszNext == 'x') + { + rc2 = RTStrToUInt32Ex(pszNext + 1, &pszNext, 10, &y); + if (rc2 == VWRN_TRAILING_CHARS && *pszNext == ',') + { + rc2 = RTStrToUInt8Ex(pszNext + 1, &pszNext, 10, &fEnabled); + if (rc2 == VINF_SUCCESS) + rc = VINF_SUCCESS; + } + } + } + + /* + * Set result if successful. + */ + if (rc == VINF_SUCCESS) + { + if (pcx) + *pcx = cx; + if (pcy) + *pcy = cy; + if (pcBits) + *pcBits = cBits; + if (px) + *px = x; + if (py) + *py = y; + if (pfEnabled) + *pfEnabled = RT_BOOL(fEnabled); + } + } + } + } + } + } + + return rc; +#else /* !VBOX_WITH_GUEST_PROPS */ + RT_NOREF(idScreen, pcx, pcy, pcBits, px, py, pfEnabled); + return VERR_NOT_SUPPORTED; +#endif /* !VBOX_WITH_GUEST_PROPS */ +} diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVrdp.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVrdp.cpp new file mode 100644 index 00000000..89a19d09 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVrdp.cpp @@ -0,0 +1,64 @@ +/* $Id: VBoxGuestR3LibVrdp.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, VRDP. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/time.h> +#include <iprt/string.h> +#include "VBoxGuestR3LibInternal.h" + + +VBGLR3DECL(int) VbglR3VrdpGetChangeRequest(bool *pfActive, uint32_t *puExperienceLevel) +{ + VMMDevVRDPChangeRequest Req; + RT_ZERO(Req); /* implicit padding */ + vmmdevInitRequest(&Req.header, VMMDevReq_GetVRDPChangeRequest); //VMMDEV_REQ_HDR_INIT(&Req.header, sizeof(Req), VMMDevReq_GetVRDPChangeRequest); + int rc = vbglR3GRPerform(&Req.header); + if (RT_SUCCESS(rc)) + { + *pfActive = Req.u8VRDPActive != 0; + *puExperienceLevel = Req.u32VRDPExperienceLevel; + } + else + { + *pfActive = false; + *puExperienceLevel = 0; + } + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VbglR0CanUsePhysPageList.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VbglR0CanUsePhysPageList.cpp new file mode 100644 index 00000000..38646784 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VbglR0CanUsePhysPageList.cpp @@ -0,0 +1,52 @@ +/* $Id: VbglR0CanUsePhysPageList.cpp $ */ +/** @file + * VBoxGuestLibR0 - Physical memory heap. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxGuestR0LibInternal.h" + + +/** + * Checks whether the host supports physical page lists or not. + * + * @returns true if it does, false if it doesn't. + */ +DECLR0VBGL(bool) VbglR0CanUsePhysPageList(void) +{ + /* a_fLocked is false, because the actual capability of the host is requested. + * See VBGLR0_CAN_USE_PHYS_PAGE_LIST definition. + */ + int rc = vbglR0Enter(); + return RT_SUCCESS(rc) + && VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ false); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/testcase/Makefile.kmk b/src/VBox/Additions/common/VBoxGuest/lib/testcase/Makefile.kmk new file mode 100644 index 00000000..6d99a40f --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/testcase/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the common guest addition code library testcases. +# + +# +# Copyright (C) 2022-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../../../.. +include $(KBUILD_PATH)/subheader.kmk +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_BUILD) + + # + # Testcase for the physical heap. + # + PROGRAMS += tstVbglR0PhysHeap-1 + tstVbglR0PhysHeap-1_TEMPLATE = VBoxR3TstExe + tstVbglR0PhysHeap-1_SOURCES = \ + tstVbglR0PhysHeap-1.cpp + + +endif # defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_BUILD) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/VBoxGuest/lib/testcase/tstVbglR0PhysHeap-1.cpp b/src/VBox/Additions/common/VBoxGuest/lib/testcase/tstVbglR0PhysHeap-1.cpp new file mode 100644 index 00000000..3646fd95 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/testcase/tstVbglR0PhysHeap-1.cpp @@ -0,0 +1,415 @@ +/* $Id: tstVbglR0PhysHeap-1.cpp $ */ +/** @file + * IPRT Testcase - Offset Based Heap. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/rand.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/param.h> +#include <iprt/test.h> +#include <iprt/time.h> + +#define IN_TESTCASE +#define IN_RING0 /* pretend we're in ring-0 so we get access to the functions */ +#include "../VBoxGuestR0LibInternal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct +{ + uint32_t cb; + void *pv; +} TSTHISTORYENTRY; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +VBGLDATA g_vbgldata; + +int g_cChunks = 0; +size_t g_cbChunks = 0; + +/** Drop-in replacement for RTMemContAlloc */ +static void *tstMemContAlloc(PRTCCPHYS pPhys, size_t cb) +{ + RTTESTI_CHECK(cb > 0); + +#define TST_MAX_CHUNKS 24 + if (g_cChunks < TST_MAX_CHUNKS) + { + void *pvRet = RTMemAlloc(cb); + if (pvRet) + { + g_cChunks++; + g_cbChunks += cb; + *pPhys = (uint32_t)(uintptr_t)pvRet ^ (UINT32_C(0xf0f0f0f0) & ~(uint32_t)PAGE_OFFSET_MASK); + + /* Avoid problematic values that won't happen in real life: */ + if (!*pPhys) + *pPhys = 4U << PAGE_SHIFT; + if (UINT32_MAX - *pPhys < cb) + *pPhys -= RT_ALIGN_32(cb, PAGE_SIZE); + + return pvRet; + } + } + + *pPhys = NIL_RTCCPHYS; + return NULL; +} + + +/** Drop-in replacement for RTMemContFree */ +static void tstMemContFree(void *pv, size_t cb) +{ + RTTESTI_CHECK(RT_VALID_PTR(pv)); + RTTESTI_CHECK(cb > 0); + RTTESTI_CHECK(g_cChunks > 0); + RTMemFree(pv); + g_cChunks--; + g_cbChunks -= cb; +} + + +#define RTMemContAlloc tstMemContAlloc +#define RTMemContFree tstMemContFree +#include "../VBoxGuestR0LibPhysHeap.cpp" + + +static void PrintStats(TSTHISTORYENTRY const *paHistory, size_t cHistory, const char *pszDesc) +{ + size_t cbAllocated = 0; + unsigned cLargeBlocks = 0; + unsigned cAllocated = 0; + for (size_t i = 0; i < cHistory; i++) + if (paHistory[i].pv) + { + cAllocated += 1; + cbAllocated += paHistory[i].cb; + cLargeBlocks += paHistory[i].cb > _1K; + } + + size_t const cbOverhead = g_cChunks * sizeof(VBGLPHYSHEAPCHUNK) + cAllocated * sizeof(VBGLPHYSHEAPBLOCK); + size_t const cbFragmentation = g_cbChunks - cbOverhead - cbAllocated; + RTTestIPrintf(RTTESTLVL_ALWAYS, + "%s: %'9zu bytes in %2d chunks; %'9zu bytes in %4u blocks (%2u large)\n" + " => int-frag %'9zu (%2zu.%1zu%%) overhead %'9zu (%1zu.%02zu%%)\n", + pszDesc, + g_cbChunks, g_cChunks, + cbAllocated, cAllocated, cLargeBlocks, + cbFragmentation, cbFragmentation * 100 / g_cbChunks, (cbFragmentation * 1000 / g_cbChunks) % 10, + cbOverhead, cbOverhead * 100 / g_cbChunks, (cbOverhead * 10000 / g_cbChunks) % 100); +} + + +int main(int argc, char **argv) +{ + RT_NOREF_PV(argc); RT_NOREF_PV(argv); + + /* + * Init runtime. + */ + RTTEST hTest; + int rc = RTTestInitAndCreate("tstVbglR0PhysHeap-1", &hTest); + if (rc) + return rc; + RTTestBanner(hTest); + + /* + * Arguments are taken to be random seeding. + */ + uint64_t uRandSeed = RTTimeNanoTS(); + for (int i = 1; i < argc; i++) + { + rc = RTStrToUInt64Full(argv[i], 0, &uRandSeed); + if (rc != VINF_SUCCESS) + { + RTTestIFailed("Invalid parameter: %Rrc: %s\n", rc, argv[i]); + return RTTestSummaryAndDestroy(hTest); + } + } + + /* + * Create a heap. + */ + RTTestSub(hTest, "Basics"); + RTTESTI_CHECK_RC(rc = VbglR0PhysHeapInit(), VINF_SUCCESS); + if (RT_FAILURE(rc)) + return RTTestSummaryAndDestroy(hTest); + RTTESTI_CHECK_RC_OK(VbglR0PhysHeapCheck(NULL)); + +#define CHECK_PHYS_ADDR(a_pv) do { \ + uint32_t const uPhys = VbglR0PhysHeapGetPhysAddr(a_pv); \ + if (uPhys == 0 || uPhys == UINT32_MAX || (uPhys & PAGE_OFFSET_MASK) != ((uintptr_t)(a_pv) & PAGE_OFFSET_MASK)) \ + RTTestIFailed("line %u: %s=%p: uPhys=%#x\n", __LINE__, #a_pv, (a_pv), uPhys); \ + } while (0) + + /* + * Try allocate. + */ + static struct TstPhysHeapOps + { + uint32_t cb; + unsigned iFreeOrder; + void *pvAlloc; + } s_aOps[] = + { + { 16, 0, NULL }, // 0 + { 16, 1, NULL }, + { 16, 2, NULL }, + { 16, 5, NULL }, + { 16, 4, NULL }, + { 32, 3, NULL }, // 5 + { 31, 6, NULL }, + { 1024, 8, NULL }, + { 1024, 10, NULL }, + { 1024, 12, NULL }, + { PAGE_SIZE, 13, NULL }, // 10 + { 1024, 9, NULL }, + { PAGE_SIZE, 11, NULL }, + { PAGE_SIZE, 14, NULL }, + { 16, 15, NULL }, + { 9, 7, NULL }, // 15 + { 16, 7, NULL }, + { 36, 7, NULL }, + { 16, 7, NULL }, + { 12344, 7, NULL }, + { 50, 7, NULL }, // 20 + { 16, 7, NULL }, + }; + uint32_t i; + //RTHeapOffsetDump(Heap, (PFNRTHEAPOFFSETPRINTF)(uintptr_t)RTPrintf); /** @todo Add some detail info output with a signature identical to RTPrintf. */ + //size_t cbBefore = VbglR0PhysHeapGetFreeSize(); + static char const s_szFill[] = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + /* allocate */ + for (i = 0; i < RT_ELEMENTS(s_aOps); i++) + { + s_aOps[i].pvAlloc = VbglR0PhysHeapAlloc(s_aOps[i].cb); + RTTESTI_CHECK_MSG(s_aOps[i].pvAlloc, ("VbglR0PhysHeapAlloc(%#x) -> NULL i=%d\n", s_aOps[i].cb, i)); + if (!s_aOps[i].pvAlloc) + return RTTestSummaryAndDestroy(hTest); + + memset(s_aOps[i].pvAlloc, s_szFill[i], s_aOps[i].cb); + RTTESTI_CHECK_MSG(RT_ALIGN_P(s_aOps[i].pvAlloc, sizeof(void *)) == s_aOps[i].pvAlloc, + ("VbglR0PhysHeapAlloc(%#x) -> %p\n", s_aOps[i].cb, i)); + + CHECK_PHYS_ADDR(s_aOps[i].pvAlloc); + + /* Check heap integrity: */ + RTTESTI_CHECK_RC_OK(VbglR0PhysHeapCheck(NULL)); + } + + /* free and allocate the same node again. */ + for (i = 0; i < RT_ELEMENTS(s_aOps); i++) + { + if (!s_aOps[i].pvAlloc) + continue; + //RTPrintf("debug: i=%d pv=%#x cb=%#zx align=%#zx cbReal=%#zx\n", i, s_aOps[i].pvAlloc, + // s_aOps[i].cb, s_aOps[i].uAlignment, RTHeapOffsetSize(Heap, s_aOps[i].pvAlloc)); + size_t cbBeforeSub = VbglR0PhysHeapGetFreeSize(); + VbglR0PhysHeapFree(s_aOps[i].pvAlloc); + size_t cbAfterSubFree = VbglR0PhysHeapGetFreeSize(); + RTTESTI_CHECK_RC_OK(VbglR0PhysHeapCheck(NULL)); + + void *pv; + pv = VbglR0PhysHeapAlloc(s_aOps[i].cb); + RTTESTI_CHECK_MSG(pv, ("VbglR0PhysHeapAlloc(%#x) -> NULL i=%d\n", s_aOps[i].cb, i)); + if (!pv) + return RTTestSummaryAndDestroy(hTest); + CHECK_PHYS_ADDR(pv); + RTTESTI_CHECK_RC_OK(VbglR0PhysHeapCheck(NULL)); + + //RTPrintf("debug: i=%d pv=%p cbReal=%#zx cbBeforeSub=%#zx cbAfterSubFree=%#zx cbAfterSubAlloc=%#zx \n", i, pv, RTHeapOffsetSize(Heap, pv), + // cbBeforeSub, cbAfterSubFree, VbglR0PhysHeapGetFreeSize()); + + if (pv != s_aOps[i].pvAlloc) + RTTestIPrintf(RTTESTLVL_ALWAYS, "Warning: Free+Alloc returned different address. new=%p old=%p i=%d\n", pv, s_aOps[i].pvAlloc, i); + s_aOps[i].pvAlloc = pv; + size_t cbAfterSubAlloc = VbglR0PhysHeapGetFreeSize(); + if (cbBeforeSub != cbAfterSubAlloc) + { + RTTestIPrintf(RTTESTLVL_ALWAYS, "Warning: cbBeforeSub=%#zx cbAfterSubFree=%#zx cbAfterSubAlloc=%#zx. i=%d\n", + cbBeforeSub, cbAfterSubFree, cbAfterSubAlloc, i); + //return 1; - won't work correctly until we start creating free block instead of donating memory on alignment. + } + } + + VbglR0PhysHeapTerminate(); + RTTESTI_CHECK_MSG(g_cChunks == 0, ("g_cChunks=%d\n", g_cChunks)); + + + /* + * Use random allocation pattern + */ + RTTestSub(hTest, "Random Test"); + RTTESTI_CHECK_RC(rc = VbglR0PhysHeapInit(), VINF_SUCCESS); + if (RT_FAILURE(rc)) + return RTTestSummaryAndDestroy(hTest); + + RTRAND hRand; + RTTESTI_CHECK_RC(rc = RTRandAdvCreateParkMiller(&hRand), VINF_SUCCESS); + if (RT_FAILURE(rc)) + return RTTestSummaryAndDestroy(hTest); + RTRandAdvSeed(hRand, uRandSeed); + RTTestValue(hTest, "RandSeed", uRandSeed, RTTESTUNIT_NONE); + + static TSTHISTORYENTRY s_aHistory[3072]; + RT_ZERO(s_aHistory); + + for (unsigned iTest = 0; iTest < 131072; iTest++) + { + i = RTRandAdvU32Ex(hRand, 0, RT_ELEMENTS(s_aHistory) - 1); + if (!s_aHistory[i].pv) + { + s_aHistory[i].cb = RTRandAdvU32Ex(hRand, 8, 1024); + s_aHistory[i].pv = VbglR0PhysHeapAlloc(s_aHistory[i].cb); + if (!s_aHistory[i].pv) + { + s_aHistory[i].cb = 9; + s_aHistory[i].pv = VbglR0PhysHeapAlloc(s_aHistory[i].cb); + } + if (s_aHistory[i].pv) + { + memset(s_aHistory[i].pv, 0xbb, s_aHistory[i].cb); + CHECK_PHYS_ADDR(s_aHistory[i].pv); + } + } + else + { + VbglR0PhysHeapFree(s_aHistory[i].pv); + s_aHistory[i].pv = NULL; + } + +#if 1 + /* Check heap integrity: */ + RTTESTI_CHECK_RC_OK(VbglR0PhysHeapCheck(NULL)); + int cChunks = 0; + for (VBGLPHYSHEAPCHUNK *pCurChunk = g_vbgldata.pChunkHead; pCurChunk; pCurChunk = pCurChunk->pNext) + cChunks++; + RTTESTI_CHECK_MSG(cChunks == g_cChunks, ("g_cChunks=%u, but only %u chunks in the list!\n", g_cChunks, cChunks)); +#endif + + if ((iTest % 7777) == 7776) + { + /* exhaust the heap */ + PrintStats(s_aHistory, RT_ELEMENTS(s_aHistory), "Exhaust-pre "); + + for (i = 0; i < RT_ELEMENTS(s_aHistory) && (VbglR0PhysHeapGetFreeSize() >= 256 || g_cChunks < TST_MAX_CHUNKS); i++) + if (!s_aHistory[i].pv) + { + s_aHistory[i].cb = RTRandAdvU32Ex(hRand, VBGL_PH_CHUNKSIZE / 8, VBGL_PH_CHUNKSIZE / 2 + VBGL_PH_CHUNKSIZE / 4); + s_aHistory[i].pv = VbglR0PhysHeapAlloc(s_aHistory[i].cb); + if (s_aHistory[i].pv) + { + memset(s_aHistory[i].pv, 0x55, s_aHistory[i].cb); + CHECK_PHYS_ADDR(s_aHistory[i].pv); + } + } + + size_t cbFree = VbglR0PhysHeapGetFreeSize(); + if (cbFree) + for (i = 0; i < RT_ELEMENTS(s_aHistory); i++) + if (!s_aHistory[i].pv) + { + s_aHistory[i].cb = RTRandAdvU32Ex(hRand, 1, (uint32_t)cbFree); + s_aHistory[i].pv = VbglR0PhysHeapAlloc(s_aHistory[i].cb); + while (s_aHistory[i].pv == NULL && s_aHistory[i].cb > 2) + { + s_aHistory[i].cb >>= 1; + s_aHistory[i].pv = VbglR0PhysHeapAlloc(s_aHistory[i].cb); + } + if (s_aHistory[i].pv) + { + memset(s_aHistory[i].pv, 0x55, s_aHistory[i].cb); + CHECK_PHYS_ADDR(s_aHistory[i].pv); + } + + cbFree = VbglR0PhysHeapGetFreeSize(); + if (!cbFree) + break; + } + + RTTESTI_CHECK_MSG(VbglR0PhysHeapGetFreeSize() == 0, ("%zu\n", VbglR0PhysHeapGetFreeSize())); + PrintStats(s_aHistory, RT_ELEMENTS(s_aHistory), "Exhaust-post"); + } + else if ((iTest % 7777) == 1111) + { + /* free all */ + RTTestIPrintf(RTTESTLVL_ALWAYS, "Free-all-pre: cFreeBlocks=%u cAllocedBlocks=%u in %u chunk(s)\n", + g_vbgldata.cFreeBlocks, g_vbgldata.cBlocks - g_vbgldata.cFreeBlocks, g_cChunks); + for (i = 0; i < RT_ELEMENTS(s_aHistory); i++) + { + VbglR0PhysHeapFree(s_aHistory[i].pv); + s_aHistory[i].pv = NULL; + } + RTTestIPrintf(RTTESTLVL_ALWAYS, "Free-all-post: cFreeBlocks=%u in %u chunk(s)\n", g_vbgldata.cFreeBlocks, g_cChunks); + RTTESTI_CHECK_MSG(g_cChunks == 1, ("g_cChunks=%d\n", g_cChunks)); + RTTESTI_CHECK_MSG(g_vbgldata.cFreeBlocks == g_vbgldata.cBlocks, + ("g_vbgldata.cFreeBlocks=%d cBlocks=%d\n", g_vbgldata.cFreeBlocks, g_vbgldata.cBlocks)); + + //size_t cbAfterRand = VbglR0PhysHeapGetFreeSize(); + //RTTESTI_CHECK_MSG(cbAfterRand == cbAfter, ("cbAfterRand=%zu cbAfter=%zu\n", cbAfterRand, cbAfter)); + } + } + + /* free the rest. */ + for (i = 0; i < RT_ELEMENTS(s_aHistory); i++) + { + VbglR0PhysHeapFree(s_aHistory[i].pv); + s_aHistory[i].pv = NULL; + } + + RTTESTI_CHECK_MSG(g_cChunks == 1, ("g_cChunks=%d\n", g_cChunks)); + + VbglR0PhysHeapTerminate(); + RTTESTI_CHECK_MSG(g_cChunks == 0, ("g_cChunks=%d\n", g_cChunks)); + + RTTESTI_CHECK_RC(rc = RTRandAdvDestroy(hRand), VINF_SUCCESS); + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/Additions/common/VBoxGuest/linux/Makefile b/src/VBox/Additions/common/VBoxGuest/linux/Makefile new file mode 100644 index 00000000..65384f7f --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/linux/Makefile @@ -0,0 +1,213 @@ +# $Id: Makefile $ +## @file +# VirtualBox Guest Additions Module Makefile. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# Linux kbuild sets this to our source directory if we are called from there +obj ?= $(CURDIR) +include $(obj)/Makefile-header.gmk +VBOXGUEST_DIR = $(VBOX_MODULE_SRC_DIR) + +#VBOX_WITHOUT_COMBINED_SOURCES=1 + +VBOXMOD_NAME = vboxguest +VBOXMOD_OBJS = \ + VBoxGuest-linux.o \ + VBoxGuest-common.o +ifndef VBOX_WITHOUT_COMBINED_SOURCES +VBOXMOD_OBJS += \ + common/string/strformatrt.o \ + combined-agnostic.o \ + combined-os-specific.o +else # VBOX_WITHOUT_COMBINED_SOURCES +VBOXMOD_OBJS += \ + VBoxGuestR0LibGenericRequest.o \ + VBoxGuestR0LibHGCMInternal.o \ + VBoxGuestR0LibInit.o \ + VBoxGuestR0LibPhysHeap.o \ + VBoxGuestR0LibVMMDev.o \ + r0drv/alloc-r0drv.o \ + r0drv/initterm-r0drv.o \ + r0drv/memobj-r0drv.o \ + r0drv/mpnotification-r0drv.o \ + r0drv/powernotification-r0drv.o \ + r0drv/linux/alloc-r0drv-linux.o \ + r0drv/linux/assert-r0drv-linux.o \ + r0drv/linux/initterm-r0drv-linux.o \ + r0drv/linux/memobj-r0drv-linux.o \ + r0drv/linux/memuserkernel-r0drv-linux.o \ + r0drv/linux/mp-r0drv-linux.o \ + r0drv/linux/mpnotification-r0drv-linux.o \ + r0drv/linux/process-r0drv-linux.o \ + r0drv/linux/semevent-r0drv-linux.o \ + r0drv/linux/semeventmulti-r0drv-linux.o \ + r0drv/linux/semfastmutex-r0drv-linux.o \ + r0drv/linux/semmutex-r0drv-linux.o \ + r0drv/linux/spinlock-r0drv-linux.o \ + r0drv/linux/thread-r0drv-linux.o \ + r0drv/linux/thread2-r0drv-linux.o \ + r0drv/linux/time-r0drv-linux.o \ + r0drv/linux/timer-r0drv-linux.o \ + r0drv/linux/RTLogWriteDebugger-r0drv-linux.o \ + r0drv/generic/semspinmutex-r0drv-generic.o \ + common/alloc/alloc.o \ + common/checksum/crc32.o \ + common/err/RTErrConvertFromErrno.o \ + common/err/RTErrConvertToErrno.o \ + common/err/errinfo.o \ + common/log/log.o \ + common/log/logellipsis.o \ + common/log/logrel.o \ + common/log/logrelellipsis.o \ + common/log/logcom.o \ + common/log/logformat.o \ + common/log/RTLogCreateEx.o \ + common/misc/RTAssertMsg1Weak.o \ + common/misc/RTAssertMsg2.o \ + common/misc/RTAssertMsg2Add.o \ + common/misc/RTAssertMsg2AddWeak.o \ + common/misc/RTAssertMsg2AddWeakV.o \ + common/misc/RTAssertMsg2Weak.o \ + common/misc/RTAssertMsg2WeakV.o \ + common/misc/assert.o \ + common/misc/thread.o \ + common/string/RTStrCat.o \ + common/string/RTStrCmp.o \ + common/string/RTStrCopy.o \ + common/string/RTStrCopyEx.o \ + common/string/RTStrCopyP.o \ + common/string/RTStrEnd.o \ + common/string/RTStrICmpAscii.o \ + common/string/RTStrNICmpAscii.o \ + common/string/RTStrNCmp.o \ + common/string/RTStrNLen.o \ + common/string/stringalloc.o \ + common/string/strformat.o \ + common/string/RTStrFormat.o \ + common/string/strformatnum.o \ + common/string/strformatrt.o \ + common/string/strformattype.o \ + common/string/strprintf.o \ + common/string/strprintf-ellipsis.o \ + common/string/strprintf2.o \ + common/string/strprintf2-ellipsis.o \ + common/string/strtonum.o \ + common/string/utf-8.o \ + common/table/avlpv.o \ + common/time/time.o \ + generic/RTAssertShouldPanic-generic.o \ + generic/RTLogWriteStdErr-stub-generic.o \ + generic/RTLogWriteStdOut-stub-generic.o \ + generic/RTMpGetCoreCount-generic.o \ + generic/RTSemEventWait-2-ex-generic.o \ + generic/RTSemEventWaitNoResume-2-ex-generic.o \ + generic/RTSemEventMultiWait-2-ex-generic.o \ + generic/RTSemEventMultiWaitNoResume-2-ex-generic.o \ + generic/rtStrFormatKernelAddress-generic.o \ + generic/errvars-generic.o \ + generic/mppresent-generic.o \ + VBox/log-vbox.o \ + VBox/logbackdoor.o \ + VBox/RTLogWriteVmm-amd64-x86.o + ifeq ($(VBOX_KBUILD_TARGET_ARCH),amd64) +VBOXMOD_OBJS += common/alloc/heapsimple.o + endif +endif # VBOX_WITHOUT_COMBINED_SOURCES +ifeq ($(VBOX_KBUILD_TARGET_ARCH),x86) +VBOXMOD_OBJS += \ + common/math/gcc/divdi3.o \ + common/math/gcc/divmoddi4.o \ + common/math/gcc/moddi3.o \ + common/math/gcc/udivdi3.o \ + common/math/gcc/udivmoddi4.o \ + common/math/gcc/umoddi3.o \ + common/math/gcc/qdivrem.o +endif + +VBOXMOD_DEFS = \ + VBOX \ + RT_OS_LINUX \ + IN_RING0 \ + IN_RT_R0 \ + IN_GUEST \ + IN_GUEST_R0 \ + IN_MODULE \ + RT_WITH_VBOX \ + VBGL_VBOXGUEST \ + VBOX_WITH_HGCM +ifeq ($(VBOX_KBUILD_TARGET_ARCH),amd64) +VBOXMOD_DEFS += VBOX_WITH_64_BITS_GUESTS +endif +ifeq ($(KERN_VERSION),24) +VBOXMOD_DEFS += EXPORT_SYMTAB +endif + +VBOXMOD_INCL = \ + $(VBOXGUEST_DIR) \ + $(VBOXGUEST_DIR)include \ + $(VBOXGUEST_DIR)r0drv/linux + +VBOXMOD_CFLAGS := $(call VBOX_GCC_CHECK_CC,-Wno-declaration-after-statement,-Wno-declaration-after-statement,,) +VBOXMOD_CFLAGS += $(call VBOX_GCC_CHECK_CC,-fno-pie,-fno-pie,,) +ifneq ($(KERN_VERSION),24) +VBOXMOD_CFLAGS += -include $(VBOXGUEST_DIR)include/VBox/VBoxGuestMangling.h +endif + +VBOXMOD_CLEAN = \ + . \ + linux \ + r0drv \ + generic \ + r0drv/linux \ + r0drv/generic \ + VBox \ + common/alloc \ + common/err \ + common/log \ + common/math/gcc \ + common/misc \ + common/string \ + common/table \ + common/time + +include $(obj)/Makefile-footer.gmk + +check: $(VBOXMOD_NAME) + @if ! readelf -p __ksymtab_strings vboxguest.ko | grep -E "\[.*\] *(RT|g_..*RT.*)"; then \ + echo "All exported IPRT symbols are properly renamed!"; \ + else \ + echo "error: Some exported IPRT symbols was not properly renamed! See above." >&2; \ + false; \ + fi + diff --git a/src/VBox/Additions/common/VBoxGuest/linux/Makefile.kup b/src/VBox/Additions/common/VBoxGuest/linux/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/linux/Makefile.kup diff --git a/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c b/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c new file mode 100644 index 00000000..8509f97f --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c @@ -0,0 +1,189 @@ +/* $Id: combined-agnostic.c $ */ +/** @file + * VBoxGuest - Combine a bunch of OS agnostic sources into one compile unit. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define LOG_GROUP LOG_GROUP_DEFAULT +#include "internal/iprt.h" +#include <VBox/log.h> + +//#undef LOG_GROUP +#include "VBoxGuestR0LibGenericRequest.c" +#undef LOG_GROUP +#include "VBoxGuestR0LibHGCMInternal.c" +//#undef LOG_GROUP +#include "VBoxGuestR0LibInit.c" +//#undef LOG_GROUP +#include "VBoxGuestR0LibPhysHeap.c" +#undef LOG_GROUP +#include "VBoxGuestR0LibVMMDev.c" +#undef LOG_GROUP +#include "r0drv/alloc-r0drv.c" +#undef LOG_GROUP +#include "r0drv/initterm-r0drv.c" +#undef LOG_GROUP +#include "r0drv/memobj-r0drv.c" +#undef LOG_GROUP +#include "r0drv/mpnotification-r0drv.c" +#undef LOG_GROUP +#include "r0drv/powernotification-r0drv.c" +#undef LOG_GROUP +#include "r0drv/generic/semspinmutex-r0drv-generic.c" +#undef LOG_GROUP +#include "common/alloc/alloc.c" +#undef LOG_GROUP +#include "common/checksum/crc32.c" +#undef LOG_GROUP +#include "common/err/errinfo.c" +#undef LOG_GROUP +#include "common/log/log.c" +#undef LOG_GROUP +#include "common/log/logellipsis.c" +#undef LOG_GROUP +#include "common/log/logrel.c" +#undef LOG_GROUP +#include "common/log/logrelellipsis.c" +#undef LOG_GROUP +#include "common/log/logcom.c" +#undef LOG_GROUP +#include "common/log/logformat.c" +#undef LOG_GROUP +#include "common/log/RTLogCreateEx.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg1Weak.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2Add.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2AddWeak.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2AddWeakV.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2Weak.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2WeakV.c" +#undef LOG_GROUP +#include "common/misc/assert.c" +#undef LOG_GROUP +#include "common/misc/thread.c" +#undef LOG_GROUP +#include "common/string/RTStrCat.c" +#undef LOG_GROUP +#include "common/string/RTStrCmp.c" +#undef LOG_GROUP +#include "common/string/RTStrCopy.c" +#undef LOG_GROUP +#include "common/string/RTStrCopyEx.c" +#undef LOG_GROUP +#include "common/string/RTStrCopyP.c" +#undef LOG_GROUP +#include "common/string/RTStrEnd.c" +#undef LOG_GROUP +#include "common/string/RTStrICmpAscii.c" +#undef LOG_GROUP +#include "common/string/RTStrNICmpAscii.c" +#undef LOG_GROUP +#include "common/string/RTStrNCmp.c" +#undef LOG_GROUP +#include "common/string/RTStrNLen.c" +#undef LOG_GROUP +#include "common/string/stringalloc.c" +#undef LOG_GROUP +#include "common/string/strformat.c" +#undef LOG_GROUP +#include "common/string/RTStrFormat.c" +#undef LOG_GROUP +#include "common/string/strformatnum.c" +#undef LOG_GROUP +#include "common/string/strformattype.c" +#undef LOG_GROUP +#include "common/string/strprintf.c" +#undef LOG_GROUP +#include "common/string/strprintf-ellipsis.c" +#undef LOG_GROUP +#include "common/string/strprintf2.c" +#undef LOG_GROUP +#include "common/string/strprintf2-ellipsis.c" +#undef LOG_GROUP +#include "common/string/strtonum.c" +#undef LOG_GROUP +#include "common/string/utf-8.c" +#undef LOG_GROUP +#include "common/table/avlpv.c" +#undef LOG_GROUP +#include "common/time/time.c" +#undef LOG_GROUP +#include "generic/RTAssertShouldPanic-generic.c" +#undef LOG_GROUP +#include "generic/RTLogWriteStdErr-stub-generic.c" +#undef LOG_GROUP +#include "generic/RTLogWriteStdOut-stub-generic.c" +#undef LOG_GROUP +#include "generic/RTMpGetCoreCount-generic.c" +#undef LOG_GROUP +#include "generic/RTSemEventWait-2-ex-generic.c" +#undef LOG_GROUP +#include "generic/RTSemEventWaitNoResume-2-ex-generic.c" +#undef LOG_GROUP +#include "generic/RTSemEventMultiWait-2-ex-generic.c" +#undef LOG_GROUP +#include "generic/RTSemEventMultiWaitNoResume-2-ex-generic.c" +#undef LOG_GROUP +#include "generic/rtStrFormatKernelAddress-generic.c" +#undef LOG_GROUP +#include "generic/errvars-generic.c" +#undef LOG_GROUP +#include "generic/mppresent-generic.c" +#undef LOG_GROUP +#include "VBox/log-vbox.c" +#undef LOG_GROUP +#include "VBox/logbackdoor.c" +#undef LOG_GROUP +#include "VBox/RTLogWriteVmm-amd64-x86.c" + +#ifdef RT_ARCH_AMD64 +# undef LOG_GROUP +# include "common/alloc/heapsimple.c" +#endif + +#if 0 //def RT_ARCH_X86 - iprt/nocrt/limit.h clashes. +# include "common/math/gcc/divdi3.c" +# include "common/math/gcc/moddi3.c" +# include "common/math/gcc/udivdi3.c" +# include "common/math/gcc/udivmoddi4.c" +# include "common/math/gcc/umoddi3.c" +# include "common/math/gcc/qdivrem.c" +#endif + diff --git a/src/VBox/Additions/common/VBoxGuest/linux/combined-os-specific.c b/src/VBox/Additions/common/VBoxGuest/linux/combined-os-specific.c new file mode 100644 index 00000000..8981b058 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/linux/combined-os-specific.c @@ -0,0 +1,61 @@ +/* $Id: combined-os-specific.c $ */ +/** @file + * VBoxGuest - Combine a bunch of OS specific sources into one compile unit. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +#include "the-linux-kernel.h" + +#include "r0drv/linux/alloc-r0drv-linux.c" +#include "r0drv/linux/assert-r0drv-linux.c" +#include "r0drv/linux/initterm-r0drv-linux.c" +#include "r0drv/linux/memobj-r0drv-linux.c" +#include "r0drv/linux/memuserkernel-r0drv-linux.c" +#include "r0drv/linux/mp-r0drv-linux.c" +#include "r0drv/linux/mpnotification-r0drv-linux.c" +#include "r0drv/linux/process-r0drv-linux.c" +#include "r0drv/linux/semevent-r0drv-linux.c" +#include "r0drv/linux/semeventmulti-r0drv-linux.c" +#include "r0drv/linux/semfastmutex-r0drv-linux.c" +#include "r0drv/linux/semmutex-r0drv-linux.c" +#include "r0drv/linux/spinlock-r0drv-linux.c" +#include "r0drv/linux/thread-r0drv-linux.c" +#include "r0drv/linux/thread2-r0drv-linux.c" +#undef LOG_GROUP +#include "r0drv/linux/time-r0drv-linux.c" +#include "r0drv/linux/timer-r0drv-linux.c" +#include "r0drv/linux/RTLogWriteDebugger-r0drv-linux.c" +#include "common/err/RTErrConvertFromErrno.c" +#include "common/err/RTErrConvertToErrno.c" + diff --git a/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest b/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest new file mode 100755 index 00000000..5df520b6 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest @@ -0,0 +1,237 @@ +#!/bin/sh +# $Id: files_vboxguest $ +## @file +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# Copyright (C) 2007-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +FILES_VBOXGUEST_NOBIN=" \ + ${PATH_ROOT}/include/iprt/nocrt/limits.h=>include/iprt/nocrt/limits.h \ + ${PATH_ROOT}/include/iprt/alloca.h=>include/iprt/alloca.h \ + ${PATH_ROOT}/include/iprt/alloc.h=>include/iprt/alloc.h \ + ${PATH_ROOT}/include/iprt/asm.h=>include/iprt/asm.h \ + ${PATH_ROOT}/include/iprt/asm-amd64-x86.h=>include/iprt/asm-amd64-x86.h \ + ${PATH_ROOT}/include/iprt/asm-math.h=>include/iprt/asm-math.h \ + ${PATH_ROOT}/include/iprt/assert.h=>include/iprt/assert.h \ + ${PATH_ROOT}/include/iprt/assertcompile.h=>include/iprt/assertcompile.h \ + ${PATH_ROOT}/include/iprt/avl.h=>include/iprt/avl.h \ + ${PATH_ROOT}/include/iprt/cdefs.h=>include/iprt/cdefs.h \ + ${PATH_ROOT}/include/iprt/cpuset.h=>include/iprt/cpuset.h \ + ${PATH_ROOT}/include/iprt/crc.h=>include/iprt/crc.h \ + ${PATH_ROOT}/include/iprt/ctype.h=>include/iprt/ctype.h \ + ${PATH_ROOT}/include/iprt/err.h=>include/iprt/err.h \ + ${PATH_ROOT}/include/iprt/errcore.h=>include/iprt/errcore.h \ + ${PATH_ROOT}/include/iprt/errno.h=>include/iprt/errno.h \ + ${PATH_ROOT}/include/iprt/heap.h=>include/iprt/heap.h \ + ${PATH_ROOT}/include/iprt/initterm.h=>include/iprt/initterm.h \ + ${PATH_ROOT}/include/iprt/latin1.h=>include/iprt/latin1.h \ + ${PATH_ROOT}/include/iprt/list.h=>include/iprt/list.h \ + ${PATH_ROOT}/include/iprt/lockvalidator.h=>include/iprt/lockvalidator.h \ + ${PATH_ROOT}/include/iprt/log.h=>include/iprt/log.h \ + ${PATH_ROOT}/include/iprt/mangling.h=>include/iprt/mangling.h \ + ${PATH_ROOT}/include/iprt/mem.h=>include/iprt/mem.h \ + ${PATH_ROOT}/include/iprt/memobj.h=>include/iprt/memobj.h \ + ${PATH_ROOT}/include/iprt/mp.h=>include/iprt/mp.h \ + ${PATH_ROOT}/include/iprt/net.h=>include/iprt/net.h \ + ${PATH_ROOT}/include/iprt/param.h=>include/iprt/param.h \ + ${PATH_ROOT}/include/iprt/path.h=>include/iprt/path.h \ + ${PATH_ROOT}/include/iprt/power.h=>include/iprt/power.h \ + ${PATH_ROOT}/include/iprt/process.h=>include/iprt/process.h \ + ${PATH_ROOT}/include/iprt/semaphore.h=>include/iprt/semaphore.h \ + ${PATH_ROOT}/include/iprt/spinlock.h=>include/iprt/spinlock.h \ + ${PATH_ROOT}/include/iprt/stdarg.h=>include/iprt/stdarg.h \ + ${PATH_ROOT}/include/iprt/stdint.h=>include/iprt/stdint.h \ + ${PATH_ROOT}/include/iprt/string.h=>include/iprt/string.h \ + ${PATH_ROOT}/include/iprt/thread.h=>include/iprt/thread.h \ + ${PATH_ROOT}/include/iprt/time.h=>include/iprt/time.h \ + ${PATH_ROOT}/include/iprt/timer.h=>include/iprt/timer.h \ + ${PATH_ROOT}/include/iprt/types.h=>include/iprt/types.h \ + ${PATH_ROOT}/include/iprt/uint64.h=>include/iprt/uint64.h \ + ${PATH_ROOT}/include/iprt/uni.h=>include/iprt/uni.h \ + ${PATH_ROOT}/include/iprt/utf16.h=>include/iprt/utf16.h \ + ${PATH_ROOT}/include/iprt/x86.h=>include/iprt/x86.h \ + ${PATH_ROOT}/include/iprt/x86-helpers.h=>include/iprt/x86-helpers.h \ + ${PATH_ROOT}/include/iprt/linux/version.h=>include/iprt/linux/version.h \ + ${PATH_ROOT}/include/VBox/cdefs.h=>include/VBox/cdefs.h \ + ${PATH_ROOT}/include/VBox/err.h=>include/VBox/err.h \ + ${PATH_ROOT}/include/VBox/log.h=>include/VBox/log.h \ + ${PATH_ROOT}/include/VBox/param.h=>include/VBox/param.h \ + ${PATH_ROOT}/include/VBox/types.h=>include/VBox/types.h \ + ${PATH_ROOT}/include/VBox/ostypes.h=>include/VBox/ostypes.h \ + ${PATH_ROOT}/include/VBox/VMMDev.h=>include/VBox/VMMDev.h \ + ${PATH_ROOT}/include/VBox/VMMDevCoreTypes.h=>include/VBox/VMMDevCoreTypes.h \ + ${PATH_ROOT}/include/VBox/VBoxGuest.h=>include/VBox/VBoxGuest.h \ + ${PATH_ROOT}/include/VBox/VBoxGuestCoreTypes.h=>include/VBox/VBoxGuestCoreTypes.h \ + ${PATH_ROOT}/include/VBox/VBoxGuestLib.h=>include/VBox/VBoxGuestLib.h \ + ${PATH_ROOT}/include/VBox/VBoxGuestMangling.h=>include/VBox/VBoxGuestMangling.h \ + ${PATH_ROOT}/include/VBox/version.h=>include/VBox/version.h \ + ${PATH_ROOT}/include/VBox/HostServices/GuestPropertySvc.h=>include/VBox/HostServices/GuestPropertySvc.h \ + ${PATH_ROOT}/include/VBox/vmm/cpuidcall.h=>include/VBox/vmm/cpuidcall.h \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp=>VBoxGuest-common.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c=>VBoxGuest-linux.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h=>VBoxGuestInternal.h \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/linux/Makefile=>Makefile \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c=>combined-agnostic.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/linux/combined-os-specific.c=>combined-os-specific.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h=>VBoxGuestR0LibInternal.h \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp=>VBoxGuestR0LibGenericRequest.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCMInternal.cpp=>VBoxGuestR0LibHGCMInternal.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp=>VBoxGuestR0LibInit.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp=>VBoxGuestR0LibPhysHeap.c \ + ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibVMMDev.cpp=>VBoxGuestR0LibVMMDev.c \ + ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-header.gmk=>Makefile-header.gmk \ + ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-footer.gmk=>Makefile-footer.gmk \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/assert.h=>include/internal/assert.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/initterm.h=>include/internal/initterm.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/iprt.h=>include/internal/iprt.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/lockvalidator.h=>include/internal/lockvalidator.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/magics.h=>include/internal/magics.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/mem.h=>include/internal/mem.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/memobj.h=>include/internal/memobj.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/string.h=>include/internal/string.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/thread.h=>include/internal/thread.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/time.h=>include/internal/time.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/sched.h=>include/internal/sched.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/process.h=>include/internal/process.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/alloc/alloc.cpp=>common/alloc/alloc.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/alloc/heapsimple.cpp=>common/alloc/heapsimple.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/checksum/crc32.cpp=>common/checksum/crc32.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertFromErrno.cpp=>common/err/RTErrConvertFromErrno.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertToErrno.cpp=>common/err/RTErrConvertToErrno.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/errinfo.cpp=>common/err/errinfo.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/log.cpp=>common/log/log.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logellipsis.cpp=>common/log/logellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logrel.cpp=>common/log/logrel.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logrelellipsis.cpp=>common/log/logrelellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logformat.cpp=>common/log/logformat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logcom.cpp=>common/log/logcom.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/RTLogCreateEx.cpp=>common/log/RTLogCreateEx.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/divdi3.c=>common/math/gcc/divdi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/divmoddi4.c=>common/math/gcc/divmoddi4.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/moddi3.c=>common/math/gcc/moddi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/qdivrem.c=>common/math/gcc/qdivrem.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/quad.h=>common/math/gcc/quad.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivdi3.c=>common/math/gcc/udivdi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivmoddi4.c=>common/math/gcc/udivmoddi4.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/umoddi3.c=>common/math/gcc/umoddi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp=>common/misc/RTAssertMsg1Weak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp=>common/misc/RTAssertMsg2.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp=>common/misc/RTAssertMsg2Add.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp=>common/misc/RTAssertMsg2AddWeak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp=>common/misc/RTAssertMsg2AddWeakV.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp=>common/misc/RTAssertMsg2Weak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp=>common/misc/RTAssertMsg2WeakV.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/assert.cpp=>common/misc/assert.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/thread.cpp=>common/misc/thread.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCat.cpp=>common/string/RTStrCat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCmp.cpp=>common/string/RTStrCmp.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopy.cpp=>common/string/RTStrCopy.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyEx.cpp=>common/string/RTStrCopyEx.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyP.cpp=>common/string/RTStrCopyP.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrEnd.cpp=>common/string/RTStrEnd.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrICmpAscii.cpp=>common/string/RTStrICmpAscii.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNICmpAscii.cpp=>common/string/RTStrNICmpAscii.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNCmp.cpp=>common/string/RTStrNCmp.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNLen.cpp=>common/string/RTStrNLen.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/stringalloc.cpp=>common/string/stringalloc.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformat.cpp=>common/string/strformat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrFormat.cpp=>common/string/RTStrFormat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatnum.cpp=>common/string/strformatnum.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatrt.cpp=>common/string/strformatrt.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformattype.cpp=>common/string/strformattype.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf.cpp=>common/string/strprintf.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf-ellipsis.cpp=>common/string/strprintf-ellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2.cpp=>common/string/strprintf2.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2-ellipsis.cpp=>common/string/strprintf2-ellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strtonum.cpp=>common/string/strtonum.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/utf-8.cpp=>common/string/utf-8.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avlpv.cpp=>common/table/avlpv.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Base.cpp.h=>common/table/avl_Base.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Get.cpp.h=>common/table/avl_Get.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h=>common/table/avl_GetBestFit.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_RemoveBestFit.cpp.h=>common/table/avl_RemoveBestFit.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h=>common/table/avl_DoWithAll.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Destroy.cpp.h=>common/table/avl_Destroy.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/time/time.cpp=>common/time/time.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTAssertShouldPanic-generic.cpp=>generic/RTAssertShouldPanic-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdErr-stub-generic.cpp=>generic/RTLogWriteStdErr-stub-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdOut-stub-generic.cpp=>generic/RTLogWriteStdOut-stub-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTMpGetCoreCount-generic.cpp=>generic/RTMpGetCoreCount-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWait-2-ex-generic.cpp=>generic/RTSemEventWait-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventWaitNoResume-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWait-2-ex-generic.cpp=>generic/RTSemEventMultiWait-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventMultiWaitNoResume-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/rtStrFormatKernelAddress-generic.cpp=>generic/rtStrFormatKernelAddress-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/errvars-generic.cpp=>generic/errvars-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/mppresent-generic.cpp=>generic/mppresent-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.cpp=>r0drv/alloc-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.h=>r0drv/alloc-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/initterm-r0drv.cpp=>r0drv/initterm-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/memobj-r0drv.cpp=>r0drv/memobj-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/mp-r0drv.h=>r0drv/mp-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/mpnotification-r0drv.c=>r0drv/mpnotification-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/power-r0drv.h=>r0drv/power-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/powernotification-r0drv.c=>r0drv/powernotification-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/alloc-r0drv-linux.c=>r0drv/linux/alloc-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/assert-r0drv-linux.c=>r0drv/linux/assert-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/initterm-r0drv-linux.c=>r0drv/linux/initterm-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/memobj-r0drv-linux.c=>r0drv/linux/memobj-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/memuserkernel-r0drv-linux.c=>r0drv/linux/memuserkernel-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/mp-r0drv-linux.c=>r0drv/linux/mp-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/mpnotification-r0drv-linux.c=>r0drv/linux/mpnotification-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/process-r0drv-linux.c=>r0drv/linux/process-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semevent-r0drv-linux.c=>r0drv/linux/semevent-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semeventmulti-r0drv-linux.c=>r0drv/linux/semeventmulti-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semfastmutex-r0drv-linux.c=>r0drv/linux/semfastmutex-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semmutex-r0drv-linux.c=>r0drv/linux/semmutex-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/spinlock-r0drv-linux.c=>r0drv/linux/spinlock-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/string.h=>r0drv/linux/string.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/the-linux-kernel.h=>r0drv/linux/the-linux-kernel.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/thread-r0drv-linux.c=>r0drv/linux/thread-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/thread2-r0drv-linux.c=>r0drv/linux/thread2-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/time-r0drv-linux.c=>r0drv/linux/time-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/timer-r0drv-linux.c=>r0drv/linux/timer-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/waitqueue-r0drv-linux.h=>r0drv/linux/waitqueue-r0drv-linux.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/RTLogWriteDebugger-r0drv-linux.c=>r0drv/linux/RTLogWriteDebugger-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/semspinmutex-r0drv-generic.c=>r0drv/generic/semspinmutex-r0drv-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/VBox/log-vbox.cpp=>VBox/log-vbox.c \ + ${PATH_ROOT}/src/VBox/Runtime/VBox/logbackdoor.cpp=>VBox/logbackdoor.c \ + ${PATH_ROOT}/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.cpp=>VBox/RTLogWriteVmm-amd64-x86.c \ + ${PATH_OUT}/version-generated.h=>version-generated.h \ + ${PATH_OUT}/product-generated.h=>product-generated.h \ + ${PATH_OUT}/revision-generated.h=>revision-generated.h \ +" + +FILES_VBOXGUEST_BIN=" \ +" + diff --git a/src/VBox/Additions/common/VBoxGuest/netbsd/locators.h b/src/VBox/Additions/common/VBoxGuest/netbsd/locators.h new file mode 100644 index 00000000..91272794 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/netbsd/locators.h @@ -0,0 +1,8 @@ +/* $Id: locators.h $ */ +/** @file + * Placeholder "locators.h" that wsmousevar.h needs (bad hygiene). + * + * It's normally generated by config(8), but see the explanatory + * comment in vboxguest.ioconf + */ +#define WSMOUSEDEVCF_MUX 0 diff --git a/src/VBox/Additions/common/VBoxGuest/netbsd/vboxguest.ioconf b/src/VBox/Additions/common/VBoxGuest/netbsd/vboxguest.ioconf new file mode 100644 index 00000000..21beed84 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/netbsd/vboxguest.ioconf @@ -0,0 +1,66 @@ +# $Id: vboxguest.ioconf $ +## @file +# NetBSD vboxguest module configuration +# + +# +# Copyright (C) 2017-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# XXX: +# +# VBoxGuest-netbsd.c has manually edited copy of the config glue and +# we also provide stub "locators.h" in this directory. Both should +# really be generated by config(8) from this ioconf file but that runs +# into a couple of problems. +# +# We want to attach wsmouse(4) as a child, but we cannot expect the +# kernel that loads us to have "wsmouse* at wsmousedev?" attachment +# and in fact until recently x86 kernels didn't have it. +# +# But when we specify "wsmouse* at vboxguest?" attachment below +# config(8) thinks that this module defines wsmouse and generates +# CFDRIVER_DECL() for it and also includes it into cfdriver and +# cfattachinit arrays it emits. + +ioconf vboxguest + +include "conf/files" + +include "dev/i2o/files.i2o" # XXX: pci needs device iop +include "dev/pci/files.pci" + +device vboxguest: wsmousedev +attach vboxguest at pci + +pseudo-root pci* +vboxguest0 at pci? dev ? function ? + +wsmouse* at vboxguest? diff --git a/src/VBox/Additions/common/VBoxGuest/solaris/deps.asm b/src/VBox/Additions/common/VBoxGuest/solaris/deps.asm new file mode 100644 index 00000000..a6abf2bf --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/solaris/deps.asm @@ -0,0 +1,48 @@ +; $Id: deps.asm $ +;; @file +; Solaris kernel module dependency +; + +; +; Copyright (C) 2012-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "iprt/solaris/kmoddeps.mac" + +kmoddeps_header ; ELF header, section table and shared string table + +kmoddeps_dynstr_start ; ELF .dynstr section +kmoddeps_dynstr_string str_misc_ctf, "misc/ctf" +kmoddeps_dynstr_end + +kmoddeps_dynamic_start ; ELF .dynamic section +kmoddeps_dynamic_needed str_misc_ctf +kmoddeps_dynamic_end + diff --git a/src/VBox/Additions/common/VBoxGuest/solaris/load.sh b/src/VBox/Additions/common/VBoxGuest/solaris/load.sh new file mode 100755 index 00000000..379cba0c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/solaris/load.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# $Id: load.sh $ +## @file +# For GA development. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +DRVNAME="vboxguest" +DRIVERS_USING_IT="vboxfs" + +DRVFILE=`dirname "$0"` +DRVFILE=`cd "$DRVFILE" && pwd` +DRVFILE="$DRVFILE/$DRVNAME" +if [ ! -f "$DRVFILE" ]; then + echo "load.sh: Cannot find $DRVFILE or it's not a file..." + exit 1; +fi + +SUDO=sudo +#set -x + +# Unload driver that may depend on the driver we're going to (re-)load +# as well as the driver itself. +for drv in $DRIVERS_USING_IT $DRVNAME; +do + LOADED=`modinfo | grep -w "$drv"` + if test -n "$LOADED"; then + MODID=`echo "$LOADED" | cut -d ' ' -f 1` + $SUDO modunload -i $MODID; + LOADED=`modinfo | grep -w "$drv"`; + if test -n "$LOADED"; then + echo "load.sh: failed to unload $drv"; + dmesg | tail + exit 1; + fi + fi +done + +# +# Update the devlink.tab file so we get a /dev/vboxguest node. +# +set -e +sed -e '/name=vboxguest/d' /etc/devlink.tab > /tmp/devlink.vbox +echo -e "type=ddi_pseudo;name=vboxguest\t\D" >> /tmp/devlink.vbox +$SUDO cp /tmp/devlink.vbox /etc/devlink.tab +$SUDO ln -fs ../devices/pci@0,0/pci80ee,cafe@4:vboxguest /dev/vboxguest +set +e + +# +# The add_drv command will load the driver, so we need to temporarily put it +# in a place that is searched in order to load it. +# +MY_RC=1 +set -e +$SUDO rm -f \ + "/usr/kernel/drv/${DRVNAME}" \ + "/usr/kernel/drv/amd64/${DRVNAME}" +sync +$SUDO cp "${DRVFILE}" /platform/i86pc/kernel/drv/amd64/ +set +e + +$SUDO rem_drv $DRVNAME +if $SUDO add_drv -ipci80ee,cafe -m"* 0666 root sys" -v $DRVNAME; then + sync + $SUDO /usr/sbin/devfsadm -i $DRVNAME + MY_RC=0 +else + dmesg | tail + echo "load.sh: add_drv failed." +fi + +$SUDO rm -f \ + "/usr/kernel/drv/${DRVNAME}" \ + "/usr/kernel/drv/amd64/${DRVNAME}" +sync + +exit $MY_RC; + diff --git a/src/VBox/Additions/common/VBoxGuest/win/Makefile.kup b/src/VBox/Additions/common/VBoxGuest/win/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/win/Makefile.kup diff --git a/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.inf b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.inf new file mode 100644 index 00000000..ce64cf7b --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.inf @@ -0,0 +1,98 @@ +; $Id: VBoxGuest.inf $ +;; @file +; INF file for installing the VirtualBox Windows guest driver, XP and later. +; + +; +; Copyright (C) 2006-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +[Version] +Signature="$WINDOWS NT$" +Provider=%ORACLE% +ClassGuid={4D36E97D-E325-11CE-BFC1-08002BE10318} +Class=System +DriverPackageType=PlugAndPlay +;edit-DriverVer=08/26/2008,2.00.0000 +;cat CatalogFile=VBoxGuest.cat + +[SourceDisksNames] +1 = %VBoxGuest.MediaDesc% + +[SourceDisksFiles] +VBoxGuest.sys = 1 +VBoxControl.exe = 1 +VBoxTray.exe = 1 + +[DestinationDirs] +DefaultDestDir = 12 ; drivers +VBoxTray_CopyFiles = 11 ; system32 + +[Manufacturer] +%ORACLE%=VBoxGuest@COMMA-NT-ARCH@ + +[VBoxGuest@DOT-NT-ARCH@] +%VBoxGuest.DeviceDesc%=VBoxGuest_Install,PCI\VEN_80ee&DEV_cafe + +[VBoxGuest_Install] +CopyFiles = VBoxGuest_CopyFiles, VBoxTray_CopyFiles +AddReg = VBoxTray_Add_Reg + +[VBoxGuest_CopyFiles] +VBoxGuest.sys + +[VBoxTray_CopyFiles] +VBoxTray.exe +VBoxControl.exe + +[VBoxGuest_Install.Services] +AddService = VBoxGuest, 0x00000002, VBoxGuest_ServiceInstallSection +DelService = VBoxTray, 0x00000004 + +[VBoxGuest_ServiceInstallSection] +DisplayName = %VBoxGuest_svcdesc% +ServiceType = 0x00000001 ; kernel driver +StartType = 0x00000000 ; boot start +ErrorControl = 0x00000001 ; normal error handling +LoadOrderGroup = Base +ServiceBinary = %12%\VBoxGuest.sys + +[VBoxTray_Add_Reg] +HKLM, SOFTWARE\Microsoft\Windows\CurrentVersion\Run, VBoxTray, 0x00020000, %%SystemRoot%%\system32\VBoxTray.exe + +[ClassInstall32] +; This should fix the error 0xe0000101 (The required section was not found in the INF). + +[Strings] +ORACLE = "Oracle Corporation" +VBoxGuest.DeviceDesc = "VirtualBox Guest Device" +VBoxGuest_svcdesc = "VirtualBox Guest Driver" +VBoxTray_svcdesc = "VirtualBox Guest Tray" +VBoxGuest.MediaDesc = "VirtualBox Guest Driver Installation Disk" diff --git a/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.rc b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.rc new file mode 100644 index 00000000..ba7d500e --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.rc @@ -0,0 +1,72 @@ +/* $Id: VBoxGuest.rc $ */ +/** @file + * VBoxGuest - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "VirtualBox Guest Driver\0" + VALUE "InternalName", "VBoxGuest\0" + VALUE "OriginalFilename", "VBoxGuest.sys\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_GA_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +// #include <VBoxGuestMsg.rc> diff --git a/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestEarlyNT.inf b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestEarlyNT.inf new file mode 100644 index 00000000..c0bfd4e5 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestEarlyNT.inf @@ -0,0 +1,100 @@ +; $Id: VBoxGuestEarlyNT.inf $ +;; @file +; INF file for installing the VirtualBox Windows guest driver, pre-XP variant. +; + +; +; Copyright (C) 2006-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +[Version] +Signature="$WINDOWS NT$" +Provider=%ORACLE% +ClassGuid={4D36E97D-E325-11CE-BFC1-08002BE10318} +Class=System +DriverPackageType=PlugAndPlay +;edit-DriverVer=08/26/2008,2.00.0000 +;cat CatalogFile=VBoxGuestEarlyNT.cat + +[SourceDisksNames] +1 = %VBoxGuest.MediaDesc% + +[SourceDisksFiles] +VBoxGuest.sys = 1 +VBoxControl.exe = 1 +VBoxTray.exe = 1 + +[DestinationDirs] +DefaultDestDir = 12 ; drivers +VBoxTray_CopyFiles = 11 ; system32 + +; Windows 2000 and NT4 treats entries here as pure 'name=section' and will not +; split the value on commas. Newer InfVerif.exe requires the comma stuff here. +[Manufacturer] +%ORACLE%=VBoxGuest + +[VBoxGuest] +%VBoxGuest.DeviceDesc%=VBoxGuest_Install,PCI\VEN_80ee&DEV_cafe + +[VBoxGuest_Install] +CopyFiles = VBoxGuest_CopyFiles, VBoxTray_CopyFiles +AddReg = VBoxTray_Add_Reg + +[VBoxGuest_CopyFiles] +VBoxGuest.sys + +[VBoxTray_CopyFiles] +VBoxTray.exe +VBoxControl.exe + +[VBoxGuest_Install.Services] +AddService = VBoxGuest, 0x00000002, VBoxGuest_ServiceInstallSection +DelService = VBoxTray, 0x00000004 + +[VBoxGuest_ServiceInstallSection] +DisplayName = %VBoxGuest_svcdesc% +ServiceType = 0x00000001 ; kernel driver +StartType = 0x00000000 ; boot start +ErrorControl = 0x00000001 ; normal error handling +LoadOrderGroup = Base +ServiceBinary = %12%\VBoxGuest.sys + +[VBoxTray_Add_Reg] +HKLM, SOFTWARE\Microsoft\Windows\CurrentVersion\Run, VBoxTray, 0x00020000, %%SystemRoot%%\system32\VBoxTray.exe + +[ClassInstall32] +; This should fix the error 0xe0000101 (The required section was not found in the INF). + +[Strings] +ORACLE = "Oracle Corporation" +VBoxGuest.DeviceDesc = "VirtualBox Guest Device" +VBoxGuest_svcdesc = "VirtualBox Guest Driver" +VBoxTray_svcdesc = "VirtualBox Guest Tray" +VBoxGuest.MediaDesc = "VirtualBox Guest Driver Installation Disk" diff --git a/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestInst.cpp b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestInst.cpp new file mode 100644 index 00000000..2429280c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestInst.cpp @@ -0,0 +1,227 @@ +/* $Id: VBoxGuestInst.cpp $ */ +/** @file + * Small tool to (un)install the VBoxGuest device driver (for testing). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> + +#include <VBox/VBoxGuest.h> /* for VBOXGUEST_SERVICE_NAME */ +#include <iprt/errcore.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/utf16.h> + + + + +static RTEXITCODE installDriver(bool fStartIt) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + SC_HANDLE hSMgrCreate = OpenSCManagerW(NULL, NULL, SERVICE_CHANGE_CONFIG); + if (!hSMgrCreate) + return RTMsgErrorExitFailure("OpenSCManager(,,create) failed: %u", GetLastError()); + + const wchar_t *pwszSlashName = L"\\VBoxGuest.sys"; + wchar_t wszDriver[MAX_PATH * 2]; + GetCurrentDirectoryW(MAX_PATH, wszDriver); + RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), pwszSlashName); + if (GetFileAttributesW(wszDriver) == INVALID_FILE_ATTRIBUTES) + { + GetSystemDirectoryW(wszDriver, MAX_PATH); + RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), L"\\drivers"); + RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), pwszSlashName); + + /* Try FAT name abbreviation. */ + if (GetFileAttributesW(wszDriver) == INVALID_FILE_ATTRIBUTES) + { + pwszSlashName = L"\\VBoxGst.sys"; + GetCurrentDirectoryW(MAX_PATH, wszDriver); + RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), pwszSlashName); + if (GetFileAttributesW(wszDriver) == INVALID_FILE_ATTRIBUTES) + { + GetSystemDirectoryW(wszDriver, MAX_PATH); + RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), L"\\drivers"); + RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), pwszSlashName); + } + } + } + + RTEXITCODE rcExit; + SC_HANDLE hService = CreateServiceW(hSMgrCreate, + RT_CONCAT(L,VBOXGUEST_SERVICE_NAME), + L"VBoxGuest Support Driver", + SERVICE_QUERY_STATUS | (fStartIt ? SERVICE_START : 0), + SERVICE_KERNEL_DRIVER, + SERVICE_BOOT_START, + SERVICE_ERROR_NORMAL, + wszDriver, + L"System", + NULL, NULL, NULL, NULL); + if (hService) + { + RTMsgInfo("Successfully created service '%s' for driver '%ls'.\n", VBOXGUEST_SERVICE_NAME, wszDriver); + rcExit = RTEXITCODE_SUCCESS; + if (fStartIt) + { + if (StartService(hService, 0, NULL)) + RTMsgInfo("successfully started driver '%ls'\n", wszDriver); + else + rcExit = RTMsgErrorExitFailure("StartService failed: %u", GetLastError()); + } + CloseServiceHandle(hService); + } + else + rcExit = RTMsgErrorExitFailure("CreateService failed! %u (wszDriver=%ls)\n", GetLastError(), wszDriver); + CloseServiceHandle(hSMgrCreate); + return rcExit; +} + + +static RTEXITCODE uninstallDriver(void) +{ + SC_HANDLE hSMgr = OpenSCManagerW(NULL, NULL, SERVICE_CHANGE_CONFIG); + if (!hSMgr) + return RTMsgErrorExitFailure("OpenSCManager(,,change_config) failed: %u", GetLastError()); + + RTEXITCODE rcExit; + SC_HANDLE hService = OpenServiceW(hSMgr, RT_CONCAT(L, VBOXGUEST_SERVICE_NAME), SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE); + if (hService) + { + /* + * Try stop it if it's running. + */ + SERVICE_STATUS Status = { 0, 0, 0, 0, 0, 0, 0 }; + QueryServiceStatus(hService, &Status); + if (Status.dwCurrentState == SERVICE_STOPPED) + rcExit = RTEXITCODE_SUCCESS; + else if (ControlService(hService, SERVICE_CONTROL_STOP, &Status)) + { + int iWait = 100; + while (Status.dwCurrentState == SERVICE_STOP_PENDING && iWait-- > 0) + { + Sleep(100); + QueryServiceStatus(hService, &Status); + } + if (Status.dwCurrentState == SERVICE_STOPPED) + rcExit = RTEXITCODE_SUCCESS; + else + rcExit = RTMsgErrorExitFailure("Failed to stop service! Service status: %u (%#x)\n", + Status.dwCurrentState, Status.dwCurrentState); + } + else + rcExit = RTMsgErrorExitFailure("ControlService failed: %u, Service status: %u (%#x)", + GetLastError(), Status.dwCurrentState, Status.dwCurrentState); + + /* + * Delete the service. + */ + if (rcExit == RTEXITCODE_SUCCESS) + { + if (DeleteService(hService)) + RTMsgInfo("Successfully deleted the %s service\n", VBOXGUEST_SERVICE_NAME); + else + rcExit = RTMsgErrorExitFailure("DeleteService failed: %u", GetLastError()); + } + + CloseServiceHandle(hService); + } + else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + { + RTMsgInfo("Nothing to do, the service %s does not exist.\n", VBOXGUEST_SERVICE_NAME); + rcExit = RTEXITCODE_SUCCESS; + } + else + rcExit = RTMsgErrorExitFailure("OpenService failed: %u", GetLastError()); + + CloseServiceHandle(hSMgr); + return rcExit; +} + + +static RTEXITCODE performTest(void) +{ + HANDLE hDevice = CreateFileW(RT_CONCAT(L,VBOXGUEST_DEVICE_NAME), // Win2k+: VBOXGUEST_DEVICE_NAME_GLOBAL + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hDevice != INVALID_HANDLE_VALUE) + { + CloseHandle(hDevice); + RTMsgInfo("Test succeeded\n"); + return RTEXITCODE_SUCCESS; + } + return RTMsgErrorExitFailure("Test failed! Unable to open driver (CreateFileW -> %u).", GetLastError()); +} + + +static RTEXITCODE usage(const char *pszProgName) +{ + RTPrintf("\n" + "Usage: %s [install|uninstall|test]\n", pszProgName); + return RTEXITCODE_SYNTAX; +} + + +int main(int argc, char **argv) +{ + if (argc != 2) + { + RTMsgError(argc < 2 ? "Too few arguments! Expected one." : "Too many arguments! Expected only one."); + return usage(argv[0]); + } + + RTEXITCODE rcExit; + if (strcmp(argv[1], "install") == 0) + rcExit = installDriver(true); + else if (strcmp(argv[1], "uninstall") == 0) + rcExit = uninstallDriver(); + else if (strcmp(argv[1], "test") == 0) + rcExit = performTest(); + else + { + RTMsgError("Unknown argument: '%s'", argv[1]); + rcExit = usage(argv[0]); + } + return rcExit; +} + diff --git a/src/VBox/Additions/common/VBoxService/Makefile.kmk b/src/VBox/Additions/common/VBoxService/Makefile.kmk new file mode 100644 index 00000000..27fcd8b0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/Makefile.kmk @@ -0,0 +1,220 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Cross Platform Guest Addition Services. +# + +# +# Copyright (C) 2007-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Incldue testcases. +# +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + + +# +# Target lists. +# +PROGRAMS += VBoxService + + +# +# Globals? +# +# Enable the timesync service within VBoxService. +VBOX_WITH_VBOXSERVICE_TIMESYNC := 1 + +# Busybox-like toolbox, embedded into VBoxService. +VBOX_WITH_VBOXSERVICE_TOOLBOX := 1 + +# VM-management functions, like memory ballooning and statistics. +VBOX_WITH_VBOXSERVICE_MANAGEMENT := 1 + +if1of ($(KBUILD_TARGET), linux) + # CPU hotplugging. + VBOX_WITH_VBOXSERVICE_CPUHOTPLUG := 1 +endif + +# Page Sharing (Page Fusion). +if1of ($(KBUILD_TARGET), win) + VBOX_WITH_VBOXSERVICE_PAGE_SHARING := 1 +endif + +ifdef VBOX_WITH_GUEST_PROPS + VBOX_WITH_VBOXSERVICE_VMINFO := 1 +endif + +# Guest Control. +ifdef VBOX_WITH_GUEST_CONTROL + VBOX_WITH_VBOXSERVICE_CONTROL := 1 +endif + +# Shared Clipboard. +ifdef VBOX_WITH_SHARED_CLIPBOARD + VBOX_WITH_VBOXSERVICE_CLIPBOARD := 1 +endif + +# DRM Resize. +if "$(KBUILD_TARGET)" == "linux" && defined(VBOX_WITH_GUEST_PROPS) + # The DRM resizing code needs guest properties. + VBOX_WITH_VBOXSERVICE_DRMRESIZE := 1 +endif + + +# +# VBoxService +# +VBoxService_TEMPLATE = VBoxGuestR3Exe + +VBoxService_DEFS = \ + $(if $(VBOX_WITH_VBOXSERVICE_CONTROL),VBOX_WITH_VBOXSERVICE_CONTROL,) \ + $(if $(VBOX_WITH_VBOXSERVICE_CPUHOTPLUG),VBOX_WITH_VBOXSERVICE_CPUHOTPLUG,) \ + $(if $(VBOX_WITH_VBOXSERVICE_DRMRESIZE),VBOX_WITH_VBOXSERVICE_DRMRESIZE,) \ + $(if $(VBOX_WITH_VBOXSERVICE_MANAGEMENT),VBOX_WITH_VBOXSERVICE_MANAGEMENT,) \ + $(if $(VBOX_WITH_VBOXSERVICE_PAGE_SHARING),VBOX_WITH_VBOXSERVICE_PAGE_SHARING,) \ + $(if $(VBOX_WITH_VBOXSERVICE_TIMESYNC),VBOX_WITH_VBOXSERVICE_TIMESYNC,) \ + $(if $(VBOX_WITH_VBOXSERVICE_TOOLBOX),VBOX_WITH_VBOXSERVICE_TOOLBOX,) \ + $(if $(VBOX_WITH_VBOXSERVICE_VMINFO),VBOX_WITH_VBOXSERVICE_VMINFO,) \ + $(if $(VBOX_WITH_DBUS),VBOX_WITH_DBUS,) \ + $(if $(VBOX_WITH_GUEST_CONTROL),VBOX_WITH_GUEST_CONTROL,) \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \ + $(if $(VBOX_WITH_HGCM),VBOX_WITH_HGCM,) +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + VBoxService_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)" +else + VBoxService_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\" +endif +VBoxService_DEFS.win += _WIN32_WINNT=0x0501 +VBoxService_DEFS.os2 = VBOX_WITH_HGCM + +VBoxService_SOURCES = \ + VBoxService.cpp \ + VBoxServiceUtils.cpp \ + VBoxServiceStats.cpp + +ifdef VBOX_WITH_VBOXSERVICE_TIMESYNC + VBoxService_SOURCES += \ + VBoxServiceTimeSync.cpp +endif + +ifdef VBOX_WITH_VBOXSERVICE_CLIPBOARD + VBoxService_DEFS.os2 += VBOX_WITH_VBOXSERVICE_CLIPBOARD VBOX_WITH_SHARED_CLIPBOARD + VBoxService_SOURCES.os2 += \ + VBoxServiceClipboard-os2.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +endif + +ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX + VBoxService_SOURCES += \ + VBoxServiceToolBox.cpp +endif + +ifdef VBOX_WITH_VBOXSERVICE_CONTROL + VBoxService_SOURCES += \ + VBoxServiceControl.cpp \ + VBoxServiceControlProcess.cpp \ + VBoxServiceControlSession.cpp +endif + +ifdef VBOX_WITH_VBOXSERVICE_MANAGEMENT + ifdef VBOX_WITH_MEMBALLOON + VBoxService_SOURCES += \ + VBoxServiceBalloon.cpp + VBoxService_DEFS += VBOX_WITH_MEMBALLOON + endif +endif + +if1of ($(KBUILD_TARGET), win) + VBoxService_SOURCES += \ + VBoxServicePageSharing.cpp +endif + +ifdef VBOX_WITH_VBOXSERVICE_VMINFO + VBoxService_SOURCES.win += \ + VBoxServiceVMInfo-win.cpp + VBoxService_SOURCES += \ + VBoxServiceVMInfo.cpp \ + VBoxServicePropCache.cpp +endif + +ifdef VBOX_WITH_VBOXSERVICE_CPUHOTPLUG + VBoxService_SOURCES += \ + VBoxServiceCpuHotPlug.cpp +endif + +ifdef VBOX_WITH_SHARED_FOLDERS + if1of ($(KBUILD_TARGET), linux os2 solaris win) + VBoxService_DEFS += VBOX_WITH_SHARED_FOLDERS + VBoxService_SOURCES += \ + VBoxServiceAutoMount.cpp + VBoxService_SOURCES.linux += \ + ../../linux/sharedfolders/vbsfmount.c + VBoxService_LIBS.win += \ + Mpr.Lib + endif +endif + +VBoxService_SOURCES.win += \ + VBoxService-win.cpp + +VBoxService_SOURCES.os2 += \ + VBoxService-os2.def + +VBoxService_LDFLAGS.darwin = -framework IOKit + +VBoxService_LIBS += \ + $(VBOX_LIB_IPRT_GUEST_R3) \ + $(VBOX_LIB_VBGL_R3) \ + $(VBOX_LIB_IPRT_GUEST_R3) # (The joy of unix linkers.) +ifdef VBOX_WITH_DBUS + if1of ($(KBUILD_TARGET), linux solaris) # FreeBSD? + VBoxService_LIBS += \ + dl + endif +endif +VBoxService_LIBS.netbsd += crypt +ifdef VBOX_WITH_GUEST_PROPS + VBoxService_LIBS.win += \ + Secur32.lib \ + WtsApi32.lib \ + Psapi.lib + VBoxService_LIBS.solaris += \ + nsl \ + kstat \ + contract +endif + +ifdef VBOX_WITH_VBOXSERVICE_VMINFO + VBoxServiceVMInfo.cpp_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) + VBoxServiceVMInfo.cpp_DEPS = $(VBOX_SVN_REV_KMK) +endif + +VBoxService_USES.win += vboximportchecker +VBoxService_VBOX_IMPORT_CHECKER.win.x86 = nt31 +VBoxService_VBOX_IMPORT_CHECKER.win.amd64 = xp64 + +$(call VBOX_SET_VER_INFO_EXE,VBoxService,VirtualBox Guest Additions Service,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description. + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/Additions/common/VBoxService/VBoxService-os2.def b/src/VBox/Additions/common/VBoxService/VBoxService-os2.def new file mode 100644 index 00000000..b50cc211 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxService-os2.def @@ -0,0 +1,33 @@ +; $Id: VBoxService-os2.def $ +;; @file +; VBoxService - OS/2 definition file. +; + +; +; Copyright (C) 2007-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; SPDX-License-Identifier: GPL-3.0-only +; + + +NAME VBoxSvc +DESCRIPTION 'VirtualBox Guest Additions Service for OS/2.' +CODE SHARED +DATA MULTIPLE NONSHARED + diff --git a/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp b/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp new file mode 100644 index 00000000..fb2769fb --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp @@ -0,0 +1,670 @@ +/* $Id: VBoxService-win.cpp $ */ +/** @file + * VBoxService - Guest Additions Service Skeleton, Windows Specific Parts. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/ldr.h> +#include <iprt/system.h> /* For querying OS version. */ +#include <VBox/VBoxGuestLib.h> + +#define WIN32_NO_STATUS +#include <iprt/win/ws2tcpip.h> +#include <iprt/win/winsock2.h> +#undef WIN32_NO_STATUS +#include <iprt/nt/nt-and-windows.h> +#include <iprt/win/iphlpapi.h> +#include <aclapi.h> +#include <tlhelp32.h> +#define _NTDEF_ +#include <Ntsecapi.h> + +#include "VBoxServiceInternal.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void WINAPI vgsvcWinMain(DWORD argc, LPTSTR *argv); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static DWORD g_dwWinServiceLastStatus = 0; +SERVICE_STATUS_HANDLE g_hWinServiceStatus = NULL; +/** The semaphore for the dummy Windows service. */ +static RTSEMEVENT g_WindowsEvent = NIL_RTSEMEVENT; + +static SERVICE_TABLE_ENTRY const g_aServiceTable[] = +{ + { VBOXSERVICE_NAME, vgsvcWinMain }, + { NULL, NULL} +}; + +/** @name APIs from ADVAPI32.DLL. + * @{ */ +decltype(RegisterServiceCtrlHandlerExA) *g_pfnRegisterServiceCtrlHandlerExA; /**< W2K+ */ +decltype(ChangeServiceConfig2A) *g_pfnChangeServiceConfig2A; /**< W2K+ */ +decltype(GetNamedSecurityInfoA) *g_pfnGetNamedSecurityInfoA; /**< NT4+ */ +decltype(SetEntriesInAclA) *g_pfnSetEntriesInAclA; /**< NT4+ */ +decltype(SetNamedSecurityInfoA) *g_pfnSetNamedSecurityInfoA; /**< NT4+ */ +decltype(LsaNtStatusToWinError) *g_pfnLsaNtStatusToWinError; /**< NT3.51+ */ +/** @} */ + +/** @name API from KERNEL32.DLL + * @{ */ +decltype(CreateToolhelp32Snapshot) *g_pfnCreateToolhelp32Snapshot; /**< W2K+, but Geoff says NT4. Hmm. */ +decltype(Process32First) *g_pfnProcess32First; /**< W2K+, but Geoff says NT4. Hmm. */ +decltype(Process32Next) *g_pfnProcess32Next; /**< W2K+, but Geoff says NT4. Hmm. */ +decltype(Module32First) *g_pfnModule32First; /**< W2K+, but Geoff says NT4. Hmm. */ +decltype(Module32Next) *g_pfnModule32Next; /**< W2K+, but Geoff says NT4. Hmm. */ +decltype(GetSystemTimeAdjustment) *g_pfnGetSystemTimeAdjustment; /**< NT 3.50+ */ +decltype(SetSystemTimeAdjustment) *g_pfnSetSystemTimeAdjustment; /**< NT 3.50+ */ +/** @} */ + +/** @name API from NTDLL.DLL + * @{ */ +decltype(ZwQuerySystemInformation) *g_pfnZwQuerySystemInformation; /**< NT4 (where as NtQuerySystemInformation is W2K). */ +/** @} */ + +/** @name API from IPHLPAPI.DLL + * @{ */ +decltype(GetAdaptersInfo) *g_pfnGetAdaptersInfo; +/** @} */ + +/** @name APIs from WS2_32.DLL + * @note WSAIoctl is not present in wsock32.dll, so no point in trying the + * fallback here. + * @{ */ +decltype(WSAStartup) *g_pfnWSAStartup; +decltype(WSACleanup) *g_pfnWSACleanup; +decltype(WSASocketA) *g_pfnWSASocketA; +decltype(WSAIoctl) *g_pfnWSAIoctl; +decltype(WSAGetLastError) *g_pfnWSAGetLastError; +decltype(closesocket) *g_pfnclosesocket; +decltype(inet_ntoa) *g_pfninet_ntoa; + +/** @} */ + +/** + * Resolve APIs not present on older windows versions. + */ +void VGSvcWinResolveApis(void) +{ + RTLDRMOD hLdrMod; +#define RESOLVE_SYMBOL(a_fn) do { RT_CONCAT(g_pfn, a_fn) = (decltype(a_fn) *)RTLdrGetFunction(hLdrMod, #a_fn); } while (0) + + /* From ADVAPI32.DLL: */ + int rc = RTLdrLoadSystem("advapi32.dll", true /*fNoUnload*/, &hLdrMod); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + RESOLVE_SYMBOL(RegisterServiceCtrlHandlerExA); + RESOLVE_SYMBOL(ChangeServiceConfig2A); + RESOLVE_SYMBOL(GetNamedSecurityInfoA); + RESOLVE_SYMBOL(SetEntriesInAclA); + RESOLVE_SYMBOL(SetNamedSecurityInfoA); + RESOLVE_SYMBOL(LsaNtStatusToWinError); + RTLdrClose(hLdrMod); + } + + /* From KERNEL32.DLL: */ + rc = RTLdrLoadSystem("kernel32.dll", true /*fNoUnload*/, &hLdrMod); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + RESOLVE_SYMBOL(CreateToolhelp32Snapshot); + RESOLVE_SYMBOL(Process32First); + RESOLVE_SYMBOL(Process32Next); + RESOLVE_SYMBOL(Module32First); + RESOLVE_SYMBOL(Module32Next); + RESOLVE_SYMBOL(GetSystemTimeAdjustment); + RESOLVE_SYMBOL(SetSystemTimeAdjustment); + RTLdrClose(hLdrMod); + } + + /* From NTDLL.DLL: */ + rc = RTLdrLoadSystem("ntdll.dll", true /*fNoUnload*/, &hLdrMod); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + RESOLVE_SYMBOL(ZwQuerySystemInformation); + RTLdrClose(hLdrMod); + } + + /* From IPHLPAPI.DLL: */ + rc = RTLdrLoadSystem("iphlpapi.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_SUCCESS(rc)) + { + RESOLVE_SYMBOL(GetAdaptersInfo); + RTLdrClose(hLdrMod); + } + + /* From WS2_32.DLL: */ + rc = RTLdrLoadSystem("ws2_32.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_SUCCESS(rc)) + { + RESOLVE_SYMBOL(WSAStartup); + RESOLVE_SYMBOL(WSACleanup); + RESOLVE_SYMBOL(WSASocketA); + RESOLVE_SYMBOL(WSAIoctl); + RESOLVE_SYMBOL(WSAGetLastError); + RESOLVE_SYMBOL(closesocket); + RESOLVE_SYMBOL(inet_ntoa); + RTLdrClose(hLdrMod); + } +} + + +/** + * @todo Add full unicode support. + * @todo Add event log capabilities / check return values. + */ +static int vgsvcWinAddAceToObjectsSecurityDescriptor(LPTSTR pszObjName, SE_OBJECT_TYPE enmObjectType, const char *pszTrustee, + TRUSTEE_FORM enmTrusteeForm, DWORD dwAccessRights, ACCESS_MODE fAccessMode, + DWORD dwInheritance) +{ + int rc; + if ( g_pfnGetNamedSecurityInfoA + && g_pfnSetEntriesInAclA + && g_pfnSetNamedSecurityInfoA) + { + /* Get a pointer to the existing DACL. */ + PSECURITY_DESCRIPTOR pSD = NULL; + PACL pOldDACL = NULL; + DWORD rcWin = g_pfnGetNamedSecurityInfoA(pszObjName, enmObjectType, DACL_SECURITY_INFORMATION, + NULL, NULL, &pOldDACL, NULL, &pSD); + if (rcWin == ERROR_SUCCESS) + { + /* Initialize an EXPLICIT_ACCESS structure for the new ACE. */ + EXPLICIT_ACCESSA ExplicitAccess; + RT_ZERO(ExplicitAccess); + ExplicitAccess.grfAccessPermissions = dwAccessRights; + ExplicitAccess.grfAccessMode = fAccessMode; + ExplicitAccess.grfInheritance = dwInheritance; + ExplicitAccess.Trustee.TrusteeForm = enmTrusteeForm; + ExplicitAccess.Trustee.ptstrName = (char *)pszTrustee; + + /* Create a new ACL that merges the new ACE into the existing DACL. */ + PACL pNewDACL = NULL; + rcWin = g_pfnSetEntriesInAclA(1, &ExplicitAccess, pOldDACL, &pNewDACL); + if (rcWin == ERROR_SUCCESS) + { + /* Attach the new ACL as the object's DACL. */ + rcWin = g_pfnSetNamedSecurityInfoA(pszObjName, enmObjectType, DACL_SECURITY_INFORMATION, + NULL, NULL, pNewDACL, NULL); + if (rcWin == ERROR_SUCCESS) + rc = VINF_SUCCESS; + else + { + VGSvcError("AddAceToObjectsSecurityDescriptor: SetNamedSecurityInfo: Error %u\n", rcWin); + rc = RTErrConvertFromWin32(rcWin); + } + if (pNewDACL) + LocalFree(pNewDACL); + } + else + { + VGSvcError("AddAceToObjectsSecurityDescriptor: SetEntriesInAcl: Error %u\n", rcWin); + rc = RTErrConvertFromWin32(rcWin); + } + if (pSD) + LocalFree(pSD); + } + else + { + if (rcWin == ERROR_FILE_NOT_FOUND) + VGSvcError("AddAceToObjectsSecurityDescriptor: Object not found/installed: %s\n", pszObjName); + else + VGSvcError("AddAceToObjectsSecurityDescriptor: GetNamedSecurityInfo: Error %u\n", rcWin); + rc = RTErrConvertFromWin32(rcWin); + } + } + else + rc = VINF_SUCCESS; /* fake it */ + return rc; +} + + +/** Reports our current status to the SCM. */ +static BOOL vgsvcWinSetStatus(DWORD dwStatus, DWORD dwCheckPoint) +{ + if (g_hWinServiceStatus == NULL) /* Program could be in testing mode, so no service environment available. */ + return FALSE; + + VGSvcVerbose(2, "Setting service status to: %ld\n", dwStatus); + g_dwWinServiceLastStatus = dwStatus; + + SERVICE_STATUS ss; + RT_ZERO(ss); + + ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ss.dwCurrentState = dwStatus; + /* Don't accept controls when in start pending state. */ + if (ss.dwCurrentState != SERVICE_START_PENDING) + { + ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; + + /* Don't use SERVICE_ACCEPT_SESSIONCHANGE on Windows 2000 or earlier. This makes SCM angry. */ + char szOSVersion[32]; + int rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOSVersion, sizeof(szOSVersion)); + if (RT_SUCCESS(rc)) + { + if (RTStrVersionCompare(szOSVersion, "5.1") >= 0) + ss.dwControlsAccepted |= SERVICE_ACCEPT_SESSIONCHANGE; + } + else + VGSvcError("Error determining OS version, rc=%Rrc\n", rc); + } + + ss.dwWin32ExitCode = NO_ERROR; + ss.dwServiceSpecificExitCode = 0; /* Not used */ + ss.dwCheckPoint = dwCheckPoint; + ss.dwWaitHint = 3000; + + BOOL fStatusSet = SetServiceStatus(g_hWinServiceStatus, &ss); + if (!fStatusSet) + VGSvcError("Error reporting service status=%ld (controls=%x, checkpoint=%ld) to SCM: %ld\n", + dwStatus, ss.dwControlsAccepted, dwCheckPoint, GetLastError()); + return fStatusSet; +} + + +/** + * Reports SERVICE_STOP_PENDING to SCM. + * + * @param uCheckPoint Some number. + */ +void VGSvcWinSetStopPendingStatus(uint32_t uCheckPoint) +{ + vgsvcWinSetStatus(SERVICE_STOP_PENDING, uCheckPoint); +} + + +static RTEXITCODE vgsvcWinSetDesc(SC_HANDLE hService) +{ + /* On W2K+ there's ChangeServiceConfig2() which lets us set some fields + like a longer service description. */ + if (g_pfnChangeServiceConfig2A) + { + /** @todo On Vista+ SERVICE_DESCRIPTION also supports localized strings! */ + SERVICE_DESCRIPTION desc; + desc.lpDescription = VBOXSERVICE_DESCRIPTION; + if (!g_pfnChangeServiceConfig2A(hService, SERVICE_CONFIG_DESCRIPTION, &desc)) + { + VGSvcError("Cannot set the service description! Error: %ld\n", GetLastError()); + return RTEXITCODE_FAILURE; + } + } + return RTEXITCODE_SUCCESS; +} + + +/** + * Installs the service. + */ +RTEXITCODE VGSvcWinInstall(void) +{ + VGSvcVerbose(1, "Installing service ...\n"); + + TCHAR imagePath[MAX_PATH] = { 0 }; + GetModuleFileName(NULL, imagePath, sizeof(imagePath)); + + SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (hSCManager == NULL) + { + VGSvcError("Could not open SCM! Error: %ld\n", GetLastError()); + return RTEXITCODE_FAILURE; + } + + RTEXITCODE rc = RTEXITCODE_SUCCESS; + SC_HANDLE hService = CreateService(hSCManager, + VBOXSERVICE_NAME, VBOXSERVICE_FRIENDLY_NAME, + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, + SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, + imagePath, NULL, NULL, NULL, NULL, NULL); + if (hService != NULL) + VGSvcVerbose(0, "Service successfully installed!\n"); + else + { + DWORD dwErr = GetLastError(); + switch (dwErr) + { + case ERROR_SERVICE_EXISTS: + VGSvcVerbose(1, "Service already exists, just updating the service config.\n"); + hService = OpenService(hSCManager, VBOXSERVICE_NAME, SERVICE_ALL_ACCESS); + if (hService) + { + if (ChangeServiceConfig(hService, + SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + imagePath, + NULL, + NULL, + NULL, + NULL, + NULL, + VBOXSERVICE_FRIENDLY_NAME)) + VGSvcVerbose(1, "The service config has been successfully updated.\n"); + else + rc = VGSvcError("Could not change service config! Error: %ld\n", GetLastError()); + } + else + rc = VGSvcError("Could not open service! Error: %ld\n", GetLastError()); + break; + + default: + rc = VGSvcError("Could not create service! Error: %ld\n", dwErr); + break; + } + } + + if (rc == RTEXITCODE_SUCCESS) + rc = vgsvcWinSetDesc(hService); + + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); + return rc; +} + +/** + * Uninstalls the service. + */ +RTEXITCODE VGSvcWinUninstall(void) +{ + VGSvcVerbose(1, "Uninstalling service ...\n"); + + SC_HANDLE hSCManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS); + if (hSCManager == NULL) + { + VGSvcError("Could not open SCM! Error: %d\n", GetLastError()); + return RTEXITCODE_FAILURE; + } + + RTEXITCODE rcExit; + SC_HANDLE hService = OpenService(hSCManager, VBOXSERVICE_NAME, SERVICE_ALL_ACCESS ); + if (hService != NULL) + { + if (DeleteService(hService)) + { + /* + * ??? + */ + HKEY hKey = NULL; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Services\\EventLog\\System", + 0, + KEY_ALL_ACCESS, + &hKey) + == ERROR_SUCCESS) + { + RegDeleteKey(hKey, VBOXSERVICE_NAME); + RegCloseKey(hKey); + } + + VGSvcVerbose(0, "Service successfully uninstalled!\n"); + rcExit = RTEXITCODE_SUCCESS; + } + else + rcExit = VGSvcError("Could not remove service! Error: %d\n", GetLastError()); + CloseServiceHandle(hService); + } + else + rcExit = VGSvcError("Could not open service! Error: %d\n", GetLastError()); + CloseServiceHandle(hSCManager); + + return rcExit; +} + + +static int vgsvcWinStart(void) +{ + int rc = VINF_SUCCESS; + + /* + * Create a well-known SID for the "Builtin Users" group and modify the ACE + * for the shared folders miniport redirector DN (whatever DN means). + */ + PSID pBuiltinUsersSID = NULL; + SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_LOCAL_SID_AUTHORITY; + if (AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_LOCAL_RID, 0, 0, 0, 0, 0, 0, 0, &pBuiltinUsersSID)) + { + rc = vgsvcWinAddAceToObjectsSecurityDescriptor(TEXT("\\\\.\\VBoxMiniRdrDN"), SE_FILE_OBJECT, + (LPTSTR)pBuiltinUsersSID, TRUSTEE_IS_SID, + FILE_GENERIC_READ | FILE_GENERIC_WRITE, SET_ACCESS, NO_INHERITANCE); + /* If we don't find our "VBoxMiniRdrDN" (for Shared Folders) object above, + don't report an error; it just might be not installed. Otherwise this + would cause the SCM to hang on starting up the service. */ + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + rc = VINF_SUCCESS; + + FreeSid(pBuiltinUsersSID); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + if (RT_SUCCESS(rc)) + { + /* + * Start the service. + */ + vgsvcWinSetStatus(SERVICE_START_PENDING, 0); + + rc = VGSvcStartServices(); + if (RT_SUCCESS(rc)) + { + vgsvcWinSetStatus(SERVICE_RUNNING, 0); + VGSvcMainWait(); + } + else + { + vgsvcWinSetStatus(SERVICE_STOPPED, 0); +#if 0 /** @todo r=bird: Enable this if SERVICE_CONTROL_STOP isn't triggered automatically */ + VGSvcStopServices(); +#endif + } + } + else + vgsvcWinSetStatus(SERVICE_STOPPED, 0); + + if (RT_FAILURE(rc)) + VGSvcError("Service failed to start with rc=%Rrc!\n", rc); + + return rc; +} + + +/** + * Call StartServiceCtrlDispatcher. + * + * The main() thread invokes this when not started in foreground mode. It + * won't return till the service is being shutdown (unless start up fails). + * + * @returns RTEXITCODE_SUCCESS on normal return after service shutdown. + * Something else on failure, error will have been reported. + */ +RTEXITCODE VGSvcWinEnterCtrlDispatcher(void) +{ + if (!StartServiceCtrlDispatcher(&g_aServiceTable[0])) + return VGSvcError("StartServiceCtrlDispatcher: %u. Please start %s with option -f (foreground)!\n", + GetLastError(), g_pszProgName); + return RTEXITCODE_SUCCESS; +} + + +/** + * Event code to description. + * + * @returns String. + * @param dwEvent The event code. + */ +static const char *vgsvcWTSStateToString(DWORD dwEvent) +{ + switch (dwEvent) + { + case WTS_CONSOLE_CONNECT: return "A session was connected to the console terminal"; + case WTS_CONSOLE_DISCONNECT: return "A session was disconnected from the console terminal"; + case WTS_REMOTE_CONNECT: return "A session connected to the remote terminal"; + case WTS_REMOTE_DISCONNECT: return "A session was disconnected from the remote terminal"; + case WTS_SESSION_LOGON: return "A user has logged on to a session"; + case WTS_SESSION_LOGOFF: return "A user has logged off the session"; + case WTS_SESSION_LOCK: return "A session has been locked"; + case WTS_SESSION_UNLOCK: return "A session has been unlocked"; + case WTS_SESSION_REMOTE_CONTROL: return "A session has changed its remote controlled status"; +#ifdef WTS_SESSION_CREATE + case WTS_SESSION_CREATE: return "A session has been created"; +#endif +#ifdef WTS_SESSION_TERMINATE + case WTS_SESSION_TERMINATE: return "The session has been terminated"; +#endif + default: return "Uknonwn state"; + } +} + + +/** + * Common control handler. + * + * @returns Return code for NT5+. + * @param dwControl The control code. + */ +static DWORD vgsvcWinCtrlHandlerCommon(DWORD dwControl) +{ + DWORD rcRet = NO_ERROR; + switch (dwControl) + { + case SERVICE_CONTROL_INTERROGATE: + vgsvcWinSetStatus(g_dwWinServiceLastStatus, 0); + break; + + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + { + vgsvcWinSetStatus(SERVICE_STOP_PENDING, 0); + + int rc2 = VGSvcStopServices(); + if (RT_FAILURE(rc2)) + rcRet = ERROR_GEN_FAILURE; + else + { + rc2 = VGSvcReportStatus(VBoxGuestFacilityStatus_Terminated); + AssertRC(rc2); + } + + vgsvcWinSetStatus(SERVICE_STOPPED, 0); + break; + } + + default: + VGSvcVerbose(1, "Control handler: Function not implemented: %#x\n", dwControl); + rcRet = ERROR_CALL_NOT_IMPLEMENTED; + break; + } + + return rcRet; +} + + +/** + * Callback registered by RegisterServiceCtrlHandler on NT4 and earlier. + */ +static VOID WINAPI vgsvcWinCtrlHandlerNt4(DWORD dwControl) RT_NOTHROW_DEF +{ + VGSvcVerbose(2, "Control handler (NT4): dwControl=%#x\n", dwControl); + vgsvcWinCtrlHandlerCommon(dwControl); +} + + +/** + * Callback registered by RegisterServiceCtrlHandler on NT5 and later. + */ +static DWORD WINAPI +vgsvcWinCtrlHandlerNt5Plus(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) RT_NOTHROW_DEF +{ + VGSvcVerbose(2, "Control handler: dwControl=%#x, dwEventType=%#x\n", dwControl, dwEventType); + RT_NOREF1(lpContext); + + switch (dwControl) + { + default: + return vgsvcWinCtrlHandlerCommon(dwControl); + + case SERVICE_CONTROL_SESSIONCHANGE: /* Only Windows 2000 and up. */ + { + AssertPtr(lpEventData); + PWTSSESSION_NOTIFICATION pNotify = (PWTSSESSION_NOTIFICATION)lpEventData; + Assert(pNotify->cbSize == sizeof(WTSSESSION_NOTIFICATION)); + + VGSvcVerbose(1, "Control handler: %s (Session=%ld, Event=%#x)\n", + vgsvcWTSStateToString(dwEventType), pNotify->dwSessionId, dwEventType); + + /* Handle all events, regardless of dwEventType. */ + int rc2 = VGSvcVMInfoSignal(); + AssertRC(rc2); + + return NO_ERROR; + } + } +} + + +static void WINAPI vgsvcWinMain(DWORD argc, LPTSTR *argv) +{ + RT_NOREF2(argc, argv); + VGSvcVerbose(2, "Registering service control handler ...\n"); + if (g_pfnRegisterServiceCtrlHandlerExA) + g_hWinServiceStatus = g_pfnRegisterServiceCtrlHandlerExA(VBOXSERVICE_NAME, vgsvcWinCtrlHandlerNt5Plus, NULL); + else + g_hWinServiceStatus = RegisterServiceCtrlHandlerA(VBOXSERVICE_NAME, vgsvcWinCtrlHandlerNt4); + if (g_hWinServiceStatus != NULL) + { + VGSvcVerbose(2, "Service control handler registered.\n"); + vgsvcWinStart(); + } + else + { + DWORD dwErr = GetLastError(); + switch (dwErr) + { + case ERROR_INVALID_NAME: + VGSvcError("Invalid service name!\n"); + break; + case ERROR_SERVICE_DOES_NOT_EXIST: + VGSvcError("Service does not exist!\n"); + break; + default: + VGSvcError("Could not register service control handle! Error: %ld\n", dwErr); + break; + } + } +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxService.cpp b/src/VBox/Additions/common/VBoxService/VBoxService.cpp new file mode 100644 index 00000000..5b5dc5ee --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxService.cpp @@ -0,0 +1,1311 @@ +/* $Id: VBoxService.cpp $ */ +/** @file + * VBoxService - Guest Additions Service Skeleton. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc VBoxService + * + * VBoxService is a root daemon for implementing guest additions features. + * + * It is structured as one binary that contains many sub-services. The reason + * for this is partially historical and partially practical. The practical + * reason is that the VBoxService binary is typically statically linked, at + * least with IPRT and the guest library, so we save quite a lot of space having + * on single binary instead individual binaries for each sub-service and their + * helpers (currently up to 9 subservices and 8 helpers). The historical is + * simply that it started its life on OS/2 dreaming of conquring Windows next, + * so it kind of felt natural to have it all in one binary. + * + * Even if it's structured as a single binary, it is possible, by using command + * line options, to start each subservice as an individual process. + * + * Subservices: + * - @subpage pg_vgsvc_timesync "Time Synchronization" + * - @subpage pg_vgsvc_vminfo "VM Information" + * - @subpage pg_vgsvc_vmstats "VM Statistics" + * - @subpage pg_vgsvc_gstctrl "Guest Control" + * - @subpage pg_vgsvc_pagesharing "Page Sharing" + * - @subpage pg_vgsvc_memballoon "Memory Balooning" + * - @subpage pg_vgsvc_cpuhotplug "CPU Hot-Plugging" + * - @subpage pg_vgsvc_automount "Shared Folder Automounting" + * - @subpage pg_vgsvc_clipboard "Clipboard (OS/2 only)" + * + * Now, since the service predates a lot of stuff, including RTGetOpt, we're + * currently doing our own version of argument parsing here, which is kind of + * stupid. That will hopefully be cleaned up eventually. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +/** @todo LOG_GROUP*/ +#ifndef _MSC_VER +# include <unistd.h> +#endif +#ifndef RT_OS_WINDOWS +# include <errno.h> +# include <signal.h> +# ifdef RT_OS_OS2 +# define pthread_sigmask sigprocmask +# endif +#endif +#ifdef RT_OS_FREEBSD +# include <pthread.h> +#endif + +#include <package-generated.h> +#include "product-generated.h" + +#include <iprt/asm.h> +#include <iprt/buildconfig.h> +#include <iprt/initterm.h> +#include <iprt/file.h> +#ifdef DEBUG +# include <iprt/memtracker.h> +#endif +#include <iprt/env.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/system.h> +#include <iprt/thread.h> + +#include <VBox/err.h> +#include <VBox/log.h> + +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#ifdef VBOX_WITH_VBOXSERVICE_CONTROL +# include "VBoxServiceControl.h" +#endif +#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX +# include "VBoxServiceToolBox.h" +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The program name (derived from argv[0]). */ +char *g_pszProgName = (char *)""; +/** The current verbosity level. */ +unsigned g_cVerbosity = 0; +char g_szLogFile[RTPATH_MAX + 128] = ""; +char g_szPidFile[RTPATH_MAX] = ""; +/** Logging parameters. */ +/** @todo Make this configurable later. */ +static PRTLOGGER g_pLoggerRelease = NULL; +static uint32_t g_cHistory = 10; /* Enable log rotation, 10 files. */ +static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; /* Max 1 day per file. */ +static uint64_t g_uHistoryFileSize = 100 * _1M; /* Max 100MB per file. */ +/** Critical section for (debug) logging. */ +#ifdef DEBUG + RTCRITSECT g_csLog; +#endif +/** The default service interval (the -i | --interval) option). */ +uint32_t g_DefaultInterval = 0; +#ifdef RT_OS_WINDOWS +/** Signal shutdown to the Windows service thread. */ +static bool volatile g_fWindowsServiceShutdown; +/** Event the Windows service thread waits for shutdown. */ +static RTSEMEVENT g_hEvtWindowsService; +#endif + +/** + * The details of the services that has been compiled in. + */ +static struct +{ + /** Pointer to the service descriptor. */ + PCVBOXSERVICE pDesc; + /** The worker thread. NIL_RTTHREAD if it's the main thread. */ + RTTHREAD Thread; + /** Whether Pre-init was called. */ + bool fPreInited; + /** Shutdown indicator. */ + bool volatile fShutdown; + /** Indicator set by the service thread exiting. */ + bool volatile fStopped; + /** Whether the service was started or not. */ + bool fStarted; + /** Whether the service is enabled or not. */ + bool fEnabled; +} g_aServices[] = +{ +#ifdef VBOX_WITH_VBOXSERVICE_CONTROL + { &g_Control, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_VBOXSERVICE_TIMESYNC + { &g_TimeSync, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_VBOXSERVICE_CLIPBOARD + { &g_Clipboard, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_VBOXSERVICE_VMINFO + { &g_VMInfo, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_VBOXSERVICE_CPUHOTPLUG + { &g_CpuHotPlug, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_VBOXSERVICE_MANAGEMENT +# ifdef VBOX_WITH_MEMBALLOON + { &g_MemBalloon, NIL_RTTHREAD, false, false, false, false, true }, +# endif + { &g_VMStatistics, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#if defined(VBOX_WITH_VBOXSERVICE_PAGE_SHARING) + { &g_PageSharing, NIL_RTTHREAD, false, false, false, false, true }, +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS + { &g_AutoMount, NIL_RTTHREAD, false, false, false, false, true }, +#endif +}; + + +/* + * Default call-backs for services which do not need special behaviour. + */ + +/** + * @interface_method_impl{VBOXSERVICE,pfnPreInit, Default Implementation} + */ +DECLCALLBACK(int) VGSvcDefaultPreInit(void) +{ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption, Default Implementation} + */ +DECLCALLBACK(int) VGSvcDefaultOption(const char **ppszShort, int argc, + char **argv, int *pi) +{ + NOREF(ppszShort); + NOREF(argc); + NOREF(argv); + NOREF(pi); + + return -1; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit, Default Implementation} + */ +DECLCALLBACK(int) VGSvcDefaultInit(void) +{ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm, Default Implementation} + */ +DECLCALLBACK(void) VGSvcDefaultTerm(void) +{ + return; +} + + +/** + * @callback_method_impl{FNRTLOGPHASE, Release logger callback} + */ +static DECLCALLBACK(void) vgsvcLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog) +{ + /* Some introductory information. */ + static RTTIMESPEC s_TimeSpec; + char szTmp[256]; + if (enmPhase == RTLOGPHASE_BEGIN) + RTTimeNow(&s_TimeSpec); + RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp)); + + switch (enmPhase) + { + case RTLOGPHASE_BEGIN: + { + pfnLog(pLoggerRelease, + "VBoxService %s r%s (verbosity: %u) %s (%s %s) release log\n" + "Log opened %s\n", + RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, VBOX_BUILD_TARGET, + __DATE__, __TIME__, szTmp); + + int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Product: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Release: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Version: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Service Pack: %s\n", szTmp); + + /* the package type is interesting for Linux distributions */ + char szExecName[RTPATH_MAX]; + char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName)); + pfnLog(pLoggerRelease, + "Executable: %s\n" + "Process ID: %u\n" + "Package type: %s" +#ifdef VBOX_OSE + " (OSE)" +#endif + "\n", + pszExecName ? pszExecName : "unknown", + RTProcSelf(), + VBOX_PACKAGE_STRING); + break; + } + + case RTLOGPHASE_PREROTATE: + pfnLog(pLoggerRelease, "Log rotated - Log started %s\n", szTmp); + break; + + case RTLOGPHASE_POSTROTATE: + pfnLog(pLoggerRelease, "Log continuation - Log started %s\n", szTmp); + break; + + case RTLOGPHASE_END: + pfnLog(pLoggerRelease, "End of log file - Log started %s\n", szTmp); + break; + + default: + /* nothing */ + break; + } +} + + +/** + * Creates the default release logger outputting to the specified file. + * + * Pass NULL to disabled logging. + * + * @return IPRT status code. + * @param pszLogFile Filename for log output. NULL disables logging + * (r=bird: No, it doesn't!). + */ +int VGSvcLogCreate(const char *pszLogFile) +{ + /* Create release logger (stdout + file). */ + static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; + RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME; +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + fFlags |= RTLOGFLAGS_USECRLF; +#endif + int rc = RTLogCreateEx(&g_pLoggerRelease, "VBOXSERVICE_RELEASE_LOG", fFlags, "all", + RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX /*cMaxEntriesPerGroup*/, + 0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT | RTLOGDEST_USER, + vgsvcLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime, + NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/, + NULL /*pErrInfo*/, "%s", pszLogFile ? pszLogFile : ""); + if (RT_SUCCESS(rc)) + { + /* register this logger as the release logger */ + RTLogRelSetDefaultInstance(g_pLoggerRelease); + + /* Explicitly flush the log in case of VBOXSERVICE_RELEASE_LOG=buffered. */ + RTLogFlush(g_pLoggerRelease); + } + + return rc; +} + + +/** + * Logs a verbose message. + * + * @param pszFormat The message text. + * @param va Format arguments. + */ +void VGSvcLogV(const char *pszFormat, va_list va) +{ +#ifdef DEBUG + int rc = RTCritSectEnter(&g_csLog); + if (RT_SUCCESS(rc)) + { +#endif + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, va); + + AssertPtr(psz); + LogRel(("%s", psz)); + + RTStrFree(psz); +#ifdef DEBUG + RTCritSectLeave(&g_csLog); + } +#endif +} + + +/** + * Destroys the currently active logging instance. + */ +void VGSvcLogDestroy(void) +{ + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); +} + + +/** + * Displays the program usage message. + * + * @returns 1. + */ +static int vgsvcUsage(void) +{ + RTPrintf("Usage: %s [-f|--foreground] [-v|--verbose] [-l|--logfile <file>]\n" + " [-p|--pidfile <file>] [-i|--interval <seconds>]\n" + " [--disable-<service>] [--enable-<service>]\n" + " [--only-<service>] [-h|-?|--help]\n", g_pszProgName); +#ifdef RT_OS_WINDOWS + RTPrintf(" [-r|--register] [-u|--unregister]\n"); +#endif + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + if (g_aServices[j].pDesc->pszUsage) + RTPrintf("%s\n", g_aServices[j].pDesc->pszUsage); + RTPrintf("\n" + "Options:\n" + " -i | --interval The default interval.\n" + " -f | --foreground Don't daemonize the program. For debugging.\n" + " -l | --logfile <file> Enables logging to a file.\n" + " -p | --pidfile <file> Write the process ID to a file.\n" + " -v | --verbose Increment the verbosity level. For debugging.\n" + " -V | --version Show version information.\n" + " -h | -? | --help Show this message and exit with status 1.\n" + ); +#ifdef RT_OS_WINDOWS + RTPrintf(" -r | --register Installs the service.\n" + " -u | --unregister Uninstall service.\n"); +#endif + + RTPrintf("\n" + "Service-specific options:\n"); + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + { + RTPrintf(" --enable-%-14s Enables the %s service. (default)\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName); + RTPrintf(" --disable-%-13s Disables the %s service.\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName); + RTPrintf(" --only-%-16s Only enables the %s service.\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName); + if (g_aServices[j].pDesc->pszOptions) + RTPrintf("%s", g_aServices[j].pDesc->pszOptions); + } + RTPrintf("\n" + " Copyright (C) 2009-" VBOX_C_YEAR " " VBOX_VENDOR "\n"); + + return 1; +} + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE. + * @param pszFormat The message text. + * @param ... Format arguments. + */ +RTEXITCODE VGSvcError(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, args); + va_end(args); + + AssertPtr(psz); + LogRel(("Error: %s", psz)); + + RTStrFree(psz); + + return RTEXITCODE_FAILURE; +} + + +/** + * Displays a verbose message based on the currently + * set global verbosity level. + * + * @param iLevel Minimum log level required to display this message. + * @param pszFormat The message text. + * @param ... Format arguments. + */ +void VGSvcVerbose(unsigned iLevel, const char *pszFormat, ...) +{ + if (iLevel <= g_cVerbosity) + { + va_list va; + va_start(va, pszFormat); + VGSvcLogV(pszFormat, va); + va_end(va); + } +} + + +/** + * Reports the current VBoxService status to the host. + * + * This makes sure that the Failed state is sticky. + * + * @return IPRT status code. + * @param enmStatus Status to report to the host. + */ +int VGSvcReportStatus(VBoxGuestFacilityStatus enmStatus) +{ + /* + * VBoxGuestFacilityStatus_Failed is sticky. + */ + static VBoxGuestFacilityStatus s_enmLastStatus = VBoxGuestFacilityStatus_Inactive; + VGSvcVerbose(4, "Setting VBoxService status to %u\n", enmStatus); + if (s_enmLastStatus != VBoxGuestFacilityStatus_Failed) + { + int rc = VbglR3ReportAdditionsStatus(VBoxGuestFacilityType_VBoxService, enmStatus, 0 /* Flags */); + if (RT_FAILURE(rc)) + { + VGSvcError("Could not report VBoxService status (%u), rc=%Rrc\n", enmStatus, rc); + return rc; + } + s_enmLastStatus = enmStatus; + } + return VINF_SUCCESS; +} + + +/** + * Gets a 32-bit value argument. + * @todo Get rid of this and VGSvcArgString() as soon as we have RTOpt handling. + * + * @returns 0 on success, non-zero exit code on error. + * @param argc The argument count. + * @param argv The argument vector + * @param psz Where in *pi to start looking for the value argument. + * @param pi Where to find and perhaps update the argument index. + * @param pu32 Where to store the 32-bit value. + * @param u32Min The minimum value. + * @param u32Max The maximum value. + */ +int VGSvcArgUInt32(int argc, char **argv, const char *psz, int *pi, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max) +{ + if (*psz == ':' || *psz == '=') + psz++; + if (!*psz) + { + if (*pi + 1 >= argc) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing value for the '%s' argument\n", argv[*pi]); + psz = argv[++*pi]; + } + + char *pszNext; + int rc = RTStrToUInt32Ex(psz, &pszNext, 0, pu32); + if (RT_FAILURE(rc) || *pszNext) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Failed to convert interval '%s' to a number\n", psz); + if (*pu32 < u32Min || *pu32 > u32Max) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The timesync interval of %RU32 seconds is out of range [%RU32..%RU32]\n", + *pu32, u32Min, u32Max); + return 0; +} + + +/** @todo Get rid of this and VGSvcArgUInt32() as soon as we have RTOpt handling. */ +static int vgsvcArgString(int argc, char **argv, const char *psz, int *pi, char *pszBuf, size_t cbBuf) +{ + AssertPtrReturn(pszBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + + if (*psz == ':' || *psz == '=') + psz++; + if (!*psz) + { + if (*pi + 1 >= argc) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing string for the '%s' argument\n", argv[*pi]); + psz = argv[++*pi]; + } + + if (!RTStrPrintf(pszBuf, cbBuf, "%s", psz)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "String for '%s' argument too big\n", argv[*pi]); + return 0; +} + + +/** + * The service thread. + * + * @returns Whatever the worker function returns. + * @param ThreadSelf My thread handle. + * @param pvUser The service index. + */ +static DECLCALLBACK(int) vgsvcThread(RTTHREAD ThreadSelf, void *pvUser) +{ + const unsigned i = (uintptr_t)pvUser; + +#ifndef RT_OS_WINDOWS + /* + * Block all signals for this thread. Only the main thread will handle signals. + */ + sigset_t signalMask; + sigfillset(&signalMask); + pthread_sigmask(SIG_BLOCK, &signalMask, NULL); +#endif + + int rc = g_aServices[i].pDesc->pfnWorker(&g_aServices[i].fShutdown); + ASMAtomicXchgBool(&g_aServices[i].fShutdown, true); + RTThreadUserSignal(ThreadSelf); + return rc; +} + + +/** + * Lazily calls the pfnPreInit method on each service. + * + * @returns VBox status code, error message displayed. + */ +static RTEXITCODE vgsvcLazyPreInit(void) +{ + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + if (!g_aServices[j].fPreInited) + { + int rc = g_aServices[j].pDesc->pfnPreInit(); + if (RT_FAILURE(rc)) + return VGSvcError("Service '%s' failed pre-init: %Rrc\n", g_aServices[j].pDesc->pszName, rc); + g_aServices[j].fPreInited = true; + } + return RTEXITCODE_SUCCESS; +} + + +/** + * Count the number of enabled services. + */ +static unsigned vgsvcCountEnabledServices(void) +{ + unsigned cEnabled = 0; + for (unsigned i = 0; i < RT_ELEMENTS(g_aServices); i++) + cEnabled += g_aServices[i].fEnabled; + return cEnabled; +} + + +#ifdef RT_OS_WINDOWS +/** + * Console control event callback. + * + * @returns TRUE if handled, FALSE if not. + * @param dwCtrlType The control event type. + * + * @remarks This is generally called on a new thread, so we're racing every + * other thread in the process. + */ +static BOOL WINAPI vgsvcWinConsoleControlHandler(DWORD dwCtrlType) RT_NOTHROW_DEF +{ + int rc = VINF_SUCCESS; + bool fEventHandled = FALSE; + switch (dwCtrlType) + { + /* User pressed CTRL+C or CTRL+BREAK or an external event was sent + * via GenerateConsoleCtrlEvent(). */ + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_C_EVENT: + VGSvcVerbose(2, "ControlHandler: Received break/close event\n"); + rc = VGSvcStopServices(); + fEventHandled = TRUE; + break; + default: + break; + /** @todo Add other events here. */ + } + + if (RT_FAILURE(rc)) + VGSvcError("ControlHandler: Event %ld handled with error rc=%Rrc\n", + dwCtrlType, rc); + return fEventHandled; +} +#endif /* RT_OS_WINDOWS */ + + +/** + * Starts the service. + * + * @returns VBox status code, errors are fully bitched. + * + * @remarks Also called from VBoxService-win.cpp, thus not static. + */ +int VGSvcStartServices(void) +{ + int rc; + + VGSvcReportStatus(VBoxGuestFacilityStatus_Init); + + /* + * Initialize the services. + */ + VGSvcVerbose(2, "Initializing services ...\n"); + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + if (g_aServices[j].fEnabled) + { + rc = g_aServices[j].pDesc->pfnInit(); + if (RT_FAILURE(rc)) + { + if (rc != VERR_SERVICE_DISABLED) + { + VGSvcError("Service '%s' failed to initialize: %Rrc\n", g_aServices[j].pDesc->pszName, rc); + VGSvcReportStatus(VBoxGuestFacilityStatus_Failed); + return rc; + } + + g_aServices[j].fEnabled = false; + VGSvcVerbose(0, "Service '%s' was disabled because of missing functionality\n", g_aServices[j].pDesc->pszName); + } + } + + /* + * Start the service(s). + */ + VGSvcVerbose(2, "Starting services ...\n"); + rc = VINF_SUCCESS; + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + { + if (!g_aServices[j].fEnabled) + continue; + + VGSvcVerbose(2, "Starting service '%s' ...\n", g_aServices[j].pDesc->pszName); + rc = RTThreadCreate(&g_aServices[j].Thread, vgsvcThread, (void *)(uintptr_t)j, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, g_aServices[j].pDesc->pszName); + if (RT_FAILURE(rc)) + { + VGSvcError("RTThreadCreate failed, rc=%Rrc\n", rc); + break; + } + g_aServices[j].fStarted = true; + + /* Wait for the thread to initialize. */ + /** @todo There is a race between waiting and checking + * the fShutdown flag of a thread here and processing + * the thread's actual worker loop. If the thread decides + * to exit the loop before we skipped the fShutdown check + * below the service will fail to start! */ + /** @todo This presumably means either a one-shot service or that + * something has gone wrong. In the second case treating it as failure + * to start is probably right, so we need a way to signal the first + * rather than leaving the idle thread hanging around. A flag in the + * service description? */ + RTThreadUserWait(g_aServices[j].Thread, 60 * 1000); + if (g_aServices[j].fShutdown) + { + VGSvcError("Service '%s' failed to start!\n", g_aServices[j].pDesc->pszName); + rc = VERR_GENERAL_FAILURE; + } + } + + if (RT_SUCCESS(rc)) + VGSvcVerbose(1, "All services started.\n"); + else + { + VGSvcError("An error occcurred while the services!\n"); + VGSvcReportStatus(VBoxGuestFacilityStatus_Failed); + } + return rc; +} + + +/** + * Stops and terminates the services. + * + * This should be called even when VBoxServiceStartServices fails so it can + * clean up anything that we succeeded in starting. + * + * @remarks Also called from VBoxService-win.cpp, thus not static. + */ +int VGSvcStopServices(void) +{ + VGSvcReportStatus(VBoxGuestFacilityStatus_Terminating); + + /* + * Signal all the services. + */ + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + ASMAtomicWriteBool(&g_aServices[j].fShutdown, true); + + /* + * Do the pfnStop callback on all running services. + */ + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + if (g_aServices[j].fStarted) + { + VGSvcVerbose(3, "Calling stop function for service '%s' ...\n", g_aServices[j].pDesc->pszName); + g_aServices[j].pDesc->pfnStop(); + } + + VGSvcVerbose(3, "All stop functions for services called\n"); + + /* + * Wait for all the service threads to complete. + */ + int rc = VINF_SUCCESS; + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + { + if (!g_aServices[j].fEnabled) /* Only stop services which were started before. */ + continue; + if (g_aServices[j].Thread != NIL_RTTHREAD) + { + VGSvcVerbose(2, "Waiting for service '%s' to stop ...\n", g_aServices[j].pDesc->pszName); + int rc2 = VINF_SUCCESS; + for (int i = 0; i < 30; i++) /* Wait 30 seconds in total */ + { + rc2 = RTThreadWait(g_aServices[j].Thread, 1000 /* Wait 1 second */, NULL); + if (RT_SUCCESS(rc2)) + break; +#ifdef RT_OS_WINDOWS + /* Notify SCM that it takes a bit longer ... */ + VGSvcWinSetStopPendingStatus(i + j*32); +#endif + } + if (RT_FAILURE(rc2)) + { + VGSvcError("Service '%s' failed to stop. (%Rrc)\n", g_aServices[j].pDesc->pszName, rc2); + rc = rc2; + } + } + VGSvcVerbose(3, "Terminating service '%s' (%d) ...\n", g_aServices[j].pDesc->pszName, j); + g_aServices[j].pDesc->pfnTerm(); + } + +#ifdef RT_OS_WINDOWS + /* + * Wake up and tell the main() thread that we're shutting down (it's + * sleeping in VBoxServiceMainWait). + */ + ASMAtomicWriteBool(&g_fWindowsServiceShutdown, true); + if (g_hEvtWindowsService != NIL_RTSEMEVENT) + { + VGSvcVerbose(3, "Stopping the main thread...\n"); + int rc2 = RTSemEventSignal(g_hEvtWindowsService); + AssertRC(rc2); + } +#endif + + VGSvcVerbose(2, "Stopping services returning: %Rrc\n", rc); + VGSvcReportStatus(RT_SUCCESS(rc) ? VBoxGuestFacilityStatus_Paused : VBoxGuestFacilityStatus_Failed); + return rc; +} + + +/** + * Block the main thread until the service shuts down. + * + * @remarks Also called from VBoxService-win.cpp, thus not static. + */ +void VGSvcMainWait(void) +{ + int rc; + + VGSvcReportStatus(VBoxGuestFacilityStatus_Active); + +#ifdef RT_OS_WINDOWS + /* + * Wait for the semaphore to be signalled. + */ + VGSvcVerbose(1, "Waiting in main thread\n"); + rc = RTSemEventCreate(&g_hEvtWindowsService); + AssertRC(rc); + while (!ASMAtomicReadBool(&g_fWindowsServiceShutdown)) + { + rc = RTSemEventWait(g_hEvtWindowsService, RT_INDEFINITE_WAIT); + AssertRC(rc); + } + RTSemEventDestroy(g_hEvtWindowsService); + g_hEvtWindowsService = NIL_RTSEMEVENT; +#else + /* + * Wait explicitly for a HUP, INT, QUIT, ABRT or TERM signal, blocking + * all important signals. + * + * The annoying EINTR/ERESTART loop is for the benefit of Solaris where + * sigwait returns when we receive a SIGCHLD. Kind of makes sense since + * the signal has to be delivered... Anyway, darwin (10.9.5) has a much + * worse way of dealing with SIGCHLD, apparently it'll just return any + * of the signals we're waiting on when SIGCHLD becomes pending on this + * thread. So, we wait for SIGCHLD here and ignores it. + */ + sigset_t signalMask; + sigemptyset(&signalMask); + sigaddset(&signalMask, SIGHUP); + sigaddset(&signalMask, SIGINT); + sigaddset(&signalMask, SIGQUIT); + sigaddset(&signalMask, SIGABRT); + sigaddset(&signalMask, SIGTERM); + sigaddset(&signalMask, SIGCHLD); + pthread_sigmask(SIG_BLOCK, &signalMask, NULL); + + int iSignal; + do + { + iSignal = -1; + rc = sigwait(&signalMask, &iSignal); + } + while ( rc == EINTR +# ifdef ERESTART + || rc == ERESTART +# endif + || iSignal == SIGCHLD + ); + + VGSvcVerbose(3, "VGSvcMainWait: Received signal %d (rc=%d)\n", iSignal, rc); +#endif /* !RT_OS_WINDOWS */ +} + + +/** + * Report VbglR3InitUser / VbglR3Init failure. + * + * @returns RTEXITCODE_FAILURE + * @param rcVbgl The failing status code. + */ +static RTEXITCODE vbglInitFailure(int rcVbgl) +{ + if (rcVbgl == VERR_ACCESS_DENIED) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + "Insufficient privileges to start %s! Please start with Administrator/root privileges!\n", + g_pszProgName); + return RTMsgErrorExit(RTEXITCODE_FAILURE, "VbglR3Init failed with rc=%Rrc\n", rcVbgl); +} + + +int main(int argc, char **argv) +{ + RTEXITCODE rcExit; + + /* + * Init globals and such. + * + * Note! The --utf8-argv stuff is an internal hack to avoid locale configuration + * issues preventing us from passing non-ASCII string to child processes. + */ + uint32_t fIprtFlags = 0; +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV + if (argc > 1 && strcmp(argv[1], VBOXSERVICE_ARG1_UTF8_ARGV) == 0) + { + argv[1] = argv[0]; + argv++; + argc--; + fIprtFlags |= RTR3INIT_FLAGS_UTF8_ARGV; + } +#endif + int rc = RTR3InitExe(argc, &argv, fIprtFlags); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + g_pszProgName = RTPathFilename(argv[0]); +#ifdef RT_OS_WINDOWS + VGSvcWinResolveApis(); +#endif +#ifdef DEBUG + rc = RTCritSectInit(&g_csLog); + AssertRC(rc); +#endif + +#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX + /* + * Run toolbox code before all other stuff since these things are simpler + * shell/file/text utility like programs that just happens to be inside + * VBoxService and shouldn't be subject to /dev/vboxguest, pid-files and + * global mutex restrictions. + */ + if (VGSvcToolboxMain(argc, argv, &rcExit)) + return rcExit; +#endif + + bool fUserSession = false; +#ifdef VBOX_WITH_VBOXSERVICE_CONTROL + /* + * Check if we're the specially spawned VBoxService.exe process that + * handles a guest control session. + */ + if ( argc >= 2 + && !RTStrICmp(argv[1], VBOXSERVICECTRLSESSION_GETOPT_PREFIX)) + fUserSession = true; +#endif + + /* + * Connect to the kernel part before daemonizing and *before* we do the sub-service + * pre-init just in case one of services needs do to some initial stuff with it. + * + * However, we do not fail till after we've parsed arguments, because that will + * prevent useful stuff like --help, --register, --unregister and --version from + * working when the driver hasn't been installed/loaded yet. + */ + int const rcVbgl = fUserSession ? VbglR3InitUser() : VbglR3Init(); + +#ifdef RT_OS_WINDOWS + /* + * Check if we're the specially spawned VBoxService.exe process that + * handles page fusion. This saves an extra statically linked executable. + */ + if ( argc == 2 + && !RTStrICmp(argv[1], "pagefusion")) + { + if (RT_SUCCESS(rcVbgl)) + return VGSvcPageSharingWorkerChild(); + return vbglInitFailure(rcVbgl); + } +#endif + +#ifdef VBOX_WITH_VBOXSERVICE_CONTROL + /* + * Check if we're the specially spawned VBoxService.exe process that + * handles a guest control session. + */ + if (fUserSession) + { + if (RT_SUCCESS(rcVbgl)) + return VGSvcGstCtrlSessionSpawnInit(argc, argv); + return vbglInitFailure(rcVbgl); + } +#endif + + /* + * Parse the arguments. + * + * Note! This code predates RTGetOpt, thus the manual parsing. + */ + bool fDaemonize = true; + bool fDaemonized = false; + for (int i = 1; i < argc; i++) + { + const char *psz = argv[i]; + if (*psz != '-') + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown argument '%s'\n", psz); + psz++; + + /* translate long argument to short */ + if (*psz == '-') + { + psz++; + size_t cch = strlen(psz); +#define MATCHES(strconst) ( cch == sizeof(strconst) - 1 \ + && !memcmp(psz, strconst, sizeof(strconst) - 1) ) + if (MATCHES("foreground")) + psz = "f"; + else if (MATCHES("verbose")) + psz = "v"; + else if (MATCHES("version")) + psz = "V"; + else if (MATCHES("help")) + psz = "h"; + else if (MATCHES("interval")) + psz = "i"; +#ifdef RT_OS_WINDOWS + else if (MATCHES("register")) + psz = "r"; + else if (MATCHES("unregister")) + psz = "u"; +#endif + else if (MATCHES("logfile")) + psz = "l"; + else if (MATCHES("pidfile")) + psz = "p"; + else if (MATCHES("daemonized")) + { + fDaemonized = true; + continue; + } + else + { + bool fFound = false; + + if (cch > sizeof("enable-") && !memcmp(psz, RT_STR_TUPLE("enable-"))) + for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++) + if ((fFound = !RTStrICmp(psz + sizeof("enable-") - 1, g_aServices[j].pDesc->pszName))) + g_aServices[j].fEnabled = true; + + if (cch > sizeof("disable-") && !memcmp(psz, RT_STR_TUPLE("disable-"))) + for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++) + if ((fFound = !RTStrICmp(psz + sizeof("disable-") - 1, g_aServices[j].pDesc->pszName))) + g_aServices[j].fEnabled = false; + + if (cch > sizeof("only-") && !memcmp(psz, RT_STR_TUPLE("only-"))) + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + { + g_aServices[j].fEnabled = !RTStrICmp(psz + sizeof("only-") - 1, g_aServices[j].pDesc->pszName); + if (g_aServices[j].fEnabled) + fFound = true; + } + + if (!fFound) + { + rcExit = vgsvcLazyPreInit(); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++) + { + rc = g_aServices[j].pDesc->pfnOption(NULL, argc, argv, &i); + fFound = rc == VINF_SUCCESS; + if (fFound) + break; + if (rc != -1) + return rc; + } + } + if (!fFound) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown option '%s'\n", argv[i]); + continue; + } +#undef MATCHES + } + + /* handle the string of short options. */ + do + { + switch (*psz) + { + case 'i': + rc = VGSvcArgUInt32(argc, argv, psz + 1, &i, &g_DefaultInterval, 1, (UINT32_MAX / 1000) - 1); + if (rc) + return rc; + psz = NULL; + break; + + case 'f': + fDaemonize = false; + break; + + case 'v': + g_cVerbosity++; + break; + + case 'V': + RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr()); + return RTEXITCODE_SUCCESS; + + case 'h': + case '?': + return vgsvcUsage(); + +#ifdef RT_OS_WINDOWS + case 'r': + return VGSvcWinInstall(); + + case 'u': + return VGSvcWinUninstall(); +#endif + + case 'l': + { + rc = vgsvcArgString(argc, argv, psz + 1, &i, g_szLogFile, sizeof(g_szLogFile)); + if (rc) + return rc; + psz = NULL; + break; + } + + case 'p': + { + rc = vgsvcArgString(argc, argv, psz + 1, &i, g_szPidFile, sizeof(g_szPidFile)); + if (rc) + return rc; + psz = NULL; + break; + } + + default: + { + rcExit = vgsvcLazyPreInit(); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + bool fFound = false; + for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) + { + rc = g_aServices[j].pDesc->pfnOption(&psz, argc, argv, &i); + fFound = rc == VINF_SUCCESS; + if (fFound) + break; + if (rc != -1) + return rc; + } + if (!fFound) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown option '%c' (%s)\n", *psz, argv[i]); + break; + } + } + } while (psz && *++psz); + } + + /* Now we can report the VBGL failure. */ + if (RT_FAILURE(rcVbgl)) + return vbglInitFailure(rcVbgl); + + /* Check that at least one service is enabled. */ + if (vgsvcCountEnabledServices() == 0) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "At least one service must be enabled\n"); + + rc = VGSvcLogCreate(g_szLogFile[0] ? g_szLogFile : NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create release log '%s', rc=%Rrc\n", + g_szLogFile[0] ? g_szLogFile : "<None>", rc); + + /* Call pre-init if we didn't do it already. */ + rcExit = vgsvcLazyPreInit(); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + +#ifdef VBOX_WITH_VBOXSERVICE_DRMRESIZE +# ifdef RT_OS_LINUX + rc = VbglR3DrmClientStart(); + if (RT_FAILURE(rc)) + VGSvcVerbose(0, "VMSVGA DRM resizing client not started, rc=%Rrc\n", rc); +# endif /* RT_OS_LINUX */ +#endif /* VBOX_WITH_VBOXSERVICE_DRMRESIZE */ + +#ifdef RT_OS_WINDOWS + /* + * Make sure only one instance of VBoxService runs at a time. Create a + * global mutex for that. + * + * Note! The \\Global\ namespace was introduced with Win2K, thus the + * version check. + * Note! If the mutex exists CreateMutex will open it and set last error to + * ERROR_ALREADY_EXISTS. + */ + OSVERSIONINFOEX OSInfoEx; + RT_ZERO(OSInfoEx); + OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + + SetLastError(NO_ERROR); + HANDLE hMutexAppRunning; + if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(5,0,0)) /* Windows 2000 */ + hMutexAppRunning = CreateMutexW(NULL, FALSE, L"Global\\" RT_CONCAT(L,VBOXSERVICE_NAME)); + else + hMutexAppRunning = CreateMutexW(NULL, FALSE, RT_CONCAT(L,VBOXSERVICE_NAME)); + if (hMutexAppRunning == NULL) + { + DWORD dwErr = GetLastError(); + if ( dwErr == ERROR_ALREADY_EXISTS + || dwErr == ERROR_ACCESS_DENIED) + { + VGSvcError("%s is already running! Terminating.\n", g_pszProgName); + return RTEXITCODE_FAILURE; + } + + VGSvcError("CreateMutex failed with last error %u! Terminating.\n", GetLastError()); + return RTEXITCODE_FAILURE; + } + +#else /* !RT_OS_WINDOWS */ + /* On other OSes we have PID file support provided by the actual service definitions / service wrapper scripts, + * like vboxadd-service.sh on Linux or vboxservice.xml on Solaris. */ +#endif /* !RT_OS_WINDOWS */ + + VGSvcVerbose(0, "%s r%s started. Verbose level = %d\n", RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity); + + /* + * Daemonize if requested. + */ + if (fDaemonize && !fDaemonized) + { +#ifdef RT_OS_WINDOWS + VGSvcVerbose(2, "Starting service dispatcher ...\n"); + rcExit = VGSvcWinEnterCtrlDispatcher(); +#else + VGSvcVerbose(1, "Daemonizing...\n"); + rc = VbglR3Daemonize(false /* fNoChDir */, false /* fNoClose */, + false /* fRespawn */, NULL /* pcRespawn */); + if (RT_FAILURE(rc)) + return VGSvcError("Daemon failed: %Rrc\n", rc); + /* in-child */ +#endif + } +#ifdef RT_OS_WINDOWS + else +#endif + { + /* + * Windows: We're running the service as a console application now. Start the + * services, enter the main thread's run loop and stop them again + * when it returns. + * + * POSIX: This is used for both daemons and console runs. Start all services + * and return immediately. + */ +#ifdef RT_OS_WINDOWS + /* Install console control handler. */ + if (!SetConsoleCtrlHandler(vgsvcWinConsoleControlHandler, TRUE /* Add handler */)) + { + VGSvcError("Unable to add console control handler, error=%ld\n", GetLastError()); + /* Just skip this error, not critical. */ + } +#endif /* RT_OS_WINDOWS */ + rc = VGSvcStartServices(); + RTFILE hPidFile = NIL_RTFILE; + if (RT_SUCCESS(rc)) + if (g_szPidFile[0]) + rc = VbglR3PidFile(g_szPidFile, &hPidFile); + rcExit = RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; + if (RT_SUCCESS(rc)) + VGSvcMainWait(); + if (g_szPidFile[0] && hPidFile != NIL_RTFILE) + VbglR3ClosePidFile(g_szPidFile, hPidFile); +#ifdef RT_OS_WINDOWS + /* Uninstall console control handler. */ + if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */)) + { + VGSvcError("Unable to remove console control handler, error=%ld\n", GetLastError()); + /* Just skip this error, not critical. */ + } +#else /* !RT_OS_WINDOWS */ + /* On Windows - since we're running as a console application - we already stopped all services + * through the console control handler. So only do the stopping of services here on other platforms + * where the break/shutdown/whatever signal was just received. */ + VGSvcStopServices(); +#endif /* RT_OS_WINDOWS */ + } + VGSvcReportStatus(VBoxGuestFacilityStatus_Terminated); + +#ifdef RT_OS_WINDOWS + /* + * Cleanup mutex. + */ + CloseHandle(hMutexAppRunning); +#endif + + VGSvcVerbose(0, "Ended.\n"); + +#ifdef DEBUG + RTCritSectDelete(&g_csLog); + //RTMemTrackerDumpAllToStdOut(); +#endif + + VGSvcLogDestroy(); + + return rcExit; +} diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp new file mode 100644 index 00000000..75bf718a --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp @@ -0,0 +1,2194 @@ +/* $Id: VBoxServiceAutoMount.cpp $ */ +/** @file + * VBoxService - Auto-mounting for Shared Folders, only Linux & Solaris atm. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc_automount VBoxService - Shared Folder Automounter + * + * The Shared Folder Automounter subservice mounts shared folders upon request + * from the host. + * + * This retrieves shared folder automount requests from Main via the VMMDev. + * The current implemention only does this once, for some inexplicable reason, + * so the run-time addition of automounted shared folders are not heeded. + * + * This subservice is only used on linux and solaris. On Windows the current + * thinking is this is better of done from VBoxTray, some one argue that for + * drive letter assigned shared folders it would be better to do some magic here + * (obviously not involving NDAddConnection). + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/semaphore.h> +#include <iprt/sort.h> +#include <iprt/string.h> +#include <VBox/err.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/shflsvc.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" + +#ifdef RT_OS_WINDOWS +#elif defined(RT_OS_OS2) +# define INCL_DOSFILEMGR +# define INCL_ERRORS +# define OS2EMX_PLAIN_CHAR +# include <os2emx.h> +#else +# include <errno.h> +# include <grp.h> +# include <sys/mount.h> +# ifdef RT_OS_SOLARIS +# include <sys/mntent.h> +# include <sys/mnttab.h> +# include <sys/vfs.h> +# elif defined(RT_OS_LINUX) +# include <mntent.h> +# include <paths.h> +# include <sys/utsname.h> +RT_C_DECLS_BEGIN +# include "../../linux/sharedfolders/vbsfmount.h" +RT_C_DECLS_END +# else +# error "Port me!" +# endif +# include <unistd.h> +#endif + + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR + * Default mount directory (unix only). + */ +#ifndef VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR +# define VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR "/media" +#endif + +/** @def VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX + * Default mount prefix (unix only). + */ +#ifndef VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX +# define VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX "sf_" +#endif + +#ifndef _PATH_MOUNTED +# ifdef RT_OS_SOLARIS +# define _PATH_MOUNTED "/etc/mnttab" +# else +# define _PATH_MOUNTED "/etc/mtab" +# endif +#endif + +/** @def VBOXSERVICE_AUTOMOUNT_MIQF + * The drive letter / path mount point flag. */ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) +# define VBOXSERVICE_AUTOMOUNT_MIQF SHFL_MIQF_DRIVE_LETTER +#else +# define VBOXSERVICE_AUTOMOUNT_MIQF SHFL_MIQF_PATH +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Automounter mount table entry. + * + * This holds the information returned by SHFL_FN_QUERY_MAP_INFO and + * additional mount state info. We only keep entries for mounted mappings. + */ +typedef struct VBSVCAUTOMOUNTERENTRY +{ + /** The root ID. */ + uint32_t idRoot; + /** The root ID version. */ + uint32_t uRootIdVersion; + /** Map info flags, SHFL_MIF_XXX. */ + uint64_t fFlags; + /** The shared folder (mapping) name. */ + char *pszName; + /** The configured mount point, NULL if none. */ + char *pszMountPoint; + /** The actual mount point, NULL if not mount. */ + char *pszActualMountPoint; +} VBSVCAUTOMOUNTERENTRY; +/** Pointer to an automounter entry. */ +typedef VBSVCAUTOMOUNTERENTRY *PVBSVCAUTOMOUNTERENTRY; + +/** Automounter mount table. */ +typedef struct VBSVCAUTOMOUNTERTABLE +{ + /** Current number of entries in the array. */ + uint32_t cEntries; + /** Max number of entries the array can hold w/o growing it. */ + uint32_t cAllocated; + /** Pointer to an array of entry pointers. */ + PVBSVCAUTOMOUNTERENTRY *papEntries; +} VBSVCAUTOMOUNTERTABLE; +/** Pointer to an automounter mount table. */ +typedef VBSVCAUTOMOUNTERTABLE *PVBSVCAUTOMOUNTERTABLE; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_hAutoMountEvent = NIL_RTSEMEVENTMULTI; +/** The Shared Folders service client ID. */ +static uint32_t g_idClientSharedFolders = 0; +/** Set if we can wait on changes to the mappings. */ +static bool g_fHostSupportsWaitAndInfoQuery = false; + +#ifdef RT_OS_OS2 +/** The attachment tag we use to identify attchments that belongs to us. */ +static char const g_szTag[] = "VBoxAutomounter"; +#elif defined(RT_OS_LINUX) +/** Tag option value that lets us identify mounts that belongs to us. */ +static char const g_szTag[] = "VBoxAutomounter"; +#elif defined(RT_OS_SOLARIS) +/** Dummy mount option that lets us identify mounts that belongs to us. */ +static char const g_szTag[] = "VBoxAutomounter"; +#endif + + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vbsvcAutomounterInit(void) +{ + VGSvcVerbose(3, "vbsvcAutomounterInit\n"); + + int rc = RTSemEventMultiCreate(&g_hAutoMountEvent); + AssertRCReturn(rc, rc); + + rc = VbglR3SharedFolderConnect(&g_idClientSharedFolders); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "vbsvcAutomounterInit: Service Client ID: %#x\n", g_idClientSharedFolders); + g_fHostSupportsWaitAndInfoQuery = RT_SUCCESS(VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders)); + } + else + { + /* If the service was not found, we disable this service without + causing VBoxService to fail. */ + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ + { + VGSvcVerbose(0, "vbsvcAutomounterInit: Shared Folders service is not available\n"); + rc = VERR_SERVICE_DISABLED; + } + else + VGSvcError("Control: Failed to connect to the Shared Folders service! Error: %Rrc\n", rc); + RTSemEventMultiDestroy(g_hAutoMountEvent); + g_hAutoMountEvent = NIL_RTSEMEVENTMULTI; + } + + return rc; +} + + +#if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) /* The old code: */ + +/** + * @todo Integrate into RTFsQueryMountpoint()? + */ +static bool vbsvcAutoMountShareIsMountedOld(const char *pszShare, char *pszMountPoint, size_t cbMountPoint) +{ + AssertPtrReturn(pszShare, false); + AssertPtrReturn(pszMountPoint, false); + AssertReturn(cbMountPoint, false); + + bool fMounted = false; + +# if defined(RT_OS_SOLARIS) + /** @todo What to do if we have a relative path in mtab instead + * of an absolute one ("temp" vs. "/media/temp")? + * procfs contains the full path but not the actual share name ... + * FILE *pFh = setmntent("/proc/mounts", "r+t"); */ + FILE *pFh = fopen(_PATH_MOUNTED, "r"); + if (!pFh) + VGSvcError("vbsvcAutoMountShareIsMountedOld: Could not open mount tab '%s'!\n", _PATH_MOUNTED); + else + { + mnttab mntTab; + while ((getmntent(pFh, &mntTab))) + { + if (!RTStrICmp(mntTab.mnt_special, pszShare)) + { + fMounted = RTStrPrintf(pszMountPoint, cbMountPoint, "%s", mntTab.mnt_mountp) + ? true : false; + break; + } + } + fclose(pFh); + } +# elif defined(RT_OS_LINUX) + FILE *pFh = setmntent(_PATH_MOUNTED, "r+t"); /** @todo r=bird: why open it for writing? (the '+') */ + if (pFh == NULL) + VGSvcError("vbsvcAutoMountShareIsMountedOld: Could not open mount tab '%s'!\n", _PATH_MOUNTED); + else + { + mntent *pMntEnt; + while ((pMntEnt = getmntent(pFh))) + { + if (!RTStrICmp(pMntEnt->mnt_fsname, pszShare)) + { + fMounted = RTStrPrintf(pszMountPoint, cbMountPoint, "%s", pMntEnt->mnt_dir) + ? true : false; + break; + } + } + endmntent(pFh); + } +# else +# error "PORTME!" +# endif + + VGSvcVerbose(4, "vbsvcAutoMountShareIsMountedOld: Share '%s' at mount point '%s' = %s\n", + pszShare, fMounted ? pszMountPoint : "<None>", fMounted ? "Yes" : "No"); + return fMounted; +} + + +/** + * Unmounts a shared folder. + * + * @returns VBox status code + * @param pszMountPoint The shared folder mount point. + */ +static int vbsvcAutoMountUnmountOld(const char *pszMountPoint) +{ + AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + uint8_t uTries = 0; + int r; + while (uTries++ < 3) + { + r = umount(pszMountPoint); + if (r == 0) + break; +/** @todo r=bird: Why do sleep 5 seconds after the final retry? + * May also be a good idea to check for EINVAL or other signs that someone + * else have already unmounted the share. */ + RTThreadSleep(5000); /* Wait a while ... */ + } + if (r == -1) /** @todo r=bird: RTThreadSleep set errno. */ + rc = RTErrConvertFromErrno(errno); + return rc; +} + + +/** + * Prepares a mount point (create it, set group and mode). + * + * @returns VBox status code + * @param pszMountPoint The mount point. + * @param pszShareName Unused. + * @param gidGroup The group ID. + */ +static int vbsvcAutoMountPrepareMountPointOld(const char *pszMountPoint, const char *pszShareName, RTGID gidGroup) +{ + AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszShareName, VERR_INVALID_PARAMETER); + + /** @todo r=bird: There is no reason why gidGroup should have write access? + * Seriously, what kind of non-sense is this? */ + + RTFMODE fMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG; /* Owner (=root) and the group (=vboxsf) have full access. */ + int rc = RTDirCreateFullPath(pszMountPoint, fMode); + if (RT_SUCCESS(rc)) + { + rc = RTPathSetOwnerEx(pszMountPoint, NIL_RTUID /* Owner, unchanged */, gidGroup, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + { + rc = RTPathSetMode(pszMountPoint, fMode); + if (RT_FAILURE(rc)) + { + if (rc == VERR_WRITE_PROTECT) + { + VGSvcVerbose(3, "vbsvcAutoMountPrepareMountPointOld: Mount directory '%s' already is used/mounted\n", + pszMountPoint); + rc = VINF_SUCCESS; + } + else + VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not set mode %RTfmode for mount directory '%s', rc = %Rrc\n", + fMode, pszMountPoint, rc); + } + } + else + VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not set permissions for mount directory '%s', rc = %Rrc\n", + pszMountPoint, rc); + } + else + VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not create mount directory '%s' with mode %RTfmode, rc = %Rrc\n", + pszMountPoint, fMode, rc); + return rc; +} + + +/** + * Mounts a shared folder. + * + * @returns VBox status code reflecting unmount and mount point preparation + * results, but not actual mounting + * + * @param pszShareName The shared folder name. + * @param pszMountPoint The mount point. + */ +static int vbsvcAutoMountSharedFolderOld(const char *pszShareName, const char *pszMountPoint) +{ + /* + * Linux and solaris share the same mount structure. + */ + struct group *grp_vboxsf = getgrnam("vboxsf"); + if (!grp_vboxsf) + { + VGSvcError("vbsvcAutoMountWorker: Group 'vboxsf' does not exist\n"); + return VINF_SUCCESS; + } + + int rc = vbsvcAutoMountPrepareMountPointOld(pszMountPoint, pszShareName, grp_vboxsf->gr_gid); + if (RT_SUCCESS(rc)) + { +# ifdef RT_OS_SOLARIS + int const fFlags = MS_OPTIONSTR; + char szOptBuf[MAX_MNTOPT_STR] = { '\0', }; + RTStrPrintf(szOptBuf, sizeof(szOptBuf), "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000", grp_vboxsf->gr_gid); + int r = mount(pszShareName, + pszMountPoint, + fFlags, + "vboxfs", + NULL, /* char *dataptr */ + 0, /* int datalen */ + szOptBuf, + sizeof(szOptBuf)); + if (r == 0) + VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint); + else if (errno != EBUSY) /* Share is already mounted? Then skip error msg. */ + VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s', error = %s\n", + pszShareName, pszMountPoint, strerror(errno)); + +# else /* RT_OS_LINUX */ + struct utsname uts; + AssertStmt(uname(&uts) != -1, strcpy(uts.release, "4.4.0")); + + unsigned long const fFlags = MS_NODEV; + char szOpts[MAX_MNTOPT_STR] = { '\0' }; + ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts), "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000", + grp_vboxsf->gr_gid); + if (cchOpts > 0 && RTStrVersionCompare(uts.release, "2.6.0") < 0) + cchOpts = RTStrPrintf2(&szOpts[cchOpts], sizeof(szOpts) - cchOpts, ",sf_name=%s", pszShareName); + if (cchOpts <= 0) + { + VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd (share %s)\n", cchOpts, pszShareName); + return VERR_BUFFER_OVERFLOW; + } + + int r = mount(pszShareName, + pszMountPoint, + "vboxsf", + fFlags, + szOpts); + if (r == 0) + { + VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint); + + r = vbsfmount_complete(pszShareName, pszMountPoint, fFlags, szOpts); + switch (r) + { + case 0: /* Success. */ + errno = 0; /* Clear all errors/warnings. */ + break; + case 1: + VGSvcError("vbsvcAutoMountWorker: Could not update mount table (malloc failure)\n"); + break; + case 2: + VGSvcError("vbsvcAutoMountWorker: Could not open mount table for update: %s\n", strerror(errno)); + break; + case 3: + /* VGSvcError("vbsvcAutoMountWorker: Could not add an entry to the mount table: %s\n", strerror(errno)); */ + errno = 0; + break; + default: + VGSvcError("vbsvcAutoMountWorker: Unknown error while completing mount operation: %d\n", r); + break; + } + } + else /* r == -1, we got some error in errno. */ + { + switch (errno) + { + /* If we get EINVAL here, the system already has mounted the Shared Folder to another + * mount point. */ + case EINVAL: + VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' is already mounted!\n", pszShareName); + /* Ignore this error! */ + break; + case EBUSY: + /* Ignore these errors! */ + break; + default: + VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s': %s (%d)\n", + pszShareName, pszMountPoint, strerror(errno), errno); + rc = RTErrConvertFromErrno(errno); + break; + } + } +# endif + } + VGSvcVerbose(3, "vbsvcAutoMountWorker: Mounting returned with rc=%Rrc\n", rc); + return rc; +} + + +/** + * Processes shared folder mappings retrieved from the host. + * + * @returns VBox status code. + * @param paMappings The mappings. + * @param cMappings The number of mappings. + * @param pszMountDir The mount directory. + * @param pszSharePrefix The share prefix. + * @param uClientID The shared folder service (HGCM) client ID. + */ +static int vbsvcAutoMountProcessMappingsOld(PCVBGLR3SHAREDFOLDERMAPPING paMappings, uint32_t cMappings, + const char *pszMountDir, const char *pszSharePrefix, uint32_t uClientID) +{ + if (cMappings == 0) + return VINF_SUCCESS; + AssertPtrReturn(paMappings, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszMountDir, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszSharePrefix, VERR_INVALID_PARAMETER); + AssertReturn(uClientID > 0, VERR_INVALID_PARAMETER); + + /** @todo r=bird: Why is this loop schitzoid about status codes? It quits if + * RTPathJoin fails (i.e. if the user specifies a very long name), but happily + * continues if RTStrAPrintf failes (mem alloc). + * + * It also happily continues if the 'vboxsf' group is missing, which is a waste + * of effort... In fact, retrieving the group ID could probably be done up + * front, outside the loop. */ + int rc = VINF_SUCCESS; + for (uint32_t i = 0; i < cMappings && RT_SUCCESS(rc); i++) + { + char *pszShareName = NULL; + rc = VbglR3SharedFolderGetName(uClientID, paMappings[i].u32Root, &pszShareName); + if ( RT_SUCCESS(rc) + && *pszShareName) + { + VGSvcVerbose(3, "vbsvcAutoMountWorker: Connecting share %u (%s) ...\n", i+1, pszShareName); + + /** @todo r=bird: why do you copy things twice here and waste heap space? + * szMountPoint has a fixed size. + * @code + * char szMountPoint[RTPATH_MAX]; + * rc = RTPathJoin(szMountPoint, sizeof(szMountPoint), pszMountDir, *pszSharePrefix ? pszSharePrefix : pszShareName); + * if (RT_SUCCESS(rc) && *pszSharePrefix) + * rc = RTStrCat(szMountPoint, sizeof(szMountPoint), pszShareName); + * @endcode */ + char *pszShareNameFull = NULL; + if (RTStrAPrintf(&pszShareNameFull, "%s%s", pszSharePrefix, pszShareName) > 0) + { + char szMountPoint[RTPATH_MAX]; + rc = RTPathJoin(szMountPoint, sizeof(szMountPoint), pszMountDir, pszShareNameFull); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "vbsvcAutoMountWorker: Processing mount point '%s'\n", szMountPoint); + + /* + * Already mounted? + */ + /** @todo r-bird: this does not take into account that a shared folder could + * be mounted twice... We're really just interested in whether the + * folder is mounted on 'szMountPoint', no where else... */ + bool fSkip = false; + char szAlreadyMountedOn[RTPATH_MAX]; + if (vbsvcAutoMountShareIsMountedOld(pszShareName, szAlreadyMountedOn, sizeof(szAlreadyMountedOn))) + { + /* Do if it not mounted to our desired mount point */ + if (RTStrICmp(szMountPoint, szAlreadyMountedOn)) + { + VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder '%s' already mounted on '%s', unmounting ...\n", + pszShareName, szAlreadyMountedOn); + rc = vbsvcAutoMountUnmountOld(szAlreadyMountedOn); + if (RT_SUCCESS(rc)) + fSkip = false; + else + VGSvcError("vbsvcAutoMountWorker: Failed to unmount '%s', %s (%d)! (rc=%Rrc)\n", + szAlreadyMountedOn, strerror(errno), errno, rc); /** @todo errno isn't reliable at this point */ + } + if (fSkip) + VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder '%s' already mounted on '%s', skipping\n", + pszShareName, szAlreadyMountedOn); + } + if (!fSkip) + { + /* + * Mount it. + */ + rc = vbsvcAutoMountSharedFolderOld(pszShareName, szMountPoint); + } + } + else + VGSvcError("vbsvcAutoMountWorker: Unable to join mount point/prefix/shrae, rc = %Rrc\n", rc); + RTStrFree(pszShareNameFull); + } + else + VGSvcError("vbsvcAutoMountWorker: Unable to allocate full share name\n"); + RTStrFree(pszShareName); + } + else + VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder name for root node = %u, rc = %Rrc\n", + paMappings[i].u32Root, rc); + } /* for cMappings. */ + return rc; +} + +#endif /* defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) - the old code*/ + + +/** + * Service worker function for old host. + * + * This only mount stuff on startup. + * + * @returns VBox status code. + * @param pfShutdown Shutdown indicator. + */ +static int vbsvcAutoMountWorkerOld(bool volatile *pfShutdown) +{ +#if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) + /* + * We only do a single pass here. + */ + uint32_t cMappings; + PVBGLR3SHAREDFOLDERMAPPING paMappings; + int rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /* Only process auto-mounted folders */, + &paMappings, &cMappings); + if ( RT_SUCCESS(rc) + && cMappings) + { + char *pszMountDir; + rc = VbglR3SharedFolderGetMountDir(&pszMountDir); + if (rc == VERR_NOT_FOUND) + rc = RTStrDupEx(&pszMountDir, VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder mount dir set to '%s'\n", pszMountDir); + + char *pszSharePrefix; + rc = VbglR3SharedFolderGetMountPrefix(&pszSharePrefix); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder mount prefix set to '%s'\n", pszSharePrefix); +# ifdef USE_VIRTUAL_SHARES + /* Check for a fixed/virtual auto-mount share. */ + if (VbglR3SharedFolderExists(g_idClientSharedFolders, "vbsfAutoMount")) + VGSvcVerbose(3, "vbsvcAutoMountWorker: Host supports auto-mount root\n"); + else + { +# endif + VGSvcVerbose(3, "vbsvcAutoMountWorker: Got %u shared folder mappings\n", cMappings); + rc = vbsvcAutoMountProcessMappingsOld(paMappings, cMappings, pszMountDir, pszSharePrefix, + g_idClientSharedFolders); +# ifdef USE_VIRTUAL_SHARES + } +# endif + RTStrFree(pszSharePrefix); + } /* Mount share prefix. */ + else + VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder mount prefix, rc = %Rrc\n", rc); + RTStrFree(pszMountDir); + } + else + VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder directory, rc = %Rrc\n", rc); + VbglR3SharedFolderFreeMappings(paMappings); + } + else if (RT_FAILURE(rc)) + VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder mappings, rc = %Rrc\n", rc); + else + VGSvcVerbose(3, "vbsvcAutoMountWorker: No shared folder mappings found\n"); + +#else + int rc = VINF_SUCCESS; +#endif /* defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) */ + + + /* + * Wait on shutdown (this used to be a silly RTThreadSleep(500) loop). + */ + while (!*pfShutdown) + { + rc = RTSemEventMultiWait(g_hAutoMountEvent, RT_MS_1MIN); + if (rc != VERR_TIMEOUT) + break; + } + + VGSvcVerbose(3, "vbsvcAutoMountWorkerOld: Finished with rc=%Rrc\n", rc); + return rc; +} + +#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) +/** + * Assembles the mount directory and prefix into @a pszDst. + * + * Will fall back on defaults if we have trouble with the configuration from the + * host. This ASSUMES that @a cbDst is rather large and won't cause trouble + * with the default. + * + * @returns IPRT status code. + * @param pszDst Where to return the prefix. + * @param cbDst The size of the prefix buffer. + */ +static int vbsvcAutomounterQueryMountDirAndPrefix(char *pszDst, size_t cbDst) +{ + /* + * Query the config first. + */ + /* Mount directory: */ + const char *pszDir = VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR; + char *pszCfgDir; + int rc = VbglR3SharedFolderGetMountDir(&pszCfgDir); + if (RT_SUCCESS(rc)) + { + if (*pszCfgDir == '/') + pszDir = pszCfgDir; + } + else + pszCfgDir = NULL; + + /* Prefix: */ + const char *pszPrefix = VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX; + char *pszCfgPrefix; + rc = VbglR3SharedFolderGetMountPrefix(&pszCfgPrefix); + if (RT_SUCCESS(rc)) + { + if ( strchr(pszCfgPrefix, '/') == NULL + && strchr(pszCfgPrefix, '\\') == NULL + && strcmp(pszCfgPrefix, "..") != 0) + pszPrefix = pszCfgPrefix; + } + else + pszCfgPrefix = NULL; + + /* + * Try combine the two. + */ + rc = RTPathAbs(pszDir, pszDst, cbDst); + if (RT_SUCCESS(rc)) + { + if (*pszPrefix) + { + rc = RTPathAppend(pszDst, cbDst, pszPrefix); + if (RT_FAILURE(rc)) + VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathAppend(%s,,%s) -> %Rrc\n", pszDst, pszPrefix, rc); + } + else + { + rc = RTPathEnsureTrailingSeparator(pszDst, cbDst); + if (RT_FAILURE(rc)) + VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathEnsureTrailingSeparator(%s) -> %Rrc\n", pszDst, rc); + } + } + else + VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathAbs(%s) -> %Rrc\n", pszDir, rc); + + + /* + * Return the default dir + prefix if the above failed. + */ + if (RT_FAILURE(rc)) + { + rc = RTStrCopy(pszDst, cbDst, VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR "/" VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX); + AssertRC(rc); + } + + RTStrFree(pszCfgDir); + RTStrFree(pszCfgPrefix); + return rc; +} +#endif /* !RT_OS_WINDOW && !RT_OS_OS2 */ + + +/** + * @callback_method_impl{FNRTSORTCMP, For sorting mount table by root ID. } + */ +static DECLCALLBACK(int) vbsvcAutomounterCompareEntry(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF_PV(pvUser); + PVBSVCAUTOMOUNTERENTRY pEntry1 = (PVBSVCAUTOMOUNTERENTRY)pvElement1; + PVBSVCAUTOMOUNTERENTRY pEntry2 = (PVBSVCAUTOMOUNTERENTRY)pvElement2; + return pEntry1->idRoot < pEntry2->idRoot ? -1 + : pEntry1->idRoot > pEntry2->idRoot ? 1 : 0; +} + + +/** + * Worker for vbsvcAutomounterPopulateTable for adding discovered entries. + * + * This is puts dummies in for missing values, depending on + * vbsvcAutomounterPopulateTable to query them later. + * + * @returns VINF_SUCCESS or VERR_NO_MEMORY; + * @param pMountTable The mount table to add an entry to. + * @param pszName The shared folder name. + * @param pszMountPoint The mount point. + */ +static int vbsvcAutomounterAddEntry(PVBSVCAUTOMOUNTERTABLE pMountTable, const char *pszName, const char *pszMountPoint) +{ + VGSvcVerbose(2, "vbsvcAutomounterAddEntry: %s -> %s\n", pszMountPoint, pszName); + PVBSVCAUTOMOUNTERENTRY pEntry = (PVBSVCAUTOMOUNTERENTRY)RTMemAlloc(sizeof(*pEntry)); + pEntry->idRoot = UINT32_MAX; + pEntry->uRootIdVersion = UINT32_MAX; + pEntry->fFlags = UINT64_MAX; + pEntry->pszName = RTStrDup(pszName); + pEntry->pszMountPoint = NULL; + pEntry->pszActualMountPoint = RTStrDup(pszMountPoint); + if (pEntry->pszName && pEntry->pszActualMountPoint) + { + if (pMountTable->cEntries + 1 <= pMountTable->cAllocated) + { + pMountTable->papEntries[pMountTable->cEntries++] = pEntry; + return VINF_SUCCESS; + } + + void *pvNew = RTMemRealloc(pMountTable->papEntries, (pMountTable->cAllocated + 8) * sizeof(pMountTable->papEntries[0])); + if (pvNew) + { + pMountTable->cAllocated += 8; + pMountTable->papEntries = (PVBSVCAUTOMOUNTERENTRY *)pvNew; + + pMountTable->papEntries[pMountTable->cEntries++] = pEntry; + return VINF_SUCCESS; + } + } + RTMemFree(pEntry->pszActualMountPoint); + RTMemFree(pEntry->pszName); + RTMemFree(pEntry); + return VERR_NO_MEMORY; +} + + +/** + * Populates the mount table as best we can with existing automount entries. + * + * @returns VINF_SUCCESS or VERR_NO_MEMORY; + * @param pMountTable The mount table (empty). + */ +static int vbsvcAutomounterPopulateTable(PVBSVCAUTOMOUNTERTABLE pMountTable) +{ + int rc; + +#ifdef RT_OS_WINDOWS + /* + * Loop thru the drive letters and check out each of them using QueryDosDeviceW. + */ + static const char s_szDevicePath[] = "\\Device\\VBoxMiniRdr\\;"; + for (char chDrive = 'Z'; chDrive >= 'A'; chDrive--) + { + RTUTF16 const wszMountPoint[4] = { (RTUTF16)chDrive, ':', '\0', '\0' }; + RTUTF16 wszTargetPath[RTPATH_MAX]; + DWORD const cwcResult = QueryDosDeviceW(wszMountPoint, wszTargetPath, RT_ELEMENTS(wszTargetPath)); + if ( cwcResult > sizeof(s_szDevicePath) + && RTUtf16NICmpAscii(wszTargetPath, RT_STR_TUPLE(s_szDevicePath)) == 0) + { + PCRTUTF16 pwsz = &wszTargetPath[RT_ELEMENTS(s_szDevicePath) - 1]; + Assert(pwsz[-1] == ';'); + if ( (pwsz[0] & ~(RTUTF16)0x20) == chDrive + && pwsz[1] == ':' + && pwsz[2] == '\\') + { + /* For now we'll just use the special capitalization of the + "server" name to identify it as our work. We could check + if the symlink is from \Global?? or \??, but that trick does + work for older OS versions (<= XP) or when running the + service manually for testing/wathever purposes. */ + /** @todo Modify the windows shared folder driver to allow tagging drives.*/ + if (RTUtf16NCmpAscii(&pwsz[3], RT_STR_TUPLE("VBoxSvr\\")) == 0) + { + pwsz += 3 + 8; + if (*pwsz != '\\' && *pwsz) + { + /* The shared folder name should follow immediately after the server prefix. */ + char *pszMountedName = NULL; + rc = RTUtf16ToUtf8(pwsz, &pszMountedName); + if (RT_SUCCESS(rc)) + { + char const szMountPoint[4] = { chDrive, ':', '\0', '\0' }; + rc = vbsvcAutomounterAddEntry(pMountTable, pszMountedName, szMountPoint); + RTStrFree(pszMountedName); + } + if (RT_FAILURE(rc)) + return rc; + } + else + VGSvcVerbose(2, "vbsvcAutomounterPopulateTable: Malformed, not ours: %ls -> %ls\n", + wszMountPoint, wszTargetPath); + } + else + VGSvcVerbose(3, "vbsvcAutomounterPopulateTable: Not ours: %ls -> %ls\n", wszMountPoint, wszTargetPath); + } + } + } + +#elif defined(RT_OS_OS2) + /* + * Just loop thru the drive letters and check the attachment of each. + */ + for (char chDrive = 'Z'; chDrive >= 'A'; chDrive--) + { + char const szMountPoint[4] = { chDrive, ':', '\0', '\0' }; + union + { + FSQBUFFER2 FsQueryBuf; + char achPadding[1024]; + } uBuf; + RT_ZERO(uBuf); + ULONG cbBuf = sizeof(uBuf) - 2; + APIRET rcOs2 = DosQueryFSAttach(szMountPoint, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf); + if (rcOs2 == NO_ERROR) + { + const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1]; + if ( uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV + && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0) + { + const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1]; + const char *pszTag = pszMountedName + strlen(pszMountedName) + 1; /* (Safe. Always two trailing zero bytes, see above.) */ + if (strcmp(pszTag, g_szTag) == 0) + { + rc = vbsvcAutomounterAddEntry(pMountTable, pszMountedName, szMountPoint); + if (RT_FAILURE(rc)) + return rc; + } + } + } + } + +#elif defined(RT_OS_LINUX) + /* + * Scan the mount table file for the mount point and then match file system + * and device/share. We identify our mounts by mount path + prefix for now, + * but later we may use the same approach as on solaris. + */ + FILE *pFile = setmntent("/proc/mounts", "r"); + int iErrMounts = errno; + if (!pFile) + pFile = setmntent("/etc/mtab", "r"); + if (pFile) + { + rc = VWRN_NOT_FOUND; + struct mntent *pEntry; + while ((pEntry = getmntent(pFile)) != NULL) + if (strcmp(pEntry->mnt_type, "vboxsf") == 0) + if (strstr(pEntry->mnt_opts, g_szTag) != NULL) + { + rc = vbsvcAutomounterAddEntry(pMountTable, pEntry->mnt_fsname, pEntry->mnt_dir); + if (RT_FAILURE(rc)) + { + endmntent(pFile); + return rc; + } + } + endmntent(pFile); + } + else + VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d) or '/proc/mounts' (errno=%d)\n", + _PATH_MOUNTED, errno, iErrMounts); + +#elif defined(RT_OS_SOLARIS) + /* + * Look thru the system mount table and inspect the vboxsf mounts. + */ + FILE *pFile = fopen(_PATH_MOUNTED, "r"); + if (pFile) + { + rc = VINF_SUCCESS; + struct mnttab Entry; + while (getmntent(pFile, &Entry) == 0) + if (strcmp(Entry.mnt_fstype, "vboxfs") == 0) + { + /* Look for the dummy automounter option. */ + if ( Entry.mnt_mntopts != NULL + && strstr(Entry.mnt_mntopts, g_szTag) != NULL) + { + rc = vbsvcAutomounterAddEntry(pMountTable, Entry.mnt_special, Entry.mnt_mountp); + if (RT_FAILURE(rc)) + { + fclose(pFile); + return rc; + } + } + } + fclose(pFile); + } + else + VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d)\n", _PATH_MOUNTED, errno); + +#else +# error "PORTME!" +#endif + + /* + * Try reconcile the detected folders with data from the host. + */ + uint32_t cMappings = 0; + PVBGLR3SHAREDFOLDERMAPPING paMappings = NULL; + rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /*fAutoMountOnly*/, &paMappings, &cMappings); + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < cMappings && RT_SUCCESS(rc); i++) + { + uint32_t const idRootSrc = paMappings[i].u32Root; + + uint32_t uRootIdVer = UINT32_MAX; + uint64_t fFlags = 0; + char *pszName = NULL; + char *pszMntPt = NULL; + int rc2 = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF, + &pszName, &pszMntPt, &fFlags, &uRootIdVer); + if (RT_SUCCESS(rc2)) + { + uint32_t iPrevHit = UINT32_MAX; + for (uint32_t iTable = 0; iTable < pMountTable->cEntries; iTable++) + { + PVBSVCAUTOMOUNTERENTRY pEntry = pMountTable->papEntries[iTable]; + if (RTStrICmp(pEntry->pszName, pszName) == 0) + { + VGSvcVerbose(2, "vbsvcAutomounterPopulateTable: Identified %s -> %s: idRoot=%u ver=%u fFlags=%#x AutoMntPt=%s\n", + pEntry->pszActualMountPoint, pEntry->pszName, idRootSrc, uRootIdVer, fFlags, pszMntPt); + pEntry->fFlags = fFlags; + pEntry->idRoot = idRootSrc; + pEntry->uRootIdVersion = uRootIdVer; + RTStrFree(pEntry->pszMountPoint); + pEntry->pszMountPoint = RTStrDup(pszMntPt); + if (!pEntry->pszMountPoint) + { + rc = VERR_NO_MEMORY; + break; + } + + /* If multiple mappings of the same folder, pick the first or the one + with matching mount point. */ + if (iPrevHit == UINT32_MAX) + iPrevHit = iTable; + else if (RTPathCompare(pszMntPt, pEntry->pszActualMountPoint) == 0) + { + if (iPrevHit != UINT32_MAX) + pMountTable->papEntries[iPrevHit]->uRootIdVersion -= 1; + iPrevHit = iTable; + } + else + pEntry->uRootIdVersion -= 1; + } + } + + RTStrFree(pszName); + RTStrFree(pszMntPt); + } + else + VGSvcError("vbsvcAutomounterPopulateTable: VbglR3SharedFolderQueryFolderInfo(%u) failed: %Rrc\n", idRootSrc, rc2); + } + + VbglR3SharedFolderFreeMappings(paMappings); + + /* + * Sort the table by root ID. + */ + if (pMountTable->cEntries > 1) + RTSortApvShell((void **)pMountTable->papEntries, pMountTable->cEntries, vbsvcAutomounterCompareEntry, NULL); + + for (uint32_t iTable = 0; iTable < pMountTable->cEntries; iTable++) + { + PVBSVCAUTOMOUNTERENTRY pEntry = pMountTable->papEntries[iTable]; + if (pMountTable->papEntries[iTable]->idRoot != UINT32_MAX) + VGSvcVerbose(1, "vbsvcAutomounterPopulateTable: #%u: %s -> %s idRoot=%u ver=%u fFlags=%#x AutoMntPt=%s\n", + iTable, pEntry->pszActualMountPoint, pEntry->pszName, pEntry->idRoot, pEntry->uRootIdVersion, + pEntry->fFlags, pEntry->pszMountPoint); + else + VGSvcVerbose(1, "vbsvcAutomounterPopulateTable: #%u: %s -> %s - not identified!\n", + iTable, pEntry->pszActualMountPoint, pEntry->pszName); + } + } + else + VGSvcError("vbsvcAutomounterPopulateTable: VbglR3SharedFolderGetMappings failed: %Rrc\n", rc); + return rc; +} + + +/** + * Checks whether the shared folder @a pszName is mounted on @a pszMountPoint. + * + * @returns Exactly one of the following IPRT status codes; + * @retval VINF_SUCCESS if mounted + * @retval VWRN_NOT_FOUND if nothing is mounted at @a pszMountPoint. + * @retval VERR_RESOURCE_BUSY if a different shared folder is mounted there. + * @retval VERR_ACCESS_DENIED if a non-shared folder file system is mounted + * there. + * + * @param pszMountPoint The mount point to check. + * @param pszName The name of the shared folder (mapping). + */ +static int vbsvcAutomounterQueryMountPoint(const char *pszMountPoint, const char *pszName) +{ + VGSvcVerbose(4, "vbsvcAutomounterQueryMountPoint: pszMountPoint=%s pszName=%s\n", pszMountPoint, pszName); + +#ifdef RT_OS_WINDOWS + /* + * We could've used RTFsQueryType here but would then have to + * calling RTFsQueryLabel for the share name hint, ending up + * doing the same work twice. We could also use QueryDosDeviceW, + * but output is less clear... + */ + PRTUTF16 pwszMountPoint = NULL; + int rc = RTStrToUtf16(pszMountPoint, &pwszMountPoint); + if (RT_SUCCESS(rc)) + { + DWORD uSerial = 0; + DWORD cchCompMax = 0; + DWORD fFlags = 0; + RTUTF16 wszLabel[512]; + RTUTF16 wszFileSystem[256]; + RT_ZERO(wszLabel); + RT_ZERO(wszFileSystem); + if (GetVolumeInformationW(pwszMountPoint, wszLabel, RT_ELEMENTS(wszLabel) - 1, &uSerial, &cchCompMax, &fFlags, + wszFileSystem, RT_ELEMENTS(wszFileSystem) - 1)) + { + if (RTUtf16ICmpAscii(wszFileSystem, "VBoxSharedFolderFS") == 0) + { + char *pszLabel = NULL; + rc = RTUtf16ToUtf8(wszLabel, &pszLabel); + if (RT_SUCCESS(rc)) + { + const char *pszMountedName = pszLabel; + if (RTStrStartsWith(pszMountedName, "VBOX_")) + pszMountedName += sizeof("VBOX_") - 1; + if (RTStrICmp(pszMountedName, pszName) == 0) + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n", + pszName, pszMountPoint); + rc = VINF_SUCCESS; + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n", + pszMountedName, pszMountPoint, pszName); + rc = VERR_RESOURCE_BUSY; + } + RTStrFree(pszLabel); + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: RTUtf16ToUtf8(%ls,) failed: %Rrc\n", wszLabel, rc); + rc = VERR_RESOURCE_BUSY; + } + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%ls' with label '%ls' mount at '%s', not '%s'...\n", + wszFileSystem, wszLabel, pszMountPoint, pszName); + rc = VERR_ACCESS_DENIED; + } + } + else + { + rc = GetLastError(); + if (rc != ERROR_PATH_NOT_FOUND || g_cVerbosity >= 4) + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: GetVolumeInformationW('%ls',,,,) failed: %u\n", pwszMountPoint, rc); + if (rc == ERROR_PATH_NOT_FOUND) + rc = VWRN_NOT_FOUND; + else if ( RT_C_IS_ALPHA(pszMountPoint[0]) + && pszMountPoint[1] == ':' + && ( pszMountPoint[2] == '\0' + || (RTPATH_IS_SLASH(pszMountPoint[2]) && pszMountPoint[3] == '\0'))) + { + /* See whether QueryDosDeviceW thinks its a malfunctioning shared folder or + something else (it doesn't access the file system). We've seen + VERR_NET_HOST_NOT_FOUND here for shared folders that was removed on the + host side. + + Note! This duplicates code from vbsvcAutomounterPopulateTable. */ + rc = VERR_ACCESS_DENIED; + static const char s_szDevicePath[] = "\\Device\\VBoxMiniRdr\\;"; + wszFileSystem[0] = pwszMountPoint[0]; + wszFileSystem[1] = pwszMountPoint[1]; + wszFileSystem[2] = '\0'; + DWORD const cwcResult = QueryDosDeviceW(wszFileSystem, wszLabel, RT_ELEMENTS(wszLabel)); + if ( cwcResult > sizeof(s_szDevicePath) + && RTUtf16NICmpAscii(wszLabel, RT_STR_TUPLE(s_szDevicePath)) == 0) + { + PCRTUTF16 pwsz = &wszLabel[RT_ELEMENTS(s_szDevicePath) - 1]; + Assert(pwsz[-1] == ';'); + if ( (pwsz[0] & ~(RTUTF16)0x20) == (wszFileSystem[0] & ~(RTUTF16)0x20) + && pwsz[1] == ':' + && pwsz[2] == '\\') + { + if (RTUtf16NICmpAscii(&pwsz[3], RT_STR_TUPLE("VBoxSvr\\")) == 0) + { + pwsz += 3 + 8; + char *pszMountedName = NULL; + rc = RTUtf16ToUtf8(pwsz, &pszMountedName); + if (RT_SUCCESS(rc)) + { + if (RTStrICmp(pszMountedName, pszName) == 0) + { + rc = VINF_SUCCESS; + VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s' (using QueryDosDeviceW).\n", + pszName, pszMountPoint); + } + else + { + VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s' (using QueryDosDeviceW), not '%s'...\n", + pszMountedName, pszMountPoint, pszName); + rc = VERR_RESOURCE_BUSY; + } + RTStrFree(pszMountedName); + } + else + { + VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: RTUtf16ToUtf8 failed: %Rrc\n", rc); + AssertRC(rc); + rc = VERR_RESOURCE_BUSY; + } + } + } + } + } + else + rc = VERR_ACCESS_DENIED; + } + RTUtf16Free(pwszMountPoint); + } + else + { + VGSvcError("vbsvcAutomounterQueryMountPoint: RTStrToUtf16(%s,) -> %Rrc\n", pszMountPoint, rc); + rc = VWRN_NOT_FOUND; + } + return rc; + +#elif defined(RT_OS_OS2) + /* + * Query file system attachment info for the given drive letter. + */ + union + { + FSQBUFFER2 FsQueryBuf; + char achPadding[512]; + } uBuf; + RT_ZERO(uBuf); + + ULONG cbBuf = sizeof(uBuf); + APIRET rcOs2 = DosQueryFSAttach(pszMountPoint, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf); + int rc; + if (rcOs2 == NO_ERROR) + { + const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1]; + if ( uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV + && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0) + { + const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1]; + if (RTStrICmp(pszMountedName, pszName) == 0) + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n", + pszName, pszMountPoint); + rc = VINF_SUCCESS; + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n", + pszMountedName, pszMountPoint, pszName); + rc = VERR_RESOURCE_BUSY; + } + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' type %u mount at '%s', not '%s'...\n", + pszFsdName, uBuf.FsQueryBuf.iType, pszMountPoint, pszName); + rc = VERR_ACCESS_DENIED; + } + } + else + { + rc = VWRN_NOT_FOUND; + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: DosQueryFSAttach(%s) -> %u\n", pszMountPoint, rcOs2); + AssertMsgStmt(rcOs2 != ERROR_BUFFER_OVERFLOW && rcOs2 != ERROR_INVALID_PARAMETER, + ("%s -> %u\n", pszMountPoint, rcOs2), rc = VERR_ACCESS_DENIED); + } + return rc; + +#elif defined(RT_OS_LINUX) + /* + * Scan one of the mount table file for the mount point and then + * match file system and device/share. + */ + FILE *pFile = setmntent("/proc/mounts", "r"); + int rc = errno; + if (!pFile) + pFile = setmntent(_PATH_MOUNTED, "r"); + if (pFile) + { + rc = VWRN_NOT_FOUND; + struct mntent *pEntry; + while ((pEntry = getmntent(pFile)) != NULL) + if (RTPathCompare(pEntry->mnt_dir, pszMountPoint) == 0) + { + if (strcmp(pEntry->mnt_type, "vboxsf") == 0) + { + if (RTStrICmp(pEntry->mnt_fsname, pszName) == 0) + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n", + pszName, pszMountPoint); + rc = VINF_SUCCESS; + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n", + pEntry->mnt_fsname, pszMountPoint, pszName); + rc = VERR_RESOURCE_BUSY; + } + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' mount of '%s' at '%s', not '%s'...\n", + pEntry->mnt_type, pEntry->mnt_fsname, pszMountPoint, pszName); + rc = VERR_ACCESS_DENIED; + } + /* We continue searching in case of stacked mounts, we want the last one. */ + } + endmntent(pFile); + } + else + { + VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '/proc/mounts' (errno=%d) or '%s' (errno=%d)\n", + rc, _PATH_MOUNTED, errno); + rc = VERR_ACCESS_DENIED; + } + return rc; + +#elif defined(RT_OS_SOLARIS) + /* + * Similar to linux. + */ + int rc; + FILE *pFile = fopen(_PATH_MOUNTED, "r"); + if (pFile) + { + rc = VWRN_NOT_FOUND; + struct mnttab Entry; + while (getmntent(pFile, &Entry) == 0) + if (RTPathCompare(Entry.mnt_mountp, pszMountPoint) == 0) + { + if (strcmp(Entry.mnt_fstype, "vboxfs") == 0) + { + if (RTStrICmp(Entry.mnt_special, pszName) == 0) + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n", + pszName, pszMountPoint); + rc = VINF_SUCCESS; + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n", + Entry.mnt_special, pszMountPoint, pszName); + rc = VERR_RESOURCE_BUSY; + } + } + else + { + VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' mount of '%s' at '%s', not '%s'...\n", + Entry.mnt_fstype, Entry.mnt_special, pszMountPoint, pszName); + rc = VERR_ACCESS_DENIED; + } + /* We continue searching in case of stacked mounts, we want the last one. */ + } + fclose(pFile); + } + else + { + VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d)\n", _PATH_MOUNTED, errno); + rc = VERR_ACCESS_DENIED; + } + return rc; +#else +# error "PORTME" +#endif +} + + +/** + * Worker for vbsvcAutomounterMountNewEntry that does the OS mounting. + * + * @returns IPRT status code. + * @param pEntry The entry to try mount. + */ +static int vbsvcAutomounterMountIt(PVBSVCAUTOMOUNTERENTRY pEntry) +{ + VGSvcVerbose(3, "vbsvcAutomounterMountIt: Trying to mount '%s' (idRoot=%#x) on '%s'...\n", + pEntry->pszName, pEntry->idRoot, pEntry->pszActualMountPoint); +#ifdef RT_OS_WINDOWS + /* + * Attach the shared folder using WNetAddConnection2W. + * + * According to google we should get a drive symlink in \\GLOBAL?? when + * we are running under the system account. Otherwise it will be a session + * local link (\\??). + */ + Assert(RT_C_IS_UPPER(pEntry->pszActualMountPoint[0]) && pEntry->pszActualMountPoint[1] == ':' && pEntry->pszActualMountPoint[2] == '\0'); + RTUTF16 wszDrive[4] = { (RTUTF16)pEntry->pszActualMountPoint[0], ':', '\0', '\0' }; + + RTUTF16 wszPrefixedName[RTPATH_MAX]; + int rc = RTUtf16CopyAscii(wszPrefixedName, RT_ELEMENTS(wszPrefixedName), "\\\\VBoxSvr\\"); + AssertRC(rc); + + size_t const offName = RTUtf16Len(wszPrefixedName); + PRTUTF16 pwszName = &wszPrefixedName[offName]; + rc = RTStrToUtf16Ex(pEntry->pszName, RTSTR_MAX, &pwszName, sizeof(wszPrefixedName) - offName, NULL); + if (RT_FAILURE(rc)) + { + VGSvcError("vbsvcAutomounterMountIt: RTStrToUtf16Ex failed on '%s': %Rrc\n", pEntry->pszName, rc); + return rc; + } + + VGSvcVerbose(3, "vbsvcAutomounterMountIt: wszDrive='%ls', wszPrefixedName='%ls'\n", + wszDrive, wszPrefixedName); + + NETRESOURCEW NetRsrc; + RT_ZERO(NetRsrc); + NetRsrc.dwType = RESOURCETYPE_DISK; + NetRsrc.lpLocalName = wszDrive; + NetRsrc.lpRemoteName = wszPrefixedName; + NetRsrc.lpProvider = L"VirtualBox Shared Folders"; /* Only try our provider. */ + NetRsrc.lpComment = pwszName; + + DWORD dwErr = WNetAddConnection2W(&NetRsrc, NULL /*pwszPassword*/, NULL /*pwszUserName*/, 0 /*dwFlags*/); + if (dwErr == NO_ERROR) + { + VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n", + pEntry->pszName, pEntry->pszActualMountPoint); + return VINF_SUCCESS; + } + VGSvcError("vbsvcAutomounterMountIt: Failed to attach '%s' to '%s': %Rrc (%u)\n", + pEntry->pszName, pEntry->pszActualMountPoint, RTErrConvertFromWin32(dwErr), dwErr); + return VERR_OPEN_FAILED; + +#elif defined(RT_OS_OS2) + /* + * It's a rather simple affair on OS/2. + * + * In order to be able to detect our mounts we add a 2nd string after + * the folder name that tags the attachment. The IFS will remember this + * and return it when DosQueryFSAttach is called. + * + * Note! Kernel currently accepts limited 7-bit ASCII names. We could + * change that to UTF-8 if we like as that means no extra string + * encoding conversion fun here. + */ + char szzNameAndTag[256]; + size_t cchName = strlen(pEntry->pszName); + if (cchName + 1 + sizeof(g_szTag) <= sizeof(szzNameAndTag)) + { + memcpy(szzNameAndTag, pEntry->pszName, cchName); + szzNameAndTag[cchName] = '\0'; + memcpy(&szzNameAndTag[cchName + 1], g_szTag, sizeof(g_szTag)); + + APIRET rc = DosFSAttach(pEntry->pszActualMountPoint, "VBOXSF", szzNameAndTag, cchName + 1 + sizeof(g_szTag), FS_ATTACH); + if (rc == NO_ERROR) + { + VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n", + pEntry->pszName, pEntry->pszActualMountPoint); + return VINF_SUCCESS; + } + VGSvcError("vbsvcAutomounterMountIt: DosFSAttach failed to attach '%s' to '%s': %u\n", + pEntry->pszName, pEntry->pszActualMountPoint, rc); + } + else + VGSvcError("vbsvcAutomounterMountIt: Share name for attach to '%s' is too long: %u chars - '%s'\n", + pEntry->pszActualMountPoint, cchName, pEntry->pszName); + return VERR_OPEN_FAILED; + +#else + /* + * Common work for unix-like systems: Get group, make sure mount directory exist. + */ + int rc = RTDirCreateFullPath(pEntry->pszActualMountPoint, + RTFS_UNIX_IRWXU | RTFS_UNIX_IXGRP | RTFS_UNIX_IRGRP | RTFS_UNIX_IXOTH | RTFS_UNIX_IROTH); + if (RT_FAILURE(rc)) + { + VGSvcError("vbsvcAutomounterMountIt: Failed to create mount path '%s' for share '%s': %Rrc\n", + pEntry->pszActualMountPoint, pEntry->pszName, rc); + return rc; + } + + gid_t gidMount; + struct group *grp_vboxsf = getgrnam("vboxsf"); + if (grp_vboxsf) + gidMount = grp_vboxsf->gr_gid; + else + { + VGSvcError("vbsvcAutomounterMountIt: Group 'vboxsf' does not exist\n"); + gidMount = 0; + } + +# if defined(RT_OS_LINUX) + /* + * Linux a bit more work... + */ + struct utsname uts; + AssertStmt(uname(&uts) != -1, strcpy(uts.release, "4.4.0")); + + /* Built mount option string. Need st_name for pre 2.6.0 kernels. */ + unsigned long const fFlags = MS_NODEV; + char szOpts[MAX_MNTOPT_STR] = { '\0' }; + ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts), + "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000,tag=%s", gidMount, g_szTag); + if (RTStrVersionCompare(uts.release, "2.6.0") < 0 && cchOpts > 0) + cchOpts += RTStrPrintf2(&szOpts[cchOpts], sizeof(szOpts) - cchOpts, ",sf_name=%s", pEntry->pszName); + if (cchOpts <= 0) + { + VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd\n", cchOpts); + return VERR_BUFFER_OVERFLOW; + } + + /* Do the mounting. The fallback w/o tag is for the Linux vboxsf fork + which lagged a lot behind when it first appeared in 5.6. */ + errno = 0; + rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, szOpts); + if (rc != 0 && errno == EINVAL && RTStrVersionCompare(uts.release, "5.6.0") >= 0) + { + VGSvcVerbose(2, "vbsvcAutomounterMountIt: mount returned EINVAL, retrying without the tag.\n"); + *strstr(szOpts, ",tag=") = '\0'; + errno = 0; + rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, szOpts); + if (rc == 0) + VGSvcVerbose(0, "vbsvcAutomounterMountIt: Running outdated vboxsf module without support for the 'tag' option?\n"); + } + if (rc == 0) + { + VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n", + pEntry->pszName, pEntry->pszActualMountPoint); + + errno = 0; + rc = vbsfmount_complete(pEntry->pszName, pEntry->pszActualMountPoint, fFlags, szOpts); + if (rc != 0) /* Ignorable. /etc/mtab is probably a link to /proc/mounts. */ + VGSvcVerbose(1, "vbsvcAutomounterMountIt: vbsfmount_complete failed: %s (%d/%d)\n", + rc == 1 ? "malloc" : rc == 2 ? "setmntent" : rc == 3 ? "addmntent" : "unknown", rc, errno); + return VINF_SUCCESS; + } + + if (errno == EINVAL) + VGSvcError("vbsvcAutomounterMountIt: Failed to mount '%s' on '%s' because it is probably mounted elsewhere arleady! (%d,%d)\n", + pEntry->pszName, pEntry->pszActualMountPoint, rc, errno); + else + VGSvcError("vbsvcAutomounterMountIt: Failed to mount '%s' on '%s': %s (%d,%d)\n", + pEntry->pszName, pEntry->pszActualMountPoint, strerror(errno), rc, errno); + return VERR_WRITE_ERROR; + +# elif defined(RT_OS_SOLARIS) + /* + * Solaris is rather simple compared to linux. + * + * The ',VBoxService=auto' option (g_szTag) is ignored by the kernel but helps + * us identify our own mounts on restart. See vbsvcAutomounterPopulateTable(). + * + * Note! Must pass MAX_MNTOPT_STR rather than cchOpts to mount, as it may fail + * with EOVERFLOW in vfs_buildoptionstr() during domount() otherwise. + */ + char szOpts[MAX_MNTOPT_STR] = { '\0', }; + ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts), + "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000,tag=%s", gidMount, g_szTag); + if (cchOpts <= 0) + { + VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd\n", cchOpts); + return VERR_BUFFER_OVERFLOW; + } + + rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, MS_OPTIONSTR, "vboxfs", + NULL /*dataptr*/, 0 /* datalen */, szOpts, MAX_MNTOPT_STR); + if (rc == 0) + { + VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n", + pEntry->pszName, pEntry->pszActualMountPoint); + return VINF_SUCCESS; + } + + rc = errno; + VGSvcError("vbsvcAutomounterMountIt: mount failed for '%s' on '%s' (szOpts=%s): %s (%d)\n", + pEntry->pszName, pEntry->pszActualMountPoint, szOpts, strerror(rc), rc); + return VERR_OPEN_FAILED; + +# else +# error "PORTME!" +# endif +#endif +} + + +/** + * Attempts to mount the given shared folder, adding it to the mount table on + * success. + * + * @returns iTable + 1 on success, iTable on failure. + * @param pTable The mount table. + * @param iTable The mount table index at which to add the mount. + * @param pszName The name of the shared folder mapping. + * @param pszMntPt The mount point (hint) specified by the host. + * @param fFlags The shared folder flags, SHFL_MIF_XXX. + * @param idRoot The root ID. + * @param uRootIdVersion The root ID version. + * @param fAutoMntPt Whether to try automatically assign a mount point if + * pszMntPt doesn't work out. This is set in pass \#3. + */ +static uint32_t vbsvcAutomounterMountNewEntry(PVBSVCAUTOMOUNTERTABLE pTable, uint32_t iTable, + const char *pszName, const char *pszMntPt, uint64_t fFlags, + uint32_t idRoot, uint32_t uRootIdVersion, bool fAutoMntPt) +{ + VGSvcVerbose(3, "vbsvcAutomounterMountNewEntry: #%u: '%s' at '%s'%s\n", + iTable, pszName, pszMntPt, fAutoMntPt ? " auto-assign" : ""); + + /* + * First we need to figure out the actual mount point. + */ + char szActualMountPoint[RTPATH_MAX]; + +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + /* + * Drive letter based. We only care about the first two characters + * and ignore the rest (see further down). + */ + char chNextLetter = 'Z'; + if (RT_C_IS_ALPHA(pszMntPt[0]) && pszMntPt[1] == ':') + szActualMountPoint[0] = RT_C_TO_UPPER(pszMntPt[0]); + else if (!fAutoMntPt) + return iTable; + else + szActualMountPoint[0] = chNextLetter--; + szActualMountPoint[1] = ':'; + szActualMountPoint[2] = '\0'; + + int rc; + for (;;) + { + rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName); + if (rc == VWRN_NOT_FOUND) + break; + + /* next */ + if (chNextLetter == 'A' || !fAutoMntPt) + return iTable; + szActualMountPoint[0] = chNextLetter--; + } + +#else + /* + * Path based #1: Host specified mount point. + */ + + /* Skip DOS drive letter if there is a UNIX mount point path following it: */ + if ( pszMntPt[0] != '/' + && pszMntPt[0] != '\0' + && pszMntPt[1] == ':' + && pszMntPt[2] == '/') + pszMntPt += 2; + + /* Try specified mount point if it starts with a UNIX slash: */ + int rc = VERR_ACCESS_DENIED; + if (*pszMntPt == '/') + { + rc = RTPathAbs(pszMntPt, szActualMountPoint, sizeof(szActualMountPoint)); + if (RT_SUCCESS(rc)) + { + static const char * const s_apszBlacklist[] = + { "/", "/dev", "/bin", "/sbin", "/lib", "/etc", "/var", "/tmp", "/usr", "/usr/bin", "/usr/sbin", "/usr/lib" }; + for (size_t i = 0; i < RT_ELEMENTS(s_apszBlacklist); i++) + if (strcmp(szActualMountPoint, s_apszBlacklist[i]) == 0) + { + rc = VERR_ACCESS_DENIED; + break; + } + if (RT_SUCCESS(rc)) + rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName); + } + } + if (rc != VWRN_NOT_FOUND) + { + if (!fAutoMntPt) + return iTable; + + /* + * Path based #2: Mount dir + prefix + share. + */ + rc = vbsvcAutomounterQueryMountDirAndPrefix(szActualMountPoint, sizeof(szActualMountPoint)); + if (RT_SUCCESS(rc)) + { + /* Append a sanitized share name: */ + size_t const offShare = strlen(szActualMountPoint); + size_t offDst = offShare; + size_t offSrc = 0; + for (;;) + { + char ch = pszName[offSrc++]; + if (ch == ' ' || ch == '/' || ch == '\\' || ch == ':' || ch == '$') + ch = '_'; + else if (!ch) + break; + else if (ch < 0x20 || ch == 0x7f) + continue; + if (offDst < sizeof(szActualMountPoint) - 1) + szActualMountPoint[offDst++] = ch; + } + szActualMountPoint[offDst] = '\0'; + if (offDst > offShare) + { + rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName); + if (rc != VWRN_NOT_FOUND) + { + /* + * Path based #3: Mount dir + prefix + share + _ + number. + */ + if (offDst + 2 >= sizeof(szActualMountPoint)) + return iTable; + + szActualMountPoint[offDst++] = '_'; + for (uint32_t iTry = 1; iTry < 10 && rc != VWRN_NOT_FOUND; iTry++) + { + szActualMountPoint[offDst] = '0' + iTry; + szActualMountPoint[offDst + 1] = '\0'; + rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName); + } + if (rc != VWRN_NOT_FOUND) + return iTable; + } + } + else + VGSvcError("vbsvcAutomounterMountNewEntry: Bad share name: %.*Rhxs", strlen(pszName), pszName); + } + else + VGSvcError("vbsvcAutomounterMountNewEntry: Failed to construct basic auto mount point for '%s'", pszName); + } +#endif + + /* + * Prepare a table entry and ensure space in the table.. + */ + if (pTable->cEntries + 1 > pTable->cAllocated) + { + void *pvEntries = RTMemRealloc(pTable->papEntries, sizeof(pTable->papEntries[0]) * (pTable->cAllocated + 8)); + if (!pvEntries) + { + VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for growing table (size %u)\n", pTable->cAllocated); + return iTable; + } + pTable->cAllocated += 8; + pTable->papEntries = (PVBSVCAUTOMOUNTERENTRY *)pvEntries; + } + + PVBSVCAUTOMOUNTERENTRY pEntry = (PVBSVCAUTOMOUNTERENTRY)RTMemAlloc(sizeof(*pEntry)); + if (pEntry) + { + pEntry->idRoot = idRoot; + pEntry->uRootIdVersion = uRootIdVersion; + pEntry->fFlags = fFlags; + pEntry->pszName = RTStrDup(pszName); + pEntry->pszMountPoint = RTStrDup(pszMntPt); + pEntry->pszActualMountPoint = RTStrDup(szActualMountPoint); + if (pEntry->pszName && pEntry->pszMountPoint && pEntry->pszActualMountPoint) + { + /* + * Now try mount it. + */ + rc = vbsvcAutomounterMountIt(pEntry); + if (RT_SUCCESS(rc)) + { + uint32_t cToMove = pTable->cEntries - iTable; + if (cToMove > 0) + memmove(&pTable->papEntries[iTable + 1], &pTable->papEntries[iTable], cToMove * sizeof(pTable->papEntries[0])); + pTable->papEntries[iTable] = pEntry; + pTable->cEntries++; + return iTable + 1; + } + } + else + VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for table entry!\n"); + RTMemFree(pEntry->pszActualMountPoint); + RTMemFree(pEntry->pszMountPoint); + RTMemFree(pEntry->pszName); + RTMemFree(pEntry); + } + else + VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for table entry!\n"); + return iTable; +} + + + +/** + * Does the actual unmounting. + * + * @returns Exactly one of the following IPRT status codes; + * @retval VINF_SUCCESS if successfully umounted or nothing was mounted there. + * @retval VERR_TRY_AGAIN if the shared folder is busy. + * @retval VERR_RESOURCE_BUSY if a different shared folder is mounted there. + * @retval VERR_ACCESS_DENIED if a non-shared folder file system is mounted + * there. + * + * @param pszMountPoint The mount point. + * @param pszName The shared folder (mapping) name. + */ +static int vbsvcAutomounterUnmount(const char *pszMountPoint, const char *pszName) +{ + /* + * Retry for 5 seconds in a hope that busy mounts will quiet down. + */ + for (unsigned iTry = 0; ; iTry++) + { + /* + * Check what's mounted there before we start umounting stuff. + */ + int rc = vbsvcAutomounterQueryMountPoint(pszMountPoint, pszName); + if (rc == VINF_SUCCESS) + { /* pszName is mounted there */ } + else if (rc == VWRN_NOT_FOUND) /* nothing mounted there */ + return VINF_SUCCESS; + else + { + Assert(rc == VERR_RESOURCE_BUSY || rc == VERR_ACCESS_DENIED); + return VERR_RESOURCE_BUSY; + } + + /* + * Do host specific unmounting. + */ +#ifdef RT_OS_WINDOWS + Assert(RT_C_IS_UPPER(pszMountPoint[0]) && pszMountPoint[1] == ':' && pszMountPoint[2] == '\0'); + RTUTF16 const wszDrive[4] = { (RTUTF16)pszMountPoint[0], ':', '\0', '\0' }; + DWORD dwErr = WNetCancelConnection2W(wszDrive, 0 /*dwFlags*/, FALSE /*fForce*/); + if (dwErr == NO_ERROR) + return VINF_SUCCESS; + VGSvcVerbose(2, "vbsvcAutomounterUnmount: WNetCancelConnection2W returns %u for '%s' ('%s')\n", dwErr, pszMountPoint, pszName); + if (dwErr == ERROR_NOT_CONNECTED) + return VINF_SUCCESS; + +#elif defined(RT_OS_OS2) + APIRET rcOs2 = DosFSAttach(pszMountPoint, "VBOXSF", NULL, 0, FS_DETACH); + if (rcOs2 == NO_ERROR) + return VINF_SUCCESS; + VGSvcVerbose(2, "vbsvcAutomounterUnmount: DosFSAttach failed on '%s' ('%s'): %u\n", pszMountPoint, pszName, rcOs2); + if (rcOs2 == ERROR_INVALID_FSD_NAME) + return VERR_ACCESS_DENIED; + if ( rcOs2 == ERROR_INVALID_DRIVE + || rcOs2 == ERROR_INVALID_PATH) + return VERR_TRY_AGAIN; + +#else + int rc2 = umount(pszMountPoint); + if (rc2 == 0) + { + /* Remove the mount directory if not directly under the root dir. */ + RTPATHPARSED Parsed; + RT_ZERO(Parsed); + RTPathParse(pszMountPoint, &Parsed, sizeof(Parsed), RTPATH_STR_F_STYLE_HOST); + if (Parsed.cComps >= 3) + RTDirRemove(pszMountPoint); + + return VINF_SUCCESS; + } + rc2 = errno; + VGSvcVerbose(2, "vbsvcAutomounterUnmount: umount failed on '%s' ('%s'): %d\n", pszMountPoint, pszName, rc2); + if (rc2 != EBUSY && rc2 != EAGAIN) + return VERR_ACCESS_DENIED; +#endif + + /* + * Check what's mounted there before we start delaying. + */ + RTThreadSleep(8); /* fudge */ + rc = vbsvcAutomounterQueryMountPoint(pszMountPoint, pszName); + if (rc == VINF_SUCCESS) + { /* pszName is mounted there */ } + else if (rc == VWRN_NOT_FOUND) /* nothing mounted there */ + return VINF_SUCCESS; + else + { + Assert(rc == VERR_RESOURCE_BUSY || rc == VERR_ACCESS_DENIED); + return VERR_RESOURCE_BUSY; + } + + if (iTry >= 5) + return VERR_TRY_AGAIN; + RTThreadSleep(1000); + } +} + + +/** + * Unmounts a mount table entry and evicts it from the table if successful. + * + * @returns The next iTable (same value on success, +1 on failure). + * @param pTable The mount table. + * @param iTable The table entry. + * @param pszReason Why we're here. + */ +static uint32_t vbsvcAutomounterUnmountEntry(PVBSVCAUTOMOUNTERTABLE pTable, uint32_t iTable, const char *pszReason) +{ + Assert(iTable < pTable->cEntries); + PVBSVCAUTOMOUNTERENTRY pEntry = pTable->papEntries[iTable]; + VGSvcVerbose(2, "vbsvcAutomounterUnmountEntry: #%u: '%s' at '%s' (reason: %s)\n", + iTable, pEntry->pszName, pEntry->pszActualMountPoint, pszReason); + + /* + * Do we need to umount the entry? Return if unmount fails and we . + */ + if (pEntry->pszActualMountPoint) + { + int rc = vbsvcAutomounterUnmount(pEntry->pszActualMountPoint, pEntry->pszName); + if (rc == VERR_TRY_AGAIN) + { + VGSvcVerbose(1, "vbsvcAutomounterUnmountEntry: Keeping '%s' -> '%s' (VERR_TRY_AGAIN)\n", + pEntry->pszActualMountPoint, pEntry->pszName); + return iTable + 1; + } + } + + /* + * Remove the entry by shifting up the ones after it. + */ + pTable->cEntries -= 1; + uint32_t cAfter = pTable->cEntries - iTable; + if (cAfter) + memmove(&pTable->papEntries[iTable], &pTable->papEntries[iTable + 1], cAfter * sizeof(pTable->papEntries[0])); + pTable->papEntries[pTable->cEntries] = NULL; + + RTStrFree(pEntry->pszActualMountPoint); + pEntry->pszActualMountPoint = NULL; + RTStrFree(pEntry->pszMountPoint); + pEntry->pszMountPoint = NULL; + RTStrFree(pEntry->pszName); + pEntry->pszName = NULL; + RTMemFree(pEntry); + + return iTable; +} + + +/** + * @callback_method_impl{FNRTSORTCMP, For sorting the mappings by ID. } + */ +static DECLCALLBACK(int) vbsvcSharedFolderMappingCompare(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF_PV(pvUser); + PVBGLR3SHAREDFOLDERMAPPING pMapping1 = (PVBGLR3SHAREDFOLDERMAPPING)pvElement1; + PVBGLR3SHAREDFOLDERMAPPING pMapping2 = (PVBGLR3SHAREDFOLDERMAPPING)pvElement2; + return pMapping1->u32Root < pMapping2->u32Root ? -1 : pMapping1->u32Root != pMapping2->u32Root ? 1 : 0; +} + + +/** + * Refreshes the mount table. + * + * @returns true if we've processed the current config, false if we failed to + * query the mappings. + * @param pTable The mount table to refresh. + */ +static bool vbsvcAutomounterRefreshTable(PVBSVCAUTOMOUNTERTABLE pTable) +{ + /* + * Query the root IDs of all auto-mountable shared folder mappings. + */ + uint32_t cMappings = 0; + PVBGLR3SHAREDFOLDERMAPPING paMappings = NULL; + int rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /*fAutoMountOnly*/, &paMappings, &cMappings); + if (RT_FAILURE(rc)) + { + VGSvcError("vbsvcAutomounterRefreshTable: VbglR3SharedFolderGetMappings failed: %Rrc\n", rc); + return false; + } + + /* + * Walk the table and the mappings in parallel, so we have to make sure + * they are both sorted by root ID. + */ + if (cMappings > 1) + RTSortShell(paMappings, cMappings, sizeof(paMappings[0]), vbsvcSharedFolderMappingCompare, NULL); + + /* + * Pass #1: Do all the umounting. + * + * By doing the umount pass separately from the mount pass, we can + * better handle changing involving the same mount points (switching + * mount points between two shares, new share on same mount point but + * with lower root ID, ++). + */ + uint32_t iTable = 0; + for (uint32_t iSrc = 0; iSrc < cMappings; iSrc++) + { + /* + * Unmount table entries up to idRootSrc. + */ + uint32_t const idRootSrc = paMappings[iSrc].u32Root; + while ( iTable < pTable->cEntries + && pTable->papEntries[iTable]->idRoot < idRootSrc) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "dropped"); + + /* + * If the paMappings entry and the mount table entry has the same + * root ID, umount if anything has changed or if we cannot query + * the mapping data. + */ + if (iTable < pTable->cEntries) + { + PVBSVCAUTOMOUNTERENTRY pEntry = pTable->papEntries[iTable]; + if (pEntry->idRoot == idRootSrc) + { + uint32_t uRootIdVer = UINT32_MAX; + uint64_t fFlags = 0; + char *pszName = NULL; + char *pszMntPt = NULL; + rc = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF, + &pszName, &pszMntPt, &fFlags, &uRootIdVer); + if (RT_FAILURE(rc)) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "VbglR3SharedFolderQueryFolderInfo failed"); + else if (pEntry->uRootIdVersion != uRootIdVer) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "root ID version changed"); + else if (RTPathCompare(pEntry->pszMountPoint, pszMntPt) != 0) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "mount point changed"); + else if (RTStrICmp(pEntry->pszName, pszName) != 0) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "name changed"); + else + { + VGSvcVerbose(3, "vbsvcAutomounterRefreshTable: Unchanged: %s -> %s\n", pEntry->pszMountPoint, pEntry->pszName); + iTable++; + } + if (RT_SUCCESS(rc)) + { + RTStrFree(pszName); + RTStrFree(pszMntPt); + } + } + } + } + + while (iTable < pTable->cEntries) + iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "dropped (tail)"); + + VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u entries in mount table after pass #1.\n", pTable->cEntries); + + /* + * Pass #2: Try mount new folders that has mount points assigned. + * Pass #3: Try mount new folders not mounted in pass #2. + */ + for (uint32_t iPass = 2; iPass <= 3; iPass++) + { + iTable = 0; + for (uint32_t iSrc = 0; iSrc < cMappings; iSrc++) + { + uint32_t const idRootSrc = paMappings[iSrc].u32Root; + + /* + * Skip tabel entries we couldn't umount in pass #1. + */ + while ( iTable < pTable->cEntries + && pTable->papEntries[iTable]->idRoot < idRootSrc) + { + VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: Skipping idRoot=%u %s\n", + iPass, iSrc, iTable, pTable->papEntries[iTable]->idRoot, pTable->papEntries[iTable]->pszName); + iTable++; + } + + /* + * New share? + */ + if ( iTable >= pTable->cEntries + || pTable->papEntries[iTable]->idRoot != idRootSrc) + { + uint32_t uRootIdVer = UINT32_MAX; + uint64_t fFlags = 0; + char *pszName = NULL; + char *pszMntPt = NULL; + rc = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF, + &pszName, &pszMntPt, &fFlags, &uRootIdVer); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: Mounting idRoot=%u/%u %s\n", iPass, iSrc, iTable, + idRootSrc, iTable >= pTable->cEntries ? UINT32_MAX : pTable->papEntries[iTable]->idRoot, pszName); + iTable = vbsvcAutomounterMountNewEntry(pTable, iTable, pszName, pszMntPt, fFlags, + idRootSrc, uRootIdVer, iPass == 3); + + RTStrFree(pszName); + RTStrFree(pszMntPt); + } + else + VGSvcVerbose(1, "vbsvcAutomounterRefreshTable: VbglR3SharedFolderQueryFolderInfo failed: %Rrc\n", rc); + } + else + VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: idRootSrc=%u vs idRoot=%u %s\n", iPass, iSrc, + iTable, idRootSrc, pTable->papEntries[iTable]->idRoot, pTable->papEntries[iTable]->pszName); + } + } + + VbglR3SharedFolderFreeMappings(paMappings); + return true; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vbsvcAutomounterWorker(bool volatile *pfShutdown) +{ + /* + * Tell the control thread that it can continue spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* Divert old hosts to original auto-mount code. */ + if (!g_fHostSupportsWaitAndInfoQuery) + return vbsvcAutoMountWorkerOld(pfShutdown); + + /* + * Initialize the state in case we're restarted... + */ + VBSVCAUTOMOUNTERTABLE MountTable = { 0, 0, NULL }; + int rc = vbsvcAutomounterPopulateTable(&MountTable); + if (RT_FAILURE(rc)) + { + VGSvcError("vbsvcAutomounterWorker: vbsvcAutomounterPopulateTable failed (%Rrc), quitting!\n", rc); + return rc; + } + + /* + * Work loop. + */ + uint32_t uConfigVer = UINT32_MAX; + uint32_t uNewVersion = 0; + bool fForceRefresh = true; + while (!*pfShutdown) + { + /* + * Update the mounts. + */ + if ( uConfigVer != uNewVersion + || fForceRefresh) + { + fForceRefresh = !vbsvcAutomounterRefreshTable(&MountTable); + uConfigVer = uNewVersion; + } + + /* + * Wait for more to do. + */ + if (!*pfShutdown) + { + uNewVersion = uConfigVer - 1; + VGSvcVerbose(2, "vbsvcAutomounterWorker: Waiting with uConfigVer=%u\n", uConfigVer); + rc = VbglR3SharedFolderWaitForMappingsChanges(g_idClientSharedFolders, uConfigVer, &uNewVersion); + VGSvcVerbose(2, "vbsvcAutomounterWorker: Woke up with uNewVersion=%u and rc=%Rrc\n", uNewVersion, rc); + + /* Delay a little before doing a table refresh so the GUI can finish + all its updates. Delay a little longer on non-shutdown failure to + avoid eating too many CPU cycles if something goes wrong here... */ + if (!*pfShutdown) + RTSemEventMultiWait(g_hAutoMountEvent, RT_SUCCESS(rc) ? 256 : 1000); + } + } + + /* + * Destroy the mount table. + */ + while (MountTable.cEntries-- > 0) + RTMemFree(MountTable.papEntries[MountTable.cEntries]); + MountTable.papEntries = NULL; + + VGSvcVerbose(3, "vbsvcAutomounterWorker: Finished\n"); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vbsvcAutomounterStop(void) +{ + RTSemEventMultiSignal(g_hAutoMountEvent); + if (g_fHostSupportsWaitAndInfoQuery) + VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vbsvcAutomounterTerm(void) +{ + VGSvcVerbose(3, "vbsvcAutoMountTerm\n"); + + if (g_fHostSupportsWaitAndInfoQuery) + VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders); + + VbglR3SharedFolderDisconnect(g_idClientSharedFolders); + g_idClientSharedFolders = 0; + + if (g_hAutoMountEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_hAutoMountEvent); + g_hAutoMountEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'automount' service description. + */ +VBOXSERVICE g_AutoMount = +{ + /* pszName. */ + "automount", + /* pszDescription. */ + "Automounter for Shared Folders", + /* pszUsage. */ + NULL, + /* pszOptions. */ + NULL, + /* methods */ + VGSvcDefaultPreInit, + VGSvcDefaultOption, + vbsvcAutomounterInit, + vbsvcAutomounterWorker, + vbsvcAutomounterStop, + vbsvcAutomounterTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp new file mode 100644 index 00000000..a1229257 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp @@ -0,0 +1,457 @@ +/* $Id: VBoxServiceBalloon.cpp $ */ +/** @file + * VBoxService - Memory Ballooning. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc_memballoon VBoxService - Memory Ballooning + * + * The Memory Ballooning subservice works with VBoxGuest, PGM and GMM to + * dynamically reallocate memory between VMs. + * + * Memory ballooning is typically used to deal with overcomitting memory on the + * host. It allowes you to borrow memory from one or more VMs and make it + * available to others. In theory it could also be used to make memory + * available to the host system, however memory fragmentation typically makes + * that difficult. + * + * The memory ballooning subservices talks to PGM, GMM and Main via the VMMDev. + * It polls for change requests at an interval and executes them when they + * arrive. There are two ways we implement the actual ballooning, either + * VBoxGuest allocates kernel memory and donates it to the host, or this service + * allocates process memory which VBoxGuest then locks down and donates to the + * host. While we prefer the former method it is not practicable on all OS and + * we have to use the latter. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/system.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <VBox/err.h> +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" + +#ifdef RT_OS_LINUX +# include <iprt/param.h> +# include <sys/mman.h> +# ifndef MADV_DONTFORK +# define MADV_DONTFORK 10 +# endif +#endif + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The balloon size. */ +static uint32_t g_cMemBalloonChunks = 0; + +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_MemBalloonEvent = NIL_RTSEMEVENTMULTI; + +/** The array holding the R3 pointers of the balloon. */ +static void **g_pavBalloon = NULL; + +#ifdef RT_OS_LINUX +/** True = madvise(MADV_DONTFORK) works, false otherwise. */ +static bool g_fSysMadviseWorks; +#endif + + +/** + * Check whether madvise() works. + */ +static void vgsvcBalloonInitMadvise(void) +{ +#ifdef RT_OS_LINUX + void *pv = (void*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pv != MAP_FAILED) + { + g_fSysMadviseWorks = madvise(pv, PAGE_SIZE, MADV_DONTFORK) == 0; + munmap(pv, PAGE_SIZE); + } +#endif +} + + +/** + * Allocate a chunk of the balloon. Fulfil the prerequisite that we can lock this memory + * and protect it against fork() in R0. See also suplibOsPageAlloc(). + */ +static void *VGSvcBalloonAllocChunk(void) +{ + size_t cb = VMMDEV_MEMORY_BALLOON_CHUNK_SIZE; + char *pu8; + +#ifdef RT_OS_LINUX + if (!g_fSysMadviseWorks) + cb += 2 * PAGE_SIZE; + + pu8 = (char*)mmap(NULL, cb, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pu8 == MAP_FAILED) + return NULL; + + if (g_fSysMadviseWorks) + { + /* + * It is not fatal if we fail here but a forked child (e.g. the ALSA sound server) + * could crash. Linux < 2.6.16 does not implement madvise(MADV_DONTFORK) but the + * kernel seems to split bigger VMAs and that is all that we want -- later we set the + * VM_DONTCOPY attribute in supdrvOSLockMemOne(). + */ + madvise(pu8, cb, MADV_DONTFORK); + } + else + { + /* + * madvise(MADV_DONTFORK) is not available (most probably Linux 2.4). Enclose any + * mmapped region by two unmapped pages to guarantee that there is exactly one VM + * area struct of the very same size as the mmap area. + */ + RTMemProtect(pu8, PAGE_SIZE, RTMEM_PROT_NONE); + RTMemProtect(pu8 + cb - PAGE_SIZE, PAGE_SIZE, RTMEM_PROT_NONE); + pu8 += PAGE_SIZE; + } + +#else + + pu8 = (char*)RTMemPageAlloc(cb); + if (!pu8) + return pu8; + +#endif + + memset(pu8, 0, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE); + return pu8; +} + + +/** + * Free an allocated chunk undoing VGSvcBalloonAllocChunk(). + */ +static void vgsvcBalloonFreeChunk(void *pv) +{ + char *pu8 = (char*)pv; + size_t cb = VMMDEV_MEMORY_BALLOON_CHUNK_SIZE; + +#ifdef RT_OS_LINUX + + if (!g_fSysMadviseWorks) + { + cb += 2 * PAGE_SIZE; + pu8 -= PAGE_SIZE; + /* This is not really necessary */ + RTMemProtect(pu8, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + RTMemProtect(pu8 + cb - PAGE_SIZE, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + } + munmap(pu8, cb); + +#else + + RTMemPageFree(pu8, cb); + +#endif +} + + +/** + * Adapt the R0 memory balloon by granting/reclaiming 1MB chunks to/from R0. + * + * returns IPRT status code. + * @param cNewChunks The new number of 1MB chunks in the balloon. + */ +static int vgsvcBalloonSetUser(uint32_t cNewChunks) +{ + if (cNewChunks == g_cMemBalloonChunks) + return VINF_SUCCESS; + + VGSvcVerbose(3, "vgsvcBalloonSetUser: cNewChunks=%u g_cMemBalloonChunks=%u\n", cNewChunks, g_cMemBalloonChunks); + int rc = VINF_SUCCESS; + if (cNewChunks > g_cMemBalloonChunks) + { + /* inflate */ + g_pavBalloon = (void**)RTMemRealloc(g_pavBalloon, cNewChunks * sizeof(void*)); + uint32_t i; + for (i = g_cMemBalloonChunks; i < cNewChunks; i++) + { + void *pv = VGSvcBalloonAllocChunk(); + if (!pv) + break; + rc = VbglR3MemBalloonChange(pv, /* inflate=*/ true); + if (RT_SUCCESS(rc)) + { + g_pavBalloon[i] = pv; +#ifndef RT_OS_SOLARIS + /* + * Protect against access by dangling pointers (ignore errors as it may fail). + * On Solaris it corrupts the address space leaving the process unkillable. This + * could perhaps be related to what the underlying segment driver does; currently + * just disable it. + */ + RTMemProtect(pv, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE, RTMEM_PROT_NONE); +#endif + g_cMemBalloonChunks++; + } + else + { + vgsvcBalloonFreeChunk(pv); + break; + } + } + VGSvcVerbose(3, "vgsvcBalloonSetUser: inflation complete. chunks=%u rc=%d\n", i, rc); + } + else + { + /* deflate */ + uint32_t i; + for (i = g_cMemBalloonChunks; i-- > cNewChunks;) + { + void *pv = g_pavBalloon[i]; + rc = VbglR3MemBalloonChange(pv, /* inflate=*/ false); + if (RT_SUCCESS(rc)) + { +#ifndef RT_OS_SOLARIS + /* unprotect */ + RTMemProtect(pv, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE); +#endif + vgsvcBalloonFreeChunk(pv); + g_pavBalloon[i] = NULL; + g_cMemBalloonChunks--; + } + else + break; + VGSvcVerbose(3, "vgsvcBalloonSetUser: deflation complete. chunks=%u rc=%d\n", i, rc); + } + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcBalloonInit(void) +{ + VGSvcVerbose(3, "vgsvcBalloonInit\n"); + + int rc = RTSemEventMultiCreate(&g_MemBalloonEvent); + AssertRCReturn(rc, rc); + + vgsvcBalloonInitMadvise(); + + g_cMemBalloonChunks = 0; + uint32_t cNewChunks = 0; + bool fHandleInR3; + + /* Check balloon size */ + rc = VbglR3MemBalloonRefresh(&cNewChunks, &fHandleInR3); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "MemBalloon: New balloon size %d MB (%s memory)\n", cNewChunks, fHandleInR3 ? "R3" : "R0"); + if (fHandleInR3) + rc = vgsvcBalloonSetUser(cNewChunks); + else + g_cMemBalloonChunks = cNewChunks; + } + if (RT_FAILURE(rc)) + { + /* If the service was not found, we disable this service without + causing VBoxService to fail. */ + if ( rc == VERR_NOT_IMPLEMENTED +#ifdef RT_OS_WINDOWS /** @todo r=bird: Windows kernel driver should return VERR_NOT_IMPLEMENTED, + * VERR_INVALID_PARAMETER has too many other uses. */ + || rc == VERR_INVALID_PARAMETER +#endif + ) + { + VGSvcVerbose(0, "MemBalloon: Memory ballooning support is not available\n"); + rc = VERR_SERVICE_DISABLED; + } + else + { + VGSvcVerbose(3, "MemBalloon: VbglR3MemBalloonRefresh failed with %Rrc\n", rc); + rc = VERR_SERVICE_DISABLED; /** @todo Playing safe for now, figure out the exact status codes here. */ + } + RTSemEventMultiDestroy(g_MemBalloonEvent); + g_MemBalloonEvent = NIL_RTSEMEVENTMULTI; + } + + return rc; +} + + +/** + * Query the size of the memory balloon, given as a page count. + * + * @returns Number of pages. + * @param cbPage The page size. + */ +uint32_t VGSvcBalloonQueryPages(uint32_t cbPage) +{ + Assert(cbPage > 0); + return g_cMemBalloonChunks * (VMMDEV_MEMORY_BALLOON_CHUNK_SIZE / cbPage); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vgsvcBalloonWorker(bool volatile *pfShutdown) +{ + /* Start monitoring of the stat event change event. */ + int rc = VbglR3CtlFilterMask(VMMDEV_EVENT_BALLOON_CHANGE_REQUEST, 0); + if (RT_FAILURE(rc)) + { + VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3CtlFilterMask failed with %Rrc\n", rc); + return rc; + } + + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Now enter the loop retrieving runtime data continuously. + */ + for (;;) + { + uint32_t fEvents = 0; + + /* Check if an update interval change is pending. */ + rc = VbglR3WaitEvent(VMMDEV_EVENT_BALLOON_CHANGE_REQUEST, 0 /* no wait */, &fEvents); + if ( RT_SUCCESS(rc) + && (fEvents & VMMDEV_EVENT_BALLOON_CHANGE_REQUEST)) + { + uint32_t cNewChunks; + bool fHandleInR3; + rc = VbglR3MemBalloonRefresh(&cNewChunks, &fHandleInR3); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "vgsvcBalloonInit: new balloon size %d MB (%s memory)\n", cNewChunks, fHandleInR3 ? "R3" : "R0"); + if (fHandleInR3) + { + rc = vgsvcBalloonSetUser(cNewChunks); + if (RT_FAILURE(rc)) + { + VGSvcVerbose(3, "vgsvcBalloonInit: failed to set balloon size %d MB (%s memory)\n", + cNewChunks, fHandleInR3 ? "R3" : "R0"); + } + else + VGSvcVerbose(3, "vgsvcBalloonInit: successfully set requested balloon size %d.\n", cNewChunks); + } + else + g_cMemBalloonChunks = cNewChunks; + } + else + VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3MemBalloonRefresh failed with %Rrc\n", rc); + } + + /* + * Block for a while. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + int rc2 = RTSemEventMultiWait(g_MemBalloonEvent, 5000); + if (*pfShutdown) + break; + if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) + { + VGSvcError("vgsvcBalloonInit: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); + rc = rc2; + break; + } + } + + /* Cancel monitoring of the memory balloon change event. */ + rc = VbglR3CtlFilterMask(0, VMMDEV_EVENT_BALLOON_CHANGE_REQUEST); + if (RT_FAILURE(rc)) + VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3CtlFilterMask failed with %Rrc\n", rc); + + VGSvcVerbose(3, "vgsvcBalloonInit: finished mem balloon change request thread\n"); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcBalloonStop(void) +{ + RTSemEventMultiSignal(g_MemBalloonEvent); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcBalloonTerm(void) +{ + if (g_MemBalloonEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_MemBalloonEvent); + g_MemBalloonEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'memballoon' service description. + */ +VBOXSERVICE g_MemBalloon = +{ + /* pszName. */ + "memballoon", + /* pszDescription. */ + "Memory Ballooning", + /* pszUsage. */ + NULL, + /* pszOptions. */ + NULL, + /* methods */ + VGSvcDefaultPreInit, + VGSvcDefaultOption, + vgsvcBalloonInit, + vgsvcBalloonWorker, + vgsvcBalloonStop, + vgsvcBalloonTerm +}; diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp new file mode 100644 index 00000000..35b123e8 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp @@ -0,0 +1,1140 @@ +/** $Id: VBoxServiceClipboard-os2.cpp $ */ +/** @file + * VBoxService - Guest Additions Clipboard Service, OS/2. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc_clipboard VBoxService - Clipboard (OS/2) + * + * The Clipboard subservice provides clipboard sharing for OS/2 guests only. + * + * This was the second subservice that was added to VBoxService. OS/2 is a + * single user system and we don't provide any VBoxTray or VBoxClient like + * processes. Because it's kind of simple system, it became natural to put the + * clipboard sharing here in VBoxService for OS/2. + * + * In addition to integrating with the native OS/2 PM clipboard formats, we also + * try provide the Odin32, a windows API layer for OS/2 (developed by Sander van + * Leeuwen and friends, later mainly InnoTek), with additional formats. + * + * Bitmaps are currently not supported, but that can easily be added should the + * need ever arrise. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define INCL_BASE +#define INCL_PM +#define INCL_ERRORS +#include <os2.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/utf16.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include "VBoxServiceInternal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Header for Odin32 specific clipboard entries. + * (Used to get the correct size of the data.) + */ +typedef struct _Odin32ClipboardHeader +{ + /** magic number */ + char achMagic[8]; + /** Size of the following data. + * (The interpretation depends on the type.) */ + unsigned cbData; + /** Odin32 format number. */ + unsigned uFormat; +} CLIPHEADER, *PCLIPHEADER; + +#define CLIPHEADER_MAGIC "Odin\1\0\1" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** The control thread (main) handle. + * Only used to avoid some queue creation trouble. */ +static RTTHREAD g_ThreadCtrl = NIL_RTTHREAD; +/** The HAB of the control thread (main). */ +static HAB g_habCtrl = NULLHANDLE; +/** The HMQ of the control thread (main). */ +static HMQ g_hmqCtrl = NULLHANDLE; + +/** The Listener thread handle. */ +static RTTHREAD g_ThreadListener = NIL_RTTHREAD; +/** The HAB of the listener thread. */ +static HAB g_habListener = NULLHANDLE; +/** The HMQ of the listener thread. */ +static HMQ g_hmqListener = NULLHANDLE; +/** Indicator that gets set if the listener thread is successfully initialized. */ +static bool volatile g_fListenerOkay = false; + +/** The HAB of the worker thread. */ +static HAB g_habWorker = NULLHANDLE; +/** The HMQ of the worker thread. */ +static HMQ g_hmqWorker = NULLHANDLE; +/** The object window handle. */ +static HWND g_hwndWorker = NULLHANDLE; +/** The timer id returned by WinStartTimer. */ +static ULONG g_idWorkerTimer = ~0UL; +/** The state of the clipboard. + * @remark I'm trying out the 'k' prefix from the mac here, bear with me. */ +static enum +{ + /** The clipboard hasn't been initialized yet. */ + kClipboardState_Uninitialized = 0, + /** WinSetClipbrdViewer call in progress, ignore WM_DRAWCLIPBOARD. */ + kClipboardState_SettingViewer, + /** We're monitoring the clipboard as a viewer. */ + kClipboardState_Viewer, + /** We're monitoring the clipboard using polling. + * This usually means something is wrong... */ + kClipboardState_Polling, + /** We're destroying the clipboard content, ignore WM_DESTROYCLIPBOARD. */ + kClipboardState_Destroying, + /** We're owning the clipboard (i.e. we have data on it). */ + kClipboardState_Owner +} g_enmState = kClipboardState_Uninitialized; +/** Set if the clipboard was empty the last time we polled it. */ +static bool g_fEmptyClipboard = false; + +/** A clipboard format atom for the dummy clipboard data we insert + * watching for clipboard changes. If this format is found on the + * clipboard, the empty clipboard function has not been called + * since we last polled it. */ +static ATOM g_atomNothingChanged = 0; + +/** The clipboard connection client ID. */ +static uint32_t g_u32ClientId; +/** Odin32 CF_UNICODETEXT. See user32.cpp. */ +static ATOM g_atomOdin32UnicodeText = 0; +/** Odin32 CF_UNICODETEXT. See user32.cpp. */ +#define SZFMT_ODIN32_UNICODETEXT (PCSZ)"Odin32 UnicodeText" + + + + +/** + * @interface_method_impl{VBOXSERVICE,pfnPreInit} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2PreInit(void) +{ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2Option(const char **ppszShort, int argc, char **argv, int *pi) +{ + NOREF(ppszShort); + NOREF(argc); + NOREF(argv); + NOREF(pi); + + return -1; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2Init(void) +{ + int rc = VERR_GENERAL_FAILURE; + g_ThreadCtrl = RTThreadSelf(); + + /* + * Make PM happy. + */ + PPIB pPib; + PTIB pTib; + DosGetInfoBlocks(&pTib, &pPib); + pPib->pib_ultype = 3; /* PM session type */ + + /* + * Since we have to send shutdown messages and such from the + * service controller (main) thread, create a HAB and HMQ for it. + */ + g_habCtrl = WinInitialize(0); + if (g_habCtrl == NULLHANDLE) + { + VGSvcError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE)); + return VERR_GENERAL_FAILURE; + } + g_hmqCtrl = WinCreateMsgQueue(g_habCtrl, 0); + if (g_hmqCtrl != NULLHANDLE) + { + WinCancelShutdown(g_hmqCtrl, TRUE); /* We don't care about shutdown */ + + /* + * Create the 'nothing-changed' format. + */ + g_atomNothingChanged = WinAddAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service"); + LONG lLastError = WinGetLastError(g_habCtrl); + if (g_atomNothingChanged == 0) + g_atomNothingChanged = WinFindAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service"); + if (g_atomNothingChanged) + { + /* + * Connect to the clipboard service. + */ + VGSvcVerbose(4, "clipboard: connecting\n"); + rc = VbglR3ClipboardConnect(&g_u32ClientId); + if (RT_SUCCESS(rc)) + { + /* + * Create any extra clipboard type atoms, like the odin unicode text. + */ + g_atomOdin32UnicodeText = WinAddAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT); + lLastError = WinGetLastError(g_habCtrl); + if (g_atomOdin32UnicodeText == 0) + g_atomOdin32UnicodeText = WinFindAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT); + if (g_atomOdin32UnicodeText == 0) + VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n", + lLastError, WinGetLastError(g_habCtrl)); + + VGSvcVerbose(2, "g_u32ClientId=%RX32 g_atomNothingChanged=%#x g_atomOdin32UnicodeText=%#x\n", + g_u32ClientId, g_atomNothingChanged, g_atomOdin32UnicodeText); + return VINF_SUCCESS; + } + + VGSvcError("Failed to connect to the clipboard service, rc=%Rrc!\n", rc); + } + else + VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n", + lLastError, WinGetLastError(g_habCtrl)); + } + else + VGSvcError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl)); + WinTerminate(g_habCtrl); + return rc; +} + + +/** + * Check that we're still the view / try make us the viewer. + */ +static void vgsvcClipboardOs2PollViewer(void) +{ + const int iOrgState = g_enmState; + + HWND hwndClipboardViewer = WinQueryClipbrdViewer(g_habWorker); + if (hwndClipboardViewer == g_hwndWorker) + return; + + if (hwndClipboardViewer == NULLHANDLE) + { + /* The API will send a WM_DRAWCLIPBOARD message before returning. */ + g_enmState = kClipboardState_SettingViewer; + if (WinSetClipbrdViewer(g_habWorker, g_hwndWorker)) + g_enmState = kClipboardState_Viewer; + else + g_enmState = kClipboardState_Polling; + } + else + g_enmState = kClipboardState_Polling; + if ((int)g_enmState != iOrgState) + { + if (g_enmState == kClipboardState_Viewer) + VGSvcVerbose(3, "clipboard: viewer\n"); + else + VGSvcVerbose(3, "clipboard: poller\n"); + } +} + + +/** + * Advertise the formats available from the host. + * + * @param fFormats The formats available on the host. + */ +static void vgsvcClipboardOs2AdvertiseHostFormats(uint32_t fFormats) +{ + /* + * Open the clipboard and switch to 'destruction' mode. + * Make sure we stop being viewer. Temporarily also make sure we're + * not the owner so that PM won't send us any WM_DESTROYCLIPBOARD message. + */ + if (WinOpenClipbrd(g_habWorker)) + { + if (g_enmState == kClipboardState_Viewer) + WinSetClipbrdViewer(g_habWorker, NULLHANDLE); + if (g_enmState == kClipboardState_Owner) + WinSetClipbrdOwner(g_habWorker, NULLHANDLE); + + g_enmState = kClipboardState_Destroying; + if (WinEmptyClipbrd(g_habWorker)) + { + /* + * Take clipboard ownership. + */ + if (WinSetClipbrdOwner(g_habWorker, g_hwndWorker)) + { + g_enmState = kClipboardState_Owner; + + /* + * Do the format advertising. + */ + if (fFormats & (VBOX_SHCL_FMT_UNICODETEXT/* | VBOX_SHCL_FMT_HTML ?? */)) + { + if (!WinSetClipbrdData(g_habWorker, 0, CF_TEXT, CFI_POINTER)) + VGSvcError("WinSetClipbrdData(,,CF_TEXT,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + if ( g_atomOdin32UnicodeText + && !WinSetClipbrdData(g_habWorker, 0, g_atomOdin32UnicodeText, CFI_POINTER)) + VGSvcError("WinSetClipbrdData(,,g_atomOdin32UnicodeText,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + } + if (fFormats & VBOX_SHCL_FMT_BITMAP) + { + /** @todo bitmaps */ + } + } + else + { + VGSvcError("WinSetClipbrdOwner failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + g_enmState = kClipboardState_Polling; + } + } + else + { + VGSvcError("WinEmptyClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + g_enmState = kClipboardState_Polling; + } + + if (g_enmState == kClipboardState_Polling) + { + g_fEmptyClipboard = true; + vgsvcClipboardOs2PollViewer(); + } + + WinCloseClipbrd(g_habWorker); + } + else + VGSvcError("vgsvcClipboardOs2AdvertiseHostFormats: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); +} + + +/** + * Converts (render) to an Odin32 clipboard format. + * + * We ASSUME we get windows data from the host and all we've got to do here is + * slapping an Odin32 header on it. + * + * @returns Pointer to the data (DosFreeMem). + * @param fFormat The host format. + * @param usFmt The PM/Odin32 format. + * @param pv The data in host formatting. + * @param cb The size of the data. + */ +static void *vgsvcClipboardOs2ConvertToOdin32(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb) +{ + PVOID pvPM = NULL; + APIRET rc = DosAllocSharedMem(&pvPM, NULL, cb + sizeof(CLIPHEADER), OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT); + if (rc) + { + PCLIPHEADER pHdr = (PCLIPHEADER)pvPM; + memcpy(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic)); + pHdr->cbData = cb; + if (usFmt == g_atomOdin32UnicodeText) + pHdr->uFormat = usFmt; + else + AssertFailed(); + memcpy(pHdr + 1, pv, cb); + } + else + { + VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), rc); + pvPM = NULL; + } + return pvPM; +} + + +/** + * Converts (render) to a PM clipboard format. + * + * @returns Pointer to the data (DosFreeMem). + * @param fFormat The host format. + * @param usFmt The PM/Odin32 format. + * @param pv The data in host formatting. + * @param cb The size of the data. + */ +static void *vgsvcClipboardOs2ConvertToPM(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb) +{ + void *pvPM = NULL; + + /* + * The Odin32 stuff is simple, we just assume windows data from the host + * and all we need to do is add the header. + */ + if ( usFmt + && ( usFmt == g_atomOdin32UnicodeText + /* || usFmt == ...*/ + ) + ) + pvPM = vgsvcClipboardOs2ConvertToOdin32(fFormat, usFmt, pv, cb); + else if (usFmt == CF_TEXT) + { + /* + * Convert the unicode text to the current ctype locale. + * + * Note that we probably should be using the current PM or DOS codepage + * here instead of the LC_CTYPE one which iconv uses by default. + * -lazybird + */ + Assert(fFormat & VBOX_SHCL_FMT_UNICODETEXT); + char *pszUtf8; + int rc = RTUtf16ToUtf8((PCRTUTF16)pv, &pszUtf8); + if (RT_SUCCESS(rc)) + { + char *pszLocale; + rc = RTStrUtf8ToCurrentCP(&pszLocale, pszUtf8); + if (RT_SUCCESS(rc)) + { + size_t cbPM = strlen(pszLocale) + 1; + APIRET orc = DosAllocSharedMem(&pvPM, NULL, cbPM, OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT); + if (orc == NO_ERROR) + memcpy(pvPM, pszLocale, cbPM); + else + { + VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), orc); + pvPM = NULL; + } + RTStrFree(pszLocale); + } + else + VGSvcError("RTStrUtf8ToCurrentCP() -> %Rrc\n", rc); + RTStrFree(pszUtf8); + } + else + VGSvcError("RTUtf16ToUtf8() -> %Rrc\n", rc); + } + + return pvPM; +} + + +/** + * Tries to deliver an advertised host format. + * + * @param usFmt The PM format name. + * + * @remark We must not try open the clipboard here because WM_RENDERFMT is a + * request send synchronously by someone who has already opened the + * clipboard. We would enter a deadlock trying to open it here. + */ +static void vgsvcClipboardOs2RenderFormat(USHORT usFmt) +{ + bool fSucceeded = false; + + /* + * Determine which format. + */ + uint32_t fFormat; + if ( usFmt == CF_TEXT + || usFmt == g_atomOdin32UnicodeText) + fFormat = VBOX_SHCL_FMT_UNICODETEXT; + else /** @todo bitmaps */ + fFormat = 0; + if (fFormat) + { + /* + * Query the data from the host. + * This might require two iterations because of buffer guessing. + */ + uint32_t cb = _4K; + uint32_t cbAllocated = cb; + int rc = VERR_NO_MEMORY; + void *pv = RTMemPageAllocZ(cbAllocated); + if (pv) + { + VGSvcVerbose(4, "clipboard: reading host data (%#x)\n", fFormat); + rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb); + if (rc == VINF_BUFFER_OVERFLOW) + { + RTMemPageFree(pv, cbAllocated); + cbAllocated = cb = RT_ALIGN_32(cb, PAGE_SIZE); + pv = RTMemPageAllocZ(cbAllocated); + rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb); + } + if (RT_FAILURE(rc)) + RTMemPageFree(pv, cbAllocated); + } + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "clipboard: read %u bytes\n", cb); + + /* + * Convert the host clipboard data to PM clipboard data and set it. + */ + PVOID pvPM = vgsvcClipboardOs2ConvertToPM(fFormat, usFmt, pv, cb); + if (pvPM) + { + if (WinSetClipbrdData(g_habWorker, (ULONG)pvPM, usFmt, CFI_POINTER)) + fSucceeded = true; + else + { + VGSvcError("vgsvcClipboardOs2RenderFormat: WinSetClipbrdData(,%p,%#x, CF_POINTER) failed, lasterror=%lx\n", + pvPM, usFmt, WinGetLastError(g_habWorker)); + DosFreeMem(pvPM); + } + } + RTMemPageFree(pv, cbAllocated); + } + else + VGSvcError("vgsvcClipboardOs2RenderFormat: Failed to query / allocate data. rc=%Rrc cb=%#RX32\n", rc, cb); + } + + /* + * Empty the clipboard on failure so we don't end up in any loops. + */ + if (!fSucceeded) + { + WinSetClipbrdOwner(g_habWorker, NULLHANDLE); + g_enmState = kClipboardState_Destroying; + WinEmptyClipbrd(g_habWorker); + g_enmState = kClipboardState_Polling; + g_fEmptyClipboard = true; + vgsvcClipboardOs2PollViewer(); + } +} + + +/** + * Sends data to the host. + * + * @param fFormat The data format the host is requesting. + */ +static void vgsvcClipboardOs2SendDataToHost(uint32_t fFormat) +{ + if (WinOpenClipbrd(g_habWorker)) + { + PRTUTF16 pwszFree = NULL; + void *pv = NULL; + uint32_t cb = 0; + + if (fFormat & VBOX_SHCL_FMT_UNICODETEXT) + { + /* Got any odin32 unicode text? */ + PVOID pvPM; + PCLIPHEADER pHdr = (PCLIPHEADER)WinQueryClipbrdData(g_habWorker, g_atomOdin32UnicodeText); + if ( pHdr + && !memcmp(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic))) + { + pv = pHdr + 1; + cb = pHdr->cbData; + } + + /* Got any CF_TEXT? */ + if ( !pv + && (pvPM = (PVOID)WinQueryClipbrdData(g_habWorker, CF_TEXT)) != NULL) + { + char *pszUtf8; + int rc = RTStrCurrentCPToUtf8(&pszUtf8, (const char *)pvPM); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwsz; + rc = RTStrToUtf16(pszUtf8, &pwsz); + if (RT_SUCCESS(rc)) + { + pv = pwszFree = pwsz; + cb = (RTUtf16Len(pwsz) + 1) * sizeof(RTUTF16); + } + RTStrFree(pszUtf8); + } + } + } + if (!pv) + VGSvcError("vgsvcClipboardOs2SendDataToHost: couldn't find data for %#x\n", fFormat); + + /* + * Now, sent whatever we've got to the host (it's waiting). + */ + VGSvcVerbose(4, "clipboard: writing %pv/%#d (fFormat=%#x)\n", pv, cb, fFormat); + VbglR3ClipboardWriteData(g_u32ClientId, fFormat, pv, cb); + RTUtf16Free(pwszFree); + + WinCloseClipbrd(g_habWorker); + } + else + { + VGSvcError("vgsvcClipboardOs2SendDataToHost: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + VGSvcVerbose(4, "clipboard: writing NULL/0 (fFormat=%x)\n", fFormat); + VbglR3ClipboardWriteData(g_u32ClientId, fFormat, NULL, 0); + } +} + + +/** + * Figure out what's on the clipboard and report it to the host. + */ +static void vgsvcClipboardOs2ReportFormats(void) +{ + uint32_t fFormats = 0; + ULONG ulFormat = 0; + while ((ulFormat = WinEnumClipbrdFmts(g_habWorker, ulFormat)) != 0) + { + if ( ulFormat == CF_TEXT + || ulFormat == g_atomOdin32UnicodeText) + fFormats |= VBOX_SHCL_FMT_UNICODETEXT; + /** @todo else bitmaps and stuff. */ + } + VGSvcVerbose(4, "clipboard: reporting fFormats=%#x\n", fFormats); + VbglR3ClipboardReportFormats(g_u32ClientId, fFormats); +} + + +/** + * Poll the clipboard for changes. + * + * This is called both when we're the viewer and when we're + * falling back to polling. If something has changed it will + * notify the host. + */ +static void vgsvcClipboardOs2Poll(void) +{ + if (WinOpenClipbrd(g_habWorker)) + { + /* + * If our dummy is no longer there, something has actually changed, + * unless the clipboard is really empty. + */ + ULONG fFmtInfo; + if (!WinQueryClipbrdFmtInfo(g_habWorker, g_atomNothingChanged, &fFmtInfo)) + { + if (WinEnumClipbrdFmts(g_habWorker, 0) != 0) + { + g_fEmptyClipboard = false; + vgsvcClipboardOs2ReportFormats(); + + /* inject the dummy */ + PVOID pv; + APIRET rc = DosAllocSharedMem(&pv, NULL, 1, OBJ_GIVEABLE | OBJ_GETTABLE | PAG_READ | PAG_WRITE | PAG_COMMIT); + if (rc == NO_ERROR) + { + if (WinSetClipbrdData(g_habWorker, (ULONG)pv, g_atomNothingChanged, CFI_POINTER)) + VGSvcVerbose(4, "clipboard: Added dummy item.\n"); + else + { + VGSvcError("vgsvcClipboardOs2Poll: WinSetClipbrdData failed, lasterr=%#lx\n", WinGetLastError(g_habWorker)); + DosFreeMem(pv); + } + } + else + VGSvcError("vgsvcClipboardOs2Poll: DosAllocSharedMem(,,1,) -> %ld\n", rc); + } + else if (!g_fEmptyClipboard) + { + g_fEmptyClipboard = true; + VGSvcVerbose(3, "Reporting empty clipboard\n"); + VbglR3ClipboardReportFormats(g_u32ClientId, 0); + } + } + WinCloseClipbrd(g_habWorker); + } + else + VGSvcError("vgsvcClipboardOs2Poll: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); +} + + +/** + * The clipboard we owned was destroyed by someone else. + */ +static void vgsvcClipboardOs2Destroyed(void) +{ + /* make sure we're no longer the owner. */ + if (WinQueryClipbrdOwner(g_habWorker) == g_hwndWorker) + WinSetClipbrdOwner(g_habWorker, NULLHANDLE); + + /* switch to polling state and notify the host. */ + g_enmState = kClipboardState_Polling; + g_fEmptyClipboard = true; + VGSvcVerbose(3, "Reporting empty clipboard\n"); + VbglR3ClipboardReportFormats(g_u32ClientId, 0); + + vgsvcClipboardOs2PollViewer(); +} + + +/** + * The window procedure for the object window. + * + * @returns Message result. + * + * @param hwnd The window handle. + * @param msg The message. + * @param mp1 Message parameter 1. + * @param mp2 Message parameter 2. + */ +static MRESULT EXPENTRY vgsvcClipboardOs2WinProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2) +{ + if (msg != WM_TIMER) + VGSvcVerbose(6, "vgsvcClipboardOs2WinProc: hwnd=%#lx msg=%#lx mp1=%#lx mp2=%#lx\n", hwnd, msg, mp1, mp2); + + switch (msg) + { + /* + * Handle the two system defined messages for object windows. + * + * We'll just use the CREATE/DESTROY message to create that timer we're + * using for the viewer checks and polling fallback. + */ + case WM_CREATE: + g_idWorkerTimer = WinStartTimer(g_habWorker, hwnd, 1 /* id */, 1000 /* 1 second */); + g_fEmptyClipboard = true; + g_enmState = kClipboardState_Polling; + return NULL; /* FALSE(/NULL) == Continue*/ + + case WM_DESTROY: + WinStopTimer(g_habWorker, hwnd, g_idWorkerTimer); + g_idWorkerTimer = ~0UL; + g_hwndWorker = NULLHANDLE; + break; + + /* + * Clipboard viewer message - the content has been changed. + * This is sent *after* releasing the clipboard sem + * and during the WinSetClipbrdViewer call. + */ + case WM_DRAWCLIPBOARD: + if (g_enmState == kClipboardState_SettingViewer) + break; + AssertMsgBreak(g_enmState == kClipboardState_Viewer, ("g_enmState=%d\n", g_enmState)); + vgsvcClipboardOs2Poll(); + break; + + /* + * Clipboard owner message - the content was replaced. + * This is sent by someone with an open clipboard, so don't try open it now. + */ + case WM_DESTROYCLIPBOARD: + if (g_enmState == kClipboardState_Destroying) + break; /* it's us doing the replacing, ignore. */ + AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState)); + vgsvcClipboardOs2Destroyed(); + break; + + /* + * Clipboard owner message - somebody is requesting us to render a format. + * This is called by someone which owns the clipboard, but that's fine. + */ + case WM_RENDERFMT: + AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState)); + vgsvcClipboardOs2RenderFormat(SHORT1FROMMP(mp1)); + break; + + /* + * Clipboard owner message - we're about to quit and should render all formats. + * + * However, because we're lazy, we'll just ASSUME that since we're quitting + * we're probably about to shutdown or something and there is no point in + * doing anything here except for emptying the clipboard and removing + * ourselves as owner. Any failures at this point are silently ignored. + */ + case WM_RENDERALLFMTS: + WinOpenClipbrd(g_habWorker); + WinSetClipbrdOwner(g_habWorker, NULLHANDLE); + g_enmState = kClipboardState_Destroying; + WinEmptyClipbrd(g_habWorker); + g_enmState = kClipboardState_Polling; + g_fEmptyClipboard = true; + WinCloseClipbrd(g_habWorker); + break; + + /* + * Listener message - the host has new formats to offer. + */ + case WM_USER + VBOX_SHCL_HOST_MSG_FORMATS_REPORT: + vgsvcClipboardOs2AdvertiseHostFormats(LONGFROMMP(mp1)); + break; + + /* + * Listener message - the host wish to read our clipboard data. + */ + case WM_USER + VBOX_SHCL_HOST_MSG_READ_DATA: + vgsvcClipboardOs2SendDataToHost(LONGFROMMP(mp1)); + break; + + /* + * This is just a fallback polling strategy in case some other + * app is trying to view the clipboard too. We also use this + * to try recover from errors. + * + * Because the way the clipboard service works, we have to monitor + * it all the time and cannot get away with simpler solutions like + * synergy is employing (basically checking upon entering and leaving + * a desktop). + */ + case WM_TIMER: + if ( g_enmState != kClipboardState_Viewer + && g_enmState != kClipboardState_Polling) + break; + + /* Lost the position as clipboard viewer?*/ + if (g_enmState == kClipboardState_Viewer) + { + if (WinQueryClipbrdViewer(g_habWorker) == hwnd) + break; + g_enmState = kClipboardState_Polling; + } + + /* poll for changes */ + vgsvcClipboardOs2Poll(); + vgsvcClipboardOs2PollViewer(); + break; + + + /* + * Clipboard owner messages dealing with owner drawn content. + * We shouldn't be seeing any of these. + */ + case WM_PAINTCLIPBOARD: + case WM_SIZECLIPBOARD: + case WM_HSCROLLCLIPBOARD: + case WM_VSCROLLCLIPBOARD: + AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg)); + break; + + /* + * We shouldn't be seeing any other messages according to the docs. + * But for whatever reason, PM sends us a WM_ADJUSTWINDOWPOS message + * during WinCreateWindow. So, ignore that and assert on anything else. + */ + default: + AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg)); + case WM_ADJUSTWINDOWPOS: + break; + } + return NULL; +} + + +/** + * The listener thread. + * + * This thread is dedicated to listening for host messages and forwarding + * these to the worker thread (using PM). + * + * The thread will set g_fListenerOkay and signal its user event when it has + * completed initialization. In the case of init failure g_fListenerOkay will + * not be set. + * + * @returns Init error code or VINF_SUCCESS. + * @param ThreadSelf Our thread handle. + * @param pvUser Pointer to the clipboard service shutdown indicator. + */ +static DECLCALLBACK(int) vgsvcClipboardOs2Listener(RTTHREAD ThreadSelf, void *pvUser) +{ + bool volatile *pfShutdown = (bool volatile *)pvUser; + int rc = VERR_GENERAL_FAILURE; + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: ThreadSelf=%RTthrd\n", ThreadSelf); + + g_habListener = WinInitialize(0); + if (g_habListener != NULLHANDLE) + { + g_hmqListener = WinCreateMsgQueue(g_habListener, 0); + if (g_hmqListener != NULLHANDLE) + { + WinCancelShutdown(g_hmqListener, TRUE); /* We don't care about shutdown */ + + /* + * Tell the worker thread that we're good. + */ + rc = VINF_SUCCESS; + ASMAtomicXchgBool(&g_fListenerOkay, true); + RTThreadUserSignal(ThreadSelf); + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: Started successfully\n"); + + /* + * Loop until termination is requested. + */ + bool fQuit = false; + while (!*pfShutdown && !fQuit) + { + uint32_t Msg; + uint32_t fFormats; + rc = VbglR3ClipboardGetHostMsgOld(g_u32ClientId, &Msg, &fFormats); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: Msg=%#x fFormats=%#x\n", Msg, fFormats); + switch (Msg) + { + /* + * The host has announced available clipboard formats. + * Forward the information to the window, so it can later + * respond do WM_RENDERFORMAT message. + */ + case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: + if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHCL_HOST_MSG_FORMATS_REPORT, + MPFROMLONG(fFormats), 0)) + VGSvcError("WinPostMsg(%lx, FORMATS,,) failed, lasterr=%#lx\n", + g_hwndWorker, WinGetLastError(g_habListener)); + break; + + /* + * The host needs data in the specified format. + */ + case VBOX_SHCL_HOST_MSG_READ_DATA: + if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHCL_HOST_MSG_READ_DATA, + MPFROMLONG(fFormats), 0)) + VGSvcError("WinPostMsg(%lx, READ_DATA,,) failed, lasterr=%#lx\n", + g_hwndWorker, WinGetLastError(g_habListener)); + break; + + /* + * The host is terminating. + */ + case VBOX_SHCL_HOST_MSG_QUIT: + fQuit = true; + break; + + default: + VGSvcVerbose(1, "vgsvcClipboardOs2Listener: Unknown message %RU32\n", Msg); + break; + } + } + else + { + if (*pfShutdown) + break; + VGSvcError("VbglR3ClipboardGetHostMsg failed, rc=%Rrc\n", rc); + RTThreadSleep(1000); + } + } /* the loop */ + + WinDestroyMsgQueue(g_hmqListener); + } + WinTerminate(g_habListener); + g_habListener = NULLHANDLE; + } + + /* Signal our semaphore to make the worker catch on. */ + RTThreadUserSignal(ThreadSelf); + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: terminating, rc=%Rrc\n", rc); + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2Worker(bool volatile *pfShutdown) +{ + int rc = VERR_GENERAL_FAILURE; + + /* + * Standard PM init. + */ + g_habWorker = RTThreadSelf() != g_ThreadCtrl ? WinInitialize(0) : g_habCtrl; + if (g_habWorker != NULLHANDLE) + { + g_hmqWorker = RTThreadSelf() != g_ThreadCtrl ? WinCreateMsgQueue(g_habWorker, 0) : g_hmqCtrl; + if (g_hmqWorker != NULLHANDLE) + { + if (g_hmqWorker != g_hmqCtrl) + WinCancelShutdown(g_hmqWorker, TRUE); /* We don't care about shutdown */ + + /* + * Create the object window. + */ + if (WinRegisterClass(g_habWorker, (PCSZ)"VBoxServiceClipboardClass", vgsvcClipboardOs2WinProc, 0, 0)) + { + g_hwndWorker = WinCreateWindow(HWND_OBJECT, /* hwndParent */ + (PCSZ)"VBoxServiceClipboardClass", /* pszClass */ + (PCSZ)"VirtualBox Clipboard Service", /* pszName */ + 0, /* flStyle */ + 0, 0, 0, 0, /* x, y, cx, cy */ + NULLHANDLE, /* hwndOwner */ + HWND_BOTTOM, /* hwndInsertBehind */ + 42, /* id */ + NULL, /* pCtlData */ + NULL); /* pPresParams */ + if (g_hwndWorker != NULLHANDLE) + { + VGSvcVerbose(3, "g_hwndWorker=%#lx g_habWorker=%#lx g_hmqWorker=%#lx\n", g_hwndWorker, g_habWorker, g_hmqWorker); + + /* + * Create the listener thread. + */ + g_fListenerOkay = false; + rc = RTThreadCreate(&g_ThreadListener, vgsvcClipboardOs2Listener, (void *)pfShutdown, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "CLIPLISTEN"); + if (RT_SUCCESS(rc)) + { + RTThreadUserWait(g_ThreadListener, 30*1000); + RTThreadUserReset(g_ThreadListener); + if (!g_fListenerOkay) + RTThreadWait(g_ThreadListener, 60*1000, NULL); + if (g_fListenerOkay) + { + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * The PM event pump. + */ + VGSvcVerbose(2, "clipboard: Entering PM message loop.\n"); + rc = VINF_SUCCESS; + QMSG qmsg; + while (WinGetMsg(g_habWorker, &qmsg, NULLHANDLE, NULLHANDLE, 0)) + { + if (qmsg.msg != WM_TIMER) + VGSvcVerbose(6, "WinGetMsg -> hwnd=%p msg=%#x mp1=%p mp2=%p time=%#x ptl=%d,%d rsrv=%#x\n", + qmsg.hwnd, qmsg.msg, qmsg.mp1, qmsg.mp2, qmsg.time, qmsg.ptl.x, qmsg.ptl.y, qmsg.reserved); + WinDispatchMsg(g_habWorker, &qmsg); + } + VGSvcVerbose(2, "clipboard: Exited PM message loop. *pfShutdown=%RTbool\n", *pfShutdown); + + RTThreadWait(g_ThreadListener, 60*1000, NULL); + } + g_ThreadListener = NIL_RTTHREAD; + } + + /* + * Got a WM_QUIT, clean up. + */ + if (g_hwndWorker != NULLHANDLE) + { + WinDestroyWindow(g_hwndWorker); + g_hwndWorker = NULLHANDLE; + } + } + else + VGSvcError("WinCreateWindow() failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + /* no class deregistration in PM. */ + } + else + VGSvcError("WinRegisterClass() failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + + if (g_hmqCtrl != g_hmqWorker) + WinDestroyMsgQueue(g_hmqWorker); + g_hmqWorker = NULLHANDLE; + } + else + VGSvcError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + + if (g_habCtrl != g_habWorker) + WinTerminate(g_habWorker); + g_habWorker = NULLHANDLE; + } + else + VGSvcError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE)); + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcClipboardOs2Stop(void) +{ + if ( g_hmqWorker != NULLHANDLE + && !WinPostQueueMsg(g_hmqWorker, WM_QUIT, NULL, NULL)) + VGSvcError("WinPostQueueMsg(g_hmqWorker, WM_QUIT, 0,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl)); + + /* Must disconnect the clipboard here otherwise the listner won't quit and + the service shutdown will not stop. */ + if (g_u32ClientId != 0) + { + if (g_hmqWorker != NULLHANDLE) + RTThreadSleep(32); /* fudge */ + + VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId); + int rc = VbglR3ClipboardDisconnect(g_u32ClientId); + if (RT_SUCCESS(rc)) + g_u32ClientId = 0; + else + VGSvcError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc); + } +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcClipboardOs2Term(void) +{ + if (g_u32ClientId != 0) + { + VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId); + int rc = VbglR3ClipboardDisconnect(g_u32ClientId); + if (RT_SUCCESS(rc)) + g_u32ClientId = 0; + else + VGSvcError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc); + } + WinDestroyMsgQueue(g_hmqCtrl); + g_hmqCtrl = NULLHANDLE; + WinTerminate(g_habCtrl); + g_habCtrl = NULLHANDLE; +} + + +/** + * The OS/2 'clipboard' service description. + */ +VBOXSERVICE g_Clipboard = +{ + /* pszName. */ + "clipboard", + /* pszDescription. */ + "Shared Clipboard", + /* pszUsage. */ + "" + , + /* pszOptions. */ + "" + , + /* methods */ + vgsvcClipboardOs2PreInit, + vgsvcClipboardOs2Option, + vgsvcClipboardOs2Init, + vgsvcClipboardOs2Worker, + vgsvcClipboardOs2Stop, + vgsvcClipboardOs2Term +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp new file mode 100644 index 00000000..6e30cdff --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp @@ -0,0 +1,629 @@ +/* $Id: VBoxServiceControl.cpp $ */ +/** @file + * VBoxServiceControl - Host-driven Guest Control. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_vgsvc_gstctrl VBoxService - Guest Control + * + * The Guest Control subservice helps implementing the IGuest APIs. + * + * The communication between this service (and its children) and IGuest goes + * over the HGCM GuestControl service. + * + * The IGuest APIs provides means to manipulate (control) files, directories, + * symbolic links and processes within the guest. Most of these means requires + * credentials of a guest OS user to operate, though some restricted ones + * operates directly as the VBoxService user (root / system service account). + * + * The current design is that a subprocess is spawned for handling operations as + * a given user. This process is represented as IGuestSession in the API. The + * subprocess will be spawned as the given use, giving up the privileges the + * parent subservice had. + * + * It will try handle as many of the operations directly from within the + * subprocess, but for more complicated things (or things that haven't yet been + * converted), it will spawn a helper process that does the actual work. + * + * These helpers are the typically modeled on similar unix core utilities, like + * mkdir, rm, rmdir, cat and so on. The helper tools can also be launched + * directly from VBoxManage by the user by prepending the 'vbox_' prefix to the + * unix command. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#include <VBox/err.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/HostServices/GuestControlSvc.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceControl.h" +#include "VBoxServiceUtils.h" + +using namespace guestControl; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The control interval (milliseconds). */ +static uint32_t g_msControlInterval = 0; +/** The semaphore we're blocking our main control thread on. */ +static RTSEMEVENTMULTI g_hControlEvent = NIL_RTSEMEVENTMULTI; +/** The VM session ID. Changes whenever the VM is restored or reset. */ +static uint64_t g_idControlSession; +/** The guest control service client ID. */ +uint32_t g_idControlSvcClient = 0; +/** VBOX_GUESTCTRL_HF_XXX */ +uint64_t g_fControlHostFeatures0 = 0; +#if 0 /** @todo process limit */ +/** How many started guest processes are kept into memory for supplying + * information to the host. Default is 256 processes. If 0 is specified, + * the maximum number of processes is unlimited. */ +static uint32_t g_uControlProcsMaxKept = 256; +#endif +/** List of guest control session threads (VBOXSERVICECTRLSESSIONTHREAD). + * A guest session thread represents a forked guest session process + * of VBoxService. */ +RTLISTANCHOR g_lstControlSessionThreads; +/** The local session object used for handling all session-related stuff. + * When using the legacy guest control protocol (< 2), this session runs + * under behalf of the VBoxService main process. On newer protocol versions + * each session is a forked version of VBoxService using the appropriate + * user credentials for opening a guest session. These forked sessions then + * are kept in VBOXSERVICECTRLSESSIONTHREAD structures. */ +VBOXSERVICECTRLSESSION g_Session; +/** Copy of VbglR3GuestCtrlSupportsOptimizations().*/ +bool g_fControlSupportsOptimizations = true; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vgsvcGstCtrlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int vgsvcGstCtrlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int vgsvcGstCtrlInvalidate(void); +static void vgsvcGstCtrlShutdown(void); + + +/** + * @interface_method_impl{VBOXSERVICE,pfnPreInit} + */ +static DECLCALLBACK(int) vgsvcGstCtrlPreInit(void) +{ + int rc; +#ifdef VBOX_WITH_GUEST_PROPS + /* + * Read the service options from the VM's guest properties. + * Note that these options can be overridden by the command line options later. + */ + uint32_t uGuestPropSvcClientID; + rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID); + if (RT_FAILURE(rc)) + { + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ + { + VGSvcVerbose(0, "Guest property service is not available, skipping\n"); + rc = VINF_SUCCESS; + } + else + VGSvcError("Failed to connect to the guest property service, rc=%Rrc\n", rc); + } + else + VbglR3GuestPropDisconnect(uGuestPropSvcClientID); + + if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */ + rc = VINF_SUCCESS; +#else + /* Nothing to do here yet. */ + rc = VINF_SUCCESS; +#endif + + if (RT_SUCCESS(rc)) + { + /* Init session object. */ + rc = VGSvcGstCtrlSessionInit(&g_Session, 0 /* Flags */); + } + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption} + */ +static DECLCALLBACK(int) vgsvcGstCtrlOption(const char **ppszShort, int argc, char **argv, int *pi) +{ + int rc = -1; + if (ppszShort) + /* no short options */; + else if (!strcmp(argv[*pi], "--control-interval")) + rc = VGSvcArgUInt32(argc, argv, "", pi, + &g_msControlInterval, 1, UINT32_MAX - 1); +#ifdef DEBUG + else if (!strcmp(argv[*pi], "--control-dump-stdout")) + { + g_Session.fFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT; + rc = 0; /* Flag this command as parsed. */ + } + else if (!strcmp(argv[*pi], "--control-dump-stderr")) + { + g_Session.fFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR; + rc = 0; /* Flag this command as parsed. */ + } +#endif + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcGstCtrlInit(void) +{ + /* + * If not specified, find the right interval default. + * Then create the event sem to block on. + */ + if (!g_msControlInterval) + g_msControlInterval = 1000; + + int rc = RTSemEventMultiCreate(&g_hControlEvent); + AssertRCReturn(rc, rc); + + VbglR3GetSessionId(&g_idControlSession); /* The status code is ignored as this information is not available with VBox < 3.2.10. */ + + RTListInit(&g_lstControlSessionThreads); + + /* + * Try connect to the host service and tell it we want to be master (if supported). + */ + rc = VbglR3GuestCtrlConnect(&g_idControlSvcClient); + if (RT_SUCCESS(rc)) + { + rc = vgsvcGstCtrlInvalidate(); + if (RT_SUCCESS(rc)) + return rc; + } + else + { + /* If the service was not found, we disable this service without + causing VBoxService to fail. */ + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ + { + VGSvcVerbose(0, "Guest control service is not available\n"); + rc = VERR_SERVICE_DISABLED; + } + else + VGSvcError("Failed to connect to the guest control service! Error: %Rrc\n", rc); + } + RTSemEventMultiDestroy(g_hControlEvent); + g_hControlEvent = NIL_RTSEMEVENTMULTI; + g_idControlSvcClient = 0; + return rc; +} + +static int vgsvcGstCtrlInvalidate(void) +{ + VGSvcVerbose(1, "Invalidating configuration ...\n"); + + int rc = VINF_SUCCESS; + + g_fControlSupportsOptimizations = VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient); + if (g_fControlSupportsOptimizations) + rc = VbglR3GuestCtrlMakeMeMaster(g_idControlSvcClient); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "Guest control service client ID=%RU32%s\n", + g_idControlSvcClient, g_fControlSupportsOptimizations ? " w/ optimizations" : ""); + + /* + * Report features to the host. + */ + const uint64_t fGuestFeatures = VBOX_GUESTCTRL_GF_0_SET_SIZE + | VBOX_GUESTCTRL_GF_0_PROCESS_ARGV0 + | VBOX_GUESTCTRL_GF_0_PROCESS_DYNAMIC_SIZES + | VBOX_GUESTCTRL_GF_0_SHUTDOWN; + + rc = VbglR3GuestCtrlReportFeatures(g_idControlSvcClient, fGuestFeatures, &g_fControlHostFeatures0); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Host features: %#RX64\n", g_fControlHostFeatures0); + else + VGSvcVerbose(1, "Warning! Feature reporing failed: %Rrc\n", rc); + + return VINF_SUCCESS; + } + VGSvcError("Failed to become guest control master: %Rrc\n", rc); + VbglR3GuestCtrlDisconnect(g_idControlSvcClient); + + return rc; +} + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vgsvcGstCtrlWorker(bool volatile *pfShutdown) +{ + /* + * Tell the control thread that it can continue spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + Assert(g_idControlSvcClient > 0); + + /* Allocate a scratch buffer for messages which also send + * payload data with them. */ + uint32_t cbScratchBuf = _64K; /** @todo Make buffer size configurable via guest properties/argv! */ + AssertReturn(RT_IS_POWER_OF_TWO(cbScratchBuf), VERR_INVALID_PARAMETER); + uint8_t *pvScratchBuf = (uint8_t*)RTMemAlloc(cbScratchBuf); + AssertReturn(pvScratchBuf, VERR_NO_MEMORY); + + int rc = VINF_SUCCESS; /* (shut up compiler warnings) */ + int cRetrievalFailed = 0; /* Number of failed message retrievals in a row. */ + while (!*pfShutdown) + { + VGSvcVerbose(3, "GstCtrl: Waiting for host msg ...\n"); + VBGLR3GUESTCTRLCMDCTX ctxHost = { g_idControlSvcClient, 0 /*idContext*/, 2 /*uProtocol*/, 0 /*cParms*/ }; + uint32_t idMsg = 0; + rc = VbglR3GuestCtrlMsgPeekWait(g_idControlSvcClient, &idMsg, &ctxHost.uNumParms, &g_idControlSession); + if (RT_SUCCESS(rc)) + { + cRetrievalFailed = 0; /* Reset failed retrieval count. */ + VGSvcVerbose(4, "idMsg=%RU32 (%s) (%RU32 parms) retrieved\n", + idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), ctxHost.uNumParms); + + /* + * Handle the host message. + */ + switch (idMsg) + { + case HOST_MSG_CANCEL_PENDING_WAITS: + VGSvcVerbose(1, "We were asked to quit ...\n"); + break; + + case HOST_MSG_SESSION_CREATE: + rc = vgsvcGstCtrlHandleSessionOpen(&ctxHost); + break; + + /* This message is also sent to the child session process (by the host). */ + case HOST_MSG_SESSION_CLOSE: + rc = vgsvcGstCtrlHandleSessionClose(&ctxHost); + break; + + default: + if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient)) + { + rc = VbglR3GuestCtrlMsgSkip(g_idControlSvcClient, VERR_NOT_SUPPORTED, idMsg); + VGSvcVerbose(1, "Skipped unexpected message idMsg=%RU32 (%s), cParms=%RU32 (rc=%Rrc)\n", + idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), ctxHost.uNumParms, rc); + } + else + { + rc = VbglR3GuestCtrlMsgSkipOld(g_idControlSvcClient); + VGSvcVerbose(3, "Skipped idMsg=%RU32, cParms=%RU32, rc=%Rrc\n", idMsg, ctxHost.uNumParms, rc); + } + break; + } + + /* Do we need to shutdown? */ + if (idMsg == HOST_MSG_CANCEL_PENDING_WAITS) + break; + + /* Let's sleep for a bit and let others run ... */ + RTThreadYield(); + } + /* + * Handle restore notification from host. All the context IDs (sessions, + * files, proceses, etc) are invalidated by a VM restore and must be closed. + */ + else if (rc == VERR_VM_RESTORED) + { + VGSvcVerbose(1, "The VM session ID changed (i.e. restored), closing stale root session\n"); + + /* Make sure that all other session threads are gone. + * This is necessary, as the new VM session (NOT to be confused with guest session!) will re-use + * the guest session IDs. */ + int rc2 = VGSvcGstCtrlSessionThreadDestroyAll(&g_lstControlSessionThreads, 0 /* Flags */); + if (RT_FAILURE(rc2)) + VGSvcError("Closing session threads failed with rc=%Rrc\n", rc2); + + /* Make sure to also close the root session (session 0). */ + rc2 = VGSvcGstCtrlSessionClose(&g_Session); + AssertRC(rc2); + + rc2 = VbglR3GuestCtrlSessionHasChanged(g_idControlSvcClient, g_idControlSession); + AssertRC(rc2); + + /* Invalidate the internal state to match the current host we got restored from. */ + rc2 = vgsvcGstCtrlInvalidate(); + AssertRC(rc2); + } + else + { + /* Note: VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */ + /** @todo r=bird: Above comment makes no sense. How can you get a timeout in a blocking HGCM call? */ + VGSvcError("GstCtrl: Getting host message failed with %Rrc\n", rc); + + /* Check for VM session change. */ + /** @todo We don't need to check the host here. */ + uint64_t idNewSession = g_idControlSession; + int rc2 = VbglR3GetSessionId(&idNewSession); + if ( RT_SUCCESS(rc2) + && (idNewSession != g_idControlSession)) + { + VGSvcVerbose(1, "GstCtrl: The VM session ID changed\n"); + g_idControlSession = idNewSession; + + /* Close all opened guest sessions -- all context IDs, sessions etc. + * are now invalid. */ + rc2 = VGSvcGstCtrlSessionClose(&g_Session); + AssertRC(rc2); + + /* Do a reconnect. */ + VGSvcVerbose(1, "Reconnecting to HGCM service ...\n"); + rc2 = VbglR3GuestCtrlConnect(&g_idControlSvcClient); + if (RT_SUCCESS(rc2)) + { + VGSvcVerbose(3, "Guest control service client ID=%RU32\n", g_idControlSvcClient); + cRetrievalFailed = 0; + continue; /* Skip waiting. */ + } + VGSvcError("Unable to re-connect to HGCM service, rc=%Rrc, bailing out\n", rc); + break; + } + + if (rc == VERR_INTERRUPTED) + RTThreadYield(); /* To be on the safe side... */ + else if (++cRetrievalFailed <= 16) /** @todo Make this configurable? */ + RTThreadSleep(1000); /* Wait a bit before retrying. */ + else + { + VGSvcError("Too many failed attempts in a row to get next message, bailing out\n"); + break; + } + } + } + + VGSvcVerbose(0, "Guest control service stopped\n"); + + /* Delete scratch buffer. */ + if (pvScratchBuf) + RTMemFree(pvScratchBuf); + + VGSvcVerbose(0, "Guest control worker returned with rc=%Rrc\n", rc); + return rc; +} + + +static int vgsvcGstCtrlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the message parameters. + */ + PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo; + int rc = VbglR3GuestCtrlSessionGetOpen(pHostCtx, &pStartupInfo); + if (RT_SUCCESS(rc)) + { + /* + * Flat out refuse to work with protocol v1 hosts. + */ + if (pStartupInfo->uProtocol == 2) + { + pHostCtx->uProtocol = pStartupInfo->uProtocol; + VGSvcVerbose(3, "Client ID=%RU32 now is using protocol %RU32\n", pHostCtx->uClientID, pHostCtx->uProtocol); + +/** @todo Someone explain why this code isn't in this file too? v1 support? */ + rc = VGSvcGstCtrlSessionThreadCreate(&g_lstControlSessionThreads, pStartupInfo, NULL /* ppSessionThread */); + /* Report failures to the host (successes are taken care of by the session thread). */ + } + else + { + VGSvcError("The host wants to use protocol v%u, we only support v2!\n", pStartupInfo->uProtocol); + rc = VERR_VERSION_MISMATCH; + } + if (RT_FAILURE(rc)) + { + int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx, GUEST_SESSION_NOTIFYTYPE_ERROR, rc); + if (RT_FAILURE(rc2)) + VGSvcError("Reporting session error status on open failed with rc=%Rrc\n", rc2); + } + } + else + { + VGSvcError("Error fetching parameters for opening guest session: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + VbglR3GuestCtrlSessionStartupInfoFree(pStartupInfo); + pStartupInfo = NULL; + + VGSvcVerbose(3, "Opening a new guest session returned rc=%Rrc\n", rc); + return rc; +} + + +static int vgsvcGstCtrlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + uint32_t idSession; + uint32_t fFlags; + int rc = VbglR3GuestCtrlSessionGetClose(pHostCtx, &fFlags, &idSession); + if (RT_SUCCESS(rc)) + { + rc = VERR_NOT_FOUND; + + PVBOXSERVICECTRLSESSIONTHREAD pThread; + RTListForEach(&g_lstControlSessionThreads, pThread, VBOXSERVICECTRLSESSIONTHREAD, Node) + { + if ( pThread->pStartupInfo + && pThread->pStartupInfo->uSessionID == idSession) + { + rc = VGSvcGstCtrlSessionThreadDestroy(pThread, fFlags); + break; + } + } + +#if 0 /** @todo A bit of a mess here as this message goes to both to this process (master) and the session process. */ + if (RT_FAILURE(rc)) + { + /* Report back on failure. On success this will be done + * by the forked session thread. */ + int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx, + GUEST_SESSION_NOTIFYTYPE_ERROR, rc); + if (RT_FAILURE(rc2)) + { + VGSvcError("Reporting session error status on close failed with rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } +#endif + VGSvcVerbose(2, "Closing guest session %RU32 returned rc=%Rrc\n", idSession, rc); + } + else + { + VGSvcError("Error fetching parameters for closing guest session: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcGstCtrlStop(void) +{ + VGSvcVerbose(3, "Stopping ...\n"); + + /** @todo Later, figure what to do if we're in RTProcWait(). It's a very + * annoying call since doesn't support timeouts in the posix world. */ + if (g_hControlEvent != NIL_RTSEMEVENTMULTI) + RTSemEventMultiSignal(g_hControlEvent); + + /* + * Ask the host service to cancel all pending requests for the main + * control thread so that we can shutdown properly here. + */ + if (g_idControlSvcClient) + { + VGSvcVerbose(3, "Cancelling pending waits (client ID=%u) ...\n", + g_idControlSvcClient); + + int rc = VbglR3GuestCtrlCancelPendingWaits(g_idControlSvcClient); + if (RT_FAILURE(rc)) + VGSvcError("Cancelling pending waits failed; rc=%Rrc\n", rc); + } +} + + +/** + * Destroys all guest process threads which are still active. + */ +static void vgsvcGstCtrlShutdown(void) +{ + VGSvcVerbose(2, "Shutting down ...\n"); + + int rc2 = VGSvcGstCtrlSessionThreadDestroyAll(&g_lstControlSessionThreads, 0 /* Flags */); + if (RT_FAILURE(rc2)) + VGSvcError("Closing session threads failed with rc=%Rrc\n", rc2); + + rc2 = VGSvcGstCtrlSessionClose(&g_Session); + if (RT_FAILURE(rc2)) + VGSvcError("Closing session failed with rc=%Rrc\n", rc2); + + VGSvcVerbose(2, "Shutting down complete\n"); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcGstCtrlTerm(void) +{ + VGSvcVerbose(3, "Terminating ...\n"); + + vgsvcGstCtrlShutdown(); + + VGSvcVerbose(3, "Disconnecting client ID=%u ...\n", g_idControlSvcClient); + VbglR3GuestCtrlDisconnect(g_idControlSvcClient); + g_idControlSvcClient = 0; + + if (g_hControlEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_hControlEvent); + g_hControlEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'vminfo' service description. + */ +VBOXSERVICE g_Control = +{ + /* pszName. */ + "control", + /* pszDescription. */ + "Host-driven Guest Control", + /* pszUsage. */ +#ifdef DEBUG + " [--control-dump-stderr] [--control-dump-stdout]\n" +#endif + " [--control-interval <ms>]" + , + /* pszOptions. */ +#ifdef DEBUG + " --control-dump-stderr Dumps all guest proccesses stderr data to the\n" + " temporary directory.\n" + " --control-dump-stdout Dumps all guest proccesses stdout data to the\n" + " temporary directory.\n" +#endif + " --control-interval Specifies the interval at which to check for\n" + " new control messages. The default is 1000 ms.\n" + , + /* methods */ + vgsvcGstCtrlPreInit, + vgsvcGstCtrlOption, + vgsvcGstCtrlInit, + vgsvcGstCtrlWorker, + vgsvcGstCtrlStop, + vgsvcGstCtrlTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h new file mode 100644 index 00000000..2a0d6513 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h @@ -0,0 +1,297 @@ +/* $Id: VBoxServiceControl.h $ */ +/** @file + * VBoxServiceControl.h - Internal guest control definitions. + */ + +/* + * Copyright (C) 2013-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/critsect.h> +#include <iprt/list.h> +#include <iprt/req.h> + +#include <VBox/VBoxGuestLib.h> +#include <VBox/GuestHost/GuestControl.h> +#include <VBox/HostServices/GuestControlSvc.h> + + +/** + * Pipe IDs for handling the guest process poll set. + */ +typedef enum VBOXSERVICECTRLPIPEID +{ + VBOXSERVICECTRLPIPEID_UNKNOWN = 0, + VBOXSERVICECTRLPIPEID_STDIN = 10, + VBOXSERVICECTRLPIPEID_STDIN_WRITABLE = 11, + /** Pipe for reading from guest process' stdout. */ + VBOXSERVICECTRLPIPEID_STDOUT = 40, + /** Pipe for reading from guest process' stderr. */ + VBOXSERVICECTRLPIPEID_STDERR = 50, + /** Notification pipe for waking up the guest process + * control thread. */ + VBOXSERVICECTRLPIPEID_IPC_NOTIFY = 100 +} VBOXSERVICECTRLPIPEID; + +/** + * Structure for one (opened) guest file. + */ +typedef struct VBOXSERVICECTRLFILE +{ + /** Pointer to list archor of following + * list node. + * @todo Would be nice to have a RTListGetAnchor(). */ + PRTLISTANCHOR pAnchor; + /** Node to global guest control file list. */ + /** @todo Use a map later? */ + RTLISTNODE Node; + /** The file name. */ + char *pszName; + /** The file handle on the guest. */ + RTFILE hFile; + /** File handle to identify this file. */ + uint32_t uHandle; + /** Context ID. */ + uint32_t uContextID; + /** RTFILE_O_XXX flags. */ + uint64_t fOpen; +} VBOXSERVICECTRLFILE; +/** Pointer to thread data. */ +typedef VBOXSERVICECTRLFILE *PVBOXSERVICECTRLFILE; + +/** + * Structure for a guest session thread to + * observe/control the forked session instance from + * the VBoxService main executable. + */ +typedef struct VBOXSERVICECTRLSESSIONTHREAD +{ + /** Node to global guest control session list. */ + /** @todo Use a map later? */ + RTLISTNODE Node; + /** The sessions's startup info. */ + PVBGLR3GUESTCTRLSESSIONSTARTUPINFO + pStartupInfo; + /** Critical section for thread-safe use. */ + RTCRITSECT CritSect; + /** The worker thread. */ + RTTHREAD Thread; + /** Process handle for forked child. */ + RTPROCESS hProcess; + /** Shutdown indicator; will be set when the thread + * needs (or is asked) to shutdown. */ + bool volatile fShutdown; + /** Indicator set by the service thread exiting. */ + bool volatile fStopped; + /** Whether the thread was started or not. */ + bool fStarted; +#if 0 /* Pipe IPC not used yet. */ + /** Pollset containing all the pipes. */ + RTPOLLSET hPollSet; + RTPIPE hStdInW; + RTPIPE hStdOutR; + RTPIPE hStdErrR; + struct StdPipe + { + RTHANDLE hChild; + PRTHANDLE phChild; + } StdIn, + StdOut, + StdErr; + /** The notification pipe associated with this guest session. + * This is NIL_RTPIPE for output pipes. */ + RTPIPE hNotificationPipeW; + /** The other end of hNotificationPipeW. */ + RTPIPE hNotificationPipeR; +#endif + /** Pipe for handing the secret key to the session process. */ + RTPIPE hKeyPipe; + /** Secret key. */ + uint8_t abKey[_4K]; +} VBOXSERVICECTRLSESSIONTHREAD; +/** Pointer to thread data. */ +typedef VBOXSERVICECTRLSESSIONTHREAD *PVBOXSERVICECTRLSESSIONTHREAD; + +/** Defines the prefix being used for telling our service executable that we're going + * to spawn a new (Guest Control) user session. */ +#define VBOXSERVICECTRLSESSION_GETOPT_PREFIX "guestsession" + +/** Flag indicating that this session has been spawned from + * the main executable. */ +#define VBOXSERVICECTRLSESSION_FLAG_SPAWN RT_BIT(0) +/** Flag indicating that this session is anonymous, that is, + * it will run start guest processes with the same credentials + * as the main executable. */ +#define VBOXSERVICECTRLSESSION_FLAG_ANONYMOUS RT_BIT(1) +/** Flag indicating that started guest processes will dump their + * stdout output to a separate file on disk. For debugging. */ +#define VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT RT_BIT(2) +/** Flag indicating that started guest processes will dump their + * stderr output to a separate file on disk. For debugging. */ +#define VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR RT_BIT(3) + +/** + * Structure for maintaining a guest session. This also + * contains all started threads (e.g. for guest processes). + * + * This structure can act in two different ways: + * - For legacy guest control handling (protocol version < 2) + * this acts as a per-guest process structure containing all + * the information needed to get a guest process up and running. + * - For newer guest control protocols (>= 2) this structure is + * part of the forked session child, maintaining all guest + * control objects under it. + */ +typedef struct VBOXSERVICECTRLSESSION +{ + /* The session's startup information. */ + VBGLR3GUESTCTRLSESSIONSTARTUPINFO + StartupInfo; + /** List of active guest process threads + * (VBOXSERVICECTRLPROCESS). */ + RTLISTANCHOR lstProcesses; + /** Number of guest processes in the process list. */ + uint32_t cProcesses; + /** List of guest control files (VBOXSERVICECTRLFILE). */ + RTLISTANCHOR lstFiles; + /** Number of guest files in the file list. */ + uint32_t cFiles; + /** The session's critical section. */ + RTCRITSECT CritSect; + /** Internal session flags, not related + * to StartupInfo stuff. + * @sa VBOXSERVICECTRLSESSION_FLAG_* flags. */ + uint32_t fFlags; + /** How many processes do we allow keeping around at a time? */ + uint32_t uProcsMaxKept; +} VBOXSERVICECTRLSESSION; +/** Pointer to guest session. */ +typedef VBOXSERVICECTRLSESSION *PVBOXSERVICECTRLSESSION; + +/** + * Structure for holding data for one (started) guest process. + */ +typedef struct VBOXSERVICECTRLPROCESS +{ + /** Node. */ + RTLISTNODE Node; + /** Process handle. */ + RTPROCESS hProcess; + /** Number of references using this struct. */ + uint32_t cRefs; + /** The worker thread. */ + RTTHREAD Thread; + /** The session this guest process + * is bound to. */ + PVBOXSERVICECTRLSESSION pSession; + /** Shutdown indicator; will be set when the thread + * needs (or is asked) to shutdown. */ + bool volatile fShutdown; + /** Whether the guest process thread was stopped or not. */ + bool volatile fStopped; + /** Whether the guest process thread was started or not. */ + bool fStarted; + /** Context ID. */ + uint32_t uContextID; + /** Critical section for thread-safe use. */ + RTCRITSECT CritSect; + /** Process startup information. */ + PVBGLR3GUESTCTRLPROCSTARTUPINFO + pStartupInfo; + /** The process' PID assigned by the guest OS. */ + uint32_t uPID; + /** The process' request queue to handle requests + * from the outside, e.g. the session. */ + RTREQQUEUE hReqQueue; + /** Our pollset, used for accessing the process' + * std* pipes + the notification pipe. */ + RTPOLLSET hPollSet; + /** StdIn pipe for addressing writes to the + * guest process' stdin.*/ + RTPIPE hPipeStdInW; + /** StdOut pipe for addressing reads from + * guest process' stdout.*/ + RTPIPE hPipeStdOutR; + /** StdOut pipe for addressing reads from + * guest process' stderr.*/ + RTPIPE hPipeStdErrR; + + /** The write end of the notification pipe that is used to poke the thread + * monitoring the process. + * This is NIL_RTPIPE for output pipes. */ + RTPIPE hNotificationPipeW; + /** The other end of hNotificationPipeW, read by vgsvcGstCtrlProcessProcLoop(). */ + RTPIPE hNotificationPipeR; +} VBOXSERVICECTRLPROCESS; +/** Pointer to thread data. */ +typedef VBOXSERVICECTRLPROCESS *PVBOXSERVICECTRLPROCESS; + +RT_C_DECLS_BEGIN + +extern RTLISTANCHOR g_lstControlSessionThreads; +extern VBOXSERVICECTRLSESSION g_Session; +extern uint32_t g_idControlSvcClient; +extern uint64_t g_fControlHostFeatures0; +extern bool g_fControlSupportsOptimizations; + + +/** @name Guest session thread handling. + * @{ */ +extern int VGSvcGstCtrlSessionThreadCreate(PRTLISTANCHOR pList, const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo, PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread); +extern int VGSvcGstCtrlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pSession, uint32_t uFlags); +extern int VGSvcGstCtrlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t uFlags); +extern int VGSvcGstCtrlSessionThreadTerminate(PVBOXSERVICECTRLSESSIONTHREAD pSession); +extern RTEXITCODE VGSvcGstCtrlSessionSpawnInit(int argc, char **argv); +/** @} */ +/** @name Per-session functions. + * @{ */ +extern PVBOXSERVICECTRLPROCESS VGSvcGstCtrlSessionRetainProcess(PVBOXSERVICECTRLSESSION pSession, uint32_t uPID); +extern int VGSvcGstCtrlSessionClose(PVBOXSERVICECTRLSESSION pSession); +extern int VGSvcGstCtrlSessionDestroy(PVBOXSERVICECTRLSESSION pSession); +extern int VGSvcGstCtrlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t uFlags); +extern int VGSvcGstCtrlSessionHandler(PVBOXSERVICECTRLSESSION pSession, uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx, void *pvScratchBuf, size_t cbScratchBuf, volatile bool *pfShutdown); +extern int VGSvcGstCtrlSessionProcessAdd(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess); +extern int VGSvcGstCtrlSessionProcessRemove(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess); +extern int VGSvcGstCtrlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession, bool *pfAllowed); +extern int VGSvcGstCtrlSessionReapProcesses(PVBOXSERVICECTRLSESSION pSession); +/** @} */ +/** @name Per-guest process functions. + * @{ */ +extern int VGSvcGstCtrlProcessFree(PVBOXSERVICECTRLPROCESS pProcess); +extern int VGSvcGstCtrlProcessHandleInput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fPendingClose, void *pvBuf, uint32_t cbBuf); +extern int VGSvcGstCtrlProcessHandleOutput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags); +extern int VGSvcGstCtrlProcessHandleTerm(PVBOXSERVICECTRLPROCESS pProcess); +extern void VGSvcGstCtrlProcessRelease(PVBOXSERVICECTRLPROCESS pProcess); +extern int VGSvcGstCtrlProcessStart(const PVBOXSERVICECTRLSESSION pSession, const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo, uint32_t uContext); +extern int VGSvcGstCtrlProcessStop(PVBOXSERVICECTRLPROCESS pProcess); +extern int VGSvcGstCtrlProcessWait(const PVBOXSERVICECTRLPROCESS pProcess, RTMSINTERVAL msTimeout, int *pRc); +/** @} */ + +RT_C_DECLS_END + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp new file mode 100644 index 00000000..e45fb77e --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp @@ -0,0 +1,2201 @@ +/* $Id: VBoxServiceControlProcess.cpp $ */ +/** @file + * VBoxServiceControlThread - Guest process handling. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/handle.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/thread.h> + +#include <VBox/VBoxGuestLib.h> +#include <VBox/HostServices/GuestControlSvc.h> + +#include "VBoxServiceInternal.h" +#include "VBoxServiceControl.h" +#include "VBoxServiceToolBox.h" + +using namespace guestControl; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vgsvcGstCtrlProcessAssignPID(PVBOXSERVICECTRLPROCESS pThread, uint32_t uPID); +static int vgsvcGstCtrlProcessLock(PVBOXSERVICECTRLPROCESS pProcess); +static int vgsvcGstCtrlProcessSetupPipe(const char *pszHowTo, int fd, PRTHANDLE ph, PRTHANDLE *pph, + PRTPIPE phPipe); +static int vgsvcGstCtrlProcessUnlock(PVBOXSERVICECTRLPROCESS pProcess); +/* Request handlers. */ +static DECLCALLBACK(int) vgsvcGstCtrlProcessOnInput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + bool fPendingClose, void *pvBuf, uint32_t cbBuf); +static DECLCALLBACK(int) vgsvcGstCtrlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags); + + + +/** + * Initialies the passed in thread data structure with the parameters given. + * + * @return IPRT status code. + * @param pProcess Process to initialize. + * @param pSession Guest session the process is bound to. + * @param pStartupInfo Startup information. + * @param u32ContextID The context ID bound to this request / command. + */ +static int vgsvcGstCtrlProcessInit(PVBOXSERVICECTRLPROCESS pProcess, + const PVBOXSERVICECTRLSESSION pSession, + const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo, + uint32_t u32ContextID) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER); + + /* General stuff. */ + pProcess->hProcess = NIL_RTPROCESS; + pProcess->pSession = pSession; + pProcess->Node.pPrev = NULL; + pProcess->Node.pNext = NULL; + + pProcess->fShutdown = false; + pProcess->fStarted = false; + pProcess->fStopped = false; + + pProcess->uPID = 0; /* Don't have a PID yet. */ + pProcess->cRefs = 0; + /* + * Use the initial context ID we got for starting + * the process to report back its status with the + * same context ID. + */ + pProcess->uContextID = u32ContextID; + /* + * Note: pProcess->ClientID will be assigned when thread is started; + * every guest process has its own client ID to detect crashes on + * a per-guest-process level. + */ + + int rc = RTCritSectInit(&pProcess->CritSect); + if (RT_FAILURE(rc)) + return rc; + + pProcess->hPollSet = NIL_RTPOLLSET; + pProcess->hPipeStdInW = NIL_RTPIPE; + pProcess->hPipeStdOutR = NIL_RTPIPE; + pProcess->hPipeStdErrR = NIL_RTPIPE; + pProcess->hNotificationPipeW = NIL_RTPIPE; + pProcess->hNotificationPipeR = NIL_RTPIPE; + + rc = RTReqQueueCreate(&pProcess->hReqQueue); + AssertReleaseRC(rc); + + /* Duplicate startup info. */ + pProcess->pStartupInfo = VbglR3GuestCtrlProcStartupInfoDup(pStartupInfo); + AssertPtrReturn(pProcess->pStartupInfo, VERR_NO_MEMORY); + + /* Adjust timeout value. */ + if ( pProcess->pStartupInfo->uTimeLimitMS == UINT32_MAX + || pProcess->pStartupInfo->uTimeLimitMS == 0) + pProcess->pStartupInfo->uTimeLimitMS = RT_INDEFINITE_WAIT; + + if (RT_FAILURE(rc)) /* Clean up on failure. */ + VGSvcGstCtrlProcessFree(pProcess); + return rc; +} + + +/** + * Frees a guest process. On success, pProcess will be + * free'd and thus won't be available anymore. + * + * @return IPRT status code. + * @param pProcess Guest process to free. + * The pointer will not be valid anymore after return. + */ +int VGSvcGstCtrlProcessFree(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pProcess->CritSect); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "[PID %RU32]: Freeing (cRefs=%RU32)...\n", pProcess->uPID, pProcess->cRefs); + + AssertReturn(pProcess->cRefs == 0, VERR_WRONG_ORDER); + AssertReturn(pProcess->fStopped, VERR_WRONG_ORDER); + AssertReturn(pProcess->fShutdown, VERR_WRONG_ORDER); + + VbglR3GuestCtrlProcStartupInfoFree(pProcess->pStartupInfo); + pProcess->pStartupInfo = NULL; + + /* + * Destroy other thread data. + */ + rc = RTPollSetDestroy(pProcess->hPollSet); + AssertRC(rc); + + rc = RTReqQueueDestroy(pProcess->hReqQueue); + AssertRC(rc); + + rc = RTPipeClose(pProcess->hNotificationPipeR); + AssertRC(rc); + rc = RTPipeClose(pProcess->hNotificationPipeW); + AssertRC(rc); + + rc = RTPipeClose(pProcess->hPipeStdInW); + AssertRC(rc); + rc = RTPipeClose(pProcess->hPipeStdErrR); + AssertRC(rc); + rc = RTPipeClose(pProcess->hPipeStdOutR); + AssertRC(rc); + + rc = RTCritSectLeave(&pProcess->CritSect); + AssertRC(rc); + + RTCritSectDelete(&pProcess->CritSect); + + /* + * Destroy thread structure as final step. + */ + RTMemFree(pProcess); + pProcess = NULL; + } + + return rc; +} + + +/** + * Signals a guest process thread that we want it to shut down in + * a gentle way. + * + * @return IPRT status code. + * @param pProcess Process to stop. + */ +int VGSvcGstCtrlProcessStop(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + VGSvcVerbose(3, "[PID %RU32]: Stopping ...\n", pProcess->uPID); + + /* Do *not* set pThread->fShutdown or other stuff here! + * The guest thread loop will clean up itself. */ + + return VGSvcGstCtrlProcessHandleTerm(pProcess); +} + + +/** + * Releases a previously acquired guest process (decreases the refcount). + * + * @param pProcess Process to release. + */ +void VGSvcGstCtrlProcessRelease(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturnVoid(pProcess); + + int rc2 = RTCritSectEnter(&pProcess->CritSect); + if (RT_SUCCESS(rc2)) + { + AssertReturnVoid(pProcess->cRefs); + pProcess->cRefs--; + + VGSvcVerbose(3, "[PID %RU32]: cRefs=%RU32, fShutdown=%RTbool, fStopped=%RTbool\n", + pProcess->uPID, pProcess->cRefs, pProcess->fShutdown, pProcess->fStopped); + + rc2 = RTCritSectLeave(&pProcess->CritSect); + AssertRC(rc2); + } +} + + +/** + * Wait for a guest process thread to shut down. + * + * @return IPRT status code. + * @param pProcess Process to wait shutting down for. + * @param msTimeout Timeout in ms to wait for shutdown. + * @param prc Where to store the thread's return code. + * Optional. + */ +int VGSvcGstCtrlProcessWait(const PVBOXSERVICECTRLPROCESS pProcess, RTMSINTERVAL msTimeout, int *prc) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + AssertPtrNullReturn(prc, VERR_INVALID_POINTER); + + int rc = vgsvcGstCtrlProcessLock(pProcess); + if (RT_SUCCESS(rc)) + { + if (RTThreadGetState(pProcess->Thread) != RTTHREADSTATE_INVALID) /* Is there a thread we can wait for? */ + { + VGSvcVerbose(2, "[PID %RU32]: Waiting for shutdown (%RU32ms) ...\n", pProcess->uPID, msTimeout); + + AssertMsgReturn(pProcess->fStarted, + ("Tried to wait on guest process=%p (PID %RU32) which has not been started yet\n", + pProcess, pProcess->uPID), VERR_INVALID_PARAMETER); + + /* Unlock process before waiting. */ + rc = vgsvcGstCtrlProcessUnlock(pProcess); + AssertRC(rc); + + /* Do the actual waiting. */ + int rcThread; + Assert(pProcess->Thread != NIL_RTTHREAD); + rc = RTThreadWait(pProcess->Thread, msTimeout, &rcThread); + + int rc2 = vgsvcGstCtrlProcessLock(pProcess); + AssertRC(rc2); + + if (RT_SUCCESS(rc)) + { + pProcess->Thread = NIL_RTTHREAD; + VGSvcVerbose(3, "[PID %RU32]: Thread shutdown complete, thread rc=%Rrc\n", pProcess->uPID, rcThread); + if (prc) + *prc = rcThread; + } + } + + int rc2 = vgsvcGstCtrlProcessUnlock(pProcess); + AssertRC(rc2); + } + + if (RT_FAILURE(rc)) + VGSvcError("[PID %RU32]: Waiting for shutting down thread returned error rc=%Rrc\n", pProcess->uPID, rc); + + VGSvcVerbose(3, "[PID %RU32]: Waiting resulted in rc=%Rrc\n", pProcess->uPID, rc); + return rc; +} + + +/** + * Closes the stdin pipe of a guest process. + * + * @return IPRT status code. + * @param pProcess The process which input pipe we close. + * @param phStdInW The standard input pipe handle. + */ +static int vgsvcGstCtrlProcessPollsetCloseInput(PVBOXSERVICECTRLPROCESS pProcess, PRTPIPE phStdInW) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + AssertPtrReturn(phStdInW, VERR_INVALID_POINTER); + + int rc = RTPollSetRemove(pProcess->hPollSet, VBOXSERVICECTRLPIPEID_STDIN); + if (rc != VERR_POLL_HANDLE_ID_NOT_FOUND) + AssertRC(rc); + + if (*phStdInW != NIL_RTPIPE) + { + rc = RTPipeClose(*phStdInW); + AssertRC(rc); + *phStdInW = NIL_RTPIPE; + } + + return rc; +} + + +#ifdef DEBUG +/** + * Names a poll handle ID. + * + * @returns Pointer to read-only string. + * @param idPollHnd What to name. + */ +static const char *vgsvcGstCtrlProcessPollHandleToString(uint32_t idPollHnd) +{ + switch (idPollHnd) + { + case VBOXSERVICECTRLPIPEID_UNKNOWN: + return "unknown"; + case VBOXSERVICECTRLPIPEID_STDIN: + return "stdin"; + case VBOXSERVICECTRLPIPEID_STDIN_WRITABLE: + return "stdin_writable"; + case VBOXSERVICECTRLPIPEID_STDOUT: + return "stdout"; + case VBOXSERVICECTRLPIPEID_STDERR: + return "stderr"; + case VBOXSERVICECTRLPIPEID_IPC_NOTIFY: + return "ipc_notify"; + default: + return "unknown"; + } +} +#endif /* DEBUG */ + + +/** + * Handle an error event on standard input. + * + * @return IPRT status code. + * @param pProcess Process to handle pollset for. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phStdInW The standard input pipe handle. + */ +static int vgsvcGstCtrlProcessPollsetOnInput(PVBOXSERVICECTRLPROCESS pProcess, uint32_t fPollEvt, PRTPIPE phStdInW) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + NOREF(fPollEvt); + + return vgsvcGstCtrlProcessPollsetCloseInput(pProcess, phStdInW); +} + + +/** + * Handle pending output data or error on standard out or standard error. + * + * @returns IPRT status code from client send. + * @param pProcess Process to handle pollset for. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phPipeR The pipe handle. + * @param idPollHnd The pipe ID to handle. + */ +static int vgsvcGstCtrlProcessHandleOutputError(PVBOXSERVICECTRLPROCESS pProcess, + uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd) +{ + RT_NOREF1(fPollEvt); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + if (!phPipeR) + return VINF_SUCCESS; + +#ifdef DEBUG + VGSvcVerbose(4, "[PID %RU32]: Output error: idPollHnd=%s, fPollEvt=0x%x\n", + pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), fPollEvt); +#endif + + /* Remove pipe from poll set. */ + int rc2 = RTPollSetRemove(pProcess->hPollSet, idPollHnd); + AssertMsg(RT_SUCCESS(rc2) || rc2 == VERR_POLL_HANDLE_ID_NOT_FOUND, ("%Rrc\n", rc2)); + + bool fClosePipe = true; /* By default close the pipe. */ + + /* Check if there's remaining data to read from the pipe. */ + if (*phPipeR != NIL_RTPIPE) + { + size_t cbReadable; + rc2 = RTPipeQueryReadable(*phPipeR, &cbReadable); + if ( RT_SUCCESS(rc2) + && cbReadable) + { +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: idPollHnd=%s has %zu bytes left, vetoing close\n", + pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), cbReadable); +#endif + /* Veto closing the pipe yet because there's still stuff to read + * from the pipe. This can happen on UNIX-y systems where on + * error/hangup there still can be data to be read out. */ + fClosePipe = false; + } + } +#ifdef DEBUG + else + VGSvcVerbose(3, "[PID %RU32]: idPollHnd=%s will be closed\n", + pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd)); +#endif + + if ( *phPipeR != NIL_RTPIPE + && fClosePipe) + { + rc2 = RTPipeClose(*phPipeR); + AssertRC(rc2); + *phPipeR = NIL_RTPIPE; + } + + return VINF_SUCCESS; +} + + +/** + * Handle pending output data or error on standard out or standard error. + * + * @returns IPRT status code from client send. + * @param pProcess Process to handle pollset for. + * @param fPollEvt The event mask returned by RTPollNoResume. + * @param phPipeR The pipe handle. + * @param idPollHnd The pipe ID to handle. + * + */ +static int vgsvcGstCtrlProcessPollsetOnOutput(PVBOXSERVICECTRLPROCESS pProcess, + uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + +#ifdef DEBUG + VGSvcVerbose(4, "[PID %RU32]: Output event phPipeR=%p, idPollHnd=%s, fPollEvt=0x%x\n", + pProcess->uPID, phPipeR, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), fPollEvt); +#endif + + if (!phPipeR) + return VINF_SUCCESS; + + int rc = VINF_SUCCESS; + +#ifdef DEBUG + if (*phPipeR != NIL_RTPIPE) + { + size_t cbReadable; + rc = RTPipeQueryReadable(*phPipeR, &cbReadable); + if ( RT_SUCCESS(rc) + && cbReadable) + { + VGSvcVerbose(4, "[PID %RU32]: Output event cbReadable=%zu\n", pProcess->uPID, cbReadable); + } + } +#endif + +#if 0 + /* Push output to the host. */ + if (fPollEvt & RTPOLL_EVT_READ) + { + size_t cbRead = 0; + uint8_t byData[_64K]; + rc = RTPipeRead(*phPipeR, byData, sizeof(byData), &cbRead); + VGSvcVerbose(4, "VGSvcGstCtrlProcessHandleOutputEvent cbRead=%u, rc=%Rrc\n", cbRead, rc); + + /* Make sure we go another poll round in case there was too much data + for the buffer to hold. */ + fPollEvt &= RTPOLL_EVT_ERROR; + } +#endif + + if (fPollEvt & RTPOLL_EVT_ERROR) + rc = vgsvcGstCtrlProcessHandleOutputError(pProcess, fPollEvt, phPipeR, idPollHnd); + return rc; +} + + +/** + * Execution loop which runs in a dedicated per-started-process thread and + * handles all pipe input/output and signalling stuff. + * + * @return IPRT status code. + * @param pProcess The guest process to handle. + */ +static int vgsvcGstCtrlProcessProcLoop(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc; + int rc2; + uint64_t const uMsStart = RTTimeMilliTS(); + RTPROCSTATUS ProcessStatus = { 254, RTPROCEXITREASON_ABEND }; + bool fProcessAlive = true; + bool fProcessTimedOut = false; + uint64_t MsProcessKilled = UINT64_MAX; + RTMSINTERVAL const cMsPollBase = pProcess->hPipeStdInW != NIL_RTPIPE + ? 100 /* Need to poll for input. */ + : 1000; /* Need only poll for process exit and aborts. */ + RTMSINTERVAL cMsPollCur = 0; + + /* + * Assign PID to thread data. + * Also check if there already was a thread with the same PID and shut it down -- otherwise + * the first (stale) entry will be found and we get really weird results! + */ + rc = vgsvcGstCtrlProcessAssignPID(pProcess, pProcess->hProcess /* Opaque PID handle */); + if (RT_FAILURE(rc)) + { + VGSvcError("Unable to assign PID=%u, to new thread, rc=%Rrc\n", pProcess->hProcess, rc); + return rc; + } + + /* + * Before entering the loop, tell the host that we've started the guest + * and that it's now OK to send input to the process. + */ + VGSvcVerbose(2, "[PID %RU32]: Process '%s' started, CID=%u, User=%s, cMsTimeout=%RU32\n", + pProcess->uPID, pProcess->pStartupInfo->pszCmd, pProcess->uContextID, + pProcess->pStartupInfo->pszUser, pProcess->pStartupInfo->uTimeLimitMS); + VBGLR3GUESTCTRLCMDCTX ctxStart = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ }; + rc = VbglR3GuestCtrlProcCbStatus(&ctxStart, + pProcess->uPID, PROC_STS_STARTED, 0 /* u32Flags */, + NULL /* pvData */, 0 /* cbData */); + if (rc == VERR_INTERRUPTED) + rc = VINF_SUCCESS; /* SIGCHLD send by quick childs! */ + if (RT_FAILURE(rc)) + VGSvcError("[PID %RU32]: Error reporting starting status to host, rc=%Rrc\n", pProcess->uPID, rc); + + /* + * Process input, output, the test pipe and client requests. + */ + while ( RT_SUCCESS(rc) + && RT_UNLIKELY(!pProcess->fShutdown)) + { + /* + * Wait/Process all pending events. + */ + uint32_t idPollHnd; + uint32_t fPollEvt; + rc2 = RTPollNoResume(pProcess->hPollSet, cMsPollCur, &fPollEvt, &idPollHnd); + if (pProcess->fShutdown) + continue; + + cMsPollCur = 0; /* No rest until we've checked everything. */ + + if (RT_SUCCESS(rc2)) + { + switch (idPollHnd) + { + case VBOXSERVICECTRLPIPEID_STDIN: + rc = vgsvcGstCtrlProcessPollsetOnInput(pProcess, fPollEvt, &pProcess->hPipeStdInW); + break; + + case VBOXSERVICECTRLPIPEID_STDOUT: + rc = vgsvcGstCtrlProcessPollsetOnOutput(pProcess, fPollEvt, &pProcess->hPipeStdOutR, idPollHnd); + break; + + case VBOXSERVICECTRLPIPEID_STDERR: + rc = vgsvcGstCtrlProcessPollsetOnOutput(pProcess, fPollEvt, &pProcess->hPipeStdErrR, idPollHnd); + break; + + case VBOXSERVICECTRLPIPEID_IPC_NOTIFY: +#ifdef DEBUG_andy + VGSvcVerbose(4, "[PID %RU32]: IPC notify\n", pProcess->uPID); +#endif + rc2 = vgsvcGstCtrlProcessLock(pProcess); + if (RT_SUCCESS(rc2)) + { + /* Drain the notification pipe. */ + uint8_t abBuf[8]; + size_t cbIgnore; + rc2 = RTPipeRead(pProcess->hNotificationPipeR, abBuf, sizeof(abBuf), &cbIgnore); + if (RT_FAILURE(rc2)) + VGSvcError("Draining IPC notification pipe failed with rc=%Rrc\n", rc2); + + /* Process all pending requests. */ + VGSvcVerbose(4, "[PID %RU32]: Processing pending requests ...\n", pProcess->uPID); + Assert(pProcess->hReqQueue != NIL_RTREQQUEUE); + rc2 = RTReqQueueProcess(pProcess->hReqQueue, + 0 /* Only process all pending requests, don't wait for new ones */); + if ( RT_FAILURE(rc2) + && rc2 != VERR_TIMEOUT) + VGSvcError("Processing requests failed with with rc=%Rrc\n", rc2); + + int rc3 = vgsvcGstCtrlProcessUnlock(pProcess); + AssertRC(rc3); +#ifdef DEBUG + VGSvcVerbose(4, "[PID %RU32]: Processing pending requests done, rc=%Rrc\n", pProcess->uPID, rc2); +#endif + } + + break; + + default: + AssertMsgFailed(("Unknown idPollHnd=%RU32\n", idPollHnd)); + break; + } + + if (RT_FAILURE(rc) || rc == VINF_EOF) + break; /* Abort command, or client dead or something. */ + } +#if 0 + VGSvcVerbose(4, "[PID %RU32]: Polling done, pollRc=%Rrc, pollCnt=%RU32, idPollHnd=%s, rc=%Rrc, fProcessAlive=%RTbool, fShutdown=%RTbool\n", + pProcess->uPID, rc2, RTPollSetGetCount(hPollSet), vgsvcGstCtrlProcessPollHandleToString(idPollHnd), rc, fProcessAlive, pProcess->fShutdown); + VGSvcVerbose(4, "[PID %RU32]: stdOut=%s, stdErrR=%s\n", + pProcess->uPID, + *phStdOutR == NIL_RTPIPE ? "closed" : "open", + *phStdErrR == NIL_RTPIPE ? "closed" : "open"); +#endif + if (RT_UNLIKELY(pProcess->fShutdown)) + break; /* We were asked to shutdown. */ + + /* + * Check for process death. + */ + if (fProcessAlive) + { + rc2 = RTProcWaitNoResume(pProcess->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus); + if (RT_SUCCESS_NP(rc2)) + { + fProcessAlive = false; + /* Note: Don't bail out here yet. First check in the next block below + * if all needed pipe outputs have been consumed. */ + } + else + { + if (RT_UNLIKELY(rc2 == VERR_INTERRUPTED)) + continue; + if (RT_UNLIKELY(rc2 == VERR_PROCESS_NOT_FOUND)) + { + fProcessAlive = false; + ProcessStatus.enmReason = RTPROCEXITREASON_ABEND; + ProcessStatus.iStatus = 255; + AssertFailed(); + } + else + AssertMsg(rc2 == VERR_PROCESS_RUNNING, ("%Rrc\n", rc2)); + } + } + + /* + * If the process has terminated and all output has been consumed, + * we should be heading out. + */ + if (!fProcessAlive) + { + if ( fProcessTimedOut + || ( pProcess->hPipeStdOutR == NIL_RTPIPE + && pProcess->hPipeStdErrR == NIL_RTPIPE) + ) + { + VGSvcVerbose(3, "[PID %RU32]: RTProcWaitNoResume=%Rrc\n", pProcess->uPID, rc2); + break; + } + } + + /* + * Check for timed out, killing the process. + */ + uint32_t cMilliesLeft = RT_INDEFINITE_WAIT; + if ( pProcess->pStartupInfo->uTimeLimitMS != RT_INDEFINITE_WAIT + && pProcess->pStartupInfo->uTimeLimitMS != 0) + { + uint64_t u64Now = RTTimeMilliTS(); + uint64_t cMsElapsed = u64Now - uMsStart; + if (cMsElapsed >= pProcess->pStartupInfo->uTimeLimitMS) + { + fProcessTimedOut = true; + if ( MsProcessKilled == UINT64_MAX + || u64Now - MsProcessKilled > 1000) + { + if (u64Now - MsProcessKilled > 20*60*1000) + break; /* Give up after 20 mins. */ + + VGSvcVerbose(3, "[PID %RU32]: Timed out (%RU64ms elapsed > %RU32ms timeout), killing ...\n", + pProcess->uPID, cMsElapsed, pProcess->pStartupInfo->uTimeLimitMS); + + rc2 = RTProcTerminate(pProcess->hProcess); + VGSvcVerbose(3, "[PID %RU32]: Killing process resulted in rc=%Rrc\n", + pProcess->uPID, rc2); + MsProcessKilled = u64Now; + continue; + } + cMilliesLeft = 10000; + } + else + cMilliesLeft = pProcess->pStartupInfo->uTimeLimitMS - (uint32_t)cMsElapsed; + } + + /* Reset the polling interval since we've done all pending work. */ + cMsPollCur = fProcessAlive + ? cMsPollBase + : RT_MS_1MIN; + if (cMilliesLeft < cMsPollCur) + cMsPollCur = cMilliesLeft; + } + + VGSvcVerbose(3, "[PID %RU32]: Loop ended: rc=%Rrc, fShutdown=%RTbool, fProcessAlive=%RTbool, fProcessTimedOut=%RTbool, MsProcessKilled=%RU64 (%RX64)\n", + pProcess->uPID, rc, pProcess->fShutdown, fProcessAlive, fProcessTimedOut, MsProcessKilled, MsProcessKilled); + VGSvcVerbose(3, "[PID %RU32]: *phStdOutR=%s, *phStdErrR=%s\n", + pProcess->uPID, + pProcess->hPipeStdOutR == NIL_RTPIPE ? "closed" : "open", + pProcess->hPipeStdErrR == NIL_RTPIPE ? "closed" : "open"); + + /* Signal that this thread is in progress of shutting down. */ + ASMAtomicWriteBool(&pProcess->fShutdown, true); + + /* + * Try killing the process if it's still alive at this point. + */ + if (fProcessAlive) + { + if (MsProcessKilled == UINT64_MAX) + { + VGSvcVerbose(2, "[PID %RU32]: Is still alive and not killed yet\n", pProcess->uPID); + + MsProcessKilled = RTTimeMilliTS(); + rc2 = RTProcTerminate(pProcess->hProcess); + if (rc2 == VERR_NOT_FOUND) + { + fProcessAlive = false; + } + else if (RT_FAILURE(rc2)) + VGSvcError("[PID %RU32]: Killing process failed with rc=%Rrc\n", pProcess->uPID, rc2); + RTThreadSleep(500); + } + + for (int i = 0; i < 10 && fProcessAlive; i++) + { + VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Waiting to exit ...\n", pProcess->uPID, i + 1); + rc2 = RTProcWait(pProcess->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus); + if (RT_SUCCESS(rc2)) + { + VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Exited\n", pProcess->uPID, i + 1); + fProcessAlive = false; + break; + } + if (i >= 5) + { + VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Trying to terminate ...\n", pProcess->uPID, i + 1); + rc2 = RTProcTerminate(pProcess->hProcess); + if ( RT_FAILURE(rc) + && rc2 != VERR_NOT_FOUND) + VGSvcError("PID %RU32]: Killing process failed with rc=%Rrc\n", + pProcess->uPID, rc2); + } + RTThreadSleep(i >= 5 ? 2000 : 500); + } + + if (fProcessAlive) + VGSvcError("[PID %RU32]: Could not be killed\n", pProcess->uPID); + } + + /* + * Shutdown procedure: + * - Set the pProcess->fShutdown indicator to let others know we're + * not accepting any new requests anymore. + * - After setting the indicator, try to process all outstanding + * requests to make sure they're getting delivered. + * + * Note: After removing the process from the session's list it's not + * even possible for the session anymore to control what's + * happening to this thread, so be careful and don't mess it up. + */ + + rc2 = vgsvcGstCtrlProcessLock(pProcess); + if (RT_SUCCESS(rc2)) + { + VGSvcVerbose(3, "[PID %RU32]: Processing outstanding requests ...\n", pProcess->uPID); + + /* Process all pending requests (but don't wait for new ones). */ + Assert(pProcess->hReqQueue != NIL_RTREQQUEUE); + rc2 = RTReqQueueProcess(pProcess->hReqQueue, 0 /* No timeout */); + if ( RT_FAILURE(rc2) + && rc2 != VERR_TIMEOUT) + VGSvcError("[PID %RU32]: Processing outstanding requests failed with with rc=%Rrc\n", pProcess->uPID, rc2); + + VGSvcVerbose(3, "[PID %RU32]: Processing outstanding requests done, rc=%Rrc\n", pProcess->uPID, rc2); + + rc2 = vgsvcGstCtrlProcessUnlock(pProcess); + AssertRC(rc2); + } + + /* + * If we don't have a client problem (RT_FAILURE(rc)) we'll reply to the + * clients exec packet now. + */ + if (RT_SUCCESS(rc)) + { + uint32_t uStatus = PROC_STS_UNDEFINED; + uint32_t fFlags = 0; + + if ( fProcessTimedOut && !fProcessAlive && MsProcessKilled != UINT64_MAX) + { + VGSvcVerbose(3, "[PID %RU32]: Timed out and got killed\n", pProcess->uPID); + uStatus = PROC_STS_TOK; + } + else if (fProcessTimedOut && fProcessAlive && MsProcessKilled != UINT64_MAX) + { + VGSvcVerbose(3, "[PID %RU32]: Timed out and did *not* get killed\n", pProcess->uPID); + uStatus = PROC_STS_TOA; + } + else if (pProcess->fShutdown && (fProcessAlive || MsProcessKilled != UINT64_MAX)) + { + VGSvcVerbose(3, "[PID %RU32]: Got terminated because system/service is about to shutdown\n", pProcess->uPID); + uStatus = PROC_STS_DWN; /* Service is stopping, process was killed. */ + fFlags = pProcess->pStartupInfo->fFlags; /* Return handed-in execution flags back to the host. */ + } + else if (fProcessAlive) + VGSvcError("[PID %RU32]: Is alive when it should not!\n", pProcess->uPID); + else if (MsProcessKilled != UINT64_MAX) + VGSvcError("[PID %RU32]: Has been killed when it should not!\n", pProcess->uPID); + else if (ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL) + { + VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_NORMAL (Exit code: %d)\n", + pProcess->uPID, ProcessStatus.iStatus); + uStatus = PROC_STS_TEN; + fFlags = ProcessStatus.iStatus; + } + else if (ProcessStatus.enmReason == RTPROCEXITREASON_SIGNAL) + { + VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_SIGNAL (Signal: %u)\n", + pProcess->uPID, ProcessStatus.iStatus); + uStatus = PROC_STS_TES; + fFlags = ProcessStatus.iStatus; + } + else if (ProcessStatus.enmReason == RTPROCEXITREASON_ABEND) + { + /* ProcessStatus.iStatus will be undefined. */ + VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_ABEND\n", pProcess->uPID); + uStatus = PROC_STS_TEA; + fFlags = ProcessStatus.iStatus; + } + else + VGSvcVerbose(1, "[PID %RU32]: Handling process status %u not implemented\n", pProcess->uPID, ProcessStatus.enmReason); + VBGLR3GUESTCTRLCMDCTX ctxEnd = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ }; + VGSvcVerbose(2, "[PID %RU32]: Ended, ClientID=%u, CID=%u, Status=%u, Flags=0x%x\n", + pProcess->uPID, ctxEnd.uClientID, pProcess->uContextID, uStatus, fFlags); + + rc2 = VbglR3GuestCtrlProcCbStatus(&ctxEnd, pProcess->uPID, uStatus, fFlags, NULL /* pvData */, 0 /* cbData */); + if ( RT_FAILURE(rc2) + && rc2 == VERR_NOT_FOUND) + VGSvcError("[PID %RU32]: Error reporting final status to host; rc=%Rrc\n", pProcess->uPID, rc2); + } + + VGSvcVerbose(3, "[PID %RU32]: Process loop returned with rc=%Rrc\n", pProcess->uPID, rc); + return rc; +} + + +#if 0 /* unused */ +/** + * Initializes a pipe's handle and pipe object. + * + * @return IPRT status code. + * @param ph The pipe's handle to initialize. + * @param phPipe The pipe's object to initialize. + */ +static int vgsvcGstCtrlProcessInitPipe(PRTHANDLE ph, PRTPIPE phPipe) +{ + AssertPtrReturn(ph, VERR_INVALID_PARAMETER); + AssertPtrReturn(phPipe, VERR_INVALID_PARAMETER); + + ph->enmType = RTHANDLETYPE_PIPE; + ph->u.hPipe = NIL_RTPIPE; + *phPipe = NIL_RTPIPE; + + return VINF_SUCCESS; +} +#endif + + +/** + * Sets up the redirection / pipe / nothing for one of the standard handles. + * + * @returns IPRT status code. No client replies made. + * @param pszHowTo How to set up this standard handle. + * @param fd Which standard handle it is (0 == stdin, 1 == + * stdout, 2 == stderr). + * @param ph The generic handle that @a pph may be set + * pointing to. Always set. + * @param pph Pointer to the RTProcCreateExec argument. + * Always set. + * @param phPipe Where to return the end of the pipe that we + * should service. + */ +static int vgsvcGstCtrlProcessSetupPipe(const char *pszHowTo, int fd, PRTHANDLE ph, PRTHANDLE *pph, PRTPIPE phPipe) +{ + AssertPtrReturn(ph, VERR_INVALID_POINTER); + AssertPtrReturn(pph, VERR_INVALID_POINTER); + AssertPtrReturn(phPipe, VERR_INVALID_POINTER); + + int rc; + + ph->enmType = RTHANDLETYPE_PIPE; + ph->u.hPipe = NIL_RTPIPE; + *pph = NULL; + *phPipe = NIL_RTPIPE; + + if (!strcmp(pszHowTo, "|")) + { + /* + * Setup a pipe for forwarding to/from the client. + * The ph union struct will be filled with a pipe read/write handle + * to represent the "other" end to phPipe. + */ + if (fd == 0) /* stdin? */ + { + /* Connect a wrtie pipe specified by phPipe to stdin. */ + rc = RTPipeCreate(&ph->u.hPipe, phPipe, RTPIPE_C_INHERIT_READ); + } + else /* stdout or stderr. */ + { + /* Connect a read pipe specified by phPipe to stdout or stderr. */ + rc = RTPipeCreate(phPipe, &ph->u.hPipe, RTPIPE_C_INHERIT_WRITE); + } + + if (RT_FAILURE(rc)) + return rc; + + ph->enmType = RTHANDLETYPE_PIPE; + *pph = ph; + } + else if (!strcmp(pszHowTo, "/dev/null")) + { + /* + * Redirect to/from /dev/null. + */ + RTFILE hFile; + rc = RTFileOpenBitBucket(&hFile, fd == 0 ? RTFILE_O_READ : RTFILE_O_WRITE); + if (RT_FAILURE(rc)) + return rc; + + ph->enmType = RTHANDLETYPE_FILE; + ph->u.hFile = hFile; + *pph = ph; + } + else /* Add other piping stuff here. */ + rc = VINF_SUCCESS; /* Same as parent (us). */ + + return rc; +} + + +/** + * Expands a file name / path to its real content. + * + * ~~This only works on Windows for now (e.g. translating "%TEMP%\foo.exe" to + * "C:\Windows\Temp" when starting with system / administrative rights).~~ See + * todo in code. + * + * @return IPRT status code. + * @param pszPath Path to resolve. + * @param pszExpanded Pointer to string to store the resolved path in. + * @param cbExpanded Size (in bytes) of string to store the resolved path. + */ +static int vgsvcGstCtrlProcessMakeFullPath(const char *pszPath, char *pszExpanded, size_t cbExpanded) +{ +/** @todo r=bird: This feature shall be made optional, i.e. require a + * flag to be passed down. Further, it shall work on the environment + * block of the new process (i.e. include env changes passed down from + * the caller). I would also suggest using the unix variable expansion + * syntax, not the DOS one. + * + * Since this currently not available on non-windows guests, I suggest + * we disable it until such a time as it is implemented correctly. */ +#if 0 /*def RT_OS_WINDOWS - see above. Don't know why this wasn't disabled before 7.0, didn't see the @todo yet? */ + int rc = VINF_SUCCESS; + if (!ExpandEnvironmentStrings(pszPath, pszExpanded, (DWORD)cbExpanded)) + rc = RTErrConvertFromWin32(GetLastError()); +#else + /* There is no expansion anywhere yet, see above @todo. */ + int rc = RTStrCopy(pszExpanded, cbExpanded, pszPath); +#endif +#ifdef DEBUG + VGSvcVerbose(3, "vgsvcGstCtrlProcessMakeFullPath: %s -> %s\n", pszPath, pszExpanded); +#endif + return rc; +} + + +/** + * Resolves the full path of a specified executable name. + * + * This function also resolves internal VBoxService tools to its appropriate + * executable path + name if VBOXSERVICE_NAME is specified as pszFilename. + * + * @return IPRT status code. + * @param pszFilename File name to resolve. + * @param pszResolved Pointer to a string where the resolved file name will be stored. + * @param cbResolved Size (in bytes) of resolved file name string. + */ +static int vgsvcGstCtrlProcessResolveExecutable(const char *pszFilename, char *pszResolved, size_t cbResolved) +{ + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertPtrReturn(pszResolved, VERR_INVALID_POINTER); + AssertReturn(cbResolved, VERR_INVALID_PARAMETER); + + const char * const pszOrgFilename = pszFilename; + if ( RTStrICmp(pszFilename, g_pszProgName) == 0 + || RTStrICmp(pszFilename, VBOXSERVICE_NAME) == 0) + pszFilename = RTProcExecutablePath(); + + int rc = vgsvcGstCtrlProcessMakeFullPath(pszFilename, pszResolved, cbResolved); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Looked up executable: %s -> %s\n", pszOrgFilename, pszResolved); + return rc; +} + + +/** + * Constructs the argv command line by resolving environment variables + * and relative paths. + * + * @return IPRT status code. + * @param pszArgv0 First argument (argv0), either original or modified version. + * @param papszArgs Original argv command line from the host, starting at argv[1]. + * @param fFlags The process creation flags pass to us from the host. + * @param fExecutingSelf Set if we're executing the VBoxService executable + * and should inject the --utf8-argv trick. + * @param ppapszArgv Pointer to a pointer with the new argv command line. + * Needs to be freed with RTGetOptArgvFree. + */ +static int vgsvcGstCtrlProcessAllocateArgv(const char *pszArgv0, const char * const *papszArgs, uint32_t fFlags, + bool fExecutingSelf, char ***ppapszArgv) +{ + VGSvcVerbose(3, "VGSvcGstCtrlProcessPrepareArgv: pszArgv0=%p, papszArgs=%p, fFlags=%#x, fExecutingSelf=%d, ppapszArgv=%p\n", + pszArgv0, papszArgs, fFlags, fExecutingSelf, ppapszArgv); + + AssertPtrReturn(pszArgv0, VERR_INVALID_POINTER); + AssertPtrReturn(ppapszArgv, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & GUEST_PROC_CREATE_FLAG_EXPAND_ARGUMENTS), VERR_INVALID_FLAGS); /** @todo implement me */ + +#ifndef VBOXSERVICE_ARG1_UTF8_ARGV + fExecutingSelf = false; +#endif + + /* Count arguments: */ + int rc = VINF_SUCCESS; + uint32_t cArgs; + for (cArgs = 0; papszArgs[cArgs]; cArgs++) + { + if (cArgs >= UINT32_MAX - 2) + return VERR_BUFFER_OVERFLOW; + } + + /* Allocate new argv vector (adding + 2 for argv0 + termination). */ + size_t cbSize = (fExecutingSelf + cArgs + 2) * sizeof(char *); + char **papszNewArgv = (char **)RTMemAlloc(cbSize); + if (!papszNewArgv) + return VERR_NO_MEMORY; + + VGSvcVerbose(3, "VGSvcGstCtrlProcessAllocateArgv: pszArgv0 = '%s', cArgs=%RU32, cbSize=%zu\n", pszArgv0, cArgs, cbSize); +#ifdef DEBUG /* Never log this stuff in release mode! */ + if (cArgs) + { + for (uint32_t i = 0; i < cArgs; i++) + VGSvcVerbose(3, "VGSvcGstCtrlProcessAllocateArgv: papszArgs[%RU32] = '%s'\n", i, papszArgs[i]); + } +#endif + + /* HACK ALERT! Older hosts (< VBox 6.1.x) did not allow the user to really specify + the first argument separately from the executable image, so we have + to fudge a little in the unquoted argument case to deal with executables + containing spaces. Windows only, as RTPROC_FLAGS_UNQUOTED_ARGS is + ignored on all other hosts. */ +#ifdef RT_OS_WINDOWS + if ( (fFlags & GUEST_PROC_CREATE_FLAG_UNQUOTED_ARGS) + && strpbrk(pszArgv0, " \t\n\r") + && pszArgv0[0] == '"') + { + size_t cchArgv0 = strlen(pszArgv0); + AssertReturn(cchArgv0, VERR_INVALID_PARAMETER); /* Paranoia. */ + rc = RTStrAllocEx(&papszNewArgv[0], 1 + cchArgv0 + 1 + 1); + if (RT_SUCCESS(rc)) + { + char *pszDst = papszNewArgv[0]; + *pszDst++ = '"'; + memcpy(pszDst, pszArgv0, cchArgv0); + pszDst += cchArgv0; + *pszDst++ = '"'; + *pszDst = '\0'; + } + } + else +#endif + rc = RTStrDupEx(&papszNewArgv[0], pszArgv0); + if (RT_SUCCESS(rc)) + { + size_t iDst = 1; + +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV + /* Insert --utf8-argv as the first argument if executing the VBoxService binary. */ + if (fExecutingSelf) + { + rc = RTStrDupEx(&papszNewArgv[iDst], VBOXSERVICE_ARG1_UTF8_ARGV); + if (RT_SUCCESS(rc)) + iDst++; + } +#endif + /* Copy over the other arguments. */ + if (RT_SUCCESS(rc)) + for (size_t iSrc = 0; iSrc < cArgs; iSrc++) + { +#if 0 /* Arguments expansion -- untested. */ + if (fFlags & GUEST_PROC_CREATE_FLAG_EXPAND_ARGUMENTS) + { +/** @todo r=bird: If you want this, we need a generic implementation, preferably in RTEnv or somewhere like that. The marking + * up of the variables must be the same on all platforms. */ + /* According to MSDN the limit on older Windows version is 32K, whereas + * Vista+ there are no limits anymore. We still stick to 4K. */ + char szExpanded[_4K]; +# ifdef RT_OS_WINDOWS + if (!ExpandEnvironmentStrings(papszArgs[i], szExpanded, sizeof(szExpanded))) + rc = RTErrConvertFromWin32(GetLastError()); +# else + /* No expansion for non-Windows yet. */ + rc = RTStrCopy(papszArgs[i], sizeof(szExpanded), szExpanded); +# endif + if (RT_SUCCESS(rc)) + rc = RTStrDupEx(&pszArg, szExpanded); + } + else +#endif + rc = RTStrDupEx(&papszNewArgv[iDst], papszArgs[iSrc]); + if (RT_SUCCESS(rc)) + iDst++; + else + break; + } + + if (RT_SUCCESS(rc)) + { + /* Terminate array. */ + papszNewArgv[iDst] = NULL; + + *ppapszArgv = papszNewArgv; + return VINF_SUCCESS; + } + + /* Failed, bail out. */ + while (iDst-- > 0) + RTStrFree(papszNewArgv[iDst]); + } + RTMemFree(papszNewArgv); + return rc; +} + + +/** + * Assigns a valid PID to a guest control thread and also checks if there already was + * another (stale) guest process which was using that PID before and destroys it. + * + * @return IPRT status code. + * @param pProcess Process to assign PID to. + * @param uPID PID to assign to the specified guest control execution thread. + */ +static int vgsvcGstCtrlProcessAssignPID(PVBOXSERVICECTRLPROCESS pProcess, uint32_t uPID) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + AssertReturn(uPID, VERR_INVALID_PARAMETER); + + AssertPtr(pProcess->pSession); + int rc = RTCritSectEnter(&pProcess->pSession->CritSect); + if (RT_SUCCESS(rc)) + { + /* Search old threads using the desired PID and shut them down completely -- it's + * not used anymore. */ + bool fTryAgain; + do + { + fTryAgain = false; + PVBOXSERVICECTRLPROCESS pProcessCur; + RTListForEach(&pProcess->pSession->lstProcesses, pProcessCur, VBOXSERVICECTRLPROCESS, Node) + { + if (pProcessCur->uPID == uPID) + { + Assert(pProcessCur != pProcess); /* can't happen */ + uint32_t uTriedPID = uPID; + uPID += 391939; + VGSvcVerbose(2, "PID %RU32 was used before (process %p), trying again with %RU32 ...\n", + uTriedPID, pProcessCur, uPID); + fTryAgain = true; + break; + } + } + } while (fTryAgain); + + /* Assign PID to current thread. */ + pProcess->uPID = uPID; + + rc = RTCritSectLeave(&pProcess->pSession->CritSect); + AssertRC(rc); + } + + return rc; +} + + +static void vgsvcGstCtrlProcessFreeArgv(char **papszArgv) +{ + if (papszArgv) + { + size_t i = 0; + while (papszArgv[i]) + RTStrFree(papszArgv[i++]); + RTMemFree(papszArgv); + } +} + + +/** + * Helper function to create/start a process on the guest. + * + * @return IPRT status code. + * @param pszExec Full qualified path of process to start (without arguments). + * @param papszArgs Pointer to array of command line arguments. + * @param hEnv Handle to environment block to use. + * @param fFlags Process execution flags. + * @param phStdIn Handle for the process' stdin pipe. + * @param phStdOut Handle for the process' stdout pipe. + * @param phStdErr Handle for the process' stderr pipe. + * @param pszAsUser User name (account) to start the process under. + * @param pszPassword Password of the specified user. + * @param pszDomain Domain to use for authentication. + * @param phProcess Pointer which will receive the process handle after + * successful process start. + */ +static int vgsvcGstCtrlProcessCreateProcess(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags, + PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, + const char *pszAsUser, const char *pszPassword, const char *pszDomain, + PRTPROCESS phProcess) +{ +#ifndef RT_OS_WINDOWS + RT_NOREF1(pszDomain); +#endif + AssertPtrReturn(pszExec, VERR_INVALID_PARAMETER); + AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER); + /* phStdIn is optional. */ + /* phStdOut is optional. */ + /* phStdErr is optional. */ + /* pszPassword is optional. */ + /* pszDomain is optional. */ + AssertPtrReturn(phProcess, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + char szExecExp[RTPATH_MAX]; + +#ifdef DEBUG + /* Never log this in release mode! */ + VGSvcVerbose(4, "pszUser=%s, pszPassword=%s, pszDomain=%s\n", pszAsUser, pszPassword, pszDomain); +#endif + +#ifdef RT_OS_WINDOWS + /* + * If sysprep should be executed do this in the context of VBoxService, which + * (usually, if started by SCM) has administrator rights. Because of that a UI + * won't be shown (doesn't have a desktop). + */ + if (!RTStrICmp(pszExec, "sysprep")) + { + /* Use a predefined sysprep path as default. */ + char szSysprepCmd[RTPATH_MAX] = "C:\\sysprep\\sysprep.exe"; + /** @todo Check digital signature of file above before executing it? */ + + /* + * On Windows Vista (and up) sysprep is located in "system32\\Sysprep\\sysprep.exe", + * so detect the OS and use a different path. + */ + if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6,0,0) /* Vista and later */) + { + rc = RTEnvGetEx(RTENV_DEFAULT, "windir", szSysprepCmd, sizeof(szSysprepCmd), NULL); +#ifndef RT_ARCH_AMD64 + /* Don't execute 64-bit sysprep from a 32-bit service host! */ + char szSysWow64[RTPATH_MAX]; + if (RTStrPrintf(szSysWow64, sizeof(szSysWow64), "%s", szSysprepCmd)) + { + rc = RTPathAppend(szSysWow64, sizeof(szSysWow64), "SysWow64"); + AssertRC(rc); + } + if ( RT_SUCCESS(rc) + && RTPathExists(szSysWow64)) + VGSvcVerbose(0, "Warning: This service is 32-bit; could not execute sysprep on 64-bit OS!\n"); +#endif + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szSysprepCmd, sizeof(szSysprepCmd), "system32\\Sysprep\\sysprep.exe"); + if (RT_SUCCESS(rc)) + RTPathChangeToDosSlashes(szSysprepCmd, false /* No forcing necessary */); + + if (RT_FAILURE(rc)) + VGSvcError("Failed to detect sysrep location, rc=%Rrc\n", rc); + } + + VGSvcVerbose(3, "Sysprep executable is: %s\n", szSysprepCmd); + + if (RT_SUCCESS(rc)) + { + char **papszArgsExp; + rc = vgsvcGstCtrlProcessAllocateArgv(szSysprepCmd /* argv0 */, papszArgs, fFlags, + false /*fExecutingSelf*/, &papszArgsExp); + if (RT_SUCCESS(rc)) + { + /* As we don't specify credentials for the sysprep process, it will + * run under behalf of the account VBoxService was started under, most + * likely local system. */ + rc = RTProcCreateEx(szSysprepCmd, papszArgsExp, hEnv, 0 /* fFlags */, + phStdIn, phStdOut, phStdErr, NULL /* pszAsUser */, + NULL /* pszPassword */, NULL, phProcess); + vgsvcGstCtrlProcessFreeArgv(papszArgsExp); + } + } + + if (RT_FAILURE(rc)) + VGSvcVerbose(3, "Starting sysprep returned rc=%Rrc\n", rc); + + return rc; + } +#endif /* RT_OS_WINDOWS */ + + bool fExecutingSelf = false; +#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX + /* The "vbox_" prefix is reserved for the toolbox (vbox_cat, vbox_mkdir, + et al.) and we will replace pszExec with the full VBoxService path instead. */ + if (RTStrStartsWith(pszExec, "vbox_")) + { + fExecutingSelf = true; + rc = vgsvcGstCtrlProcessResolveExecutable(VBOXSERVICE_NAME, szExecExp, sizeof(szExecExp)); + } + else + { +#endif + /* + * Do the environment variables expansion on executable and arguments. + */ + rc = vgsvcGstCtrlProcessResolveExecutable(pszExec, szExecExp, sizeof(szExecExp)); +#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX + } +#endif + if (RT_SUCCESS(rc)) + { + /* + * This one is a bit tricky to also support older hosts: + * + * - If the host does not provide a dedicated argv[0] (< VBox 6.1.x), we use the + * unmodified executable name (pszExec) as the (default) argv[0]. This is wrong, but we can't do + * much about it. The rest (argv[1,2,n]) then gets set starting at papszArgs[0]. + * + * - Newer hosts (>= VBox 6.1.x) provide a correct argv[0] independently of the actual + * executable name though, so actually use argv[0] *and* argv[1,2,n] as intended. + */ + const bool fHasArgv0 = RT_BOOL(g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_PROCESS_ARGV0); + + const char *pcszArgv0 = (fHasArgv0 && papszArgs[0]) ? papszArgs[0] : pszExec; + AssertPtrReturn(pcszArgv0, VERR_INVALID_POINTER); /* Paranoia. */ + + const uint32_t uArgvIdx = pcszArgv0 == papszArgs[0] ? 1 : 0; + + VGSvcVerbose(3, "vgsvcGstCtrlProcessCreateProcess: fHasArgv0=%RTbool, pcszArgv0=%p, uArgvIdx=%RU32, " + "g_fControlHostFeatures0=%#x\n", + fHasArgv0, pcszArgv0, uArgvIdx, g_fControlHostFeatures0); + + char **papszArgsExp; + rc = vgsvcGstCtrlProcessAllocateArgv(pcszArgv0, &papszArgs[uArgvIdx], fFlags, fExecutingSelf, &papszArgsExp); + if (RT_FAILURE(rc)) + { + /* Don't print any arguments -- may contain passwords or other sensible data! */ + VGSvcError("Could not prepare arguments, rc=%Rrc\n", rc); + } + else + { + uint32_t fProcCreateFlags = 0; + if (fExecutingSelf) + fProcCreateFlags |= VBOXSERVICE_PROC_F_UTF8_ARGV; + if (fFlags) + { + if (fFlags & GUEST_PROC_CREATE_FLAG_HIDDEN) + fProcCreateFlags |= RTPROC_FLAGS_HIDDEN; + if (fFlags & GUEST_PROC_CREATE_FLAG_PROFILE) + fProcCreateFlags |= RTPROC_FLAGS_PROFILE; + if (fFlags & GUEST_PROC_CREATE_FLAG_UNQUOTED_ARGS) + fProcCreateFlags |= RTPROC_FLAGS_UNQUOTED_ARGS; + } + + /* If no user name specified run with current credentials (e.g. + * full service/system rights). This is prohibited via official Main API! + * + * Otherwise use the RTPROC_FLAGS_SERVICE to use some special authentication + * code (at least on Windows) for running processes as different users + * started from our system service. */ + if (pszAsUser && *pszAsUser) + fProcCreateFlags |= RTPROC_FLAGS_SERVICE; +#ifdef DEBUG + VGSvcVerbose(3, "Command: %s\n", szExecExp); + for (size_t i = 0; papszArgsExp[i]; i++) + VGSvcVerbose(3, " argv[%zu]: %s\n", i, papszArgsExp[i]); +#endif + VGSvcVerbose(3, "Starting process '%s' ...\n", szExecExp); + +#ifdef RT_OS_WINDOWS + /* If a domain name is given, construct an UPN (User Principle Name) with + * the domain name built-in, e.g. "joedoe@example.com". */ + char *pszUserUPN = NULL; + if (pszDomain && *pszDomain != '\0') + { + pszAsUser = pszUserUPN = RTStrAPrintf2("%s@%s", pszAsUser, pszDomain); + if (pszAsUser) + VGSvcVerbose(3, "Using UPN: %s\n", pszAsUser); + else + rc = VERR_NO_STR_MEMORY; + } + if (RT_SUCCESS(rc)) +#endif + { + /* Do normal execution. */ + rc = RTProcCreateEx(szExecExp, papszArgsExp, hEnv, fProcCreateFlags, + phStdIn, phStdOut, phStdErr, + pszAsUser, + pszPassword && *pszPassword ? pszPassword : NULL, + NULL /*pvExtraData*/, + phProcess); + +#ifdef RT_OS_WINDOWS + RTStrFree(pszUserUPN); +#endif + VGSvcVerbose(3, "Starting process '%s' returned rc=%Rrc\n", szExecExp, rc); + } + vgsvcGstCtrlProcessFreeArgv(papszArgsExp); + } + } + return rc; +} + + +#ifdef DEBUG +/** + * Dumps content to a file in the OS temporary directory. + * + * @returns VBox status code. + * @param pvBuf Buffer of content to dump. + * @param cbBuf Size (in bytes) of content to dump. + * @param pszFileNmFmt Pointer to the file name format string, @see pg_rt_str_format. + * @param ... The format argument. + */ +static int vgsvcGstCtrlProcessDbgDumpToFileF(const void *pvBuf, size_t cbBuf, const char *pszFileNmFmt, ...) +{ + AssertPtrReturn(pszFileNmFmt, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + + if (!cbBuf) + return VINF_SUCCESS; + + va_list va; + va_start(va, pszFileNmFmt); + + char *pszFileName = NULL; + const int cchFileName = RTStrAPrintfV(&pszFileName, pszFileNmFmt, va); + + va_end(va); + + if (!cchFileName) + return VERR_NO_MEMORY; + + char szPathFileAbs[RTPATH_MAX]; + int rc = RTPathTemp(szPathFileAbs, sizeof(szPathFileAbs)); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szPathFileAbs, sizeof(szPathFileAbs), pszFileName); + + RTStrFree(pszFileName); + + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "Dumping %zu bytes to '%s'\n", cbBuf, szPathFileAbs); + + RTFILE fh; + rc = RTFileOpen(&fh, szPathFileAbs, RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(fh, pvBuf, cbBuf, NULL /* pcbWritten */); + RTFileClose(fh); + } + } + + return rc; +} +#endif /* DEBUG */ + + +/** + * The actual worker routine (loop) for a started guest process. + * + * @return IPRT status code. + * @param pProcess The process we're servicing and monitoring. + */ +static int vgsvcGstCtrlProcessProcessWorker(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + VGSvcVerbose(3, "Thread of process pThread=0x%p = '%s' started\n", pProcess, pProcess->pStartupInfo->pszCmd); + + VGSvcVerbose(3, "Guest process '%s', flags=0x%x\n", pProcess->pStartupInfo->pszCmd, pProcess->pStartupInfo->fFlags); + + int rc = VGSvcGstCtrlSessionProcessAdd(pProcess->pSession, pProcess); + if (RT_FAILURE(rc)) + { + VGSvcError("Error while adding guest process '%s' (%p) to session process list, rc=%Rrc\n", + pProcess->pStartupInfo->pszCmd, pProcess, rc); + RTThreadUserSignal(RTThreadSelf()); + return rc; + } + + bool fSignalled = false; /* Indicator whether we signalled the thread user event already. */ + + /* + * Prepare argument list. + */ + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: fHostFeatures0 = %#x\n", g_fControlHostFeatures0); + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.szCmd = '%s'\n", pProcess->pStartupInfo->pszCmd); + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.uNumArgs = '%RU32'\n", pProcess->pStartupInfo->cArgs); +#ifdef DEBUG /* Never log this stuff in release mode! */ + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.szArgs = '%s'\n", pProcess->pStartupInfo->pszArgs); +#endif + + char **papszArgs; + int cArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */ + rc = RTGetOptArgvFromString(&papszArgs, &cArgs, + pProcess->pStartupInfo->cArgs > 0 ? pProcess->pStartupInfo->pszArgs : "", + RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL); + + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: cArgs = %d\n", cArgs); +#ifdef VBOX_STRICT + for (int i = 0; i < cArgs; i++) + VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: papszArgs[%d] = '%s'\n", i, papszArgs[i] ? papszArgs[i] : "<NULL>"); + + const bool fHasArgv0 = RT_BOOL(g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_PROCESS_ARGV0); RT_NOREF(fHasArgv0); + const int cArgsToCheck = cArgs + (fHasArgv0 ? 0 : 1); + + /* Did we get the same result? + * Take into account that we might not have supplied a (correct) argv[0] from the host. */ + AssertMsg((int)pProcess->pStartupInfo->cArgs == cArgsToCheck, + ("rc=%Rrc, StartupInfo.uNumArgs=%RU32 != cArgsToCheck=%d, cArgs=%d, fHostFeatures0=%#x\n", + rc, pProcess->pStartupInfo->cArgs, cArgsToCheck, cArgs, g_fControlHostFeatures0)); +#endif + + /* + * Create the environment. + */ + uint32_t const cbEnv = pProcess->pStartupInfo->cbEnv; + if (RT_SUCCESS(rc)) + AssertStmt( cbEnv <= GUEST_PROC_MAX_ENV_LEN + || pProcess->pStartupInfo->cEnvVars == 0, + rc = VERR_INVALID_PARAMETER); + if (RT_SUCCESS(rc)) + { + RTENV hEnv; + rc = RTEnvClone(&hEnv, RTENV_DEFAULT); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "Additional environment variables: %RU32 (%RU32 bytes)\n", + pProcess->pStartupInfo->cEnvVars, cbEnv); + + if ( pProcess->pStartupInfo->cEnvVars + && cbEnv > 0) + { + size_t offCur = 0; + while (offCur < cbEnv) + { + const char * const pszCur = &pProcess->pStartupInfo->pszEnv[offCur]; + size_t const cchCur = RTStrNLen(pszCur, cbEnv - offCur); + AssertBreakStmt(cchCur < cbEnv - offCur, rc = VERR_INVALID_PARAMETER); + VGSvcVerbose(3, "Setting environment variable: '%s'\n", pszCur); + rc = RTEnvPutEx(hEnv, pszCur); + if (RT_SUCCESS(rc)) + offCur += cchCur + 1; + else + { + VGSvcError("Setting environment variable '%s' failed: %Rrc\n", pszCur, rc); + break; + } + } + } + + if (RT_SUCCESS(rc)) + { + /* + * Setup the redirection of the standard stuff. + */ + /** @todo consider supporting: gcc stuff.c >file 2>&1. */ + RTHANDLE hStdIn; + PRTHANDLE phStdIn; + rc = vgsvcGstCtrlProcessSetupPipe("|", 0 /*STDIN_FILENO*/, + &hStdIn, &phStdIn, &pProcess->hPipeStdInW); + if (RT_SUCCESS(rc)) + { + RTHANDLE hStdOut; + PRTHANDLE phStdOut; + rc = vgsvcGstCtrlProcessSetupPipe( (pProcess->pStartupInfo->fFlags & GUEST_PROC_CREATE_FLAG_WAIT_STDOUT) + ? "|" : "/dev/null", + 1 /*STDOUT_FILENO*/, + &hStdOut, &phStdOut, &pProcess->hPipeStdOutR); + if (RT_SUCCESS(rc)) + { + RTHANDLE hStdErr; + PRTHANDLE phStdErr; + rc = vgsvcGstCtrlProcessSetupPipe( (pProcess->pStartupInfo->fFlags & GUEST_PROC_CREATE_FLAG_WAIT_STDERR) + ? "|" : "/dev/null", + 2 /*STDERR_FILENO*/, + &hStdErr, &phStdErr, &pProcess->hPipeStdErrR); + if (RT_SUCCESS(rc)) + { + /* + * Create a poll set for the pipes and let the + * transport layer add stuff to it as well. + */ + rc = RTPollSetCreate(&pProcess->hPollSet); + if (RT_SUCCESS(rc)) + { + uint32_t uFlags = RTPOLL_EVT_ERROR; +#if 0 + /* Add reading event to pollset to get some more information. */ + uFlags |= RTPOLL_EVT_READ; +#endif + /* Stdin. */ + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pProcess->hPollSet, + pProcess->hPipeStdInW, RTPOLL_EVT_ERROR, VBOXSERVICECTRLPIPEID_STDIN); + /* Stdout. */ + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pProcess->hPollSet, + pProcess->hPipeStdOutR, uFlags, VBOXSERVICECTRLPIPEID_STDOUT); + /* Stderr. */ + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pProcess->hPollSet, + pProcess->hPipeStdErrR, uFlags, VBOXSERVICECTRLPIPEID_STDERR); + /* IPC notification pipe. */ + if (RT_SUCCESS(rc)) + rc = RTPipeCreate(&pProcess->hNotificationPipeR, &pProcess->hNotificationPipeW, 0 /* Flags */); + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pProcess->hPollSet, + pProcess->hNotificationPipeR, RTPOLL_EVT_READ, VBOXSERVICECTRLPIPEID_IPC_NOTIFY); + if (RT_SUCCESS(rc)) + { + AssertPtr(pProcess->pSession); + bool fNeedsImpersonation = !(pProcess->pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_SPAWN); + + rc = vgsvcGstCtrlProcessCreateProcess(pProcess->pStartupInfo->pszCmd, papszArgs, hEnv, + pProcess->pStartupInfo->fFlags, + phStdIn, phStdOut, phStdErr, + fNeedsImpersonation ? pProcess->pStartupInfo->pszUser : NULL, + fNeedsImpersonation ? pProcess->pStartupInfo->pszPassword : NULL, + fNeedsImpersonation ? pProcess->pStartupInfo->pszDomain : NULL, + &pProcess->hProcess); + if (RT_FAILURE(rc)) + VGSvcError("Error starting process, rc=%Rrc\n", rc); + /* + * Tell the session thread that it can continue + * spawning guest processes. This needs to be done after the new + * process has been started because otherwise signal handling + * on (Open) Solaris does not work correctly (see @bugref{5068}). + */ + int rc2 = RTThreadUserSignal(RTThreadSelf()); + if (RT_SUCCESS(rc)) + rc = rc2; + fSignalled = true; + + if (RT_SUCCESS(rc)) + { + /* + * Close the child ends of any pipes and redirected files. + */ + rc2 = RTHandleClose(phStdIn); AssertRC(rc2); + phStdIn = NULL; + rc2 = RTHandleClose(phStdOut); AssertRC(rc2); + phStdOut = NULL; + rc2 = RTHandleClose(phStdErr); AssertRC(rc2); + phStdErr = NULL; + + /* Enter the process main loop. */ + rc = vgsvcGstCtrlProcessProcLoop(pProcess); + + /* + * The handles that are no longer in the set have + * been closed by the above call in order to prevent + * the guest from getting stuck accessing them. + * So, NIL the handles to avoid closing them again. + */ + if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet, + VBOXSERVICECTRLPIPEID_IPC_NOTIFY, NULL))) + pProcess->hNotificationPipeW = NIL_RTPIPE; + if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet, + VBOXSERVICECTRLPIPEID_STDERR, NULL))) + pProcess->hPipeStdErrR = NIL_RTPIPE; + if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet, + VBOXSERVICECTRLPIPEID_STDOUT, NULL))) + pProcess->hPipeStdOutR = NIL_RTPIPE; + if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet, + VBOXSERVICECTRLPIPEID_STDIN, NULL))) + pProcess->hPipeStdInW = NIL_RTPIPE; + } + } + RTPollSetDestroy(pProcess->hPollSet); + pProcess->hPollSet = NIL_RTPOLLSET; + + RTPipeClose(pProcess->hNotificationPipeR); + pProcess->hNotificationPipeR = NIL_RTPIPE; + RTPipeClose(pProcess->hNotificationPipeW); + pProcess->hNotificationPipeW = NIL_RTPIPE; + } + RTPipeClose(pProcess->hPipeStdErrR); + pProcess->hPipeStdErrR = NIL_RTPIPE; + RTHandleClose(&hStdErr); + if (phStdErr) + RTHandleClose(phStdErr); + } + RTPipeClose(pProcess->hPipeStdOutR); + pProcess->hPipeStdOutR = NIL_RTPIPE; + RTHandleClose(&hStdOut); + if (phStdOut) + RTHandleClose(phStdOut); + } + RTPipeClose(pProcess->hPipeStdInW); + pProcess->hPipeStdInW = NIL_RTPIPE; + RTHandleClose(&hStdIn); + if (phStdIn) + RTHandleClose(phStdIn); + } + } + RTEnvDestroy(hEnv); + } + } + + if (RT_FAILURE(rc)) + { + VBGLR3GUESTCTRLCMDCTX ctx = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ }; + int rc2 = VbglR3GuestCtrlProcCbStatus(&ctx, + pProcess->uPID, PROC_STS_ERROR, rc, + NULL /* pvData */, 0 /* cbData */); + if ( RT_FAILURE(rc2) + && rc2 != VERR_NOT_FOUND) + VGSvcError("[PID %RU32]: Could not report process failure error; rc=%Rrc (process error %Rrc)\n", + pProcess->uPID, rc2, rc); + } + + /* Update stopped status. */ + ASMAtomicWriteBool(&pProcess->fStopped, true); + + if (cArgs) + RTGetOptArgvFree(papszArgs); + + /* + * If something went wrong signal the user event so that others don't wait + * forever on this thread. + */ + if ( RT_FAILURE(rc) + && !fSignalled) + { + RTThreadUserSignal(RTThreadSelf()); + } + + /* Set shut down flag in case we've forgotten it. */ + ASMAtomicWriteBool(&pProcess->fShutdown, true); + + VGSvcVerbose(3, "[PID %RU32]: Thread of process '%s' ended with rc=%Rrc (fSignalled=%RTbool)\n", + pProcess->uPID, pProcess->pStartupInfo->pszCmd, rc, fSignalled); + + return rc; +} + + +static int vgsvcGstCtrlProcessLock(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + int rc = RTCritSectEnter(&pProcess->CritSect); + AssertRC(rc); + return rc; +} + + +/** + * Thread main routine for a started process. + * + * @return IPRT status code. + * @param hThreadSelf The thread handle. + * @param pvUser Pointer to a VBOXSERVICECTRLPROCESS structure. + * + */ +static DECLCALLBACK(int) vgsvcGstCtrlProcessThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF1(hThreadSelf); + PVBOXSERVICECTRLPROCESS pProcess = (PVBOXSERVICECTRLPROCESS)pvUser; + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + return vgsvcGstCtrlProcessProcessWorker(pProcess); +} + + +static int vgsvcGstCtrlProcessUnlock(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + int rc = RTCritSectLeave(&pProcess->CritSect); + AssertRC(rc); + return rc; +} + + +/** + * Executes (starts) a process on the guest. This causes a new thread to be created + * so that this function will not block the overall program execution. + * + * @return IPRT status code. + * @param pSession Guest session. + * @param pStartupInfo Startup info. + * @param uContextID Context ID to associate the process to start with. + */ +int VGSvcGstCtrlProcessStart(const PVBOXSERVICECTRLSESSION pSession, + const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo, uint32_t uContextID) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER); + + /* + * Allocate new thread data and assign it to our thread list. + */ + PVBOXSERVICECTRLPROCESS pProcess = (PVBOXSERVICECTRLPROCESS)RTMemAlloc(sizeof(VBOXSERVICECTRLPROCESS)); + if (!pProcess) + return VERR_NO_MEMORY; + + int rc = vgsvcGstCtrlProcessInit(pProcess, pSession, pStartupInfo, uContextID); + if (RT_SUCCESS(rc)) + { + static uint32_t s_uCtrlExecThread = 0; + rc = RTThreadCreateF(&pProcess->Thread, vgsvcGstCtrlProcessThread, + pProcess /*pvUser*/, 0 /*cbStack*/, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctl%RU32", s_uCtrlExecThread++); + if (RT_FAILURE(rc)) + { + VGSvcError("Creating thread for guest process '%s' failed: rc=%Rrc, pProcess=%p\n", + pStartupInfo->pszCmd, rc, pProcess); + + /* Process has not been added to the session's process list yet, so skip VGSvcGstCtrlSessionProcessRemove() here. */ + VGSvcGstCtrlProcessFree(pProcess); + } + else + { + VGSvcVerbose(4, "Waiting for thread to initialize ...\n"); + + /* Wait for the thread to initialize. */ + rc = RTThreadUserWait(pProcess->Thread, 60 * 1000 /* 60 seconds max. */); + AssertRC(rc); + if ( ASMAtomicReadBool(&pProcess->fShutdown) + || ASMAtomicReadBool(&pProcess->fStopped) + || RT_FAILURE(rc)) + { + VGSvcError("Thread for process '%s' failed to start, rc=%Rrc\n", pStartupInfo->pszCmd, rc); + int rc2 = RTThreadWait(pProcess->Thread, RT_MS_1SEC * 30, NULL); + if (RT_SUCCESS(rc2)) + pProcess->Thread = NIL_RTTHREAD; + + VGSvcGstCtrlSessionProcessRemove(pSession, pProcess); + VGSvcGstCtrlProcessFree(pProcess); + } + else + { + ASMAtomicXchgBool(&pProcess->fStarted, true); + } + } + } + + return rc; +} + + +static DECLCALLBACK(int) vgsvcGstCtrlProcessOnInput(PVBOXSERVICECTRLPROCESS pThis, + const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + bool fPendingClose, void *pvBuf, uint32_t cbBuf) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + int rc; + + size_t cbWritten = 0; + if (pvBuf && cbBuf) + { + if (pThis->hPipeStdInW != NIL_RTPIPE) + rc = RTPipeWrite(pThis->hPipeStdInW, pvBuf, cbBuf, &cbWritten); + else + rc = VINF_EOF; + } + else + rc = VERR_INVALID_PARAMETER; + + /* + * If this is the last write + we have really have written all data + * we need to close the stdin pipe on our end and remove it from + * the poll set. + */ + if ( fPendingClose + && cbBuf == cbWritten) + { + int rc2 = vgsvcGstCtrlProcessPollsetCloseInput(pThis, &pThis->hPipeStdInW); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status to send back to the host. */ + uint32_t fFlags = 0; /* No flags at the moment. */ + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "[PID %RU32]: Written %RU32 bytes input, CID=%RU32, fPendingClose=%RTbool\n", + pThis->uPID, cbWritten, pHostCtx->uContextID, fPendingClose); + uStatus = INPUT_STS_WRITTEN; + } + else + { + if (rc == VERR_BAD_PIPE) + uStatus = INPUT_STS_TERMINATED; + else if (rc == VERR_BUFFER_OVERFLOW) + uStatus = INPUT_STS_OVERFLOW; + /* else undefined */ + } + + /* + * If there was an error and we did not set the host status + * yet, then do it now. + */ + if ( RT_FAILURE(rc) + && uStatus == INPUT_STS_UNDEFINED) + { + uStatus = INPUT_STS_ERROR; + fFlags = rc; /* funny thing to call a "flag"... */ + } + Assert(uStatus > INPUT_STS_UNDEFINED); + + int rc2 = VbglR3GuestCtrlProcCbStatusInput(pHostCtx, pThis->uPID, uStatus, fFlags, (uint32_t)cbWritten); + if (RT_SUCCESS(rc)) + rc = rc2; + +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessOnInput returned with rc=%Rrc\n", pThis->uPID, rc); +#endif + return rc; +} + + +static DECLCALLBACK(int) vgsvcGstCtrlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis, + const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + uint32_t uHandle, uint32_t cbToRead, uint32_t fFlags) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + const PVBOXSERVICECTRLSESSION pSession = pThis->pSession; + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + int rc; + + uint32_t cbBuf = cbToRead; + uint8_t *pvBuf = (uint8_t *)RTMemAlloc(cbBuf); + if (pvBuf) + { + PRTPIPE phPipe = uHandle == GUEST_PROC_OUT_H_STDOUT + ? &pThis->hPipeStdOutR + : &pThis->hPipeStdErrR; + AssertPtr(phPipe); + + size_t cbRead = 0; + if (*phPipe != NIL_RTPIPE) + { + rc = RTPipeRead(*phPipe, pvBuf, cbBuf, &cbRead); + if (RT_FAILURE(rc)) + { + RTPollSetRemove(pThis->hPollSet, uHandle == GUEST_PROC_OUT_H_STDERR + ? VBOXSERVICECTRLPIPEID_STDERR : VBOXSERVICECTRLPIPEID_STDOUT); + RTPipeClose(*phPipe); + *phPipe = NIL_RTPIPE; + if (rc == VERR_BROKEN_PIPE) + rc = VINF_EOF; + } + } + else + rc = VINF_EOF; + +#ifdef DEBUG + if (RT_SUCCESS(rc)) + { + if ( pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT + && ( uHandle == GUEST_PROC_OUT_H_STDOUT + || uHandle == GUEST_PROC_OUT_H_STDOUT_DEPRECATED) + ) + { + rc = vgsvcGstCtrlProcessDbgDumpToFileF(pvBuf, cbRead, "VBoxService_Session%RU32_PID%RU32_StdOut.txt", + pSession->StartupInfo.uSessionID, pThis->uPID); + AssertRC(rc); + } + else if ( pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR + && uHandle == GUEST_PROC_OUT_H_STDERR) + { + rc = vgsvcGstCtrlProcessDbgDumpToFileF(pvBuf, cbRead, "VBoxService_Session%RU32_PID%RU32_StdErr.txt", + pSession->StartupInfo.uSessionID, pThis->uPID); + AssertRC(rc); + } + } +#endif + + if (RT_SUCCESS(rc)) + { +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: Read %RU32 bytes output: uHandle=%RU32, CID=%RU32, fFlags=%x\n", + pThis->uPID, cbRead, uHandle, pHostCtx->uContextID, fFlags); +#endif + /** Note: Don't convert/touch/modify/whatever the output data here! This might be binary + * data which the host needs to work with -- so just pass through all data unfiltered! */ + + /* Note: Since the context ID is unique the request *has* to be completed here, + * regardless whether we got data or not! Otherwise the waiting events + * on the host never will get completed! */ + Assert((uint32_t)cbRead == cbRead); + rc = VbglR3GuestCtrlProcCbOutput(pHostCtx, pThis->uPID, uHandle, fFlags, pvBuf, (uint32_t)cbRead); + if ( RT_FAILURE(rc) + && rc == VERR_NOT_FOUND) /* Not critical if guest PID is not found on the host (anymore). */ + rc = VINF_SUCCESS; + } + + RTMemFree(pvBuf); + } + else + rc = VERR_NO_MEMORY; + +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: Reading output returned with rc=%Rrc\n", pThis->uPID, rc); +#endif + return rc; +} + + +static DECLCALLBACK(int) vgsvcGstCtrlProcessOnTerm(PVBOXSERVICECTRLPROCESS pThis) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + if (!ASMAtomicXchgBool(&pThis->fShutdown, true)) + VGSvcVerbose(3, "[PID %RU32]: Setting shutdown flag ...\n", pThis->uPID); + + return VINF_SUCCESS; +} + + +static int vgsvcGstCtrlProcessRequestExV(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fAsync, + RTMSINTERVAL uTimeoutMS, PFNRT pfnFunction, unsigned cArgs, va_list Args) +{ + RT_NOREF1(pHostCtx); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + /* pHostCtx is optional. */ + AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER); + if (!fAsync) + AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER); + + int rc = vgsvcGstCtrlProcessLock(pProcess); + if (RT_SUCCESS(rc)) + { +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessRequestExV fAsync=%RTbool, uTimeoutMS=%RU32, cArgs=%u\n", + pProcess->uPID, fAsync, uTimeoutMS, cArgs); +#endif + uint32_t fFlags = RTREQFLAGS_IPRT_STATUS; + if (fAsync) + { + Assert(uTimeoutMS == 0); + fFlags |= RTREQFLAGS_NO_WAIT; + } + + PRTREQ hReq = NIL_RTREQ; + rc = RTReqQueueCallV(pProcess->hReqQueue, &hReq, uTimeoutMS, fFlags, pfnFunction, cArgs, Args); + RTReqRelease(hReq); + if (RT_SUCCESS(rc)) + { + /* Wake up the process' notification pipe to get + * the request being processed. */ + Assert(pProcess->hNotificationPipeW != NIL_RTPIPE || pProcess->fShutdown /* latter in case of race */); + size_t cbWritten = 0; + rc = RTPipeWrite(pProcess->hNotificationPipeW, "i", 1, &cbWritten); + if ( RT_SUCCESS(rc) + && cbWritten != 1) + { + VGSvcError("[PID %RU32]: Notification pipe got %zu bytes instead of 1\n", + pProcess->uPID, cbWritten); + } + else if (RT_UNLIKELY(RT_FAILURE(rc))) + VGSvcError("[PID %RU32]: Writing to notification pipe failed, rc=%Rrc\n", + pProcess->uPID, rc); + } + else + VGSvcError("[PID %RU32]: RTReqQueueCallV failed, rc=%Rrc\n", + pProcess->uPID, rc); + + int rc2 = vgsvcGstCtrlProcessUnlock(pProcess); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessRequestExV returned rc=%Rrc\n", pProcess->uPID, rc); +#endif + return rc; +} + + +static int vgsvcGstCtrlProcessRequestAsync(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + PFNRT pfnFunction, unsigned cArgs, ...) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + /* pHostCtx is optional. */ + AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER); + + va_list va; + va_start(va, cArgs); + int rc = vgsvcGstCtrlProcessRequestExV(pProcess, pHostCtx, true /* fAsync */, 0 /* uTimeoutMS */, + pfnFunction, cArgs, va); + va_end(va); + + return rc; +} + + +#if 0 /* unused */ +static int vgsvcGstCtrlProcessRequestWait(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + RTMSINTERVAL uTimeoutMS, PFNRT pfnFunction, unsigned cArgs, ...) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + /* pHostCtx is optional. */ + AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER); + + va_list va; + va_start(va, cArgs); + int rc = vgsvcGstCtrlProcessRequestExV(pProcess, pHostCtx, false /* fAsync */, uTimeoutMS, + pfnFunction, cArgs, va); + va_end(va); + + return rc; +} +#endif + + +int VGSvcGstCtrlProcessHandleInput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + bool fPendingClose, void *pvBuf, uint32_t cbBuf) +{ + if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped)) + return vgsvcGstCtrlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)vgsvcGstCtrlProcessOnInput, + 5 /* cArgs */, pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf); + + return vgsvcGstCtrlProcessOnInput(pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf); +} + + +int VGSvcGstCtrlProcessHandleOutput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + uint32_t uHandle, uint32_t cbToRead, uint32_t fFlags) +{ + if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped)) + return vgsvcGstCtrlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)vgsvcGstCtrlProcessOnOutput, + 5 /* cArgs */, pProcess, pHostCtx, uHandle, cbToRead, fFlags); + + return vgsvcGstCtrlProcessOnOutput(pProcess, pHostCtx, uHandle, cbToRead, fFlags); +} + + +int VGSvcGstCtrlProcessHandleTerm(PVBOXSERVICECTRLPROCESS pProcess) +{ + if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped)) + return vgsvcGstCtrlProcessRequestAsync(pProcess, NULL /* pHostCtx */, (PFNRT)vgsvcGstCtrlProcessOnTerm, + 1 /* cArgs */, pProcess); + + return vgsvcGstCtrlProcessOnTerm(pProcess); +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp new file mode 100644 index 00000000..001d468d --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp @@ -0,0 +1,2886 @@ +/* $Id: VBoxServiceControlSession.cpp $ */ +/** @file + * VBoxServiceControlSession - Guest session handling. Also handles the spawned session processes. + */ + +/* + * Copyright (C) 2013-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/handle.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/rand.h> +#include <iprt/system.h> /* For RTShutdown. */ + +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#include "VBoxServiceControl.h" + +using namespace guestControl; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Generic option indices for session spawn arguments. */ +enum +{ + VBOXSERVICESESSIONOPT_FIRST = 1000, /* For initialization. */ + VBOXSERVICESESSIONOPT_DOMAIN, +#ifdef DEBUG + VBOXSERVICESESSIONOPT_DUMP_STDOUT, + VBOXSERVICESESSIONOPT_DUMP_STDERR, +#endif + VBOXSERVICESESSIONOPT_LOG_FILE, + VBOXSERVICESESSIONOPT_USERNAME, + VBOXSERVICESESSIONOPT_SESSION_ID, + VBOXSERVICESESSIONOPT_SESSION_PROTO, + VBOXSERVICESESSIONOPT_THREAD_ID +}; + + +static int vgsvcGstCtrlSessionCleanupProcesses(const PVBOXSERVICECTRLSESSION pSession); +static int vgsvcGstCtrlSessionProcessRemoveInternal(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess); + + +/** + * Helper that grows the scratch buffer. + * @returns Success indicator. + */ +static bool vgsvcGstCtrlSessionGrowScratchBuf(void **ppvScratchBuf, uint32_t *pcbScratchBuf, uint32_t cbMinBuf) +{ + uint32_t cbNew = *pcbScratchBuf * 2; + if ( cbNew <= VMMDEV_MAX_HGCM_DATA_SIZE + && cbMinBuf <= VMMDEV_MAX_HGCM_DATA_SIZE) + { + while (cbMinBuf > cbNew) + cbNew *= 2; + void *pvNew = RTMemRealloc(*ppvScratchBuf, cbNew); + if (pvNew) + { + *ppvScratchBuf = pvNew; + *pcbScratchBuf = cbNew; + return true; + } + } + return false; +} + + + +static int vgsvcGstCtrlSessionFileFree(PVBOXSERVICECTRLFILE pFile) +{ + AssertPtrReturn(pFile, VERR_INVALID_POINTER); + + int rc = RTFileClose(pFile->hFile); + if (RT_SUCCESS(rc)) + { + RTStrFree(pFile->pszName); + + /* Remove file entry in any case. */ + RTListNodeRemove(&pFile->Node); + /* Destroy this object. */ + RTMemFree(pFile); + } + + return rc; +} + + +/** @todo No locking done yet! */ +static PVBOXSERVICECTRLFILE vgsvcGstCtrlSessionFileGetLocked(const PVBOXSERVICECTRLSESSION pSession, uint32_t uHandle) +{ + AssertPtrReturn(pSession, NULL); + + /** @todo Use a map later! */ + PVBOXSERVICECTRLFILE pFileCur; + RTListForEach(&pSession->lstFiles, pFileCur, VBOXSERVICECTRLFILE, Node) + { + if (pFileCur->uHandle == uHandle) + return pFileCur; + } + + return NULL; +} + + +/** + * Recursion worker for vgsvcGstCtrlSessionHandleDirRemove. + * Only (recursively) removes directory structures which are not empty. Will fail if not empty. + * + * @returns IPRT status code. + * @param pszDir The directory buffer, RTPATH_MAX in length. + * Contains the abs path to the directory to + * recurse into. Trailing slash. + * @param cchDir The length of the directory we're recursing into, + * including the trailing slash. + * @param pDirEntry The dir entry buffer. (Shared to save stack.) + */ +static int vgsvcGstCtrlSessionHandleDirRemoveSub(char *pszDir, size_t cchDir, PRTDIRENTRY pDirEntry) +{ + RTDIR hDir; + int rc = RTDirOpen(&hDir, pszDir); + if (RT_FAILURE(rc)) + { + /* Ignore non-existing directories like RTDirRemoveRecursive does: */ + if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) + return VINF_SUCCESS; + return rc; + } + + for (;;) + { + rc = RTDirRead(hDir, pDirEntry, NULL); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + break; + } + + if (!RTDirEntryIsStdDotLink(pDirEntry)) + { + /* Construct the full name of the entry. */ + if (cchDir + pDirEntry->cbName + 1 /* dir slash */ < RTPATH_MAX) + memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1); + else + { + rc = VERR_FILENAME_TOO_LONG; + break; + } + + /* Make sure we've got the entry type. */ + if (pDirEntry->enmType == RTDIRENTRYTYPE_UNKNOWN) + RTDirQueryUnknownType(pszDir, false /*fFollowSymlinks*/, &pDirEntry->enmType); + + /* Recurse into subdirs and remove them: */ + if (pDirEntry->enmType == RTDIRENTRYTYPE_DIRECTORY) + { + size_t cchSubDir = cchDir + pDirEntry->cbName; + pszDir[cchSubDir++] = RTPATH_SLASH; + pszDir[cchSubDir] = '\0'; + rc = vgsvcGstCtrlSessionHandleDirRemoveSub(pszDir, cchSubDir, pDirEntry); + if (RT_SUCCESS(rc)) + { + pszDir[cchSubDir] = '\0'; + rc = RTDirRemove(pszDir); + if (RT_FAILURE(rc)) + break; + } + else + break; + } + /* Not a subdirectory - fail: */ + else + { + rc = VERR_DIR_NOT_EMPTY; + break; + } + } + } + + RTDirClose(hDir); + return rc; +} + + +static int vgsvcGstCtrlSessionHandleDirRemove(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the message. + */ + char szDir[RTPATH_MAX]; + uint32_t fFlags; /* DIRREMOVE_FLAG_XXX */ + int rc = VbglR3GuestCtrlDirGetRemove(pHostCtx, szDir, sizeof(szDir), &fFlags); + if (RT_SUCCESS(rc)) + { + /* + * Do some validating before executing the job. + */ + if (!(fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK)) + { + if (fFlags & DIRREMOVEREC_FLAG_RECURSIVE) + { + if (fFlags & (DIRREMOVEREC_FLAG_CONTENT_AND_DIR | DIRREMOVEREC_FLAG_CONTENT_ONLY)) + { + uint32_t fFlagsRemRec = fFlags & DIRREMOVEREC_FLAG_CONTENT_AND_DIR + ? RTDIRRMREC_F_CONTENT_AND_DIR : RTDIRRMREC_F_CONTENT_ONLY; + rc = RTDirRemoveRecursive(szDir, fFlagsRemRec); + } + else /* Only remove empty directory structures. Will fail if non-empty. */ + { + RTDIRENTRY DirEntry; + RTPathEnsureTrailingSeparator(szDir, sizeof(szDir)); + rc = vgsvcGstCtrlSessionHandleDirRemoveSub(szDir, strlen(szDir), &DirEntry); + } + VGSvcVerbose(4, "[Dir %s]: rmdir /s (%#x) -> rc=%Rrc\n", szDir, fFlags, rc); + } + else + { + /* Only delete directory if not empty. */ + rc = RTDirRemove(szDir); + VGSvcVerbose(4, "[Dir %s]: rmdir (%#x), rc=%Rrc\n", szDir, fFlags, rc); + } + } + else + { + VGSvcError("[Dir %s]: Unsupported flags: %#x (all %#x)\n", szDir, (fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK), fFlags); + rc = VERR_NOT_SUPPORTED; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc); + if (RT_FAILURE(rc2)) + { + VGSvcError("[Dir %s]: Failed to report removing status, rc=%Rrc\n", szDir, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for rmdir operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + VGSvcVerbose(6, "Removing directory '%s' returned rc=%Rrc\n", szDir, rc); + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileOpen(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the message. + */ + char szFile[RTPATH_MAX]; + char szAccess[64]; + char szDisposition[64]; + char szSharing[64]; + uint32_t uCreationMode = 0; + uint64_t offOpen = 0; + uint32_t uHandle = 0; + int rc = VbglR3GuestCtrlFileGetOpen(pHostCtx, + /* File to open. */ + szFile, sizeof(szFile), + /* Open mode. */ + szAccess, sizeof(szAccess), + /* Disposition. */ + szDisposition, sizeof(szDisposition), + /* Sharing. */ + szSharing, sizeof(szSharing), + /* Creation mode. */ + &uCreationMode, + /* Offset. */ + &offOpen); + VGSvcVerbose(4, "[File %s]: szAccess=%s, szDisposition=%s, szSharing=%s, offOpen=%RU64, rc=%Rrc\n", + szFile, szAccess, szDisposition, szSharing, offOpen, rc); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLFILE pFile = (PVBOXSERVICECTRLFILE)RTMemAllocZ(sizeof(VBOXSERVICECTRLFILE)); + if (pFile) + { + pFile->hFile = NIL_RTFILE; /* Not zero or NULL! */ + if (szFile[0]) + { + pFile->pszName = RTStrDup(szFile); + if (!pFile->pszName) + rc = VERR_NO_MEMORY; +/** @todo + * Implement szSharing! + */ + uint64_t fFlags; + if (RT_SUCCESS(rc)) + { + rc = RTFileModeToFlagsEx(szAccess, szDisposition, NULL /* pszSharing, not used yet */, &fFlags); + VGSvcVerbose(4, "[File %s] Opening with fFlags=%#RX64 -> rc=%Rrc\n", pFile->pszName, fFlags, rc); + } + + if (RT_SUCCESS(rc)) + { + fFlags |= (uCreationMode << RTFILE_O_CREATE_MODE_SHIFT) & RTFILE_O_CREATE_MODE_MASK; + /* If we're opening a file in read-only mode, strip truncation mode. + * rtFileRecalcAndValidateFlags() will validate it anyway, but avoid asserting in debug builds. */ + if (fFlags & RTFILE_O_READ) + fFlags &= ~RTFILE_O_TRUNCATE; + rc = RTFileOpen(&pFile->hFile, pFile->pszName, fFlags); + if (RT_SUCCESS(rc)) + { + RTFSOBJINFO objInfo; + rc = RTFileQueryInfo(pFile->hFile, &objInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(rc)) + { + /* Make sure that we only open stuff we really support. + * Only POSIX / UNIX we could open stuff like directories and sockets as well. */ + if ( RT_LIKELY(RTFS_IS_FILE(objInfo.Attr.fMode)) + || RTFS_IS_SYMLINK(objInfo.Attr.fMode)) + { + /* Seeking is optional. However, the whole operation + * will fail if we don't succeed seeking to the wanted position. */ + if (offOpen) + rc = RTFileSeek(pFile->hFile, (int64_t)offOpen, RTFILE_SEEK_BEGIN, NULL /* Current offset */); + if (RT_SUCCESS(rc)) + { + /* + * Succeeded! + */ + uHandle = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pHostCtx->uContextID); + pFile->uHandle = uHandle; + pFile->fOpen = fFlags; + RTListAppend(&pSession->lstFiles, &pFile->Node); + VGSvcVerbose(2, "[File %s] Opened (ID=%RU32)\n", pFile->pszName, pFile->uHandle); + } + else + VGSvcError("[File %s] Seeking to offset %RU64 failed: rc=%Rrc\n", pFile->pszName, offOpen, rc); + } + else + { + VGSvcError("[File %s] Unsupported mode %#x\n", pFile->pszName, objInfo.Attr.fMode); + rc = VERR_NOT_SUPPORTED; + } + } + else + VGSvcError("[File %s] Getting mode failed with rc=%Rrc\n", pFile->pszName, rc); + } + else + VGSvcError("[File %s] Opening failed with rc=%Rrc\n", pFile->pszName, rc); + } + } + else + { + VGSvcError("[File %s] empty filename!\n", szFile); + rc = VERR_INVALID_NAME; + } + + /* clean up if we failed. */ + if (RT_FAILURE(rc)) + { + RTStrFree(pFile->pszName); + if (pFile->hFile != NIL_RTFILE) + RTFileClose(pFile->hFile); + RTMemFree(pFile); + } + } + else + rc = VERR_NO_MEMORY; + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlFileCbOpen(pHostCtx, rc, uHandle); + if (RT_FAILURE(rc2)) + { + VGSvcError("[File %s]: Failed to report file open status, rc=%Rrc\n", szFile, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for open file operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + VGSvcVerbose(4, "[File %s] Opening (open mode='%s', disposition='%s', creation mode=0x%x) returned rc=%Rrc\n", + szFile, szAccess, szDisposition, uCreationMode, rc); + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileClose(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the message. + */ + uint32_t uHandle = 0; + int rc = VbglR3GuestCtrlFileGetClose(pHostCtx, &uHandle /* File handle to close */); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + VGSvcVerbose(2, "[File %s] Closing (handle=%RU32)\n", pFile ? pFile->pszName : "<Not found>", uHandle); + rc = vgsvcGstCtrlSessionFileFree(pFile); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlFileCbClose(pHostCtx, rc); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file close status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for close file operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileRead(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uHandle = 0; + uint32_t cbToRead; + int rc = VbglR3GuestCtrlFileGetRead(pHostCtx, &uHandle, &cbToRead); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and do the reading. + * + * If the request is larger than our scratch buffer, try grow it - just + * ignore failure as the host better respect our buffer limits. + */ + uint32_t offNew = 0; + size_t cbRead = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + if (*pcbScratchBuf < cbToRead) + vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToRead); + + rc = RTFileRead(pFile->hFile, *ppvScratchBuf, RT_MIN(cbToRead, *pcbScratchBuf), &cbRead); + offNew = (int64_t)RTFileTell(pFile->hFile); + VGSvcVerbose(5, "[File %s] Read %zu/%RU32 bytes, rc=%Rrc, offNew=%RI64\n", pFile->pszName, cbRead, cbToRead, rc, offNew); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result and data back to the host. + */ + int rc2; + if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET) + rc2 = VbglR3GuestCtrlFileCbReadOffset(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead, offNew); + else + rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file read status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file read operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileReadAt(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uHandle = 0; + uint32_t cbToRead; + uint64_t offReadAt; + int rc = VbglR3GuestCtrlFileGetReadAt(pHostCtx, &uHandle, &cbToRead, &offReadAt); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and do the reading. + * + * If the request is larger than our scratch buffer, try grow it - just + * ignore failure as the host better respect our buffer limits. + */ + int64_t offNew = 0; + size_t cbRead = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + if (*pcbScratchBuf < cbToRead) + vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToRead); + + rc = RTFileReadAt(pFile->hFile, (RTFOFF)offReadAt, *ppvScratchBuf, RT_MIN(cbToRead, *pcbScratchBuf), &cbRead); + if (RT_SUCCESS(rc)) + { + offNew = offReadAt + cbRead; + RTFileSeek(pFile->hFile, offNew, RTFILE_SEEK_BEGIN, NULL); /* RTFileReadAt does not always change position. */ + } + else + offNew = (int64_t)RTFileTell(pFile->hFile); + VGSvcVerbose(5, "[File %s] Read %zu bytes @ %RU64, rc=%Rrc, offNew=%RI64\n", pFile->pszName, cbRead, offReadAt, rc, offNew); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result and data back to the host. + */ + int rc2; + if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET) + rc2 = VbglR3GuestCtrlFileCbReadOffset(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead, offNew); + else + rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file read at status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file read at operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileWrite(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request and data to write. + */ + uint32_t uHandle = 0; + uint32_t cbToWrite; + int rc = VbglR3GuestCtrlFileGetWrite(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite); + if ( rc == VERR_BUFFER_OVERFLOW + && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToWrite)) + rc = VbglR3GuestCtrlFileGetWrite(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and do the writing. + */ + int64_t offNew = 0; + size_t cbWritten = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileWrite(pFile->hFile, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), &cbWritten); + offNew = (int64_t)RTFileTell(pFile->hFile); + VGSvcVerbose(5, "[File %s] Writing %p LB %RU32 => %Rrc, cbWritten=%zu, offNew=%RI64\n", + pFile->pszName, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), rc, cbWritten, offNew); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int rc2; + if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET) + rc2 = VbglR3GuestCtrlFileCbWriteOffset(pHostCtx, rc, (uint32_t)cbWritten, offNew); + else + rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file write status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file write operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileWriteAt(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request and data to write. + */ + uint32_t uHandle = 0; + uint32_t cbToWrite; + uint64_t offWriteAt; + int rc = VbglR3GuestCtrlFileGetWriteAt(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite, &offWriteAt); + if ( rc == VERR_BUFFER_OVERFLOW + && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToWrite)) + rc = VbglR3GuestCtrlFileGetWriteAt(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite, &offWriteAt); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and do the writing. + */ + int64_t offNew = 0; + size_t cbWritten = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileWriteAt(pFile->hFile, (RTFOFF)offWriteAt, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), &cbWritten); + if (RT_SUCCESS(rc)) + { + offNew = offWriteAt + cbWritten; + + /* RTFileWriteAt does not always change position: */ + if (!(pFile->fOpen & RTFILE_O_APPEND)) + RTFileSeek(pFile->hFile, offNew, RTFILE_SEEK_BEGIN, NULL); + else + RTFileSeek(pFile->hFile, 0, RTFILE_SEEK_END, (uint64_t *)&offNew); + } + else + offNew = (int64_t)RTFileTell(pFile->hFile); + VGSvcVerbose(5, "[File %s] Writing %p LB %RU32 @ %RU64 => %Rrc, cbWritten=%zu, offNew=%RI64\n", + pFile->pszName, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), offWriteAt, rc, cbWritten, offNew); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int rc2; + if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET) + rc2 = VbglR3GuestCtrlFileCbWriteOffset(pHostCtx, rc, (uint32_t)cbWritten, offNew); + else + rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file write status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file write at operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileSeek(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uHandle = 0; + uint32_t uSeekMethod; + uint64_t offSeek; /* Will be converted to int64_t. */ + int rc = VbglR3GuestCtrlFileGetSeek(pHostCtx, &uHandle, &uSeekMethod, &offSeek); + if (RT_SUCCESS(rc)) + { + uint64_t offActual = 0; + + /* + * Validate and convert the seek method to IPRT speak. + */ + static const uint8_t s_abMethods[GUEST_FILE_SEEKTYPE_END + 1] = + { + UINT8_MAX, RTFILE_SEEK_BEGIN, UINT8_MAX, UINT8_MAX, RTFILE_SEEK_CURRENT, + UINT8_MAX, UINT8_MAX, UINT8_MAX, RTFILE_SEEK_END + }; + if ( uSeekMethod < RT_ELEMENTS(s_abMethods) + && s_abMethods[uSeekMethod] != UINT8_MAX) + { + /* + * Locate the file and do the seek. + */ + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileSeek(pFile->hFile, (int64_t)offSeek, s_abMethods[uSeekMethod], &offActual); + VGSvcVerbose(5, "[File %s]: Seeking to offSeek=%RI64, uSeekMethodIPRT=%u, rc=%Rrc\n", + pFile->pszName, offSeek, s_abMethods[uSeekMethod], rc); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + } + else + { + VGSvcError("Invalid seek method: %#x\n", uSeekMethod); + rc = VERR_NOT_SUPPORTED; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlFileCbSeek(pHostCtx, rc, offActual); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file seek status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file seek operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileTell(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uHandle = 0; + int rc = VbglR3GuestCtrlFileGetTell(pHostCtx, &uHandle); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and ask for the current position. + */ + uint64_t offCurrent = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + offCurrent = RTFileTell(pFile->hFile); + VGSvcVerbose(5, "[File %s]: Telling offCurrent=%RU64\n", pFile->pszName, offCurrent); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlFileCbTell(pHostCtx, rc, offCurrent); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file tell status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file tell operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandleFileSetSize(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uHandle = 0; + uint64_t cbNew = 0; + int rc = VbglR3GuestCtrlFileGetSetSize(pHostCtx, &uHandle, &cbNew); + if (RT_SUCCESS(rc)) + { + /* + * Locate the file and ask for the current position. + */ + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileSetSize(pFile->hFile, cbNew); + VGSvcVerbose(5, "[File %s]: Changing size to %RU64 (%#RX64), rc=%Rrc\n", pFile->pszName, cbNew, cbNew, rc); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + cbNew = UINT64_MAX; + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlFileCbSetSize(pHostCtx, rc, cbNew); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report file tell status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for file tell operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +static int vgsvcGstCtrlSessionHandlePathRename(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + char szSource[RTPATH_MAX]; + char szDest[RTPATH_MAX]; + uint32_t fFlags = 0; /* PATHRENAME_FLAG_XXX */ + int rc = VbglR3GuestCtrlPathGetRename(pHostCtx, szSource, sizeof(szSource), szDest, sizeof(szDest), &fFlags); + if (RT_SUCCESS(rc)) + { + /* + * Validate the flags (kudos for using the same as IPRT), then do the renaming. + */ + AssertCompile(PATHRENAME_FLAG_NO_REPLACE == RTPATHRENAME_FLAGS_NO_REPLACE); + AssertCompile(PATHRENAME_FLAG_REPLACE == RTPATHRENAME_FLAGS_REPLACE); + AssertCompile(PATHRENAME_FLAG_NO_SYMLINKS == RTPATHRENAME_FLAGS_NO_SYMLINKS); + AssertCompile(PATHRENAME_FLAG_VALID_MASK == (RTPATHRENAME_FLAGS_NO_REPLACE | RTPATHRENAME_FLAGS_REPLACE | RTPATHRENAME_FLAGS_NO_SYMLINKS)); + if (!(fFlags & ~PATHRENAME_FLAG_VALID_MASK)) + { + VGSvcVerbose(4, "Renaming '%s' to '%s', fFlags=%#x, rc=%Rrc\n", szSource, szDest, fFlags, rc); + rc = RTPathRename(szSource, szDest, fFlags); + } + else + { + VGSvcError("Invalid rename flags: %#x\n", fFlags); + rc = VERR_NOT_SUPPORTED; + } + + /* + * Report result back to host. + */ + int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report renaming status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for rename operation: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + VGSvcVerbose(5, "Renaming '%s' to '%s' returned rc=%Rrc\n", szSource, szDest, rc); + return rc; +} + + +/** + * Handles getting the user's documents directory. + * + * @returns VBox status code. + * @param pSession Guest session. + * @param pHostCtx Host context. + */ +static int vgsvcGstCtrlSessionHandlePathUserDocuments(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + int rc = VbglR3GuestCtrlPathGetUserDocuments(pHostCtx); + if (RT_SUCCESS(rc)) + { + /* + * Get the path and pass it back to the host.. + */ + char szPath[RTPATH_MAX]; + rc = RTPathUserDocuments(szPath, sizeof(szPath)); +#ifdef DEBUG + VGSvcVerbose(2, "User documents is '%s', rc=%Rrc\n", szPath, rc); +#endif + + int rc2 = VbglR3GuestCtrlMsgReplyEx(pHostCtx, rc, 0 /* Type */, szPath, + RT_SUCCESS(rc) ? (uint32_t)strlen(szPath) + 1 /* Include terminating zero */ : 0); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report user documents, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for user documents path request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +/** + * Handles shutting down / rebooting the guest OS. + * + * @returns VBox status code. + * @param pSession Guest session. + * @param pHostCtx Host context. + */ +static int vgsvcGstCtrlSessionHandleShutdown(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t fAction; + int rc = VbglR3GuestCtrlGetShutdown(pHostCtx, &fAction); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(1, "Host requested to %s system ...\n", (fAction & RTSYSTEM_SHUTDOWN_REBOOT) ? "reboot" : "shutdown"); + + /* Reply first to the host, in order to avoid host hangs when issuing the guest shutdown. */ + rc = VbglR3GuestCtrlMsgReply(pHostCtx, VINF_SUCCESS); + if (RT_FAILURE(rc)) + { + VGSvcError("Failed to reply to shutdown / reboot request, rc=%Rrc\n", rc); + } + else + { + int fSystemShutdown = RTSYSTEM_SHUTDOWN_PLANNED; + + /* Translate SHUTDOWN_FLAG_ into RTSYSTEM_SHUTDOWN_ flags. */ + if (fAction & GUEST_SHUTDOWN_FLAG_REBOOT) + fSystemShutdown |= RTSYSTEM_SHUTDOWN_REBOOT; + else /* SHUTDOWN_FLAG_POWER_OFF */ + fSystemShutdown |= RTSYSTEM_SHUTDOWN_POWER_OFF; + + if (fAction & GUEST_SHUTDOWN_FLAG_FORCE) + fSystemShutdown |= RTSYSTEM_SHUTDOWN_FORCE; + + rc = RTSystemShutdown(0 /*cMsDelay*/, fSystemShutdown, "VBoxService"); + if (RT_FAILURE(rc)) + VGSvcError("%s system failed with %Rrc\n", + (fAction & RTSYSTEM_SHUTDOWN_REBOOT) ? "Rebooting" : "Shutting down", rc); + } + } + else + { + VGSvcError("Error fetching parameters for shutdown / reboot request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + return rc; +} + + +/** + * Handles getting the user's home directory. + * + * @returns VBox status code. + * @param pSession Guest session. + * @param pHostCtx Host context. + */ +static int vgsvcGstCtrlSessionHandlePathUserHome(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + int rc = VbglR3GuestCtrlPathGetUserHome(pHostCtx); + if (RT_SUCCESS(rc)) + { + /* + * Get the path and pass it back to the host.. + */ + char szPath[RTPATH_MAX]; + rc = RTPathUserHome(szPath, sizeof(szPath)); + +#ifdef DEBUG + VGSvcVerbose(2, "User home is '%s', rc=%Rrc\n", szPath, rc); +#endif + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlMsgReplyEx(pHostCtx, rc, 0 /* Type */, szPath, + RT_SUCCESS(rc) ?(uint32_t)strlen(szPath) + 1 /* Include terminating zero */ : 0); + if (RT_FAILURE(rc2)) + { + VGSvcError("Failed to report user home, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + else + { + VGSvcError("Error fetching parameters for user home directory path request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + +/** + * Handles starting a guest processes. + * + * @returns VBox status code. + * @param pSession Guest session. + * @param pHostCtx Host context. + */ +static int vgsvcGstCtrlSessionHandleProcExec(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* Initialize maximum environment block size -- needed as input + * parameter to retrieve the stuff from the host. On output this then + * will contain the actual block size. */ + PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo; + int rc = VbglR3GuestCtrlProcGetStart(pHostCtx, &pStartupInfo); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "Request to start process szCmd=%s, fFlags=0x%x, szArgs=%s, szEnv=%s, uTimeout=%RU32\n", + pStartupInfo->pszCmd, pStartupInfo->fFlags, + pStartupInfo->cArgs ? pStartupInfo->pszArgs : "<None>", + pStartupInfo->cEnvVars ? pStartupInfo->pszEnv : "<None>", + pStartupInfo->uTimeLimitMS); + + bool fStartAllowed = false; /* Flag indicating whether starting a process is allowed or not. */ + rc = VGSvcGstCtrlSessionProcessStartAllowed(pSession, &fStartAllowed); + if (RT_SUCCESS(rc)) + { + vgsvcGstCtrlSessionCleanupProcesses(pSession); + + if (fStartAllowed) + rc = VGSvcGstCtrlProcessStart(pSession, pStartupInfo, pHostCtx->uContextID); + else + rc = VERR_MAX_PROCS_REACHED; /* Maximum number of processes reached. */ + } + + /* We're responsible for signaling errors to the host (it will wait for ever otherwise). */ + if (RT_FAILURE(rc)) + { + VGSvcError("Starting process failed with rc=%Rrc, protocol=%RU32, parameters=%RU32\n", + rc, pHostCtx->uProtocol, pHostCtx->uNumParms); + int rc2 = VbglR3GuestCtrlProcCbStatus(pHostCtx, 0 /*nil-PID*/, PROC_STS_ERROR, rc, NULL /*pvData*/, 0 /*cbData*/); + if (RT_FAILURE(rc2)) + VGSvcError("Error sending start process status to host, rc=%Rrc\n", rc2); + } + + VbglR3GuestCtrlProcStartupInfoFree(pStartupInfo); + pStartupInfo = NULL; + } + else + { + VGSvcError("Failed to retrieve parameters for process start: %Rrc (cParms=%u)\n", rc, pHostCtx->uNumParms); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + return rc; +} + + +/** + * Sends stdin input to a specific guest process. + * + * @returns VBox status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + * @param ppvScratchBuf The scratch buffer, we may grow it. + * @param pcbScratchBuf The scratch buffer size for retrieving the input + * data. + */ +static int vgsvcGstCtrlSessionHandleProcInput(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the data from the host. + */ + uint32_t uPID; + uint32_t fFlags; + uint32_t cbInput; + int rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &fFlags, *ppvScratchBuf, *pcbScratchBuf, &cbInput); + if ( rc == VERR_BUFFER_OVERFLOW + && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbInput)) + rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &fFlags, *ppvScratchBuf, *pcbScratchBuf, &cbInput); + if (RT_SUCCESS(rc)) + { + if (fFlags & GUEST_PROC_IN_FLAG_EOF) + VGSvcVerbose(4, "Got last process input block for PID=%RU32 (%RU32 bytes) ...\n", uPID, cbInput); + + /* + * Locate the process and feed it. + */ + PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = VGSvcGstCtrlProcessHandleInput(pProcess, pHostCtx, RT_BOOL(fFlags & GUEST_PROC_IN_FLAG_EOF), + *ppvScratchBuf, RT_MIN(cbInput, *pcbScratchBuf)); + if (RT_FAILURE(rc)) + VGSvcError("Error handling input message for PID=%RU32, rc=%Rrc\n", uPID, rc); + VGSvcGstCtrlProcessRelease(pProcess); + } + else + { + VGSvcError("Could not find PID %u for feeding %u bytes to it.\n", uPID, cbInput); + rc = VERR_PROCESS_NOT_FOUND; + VbglR3GuestCtrlProcCbStatusInput(pHostCtx, uPID, INPUT_STS_ERROR, rc, 0); + } + } + else + { + VGSvcError("Failed to retrieve parameters for process input: %Rrc (scratch %u bytes)\n", rc, *pcbScratchBuf); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + + VGSvcVerbose(6, "Feeding input to PID=%RU32 resulted in rc=%Rrc\n", uPID, rc); + return rc; +} + + +/** + * Gets stdout/stderr output of a specific guest process. + * + * @returns VBox status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + */ +static int vgsvcGstCtrlSessionHandleProcOutput(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uPID; + uint32_t uHandleID; + uint32_t fFlags; + int rc = VbglR3GuestCtrlProcGetOutput(pHostCtx, &uPID, &uHandleID, &fFlags); +#ifdef DEBUG_andy + VGSvcVerbose(4, "Getting output for PID=%RU32, CID=%RU32, uHandleID=%RU32, fFlags=%RU32\n", + uPID, pHostCtx->uContextID, uHandleID, fFlags); +#endif + if (RT_SUCCESS(rc)) + { + /* + * Locate the process and hand it the output request. + */ + PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = VGSvcGstCtrlProcessHandleOutput(pProcess, pHostCtx, uHandleID, _64K /* cbToRead */, fFlags); + if (RT_FAILURE(rc)) + VGSvcError("Error getting output for PID=%RU32, rc=%Rrc\n", uPID, rc); + VGSvcGstCtrlProcessRelease(pProcess); + } + else + { + VGSvcError("Could not find PID %u for draining handle %u (%#x).\n", uPID, uHandleID, uHandleID); + rc = VERR_PROCESS_NOT_FOUND; +/** @todo r=bird: + * + * No way to report status status code for output requests? + * + */ + } + } + else + { + VGSvcError("Error fetching parameters for process output request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + +#ifdef DEBUG_andy + VGSvcVerbose(4, "Getting output for PID=%RU32 resulted in rc=%Rrc\n", uPID, rc); +#endif + return rc; +} + + +/** + * Tells a guest process to terminate. + * + * @returns VBox status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + */ +static int vgsvcGstCtrlSessionHandleProcTerminate(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uPID; + int rc = VbglR3GuestCtrlProcGetTerminate(pHostCtx, &uPID); + if (RT_SUCCESS(rc)) + { + /* + * Locate the process and terminate it. + */ + PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = VGSvcGstCtrlProcessHandleTerm(pProcess); + if (RT_FAILURE(rc)) + VGSvcError("Error terminating PID=%RU32, rc=%Rrc\n", uPID, rc); + + VGSvcGstCtrlProcessRelease(pProcess); + } + else + { + VGSvcError("Could not find PID %u for termination.\n", uPID); + rc = VERR_PROCESS_NOT_FOUND; + } + } + else + { + VGSvcError("Error fetching parameters for process termination request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } +#ifdef DEBUG_andy + VGSvcVerbose(4, "Terminating PID=%RU32 resulted in rc=%Rrc\n", uPID, rc); +#endif + return rc; +} + + +static int vgsvcGstCtrlSessionHandleProcWaitFor(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + /* + * Retrieve the request. + */ + uint32_t uPID; + uint32_t uWaitFlags; + uint32_t uTimeoutMS; + int rc = VbglR3GuestCtrlProcGetWaitFor(pHostCtx, &uPID, &uWaitFlags, &uTimeoutMS); + if (RT_SUCCESS(rc)) + { + /* + * Locate the process and the realize that this call makes no sense + * since we'll notify the host when a process terminates anyway and + * hopefully don't need any additional encouragement. + */ + PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = VERR_NOT_IMPLEMENTED; /** @todo */ + VGSvcGstCtrlProcessRelease(pProcess); + } + else + rc = VERR_NOT_FOUND; + } + else + { + VGSvcError("Error fetching parameters for process wait request: %Rrc\n", rc); + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX); + } + return rc; +} + + +int VGSvcGstCtrlSessionHandler(PVBOXSERVICECTRLSESSION pSession, uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void **ppvScratchBuf, uint32_t *pcbScratchBuf, volatile bool *pfShutdown) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + AssertPtrReturn(*ppvScratchBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pfShutdown, VERR_INVALID_POINTER); + + + /* + * Only anonymous sessions (that is, sessions which run with local + * service privileges) or spawned session processes can do certain + * operations. + */ + bool const fImpersonated = RT_BOOL(pSession->fFlags & ( VBOXSERVICECTRLSESSION_FLAG_SPAWN + | VBOXSERVICECTRLSESSION_FLAG_ANONYMOUS)); + int rc = VERR_NOT_SUPPORTED; /* Play safe by default. */ + + switch (uMsg) + { + case HOST_MSG_SESSION_CLOSE: + /* Shutdown (this spawn). */ + rc = VGSvcGstCtrlSessionClose(pSession); + *pfShutdown = true; /* Shutdown in any case. */ + break; + + case HOST_MSG_DIR_REMOVE: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleDirRemove(pSession, pHostCtx); + break; + + case HOST_MSG_EXEC_CMD: + rc = vgsvcGstCtrlSessionHandleProcExec(pSession, pHostCtx); + break; + + case HOST_MSG_EXEC_SET_INPUT: + rc = vgsvcGstCtrlSessionHandleProcInput(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf); + break; + + case HOST_MSG_EXEC_GET_OUTPUT: + rc = vgsvcGstCtrlSessionHandleProcOutput(pSession, pHostCtx); + break; + + case HOST_MSG_EXEC_TERMINATE: + rc = vgsvcGstCtrlSessionHandleProcTerminate(pSession, pHostCtx); + break; + + case HOST_MSG_EXEC_WAIT_FOR: + rc = vgsvcGstCtrlSessionHandleProcWaitFor(pSession, pHostCtx); + break; + + case HOST_MSG_FILE_OPEN: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileOpen(pSession, pHostCtx); + break; + + case HOST_MSG_FILE_CLOSE: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileClose(pSession, pHostCtx); + break; + + case HOST_MSG_FILE_READ: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileRead(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf); + break; + + case HOST_MSG_FILE_READ_AT: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileReadAt(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf); + break; + + case HOST_MSG_FILE_WRITE: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileWrite(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf); + break; + + case HOST_MSG_FILE_WRITE_AT: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileWriteAt(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf); + break; + + case HOST_MSG_FILE_SEEK: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileSeek(pSession, pHostCtx); + break; + + case HOST_MSG_FILE_TELL: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileTell(pSession, pHostCtx); + break; + + case HOST_MSG_FILE_SET_SIZE: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandleFileSetSize(pSession, pHostCtx); + break; + + case HOST_MSG_PATH_RENAME: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandlePathRename(pSession, pHostCtx); + break; + + case HOST_MSG_PATH_USER_DOCUMENTS: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandlePathUserDocuments(pSession, pHostCtx); + break; + + case HOST_MSG_PATH_USER_HOME: + if (fImpersonated) + rc = vgsvcGstCtrlSessionHandlePathUserHome(pSession, pHostCtx); + break; + + case HOST_MSG_SHUTDOWN: + rc = vgsvcGstCtrlSessionHandleShutdown(pSession, pHostCtx); + break; + + default: /* Not supported, see next code block. */ + break; + } + if (RT_SUCCESS(rc)) + { /* likely */ } + else if (rc != VERR_NOT_SUPPORTED) /* Note: Reply to host must must be sent by above handler. */ + VGSvcError("Error while handling message (uMsg=%RU32, cParms=%RU32), rc=%Rrc\n", uMsg, pHostCtx->uNumParms, rc); + else + { + /* We must skip and notify host here as best we can... */ + VGSvcVerbose(1, "Unsupported message (uMsg=%RU32, cParms=%RU32) from host, skipping\n", uMsg, pHostCtx->uNumParms); + if (VbglR3GuestCtrlSupportsOptimizations(pHostCtx->uClientID)) + VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, VERR_NOT_SUPPORTED, uMsg); + else + VbglR3GuestCtrlMsgSkipOld(pHostCtx->uClientID); + rc = VINF_SUCCESS; + } + + if (RT_FAILURE(rc)) + VGSvcError("Error while handling message (uMsg=%RU32, cParms=%RU32), rc=%Rrc\n", uMsg, pHostCtx->uNumParms, rc); + + return rc; +} + + +/** + * Thread main routine for a spawned guest session process. + * + * This thread runs in the main executable to control the spawned session process. + * + * @returns VBox status code. + * @param hThreadSelf Thread handle. + * @param pvUser Pointer to a VBOXSERVICECTRLSESSIONTHREAD structure. + * + */ +static DECLCALLBACK(int) vgsvcGstCtrlSessionThread(RTTHREAD hThreadSelf, void *pvUser) +{ + PVBOXSERVICECTRLSESSIONTHREAD pThread = (PVBOXSERVICECTRLSESSIONTHREAD)pvUser; + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + + uint32_t const idSession = pThread->pStartupInfo->uSessionID; + uint32_t const idClient = g_idControlSvcClient; + VGSvcVerbose(3, "Session ID=%RU32 thread running\n", idSession); + + /* Let caller know that we're done initializing, regardless of the result. */ + int rc2 = RTThreadUserSignal(hThreadSelf); + AssertRC(rc2); + + /* + * Wait for the child process to stop or the shutdown flag to be signalled. + */ + RTPROCSTATUS ProcessStatus = { 0, RTPROCEXITREASON_NORMAL }; + bool fProcessAlive = true; + bool fSessionCancelled = VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient); + uint32_t cMsShutdownTimeout = 30 * 1000; /** @todo Make this configurable. Later. */ + uint64_t msShutdownStart = 0; + uint64_t const msStart = RTTimeMilliTS(); + size_t offSecretKey = 0; + int rcWait; + for (;;) + { + /* Secret key feeding. */ + if (offSecretKey < sizeof(pThread->abKey)) + { + size_t cbWritten = 0; + rc2 = RTPipeWrite(pThread->hKeyPipe, &pThread->abKey[offSecretKey], sizeof(pThread->abKey) - offSecretKey, &cbWritten); + if (RT_SUCCESS(rc2)) + offSecretKey += cbWritten; + } + + /* Poll child process status. */ + rcWait = RTProcWaitNoResume(pThread->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus); + if ( rcWait == VINF_SUCCESS + || rcWait == VERR_PROCESS_NOT_FOUND) + { + fProcessAlive = false; + break; + } + AssertMsgBreak(rcWait == VERR_PROCESS_RUNNING || rcWait == VERR_INTERRUPTED, + ("Got unexpected rc=%Rrc while waiting for session process termination\n", rcWait)); + + /* Shutting down? */ + if (ASMAtomicReadBool(&pThread->fShutdown)) + { + if (!msShutdownStart) + { + VGSvcVerbose(3, "Notifying guest session process (PID=%RU32, session ID=%RU32) ...\n", + pThread->hProcess, idSession); + + VBGLR3GUESTCTRLCMDCTX hostCtx = + { + /* .idClient = */ idClient, + /* .idContext = */ VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession), + /* .uProtocol = */ pThread->pStartupInfo->uProtocol, + /* .cParams = */ 2 + }; + rc2 = VbglR3GuestCtrlSessionClose(&hostCtx, 0 /* fFlags */); + if (RT_FAILURE(rc2)) + { + VGSvcError("Unable to notify guest session process (PID=%RU32, session ID=%RU32), rc=%Rrc\n", + pThread->hProcess, idSession, rc2); + + if (rc2 == VERR_NOT_SUPPORTED) + { + /* Terminate guest session process in case it's not supported by a too old host. */ + rc2 = RTProcTerminate(pThread->hProcess); + VGSvcVerbose(3, "Terminating guest session process (PID=%RU32) ended with rc=%Rrc\n", + pThread->hProcess, rc2); + } + break; + } + + VGSvcVerbose(3, "Guest session ID=%RU32 thread was asked to terminate, waiting for session process to exit (%RU32 ms timeout) ...\n", + idSession, cMsShutdownTimeout); + msShutdownStart = RTTimeMilliTS(); + continue; /* Don't waste time on waiting. */ + } + if (RTTimeMilliTS() - msShutdownStart > cMsShutdownTimeout) + { + VGSvcVerbose(3, "Guest session ID=%RU32 process did not shut down within time\n", idSession); + break; + } + } + + /* Cancel the prepared session stuff after 30 seconds. */ + if ( !fSessionCancelled + && RTTimeMilliTS() - msStart >= 30000) + { + VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, idSession); + fSessionCancelled = true; + } + +/** @todo r=bird: This 100ms sleep is _extremely_ sucky! */ + RTThreadSleep(100); /* Wait a bit. */ + } + + if (!fSessionCancelled) + VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, idSession); + + if (!fProcessAlive) + { + VGSvcVerbose(2, "Guest session process (ID=%RU32) terminated with rc=%Rrc, reason=%d, status=%d\n", + idSession, rcWait, ProcessStatus.enmReason, ProcessStatus.iStatus); + if (ProcessStatus.iStatus == RTEXITCODE_INIT) + { + VGSvcError("Guest session process (ID=%RU32) failed to initialize. Here some hints:\n", idSession); + VGSvcError("- Is logging enabled and the output directory is read-only by the guest session user?\n"); + /** @todo Add more here. */ + } + } + + uint32_t uSessionStatus = GUEST_SESSION_NOTIFYTYPE_UNDEFINED; + int32_t iSessionResult = VINF_SUCCESS; + + if (fProcessAlive) + { + for (int i = 0; i < 3; i++) + { + if (i) + RTThreadSleep(3000); + + VGSvcVerbose(2, "Guest session ID=%RU32 process still alive, killing attempt %d/3\n", idSession, i + 1); + + rc2 = RTProcTerminate(pThread->hProcess); + if (RT_SUCCESS(rc2)) + break; + } + + VGSvcVerbose(2, "Guest session ID=%RU32 process termination resulted in rc=%Rrc\n", idSession, rc2); + uSessionStatus = RT_SUCCESS(rc2) ? GUEST_SESSION_NOTIFYTYPE_TOK : GUEST_SESSION_NOTIFYTYPE_TOA; + } + else if (RT_SUCCESS(rcWait)) + { + switch (ProcessStatus.enmReason) + { + case RTPROCEXITREASON_NORMAL: + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN; + iSessionResult = ProcessStatus.iStatus; /* Report back the session's exit code. */ + break; + + case RTPROCEXITREASON_ABEND: + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA; + /* iSessionResult is undefined (0). */ + break; + + case RTPROCEXITREASON_SIGNAL: + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TES; + iSessionResult = ProcessStatus.iStatus; /* Report back the signal number. */ + break; + + default: + AssertMsgFailed(("Unhandled process termination reason (%d)\n", ProcessStatus.enmReason)); + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA; + break; + } + } + else + { + /* If we didn't find the guest process anymore, just assume it terminated normally. */ + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN; + } + + /* Make sure to set stopped state before we let the host know. */ + ASMAtomicWriteBool(&pThread->fStopped, true); + + /* Report final status, regardless if we failed to wait above, so that the host knows what's going on. */ + VGSvcVerbose(3, "Reporting final status %RU32 of session ID=%RU32\n", uSessionStatus, idSession); + Assert(uSessionStatus != GUEST_SESSION_NOTIFYTYPE_UNDEFINED); + + VBGLR3GUESTCTRLCMDCTX ctx = { idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession), + 0 /* uProtocol, unused */, 0 /* uNumParms, unused */ }; + rc2 = VbglR3GuestCtrlSessionNotify(&ctx, uSessionStatus, iSessionResult); + if (RT_FAILURE(rc2)) + VGSvcError("Reporting final status of session ID=%RU32 failed with rc=%Rrc\n", idSession, rc2); + + VGSvcVerbose(3, "Thread for session ID=%RU32 ended with sessionStatus=%#x (%RU32), sessionRc=%#x (%Rrc)\n", + idSession, uSessionStatus, uSessionStatus, iSessionResult, iSessionResult); + + return VINF_SUCCESS; +} + +/** + * Reads the secret key the parent VBoxService instance passed us and pass it + * along as a authentication token to the host service. + * + * For older hosts, this sets up the message filtering. + * + * @returns VBox status code. + * @param idClient The HGCM client ID. + * @param idSession The session ID. + */ +static int vgsvcGstCtrlSessionReadKeyAndAccept(uint32_t idClient, uint32_t idSession) +{ + /* + * Read it. + */ + RTHANDLE Handle; + int rc = RTHandleGetStandard(RTHANDLESTD_INPUT, true /*fLeaveOpen*/, &Handle); + if (RT_SUCCESS(rc)) + { + if (Handle.enmType == RTHANDLETYPE_PIPE) + { + uint8_t abSecretKey[RT_SIZEOFMEMB(VBOXSERVICECTRLSESSIONTHREAD, abKey)]; + rc = RTPipeReadBlocking(Handle.u.hPipe, abSecretKey, sizeof(abSecretKey), NULL); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "Got secret key from standard input.\n"); + + /* + * Do the accepting, if appropriate. + */ + if (g_fControlSupportsOptimizations) + { + rc = VbglR3GuestCtrlSessionAccept(idClient, idSession, abSecretKey, sizeof(abSecretKey)); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Session %u accepted (client ID %u)\n", idClient, idSession); + else + VGSvcError("Failed to accept session %u (client ID %u): %Rrc\n", idClient, idSession, rc); + } + else + { + /* For legacy hosts, we do the filtering thingy. */ + rc = VbglR3GuestCtrlMsgFilterSet(idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession), + VBOX_GUESTCTRL_FILTER_BY_SESSION(idSession), 0); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Session %u filtering successfully enabled\n", idSession); + else + VGSvcError("Failed to set session filter: %Rrc\n", rc); + } + } + else + VGSvcError("Error reading secret key from standard input: %Rrc\n", rc); + } + else + { + VGSvcError("Standard input is not a pipe!\n"); + rc = VERR_INVALID_HANDLE; + } + RTHandleClose(&Handle); + } + else + VGSvcError("RTHandleGetStandard failed on standard input: %Rrc\n", rc); + return rc; +} + +/** + * Invalidates a guest session by updating all it's internal parameters like host features and stuff. + * + * @param pSession Session to invalidate. + * @param idClient Client ID to use. + */ +static void vgsvcGstCtrlSessionInvalidate(PVBOXSERVICECTRLSESSION pSession, uint32_t idClient) +{ + RT_NOREF(pSession); + + VGSvcVerbose(1, "Invalidating session %RU32 (client ID=%RU32)\n", idClient, pSession->StartupInfo.uSessionID); + + int rc2 = VbglR3GuestCtrlQueryFeatures(idClient, &g_fControlHostFeatures0); + if (RT_SUCCESS(rc2)) /* Querying host features is not fatal -- do not use rc here. */ + { + VGSvcVerbose(1, "g_fControlHostFeatures0=%#x\n", g_fControlHostFeatures0); + } + else + VGSvcVerbose(1, "Querying host features failed with %Rrc\n", rc2); +} + +/** + * Main message handler for the guest control session process. + * + * @returns exit code. + * @param pSession Pointer to g_Session. + * @thread main. + */ +static RTEXITCODE vgsvcGstCtrlSessionSpawnWorker(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, RTEXITCODE_FAILURE); + VGSvcVerbose(0, "Hi, this is guest session ID=%RU32\n", pSession->StartupInfo.uSessionID); + + /* + * Connect to the host service. + */ + uint32_t idClient; + int rc = VbglR3GuestCtrlConnect(&idClient); + if (RT_FAILURE(rc)) + return VGSvcError("Error connecting to guest control service, rc=%Rrc\n", rc); + g_fControlSupportsOptimizations = VbglR3GuestCtrlSupportsOptimizations(idClient); + g_idControlSvcClient = idClient; + + VGSvcVerbose(1, "Using client ID=%RU32\n", idClient); + + vgsvcGstCtrlSessionInvalidate(pSession, idClient); + + rc = vgsvcGstCtrlSessionReadKeyAndAccept(idClient, pSession->StartupInfo.uSessionID); + if (RT_SUCCESS(rc)) + { + /* + * Report started status. + * If session status cannot be posted to the host for some reason, bail out. + */ + VBGLR3GUESTCTRLCMDCTX ctx = { idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(pSession->StartupInfo.uSessionID), + 0 /* uProtocol, unused */, 0 /* uNumParms, unused */ }; + rc = VbglR3GuestCtrlSessionNotify(&ctx, GUEST_SESSION_NOTIFYTYPE_STARTED, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + /* + * Allocate a scratch buffer for messages which also send payload data with them. + * This buffer may grow if the host sends us larger chunks of data. + */ + uint32_t cbScratchBuf = _64K; + void *pvScratchBuf = RTMemAlloc(cbScratchBuf); + if (pvScratchBuf) + { + int cFailedMsgPeeks = 0; + + /* + * Message processing loop. + */ + VBGLR3GUESTCTRLCMDCTX CtxHost = { idClient, 0 /* Context ID */, pSession->StartupInfo.uProtocol, 0 }; + for (;;) + { + VGSvcVerbose(3, "Waiting for host msg ...\n"); + uint32_t uMsg = 0; + rc = VbglR3GuestCtrlMsgPeekWait(idClient, &uMsg, &CtxHost.uNumParms, NULL); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "Msg=%RU32 (%RU32 parms) retrieved (%Rrc)\n", uMsg, CtxHost.uNumParms, rc); + + /* + * Pass it on to the session handler. + * Note! Only when handling HOST_SESSION_CLOSE is the rc used. + */ + bool fShutdown = false; + rc = VGSvcGstCtrlSessionHandler(pSession, uMsg, &CtxHost, &pvScratchBuf, &cbScratchBuf, &fShutdown); + if (fShutdown) + break; + + cFailedMsgPeeks = 0; + + /* Let others run (guests are often single CPU) ... */ + RTThreadYield(); + } + /* + * Handle restore notification from host. All the context IDs (sessions, + * files, proceses, etc) are invalidated by a VM restore and must be closed. + */ + else if (rc == VERR_VM_RESTORED) + { + VGSvcVerbose(1, "The VM session ID changed (i.e. restored), closing stale session %RU32\n", + pSession->StartupInfo.uSessionID); + + /* We currently don't serialize guest sessions, guest processes and other guest control objects + * within saved states. So just close this session and report success to the parent process. + * + * Note: Not notifying the host here is intentional, as it wouldn't have any information + * about what to do with it. + */ + rc = VINF_SUCCESS; /* Report success as exit code. */ + break; + } + else + { + VGSvcVerbose(1, "Getting host message failed with %Rrc\n", rc); + + if (cFailedMsgPeeks++ == 3) + break; + + RTThreadSleep(3 * RT_MS_1SEC); + + /** @todo Shouldn't we have a plan for handling connection loss and such? */ + } + } + + /* + * Shutdown. + */ + RTMemFree(pvScratchBuf); + } + else + rc = VERR_NO_MEMORY; + + VGSvcVerbose(0, "Session %RU32 ended\n", pSession->StartupInfo.uSessionID); + } + else + VGSvcError("Reporting session ID=%RU32 started status failed with rc=%Rrc\n", pSession->StartupInfo.uSessionID, rc); + } + else + VGSvcError("Setting message filterAdd=0x%x failed with rc=%Rrc\n", pSession->StartupInfo.uSessionID, rc); + + VGSvcVerbose(3, "Disconnecting client ID=%RU32 ...\n", idClient); + VbglR3GuestCtrlDisconnect(idClient); + g_idControlSvcClient = 0; + + VGSvcVerbose(3, "Session worker returned with rc=%Rrc\n", rc); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Finds a (formerly) started guest process given by its PID and increases its + * reference count. + * + * Must be decreased by the caller with VGSvcGstCtrlProcessRelease(). + * + * @returns Guest process if found, otherwise NULL. + * @param pSession Pointer to guest session where to search process in. + * @param uPID PID to search for. + * + * @note This does *not lock the process! + */ +PVBOXSERVICECTRLPROCESS VGSvcGstCtrlSessionRetainProcess(PVBOXSERVICECTRLSESSION pSession, uint32_t uPID) +{ + AssertPtrReturn(pSession, NULL); + + PVBOXSERVICECTRLPROCESS pProcess = NULL; + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLPROCESS pCurProcess; + RTListForEach(&pSession->lstProcesses, pCurProcess, VBOXSERVICECTRLPROCESS, Node) + { + if (pCurProcess->uPID == uPID) + { + rc = RTCritSectEnter(&pCurProcess->CritSect); + if (RT_SUCCESS(rc)) + { + pCurProcess->cRefs++; + rc = RTCritSectLeave(&pCurProcess->CritSect); + AssertRC(rc); + } + + if (RT_SUCCESS(rc)) + pProcess = pCurProcess; + break; + } + } + + rc = RTCritSectLeave(&pSession->CritSect); + AssertRC(rc); + } + + return pProcess; +} + + +int VGSvcGstCtrlSessionClose(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + VGSvcVerbose(0, "Session %RU32 is about to close ...\n", pSession->StartupInfo.uSessionID); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Close all guest processes. + */ + VGSvcVerbose(0, "Stopping all guest processes ...\n"); + + /* Signal all guest processes in the active list that we want to shutdown. */ + PVBOXSERVICECTRLPROCESS pProcess; + RTListForEach(&pSession->lstProcesses, pProcess, VBOXSERVICECTRLPROCESS, Node) + VGSvcGstCtrlProcessStop(pProcess); + + VGSvcVerbose(1, "%RU32 guest processes were signalled to stop\n", pSession->cProcesses); + + /* Wait for all active threads to shutdown and destroy the active thread list. */ + PVBOXSERVICECTRLPROCESS pProcessNext; + RTListForEachSafe(&pSession->lstProcesses, pProcess, pProcessNext, VBOXSERVICECTRLPROCESS, Node) + { + int rc3 = RTCritSectLeave(&pSession->CritSect); + AssertRC(rc3); + + int rc2 = VGSvcGstCtrlProcessWait(pProcess, 30 * 1000 /* Wait 30 seconds max. */, NULL /* rc */); + + rc3 = RTCritSectEnter(&pSession->CritSect); + AssertRC(rc3); + + if (RT_SUCCESS(rc2)) + { + rc2 = vgsvcGstCtrlSessionProcessRemoveInternal(pSession, pProcess); + if (RT_SUCCESS(rc2)) + { + VGSvcGstCtrlProcessFree(pProcess); + pProcess = NULL; + } + } + } + + AssertMsg(pSession->cProcesses == 0, + ("Session process list still contains %RU32 when it should not\n", pSession->cProcesses)); + AssertMsg(RTListIsEmpty(&pSession->lstProcesses), + ("Session process list is not empty when it should\n")); + + /* + * Close all left guest files. + */ + VGSvcVerbose(0, "Closing all guest files ...\n"); + + PVBOXSERVICECTRLFILE pFile, pFileNext; + RTListForEachSafe(&pSession->lstFiles, pFile, pFileNext, VBOXSERVICECTRLFILE, Node) + { + int rc2 = vgsvcGstCtrlSessionFileFree(pFile); + if (RT_FAILURE(rc2)) + { + VGSvcError("Unable to close file '%s'; rc=%Rrc\n", pFile->pszName, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + /* Keep going. */ + } + + pFile = NULL; /* To make it obvious. */ + } + + AssertMsg(pSession->cFiles == 0, + ("Session file list still contains %RU32 when it should not\n", pSession->cFiles)); + AssertMsg(RTListIsEmpty(&pSession->lstFiles), + ("Session file list is not empty when it should\n")); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + + +int VGSvcGstCtrlSessionDestroy(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + int rc = VGSvcGstCtrlSessionClose(pSession); + + /* Destroy critical section. */ + RTCritSectDelete(&pSession->CritSect); + + return rc; +} + + +int VGSvcGstCtrlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t fFlags) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + RTListInit(&pSession->lstProcesses); + RTListInit(&pSession->lstFiles); + + pSession->cProcesses = 0; + pSession->cFiles = 0; + + pSession->fFlags = fFlags; + + /* Init critical section for protecting the thread lists. */ + int rc = RTCritSectInit(&pSession->CritSect); + AssertRC(rc); + + return rc; +} + + +/** + * Adds a guest process to a session's process list. + * + * @return VBox status code. + * @param pSession Guest session to add process to. + * @param pProcess Guest process to add. + */ +int VGSvcGstCtrlSessionProcessAdd(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "Adding process (PID %RU32) to session ID=%RU32\n", pProcess->uPID, pSession->StartupInfo.uSessionID); + + /* Add process to session list. */ + RTListAppend(&pSession->lstProcesses, &pProcess->Node); + + pSession->cProcesses++; + VGSvcVerbose(3, "Now session ID=%RU32 has %RU32 processes total\n", + pSession->StartupInfo.uSessionID, pSession->cProcesses); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return VINF_SUCCESS; +} + +/** + * Removes a guest process from a session's process list. + * Internal version, does not do locking. + * + * @return VBox status code. + * @param pSession Guest session to remove process from. + * @param pProcess Guest process to remove. + */ +static int vgsvcGstCtrlSessionProcessRemoveInternal(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess) +{ + VGSvcVerbose(3, "Removing process (PID %RU32) from session ID=%RU32\n", pProcess->uPID, pSession->StartupInfo.uSessionID); + AssertReturn(pProcess->cRefs == 0, VERR_WRONG_ORDER); + + RTListNodeRemove(&pProcess->Node); + + AssertReturn(pSession->cProcesses, VERR_WRONG_ORDER); + pSession->cProcesses--; + VGSvcVerbose(3, "Now session ID=%RU32 has %RU32 processes total\n", + pSession->StartupInfo.uSessionID, pSession->cProcesses); + + return VINF_SUCCESS; +} + +/** + * Removes a guest process from a session's process list. + * + * @return VBox status code. + * @param pSession Guest session to remove process from. + * @param pProcess Guest process to remove. + */ +int VGSvcGstCtrlSessionProcessRemove(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + rc = vgsvcGstCtrlSessionProcessRemoveInternal(pSession, pProcess); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + + +/** + * Determines whether starting a new guest process according to the + * maximum number of concurrent guest processes defined is allowed or not. + * + * @return VBox status code. + * @param pSession The guest session. + * @param pfAllowed \c True if starting (another) guest process + * is allowed, \c false if not. + */ +int VGSvcGstCtrlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession, bool *pfAllowed) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pfAllowed, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Check if we're respecting our memory policy by checking + * how many guest processes are started and served already. + */ + bool fLimitReached = false; + if (pSession->uProcsMaxKept) /* If we allow unlimited processes (=0), take a shortcut. */ + { + VGSvcVerbose(3, "Maximum kept guest processes set to %RU32, acurrent=%RU32\n", + pSession->uProcsMaxKept, pSession->cProcesses); + + int32_t iProcsLeft = (pSession->uProcsMaxKept - pSession->cProcesses - 1); + if (iProcsLeft < 0) + { + VGSvcVerbose(3, "Maximum running guest processes reached (%RU32)\n", pSession->uProcsMaxKept); + fLimitReached = true; + } + } + + *pfAllowed = !fLimitReached; + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + + +/** + * Cleans up stopped and no longer used processes. + * + * This will free and remove processes from the session's process list. + * + * @returns VBox status code. + * @param pSession Session to clean up processes for. + */ +static int vgsvcGstCtrlSessionCleanupProcesses(const PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + VGSvcVerbose(3, "Cleaning up stopped processes for session %RU32 ...\n", pSession->StartupInfo.uSessionID); + + int rc2 = RTCritSectEnter(&pSession->CritSect); + AssertRC(rc2); + + int rc = VINF_SUCCESS; + + PVBOXSERVICECTRLPROCESS pCurProcess, pNextProcess; + RTListForEachSafe(&pSession->lstProcesses, pCurProcess, pNextProcess, VBOXSERVICECTRLPROCESS, Node) + { + if (ASMAtomicReadBool(&pCurProcess->fStopped)) + { + rc2 = RTCritSectLeave(&pSession->CritSect); + AssertRC(rc2); + + rc = VGSvcGstCtrlProcessWait(pCurProcess, 30 * 1000 /* Wait 30 seconds max. */, NULL /* rc */); + if (RT_SUCCESS(rc)) + { + VGSvcGstCtrlSessionProcessRemove(pSession, pCurProcess); + VGSvcGstCtrlProcessFree(pCurProcess); + } + + rc2 = RTCritSectEnter(&pSession->CritSect); + AssertRC(rc2); + + /* If failed, try next time we're being called. */ + } + } + + rc2 = RTCritSectLeave(&pSession->CritSect); + AssertRC(rc2); + + if (RT_FAILURE(rc)) + VGSvcError("Cleaning up stopped processes for session %RU32 failed with %Rrc\n", pSession->StartupInfo.uSessionID, rc); + + return rc; +} + + +/** + * Creates the process for a guest session. + * + * @return VBox status code. + * @param pSessionStartupInfo Session startup info. + * @param pSessionThread The session thread under construction. + * @param uCtrlSessionThread The session thread debug ordinal. + */ +static int vgsvcVGSvcGstCtrlSessionThreadCreateProcess(const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo, + PVBOXSERVICECTRLSESSIONTHREAD pSessionThread, uint32_t uCtrlSessionThread) +{ + RT_NOREF(uCtrlSessionThread); + + /* + * Is this an anonymous session? Anonymous sessions run with the same + * privileges as the main VBoxService executable. + */ + bool const fAnonymous = pSessionThread->pStartupInfo->pszUser + && pSessionThread->pStartupInfo->pszUser[0] == '\0'; + if (fAnonymous) + { + Assert(!strlen(pSessionThread->pStartupInfo->pszPassword)); + Assert(!strlen(pSessionThread->pStartupInfo->pszDomain)); + + VGSvcVerbose(3, "New anonymous guest session ID=%RU32 created, fFlags=%x, using protocol %RU32\n", + pSessionStartupInfo->uSessionID, + pSessionStartupInfo->fFlags, + pSessionStartupInfo->uProtocol); + } + else + { + VGSvcVerbose(3, "Spawning new guest session ID=%RU32, szUser=%s, szPassword=%s, szDomain=%s, fFlags=%x, using protocol %RU32\n", + pSessionStartupInfo->uSessionID, + pSessionStartupInfo->pszUser, +#ifdef DEBUG + pSessionStartupInfo->pszPassword, +#else + "XXX", /* Never show passwords in release mode. */ +#endif + pSessionStartupInfo->pszDomain, + pSessionStartupInfo->fFlags, + pSessionStartupInfo->uProtocol); + } + + /* + * Spawn a child process for doing the actual session handling. + * Start by assembling the argument list. + */ + char szExeName[RTPATH_MAX]; + char *pszExeName = RTProcGetExecutablePath(szExeName, sizeof(szExeName)); + AssertPtrReturn(pszExeName, VERR_FILENAME_TOO_LONG); + + char szParmSessionID[32]; + RTStrPrintf(szParmSessionID, sizeof(szParmSessionID), "--session-id=%RU32", pSessionThread->pStartupInfo->uSessionID); + + char szParmSessionProto[32]; + RTStrPrintf(szParmSessionProto, sizeof(szParmSessionProto), "--session-proto=%RU32", + pSessionThread->pStartupInfo->uProtocol); +#ifdef DEBUG + char szParmThreadId[32]; + RTStrPrintf(szParmThreadId, sizeof(szParmThreadId), "--thread-id=%RU32", uCtrlSessionThread); +#endif + unsigned idxArg = 0; /* Next index in argument vector. */ + char const *apszArgs[24]; + + apszArgs[idxArg++] = pszExeName; +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV + apszArgs[idxArg++] = VBOXSERVICE_ARG1_UTF8_ARGV; Assert(idxArg == 2); +#endif + apszArgs[idxArg++] = "guestsession"; + apszArgs[idxArg++] = szParmSessionID; + apszArgs[idxArg++] = szParmSessionProto; +#ifdef DEBUG + apszArgs[idxArg++] = szParmThreadId; +#endif + if (!fAnonymous) /* Do we need to pass a user name? */ + { + apszArgs[idxArg++] = "--user"; + apszArgs[idxArg++] = pSessionThread->pStartupInfo->pszUser; + + if (strlen(pSessionThread->pStartupInfo->pszDomain)) + { + apszArgs[idxArg++] = "--domain"; + apszArgs[idxArg++] = pSessionThread->pStartupInfo->pszDomain; + } + } + + /* Add same verbose flags as parent process. */ + char szParmVerbose[32]; + if (g_cVerbosity > 0) + { + unsigned cVs = RT_MIN(g_cVerbosity, RT_ELEMENTS(szParmVerbose) - 2); + szParmVerbose[0] = '-'; + memset(&szParmVerbose[1], 'v', cVs); + szParmVerbose[1 + cVs] = '\0'; + apszArgs[idxArg++] = szParmVerbose; + } + + /* Add log file handling. Each session will have an own + * log file, naming based on the parent log file. */ + char szParmLogFile[sizeof(g_szLogFile) + 128]; + if (g_szLogFile[0]) + { + const char *pszSuffix = RTPathSuffix(g_szLogFile); + if (!pszSuffix) + pszSuffix = strchr(g_szLogFile, '\0'); + size_t cchBase = pszSuffix - g_szLogFile; + + RTTIMESPEC Now; + RTTimeNow(&Now); + char szTime[64]; + RTTimeSpecToString(&Now, szTime, sizeof(szTime)); + + /* Replace out characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */ + static const RTUNICP s_uszValidRangePairs[] = + { + ' ', ' ', + '(', ')', + '-', '.', + '0', '9', + 'A', 'Z', + 'a', 'z', + '_', '_', + 0xa0, 0xd7af, + '\0' + }; + ssize_t cReplaced = RTStrPurgeComplementSet(szTime, s_uszValidRangePairs, '_' /* chReplacement */); + AssertReturn(cReplaced, VERR_INVALID_UTF8_ENCODING); + +#ifndef DEBUG + RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), "%.*s-%RU32-%s-%s%s", + cchBase, g_szLogFile, pSessionStartupInfo->uSessionID, pSessionStartupInfo->pszUser, szTime, pszSuffix); +#else + RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), "%.*s-%RU32-%RU32-%s-%s%s", + cchBase, g_szLogFile, pSessionStartupInfo->uSessionID, uCtrlSessionThread, + pSessionStartupInfo->pszUser, szTime, pszSuffix); +#endif + apszArgs[idxArg++] = "--logfile"; + apszArgs[idxArg++] = szParmLogFile; + } + +#ifdef DEBUG + if (g_Session.fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT) + apszArgs[idxArg++] = "--dump-stdout"; + if (g_Session.fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR) + apszArgs[idxArg++] = "--dump-stderr"; +#endif + apszArgs[idxArg] = NULL; + Assert(idxArg < RT_ELEMENTS(apszArgs)); + + if (g_cVerbosity > 3) + { + VGSvcVerbose(4, "Spawning parameters:\n"); + for (idxArg = 0; apszArgs[idxArg]; idxArg++) + VGSvcVerbose(4, " %s\n", apszArgs[idxArg]); + } + + /* + * Flags. + */ + uint32_t const fProcCreate = RTPROC_FLAGS_PROFILE +#ifdef RT_OS_WINDOWS + | RTPROC_FLAGS_SERVICE + | RTPROC_FLAGS_HIDDEN +#endif + | VBOXSERVICE_PROC_F_UTF8_ARGV; + + /* + * Configure standard handles. + */ + RTHANDLE hStdIn; + int rc = RTPipeCreate(&hStdIn.u.hPipe, &pSessionThread->hKeyPipe, RTPIPE_C_INHERIT_READ); + if (RT_SUCCESS(rc)) + { + hStdIn.enmType = RTHANDLETYPE_PIPE; + + RTHANDLE hStdOutAndErr; + rc = RTFileOpenBitBucket(&hStdOutAndErr.u.hFile, RTFILE_O_WRITE); + if (RT_SUCCESS(rc)) + { + hStdOutAndErr.enmType = RTHANDLETYPE_FILE; + + /* + * Windows: If a domain name is given, construct an UPN (User Principle Name) + * with the domain name built-in, e.g. "joedoe@example.com". + */ + const char *pszUser = pSessionThread->pStartupInfo->pszUser; +#ifdef RT_OS_WINDOWS + char *pszUserUPN = NULL; + if (pSessionThread->pStartupInfo->pszDomain[0]) + { + int cchbUserUPN = RTStrAPrintf(&pszUserUPN, "%s@%s", + pSessionThread->pStartupInfo->pszUser, + pSessionThread->pStartupInfo->pszDomain); + if (cchbUserUPN > 0) + { + pszUser = pszUserUPN; + VGSvcVerbose(3, "Using UPN: %s\n", pszUserUPN); + } + else + rc = VERR_NO_STR_MEMORY; + } + if (RT_SUCCESS(rc)) +#endif + { + /* + * Finally, create the process. + */ + rc = RTProcCreateEx(pszExeName, apszArgs, RTENV_DEFAULT, fProcCreate, + &hStdIn, &hStdOutAndErr, &hStdOutAndErr, + !fAnonymous ? pszUser : NULL, + !fAnonymous ? pSessionThread->pStartupInfo->pszPassword : NULL, + NULL /*pvExtraData*/, + &pSessionThread->hProcess); + } +#ifdef RT_OS_WINDOWS + RTStrFree(pszUserUPN); +#endif + RTFileClose(hStdOutAndErr.u.hFile); + } + + RTPipeClose(hStdIn.u.hPipe); + } + return rc; +} + + +/** + * Creates a guest session. + * + * This will spawn a new VBoxService.exe instance under behalf of the given user + * which then will act as a session host. On successful open, the session will + * be added to the given session thread list. + * + * @return VBox status code. + * @param pList Which list to use to store the session thread in. + * @param pSessionStartupInfo Session startup info. + * @param ppSessionThread Returns newly created session thread on success. + * Optional. + */ +int VGSvcGstCtrlSessionThreadCreate(PRTLISTANCHOR pList, const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo, + PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread) +{ + AssertPtrReturn(pList, VERR_INVALID_POINTER); + AssertPtrReturn(pSessionStartupInfo, VERR_INVALID_POINTER); + /* ppSessionThread is optional. */ + +#ifdef VBOX_STRICT + /* Check for existing session in debug mode. Should never happen because of + * Main consistency. */ + PVBOXSERVICECTRLSESSIONTHREAD pSessionCur; + RTListForEach(pList, pSessionCur, VBOXSERVICECTRLSESSIONTHREAD, Node) + { + AssertMsgReturn( pSessionCur->fStopped == true + || pSessionCur->pStartupInfo->uSessionID != pSessionStartupInfo->uSessionID, + ("Guest session thread ID=%RU32 already exists (fStopped=%RTbool)\n", + pSessionCur->pStartupInfo->uSessionID, pSessionCur->fStopped), VERR_ALREADY_EXISTS); + } +#endif + + /* Static counter to help tracking session thread <-> process relations. */ + static uint32_t s_uCtrlSessionThread = 0; + + /* + * Allocate and initialize the session thread structure. + */ + int rc; + PVBOXSERVICECTRLSESSIONTHREAD pSessionThread = (PVBOXSERVICECTRLSESSIONTHREAD)RTMemAllocZ(sizeof(*pSessionThread)); + if (pSessionThread) + { + //pSessionThread->fShutdown = false; + //pSessionThread->fStarted = false; + //pSessionThread->fStopped = false; + pSessionThread->hKeyPipe = NIL_RTPIPE; + pSessionThread->Thread = NIL_RTTHREAD; + pSessionThread->hProcess = NIL_RTPROCESS; + + /* Duplicate startup info. */ + pSessionThread->pStartupInfo = VbglR3GuestCtrlSessionStartupInfoDup(pSessionStartupInfo); + AssertPtrReturn(pSessionThread->pStartupInfo, VERR_NO_MEMORY); + + /* Generate the secret key. */ + RTRandBytes(pSessionThread->abKey, sizeof(pSessionThread->abKey)); + + rc = RTCritSectInit(&pSessionThread->CritSect); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + /* + * Give the session key to the host so it can validate the client. + */ + if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient)) + { + for (uint32_t i = 0; i < 10; i++) + { + rc = VbglR3GuestCtrlSessionPrepare(g_idControlSvcClient, pSessionStartupInfo->uSessionID, + pSessionThread->abKey, sizeof(pSessionThread->abKey)); + if (rc != VERR_OUT_OF_RESOURCES) + break; + RTThreadSleep(100); + } + } + if (RT_SUCCESS(rc)) + { + s_uCtrlSessionThread++; + + /* + * Start the session child process. + */ + rc = vgsvcVGSvcGstCtrlSessionThreadCreateProcess(pSessionStartupInfo, pSessionThread, s_uCtrlSessionThread); + if (RT_SUCCESS(rc)) + { + /* + * Start the session thread. + */ + rc = RTThreadCreateF(&pSessionThread->Thread, vgsvcGstCtrlSessionThread, pSessionThread /*pvUser*/, 0 /*cbStack*/, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctls%RU32", s_uCtrlSessionThread); + if (RT_SUCCESS(rc)) + { + /* Wait for the thread to initialize. */ + rc = RTThreadUserWait(pSessionThread->Thread, RT_MS_1MIN); + if ( RT_SUCCESS(rc) + && !ASMAtomicReadBool(&pSessionThread->fShutdown)) + { + VGSvcVerbose(2, "Thread for session ID=%RU32 started\n", pSessionThread->pStartupInfo->uSessionID); + + ASMAtomicXchgBool(&pSessionThread->fStarted, true); + + /* Add session to list. */ + RTListAppend(pList, &pSessionThread->Node); + if (ppSessionThread) /* Return session if wanted. */ + *ppSessionThread = pSessionThread; + return VINF_SUCCESS; + } + + /* + * Bail out. + */ + VGSvcError("Thread for session ID=%RU32 failed to start, rc=%Rrc\n", + pSessionThread->pStartupInfo->uSessionID, rc); + if (RT_SUCCESS_NP(rc)) + rc = VERR_CANT_CREATE; /** @todo Find a better rc. */ + } + else + VGSvcError("Creating session thread failed, rc=%Rrc\n", rc); + + RTProcTerminate(pSessionThread->hProcess); + uint32_t cMsWait = 1; + while ( RTProcWait(pSessionThread->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, NULL) == VERR_PROCESS_RUNNING + && cMsWait <= 9) /* 1023 ms */ + { + RTThreadSleep(cMsWait); + cMsWait <<= 1; + } + } + + if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient)) + VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, pSessionStartupInfo->uSessionID); + } + else + VGSvcVerbose(3, "VbglR3GuestCtrlSessionPrepare failed: %Rrc\n", rc); + RTPipeClose(pSessionThread->hKeyPipe); + pSessionThread->hKeyPipe = NIL_RTPIPE; + RTCritSectDelete(&pSessionThread->CritSect); + } + RTMemFree(pSessionThread); + } + else + rc = VERR_NO_MEMORY; + + VGSvcVerbose(3, "Spawning session thread returned returned rc=%Rrc\n", rc); + return rc; +} + + +/** + * Waits for a formerly opened guest session process to close. + * + * @return VBox status code. + * @param pThread Guest session thread to wait for. + * @param uTimeoutMS Waiting timeout (in ms). + * @param fFlags Closing flags. + */ +int VGSvcGstCtrlSessionThreadWait(PVBOXSERVICECTRLSESSIONTHREAD pThread, uint32_t uTimeoutMS, uint32_t fFlags) +{ + RT_NOREF(fFlags); + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + /** @todo Validate closing flags. */ + + AssertMsgReturn(pThread->Thread != NIL_RTTHREAD, + ("Guest session thread of session %p does not exist when it should\n", pThread), + VERR_NOT_FOUND); + + int rc = VINF_SUCCESS; + + /* + * The spawned session process should have received the same closing request, + * so just wait for the process to close. + */ + if (ASMAtomicReadBool(&pThread->fStarted)) + { + /* Ask the thread to shutdown. */ + ASMAtomicXchgBool(&pThread->fShutdown, true); + + VGSvcVerbose(3, "Waiting for session thread ID=%RU32 to close (%RU32ms) ...\n", + pThread->pStartupInfo->uSessionID, uTimeoutMS); + + int rcThread; + rc = RTThreadWait(pThread->Thread, uTimeoutMS, &rcThread); + if (RT_SUCCESS(rc)) + { + AssertMsg(pThread->fStopped, ("Thread of session ID=%RU32 not in stopped state when it should\n", + pThread->pStartupInfo->uSessionID)); + + VGSvcVerbose(3, "Session thread ID=%RU32 ended with rc=%Rrc\n", pThread->pStartupInfo->uSessionID, rcThread); + } + else + VGSvcError("Waiting for session thread ID=%RU32 to close failed with rc=%Rrc\n", pThread->pStartupInfo->uSessionID, rc); + } + else + VGSvcVerbose(3, "Thread for session ID=%RU32 not in started state, skipping wait\n", pThread->pStartupInfo->uSessionID); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Waits for the specified session thread to end and remove + * it from the session thread list. + * + * @return VBox status code. + * @param pThread Session thread to destroy. + * @param fFlags Closing flags. + */ +int VGSvcGstCtrlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pThread, uint32_t fFlags) +{ + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + AssertPtrReturn(pThread->pStartupInfo, VERR_WRONG_ORDER); + + const uint32_t uSessionID = pThread->pStartupInfo->uSessionID; + + VGSvcVerbose(3, "Destroying session ID=%RU32 ...\n", uSessionID); + + int rc = VGSvcGstCtrlSessionThreadWait(pThread, 5 * 60 * 1000 /* 5 minutes timeout */, fFlags); + if (RT_SUCCESS(rc)) + { + VbglR3GuestCtrlSessionStartupInfoFree(pThread->pStartupInfo); + pThread->pStartupInfo = NULL; + + RTPipeClose(pThread->hKeyPipe); + pThread->hKeyPipe = NIL_RTPIPE; + + RTCritSectDelete(&pThread->CritSect); + + /* Remove session from list and destroy object. */ + RTListNodeRemove(&pThread->Node); + + RTMemFree(pThread); + pThread = NULL; + } + + VGSvcVerbose(3, "Destroyed session ID=%RU32 with %Rrc\n", uSessionID, rc); + return rc; +} + +/** + * Close all open guest session threads. + * + * @note Caller is responsible for locking! + * + * @return VBox status code. + * @param pList Which list to close the session threads for. + * @param fFlags Closing flags. + */ +int VGSvcGstCtrlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t fFlags) +{ + AssertPtrReturn(pList, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + /*int rc = VbglR3GuestCtrlClose + if (RT_FAILURE(rc)) + VGSvcError("Cancelling pending waits failed; rc=%Rrc\n", rc);*/ + + PVBOXSERVICECTRLSESSIONTHREAD pSessIt; + PVBOXSERVICECTRLSESSIONTHREAD pSessItNext; + RTListForEachSafe(pList, pSessIt, pSessItNext, VBOXSERVICECTRLSESSIONTHREAD, Node) + { + int rc2 = VGSvcGstCtrlSessionThreadDestroy(pSessIt, fFlags); + if (RT_FAILURE(rc2)) + { + VGSvcError("Closing session thread '%s' failed with rc=%Rrc\n", RTThreadGetName(pSessIt->Thread), rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + /* Keep going. */ + } + } + + VGSvcVerbose(4, "Destroying guest session threads ended with %Rrc\n", rc); + return rc; +} + + +/** + * Main function for the session process. + * + * @returns exit code. + * @param argc Argument count. + * @param argv Argument vector (UTF-8). + */ +RTEXITCODE VGSvcGstCtrlSessionSpawnInit(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--domain", VBOXSERVICESESSIONOPT_DOMAIN, RTGETOPT_REQ_STRING }, +#ifdef DEBUG + { "--dump-stdout", VBOXSERVICESESSIONOPT_DUMP_STDOUT, RTGETOPT_REQ_NOTHING }, + { "--dump-stderr", VBOXSERVICESESSIONOPT_DUMP_STDERR, RTGETOPT_REQ_NOTHING }, +#endif + { "--logfile", VBOXSERVICESESSIONOPT_LOG_FILE, RTGETOPT_REQ_STRING }, + { "--user", VBOXSERVICESESSIONOPT_USERNAME, RTGETOPT_REQ_STRING }, + { "--session-id", VBOXSERVICESESSIONOPT_SESSION_ID, RTGETOPT_REQ_UINT32 }, + { "--session-proto", VBOXSERVICESESSIONOPT_SESSION_PROTO, RTGETOPT_REQ_UINT32 }, +#ifdef DEBUG + { "--thread-id", VBOXSERVICESESSIONOPT_THREAD_ID, RTGETOPT_REQ_UINT32 }, +#endif /* DEBUG */ + { "--verbose", 'v', RTGETOPT_REQ_NOTHING } + }; + + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, + s_aOptions, RT_ELEMENTS(s_aOptions), + 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + uint32_t fSession = VBOXSERVICECTRLSESSION_FLAG_SPAWN; + + /* Protocol and session ID must be specified explicitly. */ + g_Session.StartupInfo.uProtocol = UINT32_MAX; + g_Session.StartupInfo.uSessionID = UINT32_MAX; + + int ch; + RTGETOPTUNION ValueUnion; + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case VBOXSERVICESESSIONOPT_DOMAIN: + /* Information not needed right now, skip. */ + break; +#ifdef DEBUG + case VBOXSERVICESESSIONOPT_DUMP_STDOUT: + fSession |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT; + break; + + case VBOXSERVICESESSIONOPT_DUMP_STDERR: + fSession |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR; + break; +#endif + case VBOXSERVICESESSIONOPT_SESSION_ID: + g_Session.StartupInfo.uSessionID = ValueUnion.u32; + break; + + case VBOXSERVICESESSIONOPT_SESSION_PROTO: + g_Session.StartupInfo.uProtocol = ValueUnion.u32; + break; +#ifdef DEBUG + case VBOXSERVICESESSIONOPT_THREAD_ID: + /* Not handled. Mainly for processs listing. */ + break; +#endif + case VBOXSERVICESESSIONOPT_LOG_FILE: + { + int rc = RTStrCopy(g_szLogFile, sizeof(g_szLogFile), ValueUnion.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error copying log file name: %Rrc", rc); + break; + } + + case VBOXSERVICESESSIONOPT_USERNAME: + /* Information not needed right now, skip. */ + break; + + /** @todo Implement help? */ + + case 'v': + g_cVerbosity++; + break; + + case VINF_GETOPT_NOT_OPTION: + { + if (!RTStrICmp(ValueUnion.psz, VBOXSERVICECTRLSESSION_GETOPT_PREFIX)) + break; + /* else fall through and bail out. */ + RT_FALL_THROUGH(); + } + default: + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown argument '%s'", ValueUnion.psz); + } + } + + /* Check that we've got all the required options. */ + if (g_Session.StartupInfo.uProtocol == UINT32_MAX) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No protocol version specified"); + + if (g_Session.StartupInfo.uSessionID == UINT32_MAX) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No session ID specified"); + + /* Init the session object. */ + int rc = VGSvcGstCtrlSessionInit(&g_Session, fSession); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_INIT, "Failed to initialize session object, rc=%Rrc\n", rc); + + rc = VGSvcLogCreate(g_szLogFile[0] ? g_szLogFile : NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_INIT, "Failed to create log file '%s', rc=%Rrc\n", + g_szLogFile[0] ? g_szLogFile : "<None>", rc); + + RTEXITCODE rcExit = vgsvcGstCtrlSessionSpawnWorker(&g_Session); + + VGSvcLogDestroy(); + return rcExit; +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp new file mode 100644 index 00000000..d1aa77cc --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp @@ -0,0 +1,672 @@ +/* $Id: VBoxServiceCpuHotPlug.cpp $ */ +/** @file + * VBoxService - Guest Additions CPU Hot-Plugging Service. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_vgsvc_cpuhotplug VBoxService - CPU Hot-Plugging + * + * The CPU Hot-Plugging subservice helps execute and coordinate CPU hot-plugging + * between the guest OS and the VMM. + * + * CPU Hot-Plugging is useful for reallocating CPU resources from one VM to + * other VMs or/and the host. It talks to the VMM via VMMDev, new hot-plugging + * events being signalled with an interrupt (no polling). + * + * Currently only supported for linux guests. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" + +#ifdef RT_OS_LINUX +# include <iprt/linux/sysfs.h> +# include <errno.h> /* For the sysfs API */ +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifdef RT_OS_LINUX + +/** @name Paths to access the CPU device + * @{ + */ +# define SYSFS_ACPI_CPU_PATH "/sys/devices" +# define SYSFS_CPU_PATH "/sys/devices/system/cpu" +/** @} */ + +/** Path component for the ACPI CPU path. */ +typedef struct SYSFSCPUPATHCOMP +{ + /** Flag whether the name is suffixed with a number */ + bool fNumberedSuffix; + /** Name of the component */ + const char *pcszName; +} SYSFSCPUPATHCOMP, *PSYSFSCPUPATHCOMP; +/** Pointer to a const component. */ +typedef const SYSFSCPUPATHCOMP *PCSYSFSCPUPATHCOMP; + +/** + * Structure which defines how the entries are assembled. + */ +typedef struct SYSFSCPUPATH +{ + /** Id when probing for the correct path. */ + uint32_t uId; + /** Array holding the possible components. */ + PCSYSFSCPUPATHCOMP aComponentsPossible; + /** Number of entries in the array, excluding the terminator. */ + unsigned cComponents; + /** Directory handle */ + RTDIR hDir; + /** Current directory to try. */ + char *pszPath; +} SYSFSCPUPATH, *PSYSFSCPUPATH; + +/** Content of uId if the path wasn't probed yet. */ +# define ACPI_CPU_PATH_NOT_PROBED UINT32_MAX +#endif /* RT_OS_LINUX*/ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef RT_OS_LINUX +/** Possible combinations of all path components for level 1. */ +static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl1[] = +{ + /** LNXSYSTEM:<id> */ + { true, "LNXSYSTM:*" } +}; + +/** Possible combinations of all path components for level 2. */ +static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl2[] = +{ + /** device:<id> */ + { true, "device:*" }, + /** LNXSYBUS:<id> */ + { true, "LNXSYBUS:*" } +}; + +/** Possible combinations of all path components for level 3 */ +static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl3[] = +{ + /** ACPI0004:<id> */ + { true, "ACPI0004:*" } +}; + +/** Possible combinations of all path components for level 4 */ +static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl4[] = +{ + /** LNXCPU:<id> */ + { true, "LNXCPU:*" }, + /** ACPI_CPU:<id> */ + { true, "ACPI_CPU:*" } +}; + +/** All possible combinations. */ +static SYSFSCPUPATH g_aAcpiCpuPath[] = +{ + /** Level 1 */ + { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl1, RT_ELEMENTS(g_aAcpiCpuPathLvl1), NULL, NULL }, + /** Level 2 */ + { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl2, RT_ELEMENTS(g_aAcpiCpuPathLvl2), NULL, NULL }, + /** Level 3 */ + { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl3, RT_ELEMENTS(g_aAcpiCpuPathLvl3), NULL, NULL }, + /** Level 4 */ + { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl4, RT_ELEMENTS(g_aAcpiCpuPathLvl4), NULL, NULL }, +}; + +/** + * Possible directories to get to the topology directory for reading core and package id. + * + * @remark: This is not part of the path above because the eject file is not in one of the directories + * below and would make the hot unplug code fail. + */ +static const char *g_apszTopologyPath[] = +{ + "sysdev", + "physical_node" +}; + +#endif /* RT_OS_LINUX*/ + + +#ifdef RT_OS_LINUX + +/** + * Probes for the correct path to the ACPI CPU object in sysfs for the + * various different kernel versions and distro's. + * + * @returns VBox status code. + */ +static int vgsvcCpuHotPlugProbePath(void) +{ + int rc = VINF_SUCCESS; + + /* Probe for the correct path if we didn't already. */ + if (RT_UNLIKELY(g_aAcpiCpuPath[0].uId == ACPI_CPU_PATH_NOT_PROBED)) + { + char *pszPath = NULL; /** < Current path, increasing while we dig deeper. */ + + pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH); + if (!pszPath) + return VERR_NO_MEMORY; + + /* + * Simple algorithm to find the path. + * Performance is not a real problem because it is + * only executed once. + */ + for (unsigned iLvlCurr = 0; iLvlCurr < RT_ELEMENTS(g_aAcpiCpuPath); iLvlCurr++) + { + PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr]; + + for (unsigned iCompCurr = 0; iCompCurr < pAcpiCpuPathLvl->cComponents; iCompCurr++) + { + PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[iCompCurr]; + + /* Open the directory */ + RTDIR hDirCurr = NIL_RTDIR; + char *pszPathTmp = RTPathJoinA(pszPath, pPathComponent->pcszName); + if (pszPathTmp) + { + rc = RTDirOpenFiltered(&hDirCurr, pszPathTmp, RTDIRFILTER_WINNT, 0 /*fFlags*/); + RTStrFree(pszPathTmp); + } + else + rc = VERR_NO_STR_MEMORY; + if (RT_FAILURE(rc)) + break; + + /* Search if the current directory contains one of the possible parts. */ + size_t cchName = strlen(pPathComponent->pcszName); + RTDIRENTRY DirFolderContent; + bool fFound = false; + + /* Get rid of the * filter which is in the path component. */ + if (pPathComponent->fNumberedSuffix) + cchName--; + + while (RT_SUCCESS(RTDirRead(hDirCurr, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */ + { + if ( DirFolderContent.cbName >= cchName + && !strncmp(DirFolderContent.szName, pPathComponent->pcszName, cchName)) + { + /* Found, use the complete name to dig deeper. */ + fFound = true; + pAcpiCpuPathLvl->uId = iCompCurr; + char *pszPathLvl = RTPathJoinA(pszPath, DirFolderContent.szName); + if (pszPathLvl) + { + RTStrFree(pszPath); + pszPath = pszPathLvl; + } + else + rc = VERR_NO_STR_MEMORY; + break; + } + } + RTDirClose(hDirCurr); + + if (fFound) + break; + } /* For every possible component. */ + + /* No matching component for this part, no need to continue */ + if (RT_FAILURE(rc)) + break; + } /* For every level */ + + VGSvcVerbose(1, "Final path after probing %s rc=%Rrc\n", pszPath, rc); + RTStrFree(pszPath); + } + + return rc; +} + + +/** + * Returns the path of the ACPI CPU device with the given core and package ID. + * + * @returns VBox status code. + * @param ppszPath Where to store the path. + * @param idCpuCore The core ID of the CPU. + * @param idCpuPackage The package ID of the CPU. + */ +static int vgsvcCpuHotPlugGetACPIDevicePath(char **ppszPath, uint32_t idCpuCore, uint32_t idCpuPackage) +{ + int rc = VINF_SUCCESS; + + AssertPtrReturn(ppszPath, VERR_INVALID_PARAMETER); + + rc = vgsvcCpuHotPlugProbePath(); + if (RT_SUCCESS(rc)) + { + /* Build the path from all components. */ + bool fFound = false; + unsigned iLvlCurr = 0; + char *pszPath = NULL; + char *pszPathDir = NULL; + PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr]; + + /* Init everything. */ + Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED); + pszPath = RTPathJoinA(SYSFS_ACPI_CPU_PATH, pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId].pcszName); + if (!pszPath) + return VERR_NO_STR_MEMORY; + + pAcpiCpuPathLvl->pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH); + if (!pAcpiCpuPathLvl->pszPath) + { + RTStrFree(pszPath); + return VERR_NO_STR_MEMORY; + } + + /* Open the directory */ + rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->hDir, pszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + RTStrFree(pszPath); + pszPath = NULL; + + /* Search for CPU */ + while (!fFound) + { + /* Get the next directory. */ + RTDIRENTRY DirFolderContent; + rc = RTDirRead(pAcpiCpuPathLvl->hDir, &DirFolderContent, NULL); + if (RT_SUCCESS(rc)) + { + /* Create the new path. */ + char *pszPathCurr = RTPathJoinA(pAcpiCpuPathLvl->pszPath, DirFolderContent.szName); + if (!pszPathCurr) + { + rc = VERR_NO_STR_MEMORY; + break; + } + + /* If this is the last level check for the given core and package id. */ + if (iLvlCurr == RT_ELEMENTS(g_aAcpiCpuPath) - 1) + { + /* Get the sysdev */ + uint32_t idCore = 0; + uint32_t idPackage = 0; + + for (unsigned i = 0; i < RT_ELEMENTS(g_apszTopologyPath); i++) + { + int64_t i64Core = 0; + int64_t i64Package = 0; + + int rc2 = RTLinuxSysFsReadIntFile(10, &i64Core, "%s/%s/topology/core_id", + pszPathCurr, g_apszTopologyPath[i]); + if (RT_SUCCESS(rc2)) + rc2 = RTLinuxSysFsReadIntFile(10, &i64Package, "%s/%s/topology/physical_package_id", + pszPathCurr, g_apszTopologyPath[i]); + + if (RT_SUCCESS(rc2)) + { + idCore = (uint32_t)i64Core; + idPackage = (uint32_t)i64Package; + break; + } + } + + if ( idCore == idCpuCore + && idPackage == idCpuPackage) + { + /* Return the path */ + pszPath = pszPathCurr; + fFound = true; + VGSvcVerbose(3, "CPU found\n"); + break; + } + else + { + /* Get the next directory. */ + RTStrFree(pszPathCurr); + pszPathCurr = NULL; + VGSvcVerbose(3, "CPU doesn't match, next directory\n"); + } + } + else + { + /* Go deeper */ + iLvlCurr++; + + VGSvcVerbose(3, "Going deeper (iLvlCurr=%u)\n", iLvlCurr); + + pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr]; + + Assert(pAcpiCpuPathLvl->hDir == NIL_RTDIR); + Assert(!pAcpiCpuPathLvl->pszPath); + pAcpiCpuPathLvl->pszPath = pszPathCurr; + PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId]; + + Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED); + + pszPathDir = RTPathJoinA(pszPathCurr, pPathComponent->pcszName); + if (!pszPathDir) + { + rc = VERR_NO_STR_MEMORY; + break; + } + + VGSvcVerbose(3, "New path %s\n", pszPathDir); + + /* Open the directory */ + rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->hDir, pszPathDir, RTDIRFILTER_WINNT, 0 /*fFlags*/); + RTStrFree(pszPathDir); + pszPathDir = NULL; + if (RT_FAILURE(rc)) + break; + } + } + else + { + RTDirClose(pAcpiCpuPathLvl->hDir); + RTStrFree(pAcpiCpuPathLvl->pszPath); + pAcpiCpuPathLvl->hDir = NIL_RTDIR; + pAcpiCpuPathLvl->pszPath = NULL; + + /* + * If we reached the end we didn't find the matching path + * meaning the CPU is already offline. + */ + if (!iLvlCurr) + { + rc = VERR_NOT_FOUND; + break; + } + + iLvlCurr--; + pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr]; + VGSvcVerbose(3, "Directory not found, going back (iLvlCurr=%u)\n", iLvlCurr); + } + } /* while not found */ + } /* Successful init */ + + /* Cleanup */ + for (unsigned i = 0; i < RT_ELEMENTS(g_aAcpiCpuPath); i++) + { + if (g_aAcpiCpuPath[i].hDir) + RTDirClose(g_aAcpiCpuPath[i].hDir); + if (g_aAcpiCpuPath[i].pszPath) + RTStrFree(g_aAcpiCpuPath[i].pszPath); + g_aAcpiCpuPath[i].hDir = NIL_RTDIR; + g_aAcpiCpuPath[i].pszPath = NULL; + } + if (pszPathDir) + RTStrFree(pszPathDir); + if (RT_FAILURE(rc) && pszPath) + RTStrFree(pszPath); + + if (RT_SUCCESS(rc)) + *ppszPath = pszPath; + } + + return rc; +} + +#endif /* RT_OS_LINUX */ + +/** + * Handles VMMDevCpuEventType_Plug. + * + * @param idCpuCore The CPU core ID. + * @param idCpuPackage The CPU package ID. + */ +static void vgsvcCpuHotPlugHandlePlugEvent(uint32_t idCpuCore, uint32_t idCpuPackage) +{ +#ifdef RT_OS_LINUX + /* + * The topology directory (containing the physical and core id properties) + * is not available until the CPU is online. So we just iterate over all directories + * and enable the first CPU which is not online already. + * Because the directory might not be available immediately we try a few times. + * + */ + /** @todo Maybe use udev to monitor hot-add events from the kernel */ + bool fCpuOnline = false; + unsigned cTries = 5; + + do + { + RTDIR hDirDevices = NULL; + int rc = RTDirOpen(&hDirDevices, SYSFS_CPU_PATH); + if (RT_SUCCESS(rc)) + { + RTDIRENTRY DirFolderContent; + while (RT_SUCCESS(RTDirRead(hDirDevices, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */ + { + /* Check if this is a CPU object which can be brought online. */ + if (RTLinuxSysFsExists("%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName)) + { + /* Check the status of the CPU by reading the online flag. */ + int64_t i64Status = 0; + rc = RTLinuxSysFsReadIntFile(10 /*uBase*/, &i64Status, "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName); + if ( RT_SUCCESS(rc) + && i64Status == 0) + { + /* CPU is offline, turn it on. */ + rc = RTLinuxSysFsWriteU8File(10 /*uBase*/, 1, "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was brought online\n", idCpuPackage, idCpuCore); + fCpuOnline = true; + break; + } + } + else if (RT_FAILURE(rc)) + VGSvcError("CpuHotPlug: Failed to open '%s/%s/online' rc=%Rrc\n", + SYSFS_CPU_PATH, DirFolderContent.szName, rc); + else + { + /* + * Check whether the topology matches what we got (which means someone raced us and brought the CPU + * online already). + */ + int64_t i64Core = 0; + int64_t i64Package = 0; + + int rc2 = RTLinuxSysFsReadIntFile(10, &i64Core, "%s/%s/topology/core_id", + SYSFS_CPU_PATH, DirFolderContent.szName); + if (RT_SUCCESS(rc2)) + rc2 = RTLinuxSysFsReadIntFile(10, &i64Package, "%s/%s/topology/physical_package_id", + SYSFS_CPU_PATH, DirFolderContent.szName); + if ( RT_SUCCESS(rc2) + && idCpuPackage == i64Package + && idCpuCore == i64Core) + { + VGSvcVerbose(1, "CpuHotPlug: '%s' is already online\n", DirFolderContent.szName); + fCpuOnline = true; + break; + } + } + } + } + RTDirClose(hDirDevices); + } + else + VGSvcError("CpuHotPlug: Failed to open path %s rc=%Rrc\n", SYSFS_CPU_PATH, rc); + + /* Sleep a bit */ + if (!fCpuOnline) + RTThreadSleep(100); + + } while ( !fCpuOnline + && cTries-- > 0); +#else +# error "Port me" +#endif +} + + +/** + * Handles VMMDevCpuEventType_Unplug. + * + * @param idCpuCore The CPU core ID. + * @param idCpuPackage The CPU package ID. + */ +static void vgsvcCpuHotPlugHandleUnplugEvent(uint32_t idCpuCore, uint32_t idCpuPackage) +{ +#ifdef RT_OS_LINUX + char *pszCpuDevicePath = NULL; + int rc = vgsvcCpuHotPlugGetACPIDevicePath(&pszCpuDevicePath, idCpuCore, idCpuPackage); + if (RT_SUCCESS(rc)) + { + RTFILE hFileCpuEject; + rc = RTFileOpenF(&hFileCpuEject, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, "%s/eject", pszCpuDevicePath); + if (RT_SUCCESS(rc)) + { + /* Write a 1 to eject the CPU */ + rc = RTFileWrite(hFileCpuEject, "1", 1, NULL); + if (RT_SUCCESS(rc)) + VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was ejected\n", idCpuPackage, idCpuCore); + else + VGSvcError("CpuHotPlug: Failed to eject CPU %u/%u rc=%Rrc\n", idCpuPackage, idCpuCore, rc); + + RTFileClose(hFileCpuEject); + } + else + VGSvcError("CpuHotPlug: Failed to open '%s/eject' rc=%Rrc\n", pszCpuDevicePath, rc); + RTStrFree(pszCpuDevicePath); + } + else if (rc == VERR_NOT_FOUND) + VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was aleady ejected by someone else!\n", idCpuPackage, idCpuCore); + else + VGSvcError("CpuHotPlug: Failed to get CPU device path rc=%Rrc\n", rc); +#else +# error "Port me" +#endif +} + + +/** @interface_method_impl{VBOXSERVICE,pfnWorker} */ +static DECLCALLBACK(int) vgsvcCpuHotPlugWorker(bool volatile *pfShutdown) +{ + /* + * Tell the control thread that it can continue spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Enable the CPU hotplug notifier. + */ + int rc = VbglR3CpuHotPlugInit(); + if (RT_FAILURE(rc)) + return rc; + + /* + * The Work Loop. + */ + for (;;) + { + /* Wait for CPU hot-plugging event. */ + uint32_t idCpuCore; + uint32_t idCpuPackage; + VMMDevCpuEventType enmEventType; + rc = VbglR3CpuHotPlugWaitForEvent(&enmEventType, &idCpuCore, &idCpuPackage); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "CpuHotPlug: Event happened idCpuCore=%u idCpuPackage=%u enmEventType=%d\n", + idCpuCore, idCpuPackage, enmEventType); + switch (enmEventType) + { + case VMMDevCpuEventType_Plug: + vgsvcCpuHotPlugHandlePlugEvent(idCpuCore, idCpuPackage); + break; + + case VMMDevCpuEventType_Unplug: + vgsvcCpuHotPlugHandleUnplugEvent(idCpuCore, idCpuPackage); + break; + + default: + { + static uint32_t s_iErrors = 0; + if (s_iErrors++ < 10) + VGSvcError("CpuHotPlug: Unknown event: idCpuCore=%u idCpuPackage=%u enmEventType=%d\n", + idCpuCore, idCpuPackage, enmEventType); + break; + } + } + } + else if (rc != VERR_INTERRUPTED && rc != VERR_TRY_AGAIN) + { + VGSvcError("CpuHotPlug: VbglR3CpuHotPlugWaitForEvent returned %Rrc\n", rc); + break; + } + + if (*pfShutdown) + break; + } + + VbglR3CpuHotPlugTerm(); + return rc; +} + + +/** @interface_method_impl{VBOXSERVICE,pfnStop} */ +static DECLCALLBACK(void) vgsvcCpuHotPlugStop(void) +{ + VbglR3InterruptEventWaits(); + return; +} + + +/** + * The 'CpuHotPlug' service description. + */ +VBOXSERVICE g_CpuHotPlug = +{ + /* pszName. */ + "cpuhotplug", + /* pszDescription. */ + "CPU hot-plugging monitor", + /* pszUsage. */ + NULL, + /* pszOptions. */ + NULL, + /* methods */ + VGSvcDefaultPreInit, + VGSvcDefaultOption, + VGSvcDefaultInit, + vgsvcCpuHotPlugWorker, + vgsvcCpuHotPlugStop, + VGSvcDefaultTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h b/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h new file mode 100644 index 00000000..cb85b59b --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h @@ -0,0 +1,284 @@ +/* $Id: VBoxServiceInternal.h $ */ +/** @file + * VBoxService - Guest Additions Services. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#endif + +#include <iprt/list.h> +#include <iprt/critsect.h> +#include <iprt/path.h> /* RTPATH_MAX */ +#include <iprt/stdarg.h> + +#include <VBox/VBoxGuestLib.h> +#include <VBox/HostServices/GuestControlSvc.h> + + +#if !defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING) +/** Special argv[1] value that indicates that argv is UTF-8. + * This causes RTR3Init to be called with RTR3INIT_FLAGS_UTF8_ARGV and helps + * work around potential issues caused by a user's locale config not being + * UTF-8. See @bugref{10153}. + * + * @note We don't need this on windows and it would be harmful to enable it + * as the argc/argv vs __argc/__argv comparison would fail and we would + * not use the unicode command line to create a UTF-8 argv. Since the + * original argv is ANSI, it may be missing codepoints not present in + * the ANSI code page of the process. */ +# define VBOXSERVICE_ARG1_UTF8_ARGV "--utf8-argv" +#endif +/** RTProcCreateEx flags corresponding to VBOXSERVICE_ARG1_UTF8_ARGV. */ +#ifdef VBOXSERVICE_ARG1_UTF8_ARGV +# define VBOXSERVICE_PROC_F_UTF8_ARGV RTPROC_FLAGS_UTF8_ARGV +#else +# define VBOXSERVICE_PROC_F_UTF8_ARGV 0 +#endif + + +/** + * A service descriptor. + */ +typedef struct +{ + /** The short service name. */ + const char *pszName; + /** The longer service name. */ + const char *pszDescription; + /** The usage options stuff for the --help screen. */ + const char *pszUsage; + /** The option descriptions for the --help screen. */ + const char *pszOptions; + + /** + * Called before parsing arguments. + * @returns VBox status code. + */ + DECLCALLBACKMEMBER(int, pfnPreInit,(void)); + + /** + * Tries to parse the given command line option. + * + * @returns 0 if we parsed, -1 if it didn't and anything else means exit. + * @param ppszShort If not NULL it points to the short option iterator. a short argument. + * If NULL examine argv[*pi]. + * @param argc The argument count. + * @param argv The argument vector. + * @param pi The argument vector index. Update if any value(s) are eaten. + */ + DECLCALLBACKMEMBER(int, pfnOption,(const char **ppszShort, int argc, char **argv, int *pi)); + + /** + * Called before parsing arguments. + * @returns VBox status code. + */ + DECLCALLBACKMEMBER(int, pfnInit,(void)); + + /** Called from the worker thread. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if exitting because *pfShutdown was set. + * @param pfShutdown Pointer to a per service termination flag to check + * before and after blocking. + */ + DECLCALLBACKMEMBER(int, pfnWorker,(bool volatile *pfShutdown)); + + /** + * Stops a service. + */ + DECLCALLBACKMEMBER(void, pfnStop,(void)); + + /** + * Does termination cleanups. + * + * @remarks This may be called even if pfnInit hasn't been called! + */ + DECLCALLBACKMEMBER(void, pfnTerm,(void)); +} VBOXSERVICE; +/** Pointer to a VBOXSERVICE. */ +typedef VBOXSERVICE *PVBOXSERVICE; +/** Pointer to a const VBOXSERVICE. */ +typedef VBOXSERVICE const *PCVBOXSERVICE; + +/* Default call-backs for services which do not need special behaviour. */ +DECLCALLBACK(int) VGSvcDefaultPreInit(void); +DECLCALLBACK(int) VGSvcDefaultOption(const char **ppszShort, int argc, char **argv, int *pi); +DECLCALLBACK(int) VGSvcDefaultInit(void); +DECLCALLBACK(void) VGSvcDefaultTerm(void); + +/** The service name. + * @note Used on windows to name the service as well as the global mutex. */ +#define VBOXSERVICE_NAME "VBoxService" + +#ifdef RT_OS_WINDOWS +/** The friendly service name. */ +# define VBOXSERVICE_FRIENDLY_NAME "VirtualBox Guest Additions Service" +/** The service description (only W2K+ atm) */ +# define VBOXSERVICE_DESCRIPTION "Manages VM runtime information, time synchronization, guest control execution and miscellaneous utilities for guest operating systems." +/** The following constant may be defined by including NtStatus.h. */ +# define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif /* RT_OS_WINDOWS */ + +#ifdef VBOX_WITH_GUEST_PROPS +/** + * A guest property cache. + */ +typedef struct VBOXSERVICEVEPROPCACHE +{ + /** The client ID for HGCM communication. */ + uint32_t uClientID; + /** Head in a list of VBOXSERVICEVEPROPCACHEENTRY nodes. */ + RTLISTANCHOR NodeHead; + /** Critical section for thread-safe use. */ + RTCRITSECT CritSect; +} VBOXSERVICEVEPROPCACHE; +/** Pointer to a guest property cache. */ +typedef VBOXSERVICEVEPROPCACHE *PVBOXSERVICEVEPROPCACHE; + +/** + * An entry in the property cache (VBOXSERVICEVEPROPCACHE). + */ +typedef struct VBOXSERVICEVEPROPCACHEENTRY +{ + /** Node to successor. + * @todo r=bird: This is not really the node to the successor, but + * rather the OUR node in the list. If it helps, remember that + * its a doubly linked list. */ + RTLISTNODE NodeSucc; + /** Name (and full path) of guest property. */ + char *pszName; + /** The last value stored (for reference). */ + char *pszValue; + /** Reset value to write if property is temporary. If NULL, it will be + * deleted. */ + char *pszValueReset; + /** Flags. */ + uint32_t fFlags; +} VBOXSERVICEVEPROPCACHEENTRY; +/** Pointer to a cached guest property. */ +typedef VBOXSERVICEVEPROPCACHEENTRY *PVBOXSERVICEVEPROPCACHEENTRY; + +#endif /* VBOX_WITH_GUEST_PROPS */ + +RT_C_DECLS_BEGIN + +extern char *g_pszProgName; +extern unsigned g_cVerbosity; +extern char g_szLogFile[RTPATH_MAX + 128]; +extern uint32_t g_DefaultInterval; +extern VBOXSERVICE g_TimeSync; +#ifdef VBOX_WITH_VBOXSERVICE_CLIPBOARD +extern VBOXSERVICE g_Clipboard; +#endif +extern VBOXSERVICE g_Control; +extern VBOXSERVICE g_VMInfo; +extern VBOXSERVICE g_CpuHotPlug; +#ifdef VBOX_WITH_VBOXSERVICE_MANAGEMENT +extern VBOXSERVICE g_MemBalloon; +extern VBOXSERVICE g_VMStatistics; +#endif +#ifdef VBOX_WITH_VBOXSERVICE_PAGE_SHARING +extern VBOXSERVICE g_PageSharing; +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS +extern VBOXSERVICE g_AutoMount; +#endif +#ifdef DEBUG +extern RTCRITSECT g_csLog; /* For guest process stdout dumping. */ +#endif + +extern RTEXITCODE VGSvcSyntax(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2); +extern RTEXITCODE VGSvcError(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2); +extern void VGSvcVerbose(unsigned iLevel, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(2, 3); +extern int VGSvcLogCreate(const char *pszLogFile); +extern void VGSvcLogV(const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(1, 0); +extern void VGSvcLogDestroy(void); +extern int VGSvcArgUInt32(int argc, char **argv, const char *psz, int *pi, uint32_t *pu32, + uint32_t u32Min, uint32_t u32Max); + +/* Exposing the following bits because of windows: */ +extern int VGSvcStartServices(void); +extern int VGSvcStopServices(void); +extern void VGSvcMainWait(void); +extern int VGSvcReportStatus(VBoxGuestFacilityStatus enmStatus); +#ifdef RT_OS_WINDOWS +extern void VGSvcWinResolveApis(void); +extern RTEXITCODE VGSvcWinInstall(void); +extern RTEXITCODE VGSvcWinUninstall(void); +extern RTEXITCODE VGSvcWinEnterCtrlDispatcher(void); +extern void VGSvcWinSetStopPendingStatus(uint32_t uCheckPoint); +# ifdef TH32CS_SNAPHEAPLIST +extern decltype(CreateToolhelp32Snapshot) *g_pfnCreateToolhelp32Snapshot; +extern decltype(Process32First) *g_pfnProcess32First; +extern decltype(Process32Next) *g_pfnProcess32Next; +extern decltype(Module32First) *g_pfnModule32First; +extern decltype(Module32Next) *g_pfnModule32Next; +# endif +extern decltype(GetSystemTimeAdjustment) *g_pfnGetSystemTimeAdjustment; +extern decltype(SetSystemTimeAdjustment) *g_pfnSetSystemTimeAdjustment; +# ifdef IPRT_INCLUDED_nt_nt_h +extern decltype(ZwQuerySystemInformation) *g_pfnZwQuerySystemInformation; +# endif +extern ULONG (WINAPI *g_pfnGetAdaptersInfo)(struct _IP_ADAPTER_INFO *, PULONG); +#ifdef WINSOCK_VERSION +extern decltype(WSAStartup) *g_pfnWSAStartup; +extern decltype(WSACleanup) *g_pfnWSACleanup; +extern decltype(WSASocketA) *g_pfnWSASocketA; +extern decltype(WSAIoctl) *g_pfnWSAIoctl; +extern decltype(WSAGetLastError) *g_pfnWSAGetLastError; +extern decltype(closesocket) *g_pfnclosesocket; +extern decltype(inet_ntoa) *g_pfninet_ntoa; +# endif /* WINSOCK_VERSION */ + +#ifdef SE_INTERACTIVE_LOGON_NAME +extern decltype(LsaNtStatusToWinError) *g_pfnLsaNtStatusToWinError; +#endif + +# ifdef VBOX_WITH_GUEST_PROPS +extern int VGSvcVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, char **ppszUserList, uint32_t *pcUsersInList); +extern int VGSvcVMInfoWinGetComponentVersions(uint32_t uClientID); +# endif /* VBOX_WITH_GUEST_PROPS */ + +#endif /* RT_OS_WINDOWS */ + +#ifdef VBOX_WITH_MEMBALLOON +extern uint32_t VGSvcBalloonQueryPages(uint32_t cbPage); +#endif +#if defined(VBOX_WITH_VBOXSERVICE_PAGE_SHARING) +extern RTEXITCODE VGSvcPageSharingWorkerChild(void); +#endif +extern int VGSvcVMInfoSignal(void); + +RT_C_DECLS_END + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp b/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp new file mode 100644 index 00000000..c8e72c62 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp @@ -0,0 +1,803 @@ +/* $Id: VBoxServicePageSharing.cpp $ */ +/** @file + * VBoxService - Guest page sharing. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc_pagesharing VBoxService - Page Sharing + * + * The Page Sharing subservice is responsible for finding memory mappings + * suitable page fusions. + * + * It is the driving force behind the Page Fusion feature in VirtualBox. + * Working with PGM and GMM (ring-0) thru the VMMDev interface. Every so often + * it reenumerates the memory mappings (executables and shared libraries) of the + * guest OS and reports additions and removals to GMM. For each mapping there + * is a filename and version as well as and address range and subsections. GMM + * will match the mapping with mapping with the same name and version from other + * VMs and see if there are any identical pages between the two. + * + * To increase the hit rate and reduce the volatility, the service launches a + * child process which loads all the Windows system DLLs it can. The child + * process is necessary as the DLLs are loaded without running the init code, + * and therefore not actually callable for other VBoxService code (may crash). + * + * This is currently only implemented on Windows. There is no technical reason + * for it not to be doable for all the other guests too, it's just a matter of + * customer demand and engineering time. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/avl.h> +#include <iprt/asm.h> +#include <iprt/mem.h> +#include <iprt/ldr.h> +#include <iprt/process.h> +#include <iprt/env.h> +#include <iprt/stream.h> +#include <iprt/file.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <VBox/err.h> +#include <VBox/VMMDev.h> +#include <VBox/VBoxGuestLib.h> + +#ifdef RT_OS_WINDOWS +#include <iprt/nt/nt-and-windows.h> +# include <tlhelp32.h> +# include <psapi.h> +# include <winternl.h> +#endif + +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +typedef struct +{ + AVLPVNODECORE Core; +#ifdef RT_OS_WINDOWS + HMODULE hModule; + char szFileVersion[16]; + MODULEENTRY32 Info; +#endif +} VGSVCPGSHKNOWNMOD, *PVGSVCPGSHKNOWNMOD; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_PageSharingEvent = NIL_RTSEMEVENTMULTI; + +static PAVLPVNODECORE g_pKnownModuleTree = NULL; +static uint64_t g_idSession = 0; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) vgsvcPageSharingEmptyTreeCallback(PAVLPVNODECORE pNode, void *pvUser); + + +#ifdef RT_OS_WINDOWS + +/** + * Registers a new module with the VMM + * @param pModule Module ptr + * @param fValidateMemory Validate/touch memory pages or not + */ +static void vgsvcPageSharingRegisterModule(PVGSVCPGSHKNOWNMOD pModule, bool fValidateMemory) +{ + VMMDEVSHAREDREGIONDESC aRegions[VMMDEVSHAREDREGIONDESC_MAX]; + DWORD dwModuleSize = pModule->Info.modBaseSize; + BYTE *pBaseAddress = pModule->Info.modBaseAddr; + + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule\n"); + + DWORD dwDummy; + DWORD cbVersion = GetFileVersionInfoSize(pModule->Info.szExePath, &dwDummy); + if (!cbVersion) + { + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: GetFileVersionInfoSize failed with %d\n", GetLastError()); + return; + } + BYTE *pVersionInfo = (BYTE *)RTMemAllocZ(cbVersion); + if (!pVersionInfo) + return; + + if (!GetFileVersionInfo(pModule->Info.szExePath, 0, cbVersion, pVersionInfo)) + { + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: GetFileVersionInfo failed with %d\n", GetLastError()); + goto end; + } + + /* Fetch default code page. */ + struct LANGANDCODEPAGE + { + WORD wLanguage; + WORD wCodePage; + } *lpTranslate; + + UINT cbTranslate; + BOOL fRet = VerQueryValue(pVersionInfo, TEXT("\\VarFileInfo\\Translation"), (LPVOID *)&lpTranslate, &cbTranslate); + if ( !fRet + || cbTranslate < 4) + { + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VerQueryValue failed with %d (cb=%d)\n", GetLastError(), cbTranslate); + goto end; + } + + unsigned i; + UINT cbFileVersion; + char *pszFileVersion = NULL; /* Shut up MSC */ + unsigned cTranslationBlocks = cbTranslate/sizeof(struct LANGANDCODEPAGE); + + pModule->szFileVersion[0] = '\0'; + for (i = 0; i < cTranslationBlocks; i++) + { + /* Fetch file version string. */ + char szFileVersionLocation[256]; + +/** @todo r=bird: Mixing ANSI and TCHAR crap again. This code is a mess. We + * always use the wide version of the API and convert to UTF-8/whatever. */ + + RTStrPrintf(szFileVersionLocation, sizeof(szFileVersionLocation), + "\\StringFileInfo\\%04x%04x\\FileVersion", lpTranslate[i].wLanguage, lpTranslate[i].wCodePage); + fRet = VerQueryValue(pVersionInfo, szFileVersionLocation, (LPVOID *)&pszFileVersion, &cbFileVersion); + if (fRet) + { + RTStrCopy(pModule->szFileVersion, sizeof(pModule->szFileVersion), pszFileVersion); + break; + } + } + if (i == cTranslationBlocks) + { + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: no file version found!\n"); + goto end; + } + + unsigned idxRegion = 0; + + if (fValidateMemory) + { + do + { + MEMORY_BASIC_INFORMATION MemInfo; + SIZE_T cbRet = VirtualQuery(pBaseAddress, &MemInfo, sizeof(MemInfo)); + Assert(cbRet); + if (!cbRet) + { + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VirtualQueryEx failed with %d\n", GetLastError()); + break; + } + + if ( MemInfo.State == MEM_COMMIT + && MemInfo.Type == MEM_IMAGE) + { + switch (MemInfo.Protect) + { + case PAGE_EXECUTE: + case PAGE_EXECUTE_READ: + case PAGE_READONLY: + { + char *pRegion = (char *)MemInfo.BaseAddress; + + /* Skip the first region as it only contains the image file header. */ + if (pRegion != (char *)pModule->Info.modBaseAddr) + { + /* Touch all pages. */ + while ((uintptr_t)pRegion < (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize) + { + /* Try to trick the optimizer to leave the page touching code in place. */ + ASMProbeReadByte(pRegion); + pRegion += PAGE_SIZE; + } + } +#ifdef RT_ARCH_X86 + aRegions[idxRegion].GCRegionAddr = (RTGCPTR32)MemInfo.BaseAddress; +#else + aRegions[idxRegion].GCRegionAddr = (RTGCPTR64)MemInfo.BaseAddress; +#endif + aRegions[idxRegion].cbRegion = MemInfo.RegionSize; + idxRegion++; + + break; + } + + default: + break; /* ignore */ + } + } + + pBaseAddress = (BYTE *)MemInfo.BaseAddress + MemInfo.RegionSize; + if (dwModuleSize > MemInfo.RegionSize) + dwModuleSize -= MemInfo.RegionSize; + else + { + dwModuleSize = 0; + break; + } + + if (idxRegion >= RT_ELEMENTS(aRegions)) + break; /* out of room */ + } + while (dwModuleSize); + } + else + { + /* We can't probe kernel memory ranges, so pretend it's one big region. */ +#ifdef RT_ARCH_X86 + aRegions[idxRegion].GCRegionAddr = (RTGCPTR32)pBaseAddress; +#else + aRegions[idxRegion].GCRegionAddr = (RTGCPTR64)pBaseAddress; +#endif + aRegions[idxRegion].cbRegion = dwModuleSize; + idxRegion++; + } + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VbglR3RegisterSharedModule %s %s base=%p size=%x cregions=%d\n", pModule->Info.szModule, pModule->szFileVersion, pModule->Info.modBaseAddr, pModule->Info.modBaseSize, idxRegion); + int rc = VbglR3RegisterSharedModule(pModule->Info.szModule, pModule->szFileVersion, (uintptr_t)pModule->Info.modBaseAddr, + pModule->Info.modBaseSize, idxRegion, aRegions); + if (RT_FAILURE(rc)) + VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VbglR3RegisterSharedModule failed with %Rrc\n", rc); + +end: + RTMemFree(pVersionInfo); + return; +} + + +/** + * Inspect all loaded modules for the specified process + * + * @param dwProcessId Process id + * @param ppNewTree The module tree we're assembling from modules found + * in this process. Modules found are moved from + * g_pKnownModuleTree or created new. + */ +static void vgsvcPageSharingInspectModules(DWORD dwProcessId, PAVLPVNODECORE *ppNewTree) +{ + /* Get a list of all the modules in this process. */ + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE /* no child process handle inheritance */, dwProcessId); + if (hProcess == NULL) + { + VGSvcVerbose(3, "vgsvcPageSharingInspectModules: OpenProcess %x failed with %d\n", dwProcessId, GetLastError()); + return; + } + + HANDLE hSnapshot = g_pfnCreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId); + if (hSnapshot == INVALID_HANDLE_VALUE) + { + VGSvcVerbose(3, "vgsvcPageSharingInspectModules: CreateToolhelp32Snapshot failed with %d\n", GetLastError()); + CloseHandle(hProcess); + return; + } + + VGSvcVerbose(3, "vgsvcPageSharingInspectModules\n"); + + MODULEENTRY32 ModuleInfo; + BOOL bRet; + + ModuleInfo.dwSize = sizeof(ModuleInfo); + bRet = g_pfnModule32First(hSnapshot, &ModuleInfo); + do + { + /** @todo when changing this make sure VBoxService.exe is excluded! */ + char *pszDot = strrchr(ModuleInfo.szModule, '.'); + if ( pszDot + && (pszDot[1] == 'e' || pszDot[1] == 'E')) + continue; /* ignore executables for now. */ + + /* Found it before? */ + PAVLPVNODECORE pRec = RTAvlPVGet(ppNewTree, ModuleInfo.modBaseAddr); + if (!pRec) + { + pRec = RTAvlPVRemove(&g_pKnownModuleTree, ModuleInfo.modBaseAddr); + if (!pRec) + { + /* New module; register it. */ + PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)RTMemAllocZ(sizeof(*pModule)); + Assert(pModule); + if (!pModule) + break; + + pModule->Info = ModuleInfo; + pModule->Core.Key = ModuleInfo.modBaseAddr; + pModule->hModule = LoadLibraryEx(ModuleInfo.szExePath, 0, DONT_RESOLVE_DLL_REFERENCES); + if (pModule->hModule) + vgsvcPageSharingRegisterModule(pModule, true /* validate pages */); + + VGSvcVerbose(3, "\n\n MODULE NAME: %s", ModuleInfo.szModule ); + VGSvcVerbose(3, "\n executable = %s", ModuleInfo.szExePath ); + VGSvcVerbose(3, "\n process ID = 0x%08X", ModuleInfo.th32ProcessID ); + VGSvcVerbose(3, "\n base address = %#010p", (uintptr_t) ModuleInfo.modBaseAddr ); + VGSvcVerbose(3, "\n base size = %d", ModuleInfo.modBaseSize ); + + pRec = &pModule->Core; + } + bool ret = RTAvlPVInsert(ppNewTree, pRec); + Assert(ret); NOREF(ret); + } + } while (g_pfnModule32Next(hSnapshot, &ModuleInfo)); + + CloseHandle(hSnapshot); + CloseHandle(hProcess); +} + + +/** + * Inspect all running processes for executables and dlls that might be worth sharing + * with other VMs. + * + */ +static void vgsvcPageSharingInspectGuest(void) +{ + VGSvcVerbose(3, "vgsvcPageSharingInspectGuest\n"); + PAVLPVNODECORE pNewTree = NULL; + + /* + * Check loaded modules for all running processes. + */ + if ( g_pfnProcess32First + && g_pfnProcess32Next + && g_pfnModule32First + && g_pfnModule32Next + && g_pfnCreateToolhelp32Snapshot) + { + HANDLE hSnapshot = g_pfnCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) + { + VGSvcVerbose(3, "vgsvcPageSharingInspectGuest: CreateToolhelp32Snapshot failed with %d\n", GetLastError()); + return; + } + + DWORD const dwProcessId = GetCurrentProcessId(); + + PROCESSENTRY32 ProcessInfo; + ProcessInfo.dwSize = sizeof(ProcessInfo); + g_pfnProcess32First(hSnapshot, &ProcessInfo); + + do + { + /* Skip our own process. */ + if (ProcessInfo.th32ProcessID != dwProcessId) + vgsvcPageSharingInspectModules(ProcessInfo.th32ProcessID, &pNewTree); + } + while (g_pfnProcess32Next(hSnapshot, &ProcessInfo)); + + CloseHandle(hSnapshot); + } + + /* + * Check all loaded kernel modules. + */ + if (g_pfnZwQuerySystemInformation) + { + ULONG cbBuffer = 0; + PVOID pBuffer = NULL; + PRTL_PROCESS_MODULES pSystemModules; + + NTSTATUS ret = g_pfnZwQuerySystemInformation(SystemModuleInformation, (PVOID)&cbBuffer, 0, &cbBuffer); + if (!cbBuffer) + { + VGSvcVerbose(1, "ZwQuerySystemInformation returned length 0\n"); + goto skipkernelmodules; + } + + pBuffer = RTMemAllocZ(cbBuffer); + if (!pBuffer) + goto skipkernelmodules; + + ret = g_pfnZwQuerySystemInformation(SystemModuleInformation, pBuffer, cbBuffer, &cbBuffer); + if (ret != STATUS_SUCCESS) + { + VGSvcVerbose(1, "ZwQuerySystemInformation returned %x (1)\n", ret); + goto skipkernelmodules; + } + + pSystemModules = (PRTL_PROCESS_MODULES)pBuffer; + for (unsigned i = 0; i < pSystemModules->NumberOfModules; i++) + { + VGSvcVerbose(4, "\n\n KERNEL MODULE NAME: %s", pSystemModules->Modules[i].FullPathName[pSystemModules->Modules[i].OffsetToFileName] ); + VGSvcVerbose(4, "\n executable = %s", pSystemModules->Modules[i].FullPathName ); + VGSvcVerbose(4, "\n flags = 0x%08X\n", pSystemModules->Modules[i].Flags); + + /* User-mode modules seem to have no flags set; skip them as we detected them above. */ + if (pSystemModules->Modules[i].Flags == 0) + continue; + + /* Found it before? */ + PAVLPVNODECORE pRec = RTAvlPVGet(&pNewTree, pSystemModules->Modules[i].ImageBase); + if (!pRec) + { + pRec = RTAvlPVRemove(&g_pKnownModuleTree, pSystemModules->Modules[i].ImageBase); + if (!pRec) + { + /* New module; register it. */ + char szFullFilePath[512]; + PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)RTMemAllocZ(sizeof(*pModule)); + Assert(pModule); + if (!pModule) + break; + +/** @todo FullPathName not an UTF-8 string is! An ANSI string it is + * according to the SYSTEM locale. Best use RtlAnsiStringToUnicodeString to + * convert to UTF-16. */ + strcpy(pModule->Info.szModule, + (const char *)&pSystemModules->Modules[i].FullPathName[pSystemModules->Modules[i].OffsetToFileName]); + GetSystemDirectoryA(szFullFilePath, sizeof(szFullFilePath)); + + /* skip \Systemroot\system32 */ + char *lpPath = strchr((char *)&pSystemModules->Modules[i].FullPathName[1], '\\'); + if (!lpPath) + { + /* Seen just file names in XP; try to locate the file in the system32 and system32\drivers directories. */ + RTStrCat(szFullFilePath, sizeof(szFullFilePath), "\\"); + RTStrCat(szFullFilePath, sizeof(szFullFilePath), (const char *)pSystemModules->Modules[i].FullPathName); + VGSvcVerbose(3, "Unexpected kernel module name try %s\n", szFullFilePath); + if (RTFileExists(szFullFilePath) == false) + { + GetSystemDirectoryA(szFullFilePath, sizeof(szFullFilePath)); + RTStrCat(szFullFilePath, sizeof(szFullFilePath), "\\drivers\\"); + RTStrCat(szFullFilePath, sizeof(szFullFilePath), (const char *)pSystemModules->Modules[i].FullPathName); + VGSvcVerbose(3, "Unexpected kernel module name try %s\n", szFullFilePath); + if (RTFileExists(szFullFilePath) == false) + { + VGSvcVerbose(1, "Unexpected kernel module name %s\n", pSystemModules->Modules[i].FullPathName); + RTMemFree(pModule); + continue; + } + } + } + else + { + lpPath = strchr(lpPath + 1, '\\'); + if (!lpPath) + { + VGSvcVerbose(1, "Unexpected kernel module name %s (2)\n", pSystemModules->Modules[i].FullPathName); + RTMemFree(pModule); + continue; + } + + RTStrCat(szFullFilePath, sizeof(szFullFilePath), lpPath); + } + + strcpy(pModule->Info.szExePath, szFullFilePath); + pModule->Info.modBaseAddr = (BYTE *)pSystemModules->Modules[i].ImageBase; + pModule->Info.modBaseSize = pSystemModules->Modules[i].ImageSize; + + pModule->Core.Key = pSystemModules->Modules[i].ImageBase; + vgsvcPageSharingRegisterModule(pModule, false /* don't check memory pages */); + + VGSvcVerbose(3, "\n\n KERNEL MODULE NAME: %s", pModule->Info.szModule ); + VGSvcVerbose(3, "\n executable = %s", pModule->Info.szExePath ); + VGSvcVerbose(3, "\n base address = %#010p", (uintptr_t)pModule->Info.modBaseAddr ); + VGSvcVerbose(3, "\n flags = 0x%08X", pSystemModules->Modules[i].Flags); + VGSvcVerbose(3, "\n base size = %d", pModule->Info.modBaseSize ); + + pRec = &pModule->Core; + } + bool fRet = RTAvlPVInsert(&pNewTree, pRec); + Assert(fRet); NOREF(fRet); + } + } +skipkernelmodules: + if (pBuffer) + RTMemFree(pBuffer); + } + + /* Delete leftover modules in the old tree. */ + RTAvlPVDestroy(&g_pKnownModuleTree, vgsvcPageSharingEmptyTreeCallback, NULL); + + /* Check all registered modules. */ + VbglR3CheckSharedModules(); + + /* Activate new module tree. */ + g_pKnownModuleTree = pNewTree; +} + + +/** + * RTAvlPVDestroy callback. + */ +static DECLCALLBACK(int) vgsvcPageSharingEmptyTreeCallback(PAVLPVNODECORE pNode, void *pvUser) +{ + PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)pNode; + bool *pfUnregister = (bool *)pvUser; + + VGSvcVerbose(3, "vgsvcPageSharingEmptyTreeCallback %s %s\n", pModule->Info.szModule, pModule->szFileVersion); + + /* Dereference module in the hypervisor. */ + if ( !pfUnregister + || *pfUnregister) + { + int rc = VbglR3UnregisterSharedModule(pModule->Info.szModule, pModule->szFileVersion, + (uintptr_t)pModule->Info.modBaseAddr, pModule->Info.modBaseSize); + AssertRC(rc); + } + + if (pModule->hModule) + FreeLibrary(pModule->hModule); + RTMemFree(pNode); + return 0; +} + + +#else /* !RT_OS_WINDOWS */ + +static void vgsvcPageSharingInspectGuest(void) +{ + /** @todo other platforms */ +} + +#endif /* !RT_OS_WINDOWS */ + +/** @interface_method_impl{VBOXSERVICE,pfnInit} */ +static DECLCALLBACK(int) vgsvcPageSharingInit(void) +{ + VGSvcVerbose(3, "vgsvcPageSharingInit\n"); + + int rc = RTSemEventMultiCreate(&g_PageSharingEvent); + AssertRCReturn(rc, rc); + +#ifdef RT_OS_WINDOWS + rc = VbglR3GetSessionId(&g_idSession); + if (RT_FAILURE(rc)) + { + if (rc == VERR_IO_GEN_FAILURE) + VGSvcVerbose(0, "PageSharing: Page sharing support is not available by the host\n"); + else + VGSvcError("vgsvcPageSharingInit: Failed with rc=%Rrc\n", rc); + + rc = VERR_SERVICE_DISABLED; + + RTSemEventMultiDestroy(g_PageSharingEvent); + g_PageSharingEvent = NIL_RTSEMEVENTMULTI; + + } +#endif + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vgsvcPageSharingWorker(bool volatile *pfShutdown) +{ + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Now enter the loop retrieving runtime data continuously. + */ + for (;;) + { + bool fEnabled = VbglR3PageSharingIsEnabled(); + VGSvcVerbose(3, "vgsvcPageSharingWorker: enabled=%d\n", fEnabled); + + if (fEnabled) + vgsvcPageSharingInspectGuest(); + + /* + * Block for a minute. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + int rc = RTSemEventMultiWait(g_PageSharingEvent, 60000); + if (*pfShutdown) + break; + if (rc != VERR_TIMEOUT && RT_FAILURE(rc)) + { + VGSvcError("vgsvcPageSharingWorker: RTSemEventMultiWait failed; rc=%Rrc\n", rc); + break; + } +#ifdef RT_OS_WINDOWS + uint64_t idNewSession = g_idSession; + rc = VbglR3GetSessionId(&idNewSession); + AssertRC(rc); + + if (idNewSession != g_idSession) + { + bool fUnregister = false; + + VGSvcVerbose(3, "vgsvcPageSharingWorker: VM was restored!!\n"); + /* The VM was restored, so reregister all modules the next time. */ + RTAvlPVDestroy(&g_pKnownModuleTree, vgsvcPageSharingEmptyTreeCallback, &fUnregister); + g_pKnownModuleTree = NULL; + + g_idSession = idNewSession; + } +#endif + } + + RTSemEventMultiDestroy(g_PageSharingEvent); + g_PageSharingEvent = NIL_RTSEMEVENTMULTI; + + VGSvcVerbose(3, "vgsvcPageSharingWorker: finished thread\n"); + return 0; +} + +#ifdef RT_OS_WINDOWS + +/** + * This gets control when VBoxService is launched with "pagefusion" by + * vgsvcPageSharingWorkerProcess(). + * + * @returns RTEXITCODE_SUCCESS. + * + * @remarks It won't normally return since the parent drops the shutdown hint + * via RTProcTerminate(). + */ +RTEXITCODE VGSvcPageSharingWorkerChild(void) +{ + VGSvcVerbose(3, "vgsvcPageSharingInitFork\n"); + + bool fShutdown = false; + vgsvcPageSharingInit(); + vgsvcPageSharingWorker(&fShutdown); + + return RTEXITCODE_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vgsvcPageSharingWorkerProcess(bool volatile *pfShutdown) +{ + RTPROCESS hProcess = NIL_RTPROCESS; + int rc; + + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Now enter the loop retrieving runtime data continuously. + */ + for (;;) + { + bool fEnabled = VbglR3PageSharingIsEnabled(); + VGSvcVerbose(3, "vgsvcPageSharingWorkerProcess: enabled=%d\n", fEnabled); + + /* + * Start a 2nd VBoxService process to deal with page fusion as we do + * not wish to dummy load dlls into this process. (First load with + * DONT_RESOLVE_DLL_REFERENCES, 2nd normal -> dll init routines not called!) + */ + if ( fEnabled + && hProcess == NIL_RTPROCESS) + { + char szExeName[256]; + char *pszExeName = RTProcGetExecutablePath(szExeName, sizeof(szExeName)); + if (pszExeName) + { + char const *papszArgs[3]; + papszArgs[0] = pszExeName; + papszArgs[1] = "pagefusion"; + papszArgs[2] = NULL; + rc = RTProcCreate(pszExeName, papszArgs, RTENV_DEFAULT, 0 /* normal child */, &hProcess); + if (RT_FAILURE(rc)) + VGSvcError("vgsvcPageSharingWorkerProcess: RTProcCreate %s failed; rc=%Rrc\n", pszExeName, rc); + } + } + + /* + * Block for a minute. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + rc = RTSemEventMultiWait(g_PageSharingEvent, 60000); + if (*pfShutdown) + break; + if (rc != VERR_TIMEOUT && RT_FAILURE(rc)) + { + VGSvcError("vgsvcPageSharingWorkerProcess: RTSemEventMultiWait failed; rc=%Rrc\n", rc); + break; + } + } + + if (hProcess != NIL_RTPROCESS) + RTProcTerminate(hProcess); + + VGSvcVerbose(3, "vgsvcPageSharingWorkerProcess: finished thread\n"); + return 0; +} + +#endif /* RT_OS_WINDOWS */ + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcPageSharingStop(void) +{ + RTSemEventMultiSignal(g_PageSharingEvent); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcPageSharingTerm(void) +{ + if (g_PageSharingEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_PageSharingEvent); + g_PageSharingEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'pagesharing' service description. + */ +VBOXSERVICE g_PageSharing = +{ + /* pszName. */ + "pagesharing", + /* pszDescription. */ + "Page Sharing", + /* pszUsage. */ + NULL, + /* pszOptions. */ + NULL, + /* methods */ + VGSvcDefaultPreInit, + VGSvcDefaultOption, + vgsvcPageSharingInit, +#ifdef RT_OS_WINDOWS + vgsvcPageSharingWorkerProcess, +#else + vgsvcPageSharingWorker, +#endif + vgsvcPageSharingStop, + vgsvcPageSharingTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp new file mode 100644 index 00000000..6df00ba3 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp @@ -0,0 +1,439 @@ +/* $Id: VBoxServicePropCache.cpp $ */ +/** @file + * VBoxServicePropCache - Guest property cache. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#include "VBoxServicePropCache.h" + + + +/** @todo Docs */ +static PVBOXSERVICEVEPROPCACHEENTRY vgsvcPropCacheFindInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, + uint32_t fFlags) +{ + RT_NOREF1(fFlags); + AssertPtrReturn(pCache, NULL); + AssertPtrReturn(pszName, NULL); + + /** @todo This is a O(n) lookup, maybe improve this later to O(1) using a + * map. + * r=bird: Use a string space (RTstrSpace*). That is O(log n) in its current + * implementation (AVL tree). However, this is not important at the + * moment. */ + PVBOXSERVICEVEPROPCACHEENTRY pNode = NULL; + if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect))) + { + PVBOXSERVICEVEPROPCACHEENTRY pNodeIt; + RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc) + { + if (strcmp(pNodeIt->pszName, pszName) == 0) + { + pNode = pNodeIt; + break; + } + } + RTCritSectLeave(&pCache->CritSect); + } + return pNode; +} + + +/** @todo Docs */ +static PVBOXSERVICEVEPROPCACHEENTRY vgsvcPropCacheInsertEntryInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName) +{ + AssertPtrReturn(pCache, NULL); + AssertPtrReturn(pszName, NULL); + + PVBOXSERVICEVEPROPCACHEENTRY pNode = (PVBOXSERVICEVEPROPCACHEENTRY)RTMemAlloc(sizeof(VBOXSERVICEVEPROPCACHEENTRY)); + if (pNode) + { + pNode->pszName = RTStrDup(pszName); + if (!pNode->pszName) + { + RTMemFree(pNode); + return NULL; + } + pNode->pszValue = NULL; + pNode->fFlags = 0; + pNode->pszValueReset = NULL; + + int rc = RTCritSectEnter(&pCache->CritSect); + if (RT_SUCCESS(rc)) + { + RTListAppend(&pCache->NodeHead, &pNode->NodeSucc); + rc = RTCritSectLeave(&pCache->CritSect); + } + } + return pNode; +} + + +/** @todo Docs */ +static int vgsvcPropCacheWritePropF(uint32_t u32ClientId, const char *pszName, uint32_t fFlags, const char *pszValueFormat, ...) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + + int rc; + if (pszValueFormat != NULL) + { + va_list va; + va_start(va, pszValueFormat); + + char *pszValue; + if (RTStrAPrintfV(&pszValue, pszValueFormat, va) >= 0) + { + if (fFlags & VGSVCPROPCACHE_FLAGS_TRANSIENT) + { + /* + * Because a value can be temporary we have to make sure it also + * gets deleted when the property cache did not have the chance to + * gracefully clean it up (due to a hard VM reset etc), so set this + * guest property using the TRANSRESET flag.. + */ + rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, "TRANSRESET"); + if (rc == VERR_PARSE_ERROR) + { + /* Host does not support the "TRANSRESET" flag, so only + * use the "TRANSIENT" flag -- better than nothing :-). */ + rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, "TRANSIENT"); + /** @todo r=bird: Remember that the host doesn't support + * this. */ + } + } + else + rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, pszValue /* No transient flags set */); + RTStrFree(pszValue); + } + else + rc = VERR_NO_MEMORY; + va_end(va); + } + else + rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, NULL); + return rc; +} + + +/** + * Creates a property cache. + * + * @returns IPRT status code. + * @param pCache Pointer to the cache. + * @param uClientId The HGCM handle of to the guest property service. + */ +int VGSvcPropCacheCreate(PVBOXSERVICEVEPROPCACHE pCache, uint32_t uClientId) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + /** @todo Prevent init the cache twice! + * r=bird: Use a magic. */ + RTListInit(&pCache->NodeHead); + pCache->uClientID = uClientId; + return RTCritSectInit(&pCache->CritSect); +} + + +/** + * Updates a cache entry without submitting any changes to the host. + * + * This is handy for defining default values/flags. + * + * @returns VBox status code. + * + * @param pCache The property cache. + * @param pszName The property name. + * @param fFlags The property flags to set. + * @param pszValueReset The property reset value. + */ +int VGSvcPropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, uint32_t fFlags, const char *pszValueReset) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + PVBOXSERVICEVEPROPCACHEENTRY pNode = vgsvcPropCacheFindInternal(pCache, pszName, 0); + if (pNode == NULL) + pNode = vgsvcPropCacheInsertEntryInternal(pCache, pszName); + + int rc; + if (pNode != NULL) + { + rc = RTCritSectEnter(&pCache->CritSect); + if (RT_SUCCESS(rc)) + { + pNode->fFlags = fFlags; + if (pszValueReset) + { + if (pNode->pszValueReset) + RTStrFree(pNode->pszValueReset); + pNode->pszValueReset = RTStrDup(pszValueReset); + AssertPtr(pNode->pszValueReset); + } + rc = RTCritSectLeave(&pCache->CritSect); + } + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Updates the local guest property cache and writes it to HGCM if outdated. + * + * @returns VBox status code. + * + * @param pCache The property cache. + * @param pszName The property name. + * @param pszValueFormat The property format string. If this is NULL then + * the property will be deleted (if possible). + * @param ... Format arguments. + */ +int VGSvcPropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, const char *pszValueFormat, ...) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + + Assert(pCache->uClientID); + + /* + * Format the value first. + */ + char *pszValue = NULL; + if (pszValueFormat) + { + va_list va; + va_start(va, pszValueFormat); + RTStrAPrintfV(&pszValue, pszValueFormat, va); + va_end(va); + if (!pszValue) + return VERR_NO_STR_MEMORY; + } + + PVBOXSERVICEVEPROPCACHEENTRY pNode = vgsvcPropCacheFindInternal(pCache, pszName, 0); + + /* Lock the cache. */ + int rc = RTCritSectEnter(&pCache->CritSect); + if (RT_SUCCESS(rc)) + { + if (pNode == NULL) + pNode = vgsvcPropCacheInsertEntryInternal(pCache, pszName); + + AssertPtr(pNode); + if (pszValue) /* Do we have a value to check for? */ + { + bool fUpdate = false; + /* Always update this property, no matter what? */ + if (pNode->fFlags & VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE) + fUpdate = true; + /* Did the value change so we have to update? */ + else if (pNode->pszValue && strcmp(pNode->pszValue, pszValue) != 0) + fUpdate = true; + /* No value stored at the moment but we have a value now? */ + else if (pNode->pszValue == NULL) + fUpdate = true; + + if (fUpdate) + { + /* Write the update. */ + rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, pNode->fFlags, pszValue); + VGSvcVerbose(4, "[PropCache %p]: Written '%s'='%s' (flags: %x), rc=%Rrc\n", + pCache, pNode->pszName, pszValue, pNode->fFlags, rc); + if (RT_SUCCESS(rc)) /* Only update the node's value on successful write. */ + { + RTStrFree(pNode->pszValue); + pNode->pszValue = RTStrDup(pszValue); + if (!pNode->pszValue) + rc = VERR_NO_MEMORY; + } + } + else + rc = VINF_NO_CHANGE; /* No update needed. */ + } + else + { + /* No value specified. Deletion (or no action required). */ + if (pNode->pszValue) /* Did we have a value before? Then the value needs to be deleted. */ + { + rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, + 0, /* Flags */ NULL /* Value */); + VGSvcVerbose(4, "[PropCache %p]: Deleted '%s'='%s' (flags: %x), rc=%Rrc\n", + pCache, pNode->pszName, pNode->pszValue, pNode->fFlags, rc); + if (RT_SUCCESS(rc)) /* Only delete property value on successful Vbgl deletion. */ + { + /* Delete property (but do not remove from cache) if not deleted yet. */ + RTStrFree(pNode->pszValue); + pNode->pszValue = NULL; + } + } + else + rc = VINF_NO_CHANGE; /* No update needed. */ + } + + /* Release cache. */ + RTCritSectLeave(&pCache->CritSect); + } + + VGSvcVerbose(4, "[PropCache %p]: Updating '%s' resulted in rc=%Rrc\n", pCache, pszName, rc); + + /* Delete temp stuff. */ + RTStrFree(pszValue); + return rc; +} + + +/** + * Updates all cache values which are matching the specified path. + * + * @returns VBox status code. + * + * @param pCache The property cache. + * @param pszValue The value to set. A NULL will delete the value. + * @param fFlags Flags to set. + * @param pszPathFormat The path format string. May not be null and has + * to be an absolute path. + * @param ... Format arguments. + */ +int VGSvcPropCacheUpdateByPath(PVBOXSERVICEVEPROPCACHE pCache, const char *pszValue, uint32_t fFlags, + const char *pszPathFormat, ...) +{ + RT_NOREF1(fFlags); + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszPathFormat, VERR_INVALID_POINTER); + + int rc = VERR_NOT_FOUND; + if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect))) + { + /* + * Format the value first. + */ + char *pszPath = NULL; + va_list va; + va_start(va, pszPathFormat); + RTStrAPrintfV(&pszPath, pszPathFormat, va); + va_end(va); + if (!pszPath) + { + rc = VERR_NO_STR_MEMORY; + } + else + { + /* Iterate through all nodes and compare their paths. */ + PVBOXSERVICEVEPROPCACHEENTRY pNodeIt; + RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc) + { + if (RTStrStr(pNodeIt->pszName, pszPath) == pNodeIt->pszName) + { + /** @todo Use some internal function to update the node directly, this is slow atm. */ + rc = VGSvcPropCacheUpdate(pCache, pNodeIt->pszName, pszValue); + } + if (RT_FAILURE(rc)) + break; + } + RTStrFree(pszPath); + } + RTCritSectLeave(&pCache->CritSect); + } + return rc; +} + + +/** + * Flushes the cache by writing every item regardless of its state. + * + * @param pCache The property cache. + */ +int VGSvcPropCacheFlush(PVBOXSERVICEVEPROPCACHE pCache) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect))) + { + PVBOXSERVICEVEPROPCACHEENTRY pNodeIt; + RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc) + { + rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNodeIt->pszName, pNodeIt->fFlags, pNodeIt->pszValue); + if (RT_FAILURE(rc)) + break; + } + RTCritSectLeave(&pCache->CritSect); + } + return rc; +} + + +/** + * Reset all temporary properties and destroy the cache. + * + * @param pCache The property cache. + */ +void VGSvcPropCacheDestroy(PVBOXSERVICEVEPROPCACHE pCache) +{ + AssertPtrReturnVoid(pCache); + Assert(pCache->uClientID); + + /* Lock the cache. */ + int rc = RTCritSectEnter(&pCache->CritSect); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICEVEPROPCACHEENTRY pNode = RTListGetFirst(&pCache->NodeHead, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc); + while (pNode) + { + PVBOXSERVICEVEPROPCACHEENTRY pNext = RTListNodeIsLast(&pCache->NodeHead, &pNode->NodeSucc) + ? NULL : + RTListNodeGetNext(&pNode->NodeSucc, + VBOXSERVICEVEPROPCACHEENTRY, NodeSucc); + RTListNodeRemove(&pNode->NodeSucc); + + if (pNode->fFlags & VGSVCPROPCACHE_FLAGS_TEMPORARY) + rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, pNode->fFlags, pNode->pszValueReset); + + AssertPtr(pNode->pszName); + RTStrFree(pNode->pszName); + RTStrFree(pNode->pszValue); + RTStrFree(pNode->pszValueReset); + pNode->fFlags = 0; + + RTMemFree(pNode); + + pNode = pNext; + } + RTCritSectLeave(&pCache->CritSect); + } + + /* Destroy critical section. */ + RTCritSectDelete(&pCache->CritSect); +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h new file mode 100644 index 00000000..4abc4085 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h @@ -0,0 +1,66 @@ +/* $Id: */ +/** @file + * VBoxServicePropCache - Guest property cache. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxServiceInternal.h" + +#ifdef VBOX_WITH_GUEST_PROPS + +/** @name VGSVCPROPCACHE_FLAG_XXX - Guest Property Cache Flags. + * @{ */ +/** Indicates wheter a guest property is temporary and either should + * - a) get a "reset" value assigned (via VBoxServicePropCacheUpdateEntry) + * as soon as the property cache gets destroyed, or + * - b) get deleted when no reset value is specified. + */ +# define VGSVCPROPCACHE_FLAGS_TEMPORARY RT_BIT(1) +/** Indicates whether a property every time needs to be updated, regardless + * if its real value changed or not. */ +# define VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE RT_BIT(2) +/** The guest property gets deleted when + * - a) the property cache gets destroyed, or + * - b) the VM gets reset / shutdown / destroyed. + */ +# define VGSVCPROPCACHE_FLAGS_TRANSIENT RT_BIT(3) +/** @} */ + +int VGSvcPropCacheCreate(PVBOXSERVICEVEPROPCACHE pCache, uint32_t uClientId); +int VGSvcPropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, uint32_t fFlags, const char *pszValueReset); +int VGSvcPropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, const char *pszValueFormat, ...); +int VGSvcPropCacheUpdateByPath(PVBOXSERVICEVEPROPCACHE pCache, const char *pszValue, uint32_t fFlags, + const char *pszPathFormat, ...); +int VGSvcPropCacheFlush(PVBOXSERVICEVEPROPCACHE pCache); +void VGSvcPropCacheDestroy(PVBOXSERVICEVEPROPCACHE pCache); +#endif /* VBOX_WITH_GUEST_PROPS */ + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h b/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h new file mode 100644 index 00000000..b11e862d --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h @@ -0,0 +1,37 @@ +/* $Id: VBoxServiceResource-win.h $ */ +/** @file + * VBoxService - Guest Additions Service, resource IDs. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define IDI_VIRTUALBOX 101 + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp new file mode 100644 index 00000000..8d68b082 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp @@ -0,0 +1,747 @@ +/* $Id: VBoxServiceStats.cpp $ */ +/** @file + * VBoxStats - Guest statistics notification + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_vgsvc_vmstats VBoxService - VM Statistics + * + * The VM statistics subservice helps out the performance collector API on the + * host side by providing metrics from inside the guest. + * + * See IPerformanceCollector, CollectorGuest and the "Guest/" submetrics that + * gets registered by Machine::i_registerMetrics in Main. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if defined(RT_OS_WINDOWS) +# include <iprt/win/windows.h> +# include <psapi.h> +# include <winternl.h> + +#elif defined(RT_OS_LINUX) +# include <iprt/ctype.h> +# include <iprt/stream.h> +# include <unistd.h> + +#elif defined(RT_OS_SOLARIS) +# include <kstat.h> +# include <sys/sysinfo.h> +# include <unistd.h> +#else +/** @todo port me. */ + +#endif + +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/ldr.h> +#include <VBox/param.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <VBox/err.h> +#include <VBox/VMMDev.h> /* For VMMDevReportGuestStats and indirectly VbglR3StatReport. */ +#include <VBox/VBoxGuestLib.h> + +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct VBOXSTATSCONTEXT +{ + RTMSINTERVAL cMsStatInterval; + + uint64_t au64LastCpuLoad_Idle[VMM_MAX_CPU_COUNT]; + uint64_t au64LastCpuLoad_Kernel[VMM_MAX_CPU_COUNT]; + uint64_t au64LastCpuLoad_User[VMM_MAX_CPU_COUNT]; + uint64_t au64LastCpuLoad_Nice[VMM_MAX_CPU_COUNT]; + +#ifdef RT_OS_WINDOWS + DECLCALLBACKMEMBER_EX(NTSTATUS, WINAPI, pfnNtQuerySystemInformation,(SYSTEM_INFORMATION_CLASS SystemInformationClass, + PVOID SystemInformation, ULONG SystemInformationLength, + PULONG ReturnLength)); + DECLCALLBACKMEMBER_EX(void, WINAPI, pfnGlobalMemoryStatusEx,(LPMEMORYSTATUSEX lpBuffer)); + DECLCALLBACKMEMBER_EX(BOOL, WINAPI, pfnGetPerformanceInfo,(PPERFORMANCE_INFORMATION pPerformanceInformation, DWORD cb)); +#endif +} VBOXSTATSCONTEXT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Global data. */ +static VBOXSTATSCONTEXT g_VMStat = {0}; + +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_VMStatEvent = NIL_RTSEMEVENTMULTI; + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcVMStatsInit(void) +{ + VGSvcVerbose(3, "vgsvcVMStatsInit\n"); + + int rc = RTSemEventMultiCreate(&g_VMStatEvent); + AssertRCReturn(rc, rc); + + g_VMStat.cMsStatInterval = 0; /* default; update disabled */ + RT_ZERO(g_VMStat.au64LastCpuLoad_Idle); + RT_ZERO(g_VMStat.au64LastCpuLoad_Kernel); + RT_ZERO(g_VMStat.au64LastCpuLoad_User); + RT_ZERO(g_VMStat.au64LastCpuLoad_Nice); + + rc = VbglR3StatQueryInterval(&g_VMStat.cMsStatInterval); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsInit: New statistics interval %u seconds\n", g_VMStat.cMsStatInterval); + else + VGSvcVerbose(3, "vgsvcVMStatsInit: DeviceIoControl failed with %d\n", rc); + +#ifdef RT_OS_WINDOWS + /* NtQuerySystemInformation might be dropped in future releases, so load + it dynamically as per Microsoft's recommendation. */ + *(void **)&g_VMStat.pfnNtQuerySystemInformation = RTLdrGetSystemSymbol("ntdll.dll", "NtQuerySystemInformation"); + if (g_VMStat.pfnNtQuerySystemInformation) + VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.pfnNtQuerySystemInformation = %x\n", g_VMStat.pfnNtQuerySystemInformation); + else + { + VGSvcVerbose(3, "vgsvcVMStatsInit: ntdll.NtQuerySystemInformation not found!\n"); + return VERR_SERVICE_DISABLED; + } + + /* GlobalMemoryStatus is win2k and up, so load it dynamically */ + *(void **)&g_VMStat.pfnGlobalMemoryStatusEx = RTLdrGetSystemSymbol("kernel32.dll", "GlobalMemoryStatusEx"); + if (g_VMStat.pfnGlobalMemoryStatusEx) + VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.GlobalMemoryStatusEx = %x\n", g_VMStat.pfnGlobalMemoryStatusEx); + else + { + /** @todo Now fails in NT4; do we care? */ + VGSvcVerbose(3, "vgsvcVMStatsInit: kernel32.GlobalMemoryStatusEx not found!\n"); + return VERR_SERVICE_DISABLED; + } + + /* GetPerformanceInfo is xp and up, so load it dynamically */ + *(void **)&g_VMStat.pfnGetPerformanceInfo = RTLdrGetSystemSymbol("psapi.dll", "GetPerformanceInfo"); + if (g_VMStat.pfnGetPerformanceInfo) + VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.pfnGetPerformanceInfo= %x\n", g_VMStat.pfnGetPerformanceInfo); +#endif /* RT_OS_WINDOWS */ + + return VINF_SUCCESS; +} + + +/** + * Gathers VM statistics and reports them to the host. + */ +static void vgsvcVMStatsReport(void) +{ +#if defined(RT_OS_WINDOWS) + Assert(g_VMStat.pfnGlobalMemoryStatusEx && g_VMStat.pfnNtQuerySystemInformation); + if ( !g_VMStat.pfnGlobalMemoryStatusEx + || !g_VMStat.pfnNtQuerySystemInformation) + return; + + /* Clear the report so we don't report garbage should NtQuerySystemInformation + behave in an unexpected manner. */ + VMMDevReportGuestStats req; + RT_ZERO(req); + + /* Query and report guest statistics */ + SYSTEM_INFO systemInfo; + GetSystemInfo(&systemInfo); + + MEMORYSTATUSEX memStatus; + memStatus.dwLength = sizeof(memStatus); + g_VMStat.pfnGlobalMemoryStatusEx(&memStatus); + + req.guestStats.u32PageSize = systemInfo.dwPageSize; + req.guestStats.u32PhysMemTotal = (uint32_t)(memStatus.ullTotalPhys / _4K); + req.guestStats.u32PhysMemAvail = (uint32_t)(memStatus.ullAvailPhys / _4K); + /* The current size of the committed memory limit, in bytes. This is physical + memory plus the size of the page file, minus a small overhead. */ + req.guestStats.u32PageFileSize = (uint32_t)(memStatus.ullTotalPageFile / _4K) - req.guestStats.u32PhysMemTotal; + req.guestStats.u32MemoryLoad = memStatus.dwMemoryLoad; + req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL + | VBOX_GUEST_STAT_PHYS_MEM_AVAIL + | VBOX_GUEST_STAT_PAGE_FILE_SIZE + | VBOX_GUEST_STAT_MEMORY_LOAD; +# ifdef VBOX_WITH_MEMBALLOON + req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K); + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON; +# else + req.guestStats.u32PhysMemBalloon = 0; +# endif + + if (g_VMStat.pfnGetPerformanceInfo) + { + PERFORMANCE_INFORMATION perfInfo; + + if (g_VMStat.pfnGetPerformanceInfo(&perfInfo, sizeof(perfInfo))) + { + req.guestStats.u32Processes = perfInfo.ProcessCount; + req.guestStats.u32Threads = perfInfo.ThreadCount; + req.guestStats.u32Handles = perfInfo.HandleCount; + req.guestStats.u32MemCommitTotal = perfInfo.CommitTotal; /* already in pages */ + req.guestStats.u32MemKernelTotal = perfInfo.KernelTotal; /* already in pages */ + req.guestStats.u32MemKernelPaged = perfInfo.KernelPaged; /* already in pages */ + req.guestStats.u32MemKernelNonPaged = perfInfo.KernelNonpaged; /* already in pages */ + req.guestStats.u32MemSystemCache = perfInfo.SystemCache; /* already in pages */ + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PROCESSES | VBOX_GUEST_STAT_THREADS | VBOX_GUEST_STAT_HANDLES + | VBOX_GUEST_STAT_MEM_COMMIT_TOTAL | VBOX_GUEST_STAT_MEM_KERNEL_TOTAL + | VBOX_GUEST_STAT_MEM_KERNEL_PAGED | VBOX_GUEST_STAT_MEM_KERNEL_NONPAGED + | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE; + } + else + VGSvcVerbose(3, "vgsvcVMStatsReport: GetPerformanceInfo failed with %d\n", GetLastError()); + } + + /* Query CPU load information */ + uint32_t cbStruct = systemInfo.dwNumberOfProcessors * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION); + PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION pProcInfo; + pProcInfo = (PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)RTMemAlloc(cbStruct); + if (!pProcInfo) + return; + + /* Unfortunately GetSystemTimes is XP SP1 and up only, so we need to use the semi-undocumented NtQuerySystemInformation */ + bool fCpuInfoAvail = false; + DWORD cbReturned; + NTSTATUS rcNt = g_VMStat.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned); + if ( !rcNt + && cbReturned == cbStruct) + { + for (uint32_t i = 0; i < systemInfo.dwNumberOfProcessors; i++) + { + if (i >= VMM_MAX_CPU_COUNT) + { + VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPUs %u..%u\n", i, systemInfo.dwNumberOfProcessors); + break; + } + + if (g_VMStat.au64LastCpuLoad_Kernel[i] == 0) + { + /* first time */ + g_VMStat.au64LastCpuLoad_Idle[i] = pProcInfo[i].IdleTime.QuadPart; + g_VMStat.au64LastCpuLoad_Kernel[i] = pProcInfo[i].KernelTime.QuadPart; + g_VMStat.au64LastCpuLoad_User[i] = pProcInfo[i].UserTime.QuadPart; + + Sleep(250); + + rcNt = g_VMStat.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned); + Assert(!rcNt); + } + + uint64_t deltaIdle = (pProcInfo[i].IdleTime.QuadPart - g_VMStat.au64LastCpuLoad_Idle[i]); + uint64_t deltaKernel = (pProcInfo[i].KernelTime.QuadPart - g_VMStat.au64LastCpuLoad_Kernel[i]); + uint64_t deltaUser = (pProcInfo[i].UserTime.QuadPart - g_VMStat.au64LastCpuLoad_User[i]); + deltaKernel -= deltaIdle; /* idle time is added to kernel time */ + uint64_t ullTotalTime = deltaIdle + deltaKernel + deltaUser; + if (ullTotalTime == 0) /* Prevent division through zero. */ + ullTotalTime = 1; + + req.guestStats.u32CpuLoad_Idle = (uint32_t)(deltaIdle * 100 / ullTotalTime); + req.guestStats.u32CpuLoad_Kernel = (uint32_t)(deltaKernel* 100 / ullTotalTime); + req.guestStats.u32CpuLoad_User = (uint32_t)(deltaUser * 100 / ullTotalTime); + + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE + | VBOX_GUEST_STAT_CPU_LOAD_KERNEL + | VBOX_GUEST_STAT_CPU_LOAD_USER; + req.guestStats.u32CpuId = i; + fCpuInfoAvail = true; + int rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", i); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: VbglR3StatReport failed with rc=%Rrc\n", rc); + + g_VMStat.au64LastCpuLoad_Idle[i] = pProcInfo[i].IdleTime.QuadPart; + g_VMStat.au64LastCpuLoad_Kernel[i] = pProcInfo[i].KernelTime.QuadPart; + g_VMStat.au64LastCpuLoad_User[i] = pProcInfo[i].UserTime.QuadPart; + } + } + RTMemFree(pProcInfo); + + if (!fCpuInfoAvail) + { + VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n"); + int rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n"); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); + } + +#elif defined(RT_OS_LINUX) + VMMDevReportGuestStats req; + RT_ZERO(req); + PRTSTREAM pStrm; + char szLine[256]; + char *psz; + + int rc = RTStrmOpen("/proc/meminfo", "r", &pStrm); + if (RT_SUCCESS(rc)) + { + uint64_t u64Kb; + uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0; + for (;;) + { + rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine)); + if (RT_FAILURE(rc)) + break; + if (strstr(szLine, "MemTotal:") == szLine) + { + rc = RTStrToUInt64Ex(RTStrStripL(&szLine[9]), &psz, 0, &u64Kb); + if (RT_SUCCESS(rc)) + u64Total = u64Kb * _1K; + } + else if (strstr(szLine, "MemFree:") == szLine) + { + rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb); + if (RT_SUCCESS(rc)) + u64Free = u64Kb * _1K; + } + else if (strstr(szLine, "Buffers:") == szLine) + { + rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb); + if (RT_SUCCESS(rc)) + u64Buffers = u64Kb * _1K; + } + else if (strstr(szLine, "Cached:") == szLine) + { + rc = RTStrToUInt64Ex(RTStrStripL(&szLine[7]), &psz, 0, &u64Kb); + if (RT_SUCCESS(rc)) + u64Cached = u64Kb * _1K; + } + else if (strstr(szLine, "SwapTotal:") == szLine) + { + rc = RTStrToUInt64Ex(RTStrStripL(&szLine[10]), &psz, 0, &u64Kb); + if (RT_SUCCESS(rc)) + u64PagedTotal = u64Kb * _1K; + } + } + req.guestStats.u32PhysMemTotal = u64Total / _4K; + req.guestStats.u32PhysMemAvail = (u64Free + u64Buffers + u64Cached) / _4K; + req.guestStats.u32MemSystemCache = (u64Buffers + u64Cached) / _4K; + req.guestStats.u32PageFileSize = u64PagedTotal / _4K; + RTStrmClose(pStrm); + } + else + VGSvcVerbose(3, "vgsvcVMStatsReport: memory info not available!\n"); + + req.guestStats.u32PageSize = getpagesize(); + req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL + | VBOX_GUEST_STAT_PHYS_MEM_AVAIL + | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE + | VBOX_GUEST_STAT_PAGE_FILE_SIZE; +# ifdef VBOX_WITH_MEMBALLOON + req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K); + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON; +# else + req.guestStats.u32PhysMemBalloon = 0; +# endif + + + /** @todo req.guestStats.u32Threads */ + /** @todo req.guestStats.u32Processes */ + /* req.guestStats.u32Handles doesn't make sense here. */ + /** @todo req.guestStats.u32MemoryLoad */ + /** @todo req.guestStats.u32MemCommitTotal */ + /** @todo req.guestStats.u32MemKernelTotal */ + /** @todo req.guestStats.u32MemKernelPaged, make any sense? = u32MemKernelTotal? */ + /** @todo req.guestStats.u32MemKernelNonPaged, make any sense? = 0? */ + + bool fCpuInfoAvail = false; + rc = RTStrmOpen("/proc/stat", "r", &pStrm); + if (RT_SUCCESS(rc)) + { + for (;;) + { + rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine)); + if (RT_FAILURE(rc)) + break; + if ( strstr(szLine, "cpu") == szLine + && strlen(szLine) > 3 + && RT_C_IS_DIGIT(szLine[3])) + { + uint32_t u32CpuId; + rc = RTStrToUInt32Ex(&szLine[3], &psz, 0, &u32CpuId); + if (u32CpuId < VMM_MAX_CPU_COUNT) + { + uint64_t u64User = 0; + if (RT_SUCCESS(rc)) + rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64User); + + uint64_t u64Nice = 0; + if (RT_SUCCESS(rc)) + rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Nice); + + uint64_t u64System = 0; + if (RT_SUCCESS(rc)) + rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64System); + + uint64_t u64Idle = 0; + if (RT_SUCCESS(rc)) + rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Idle); + + uint64_t u64DeltaIdle = u64Idle - g_VMStat.au64LastCpuLoad_Idle[u32CpuId]; + uint64_t u64DeltaSystem = u64System - g_VMStat.au64LastCpuLoad_Kernel[u32CpuId]; + uint64_t u64DeltaUser = u64User - g_VMStat.au64LastCpuLoad_User[u32CpuId]; + uint64_t u64DeltaNice = u64Nice - g_VMStat.au64LastCpuLoad_Nice[u32CpuId]; + + uint64_t u64DeltaAll = u64DeltaIdle + + u64DeltaSystem + + u64DeltaUser + + u64DeltaNice; + if (u64DeltaAll == 0) /* Prevent division through zero. */ + u64DeltaAll = 1; + + g_VMStat.au64LastCpuLoad_Idle[u32CpuId] = u64Idle; + g_VMStat.au64LastCpuLoad_Kernel[u32CpuId] = u64System; + g_VMStat.au64LastCpuLoad_User[u32CpuId] = u64User; + g_VMStat.au64LastCpuLoad_Nice[u32CpuId] = u64Nice; + + req.guestStats.u32CpuLoad_Idle = (uint32_t)(u64DeltaIdle * 100 / u64DeltaAll); + req.guestStats.u32CpuLoad_Kernel = (uint32_t)(u64DeltaSystem * 100 / u64DeltaAll); + req.guestStats.u32CpuLoad_User = (uint32_t)((u64DeltaUser + + u64DeltaNice) * 100 / u64DeltaAll); + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE + | VBOX_GUEST_STAT_CPU_LOAD_KERNEL + | VBOX_GUEST_STAT_CPU_LOAD_USER; + req.guestStats.u32CpuId = u32CpuId; + fCpuInfoAvail = true; + rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", u32CpuId); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); + } + else + VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPU%u\n", u32CpuId); + } + } + RTStrmClose(pStrm); + } + if (!fCpuInfoAvail) + { + VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n"); + rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n"); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); + } + +#elif defined(RT_OS_SOLARIS) + VMMDevReportGuestStats req; + RT_ZERO(req); + kstat_ctl_t *pStatKern = kstat_open(); + if (pStatKern) + { + /* + * Memory statistics. + */ + uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0; + int rc = -1; + kstat_t *pStatPages = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"system_pages"); + if (pStatPages) + { + rc = kstat_read(pStatKern, pStatPages, NULL /* optional-copy-buf */); + if (rc != -1) + { + kstat_named_t *pStat = NULL; + pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"pagestotal"); + if (pStat) + u64Total = pStat->value.ul; + + pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"freemem"); + if (pStat) + u64Free = pStat->value.ul; + } + } + + kstat_t *pStatZFS = kstat_lookup(pStatKern, (char *)"zfs", 0 /* instance */, (char *)"arcstats"); + if (pStatZFS) + { + rc = kstat_read(pStatKern, pStatZFS, NULL /* optional-copy-buf */); + if (rc != -1) + { + kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(pStatZFS, (char *)"size"); + if (pStat) + u64Cached = pStat->value.ul; + } + } + + /* + * The vminfo are accumulative counters updated every "N" ticks. Let's get the + * number of stat updates so far and use that to divide the swap counter. + */ + kstat_t *pStatInfo = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"sysinfo"); + if (pStatInfo) + { + sysinfo_t SysInfo; + rc = kstat_read(pStatKern, pStatInfo, &SysInfo); + if (rc != -1) + { + kstat_t *pStatVMInfo = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"vminfo"); + if (pStatVMInfo) + { + vminfo_t VMInfo; + rc = kstat_read(pStatKern, pStatVMInfo, &VMInfo); + if (rc != -1) + { + Assert(SysInfo.updates != 0); + u64PagedTotal = VMInfo.swap_avail / SysInfo.updates; + } + } + } + } + + req.guestStats.u32PhysMemTotal = u64Total; /* already in pages */ + req.guestStats.u32PhysMemAvail = u64Free; /* already in pages */ + req.guestStats.u32MemSystemCache = u64Cached / _4K; + req.guestStats.u32PageFileSize = u64PagedTotal; /* already in pages */ + /** @todo req.guestStats.u32Threads */ + /** @todo req.guestStats.u32Processes */ + /** @todo req.guestStats.u32Handles -- ??? */ + /** @todo req.guestStats.u32MemoryLoad */ + /** @todo req.guestStats.u32MemCommitTotal */ + /** @todo req.guestStats.u32MemKernelTotal */ + /** @todo req.guestStats.u32MemKernelPaged */ + /** @todo req.guestStats.u32MemKernelNonPaged */ + req.guestStats.u32PageSize = getpagesize(); + + req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL + | VBOX_GUEST_STAT_PHYS_MEM_AVAIL + | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE + | VBOX_GUEST_STAT_PAGE_FILE_SIZE; +# ifdef VBOX_WITH_MEMBALLOON + req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K); + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON; +# else + req.guestStats.u32PhysMemBalloon = 0; +# endif + + /* + * CPU statistics. + */ + cpu_stat_t StatCPU; + RT_ZERO(StatCPU); + kstat_t *pStatNode = NULL; + uint32_t cCPUs = 0; + bool fCpuInfoAvail = false; + for (pStatNode = pStatKern->kc_chain; pStatNode != NULL; pStatNode = pStatNode->ks_next) + { + if (!strcmp(pStatNode->ks_module, "cpu_stat")) + { + rc = kstat_read(pStatKern, pStatNode, &StatCPU); + if (rc == -1) + break; + + if (cCPUs < VMM_MAX_CPU_COUNT) + { + uint64_t u64Idle = StatCPU.cpu_sysinfo.cpu[CPU_IDLE]; + uint64_t u64User = StatCPU.cpu_sysinfo.cpu[CPU_USER]; + uint64_t u64System = StatCPU.cpu_sysinfo.cpu[CPU_KERNEL]; + + uint64_t u64DeltaIdle = u64Idle - g_VMStat.au64LastCpuLoad_Idle[cCPUs]; + uint64_t u64DeltaSystem = u64System - g_VMStat.au64LastCpuLoad_Kernel[cCPUs]; + uint64_t u64DeltaUser = u64User - g_VMStat.au64LastCpuLoad_User[cCPUs]; + + uint64_t u64DeltaAll = u64DeltaIdle + u64DeltaSystem + u64DeltaUser; + if (u64DeltaAll == 0) /* Prevent division through zero. */ + u64DeltaAll = 1; + + g_VMStat.au64LastCpuLoad_Idle[cCPUs] = u64Idle; + g_VMStat.au64LastCpuLoad_Kernel[cCPUs] = u64System; + g_VMStat.au64LastCpuLoad_User[cCPUs] = u64User; + + req.guestStats.u32CpuId = cCPUs; + req.guestStats.u32CpuLoad_Idle = (uint32_t)(u64DeltaIdle * 100 / u64DeltaAll); + req.guestStats.u32CpuLoad_Kernel = (uint32_t)(u64DeltaSystem * 100 / u64DeltaAll); + req.guestStats.u32CpuLoad_User = (uint32_t)(u64DeltaUser * 100 / u64DeltaAll); + + req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE + | VBOX_GUEST_STAT_CPU_LOAD_KERNEL + | VBOX_GUEST_STAT_CPU_LOAD_USER; + fCpuInfoAvail = true; + rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", cCPUs); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); + cCPUs++; + } + else + VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPU%u\n", cCPUs); + } + } + + /* + * Report whatever statistics were collected. + */ + if (!fCpuInfoAvail) + { + VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n"); + rc = VbglR3StatReport(&req); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n"); + else + VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); + } + + kstat_close(pStatKern); + } + +#else + /** @todo implement for other platforms. */ + +#endif +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +DECLCALLBACK(int) vgsvcVMStatsWorker(bool volatile *pfShutdown) +{ + int rc = VINF_SUCCESS; + + /* Start monitoring of the stat event change event. */ + rc = VbglR3CtlFilterMask(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0); + if (RT_FAILURE(rc)) + { + VGSvcVerbose(3, "vgsvcVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc); + return rc; + } + + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Now enter the loop retrieving runtime data continuously. + */ + for (;;) + { + uint32_t fEvents = 0; + RTMSINTERVAL cWaitMillies; + + /* Check if an update interval change is pending. */ + rc = VbglR3WaitEvent(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0 /* no wait */, &fEvents); + if ( RT_SUCCESS(rc) + && (fEvents & VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST)) + VbglR3StatQueryInterval(&g_VMStat.cMsStatInterval); + + if (g_VMStat.cMsStatInterval) + { + vgsvcVMStatsReport(); + cWaitMillies = g_VMStat.cMsStatInterval; + } + else + cWaitMillies = 3000; + + /* + * Block for a while. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + int rc2 = RTSemEventMultiWait(g_VMStatEvent, cWaitMillies); + if (*pfShutdown) + break; + if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) + { + VGSvcError("vgsvcVMStatsWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); + rc = rc2; + break; + } + } + + /* Cancel monitoring of the stat event change event. */ + rc = VbglR3CtlFilterMask(0, VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST); + if (RT_FAILURE(rc)) + VGSvcVerbose(3, "vgsvcVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc); + + VGSvcVerbose(3, "VBoxStatsThread: finished statistics change request thread\n"); + return 0; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcVMStatsStop(void) +{ + RTSemEventMultiSignal(g_VMStatEvent); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcVMStatsTerm(void) +{ + if (g_VMStatEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_VMStatEvent); + g_VMStatEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'vminfo' service description. + */ +VBOXSERVICE g_VMStatistics = +{ + /* pszName. */ + "vmstats", + /* pszDescription. */ + "Virtual Machine Statistics", + /* pszUsage. */ + NULL, + /* pszOptions. */ + NULL, + /* methods */ + VGSvcDefaultPreInit, + VGSvcDefaultOption, + vgsvcVMStatsInit, + vgsvcVMStatsWorker, + vgsvcVMStatsStop, + vgsvcVMStatsTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp new file mode 100644 index 00000000..ae5dd69b --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp @@ -0,0 +1,807 @@ +/* $Id: VBoxServiceTimeSync.cpp $ */ +/** @file + * VBoxService - Guest Additions TimeSync Service. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc_timesync VBoxService - The Time Sync Service + * + * The time sync subservice synchronizes the guest OS walltime with the host. + * + * The time sync service plays along with the Time Manager (TM) in the VMM + * to keep the guest time accurate using the host machine as a reference. + * Communication is facilitated by VMMDev. TM will try its best to make sure + * all timer ticks get delivered so that there isn't normally any need to + * adjust the guest time. + * + * There are three normal (= acceptable) cases: + * -# When the service starts up. This is because ticks and such might + * be lost during VM and OS startup. (Need to figure out exactly why!) + * -# When the TM is unable to deliver all the ticks and swallows a + * backlog of ticks. The threshold for this is configurable with + * a default of 60 seconds. + * -# The time is adjusted on the host. This can be caused manually by + * the user or by some time sync daemon (NTP, LAN server, etc.). + * + * There are a number of very odd case where adjusting is needed. Here + * are some of them: + * -# Timer device emulation inaccuracies (like rounding). + * -# Inaccuracies in time source VirtualBox uses. + * -# The Guest and/or Host OS doesn't perform proper time keeping. This + * can come about as a result of OS and/or hardware issues. + * + * The TM is our source for the host time and will make adjustments for + * current timer delivery lag. The simplistic approach taken by TM is to + * adjust the host time by the current guest timer delivery lag, meaning that + * if the guest is behind 1 second with PIT/RTC/++ ticks, this should be + * reflected in the guest wall time as well. + * + * Now, there is any amount of trouble we can cause by changing the time. + * Most applications probably use the wall time when they need to measure + * things. A walltime that is being juggled about every so often, even if just + * a little bit, could occasionally upset these measurements by for instance + * yielding negative results. + * + * This bottom line here is that the time sync service isn't really supposed + * to do anything and will try avoid having to do anything when possible. + * + * The implementation uses the latency it takes to query host time as the + * absolute maximum precision to avoid messing up under timer tick catchup + * and/or heavy host/guest load. (Rationale is that a *lot* of stuff may + * happen on our way back from ring-3 and TM/VMMDev since we're taking the + * route thru the inner EM loop with its force flag processing.) + * + * But this latency has to be measured from our perspective, which means it + * could just as easily come out as 0. (OS/2 and Windows guests only update + * the current time when the timer ticks for instance.) The good thing is + * that this isn't really a problem since we won't ever do anything unless + * the drift is noticeable. + * + * It now boils down to these three (configuration) factors: + * -# g_cMsTimeSyncMinAdjust - The minimum drift we will ever bother with. + * -# g_TimeSyncLatencyFactor - The factor we multiply the latency by to + * calculate the dynamic minimum adjust factor. + * -# g_cMsTimeSyncMaxLatency - When to start discarding the data as utterly + * useless and take a rest (someone is too busy to give us good data). + * -# g_TimeSyncSetThreshold - The threshold at which we will just set the time + * instead of trying to adjust it (milliseconds). + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#else +# include <unistd.h> +# include <errno.h> +# include <time.h> +# include <sys/time.h> +#endif + +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <VBox/err.h> +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The timesync interval (milliseconds). */ +static uint32_t g_TimeSyncInterval = 0; +/** + * @see pg_vgsvc_timesync + * + * @remark OS/2: There is either a 1 second resolution on the DosSetDateTime + * API or a bug in my settimeofday implementation. Thus, don't + * bother unless there is at least a 1 second drift. + */ +#ifdef RT_OS_OS2 +static uint32_t g_cMsTimeSyncMinAdjust = 1000; +#else +static uint32_t g_cMsTimeSyncMinAdjust = 100; +#endif +/** @see pg_vgsvc_timesync */ +static uint32_t g_TimeSyncLatencyFactor = 8; +/** @see pg_vgsvc_timesync */ +static uint32_t g_cMsTimeSyncMaxLatency = 250; +/** @see pg_vgsvc_timesync */ +static uint32_t g_TimeSyncSetThreshold = 20*60*1000; +/** Whether the next adjustment should just set the time instead of trying to + * adjust it. This is used to implement --timesync-set-start. + * For purposes of setting the kernel timezone, OS/2 always starts with this. */ +#ifdef RT_OS_OS2 +static bool volatile g_fTimeSyncSetOnStart = true; +#else +static bool volatile g_fTimeSyncSetOnStart = false; +#endif +/** Whether to set the time when the VM was restored. */ +static bool g_fTimeSyncSetOnRestore = true; +/** The logging verbosity level. + * This uses the global verbosity level by default. */ +static uint32_t g_cTimeSyncVerbosity = 0; + +/** Current error count. Used to decide when to bitch and when not to. */ +static uint32_t g_cTimeSyncErrors = 0; + +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_TimeSyncEvent = NIL_RTSEMEVENTMULTI; + +/** The VM session ID. Changes whenever the VM is restored or reset. */ +static uint64_t g_idTimeSyncSession; + +#ifdef RT_OS_WINDOWS +/** Process token. */ +static HANDLE g_hTokenProcess = NULL; +/** Old token privileges. */ +static TOKEN_PRIVILEGES g_TkOldPrivileges; +/** Backup values for time adjustment. */ +static DWORD g_dwWinTimeAdjustment; +static DWORD g_dwWinTimeIncrement; +static BOOL g_bWinTimeAdjustmentDisabled; +#endif + + +/** + * @interface_method_impl{VBOXSERVICE,pfnPreInit} + */ +static DECLCALLBACK(int) vgsvcTimeSyncPreInit(void) +{ + /* Use global verbosity as default. */ + g_cTimeSyncVerbosity = g_cVerbosity; + +#ifdef VBOX_WITH_GUEST_PROPS + /** @todo Merge this function with vgsvcTimeSyncOption() to generalize + * the "command line args override guest property values" behavior. */ + + /* + * Read the service options from the VM's guest properties. + * Note that these options can be overridden by the command line options later. + */ + uint32_t uGuestPropSvcClientID; + int rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID); + if (RT_FAILURE(rc)) + { + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ + { + VGSvcVerbose(0, "VMInfo: Guest property service is not available, skipping\n"); + rc = VINF_SUCCESS; + } + else + VGSvcError("Failed to connect to the guest property service! Error: %Rrc\n", rc); + } + else + { + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-interval", + &g_TimeSyncInterval, 50, UINT32_MAX - 1); + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-min-adjust", + &g_cMsTimeSyncMinAdjust, 0, 3600000); + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-latency-factor", + &g_TimeSyncLatencyFactor, 1, 1024); + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-max-latency", + &g_cMsTimeSyncMaxLatency, 1, 3600000); + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold", + &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000 /* a week */); + + if (VbglR3GuestPropExist(uGuestPropSvcClientID, + "/VirtualBox/GuestAdd/VBoxService/--timesync-set-start")) + g_fTimeSyncSetOnStart = true; + + if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-start")) + g_fTimeSyncSetOnStart = false; + + + if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-on-restore")) + g_fTimeSyncSetOnRestore = true; + + if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-on-restore")) + g_fTimeSyncSetOnRestore = false; + + uint32_t uValue; + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-verbosity", + &uValue, 0 /*uMin*/, 255 /*uMax*/); + if (RT_SUCCESS(rc)) + g_cTimeSyncVerbosity = uValue; + + VbglR3GuestPropDisconnect(uGuestPropSvcClientID); + } + + if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */ + rc = VINF_SUCCESS; + return rc; +#else + /* Nothing to do here yet. */ + return VINF_SUCCESS; +#endif +} + + +/** + * Displays a verbose message based on the currently + * set timesync verbosity level. + * + * @param iLevel Minimum log level required to display this message. + * @param pszFormat The message text. + * @param ... Format arguments. + */ +static void vgsvcTimeSyncLog(unsigned iLevel, const char *pszFormat, ...) +{ + if (iLevel <= g_cTimeSyncVerbosity) + { + va_list va; + va_start(va, pszFormat); + VGSvcLogV(pszFormat, va); + va_end(va); + } +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption} + */ +static DECLCALLBACK(int) vgsvcTimeSyncOption(const char **ppszShort, int argc, char **argv, int *pi) +{ + int rc = VINF_SUCCESS; + if (ppszShort) + rc = -1 ;/* no short options */ + else if (!strcmp(argv[*pi], "--timesync-interval")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncInterval, 50, UINT32_MAX - 1); + else if (!strcmp(argv[*pi], "--timesync-min-adjust")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMinAdjust, 0, 3600000); + else if (!strcmp(argv[*pi], "--timesync-latency-factor")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncLatencyFactor, 1, 1024); + else if (!strcmp(argv[*pi], "--timesync-max-latency")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMaxLatency, 1, 3600000); + else if (!strcmp(argv[*pi], "--timesync-set-threshold")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000); /* a week */ + else if (!strcmp(argv[*pi], "--timesync-set-start")) + g_fTimeSyncSetOnStart = true; + else if (!strcmp(argv[*pi], "--timesync-no-set-start")) + g_fTimeSyncSetOnStart = false; + else if (!strcmp(argv[*pi], "--timesync-set-on-restore")) + g_fTimeSyncSetOnRestore = true; + else if (!strcmp(argv[*pi], "--timesync-no-set-on-restore")) + g_fTimeSyncSetOnRestore = false; + else if (!strcmp(argv[*pi], "--timesync-verbosity")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cTimeSyncVerbosity, 0 /*uMin*/, 255 /*uMax*/); + else + rc = -1; + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcTimeSyncInit(void) +{ + /* + * If not specified, find the right interval default. + * Then create the event sem to block on. + */ + if (!g_TimeSyncInterval) + g_TimeSyncInterval = g_DefaultInterval * 1000; + if (!g_TimeSyncInterval) + g_TimeSyncInterval = 10 * 1000; + + VbglR3GetSessionId(&g_idTimeSyncSession); + /* The status code is ignored as this information is not available with VBox < 3.2.10. */ + + int rc = RTSemEventMultiCreate(&g_TimeSyncEvent); + AssertRC(rc); +#ifdef RT_OS_WINDOWS + if (RT_SUCCESS(rc)) + { + /* + * Adjust privileges of this process so we can make system time adjustments. + */ + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &g_hTokenProcess)) + { + TOKEN_PRIVILEGES tkPriv; + RT_ZERO(tkPriv); + tkPriv.PrivilegeCount = 1; + tkPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &tkPriv.Privileges[0].Luid)) + { + DWORD cbRet = sizeof(g_TkOldPrivileges); + if (AdjustTokenPrivileges(g_hTokenProcess, FALSE, &tkPriv, sizeof(TOKEN_PRIVILEGES), &g_TkOldPrivileges, &cbRet)) + rc = VINF_SUCCESS; + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Adjusting token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", + dwErr, rc); + } + } + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Looking up token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", + dwErr, rc); + } + if (RT_FAILURE(rc)) + { + CloseHandle(g_hTokenProcess); + g_hTokenProcess = NULL; + } + } + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Opening process token (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", + dwErr, rc); + g_hTokenProcess = NULL; + } + } + + if (g_pfnGetSystemTimeAdjustment) + { + if (g_pfnGetSystemTimeAdjustment(&g_dwWinTimeAdjustment, &g_dwWinTimeIncrement, &g_bWinTimeAdjustmentDisabled)) + vgsvcTimeSyncLog(0, "vgsvcTimeSyncInit: Initially %ld (100ns) units per %ld (100 ns) units interval, disabled=%d\n", + g_dwWinTimeAdjustment, g_dwWinTimeIncrement, g_bWinTimeAdjustmentDisabled ? 1 : 0); + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Could not get time adjustment values! Last error: %ld!\n", dwErr); + } + } +#endif /* RT_OS_WINDOWS */ + + return rc; +} + + +/** + * Try adjusting the time using adjtime or similar. + * + * @returns true on success, false on failure. + * + * @param pDrift The time adjustment. + */ +static bool vgsvcTimeSyncAdjust(PCRTTIMESPEC pDrift) +{ +#ifdef RT_OS_WINDOWS +/** @todo r=bird: g_hTokenProcess cannot be NULL here. + * vgsvcTimeSyncInit will fail and the service will not be started with + * it being NULL. vgsvcTimeSyncInit OTOH will *NOT* be called until the + * service thread has terminated. If anything + * else is the case, there is buggy code somewhere.*/ + if (g_hTokenProcess == NULL) /* Is the token already closed when shutting down? */ + return false; + + /* The API appeared in NT 3.50. */ + if ( !g_pfnSetSystemTimeAdjustment + || !g_pfnGetSystemTimeAdjustment) + return false; + + DWORD dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwWinTimeIncrement; + BOOL fWinTimeAdjustmentDisabled; + if (g_pfnGetSystemTimeAdjustment(&dwWinTimeAdjustment, &dwWinTimeIncrement, &fWinTimeAdjustmentDisabled)) + { + DWORD dwDiffMax = g_dwWinTimeAdjustment * 0.50; + DWORD dwDiffNew = dwWinTimeAdjustment * 0.10; + + if (RTTimeSpecGetMilli(pDrift) > 0) + { + dwWinNewTimeAdjustment = dwWinTimeAdjustment + dwDiffNew; + if (dwWinNewTimeAdjustment > (g_dwWinTimeAdjustment + dwDiffMax)) + { + dwWinNewTimeAdjustment = g_dwWinTimeAdjustment + dwDiffMax; + dwDiffNew = dwDiffMax; + } + } + else + { + dwWinNewTimeAdjustment = dwWinTimeAdjustment - dwDiffNew; + if (dwWinNewTimeAdjustment < (g_dwWinTimeAdjustment - dwDiffMax)) + { + dwWinNewTimeAdjustment = g_dwWinTimeAdjustment - dwDiffMax; + dwDiffNew = dwDiffMax; + } + } + + vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: Drift=%lldms\n", RTTimeSpecGetMilli(pDrift)); + vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: OrgTA=%ld, CurTA=%ld, NewTA=%ld, DiffNew=%ld, DiffMax=%ld\n", + g_dwWinTimeAdjustment, dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwDiffNew, dwDiffMax); + if (g_pfnSetSystemTimeAdjustment(dwWinNewTimeAdjustment, FALSE /* Periodic adjustments enabled. */)) + { + g_cTimeSyncErrors = 0; + return true; + } + + if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncAdjust: SetSystemTimeAdjustment failed, error=%u\n", GetLastError()); + } + else if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncAdjust: GetSystemTimeAdjustment failed, error=%ld\n", GetLastError()); + +#elif defined(RT_OS_OS2) || defined(RT_OS_HAIKU) + /* No API for doing gradual time adjustments. */ + +#else /* PORTME */ + /* + * Try using adjtime(), most unix-like systems have this. + */ + struct timeval tv; + RTTimeSpecGetTimeval(pDrift, &tv); + if (adjtime(&tv, NULL) == 0) + { + vgsvcTimeSyncLog(1, "vgsvcTimeSyncAdjust: adjtime by %RDtimespec\n", pDrift); + g_cTimeSyncErrors = 0; + return true; + } +#endif + + /* failed */ + return false; +} + + +/** + * Cancels any pending time adjustment. + * + * Called when we've caught up and before calls to vgsvcTimeSyncSet. + */ +static void vgsvcTimeSyncCancelAdjust(void) +{ +#ifdef RT_OS_WINDOWS +/** @todo r=bird: g_hTokenProcess cannot be NULL here. See argumentation in + * vgsvcTimeSyncAdjust. */ + if (g_hTokenProcess == NULL) /* No process token (anymore)? */ + return; + if (!g_pfnSetSystemTimeAdjustment) + return; + if (g_pfnSetSystemTimeAdjustment(0, TRUE /* Periodic adjustments disabled. */)) + vgsvcTimeSyncLog(5, "vgsvcTimeSyncCancelAdjust: Windows Time Adjustment is now disabled.\n"); + else if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncCancelAdjust: SetSystemTimeAdjustment(,disable) failed, error=%u\n", GetLastError()); +#endif /* !RT_OS_WINDOWS */ +} + + +/** + * Set the wall clock to compensate for drift. + * + * @param pDrift The time adjustment. + */ +static void vgsvcTimeSyncSet(PCRTTIMESPEC pDrift) +{ + /* + * Query the current time, adjust it by adding the drift and set it. + */ + RTTIMESPEC NewGuestTime; + int rc = RTTimeSet(RTTimeSpecAdd(RTTimeNow(&NewGuestTime), pDrift)); + if (RT_SUCCESS(rc)) + { + /* Succeeded - reset the error count and log the change. */ + g_cTimeSyncErrors = 0; + + if (g_cTimeSyncVerbosity >= 1) + { + char sz[64]; + RTTIME Time; + vgsvcTimeSyncLog(1, "time set to %s\n", RTTimeToString(RTTimeExplode(&Time, &NewGuestTime), sz, sizeof(sz))); +#ifdef DEBUG + RTTIMESPEC Tmp; + vgsvcTimeSyncLog(3, " now %s\n", RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&Tmp)), sz, sizeof(sz))); +#endif + } + } + else if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncSet: RTTimeSet(%RDtimespec) failed: %Rrc\n", &NewGuestTime, rc); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +DECLCALLBACK(int) vgsvcTimeSyncWorker(bool volatile *pfShutdown) +{ + RTTIME Time; + int rc = VINF_SUCCESS; + + /* + * Tell the control thread that it can continue spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Initialize the last host and guest times to prevent log message. + * We also track whether we set the time in the previous loop. + */ + RTTIMESPEC HostLast; + if (RT_FAILURE(VbglR3GetHostTime(&HostLast))) + RTTimeSpecSetNano(&HostLast, 0); + RTTIMESPEC GuestLast; + RTTimeNow(&GuestLast); + bool fSetTimeLastLoop = false; + + /* + * The Work Loop. + */ + for (;;) + { + /* + * Try to get a reliable time reading. + */ + int cTries = 3; + do + { + /* + * Query the session id (first to keep lantency low) and the time. + */ + uint64_t idNewSession = g_idTimeSyncSession; + if (g_fTimeSyncSetOnRestore) + VbglR3GetSessionId(&idNewSession); + + RTTIMESPEC GuestNow0; + RTTimeNow(&GuestNow0); + + RTTIMESPEC HostNow; + int rc2 = VbglR3GetHostTime(&HostNow); + if (RT_FAILURE(rc2)) + { + if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncWorker: VbglR3GetHostTime failed; rc2=%Rrc\n", rc2); + break; + } + + RTTIMESPEC GuestNow; + RTTimeNow(&GuestNow); + + /* + * Calc latency and check if it's ok. + */ + RTTIMESPEC GuestElapsed = GuestNow; + RTTimeSpecSub(&GuestElapsed, &GuestNow0); + if ((uint32_t)RTTimeSpecGetMilli(&GuestElapsed) < g_cMsTimeSyncMaxLatency) + { + /* + * If we were just restored, set the adjustment threshold to zero to force a resync. + */ + uint32_t TimeSyncSetThreshold = g_TimeSyncSetThreshold; + if ( g_fTimeSyncSetOnRestore + && idNewSession != g_idTimeSyncSession) + { + vgsvcTimeSyncLog(2, "vgsvcTimeSyncWorker: The VM session ID changed, forcing resync.\n"); + g_idTimeSyncSession = idNewSession; + TimeSyncSetThreshold = 0; + } + + /* + * Calculate the adjustment threshold and the current drift. + */ + uint32_t MinAdjust = RTTimeSpecGetMilli(&GuestElapsed) * g_TimeSyncLatencyFactor; + if (MinAdjust < g_cMsTimeSyncMinAdjust) + MinAdjust = g_cMsTimeSyncMinAdjust; + + RTTIMESPEC Drift = HostNow; + RTTimeSpecSub(&Drift, &GuestNow); + if (RTTimeSpecGetMilli(&Drift) < 0) + MinAdjust += g_cMsTimeSyncMinAdjust; /* extra buffer against moving time backwards. */ + + RTTIMESPEC AbsDrift = Drift; + RTTimeSpecAbsolute(&AbsDrift); + + if (g_cTimeSyncVerbosity >= 4) + { + char sz1[64]; + char sz2[64]; + vgsvcTimeSyncLog(4, "vgsvcTimeSyncWorker: Host: %s (MinAdjust: %RU32 ms), Guest: %s => %RDtimespec drift\n", + RTTimeToString(RTTimeExplode(&Time, &HostNow), sz1, sizeof(sz1)), MinAdjust, + RTTimeToString(RTTimeExplode(&Time, &GuestNow), sz2, sizeof(sz2)), &Drift); + } + + bool fSetTimeInThisLoop = false; + uint64_t AbsDriftMilli = RTTimeSpecGetMilli(&AbsDrift); + if ( AbsDriftMilli > MinAdjust + || g_fTimeSyncSetOnStart) + { + /* + * Ok, the drift is above the threshold. + * + * Try a gradual adjustment first, if that fails or the drift is + * too big, fall back on just setting the time. + */ + if ( AbsDriftMilli > TimeSyncSetThreshold + || g_fTimeSyncSetOnStart + || !vgsvcTimeSyncAdjust(&Drift)) + { + vgsvcTimeSyncCancelAdjust(); + vgsvcTimeSyncSet(&Drift); + fSetTimeInThisLoop = true; + } + + /* + * Log radical host time changes. + */ + int64_t cNsHostDelta = RTTimeSpecGetNano(&HostNow) - RTTimeSpecGetNano(&HostLast); + if ((uint64_t)RT_ABS(cNsHostDelta) > RT_NS_1HOUR / 2) + vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical host time change: %'RI64ns (HostNow=%RDtimespec HostLast=%RDtimespec)\n", + cNsHostDelta, &HostNow, &HostLast); + } + else + vgsvcTimeSyncCancelAdjust(); + HostLast = HostNow; + + /* + * Log radical guest time changes (we could be the cause of these, mind). + * Note! Right now we don't care about an extra log line after we called + * vgsvcTimeSyncSet. fSetTimeLastLoop helps show it though. + */ + int64_t cNsGuestDelta = RTTimeSpecGetNano(&GuestNow) - RTTimeSpecGetNano(&GuestLast); + if ((uint64_t)RT_ABS(cNsGuestDelta) > RT_NS_1HOUR / 2) + vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical guest time change: %'RI64ns (GuestNow=%RDtimespec GuestLast=%RDtimespec fSetTimeLastLoop=%RTbool)\n", + cNsGuestDelta, &GuestNow, &GuestLast, fSetTimeLastLoop); + GuestLast = GuestNow; + fSetTimeLastLoop = fSetTimeInThisLoop; + break; + } + vgsvcTimeSyncLog(3, "vgsvcTimeSyncWorker: %RDtimespec: latency too high (%RDtimespec, max %ums) sleeping 1s\n", + &GuestNow, &GuestElapsed, g_cMsTimeSyncMaxLatency); + RTThreadSleep(1000); + } while (--cTries > 0); + + /* Clear the set-next/set-start flag. */ + g_fTimeSyncSetOnStart = false; + + /* + * Block for a while. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + int rc2 = RTSemEventMultiWait(g_TimeSyncEvent, g_TimeSyncInterval); + if (*pfShutdown) + break; + if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) + { + VGSvcError("vgsvcTimeSyncWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); + rc = rc2; + break; + } + } + + vgsvcTimeSyncCancelAdjust(); + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcTimeSyncStop(void) +{ + if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI) + RTSemEventMultiSignal(g_TimeSyncEvent); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcTimeSyncTerm(void) +{ +#ifdef RT_OS_WINDOWS + /* + * Restore the SE_SYSTEMTIME_NAME token privileges (if init succeeded). + */ + if (g_hTokenProcess) + { + if (!AdjustTokenPrivileges(g_hTokenProcess, FALSE, &g_TkOldPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) + { + DWORD dwErr = GetLastError(); + VGSvcError("vgsvcTimeSyncTerm: Restoring token privileges (SE_SYSTEMTIME_NAME) failed with code %u!\n", dwErr); + } + CloseHandle(g_hTokenProcess); + g_hTokenProcess = NULL; + } +#endif /* !RT_OS_WINDOWS */ + + if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_TimeSyncEvent); + g_TimeSyncEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'timesync' service description. + */ +VBOXSERVICE g_TimeSync = +{ + /* pszName. */ + "timesync", + /* pszDescription. */ + "Time synchronization", + /* pszUsage. */ + " [--timesync-interval <ms>] [--timesync-min-adjust <ms>]\n" + " [--timesync-latency-factor <x>] [--timesync-max-latency <ms>]\n" + " [--timesync-set-threshold <ms>]\n" + " [--timesync-set-start|--timesync-no-set-start]\n" + " [--timesync-set-on-restore|--timesync-no-set-on-restore]\n" + " [--timesync-verbosity <level>]" + , + /* pszOptions. */ + " --timesync-interval Specifies the interval at which to synchronize the\n" + " time with the host. The default is 10000 ms.\n" + " --timesync-min-adjust The minimum absolute drift value measured in\n" + " milliseconds to make adjustments for.\n" + " The default is 1000 ms on OS/2 and 100 ms elsewhere.\n" + " --timesync-latency-factor\n" + " The factor to multiply the time query latency with\n" + " to calculate the dynamic minimum adjust time.\n" + " The default is 8 times.\n" + " --timesync-max-latency The max host timer query latency to accept.\n" + " The default is 250 ms.\n" + " --timesync-set-threshold\n" + " The absolute drift threshold, given as milliseconds,\n" + " where to start setting the time instead of trying to\n" + " adjust it. The default is 20 min.\n" + " --timesync-set-start, --timesync-no-set-start \n" + " Set the time when starting the time sync service.\n" +#ifdef RT_OS_OS2 + " Default: --timesync-set-start\n" +#else + " Default: --timesync-no-set-start\n" +#endif + " --timesync-set-on-restore, --timesync-no-set-on-restore\n" + " Whether to immediately set the time when the VM is\n" + " restored or not. Default: --timesync-set-on-restore\n" + " --timesync-verbosity Sets the verbosity level. Defaults to service wide\n" + " verbosity level.\n" + , + /* methods */ + vgsvcTimeSyncPreInit, + vgsvcTimeSyncOption, + vgsvcTimeSyncInit, + vgsvcTimeSyncWorker, + vgsvcTimeSyncStop, + vgsvcTimeSyncTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp new file mode 100644 index 00000000..a4c9dbe5 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp @@ -0,0 +1,1769 @@ +/* $Id: VBoxServiceToolBox.cpp $ */ +/** @file + * VBoxServiceToolbox - Internal (BusyBox-like) toolbox. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/buildconfig.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/symlink.h> + +#ifndef RT_OS_WINDOWS +# include <sys/stat.h> /* need umask */ +#endif + +#include <VBox/VBoxGuestLib.h> +#include <VBox/version.h> + +#include <VBox/GuestHost/GuestControl.h> + +#include "VBoxServiceInternal.h" +#include "VBoxServiceToolBox.h" +#include "VBoxServiceUtils.h" + +using namespace guestControl; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Generic option indices for commands. */ +enum +{ + VBOXSERVICETOOLBOXOPT_MACHINE_READABLE = 1000, + VBOXSERVICETOOLBOXOPT_VERBOSE +}; + +/** Options indices for "vbox_cat". */ +typedef enum VBOXSERVICETOOLBOXCATOPT +{ + VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED = 1000 +} VBOXSERVICETOOLBOXCATOPT; + +/** Flags for "vbox_ls". */ +typedef enum VBOXSERVICETOOLBOXLSFLAG +{ + VBOXSERVICETOOLBOXLSFLAG_NONE, + VBOXSERVICETOOLBOXLSFLAG_RECURSIVE, + VBOXSERVICETOOLBOXLSFLAG_SYMLINKS +} VBOXSERVICETOOLBOXLSFLAG; + +/** Flags for fs object output. */ +typedef enum VBOXSERVICETOOLBOXOUTPUTFLAG +{ + VBOXSERVICETOOLBOXOUTPUTFLAG_NONE, + VBOXSERVICETOOLBOXOUTPUTFLAG_LONG, + VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE +} VBOXSERVICETOOLBOXOUTPUTFLAG; + +/** The size of the directory entry buffer we're using. */ +#define VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE (sizeof(RTDIRENTRYEX) + RTPATH_MAX) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to a tool handler function. */ +typedef RTEXITCODE (*PFNHANDLER)(int , char **); + +/** Definition for a specific toolbox tool. */ +typedef struct VBOXSERVICETOOLBOXTOOL +{ + /** Friendly name of the tool. */ + const char *pszName; + /** Main handler to be invoked to use the tool. */ + RTEXITCODE (*pfnHandler)(int argc, char **argv); + /** Conversion routine to convert the tool's exit code back to an IPRT rc. Optional. + * + * @todo r=bird: You better revert this, i.e. having pfnHandler return a VBox + * status code and have a routine for converting it to RTEXITCODE. + * Unless, what you really want to do here is to get a cached status, in + * which case you better call it what it is. + */ + int (*pfnExitCodeConvertToRc)(RTEXITCODE rcExit); +} VBOXSERVICETOOLBOXTOOL; +/** Pointer to a const tool definition. */ +typedef VBOXSERVICETOOLBOXTOOL const *PCVBOXSERVICETOOLBOXTOOL; + +/** + * An file/directory entry. Used to cache + * file names/paths for later processing. + */ +typedef struct VBOXSERVICETOOLBOXPATHENTRY +{ + /** Our node. */ + RTLISTNODE Node; + /** Name of the entry. */ + char *pszName; +} VBOXSERVICETOOLBOXPATHENTRY, *PVBOXSERVICETOOLBOXPATHENTRY; + +/** ID cache entry. */ +typedef struct VGSVCTOOLBOXUIDENTRY +{ + /** The identifier name. */ + uint32_t id; + /** Set if UID, clear if GID. */ + bool fIsUid; + /** The name. */ + char szName[128 - 4 - 1]; +} VGSVCTOOLBOXUIDENTRY; +typedef VGSVCTOOLBOXUIDENTRY *PVGSVCTOOLBOXUIDENTRY; + + +/** ID cache. */ +typedef struct VGSVCTOOLBOXIDCACHE +{ + /** Number of valid cache entries. */ + uint32_t cEntries; + /** The next entry to replace. */ + uint32_t iNextReplace; + /** The cache entries. */ + VGSVCTOOLBOXUIDENTRY aEntries[16]; +} VGSVCTOOLBOXIDCACHE; +typedef VGSVCTOOLBOXIDCACHE *PVGSVCTOOLBOXIDCACHE; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static RTEXITCODE vgsvcToolboxCat(int argc, char **argv); +static RTEXITCODE vgsvcToolboxLs(int argc, char **argv); +static RTEXITCODE vgsvcToolboxRm(int argc, char **argv); +static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv); +static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv); +static RTEXITCODE vgsvcToolboxStat(int argc, char **argv); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Tool definitions. */ +static VBOXSERVICETOOLBOXTOOL const g_aTools[] = +{ + { VBOXSERVICE_TOOL_CAT, vgsvcToolboxCat , NULL }, + { VBOXSERVICE_TOOL_LS, vgsvcToolboxLs , NULL }, + { VBOXSERVICE_TOOL_RM, vgsvcToolboxRm , NULL }, + { VBOXSERVICE_TOOL_MKTEMP, vgsvcToolboxMkTemp, NULL }, + { VBOXSERVICE_TOOL_MKDIR, vgsvcToolboxMkDir , NULL }, + { VBOXSERVICE_TOOL_STAT, vgsvcToolboxStat , NULL } +}; + + + + +/** + * Displays a common header for all help text to stdout. + */ +static void vgsvcToolboxShowUsageHeader(void) +{ + RTPrintf(VBOX_PRODUCT " Guest Toolbox Version " + VBOX_VERSION_STRING "\n" + "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n\n"); + RTPrintf("Usage:\n\n"); +} + + +/** + * Displays a help text to stdout. + */ +static void vgsvcToolboxShowUsage(void) +{ + vgsvcToolboxShowUsageHeader(); + RTPrintf(" VBoxService [--use-toolbox] vbox_<command> [<general options>] <parameters>\n\n" + "General options:\n\n" + " --machinereadable produce all output in machine-readable form\n" + " -V print version number and exit\n" + "\n" + "Commands:\n\n" + " vbox_cat [<general options>] <file>...\n" + " vbox_ls [<general options>] [--dereference|-L] [-l] [-R]\n" + " [--verbose|-v] [<file>...]\n" + " vbox_rm [<general options>] [-r|-R] <file>...\n" + " vbox_mktemp [<general options>] [--directory|-d] [--mode|-m <mode>]\n" + " [--secure|-s] [--tmpdir|-t <path>] <template>\n" + " vbox_mkdir [<general options>] [--mode|-m <mode>] [--parents|-p]\n" + " [--verbose|-v] <directory>...\n" + " vbox_stat [<general options>] [--file-system|-f]\n" + " [--dereference|-L] [--terse|-t] [--verbose|-v] <file>...\n" + "\n"); +} + + +/** + * Displays the program's version number. + */ +static void vgsvcToolboxShowVersion(void) +{ + RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision()); +} + + +/** + * Initializes the parseable stream(s). + * + * @return IPRT status code. + */ +static int vgsvcToolboxStrmInit(void) +{ + /* Set stdout's mode to binary. This is required for outputting all the machine-readable + * data correctly. */ + int rc = RTStrmSetMode(g_pStdOut, true /* Binary mode */, -1 /* Current code set, not changed */); + if (RT_FAILURE(rc)) + RTMsgError("Unable to set stdout to binary mode, rc=%Rrc\n", rc); + + return rc; +} + + +/** + * Prints a parseable stream header which contains the actual tool + * which was called/used along with its stream version. + * + * @param pszToolName Name of the tool being used, e.g. "vbt_ls". + * @param uVersion Stream version name. Handy for distinguishing + * different stream versions later. + */ +static void vgsvcToolboxPrintStrmHeader(const char *pszToolName, uint32_t uVersion) +{ + AssertPtrReturnVoid(pszToolName); + RTPrintf("hdr_id=%s%chdr_ver=%u%c", pszToolName, 0, uVersion, 0); +} + + +/** + * Prints a standardized termination sequence indicating that the + * parseable stream just ended. + */ +static void vgsvcToolboxPrintStrmTermination() +{ + RTPrintf("%c%c%c%c", 0, 0, 0, 0); +} + + +/** + * Parse a file mode string from the command line (currently octal only) + * and print an error message and return an error if necessary. + */ +static int vgsvcToolboxParseMode(const char *pcszMode, RTFMODE *pfMode) +{ + int rc = RTStrToUInt32Ex(pcszMode, NULL, 8 /* Base */, pfMode); + if (RT_FAILURE(rc)) /* Only octet based values supported right now! */ + RTMsgError("Mode flag strings not implemented yet! Use octal numbers instead. (%s)\n", pcszMode); + return rc; +} + + +/** + * Destroys a path buffer list. + * + * @param pList Pointer to list to destroy. + */ +static void vgsvcToolboxPathBufDestroy(PRTLISTNODE pList) +{ + if (!pList) + return; + + PVBOXSERVICETOOLBOXPATHENTRY pEntry, pEntryNext; + RTListForEachSafe(pList, pEntry, pEntryNext, VBOXSERVICETOOLBOXPATHENTRY, Node) + { + RTListNodeRemove(&pEntry->Node); + + RTStrFree(pEntry->pszName); + RTMemFree(pEntry); + } +} + + +/** + * Adds a path entry (file/directory/whatever) to a given path buffer list. + * + * @return IPRT status code. + * @param pList Pointer to list to add entry to. + * @param pszName Name of entry to add. + */ +static int vgsvcToolboxPathBufAddPathEntry(PRTLISTNODE pList, const char *pszName) +{ + AssertPtrReturn(pList, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + PVBOXSERVICETOOLBOXPATHENTRY pNode = (PVBOXSERVICETOOLBOXPATHENTRY)RTMemAlloc(sizeof(VBOXSERVICETOOLBOXPATHENTRY)); + if (pNode) + { + pNode->pszName = RTStrDup(pszName); + AssertPtr(pNode->pszName); + + RTListAppend(pList, &pNode->Node); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Performs the actual output operation of "vbox_cat". + * + * @return IPRT status code. + * @param hInput Handle of input file (if any) to use; + * else stdin will be used. + * @param hOutput Handle of output file (if any) to use; + * else stdout will be used. + */ +static int vgsvcToolboxCatOutput(RTFILE hInput, RTFILE hOutput) +{ + int rc = VINF_SUCCESS; + if (hInput == NIL_RTFILE) + { + rc = RTFileFromNative(&hInput, RTFILE_NATIVE_STDIN); + if (RT_FAILURE(rc)) + RTMsgError("Could not translate input file to native handle, rc=%Rrc\n", rc); + } + + if (hOutput == NIL_RTFILE) + { + rc = RTFileFromNative(&hOutput, RTFILE_NATIVE_STDOUT); + if (RT_FAILURE(rc)) + RTMsgError("Could not translate output file to native handle, rc=%Rrc\n", rc); + } + + if (RT_SUCCESS(rc)) + { + uint8_t abBuf[_64K]; + size_t cbRead; + for (;;) + { + rc = RTFileRead(hInput, abBuf, sizeof(abBuf), &cbRead); + if (RT_SUCCESS(rc) && cbRead > 0) + { + rc = RTFileWrite(hOutput, abBuf, cbRead, NULL /* Try to write all at once! */); + if (RT_FAILURE(rc)) + { + RTMsgError("Error while writing output, rc=%Rrc\n", rc); + break; + } + } + else + { + if (rc == VERR_BROKEN_PIPE) + rc = VINF_SUCCESS; + else if (RT_FAILURE(rc)) + RTMsgError("Error while reading input, rc=%Rrc\n", rc); + break; + } + } + } + return rc; +} + + +/** @todo Document options! */ +static char g_paszCatHelp[] = + " VBoxService [--use-toolbox] vbox_cat [<general options>] <file>...\n\n" + "Concatenate files, or standard input, to standard output.\n" + "\n"; + + +/** + * Main function for tool "vbox_cat". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxCat(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + /* Sorted by short ops. */ + { "--show-all", 'a', RTGETOPT_REQ_NOTHING }, + { "--number-nonblank", 'b', RTGETOPT_REQ_NOTHING}, + { NULL, 'e', RTGETOPT_REQ_NOTHING}, + { NULL, 'E', RTGETOPT_REQ_NOTHING}, + { "--flags", 'f', RTGETOPT_REQ_STRING}, + { "--no-content-indexed", VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED, RTGETOPT_REQ_NOTHING}, + { "--number", 'n', RTGETOPT_REQ_NOTHING}, + { "--output", 'o', RTGETOPT_REQ_STRING}, + { "--squeeze-blank", 's', RTGETOPT_REQ_NOTHING}, + { NULL, 't', RTGETOPT_REQ_NOTHING}, + { "--show-tabs", 'T', RTGETOPT_REQ_NOTHING}, + { NULL, 'u', RTGETOPT_REQ_NOTHING}, + { "--show-noneprinting", 'v', RTGETOPT_REQ_NOTHING} + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, 0 /*fFlags*/); + + int rc = VINF_SUCCESS; + + const char *pszOutput = NULL; + RTFILE hOutput = NIL_RTFILE; + uint32_t fFlags = RTFILE_O_CREATE_REPLACE /* Output file flags. */ + | RTFILE_O_WRITE + | RTFILE_O_DENY_WRITE; + + /* Init directory list. */ + RTLISTANCHOR inputList; + RTListInit(&inputList); + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'a': + case 'b': + case 'e': + case 'E': + case 'n': + case 's': + case 't': + case 'T': + case 'v': + RTMsgError("Sorry, option '%s' is not implemented yet!\n", + ValueUnion.pDef->pszLong); + rc = VERR_INVALID_PARAMETER; + break; + + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszCatHelp); + return RTEXITCODE_SUCCESS; + + case 'o': + pszOutput = ValueUnion.psz; + break; + + case 'u': + /* Ignored. */ + break; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED: + fFlags |= RTFILE_O_NOT_CONTENT_INDEXED; + break; + + case VINF_GETOPT_NOT_OPTION: + /* Add file(s) to buffer. This enables processing multiple paths + * at once. + * + * Since the non-options (RTGETOPTINIT_FLAGS_OPTS_FIRST) come last when + * processing this loop it's safe to immediately exit on syntax errors + * or showing the help text (see above). */ + rc = vgsvcToolboxPathBufAddPathEntry(&inputList, ValueUnion.psz); + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + if (RT_SUCCESS(rc)) + { + if (pszOutput) + { + rc = RTFileOpen(&hOutput, pszOutput, fFlags); + if (RT_FAILURE(rc)) + RTMsgError("Could not create output file '%s', rc=%Rrc\n", pszOutput, rc); + } + + if (RT_SUCCESS(rc)) + { + /* Process each input file. */ + RTFILE hInput = NIL_RTFILE; + PVBOXSERVICETOOLBOXPATHENTRY pNodeIt; + RTListForEach(&inputList, pNodeIt, VBOXSERVICETOOLBOXPATHENTRY, Node) + { + rc = RTFileOpen(&hInput, pNodeIt->pszName, + RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + rc = vgsvcToolboxCatOutput(hInput, hOutput); + RTFileClose(hInput); + } + else + RTMsgError("Could not open input file '%s': %Rrc\n", pNodeIt->pszName, rc); + if (RT_FAILURE(rc)) + break; + } + + /* If no input files were defined, process stdin. */ + if (RTListNodeIsFirst(&inputList, &inputList)) + rc = vgsvcToolboxCatOutput(hInput, hOutput); + } + } + + if (hOutput != NIL_RTFILE) + RTFileClose(hOutput); + vgsvcToolboxPathBufDestroy(&inputList); + + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_ACCESS_DENIED: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED; + + case VERR_FILE_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND; + + case VERR_PATH_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND; + + case VERR_SHARING_VIOLATION: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION; + + case VERR_IS_A_DIRECTORY: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_IS_A_DIRECTORY; + + default: +#ifdef DEBUG_andy + AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc)); +#endif + break; + } + + return RTEXITCODE_FAILURE; + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Resolves the UID to a name as best as we can. + * + * @returns Read-only name string. Only valid till the next cache call. + * @param pIdCache The ID cache. + * @param uid The UID to resolve. + * @param pszEntry The filename of the UID. + * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute. + */ +static const char *vgsvcToolboxIdCacheGetUidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTUID uid, + const char *pszEntry, const char *pszRelativeTo) +{ + /* Check cached entries. */ + for (uint32_t i = 0; i < pIdCache->cEntries; i++) + if ( pIdCache->aEntries[i].id == uid + && pIdCache->aEntries[i].fIsUid) + return pIdCache->aEntries[i].szName; + + /* Miss. */ + RTFSOBJINFO ObjInfo; + RT_ZERO(ObjInfo); /* shut up msc */ + int rc; + if (!pszRelativeTo) + rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK); + else + { + char szPath[RTPATH_MAX]; + rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry); + if (RT_SUCCESS(rc)) + rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK); + } + + if ( RT_SUCCESS(rc) + && ObjInfo.Attr.u.UnixOwner.uid == uid) + { + uint32_t i = pIdCache->cEntries; + if (i < RT_ELEMENTS(pIdCache->aEntries)) + pIdCache->cEntries = i + 1; + else + i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries); + pIdCache->aEntries[i].id = uid; + pIdCache->aEntries[i].fIsUid = true; + RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixOwner.szName); + return pIdCache->aEntries[i].szName; + } + return ""; +} + + +/** + * Resolves the GID to a name as best as we can. + * + * @returns Read-only name string. Only valid till the next cache call. + * @param pIdCache The ID cache. + * @param gid The GID to resolve. + * @param pszEntry The filename of the GID. + * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute. + */ +static const char *vgsvcToolboxIdCacheGetGidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTGID gid, + const char *pszEntry, const char *pszRelativeTo) +{ + /* Check cached entries. */ + for (uint32_t i = 0; i < pIdCache->cEntries; i++) + if ( pIdCache->aEntries[i].id == gid + && !pIdCache->aEntries[i].fIsUid) + return pIdCache->aEntries[i].szName; + + /* Miss. */ + RTFSOBJINFO ObjInfo; + RT_ZERO(ObjInfo); /* shut up msc */ + int rc; + if (!pszRelativeTo) + rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK); + else + { + char szPath[RTPATH_MAX]; + rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry); + if (RT_SUCCESS(rc)) + rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK); + } + + if ( RT_SUCCESS(rc) + && ObjInfo.Attr.u.UnixGroup.gid == gid) + { + uint32_t i = pIdCache->cEntries; + if (i < RT_ELEMENTS(pIdCache->aEntries)) + pIdCache->cEntries = i + 1; + else + i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries); + pIdCache->aEntries[i].id = gid; + pIdCache->aEntries[i].fIsUid = false; + RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixGroup.szName); + return pIdCache->aEntries[i].szName; + } + return ""; +} + + +/** + * Prints information (based on given flags) of a file system object (file/directory/...) + * to stdout. + * + * @return IPRT status code. + * @param pszName Object name. + * @param cchName Length of pszName. + * @param fOutputFlags Output / handling flags of type + * VBOXSERVICETOOLBOXOUTPUTFLAG. + * @param pszRelativeTo What pszName is relative to. + * @param pIdCache The ID cache. + * @param pObjInfo Pointer to object information. + */ +static int vgsvcToolboxPrintFsInfo(const char *pszName, size_t cchName, uint32_t fOutputFlags, const char *pszRelativeTo, + PVGSVCTOOLBOXIDCACHE pIdCache, PRTFSOBJINFO pObjInfo) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertReturn(cchName, VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER); + + RTFMODE fMode = pObjInfo->Attr.fMode; + char chFileType; + switch (fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_FIFO: chFileType = 'f'; break; + case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break; + case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break; + case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break; + case RTFS_TYPE_FILE: chFileType = '-'; break; + case RTFS_TYPE_SYMLINK: chFileType = 'l'; break; + case RTFS_TYPE_SOCKET: chFileType = 's'; break; + case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break; + default: chFileType = '?'; break; + } + /** @todo sticy bits++ */ + +/** @todo r=bird: turns out the host doesn't use or need cname_len, so perhaps we could drop it? */ + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_LONG)) + { + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + RTPrintf("ftype=%c%cnode_id=%RU64%cinode_dev=%RU32%ccname_len=%zu%cname=%s%c", + chFileType, 0, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0, + (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0, cchName, 0, pszName, 0); + RTPrintf("%c%c", 0, 0); + } + else + RTPrintf("%c %#18llx %3zu %s\n", chFileType, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, cchName, pszName); + } + else + { + char szTimeBirth[RTTIME_STR_LEN]; + char szTimeChange[RTTIME_STR_LEN]; + char szTimeModification[RTTIME_STR_LEN]; + char szTimeAccess[RTTIME_STR_LEN]; + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + RTPrintf("ftype=%c%c", chFileType, 0); + if (pObjInfo->Attr.u.Unix.INodeId || pObjInfo->Attr.u.Unix.INodeIdDevice) + RTPrintf("node_id=%RU64%cinode_dev=%RU32%c", (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0, + (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0); + RTPrintf("owner_mask=%c%c%c%c", + fMode & RTFS_UNIX_IRUSR ? 'r' : '-', + fMode & RTFS_UNIX_IWUSR ? 'w' : '-', + fMode & RTFS_UNIX_IXUSR ? 'x' : '-', 0); + RTPrintf("group_mask=%c%c%c%c", + fMode & RTFS_UNIX_IRGRP ? 'r' : '-', + fMode & RTFS_UNIX_IWGRP ? 'w' : '-', + fMode & RTFS_UNIX_IXGRP ? 'x' : '-', 0); + RTPrintf("other_mask=%c%c%c%c", + fMode & RTFS_UNIX_IROTH ? 'r' : '-', + fMode & RTFS_UNIX_IWOTH ? 'w' : '-', + fMode & RTFS_UNIX_IXOTH ? 'x' : '-', 0); + /** @todo sticky bits. */ + RTPrintf("dos_mask=%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", + fMode & RTFS_DOS_READONLY ? 'R' : '-', + fMode & RTFS_DOS_HIDDEN ? 'H' : '-', + fMode & RTFS_DOS_SYSTEM ? 'S' : '-', + fMode & RTFS_DOS_DIRECTORY ? 'D' : '-', + fMode & RTFS_DOS_ARCHIVED ? 'A' : '-', + fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-', + fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-', + fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-', + fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-', + fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-', + fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-', + fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-', + fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-', + fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-', 0); + RTPrintf("hlinks=%RU32%cst_size=%RI64%calloc=%RI64%c", + pObjInfo->Attr.u.Unix.cHardlinks, 0, + pObjInfo->cbObject, 0, + pObjInfo->cbAllocated, 0); + RTPrintf("st_birthtime=%s%cst_ctime=%s%cst_mtime=%s%cst_atime=%s%c", + RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), 0, + RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), 0, + RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), 0, + RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)), 0); + if (pObjInfo->Attr.u.Unix.uid != NIL_RTUID) + RTPrintf("uid=%RU32%cusername=%s%c", pObjInfo->Attr.u.Unix.uid, 0, + vgsvcToolboxIdCacheGetUidName(pIdCache, pObjInfo->Attr.u.Unix.uid, pszName, pszRelativeTo), 0); + if (pObjInfo->Attr.u.Unix.gid != NIL_RTGID) + RTPrintf("gid=%RU32%cgroupname=%s%c", pObjInfo->Attr.u.Unix.gid, 0, + vgsvcToolboxIdCacheGetGidName(pIdCache, pObjInfo->Attr.u.Unix.gid, pszName, pszRelativeTo), 0); + if ( (RTFS_IS_DEV_BLOCK(pObjInfo->Attr.fMode) || RTFS_IS_DEV_CHAR(pObjInfo->Attr.fMode)) + && pObjInfo->Attr.u.Unix.Device) + RTPrintf("st_rdev=%RU32%c", pObjInfo->Attr.u.Unix.Device, 0); + if (pObjInfo->Attr.u.Unix.GenerationId) + RTPrintf("st_gen=%RU32%c", pObjInfo->Attr.u.Unix.GenerationId, 0); + if (pObjInfo->Attr.u.Unix.fFlags) + RTPrintf("st_flags=%RU32%c", pObjInfo->Attr.u.Unix.fFlags, 0); + RTPrintf("cname_len=%zu%cname=%s%c", cchName, 0, pszName, 0); + RTPrintf("%c%c", 0, 0); /* End of data block. */ + } + else + { + RTPrintf("%c", chFileType); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IRUSR ? 'r' : '-', + fMode & RTFS_UNIX_IWUSR ? 'w' : '-', + fMode & RTFS_UNIX_IXUSR ? 'x' : '-'); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IRGRP ? 'r' : '-', + fMode & RTFS_UNIX_IWGRP ? 'w' : '-', + fMode & RTFS_UNIX_IXGRP ? 'x' : '-'); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IROTH ? 'r' : '-', + fMode & RTFS_UNIX_IWOTH ? 'w' : '-', + fMode & RTFS_UNIX_IXOTH ? 'x' : '-'); + RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c", + fMode & RTFS_DOS_READONLY ? 'R' : '-', + fMode & RTFS_DOS_HIDDEN ? 'H' : '-', + fMode & RTFS_DOS_SYSTEM ? 'S' : '-', + fMode & RTFS_DOS_DIRECTORY ? 'D' : '-', + fMode & RTFS_DOS_ARCHIVED ? 'A' : '-', + fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-', + fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-', + fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-', + fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-', + fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-', + fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-', + fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-', + fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-', + fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-'); + RTPrintf(" %d %4d %4d %10lld %10lld", + pObjInfo->Attr.u.Unix.cHardlinks, + pObjInfo->Attr.u.Unix.uid, + pObjInfo->Attr.u.Unix.gid, + pObjInfo->cbObject, + pObjInfo->cbAllocated); + RTPrintf(" %s %s %s %s", + RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), + RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), + RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), + RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) ); + RTPrintf(" %2zu %s\n", cchName, pszName); + } + } + + return VINF_SUCCESS; +} + +/** + * Helper routine for ls tool for handling sub directories. + * + * @return IPRT status code. + * @param pszDir Pointer to the directory buffer. + * @param cchDir The length of pszDir in pszDir. + * @param pDirEntry Pointer to the directory entry. + * @param fFlags Flags of type VBOXSERVICETOOLBOXLSFLAG. + * @param fOutputFlags Flags of type VBOXSERVICETOOLBOXOUTPUTFLAG. + * @param pIdCache The ID cache. + */ +static int vgsvcToolboxLsHandleDirSub(char *pszDir, size_t cchDir, PRTDIRENTRYEX pDirEntry, + uint32_t fFlags, uint32_t fOutputFlags, PVGSVCTOOLBOXIDCACHE pIdCache) +{ + Assert(cchDir > 0); Assert(pszDir[cchDir] == '\0'); + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + RTPrintf("dname=%s%c", pszDir, 0); + else if (fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE) + RTPrintf("%s:\n", pszDir); + + /* Make sure we've got some room in the path, to save us extra work further down. */ + if (cchDir + 3 >= RTPATH_MAX) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Path too long: '%s'\n", pszDir); + return VERR_BUFFER_OVERFLOW; + } + + /* Open directory. */ + RTDIR hDir; + int rc = RTDirOpen(&hDir, pszDir); + if (RT_FAILURE(rc)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Failed to open directory '%s', rc=%Rrc\n", pszDir, rc); + return rc; + } + + /* Ensure we've got a trailing slash (there is space for it see above). */ + if (!RTPATH_IS_SEP(pszDir[cchDir - 1])) + { + pszDir[cchDir++] = RTPATH_SLASH; + pszDir[cchDir] = '\0'; + } + + /* + * Process the files and subdirs. + */ + for (;;) + { + /* Get the next directory. */ + size_t cbDirEntry = VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE; + rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_FAILURE(rc)) + break; + + /* Check length. */ + if (pDirEntry->cbName + cchDir + 3 >= RTPATH_MAX) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Path too long: '%s' in '%.*s'\n", pDirEntry->szName, cchDir, pszDir); + rc = VERR_BUFFER_OVERFLOW; + break; + } + + switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_SYMLINK: + { + if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS)) + break; + RT_FALL_THRU(); + } + case RTFS_TYPE_DIRECTORY: + { + rc = vgsvcToolboxPrintFsInfo(pDirEntry->szName, pDirEntry->cbName, fOutputFlags, pszDir, + pIdCache, &pDirEntry->Info); + if (RT_FAILURE(rc)) + break; + + if (RTDirEntryExIsStdDotLink(pDirEntry)) + continue; + + if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE)) + continue; + + memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1); + int rc2 = vgsvcToolboxLsHandleDirSub(pszDir, cchDir + pDirEntry->cbName, pDirEntry, fFlags, fOutputFlags, pIdCache); + if (RT_SUCCESS(rc)) + rc = rc2; + break; + } + + case RTFS_TYPE_FILE: + { + rc = vgsvcToolboxPrintFsInfo(pDirEntry->szName, pDirEntry->cbName, fOutputFlags, pszDir, + pIdCache, &pDirEntry->Info); + break; + } + + default: + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Entry '%.*s%s' of mode %#x not supported, skipping", + cchDir, pszDir, pDirEntry->szName, pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK); + break; + } + } + } + if (rc != VERR_NO_MORE_FILES) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("RTDirReadEx failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir); + } + + rc = RTDirClose(hDir); + if (RT_FAILURE(rc)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("RTDirClose failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir); + } + + return rc; +} + +/** + * Helper routine for ls tool doing the actual parsing and output of + * a specified directory. + * + * @return IPRT status code. + * @param pszDir Absolute path to directory to ouptut. + * @param fFlags Flags of type VBOXSERVICETOOLBOXLSFLAG. + * @param fOutputFlags Flags of type VBOXSERVICETOOLBOXOUTPUTFLAG. + * @param pIdCache The ID cache. + */ +static int vgsvcToolboxLsHandleDir(const char *pszDir, uint32_t fFlags, uint32_t fOutputFlags, PVGSVCTOOLBOXIDCACHE pIdCache) +{ + AssertPtrReturn(pszDir, VERR_INVALID_PARAMETER); + AssertPtrReturn(pIdCache, VERR_INVALID_PARAMETER); + + char szPath[RTPATH_MAX]; + int rc = RTPathAbs(pszDir, szPath, sizeof(szPath)); + if (RT_FAILURE(rc)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("RTPathAbs failed on '%s': %Rrc\n", pszDir, rc); + return rc; + } + + union + { + uint8_t abPadding[VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE]; + RTDIRENTRYEX DirEntry; + } uBuf; + return vgsvcToolboxLsHandleDirSub(szPath, strlen(szPath), &uBuf.DirEntry, fFlags, fOutputFlags, pIdCache); +} + + +/** @todo Document options! */ +static char g_paszLsHelp[] = + " VBoxService [--use-toolbox] vbox_ls [<general options>] [option]...\n" + " [<file>...]\n\n" + "List information about files (the current directory by default).\n\n" + "Options:\n\n" + " [--dereference|-L]\n" + " [-l][-R]\n" + " [--verbose|-v]\n" + " [<file>...]\n" + "\n"; + + +/** + * Main function for tool "vbox_ls". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxLs(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING }, + { "--dereference", 'L', RTGETOPT_REQ_NOTHING }, + { NULL, 'l', RTGETOPT_REQ_NOTHING }, + { NULL, 'R', RTGETOPT_REQ_NOTHING }, + { "--verbose", VBOXSERVICETOOLBOXOPT_VERBOSE, RTGETOPT_REQ_NOTHING} + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), + 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_INIT); + + bool fVerbose = false; + uint32_t fFlags = VBOXSERVICETOOLBOXLSFLAG_NONE; + uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_NONE; + + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszLsHelp); + return RTEXITCODE_SUCCESS; + + case 'L': /* Dereference symlinks. */ + fFlags |= VBOXSERVICETOOLBOXLSFLAG_SYMLINKS; + break; + + case 'l': /* Print long format. */ + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_LONG; + break; + + case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE: + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE; + break; + + case 'R': /* Recursive processing. */ + fFlags |= VBOXSERVICETOOLBOXLSFLAG_RECURSIVE; + break; + + case VBOXSERVICETOOLBOXOPT_VERBOSE: + fVerbose = true; + break; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VINF_GETOPT_NOT_OPTION: + Assert(GetState.iNext); + GetState.iNext--; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + + /* All flags / options processed? Bail out here. + * Processing the file / directory list comes down below. */ + if (ch == VINF_GETOPT_NOT_OPTION) + break; + } + + /* Print magic/version. */ + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + rc = vgsvcToolboxStrmInit(); + if (RT_FAILURE(rc)) + RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc); + vgsvcToolboxPrintStrmHeader("vbt_ls", 1 /* Stream version */); + } + + VGSVCTOOLBOXIDCACHE IdCache; + RT_ZERO(IdCache); + + char szDirCur[RTPATH_MAX]; + rc = RTPathGetCurrent(szDirCur, sizeof(szDirCur)); + if (RT_FAILURE(rc)) + { + RTMsgError("Getting current directory failed, rc=%Rrc\n", rc); + return RTEXITCODE_FAILURE; + } + + ch = RTGetOpt(&GetState, &ValueUnion); + do + { + char const *pszPath; + + if (ch == 0) /* Use current directory if no element specified. */ + pszPath = szDirCur; + else + pszPath = ValueUnion.psz; + + RTFSOBJINFO objInfo; + int rc2 = RTPathQueryInfoEx(pszPath, &objInfo, + RTFSOBJATTRADD_UNIX, + fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc2)) + { + if ( RTFS_IS_FILE(objInfo.Attr.fMode) + || ( RTFS_IS_SYMLINK(objInfo.Attr.fMode) + && (fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS))) + { + rc2 = vgsvcToolboxPrintFsInfo(pszPath, strlen(pszPath), fOutputFlags, NULL, &IdCache, &objInfo); + if (RT_SUCCESS(rc)) /* Keep initial failing rc. */ + rc = rc2; + } + else if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode)) + { + rc2 = vgsvcToolboxLsHandleDir(pszPath, fFlags, fOutputFlags, &IdCache); + if (RT_SUCCESS(rc)) /* Keep initial failing rc. */ + rc = rc2; + } + } + else + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Cannot access '%s': No such file or directory\n", pszPath); + if (RT_SUCCESS(rc)) + rc = VERR_FILE_NOT_FOUND; + /* Do not break here -- process every element in the list + * and keep failing rc. */ + } + + } while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0); + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + vgsvcToolboxPrintStrmTermination(); + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/* Try using RTPathRmCmd. */ +static RTEXITCODE vgsvcToolboxRm(int argc, char **argv) +{ + return RTPathRmCmd(argc, argv); +} + + +static char g_paszMkTempHelp[] = + " VBoxService [--use-toolbox] vbox_mktemp [<general options>] [<options>]\n" + " <template>\n\n" + "Create a temporary directory based on the template supplied. The first string\n" + "of consecutive 'X' characters in the template will be replaced to form a unique\n" + "name for the directory. The template may not contain a path. The default\n" + "creation mode is 0600 for files and 0700 for directories. If no path is\n" + "specified the default temporary directory will be used.\n" + "Options:\n\n" + " [--directory|-d] Create a directory instead of a file.\n" + " [--mode|-m <mode>] Create the object with mode <mode>.\n" + " [--secure|-s] Fail if the object cannot be created securely.\n" + " [--tmpdir|-t <path>] Create the object with the absolute path <path>.\n" + "\n"; + + +/** + * Report the result of a vbox_mktemp operation. + * + * Either errors to stderr (not machine-readable) or everything to stdout as + * {name}\0{rc}\0 (machine- readable format). The message may optionally + * contain a '%s' for the file name and an %Rrc for the result code in that + * order. In future a "verbose" flag may be added, without which nothing will + * be output in non-machine- readable mode. Sets prc if rc is a non-success + * code. + */ +static void toolboxMkTempReport(const char *pcszMessage, const char *pcszFile, + bool fActive, int rc, uint32_t fOutputFlags, int *prc) +{ + if (!fActive) + return; + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + if (RT_SUCCESS(rc)) + RTPrintf(pcszMessage, pcszFile, rc); + else + RTMsgError(pcszMessage, pcszFile, rc); + else + RTPrintf("name=%s%crc=%d%c", pcszFile, 0, rc, 0); + if (prc && RT_FAILURE(rc)) + *prc = rc; +} + + +/** + * Main function for tool "vbox_mktemp". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, + RTGETOPT_REQ_NOTHING }, + { "--directory", 'd', RTGETOPT_REQ_NOTHING }, + { "--mode", 'm', RTGETOPT_REQ_STRING }, + { "--secure", 's', RTGETOPT_REQ_NOTHING }, + { "--tmpdir", 't', RTGETOPT_REQ_STRING }, + }; + + enum + { + /* Isn't that a bit long? s/VBOXSERVICETOOLBOX/VSTB/ ? */ + /** Create a temporary directory instead of a temporary file. */ + VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY = RT_BIT_32(0), + /** Only create the temporary object if the operation is expected + * to be secure. Not guaranteed to be supported on a particular + * set-up. */ + VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE = RT_BIT_32(1) + }; + + int ch, rc; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_INIT); + + uint32_t fFlags = 0; + uint32_t fOutputFlags = 0; + int cNonOptions = 0; + RTFMODE fMode = 0700; + bool fModeSet = false; + const char *pcszPath = NULL; + const char *pcszTemplate; + char szTemplateWithPath[RTPATH_MAX] = ""; + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszMkTempHelp); + return RTEXITCODE_SUCCESS; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE: + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE; + break; + + case 'd': + fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY; + break; + + case 'm': + rc = vgsvcToolboxParseMode(ValueUnion.psz, &fMode); + if (RT_FAILURE(rc)) + return RTEXITCODE_SYNTAX; + fModeSet = true; +#ifndef RT_OS_WINDOWS + umask(0); /* RTDirCreate workaround */ +#endif + break; + case 's': + fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE; + break; + + case 't': + pcszPath = ValueUnion.psz; + break; + + case VINF_GETOPT_NOT_OPTION: + /* RTGetOpt will sort these to the end of the argv vector so + * that we will deal with them afterwards. */ + ++cNonOptions; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + /* Print magic/version. */ + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + rc = vgsvcToolboxStrmInit(); + if (RT_FAILURE(rc)) + RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc); + vgsvcToolboxPrintStrmHeader("vbt_mktemp", 1 /* Stream version */); + } + + if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE && fModeSet) + { + toolboxMkTempReport("'-s' and '-m' parameters cannot be used together.\n", "", + true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_SYNTAX; + } + + /* We need exactly one template, containing at least one 'X'. */ + if (cNonOptions != 1) + { + toolboxMkTempReport("Please specify exactly one template.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_SYNTAX; + } + pcszTemplate = argv[argc - 1]; + + /* Validate that the template is as IPRT requires (asserted by IPRT). */ + if ( RTPathHasPath(pcszTemplate) + || ( !strstr(pcszTemplate, "XXX") + && pcszTemplate[strlen(pcszTemplate) - 1] != 'X')) + { + toolboxMkTempReport("Template '%s' should contain a file name with no path and at least three consecutive 'X' characters or ending in 'X'.\n", + pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + if (pcszPath && !RTPathStartsWithRoot(pcszPath)) + { + toolboxMkTempReport("Path '%s' should be absolute.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + if (pcszPath) + { + rc = RTStrCopy(szTemplateWithPath, sizeof(szTemplateWithPath), pcszPath); + if (RT_FAILURE(rc)) + { + toolboxMkTempReport("Path '%s' too long.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + } + else + { + rc = RTPathTemp(szTemplateWithPath, sizeof(szTemplateWithPath)); + if (RT_FAILURE(rc)) + { + toolboxMkTempReport("Failed to get the temporary directory.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + } + rc = RTPathAppend(szTemplateWithPath, sizeof(szTemplateWithPath), pcszTemplate); + if (RT_FAILURE(rc)) + { + toolboxMkTempReport("Template '%s' too long for path.\n", pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + + if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY) + { + rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE + ? RTDirCreateTempSecure(szTemplateWithPath) + : RTDirCreateTemp(szTemplateWithPath, fMode); + toolboxMkTempReport("Created temporary directory '%s'.\n", + szTemplateWithPath, RT_SUCCESS(rc), rc, + fOutputFlags, NULL); + /* RTDirCreateTemp[Secure] sets the template to "" on failure. */ + toolboxMkTempReport("The following error occurred while creating a temporary directory from template '%s': %Rrc.\n", + pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/); + } + else + { + rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE + ? RTFileCreateTempSecure(szTemplateWithPath) + : RTFileCreateTemp(szTemplateWithPath, fMode); + toolboxMkTempReport("Created temporary file '%s'.\n", + szTemplateWithPath, RT_SUCCESS(rc), rc, + fOutputFlags, NULL); + /* RTFileCreateTemp[Secure] sets the template to "" on failure. */ + toolboxMkTempReport("The following error occurred while creating a temporary file from template '%s': %Rrc.\n", + pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/); + } + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + vgsvcToolboxPrintStrmTermination(); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** @todo Document options! */ +static char g_paszMkDirHelp[] = + " VBoxService [--use-toolbox] vbox_mkdir [<general options>] [<options>]\n" + " <directory>...\n\n" + "Options:\n\n" + " [--mode|-m <mode>] The file mode to set (chmod) on the created\n" + " directories. Default: a=rwx & umask.\n" + " [--parents|-p] Create parent directories as needed, no\n" + " error if the directory already exists.\n" + " [--verbose|-v] Display a message for each created directory.\n" + "\n"; + + +/** + * Main function for tool "vbox_mkdir". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--mode", 'm', RTGETOPT_REQ_STRING }, + { "--parents", 'p', RTGETOPT_REQ_NOTHING}, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING} + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), + 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_INIT); + + bool fMakeParentDirs = false; + bool fVerbose = false; + RTFMODE fDirMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO; + int cDirsCreated = 0; + + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'p': + fMakeParentDirs = true; + break; + + case 'm': + rc = vgsvcToolboxParseMode(ValueUnion.psz, &fDirMode); + if (RT_FAILURE(rc)) + return RTEXITCODE_SYNTAX; +#ifndef RT_OS_WINDOWS + umask(0); /* RTDirCreate workaround */ +#endif + break; + + case 'v': + fVerbose = true; + break; + + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszMkDirHelp); + return RTEXITCODE_SUCCESS; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VINF_GETOPT_NOT_OPTION: + if (fMakeParentDirs) + /** @todo r=bird: If fVerbose is set, we should also show + * which directories that get created, parents as well as + * omitting existing final dirs. Annoying, but check any + * mkdir implementation (try "mkdir -pv asdf/1/2/3/4" + * twice). */ + rc = RTDirCreateFullPath(ValueUnion.psz, fDirMode); + else + rc = RTDirCreate(ValueUnion.psz, fDirMode, 0); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Could not create directory '%s': %Rra\n", + ValueUnion.psz, rc); + if (fVerbose) + RTMsgInfo("Created directory '%s', mode %#RTfmode\n", ValueUnion.psz, fDirMode); + cDirsCreated++; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + AssertRC(rc); + + if (cDirsCreated == 0) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No directory argument."); + + return RTEXITCODE_SUCCESS; +} + + +/** @todo Document options! */ +static char g_paszStatHelp[] = + " VBoxService [--use-toolbox] vbox_stat [<general options>] [<options>]\n" + " <file>...\n\n" + "Display file or file system status.\n\n" + "Options:\n\n" + " [--file-system|-f]\n" + " [--dereference|-L]\n" + " [--terse|-t]\n" + " [--verbose|-v]\n" + "\n"; + + +/** + * Main function for tool "vbox_stat". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxStat(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--file-system", 'f', RTGETOPT_REQ_NOTHING }, + { "--dereference", 'L', RTGETOPT_REQ_NOTHING }, + { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING }, + { "--terse", 't', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + int rc = VINF_SUCCESS; + uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_LONG; /* Use long mode by default. */ + uint32_t fQueryInfoFlags = RTPATH_F_ON_LINK; + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'f': + RTMsgError("Sorry, option '%s' is not implemented yet!\n", ValueUnion.pDef->pszLong); + rc = VERR_INVALID_PARAMETER; + break; + + case 'L': + fQueryInfoFlags &= ~RTPATH_F_ON_LINK; + fQueryInfoFlags |= RTPATH_F_FOLLOW_LINK; + break; + + case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE: + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE; + break; + + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszStatHelp); + return RTEXITCODE_SUCCESS; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VINF_GETOPT_NOT_OPTION: + { + Assert(GetState.iNext); + GetState.iNext--; + break; + } + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + + /* All flags / options processed? Bail out here. + * Processing the file / directory list comes down below. */ + if (ch == VINF_GETOPT_NOT_OPTION) + break; + } + + if (RT_SUCCESS(rc)) + { + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + { + rc = vgsvcToolboxStrmInit(); + if (RT_FAILURE(rc)) + RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc); + vgsvcToolboxPrintStrmHeader("vbt_stat", 1 /* Stream version */); + } + + VGSVCTOOLBOXIDCACHE IdCache; + RT_ZERO(IdCache); + + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + RTFSOBJINFO objInfo; + int rc2 = RTPathQueryInfoEx(ValueUnion.psz, &objInfo, RTFSOBJATTRADD_UNIX, fQueryInfoFlags); + if (RT_FAILURE(rc2)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Cannot stat for '%s': %Rrc\n", ValueUnion.psz, rc2); + } + else + rc2 = vgsvcToolboxPrintFsInfo(ValueUnion.psz, strlen(ValueUnion.psz), fOutputFlags, NULL, &IdCache, &objInfo); + + if (RT_SUCCESS(rc)) + rc = rc2; + /* Do not break here -- process every element in the list + * and keep (initial) failing rc. */ + } + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + vgsvcToolboxPrintStrmTermination(); + + /* At this point the overall result (success/failure) should be in rc. */ + } + else + RTMsgError("Failed with rc=%Rrc\n", rc); + + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_ACCESS_DENIED: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_ACCESS_DENIED; + + case VERR_FILE_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_FILE_NOT_FOUND; + + case VERR_PATH_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_PATH_NOT_FOUND; + + case VERR_NET_PATH_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_NET_PATH_NOT_FOUND; + + case VERR_INVALID_NAME: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_INVALID_NAME; + + default: +#ifdef DEBUG_andy + AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc)); +#endif + break; + } + + return RTEXITCODE_FAILURE; + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Looks up the tool definition entry for the tool give by @a pszTool. + * + * @returns Pointer to the tool definition. NULL if not found. + * @param pszTool The name of the tool. + */ +static PCVBOXSERVICETOOLBOXTOOL vgsvcToolboxLookUp(const char *pszTool) +{ + AssertPtrReturn(pszTool, NULL); + + /* Do a linear search, since we don't have that much stuff in the table. */ + for (unsigned i = 0; i < RT_ELEMENTS(g_aTools); i++) + if (!strcmp(g_aTools[i].pszName, pszTool)) + return &g_aTools[i]; + + return NULL; +} + + +/** + * Converts a tool's exit code back to an IPRT error code. + * + * @return Converted IPRT status code. + * @param pszTool Name of the toolbox tool to convert exit code for. + * @param rcExit The tool's exit code to convert. + */ +int VGSvcToolboxExitCodeConvertToRc(const char *pszTool, RTEXITCODE rcExit) +{ + AssertPtrReturn(pszTool, VERR_INVALID_POINTER); + + PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool); + if (pTool) + return pTool->pfnExitCodeConvertToRc(rcExit); + + AssertMsgFailed(("Tool '%s' not found\n", pszTool)); + return VERR_GENERAL_FAILURE; /* Lookup failed, should not happen. */ +} + + +/** + * Entry point for internal toolbox. + * + * @return True if an internal tool was handled, false if not. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + * @param prcExit Where to store the exit code when an + * internal toolbox command was handled. + */ +bool VGSvcToolboxMain(int argc, char **argv, RTEXITCODE *prcExit) +{ + + /* + * Check if the file named in argv[0] is one of the toolbox programs. + */ + AssertReturn(argc > 0, false); + const char *pszTool = RTPathFilename(argv[0]); + PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool); + if (!pTool) + { + /* + * For debugging and testing purposes we also allow toolbox program access + * when the first VBoxService argument is --use-toolbox. + */ + if (argc < 2 || strcmp(argv[1], "--use-toolbox")) + { + /* We must match vgsvcGstCtrlProcessCreateProcess here and claim + everything starting with "vbox_". */ + if (!RTStrStartsWith(pszTool, "vbox_")) + return false; + RTMsgError("Unknown tool: %s\n", pszTool); + *prcExit = RTEXITCODE_SYNTAX; + return true; + } + + /* No tool specified? Show toolbox help. */ + if (argc < 3) + { + RTMsgError("No tool following --use-toolbox\n"); + *prcExit = RTEXITCODE_SYNTAX; + return true; + } + + argc -= 2; + argv += 2; + pszTool = argv[0]; + pTool = vgsvcToolboxLookUp(pszTool); + if (!pTool) + { + *prcExit = RTEXITCODE_SUCCESS; + if ( !strcmp(pszTool, "-V") + || !strcmp(pszTool, "version")) + vgsvcToolboxShowVersion(); + else if ( !strcmp(pszTool, "help") + || !strcmp(pszTool, "--help") + || !strcmp(pszTool, "-h")) + vgsvcToolboxShowUsage(); + else + { + RTMsgError("Unknown tool: %s\n", pszTool); + *prcExit = RTEXITCODE_SYNTAX; + } + return true; + } + } + + /* + * Invoke the handler. + */ + RTMsgSetProgName("VBoxService/%s", pszTool); + AssertPtr(pTool); + *prcExit = pTool->pfnHandler(argc, argv); + + return true; +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h new file mode 100644 index 00000000..e9f1f4ea --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h @@ -0,0 +1,42 @@ +/* $Id: VBoxServiceToolBox.h $ */ +/** @file + * VBoxService - Toolbox header for sharing defines between toolbox binary and VBoxService. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/GuestHost/GuestControl.h> + +RT_C_DECLS_BEGIN +extern bool VGSvcToolboxMain(int argc, char **argv, RTEXITCODE *prcExit); +extern int VGSvcToolboxExitCodeConvertToRc(const char *pszTool, RTEXITCODE rcExit); +RT_C_DECLS_END + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp new file mode 100644 index 00000000..5949dd84 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp @@ -0,0 +1,324 @@ +/* $Id: VBoxServiceUtils.cpp $ */ +/** @file + * VBoxServiceUtils - Some utility functions. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +# include <iprt/param.h> +# include <iprt/path.h> +#endif +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" + + +#ifdef VBOX_WITH_GUEST_PROPS +/** + * Reads a guest property as a 32-bit value. + * + * @returns VBox status code, fully bitched. + * + * @param u32ClientId The HGCM client ID for the guest property session. + * @param pszPropName The property name. + * @param pu32 Where to store the 32-bit value. + * + */ +int VGSvcReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max) +{ + char *pszValue; + int rc = VbglR3GuestPropReadEx(u32ClientId, pszPropName, &pszValue, NULL /* ppszFlags */, NULL /* puTimestamp */); + if (RT_SUCCESS(rc)) + { + char *pszNext; + rc = RTStrToUInt32Ex(pszValue, &pszNext, 0, pu32); + if ( RT_SUCCESS(rc) + && (*pu32 < u32Min || *pu32 > u32Max)) + rc = VGSvcError("The guest property value %s = %RU32 is out of range [%RU32..%RU32].\n", + pszPropName, *pu32, u32Min, u32Max); + RTStrFree(pszValue); + } + return rc; +} + +/** + * Reads a guest property from the host side. + * + * @returns IPRT status code, fully bitched. + * @param u32ClientId The HGCM client ID for the guest property session. + * @param pszPropName The property name. + * @param fReadOnly Whether or not this property needs to be read only + * by the guest side. Otherwise VERR_ACCESS_DENIED will + * be returned. + * @param ppszValue Where to return the value. This is always set + * to NULL. Free it using RTStrFree(). + * @param ppszFlags Where to return the value flags. Free it + * using RTStrFree(). Optional. + * @param puTimestamp Where to return the timestamp. This is only set + * on success. Optional. + */ +int VGSvcReadHostProp(uint32_t u32ClientId, const char *pszPropName, bool fReadOnly, + char **ppszValue, char **ppszFlags, uint64_t *puTimestamp) +{ + AssertPtrReturn(ppszValue, VERR_INVALID_PARAMETER); + + char *pszValue = NULL; + char *pszFlags = NULL; + int rc = VbglR3GuestPropReadEx(u32ClientId, pszPropName, &pszValue, &pszFlags, puTimestamp); + if (RT_SUCCESS(rc)) + { + /* Check security bits. */ + if ( fReadOnly /* Do we except a guest read-only property */ + && !RTStrStr(pszFlags, "RDONLYGUEST")) + { + /* If we want a property which is read-only on the guest + * and it is *not* marked as such, deny access! */ + rc = VERR_ACCESS_DENIED; + } + + if (RT_SUCCESS(rc)) + { + *ppszValue = pszValue; + + if (ppszFlags) + *ppszFlags = pszFlags; + else if (pszFlags) + RTStrFree(pszFlags); + } + else + { + if (pszValue) + RTStrFree(pszValue); + if (pszFlags) + RTStrFree(pszFlags); + } + } + + return rc; +} + + +/** + * Wrapper around VbglR3GuestPropWriteValue that does value formatting and + * logging. + * + * @returns VBox status code. Errors will be logged. + * + * @param u32ClientId The HGCM client ID for the guest property session. + * @param pszName The property name. + * @param pszValueFormat The property format string. If this is NULL then + * the property will be deleted (if possible). + * @param ... Format arguments. + */ +int VGSvcWritePropF(uint32_t u32ClientId, const char *pszName, const char *pszValueFormat, ...) +{ + AssertPtr(pszName); + int rc; + if (pszValueFormat != NULL) + { + va_list va; + va_start(va, pszValueFormat); + VGSvcVerbose(3, "Writing guest property '%s' = '%N'\n", pszName, pszValueFormat, &va); + va_end(va); + + va_start(va, pszValueFormat); + rc = VbglR3GuestPropWriteValueV(u32ClientId, pszName, pszValueFormat, va); + va_end(va); + + if (RT_FAILURE(rc)) + VGSvcError("Error writing guest property '%s' (rc=%Rrc)\n", pszName, rc); + } + else + { + VGSvcVerbose(3, "Deleting guest property '%s'\n", pszName); + rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, NULL); + if (RT_FAILURE(rc)) + VGSvcError("Error deleting guest property '%s' (rc=%Rrc)\n", pszName, rc); + } + return rc; +} + +#endif /* VBOX_WITH_GUEST_PROPS */ +#ifdef RT_OS_WINDOWS + +/** + * Helper for vgsvcUtilGetFileVersion and attempts to read and parse + * FileVersion. + * + * @returns Success indicator. + */ +static bool vgsvcUtilGetFileVersionOwn(LPSTR pVerData, uint32_t *puMajor, uint32_t *puMinor, + uint32_t *puBuildNumber, uint32_t *puRevisionNumber) +{ + UINT cchStrValue = 0; + LPTSTR pStrValue = NULL; + if (!VerQueryValueA(pVerData, "\\StringFileInfo\\040904b0\\FileVersion", (LPVOID *)&pStrValue, &cchStrValue)) + return false; + + char *pszNext = pStrValue; + int rc = RTStrToUInt32Ex(pszNext, &pszNext, 0, puMajor); + AssertReturn(rc == VWRN_TRAILING_CHARS, false); + AssertReturn(*pszNext == '.', false); + + rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puMinor); + AssertReturn(rc == VWRN_TRAILING_CHARS, false); + AssertReturn(*pszNext == '.', false); + + rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puBuildNumber); + AssertReturn(rc == VWRN_TRAILING_CHARS, false); + AssertReturn(*pszNext == '.', false); + + rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puRevisionNumber); + AssertReturn(rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS /*??*/, false); + + return true; +} + + +/** + * Worker for VGSvcUtilWinGetFileVersionString. + * + * @returns VBox status code. + * @param pszFilename ASCII & ANSI & UTF-8 compliant name. + * @param puMajor Where to return the major version number. + * @param puMinor Where to return the minor version number. + * @param puBuildNumber Where to return the build number. + * @param puRevisionNumber Where to return the revision number. + */ +static int vgsvcUtilGetFileVersion(const char *pszFilename, uint32_t *puMajor, uint32_t *puMinor, uint32_t *puBuildNumber, + uint32_t *puRevisionNumber) +{ + int rc; + + *puMajor = *puMinor = *puBuildNumber = *puRevisionNumber = 0; + + /* + * Get the file version info. + */ + DWORD dwHandleIgnored; + DWORD cbVerData = GetFileVersionInfoSizeA(pszFilename, &dwHandleIgnored); + if (cbVerData) + { + LPTSTR pVerData = (LPTSTR)RTMemTmpAllocZ(cbVerData); + if (pVerData) + { + if (GetFileVersionInfoA(pszFilename, dwHandleIgnored, cbVerData, pVerData)) + { + /* + * Try query and parse the FileVersion string our selves first + * since this will give us the correct revision number when + * it goes beyond the range of an uint16_t / WORD. + */ + if (vgsvcUtilGetFileVersionOwn(pVerData, puMajor, puMinor, puBuildNumber, puRevisionNumber)) + rc = VINF_SUCCESS; + else + { + /* Fall back on VS_FIXEDFILEINFO */ + UINT cbFileInfoIgnored = 0; + VS_FIXEDFILEINFO *pFileInfo = NULL; + if (VerQueryValue(pVerData, "\\", (LPVOID *)&pFileInfo, &cbFileInfoIgnored)) + { + *puMajor = HIWORD(pFileInfo->dwFileVersionMS); + *puMinor = LOWORD(pFileInfo->dwFileVersionMS); + *puBuildNumber = HIWORD(pFileInfo->dwFileVersionLS); + *puRevisionNumber = LOWORD(pFileInfo->dwFileVersionLS); + rc = VINF_SUCCESS; + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + VGSvcVerbose(3, "No file version value for file '%s' available! (%d / rc=%Rrc)\n", + pszFilename, GetLastError(), rc); + } + } + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + VGSvcVerbose(0, "GetFileVersionInfo(%s) -> %u / %Rrc\n", pszFilename, GetLastError(), rc); + } + + RTMemTmpFree(pVerData); + } + else + { + VGSvcVerbose(0, "Failed to allocate %u byte for file version info for '%s'\n", cbVerData, pszFilename); + rc = VERR_NO_TMP_MEMORY; + } + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + VGSvcVerbose(3, "GetFileVersionInfoSize(%s) -> %u / %Rrc\n", pszFilename, GetLastError(), rc); + } + return rc; +} + + +/** + * Gets a re-formatted version string from the VS_FIXEDFILEINFO table. + * + * @returns VBox status code. The output buffer is always valid and the status + * code can safely be ignored. + * + * @param pszPath The base path. + * @param pszFilename The filename. + * @param pszVersion Where to return the version string. + * @param cbVersion The size of the version string buffer. This MUST be + * at least 2 bytes! + */ +int VGSvcUtilWinGetFileVersionString(const char *pszPath, const char *pszFilename, char *pszVersion, size_t cbVersion) +{ + /* + * We will ALWAYS return with a valid output buffer. + */ + AssertReturn(cbVersion >= 2, VERR_BUFFER_OVERFLOW); + pszVersion[0] = '-'; + pszVersion[1] = '\0'; + + /* + * Create the path and query the bits. + */ + char szFullPath[RTPATH_MAX]; + int rc = RTPathJoin(szFullPath, sizeof(szFullPath), pszPath, pszFilename); + if (RT_SUCCESS(rc)) + { + uint32_t uMajor, uMinor, uBuild, uRev; + rc = vgsvcUtilGetFileVersion(szFullPath, &uMajor, &uMinor, &uBuild, &uRev); + if (RT_SUCCESS(rc)) + RTStrPrintf(pszVersion, cbVersion, "%u.%u.%ur%u", uMajor, uMinor, uBuild, uRev); + } + return rc; +} + +#endif /* RT_OS_WINDOWS */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h new file mode 100644 index 00000000..8eb4468d --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h @@ -0,0 +1,49 @@ +/* $Id: VBoxServiceUtils.h $ */ +/** @file + * VBoxServiceUtils - Guest Additions Services (Utilities). + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxServiceInternal.h" + +#ifdef VBOX_WITH_GUEST_PROPS +int VGSvcReadProp(uint32_t u32ClientId, const char *pszPropName, char **ppszValue, char **ppszFlags, uint64_t *puTimestamp); +int VGSvcReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max); +int VGSvcReadHostProp(uint32_t u32ClientId, const char *pszPropName, bool fReadOnly, char **ppszValue, char **ppszFlags, + uint64_t *puTimestamp); +int VGSvcWritePropF(uint32_t u32ClientId, const char *pszName, const char *pszValueFormat, ...); +#endif + +#ifdef RT_OS_WINDOWS +int VGSvcUtilWinGetFileVersionString(const char *pszPath, const char *pszFileName, char *pszVersion, size_t cbVersion); +#endif + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h */ + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp new file mode 100644 index 00000000..8560b6d8 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp @@ -0,0 +1,1363 @@ +/* $Id: VBoxServiceVMInfo-win.cpp $ */ +/** @file + * VBoxService - Virtual Machine Information for the Host, Windows specifics. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0600 +# undef _WIN32_WINNT +# define _WIN32_WINNT 0x0600 /* QueryFullProcessImageNameW in recent SDKs. */ +#endif +#include <iprt/win/windows.h> +#include <wtsapi32.h> /* For WTS* calls. */ +#include <psapi.h> /* EnumProcesses. */ +#include <Ntsecapi.h> /* Needed for process security information. */ + +#include <iprt/assert.h> +#include <iprt/ldr.h> +#include <iprt/localipc.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/system.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/utf16.h> + +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#include "VBoxServiceVMInfo.h" +#include "../../WINNT/VBoxTray/VBoxTrayMsg.h" /* For IPC. */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Structure for storing the looked up user information. */ +typedef struct VBOXSERVICEVMINFOUSER +{ + WCHAR wszUser[MAX_PATH]; + WCHAR wszAuthenticationPackage[MAX_PATH]; + WCHAR wszLogonDomain[MAX_PATH]; + /** Number of assigned user processes. */ + ULONG ulNumProcs; + /** Last (highest) session ID. This + * is needed for distinguishing old session + * process counts from new (current) session + * ones. */ + ULONG ulLastSession; +} VBOXSERVICEVMINFOUSER, *PVBOXSERVICEVMINFOUSER; + +/** Structure for the file information lookup. */ +typedef struct VBOXSERVICEVMINFOFILE +{ + char *pszFilePath; + char *pszFileName; +} VBOXSERVICEVMINFOFILE, *PVBOXSERVICEVMINFOFILE; + +/** Structure for process information lookup. */ +typedef struct VBOXSERVICEVMINFOPROC +{ + /** The PID. */ + DWORD id; + /** The SID. */ + PSID pSid; + /** The LUID. */ + LUID luid; + /** Interactive process. */ + bool fInteractive; +} VBOXSERVICEVMINFOPROC, *PVBOXSERVICEVMINFOPROC; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static uint32_t vgsvcVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs); +static bool vgsvcVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER a_pUserInfo, PLUID a_pSession); +static int vgsvcVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppProc, DWORD *pdwCount); +static void vgsvcVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs); +static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static uint32_t s_uDebugGuestPropClientID = 0; +static uint32_t s_uDebugIter = 0; +/** Whether to skip the logged-in user detection over RDP or not. + * See notes in this section why we might want to skip this. */ +static bool s_fSkipRDPDetection = false; + +static RTONCE g_vgsvcWinVmInitOnce = RTONCE_INITIALIZER; + +/** @name Secur32.dll imports are dynamically resolved because of NT4. + * @{ */ +static decltype(LsaGetLogonSessionData) *g_pfnLsaGetLogonSessionData = NULL; +static decltype(LsaEnumerateLogonSessions) *g_pfnLsaEnumerateLogonSessions = NULL; +static decltype(LsaFreeReturnBuffer) *g_pfnLsaFreeReturnBuffer = NULL; +/** @} */ + +/** @name WtsApi32.dll imports are dynamically resolved because of NT4. + * @{ */ +static decltype(WTSFreeMemory) *g_pfnWTSFreeMemory = NULL; +static decltype(WTSQuerySessionInformationA) *g_pfnWTSQuerySessionInformationA = NULL; +/** @} */ + +/** @name PsApi.dll imports are dynamically resolved because of NT4. + * @{ */ +static decltype(EnumProcesses) *g_pfnEnumProcesses = NULL; +static decltype(GetModuleFileNameExW) *g_pfnGetModuleFileNameExW = NULL; +/** @} */ + +/** @name New Kernel32.dll APIs we may use when present. + * @{ */ +static decltype(QueryFullProcessImageNameW) *g_pfnQueryFullProcessImageNameW = NULL; + +/** @} */ + + +/** + * An RTOnce callback function. + */ +static DECLCALLBACK(int) vgsvcWinVmInfoInitOnce(void *pvIgnored) +{ + RT_NOREF1(pvIgnored); + + /* SECUR32 */ + RTLDRMOD hLdrMod; + int rc = RTLdrLoadSystem("secur32.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hLdrMod, "LsaGetLogonSessionData", (void **)&g_pfnLsaGetLogonSessionData); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hLdrMod, "LsaEnumerateLogonSessions", (void **)&g_pfnLsaEnumerateLogonSessions); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hLdrMod, "LsaFreeReturnBuffer", (void **)&g_pfnLsaFreeReturnBuffer); + AssertRC(rc); + RTLdrClose(hLdrMod); + } + if (RT_FAILURE(rc)) + { + VGSvcVerbose(1, "Secur32.dll APIs are not available (%Rrc)\n", rc); + g_pfnLsaGetLogonSessionData = NULL; + g_pfnLsaEnumerateLogonSessions = NULL; + g_pfnLsaFreeReturnBuffer = NULL; + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0)); + } + + /* WTSAPI32 */ + rc = RTLdrLoadSystem("wtsapi32.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hLdrMod, "WTSFreeMemory", (void **)&g_pfnWTSFreeMemory); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hLdrMod, "WTSQuerySessionInformationA", (void **)&g_pfnWTSQuerySessionInformationA); + AssertRC(rc); + RTLdrClose(hLdrMod); + } + if (RT_FAILURE(rc)) + { + VGSvcVerbose(1, "WtsApi32.dll APIs are not available (%Rrc)\n", rc); + g_pfnWTSFreeMemory = NULL; + g_pfnWTSQuerySessionInformationA = NULL; + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0)); + } + + /* PSAPI */ + rc = RTLdrLoadSystem("psapi.dll", true /*fNoUnload*/, &hLdrMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hLdrMod, "EnumProcesses", (void **)&g_pfnEnumProcesses); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hLdrMod, "GetModuleFileNameExW", (void **)&g_pfnGetModuleFileNameExW); + AssertRC(rc); + RTLdrClose(hLdrMod); + } + if (RT_FAILURE(rc)) + { + VGSvcVerbose(1, "psapi.dll APIs are not available (%Rrc)\n", rc); + g_pfnEnumProcesses = NULL; + g_pfnGetModuleFileNameExW = NULL; + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0)); + } + + /* Kernel32: */ + rc = RTLdrLoadSystem("kernel32.dll", true /*fNoUnload*/, &hLdrMod); + AssertRCReturn(rc, rc); + rc = RTLdrGetSymbol(hLdrMod, "QueryFullProcessImageNameW", (void **)&g_pfnQueryFullProcessImageNameW); + if (RT_FAILURE(rc)) + { + Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(6, 0, 0)); + g_pfnQueryFullProcessImageNameW = NULL; + } + RTLdrClose(hLdrMod); + + return VINF_SUCCESS; +} + + +static bool vgsvcVMInfoSession0Separation(void) +{ + return RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0); /* Vista */ +} + + +/** + * Retrieves the module name of a given process. + * + * @return IPRT status code. + */ +static int vgsvcVMInfoWinProcessesGetModuleNameW(PVBOXSERVICEVMINFOPROC const pProc, PRTUTF16 *ppszName) +{ + *ppszName = NULL; + AssertPtrReturn(ppszName, VERR_INVALID_POINTER); + AssertPtrReturn(pProc, VERR_INVALID_POINTER); + AssertReturn(g_pfnGetModuleFileNameExW || g_pfnQueryFullProcessImageNameW, VERR_NOT_SUPPORTED); + + /* + * Open the process. + */ + DWORD dwFlags = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0)) /* Vista and later */ + dwFlags = PROCESS_QUERY_LIMITED_INFORMATION; /* possible to do on more processes */ + + HANDLE hProcess = OpenProcess(dwFlags, FALSE, pProc->id); + if (hProcess == NULL) + { + DWORD dwErr = GetLastError(); + if (g_cVerbosity) + VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr); + return RTErrConvertFromWin32(dwErr); + } + + /* + * Since GetModuleFileNameEx has trouble with cross-bitness stuff (32-bit apps + * cannot query 64-bit apps and vice verse) we have to use a different code + * path for Vista and up. + * + * So use QueryFullProcessImageNameW when available (Vista+), fall back on + * GetModuleFileNameExW on older windows version ( + */ + WCHAR wszName[_1K]; + DWORD dwLen = _1K; + BOOL fRc; + if (g_pfnQueryFullProcessImageNameW) + fRc = g_pfnQueryFullProcessImageNameW(hProcess, 0 /*PROCESS_NAME_NATIVE*/, wszName, &dwLen); + else + fRc = g_pfnGetModuleFileNameExW(hProcess, NULL /* Get main executable */, wszName, dwLen); + + int rc; + if (fRc) + rc = RTUtf16DupEx(ppszName, wszName, 0); + else + { + DWORD dwErr = GetLastError(); + if (g_cVerbosity > 3) + VGSvcError("Unable to retrieve process name for PID=%u, LastError=%Rwc\n", pProc->id, dwErr); + rc = RTErrConvertFromWin32(dwErr); + } + + CloseHandle(hProcess); + return rc; +} + + +/** + * Fills in more data for a process. + * + * @returns VBox status code. + * @param pProc The process structure to fill data into. + * @param tkClass The kind of token information to get. + */ +static int vgsvcVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pProc, TOKEN_INFORMATION_CLASS tkClass) +{ + AssertPtrReturn(pProc, VERR_INVALID_POINTER); + + DWORD dwErr = ERROR_SUCCESS; + HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pProc->id); + if (h == NULL) + { + dwErr = GetLastError(); + if (g_cVerbosity > 4) + VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr); + return RTErrConvertFromWin32(dwErr); + } + + int rc = VINF_SUCCESS; + HANDLE hToken; + if (OpenProcessToken(h, TOKEN_QUERY, &hToken)) + { + void *pvTokenInfo = NULL; + DWORD dwTokenInfoSize; + switch (tkClass) + { + case TokenStatistics: + /** @todo r=bird: Someone has been reading too many MSDN examples. You shall + * use RTMemAlloc here! There is absolutely not reason for + * complicating things uncessarily by using HeapAlloc! */ + dwTokenInfoSize = sizeof(TOKEN_STATISTICS); + pvTokenInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwTokenInfoSize); + AssertPtr(pvTokenInfo); + break; + + case TokenGroups: + dwTokenInfoSize = 0; + /* Allocation will follow in a second step. */ + break; + + case TokenUser: + dwTokenInfoSize = 0; + /* Allocation will follow in a second step. */ + break; + + default: + VGSvcError("Token class not implemented: %d\n", tkClass); + rc = VERR_NOT_IMPLEMENTED; + dwTokenInfoSize = 0; /* Shut up MSC. */ + break; + } + + if (RT_SUCCESS(rc)) + { + DWORD dwRetLength; + if (!GetTokenInformation(hToken, tkClass, pvTokenInfo, dwTokenInfoSize, &dwRetLength)) + { + dwErr = GetLastError(); + if (dwErr == ERROR_INSUFFICIENT_BUFFER) + { + dwErr = ERROR_SUCCESS; + + switch (tkClass) + { + case TokenGroups: + pvTokenInfo = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwRetLength); + if (!pvTokenInfo) + dwErr = GetLastError(); + dwTokenInfoSize = dwRetLength; + break; + + case TokenUser: + pvTokenInfo = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwRetLength); + if (!pvTokenInfo) + dwErr = GetLastError(); + dwTokenInfoSize = dwRetLength; + break; + + default: + AssertMsgFailed(("Re-allocating of token information for token class not implemented\n")); + break; + } + + if (dwErr == ERROR_SUCCESS) + { + if (!GetTokenInformation(hToken, tkClass, pvTokenInfo, dwTokenInfoSize, &dwRetLength)) + dwErr = GetLastError(); + } + } + } + + if (dwErr == ERROR_SUCCESS) + { + rc = VINF_SUCCESS; + + switch (tkClass) + { + case TokenStatistics: + { + PTOKEN_STATISTICS pStats = (PTOKEN_STATISTICS)pvTokenInfo; + AssertPtr(pStats); + memcpy(&pProc->luid, &pStats->AuthenticationId, sizeof(LUID)); + /** @todo Add more information of TOKEN_STATISTICS as needed. */ + break; + } + + case TokenGroups: + { + pProc->fInteractive = false; + + SID_IDENTIFIER_AUTHORITY sidAuthNT = SECURITY_NT_AUTHORITY; + PSID pSidInteractive = NULL; /* S-1-5-4 */ + if (!AllocateAndInitializeSid(&sidAuthNT, 1, 4, 0, 0, 0, 0, 0, 0, 0, &pSidInteractive)) + dwErr = GetLastError(); + + PSID pSidLocal = NULL; /* S-1-2-0 */ + if (dwErr == ERROR_SUCCESS) + { + SID_IDENTIFIER_AUTHORITY sidAuthLocal = SECURITY_LOCAL_SID_AUTHORITY; + if (!AllocateAndInitializeSid(&sidAuthLocal, 1, 0, 0, 0, 0, 0, 0, 0, 0, &pSidLocal)) + dwErr = GetLastError(); + } + + if (dwErr == ERROR_SUCCESS) + { + PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)pvTokenInfo; + AssertPtr(pGroups); + for (DWORD i = 0; i < pGroups->GroupCount; i++) + { + if ( EqualSid(pGroups->Groups[i].Sid, pSidInteractive) + || EqualSid(pGroups->Groups[i].Sid, pSidLocal) + || pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) + { + pProc->fInteractive = true; + break; + } + } + } + + if (pSidInteractive) + FreeSid(pSidInteractive); + if (pSidLocal) + FreeSid(pSidLocal); + break; + } + + case TokenUser: + { + PTOKEN_USER pUser = (PTOKEN_USER)pvTokenInfo; + AssertPtr(pUser); + + DWORD dwLength = GetLengthSid(pUser->User.Sid); + Assert(dwLength); + if (dwLength) + { + pProc->pSid = (PSID)HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, dwLength); + AssertPtr(pProc->pSid); + if (CopySid(dwLength, pProc->pSid, pUser->User.Sid)) + { + if (!IsValidSid(pProc->pSid)) + dwErr = ERROR_INVALID_NAME; + } + else + dwErr = GetLastError(); + } + else + dwErr = ERROR_NO_DATA; + + if (dwErr != ERROR_SUCCESS) + { + VGSvcError("Error retrieving SID of process PID=%u: %u\n", pProc->id, dwErr); + if (pProc->pSid) + { + HeapFree(GetProcessHeap(), 0 /* Flags */, pProc->pSid); + pProc->pSid = NULL; + } + } + break; + } + + default: + AssertMsgFailed(("Unhandled token information class\n")); + break; + } + } + + if (pvTokenInfo) + HeapFree(GetProcessHeap(), 0 /* Flags */, pvTokenInfo); + } + CloseHandle(hToken); + } + else + dwErr = GetLastError(); + + if (dwErr != ERROR_SUCCESS) + { + if (g_cVerbosity) + VGSvcError("Unable to query token information for PID=%u, error=%u\n", pProc->id, dwErr); + rc = RTErrConvertFromWin32(dwErr); + } + + CloseHandle(h); + return rc; +} + + +/** + * Enumerate all the processes in the system and get the logon user IDs for + * them. + * + * @returns VBox status code. + * @param ppaProcs Where to return the process snapshot. This must be + * freed by calling vgsvcVMInfoWinProcessesFree. + * + * @param pcProcs Where to store the returned process count. + */ +static int vgsvcVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDWORD pcProcs) +{ + AssertPtr(ppaProcs); + AssertPtr(pcProcs); + + if (!g_pfnEnumProcesses) + return VERR_NOT_SUPPORTED; + + /* + * Call EnumProcesses with an increasingly larger buffer until it all fits + * or we think something is screwed up. + */ + DWORD cProcesses = 64; + PDWORD paPID = NULL; + int rc = VINF_SUCCESS; + do + { + /* Allocate / grow the buffer first. */ + cProcesses *= 2; + void *pvNew = RTMemRealloc(paPID, cProcesses * sizeof(DWORD)); + if (!pvNew) + { + rc = VERR_NO_MEMORY; + break; + } + paPID = (PDWORD)pvNew; + + /* Query the processes. Not the cbRet == buffer size means there could be more work to be done. */ + DWORD cbRet; + if (!g_pfnEnumProcesses(paPID, cProcesses * sizeof(DWORD), &cbRet)) + { + rc = RTErrConvertFromWin32(GetLastError()); + break; + } + if (cbRet < cProcesses * sizeof(DWORD)) + { + cProcesses = cbRet / sizeof(DWORD); + break; + } + } while (cProcesses <= _32K); /* Should be enough; see: http://blogs.technet.com/markrussinovich/archive/2009/07/08/3261309.aspx */ + + if (RT_SUCCESS(rc)) + { + /* + * Allocate out process structures and fill data into them. + * We currently only try lookup their LUID's. + */ + PVBOXSERVICEVMINFOPROC paProcs; + paProcs = (PVBOXSERVICEVMINFOPROC)RTMemAllocZ(cProcesses * sizeof(VBOXSERVICEVMINFOPROC)); + if (paProcs) + { + for (DWORD i = 0; i < cProcesses; i++) + { + paProcs[i].id = paPID[i]; + paProcs[i].pSid = NULL; + + int rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenUser); + if (RT_FAILURE(rc2) && g_cVerbosity) + VGSvcError("Get token class 'user' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2); + + rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenGroups); + if (RT_FAILURE(rc2) && g_cVerbosity) + VGSvcError("Get token class 'groups' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2); + + rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenStatistics); + if (RT_FAILURE(rc2) && g_cVerbosity) + VGSvcError("Get token class 'statistics' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2); + } + + /* Save number of processes */ + if (RT_SUCCESS(rc)) + { + *pcProcs = cProcesses; + *ppaProcs = paProcs; + } + else + vgsvcVMInfoWinProcessesFree(cProcesses, paProcs); + } + else + rc = VERR_NO_MEMORY; + } + + RTMemFree(paPID); + return rc; +} + +/** + * Frees the process structures returned by + * vgsvcVMInfoWinProcessesEnumerate() before. + * + * @param cProcs Number of processes in paProcs. + * @param paProcs The process array. + */ +static void vgsvcVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs) +{ + for (DWORD i = 0; i < cProcs; i++) + if (paProcs[i].pSid) + { + HeapFree(GetProcessHeap(), 0 /* Flags */, paProcs[i].pSid); + paProcs[i].pSid = NULL; + } + RTMemFree(paProcs); +} + +/** + * Determines whether the specified session has processes on the system. + * + * @returns Number of processes found for a specified session. + * @param pSession The current user's SID. + * @param paProcs The process snapshot. + * @param cProcs The number of processes in the snaphot. + * @param puTerminalSession Where to return terminal session number. + * Optional. + */ +/** @todo r=bird: The 'Has' indicates a predicate function, which this is + * not. Predicate functions always returns bool. */ +static uint32_t vgsvcVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs, + PULONG puTerminalSession) +{ + if (!pSession) + { + VGSvcVerbose(1, "Session became invalid while enumerating!\n"); + return 0; + } + if (!g_pfnLsaGetLogonSessionData) + return 0; + + PSECURITY_LOGON_SESSION_DATA pSessionData = NULL; + NTSTATUS rcNt = g_pfnLsaGetLogonSessionData(pSession, &pSessionData); + if (rcNt != STATUS_SUCCESS) + { + VGSvcError("Could not get logon session data! rcNt=%#x\n", rcNt); + return 0; + } + + if (!IsValidSid(pSessionData->Sid)) + { + VGSvcError("User SID=%p is not valid\n", pSessionData->Sid); + if (pSessionData) + g_pfnLsaFreeReturnBuffer(pSessionData); + return 0; + } + + + /* + * Even if a user seems to be logged in, it could be a stale/orphaned logon + * session. So check if we have some processes bound to it by comparing the + * session <-> process LUIDs. + */ + int rc = VINF_SUCCESS; + uint32_t cProcessesFound = 0; + for (DWORD i = 0; i < cProcs; i++) + { + PSID pProcSID = paProcs[i].pSid; + if ( RT_SUCCESS(rc) + && pProcSID + && IsValidSid(pProcSID)) + { + if (EqualSid(pSessionData->Sid, paProcs[i].pSid)) + { + if (g_cVerbosity) + { + PRTUTF16 pszName; + int rc2 = vgsvcVMInfoWinProcessesGetModuleNameW(&paProcs[i], &pszName); + VGSvcVerbose(4, "Session %RU32: PID=%u (fInt=%RTbool): %ls\n", + pSessionData->Session, paProcs[i].id, paProcs[i].fInteractive, + RT_SUCCESS(rc2) ? pszName : L"<Unknown>"); + if (RT_SUCCESS(rc2)) + RTUtf16Free(pszName); + } + + if (paProcs[i].fInteractive) + { + cProcessesFound++; + if (!g_cVerbosity) /* We want a bit more info on higher verbosity. */ + break; + } + } + } + } + + if (puTerminalSession) + *puTerminalSession = pSessionData->Session; + + g_pfnLsaFreeReturnBuffer(pSessionData); + + return cProcessesFound; +} + + +/** + * Save and noisy string copy. + * + * @param pwszDst Destination buffer. + * @param cbDst Size in bytes - not WCHAR count! + * @param pSrc Source string. + * @param pszWhat What this is. For the log. + */ +static void vgsvcVMInfoWinSafeCopy(PWCHAR pwszDst, size_t cbDst, LSA_UNICODE_STRING const *pSrc, const char *pszWhat) +{ + Assert(RT_ALIGN(cbDst, sizeof(WCHAR)) == cbDst); + + size_t cbCopy = pSrc->Length; + if (cbCopy + sizeof(WCHAR) > cbDst) + { + VGSvcVerbose(0, "%s is too long - %u bytes, buffer %u bytes! It will be truncated.\n", pszWhat, cbCopy, cbDst); + cbCopy = cbDst - sizeof(WCHAR); + } + if (cbCopy) + memcpy(pwszDst, pSrc->Buffer, cbCopy); + pwszDst[cbCopy / sizeof(WCHAR)] = '\0'; +} + + +/** + * Detects whether a user is logged on. + * + * @returns true if logged in, false if not (or error). + * @param pUserInfo Where to return the user information. + * @param pSession The session to check. + */ +static bool vgsvcVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER pUserInfo, PLUID pSession) +{ + AssertPtrReturn(pUserInfo, false); + if (!pSession) + return false; + if ( !g_pfnLsaGetLogonSessionData + || !g_pfnLsaNtStatusToWinError) + return false; + + PSECURITY_LOGON_SESSION_DATA pSessionData = NULL; + NTSTATUS rcNt = g_pfnLsaGetLogonSessionData(pSession, &pSessionData); + if (rcNt != STATUS_SUCCESS) + { + ULONG ulError = g_pfnLsaNtStatusToWinError(rcNt); + switch (ulError) + { + case ERROR_NOT_ENOUGH_MEMORY: + /* If we don't have enough memory it's hard to judge whether the specified user + * is logged in or not, so just assume he/she's not. */ + VGSvcVerbose(3, "Not enough memory to retrieve logon session data!\n"); + break; + + case ERROR_NO_SUCH_LOGON_SESSION: + /* Skip session data which is not valid anymore because it may have been + * already terminated. */ + break; + + default: + VGSvcError("LsaGetLogonSessionData failed with error %u\n", ulError); + break; + } + if (pSessionData) + g_pfnLsaFreeReturnBuffer(pSessionData); + return false; + } + if (!pSessionData) + { + VGSvcError("Invalid logon session data!\n"); + return false; + } + + VGSvcVerbose(3, "Session data: Name=%ls, SessionID=%RU32, LogonID=%d,%u, LogonType=%u\n", + pSessionData->UserName.Buffer, pSessionData->Session, + pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart, pSessionData->LogonType); + + if (vgsvcVMInfoSession0Separation()) + { + /* Starting at Windows Vista user sessions begin with session 1, so + * ignore (stale) session 0 users. */ + if ( pSessionData->Session == 0 + /* Also check the logon time. */ + || pSessionData->LogonTime.QuadPart == 0) + { + g_pfnLsaFreeReturnBuffer(pSessionData); + return false; + } + } + + /* + * Only handle users which can login interactively or logged in + * remotely over native RDP. + */ + bool fFoundUser = false; + if ( IsValidSid(pSessionData->Sid) + && ( (SECURITY_LOGON_TYPE)pSessionData->LogonType == Interactive + || (SECURITY_LOGON_TYPE)pSessionData->LogonType == RemoteInteractive + /* Note: We also need CachedInteractive in case Windows cached the credentials + * or just wants to reuse them! */ + || (SECURITY_LOGON_TYPE)pSessionData->LogonType == CachedInteractive)) + { + VGSvcVerbose(3, "Session LogonType=%u is supported -- looking up SID + type ...\n", pSessionData->LogonType); + + /* + * Copy out relevant data. + */ + vgsvcVMInfoWinSafeCopy(pUserInfo->wszUser, sizeof(pUserInfo->wszUser), &pSessionData->UserName, "User name"); + vgsvcVMInfoWinSafeCopy(pUserInfo->wszAuthenticationPackage, sizeof(pUserInfo->wszAuthenticationPackage), + &pSessionData->AuthenticationPackage, "Authentication pkg name"); + vgsvcVMInfoWinSafeCopy(pUserInfo->wszLogonDomain, sizeof(pUserInfo->wszLogonDomain), + &pSessionData->LogonDomain, "Logon domain name"); + + TCHAR szOwnerName[MAX_PATH] = { 0 }; + DWORD dwOwnerNameSize = sizeof(szOwnerName); + TCHAR szDomainName[MAX_PATH] = { 0 }; + DWORD dwDomainNameSize = sizeof(szDomainName); + SID_NAME_USE enmOwnerType = SidTypeInvalid; + if (!LookupAccountSid(NULL, + pSessionData->Sid, + szOwnerName, + &dwOwnerNameSize, + szDomainName, + &dwDomainNameSize, + &enmOwnerType)) + { + DWORD dwErr = GetLastError(); + /* + * If a network time-out prevents the function from finding the name or + * if a SID that does not have a corresponding account name (such as a + * logon SID that identifies a logon session), we get ERROR_NONE_MAPPED + * here that we just skip. + */ + if (dwErr != ERROR_NONE_MAPPED) + VGSvcError("Failed looking up account info for user=%ls, error=$ld!\n", pUserInfo->wszUser, dwErr); + } + else + { + if (enmOwnerType == SidTypeUser) /* Only recognize users; we don't care about the rest! */ + { + VGSvcVerbose(3, "Account User=%ls, Session=%u, LogonID=%d,%u, AuthPkg=%ls, Domain=%ls\n", + pUserInfo->wszUser, pSessionData->Session, pSessionData->LogonId.HighPart, + pSessionData->LogonId.LowPart, pUserInfo->wszAuthenticationPackage, pUserInfo->wszLogonDomain); + + /* KB970910 (check http://support.microsoft.com/kb/970910 on archive.org) + * indicates that WTSQuerySessionInformation may leak memory and return the + * wrong status code for WTSApplicationName and WTSInitialProgram queries. + * + * The system must be low on resources, and presumably some internal operation + * must fail because of this, triggering an error handling path that forgets + * to free memory and set last error. + * + * bird 2022-08-26: However, we do not query either of those info items. We + * query WTSConnectState, which is a rather simple affair. So, I've + * re-enabled the code for all systems that includes the API. + */ + if (!s_fSkipRDPDetection) + { + /* Skip if we don't have the WTS API. */ + if (!g_pfnWTSQuerySessionInformationA) + s_fSkipRDPDetection = true; +#if 0 /* bird: see above */ + /* Skip RDP detection on Windows 2000 and older. + For Windows 2000 however we don't have any hotfixes, so just skip the + RDP detection in any case. */ + else if (RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 1, 0)) /* older than XP */ + s_fSkipRDPDetection = true; +#endif + if (s_fSkipRDPDetection) + VGSvcVerbose(0, "Detection of logged-in users via RDP is disabled\n"); + } + + if (!s_fSkipRDPDetection) + { + Assert(g_pfnWTSQuerySessionInformationA); + Assert(g_pfnWTSFreeMemory); + + /* Detect RDP sessions as well. */ + LPTSTR pBuffer = NULL; + DWORD cbRet = 0; + int iState = -1; + if (g_pfnWTSQuerySessionInformationA(WTS_CURRENT_SERVER_HANDLE, + pSessionData->Session, + WTSConnectState, + &pBuffer, + &cbRet)) + { + if (cbRet) + iState = *pBuffer; + VGSvcVerbose(3, "Account User=%ls, WTSConnectState=%d (%u)\n", pUserInfo->wszUser, iState, cbRet); + if ( iState == WTSActive /* User logged on to WinStation. */ + || iState == WTSShadow /* Shadowing another WinStation. */ + || iState == WTSDisconnected) /* WinStation logged on without client. */ + { + /** @todo On Vista and W2K, always "old" user name are still + * there. Filter out the old one! */ + VGSvcVerbose(3, "Account User=%ls using TCS/RDP, state=%d \n", pUserInfo->wszUser, iState); + fFoundUser = true; + } + if (pBuffer) + g_pfnWTSFreeMemory(pBuffer); + } + else + { + DWORD dwLastErr = GetLastError(); + switch (dwLastErr) + { + /* + * Terminal services don't run (for example in W2K, + * nothing to worry about ...). ... or is on the Vista + * fast user switching page! + */ + case ERROR_CTX_WINSTATION_NOT_FOUND: + VGSvcVerbose(3, "No WinStation found for user=%ls\n", pUserInfo->wszUser); + break; + + default: + VGSvcVerbose(3, "Cannot query WTS connection state for user=%ls, error=%u\n", + pUserInfo->wszUser, dwLastErr); + break; + } + + fFoundUser = true; + } + } + } + else + VGSvcVerbose(3, "SID owner type=%d not handled, skipping\n", enmOwnerType); + } + + VGSvcVerbose(3, "Account User=%ls %s logged in\n", pUserInfo->wszUser, fFoundUser ? "is" : "is not"); + } + + if (fFoundUser) + pUserInfo->ulLastSession = pSessionData->Session; + + g_pfnLsaFreeReturnBuffer(pSessionData); + return fFoundUser; +} + + +static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszUser, VERR_INVALID_POINTER); + /* pszDomain is optional. */ + + char szPipeName[512 + sizeof(VBOXTRAY_IPC_PIPE_PREFIX)]; + memcpy(szPipeName, VBOXTRAY_IPC_PIPE_PREFIX, sizeof(VBOXTRAY_IPC_PIPE_PREFIX)); + int rc = RTStrCat(szPipeName, sizeof(szPipeName), pszUser); + if (RT_SUCCESS(rc)) + { + bool fReportToHost = false; + VBoxGuestUserState userState = VBoxGuestUserState_Unknown; + + RTLOCALIPCSESSION hSession; + rc = RTLocalIpcSessionConnect(&hSession, szPipeName, RTLOCALIPC_FLAGS_NATIVE_NAME); + if (RT_SUCCESS(rc)) + { + VBOXTRAYIPCHEADER ipcHdr = + { + /* .uMagic = */ VBOXTRAY_IPC_HDR_MAGIC, + /* .uVersion = */ VBOXTRAY_IPC_HDR_VERSION, + /* .enmMsgType = */ VBOXTRAYIPCMSGTYPE_USER_LAST_INPUT, + /* .cbPayload = */ 0 /* No payload */ + }; + + rc = RTLocalIpcSessionWrite(hSession, &ipcHdr, sizeof(ipcHdr)); + if (RT_SUCCESS(rc)) + { + VBOXTRAYIPCREPLY_USER_LAST_INPUT_T ipcReply; + rc = RTLocalIpcSessionRead(hSession, &ipcReply, sizeof(ipcReply), NULL /* Exact read */); + if ( RT_SUCCESS(rc) + /* If uLastInput is set to UINT32_MAX VBoxTray was not able to retrieve the + * user's last input time. This might happen when running on Windows NT4 or older. */ + && ipcReply.cSecSinceLastInput != UINT32_MAX) + { + userState = ipcReply.cSecSinceLastInput * 1000 < g_uVMInfoUserIdleThresholdMS + ? VBoxGuestUserState_InUse + : VBoxGuestUserState_Idle; + + rc = VGSvcUserUpdateF(pCache, pszUser, pszDomain, "UsageState", + userState == VBoxGuestUserState_InUse ? "InUse" : "Idle"); + + /* + * Note: vboxServiceUserUpdateF can return VINF_NO_CHANGE in case there wasn't anything + * to update. So only report the user's status to host when we really got something + * new. + */ + fReportToHost = rc == VINF_SUCCESS; + VGSvcVerbose(4, "User '%s' (domain '%s') is idle for %RU32, fReportToHost=%RTbool\n", + pszUser, pszDomain ? pszDomain : "<None>", ipcReply.cSecSinceLastInput, fReportToHost); + +#if 0 /* Do we want to write the idle time as well? */ + /* Also write the user's current idle time, if there is any. */ + if (userState == VBoxGuestUserState_Idle) + rc = vgsvcUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", "%RU32", ipcReply.cSecSinceLastInput); + else + rc = vgsvcUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", NULL /* Delete property */); + + if (RT_SUCCESS(rc)) +#endif + } +#ifdef DEBUG + else if (RT_SUCCESS(rc) && ipcReply.cSecSinceLastInput == UINT32_MAX) + VGSvcVerbose(4, "Last input for user '%s' is not supported, skipping\n", pszUser, rc); +#endif + } +#ifdef DEBUG + VGSvcVerbose(4, "Getting last input for user '%s' ended with rc=%Rrc\n", pszUser, rc); +#endif + int rc2 = RTLocalIpcSessionClose(hSession); + if (RT_SUCCESS(rc) && RT_FAILURE(rc2)) + rc = rc2; + } + else + { + switch (rc) + { + case VERR_FILE_NOT_FOUND: + { + /* No VBoxTray (or too old version which does not support IPC) running + for the given user. Not much we can do then. */ + VGSvcVerbose(4, "VBoxTray for user '%s' not running (anymore), no last input available\n", pszUser); + + /* Overwrite rc from above. */ + rc = VGSvcUserUpdateF(pCache, pszUser, pszDomain, "UsageState", "Idle"); + + fReportToHost = rc == VINF_SUCCESS; + if (fReportToHost) + userState = VBoxGuestUserState_Idle; + break; + } + + default: + VGSvcError("Error querying last input for user '%s', rc=%Rrc\n", pszUser, rc); + break; + } + } + + if (fReportToHost) + { + Assert(userState != VBoxGuestUserState_Unknown); + int rc2 = VbglR3GuestUserReportState(pszUser, pszDomain, userState, NULL /* No details */, 0); + if (RT_FAILURE(rc2)) + VGSvcError("Error reporting usage state %d for user '%s' to host, rc=%Rrc\n", userState, pszUser, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + + return rc; +} + + +/** + * Retrieves the currently logged in users and stores their names along with the + * user count. + * + * @returns VBox status code. + * @param pCache Property cache to use for storing some of the lookup + * data in between calls. + * @param ppszUserList Where to store the user list (separated by commas). + * Must be freed with RTStrFree(). + * @param pcUsersInList Where to store the number of users in the list. + */ +int VGSvcVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, char **ppszUserList, uint32_t *pcUsersInList) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(ppszUserList, VERR_INVALID_POINTER); + AssertPtrReturn(pcUsersInList, VERR_INVALID_POINTER); + + int rc = RTOnce(&g_vgsvcWinVmInitOnce, vgsvcWinVmInfoInitOnce, NULL); + if (RT_FAILURE(rc)) + return rc; + if (!g_pfnLsaEnumerateLogonSessions || !g_pfnEnumProcesses || !g_pfnLsaNtStatusToWinError) + return VERR_NOT_SUPPORTED; + + rc = VbglR3GuestPropConnect(&s_uDebugGuestPropClientID); + AssertRC(rc); + + char *pszUserList = NULL; + uint32_t cUsersInList = 0; + + /* This function can report stale or orphaned interactive logon sessions + of already logged off users (especially in Windows 2000). */ + PLUID paSessions = NULL; + ULONG cSessions = 0; + NTSTATUS rcNt = g_pfnLsaEnumerateLogonSessions(&cSessions, &paSessions); + if (rcNt != STATUS_SUCCESS) + { + ULONG uError = g_pfnLsaNtStatusToWinError(rcNt); + switch (uError) + { + case ERROR_NOT_ENOUGH_MEMORY: + VGSvcError("Not enough memory to enumerate logon sessions!\n"); + break; + + case ERROR_SHUTDOWN_IN_PROGRESS: + /* If we're about to shutdown when we were in the middle of enumerating the logon + * sessions, skip the error to not confuse the user with an unnecessary log message. */ + VGSvcVerbose(3, "Shutdown in progress ...\n"); + uError = ERROR_SUCCESS; + break; + + default: + VGSvcError("LsaEnumerate failed with error %RU32\n", uError); + break; + } + + if (paSessions) + g_pfnLsaFreeReturnBuffer(paSessions); + + return RTErrConvertFromWin32(uError); + } + VGSvcVerbose(3, "Found %u sessions\n", cSessions); + + PVBOXSERVICEVMINFOPROC paProcs; + DWORD cProcs; + rc = vgsvcVMInfoWinProcessesEnumerate(&paProcs, &cProcs); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NO_MEMORY) + VGSvcError("Not enough memory to enumerate processes\n"); + else + VGSvcError("Failed to enumerate processes, rc=%Rrc\n", rc); + } + else + { + PVBOXSERVICEVMINFOUSER pUserInfo; + pUserInfo = (PVBOXSERVICEVMINFOUSER)RTMemAllocZ(cSessions * sizeof(VBOXSERVICEVMINFOUSER) + 1); + if (!pUserInfo) + VGSvcError("Not enough memory to store enumerated users!\n"); + else + { + ULONG cUniqueUsers = 0; + + /* + * Note: The cSessions loop variable does *not* correlate with + * the Windows session ID! + */ + for (ULONG i = 0; i < cSessions; i++) + { + VGSvcVerbose(3, "Handling session %RU32 (of %RU32)\n", i + 1, cSessions); + + VBOXSERVICEVMINFOUSER userSession; + if (vgsvcVMInfoWinIsLoggedIn(&userSession, &paSessions[i])) + { + VGSvcVerbose(4, "Handling user=%ls, domain=%ls, package=%ls, session=%RU32\n", + userSession.wszUser, userSession.wszLogonDomain, userSession.wszAuthenticationPackage, + userSession.ulLastSession); + + /* Retrieve assigned processes of current session. */ + uint32_t cCurSessionProcs = vgsvcVMInfoWinSessionHasProcesses(&paSessions[i], paProcs, cProcs, + NULL /* Terminal session ID */); + /* Don't return here when current session does not have assigned processes + * anymore -- in that case we have to search through the unique users list below + * and see if got a stale user/session entry. */ + + if (g_cVerbosity > 3) + { + char szDebugSessionPath[255]; + RTStrPrintf(szDebugSessionPath, sizeof(szDebugSessionPath), + "/VirtualBox/GuestInfo/Debug/LSA/Session/%RU32", userSession.ulLastSession); + VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugSessionPath, + "#%RU32: cSessionProcs=%RU32 (of %RU32 procs total)", + s_uDebugIter, cCurSessionProcs, cProcs); + } + + bool fFoundUser = false; + for (ULONG a = 0; a < cUniqueUsers; a++) + { + PVBOXSERVICEVMINFOUSER pCurUser = &pUserInfo[a]; + AssertPtr(pCurUser); + + if ( !RTUtf16Cmp(userSession.wszUser, pCurUser->wszUser) + && !RTUtf16Cmp(userSession.wszLogonDomain, pCurUser->wszLogonDomain) + && !RTUtf16Cmp(userSession.wszAuthenticationPackage, pCurUser->wszAuthenticationPackage)) + { + /* + * Only respect the highest session for the current user. + */ + if (userSession.ulLastSession > pCurUser->ulLastSession) + { + VGSvcVerbose(4, "Updating user=%ls to %u processes (last used session: %RU32)\n", + pCurUser->wszUser, cCurSessionProcs, userSession.ulLastSession); + + if (!cCurSessionProcs) + VGSvcVerbose(3, "Stale session for user=%ls detected! Processes: %RU32 -> %RU32, Session: %RU32 -> %RU32\n", + pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs, + pCurUser->ulLastSession, userSession.ulLastSession); + + pCurUser->ulNumProcs = cCurSessionProcs; + pCurUser->ulLastSession = userSession.ulLastSession; + } + /* There can be multiple session objects using the same session ID for the + * current user -- so when we got the same session again just add the found + * processes to it. */ + else if (pCurUser->ulLastSession == userSession.ulLastSession) + { + VGSvcVerbose(4, "Updating processes for user=%ls (old procs=%RU32, new procs=%RU32, session=%RU32)\n", + pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs, pCurUser->ulLastSession); + + pCurUser->ulNumProcs = cCurSessionProcs; + } + + fFoundUser = true; + break; + } + } + + if (!fFoundUser) + { + VGSvcVerbose(4, "Adding new user=%ls (session=%RU32) with %RU32 processes\n", + userSession.wszUser, userSession.ulLastSession, cCurSessionProcs); + + memcpy(&pUserInfo[cUniqueUsers], &userSession, sizeof(VBOXSERVICEVMINFOUSER)); + pUserInfo[cUniqueUsers].ulNumProcs = cCurSessionProcs; + cUniqueUsers++; + Assert(cUniqueUsers <= cSessions); + } + } + } + + if (g_cVerbosity > 3) + VGSvcWritePropF(s_uDebugGuestPropClientID, "/VirtualBox/GuestInfo/Debug/LSA", + "#%RU32: cSessions=%RU32, cProcs=%RU32, cUniqueUsers=%RU32", + s_uDebugIter, cSessions, cProcs, cUniqueUsers); + + VGSvcVerbose(3, "Found %u unique logged-in user(s)\n", cUniqueUsers); + + for (ULONG i = 0; i < cUniqueUsers; i++) + { + if (g_cVerbosity > 3) + { + char szDebugUserPath[255]; RTStrPrintf(szDebugUserPath, sizeof(szDebugUserPath), "/VirtualBox/GuestInfo/Debug/LSA/User/%RU32", i); + VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugUserPath, + "#%RU32: szName=%ls, sessionID=%RU32, cProcs=%RU32", + s_uDebugIter, pUserInfo[i].wszUser, pUserInfo[i].ulLastSession, pUserInfo[i].ulNumProcs); + } + + bool fAddUser = false; + if (pUserInfo[i].ulNumProcs) + fAddUser = true; + + if (fAddUser) + { + VGSvcVerbose(3, "User '%ls' has %RU32 interactive processes (session=%RU32)\n", + pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, pUserInfo[i].ulLastSession); + + if (cUsersInList > 0) + { + rc = RTStrAAppend(&pszUserList, ","); + AssertRCBreakStmt(rc, RTStrFree(pszUserList)); + } + + cUsersInList += 1; + + char *pszUser = NULL; + char *pszDomain = NULL; + rc = RTUtf16ToUtf8(pUserInfo[i].wszUser, &pszUser); + if ( RT_SUCCESS(rc) + && pUserInfo[i].wszLogonDomain) + rc = RTUtf16ToUtf8(pUserInfo[i].wszLogonDomain, &pszDomain); + if (RT_SUCCESS(rc)) + { + /* Append user to users list. */ + rc = RTStrAAppend(&pszUserList, pszUser); + + /* Do idle detection. */ + if (RT_SUCCESS(rc)) + rc = vgsvcVMInfoWinWriteLastInput(pCache, pszUser, pszDomain); + } + else + rc = RTStrAAppend(&pszUserList, "<string-conversion-error>"); + + RTStrFree(pszUser); + RTStrFree(pszDomain); + + AssertRCBreakStmt(rc, RTStrFree(pszUserList)); + } + } + + RTMemFree(pUserInfo); + } + vgsvcVMInfoWinProcessesFree(cProcs, paProcs); + } + if (paSessions) + g_pfnLsaFreeReturnBuffer(paSessions); + + if (RT_SUCCESS(rc)) + { + *ppszUserList = pszUserList; + *pcUsersInList = cUsersInList; + } + + s_uDebugIter++; + VbglR3GuestPropDisconnect(s_uDebugGuestPropClientID); + + return rc; +} + + +int VGSvcVMInfoWinGetComponentVersions(uint32_t uClientID) +{ + int rc; + char szSysDir[MAX_PATH] = {0}; + char szWinDir[MAX_PATH] = {0}; + char szDriversDir[MAX_PATH + 32] = {0}; + + /* ASSUME: szSysDir and szWinDir and derivatives are always ASCII compatible. */ + GetSystemDirectory(szSysDir, MAX_PATH); + GetWindowsDirectory(szWinDir, MAX_PATH); + RTStrPrintf(szDriversDir, sizeof(szDriversDir), "%s\\drivers", szSysDir); +#ifdef RT_ARCH_AMD64 + char szSysWowDir[MAX_PATH + 32] = {0}; + RTStrPrintf(szSysWowDir, sizeof(szSysWowDir), "%s\\SysWow64", szWinDir); +#endif + + /* The file information table. */ + const VBOXSERVICEVMINFOFILE aVBoxFiles[] = + { + { szSysDir, "VBoxControl.exe" }, + { szSysDir, "VBoxHook.dll" }, + { szSysDir, "VBoxDisp.dll" }, + { szSysDir, "VBoxTray.exe" }, + { szSysDir, "VBoxService.exe" }, + { szSysDir, "VBoxMRXNP.dll" }, + { szSysDir, "VBoxGINA.dll" }, + { szSysDir, "VBoxCredProv.dll" }, + + /* On 64-bit we don't yet have the OpenGL DLLs in native format. + So just enumerate the 32-bit files in the SYSWOW directory. */ +#ifdef RT_ARCH_AMD64 + { szSysWowDir, "VBoxOGL-x86.dll" }, +#else /* !RT_ARCH_AMD64 */ + { szSysDir, "VBoxOGL.dll" }, +#endif /* !RT_ARCH_AMD64 */ + + { szDriversDir, "VBoxGuest.sys" }, + { szDriversDir, "VBoxMouseNT.sys" }, + { szDriversDir, "VBoxMouse.sys" }, + { szDriversDir, "VBoxSF.sys" }, + { szDriversDir, "VBoxVideo.sys" }, + }; + + for (unsigned i = 0; i < RT_ELEMENTS(aVBoxFiles); i++) + { + char szVer[128]; + rc = VGSvcUtilWinGetFileVersionString(aVBoxFiles[i].pszFilePath, aVBoxFiles[i].pszFileName, szVer, sizeof(szVer)); + char szPropPath[256]; + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestAdd/Components/%s", aVBoxFiles[i].pszFileName); + if ( rc != VERR_FILE_NOT_FOUND + && rc != VERR_PATH_NOT_FOUND) + VGSvcWritePropF(uClientID, szPropPath, "%s", szVer); + else + VGSvcWritePropF(uClientID, szPropPath, NULL); + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp new file mode 100644 index 00000000..f7e42756 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp @@ -0,0 +1,1707 @@ +/* $Id: VBoxServiceVMInfo.cpp $ */ +/** @file + * VBoxService - Virtual Machine Information for the Host. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_vgsvc_vminfo VBoxService - VM Information + * + * The VM Information subservice provides heaps of useful information about the + * VM via guest properties. + * + * Guest properties is a limited database maintained by the HGCM GuestProperties + * service in cooperation with the Main API (VBoxSVC). Properties have a name + * (ours are path like), a string value, and a nanosecond timestamp (unix + * epoch). The timestamp lets the user see how recent the information is. As + * an laternative to polling on changes, it is also possible to wait on changes + * via the Main API or VBoxManage on the host side and VBoxControl in the guest. + * + * The namespace "/VirtualBox/" is reserved for value provided by VirtualBox. + * This service provides all the information under "/VirtualBox/GuestInfo/". + * + * + * @section sec_vgsvc_vminfo_beacons Beacons + * + * The subservice does not write properties unless there are changes. So, in + * order for the host side to know that information is up to date despite an + * oldish timestamp we define a couple of values that are always updated and can + * reliably used to figure how old the information actually is. + * + * For the networking part "/VirtualBox/GuestInfo/Net/Count" is the value to + * watch out for. + * + * For the login part, it's possible that we intended to use + * "/VirtualBox/GuestInfo/OS/LoggedInUsers" for this, however it is not defined + * correctly and current does NOT work as a beacon. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/winsock2.h> +# include <iprt/win/iphlpapi.h> +# include <iprt/win/ws2tcpip.h> +# include <iprt/win/windows.h> +# include <Ntsecapi.h> +#else +# define __STDC_LIMIT_MACROS +# include <arpa/inet.h> +# include <errno.h> +# include <netinet/in.h> +# include <sys/ioctl.h> +# include <sys/socket.h> +# include <net/if.h> +# include <pwd.h> /* getpwuid */ +# include <unistd.h> +# if !defined(RT_OS_OS2) && !defined(RT_OS_FREEBSD) && !defined(RT_OS_HAIKU) +# include <utmpx.h> /** @todo FreeBSD 9 should have this. */ +# endif +# ifdef RT_OS_OS2 +# include <net/if_dl.h> +# endif +# ifdef RT_OS_SOLARIS +# include <sys/sockio.h> +# include <net/if_arp.h> +# endif +# if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) +# include <ifaddrs.h> /* getifaddrs, freeifaddrs */ +# include <net/if_dl.h> /* LLADDR */ +# include <netdb.h> /* getnameinfo */ +# endif +# ifdef VBOX_WITH_DBUS +# include <VBox/dbus.h> +# endif +#endif + +#include <iprt/mem.h> +#include <iprt/thread.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/system.h> +#include <iprt/time.h> +#include <iprt/assert.h> +#include <VBox/err.h> +#include <VBox/version.h> +#include <VBox/VBoxGuestLib.h> +#include "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#include "VBoxServicePropCache.h" + + +/** Structure containing information about a location awarness + * client provided by the host. */ +/** @todo Move this (and functions) into VbglR3. */ +typedef struct VBOXSERVICELACLIENTINFO +{ + uint32_t uID; + char *pszName; + char *pszLocation; + char *pszDomain; + bool fAttached; + uint64_t uAttachedTS; +} VBOXSERVICELACLIENTINFO, *PVBOXSERVICELACLIENTINFO; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The vminfo interval (milliseconds). */ +static uint32_t g_cMsVMInfoInterval = 0; +/** The semaphore we're blocking on. */ +static RTSEMEVENTMULTI g_hVMInfoEvent = NIL_RTSEMEVENTMULTI; +/** The guest property service client ID. */ +static uint32_t g_uVMInfoGuestPropSvcClientID = 0; +/** Number of currently logged in users in OS. */ +static uint32_t g_cVMInfoLoggedInUsers = 0; +/** The guest property cache. */ +static VBOXSERVICEVEPROPCACHE g_VMInfoPropCache; +static const char *g_pszPropCacheValLoggedInUsersList = "/VirtualBox/GuestInfo/OS/LoggedInUsersList"; +static const char *g_pszPropCacheValLoggedInUsers = "/VirtualBox/GuestInfo/OS/LoggedInUsers"; +static const char *g_pszPropCacheValNoLoggedInUsers = "/VirtualBox/GuestInfo/OS/NoLoggedInUsers"; +static const char *g_pszPropCacheValNetCount = "/VirtualBox/GuestInfo/Net/Count"; +/** A guest user's guest property root key. */ +static const char *g_pszPropCacheValUser = "/VirtualBox/GuestInfo/User/"; +/** The VM session ID. Changes whenever the VM is restored or reset. */ +static uint64_t g_idVMInfoSession; +/** The last attached locartion awareness (LA) client timestamp. */ +static uint64_t g_LAClientAttachedTS = 0; +/** The current LA client info. */ +static VBOXSERVICELACLIENTINFO g_LAClientInfo; +/** User idle threshold (in ms). This specifies the minimum time a user is considered + * as being idle and then will be reported to the host. Default is 5s. */ +uint32_t g_uVMInfoUserIdleThresholdMS = 5 * 1000; + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ +static const char *g_pszLAActiveClient = "/VirtualBox/HostInfo/VRDP/ActiveClient"; + +#ifdef VBOX_WITH_DBUS +/** @name ConsoleKit defines (taken from 0.4.5). + * @{ */ +# define CK_NAME "org.freedesktop.ConsoleKit" +# define CK_PATH "/org/freedesktop/ConsoleKit" +# define CK_INTERFACE "org.freedesktop.ConsoleKit" +# define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager" +# define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager" +# define CK_SEAT_INTERFACE "org.freedesktop.ConsoleKit.Seat" +# define CK_SESSION_INTERFACE "org.freedesktop.ConsoleKit.Session" +/** @} */ +#endif + + + +/** + * Signals the event so that a re-enumeration of VM-specific + * information (like logged in users) can happen. + * + * @return IPRT status code. + */ +int VGSvcVMInfoSignal(void) +{ + /* Trigger a re-enumeration of all logged-in users by unblocking + * the multi event semaphore of the VMInfo thread. */ + if (g_hVMInfoEvent) + return RTSemEventMultiSignal(g_hVMInfoEvent); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnPreInit} + */ +static DECLCALLBACK(int) vbsvcVMInfoPreInit(void) +{ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption} + */ +static DECLCALLBACK(int) vbsvcVMInfoOption(const char **ppszShort, int argc, char **argv, int *pi) +{ + /** @todo Use RTGetOpt here. */ + + int rc = -1; + if (ppszShort) + /* no short options */; + else if (!strcmp(argv[*pi], "--vminfo-interval")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsVMInfoInterval, 1, UINT32_MAX - 1); + else if (!strcmp(argv[*pi], "--vminfo-user-idle-threshold")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_uVMInfoUserIdleThresholdMS, 1, UINT32_MAX - 1); + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vbsvcVMInfoInit(void) +{ + /* + * If not specified, find the right interval default. + * Then create the event sem to block on. + */ + if (!g_cMsVMInfoInterval) + g_cMsVMInfoInterval = g_DefaultInterval * 1000; + if (!g_cMsVMInfoInterval) + { + /* Set it to 5s by default for location awareness checks. */ + g_cMsVMInfoInterval = 5 * 1000; + } + + int rc = RTSemEventMultiCreate(&g_hVMInfoEvent); + AssertRCReturn(rc, rc); + + VbglR3GetSessionId(&g_idVMInfoSession); + /* The status code is ignored as this information is not available with VBox < 3.2.10. */ + + /* Initialize the LA client object. */ + RT_ZERO(g_LAClientInfo); + + rc = VbglR3GuestPropConnect(&g_uVMInfoGuestPropSvcClientID); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Property Service Client ID: %#x\n", g_uVMInfoGuestPropSvcClientID); + else + { + /* If the service was not found, we disable this service without + causing VBoxService to fail. */ + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ + { + VGSvcVerbose(0, "Guest property service is not available, disabling the service\n"); + rc = VERR_SERVICE_DISABLED; + } + else + VGSvcError("Failed to connect to the guest property service! Error: %Rrc\n", rc); + RTSemEventMultiDestroy(g_hVMInfoEvent); + g_hVMInfoEvent = NIL_RTSEMEVENTMULTI; + } + + if (RT_SUCCESS(rc)) + { + VGSvcPropCacheCreate(&g_VMInfoPropCache, g_uVMInfoGuestPropSvcClientID); + + /* + * Declare some guest properties with flags and reset values. + */ + int rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, + VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, + NULL /* Delete on exit */); + if (RT_FAILURE(rc2)) + VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValLoggedInUsersList, rc2); + + rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers, + VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, "0"); + if (RT_FAILURE(rc2)) + VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValLoggedInUsers, rc2); + + rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers, + VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, "true"); + if (RT_FAILURE(rc2)) + VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValNoLoggedInUsers, rc2); + + rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNetCount, + VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE, + NULL /* Delete on exit */); + if (RT_FAILURE(rc2)) + VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValNetCount, rc2); + + /* + * Get configuration guest properties from the host. + * Note: All properties should have sensible defaults in case the lookup here fails. + */ + char *pszValue; + rc2 = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--vminfo-user-idle-threshold", + true /* Read only */, &pszValue, NULL /* Flags */, NULL /* Timestamp */); + if (RT_SUCCESS(rc2)) + { + AssertPtr(pszValue); + g_uVMInfoUserIdleThresholdMS = RT_CLAMP(RTStrToUInt32(pszValue), 1000, UINT32_MAX - 1); + RTStrFree(pszValue); + } + } + return rc; +} + + +/** + * Retrieves a specifiy client LA property. + * + * @return IPRT status code. + * @param uClientID LA client ID to retrieve property for. + * @param pszProperty Property (without path) to retrieve. + * @param ppszValue Where to store value of property. + * @param puTimestamp Timestamp of property to retrieve. Optional. + */ +static int vgsvcGetLAClientValue(uint32_t uClientID, const char *pszProperty, char **ppszValue, uint64_t *puTimestamp) +{ + AssertReturn(uClientID, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszProperty, VERR_INVALID_POINTER); + + int rc; + + char pszClientPath[255]; +/** @todo r=bird: Another pointless RTStrPrintf test with wrong status code to boot. */ + if (RTStrPrintf(pszClientPath, sizeof(pszClientPath), + "/VirtualBox/HostInfo/VRDP/Client/%RU32/%s", uClientID, pszProperty)) + { + rc = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, pszClientPath, true /* Read only */, + ppszValue, NULL /* Flags */, puTimestamp); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +/** + * Retrieves LA client information. On success the returned structure will have allocated + * objects which need to be free'd with vboxServiceFreeLAClientInfo. + * + * @return IPRT status code. + * @param uClientID Client ID to retrieve information for. + * @param pClient Pointer where to store the client information. + */ +static int vgsvcGetLAClientInfo(uint32_t uClientID, PVBOXSERVICELACLIENTINFO pClient) +{ + AssertReturn(uClientID, VERR_INVALID_PARAMETER); + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + int rc = vgsvcGetLAClientValue(uClientID, "Name", &pClient->pszName, + NULL /* Timestamp */); + if (RT_SUCCESS(rc)) + { + char *pszAttach; + rc = vgsvcGetLAClientValue(uClientID, "Attach", &pszAttach, &pClient->uAttachedTS); + if (RT_SUCCESS(rc)) + { + AssertPtr(pszAttach); + pClient->fAttached = RTStrICmp(pszAttach, "1") == 0; + + RTStrFree(pszAttach); + } + } + if (RT_SUCCESS(rc)) + rc = vgsvcGetLAClientValue(uClientID, "Location", &pClient->pszLocation, NULL /* Timestamp */); + if (RT_SUCCESS(rc)) + rc = vgsvcGetLAClientValue(uClientID, "Domain", &pClient->pszDomain, NULL /* Timestamp */); + if (RT_SUCCESS(rc)) + pClient->uID = uClientID; + + return rc; +} + + +/** + * Frees all allocated LA client information of a structure. + * + * @param pClient Pointer to client information structure to free. + */ +static void vgsvcFreeLAClientInfo(PVBOXSERVICELACLIENTINFO pClient) +{ + if (pClient) + { + if (pClient->pszName) + { + RTStrFree(pClient->pszName); + pClient->pszName = NULL; + } + if (pClient->pszLocation) + { + RTStrFree(pClient->pszLocation); + pClient->pszLocation = NULL; + } + if (pClient->pszDomain) + { + RTStrFree(pClient->pszDomain); + pClient->pszDomain = NULL; + } + } +} + + +/** + * Updates a per-guest user guest property inside the given property cache. + * + * @return IPRT status code. + * @param pCache Pointer to guest property cache to update user in. + * @param pszUser Name of guest user to update. + * @param pszDomain Domain of guest user to update. Optional. + * @param pszKey Key name of guest property to update. + * @param pszValueFormat Guest property value to set. Pass NULL for deleting + * the property. + */ +int VGSvcUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain, + const char *pszKey, const char *pszValueFormat, ...) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszUser, VERR_INVALID_POINTER); + /* pszDomain is optional. */ + AssertPtrReturn(pszKey, VERR_INVALID_POINTER); + /* pszValueFormat is optional. */ + + int rc = VINF_SUCCESS; + + char *pszName; + if (pszDomain) + { + if (RTStrAPrintf(&pszName, "%s%s@%s/%s", g_pszPropCacheValUser, pszUser, pszDomain, pszKey) < 0) + rc = VERR_NO_MEMORY; + } + else + { + if (RTStrAPrintf(&pszName, "%s%s/%s", g_pszPropCacheValUser, pszUser, pszKey) < 0) + rc = VERR_NO_MEMORY; + } + + char *pszValue = NULL; + if ( RT_SUCCESS(rc) + && pszValueFormat) + { + va_list va; + va_start(va, pszValueFormat); + if (RTStrAPrintfV(&pszValue, pszValueFormat, va) < 0) + rc = VERR_NO_MEMORY; + va_end(va); + if ( RT_SUCCESS(rc) + && !pszValue) + rc = VERR_NO_STR_MEMORY; + } + + if (RT_SUCCESS(rc)) + rc = VGSvcPropCacheUpdate(pCache, pszName, pszValue); + if (rc == VINF_SUCCESS) /* VGSvcPropCacheUpdate will also return VINF_NO_CHANGE. */ + { + /** @todo Combine updating flags w/ updating the actual value. */ + rc = VGSvcPropCacheUpdateEntry(pCache, pszName, + VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, + NULL /* Delete on exit */); + } + + RTStrFree(pszValue); + RTStrFree(pszName); + return rc; +} + + +/** + * Writes the properties that won't change while the service is running. + * + * Errors are ignored. + */ +static void vgsvcVMInfoWriteFixedProperties(void) +{ + /* + * First get OS information that won't change. + */ + char szInfo[256]; + int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szInfo, sizeof(szInfo)); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Product", + "%s", RT_FAILURE(rc) ? "" : szInfo); + + rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szInfo, sizeof(szInfo)); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Release", + "%s", RT_FAILURE(rc) ? "" : szInfo); + + rc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szInfo, sizeof(szInfo)); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Version", + "%s", RT_FAILURE(rc) ? "" : szInfo); + + rc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szInfo, sizeof(szInfo)); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/ServicePack", + "%s", RT_FAILURE(rc) ? "" : szInfo); + + /* + * Retrieve version information about Guest Additions and installed files (components). + */ + char *pszAddVer; + char *pszAddVerExt; + char *pszAddRev; + rc = VbglR3GetAdditionsVersion(&pszAddVer, &pszAddVerExt, &pszAddRev); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/Version", + "%s", RT_FAILURE(rc) ? "" : pszAddVer); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/VersionExt", + "%s", RT_FAILURE(rc) ? "" : pszAddVerExt); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/Revision", + "%s", RT_FAILURE(rc) ? "" : pszAddRev); + if (RT_SUCCESS(rc)) + { + RTStrFree(pszAddVer); + RTStrFree(pszAddVerExt); + RTStrFree(pszAddRev); + } + +#ifdef RT_OS_WINDOWS + /* + * Do windows specific properties. + */ + char *pszInstDir; + rc = VbglR3GetAdditionsInstallationPath(&pszInstDir); + VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/InstallDir", + "%s", RT_FAILURE(rc) ? "" : pszInstDir); + if (RT_SUCCESS(rc)) + RTStrFree(pszInstDir); + + VGSvcVMInfoWinGetComponentVersions(g_uVMInfoGuestPropSvcClientID); +#endif +} + + +#if defined(VBOX_WITH_DBUS) && defined(RT_OS_LINUX) /* Not yet for Solaris/FreeBSB. */ +/* + * Simple wrapper to work around compiler-specific va_list madness. + */ +static dbus_bool_t vboxService_dbus_message_get_args(DBusMessage *message, DBusError *error, int first_arg_type, ...) +{ + va_list va; + va_start(va, first_arg_type); + dbus_bool_t ret = dbus_message_get_args_valist(message, error, first_arg_type, va); + va_end(va); + return ret; +} +#endif + + +/** + * Provide information about active users. + */ +static int vgsvcVMInfoWriteUsers(void) +{ + int rc; + char *pszUserList = NULL; + uint32_t cUsersInList = 0; + +#ifdef RT_OS_WINDOWS + rc = VGSvcVMInfoWinWriteUsers(&g_VMInfoPropCache, &pszUserList, &cUsersInList); + +#elif defined(RT_OS_FREEBSD) + /** @todo FreeBSD: Port logged on user info retrieval. + * However, FreeBSD 9 supports utmpx, so we could use the code + * block below (?). */ + rc = VERR_NOT_IMPLEMENTED; + +#elif defined(RT_OS_HAIKU) + /** @todo Haiku: Port logged on user info retrieval. */ + rc = VERR_NOT_IMPLEMENTED; + +#elif defined(RT_OS_OS2) + /** @todo OS/2: Port logged on (LAN/local/whatever) user info retrieval. */ + rc = VERR_NOT_IMPLEMENTED; + +#else + setutxent(); + utmpx *ut_user; + uint32_t cListSize = 32; + + /* Allocate a first array to hold 32 users max. */ + char **papszUsers = (char **)RTMemAllocZ(cListSize * sizeof(char *)); + if (papszUsers) + rc = VINF_SUCCESS; + else + rc = VERR_NO_MEMORY; + + /* Process all entries in the utmp file. + * Note: This only handles */ + while ( (ut_user = getutxent()) + && RT_SUCCESS(rc)) + { +# ifdef RT_OS_DARWIN /* No ut_user->ut_session on Darwin */ + VGSvcVerbose(4, "Found entry '%s' (type: %d, PID: %RU32)\n", ut_user->ut_user, ut_user->ut_type, ut_user->ut_pid); +# else + VGSvcVerbose(4, "Found entry '%s' (type: %d, PID: %RU32, session: %RU32)\n", + ut_user->ut_user, ut_user->ut_type, ut_user->ut_pid, ut_user->ut_session); +# endif + if (cUsersInList > cListSize) + { + cListSize += 32; + void *pvNew = RTMemRealloc(papszUsers, cListSize * sizeof(char*)); + AssertBreakStmt(pvNew, cListSize -= 32); + papszUsers = (char **)pvNew; + } + + /* Make sure we don't add user names which are not + * part of type USER_PROCES. */ + if (ut_user->ut_type == USER_PROCESS) /* Regular user process. */ + { + bool fFound = false; + for (uint32_t i = 0; i < cUsersInList && !fFound; i++) + fFound = strncmp(papszUsers[i], ut_user->ut_user, sizeof(ut_user->ut_user)) == 0; + + if (!fFound) + { + VGSvcVerbose(4, "Adding user '%s' (type: %d) to list\n", ut_user->ut_user, ut_user->ut_type); + + rc = RTStrDupEx(&papszUsers[cUsersInList], (const char *)ut_user->ut_user); + if (RT_FAILURE(rc)) + break; + cUsersInList++; + } + } + } + +# ifdef VBOX_WITH_DBUS +# if defined(RT_OS_LINUX) /* Not yet for Solaris/FreeBSB. */ + DBusError dbErr; + DBusConnection *pConnection = NULL; + int rc2 = RTDBusLoadLib(); + bool fHaveLibDbus = false; + if (RT_SUCCESS(rc2)) + { + /* Handle desktop sessions using ConsoleKit. */ + VGSvcVerbose(4, "Checking ConsoleKit sessions ...\n"); + fHaveLibDbus = true; + dbus_error_init(&dbErr); + pConnection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbErr); + } + + if ( pConnection + && !dbus_error_is_set(&dbErr)) + { + /* Get all available sessions. */ +/** @todo r=bird: What's the point of hardcoding things here when we've taken the pain of defining CK_XXX constants at the top of the file (or vice versa)? */ + DBusMessage *pMsgSessions = dbus_message_new_method_call("org.freedesktop.ConsoleKit", + "/org/freedesktop/ConsoleKit/Manager", + "org.freedesktop.ConsoleKit.Manager", + "GetSessions"); + if ( pMsgSessions + && dbus_message_get_type(pMsgSessions) == DBUS_MESSAGE_TYPE_METHOD_CALL) + { + DBusMessage *pReplySessions = dbus_connection_send_with_reply_and_block(pConnection, + pMsgSessions, 30 * 1000 /* 30s timeout */, + &dbErr); + if ( pReplySessions + && !dbus_error_is_set(&dbErr)) + { + char **ppszSessions; + int cSessions; + if ( dbus_message_get_type(pMsgSessions) == DBUS_MESSAGE_TYPE_METHOD_CALL + && vboxService_dbus_message_get_args(pReplySessions, &dbErr, DBUS_TYPE_ARRAY, + DBUS_TYPE_OBJECT_PATH, &ppszSessions, &cSessions, + DBUS_TYPE_INVALID /* Termination */)) + { + VGSvcVerbose(4, "ConsoleKit: retrieved %RU16 session(s)\n", cSessions); + + char **ppszCurSession = ppszSessions; + for (ppszCurSession; ppszCurSession && *ppszCurSession; ppszCurSession++) + { + VGSvcVerbose(4, "ConsoleKit: processing session '%s' ...\n", *ppszCurSession); + + /* Only respect active sessions .*/ + bool fActive = false; + DBusMessage *pMsgSessionActive = dbus_message_new_method_call("org.freedesktop.ConsoleKit", + *ppszCurSession, + "org.freedesktop.ConsoleKit.Session", + "IsActive"); + if ( pMsgSessionActive + && dbus_message_get_type(pMsgSessionActive) == DBUS_MESSAGE_TYPE_METHOD_CALL) + { + DBusMessage *pReplySessionActive = dbus_connection_send_with_reply_and_block(pConnection, + pMsgSessionActive, + 30 * 1000 /*sec*/, + &dbErr); + if ( pReplySessionActive + && !dbus_error_is_set(&dbErr)) + { + DBusMessageIter itMsg; + if ( dbus_message_iter_init(pReplySessionActive, &itMsg) + && dbus_message_iter_get_arg_type(&itMsg) == DBUS_TYPE_BOOLEAN) + { + /* Get uid from message. */ + int val; + dbus_message_iter_get_basic(&itMsg, &val); + fActive = val >= 1; + } + + if (pReplySessionActive) + dbus_message_unref(pReplySessionActive); + } + + if (pMsgSessionActive) + dbus_message_unref(pMsgSessionActive); + } + + VGSvcVerbose(4, "ConsoleKit: session '%s' is %s\n", + *ppszCurSession, fActive ? "active" : "not active"); + + /* *ppszCurSession now contains the object path + * (e.g. "/org/freedesktop/ConsoleKit/Session1"). */ + DBusMessage *pMsgUnixUser = dbus_message_new_method_call("org.freedesktop.ConsoleKit", + *ppszCurSession, + "org.freedesktop.ConsoleKit.Session", + "GetUnixUser"); + if ( fActive + && pMsgUnixUser + && dbus_message_get_type(pMsgUnixUser) == DBUS_MESSAGE_TYPE_METHOD_CALL) + { + DBusMessage *pReplyUnixUser = dbus_connection_send_with_reply_and_block(pConnection, + pMsgUnixUser, + 30 * 1000 /* 30s timeout */, + &dbErr); + if ( pReplyUnixUser + && !dbus_error_is_set(&dbErr)) + { + DBusMessageIter itMsg; + if ( dbus_message_iter_init(pReplyUnixUser, &itMsg) + && dbus_message_iter_get_arg_type(&itMsg) == DBUS_TYPE_UINT32) + { + /* Get uid from message. */ + uint32_t uid; + dbus_message_iter_get_basic(&itMsg, &uid); + + /** @todo Add support for getting UID_MIN (/etc/login.defs on + * Debian). */ + uint32_t uid_min = 1000; + + /* Look up user name (realname) from uid. */ + setpwent(); + struct passwd *ppwEntry = getpwuid(uid); + if ( ppwEntry + && ppwEntry->pw_name) + { + if (ppwEntry->pw_uid >= uid_min /* Only respect users, not daemons etc. */) + { + VGSvcVerbose(4, "ConsoleKit: session '%s' -> %s (uid: %RU32)\n", + *ppszCurSession, ppwEntry->pw_name, uid); + + bool fFound = false; + for (uint32_t i = 0; i < cUsersInList && !fFound; i++) + fFound = strcmp(papszUsers[i], ppwEntry->pw_name) == 0; + + if (!fFound) + { + VGSvcVerbose(4, "ConsoleKit: adding user '%s' to list\n", ppwEntry->pw_name); + + rc = RTStrDupEx(&papszUsers[cUsersInList], (const char *)ppwEntry->pw_name); + if (RT_FAILURE(rc)) + break; + cUsersInList++; + } + } + /* else silently ignore the user */ + } + else + VGSvcError("ConsoleKit: unable to lookup user name for uid=%RU32\n", uid); + } + else + AssertMsgFailed(("ConsoleKit: GetUnixUser returned a wrong argument type\n")); + } + + if (pReplyUnixUser) + dbus_message_unref(pReplyUnixUser); + } + else if (fActive) /* don't bitch about inactive users */ + { + static int s_iBitchedAboutConsoleKit = 0; + if (s_iBitchedAboutConsoleKit < 1) + { + s_iBitchedAboutConsoleKit++; + VGSvcError("ConsoleKit: unable to retrieve user for session '%s' (msg type=%d): %s\n", + *ppszCurSession, dbus_message_get_type(pMsgUnixUser), + dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available"); + } + } + + if (pMsgUnixUser) + dbus_message_unref(pMsgUnixUser); + } + + dbus_free_string_array(ppszSessions); + } + else + VGSvcError("ConsoleKit: unable to retrieve session parameters (msg type=%d): %s\n", + dbus_message_get_type(pMsgSessions), + dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available"); + dbus_message_unref(pReplySessions); + } + + if (pMsgSessions) + { + dbus_message_unref(pMsgSessions); + pMsgSessions = NULL; + } + } + else + { + static int s_iBitchedAboutConsoleKit = 0; + if (s_iBitchedAboutConsoleKit < 3) + { + s_iBitchedAboutConsoleKit++; + VGSvcError("Unable to invoke ConsoleKit (%d/3) -- maybe not installed / used? Error: %s\n", + s_iBitchedAboutConsoleKit, + dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available"); + } + } + + if (pMsgSessions) + dbus_message_unref(pMsgSessions); + } + else + { + static int s_iBitchedAboutDBus = 0; + if (s_iBitchedAboutDBus < 3) + { + s_iBitchedAboutDBus++; + VGSvcError("Unable to connect to system D-Bus (%d/3): %s\n", s_iBitchedAboutDBus, + fHaveLibDbus && dbus_error_is_set(&dbErr) ? dbErr.message : "D-Bus not installed"); + } + } + + if ( fHaveLibDbus + && dbus_error_is_set(&dbErr)) + dbus_error_free(&dbErr); +# endif /* RT_OS_LINUX */ +# endif /* VBOX_WITH_DBUS */ + + /** @todo Fedora/others: Handle systemd-loginctl. */ + + /* Calc the string length. */ + size_t cchUserList = 0; + if (RT_SUCCESS(rc)) + for (uint32_t i = 0; i < cUsersInList; i++) + cchUserList += (i != 0) + strlen(papszUsers[i]); + + /* Build the user list. */ + if (cchUserList > 0) + { + if (RT_SUCCESS(rc)) + rc = RTStrAllocEx(&pszUserList, cchUserList + 1); + if (RT_SUCCESS(rc)) + { + char *psz = pszUserList; + for (uint32_t i = 0; i < cUsersInList; i++) + { + if (i != 0) + *psz++ = ','; + size_t cch = strlen(papszUsers[i]); + memcpy(psz, papszUsers[i], cch); + psz += cch; + } + *psz = '\0'; + } + } + + /* Cleanup. */ + for (uint32_t i = 0; i < cUsersInList; i++) + RTStrFree(papszUsers[i]); + RTMemFree(papszUsers); + + endutxent(); /* Close utmpx file. */ +#endif /* !RT_OS_WINDOWS && !RT_OS_FREEBSD && !RT_OS_HAIKU && !RT_OS_OS2 */ + + Assert(RT_FAILURE(rc) || cUsersInList == 0 || (pszUserList && *pszUserList)); + + /* + * If the user enumeration above failed, reset the user count to 0 except + * we didn't have enough memory anymore. In that case we want to preserve + * the previous user count in order to not confuse third party tools which + * rely on that count. + */ + if (RT_FAILURE(rc)) + { + if (rc == VERR_NO_MEMORY) + { + static int s_iVMInfoBitchedOOM = 0; + if (s_iVMInfoBitchedOOM++ < 3) + VGSvcVerbose(0, "Warning: Not enough memory available to enumerate users! Keeping old value (%RU32)\n", + g_cVMInfoLoggedInUsers); + cUsersInList = g_cVMInfoLoggedInUsers; + } + else + cUsersInList = 0; + } + else /* Preserve logged in users count. */ + g_cVMInfoLoggedInUsers = cUsersInList; + + VGSvcVerbose(4, "cUsersInList=%RU32, pszUserList=%s, rc=%Rrc\n", cUsersInList, pszUserList ? pszUserList : "<NULL>", rc); + + if (pszUserList) + { + AssertMsg(cUsersInList, ("pszUserList contains users whereas cUsersInList is 0\n")); + rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, "%s", pszUserList); + } + else + rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, NULL); + if (RT_FAILURE(rc)) + VGSvcError("Error writing logged in users list, rc=%Rrc\n", rc); + + rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers, "%RU32", cUsersInList); + if (RT_FAILURE(rc)) + VGSvcError("Error writing logged in users count, rc=%Rrc\n", rc); + +/** @todo r=bird: What's this 'beacon' nonsense here? It's _not_ defined with + * the VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE flag set!! */ + rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers, cUsersInList == 0 ? "true" : "false"); + if (RT_FAILURE(rc)) + VGSvcError("Error writing no logged in users beacon, rc=%Rrc\n", rc); + + if (pszUserList) + RTStrFree(pszUserList); + + VGSvcVerbose(4, "Writing users returned with rc=%Rrc\n", rc); + return rc; +} + + +/** + * Provide information about the guest network. + */ +static int vgsvcVMInfoWriteNetwork(void) +{ + uint32_t cIfsReported = 0; + char szPropPath[256]; + +#ifdef RT_OS_WINDOWS + /* + * Check that the APIs we need are present. + */ + if ( !g_pfnWSAIoctl + || !g_pfnWSASocketA + || !g_pfnWSAGetLastError + || !g_pfninet_ntoa + || !g_pfnclosesocket) + return VINF_SUCCESS; + + /* + * Query the IP adapter info first, if we have the API. + */ + IP_ADAPTER_INFO *pAdpInfo = NULL; + if (g_pfnGetAdaptersInfo) + { + ULONG cbAdpInfo = RT_MAX(sizeof(IP_ADAPTER_INFO) * 2, _2K); + pAdpInfo = (IP_ADAPTER_INFO *)RTMemAllocZ(cbAdpInfo); + if (!pAdpInfo) + { + VGSvcError("VMInfo/Network: Failed to allocate two IP_ADAPTER_INFO structures\n"); + return VERR_NO_MEMORY; + } + + DWORD dwRet = g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo); + if (dwRet == ERROR_BUFFER_OVERFLOW) + { + IP_ADAPTER_INFO *pAdpInfoNew = (IP_ADAPTER_INFO*)RTMemRealloc(pAdpInfo, cbAdpInfo); + if (pAdpInfoNew) + { + pAdpInfo = pAdpInfoNew; + RT_BZERO(pAdpInfo, cbAdpInfo); + dwRet = g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo); + } + } + if (dwRet != NO_ERROR) + { + RTMemFree(pAdpInfo); + pAdpInfo = NULL; + if (dwRet == ERROR_NO_DATA) + /* If no network adapters available / present in the + system we pretend success to not bail out too early. */ + VGSvcVerbose(3, "VMInfo/Network: No network adapters present according to GetAdaptersInfo.\n"); + else + { + VGSvcError("VMInfo/Network: Failed to get adapter info: Error %d\n", dwRet); + return RTErrConvertFromWin32(dwRet); + } + } + } + + /* + * Ask the TCP/IP stack for an interface list. + */ + SOCKET sd = g_pfnWSASocketA(AF_INET, SOCK_DGRAM, 0, 0, 0, 0); + if (sd == SOCKET_ERROR) /* Socket invalid. */ + { + int const wsaErr = g_pfnWSAGetLastError(); + RTMemFree(pAdpInfo); + + /* Don't complain/bail out with an error if network stack is not up; can happen + * on NT4 due to start up when not connected shares dialogs pop up. */ + if (wsaErr == WSAENETDOWN) + { + VGSvcVerbose(0, "VMInfo/Network: Network is not up yet.\n"); + return VINF_SUCCESS; + } + VGSvcError("VMInfo/Network: Failed to get a socket: Error %d\n", wsaErr); + return RTErrConvertFromWin32(wsaErr); + } + + INTERFACE_INFO aInterfaces[20] = {{0}}; + DWORD cbReturned = 0; +# ifdef RT_ARCH_X86 + /* Workaround for uninitialized variable used in memcpy in GetTcpipInterfaceList + (NT4SP1 at least). It seems to be happy enough with garbages, no failure + returns so far, so we just need to prevent it from crashing by filling the + stack with valid pointer values prior to the API call. */ + _asm + { + mov edx, edi + lea eax, aInterfaces + mov [esp - 0x1000], eax + mov [esp - 0x2000], eax + mov ecx, 0x2000/4 - 1 + cld + lea edi, [esp - 0x2000] + rep stosd + mov edi, edx + } +# endif + int rc = g_pfnWSAIoctl(sd, + SIO_GET_INTERFACE_LIST, + NULL, /* pvInBuffer */ + 0, /* cbInBuffer */ + &aInterfaces[0], /* pvOutBuffer */ + sizeof(aInterfaces), /* cbOutBuffer */ + &cbReturned, + NULL, /* pOverlapped */ + NULL); /* pCompletionRoutine */ + if (rc == SOCKET_ERROR) + { + VGSvcError("VMInfo/Network: Failed to WSAIoctl() on socket: Error: %d\n", g_pfnWSAGetLastError()); + RTMemFree(pAdpInfo); + g_pfnclosesocket(sd); + return RTErrConvertFromWin32(g_pfnWSAGetLastError()); + } + g_pfnclosesocket(sd); + int cIfacesSystem = cbReturned / sizeof(INTERFACE_INFO); + + /* + * Iterate the inteface list we got back from the TCP/IP, + * using the pAdpInfo list to supply the MAC address. + */ + /** @todo Use GetAdaptersInfo() and GetAdapterAddresses (IPv4 + IPv6) for more information. */ + for (int i = 0; i < cIfacesSystem; ++i) + { + if (aInterfaces[i].iiFlags & IFF_LOOPBACK) /* Skip loopback device. */ + continue; + sockaddr_in *pAddress = &aInterfaces[i].iiAddress.AddressIn; + char szIp[32]; + RTStrPrintf(szIp, sizeof(szIp), "%s", g_pfninet_ntoa(pAddress->sin_addr)); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szIp); + + pAddress = &aInterfaces[i].iiBroadcastAddress.AddressIn; + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", g_pfninet_ntoa(pAddress->sin_addr)); + + pAddress = (sockaddr_in *)&aInterfaces[i].iiNetmask; + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", g_pfninet_ntoa(pAddress->sin_addr)); + + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, aInterfaces[i].iiFlags & IFF_UP ? "Up" : "Down"); + + if (pAdpInfo) + { + IP_ADAPTER_INFO *pAdp; + for (pAdp = pAdpInfo; pAdp; pAdp = pAdp->Next) + if (!strcmp(pAdp->IpAddressList.IpAddress.String, szIp)) + break; + + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfsReported); + if (pAdp) + { + char szMac[32]; + RTStrPrintf(szMac, sizeof(szMac), "%02X%02X%02X%02X%02X%02X", + pAdp->Address[0], pAdp->Address[1], pAdp->Address[2], + pAdp->Address[3], pAdp->Address[4], pAdp->Address[5]); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac); + } + else + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, NULL); + } + + cIfsReported++; + } + + RTMemFree(pAdpInfo); + +#elif defined(RT_OS_HAIKU) + /** @todo Haiku: implement network info. retreival */ + return VERR_NOT_IMPLEMENTED; + +#elif defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) + struct ifaddrs *pIfHead = NULL; + + /* Get all available interfaces */ + int rc = getifaddrs(&pIfHead); + if (rc < 0) + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to get all interfaces: Error %Rrc\n"); + return rc; + } + + /* Loop through all interfaces and set the data. */ + for (struct ifaddrs *pIfCurr = pIfHead; pIfCurr; pIfCurr = pIfCurr->ifa_next) + { + /* + * Only AF_INET and no loopback interfaces + */ + /** @todo IPv6 interfaces */ + if ( pIfCurr->ifa_addr->sa_family == AF_INET + && !(pIfCurr->ifa_flags & IFF_LOOPBACK)) + { + char szInetAddr[NI_MAXHOST]; + + memset(szInetAddr, 0, NI_MAXHOST); + getnameinfo(pIfCurr->ifa_addr, sizeof(struct sockaddr_in), + szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr); + + memset(szInetAddr, 0, NI_MAXHOST); + getnameinfo(pIfCurr->ifa_broadaddr, sizeof(struct sockaddr_in), + szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr); + + memset(szInetAddr, 0, NI_MAXHOST); + getnameinfo(pIfCurr->ifa_netmask, sizeof(struct sockaddr_in), + szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr); + + /* Search for the AF_LINK interface of the current AF_INET one and get the mac. */ + for (struct ifaddrs *pIfLinkCurr = pIfHead; pIfLinkCurr; pIfLinkCurr = pIfLinkCurr->ifa_next) + { + if ( pIfLinkCurr->ifa_addr->sa_family == AF_LINK + && !strcmp(pIfCurr->ifa_name, pIfLinkCurr->ifa_name)) + { + char szMac[32]; + uint8_t *pu8Mac = NULL; + struct sockaddr_dl *pLinkAddress = (struct sockaddr_dl *)pIfLinkCurr->ifa_addr; + + AssertPtr(pLinkAddress); + pu8Mac = (uint8_t *)LLADDR(pLinkAddress); + RTStrPrintf(szMac, sizeof(szMac), "%02X%02X%02X%02X%02X%02X", + pu8Mac[0], pu8Mac[1], pu8Mac[2], pu8Mac[3], pu8Mac[4], pu8Mac[5]); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac); + break; + } + } + + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfsReported); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, pIfCurr->ifa_flags & IFF_UP ? "Up" : "Down"); + + cIfsReported++; + } + } + + /* Free allocated resources. */ + freeifaddrs(pIfHead); + +#else /* !RT_OS_WINDOWS && !RT_OS_FREEBSD */ + /* + * Use SIOCGIFCONF to get a list of interface/protocol configurations. + * + * See "UNIX Network Programming Volume 1" by W. R. Stevens, section 17.6 + * for details on this ioctl. + */ + int sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) + { + int rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to get a socket: Error %Rrc\n", rc); + return rc; + } + + /* Call SIOCGIFCONF with the right sized buffer (remember the size). */ + static int s_cbBuf = 256; // 1024 + int cbBuf = s_cbBuf; + char *pchBuf; + struct ifconf IfConf; + int rc = VINF_SUCCESS; + for (;;) + { + pchBuf = (char *)RTMemTmpAllocZ(cbBuf); + if (!pchBuf) + { + rc = VERR_NO_TMP_MEMORY; + break; + } + + IfConf.ifc_len = cbBuf; + IfConf.ifc_buf = pchBuf; + if (ioctl(sd, SIOCGIFCONF, &IfConf) >= 0) + { + /* Hard to anticipate how space an address might possibly take, so + making some generous assumptions here to avoid performing the + query twice with different buffer sizes. */ + if (IfConf.ifc_len + 128 < cbBuf) + break; + } + else if (errno != EOVERFLOW) + { + rc = RTErrConvertFromErrno(errno); + break; + } + + /* grow the buffer */ + s_cbBuf = cbBuf *= 2; + RTMemFree(pchBuf); + } + if (RT_FAILURE(rc)) + { + close(sd); + RTMemTmpFree(pchBuf); + VGSvcError("VMInfo/Network: Error doing SIOCGIFCONF (cbBuf=%d): %Rrc\n", cbBuf, rc); + return rc; + } + + /* + * Iterate the interface/protocol configurations. + * + * Note! The current code naively assumes one IPv4 address per interface. + * This means that guest assigning more than one address to an + * interface will get multiple entries for one physical interface. + */ +# ifdef RT_OS_OS2 + struct ifreq *pPrevLinkAddr = NULL; +# endif + struct ifreq *pCur = IfConf.ifc_req; + size_t cbLeft = IfConf.ifc_len; + while (cbLeft >= sizeof(*pCur)) + { +# if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) + /* These two do not provide the sa_len member but only support address + * families which do not need extra bytes on the end. */ +# define SA_LEN(pAddr) sizeof(struct sockaddr) +# elif !defined(SA_LEN) +# define SA_LEN(pAddr) (pAddr)->sa_len +# endif + /* Figure the size of the current request. */ + size_t cbCur = RT_UOFFSETOF(struct ifreq, ifr_addr) + + SA_LEN(&pCur->ifr_addr); + cbCur = RT_MAX(cbCur, sizeof(struct ifreq)); +# if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) + Assert(pCur->ifr_addr.sa_family == AF_INET); +# endif + AssertBreak(cbCur <= cbLeft); + +# ifdef RT_OS_OS2 + /* On OS/2 we get the MAC address in the AF_LINK that the BSD 4.4 stack + emits. We boldly ASSUME these always comes first. */ + if ( pCur->ifr_addr.sa_family == AF_LINK + && ((struct sockaddr_dl *)&pCur->ifr_addr)->sdl_alen == 6) + pPrevLinkAddr = pCur; +# endif + + /* Skip it if it's not the kind of address we're looking for. */ + struct ifreq IfReqTmp; + bool fIfUp = false; + bool fSkip = false; + if (pCur->ifr_addr.sa_family != AF_INET) + fSkip = true; + else + { + /* Get the interface flags so we can detect loopback and check if it's up. */ + IfReqTmp = *pCur; + if (ioctl(sd, SIOCGIFFLAGS, &IfReqTmp) < 0) + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFFLAGS,%s) on socket: Error %Rrc\n", pCur->ifr_name, rc); + break; + } + fIfUp = !!(IfReqTmp.ifr_flags & IFF_UP); + if (IfReqTmp.ifr_flags & IFF_LOOPBACK) /* Skip the loopback device. */ + fSkip = true; + } + if (!fSkip) + { + size_t offSubProp = RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32", cIfsReported); + + sockaddr_in *pAddress = (sockaddr_in *)&pCur->ifr_addr; + strcpy(&szPropPath[offSubProp], "/V4/IP"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr)); + + /* Get the broadcast address. */ + IfReqTmp = *pCur; + if (ioctl(sd, SIOCGIFBRDADDR, &IfReqTmp) < 0) + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFBRDADDR) on socket: Error %Rrc\n", rc); + break; + } + pAddress = (sockaddr_in *)&IfReqTmp.ifr_broadaddr; + strcpy(&szPropPath[offSubProp], "/V4/Broadcast"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr)); + + /* Get the net mask. */ + IfReqTmp = *pCur; + if (ioctl(sd, SIOCGIFNETMASK, &IfReqTmp) < 0) + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFNETMASK) on socket: Error %Rrc\n", rc); + break; + } +# if defined(RT_OS_OS2) || defined(RT_OS_SOLARIS) + pAddress = (sockaddr_in *)&IfReqTmp.ifr_addr; +# else + pAddress = (sockaddr_in *)&IfReqTmp.ifr_netmask; +# endif + strcpy(&szPropPath[offSubProp], "/V4/Netmask"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr)); + +# if defined(RT_OS_SOLARIS) + /* + * "ifreq" is obsolete on Solaris. We use the recommended "lifreq". + * We might fail if the interface has not been assigned an IP address. + * That doesn't matter; as long as it's plumbed we can pick it up. + * But, if it has not acquired an IP address we cannot obtain it's MAC + * address this way, so we just use all zeros there. + */ + RTMAC IfMac; + struct lifreq IfReq; + RT_ZERO(IfReq); + AssertCompile(sizeof(IfReq.lifr_name) >= sizeof(pCur->ifr_name)); + strncpy(IfReq.lifr_name, pCur->ifr_name, sizeof(IfReq.lifr_name)); + if (ioctl(sd, SIOCGLIFADDR, &IfReq) >= 0) + { + struct arpreq ArpReq; + RT_ZERO(ArpReq); + memcpy(&ArpReq.arp_pa, &IfReq.lifr_addr, sizeof(struct sockaddr_in)); + + if (ioctl(sd, SIOCGARP, &ArpReq) >= 0) + memcpy(&IfMac, ArpReq.arp_ha.sa_data, sizeof(IfMac)); + else + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: failed to ioctl(SIOCGARP) on socket: Error %Rrc\n", rc); + break; + } + } + else + { + VGSvcVerbose(2, "VMInfo/Network: Interface '%s' has no assigned IP address, skipping ...\n", pCur->ifr_name); + continue; + } +# elif defined(RT_OS_OS2) + RTMAC IfMac; + if ( pPrevLinkAddr + && strncmp(pCur->ifr_name, pPrevLinkAddr->ifr_name, sizeof(pCur->ifr_name)) == 0) + { + struct sockaddr_dl *pDlAddr = (struct sockaddr_dl *)&pPrevLinkAddr->ifr_addr; + IfMac = *(PRTMAC)&pDlAddr->sdl_data[pDlAddr->sdl_nlen]; + } + else + RT_ZERO(IfMac); +#else + if (ioctl(sd, SIOCGIFHWADDR, &IfReqTmp) < 0) + { + rc = RTErrConvertFromErrno(errno); + VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFHWADDR) on socket: Error %Rrc\n", rc); + break; + } + RTMAC IfMac = *(PRTMAC)&IfReqTmp.ifr_hwaddr.sa_data[0]; +# endif + strcpy(&szPropPath[offSubProp], "/MAC"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%02X%02X%02X%02X%02X%02X", + IfMac.au8[0], IfMac.au8[1], IfMac.au8[2], IfMac.au8[3], IfMac.au8[4], IfMac.au8[5]); + + strcpy(&szPropPath[offSubProp], "/Status"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, fIfUp ? "Up" : "Down"); + + /* The name. */ + int rc2 = RTStrValidateEncodingEx(pCur->ifr_name, sizeof(pCur->ifr_name), 0); + if (RT_SUCCESS(rc2)) + { + strcpy(&szPropPath[offSubProp], "/Name"); + VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%.*s", sizeof(pCur->ifr_name), pCur->ifr_name); + } + + cIfsReported++; + } + + /* + * Next interface/protocol configuration. + */ + pCur = (struct ifreq *)((uintptr_t)pCur + cbCur); + cbLeft -= cbCur; + } + + RTMemTmpFree(pchBuf); + close(sd); + if (RT_FAILURE(rc)) + VGSvcError("VMInfo/Network: Network enumeration for interface %RU32 failed with error %Rrc\n", cIfsReported, rc); + +#endif /* !RT_OS_WINDOWS */ + +#if 0 /* Zapping not enabled yet, needs more testing first. */ + /* + * Zap all stale network interface data if the former (saved) network ifaces count + * is bigger than the current one. + */ + + /* Get former count. */ + uint32_t cIfsReportedOld; + rc = VGSvcReadPropUInt32(g_uVMInfoGuestPropSvcClientID, g_pszPropCacheValNetCount, &cIfsReportedOld, + 0 /* Min */, UINT32_MAX /* Max */); + if ( RT_SUCCESS(rc) + && cIfsReportedOld > cIfsReported) /* Are some ifaces not around anymore? */ + { + VGSvcVerbose(3, "VMInfo/Network: Stale interface data detected (%RU32 old vs. %RU32 current)\n", + cIfsReportedOld, cIfsReported); + + uint32_t uIfaceDeleteIdx = cIfsReported; + do + { + VGSvcVerbose(3, "VMInfo/Network: Deleting stale data of interface %d ...\n", uIfaceDeleteIdx); + rc = VGSvcPropCacheUpdateByPath(&g_VMInfoPropCache, NULL /* Value, delete */, 0 /* Flags */, "/VirtualBox/GuestInfo/Net/%RU32", uIfaceDeleteIdx++); + } while (RT_SUCCESS(rc)); + } + else if ( RT_FAILURE(rc) + && rc != VERR_NOT_FOUND) + { + VGSvcError("VMInfo/Network: Failed retrieving old network interfaces count with error %Rrc\n", rc); + } +#endif + + /* + * This property is a beacon which is _always_ written, even if the network configuration + * does not change. If this property is missing, the host assumes that all other GuestInfo + * properties are no longer valid. + */ + VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNetCount, "%RU32", cIfsReported); + + /* Don't fail here; just report everything we got. */ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vbsvcVMInfoWorker(bool volatile *pfShutdown) +{ + int rc; + + /* + * Tell the control thread that it can continue + * spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + +#ifdef RT_OS_WINDOWS + /* Required for network information (must be called per thread). */ + if (g_pfnWSAStartup) + { + WSADATA wsaData; + RT_ZERO(wsaData); + if (g_pfnWSAStartup(MAKEWORD(2, 2), &wsaData)) + VGSvcError("VMInfo/Network: WSAStartup failed! Error: %Rrc\n", RTErrConvertFromWin32(g_pfnWSAGetLastError())); + } +#endif + + /* + * Write the fixed properties first. + */ + vgsvcVMInfoWriteFixedProperties(); + + /* + * Now enter the loop retrieving runtime data continuously. + */ + for (;;) + { + rc = vgsvcVMInfoWriteUsers(); + if (RT_FAILURE(rc)) + break; + + rc = vgsvcVMInfoWriteNetwork(); + if (RT_FAILURE(rc)) + break; + + /* Whether to wait for event semaphore or not. */ + bool fWait = true; + + /* Check for location awareness. This most likely only + * works with VBox (latest) 4.1 and up. */ + + /* Check for new connection. */ + char *pszLAClientID = NULL; + int rc2 = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, g_pszLAActiveClient, true /* Read only */, + &pszLAClientID, NULL /* Flags */, NULL /* Timestamp */); + if (RT_SUCCESS(rc2)) + { + AssertPtr(pszLAClientID); + if (RTStrICmp(pszLAClientID, "0")) /* Is a client connected? */ + { + uint32_t uLAClientID = RTStrToInt32(pszLAClientID); + uint64_t uLAClientAttachedTS; + + /* Peek at "Attach" value to figure out if hotdesking happened. */ + char *pszAttach = NULL; + rc2 = vgsvcGetLAClientValue(uLAClientID, "Attach", &pszAttach, + &uLAClientAttachedTS); + + if ( RT_SUCCESS(rc2) + && ( !g_LAClientAttachedTS + || (g_LAClientAttachedTS != uLAClientAttachedTS))) + { + vgsvcFreeLAClientInfo(&g_LAClientInfo); + + /* Note: There is a race between setting the guest properties by the host and getting them by + * the guest. */ + rc2 = vgsvcGetLAClientInfo(uLAClientID, &g_LAClientInfo); + if (RT_SUCCESS(rc2)) + { + VGSvcVerbose(1, "VRDP: Hotdesk client %s with ID=%RU32, Name=%s, Domain=%s\n", + /* If g_LAClientAttachedTS is 0 this means there already was an active + * hotdesk session when VBoxService started. */ + !g_LAClientAttachedTS ? "already active" : g_LAClientInfo.fAttached ? "connected" : "disconnected", + uLAClientID, g_LAClientInfo.pszName, g_LAClientInfo.pszDomain); + + g_LAClientAttachedTS = g_LAClientInfo.uAttachedTS; + + /* Don't wait for event semaphore below anymore because we now know that the client + * changed. This means we need to iterate all VM information again immediately. */ + fWait = false; + } + else + { + static int s_iBitchedAboutLAClientInfo = 0; + if (s_iBitchedAboutLAClientInfo < 10) + { + s_iBitchedAboutLAClientInfo++; + VGSvcError("Error getting active location awareness client info, rc=%Rrc\n", rc2); + } + } + } + else if (RT_FAILURE(rc2)) + VGSvcError("Error getting attached value of location awareness client %RU32, rc=%Rrc\n", uLAClientID, rc2); + if (pszAttach) + RTStrFree(pszAttach); + } + else + { + VGSvcVerbose(1, "VRDP: UTTSC disconnected from VRDP server\n"); + vgsvcFreeLAClientInfo(&g_LAClientInfo); + } + + RTStrFree(pszLAClientID); + } + else + { + static int s_iBitchedAboutLAClient = 0; + if ( (rc2 != VERR_NOT_FOUND) /* No location awareness installed, skip. */ + && s_iBitchedAboutLAClient < 3) + { + s_iBitchedAboutLAClient++; + VGSvcError("VRDP: Querying connected location awareness client failed with rc=%Rrc\n", rc2); + } + } + + VGSvcVerbose(3, "VRDP: Handling location awareness done\n"); + + /* + * Flush all properties if we were restored. + */ + uint64_t idNewSession = g_idVMInfoSession; + VbglR3GetSessionId(&idNewSession); + if (idNewSession != g_idVMInfoSession) + { + VGSvcVerbose(3, "The VM session ID changed, flushing all properties\n"); + vgsvcVMInfoWriteFixedProperties(); + VGSvcPropCacheFlush(&g_VMInfoPropCache); + g_idVMInfoSession = idNewSession; + } + + /* + * Block for a while. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + if (fWait) + rc2 = RTSemEventMultiWait(g_hVMInfoEvent, g_cMsVMInfoInterval); + if (*pfShutdown) + break; + if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) + { + VGSvcError("RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); + rc = rc2; + break; + } + else if (RT_LIKELY(RT_SUCCESS(rc2))) + { + /* Reset event semaphore if it got triggered. */ + rc2 = RTSemEventMultiReset(g_hVMInfoEvent); + if (RT_FAILURE(rc2)) + rc2 = VGSvcError("RTSemEventMultiReset failed; rc2=%Rrc\n", rc2); + } + } + +#ifdef RT_OS_WINDOWS + if (g_pfnWSACleanup) + g_pfnWSACleanup(); +#endif + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vbsvcVMInfoStop(void) +{ + RTSemEventMultiSignal(g_hVMInfoEvent); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vbsvcVMInfoTerm(void) +{ + if (g_hVMInfoEvent != NIL_RTSEMEVENTMULTI) + { + /** @todo temporary solution: Zap all values which are not valid + * anymore when VM goes down (reboot/shutdown ). Needs to + * be replaced with "temporary properties" later. + * + * One idea is to introduce a (HGCM-)session guest property + * flag meaning that a guest property is only valid as long + * as the HGCM session isn't closed (e.g. guest application + * terminates). [don't remove till implemented] + */ + /** @todo r=bird: Drop the VbglR3GuestPropDelSet call here and use the cache + * since it remembers what we've written. */ + /* Delete the "../Net" branch. */ + const char *apszPat[1] = { "/VirtualBox/GuestInfo/Net/*" }; + int rc = VbglR3GuestPropDelSet(g_uVMInfoGuestPropSvcClientID, &apszPat[0], RT_ELEMENTS(apszPat)); + + /* Destroy LA client info. */ + vgsvcFreeLAClientInfo(&g_LAClientInfo); + + /* Destroy property cache. */ + VGSvcPropCacheDestroy(&g_VMInfoPropCache); + + /* Disconnect from guest properties service. */ + rc = VbglR3GuestPropDisconnect(g_uVMInfoGuestPropSvcClientID); + if (RT_FAILURE(rc)) + VGSvcError("Failed to disconnect from guest property service! Error: %Rrc\n", rc); + g_uVMInfoGuestPropSvcClientID = 0; + + RTSemEventMultiDestroy(g_hVMInfoEvent); + g_hVMInfoEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'vminfo' service description. + */ +VBOXSERVICE g_VMInfo = +{ + /* pszName. */ + "vminfo", + /* pszDescription. */ + "Virtual Machine Information", + /* pszUsage. */ + " [--vminfo-interval <ms>] [--vminfo-user-idle-threshold <ms>]" + , + /* pszOptions. */ + " --vminfo-interval Specifies the interval at which to retrieve the\n" + " VM information. The default is 10000 ms.\n" + " --vminfo-user-idle-threshold <ms>\n" + " Specifies the user idle threshold (in ms) for\n" + " considering a guest user as being idle. The default\n" + " is 5000 (5 seconds).\n" + , + /* methods */ + vbsvcVMInfoPreInit, + vbsvcVMInfoOption, + vbsvcVMInfoInit, + vbsvcVMInfoWorker, + vbsvcVMInfoStop, + vbsvcVMInfoTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h new file mode 100644 index 00000000..10e380be --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h @@ -0,0 +1,42 @@ +/* $Id: VBoxServiceVMInfo.h $ */ +/** @file + * VBoxServiceVMInfo.h - Internal VM info definitions. + */ + +/* + * Copyright (C) 2013-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +extern int VGSvcUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain, + const char *pszKey, const char *pszValueFormat, ...); + + +extern uint32_t g_uVMInfoUserIdleThresholdMS; + +#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h */ + diff --git a/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk b/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk new file mode 100644 index 00000000..9a92d243 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk @@ -0,0 +1,42 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for VBoxServicec test cases. +# + +# +# Copyright (C) 2010-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# tstUserInfo +# +PROGRAMS.win += tstUserInfo +tstUserInfo_TEMPLATE = VBoxGuestR3Exe +tstUserInfo_SOURCES = \ + tstUserInfo.cpp +tstUserInfo_VBOX_IMPORT_CHECKER.win.x86 = xp + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp b/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp new file mode 100644 index 00000000..3fa93638 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp @@ -0,0 +1,87 @@ +/* $Id: tstUserInfo.cpp $ */ +/** @file + * Test case for correct user environment. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +# include <iprt/win/shlobj.h> +#endif + +#include <iprt/initterm.h> +#include <iprt/path.h> +#include <iprt/env.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <VBox/log.h> +#include <VBox/version.h> +#include <VBox/VBoxGuestLib.h> + + +int main() +{ + /* + * Init globals and such. + */ + RTR3InitExeNoArguments(0); + + int rc = VbglR3Init(); + if (RT_FAILURE(rc)) + { + RTPrintf("VbglR3Init failed with rc=%Rrc.\n", rc); + return -1; + } +#ifdef RT_OS_WINDOWS + WCHAR wszPath[MAX_PATH]; + HRESULT hRes = SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, wszPath); + + if (SUCCEEDED(hRes)) + { + RTPrintf("SHGetFolderPathW (CSIDL_APPDATA) = %ls\n", wszPath); + hRes = SHGetFolderPathW(0, CSIDL_PERSONAL, 0, 0, wszPath); + if (SUCCEEDED(hRes)) + { + RTPrintf("SHGetFolderPathW (CSIDL_PERSONAL) = %ls\n", wszPath); + } + else + RTPrintf("SHGetFolderPathW (CSIDL_PERSONAL) returned error: 0x%x\n", hRes); + } + else + RTPrintf("SHGetFolderPathW (CSIDL_APPDATA) returned error: 0x%x\n", hRes); + + if (FAILED(hRes)) + rc = RTErrConvertFromWin32(hRes); + + /* Dump env bits. */ + RTPrintf("Environment:\n\n"); + RTPrintf("APPDATA = %s\n", RTEnvGet("APPDATA")); +#endif + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + diff --git a/src/VBox/Additions/common/VBoxVideo/.scm-settings b/src/VBox/Additions/common/VBoxVideo/.scm-settings new file mode 100644 index 00000000..706de605 --- /dev/null +++ b/src/VBox/Additions/common/VBoxVideo/.scm-settings @@ -0,0 +1,33 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for common/VBoxVideo +# + +# +# Copyright (C) 2010-2023 Oracle and/or its affiliates. +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# + +# This graphics stuff is using MIT to encourage kernel upstreaming. +--license-mit + diff --git a/src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp b/src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp new file mode 100644 index 00000000..7a5467a9 --- /dev/null +++ b/src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp @@ -0,0 +1,300 @@ +/* $Id: HGSMIBase.cpp $ */ +/** @file + * VirtualBox Video driver, common code - HGSMI guest-to-host communication. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <HGSMIBase.h> +#include <VBoxVideoIPRT.h> +#include <VBoxVideoGuest.h> +#include <VBoxVideoVBE.h> +#include <HGSMIChannels.h> +#include <HGSMIChSetup.h> + +/** Detect whether HGSMI is supported by the host. */ +DECLHIDDEN(bool) VBoxHGSMIIsSupported(void) +{ + uint16_t DispiId; + + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ID); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, VBE_DISPI_ID_HGSMI); + + DispiId = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA); + + return (DispiId == VBE_DISPI_ID_HGSMI); +} + + +/** + * Inform the host of the location of the host flags in VRAM via an HGSMI command. + * @returns IPRT status value. + * @returns VERR_NOT_IMPLEMENTED if the host does not support the command. + * @returns VERR_NO_MEMORY if a heap allocation fails. + * @param pCtx the context of the guest heap to use. + * @param offLocation the offset chosen for the flags withing guest VRAM. + */ +DECLHIDDEN(int) VBoxHGSMIReportFlagsLocation(PHGSMIGUESTCOMMANDCONTEXT pCtx, HGSMIOFFSET offLocation) +{ + + /* Allocate the IO buffer. */ + HGSMIBUFFERLOCATION RT_UNTRUSTED_VOLATILE_HOST *p = + (HGSMIBUFFERLOCATION RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pCtx, sizeof(*p), HGSMI_CH_HGSMI, + HGSMI_CC_HOST_FLAGS_LOCATION); + if (!p) + return VERR_NO_MEMORY; + + /* Prepare data to be sent to the host. */ + p->offLocation = offLocation; + p->cbLocation = sizeof(HGSMIHOSTFLAGS); + /* No need to check that the buffer is valid as we have just allocated it. */ + VBoxHGSMIBufferSubmit(pCtx, p); + /* Free the IO buffer. */ + VBoxHGSMIBufferFree(pCtx, p); + + return VINF_SUCCESS; +} + + +/** + * Notify the host of HGSMI-related guest capabilities via an HGSMI command. + * @returns IPRT status value. + * @returns VERR_NOT_IMPLEMENTED if the host does not support the command. + * @returns VERR_NO_MEMORY if a heap allocation fails. + * @param pCtx the context of the guest heap to use. + * @param fCaps the capabilities to report, see VBVACAPS. + */ +DECLHIDDEN(int) VBoxHGSMISendCapsInfo(PHGSMIGUESTCOMMANDCONTEXT pCtx, uint32_t fCaps) +{ + + /* Allocate the IO buffer. */ + VBVACAPS RT_UNTRUSTED_VOLATILE_HOST *p = + (VBVACAPS RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pCtx, sizeof(*p), HGSMI_CH_VBVA, VBVA_INFO_CAPS); + + if (!p) + return VERR_NO_MEMORY; + + /* Prepare data to be sent to the host. */ + p->rc = VERR_NOT_IMPLEMENTED; + p->fCaps = fCaps; + /* No need to check that the buffer is valid as we have just allocated it. */ + VBoxHGSMIBufferSubmit(pCtx, p); + + AssertRC(p->rc); + /* Free the IO buffer. */ + VBoxHGSMIBufferFree(pCtx, p); + return p->rc; +} + + +/** + * Get the information needed to map the basic communication structures in + * device memory into our address space. All pointer parameters are optional. + * + * @param cbVRAM how much video RAM is allocated to the device + * @param poffVRAMBaseMapping where to save the offset from the start of the + * device VRAM of the whole area to map + * @param pcbMapping where to save the mapping size + * @param poffGuestHeapMemory where to save the offset into the mapped area + * of the guest heap backing memory + * @param pcbGuestHeapMemory where to save the size of the guest heap + * backing memory + * @param poffHostFlags where to save the offset into the mapped area + * of the host flags + */ +DECLHIDDEN(void) VBoxHGSMIGetBaseMappingInfo(uint32_t cbVRAM, + uint32_t *poffVRAMBaseMapping, + uint32_t *pcbMapping, + uint32_t *poffGuestHeapMemory, + uint32_t *pcbGuestHeapMemory, + uint32_t *poffHostFlags) +{ + AssertPtrNullReturnVoid(poffVRAMBaseMapping); + AssertPtrNullReturnVoid(pcbMapping); + AssertPtrNullReturnVoid(poffGuestHeapMemory); + AssertPtrNullReturnVoid(pcbGuestHeapMemory); + AssertPtrNullReturnVoid(poffHostFlags); + if (poffVRAMBaseMapping) + *poffVRAMBaseMapping = cbVRAM - VBVA_ADAPTER_INFORMATION_SIZE; + if (pcbMapping) + *pcbMapping = VBVA_ADAPTER_INFORMATION_SIZE; + if (poffGuestHeapMemory) + *poffGuestHeapMemory = 0; + if (pcbGuestHeapMemory) + *pcbGuestHeapMemory = VBVA_ADAPTER_INFORMATION_SIZE + - sizeof(HGSMIHOSTFLAGS); + if (poffHostFlags) + *poffHostFlags = VBVA_ADAPTER_INFORMATION_SIZE + - sizeof(HGSMIHOSTFLAGS); +} + +/** + * Query the host for an HGSMI configuration parameter via an HGSMI command. + * @returns iprt status value + * @param pCtx the context containing the heap used + * @param u32Index the index of the parameter to query, + * @see VBVACONF32::u32Index + * @param pulValue where to store the value of the parameter on success + */ +DECLHIDDEN(int) VBoxQueryConfHGSMI(PHGSMIGUESTCOMMANDCONTEXT pCtx, uint32_t u32Index, uint32_t *pulValue) +{ + VBVACONF32 *p; + + /* Allocate the IO buffer. */ + p = (VBVACONF32 *)VBoxHGSMIBufferAlloc(pCtx, sizeof(*p), HGSMI_CH_VBVA, VBVA_QUERY_CONF32); + if (!p) + return VERR_NO_MEMORY; + + /* Prepare data to be sent to the host. */ + p->u32Index = u32Index; + p->u32Value = UINT32_MAX; + /* No need to check that the buffer is valid as we have just allocated it. */ + VBoxHGSMIBufferSubmit(pCtx, p); + *pulValue = p->u32Value; + /* Free the IO buffer. */ + VBoxHGSMIBufferFree(pCtx, p); + return VINF_SUCCESS; +} + +/** + * Pass the host a new mouse pointer shape via an HGSMI command. + * + * @returns success or failure + * @param pCtx the context containing the heap to be used + * @param fFlags cursor flags, @see VMMDevReqMousePointer::fFlags + * @param cHotX horizontal position of the hot spot + * @param cHotY vertical position of the hot spot + * @param cWidth width in pixels of the cursor + * @param cHeight height in pixels of the cursor + * @param pPixels pixel data, @see VMMDevReqMousePointer for the format + * @param cbLength size in bytes of the pixel data + */ +DECLHIDDEN(int) VBoxHGSMIUpdatePointerShape(PHGSMIGUESTCOMMANDCONTEXT pCtx, uint32_t fFlags, + uint32_t cHotX, uint32_t cHotY, uint32_t cWidth, uint32_t cHeight, + uint8_t *pPixels, uint32_t cbLength) +{ + VBVAMOUSEPOINTERSHAPE *p; + uint32_t cbPixels = 0; + int rc; + + if (fFlags & VBOX_MOUSE_POINTER_SHAPE) + { + /* + * Size of the pointer data: + * sizeof (AND mask) + sizeof (XOR_MASK) + */ + cbPixels = ((((cWidth + 7) / 8) * cHeight + 3) & ~3) + + cWidth * 4 * cHeight; + if (cbPixels > cbLength) + return VERR_INVALID_PARAMETER; + /* + * If shape is supplied, then always create the pointer visible. + * See comments in 'vboxUpdatePointerShape' + */ + fFlags |= VBOX_MOUSE_POINTER_VISIBLE; + } + /* Allocate the IO buffer. */ + p = (VBVAMOUSEPOINTERSHAPE *)VBoxHGSMIBufferAlloc(pCtx, sizeof(*p) + cbPixels, HGSMI_CH_VBVA, + VBVA_MOUSE_POINTER_SHAPE); + if (!p) + return VERR_NO_MEMORY; + /* Prepare data to be sent to the host. */ + /* Will be updated by the host. */ + p->i32Result = VINF_SUCCESS; + /* We have our custom flags in the field */ + p->fu32Flags = fFlags; + p->u32HotX = cHotX; + p->u32HotY = cHotY; + p->u32Width = cWidth; + p->u32Height = cHeight; + if (cbPixels) + /* Copy the actual pointer data. */ + memcpy (p->au8Data, pPixels, cbPixels); + /* No need to check that the buffer is valid as we have just allocated it. */ + VBoxHGSMIBufferSubmit(pCtx, p); + rc = p->i32Result; + /* Free the IO buffer. */ + VBoxHGSMIBufferFree(pCtx, p); + return rc; +} + + +/** + * Report the guest cursor position. The host may wish to use this information + * to re-position its own cursor (though this is currently unlikely). The + * current host cursor position is returned. + * @param pCtx The context containing the heap used. + * @param fReportPosition Are we reporting a position? + * @param x Guest cursor X position. + * @param y Guest cursor Y position. + * @param pxHost Host cursor X position is stored here. Optional. + * @param pyHost Host cursor Y position is stored here. Optional. + * @returns iprt status code. + * @returns VERR_NO_MEMORY HGSMI heap allocation failed. + */ +DECLHIDDEN(int) VBoxHGSMICursorPosition(PHGSMIGUESTCOMMANDCONTEXT pCtx, bool fReportPosition, + uint32_t x, uint32_t y, uint32_t *pxHost, uint32_t *pyHost) +{ + VBVACURSORPOSITION *p; + + /* Allocate the IO buffer. */ + p = (VBVACURSORPOSITION *)VBoxHGSMIBufferAlloc(pCtx, sizeof(*p), HGSMI_CH_VBVA, + VBVA_CURSOR_POSITION); + if (!p) + return VERR_NO_MEMORY; + /* Prepare data to be sent to the host. */ + p->fReportPosition = fReportPosition; + p->x = x; + p->y = y; + /* No need to check that the buffer is valid as we have just allocated it. */ + VBoxHGSMIBufferSubmit(pCtx, p); + if (pxHost) + *pxHost = p->x; + if (pyHost) + *pyHost = p->y; + /* Free the IO buffer. */ + VBoxHGSMIBufferFree(pCtx, p); + return VINF_SUCCESS; +} + + +/** + * @todo Mouse pointer position to be read from VMMDev memory, address of the + * memory region can be queried from VMMDev via an IOCTL. This VMMDev memory + * region will contain host information which is needed by the guest. + * + * Reading will not cause a switch to the host. + * + * Have to take into account: + * * synchronization: host must write to the memory only from EMT, + * large structures must be read under flag, which tells the host + * that the guest is currently reading the memory (OWNER flag?). + * * guest writes: may be allocate a page for the host info and make + * the page readonly for the guest. + * * the information should be available only for additions drivers. + * * VMMDev additions driver will inform the host which version of the info + * it expects, host must support all versions. + */ diff --git a/src/VBox/Additions/common/VBoxVideo/HGSMIBuffers.cpp b/src/VBox/Additions/common/VBoxVideo/HGSMIBuffers.cpp new file mode 100644 index 00000000..84a35a4a --- /dev/null +++ b/src/VBox/Additions/common/VBoxVideo/HGSMIBuffers.cpp @@ -0,0 +1,124 @@ +/* $Id: HGSMIBuffers.cpp $ */ +/** @file + * VirtualBox Video driver, common code - HGSMI buffer management. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <VBoxVideoGuest.h> +#include <VBoxVideoVBE.h> +#include <VBoxVideoIPRT.h> + +/** + * Set up the HGSMI guest-to-host command context. + * @returns iprt status value + * @param pCtx the context to set up + * @param pvGuestHeapMemory a pointer to the mapped backing memory for + * the guest heap + * @param cbGuestHeapMemory the size of the backing memory area + * @param offVRAMGuestHeapMemory the offset of the memory pointed to by + * @a pvGuestHeapMemory within the video RAM + * @param pEnv HGSMI environment. + */ +DECLHIDDEN(int) VBoxHGSMISetupGuestContext(PHGSMIGUESTCOMMANDCONTEXT pCtx, + void *pvGuestHeapMemory, + uint32_t cbGuestHeapMemory, + uint32_t offVRAMGuestHeapMemory, + const HGSMIENV *pEnv) +{ + /** @todo should we be using a fixed ISA port value here? */ + pCtx->port = (RTIOPORT)VGA_PORT_HGSMI_GUEST; +#ifdef VBOX_WDDM_MINIPORT + return VBoxSHGSMIInit(&pCtx->heapCtx, pvGuestHeapMemory, + cbGuestHeapMemory, offVRAMGuestHeapMemory, pEnv); +#else + return HGSMIHeapSetup(&pCtx->heapCtx, pvGuestHeapMemory, + cbGuestHeapMemory, offVRAMGuestHeapMemory, pEnv); +#endif +} + + +/** + * Allocate and initialise a command descriptor in the guest heap for a + * guest-to-host command. + * + * @returns pointer to the descriptor's command data buffer + * @param pCtx the context containing the heap to be used + * @param cbData the size of the command data to go into the descriptor + * @param u8Ch the HGSMI channel to be used, set to the descriptor + * @param u16Op the HGSMI command to be sent, set to the descriptor + */ +DECLHIDDEN(void RT_UNTRUSTED_VOLATILE_HOST *) VBoxHGSMIBufferAlloc(PHGSMIGUESTCOMMANDCONTEXT pCtx, + HGSMISIZE cbData, + uint8_t u8Ch, + uint16_t u16Op) +{ +#ifdef VBOX_WDDM_MINIPORT + return VBoxSHGSMIHeapAlloc(&pCtx->heapCtx, cbData, u8Ch, u16Op); +#else + return HGSMIHeapAlloc(&pCtx->heapCtx, cbData, u8Ch, u16Op); +#endif +} + + +/** + * Free a descriptor allocated by @a VBoxHGSMIBufferAlloc. + * + * @param pCtx the context containing the heap used + * @param pvBuffer the pointer returned by @a VBoxHGSMIBufferAlloc + */ +DECLHIDDEN(void) VBoxHGSMIBufferFree(PHGSMIGUESTCOMMANDCONTEXT pCtx, void RT_UNTRUSTED_VOLATILE_HOST *pvBuffer) +{ +#ifdef VBOX_WDDM_MINIPORT + VBoxSHGSMIHeapFree(&pCtx->heapCtx, pvBuffer); +#else + HGSMIHeapFree(&pCtx->heapCtx, pvBuffer); +#endif +} + +/** + * Submit a command descriptor allocated by @a VBoxHGSMIBufferAlloc. + * + * @param pCtx the context containing the heap used + * @param pvBuffer the pointer returned by @a VBoxHGSMIBufferAlloc + */ +DECLHIDDEN(int) VBoxHGSMIBufferSubmit(PHGSMIGUESTCOMMANDCONTEXT pCtx, void RT_UNTRUSTED_VOLATILE_HOST *pvBuffer) +{ + /* Initialize the buffer and get the offset for port IO. */ + HGSMIOFFSET offBuffer = HGSMIHeapBufferOffset(HGSMIGUESTCMDHEAP_GET(&pCtx->heapCtx), pvBuffer); + + Assert(offBuffer != HGSMIOFFSET_VOID); + if (offBuffer != HGSMIOFFSET_VOID) + { + /* Submit the buffer to the host. */ + VBVO_PORT_WRITE_U32(pCtx->port, offBuffer); + /* Make the compiler aware that the host has changed memory. */ + ASMCompilerBarrier(); + return VINF_SUCCESS; + } + + return VERR_INVALID_PARAMETER; +} diff --git a/src/VBox/Additions/common/VBoxVideo/HGSMIHostCmd.cpp b/src/VBox/Additions/common/VBoxVideo/HGSMIHostCmd.cpp new file mode 100644 index 00000000..bd1733e5 --- /dev/null +++ b/src/VBox/Additions/common/VBoxVideo/HGSMIHostCmd.cpp @@ -0,0 +1,245 @@ +/* $Id: HGSMIHostCmd.cpp $ */ +/** @file + * VirtualBox Video driver, common code - HGSMI host-to-guest communication. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <VBoxVideoGuest.h> +#include <VBoxVideoVBE.h> +#include <VBoxVideoIPRT.h> +#include <HGSMIHostCmd.h> + +/** + * Initialise the host context structure. + * + * @param pCtx the context structure to initialise + * @param pvBaseMapping where the basic HGSMI structures are mapped at + * @param offHostFlags the offset of the host flags into the basic HGSMI + * structures + * @param pvHostAreaMapping where the area for the host heap is mapped at + * @param offVRAMHostArea offset of the host heap area into VRAM + * @param cbHostArea size in bytes of the host heap area + */ +DECLHIDDEN(void) VBoxHGSMISetupHostContext(PHGSMIHOSTCOMMANDCONTEXT pCtx, + void *pvBaseMapping, + uint32_t offHostFlags, + void *pvHostAreaMapping, + uint32_t offVRAMHostArea, + uint32_t cbHostArea) +{ + uint8_t *pu8HostFlags = ((uint8_t *)pvBaseMapping) + offHostFlags; + pCtx->pfHostFlags = (HGSMIHOSTFLAGS *)pu8HostFlags; + /** @todo should we really be using a fixed ISA port value here? */ + pCtx->port = (RTIOPORT)VGA_PORT_HGSMI_HOST; + HGSMIAreaInitialize(&pCtx->areaCtx, pvHostAreaMapping, cbHostArea, + offVRAMHostArea); +} + + +/** Send completion notification to the host for the command located at offset + * @a offt into the host command buffer. */ +static void HGSMINotifyHostCmdComplete(PHGSMIHOSTCOMMANDCONTEXT pCtx, HGSMIOFFSET offt) +{ + VBVO_PORT_WRITE_U32(pCtx->port, offt); +} + + +/** + * Inform the host that a command has been handled. + * + * @param pCtx the context containing the heap to be used + * @param pvMem pointer into the heap as mapped in @a pCtx to the command to + * be completed + */ +DECLHIDDEN(void) VBoxHGSMIHostCmdComplete(PHGSMIHOSTCOMMANDCONTEXT pCtx, void RT_UNTRUSTED_VOLATILE_HOST *pvMem) +{ + HGSMIBUFFERHEADER RT_UNTRUSTED_VOLATILE_GUEST *pHdr = HGSMIBufferHeaderFromData(pvMem); + HGSMIOFFSET offMem = HGSMIPointerToOffset(&pCtx->areaCtx, pHdr); + Assert(offMem != HGSMIOFFSET_VOID); + if (offMem != HGSMIOFFSET_VOID) + HGSMINotifyHostCmdComplete(pCtx, offMem); +} + + +/** Submit an incoming host command to the appropriate handler. */ +static void hgsmiHostCmdProcess(PHGSMIHOSTCOMMANDCONTEXT pCtx, + HGSMIOFFSET offBuffer) +{ + int rc = HGSMIBufferProcess(&pCtx->areaCtx, &pCtx->channels, offBuffer); + Assert(!RT_FAILURE(rc)); + if(RT_FAILURE(rc)) + { + /* failure means the command was not submitted to the handler for some reason + * it's our responsibility to notify its completion in this case */ + HGSMINotifyHostCmdComplete(pCtx, offBuffer); + } + /* if the cmd succeeded it's responsibility of the callback to complete it */ +} + +/** Get the next command from the host. */ +static HGSMIOFFSET hgsmiGetHostBuffer(PHGSMIHOSTCOMMANDCONTEXT pCtx) +{ + return VBVO_PORT_READ_U32(pCtx->port); +} + + +/** Get and handle the next command from the host. */ +static void hgsmiHostCommandQueryProcess(PHGSMIHOSTCOMMANDCONTEXT pCtx) +{ + HGSMIOFFSET offset = hgsmiGetHostBuffer(pCtx); + AssertReturnVoid(offset != HGSMIOFFSET_VOID); + hgsmiHostCmdProcess(pCtx, offset); +} + + +/** Drain the host command queue. */ +DECLHIDDEN(void) VBoxHGSMIProcessHostQueue(PHGSMIHOSTCOMMANDCONTEXT pCtx) +{ + while (pCtx->pfHostFlags->u32HostFlags & HGSMIHOSTFLAGS_COMMANDS_PENDING) + { + if (!ASMAtomicCmpXchgBool(&pCtx->fHostCmdProcessing, true, false)) + return; + hgsmiHostCommandQueryProcess(pCtx); + ASMAtomicWriteBool(&pCtx->fHostCmdProcessing, false); + } +} + + +/** Tell the host about the location of the area of VRAM set aside for the host + * heap. */ +static int vboxHGSMIReportHostArea(PHGSMIGUESTCOMMANDCONTEXT pCtx, + uint32_t u32AreaOffset, uint32_t u32AreaSize) +{ + VBVAINFOHEAP *p; + int rc = VINF_SUCCESS; + + /* Allocate the IO buffer. */ + p = (VBVAINFOHEAP *)VBoxHGSMIBufferAlloc(pCtx, + sizeof (VBVAINFOHEAP), HGSMI_CH_VBVA, + VBVA_INFO_HEAP); + if (p) + { + /* Prepare data to be sent to the host. */ + p->u32HeapOffset = u32AreaOffset; + p->u32HeapSize = u32AreaSize; + rc = VBoxHGSMIBufferSubmit(pCtx, p); + /* Free the IO buffer. */ + VBoxHGSMIBufferFree(pCtx, p); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Get the information needed to map the area used by the host to send back + * requests. + * + * @param pCtx the context containing the heap to use + * @param cbVRAM how much video RAM is allocated to the device + * @param offVRAMBaseMapping the offset of the basic communication structures + * into the guest's VRAM + * @param poffVRAMHostArea where to store the offset into VRAM of the host + * heap area + * @param pcbHostArea where to store the size of the host heap area + */ +DECLHIDDEN(void) VBoxHGSMIGetHostAreaMapping(PHGSMIGUESTCOMMANDCONTEXT pCtx, + uint32_t cbVRAM, + uint32_t offVRAMBaseMapping, + uint32_t *poffVRAMHostArea, + uint32_t *pcbHostArea) +{ + uint32_t offVRAMHostArea = offVRAMBaseMapping, cbHostArea = 0; + + AssertPtrReturnVoid(poffVRAMHostArea); + AssertPtrReturnVoid(pcbHostArea); + VBoxQueryConfHGSMI(pCtx, VBOX_VBVA_CONF32_HOST_HEAP_SIZE, &cbHostArea); + if (cbHostArea != 0) + { + uint32_t cbHostAreaMaxSize = cbVRAM / 4; + /** @todo what is the idea of this? */ + if (cbHostAreaMaxSize >= VBVA_ADAPTER_INFORMATION_SIZE) + { + cbHostAreaMaxSize -= VBVA_ADAPTER_INFORMATION_SIZE; + } + if (cbHostArea > cbHostAreaMaxSize) + { + cbHostArea = cbHostAreaMaxSize; + } + /* Round up to 4096 bytes. */ + cbHostArea = (cbHostArea + 0xFFF) & ~0xFFF; + offVRAMHostArea = offVRAMBaseMapping - cbHostArea; + } + + *pcbHostArea = cbHostArea; + *poffVRAMHostArea = offVRAMHostArea; + // LogFunc(("offVRAMHostArea = 0x%08X, cbHostArea = 0x%08X\n", + // offVRAMHostArea, cbHostArea)); +} + + +/** + * Tell the host about the ways it can use to communicate back to us via an + * HGSMI command + * + * @returns iprt status value + * @param pCtx the context containing the heap to use + * @param offVRAMFlagsLocation where we wish the host to place its flags + * relative to the start of the VRAM + * @param fCaps additions HGSMI capabilities the guest + * supports + * @param offVRAMHostArea offset into VRAM of the host heap area + * @param cbHostArea size in bytes of the host heap area + */ +DECLHIDDEN(int) VBoxHGSMISendHostCtxInfo(PHGSMIGUESTCOMMANDCONTEXT pCtx, + HGSMIOFFSET offVRAMFlagsLocation, + uint32_t fCaps, + uint32_t offVRAMHostArea, + uint32_t cbHostArea) +{ + // Log(("VBoxVideo::vboxSetupAdapterInfo\n")); + + /* setup the flags first to ensure they are initialized by the time the + * host heap is ready */ + int rc = VBoxHGSMIReportFlagsLocation(pCtx, offVRAMFlagsLocation); + AssertRC(rc); + if (RT_SUCCESS(rc) && fCaps) + { + /* Inform about caps */ + rc = VBoxHGSMISendCapsInfo(pCtx, fCaps); + AssertRC(rc); + } + if (RT_SUCCESS (rc)) + { + /* Report the host heap location. */ + rc = vboxHGSMIReportHostArea(pCtx, offVRAMHostArea, cbHostArea); + AssertRC(rc); + } + // Log(("VBoxVideo::vboxSetupAdapterInfo finished rc = %d\n", rc)); + return rc; +} diff --git a/src/VBox/Additions/common/VBoxVideo/Modesetting.cpp b/src/VBox/Additions/common/VBoxVideo/Modesetting.cpp new file mode 100644 index 00000000..a84613a7 --- /dev/null +++ b/src/VBox/Additions/common/VBoxVideo/Modesetting.cpp @@ -0,0 +1,419 @@ +/* $Id: Modesetting.cpp $ */ +/** @file + * VirtualBox Video driver, common code - HGSMI initialisation and helper + * functions. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <VBoxVideoGuest.h> +#include <VBoxVideoVBE.h> +#include <HGSMIChannels.h> + +#ifndef VBOX_GUESTR3XF86MOD +# include <VBoxVideoIPRT.h> +#endif + +/** + * Gets the count of virtual monitors attached to the guest via an HGSMI + * command + * + * @returns the right count on success or 1 on failure. + * @param pCtx the context containing the heap to use + */ +DECLHIDDEN(uint32_t) VBoxHGSMIGetMonitorCount(PHGSMIGUESTCOMMANDCONTEXT pCtx) +{ + /* Query the configured number of displays. */ + uint32_t cDisplays = 0; + VBoxQueryConfHGSMI(pCtx, VBOX_VBVA_CONF32_MONITOR_COUNT, &cDisplays); + // LogFunc(("cDisplays = %d\n", cDisplays)); + if (cDisplays == 0 || cDisplays > VBOX_VIDEO_MAX_SCREENS) + /* Host reported some bad value. Continue in the 1 screen mode. */ + cDisplays = 1; + return cDisplays; +} + + +/** + * Query whether the virtual hardware supports VBE_DISPI_ID_CFG + * and set the interface. + * + * @returns Whether the interface is supported. + */ +DECLHIDDEN(bool) VBoxVGACfgAvailable(void) +{ + uint16_t DispiId; + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ID); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, VBE_DISPI_ID_CFG); + DispiId = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA); + return (DispiId == VBE_DISPI_ID_CFG); +} + + +/** + * Query a configuration value from the virtual hardware which supports VBE_DISPI_ID_CFG. + * I.e. use this function only if VBoxVGACfgAvailable returns true. + * + * @returns Whether the value is supported. + * @param u16Id Identifier of the configuration value (VBE_DISPI_CFG_ID_*). + * @param pu32Value Where to store value from the host. + * @param u32DefValue What to assign to *pu32Value if the value is not supported. + */ +DECLHIDDEN(bool) VBoxVGACfgQuery(uint16_t u16Id, uint32_t *pu32Value, uint32_t u32DefValue) +{ + uint32_t u32; + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_CFG); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, VBE_DISPI_CFG_MASK_SUPPORT | u16Id); + u32 = VBVO_PORT_READ_U32(VBE_DISPI_IOPORT_DATA); + if (u32) + { + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, u16Id); + *pu32Value = VBVO_PORT_READ_U32(VBE_DISPI_IOPORT_DATA); + return true; + } + + *pu32Value = u32DefValue; + return false; +} + + +/** + * Returns the size of the video RAM in bytes. + * + * @returns the size + */ +DECLHIDDEN(uint32_t) VBoxVideoGetVRAMSize(void) +{ + /** @note A 32bit read on this port returns the VRAM size if interface is older than VBE_DISPI_ID_CFG. */ + return VBVO_PORT_READ_U32(VBE_DISPI_IOPORT_DATA); +} + + +/** + * Check whether this hardware allows the display width to have non-multiple- + * of-eight values. + * + * @returns true if any width is allowed, false otherwise. + */ +DECLHIDDEN(bool) VBoxVideoAnyWidthAllowed(void) +{ + unsigned DispiId; + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ID); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, VBE_DISPI_ID_ANYX); + DispiId = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA); + return (DispiId == VBE_DISPI_ID_ANYX); +} + + +/** + * Tell the host about how VRAM is divided up between each screen via an HGSMI + * command. It is acceptable to specifiy identical data for each screen if + * they share a single framebuffer. + * + * @returns iprt status code, either VERR_NO_MEMORY or the status returned by + * @a pfnFill + * @todo What was I thinking of with that callback function? It + * would be much simpler to just pass in a structure in normal + * memory and copy it. + * @param pCtx the context containing the heap to use + * @param u32Count the number of screens we are activating + * @param pfnFill a callback which initialises the VBVAINFOVIEW structures + * for all screens + * @param pvData context data for @a pfnFill + */ +DECLHIDDEN(int) VBoxHGSMISendViewInfo(PHGSMIGUESTCOMMANDCONTEXT pCtx, + uint32_t u32Count, + PFNHGSMIFILLVIEWINFO pfnFill, + void *pvData) +{ + int rc; + /* Issue the screen info command. */ + VBVAINFOVIEW RT_UNTRUSTED_VOLATILE_HOST *pInfo = + (VBVAINFOVIEW RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pCtx, sizeof(VBVAINFOVIEW) * u32Count, + HGSMI_CH_VBVA, VBVA_INFO_VIEW); + if (pInfo) + { + rc = pfnFill(pvData, (VBVAINFOVIEW *)pInfo /* lazy bird */, u32Count); + if (RT_SUCCESS(rc)) + VBoxHGSMIBufferSubmit(pCtx, pInfo); + VBoxHGSMIBufferFree(pCtx, pInfo); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Set a video mode using port registers. This must be done for the first + * screen before every HGSMI modeset and also works when HGSM is not enabled. + * @param cWidth the mode width + * @param cHeight the mode height + * @param cVirtWidth the mode pitch + * @param cBPP the colour depth of the mode + * @param fFlags flags for the mode. These will be or-ed with the + * default _ENABLED flag, so unless you are restoring + * a saved mode or have special requirements you can pass + * zero here. + * @param cx the horizontal panning offset + * @param cy the vertical panning offset + */ +DECLHIDDEN(void) VBoxVideoSetModeRegisters(uint16_t cWidth, uint16_t cHeight, + uint16_t cVirtWidth, uint16_t cBPP, + uint16_t fFlags, uint16_t cx, + uint16_t cy) +{ + /* set the mode characteristics */ + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_XRES); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cWidth); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_YRES); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cHeight); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_VIRT_WIDTH); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cVirtWidth); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_BPP); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cBPP); + /* enable the mode */ + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ENABLE); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, fFlags | VBE_DISPI_ENABLED); + /* Panning registers */ + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_X_OFFSET); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cx); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_Y_OFFSET); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cy); + /** @todo read from the port to see if the mode switch was successful */ +} + + +/** + * Get the video mode for the first screen using the port registers. All + * parameters are optional + * @returns true if the VBE mode returned is active, false if we are in VGA + * mode + * @note If anyone else needs additional register values just extend the + * function with additional parameters and fix any existing callers. + * @param pcWidth where to store the mode width + * @param pcHeight where to store the mode height + * @param pcVirtWidth where to store the mode pitch + * @param pcBPP where to store the colour depth of the mode + * @param pfFlags where to store the flags for the mode + */ +DECLHIDDEN(bool) VBoxVideoGetModeRegisters(uint16_t *pcWidth, uint16_t *pcHeight, + uint16_t *pcVirtWidth, uint16_t *pcBPP, + uint16_t *pfFlags) +{ + uint16_t fFlags; + + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ENABLE); + fFlags = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA); + if (pcWidth) + { + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_XRES); + *pcWidth = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA); + } + if (pcHeight) + { + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_YRES); + *pcHeight = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA); + } + if (pcVirtWidth) + { + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_VIRT_WIDTH); + *pcVirtWidth = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA); + } + if (pcBPP) + { + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_BPP); + *pcBPP = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA); + } + if (pfFlags) + *pfFlags = fFlags; + return RT_BOOL(fFlags & VBE_DISPI_ENABLED); +} + + +/** + * Disable our extended graphics mode and go back to VGA mode. + */ +DECLHIDDEN(void) VBoxVideoDisableVBE(void) +{ + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ENABLE); + VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, 0); +} + + +/** + * Set a video mode via an HGSMI request. The views must have been + * initialised first using @a VBoxHGSMISendViewInfo and if the mode is being + * set on the first display then it must be set first using registers. + * @param pCtx The context containing the heap to use. + * @param cDisplay the screen number + * @param cOriginX the horizontal displacement relative to the first screen + * @param cOriginY the vertical displacement relative to the first screen + * @param offStart the offset of the visible area of the framebuffer + * relative to the framebuffer start + * @param cbPitch the offset in bytes between the starts of two adjecent + * scan lines in video RAM + * @param cWidth the mode width + * @param cHeight the mode height + * @param cBPP the colour depth of the mode + * @param fFlags flags + */ +DECLHIDDEN(void) VBoxHGSMIProcessDisplayInfo(PHGSMIGUESTCOMMANDCONTEXT pCtx, + uint32_t cDisplay, + int32_t cOriginX, + int32_t cOriginY, + uint32_t offStart, + uint32_t cbPitch, + uint32_t cWidth, + uint32_t cHeight, + uint16_t cBPP, + uint16_t fFlags) +{ + /* Issue the screen info command. */ + VBVAINFOSCREEN RT_UNTRUSTED_VOLATILE_HOST *pScreen = + (VBVAINFOSCREEN RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pCtx, sizeof(VBVAINFOSCREEN), + HGSMI_CH_VBVA, VBVA_INFO_SCREEN); + if (pScreen != NULL) + { + pScreen->u32ViewIndex = cDisplay; + pScreen->i32OriginX = cOriginX; + pScreen->i32OriginY = cOriginY; + pScreen->u32StartOffset = offStart; + pScreen->u32LineSize = cbPitch; + pScreen->u32Width = cWidth; + pScreen->u32Height = cHeight; + pScreen->u16BitsPerPixel = cBPP; + pScreen->u16Flags = fFlags; + + VBoxHGSMIBufferSubmit(pCtx, pScreen); + + VBoxHGSMIBufferFree(pCtx, pScreen); + } + else + { + // LogFunc(("HGSMIHeapAlloc failed\n")); + } +} + + +/** Report the rectangle relative to which absolute pointer events should be + * expressed. This information remains valid until the next VBVA resize event + * for any screen, at which time it is reset to the bounding rectangle of all + * virtual screens. + * @param pCtx The context containing the heap to use. + * @param cOriginX Upper left X co-ordinate relative to the first screen. + * @param cOriginY Upper left Y co-ordinate relative to the first screen. + * @param cWidth Rectangle width. + * @param cHeight Rectangle height. + * @returns iprt status code. + * @returns VERR_NO_MEMORY HGSMI heap allocation failed. + */ +DECLHIDDEN(int) VBoxHGSMIUpdateInputMapping(PHGSMIGUESTCOMMANDCONTEXT pCtx, int32_t cOriginX, int32_t cOriginY, + uint32_t cWidth, uint32_t cHeight) +{ + int rc; + VBVAREPORTINPUTMAPPING *p; + // Log(("%s: cOriginX=%d, cOriginY=%d, cWidth=%u, cHeight=%u\n", __PRETTY_FUNCTION__, (int)cOriginX, (int)cOriginX, + // (unsigned)cWidth, (unsigned)cHeight)); + + /* Allocate the IO buffer. */ + p = (VBVAREPORTINPUTMAPPING *)VBoxHGSMIBufferAlloc(pCtx, sizeof(VBVAREPORTINPUTMAPPING), HGSMI_CH_VBVA, + VBVA_REPORT_INPUT_MAPPING); + if (p) + { + /* Prepare data to be sent to the host. */ + p->x = cOriginX; + p->y = cOriginY; + p->cx = cWidth; + p->cy = cHeight; + rc = VBoxHGSMIBufferSubmit(pCtx, p); + /* Free the IO buffer. */ + VBoxHGSMIBufferFree(pCtx, p); + } + else + rc = VERR_NO_MEMORY; + // LogFunc(("rc = %d\n", rc)); + return rc; +} + + +/** + * Get most recent video mode hints. + * @param pCtx the context containing the heap to use + * @param cScreens the number of screens to query hints for, starting at 0. + * @param paHints array of VBVAMODEHINT structures for receiving the hints. + * @returns iprt status code + * @returns VERR_NO_MEMORY HGSMI heap allocation failed. + * @returns VERR_NOT_SUPPORTED Host does not support this command. + */ +DECLHIDDEN(int) VBoxHGSMIGetModeHints(PHGSMIGUESTCOMMANDCONTEXT pCtx, + unsigned cScreens, VBVAMODEHINT *paHints) +{ + int rc; + VBVAQUERYMODEHINTS RT_UNTRUSTED_VOLATILE_HOST *pQuery; + + AssertPtrReturn(paHints, VERR_INVALID_POINTER); + pQuery = (VBVAQUERYMODEHINTS RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pCtx, + sizeof(VBVAQUERYMODEHINTS) + + cScreens * sizeof(VBVAMODEHINT), + HGSMI_CH_VBVA, VBVA_QUERY_MODE_HINTS); + if (pQuery != NULL) + { + pQuery->cHintsQueried = cScreens; + pQuery->cbHintStructureGuest = sizeof(VBVAMODEHINT); + pQuery->rc = VERR_NOT_SUPPORTED; + + VBoxHGSMIBufferSubmit(pCtx, pQuery); + rc = pQuery->rc; + if (RT_SUCCESS(rc)) + memcpy(paHints, (void *)(pQuery + 1), cScreens * sizeof(VBVAMODEHINT)); + + VBoxHGSMIBufferFree(pCtx, pQuery); + } + else + { + // LogFunc(("HGSMIHeapAlloc failed\n")); + rc = VERR_NO_MEMORY; + } + return rc; +} + + +/** + * Query the supported flags in VBVAINFOSCREEN::u16Flags. + * + * @returns The mask of VBVA_SCREEN_F_* flags or 0 if host does not support the request. + * @param pCtx the context containing the heap to use + */ +DECLHIDDEN(uint16_t) VBoxHGSMIGetScreenFlags(PHGSMIGUESTCOMMANDCONTEXT pCtx) +{ + uint32_t u32Flags = 0; + int rc = VBoxQueryConfHGSMI(pCtx, VBOX_VBVA_CONF32_SCREEN_FLAGS, &u32Flags); + // LogFunc(("u32Flags = 0x%x rc %Rrc\n", u32Flags, rc)); + if (RT_FAILURE(rc) || u32Flags > UINT16_MAX) + u32Flags = 0; + return (uint16_t)u32Flags; +} diff --git a/src/VBox/Additions/common/VBoxVideo/VBVABase.cpp b/src/VBox/Additions/common/VBoxVideo/VBVABase.cpp new file mode 100644 index 00000000..9bb0269f --- /dev/null +++ b/src/VBox/Additions/common/VBoxVideo/VBVABase.cpp @@ -0,0 +1,378 @@ +/* $Id: VBVABase.cpp $ */ +/** @file + * VirtualBox Video driver, common code - VBVA initialisation and helper + * functions. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <VBoxVideoGuest.h> +#include <VBoxVideoIPRT.h> +#include <HGSMIChannels.h> + +/* + * There is a hardware ring buffer in the graphics device video RAM, formerly + * in the VBox VMMDev PCI memory space. + * All graphics commands go there serialized by VBoxVBVABufferBeginUpdate. + * and vboxHwBufferEndUpdate. + * + * off32Free is writing position. off32Data is reading position. + * off32Free == off32Data means buffer is empty. + * There must be always gap between off32Data and off32Free when data + * are in the buffer. + * Guest only changes off32Free, host changes off32Data. + */ + +/* Forward declarations of internal functions. */ +static void vboxHwBufferFlush(PHGSMIGUESTCOMMANDCONTEXT pCtx); +static void vboxHwBufferPlaceDataAt(PVBVABUFFERCONTEXT pCtx, const void *p, + uint32_t cb, uint32_t offset); +static bool vboxHwBufferWrite(PVBVABUFFERCONTEXT pCtx, + PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, + const void *p, uint32_t cb); + + +static bool vboxVBVAInformHost(PVBVABUFFERCONTEXT pCtx, PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, int32_t cScreen, bool fEnable) +{ + bool fRc = false; + +#if 0 /* All callers check this */ + if (ppdev->bHGSMISupported) +#endif + { + VBVAENABLE_EX RT_UNTRUSTED_VOLATILE_HOST *pEnable = + (VBVAENABLE_EX RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pHGSMICtx, sizeof(VBVAENABLE_EX), + HGSMI_CH_VBVA, VBVA_ENABLE); + if (pEnable != NULL) + { + pEnable->Base.u32Flags = fEnable ? VBVA_F_ENABLE : VBVA_F_DISABLE; + pEnable->Base.u32Offset = pCtx->offVRAMBuffer; + pEnable->Base.i32Result = VERR_NOT_SUPPORTED; + if (cScreen >= 0) + { + pEnable->Base.u32Flags |= VBVA_F_EXTENDED | VBVA_F_ABSOFFSET; + pEnable->u32ScreenId = cScreen; + } + + VBoxHGSMIBufferSubmit(pHGSMICtx, pEnable); + + if (fEnable) + fRc = RT_SUCCESS(pEnable->Base.i32Result); + else + fRc = true; + + VBoxHGSMIBufferFree(pHGSMICtx, pEnable); + } + else + { + // LogFunc(("HGSMIHeapAlloc failed\n")); + } + } + + return fRc; +} + +/* + * Public hardware buffer methods. + */ +DECLHIDDEN(bool) VBoxVBVAEnable(PVBVABUFFERCONTEXT pCtx, + PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, + VBVABUFFER *pVBVA, int32_t cScreen) +{ + bool fRc = false; + + // LogFlowFunc(("pVBVA %p\n", pVBVA)); + +#if 0 /* All callers check this */ + if (ppdev->bHGSMISupported) +#endif + { + // LogFunc(("pVBVA %p vbva off 0x%x\n", pVBVA, pCtx->offVRAMBuffer)); + + pVBVA->hostFlags.u32HostEvents = 0; + pVBVA->hostFlags.u32SupportedOrders = 0; + pVBVA->off32Data = 0; + pVBVA->off32Free = 0; + memset(pVBVA->aRecords, 0, sizeof (pVBVA->aRecords)); + pVBVA->indexRecordFirst = 0; + pVBVA->indexRecordFree = 0; + pVBVA->cbPartialWriteThreshold = 256; + pVBVA->cbData = pCtx->cbBuffer - sizeof (VBVABUFFER) + sizeof (pVBVA->au8Data); + + pCtx->fHwBufferOverflow = false; + pCtx->pRecord = NULL; + pCtx->pVBVA = pVBVA; + + fRc = vboxVBVAInformHost(pCtx, pHGSMICtx, cScreen, true); + } + + if (!fRc) + { + VBoxVBVADisable(pCtx, pHGSMICtx, cScreen); + } + + return fRc; +} + +DECLHIDDEN(void) VBoxVBVADisable(PVBVABUFFERCONTEXT pCtx, + PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, + int32_t cScreen) +{ + // LogFlowFunc(("\n")); + + pCtx->fHwBufferOverflow = false; + pCtx->pRecord = NULL; + pCtx->pVBVA = NULL; + + vboxVBVAInformHost(pCtx, pHGSMICtx, cScreen, false); +} + +DECLHIDDEN(bool) VBoxVBVABufferBeginUpdate(PVBVABUFFERCONTEXT pCtx, + PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx) +{ + bool fRc = false; + + // LogFunc(("flags = 0x%08X\n", pCtx->pVBVA? pCtx->pVBVA->u32HostEvents: -1)); + + if ( pCtx->pVBVA + && (pCtx->pVBVA->hostFlags.u32HostEvents & VBVA_F_MODE_ENABLED)) + { + uint32_t indexRecordNext; + + Assert(!pCtx->fHwBufferOverflow); + Assert(pCtx->pRecord == NULL); + + indexRecordNext = (pCtx->pVBVA->indexRecordFree + 1) % VBVA_MAX_RECORDS; + + if (indexRecordNext == pCtx->pVBVA->indexRecordFirst) + { + /* All slots in the records queue are used. */ + vboxHwBufferFlush (pHGSMICtx); + } + + if (indexRecordNext == pCtx->pVBVA->indexRecordFirst) + { + /* Even after flush there is no place. Fail the request. */ + // LogFunc(("no space in the queue of records!!! first %d, last %d\n", + // pCtx->pVBVA->indexRecordFirst, pCtx->pVBVA->indexRecordFree)); + } + else + { + /* Initialize the record. */ + VBVARECORD *pRecord = &pCtx->pVBVA->aRecords[pCtx->pVBVA->indexRecordFree]; + + pRecord->cbRecord = VBVA_F_RECORD_PARTIAL; + + pCtx->pVBVA->indexRecordFree = indexRecordNext; + + // LogFunc(("indexRecordNext = %d\n", indexRecordNext)); + + /* Remember which record we are using. */ + pCtx->pRecord = pRecord; + + fRc = true; + } + } + + return fRc; +} + +DECLHIDDEN(void) VBoxVBVABufferEndUpdate(PVBVABUFFERCONTEXT pCtx) +{ + VBVARECORD *pRecord; + + // LogFunc(("\n")); + + Assert(pCtx->pVBVA); + + pRecord = pCtx->pRecord; + Assert(pRecord && (pRecord->cbRecord & VBVA_F_RECORD_PARTIAL)); + + /* Mark the record completed. */ + pRecord->cbRecord &= ~VBVA_F_RECORD_PARTIAL; + + pCtx->fHwBufferOverflow = false; + pCtx->pRecord = NULL; +} + +/* + * Private operations. + */ +static uint32_t vboxHwBufferAvail (const VBVABUFFER *pVBVA) +{ + int32_t i32Diff = pVBVA->off32Data - pVBVA->off32Free; + + return i32Diff > 0? i32Diff: pVBVA->cbData + i32Diff; +} + +static void vboxHwBufferFlush(PHGSMIGUESTCOMMANDCONTEXT pCtx) +{ + /* Issue the flush command. */ + VBVAFLUSH RT_UNTRUSTED_VOLATILE_HOST *pFlush = + (VBVAFLUSH RT_UNTRUSTED_VOLATILE_HOST * )VBoxHGSMIBufferAlloc(pCtx, sizeof(VBVAFLUSH), HGSMI_CH_VBVA, VBVA_FLUSH); + if (pFlush != NULL) + { + pFlush->u32Reserved = 0; + + VBoxHGSMIBufferSubmit(pCtx, pFlush); + + VBoxHGSMIBufferFree(pCtx, pFlush); + } + else + { + // LogFunc(("HGSMIHeapAlloc failed\n")); + } +} + +static void vboxHwBufferPlaceDataAt(PVBVABUFFERCONTEXT pCtx, const void *p, + uint32_t cb, uint32_t offset) +{ + VBVABUFFER *pVBVA = pCtx->pVBVA; + uint32_t u32BytesTillBoundary = pVBVA->cbData - offset; + uint8_t *dst = &pVBVA->au8Data[offset]; + int32_t i32Diff = cb - u32BytesTillBoundary; + + if (i32Diff <= 0) + { + /* Chunk will not cross buffer boundary. */ + memcpy (dst, p, cb); + } + else + { + /* Chunk crosses buffer boundary. */ + memcpy (dst, p, u32BytesTillBoundary); + memcpy (&pVBVA->au8Data[0], (uint8_t *)p + u32BytesTillBoundary, i32Diff); + } +} + +static bool vboxHwBufferWrite(PVBVABUFFERCONTEXT pCtx, + PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, + const void *p, uint32_t cb) +{ + VBVARECORD *pRecord; + uint32_t cbHwBufferAvail; + + uint32_t cbWritten = 0; + + VBVABUFFER *pVBVA = pCtx->pVBVA; + Assert(pVBVA); + + if (!pVBVA || pCtx->fHwBufferOverflow) + { + return false; + } + + Assert(pVBVA->indexRecordFirst != pVBVA->indexRecordFree); + + pRecord = pCtx->pRecord; + Assert(pRecord && (pRecord->cbRecord & VBVA_F_RECORD_PARTIAL)); + + // LogFunc(("%d\n", cb)); + + cbHwBufferAvail = vboxHwBufferAvail (pVBVA); + + while (cb > 0) + { + uint32_t cbChunk = cb; + + // LogFunc(("pVBVA->off32Free %d, pRecord->cbRecord 0x%08X, cbHwBufferAvail %d, cb %d, cbWritten %d\n", + // pVBVA->off32Free, pRecord->cbRecord, cbHwBufferAvail, cb, cbWritten)); + + if (cbChunk >= cbHwBufferAvail) + { + // LogFunc(("1) avail %d, chunk %d\n", cbHwBufferAvail, cbChunk)); + + vboxHwBufferFlush (pHGSMICtx); + + cbHwBufferAvail = vboxHwBufferAvail (pVBVA); + + if (cbChunk >= cbHwBufferAvail) + { + // LogFunc(("no place for %d bytes. Only %d bytes available after flush. Going to partial writes.\n", + // cb, cbHwBufferAvail)); + + if (cbHwBufferAvail <= pVBVA->cbPartialWriteThreshold) + { + // LogFunc(("Buffer overflow!!!\n")); + pCtx->fHwBufferOverflow = true; + Assert(false); + return false; + } + + cbChunk = cbHwBufferAvail - pVBVA->cbPartialWriteThreshold; + } + } + + Assert(cbChunk <= cb); + Assert(cbChunk <= vboxHwBufferAvail (pVBVA)); + + vboxHwBufferPlaceDataAt (pCtx, (uint8_t *)p + cbWritten, cbChunk, pVBVA->off32Free); + + pVBVA->off32Free = (pVBVA->off32Free + cbChunk) % pVBVA->cbData; + pRecord->cbRecord += cbChunk; + cbHwBufferAvail -= cbChunk; + + cb -= cbChunk; + cbWritten += cbChunk; + } + + return true; +} + +/* + * Public writer to the hardware buffer. + */ +DECLHIDDEN(bool) VBoxVBVAWrite(PVBVABUFFERCONTEXT pCtx, + PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, + const void *pv, uint32_t cb) +{ + return vboxHwBufferWrite (pCtx, pHGSMICtx, pv, cb); +} + +DECLHIDDEN(bool) VBoxVBVAOrderSupported(PVBVABUFFERCONTEXT pCtx, unsigned code) +{ + VBVABUFFER *pVBVA = pCtx->pVBVA; + + if (!pVBVA) + { + return false; + } + + if (pVBVA->hostFlags.u32SupportedOrders & (1 << code)) + { + return true; + } + + return false; +} + +DECLHIDDEN(void) VBoxVBVASetupBufferContext(PVBVABUFFERCONTEXT pCtx, + uint32_t offVRAMBuffer, + uint32_t cbBuffer) +{ + pCtx->offVRAMBuffer = offVRAMBuffer; + pCtx->cbBuffer = cbBuffer; +} diff --git a/src/VBox/Additions/common/VBoxVideo/todo-create-library-from-these-files-for-windows b/src/VBox/Additions/common/VBoxVideo/todo-create-library-from-these-files-for-windows new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Additions/common/VBoxVideo/todo-create-library-from-these-files-for-windows diff --git a/src/VBox/Additions/common/pam/Makefile.kmk b/src/VBox/Additions/common/pam/Makefile.kmk new file mode 100644 index 00000000..60c5962e --- /dev/null +++ b/src/VBox/Additions/common/pam/Makefile.kmk @@ -0,0 +1,40 @@ +# $Id: Makefile.kmk $ +## @file +# Makefile for VBox PAM module for automated logons. +# + +# +# Copyright (C) 2011-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# The PAM module. +DLLS += pam_vbox + +pam_vbox_TEMPLATE := VBoxGuestR3Dll +pam_vbox_DEFS := LOG_TO_BACKDOOR VBOX_WITH_HGCM +pam_vbox_DEFS += VBOX_WITH_GUEST_PROPS +pam_vbox_SOURCES := pam_vbox.cpp +pam_vbox_LIBS := pam + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/Additions/common/pam/pam_vbox.cpp b/src/VBox/Additions/common/pam/pam_vbox.cpp new file mode 100644 index 00000000..6e1b6a7c --- /dev/null +++ b/src/VBox/Additions/common/pam/pam_vbox.cpp @@ -0,0 +1,879 @@ +/* $Id: pam_vbox.cpp $ */ +/** @file + * pam_vbox - PAM module for auto logons. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define PAM_SM_AUTH +#define PAM_SM_ACCOUNT +#define PAM_SM_PASSWORD +#define PAM_SM_SESSION + +#ifdef DEBUG +# define PAM_DEBUG +#endif + +#include <security/pam_appl.h> +#ifdef RT_OS_LINUX +# include <security/_pam_macros.h> +#endif + +#include <pwd.h> +#include <syslog.h> +#include <stdlib.h> + +#include <iprt/assert.h> +#include <iprt/buildconfig.h> +#include <iprt/err.h> +#include <iprt/env.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include <VBox/VBoxGuestLib.h> + +#include <VBox/log.h> +#include <VBox/HostServices/GuestPropertySvc.h> + +#define VBOX_MODULE_NAME "pam_vbox" + +#define VBOX_PAM_FLAG_SILENT "PAM_SILENT" +#define VBOX_PAM_FLAG_DISALLOW_NULL_AUTHTOK "PAM_DISALLOW_NULL_AUTHTOK" +#define VBOX_PAM_FLAG_ESTABLISH_CRED "PAM_ESTABLISH_CRED" +#define VBOX_PAM_FLAG_DELETE_CRED "PAM_DELETE_CRED" +#define VBOX_PAM_FLAG_REINITIALIZE_CRED "PAM_REINITIALIZE_CRED" +#define VBOX_PAM_FLAG_REFRESH_CRED "PAM_REFRESH_CRED" + +RT_C_DECLS_BEGIN +DECLEXPORT(int) pam_sm_authenticate(pam_handle_t *hPAM, int iFlags, int argc, const char **argv); +DECLEXPORT(int) pam_sm_setcred(pam_handle_t *hPAM, int iFlags, int argc, const char **argv); +DECLEXPORT(int) pam_sm_acct_mgmt(pam_handle_t *hPAM, int iFlags, int argc, const char **argv); +DECLEXPORT(int) pam_sm_open_session(pam_handle_t *hPAM, int iFlags, int argc, const char **argv); +DECLEXPORT(int) pam_sm_close_session(pam_handle_t *hPAM, int iFlags, int argc, const char **argv); +DECLEXPORT(int) pam_sm_chauthtok(pam_handle_t *hPAM, int iFlags, int argc, const char **argv); +RT_C_DECLS_END + +/** For debugging. */ +#ifdef DEBUG +static pam_handle_t *g_pam_handle; +static int g_verbosity = 99; +#else +static int g_verbosity = 0; +#endif + +/** + * User-provided thread data for the credentials waiting thread. + */ +typedef struct PAMVBOXTHREAD +{ + /** The PAM handle. */ + pam_handle_t *hPAM; + /** The timeout (in ms) to wait for credentials. */ + uint32_t uTimeoutMS; + /** The overall result of the thread operation. */ + int rc; +} PAMVBOXTHREAD, *PPAMVBOXTHREAD; + +/** + * Write to system log. + * + * @param pszBuf Buffer to write to the log (NULL-terminated) + */ +static void pam_vbox_writesyslog(char *pszBuf) +{ +#ifdef RT_OS_LINUX + openlog("pam_vbox", LOG_PID, LOG_AUTHPRIV); + syslog(LOG_ERR, "%s", pszBuf); + closelog(); +#elif defined(RT_OS_SOLARIS) + syslog(LOG_ERR, "pam_vbox: %s\n", pszBuf); +#endif +} + + +/** + * Displays an error message. + * + * @param hPAM PAM handle. + * @param pszFormat The message text. + * @param ... Format arguments. + */ +static void pam_vbox_error(pam_handle_t *hPAM, const char *pszFormat, ...) +{ + RT_NOREF1(hPAM); + va_list va; + char *buf; + va_start(va, pszFormat); + if (RT_SUCCESS(RTStrAPrintfV(&buf, pszFormat, va))) + { + LogRel(("%s: Error: %s", VBOX_MODULE_NAME, buf)); + pam_vbox_writesyslog(buf); + RTStrFree(buf); + } + va_end(va); +} + + +/** + * Displays a debug message. + * + * @param hPAM PAM handle. + * @param pszFormat The message text. + * @param ... Format arguments. + */ +static void pam_vbox_log(pam_handle_t *hPAM, const char *pszFormat, ...) +{ + RT_NOREF1(hPAM); + if (g_verbosity) + { + va_list va; + char *buf; + va_start(va, pszFormat); + if (RT_SUCCESS(RTStrAPrintfV(&buf, pszFormat, va))) + { + /* Only do normal logging in debug mode; could contain + * sensitive data! */ + LogRel(("%s: %s", VBOX_MODULE_NAME, buf)); + /* Log to syslog */ + pam_vbox_writesyslog(buf); + RTStrFree(buf); + } + va_end(va); + } +} + + +/** + * Sets a message using PAM's conversation function. + * + * @return IPRT status code. + * @param hPAM PAM handle. + * @param iStyle Style of message (0 = Information, 1 = Prompt + * echo off, 2 = Prompt echo on, 3 = Error). + * @param pszText Message text to set. + */ +static int vbox_set_msg(pam_handle_t *hPAM, int iStyle, const char *pszText) +{ + AssertPtrReturn(hPAM, VERR_INVALID_POINTER); + AssertPtrReturn(pszText, VERR_INVALID_POINTER); + + if (!iStyle) + iStyle = PAM_TEXT_INFO; + + int rc = VINF_SUCCESS; + + pam_message msg; + msg.msg_style = iStyle; +#ifdef RT_OS_SOLARIS + msg.msg = (char*)pszText; +#else + msg.msg = pszText; +#endif + +#ifdef RT_OS_SOLARIS + pam_conv *conv = NULL; + int pamrc = pam_get_item(hPAM, PAM_CONV, (void **)&conv); +#else + const pam_conv *conv = NULL; + int pamrc = pam_get_item(hPAM, PAM_CONV, (const void **)&conv); +#endif + if ( pamrc == PAM_SUCCESS + && conv) + { + pam_response *resp = NULL; +#ifdef RT_OS_SOLARIS + pam_message *msg_p = &msg; +#else + const pam_message *msg_p = &msg; +#endif + pam_vbox_log(hPAM, "Showing message \"%s\" (type %d)", pszText, iStyle); + + pamrc = conv->conv(1 /* One message only */, &msg_p, &resp, conv->appdata_ptr); + if (resp != NULL) /* If we use PAM_TEXT_INFO we never will get something back! */ + { + if (resp->resp) + { + pam_vbox_log(hPAM, "Response to message \"%s\" was \"%s\"", + pszText, resp->resp); + /** @todo Save response! */ + free(resp->resp); + } + free(resp); + } + } + else + rc = VERR_NOT_FOUND; + return rc; +} + + +/** + * Initializes pam_vbox. + * + * @return IPRT status code. + * @param hPAM PAM handle. + */ +static int pam_vbox_init(pam_handle_t *hPAM) +{ +#ifdef DEBUG + g_pam_handle = hPAM; /* hack for getting assertion text */ +#endif + + /* Don't make assertions panic because the PAM stack + + * the current logon module won't work anymore (or just restart). + * This could result in not able to log into the system anymore. */ + RTAssertSetMayPanic(false); + + pam_vbox_log(hPAM, "pam_vbox: %sr%s, running on %s\n", + RTBldCfgVersion(), RTBldCfgRevisionStr(), RTBldCfgTargetArch()); + + int rc = RTR3InitDll(0); + if (RT_FAILURE(rc)) + { + pam_vbox_error(hPAM, "pam_vbox_init: could not init runtime! rc=%Rrc. Aborting\n", rc); + return PAM_SUCCESS; /* Jump out as early as we can to not mess around. */ + } + + pam_vbox_log(hPAM, "pam_vbox_init: runtime initialized\n"); + if (RT_SUCCESS(rc)) + { + rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + { + switch(rc) + { + case VERR_ACCESS_DENIED: + pam_vbox_error(hPAM, "pam_vbox_init: access is denied to guest driver! Please make sure you run with sufficient rights. Aborting\n"); + break; + + case VERR_FILE_NOT_FOUND: + pam_vbox_error(hPAM, "pam_vbox_init: guest driver not found! Guest Additions installed? Aborting\n"); + break; + + default: + pam_vbox_error(hPAM, "pam_vbox_init: could not init VbglR3 library! rc=%Rrc. Aborting\n", rc); + break; + } + } + pam_vbox_log(hPAM, "pam_vbox_init: guest lib initialized\n"); + } + + if (RT_SUCCESS(rc)) + { + char *rhost = NULL; + char *tty = NULL; + char *prompt = NULL; +#ifdef RT_OS_SOLARIS + pam_get_item(hPAM, PAM_RHOST, (void**) &rhost); + pam_get_item(hPAM, PAM_TTY, (void**) &tty); + pam_get_item(hPAM, PAM_USER_PROMPT, (void**) &prompt); +#else + pam_get_item(hPAM, PAM_RHOST, (const void**) &rhost); + pam_get_item(hPAM, PAM_TTY, (const void**) &tty); + pam_get_item(hPAM, PAM_USER_PROMPT, (const void**) &prompt); +#endif + pam_vbox_log(hPAM, "pam_vbox_init: rhost=%s, tty=%s, prompt=%s\n", + rhost ? rhost : "<none>", tty ? tty : "<none>", prompt ? prompt : "<none>"); + } + + return rc; +} + + +/** + * Shuts down pam_vbox. + * + * @param hPAM PAM handle. + */ +static void pam_vbox_shutdown(pam_handle_t *hPAM) +{ + RT_NOREF1(hPAM); + VbglR3Term(); +} + + +/** + * Checks for credentials provided by the host / HGCM. + * + * @return IPRT status code. VERR_NOT_FOUND if no credentials are available, + * VINF_SUCCESS on successful retrieval or another IPRT error. + * @param hPAM PAM handle. + */ +static int pam_vbox_check_creds(pam_handle_t *hPAM) +{ + int rc = VbglR3CredentialsQueryAvailability(); + if (RT_FAILURE(rc)) + { + if (rc != VERR_NOT_FOUND) + pam_vbox_error(hPAM, "pam_vbox_check_creds: could not query for credentials! rc=%Rrc. Aborting\n", rc); +#ifdef DEBUG + else + pam_vbox_log(hPAM, "pam_vbox_check_creds: no credentials available\n"); +#endif + } + else + { + char *pszUsername; + char *pszPassword; + char *pszDomain; + + rc = VbglR3CredentialsRetrieve(&pszUsername, &pszPassword, &pszDomain); + if (RT_FAILURE(rc)) + { + pam_vbox_error(hPAM, "pam_vbox_check_creds: could not retrieve credentials! rc=%Rrc. Aborting\n", rc); + } + else + { +#ifdef DEBUG + pam_vbox_log(hPAM, "pam_vbox_check_creds: credentials retrieved: user=%s, password=%s, domain=%s\n", + pszUsername, pszPassword, pszDomain); +#else + /* Don't log passwords in release mode! */ + pam_vbox_log(hPAM, "pam_vbox_check_creds: credentials retrieved: user=%s, password=XXX, domain=%s\n", + pszUsername, pszDomain); +#endif + /* Fill credentials into PAM. */ + int pamrc = pam_set_item(hPAM, PAM_USER, pszUsername); + if (pamrc != PAM_SUCCESS) + { + pam_vbox_error(hPAM, "pam_vbox_check_creds: could not set user name! pamrc=%d, msg=%s. Aborting\n", + pamrc, pam_strerror(hPAM, pamrc)); + } + else + { + pamrc = pam_set_item(hPAM, PAM_AUTHTOK, pszPassword); + if (pamrc != PAM_SUCCESS) + pam_vbox_error(hPAM, "pam_vbox_check_creds: could not set password! pamrc=%d, msg=%s. Aborting\n", + pamrc, pam_strerror(hPAM, pamrc)); + + } + /** @todo Add handling domains as well. */ + VbglR3CredentialsDestroy(pszUsername, pszPassword, pszDomain, + 3 /* Three wipe passes */); + pam_vbox_log(hPAM, "pam_vbox_check_creds: returned with pamrc=%d, msg=%s\n", + pamrc, pam_strerror(hPAM, pamrc)); + } + } + +#ifdef DEBUG + pam_vbox_log(hPAM, "pam_vbox_check_creds: returned with rc=%Rrc\n", rc); +#endif + return rc; +} + +/** + * Reads a guest property. + * + * @return IPRT status code. + * @param hPAM PAM handle. + * @param uClientID Guest property service client ID. + * @param pszKey Key (name) of guest property to read. + * @param fReadOnly Indicates whether this key needs to be + * checked if it only can be read (and *not* written) + * by the guest. + * @param pszValue Buffer where to store the key's value. + * @param cbValue Size of buffer (in bytes). + */ +static int pam_vbox_read_prop(pam_handle_t *hPAM, uint32_t uClientID, + const char *pszKey, bool fReadOnly, + char *pszValue, size_t cbValue) +{ + AssertPtrReturn(hPAM, VERR_INVALID_POINTER); + AssertReturn(uClientID, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszKey, VERR_INVALID_POINTER); + AssertPtrReturn(pszValue, VERR_INVALID_POINTER); + + int rc; + + uint64_t u64Timestamp = 0; + char *pszValTemp; + char *pszFlags = NULL; + /* The buffer for storing the data and its initial size. We leave a bit + * of space here in case the maximum values are raised. */ + void *pvBuf = NULL; + uint32_t cbBuf = GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN + _1K; + + /* Because there is a race condition between our reading the size of a + * property and the guest updating it, we loop a few times here and + * hope. Actually this should never go wrong, as we are generous + * enough with buffer space. */ + for (unsigned i = 0; ; i++) + { + void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf); + if (pvTmpBuf) + { + pvBuf = pvTmpBuf; + rc = VbglR3GuestPropRead(uClientID, pszKey, pvBuf, cbBuf, + &pszValTemp, &u64Timestamp, &pszFlags, + &cbBuf); + if (rc == VERR_BUFFER_OVERFLOW && i < 10) + { + /* Buffer too small, try it with a bigger one next time. */ + cbBuf += _1K; + continue; /* Try next round. */ + } + } + else + rc = VERR_NO_MEMORY; + break; /* Everything except VERR_BUFFER_OVERFLOW makes us bail out ... */ + } + + if (RT_SUCCESS(rc)) + { + /* Check security bits. */ + if (pszFlags) + { + if ( fReadOnly + && !RTStrStr(pszFlags, "RDONLYGUEST")) + { + /* If we want a property which is read-only on the guest + * and it is *not* marked as such, deny access! */ + pam_vbox_error(hPAM, "pam_vbox_read_prop: key \"%s\" should be read-only on guest but it is not\n", pszKey); + rc = VERR_ACCESS_DENIED; + } + } + else /* No flags, no access! */ + { + pam_vbox_error(hPAM, "pam_vbox_read_prop: key \"%s\" contains no/wrong flags (%s)\n", pszKey, pszFlags); + rc = VERR_ACCESS_DENIED; + } + + if (RT_SUCCESS(rc)) + { + /* If everything went well copy property value to our destination buffer. */ + if (!RTStrPrintf(pszValue, cbValue, "%s", pszValTemp)) + { + pam_vbox_error(hPAM, "pam_vbox_read_prop: could not store value of key \"%s\"\n", pszKey); + rc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(rc)) + pam_vbox_log(hPAM, "pam_vbox_read_prop: read key \"%s\"=\"%s\"\n", pszKey, pszValue); + } + } + + RTMemFree(pvBuf); + pam_vbox_log(hPAM, "pam_vbox_read_prop: read key \"%s\" with rc=%Rrc\n", + pszKey, rc); + return rc; +} + + +/** + * Waits for a guest property to be changed. + * + * @return IPRT status code. + * @param hPAM PAM handle. + * @param uClientID Guest property service client ID. + * @param pszKey Key (name) of guest property to wait for. + * @param uTimeoutMS Timeout (in ms) to wait for the change. Specify + * RT_INDEFINITE_WAIT to wait indefinitly. + */ +static int pam_vbox_wait_prop(pam_handle_t *hPAM, uint32_t uClientID, + const char *pszKey, uint32_t uTimeoutMS) +{ + AssertPtrReturn(hPAM, VERR_INVALID_POINTER); + AssertReturn(uClientID, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszKey, VERR_INVALID_POINTER); + + int rc; + + /* The buffer for storing the data and its initial size. We leave a bit + * of space here in case the maximum values are raised. */ + void *pvBuf = NULL; + uint32_t cbBuf = GUEST_PROP_MAX_NAME_LEN + GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN + _1K; + + for (int i = 0; ; i++) + { + void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf); + if (pvTmpBuf) + { + char *pszName = NULL; + char *pszValue = NULL; + uint64_t u64TimestampOut = 0; + char *pszFlags = NULL; + + pvBuf = pvTmpBuf; + rc = VbglR3GuestPropWait(uClientID, pszKey, pvBuf, cbBuf, + 0 /* Last timestamp; just wait for next event */, uTimeoutMS, + &pszName, &pszValue, &u64TimestampOut, + &pszFlags, &cbBuf, NULL /* pfWasDeleted */); + if (rc == VERR_BUFFER_OVERFLOW && i < 10) + { + cbBuf += _1K; /* Buffer too small, try it with a bigger one more time. */ + continue; + } + } + else + rc = VERR_NO_MEMORY; + break; + } + + RTMemFree(pvBuf); + return rc; +} + +/** + * Thread function waiting for credentials to arrive. + * + * @return IPRT status code. + * @param hThreadSelf Thread handle. + * @param pvUser Pointer to a PAMVBOXTHREAD structure providing + * required data used / set by the thread. + */ +static DECLCALLBACK(int) pam_vbox_wait_thread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF1(hThreadSelf); + PPAMVBOXTHREAD pUserData = (PPAMVBOXTHREAD)pvUser; + AssertPtr(pUserData); + + int rc = VINF_SUCCESS; + /* Get current time stamp to later calculate rest of timeout left. */ + uint64_t u64StartMS = RTTimeMilliTS(); + + uint32_t uClientID = 0; + rc = VbglR3GuestPropConnect(&uClientID); + if (RT_FAILURE(rc)) + { + pam_vbox_error(pUserData->hPAM, "pam_vbox_wait_thread: Unable to connect to guest property service, rc=%Rrc\n", rc); + } + else + { + pam_vbox_log(pUserData->hPAM, "pam_vbox_wait_thread: clientID=%u\n", uClientID); + + for (;;) + { + + if (uClientID) + { + rc = pam_vbox_wait_prop(pUserData->hPAM, uClientID, + "/VirtualBox/GuestAdd/PAM/CredsWaitAbort", + 500 /* Wait 500ms, same as VBoxGINA/VBoxCredProv. */); + switch (rc) + { + case VINF_SUCCESS: + /* Somebody (guest/host) wants to abort waiting for credentials. */ + break; + + case VERR_INTERRUPTED: + pam_vbox_error(pUserData->hPAM, "pam_vbox_wait_thread: The abort notification request timed out or was interrupted\n"); + break; + + case VERR_TIMEOUT: + /* We did not receive an abort message within time. */ + break; + + case VERR_TOO_MUCH_DATA: + pam_vbox_error(pUserData->hPAM, "pam_vbox_wait_thread: Temporarily unable to get abort notification\n"); + break; + + default: + pam_vbox_error(pUserData->hPAM, "pam_vbox_wait_thread: The abort notification request failed with rc=%Rrc\n", rc); + break; + } + + if (RT_SUCCESS(rc)) /* Abort waiting. */ + { + pam_vbox_log(pUserData->hPAM, "pam_vbox_wait_thread: Got notification to abort waiting\n"); + rc = VERR_CANCELLED; + break; + } + } + + if ( RT_SUCCESS(rc) + || rc == VERR_TIMEOUT) + { + rc = pam_vbox_check_creds(pUserData->hPAM); + if (RT_SUCCESS(rc)) + { + /* Credentials retrieved. */ + break; /* Thread no longer is required, bail out. */ + } + else if (rc == VERR_NOT_FOUND) + { + /* No credentials found, but try next round (if there's + * time left for) ... */ + RTThreadSleep(500); /* Wait 500 ms. */ + } + else + break; /* Something bad happend ... */ + } + else + break; + + /* Calculate timeout value left after process has been started. */ + uint64_t u64Elapsed = RTTimeMilliTS() - u64StartMS; + /* Is it time to bail out? */ + if (pUserData->uTimeoutMS < u64Elapsed) + { + pam_vbox_log(pUserData->hPAM, "pam_vbox_wait_thread: Waiting thread has reached timeout (%dms), exiting ...\n", + pUserData->uTimeoutMS); + rc = VERR_TIMEOUT; + break; + } + } + } + VbglR3GuestPropDisconnect(uClientID); + + /* Save result. */ + pUserData->rc = rc; /** @todo Use ASMAtomicXXX? */ + + int rc2 = RTThreadUserSignal(RTThreadSelf()); + AssertRC(rc2); + + pam_vbox_log(pUserData->hPAM, "pam_vbox_wait_thread: Waiting thread returned with rc=%Rrc\n", rc); + return rc; +} + +/** + * Waits for credentials to arrive by creating and waiting for a thread. + * + * @return IPRT status code. + * @param hPAM PAM handle. + * @param uClientID Guest property service client ID. + * @param uTimeoutMS Timeout (in ms) to wait for the change. Specify + * RT_INDEFINITE_WAIT to wait indefinitly. + */ +static int pam_vbox_wait_for_creds(pam_handle_t *hPAM, uint32_t uClientID, uint32_t uTimeoutMS) +{ + RT_NOREF1(uClientID); + PAMVBOXTHREAD threadData; + threadData.hPAM = hPAM; + threadData.uTimeoutMS = uTimeoutMS; + + RTTHREAD threadWait; + int rc = RTThreadCreate(&threadWait, pam_vbox_wait_thread, + (void *)&threadData, 0, + RTTHREADTYPE_DEFAULT, 0 /* Flags */, "pam_vbox"); + if (RT_SUCCESS(rc)) + { + pam_vbox_log(hPAM, "pam_vbox_wait_for_creds: Waiting for credentials (%dms) ...\n", uTimeoutMS); + /* Wait for thread to initialize. */ + /** @todo We can do something else here in the meantime later. */ + rc = RTThreadUserWait(threadWait, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + rc = threadData.rc; /* Get back thread result to take further actions. */ + } + else + pam_vbox_error(hPAM, "pam_vbox_wait_for_creds: Creating thread failed with rc=%Rrc\n", rc); + + pam_vbox_log(hPAM, "pam_vbox_wait_for_creds: Waiting for credentials returned with rc=%Rrc\n", rc); + return rc; +} + +DECLEXPORT(int) pam_sm_authenticate(pam_handle_t *hPAM, int iFlags, int argc, const char **argv) +{ + RT_NOREF1(iFlags); + + /* Parse arguments. */ + for (int i = 0; i < argc; i++) + { + if (!RTStrICmp(argv[i], "debug")) + g_verbosity = 1; + else + pam_vbox_error(hPAM, "pam_vbox_authenticate: unknown command line argument \"%s\"\n", argv[i]); + } + pam_vbox_log(hPAM, "pam_vbox_authenticate called\n"); + + int rc = pam_vbox_init(hPAM); + if (RT_FAILURE(rc)) + return PAM_SUCCESS; /* Jump out as early as we can to not mess around. */ + + bool fFallback = true; + + uint32_t uClientId; + rc = VbglR3GuestPropConnect(&uClientId); + if (RT_SUCCESS(rc)) + { + char szVal[256]; + rc = pam_vbox_read_prop(hPAM, uClientId, + "/VirtualBox/GuestAdd/PAM/CredsWait", + true /* Read-only on guest */, + szVal, sizeof(szVal)); + if (RT_SUCCESS(rc)) + { + /* All calls which are checked against rc2 are not critical, e.g. it does + * not matter if they succeed or not. */ + uint32_t uTimeoutMS = RT_INDEFINITE_WAIT; /* Wait infinite by default. */ + int rc2 = pam_vbox_read_prop(hPAM, uClientId, + "/VirtualBox/GuestAdd/PAM/CredsWaitTimeout", + true /* Read-only on guest */, + szVal, sizeof(szVal)); + if (RT_SUCCESS(rc2)) + { + uTimeoutMS = RTStrToUInt32(szVal); + if (!uTimeoutMS) + { + pam_vbox_error(hPAM, "pam_vbox_authenticate: invalid waiting timeout value specified, defaulting to infinite timeout\n"); + uTimeoutMS = RT_INDEFINITE_WAIT; + } + else + uTimeoutMS = uTimeoutMS * 1000; /* Make ms out of s. */ + } + + rc2 = pam_vbox_read_prop(hPAM, uClientId, + "/VirtualBox/GuestAdd/PAM/CredsMsgWaiting", + true /* Read-only on guest */, + szVal, sizeof(szVal)); + const char *pszWaitMsg = NULL; + if (RT_SUCCESS(rc2)) + pszWaitMsg = szVal; + + rc2 = vbox_set_msg(hPAM, 0 /* Info message */, + pszWaitMsg ? pszWaitMsg : "Waiting for credentials ..."); + if (RT_FAILURE(rc2)) /* Not critical. */ + pam_vbox_error(hPAM, "pam_vbox_authenticate: error setting waiting information message, rc=%Rrc\n", rc2); + + if (RT_SUCCESS(rc)) + { + /* Before we actuall wait for credentials just make sure we didn't already get credentials + * set so that we can skip waiting for them ... */ + rc = pam_vbox_check_creds(hPAM); + if (rc == VERR_NOT_FOUND) + { + rc = pam_vbox_wait_for_creds(hPAM, uClientId, uTimeoutMS); + if (rc == VERR_TIMEOUT) + { + pam_vbox_log(hPAM, "pam_vbox_authenticate: no credentials given within time\n"); + + rc2 = pam_vbox_read_prop(hPAM, uClientId, + "/VirtualBox/GuestAdd/PAM/CredsMsgWaitTimeout", + true /* Read-only on guest */, + szVal, sizeof(szVal)); + if (RT_SUCCESS(rc2)) + { + rc2 = vbox_set_msg(hPAM, 0 /* Info message */, szVal); + AssertRC(rc2); + } + } + else if (rc == VERR_CANCELLED) + { + pam_vbox_log(hPAM, "pam_vbox_authenticate: waiting aborted\n"); + + rc2 = pam_vbox_read_prop(hPAM, uClientId, + "/VirtualBox/GuestAdd/PAM/CredsMsgWaitAbort", + true /* Read-only on guest */, + szVal, sizeof(szVal)); + if (RT_SUCCESS(rc2)) + { + rc2 = vbox_set_msg(hPAM, 0 /* Info message */, szVal); + AssertRC(rc2); + } + } + } + + /* If we got here we don't need the fallback, so just deactivate it. */ + fFallback = false; + } + } + + VbglR3GuestPropDisconnect(uClientId); + } + + if (fFallback) + { + pam_vbox_log(hPAM, "pam_vbox_authenticate: falling back to old method\n"); + + /* If anything went wrong in the code above we just do a credentials + * check like it was before: Try retrieving the stuff and authenticating. */ + int rc2 = pam_vbox_check_creds(hPAM); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + pam_vbox_shutdown(hPAM); + + pam_vbox_log(hPAM, "pam_vbox_authenticate: overall result rc=%Rrc\n", rc); + + /* Never report an error here because if no credentials from the host are available or something + * went wrong we then let do the authentication by the next module in the stack. */ + + /* We report success here because this is all we can do right now -- we passed the credentials + * to the next PAM module in the block above which then might do a shadow (like pam_unix/pam_unix2) + * password verification to "really" authenticate the user. */ + return PAM_SUCCESS; +} + + +DECLEXPORT(int) pam_sm_setcred(pam_handle_t *hPAM, int iFlags, int argc, const char **argv) +{ + pam_vbox_log(hPAM, "pam_vbox_setcred called, iFlags=0x%x\n", iFlags); + for (int i = 0; i < argc; i++) + pam_vbox_log(hPAM, "pam_vbox_setcred: argv[%d] = %s\n", i, argv[i]); + return PAM_SUCCESS; +} + + +DECLEXPORT(int) pam_sm_acct_mgmt(pam_handle_t *hPAM, int iFlags, int argc, const char **argv) +{ + RT_NOREF3(iFlags, argc, argv); + pam_vbox_log(hPAM, "pam_vbox_acct_mgmt called\n"); + return PAM_SUCCESS; +} + + +DECLEXPORT(int) pam_sm_open_session(pam_handle_t *hPAM, int iFlags, int argc, const char **argv) +{ + RT_NOREF3(iFlags, argc, argv); + pam_vbox_log(hPAM, "pam_vbox_open_session called\n"); + RTPrintf("This session was provided by VirtualBox Guest Additions. Have a lot of fun!\n"); + return PAM_SUCCESS; +} + + +DECLEXPORT(int) pam_sm_close_session(pam_handle_t *hPAM, int iFlags, int argc, const char **argv) +{ + RT_NOREF3(iFlags, argc, argv); + pam_vbox_log(hPAM, "pam_vbox_close_session called\n"); + return PAM_SUCCESS; +} + + +DECLEXPORT(int) pam_sm_chauthtok(pam_handle_t *hPAM, int iFlags, int argc, const char **argv) +{ + RT_NOREF3(iFlags, argc, argv); + pam_vbox_log(hPAM, "pam_vbox_sm_chauthtok called\n"); + return PAM_SUCCESS; +} + + +#ifdef DEBUG +RTDECL(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction) +{ + pam_vbox_log(g_pam_handle, + "\n!!Assertion Failed!!\n" + "Expression: %s\n" + "Location : %s(%d) %s\n", + pszExpr, pszFile, uLine, pszFunction); + RTAssertMsg1(pszExpr, uLine, pszFile, pszFunction); +} +#endif + diff --git a/src/VBox/Additions/common/testcase/Makefile.kmk b/src/VBox/Additions/common/testcase/Makefile.kmk new file mode 100644 index 00000000..f18504b2 --- /dev/null +++ b/src/VBox/Additions/common/testcase/Makefile.kmk @@ -0,0 +1,53 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Cross Platform Guest Addition test cases. +# + +# +# Copyright (C) 2007-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Target lists. +# +PROGRAMS += tstPageFusion + +# +# tstPageFusion +# +tstPageFusion_TEMPLATE = VBoxGuestR3Exe +tstPageFusion_DEFS.win += _WIN32_WINNT=0x0501 +tstPageFusion_SOURCES = \ + tstPageFusion.cpp + +# +# Install the LED test script to bin. +# +INSTALLS += lights-test-script +lights-test-script_INST = $(INST_BIN) +lights-test-script_MODE = a+rx,u+w +lights-test-script_SOURCES = led-lights.sh + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/testcase/led-lights.sh b/src/VBox/Additions/common/testcase/led-lights.sh new file mode 100755 index 00000000..6d7e6504 --- /dev/null +++ b/src/VBox/Additions/common/testcase/led-lights.sh @@ -0,0 +1,276 @@ +#!/bin/bash +# $Id: led-lights.sh $ +## @file +# VirtualBox guest LED demonstration test +# + +# +# Copyright (C) 2021-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +# +# Usage: +# led-lights.sh [-a | -r] +# + +# +# Test script to twiddle the console LEDs of a VirtualBox VM. +# +# This is not an automated test, just something for humans to look +# at, to convince themselves that the VM console LEDs are working. +# By default it cycles through the LED types in a specific order. +# +# '-a' twiddles all possible LEDs at the same time +# '-r' reverses the default order +# +# For instance, run the script in 2 VMs at once, one with '-r'. +# +# LEDs are not expected to track perfectly, as other OS activities +# will light them (and buffer cache effects can delay things). Just +# make sure that all the tested ones (hard disk, optical, USB storage, +# floppy, shared folders, net) are working. Expected activity: +# +# - Disk & optical devices show solid 'read' +# - Virtual USB disk & optical devices show 'write' on the USB LED +# - Floppy devices and shared folders alternate 'read/write' +# - Net blinks 'write' +# +# Pre-VM setup: +# +# Download or locate a bootable Linux ISO able to be used 'live'. +# Make a tarball of this script + extra junk: +# +# $ dd if=/dev/zero of=junk bs=100k count=1 +# $ tar cf floppy.img led-lights.sh junk +# +# NOTE: floppy.img must be >= 20KiB or you will get I/O errors! +# +# VM setup: +# +# New VM; type: Linux (subtype to match ISO); create default HD. +# VM Settings: +# System > raise base memory to 4GiB +# Storage > insert 'Live bootable' Linux ISO image; +# turn on 'Live CD/DVD' +# Storage > add floppy controller (i82078); insert floppy.img +# Storage > add USB controller; insert USB HD & USB CD +# System > move Optical before Floppy in boot order +# Shared Folders > set up one or more Shared Folders if desired +# (they should be permanent, auto-mount, +# writable, with at least 10MB free space) +# +# Boot the VM. Open a shell, become root, optionally install +# VirtualBox Guest Utilities to access Shared Folders, then extract +# and run this script: +# +# $ sudo bash +# # yum install virtualbox-guest-utils # for Shared Folders +# # tar xf /dev/fd0 led-lights.sh +# # ./led-lights.sh [-a | -r] + +if [ ! -w / ]; then + echo "Must be run as root!" 1>&2 + exit 1 +fi + +all_all=false +reverse=false + +if [ "x$1" = "x-a" ]; then + all_all=true +fi + +if [ "x$1" = "x-r" ]; then + reverse=true +fi + +# Copy binaries to RAM tmpfs to avoid CD I/O after cache purges +MYTMP=/tmp/led-lights.$$ +mkdir $MYTMP +for bin in $(which dd sleep sync); do + case $bin in + /*) + cp -p $bin $MYTMP + ;; + esac +done +export MYTMP PATH=$MYTMP:$PATH + +set -o monitor + +# Make device reads keep hitting the 'hardware' +# even if the whole medium fits in cache... +drop_cache() +{ + echo 1 >/proc/sys/vm/drop_caches +} + +activate() +{ + kill -CONT -$1 2>/dev/null +} + +suppress() +{ + $all_all || kill -STOP -$1 2>/dev/null +} + +declare -a pids pidnames +cpids=0 + +twiddle() +{ + let ++cpids + new_pid=$! + pidname=$1 + pids[$cpids]=$new_pid + pidnames[$cpids]=$pidname + suppress $new_pid +} + +hide_stderr() +{ + exec 3>&2 2>/dev/null +} + +show_stderr() +{ + exec 2>&3 3>&- +} + +bail() +{ + hide_stderr + for pid in ${pids[*]}; do + activate $pid + kill -TERM -$pid + kill -TERM $pid + done 2>/dev/null + rm -rf $MYTMP + kill $$ +} + +trap "bail" INT + +drives() +{ + echo $( + awk '$NF ~/^('$1')$/ { print $NF }' /proc/partitions + ) +} + +# Prevent job control 'stopped' msgs during twiddler startup +hide_stderr + +# Hard disks +for hdd in $(drives '[sh]d.'); do + while :; do + dd if=/dev/$hdd of=/dev/null + drop_cache + done 2>/dev/null & + twiddle disk:$hdd +done + +# Optical drives +for odd in $(drives 'sr.|scd.'); do + while :; do + dd if=/dev/$odd of=/dev/null + drop_cache + done 2>/dev/null & + twiddle optical:$odd +done + +# Floppy drives +for fdd in $(drives 'fd.'); do + while :; do + dd if=/dev/$fdd of=$MYTMP/$fdd bs=1k count=20 + dd of=/dev/$fdd if=$MYTMP/$fdd bs=1k count=20 + done 2>/dev/null & + twiddle floppy:$fdd +done + +# Shared folders +if ! lsmod | grep -q vboxsf; then + echo + echo "Note: to test the Shared Folders LED, install this" + echo "distro's VirtualBox Guest Utilities package, e.g.:" + echo + echo " # yum install virtualbox-guest-utils (Red Hat family)" + echo " # apt install virtualbox-guest-utils (Debian family)" + echo +fi >&3 # original stderr +for shf in $(mount -t vboxsf | awk '{ print $3 }'); do + while :; do + dd if=/dev/urandom of=$shf/tmp.led-lights.$$ bs=100k count=100 + for rep in $(seq 1 10); do + drop_cache + dd of=/dev/zero if=$shf/tmp.led-lights.$$ bs=100k count=100 + done + sync + rm -f $shf/tmp.led-lights.$$ + done >/dev/null 2>&1 & + twiddle sharedfs:$shf +done + +# Network +ping -i.2 1.2.3.4 >/dev/null & +twiddle net + +# Untested LED: Graphics3D -- add some day? + +sleep 0.1 +show_stderr + +if $reverse; then + seq=$(seq $cpids -1 1) +else + seq=$(seq 1 $cpids) +fi + +show_intr() +{ + intr=$(stty -a | sed -n '/intr/ { s/.*intr *=* *//; s/[ ;].*//p }') + echo + echo "[ Hit $intr to stop ]" + echo +} + +if $all_all; then + printf "%s ...\n" ${pidnames[*]} + show_intr + wait +else + CEOL=$(tput el) + show_intr + while :; do + for pidx in $seq; do + pid=${pids[$pidx]} + pidname=${pidnames[$pidx]} + echo -e -n "$pidname$CEOL\r" + shift + activate $pid + sleep 2 + suppress $pid + sync + sleep .5 + done + done +fi diff --git a/src/VBox/Additions/common/testcase/tstPageFusion.cpp b/src/VBox/Additions/common/testcase/tstPageFusion.cpp new file mode 100644 index 00000000..264d887a --- /dev/null +++ b/src/VBox/Additions/common/testcase/tstPageFusion.cpp @@ -0,0 +1,389 @@ +/* $Id: tstPageFusion.cpp $ */ +/** @file + * VBoxService - Guest page sharing testcase + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/mem.h> +#include <iprt/messages.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <VBox/VBoxGuestLib.h> +#include <iprt/x86.h> +#include <stdio.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +#ifdef RT_OS_WINDOWS +#include <iprt/win/windows.h> +#include <process.h> /* Needed for file version information. */ +#include <tlhelp32.h> +#include <psapi.h> +#include <winternl.h> + +#define SystemModuleInformation 11 + +typedef struct _RTL_PROCESS_MODULE_INFORMATION +{ + ULONG Section; + PVOID MappedBase; + PVOID ImageBase; + ULONG ImageSize; + ULONG Flags; + USHORT LoadOrderIndex; + USHORT InitOrderIndex; + USHORT LoadCount; + USHORT OffsetToFileName; + CHAR FullPathName[256]; +} RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION; + +typedef struct _RTL_PROCESS_MODULES +{ + ULONG NumberOfModules; + RTL_PROCESS_MODULE_INFORMATION Modules[1]; +} RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES; + +typedef NTSTATUS (WINAPI *PFNZWQUERYSYSTEMINFORMATION)(ULONG, PVOID, ULONG, PULONG); +static PFNZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL; +static HMODULE hNtdll = 0; + +#define PAGE_STATE_INVALID 0 +#define PAGE_STATE_SHARED 1 +#define PAGE_STATE_READ_WRITE 2 +#define PAGE_STATE_READ_ONLY 3 +#define PAGE_STATE_NOT_PRESENT 4 + +/* Page counters. */ +static unsigned cNotPresentPages = 0; +static unsigned cWritablePages = 0; +static unsigned cSharedPages = 0; +static unsigned cPrivatePages = 0; + +/** + * Registers a new module with the VMM + * @param pModule Module ptr + */ +void VBoxServicePageSharingCheckModule(MODULEENTRY32 *pModule) +{ + DWORD dwModuleSize = pModule->modBaseSize; + BYTE *pBaseAddress = pModule->modBaseAddr; + bool fFirstLine = true; + unsigned uPageState, uLastPageState; + bool fLastWritable = false; + BYTE *pLastBaseAddress = pBaseAddress; + + uPageState = uLastPageState = PAGE_STATE_INVALID; + + printf("Check module %s base %p size %x\n", pModule->szModule, pBaseAddress, dwModuleSize); + do + { + bool fShared; + uint64_t uPageFlags; + +#ifdef RT_ARCH_X86 + int rc = VbglR3PageIsShared((uint32_t)pLastBaseAddress, &fShared, &uPageFlags); +#else + int rc = VbglR3PageIsShared((RTGCPTR)pLastBaseAddress, &fShared, &uPageFlags); +#endif + if (RT_FAILURE(rc)) + printf("VbglR3PageIsShared %p failed with %d\n", pLastBaseAddress, rc); + + if (RT_SUCCESS(rc)) + { + if (uPageFlags & X86_PTE_P) + { + if (uPageFlags & X86_PTE_RW) + { + cWritablePages++; + uPageState = PAGE_STATE_READ_WRITE; + } + else + if (fShared) + { + cSharedPages++; + uPageState = PAGE_STATE_SHARED; + } + else + { + cPrivatePages++; + uPageState = PAGE_STATE_READ_ONLY; + } + } + else + { + cNotPresentPages++; + uPageState = PAGE_STATE_NOT_PRESENT; + } + + if ( !fFirstLine + && uPageState != uLastPageState) + { + printf("0x%p\n", pLastBaseAddress + 0xfff); + } + + if (uPageState != uLastPageState) + { + switch (uPageState) + { + case PAGE_STATE_READ_WRITE: + printf("%s RW 0x%p - ", pModule->szModule, pBaseAddress); + break; + case PAGE_STATE_SHARED: + printf("%s SHARED 0x%p - ", pModule->szModule, pBaseAddress); + break; + case PAGE_STATE_READ_ONLY: + printf("%s PRIV 0x%p - ", pModule->szModule, pBaseAddress); + break; + case PAGE_STATE_NOT_PRESENT: + printf("%s NP 0x%p - ", pModule->szModule, pBaseAddress); + break; + } + + fFirstLine = false; + } + uLastPageState = uPageState; + } + else + if (!fFirstLine) + { + printf("0x%p\n", pLastBaseAddress + 0xfff); + fFirstLine = true; + } + + if (dwModuleSize > PAGE_SIZE) + dwModuleSize -= PAGE_SIZE; + else + dwModuleSize = 0; + + pLastBaseAddress = pBaseAddress; + pBaseAddress += PAGE_SIZE; + } + while (dwModuleSize); + + printf("0x%p\n", pLastBaseAddress + 0xfff); + return; +} + +/** + * Inspect all loaded modules for the specified process + * @param dwProcessId Process id + */ +void VBoxServicePageSharingInspectModules(DWORD dwProcessId) +{ + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId); + if (hSnapshot == INVALID_HANDLE_VALUE) + { + printf("VBoxServicePageSharingInspectModules: CreateToolhelp32Snapshot failed with %d\n", GetLastError()); + return; + } + + printf("VBoxServicePageSharingInspectModules\n"); + + MODULEENTRY32 ModuleInfo; + BOOL bRet; + + ModuleInfo.dwSize = sizeof(ModuleInfo); + bRet = Module32First(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. */ + + VBoxServicePageSharingCheckModule(&ModuleInfo); + } + while (Module32Next(hSnapshot, &ModuleInfo)); + + CloseHandle(hSnapshot); +} + +/** + * Inspect all running processes for executables and dlls that might be worth sharing + * with other VMs. + * + */ +void VBoxServicePageSharingInspectGuest() +{ + VBoxServicePageSharingInspectModules(GetCurrentProcessId()); + + printf("\n\nUSER RESULTS\n"); + printf("cNotPresentPages = %d\n", cNotPresentPages); + printf("cWritablePages = %d\n", cWritablePages); + printf("cPrivatePages = %d\n", cPrivatePages); + printf("cSharedPages = %d\n", cSharedPages); + + cNotPresentPages = 0; + cWritablePages = 0; + cPrivatePages = 0; + cSharedPages = 0; + + /* Check all loaded kernel modules. */ + if (ZwQuerySystemInformation) + { + ULONG cbBuffer = 0; + PVOID pBuffer = NULL; + PRTL_PROCESS_MODULES pSystemModules; + + NTSTATUS ret = ZwQuerySystemInformation(SystemModuleInformation, (PVOID)&cbBuffer, 0, &cbBuffer); + if (!cbBuffer) + { + printf("ZwQuerySystemInformation returned length 0\n"); + goto skipkernelmodules; + } + + pBuffer = RTMemAllocZ(cbBuffer); + if (!pBuffer) + goto skipkernelmodules; + + ret = ZwQuerySystemInformation(SystemModuleInformation, pBuffer, cbBuffer, &cbBuffer); + if (ret != 0) + { + printf("ZwQuerySystemInformation returned %x (1)\n", ret); + goto skipkernelmodules; + } + + pSystemModules = (PRTL_PROCESS_MODULES)pBuffer; + for (unsigned i = 0; i < pSystemModules->NumberOfModules; i++) + { + /* User-mode modules seem to have no flags set; skip them as we detected them above. */ + if (pSystemModules->Modules[i].Flags == 0) + continue; + + /* New module; register it. */ + char szFullFilePath[512]; + MODULEENTRY32 ModuleInfo; + + strcpy(ModuleInfo.szModule, &pSystemModules->Modules[i].FullPathName[pSystemModules->Modules[i].OffsetToFileName]); + GetSystemDirectoryA(szFullFilePath, sizeof(szFullFilePath)); + + /* skip \Systemroot\system32 */ + char *lpPath = strchr(&pSystemModules->Modules[i].FullPathName[1], '\\'); + if (!lpPath) + { + printf("Unexpected kernel module name %s\n", pSystemModules->Modules[i].FullPathName); + break; + } + + lpPath = strchr(lpPath+1, '\\'); + if (!lpPath) + { + printf("Unexpected kernel module name %s\n", pSystemModules->Modules[i].FullPathName); + break; + } + + strcat(szFullFilePath, lpPath); + strcpy(ModuleInfo.szExePath, szFullFilePath); + ModuleInfo.modBaseAddr = (BYTE *)pSystemModules->Modules[i].ImageBase; + ModuleInfo.modBaseSize = pSystemModules->Modules[i].ImageSize; + + VBoxServicePageSharingCheckModule(&ModuleInfo); + } +skipkernelmodules: + if (pBuffer) + RTMemFree(pBuffer); + } + printf("\n\nKERNEL RESULTS\n"); + printf("cNotPresentPages = %d\n", cNotPresentPages); + printf("cWritablePages = %d\n", cWritablePages); + printf("cPrivatePages = %d\n", cPrivatePages); + printf("cSharedPages = %d\n", cSharedPages); +} +#else +void VBoxServicePageSharingInspectGuest() +{ + /** @todo other platforms */ +} +#endif + + +/** @copydoc VBOXSERVICE::pfnInit */ +static DECLCALLBACK(int) VBoxServicePageSharingInit(void) +{ + printf("VBoxServicePageSharingInit\n"); + +#ifdef RT_OS_WINDOWS + hNtdll = LoadLibrary("ntdll.dll"); + + if (hNtdll) + ZwQuerySystemInformation = (PFNZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll, "ZwQuerySystemInformation"); +#endif + + /** @todo report system name and version */ + /* Never fail here. */ + return VINF_SUCCESS; +} + +static DECLCALLBACK(void) VBoxServicePageSharingTerm(void) +{ + printf("VBoxServicePageSharingTerm\n"); + +#ifdef RT_OS_WINDOWS + if (hNtdll) + FreeLibrary(hNtdll); +#endif + return; +} + +int main(int argc, char **argv) +{ + /* + * Init globals and such. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Connect to the kernel part before daemonizing so we can fail + * and complain if there is some kind of problem. We need to initialize + * the guest lib *before* we do the pre-init just in case one of services + * needs do to some initial stuff with it. + */ + printf("Calling VbgR3Init()\n"); + rc = VbglR3Init(); + if (RT_FAILURE(rc)) + { + printf("VbglR3Init failed with rc=%Rrc.\n", rc); + return -1; + } + VBoxServicePageSharingInit(); + + VBoxServicePageSharingInspectGuest(); + + VBoxServicePageSharingTerm(); + return 0; +} + |