diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Additions/common | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream.tar.xz virtualbox-upstream.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Additions/common')
238 files changed, 87777 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..94f99f26 --- /dev/null +++ b/src/VBox/Additions/common/Makefile.kmk @@ -0,0 +1,34 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the common addition code. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile. +ifndef VBOX_ONLY_VALIDATIONKIT + include $(PATH_SUB_CURRENT)/VBoxControl/Makefile.kmk + include $(PATH_SUB_CURRENT)/VBoxGuest/Makefile.kmk + include $(PATH_SUB_CURRENT)/VBoxService/Makefile.kmk + ifdef VBOX_WITH_CROGL + include $(PATH_SUB_CURRENT)/crOpenGL/Makefile.kmk + endif + ifdef VBOX_WITH_PAM + include $(PATH_SUB_CURRENT)/pam/Makefile.kmk + endif +endif # !VBOX_ONLY_VALIDATIONKIT + +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..f0d60036 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/Makefile.kmk @@ -0,0 +1,56 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Guest Additions Command Line Management Interface. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile(s). +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# VBoxControl +# +PROGRAMS += VBoxControl +if "$(KBUILD_TARGET)" == "win" || defined(VBOX_WITH_MASOCHISTIC_WARNINGS) ## @todo use VBoxGuestR3Exe everywhere +VBoxControl_TEMPLATE = VBoxGuestR3Exe +else +VBoxControl_TEMPLATE = NewVBoxGuestR3Exe +endif +ifeq ($(KBUILD_TARGET),win) + ifdef VBOX_SIGN_ADDITIONS # (See the main Windows Additions makefile.) + VBoxControl_INSTTYPE = none + VBoxControl_DEBUG_INSTTYPE = both + endif +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 = VBOX_ZLIB_STATIC +VBoxControl_SOURCES = \ + VBoxControl.cpp +VBoxControl_SOURCES.win = \ + VBoxControl.rc +VBoxControl_LDFLAGS.darwin = -framework IOKit +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..b28e0282 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/VBoxControl.cpp @@ -0,0 +1,2192 @@ +/* $Id: VBoxControl.cpp $ */ +/** @file + * VBoxControl - Guest Additions Command Line Management Interface. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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", 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 = atoi(argv[0]); + DWORD yres = atoi(argv[1]); + DWORD bpp = atoi(argv[2]); + DWORD scr = 0; + if (argc == 4) + scr = atoi(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", + g_pfnChangeDisplaySettingsExA, g_pfnChangeDisplaySettingsA, 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++) + { + char szValueName[64]; + RTStrPrintf(szValueName, sizeof(szValueName), "\\Device\\Video%u", adwObjectNumberList[iDevice]); + + char szVideoLocation[256]; + cbValue = sizeof(szVideoLocation); + status = RegQueryValueExA(hkeyDeviceMap, szValueName, NULL, &dwKeyType, (LPBYTE)&szVideoLocation[0], &cbValue); + + /* This value starts with '\REGISTRY\Machine' */ + if ( status == ERROR_SUCCESS + && dwKeyType == REG_SZ + && _strnicmp(szVideoLocation, "\\REGISTRY\\Machine", 17) == 0) + { + status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, &szVideoLocation[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 = atoi(argv[0]); + DWORD yres = atoi(argv[1]); + DWORD bpp = atoi(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 = atoi(argv[0]); + DWORD yres = atoi(argv[1]); + DWORD bpp = atoi(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; + /* 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 + 1024; + /* 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; (RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW) && !fFinished && i < 10; i++) + { + void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf); + if (NULL == pvTmpBuf) + { + rc = VERR_NO_MEMORY; + VBoxControlError("Out of memory\n"); + } + else + { + pvBuf = pvTmpBuf; + rc = VbglR3GuestPropWait(u32ClientId, pszPatterns, pvBuf, cbBuf, + u64TimestampIn, u32Timeout, + &pszName, &pszValue, &u64TimestampOut, + &pszFlags, &cbBuf); + } + if (VERR_BUFFER_OVERFLOW == rc) + /* Leave a bit of extra space to be safe */ + cbBuf += 1024; + else + fFinished = true; + 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); + } + + /* + * 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)) + { + 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 DECLCALLBACK(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" + "(C) 2008-" VBOX_C_YEAR " " VBOX_VENDOR "\n" + "All rights reserved.\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..e8c10a32 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/VBoxControl.rc @@ -0,0 +1,51 @@ +/* $Id: VBoxControl.rc $ */ +/** @file + * VBoxControl - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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..bdc3ddb6 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/testcase/Makefile.kmk @@ -0,0 +1,38 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the VBoxControl testcases. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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..e1a1f8db --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/testcase/tstVBoxControl.cpp @@ -0,0 +1,213 @@ +/* $Id: tstVBoxControl.cpp $ */ +/** @file + * VBoxControl - Guest Additions Command Line Management Interface, test case + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + + +/********************************************************************************************************************************* +* 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 (VALID_PTR(ppszValue)) + *ppszValue = szValue; + if (VALID_PTR(pu64Timestamp)) + *pu64Timestamp = 12345; + if (VALID_PTR(ppszFlags)) + *ppszFlags = szFlags; + if (VALID_PTR(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 (VALID_PTR(ppszName)) + *ppszName = szName; + if (VALID_PTR(ppszValue)) + *ppszValue = szValue; + if (VALID_PTR(pu64Timestamp)) + *pu64Timestamp = 12345; + if (VALID_PTR(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(VALID_PTR(ppszName) || VALID_PTR(ppszValue) || VALID_PTR(ppszFlags), + VERR_INVALID_POINTER); + if (VALID_PTR(ppszName)) + *ppszName = NULL; + if (VALID_PTR(ppszValue)) + *ppszValue = NULL; + if (VALID_PTR(pu64Timestamp)) + *pu64Timestamp = 0; + if (VALID_PTR(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) +{ + 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 (VALID_PTR(ppszName)) + *ppszName = szName; + if (VALID_PTR(ppszValue)) + *ppszValue = szValue; + if (VALID_PTR(pu64Timestamp)) + *pu64Timestamp = 12345; + if (VALID_PTR(ppszFlags)) + *ppszFlags = szFlags; + if (VALID_PTR(pcbBufActual)) + *pcbBufActual = 256; + 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..11891b9e --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/.scm-settings @@ -0,0 +1,55 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for VBoxGuest +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE 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. +# + +# Like the host drivers, this is dual licensed. +--license-ose-dual + +-solaris.conf: --treat-as .sh + +/netbsd.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..b01fb72b --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/Makefile.kmk @@ -0,0 +1,269 @@ +# $Id: Makefile.kmk $ +## @file +# Makefile for the Cross Platform Guest Additions Driver. +# + +# +# Copyright (C) 2007-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE 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. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile. +include $(PATH_SUB_CURRENT)/lib/Makefile.kmk + + +if1of ($(KBUILD_TARGET), darwin freebsd haiku netbsd os2 solaris win) + # + # VBoxGuest - The Guest Additions Driver. + # + SYSMODS += VBoxGuest + VBoxGuest_TEMPLATE = VBOXGUESTR0 + 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/ + ifdef 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_NT4)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK_GST_NT4)_LIB)/hal.lib + 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 = VBOXGUESTR0 + 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 = VBOXGUESTR0LIB +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 + + +ifeq ($(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 + VBoxGuestInstNT_SOURCES = win/VBoxGuestInst.cpp + VBoxGuestInstNT_USES = vboximportchecker + VBoxGuestInstNT_VBOX_IMPORT_CHECKER.win.x86 = nt31 +endif + + +# +# Helper script. +# +INSTALLS.solaris += VBoxGuestLoad +VBoxGuestLoad_TEMPLATE = VBOXGUESTR0 +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..d307f4d9 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxDev-haiku.c @@ -0,0 +1,436 @@ +/* $Id: VBoxDev-haiku.c $ */ +/** @file + * VBoxGuest kernel driver, Haiku Guest Additions, implementation. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +/* + * 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 (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(!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(!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..8c6b08e3 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-darwin.cpp @@ -0,0 +1,1374 @@ +/* $Id: VBoxGuest-darwin.cpp $ */ +/** @file + * VBoxGuest - Darwin Specifics. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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> +#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) +/** @} */ + + +/********************************************************************************************************************************* +* 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) +DECLHIDDEN(kmod_start_func_t *) _realmain = vgdrvDarwinStart; +DECLHIDDEN(kmod_stop_func_t *) _antimain = vgdrvDarwinStop; +DECLHIDDEN(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(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(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 = 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 = 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..02b16566 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-freebsd.c @@ -0,0 +1,789 @@ +/* $Id: VBoxGuest-freebsd.c $ */ +/** @file + * VirtualBox Guest Additions Driver for FreeBSD. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +/** @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 (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(!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..ec0daa70 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku-stubs.c @@ -0,0 +1,461 @@ +/* $Id: VBoxGuest-haiku-stubs.c $ */ +/** @file + * VBoxGuest kernel module, Haiku Guest Additions, stubs. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +/* + * 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..5c2a64fc --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.c @@ -0,0 +1,578 @@ +/* $Id: VBoxGuest-haiku.c $ */ +/** @file + * VBoxGuest kernel module, Haiku Guest Additions, implementation. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +/* + * 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..f26eeb2c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.h @@ -0,0 +1,238 @@ +/* $Id: VBoxGuest-haiku.h $ */ +/** @file + * VBoxGuest kernel module, Haiku Guest Additions, header. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +/* + * 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..af4d3391 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c @@ -0,0 +1,1307 @@ +/* $Rev: 127855 $ */ +/** @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-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV + +#include "the-linux-kernel.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 15) +# define VBOXGUEST_WITH_INPUT_DRIVER +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0) +# define kgid_t gid_t +# define kuid_t uid_t +#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 vgdrvLinuxModInit(void); +static void 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 request for use in the IRQ + * handler. */ +static VMMDevReqMouseStatus *g_pMouseStatusReq; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 + +/** + * 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); + 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); +} + + +/** + * Creates the kernel input device. + */ +static int __init vgdrvLinuxCreateInputDevice(void) +{ + int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&g_pMouseStatusReq, sizeof(*g_pMouseStatusReq), VMMDevReq_GetMouseStatus); + if (RT_SUCCESS(rc)) + { + g_pInputDevice = input_allocate_device(); + if (g_pInputDevice) + { + 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 LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 22) + g_pInputDevice->cdev.dev = &g_pPciDev->dev; +# else + g_pInputDevice->dev.parent = &g_pPciDev->dev; +# endif + rc = input_register_device(g_pInputDevice); + if (rc == 0) + { + /* Do what one of our competitors apparently does as that works. */ + ASMBitSet(g_pInputDevice->evbit, EV_ABS); + ASMBitSet(g_pInputDevice->evbit, EV_KEY); +# ifdef EV_SYN + ASMBitSet(g_pInputDevice->evbit, EV_SYN); +# endif + 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); + /** @todo this string should be in a header file somewhere. */ + g_pInputDevice->name = "VirtualBox mouse integration"; + return 0; + } + + input_free_device(g_pInputDevice); + } + else + rc = -ENOMEM; + VbglR0GRFree(&g_pMouseStatusReq->header); + g_pMouseStatusReq = NULL; + } + else + rc = -ENOMEM; + return rc; +} + + +/** + * Terminates the kernel input device. + */ +static void vgdrvLinuxTermInputDevice(void) +{ + VbglR0GRFree(&g_pMouseStatusReq->header); + g_pMouseStatusReq = NULL; + + /* 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 LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + RTLogGroupSettings(pRelLogger, g_szLogGrp); + RTLogFlags(pRelLogger, g_szLogFlags); + RTLogDestinations(pRelLogger, g_szLogDst); +#endif + RTLogRelSetDefaultInstance(pRelLogger); + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) && defined(RT_ARCH_X86) + VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux26; +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) && defined(RT_ARCH_AMD64) + VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux26_x64; +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 0) && defined(RT_ARCH_X86) + VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux24; +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(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 ": 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 " (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 LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) +# if LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE < KERNEL_VERSION(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; +} + + +void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt) +{ +#ifdef VBOXGUEST_WITH_INPUT_DRIVER + int rc; +#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 + /* Report events to the kernel input device */ + g_pMouseStatusReq->mouseFeatures = 0; + g_pMouseStatusReq->pointerXPos = 0; + g_pMouseStatusReq->pointerYPos = 0; + rc = VbglR0GRPerform(&g_pMouseStatusReq->header); + if (RT_SUCCESS(rc)) + { + input_report_abs(g_pInputDevice, ABS_X, + g_pMouseStatusReq->pointerXPos); + input_report_abs(g_pInputDevice, ABS_Y, + g_pMouseStatusReq->pointerYPos); +# 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 LINUX_VERSION_CODE >= KERNEL_VERSION(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) + RTLogGetGroupSettings(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) + RTLogGetFlags(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) + RTLogGetDestinations(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..b142d7db --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c @@ -0,0 +1,1033 @@ +/* $Id: VBoxGuest-netbsd.c $ */ +/** @file + * VirtualBox Guest Additions Driver for NetBSD. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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 = +{ + VBoxGuestNetBSDOpen, + noclose, + noread, + nowrite, + noioctl, + nostop, + notty, + nopoll, + nommap, + 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, +}; + + +static struct wsmouse_calibcoords vboxguest_wsm_default_calib = { + .minx = VMMDEV_MOUSE_RANGE_MIN, + .miny = VMMDEV_MOUSE_RANGE_MIN, + .maxx = VMMDEV_MOUSE_RANGE_MAX, + .maxy = 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; + + sc->sc_wsmousedev = config_found_ia(sc->sc_dev, "wsmousedev", &am, wsmousedevprint); + 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; + + tpcalib_trans(&sc->sc_tpcalib, + sc->sc_vmmmousereq->pointerXPos, + sc->sc_vmmmousereq->pointerYPos, + &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; + register_t retval; + 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), + &retval, UIO_SYSSPACE); + if (error == EEXIST) + error = 0; + 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..499296c8 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.cpp @@ -0,0 +1,688 @@ +/* $Id: VBoxGuest-os2.cpp $ */ +/** @file + * VBoxGuest - OS/2 specifics. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + * --------------------------------------------------------------------------- + * 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..107aa236 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.def @@ -0,0 +1,45 @@ +; $Id: VBoxGuest-os2.def $ +;; @file +; VBoxGuest - OS/2 definition file. +; + +; +; Copyright (C) 2007-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE 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. +; + + +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..082f45d6 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c @@ -0,0 +1,1129 @@ +/* $Id: VBoxGuest-solaris.c $ */ +/** @file + * VirtualBox Guest Additions Driver for Solaris. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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; + } + + int instance = ddi_get_instance(pDip); + + /* + * 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, 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; + break; + + case DDI_INFO_DEVT2INSTANCE: + *ppvResult = (void *)(uintptr_t)ddi_get_instance(g_pDip); + 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; +} + + +/** + * Poll notifier for mouse poll events. + * + * @param pDevExt Pointer to the device extension. + * + * @remarks This must be called without holding any spinlocks. + */ +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..c751ec95 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.conf @@ -0,0 +1,31 @@ +# $Id: VBoxGuest-solaris.conf $ +## @file +# OpenSolaris Guest Driver Configuration + +# +# Copyright (C) 2007-2017 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE 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. +# + +# 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..2bb07704 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp @@ -0,0 +1,3470 @@ +/* $Id: VBoxGuest-win.cpp $ */ +/** @file + * VBoxGuest - Windows specifics. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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; + + +/********************************************************************************************************************************* +* 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; + 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; + int 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; + + 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 (succcess)\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_GUEST; + 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); + } + else + LogRel(("vgdrvNtCalcRequestorFlags: NtOpenProcessToken query failed: %#x\n", rcNt)); + + 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; +} + + +/** + * Overridden routine for mouse polling events. + * + * @param pDevExt Device extension structure. + */ +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..63840d42 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp @@ -0,0 +1,4507 @@ +/* $Id: VBoxGuest.cpp $ */ +/** @file + * VBoxGuest - Guest Additions Driver, Common Code. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +/** @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. + */ +PFNRT 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 fLogRel = *pszName == 'd' || *pszName == 'D'; + const char *pszSubName = &pszName[fLogRel ? 4 + 3 : 3]; + if ( !*pszSubName + || RTStrICmpAscii(pszSubName, "_flags") == 0 + || RTStrICmpAscii(pszSubName, "_dest") == 0) + { + PRTLOGGER pLogger = fLogRel ? 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. + */ +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_HGCMCall32: + case VMMDevReq_HGCMCall64: +# else + case VMMDevReq_HGCMCall: +# endif /* VBOX_WITH_64_BITS_GUESTS */ + 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: + 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: + case VMMDevReq_ChangeMemBalloon: + 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_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; + 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(); + + pCallReq->Hdr.rc = 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)); + 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..30a1f1b6 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuestA-os2.asm @@ -0,0 +1,1669 @@ +; $Id: VBoxGuestA-os2.asm $ +;; @file +; VBoxGuest - OS/2 assembly file, the first file in the link. +; + +; +; Copyright (C) 2007-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE 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. +;----------------------------------------------------------------------------- +; 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..bf3060c5 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h @@ -0,0 +1,405 @@ +/* $Id: VBoxGuestInternal.h $ */ +/** @file + * VBoxGuest - Guest Additions Driver, Internal Header. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +#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..097f1fb3 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile @@ -0,0 +1,185 @@ +# $Id: Makefile $ +## @file +# VirtualBox Guest Additions Module Makefile. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE 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. +# +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 + +.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 \ + RTStrICmpAscii.c \ + RTStrNICmpAscii.c \ + RTStrNCmp.c \ + RTStrNLen.c \ + stringalloc.c \ + strformat.c \ + strformatnum.c \ + strformatrt.c \ + strformattype.c \ + strprintf.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 + +.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..581de04f --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest @@ -0,0 +1,221 @@ +#!/bin/sh +# $Id: files_vboxguest $ +## @file +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# Copyright (C) 2007-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE 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. +# + +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/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}/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/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/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/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/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_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..1aa4102c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk @@ -0,0 +1,237 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the common guest addition code library. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE 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. +# + +SUB_DEPTH = ../../../../../.. +include $(KBUILD_PATH)/subheader.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 = VBOXGUESTR0LIB +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 = VBOXGUESTR0LIB +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 +# +if "$(KBUILD_TARGET)" == "win" || defined(VBOX_WITH_MASOCHISTIC_WARNINGS) ## @todo use VBoxGuestR3Lib everywhere +VBoxGuestR3Lib_TEMPLATE = VBoxGuestR3Lib +else +VBoxGuestR3Lib_TEMPLATE = VBOXGUESTR3LIB +endif +VBoxGuestR3Lib_DEFS = \ + VBOX_WITH_HGCM \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \ + $(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 \ + VBoxGuestR3LibClipboard.cpp \ + VBoxGuestR3LibCoreDump.cpp \ + VBoxGuestR3LibCpuHotPlug.cpp \ + VBoxGuestR3LibCredentials.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_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. +# +if "$(KBUILD_TARGET)" == "win" || defined(VBOX_WITH_MASOCHISTIC_WARNINGS) ## @todo use VBoxGuestR3Lib everywhere +VBoxGuestR3LibShared_TEMPLATE = VBoxGuestR3Dll +else +VBoxGuestR3LibShared_TEMPLATE = VBOXGUESTR3DLL +endif +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 = VBOXGUESTR3XF86LIB +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,) +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,) +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..688a7004 --- /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-2019 Oracle Corporation + * + * 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..1e559e8b --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp @@ -0,0 +1,185 @@ +/* $Id: VBoxGuestR0LibGenericRequest.cpp $ */ +/** @file + * VBoxGuestLibR0 - Generic VMMDev request management. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * 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_HGCMCall32 + || pReq->requestType == VMMDevReq_HGCMCall64 +#else + || pReq->requestType == VMMDevReq_HGCMCall +#endif + || pReq->requestType == VMMDevReq_RegisterSharedModule + || pReq->requestType == VMMDevReq_ReportGuestUserState + || pReq->requestType == VMMDevReq_LogString + || pReq->requestType == VMMDevReq_SetPointerShape + || pReq->requestType == VMMDevReq_VideoSetVisibleRegion) + { + 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..78a554e8 --- /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-2019 Oracle Corporation + * + * 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..4eb46c2a --- /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-2019 Oracle Corporation + * + * 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(); + 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..7ee61d93 --- /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-2019 Oracle Corporation + * + * 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..27d04dca --- /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-2019 Oracle Corporation + * + * 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..40bd4ab7 --- /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-2019 Oracle Corporation + * + * 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..c2c38952 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-win.cpp @@ -0,0 +1,192 @@ +/* $Id: VBoxGuestR0LibIdc-win.cpp $ */ +/** @file + * VBoxGuestLib - Ring-0 Support Library for VBoxGuest, IDC, Windows specific. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * 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. + */ + 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 + pIrp->Flags |= IRP_SYNCHRONOUS_API; + 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..a6cfa673 --- /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-2019 Oracle Corporation + * + * 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..3a758d70 --- /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-2019 Oracle Corporation + * + * 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..34f14ac4 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h @@ -0,0 +1,202 @@ +/* $Id: VBoxGuestR0LibInternal.h $ */ +/** @file + * VBoxGuestLibR0 - Internal header. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * 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 _VBGLPHYSHEAPCHUNK; +typedef struct _VBGLPHYSHEAPCHUNK VBGLPHYSHEAPCHUNK; + +enum VbglLibStatus +{ + VbglStatusNotInitialized = 0, + VbglStatusInitializing, + VbglStatusReady +}; + +/** + * Global VBGL ring-0 data. + * Lives in VbglR0Init.cpp. + */ +typedef struct VBGLDATA +{ + enum VbglLibStatus status; + + RTIOPORT portVMMDev; + + VMMDevMemory *pVMMDevMemory; + + /** + * Physical memory heap data. + * @{ + */ + + VBGLPHYSHEAPBLOCK *pFreeBlocksHead; + VBGLPHYSHEAPBLOCK *pAllocBlocksHead; + VBGLPHYSHEAPCHUNK *pChunkHead; + + RTSEMFASTMUTEX mutexHeap; + /** @} */ + + /** + * 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..b41c31bb --- /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-2019 Oracle Corporation + * + * 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..c409530e --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp @@ -0,0 +1,644 @@ +/* $Id: VBoxGuestR0LibPhysHeap.cpp $ */ +/** @file + * VBoxGuestLibR0 - Physical memory heap. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * 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/alloc.h> + +/* Physical memory heap consists of double linked list + * of chunks. Memory blocks are allocated inside these chunks + * and are members of Allocated and Free double linked lists. + * + * When allocating a block, we search in Free linked + * list for a suitable free block. If there is no such block, + * a new chunk is allocated and the new block is taken from + * the new chunk as the only chunk-sized free block. + * Allocated block is excluded from the Free list and goes to + * Alloc list. + * + * When freeing block, we check the pointer and then + * exclude block from Alloc list and move it to free list. + * + * For each chunk we maintain the allocated blocks counter. + * if 2 (or more) entire chunks are free they are immediately + * deallocated, so we always have at most 1 free chunk. + * + * When freeing blocks, two subsequent free blocks are always + * merged together. Current implementation merges blocks only + * when there is a block after the just freed one. + * + */ + +#define VBGL_PH_ASSERT Assert +#define VBGL_PH_ASSERTMsg AssertMsg + +// #define DUMPHEAP + +#ifdef DUMPHEAP +# define VBGL_PH_dprintf(a) RTAssertMsg2Weak a +#else +# define VBGL_PH_dprintf(a) +#endif + +/* Heap block signature */ +#define VBGL_PH_BLOCKSIGNATURE (0xADDBBBBB) + + +/* Heap chunk signature */ +#define VBGL_PH_CHUNKSIGNATURE (0xADDCCCCC) +/* Heap chunk allocation unit */ +#define VBGL_PH_CHUNKSIZE (0x10000) + +/* Heap block bit flags */ +#define VBGL_PH_BF_ALLOCATED (0x1) + +struct _VBGLPHYSHEAPBLOCK +{ + uint32_t u32Signature; + + /* Size of user data in the block. Does not include the block header. */ + uint32_t cbDataSize; + + uint32_t fu32Flags; + + struct _VBGLPHYSHEAPBLOCK *pNext; + struct _VBGLPHYSHEAPBLOCK *pPrev; + + struct _VBGLPHYSHEAPCHUNK *pChunk; +}; + +struct _VBGLPHYSHEAPCHUNK +{ + uint32_t u32Signature; + + /* Size of the chunk. Includes the chunk header. */ + uint32_t cbSize; + + /* Physical address of the chunk */ + uint32_t physAddr; + + /* Number of allocated blocks in the chunk */ + int32_t cAllocatedBlocks; + + struct _VBGLPHYSHEAPCHUNK *pNext; + struct _VBGLPHYSHEAPCHUNK *pPrev; +}; + + +#ifndef DUMPHEAP +#define dumpheap(a) +#else +void dumpheap (char *point) +{ + VBGL_PH_dprintf(("VBGL_PH dump at '%s'\n", point)); + + VBGL_PH_dprintf(("Chunks:\n")); + + VBGLPHYSHEAPCHUNK *pChunk = g_vbgldata.pChunkHead; + + while (pChunk) + { + VBGL_PH_dprintf(("%p: pNext = %p, pPrev = %p, sign = %08X, size = %8d, allocated = %8d, phys = %08X\n", + pChunk, pChunk->pNext, pChunk->pPrev, pChunk->u32Signature, pChunk->cbSize, pChunk->cAllocatedBlocks, pChunk->physAddr)); + + pChunk = pChunk->pNext; + } + + VBGL_PH_dprintf(("Allocated blocks:\n")); + + VBGLPHYSHEAPBLOCK *pBlock = g_vbgldata.pAllocBlocksHead; + + while (pBlock) + { + VBGL_PH_dprintf(("%p: pNext = %p, pPrev = %p, sign = %08X, size = %8d, flags = %08X, pChunk = %p\n", + pBlock, pBlock->pNext, pBlock->pPrev, pBlock->u32Signature, pBlock->cbDataSize, pBlock->fu32Flags, pBlock->pChunk)); + + pBlock = pBlock->pNext; + } + + VBGL_PH_dprintf(("Free blocks:\n")); + + pBlock = g_vbgldata.pFreeBlocksHead; + + while (pBlock) + { + VBGL_PH_dprintf(("%p: pNext = %p, pPrev = %p, sign = %08X, size = %8d, flags = %08X, pChunk = %p\n", + pBlock, pBlock->pNext, pBlock->pPrev, pBlock->u32Signature, pBlock->cbDataSize, pBlock->fu32Flags, pBlock->pChunk)); + + pBlock = pBlock->pNext; + } + + VBGL_PH_dprintf(("VBGL_PH dump at '%s' done\n", point)); +} +#endif + + +DECLINLINE(void *) vbglPhysHeapBlock2Data (VBGLPHYSHEAPBLOCK *pBlock) +{ + return (void *)(pBlock? (char *)pBlock + sizeof (VBGLPHYSHEAPBLOCK): NULL); +} + +DECLINLINE(VBGLPHYSHEAPBLOCK *) vbglPhysHeapData2Block (void *p) +{ + VBGLPHYSHEAPBLOCK *pBlock = (VBGLPHYSHEAPBLOCK *)(p? (char *)p - sizeof (VBGLPHYSHEAPBLOCK): NULL); + + VBGL_PH_ASSERTMsg(pBlock == NULL || pBlock->u32Signature == VBGL_PH_BLOCKSIGNATURE, + ("pBlock->u32Signature = %08X\n", pBlock->u32Signature)); + + return pBlock; +} + +DECLINLINE(int) vbglPhysHeapEnter (void) +{ + int rc = RTSemFastMutexRequest(g_vbgldata.mutexHeap); + + VBGL_PH_ASSERTMsg(RT_SUCCESS(rc), + ("Failed to request heap mutex, rc = %Rrc\n", rc)); + + return rc; +} + +DECLINLINE(void) vbglPhysHeapLeave (void) +{ + RTSemFastMutexRelease(g_vbgldata.mutexHeap); +} + + +static void vbglPhysHeapInitBlock (VBGLPHYSHEAPBLOCK *pBlock, VBGLPHYSHEAPCHUNK *pChunk, uint32_t cbDataSize) +{ + VBGL_PH_ASSERT(pBlock != NULL); + VBGL_PH_ASSERT(pChunk != NULL); + + pBlock->u32Signature = VBGL_PH_BLOCKSIGNATURE; + pBlock->cbDataSize = cbDataSize; + pBlock->fu32Flags = 0; + pBlock->pNext = NULL; + pBlock->pPrev = NULL; + pBlock->pChunk = pChunk; +} + + +static void vbglPhysHeapInsertBlock (VBGLPHYSHEAPBLOCK *pInsertAfter, VBGLPHYSHEAPBLOCK *pBlock) +{ + VBGL_PH_ASSERTMsg(pBlock->pNext == NULL, + ("pBlock->pNext = %p\n", pBlock->pNext)); + VBGL_PH_ASSERTMsg(pBlock->pPrev == NULL, + ("pBlock->pPrev = %p\n", pBlock->pPrev)); + + if (pInsertAfter) + { + pBlock->pNext = pInsertAfter->pNext; + pBlock->pPrev = pInsertAfter; + + if (pInsertAfter->pNext) + { + pInsertAfter->pNext->pPrev = pBlock; + } + + pInsertAfter->pNext = pBlock; + } + else + { + /* inserting to head of list */ + pBlock->pPrev = NULL; + + if (pBlock->fu32Flags & VBGL_PH_BF_ALLOCATED) + { + pBlock->pNext = g_vbgldata.pAllocBlocksHead; + + if (g_vbgldata.pAllocBlocksHead) + { + g_vbgldata.pAllocBlocksHead->pPrev = pBlock; + } + + g_vbgldata.pAllocBlocksHead = pBlock; + } + else + { + pBlock->pNext = g_vbgldata.pFreeBlocksHead; + + if (g_vbgldata.pFreeBlocksHead) + { + g_vbgldata.pFreeBlocksHead->pPrev = pBlock; + } + + g_vbgldata.pFreeBlocksHead = pBlock; + } + } +} + +static void vbglPhysHeapExcludeBlock (VBGLPHYSHEAPBLOCK *pBlock) +{ + if (pBlock->pNext) + { + pBlock->pNext->pPrev = pBlock->pPrev; + } + else + { + /* this is tail of list but we do not maintain tails of block lists. + * so do nothing. + */ + ; + } + + if (pBlock->pPrev) + { + pBlock->pPrev->pNext = pBlock->pNext; + } + else + { + /* this is head of list but we do not maintain tails of block lists. */ + if (pBlock->fu32Flags & VBGL_PH_BF_ALLOCATED) + { + g_vbgldata.pAllocBlocksHead = pBlock->pNext; + } + else + { + g_vbgldata.pFreeBlocksHead = pBlock->pNext; + } + } + + pBlock->pNext = NULL; + pBlock->pPrev = NULL; +} + +static VBGLPHYSHEAPBLOCK *vbglPhysHeapChunkAlloc (uint32_t cbSize) +{ + RTCCPHYS physAddr; + VBGLPHYSHEAPCHUNK *pChunk; + VBGLPHYSHEAPBLOCK *pBlock; + VBGL_PH_dprintf(("Allocating new chunk of size %d\n", cbSize)); + + /* Compute chunk size to allocate */ + if (cbSize < VBGL_PH_CHUNKSIZE) + { + /* Includes case of block size 0 during initialization */ + cbSize = VBGL_PH_CHUNKSIZE; + } + else + { + /* Round up to next chunk size, which must be power of 2 */ + cbSize = (cbSize + (VBGL_PH_CHUNKSIZE - 1)) & ~(VBGL_PH_CHUNKSIZE - 1); + } + + physAddr = 0; + /* This function allocates physical contiguous memory (below 4GB) according to the IPRT docs. + * Address < 4G is required for the port IO. + */ + pChunk = (VBGLPHYSHEAPCHUNK *)RTMemContAlloc (&physAddr, cbSize); + + if (!pChunk) + { + LogRel(("vbglPhysHeapChunkAlloc: failed to alloc %u contiguous bytes.\n", cbSize)); + return NULL; + } + + AssertRelease(physAddr < _4G && physAddr + cbSize <= _4G); + + pChunk->u32Signature = VBGL_PH_CHUNKSIGNATURE; + pChunk->cbSize = cbSize; + pChunk->physAddr = (uint32_t)physAddr; + pChunk->cAllocatedBlocks = 0; + pChunk->pNext = g_vbgldata.pChunkHead; + pChunk->pPrev = NULL; + + /* Initialize the free block, which now occupies entire chunk. */ + pBlock = (VBGLPHYSHEAPBLOCK *)((char *)pChunk + sizeof (VBGLPHYSHEAPCHUNK)); + + vbglPhysHeapInitBlock (pBlock, pChunk, cbSize - sizeof (VBGLPHYSHEAPCHUNK) - sizeof (VBGLPHYSHEAPBLOCK)); + + vbglPhysHeapInsertBlock (NULL, pBlock); + + g_vbgldata.pChunkHead = pChunk; + + VBGL_PH_dprintf(("Allocated chunk %p, block = %p size=%x\n", pChunk, pBlock, cbSize)); + + return pBlock; +} + + +void vbglPhysHeapChunkDelete (VBGLPHYSHEAPCHUNK *pChunk) +{ + char *p; + VBGL_PH_ASSERT(pChunk != NULL); + VBGL_PH_ASSERTMsg(pChunk->u32Signature == VBGL_PH_CHUNKSIGNATURE, + ("pChunk->u32Signature = %08X\n", pChunk->u32Signature)); + + VBGL_PH_dprintf(("Deleting chunk %p size %x\n", pChunk, pChunk->cbSize)); + + /* first scan the chunk and exclude all blocks from lists */ + + p = (char *)pChunk + sizeof (VBGLPHYSHEAPCHUNK); + + while (p < (char *)pChunk + pChunk->cbSize) + { + VBGLPHYSHEAPBLOCK *pBlock = (VBGLPHYSHEAPBLOCK *)p; + + p += pBlock->cbDataSize + sizeof (VBGLPHYSHEAPBLOCK); + + vbglPhysHeapExcludeBlock (pBlock); + } + + VBGL_PH_ASSERTMsg(p == (char *)pChunk + pChunk->cbSize, + ("p = %p, (char *)pChunk + pChunk->cbSize = %p, pChunk->cbSize = %08X\n", + p, (char *)pChunk + pChunk->cbSize, pChunk->cbSize)); + + /* Exclude chunk from the chunk list */ + if (pChunk->pNext) + { + pChunk->pNext->pPrev = pChunk->pPrev; + } + else + { + /* we do not maintain tail */ + ; + } + + if (pChunk->pPrev) + { + pChunk->pPrev->pNext = pChunk->pNext; + } + else + { + /* the chunk was head */ + g_vbgldata.pChunkHead = pChunk->pNext; + } + + RTMemContFree (pChunk, pChunk->cbSize); +} + + +DECLR0VBGL(void *) VbglR0PhysHeapAlloc (uint32_t cbSize) +{ + VBGLPHYSHEAPBLOCK *pBlock, *iter; + int rc = vbglPhysHeapEnter (); + + if (RT_FAILURE(rc)) + return NULL; + + dumpheap ("pre alloc"); + + pBlock = NULL; + + /* If there are free blocks in the heap, look at them. */ + iter = g_vbgldata.pFreeBlocksHead; + + /* There will be not many blocks in the heap, so + * linear search would be fast enough. + */ + + while (iter) + { + if (iter->cbDataSize == cbSize) + { + /* exact match */ + pBlock = iter; + break; + } + + /* Looking for a free block with nearest size */ + if (iter->cbDataSize > cbSize) + { + if (pBlock) + { + if (iter->cbDataSize < pBlock->cbDataSize) + { + pBlock = iter; + } + } + else + { + pBlock = iter; + } + } + + iter = iter->pNext; + } + + if (!pBlock) + { + /* No free blocks, allocate a new chunk, + * the only free block of the chunk will + * be returned. + */ + pBlock = vbglPhysHeapChunkAlloc (cbSize); + } + + if (pBlock) + { + VBGL_PH_ASSERTMsg(pBlock->u32Signature == VBGL_PH_BLOCKSIGNATURE, + ("pBlock = %p, pBlock->u32Signature = %08X\n", pBlock, pBlock->u32Signature)); + VBGL_PH_ASSERTMsg((pBlock->fu32Flags & VBGL_PH_BF_ALLOCATED) == 0, + ("pBlock = %p, pBlock->fu32Flags = %08X\n", pBlock, pBlock->fu32Flags)); + + /* We have a free block, either found or allocated. */ + + if (pBlock->cbDataSize > 2*(cbSize + sizeof (VBGLPHYSHEAPBLOCK))) + { + /* Data will occupy less than a half of the block, + * the block should be split. + */ + iter = (VBGLPHYSHEAPBLOCK *)((char *)pBlock + sizeof (VBGLPHYSHEAPBLOCK) + cbSize); + + /* Init the new 'iter' block, initialized blocks are always marked as free. */ + vbglPhysHeapInitBlock (iter, pBlock->pChunk, pBlock->cbDataSize - cbSize - sizeof (VBGLPHYSHEAPBLOCK)); + + pBlock->cbDataSize = cbSize; + + /* Insert the new 'iter' block after the 'pBlock' in the free list */ + vbglPhysHeapInsertBlock (pBlock, iter); + } + + /* Exclude pBlock from free list */ + vbglPhysHeapExcludeBlock (pBlock); + + /* Mark as allocated */ + pBlock->fu32Flags |= VBGL_PH_BF_ALLOCATED; + + /* Insert to allocated list */ + vbglPhysHeapInsertBlock (NULL, pBlock); + + /* Adjust the chunk allocated blocks counter */ + pBlock->pChunk->cAllocatedBlocks++; + } + + dumpheap ("post alloc"); + + vbglPhysHeapLeave (); + VBGL_PH_dprintf(("VbglR0PhysHeapAlloc %x size %x\n", vbglPhysHeapBlock2Data (pBlock), pBlock->cbDataSize)); + + return vbglPhysHeapBlock2Data (pBlock); +} + +DECLR0VBGL(uint32_t) VbglR0PhysHeapGetPhysAddr (void *p) +{ + uint32_t physAddr = 0; + VBGLPHYSHEAPBLOCK *pBlock = vbglPhysHeapData2Block (p); + + if (pBlock) + { + VBGL_PH_ASSERTMsg((pBlock->fu32Flags & VBGL_PH_BF_ALLOCATED) != 0, + ("pBlock = %p, pBlock->fu32Flags = %08X\n", pBlock, pBlock->fu32Flags)); + + if (pBlock->fu32Flags & VBGL_PH_BF_ALLOCATED) + physAddr = pBlock->pChunk->physAddr + (uint32_t)((uintptr_t)p - (uintptr_t)pBlock->pChunk); + } + + return physAddr; +} + +DECLR0VBGL(void) VbglR0PhysHeapFree(void *p) +{ + VBGLPHYSHEAPBLOCK *pBlock; + VBGLPHYSHEAPBLOCK *pNeighbour; + + int rc = vbglPhysHeapEnter (); + if (RT_FAILURE(rc)) + return; + + dumpheap ("pre free"); + + pBlock = vbglPhysHeapData2Block (p); + + if (!pBlock) + { + vbglPhysHeapLeave (); + return; + } + + VBGL_PH_ASSERTMsg((pBlock->fu32Flags & VBGL_PH_BF_ALLOCATED) != 0, + ("pBlock = %p, pBlock->fu32Flags = %08X\n", pBlock, pBlock->fu32Flags)); + + /* Exclude from allocated list */ + vbglPhysHeapExcludeBlock (pBlock); + + dumpheap ("post exclude"); + + VBGL_PH_dprintf(("VbglR0PhysHeapFree %x size %x\n", p, pBlock->cbDataSize)); + + /* Mark as free */ + pBlock->fu32Flags &= ~VBGL_PH_BF_ALLOCATED; + + /* Insert to free list */ + vbglPhysHeapInsertBlock (NULL, pBlock); + + dumpheap ("post insert"); + + /* Adjust the chunk allocated blocks counter */ + pBlock->pChunk->cAllocatedBlocks--; + + VBGL_PH_ASSERT(pBlock->pChunk->cAllocatedBlocks >= 0); + + /* Check if we can merge 2 free blocks. To simplify heap maintenance, + * we will look at block after the just freed one. + * This will not prevent us from detecting free memory chunks. + * Also in most cases blocks are deallocated in reverse allocation order + * and in that case the merging will work. + */ + + pNeighbour = (VBGLPHYSHEAPBLOCK *)((char *)p + pBlock->cbDataSize); + + if ((char *)pNeighbour < (char *)pBlock->pChunk + pBlock->pChunk->cbSize + && (pNeighbour->fu32Flags & VBGL_PH_BF_ALLOCATED) == 0) + { + /* The next block is free as well. */ + + /* Adjust size of current memory block */ + pBlock->cbDataSize += pNeighbour->cbDataSize + sizeof (VBGLPHYSHEAPBLOCK); + + /* Exclude the next neighbour */ + vbglPhysHeapExcludeBlock (pNeighbour); + } + + dumpheap ("post merge"); + + /* now check if there are 2 or more free chunks */ + if (pBlock->pChunk->cAllocatedBlocks == 0) + { + VBGLPHYSHEAPCHUNK *pChunk = g_vbgldata.pChunkHead; + + uint32_t u32FreeChunks = 0; + + while (pChunk) + { + if (pChunk->cAllocatedBlocks == 0) + { + u32FreeChunks++; + } + + pChunk = pChunk->pNext; + } + + if (u32FreeChunks > 1) + { + /* Delete current chunk, it will also exclude all free blocks + * remaining in the chunk from the free list, so the pBlock + * will also be invalid after this. + */ + vbglPhysHeapChunkDelete (pBlock->pChunk); + } + } + + dumpheap ("post free"); + + vbglPhysHeapLeave (); +} + +DECLR0VBGL(int) VbglR0PhysHeapInit (void) +{ + int rc = VINF_SUCCESS; + + /* Allocate the first chunk of the heap. */ + VBGLPHYSHEAPBLOCK *pBlock = vbglPhysHeapChunkAlloc (0); + + if (!pBlock) + rc = VERR_NO_MEMORY; + + RTSemFastMutexCreate(&g_vbgldata.mutexHeap); + + return rc; +} + +DECLR0VBGL(void) VbglR0PhysHeapTerminate (void) +{ + while (g_vbgldata.pChunkHead) + { + vbglPhysHeapChunkDelete (g_vbgldata.pChunkHead); + } + + RTSemFastMutexDestroy(g_vbgldata.mutexHeap); +} + 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..13db0c1e --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibSharedFolders.c @@ -0,0 +1,704 @@ +/* $Id: VBoxGuestR0LibSharedFolders.c $ */ +/** @file + * VBoxGuestR0LibSharedFolders - Ring 0 Shared Folders calls. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * 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 SHFL_CPARMS_SET_UTF8 0 +#define SHFL_CPARMS_SET_SYMLINKS 0 + +#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). */ + + + +/** @todo We only need HGCM, not physical memory, so other guests should also + * switch to calling vbglR0HGCMInit() and vbglR0HGCMTerminate() instead + * of VbglR0SfInit() and VbglR0SfTerm(). */ +#ifndef RT_OS_LINUX +DECLVBGL(int) VbglR0SfInit(void) +{ + return VbglR0InitClient(); +} + +DECLVBGL(void) VbglR0SfTerm(void) +{ + VbglR0TerminateClient(); +} +#endif + +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; +} + +/** @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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + + +/** @} */ + 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..850ee33a --- /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-2019 Oracle Corporation + * + * 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..eab9c3e9 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3Lib.cpp @@ -0,0 +1,475 @@ +/* $Id: VBoxGuestR3Lib.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Core. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..f3bb9a57 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAdditions.cpp @@ -0,0 +1,353 @@ +/* $Id: VBoxGuestR3LibAdditions.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Additions Info. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..3374326c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAutoLogon.cpp @@ -0,0 +1,120 @@ +/* $Id: VBoxGuestR3LibAutoLogon.cpp $ */ +/** @file + * VBoxGuestR3LibAutoLogon - Ring-3 utility functions for auto-logon modules + * (VBoxGINA / VBoxCredProv / pam_vbox). + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..2c192f0d --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibBalloon.cpp @@ -0,0 +1,73 @@ +/* $Id: VBoxGuestR3LibBalloon.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Ballooning. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..f7653bcf --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp @@ -0,0 +1,180 @@ +/* $Id: VBoxGuestR3LibClipboard.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Clipboard. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/err.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include "VBoxGuestR3LibInternal.h" + + +/** + * Connects to the clipboard 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 clipboard calls. + */ +VBGLR3DECL(int) VbglR3ClipboardConnect(HGCMCLIENTID *pidClient) +{ + int rc = VbglR3HGCMConnect("VBoxSharedClipboard", pidClient); + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) + rc = VINF_PERMISSION_DENIED; + return rc; +} + + +/** + * Disconnect from the clipboard service. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3ClipboardConnect(). + */ +VBGLR3DECL(int) VbglR3ClipboardDisconnect(HGCMCLIENTID idClient) +{ + return VbglR3HGCMDisconnect(idClient); +} + + +/** + * Get a host message. + * + * 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) VbglR3ClipboardGetHostMsg(HGCMCLIENTID idClient, uint32_t *pidMsg, uint32_t *pfFormats) +{ + VBoxClipboardGetHostMsg Msg; + + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG, 2); + 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. + * + * @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 pv Where to store the data. + * @param cb The size of the buffer pointed to by pv. + * @param pcb The actual size of the host clipboard data. May be larger than cb. + */ +VBGLR3DECL(int) VbglR3ClipboardReadData(HGCMCLIENTID idClient, uint32_t fFormat, void *pv, uint32_t cb, uint32_t *pcb) +{ + VBoxClipboardReadData Msg; + + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, VBOX_SHARED_CLIPBOARD_FN_READ_DATA, 3); + VbglHGCMParmUInt32Set(&Msg.format, fFormat); + VbglHGCMParmPtrSet(&Msg.ptr, pv, cb); + VbglHGCMParmUInt32Set(&Msg.size, 0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + uint32_t cbActual; + int rc2 = VbglHGCMParmUInt32Get(&Msg.size, &cbActual); + if (RT_SUCCESS(rc2)) + { + *pcb = cbActual; + if (cbActual > cb) + return VINF_BUFFER_OVERFLOW; + return rc; + } + rc = rc2; + } + return rc; +} + + +/** + * Advertises guest clipboard formats to the host. + * + * @returns VBox status code. + * @param idClient The client id returned by VbglR3ClipboardConnect(). + * @param fFormats The formats to advertise. + */ +VBGLR3DECL(int) VbglR3ClipboardReportFormats(HGCMCLIENTID idClient, uint32_t fFormats) +{ + VBoxClipboardFormats Msg; + + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, VBOX_SHARED_CLIPBOARD_FN_FORMATS, 1); + VbglHGCMParmUInt32Set(&Msg.formats, fFormats); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + + +/** + * Send guest clipboard data to the host. + * + * This is usually called in reply to a VBOX_SHARED_CLIPBOARD_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 pv The data. + * @param cb The size of the data. + */ +VBGLR3DECL(int) VbglR3ClipboardWriteData(HGCMCLIENTID idClient, uint32_t fFormat, void *pv, uint32_t cb) +{ + VBoxClipboardWriteData Msg; + VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, VBOX_SHARED_CLIPBOARD_FN_WRITE_DATA, 2); + VbglHGCMParmUInt32Set(&Msg.format, fFormat); + VbglHGCMParmPtrSet(&Msg.ptr, pv, cb); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + 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..3792db30 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCoreDump.cpp @@ -0,0 +1,46 @@ +/* $Id: VBoxGuestR3LibCoreDump.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Core Dumps. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..6f21aae0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCpuHotPlug.cpp @@ -0,0 +1,123 @@ +/* $Id: VBoxGuestR3LibCpuHotPlug.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, CPU Hot Plugging. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..63771dc7 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCredentials.cpp @@ -0,0 +1,212 @@ +/* $Id: VBoxGuestR3LibCredentials.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, user credentials. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..f31ebc15 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp @@ -0,0 +1,254 @@ +/** $Id: VBoxGuestR3LibDaemonize.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, daemonize a process. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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 "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. + * + * @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) VbglR3Daemonize(bool fNoChDir, bool fNoClose, bool fRespawn, unsigned *pcRespawn) +{ +#if defined(RT_OS_OS2) + PPIB pPib; + PTIB pTib; + DosGetInfoBlocks(&pTib, &pPib); + + 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 */ + + 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) && WEXITSTATUS(iStatus) == 0) + exit(0); + sleep(5); + ++cRespawn; + } + } + return VINF_SUCCESS; +#endif +} 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..1385fa52 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp @@ -0,0 +1,1898 @@ +/* $Id: VBoxGuestR3LibDragAndDrop.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Drag & Drop. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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. + * @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); + + VBOXDNDNEXTMSGMSG Msg; + RT_ZERO(Msg); + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_GET_NEXT_HOST_MSG, 3); + Msg.uMsg.SetUInt32(0); + Msg.cParms.SetUInt32(0); + Msg.fBlock.SetUInt32(fWait ? 1 : 0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.uMsg.GetUInt32(puMsg); AssertRC(rc); + rc = Msg.cParms.GetUInt32(pcParms); AssertRC(rc); + } + + 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; + + VBOXDNDHGACTIONMSG Msg; + RT_ZERO(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_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); + + VBOXDNDHGLEAVEMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_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_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); + + VBOXDNDHGCANCELMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_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_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); + + VBOXDNDHGSENDDIRMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_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_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); + + VBOXDNDHGSENDFILEDATAMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_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_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); + + VBOXDNDHGSENDFILEHDRMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_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. + * @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, DnDDroppedFiles *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; + + /* + * 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. */ + + /* + * Create and query the (unique) drop target directory in the user's temporary directory. + */ + int rc = pDroppedFiles->OpenTemp(0 /* fFlags */); + if (RT_FAILURE(rc)) + { + RTMemFree(pvChunk); + return VERR_NO_MEMORY; + } + + const char *pszDropDir = pDroppedFiles->GetDirAbs(); + AssertPtr(pszDropDir); + + /* + * Enter the main loop of retieving files + directories. + */ + DnDURIObject objFile(DnDURIObject::Type_File); + + char szPathName[RTPATH_MAX] = { 0 }; + uint32_t cbPathName = 0; + uint32_t fFlags = 0; + uint32_t fMode = 0; + + do + { + LogFlowFunc(("Wating 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_HG_SND_DIR: + { + rc = vbglR3DnDHGRecvDir(pCtx, + szPathName, + sizeof(szPathName), + &cbPathName, + &fMode); + LogFlowFunc(("HOST_DND_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 = pDroppedFiles->AddDir(pszPathAbs); + + if (RT_SUCCESS(rc)) + { + Assert(cToRecvObjs); + cToRecvObjs--; + } + + RTStrFree(pszPathAbs); + } + else + rc = VERR_NO_MEMORY; + break; + } + case HOST_DND_HG_SND_FILE_HDR: + case HOST_DND_HG_SND_FILE_DATA: + { + if (uNextMsg == HOST_DND_HG_SND_FILE_HDR) + { + rc = vbglR3DnDHGRecvFileHdr(pCtx, + szPathName, + sizeof(szPathName), + &fFlags, + &fMode, + &cbFileSize); + LogFlowFunc(("HOST_DND_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_HG_SND_FILE_DATA: " + "cbChunkRead=%RU32, rc=%Rrc\n", cbChunkRead, rc)); + } + + if ( RT_SUCCESS(rc) + && uNextMsg == HOST_DND_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 (!objFile.IsOpen()) + { + RTCString strPathAbs(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_IRUSR | RTFS_UNIX_IWUSR; +#endif + rc = objFile.OpenEx(strPathAbs, DnDURIObject::View_Target, fOpen, fCreationMode); + if (RT_SUCCESS(rc)) + { + rc = pDroppedFiles->AddFile(strPathAbs.c_str()); + if (RT_SUCCESS(rc)) + { + cbFileWritten = 0; + objFile.SetSize(cbFileSize); + } + } + } + else + { + AssertMsgFailed(("ObjType=%RU32\n", objFile.GetType())); + rc = VERR_WRONG_ORDER; + } + + RTStrFree(pszPathAbs); + } + else + rc = VERR_NO_MEMORY; + } + + if ( RT_SUCCESS(rc) + && uNextMsg == HOST_DND_HG_SND_FILE_DATA + && cbChunkRead) + { + uint32_t cbChunkWritten; + rc = objFile.Write(pvChunk, cbChunkRead, &cbChunkWritten); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("HOST_DND_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 = objFile.IsComplete(); + if (fClose) + { + Assert(cToRecvObjs); + cToRecvObjs--; + } + + /* Only since protocol v2 we know the file size upfront. */ + Assert(cbFileWritten <= cbFileSize); + + if (fClose) + { + LogFlowFunc(("Closing file\n")); + objFile.Close(); + } + + break; + } + case HOST_DND_CANCEL: + { + rc = vbglR3DnDHGRecvCancel(pCtx); + if (RT_SUCCESS(rc)) + rc = VERR_CANCELLED; + break; + } + default: + { + LogFlowFunc(("Message %RU32 not supported\n", 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)) + { + objFile.Close(); + pDroppedFiles->Rollback(); + } + else + { + /** @todo Compare the URI 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 = pDroppedFiles->Reset(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_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)); + + VBOXDNDHGSENDDATAMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_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_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->uProtocol >= 3); /* Only for protocol v3 and up. */ + + VBOXDNDHGSENDDATAHDRMSG Msg; + RT_ZERO(Msg); + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_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, extended version. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pEnmType Where to store the meta data type. Optional. + * @param ppvData Returns the received meta data. Needs to be free'd by the caller. Optional. + * @param pcbData Where to store the size (in bytes) of the received meta data. Optional. + */ +static int vbglR3DnDHGRecvDataMainEx(PVBGLR3GUESTDNDCMDCTX pCtx, + VBGLR3GUESTDNDMETADATATYPE *pEnmType, + void **ppvData, + uint32_t *pcbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + /* The rest is optional. */ + + VBOXDNDDATAHDR dataHdr; + RT_ZERO(dataHdr); + + AssertMsg(pCtx->cbMaxChunkSize, ("Maximum chunk size must not be 0\n")); + + dataHdr.cbMetaFmt = pCtx->cbMaxChunkSize; + dataHdr.pvMetaFmt = RTMemAlloc(dataHdr.cbMetaFmt); + if (!dataHdr.pvMetaFmt) + return VERR_NO_MEMORY; + + DnDURIList lstURI; + DnDDroppedFiles droppedFiles; + + void *pvData = NULL; + uint64_t cbData = 0; + + int rc = vbglR3DnDHGRecvDataLoop(pCtx, &dataHdr, &pvData, &cbData); + if (RT_SUCCESS(rc)) + { + /** + * 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. */ + { + AssertPtr(pvData); + Assert(cbData); + + rc = lstURI.SetFromURIData(pvData, cbData, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + rc = vbglR3DnDHGRecvURIData(pCtx, &dataHdr, &droppedFiles); + + if (RT_SUCCESS(rc)) /** @todo Remove this block as soon as we hand in DnDURIList. */ + { + if (pvData) + { + /* Reuse data buffer to fill in the transformed URI file list. */ + RTMemFree(pvData); + pvData = NULL; + } + + RTCString strData = lstURI.GetRootEntries(droppedFiles.GetDirAbs()); + Assert(!strData.isEmpty()); + + cbData = strData.length() + 1; + LogFlowFunc(("URI list has %zu bytes\n", cbData)); + + pvData = RTMemAlloc(cbData); + if (pvData) + { + memcpy(pvData, strData.c_str(), cbData); + + if (pEnmType) + *pEnmType = VBGLR3GUESTDNDMETADATATYPE_URI_LIST; + } + else + rc = VERR_NO_MEMORY; + } + } + else /* Raw data. */ + { + if (pEnmType) + *pEnmType = VBGLR3GUESTDNDMETADATATYPE_RAW; + } + } + + if (dataHdr.pvMetaFmt) + RTMemFree(dataHdr.pvMetaFmt); + + if (RT_SUCCESS(rc)) + { + if ( pvData + && cbData) + { + if (pcbData) + *pcbData = cbData; + if (ppvData) + *ppvData = pvData; + else + RTMemFree(pvData); + } + } + else if ( RT_FAILURE(rc) + && rc != VERR_CANCELLED) + { + if (pvData) + RTMemFree(pvData); + + int rc2 = VbglR3DnDHGSendProgress(pCtx, DND_PROGRESS_ERROR, 100 /* Percent */, rc); + if (RT_FAILURE(rc2)) + LogFlowFunc(("Unable to send progress error %Rrc to host: %Rrc\n", rc, rc2)); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest + * Main function for receiving the actual DnD data from the host. + * + * @returns IPRT status code. + * @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(pMeta, VERR_INVALID_POINTER); + + int rc = vbglR3DnDHGRecvDataMainEx(pCtx, + &pMeta->enmType, + &pMeta->pvMeta, + &pMeta->cbMeta); + return rc; +} + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH +/** + * Guest -> Host + * Utility function to receive the HOST_DND_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. */ + + VBOXDNDGHREQPENDINGMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_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_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; + + VBOXDNDGHDROPPEDMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_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; + + /* Set the default protocol version to use. */ + pCtx->uProtocol = 3; + Assert(pCtx->uClientID); + + /* + * 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); + LogFlowFunc(("uSessionID=%RU64, rc=%Rrc\n", pCtx->uSessionID, rc2)); + + /* + * Check if the host is >= VBox 5.0 which in case supports GUEST_DND_CONNECT. + */ + bool fSupportsConnectReq = false; + if (RT_SUCCESS(rc)) + { + /* The guest property service might not be available. Not fatal. */ + uint32_t uGuestPropSvcClientID; + rc2 = VbglR3GuestPropConnect(&uGuestPropSvcClientID); + if (RT_SUCCESS(rc2)) + { + char *pszHostVersion; + rc2 = VbglR3GuestPropReadValueAlloc(uGuestPropSvcClientID, "/VirtualBox/HostInfo/VBoxVer", &pszHostVersion); + if (RT_SUCCESS(rc2)) + { + fSupportsConnectReq = RTStrVersionCompare(pszHostVersion, "5.0") >= 0; + LogFlowFunc(("pszHostVersion=%s, fSupportsConnectReq=%RTbool\n", pszHostVersion, fSupportsConnectReq)); + VbglR3GuestPropReadValueFree(pszHostVersion); + } + + VbglR3GuestPropDisconnect(uGuestPropSvcClientID); + } + + if (RT_FAILURE(rc2)) + LogFlowFunc(("Retrieving host version failed with rc=%Rrc\n", rc2)); + } + + if (fSupportsConnectReq) + { + /* + * 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. + */ + VBOXDNDCONNECTMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_CONNECT, 3); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uProtocol.SetUInt32(pCtx->uProtocol); + Msg.u.v3.uFlags.SetUInt32(0); /* Unused at the moment. */ + + rc2 = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_FAILURE(rc2)) + fSupportsConnectReq = false; + + LogFlowFunc(("Connection request ended with rc=%Rrc\n", rc2)); + } + + if (fSupportsConnectReq) + { + pCtx->cbMaxChunkSize = _64K; /** @todo Use a scratch buffer on the heap? */ + } + else /* GUEST_DND_CONNECT not supported; the user needs to upgrade the host. */ + rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("uClient=%RU32, uProtocol=%RU32, rc=%Rrc\n", pCtx->uClientID, pCtx->uProtocol, 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); + int rc = VbglR3HGCMDisconnect(pCtx->uClientID); + if (RT_SUCCESS(rc)) + pCtx->uClientID = 0; + 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 IPRT 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)) + { + LogFlowFunc(("VM session ID changed to %RU64\n", uSessionID)); + } + } + + if (RT_SUCCESS(rc)) + { + LogFunc(("Handling uMsg=%RU32\n", uMsg)); + + switch(uMsg) + { + case HOST_DND_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_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_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_HG_EVT_LEAVE: + { + rc = vbglR3DnDHGRecvLeave(pCtx); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_LEAVE; + break; + } + case HOST_DND_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_HG_SND_DIR: + RT_FALL_THROUGH(); + case HOST_DND_HG_SND_FILE_DATA: + { + /* + * All messages in this case are handled internally + * by vbglR3DnDHGRecvDataMain() and must be specified + * by preceeding HOST_DND_HG_SND_DATA or HOST_DND_HG_SND_DATA_HDR calls. + */ + rc = VERR_WRONG_ORDER; + break; + } + case HOST_DND_CANCEL: + { + rc = vbglR3DnDHGRecvCancel(pCtx); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_CANCEL; + break; + } +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case HOST_DND_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_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)) + { + VbglR3DnDEventFree(pEvent); + LogFlowFunc(("Failed with %Rrc\n", rc)); + } + else + *ppEvent = pEvent; + + 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; + if (pMeta->pvMeta) + { + Assert(pMeta->cbMeta); + RTMemFree(pMeta->pvMeta); + pMeta->cbMeta = 0; + } + break; + } + + default: + break; + } + + RTMemFree(pEvent); + pEvent = NULL; +} + +VBGLR3DECL(int) VbglR3DnDHGSendAckOp(PVBGLR3GUESTDNDCMDCTX pCtx, VBOXDNDACTION dndAction) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBOXDNDHGACKOPMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_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 */ + + VBOXDNDHGREQDATAMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_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); + + VBOXDNDHGEVTPROGRESSMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_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; + + VBOXDNDGHACKPENDINGMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_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); + + VBOXDNDGHSENDDATAHDRMSG MsgHdr; + RT_ZERO(MsgHdr); + + VBGL_HGCM_HDR_INIT(&MsgHdr.hdr, pCtx->uClientID, GUEST_DND_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)) + { + VBOXDNDGHSENDDATAMSG MsgData; + RT_ZERO(MsgData); + + VBGL_HGCM_HDR_INIT(&MsgData.hdr, pCtx->uClientID, GUEST_DND_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; + + HGCMFunctionParameter *pParm = &MsgData.u.v3.pvData; + + while (cbSent < cbData) + { + cbCurChunk = RT_MIN(cbData - cbSent, cbMaxChunk); + pParm->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 URI object containing the directory to send. + */ +static int vbglR3DnDGHSendDir(PVBGLR3GUESTDNDCMDCTX pCtx, DnDURIObject *pObj) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pObj->GetType() == DnDURIObject::Type_Directory, VERR_INVALID_PARAMETER); + + RTCString strPath = pObj->GetDestPathAbs(); + LogFlowFunc(("strDir=%s (%zu), fMode=0x%x\n", + strPath.c_str(), strPath.length(), pObj->GetMode())); + + if (strPath.length() > RTPATH_MAX) + return VERR_INVALID_PARAMETER; + + const uint32_t cbPath = (uint32_t)strPath.length() + 1; /* Include termination. */ + + VBOXDNDGHSENDDIRMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_GH_SND_DIR, 4); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvName.SetPtr((void *)strPath.c_str(), (uint32_t)cbPath); + Msg.u.v3.cbName.SetUInt32((uint32_t)cbPath); + Msg.u.v3.fMode.SetUInt32(pObj->GetMode()); + + 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 URI object containing the file to send. + */ +static int vbglR3DnDGHSendFile(PVBGLR3GUESTDNDCMDCTX pCtx, DnDURIObject *pObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertReturn(pObj->GetType() == DnDURIObject::Type_File, VERR_INVALID_PARAMETER); + AssertReturn(pObj->IsOpen(), VERR_INVALID_STATE); + + uint32_t cbBuf = _64K; /** @todo Make this configurable? */ + void *pvBuf = RTMemAlloc(cbBuf); /** @todo Make this buffer part of PVBGLR3GUESTDNDCMDCTX? */ + if (!pvBuf) + return VERR_NO_MEMORY; + + RTCString strPath = pObj->GetDestPathAbs(); + + LogFlowFunc(("strFile=%s (%zu), cbSize=%RU64, fMode=0x%x\n", strPath.c_str(), strPath.length(), + pObj->GetSize(), pObj->GetMode())); + + VBOXDNDGHSENDFILEHDRMSG MsgHdr; + RT_ZERO(MsgHdr); + + VBGL_HGCM_HDR_INIT(&MsgHdr.hdr, pCtx->uClientID, GUEST_DND_GH_SND_FILE_HDR, 6); + MsgHdr.uContext.SetUInt32(0); /* Context ID; unused at the moment. */ + MsgHdr.pvName.SetPtr((void *)strPath.c_str(), (uint32_t)(strPath.length() + 1)); + MsgHdr.cbName.SetUInt32((uint32_t)(strPath.length() + 1)); + MsgHdr.uFlags.SetUInt32(0); /* Flags; unused at the moment. */ + MsgHdr.fMode.SetUInt32(pObj->GetMode()); /* File mode */ + MsgHdr.cbTotal.SetUInt64(pObj->GetSize()); /* File size (in bytes). */ + + int 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. + */ + VBOXDNDGHSENDFILEDATAMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_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 = pObj->GetSize(); + uint64_t cbWrittenTotal = 0; + while (cbToReadTotal) + { + uint32_t cbToRead = RT_MIN(cbToReadTotal, cbBuf); + uint32_t cbRead = 0; + if (cbToRead) + rc = pObj->Read(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, pObj->GetSize(), cbWrittenTotal * 100 / pObj->GetSize())); + }; + } + + RTMemFree(pvBuf); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Guest -> Host + * Utility function to send an URI object from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pObj URI object to send from guest to the host. + */ +static int vbglR3DnDGHSendURIObject(PVBGLR3GUESTDNDCMDCTX pCtx, DnDURIObject *pObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + + int rc; + + switch (pObj->GetType()) + { + case DnDURIObject::Type_Directory: + rc = vbglR3DnDGHSendDir(pCtx, pObj); + break; + + case DnDURIObject::Type_File: + rc = vbglR3DnDGHSendFile(pCtx, pObj); + break; + + default: + AssertMsgFailed(("Object type %ld not implemented\n", pObj->GetType())); + 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); + + /* For raw data only the total size is required to be specified. */ + dataHdr.cbTotal = cbData; + + return vbglR3DnDGHSendDataInternal(pCtx, pvData, cbData, &dataHdr); +} + +/** + * Guest -> Host + * Utility function to send URI data from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pvData Block to URI data to send. + * @param cbData Size (in bytes) of URI data to send. + */ +static int vbglR3DnDGHSendURIData(PVBGLR3GUESTDNDCMDCTX pCtx, const void *pvData, size_t cbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + RTCList<RTCString> lstPaths = + RTCString((const char *)pvData, cbData).split("\r\n"); + + /** @todo Add symlink support (DNDURILIST_FLAGS_RESOLVE_SYMLINKS) here. */ + /** @todo Add lazy loading (DNDURILIST_FLAGS_LAZY) here. */ + uint32_t fFlags = DNDURILIST_FLAGS_KEEP_OPEN; + + DnDURIList lstURI; + int rc = lstURI.AppendURIPathsFromList(lstPaths, fFlags); + if (RT_SUCCESS(rc)) + { + /* + * Send the (meta) data; in case of URIs it's the (non-recursive) file/directory + * URI list the host needs to know upfront to set up the drag'n drop operation. + */ + RTCString strRootDest = lstURI.GetRootEntries(); + if (strRootDest.isNotEmpty()) + { + void *pvURIList = (void *)strRootDest.c_str(); /* URI root list. */ + uint32_t cbURLIist = (uint32_t)strRootDest.length() + 1; /* Include string termination. */ + + /* The total size also contains the size of the meta data. */ + uint64_t cbTotal = cbURLIist; + cbTotal += lstURI.GetTotalBytes(); + + /* We're going to send an URI 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 = lstURI.GetTotalCount(); + + rc = vbglR3DnDGHSendDataInternal(pCtx, + pvURIList, cbURLIist, &dataHdr); + } + else + rc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(rc)) + { + while (!lstURI.IsEmpty()) + { + DnDURIObject *pNextObj = lstURI.First(); + + rc = vbglR3DnDGHSendURIObject(pCtx, pNextObj); + if (RT_FAILURE(rc)) + break; + + lstURI.RemoveFirst(); + } + } + + 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. + * @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)); + + int rc; + if (DnDMIMEHasFileURLs(pszFormat, strlen(pszFormat))) + { + /* Send file data. */ + rc = vbglR3DnDGHSendURIData(pCtx, pvData, cbData); + } + else + rc = vbglR3DnDGHSendRawData(pCtx, pvData, cbData); + + if (RT_FAILURE(rc)) + { + int rc2 = VbglR3DnDGHSendError(pCtx, rc); + if (RT_FAILURE(rc2)) + LogFlowFunc(("Unable to send error (%Rrc) to host, rc=%Rrc\n", rc, rc2)); + } + + return rc; +} + +/** + * Guest -> Host + * Send an error back to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param rcErr Error (IPRT-style) to send. + */ +VBGLR3DECL(int) VbglR3DnDGHSendError(PVBGLR3GUESTDNDCMDCTX pCtx, int rcErr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBOXDNDGHEVTERRORMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_GH_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; +} +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + 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..b96f18af --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibEvent.cpp @@ -0,0 +1,93 @@ +/* $Id: VBoxGuestR3LibEvent.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Events. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..583a2ea9 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGR.cpp @@ -0,0 +1,80 @@ +/* $Id: VBoxGuestR3LibGR.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, GR. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..d3db9cdf --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestCtrl.cpp @@ -0,0 +1,1531 @@ +/* $Id: VBoxGuestR3LibGuestCtrl.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, guest control. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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/HostServices/GuestControlSvc.h> + +#ifndef RT_OS_WINDOWS +# include <signal.h> +#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; +} + + +/** + * 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)); +} + + +VBGLR3DECL(int) VbglR3GuestCtrlMsgReply(PVBGLR3GUESTCTRLCMDCTX pCtx, + int rc) +{ + return VbglR3GuestCtrlMsgReplyEx(pCtx, rc, 0 /* uType */, + NULL /* pvPayload */, 0 /* cbPayload */); +} + + +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; +} + + +/** + * Asks a specific guest session to close. + * + * @return IPRT status code. + * @param pCtx Host context. + * @param fFlags Some kind of flag. Figure it out yourself. + ** @todo Docs! + */ +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)); +} + + +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)); +} + + +/** + * Retrieves a HOST_SESSION_CREATE message. + */ +VBGLR3DECL(int) VbglR3GuestCtrlSessionGetOpen(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t *puProtocol, + char *pszUser, uint32_t cbUser, + char *pszPassword, uint32_t cbPassword, + char *pszDomain, uint32_t cbDomain, + uint32_t *pfFlags, uint32_t *pidSession) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->uNumParms == 6, VERR_INVALID_PARAMETER); + + AssertPtrReturn(puProtocol, VERR_INVALID_POINTER); + AssertPtrReturn(pszUser, VERR_INVALID_POINTER); + AssertPtrReturn(pszPassword, VERR_INVALID_POINTER); + AssertPtrReturn(pszDomain, VERR_INVALID_POINTER); + AssertPtrReturn(pfFlags, VERR_INVALID_POINTER); + + int 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, pszUser, cbUser); + VbglHGCMParmPtrSet(&Msg.password, pszPassword, cbPassword); + VbglHGCMParmPtrSet(&Msg.domain, pszDomain, cbDomain); + VbglHGCMParmUInt32Set(&Msg.flags, 0); + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.protocol.GetUInt32(puProtocol); + 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_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_EXEC_CMD message. + * + * @todo Move the parameters in an own struct! + */ +VBGLR3DECL(int) VbglR3GuestCtrlProcGetStart(PVBGLR3GUESTCTRLCMDCTX pCtx, + char *pszCmd, uint32_t cbCmd, + uint32_t *pfFlags, + char *pszArgs, uint32_t cbArgs, uint32_t *pcArgs, + char *pszEnv, uint32_t *pcbEnv, uint32_t *pcEnvVars, + char *pszUser, uint32_t cbUser, + char *pszPassword, uint32_t cbPassword, + uint32_t *puTimeoutMS, + uint32_t *puPriority, + uint64_t *puAffinity, uint32_t cbAffinity, uint32_t *pcAffinity) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + AssertPtrReturn(pszCmd, VERR_INVALID_POINTER); + AssertPtrReturn(pfFlags, VERR_INVALID_POINTER); + AssertPtrReturn(pszArgs, VERR_INVALID_POINTER); + AssertPtrReturn(pcArgs, VERR_INVALID_POINTER); + AssertPtrReturn(pszEnv, VERR_INVALID_POINTER); + AssertPtrReturn(pcbEnv, VERR_INVALID_POINTER); + AssertPtrReturn(pcEnvVars, VERR_INVALID_POINTER); + AssertPtrReturn(puTimeoutMS, VERR_INVALID_POINTER); + + int rc; + do + { + 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, pszCmd, cbCmd); + VbglHGCMParmUInt32Set(&Msg.flags, 0); + VbglHGCMParmUInt32Set(&Msg.num_args, 0); + VbglHGCMParmPtrSet(&Msg.args, pszArgs, cbArgs); + VbglHGCMParmUInt32Set(&Msg.num_env, 0); + VbglHGCMParmUInt32Set(&Msg.cb_env, 0); + VbglHGCMParmPtrSet(&Msg.env, pszEnv, *pcbEnv); + if (pCtx->uProtocol < 2) + { + AssertPtrReturn(pszUser, VERR_INVALID_POINTER); + AssertReturn(cbUser, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszPassword, VERR_INVALID_POINTER); + AssertReturn(pszPassword, VERR_INVALID_PARAMETER); + + VbglHGCMParmPtrSet(&Msg.u.v1.username, pszUser, cbUser); + VbglHGCMParmPtrSet(&Msg.u.v1.password, pszPassword, cbPassword); + VbglHGCMParmUInt32Set(&Msg.u.v1.timeout, 0); + } + else + { + AssertPtrReturn(puAffinity, VERR_INVALID_POINTER); + AssertReturn(cbAffinity, VERR_INVALID_PARAMETER); + + VbglHGCMParmUInt32Set(&Msg.u.v2.timeout, 0); + VbglHGCMParmUInt32Set(&Msg.u.v2.priority, 0); + VbglHGCMParmUInt32Set(&Msg.u.v2.num_affinity, 0); + VbglHGCMParmPtrSet(&Msg.u.v2.affinity, puAffinity, cbAffinity); + } + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + Msg.context.GetUInt32(&pCtx->uContextID); + Msg.flags.GetUInt32(pfFlags); + Msg.num_args.GetUInt32(pcArgs); + Msg.num_env.GetUInt32(pcEnvVars); + Msg.cb_env.GetUInt32(pcbEnv); + if (pCtx->uProtocol < 2) + Msg.u.v1.timeout.GetUInt32(puTimeoutMS); + else + { + Msg.u.v2.timeout.GetUInt32(puTimeoutMS); + Msg.u.v2.priority.GetUInt32(puPriority); + Msg.u.v2.num_affinity.GetUInt32(pcAffinity); + } + } + } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel); + return rc; +} + + +/** + * Allocates and gets host data, based on the message id. + * + * This will block until data becomes available. + * + * @returns VBox status code. + ** @todo Docs! + */ +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); + VbglHGCMParmUInt32Set(&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); + VbglHGCMParmUInt32Set(&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_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; +} + + +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)); +} + + +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)); +} + + +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)); +} + + +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)); +} + + +VBGLR3DECL(int) VbglR3GuestCtrlFileCbWrite(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t uRc, uint32_t uWritten) +{ + 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, uWritten); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.write)); +} + + +VBGLR3DECL(int) VbglR3GuestCtrlFileCbSeek(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t uRc, uint64_t uOffActual) +{ + 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, uOffActual); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.seek)); +} + + +VBGLR3DECL(int) VbglR3GuestCtrlFileCbTell(PVBGLR3GUESTCTRLCMDCTX pCtx, + uint32_t uRc, uint64_t uOffActual) +{ + 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, uOffActual); + + return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.tell)); +} + + +/** + * Callback for reporting a guest process status (along with some other stuff) to the host. + * + * @returns VBox status code. + ** @todo Docs! + */ +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. + ** @todo Docs! + */ +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. + ** @todo Docs! + */ +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..be8018e9 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestProp.cpp @@ -0,0 +1,924 @@ +/* $Id: VBoxGuestR3LibGuestProp.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, guest properties. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +#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/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); +} + + +/** + * 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; +} + +#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. + */ +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) +{ + /* + * 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); + 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\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)) + { + /* 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) + { + /* 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 = 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..c8fb7450 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestUser.cpp @@ -0,0 +1,109 @@ +/* $Id: VBoxGuestR3LibGuestUser.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, + * guest user reporting / utility functions. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..c2f907a7 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp @@ -0,0 +1,95 @@ +/* $Id: VBoxGuestR3LibHGCM.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, + * generic HGCM. + */ + +/* + * Copyright (C) 2015-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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) +{ + VBGLIOCHGCMCONNECT Info; + RT_ZERO(Info); + VBGLREQHDR_INIT(&Info.Hdr, HGCM_CONNECT); + Info.u.In.Loc.type = VMMDevHGCMLoc_LocalHost_Existing; + strcpy(Info.u.In.Loc.u.host.achName, pszServiceName); + + int 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 VbglR3InfoSvcConnect(). + */ +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..3e6ad14b --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostChannel.cpp @@ -0,0 +1,225 @@ +/* $Id: VBoxGuestR3LibHostChannel.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Host Channel. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +#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..278b3076 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostVersion.cpp @@ -0,0 +1,201 @@ +/* $Id: */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, host version check. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdio.h> /* Required for sscanf */ +#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) +{ + 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; +} + + +/** 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) +{ + Assert(idClient > 0); + AssertPtr(ppszVer); + return VbglR3GuestPropReadValueAlloc(idClient, "/VirtualBox/GuestAdd/HostVerLastChecked", ppszVer); +} + + +/** 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) +{ + Assert(idClient > 0); + AssertPtr(pszVer); + return VbglR3GuestPropWriteValue(idClient, "/VirtualBox/GuestAdd/HostVerLastChecked", pszVer); +} + 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..2444b7d6 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibInternal.h @@ -0,0 +1,119 @@ +/* $Id: VBoxGuestR3LibInternal.h $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 support library for the guest additions, Internal header. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +#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; + } + return VERR_INVALID_PARAMETER; +} + + +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; + } + return VERR_INVALID_PARAMETER; +} + + +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..1663f2fe --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibLog.cpp @@ -0,0 +1,84 @@ +/* $Id: VBoxGuestR3LibLog.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Logging. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..67def73a --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMisc.cpp @@ -0,0 +1,125 @@ +/* $Id: VBoxGuestR3LibMisc.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Misc. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..a43bbf7c --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibModule.cpp @@ -0,0 +1,170 @@ +/* $Id: VBoxGuestR3LibModule.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared modules. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..8cfd8100 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMouse.cpp @@ -0,0 +1,80 @@ +/* $Id: VBoxGuestR3LibMouse.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Mouse. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..c0396cbb --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibPidFile.cpp @@ -0,0 +1,108 @@ +/** $Id: VBoxGuestR3LibPidFile.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, + * Create a PID file. + */ + +/* + * Copyright (C) 2015-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/file.h> +#include <iprt/string.h> +#include <iprt/process.h> +#include "VBoxGuestR3LibInternal.h" + +/** + * 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); + } +} + 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..e9707800 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibRuntimeXF86.cpp @@ -0,0 +1,98 @@ +/* $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-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..78c19eb7 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSeamless.cpp @@ -0,0 +1,172 @@ +/* $Id: VBoxGuestR3LibSeamless.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Seamless mode. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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) +{ + uint32_t fEvent = 0; + int rc; + + AssertPtrReturn(pMode, VERR_INVALID_PARAMETER); + rc = VbglR3WaitEvent(VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST, RT_INDEFINITE_WAIT, &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; +} + 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..0fb9a6a9 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSharedFolders.cpp @@ -0,0 +1,422 @@ +/* $Id: VBoxGuestR3LibSharedFolders.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, shared folders. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..22e5caee --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibStat.cpp @@ -0,0 +1,69 @@ +/* $Id: VBoxGuestR3LibStat.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Statistics. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..ecabde0e --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibTime.cpp @@ -0,0 +1,45 @@ +/* $Id: VBoxGuestR3LibTime.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Time. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..9757d2dc --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVideo.cpp @@ -0,0 +1,575 @@ +/* $Id: VBoxGuestR3LibVideo.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Video. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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> + +#include <stdio.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 */ + +#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 */ + 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 */ + 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)) + { + unsigned cx = 0; + unsigned cy = 0; + unsigned cBits = 0; + unsigned x = 0; + unsigned y = 0; + unsigned fEnabled = 1; + char ch1 = 0; + char ch2 = 0; + int cMatches = sscanf(szModeParms, "%5ux%5ux%2u%c%5ux%5u,%1u%c", &cx, &cy, &cBits, &ch1, &x, &y, &fEnabled, &ch2); + if ( (cMatches == 7 && ch1 == ',') + || cMatches == 3) + { + 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); + rc = VINF_SUCCESS; + } + else if (cMatches < 0) + rc = VERR_READ_ERROR; + else + rc = VERR_PARSE_ERROR; + } + } + + return rc; +#else /* !VBOX_WITH_GUEST_PROPS */ + 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..5e7099ca --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVrdp.cpp @@ -0,0 +1,54 @@ +/* $Id: VBoxGuestR3LibVrdp.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, VRDP. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* 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..a9c93970 --- /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-2019 Oracle Corporation + * + * 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/linux/Makefile b/src/VBox/Additions/common/VBoxGuest/linux/Makefile new file mode 100644 index 00000000..23d32214 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/linux/Makefile @@ -0,0 +1,168 @@ +# $Revision: 127855 $ +## @file +# VirtualBox Guest Additions Module Makefile. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE 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. +# + +# Linux kbuild sets this to our source directory if we are called from there +obj ?= $(CURDIR) +include $(obj)/Makefile.include.header + +MOD_NAME = vboxguest + +MOD_OBJS = \ + VBoxGuest-linux.o \ + VBoxGuest.o \ + 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/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/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/RTStrICmpAscii.o \ + common/string/RTStrNICmpAscii.o \ + common/string/RTStrNCmp.o \ + common/string/RTStrNLen.o \ + common/string/stringalloc.o \ + common/string/strformat.o \ + common/string/strformatnum.o \ + common/string/strformatrt.o \ + common/string/strformattype.o \ + common/string/strprintf.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 +ifeq ($(BUILD_TARGET_ARCH),x86) +MOD_OBJS += \ + common/math/gcc/divdi3.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 +ifeq ($(BUILD_TARGET_ARCH),amd64) +MOD_OBJS += common/alloc/heapsimple.o +endif + +MOD_DEFS = -DVBOX -DRT_OS_LINUX -DIN_RING0 -DIN_RT_R0 -DIN_GUEST \ + -DIN_GUEST_R0 -DIN_MODULE -DRT_WITH_VBOX -DVBGL_VBOXGUEST \ + -DVBOX_WITH_HGCM +ifeq ($(BUILD_TARGET_ARCH),amd64) + MOD_DEFS += -DRT_ARCH_AMD64 +else + MOD_DEFS += -DRT_ARCH_X86 +endif +ifeq ($(BUILD_TARGET_ARCH),amd64) + MOD_DEFS += -DVBOX_WITH_64_BITS_GUESTS +endif +MOD_INCL = $(addprefix -I$(KBUILD_EXTMOD),/ /include /r0drv/linux) +MOD_INCL += $(addprefix -I$(KBUILD_EXTMOD)/vboxguest,/ /include /r0drv/linux) + +ifneq ($(wildcard $(KBUILD_EXTMOD)/vboxguest),) + MANGLING := $(KBUILD_EXTMOD)/vboxguest/include/VBox/VBoxGuestMangling.h +else + MANGLING := $(KBUILD_EXTMOD)/include/VBox/VBoxGuestMangling.h +endif +ifeq ($(KERN_VERSION),24) + ## @todo move to MOD_DEFS when we have finished refactoring + MOD_CFLAGS = -DEXPORT_SYMTAB +else + MOD_CFLAGS = -Wno-declaration-after-statement -include $(MANGLING) -fno-pie +endif + +MOD_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.include.footer + +check: $(MOD_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/files_vboxguest b/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest new file mode 100755 index 00000000..0724653a --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest @@ -0,0 +1,212 @@ +#!/bin/sh +# $Id: files_vboxguest $ +## @file +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# Copyright (C) 2007-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE 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. +# + +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/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/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}/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp=>VBoxGuest.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/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.include.header=>Makefile.include.header \ + ${PATH_ROOT}/src/VBox/Installer/linux/Makefile.include.footer=>Makefile.include.footer \ + ${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/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/math/gcc/divdi3.c=>common/math/gcc/divdi3.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/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/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/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_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..044013ae --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/netbsd/vboxguest.ioconf @@ -0,0 +1,56 @@ +# $Id: vboxguest.ioconf $ +## @file +# NetBSD vboxguest module configuration +# + +# +# Copyright (C) 2017 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE 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. +# + +# 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..ecc08fa9 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/solaris/deps.asm @@ -0,0 +1,38 @@ +; $Id: deps.asm $ +;; @file +; Solaris kernel module dependency +; + +; +; Copyright (C) 2012-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE 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. +; + +%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..d578c024 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/solaris/load.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# $Id: load.sh $ +## @file +# For GA development. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE 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. +# + +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..c8dca3f8 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.inf @@ -0,0 +1,92 @@ +; $Id: VBoxGuest.inf $ +;; @file +; INF file for installing the VirtualBox Windows guest driver. +; + +; +; Copyright (C) 2006-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE 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. +; + +[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% +2 = %VBoxControl.MediaDesc% +3 = %VBoxTray.MediaDesc% + +[SourceDisksFiles] +VBoxGuest.sys = 1 +VBoxControl.exe = 2 +VBoxTray.exe = 3 + +[DestinationDirs] +DefaultDestDir = 12 ; drivers +VBoxTray_CopyFiles = 11 ; system32 + +[Manufacturer] +;x86 %ORACLE%=VBoxGuest +;amd64 %ORACLE%=VBoxGuest, NTamd64 + +;x86 [VBoxGuest] +;amd64 [VBoxGuest.NTamd64] +%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, 0x00000000, %11%\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 Device" +VBoxGuest_svcdesc = "VirtualBox Guest Driver" +VBoxTray_svcdesc = "VirtualBox Guest Tray" + 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..abbe33b0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.rc @@ -0,0 +1,62 @@ +/* $Id: VBoxGuest.rc $ */ +/** @file + * VBoxGuest - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +#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/VBoxGuestInst.cpp b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestInst.cpp new file mode 100644 index 00000000..5b680edb --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestInst.cpp @@ -0,0 +1,269 @@ +/* $Id: VBoxGuestInst.cpp $ */ +/** @file + * Small tool to (un)install the VBoxGuest device driver. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <VBox/VBoxGuest.h> /* for VBOXGUEST_SERVICE_NAME */ +#include <iprt/err.h> + + +//#define TESTMODE + + + +static int installDriver(bool fStartIt) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + SC_HANDLE hSMgrCreate = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + if (!hSMgrCreate) + { + printf("OpenSCManager(,,create) failed rc=%d\n", GetLastError()); + return -1; + } + + const char *pszSlashName = "\\VBoxGuest.sys"; + char szDriver[MAX_PATH * 2]; + GetCurrentDirectory(MAX_PATH, szDriver); + strcat(szDriver, pszSlashName); + if (GetFileAttributesA(szDriver) == INVALID_FILE_ATTRIBUTES) + { + GetSystemDirectory(szDriver, sizeof(szDriver)); + strcat(strcat(szDriver, "\\drivers"), pszSlashName); + + /* Try FAT name abbreviation. */ + if (GetFileAttributesA(szDriver) == INVALID_FILE_ATTRIBUTES) + { + pszSlashName = "\\VBoxGst.sys"; + GetCurrentDirectory(MAX_PATH, szDriver); + strcat(szDriver, pszSlashName); + if (GetFileAttributesA(szDriver) == INVALID_FILE_ATTRIBUTES) + { + GetSystemDirectory(szDriver, sizeof(szDriver)); + strcat(strcat(szDriver, "\\drivers"), pszSlashName); + + } + } + } + + SC_HANDLE hService = CreateService(hSMgrCreate, + VBOXGUEST_SERVICE_NAME, + "VBoxGuest Support Driver", + SERVICE_QUERY_STATUS | (fStartIt ? SERVICE_START : 0), + SERVICE_KERNEL_DRIVER, + SERVICE_BOOT_START, + SERVICE_ERROR_NORMAL, + szDriver, + "System", + NULL, NULL, NULL, NULL); + if (hService) + { + printf("Successfully created service '%s' for driver '%s'.\n", VBOXGUEST_SERVICE_NAME, szDriver); + if (fStartIt) + { + if (StartService(hService, 0, NULL)) + printf("successfully started driver '%s'\n", szDriver); + else + printf("StartService failed: %d\n", GetLastError()); + } + CloseServiceHandle(hService); + } + else + printf("CreateService failed! lasterr=%d (szDriver=%s)\n", GetLastError(), szDriver); + CloseServiceHandle(hSMgrCreate); + return hService ? 0 : -1; +} + +static int uninstallDriver(void) +{ + int rc = -1; + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + if (!hSMgr) + { + printf("OpenSCManager(,,delete) failed rc=%d\n", GetLastError()); + return -1; + } + SC_HANDLE hService = OpenService(hSMgr, 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) + rc = VINF_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) + rc = VINF_SUCCESS; + else + { + printf("Failed to stop service. status=%d (%#x)\n", Status.dwCurrentState, Status.dwCurrentState); + rc = VERR_GENERAL_FAILURE; + } + } + else + { + DWORD dwErr = GetLastError(); + if ( Status.dwCurrentState == SERVICE_STOP_PENDING + && dwErr == ERROR_SERVICE_CANNOT_ACCEPT_CTRL) + rc = VERR_RESOURCE_BUSY; /* better than VERR_GENERAL_FAILURE */ + else + { + printf("ControlService failed with dwErr=%u. status=%d (%#x)\n", + dwErr, Status.dwCurrentState, Status.dwCurrentState); + rc = -1; + } + } + + /* + * Delete the service. + */ + if (RT_SUCCESS(rc)) + { + if (DeleteService(hService)) + rc = 0; + else + { + printf("DeleteService failed lasterr=%d\n", GetLastError()); + rc = -1; + } + } + CloseServiceHandle(hService); + } + else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + rc = 0; + else + printf("OpenService failed lasterr=%d\n", GetLastError()); + CloseServiceHandle(hSMgr); + return rc; +} + +#ifdef TESTMODE + +static HANDLE openDriver(void) +{ + HANDLE hDevice; + + hDevice = CreateFile(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) + { + printf("CreateFile did not work. GetLastError() 0x%x\n", GetLastError()); + } + return hDevice; +} + +static int closeDriver(HANDLE hDevice) +{ + CloseHandle(hDevice); + return 0; +} + +static int performTest(void) +{ + int rc = 0; + + HANDLE hDevice = openDriver(); + + if (hDevice != INVALID_HANDLE_VALUE) + closeDriver(hDevice); + else + printf("openDriver failed!\n"); + + return rc; +} + +#endif /* TESTMODE */ + +static int usage(char *programName) +{ + printf("error, syntax: %s [install|uninstall]\n", programName); + return 1; +} + +int main(int argc, char **argv) +{ + bool installMode; +#ifdef TESTMODE + bool testMode = false; +#endif + + if (argc != 2) + return usage(argv[0]); + + if (strcmp(argv[1], "install") == 0) + installMode = true; + else if (strcmp(argv[1], "uninstall") == 0) + installMode = false; +#ifdef TESTMODE + else if (strcmp(argv[1], "test") == 0) + testMode = true; +#endif + else + return usage(argv[0]); + + + int rc; +#ifdef TESTMODE + if (testMode) + rc = performTest(); + else +#endif + if (installMode) + rc = installDriver(true); + else + rc = uninstallDriver(); + + if (rc == 0) + printf("operation completed successfully!\n"); + else + printf("error: operation failed with status code %d\n", rc); + + return rc; +} + diff --git a/src/VBox/Additions/common/VBoxService/Makefile.kmk b/src/VBox/Additions/common/VBoxService/Makefile.kmk new file mode 100644 index 00000000..1a6f2fce --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/Makefile.kmk @@ -0,0 +1,206 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Cross Platform Guest Addition Services. +# + +# +# Copyright (C) 2007-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Incldue testcases. +# +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# Target lists. +# +PROGRAMS += VBoxService + +# Enable the timesync service within VBoxService. +VBOX_WITH_VBOXSERVICE_TIMESYNC := 1 + +# Busybox-like toolbox, embedded into VBoxService. +VBOX_WITH_VBOXSERVICE_TOOLBOX := 1 + +# VM-management functions, like memory ballooning and statistics. +VBOX_WITH_VBOXSERVICE_MANAGEMENT := 1 + +if1of ($(KBUILD_TARGET), linux) + # CPU hotplugging. +VBOX_WITH_VBOXSERVICE_CPUHOTPLUG := 1 +endif + +# Page Sharing (Page Fusion). +if1of ($(KBUILD_TARGET), win) +VBOX_WITH_VBOXSERVICE_PAGE_SHARING := 1 +endif + +ifdef VBOX_WITH_GUEST_PROPS +VBOX_WITH_VBOXSERVICE_VMINFO := 1 +endif + +# Guest Control. +ifdef VBOX_WITH_GUEST_CONTROL +VBOX_WITH_VBOXSERVICE_CONTROL := 1 +endif + +# +# VBoxService +# +if "$(KBUILD_TARGET)" == "win" || defined(VBOX_WITH_MASOCHISTIC_WARNINGS) ## @todo use VBoxGuestR3Exe everywhere +VBoxService_TEMPLATE = VBoxGuestR3Exe +else +VBoxService_TEMPLATE = NewVBoxGuestR3Exe +endif + +# Define features to be activate. +VBoxService_DEFS += \ + $(if $(VBOX_WITH_VBOXSERVICE_CONTROL),VBOX_WITH_VBOXSERVICE_CONTROL,) \ + $(if $(VBOX_WITH_VBOXSERVICE_CPUHOTPLUG),VBOX_WITH_VBOXSERVICE_CPUHOTPLUG,) \ + $(if $(VBOX_WITH_VBOXSERVICE_MANAGEMENT),VBOX_WITH_VBOXSERVICE_MANAGEMENT,) \ + $(if $(VBOX_WITH_VBOXSERVICE_PAGE_SHARING),VBOX_WITH_VBOXSERVICE_PAGE_SHARING,) \ + $(if $(VBOX_WITH_VBOXSERVICE_TIMESYNC),VBOX_WITH_VBOXSERVICE_TIMESYNC,) \ + $(if $(VBOX_WITH_VBOXSERVICE_TOOLBOX),VBOX_WITH_VBOXSERVICE_TOOLBOX,) \ + $(if $(VBOX_WITH_VBOXSERVICE_VMINFO),VBOX_WITH_VBOXSERVICE_VMINFO,) + +# Import global defines. +VBoxService_DEFS += \ + $(if $(VBOX_WITH_DBUS),VBOX_WITH_DBUS,) \ + $(if $(VBOX_WITH_GUEST_CONTROL),VBOX_WITH_GUEST_CONTROL,) \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \ + $(if $(VBOX_WITH_HGCM),VBOX_WITH_HGCM,) + +VBoxService_DEFS += \ + VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\" +VBoxService_DEFS.win += _WIN32_WINNT=0x0501 +VBoxService_DEFS.os2 = VBOX_WITH_HGCM VBOX_WITH_VBOXSERVICE_CLIPBOARD + +VBoxService_SOURCES = \ + VBoxService.cpp \ + VBoxServiceUtils.cpp \ + VBoxServiceStats.cpp + +ifdef VBOX_WITH_VBOXSERVICE_TIMESYNC +VBoxService_SOURCES += \ + VBoxServiceTimeSync.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 +VBoxService_SOURCES += \ + VBoxServiceBalloon.cpp + ifdef VBOX_WITH_MEMBALLOON +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.rc \ + VBoxService-win.cpp + +VBoxService_SOURCES.os2 = \ + VBoxService-os2.def \ + VBoxServiceClipboard-os2.cpp + +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.) +if1of ($(KBUILD_TARGET), linux) + VBoxService_LIBS += \ + crypt +endif +ifdef VBOX_WITH_DBUS + if1of ($(KBUILD_TARGET), linux solaris) # FreeBSD? +VBoxService_LIBS += \ + dl + endif +endif +ifdef VBOX_WITH_GUEST_PROPS +VBoxService_LIBS.win += \ + Secur32.lib \ + WtsApi32.lib \ + Psapi.lib +VBoxService_LIBS.solaris += \ + nsl \ + kstat \ + contract +endif + +VBoxServiceVMInfo.cpp_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) +VBoxServiceVMInfo.cpp_DEPS = $(VBOX_SVN_REV_KMK) + +VBoxService_USES.win += vboximportchecker +VBoxService_VBOX_IMPORT_CHECKER.win.x86 = nt31 +VBoxService_VBOX_IMPORT_CHECKER.win.amd64 = xp64 + + +# +# The icon is configurable. +# +VBoxService-win.rc_INCS = $(VBoxService_0_OUTDIR) +VBoxService-win.rc_DEPS = $(VBoxService_0_OUTDIR)/VBoxService-win-icon.rc +VBoxService-win.rc_CLEAN = $(VBoxService_0_OUTDIR)/VBoxService-win-icon.rc + +# Icon include file. +$$(VBoxService_0_OUTDIR)/VBoxService-win-icon.rc: $(VBOX_WINDOWS_ADDITIONS_ICON_FILE) $$(VBoxService_DEFPATH)/Makefile.kmk | $$(dir $$@) + $(RM) -f $@ + $(APPEND) $@ 'IDI_VIRTUALBOX ICON DISCARDABLE "$(subst /,\\,$(VBOX_WINDOWS_ADDITIONS_ICON_FILE))"' + +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..ae2d6d9e --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxService-os2.def @@ -0,0 +1,23 @@ +; $Id: VBoxService-os2.def $ +;; @file +; VBoxService - OS/2 definition file. +; + +; +; Copyright (C) 2007-2019 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; + + +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..1262cdf7 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp @@ -0,0 +1,660 @@ +/* $Id: VBoxService-win.cpp $ */ +/** @file + * VBoxService - Guest Additions Service Skeleton, Windows Specific Parts. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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 <process.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) +{ + 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) +{ + 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-win.rc b/src/VBox/Additions/common/VBoxService/VBoxService-win.rc new file mode 100644 index 00000000..0b5185ab --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxService-win.rc @@ -0,0 +1,55 @@ +/* $Id: VBoxService-win.rc $ */ +/** @file + * VBoxService - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <windows.h> +#include <VBox/version.h> +#include "VBoxServiceResource-win.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 Service\0" + VALUE "InternalName", "VBoxService\0" + VALUE "OriginalFilename", "VBoxService.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 + +#include "VBoxService-win-icon.rc" + diff --git a/src/VBox/Additions/common/VBoxService/VBoxService.cpp b/src/VBox/Additions/common/VBoxService/VBoxService.cpp new file mode 100644 index 00000000..d152b322 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxService.cpp @@ -0,0 +1,1270 @@ +/* $Id: VBoxService.cpp $ */ +/** @file + * VBoxService - Guest Additions Service Skeleton. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/** @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 +#include <errno.h> +#ifndef RT_OS_WINDOWS +# 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/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" +#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, fFlags, "all", +#ifdef DEBUG + "VBOXSERVICE_LOG", +#else + "VBOXSERVICE_RELEASE_LOG", +#endif + RT_ELEMENTS(s_apszGroups), s_apszGroups, + RTLOGDEST_STDOUT | RTLOGDEST_USER, + vgsvcLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime, + 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:\n" + " %-12s [-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) +{ + 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 */ +} + + +int main(int argc, char **argv) +{ + RTEXITCODE rcExit; + + /* + * Init globals and such. + */ + int rc = RTR3InitExe(argc, &argv, 0); + 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], "guestsession")) + fUserSession = true; +#endif + + /* + * 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. + */ + if (fUserSession) + rc = VbglR3InitUser(); + else + rc = VbglR3Init(); + if (RT_FAILURE(rc)) + { + if (rc == 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", rc); + } + +#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")) + return VGSvcPageSharingWorkerChild(); +#endif + +#ifdef VBOX_WITH_VBOXSERVICE_CONTROL + /* + * Check if we're the specially spawned VBoxService.exe process that + * handles a guest control session. + */ + if (fUserSession) + return VGSvcGstCtrlSessionSpawnInit(argc, argv); +#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); + } + + /* 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 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 ( GetVersionEx((LPOSVERSIONINFO)&OSInfoEx) + && OSInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT + && OSInfoEx.dwMajorVersion >= 5 /* NT 5.0 a.k.a W2K */) + hMutexAppRunning = CreateMutex(NULL, FALSE, "Global\\" VBOXSERVICE_NAME); + else + hMutexAppRunning = CreateMutex(NULL, FALSE, 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 */ + /** @todo Add PID file creation here? */ +#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 +# ifndef RT_OS_NT4 /** @todo r=bird: What's RT_OS_NT4??? */ + /* Install console control handler. */ + if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)vgsvcWinConsoleControlHandler, TRUE /* Add handler */)) + { + VGSvcError("Unable to add console control handler, error=%ld\n", GetLastError()); + /* Just skip this error, not critical. */ + } +# endif /* !RT_OS_NT4 */ +#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 +# ifndef RT_OS_NT4 + /* 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. */ + } +# endif /* !RT_OS_NT4 */ +#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..8439f063 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp @@ -0,0 +1,2182 @@ +/* $Id: VBoxServiceAutoMount.cpp $ */ +/** @file + * VBoxService - Auto-mounting for Shared Folders, only Linux & Solaris atm. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/** @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> +RT_C_DECLS_BEGIN /* Only needed for old code.*/ +# include "../../linux/sharedfolders/vbsfmount.h" +RT_C_DECLS_END +# elif defined(RT_OS_LINUX) +# include <mntent.h> +# include <paths.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 pOpts For getting the group ID. + */ +static int vbsvcAutoMountPrepareMountPointOld(const char *pszMountPoint, const char *pszShareName, vbsf_mount_opts *pOpts) +{ + AssertPtrReturn(pOpts, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszShareName, VERR_INVALID_PARAMETER); + + 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 */, pOpts->gid, 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; + } + + struct vbsf_mount_opts Opts = + { + 0, /* uid */ + (int)grp_vboxsf->gr_gid, /* gid */ + 0, /* ttl */ + 0770, /* dmode, owner and group "vboxsf" have full access */ + 0770, /* fmode, owner and group "vboxsf" have full access */ + 0, /* dmask */ + 0, /* fmask */ + 0, /* ronly */ + 0, /* sloppy */ + 0, /* noexec */ + 0, /* nodev */ + 0, /* nosuid */ + 0, /* remount */ + "\0", /* nls_name */ + NULL, /* convertcp */ + }; + + int rc = vbsvcAutoMountPrepareMountPointOld(pszMountPoint, pszShareName, &Opts); + if (RT_SUCCESS(rc)) + { +# ifdef RT_OS_SOLARIS + int fFlags = 0; + if (Opts.ronly) + fFlags |= MS_RDONLY; + char szOptBuf[MAX_MNTOPT_STR] = { '\0', }; + RTStrPrintf(szOptBuf, sizeof(szOptBuf), "uid=%d,gid=%d,dmode=%0o,fmode=%0o,dmask=%0o,fmask=%0o", + Opts.uid, Opts.gid, Opts.dmode, Opts.fmode, Opts.dmask, Opts.fmask); + int r = mount(pszShareName, + pszMountPoint, + fFlags | MS_OPTIONSTR, + "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 */ + unsigned long fFlags = MS_NODEV; + + /*const char *szOptions = { "rw" }; - ??? */ + struct vbsf_mount_info_new mntinf; + RT_ZERO(mntinf); + + mntinf.nullchar = '\0'; + mntinf.signature[0] = VBSF_MOUNT_SIGNATURE_BYTE_0; + mntinf.signature[1] = VBSF_MOUNT_SIGNATURE_BYTE_1; + mntinf.signature[2] = VBSF_MOUNT_SIGNATURE_BYTE_2; + mntinf.length = sizeof(mntinf); + + mntinf.uid = Opts.uid; + mntinf.gid = Opts.gid; + mntinf.ttl = Opts.ttl; + mntinf.dmode = Opts.dmode; + mntinf.fmode = Opts.fmode; + mntinf.dmask = Opts.dmask; + mntinf.fmask = Opts.fmask; + mntinf.tag[0] = '\0'; + + strcpy(mntinf.name, pszShareName); + strcpy(mntinf.nls_name, "\0"); + + int r = mount(pszShareName, + pszMountPoint, + "vboxsf", + fFlags, + &mntinf); + if (r == 0) + { + VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint); + + r = vbsfmount_complete(pszShareName, pszMountPoint, fFlags, &Opts); + switch (r) + { + case 0: /* Success. */ + errno = 0; /* Clear all errors/warnings. */ + break; + + case 1: + VGSvcError("vbsvcAutoMountWorker: Could not update mount table (failed to create memstream): %s\n", + strerror(errno)); + 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. */ + { + if (errno == EPROTO) + { + VGSvcVerbose(3, "vbsvcAutoMountWorker: Messed up share name, re-trying ...\n"); + + /** @todo r=bird: What on earth is going on here????? Why can't you + * strcpy(mntinf.name, pszShareName) to fix it again? */ + + /* Sometimes the mount utility messes up the share name. Try to + * un-mangle it again. */ + char szCWD[RTPATH_MAX]; + size_t cchCWD; + if (!getcwd(szCWD, sizeof(szCWD))) + { + VGSvcError("vbsvcAutoMountWorker: Failed to get the current working directory\n"); + szCWD[0] = '\0'; + } + cchCWD = strlen(szCWD); + if (!strncmp(pszMountPoint, szCWD, cchCWD)) + { + while (pszMountPoint[cchCWD] == '/') + ++cchCWD; + /* We checked before that we have enough space */ + strcpy(mntinf.name, pszMountPoint + cchCWD); + } + r = mount(mntinf.name, pszMountPoint, "vboxsf", fFlags, &mntinf); + } + if (r == -1) /* Was there some error from one of the tries above? */ + { + 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' already is 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] = { 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); + rc = VWRN_NOT_FOUND; + } + 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 a session + * local link (\\??). + */ + Assert(RT_C_IS_UPPER(pEntry->pszActualMountPoint[0]) && pEntry->pszActualMountPoint[1] == ':' && pEntry->pszActualMountPoint[2] == '\0'); + RTUTF16 wszDrive[4] = { 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; + } + + 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': %u\n", + pEntry->pszName, pEntry->pszActualMountPoint, rc); + 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 vbsf_mount_info_new MntInfo; + RT_ZERO(MntInfo); + struct vbsf_mount_opts MntOpts; + RT_ZERO(MntOpts); + MntInfo.nullchar = '\0'; + MntInfo.signature[0] = VBSF_MOUNT_SIGNATURE_BYTE_0; + MntInfo.signature[1] = VBSF_MOUNT_SIGNATURE_BYTE_1; + MntInfo.signature[2] = VBSF_MOUNT_SIGNATURE_BYTE_2; + MntInfo.length = sizeof(MntInfo); + MntInfo.uid = MntOpts.uid = 0; + MntInfo.gid = MntOpts.gid = gidMount; + MntInfo.dmode = MntOpts.dmode = 0770; + MntInfo.fmode = MntOpts.fmode = 0770; + MntInfo.dmask = MntOpts.dmask = 0000; + MntInfo.fmask = MntOpts.fmask = 0000; + memcpy(MntInfo.tag, g_szTag, sizeof(g_szTag)); AssertCompile(sizeof(MntInfo.tag) >= sizeof(g_szTag)); + rc = RTStrCopy(MntInfo.name, sizeof(MntInfo.name), pEntry->pszName); + if (RT_FAILURE(rc)) + { + VGSvcError("vbsvcAutomounterMountIt: Share name '%s' is too long for the MntInfo.name field!\n", pEntry->pszName); + return rc; + } + + errno = 0; + unsigned long fFlags = MS_NODEV; + rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, &MntInfo); + 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, &MntOpts); + 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 ? "open_memstream" : rc == 2 ? "setmntent" : rc == 3 ? "addmntent" : "unknown", rc, errno); + return VINF_SUCCESS; + } + else 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] = { 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 = { 0 }; + 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..b4522fbd --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp @@ -0,0 +1,447 @@ +/* $Id: VBoxServiceBalloon.cpp $ */ +/** @file + * VBoxService - Memory Ballooning. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/** @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..cd18432f --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp @@ -0,0 +1,1129 @@ +/** $Id: VBoxServiceClipboard-os2.cpp $ */ +/** @file + * VBoxService - Guest Additions Clipboard Service, OS/2. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/** @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/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_SHARED_CLIPBOARD_FMT_UNICODETEXT/* | VBOX_SHARED_CLIPBOARD_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_SHARED_CLIPBOARD_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_SHARED_CLIPBOARD_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_SHARED_CLIPBOARD_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_SHARED_CLIPBOARD_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_SHARED_CLIPBOARD_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_SHARED_CLIPBOARD_HOST_MSG_FORMATS: + vgsvcClipboardOs2AdvertiseHostFormats(LONGFROMMP(mp1)); + break; + + /* + * Listener message - the host wish to read our clipboard data. + */ + case WM_USER + VBOX_SHARED_CLIPBOARD_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 = VbglR3ClipboardGetHostMsg(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_SHARED_CLIPBOARD_HOST_MSG_FORMATS: + if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, + 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_SHARED_CLIPBOARD_HOST_MSG_READ_DATA: + if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHARED_CLIPBOARD_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_SHARED_CLIPBOARD_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..dd2b23b8 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp @@ -0,0 +1,573 @@ +/* $Id: VBoxServiceControl.cpp $ */ +/** @file + * VBoxServiceControl - Host-driven Guest Control. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/** @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; +#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 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)) + { + 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" : ""); + return VINF_SUCCESS; + } + VGSvcError("Failed to become guest control master: %Rrc\n", rc); + VbglR3GuestCtrlDisconnect(g_idControlSvcClient); + } + 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; +} + + +/** + * @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).\n"); + int rc2 = VGSvcGstCtrlSessionClose(&g_Session); + 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. + */ + VBOXSERVICECTRLSESSIONSTARTUPINFO ssInfo = { 0 }; + int rc = VbglR3GuestCtrlSessionGetOpen(pHostCtx, + &ssInfo.uProtocol, + ssInfo.szUser, sizeof(ssInfo.szUser), + ssInfo.szPassword, sizeof(ssInfo.szPassword), + ssInfo.szDomain, sizeof(ssInfo.szDomain), + &ssInfo.fFlags, &ssInfo.uSessionID); + if (RT_SUCCESS(rc)) + { + /* + * Flat out refuse to work with protocol v1 hosts. + */ + if (ssInfo.uProtocol == 2) + { + pHostCtx->uProtocol = ssInfo.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, &ssInfo, 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", ssInfo.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); + } + 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->StartupInfo.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..e02d0871 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h @@ -0,0 +1,334 @@ +/* $Id: VBoxServiceControl.h $ */ +/** @file + * VBoxServiceControl.h - Internal guest control definitions. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 szName[RTPATH_MAX]; + /** The file handle on the guest. */ + RTFILE hFile; + /** File handle to identify this file. */ + uint32_t uHandle; + /** Context ID. */ + uint32_t uContextID; +} VBOXSERVICECTRLFILE; +/** Pointer to thread data. */ +typedef VBOXSERVICECTRLFILE *PVBOXSERVICECTRLFILE; + +typedef struct VBOXSERVICECTRLSESSIONSTARTUPINFO +{ + /** The session's protocol version to use. */ + uint32_t uProtocol; + /** The session's ID. */ + uint32_t uSessionID; + /** User name (account) to start the guest session under. */ + char szUser[GUESTPROCESS_MAX_USER_LEN]; + /** Password of specified user name (account). */ + char szPassword[GUESTPROCESS_MAX_PASSWORD_LEN]; + /** Domain of the user account. */ + char szDomain[GUESTPROCESS_MAX_DOMAIN_LEN]; + /** Session creation flags. + * @sa VBOXSERVICECTRLSESSIONSTARTUPFLAG_* flags. */ + uint32_t fFlags; +} VBOXSERVICECTRLSESSIONSTARTUPINFO; +/** Pointer to thread data. */ +typedef VBOXSERVICECTRLSESSIONSTARTUPINFO *PVBOXSERVICECTRLSESSIONSTARTUPINFO; + +/** + * 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. */ + VBOXSERVICECTRLSESSIONSTARTUPINFO StartupInfo; + /** 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; + +/** 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. */ + VBOXSERVICECTRLSESSIONSTARTUPINFO + StartupInfo; + /** List of active guest process threads + * (VBOXSERVICECTRLPROCESS). */ + RTLISTANCHOR lstProcesses; + /** List of guest control files (VBOXSERVICECTRLFILE). */ + RTLISTANCHOR lstFiles; + /** 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 holding information for starting a guest + * process. + */ +typedef struct VBOXSERVICECTRLPROCSTARTUPINFO +{ + /** Full qualified path of process to start (without arguments). */ + char szCmd[GUESTPROCESS_MAX_CMD_LEN]; + /** Process execution flags. @sa */ + uint32_t uFlags; + /** Command line arguments. */ + char szArgs[GUESTPROCESS_MAX_ARGS_LEN]; + /** Number of arguments specified in pszArgs. */ + uint32_t uNumArgs; + /** String of environment variables ("FOO=BAR") to pass to the process + * to start. */ + char szEnv[GUESTPROCESS_MAX_ENV_LEN]; + /** Size (in bytes) of environment variables block. */ + uint32_t cbEnv; + /** Number of environment variables specified in pszEnv. */ + uint32_t uNumEnvVars; + /** User name (account) to start the process under. */ + char szUser[GUESTPROCESS_MAX_USER_LEN]; + /** Password of specified user name (account). */ + char szPassword[GUESTPROCESS_MAX_PASSWORD_LEN]; + /** Domain to be used for authenticating the specified user name (account). */ + char szDomain[GUESTPROCESS_MAX_DOMAIN_LEN]; + /** Time limit (in ms) of the process' life time. */ + uint32_t uTimeLimitMS; + /** Process priority. */ + uint32_t uPriority; + /** Process affinity. At the moment we support + * up to 4 * 64 = 256 CPUs. */ + uint64_t uAffinity[4]; + /** Number of used process affinity blocks. */ + uint32_t uNumAffinity; +} VBOXSERVICECTRLPROCSTARTUPINFO; +/** Pointer to a guest process block. */ +typedef VBOXSERVICECTRLPROCSTARTUPINFO *PVBOXSERVICECTRLPROCSTARTUPINFO; + +/** + * 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. */ + VBOXSERVICECTRLPROCSTARTUPINFO + StartupInfo; + /** 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' stdout.*/ + 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 bool g_fControlSupportsOptimizations; + + +/** @name Guest session thread handling. + * @{ */ +extern int VGSvcGstCtrlSessionThreadCreate(PRTLISTANCHOR pList, const PVBOXSERVICECTRLSESSIONSTARTUPINFO 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 *pbAllowed); +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 PVBOXSERVICECTRLPROCSTARTUPINFO 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..1705a174 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp @@ -0,0 +1,2129 @@ +/* $Id: VBoxServiceControlProcess.cpp $ */ +/** @file + * VBoxServiceControlThread - Guest process handling. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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/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 PVBOXSERVICECTRLPROCSTARTUPINFO 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); + + /* Copy over startup info. */ + memcpy(&pProcess->StartupInfo, pStartupInfo, sizeof(VBOXSERVICECTRLPROCSTARTUPINFO)); + + /* Adjust timeout value. */ + if ( pProcess->StartupInfo.uTimeLimitMS == UINT32_MAX + || pProcess->StartupInfo.uTimeLimitMS == 0) + pProcess->StartupInfo.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. + */ +int VGSvcGstCtrlProcessFree(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + VGSvcVerbose(3, "[PID %RU32]: Freeing (cRefs=%RU32)...\n", pProcess->uPID, pProcess->cRefs); + Assert(pProcess->cRefs == 0); + + /* + * Destroy other thread data. + */ + if (RTCritSectIsInitialized(&pProcess->CritSect)) + RTCritSectDelete(&pProcess->CritSect); + + int rc = RTReqQueueDestroy(pProcess->hReqQueue); + AssertRC(rc); + + /* + * Remove from list. + */ + AssertPtr(pProcess->pSession); + rc = VGSvcGstCtrlSessionProcessRemove(pProcess->pSession, pProcess); + AssertRC(rc); + + /* + * Destroy thread structure as final step. + */ + RTMemFree(pProcess); + pProcess = NULL; + + return VINF_SUCCESS; +} + + +/** + * 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 unlock. + */ +void VGSvcGstCtrlProcessRelease(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturnVoid(pProcess); + + bool fShutdown = false; + + int rc = RTCritSectEnter(&pProcess->CritSect); + if (RT_SUCCESS(rc)) + { + Assert(pProcess->cRefs); + pProcess->cRefs--; + fShutdown = pProcess->fStopped; /* Has the process' thread been stopped? */ + + rc = RTCritSectLeave(&pProcess->CritSect); + AssertRC(rc); + } + + if (fShutdown) + VGSvcGstCtrlProcessFree(pProcess); +} + + +/** + * 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)) + { + 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); + + /* Guest process already has been stopped, no need to wait. */ + if (!pProcess->fStopped) + { + /* 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); + 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; + } + else + VGSvcError("[PID %RU32]: Waiting for shutting down thread returned error rc=%Rrc\n", pProcess->uPID, rc); + } + else + { + VGSvcVerbose(3, "[PID %RU32]: Thread already shut down, no waiting needed\n", pProcess->uPID); + + int rc2 = vgsvcGstCtrlProcessUnlock(pProcess); + AssertRC(rc2); + } + } + + 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->StartupInfo.szCmd, pProcess->uContextID, + pProcess->StartupInfo.szUser, pProcess->StartupInfo.uTimeLimitMS); + VBGLR3GUESTCTRLCMDCTX ctxStart = { g_idControlSvcClient, pProcess->uContextID }; + 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->hPipeStdOutR, 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->StartupInfo.uTimeLimitMS != RT_INDEFINITE_WAIT + && pProcess->StartupInfo.uTimeLimitMS != 0) + { + uint64_t u64Now = RTTimeMilliTS(); + uint64_t cMsElapsed = u64Now - uMsStart; + if (cMsElapsed >= pProcess->StartupInfo.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->StartupInfo.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->StartupInfo.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->StartupInfo.uFlags; /* 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 }; + 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). + * + * @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) +{ + int rc = VINF_SUCCESS; +/** @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. */ +#ifdef RT_OS_WINDOWS + if (!ExpandEnvironmentStrings(pszPath, pszExpanded, (DWORD)cbExpanded)) + rc = RTErrConvertFromWin32(GetLastError()); +#else + /* No expansion for non-Windows yet. */ + 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); + + int rc = VINF_SUCCESS; + + char szPathToResolve[RTPATH_MAX]; + if ( (g_pszProgName && (RTStrICmp(pszFileName, g_pszProgName) == 0)) + || !RTStrICmp(pszFileName, VBOXSERVICE_NAME)) + { + /* Resolve executable name of this process. */ + if (!RTProcGetExecutablePath(szPathToResolve, sizeof(szPathToResolve))) + rc = VERR_FILE_NOT_FOUND; + } + else + { + /* Take the raw argument to resolve. */ + rc = RTStrCopy(szPathToResolve, sizeof(szPathToResolve), pszFileName); + } + + if (RT_SUCCESS(rc)) + { + rc = vgsvcGstCtrlProcessMakeFullPath(szPathToResolve, pszResolved, cbResolved); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Looked up executable: %s -> %s\n", pszFileName, pszResolved); + } + + if (RT_FAILURE(rc)) + VGSvcError("Failed to lookup executable '%s' with rc=%Rrc\n", pszFileName, rc); + 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. Optional. + * @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 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, + char ***ppapszArgv) +{ + AssertPtrReturn(ppapszArgv, VERR_INVALID_POINTER); + + VGSvcVerbose(3, "VGSvcGstCtrlProcessPrepareArgv: pszArgv0=%p, papszArgs=%p, fFlags=%#x, ppapszArgv=%p\n", + pszArgv0, papszArgs, fFlags, ppapszArgv); + + 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 = (cArgs + 2) * sizeof(char *); + char **papszNewArgv = (char **)RTMemAlloc(cbSize); + if (!papszNewArgv) + return VERR_NO_MEMORY; + +#ifdef DEBUG + VGSvcVerbose(3, "VGSvcGstCtrlProcessAllocateArgv: cbSize=%RU32, cArgs=%RU32\n", cbSize, cArgs); +#endif + + /* HACK ALERT! Since we still don't allow the user to really specify the first + argument separately from the executable image, we have to fudge + a little in the unquoted argument case to deal with executables + containing spaces. */ + /** @todo Fix the stupid host/guest protocol so the user can do this for us! */ + if ( !(fFlags & EXECUTEPROCESSFLAG_UNQUOTED_ARGS) + || !strpbrk(pszArgv0, " \t\n\r") + || pszArgv0[0] == '"') + rc = RTStrDupEx(&papszNewArgv[0], pszArgv0); + else + { + size_t cchArgv0 = strlen(pszArgv0); + 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'; + } + } + if (RT_SUCCESS(rc)) + { + size_t i; + for (i = 0; i < cArgs; i++) + { + char *pszArg; +#if 0 /* Arguments expansion -- untested. */ + if (fFlags & EXECUTEPROCESSFLAG_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(&pszArg, papszArgs[i]); + + if (RT_FAILURE(rc)) + break; + + papszNewArgv[i + 1] = pszArg; + } + + if (RT_SUCCESS(rc)) + { + /* Terminate array. */ + papszNewArgv[cArgs + 1] = NULL; + + *ppapszArgv = papszNewArgv; + return VINF_SUCCESS; + } + + /* Failed, bail out. */ + for (; i > 0; i--) + RTStrFree(papszNewArgv[i]); + } + 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. + */ + OSVERSIONINFOEX OSInfoEx; + RT_ZERO(OSInfoEx); + OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + BOOL fRet = GetVersionEx((LPOSVERSIONINFO) &OSInfoEx); + if ( fRet + && OSInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT + && OSInfoEx.dwMajorVersion >= 6 /* Vista or 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); + } + else if (!fRet) + VGSvcError("Failed to retrieve OS information, last error=%ld\n", GetLastError()); + + VGSvcVerbose(3, "Sysprep executable is: %s\n", szSysprepCmd); + + if (RT_SUCCESS(rc)) + { + char **papszArgsExp; + rc = vgsvcGstCtrlProcessAllocateArgv(szSysprepCmd /* argv0 */, papszArgs, fFlags, &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 */, phProcess); + vgsvcGstCtrlProcessFreeArgv(papszArgsExp); + } + } + + if (RT_FAILURE(rc)) + VGSvcVerbose(3, "Starting sysprep returned rc=%Rrc\n", rc); + + return rc; + } +#endif /* RT_OS_WINDOWS */ + +#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX + if (RTStrStr(pszExec, "vbox_") == pszExec) + { + /* We want to use the internal toolbox (all internal + * tools are starting with "vbox_" (e.g. "vbox_cat"). */ + 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)) + { + char **papszArgsExp; + /** @todo r-bird: pszExec != argv[0]! When are you going to get that?!? How many + * times does this need to be pointed out? HOST/GUEST INTERFACE IS MISDESIGNED! */ + rc = vgsvcGstCtrlProcessAllocateArgv(pszExec /* Always use the unmodified executable name as argv0. */, + papszArgs /* Append the rest of the argument vector (if any). */, + fFlags, &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 uProcFlags = 0; + if (fFlags) + { + if (fFlags & EXECUTEPROCESSFLAG_HIDDEN) + uProcFlags |= RTPROC_FLAGS_HIDDEN; + if (fFlags & EXECUTEPROCESSFLAG_PROFILE) + uProcFlags |= RTPROC_FLAGS_PROFILE; + if (fFlags & EXECUTEPROCESSFLAG_UNQUOTED_ARGS) + uProcFlags |= 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) + uProcFlags |= RTPROC_FLAGS_SERVICE; +#ifdef DEBUG + VGSvcVerbose(3, "Command: %s\n", szExecExp); + for (size_t i = 0; papszArgsExp[i]; i++) + VGSvcVerbose(3, "\targv[%ld]: %s\n", i, papszArgsExp[i]); +#endif + VGSvcVerbose(3, "Starting process '%s' ...\n", szExecExp); + + const char *pszUser = pszAsUser; +#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 + && strlen(pszDomain)) + { + int cbUserUPN = RTStrAPrintf(&pszUserUPN, "%s@%s", pszAsUser, pszDomain); + if (cbUserUPN > 0) + { + pszUser = pszUserUPN; + VGSvcVerbose(3, "Using UPN: %s\n", pszUserUPN); + } + } +#endif + + /* Do normal execution. */ + rc = RTProcCreateEx(szExecExp, papszArgsExp, hEnv, uProcFlags, + phStdIn, phStdOut, phStdErr, + pszUser, + pszPassword && *pszPassword ? pszPassword : NULL, + phProcess); +#ifdef RT_OS_WINDOWS + if (pszUserUPN) + RTStrFree(pszUserUPN); +#endif + VGSvcVerbose(3, "Starting process '%s' returned rc=%Rrc\n", szExecExp, rc); + + vgsvcGstCtrlProcessFreeArgv(papszArgsExp); + } + } + return rc; +} + + +#ifdef DEBUG +static int vgsvcGstCtrlProcessDumpToFile(const char *pszFileName, void *pvBuf, size_t cbBuf) +{ + AssertPtrReturn(pszFileName, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + + if (!cbBuf) + return VINF_SUCCESS; + + char szFile[RTPATH_MAX]; + + int rc = RTPathTemp(szFile, sizeof(szFile)); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szFile, sizeof(szFile), pszFileName); + + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(4, "Dumping %ld bytes to '%s'\n", cbBuf, szFile); + + RTFILE fh; + rc = RTFileOpen(&fh, szFile, 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->StartupInfo.szCmd); + + VGSvcVerbose(3, "Guest process '%s', flags=0x%x\n", pProcess->StartupInfo.szCmd, pProcess->StartupInfo.uFlags); + + int rc = VGSvcGstCtrlSessionProcessAdd(pProcess->pSession, pProcess); + if (RT_FAILURE(rc)) + { + VGSvcError("Errorwhile adding guest process '%s' (%p) to session process list, rc=%Rrc\n", + pProcess->StartupInfo.szCmd, pProcess, rc); + RTThreadUserSignal(RTThreadSelf()); + return rc; + } + + bool fSignalled = false; /* Indicator whether we signalled the thread user event already. */ + + /* + * Prepare argument list. + */ + char **papszArgs; + int cArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */ + rc = RTGetOptArgvFromString(&papszArgs, &cArgs, + pProcess->StartupInfo.uNumArgs > 0 ? pProcess->StartupInfo.szArgs : "", + RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL); + /* Did we get the same result? */ + Assert((int)pProcess->StartupInfo.uNumArgs == cArgs + 1 /* Take argv[0] into account */); + + /* + * Prepare environment variables list. + */ +/** @todo r=bird: you don't need to prepare this, do you? Why don't you replace + * the brilliant RTStrAPrintf call with RTEnvPutEx and drop the papszEnv related code? */ + char **papszEnv = NULL; + uint32_t uNumEnvVars = 0; /* Initialize in case of failing ... */ + if (RT_SUCCESS(rc)) + { + /* Prepare environment list. */ + if (pProcess->StartupInfo.uNumEnvVars) + { + papszEnv = (char **)RTMemAlloc(pProcess->StartupInfo.uNumEnvVars * sizeof(char*)); + AssertPtr(papszEnv); + uNumEnvVars = pProcess->StartupInfo.uNumEnvVars; + + const char *pszCur = pProcess->StartupInfo.szEnv; + uint32_t i = 0; + uint32_t cbLen = 0; + while (cbLen < pProcess->StartupInfo.cbEnv) + { + /* sanity check */ + if (i >= pProcess->StartupInfo.uNumEnvVars) + { + rc = VERR_INVALID_PARAMETER; + break; + } + int cbStr = RTStrAPrintf(&papszEnv[i++], "%s", pszCur); + if (cbStr < 0) + { + rc = VERR_NO_STR_MEMORY; + break; + } + pszCur += cbStr + 1; /* Skip terminating '\0' */ + cbLen += cbStr + 1; /* Skip terminating '\0' */ + } + Assert(i == pProcess->StartupInfo.uNumEnvVars); + } + } + + /* + * Create the environment. + */ + if (RT_SUCCESS(rc)) + { + RTENV hEnv; + rc = RTEnvClone(&hEnv, RTENV_DEFAULT); + if (RT_SUCCESS(rc)) + { + size_t i; + for (i = 0; i < uNumEnvVars && papszEnv; i++) + { + rc = RTEnvPutEx(hEnv, papszEnv[i]); + if (RT_FAILURE(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->StartupInfo.uFlags & EXECUTEPROCESSFLAG_WAIT_STDOUT) + ? "|" : "/dev/null", + 1 /*STDOUT_FILENO*/, + &hStdOut, &phStdOut, &pProcess->hPipeStdOutR); + if (RT_SUCCESS(rc)) + { + RTHANDLE hStdErr; + PRTHANDLE phStdErr; + rc = vgsvcGstCtrlProcessSetupPipe( (pProcess->StartupInfo.uFlags & EXECUTEPROCESSFLAG_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->StartupInfo.szCmd, papszArgs, hEnv, + pProcess->StartupInfo.uFlags, + phStdIn, phStdOut, phStdErr, + fNeedsImpersonation ? pProcess->StartupInfo.szUser : NULL, + fNeedsImpersonation ? pProcess->StartupInfo.szPassword : NULL, + fNeedsImpersonation ? pProcess->StartupInfo.szDomain : 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. + */ + /** @todo r=bird: Can't see how hNotificationPipeR could be closed here! Found (and fixed) + * confused comments documenting hNotificationPipeW, probably related. */ + if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet, + VBOXSERVICECTRLPIPEID_IPC_NOTIFY, NULL))) + { + pProcess->hNotificationPipeR = NIL_RTPIPE; + 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); + + RTPipeClose(pProcess->hNotificationPipeR); + pProcess->hNotificationPipeR = NIL_RTPIPE; + RTPipeClose(pProcess->hNotificationPipeW); + pProcess->hNotificationPipeW = NIL_RTPIPE; + } + RTPipeClose(pProcess->hPipeStdErrR); + pProcess->hPipeStdErrR = NIL_RTPIPE; + RTHandleClose(phStdErr); + 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(phStdIn); + } + } + RTEnvDestroy(hEnv); + } + } + + if (RT_FAILURE(rc)) + { + VBGLR3GUESTCTRLCMDCTX ctx = { g_idControlSvcClient, pProcess->uContextID }; + 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); + } + + /* Free argument + environment variable lists. */ + if (uNumEnvVars) + { + for (uint32_t i = 0; i < uNumEnvVars; i++) + RTStrFree(papszEnv[i]); + RTMemFree(papszEnv); + } + 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()); + + VGSvcVerbose(3, "[PID %RU32]: Thread of process '%s' ended with rc=%Rrc\n", + pProcess->uPID, pProcess->StartupInfo.szCmd, rc); + + /* Finally, update stopped status. */ + ASMAtomicWriteBool(&pProcess->fStopped, true); + ASMAtomicWriteBool(&pProcess->fShutdown, true); + + 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 PVBOXSERVICECTRLPROCSTARTUPINFO 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; + if (s_uCtrlExecThread++ == UINT32_MAX) /** @todo r=bird: ????????????? */ + s_uCtrlExecThread = 0; /* Wrap around to not let IPRT freak out. */ + rc = RTThreadCreateF(&pProcess->Thread, vgsvcGstCtrlProcessThread, + pProcess /*pvUser*/, 0 /*cbStack*/, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctl%u", s_uCtrlExecThread); + if (RT_FAILURE(rc)) + { + VGSvcError("Creating thread for guest process '%s' failed: rc=%Rrc, pProcess=%p\n", + pStartupInfo->szCmd, rc, pProcess); + + 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->szCmd, rc); + int rc2 = RTThreadWait(pProcess->Thread, RT_MS_1SEC * 30, NULL); + if (RT_SUCCESS(rc2)) + pProcess->Thread = NIL_RTTHREAD; + 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 VINF_SUCCESS; /** @todo Return rc here as soon as RTReqQueue todos are fixed. */ +} + + +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 == OUTPUT_HANDLE_ID_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 == OUTPUT_HANDLE_ID_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 == OUTPUT_HANDLE_ID_STDOUT + || uHandle == OUTPUT_HANDLE_ID_STDOUT_DEPRECATED) + ) + { + /** @todo r=bird: vgsvcGstCtrlProcessDumpToFile(void *pvBuf, size_t cbBuf, const char *pszFileNmFmt, ...) */ + char szDumpFile[RTPATH_MAX]; + if (!RTStrPrintf(szDumpFile, sizeof(szDumpFile), "VBoxService_Session%RU32_PID%RU32_StdOut.txt", + pSession->StartupInfo.uSessionID, pThis->uPID)) rc = VERR_BUFFER_UNDERFLOW; + if (RT_SUCCESS(rc)) + rc = vgsvcGstCtrlProcessDumpToFile(szDumpFile, pvBuf, cbRead); + AssertRC(rc); + } + else if ( pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR + && uHandle == OUTPUT_HANDLE_ID_STDERR) + { + char szDumpFile[RTPATH_MAX]; + if (!RTStrPrintf(szDumpFile, sizeof(szDumpFile), "VBoxService_Session%RU32_PID%RU32_StdErr.txt", + pSession->StartupInfo.uSessionID, pThis->uPID)) + rc = VERR_BUFFER_UNDERFLOW; + if (RT_SUCCESS(rc)) + rc = vgsvcGstCtrlProcessDumpToFile(szDumpFile, pvBuf, cbRead); + 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 VINF_SUCCESS; /** @todo Return rc here as soon as RTReqQueue todos are fixed. */ +} + + +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; /** @todo Return rc here as soon as RTReqQueue todos are fixed. */ +} + + +static int vgsvcGstCtrlProcessRequestExV(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fAsync, + RTMSINTERVAL uTimeoutMS, PRTREQ pReq, 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; + } + + rc = RTReqQueueCallV(pProcess->hReqQueue, &pReq, uTimeoutMS, fFlags, pfnFunction, cArgs, Args); + 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 */, + NULL /* pReq */, pfnFunction, cArgs, va); + va_end(va); + + return rc; +} + + +#if 0 /* unused */ +static int vgsvcGstCtrlProcessRequestWait(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + RTMSINTERVAL uTimeoutMS, PRTREQ pReq, 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, + pReq, 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..c30e0c8e --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp @@ -0,0 +1,2453 @@ +/* $Id: VBoxServiceControlSession.cpp $ */ +/** @file + * VBoxServiceControlSession - Guest session handling. Also handles the spawned session processes. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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 "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 +}; + + +/** + * 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 vgsvcGstCtrlSessionFileDestroy(PVBOXSERVICECTRLFILE pFile) +{ + AssertPtrReturn(pFile, VERR_INVALID_POINTER); + + int rc = RTFileClose(pFile->hFile); + if (RT_SUCCESS(rc)) + { + /* 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; +} + + +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 & ~DIRREMOVE_FLAG_VALID_MASK)) + { + if (fFlags & DIRREMOVE_FLAG_RECURSIVE) + { + uint32_t fFlagsRemRec = RTDIRRMREC_F_CONTENT_AND_DIR; /* Set default. */ + if (fFlags & DIRREMOVE_FLAG_CONTENT_ONLY) + fFlagsRemRec |= RTDIRRMREC_F_CONTENT_ONLY; + rc = RTDirRemoveRecursive(szDir, fFlagsRemRec); + 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 & ~DIRREMOVE_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]) + { + RTStrCopy(pFile->szName, sizeof(pFile->szName), szFile); + +/** @todo + * Implement szSharing! + */ + uint64_t fFlags; + rc = RTFileModeToFlagsEx(szAccess, szDisposition, NULL /* pszSharing, not used yet */, &fFlags); + VGSvcVerbose(4, "[File %s] Opening with fFlags=0x%x, rc=%Rrc\n", pFile->szName, fFlags, rc); + if (RT_SUCCESS(rc)) + { + rc = RTFileOpen(&pFile->hFile, pFile->szName, fFlags); + if (RT_SUCCESS(rc)) + { + /* 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; + RTListAppend(&pSession->lstFiles, &pFile->Node); + VGSvcVerbose(2, "[File %s] Opened (ID=%RU32)\n", pFile->szName, pFile->uHandle); + } + else + VGSvcError("[File %s] Seeking to offset %RU64 failed: rc=%Rrc\n", pFile->szName, offOpen, rc); + } + else + VGSvcError("[File %s] Opening failed with rc=%Rrc\n", pFile->szName, rc); + } + } + else + { + VGSvcError("[File %s] empty filename!\n", szFile); + rc = VERR_INVALID_NAME; + } + + /* clean up if we failed. */ + if (RT_FAILURE(rc)) + { + 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->szName : "<Not found>", uHandle); + rc = vgsvcGstCtrlSessionFileDestroy(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. + */ + 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); + VGSvcVerbose(5, "[File %s] Read %zu/%RU32 bytes, rc=%Rrc\n", pFile->szName, cbRead, cbToRead, rc); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result and data back to the host. + */ + int 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. + */ + 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); + VGSvcVerbose(5, "[File %s] Read %zu bytes @ %RU64, rc=%Rrc\n", pFile->szName, cbRead, offReadAt, rc); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result and data back to the host. + */ + int 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. + */ + size_t cbWritten = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileWrite(pFile->hFile, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), &cbWritten); + VGSvcVerbose(5, "[File %s] Writing %p LB %RU32 => %Rrc, cbWritten=%zu\n", + pFile->szName, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), rc, cbWritten); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int 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. + */ + size_t cbWritten = 0; + PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileWriteAt(pFile->hFile, (RTFOFF)offWriteAt, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), &cbWritten); + VGSvcVerbose(5, "[File %s] Writing %p LB %RU32 @ %RU64 => %Rrc, cbWritten=%zu\n", + pFile->szName, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), offWriteAt, rc, cbWritten); + } + else + { + VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle); + rc = VERR_NOT_FOUND; + } + + /* + * Report result back to host. + */ + int 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, GUEST_FILE_SEEKTYPE_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->szName, 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->szName, 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 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 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); + +/** @todo this hardcoded stuff needs redoing. */ + + /* 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. */ + VBOXSERVICECTRLPROCSTARTUPINFO startupInfo; + RT_ZERO(startupInfo); + startupInfo.cbEnv = sizeof(startupInfo.szEnv); + + int rc = VbglR3GuestCtrlProcGetStart(pHostCtx, + /* Command */ + startupInfo.szCmd, sizeof(startupInfo.szCmd), + /* Flags */ + &startupInfo.uFlags, + /* Arguments */ + startupInfo.szArgs, sizeof(startupInfo.szArgs), &startupInfo.uNumArgs, + /* Environment */ + startupInfo.szEnv, &startupInfo.cbEnv, &startupInfo.uNumEnvVars, + /* Credentials; for hosts with VBox < 4.3 (protocol version 1). + * For protocol v2 and up the credentials are part of the session + * opening call. */ + startupInfo.szUser, sizeof(startupInfo.szUser), + startupInfo.szPassword, sizeof(startupInfo.szPassword), + /* Timeout (in ms) */ + &startupInfo.uTimeLimitMS, + /* Process priority */ + &startupInfo.uPriority, + /* Process affinity */ + startupInfo.uAffinity, sizeof(startupInfo.uAffinity), &startupInfo.uNumAffinity); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "Request to start process szCmd=%s, fFlags=0x%x, szArgs=%s, szEnv=%s, uTimeout=%RU32\n", + startupInfo.szCmd, startupInfo.uFlags, + startupInfo.uNumArgs ? startupInfo.szArgs : "<None>", + startupInfo.uNumEnvVars ? startupInfo.szEnv : "<None>", + startupInfo.uTimeLimitMS); + + bool fStartAllowed = false; /* Flag indicating whether starting a process is allowed or not. */ + rc = VGSvcGstCtrlSessionProcessStartAllowed(pSession, &fStartAllowed); + if (RT_SUCCESS(rc)) + { + if (fStartAllowed) + rc = VGSvcGstCtrlProcessStart(pSession, &startupInfo, 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); + } + } + 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 & INPUT_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 & INPUT_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); + + VGSvcGstCtrlProcessRelease(pProcess); + } + else + { + VGSvcError("Could not find PID %u for termination.\n", uPID); + 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 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_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; + + 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->StartupInfo.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->StartupInfo.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; + uint32_t uSessionRc = VINF_SUCCESS; /** uint32_t vs. int. */ + + if (fProcessAlive) + { + for (int i = 0; i < 3; i++) + { + 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; + /** @todo r=bird: What's the point of sleeping 3 second after the last attempt? */ + RTThreadSleep(3000); + } + + 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; + break; + + case RTPROCEXITREASON_ABEND: + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA; + break; + + case RTPROCEXITREASON_SIGNAL: + uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TES; + 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; + } + + VGSvcVerbose(3, "Guest session ID=%RU32 thread ended with sessionStatus=%RU32, sessionRc=%Rrc\n", + idSession, uSessionStatus, uSessionRc); + + /* + * Report final status. + */ + Assert(uSessionStatus != GUEST_SESSION_NOTIFYTYPE_UNDEFINED); + VBGLR3GUESTCTRLCMDCTX ctx = { idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession) }; + rc2 = VbglR3GuestCtrlSessionNotify(&ctx, uSessionStatus, uSessionRc); + if (RT_FAILURE(rc2)) + VGSvcError("Reporting session ID=%RU32 final status failed with rc=%Rrc\n", idSession, rc2); + + VGSvcVerbose(3, "Session ID=%RU32 thread ending\n", idSession); + 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, &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; +} + +/** + * 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; + + rc = vgsvcGstCtrlSessionReadKeyAndAccept(idClient, pSession->StartupInfo.uSessionID); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(1, "Using client ID=%RU32\n", idClient); + + /* + * 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) }; + 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) + { + /* + * 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; + } + else /** @todo Shouldn't we have a plan for handling connection loss and such? Now, we'll just spin like crazy. */ + VGSvcVerbose(3, "Getting host message failed with %Rrc\n", rc); /* VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */ + + /* Let others run (guests are often single CPU) ... */ + RTThreadYield(); + } + + /* + * 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. */ + size_t cProcesses = 0; + PVBOXSERVICECTRLPROCESS pProcess; + RTListForEach(&pSession->lstProcesses, pProcess, VBOXSERVICECTRLPROCESS, Node) + { + VGSvcGstCtrlProcessStop(pProcess); + cProcesses++; + } + + VGSvcVerbose(1, "%zu guest processes were signalled to stop\n", cProcesses); + + /* Wait for all active threads to shutdown and destroy the active thread list. */ + pProcess = RTListGetFirst(&pSession->lstProcesses, VBOXSERVICECTRLPROCESS, Node); + while (pProcess) + { + PVBOXSERVICECTRLPROCESS pNext = RTListNodeGetNext(&pProcess->Node, VBOXSERVICECTRLPROCESS, Node); + bool fLast = RTListNodeIsLast(&pSession->lstProcesses, &pProcess->Node); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + AssertRC(rc2); + + rc2 = VGSvcGstCtrlProcessWait(pProcess, 30 * 1000 /* Wait 30 seconds max. */, NULL /* rc */); + + int rc3 = RTCritSectEnter(&pSession->CritSect); + AssertRC(rc3); + + if (RT_SUCCESS(rc2)) + VGSvcGstCtrlProcessFree(pProcess); + + if (fLast) + break; + + pProcess = pNext; + } + +#ifdef DEBUG + pProcess = RTListGetFirst(&pSession->lstProcesses, VBOXSERVICECTRLPROCESS, Node); + while (pProcess) + { + PVBOXSERVICECTRLPROCESS pNext = RTListNodeGetNext(&pProcess->Node, VBOXSERVICECTRLPROCESS, Node); + bool fLast = RTListNodeIsLast(&pSession->lstProcesses, &pProcess->Node); + + VGSvcVerbose(1, "Process %p (PID %RU32) still in list\n", pProcess, pProcess->uPID); + if (fLast) + break; + + pProcess = pNext; + } +#endif + AssertMsg(RTListIsEmpty(&pSession->lstProcesses), + ("Guest process list still contains entries when it should not\n")); + + /* + * Close all left guest files. + */ + VGSvcVerbose(0, "Closing all guest files ...\n"); + + PVBOXSERVICECTRLFILE pFile; + pFile = RTListGetFirst(&pSession->lstFiles, VBOXSERVICECTRLFILE, Node); + while (pFile) + { + PVBOXSERVICECTRLFILE pNext = RTListNodeGetNext(&pFile->Node, VBOXSERVICECTRLFILE, Node); + bool fLast = RTListNodeIsLast(&pSession->lstFiles, &pFile->Node); + + int rc2 = vgsvcGstCtrlSessionFileDestroy(pFile); + if (RT_FAILURE(rc2)) + { + VGSvcError("Unable to close file '%s'; rc=%Rrc\n", pFile->szName, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + /* Keep going. */ + } + + if (fLast) + break; + + pFile = pNext; + } + + AssertMsg(RTListIsEmpty(&pSession->lstFiles), ("Guest file list still contains entries when it should not\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->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); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + 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)) + { + VGSvcVerbose(3, "Removing process (PID %RU32) from session ID=%RU32\n", pProcess->uPID, pSession->StartupInfo.uSessionID); + Assert(pProcess->cRefs == 0); + + RTListNodeRemove(&pProcess->Node); + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return VINF_SUCCESS; +} + + +/** + * 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 pbAllowed True if starting (another) guest process + * is allowed, false if not. + */ +int VGSvcGstCtrlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession, bool *pbAllowed) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pbAllowed, 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. */ + { + uint32_t uProcsRunning = 0; + PVBOXSERVICECTRLPROCESS pProcess; + RTListForEach(&pSession->lstProcesses, pProcess, VBOXSERVICECTRLPROCESS, Node) + uProcsRunning++; + + VGSvcVerbose(3, "Maximum served guest processes set to %u, running=%u\n", pSession->uProcsMaxKept, uProcsRunning); + + int32_t iProcsLeft = (pSession->uProcsMaxKept - uProcsRunning - 1); + if (iProcsLeft < 0) + { + VGSvcVerbose(3, "Maximum running guest processes reached (%u)\n", pSession->uProcsMaxKept); + fLimitReached = true; + } + } + + *pbAllowed = !fLimitReached; + + int rc2 = RTCritSectLeave(&pSession->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + 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 PVBOXSERVICECTRLSESSIONSTARTUPINFO 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->StartupInfo.szUser[0] == '\0'; + if (fAnonymous) + { + Assert(!strlen(pSessionThread->StartupInfo.szPassword)); + Assert(!strlen(pSessionThread->StartupInfo.szDomain)); + + 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->szUser, +#ifdef DEBUG + pSessionStartupInfo->szPassword, +#else + "XXX", /* Never show passwords in release mode. */ +#endif + pSessionStartupInfo->szDomain, + 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)); + AssertReturn(pszExeName, VERR_FILENAME_TOO_LONG); + + char szParmSessionID[32]; + RTStrPrintf(szParmSessionID, sizeof(szParmSessionID), "--session-id=%RU32", pSessionThread->StartupInfo.uSessionID); + + char szParmSessionProto[32]; + RTStrPrintf(szParmSessionProto, sizeof(szParmSessionProto), "--session-proto=%RU32", + pSessionThread->StartupInfo.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; + 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->StartupInfo.szUser; + + if (strlen(pSessionThread->StartupInfo.szDomain)) + { + apszArgs[idxArg++] = "--domain"; + apszArgs[idxArg++] = pSessionThread->StartupInfo.szDomain; + } + } + + /* 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; +#ifndef DEBUG + RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), "%.*s-%RU32-%s%s", + cchBase, g_szLogFile, pSessionStartupInfo->uSessionID, pSessionStartupInfo->szUser, pszSuffix); +#else + RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), "%.*s-%RU32-%RU32-%s%s", + cchBase, g_szLogFile, pSessionStartupInfo->uSessionID, uCtrlSessionThread, + pSessionStartupInfo->szUser, 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 + ; + + /* + * 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->StartupInfo.szUser; +#ifdef RT_OS_WINDOWS + char *pszUserUPN = NULL; + if (pSessionThread->StartupInfo.szDomain[0]) + { + int cchbUserUPN = RTStrAPrintf(&pszUserUPN, "%s@%s", + pSessionThread->StartupInfo.szUser, + pSessionThread->StartupInfo.szDomain); + 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->StartupInfo.szPassword : NULL, + &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 PVBOXSERVICECTRLSESSIONSTARTUPINFO 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->StartupInfo.uSessionID != pSessionStartupInfo->uSessionID, + ("Guest session thread ID=%RU32 (%p) already exists when it should not\n", + pSessionCur->StartupInfo.uSessionID, pSessionCur), VERR_ALREADY_EXISTS); + } +#endif + + /* Static counter to help tracking session thread <-> process relations. */ + static uint32_t s_uCtrlSessionThread = 0; +#if 1 + if (++s_uCtrlSessionThread == 100000) +#else /* This must be some joke, right? ;-) */ + if (s_uCtrlSessionThread++ == UINT32_MAX) +#endif + s_uCtrlSessionThread = 0; /* Wrap around to not let IPRT freak out. */ + + /* + * 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; + + /* Copy over session startup info. */ + memcpy(&pSessionThread->StartupInfo, pSessionStartupInfo, sizeof(VBOXSERVICECTRLSESSIONSTARTUPINFO)); + + /* 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)) + { + /* + * 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, "CtrlSess%u", 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->StartupInfo.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->StartupInfo.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->StartupInfo.uSessionID, uTimeoutMS); + + int rcThread; + rc = RTThreadWait(pThread->Thread, uTimeoutMS, &rcThread); + if (RT_SUCCESS(rc)) + VGSvcVerbose(3, "Session thread ID=%RU32 ended with rc=%Rrc\n", pThread->StartupInfo.uSessionID, rcThread); + else + VGSvcError("Waiting for session thread ID=%RU32 to close failed with rc=%Rrc\n", pThread->StartupInfo.uSessionID, 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); + + int rc = VGSvcGstCtrlSessionThreadWait(pThread, 5 * 60 * 1000 /* 5 minutes timeout */, fFlags); + + /* Remove session from list and destroy object. */ + RTListNodeRemove(&pThread->Node); + + RTMemFree(pThread); + pThread = NULL; + + 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: + /* Ignore; might be "guestsession" main command. */ + /** @todo r=bird: We DO NOT ignore stuff on the command line! */ + break; + + default: + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown command '%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..d912ec2a --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp @@ -0,0 +1,644 @@ +/* $Id: VBoxServiceCpuHotPlug.cpp $ */ +/** @file + * VBoxService - Guest Additions CPU Hot-Plugging Service. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/** @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); + + /* 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); + 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*/); + if (RT_FAILURE(rc)) + break; + } + } + else + { + /* Go back one level and try to get the next entry. */ + Assert(iLvlCurr > 0); + + RTDirClose(pAcpiCpuPathLvl->hDir); + RTStrFree(pAcpiCpuPathLvl->pszPath); + pAcpiCpuPathLvl->hDir = NIL_RTDIR; + pAcpiCpuPathLvl->pszPath = NULL; + + 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 every 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 */ + { + /** @todo r-bird: This code is bringing all CPUs online; the idCpuCore and + * idCpuPackage parameters are unused! + * aeichner: These files are not available at this point unfortunately. (see comment above) + * bird: Yes, but isn't that easily dealt with by doing: + * if (matching_topology() || !have_topology_directory()) + * bring_cpu_online() + * That could save you the cpu0 and cpuidle checks to. + */ + /* + * Check if this is a CPU object. + * cpu0 is excluded because it is not possible to change the state + * of the first CPU on Linux (it doesn't even have an online file) + * and cpuidle is no CPU device. Prevents error messages later. + */ + if( !strncmp(DirFolderContent.szName, "cpu", 3) + && strncmp(DirFolderContent.szName, "cpu0", 4) + && strncmp(DirFolderContent.szName, "cpuidle", 7)) + { + /* Get the sysdev */ + RTFILE hFileCpuOnline = NIL_RTFILE; + + rc = RTFileOpenF(&hFileCpuOnline, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, + "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName); + if (RT_SUCCESS(rc)) + { + /* Write a 1 to online the CPU */ + rc = RTFileWrite(hFileCpuOnline, "1", 1, NULL); + RTFileClose(hFileCpuOnline); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was brought online\n", idCpuPackage, idCpuCore); + fCpuOnline = true; + break; + } + /* Error means CPU not present or online already */ + } + else + VGSvcError("CpuHotPlug: Failed to open '%s/%s/online' rc=%Rrc\n", + SYSFS_CPU_PATH, DirFolderContent.szName, rc); + } + } + RTDirClose(hDirDevices); + } + else + VGSvcError("CpuHotPlug: Failed to open path %s rc=%Rrc\n", SYSFS_CPU_PATH, rc); + + /* Sleep a bit */ + if (!fCpuOnline) + RTThreadSleep(10); + + } 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 + 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..dc719233 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h @@ -0,0 +1,252 @@ +/* $Id: VBoxServiceInternal.h $ */ +/** @file + * VBoxService - Guest Additions Services. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h +#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <stdio.h> +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +# include <process.h> /* Needed for file version information. */ +#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> + +/** + * 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; +extern VBOXSERVICE g_Clipboard; +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_VBOXSERVICE_MANAGEMENT +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..885fb25e --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp @@ -0,0 +1,790 @@ +/* $Id: VBoxServicePageSharing.cpp $ */ +/** @file + * VBoxService - Guest page sharing. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/** @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. */ + + sprintf(szFileVersionLocation, TEXT("\\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. */ + 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. */ + strcat(szFullFilePath, "\\"); + strcat(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)); + strcat(szFullFilePath, "\\drivers\\"); + strcat(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; + } + + strcat(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 ret = RTAvlPVInsert(&pNewTree, pRec); + Assert(ret); NOREF(ret); + } + } +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..3ef2346d --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp @@ -0,0 +1,429 @@ +/* $Id: VBoxServicePropCache.cpp $ */ +/** @file + * VBoxServicePropCache - Guest property cache. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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..ee6e3f81 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h @@ -0,0 +1,56 @@ +/* $Id: */ +/** @file + * VBoxServicePropCache - Guest property cache. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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..b5c106db --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h @@ -0,0 +1,27 @@ +/* $Id: VBoxServiceResource-win.h $ */ +/** @file + * VBoxService - Guest Additions Service, resource IDs. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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..881d0f94 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp @@ -0,0 +1,736 @@ +/* $Id: VBoxServiceStats.cpp $ */ +/** @file + * VBoxStats - Guest statistics notification + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/** @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 + NTSTATUS (WINAPI *pfnNtQuerySystemInformation)(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, + ULONG SystemInformationLength, PULONG ReturnLength); + void (WINAPI *pfnGlobalMemoryStatusEx)(LPMEMORYSTATUSEX lpBuffer); + 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..ca2ea383 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp @@ -0,0 +1,815 @@ +/* $Id: VBoxServiceTimeSync.cpp $ */ +/** @file + * VBoxService - Guest Additions TimeSync Service. + */ + +/* + * Copyright (C) 2007-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/** @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 ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + { + rc = VGSvcCheckPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-start"); + if (RT_SUCCESS(rc)) + g_fTimeSyncSetOnStart = true; + } + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + { + rc = VGSvcCheckPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-start"); + if (RT_SUCCESS(rc)) + g_fTimeSyncSetOnStart = false; + } + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + { + rc = VGSvcCheckPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-on-restore"); + if (RT_SUCCESS(rc)) + g_fTimeSyncSetOnRestore = true; + } + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + { + rc = VGSvcCheckPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-on-restore"); + if (RT_SUCCESS(rc)) + g_fTimeSyncSetOnRestore = false; + } + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + { + uint32_t uValue; + rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-verbosity", + &uValue, 0 /*uMin*/, 255 /*uMax*/); + if (RT_SUCCESS(rc)) + g_cTimeSyncVerbosity = uValue; + } + VbglR3GuestPropDisconnect(uGuestPropSvcClientID); + } + + if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */ + rc = VINF_SUCCESS; + return rc; +#else + /* Nothing to do here yet. */ + return VINF_SUCCESS; +#endif +} + + +/** + * Displays a verbose message based on the currently + * set timesync verbosity level. + * + * @param iLevel Minimum log level required to display this message. + * @param pszFormat The message text. + * @param ... Format arguments. + */ +static void vgsvcTimeSyncLog(unsigned iLevel, const char *pszFormat, ...) +{ + if (iLevel <= g_cTimeSyncVerbosity) + { + va_list va; + va_start(va, pszFormat); + VGSvcLogV(pszFormat, va); + va_end(va); + } +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption} + */ +static DECLCALLBACK(int) vgsvcTimeSyncOption(const char **ppszShort, int argc, char **argv, int *pi) +{ + int rc = VINF_SUCCESS; + if (ppszShort) + rc = -1 ;/* no short options */ + else if (!strcmp(argv[*pi], "--timesync-interval")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncInterval, 50, UINT32_MAX - 1); + else if (!strcmp(argv[*pi], "--timesync-min-adjust")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMinAdjust, 0, 3600000); + else if (!strcmp(argv[*pi], "--timesync-latency-factor")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncLatencyFactor, 1, 1024); + else if (!strcmp(argv[*pi], "--timesync-max-latency")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMaxLatency, 1, 3600000); + else if (!strcmp(argv[*pi], "--timesync-set-threshold")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000); /* a week */ + else if (!strcmp(argv[*pi], "--timesync-set-start")) + g_fTimeSyncSetOnStart = true; + else if (!strcmp(argv[*pi], "--timesync-no-set-start")) + g_fTimeSyncSetOnStart = false; + else if (!strcmp(argv[*pi], "--timesync-set-on-restore")) + g_fTimeSyncSetOnRestore = true; + else if (!strcmp(argv[*pi], "--timesync-no-set-on-restore")) + g_fTimeSyncSetOnRestore = false; + else if (!strcmp(argv[*pi], "--timesync-verbosity")) + rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cTimeSyncVerbosity, 0 /*uMin*/, 255 /*uMax*/); + else + rc = -1; + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcTimeSyncInit(void) +{ + /* + * If not specified, find the right interval default. + * Then create the event sem to block on. + */ + if (!g_TimeSyncInterval) + g_TimeSyncInterval = g_DefaultInterval * 1000; + if (!g_TimeSyncInterval) + g_TimeSyncInterval = 10 * 1000; + + VbglR3GetSessionId(&g_idTimeSyncSession); + /* The status code is ignored as this information is not available with VBox < 3.2.10. */ + + int rc = RTSemEventMultiCreate(&g_TimeSyncEvent); + AssertRC(rc); +#ifdef RT_OS_WINDOWS + if (RT_SUCCESS(rc)) + { + /* + * Adjust privileges of this process so we can make system time adjustments. + */ + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &g_hTokenProcess)) + { + TOKEN_PRIVILEGES tkPriv; + RT_ZERO(tkPriv); + tkPriv.PrivilegeCount = 1; + tkPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &tkPriv.Privileges[0].Luid)) + { + DWORD cbRet = sizeof(g_TkOldPrivileges); + if (AdjustTokenPrivileges(g_hTokenProcess, FALSE, &tkPriv, sizeof(TOKEN_PRIVILEGES), &g_TkOldPrivileges, &cbRet)) + rc = VINF_SUCCESS; + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Adjusting token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", + dwErr, rc); + } + } + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Looking up token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", + dwErr, rc); + } + if (RT_FAILURE(rc)) + { + CloseHandle(g_hTokenProcess); + g_hTokenProcess = NULL; + } + } + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Opening process token (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", + dwErr, rc); + g_hTokenProcess = NULL; + } + } + + if (g_pfnGetSystemTimeAdjustment) + { + if (g_pfnGetSystemTimeAdjustment(&g_dwWinTimeAdjustment, &g_dwWinTimeIncrement, &g_bWinTimeAdjustmentDisabled)) + vgsvcTimeSyncLog(0, "vgsvcTimeSyncInit: Initially %ld (100ns) units per %ld (100 ns) units interval, disabled=%d\n", + g_dwWinTimeAdjustment, g_dwWinTimeIncrement, g_bWinTimeAdjustmentDisabled ? 1 : 0); + else + { + DWORD dwErr = GetLastError(); + rc = RTErrConvertFromWin32(dwErr); + VGSvcError("vgsvcTimeSyncInit: Could not get time adjustment values! Last error: %ld!\n", dwErr); + } + } +#endif /* RT_OS_WINDOWS */ + + return rc; +} + + +/** + * Try adjusting the time using adjtime or similar. + * + * @returns true on success, false on failure. + * + * @param pDrift The time adjustment. + */ +static bool vgsvcTimeSyncAdjust(PCRTTIMESPEC pDrift) +{ +#ifdef RT_OS_WINDOWS +/** @todo r=bird: g_hTokenProcess cannot be NULL here. + * vgsvcTimeSyncInit will fail and the service will not be started with + * it being NULL. vgsvcTimeSyncInit OTOH will *NOT* be called until the + * service thread has terminated. If anything + * else is the case, there is buggy code somewhere.*/ + if (g_hTokenProcess == NULL) /* Is the token already closed when shutting down? */ + return false; + + /* The API appeared in NT 3.50. */ + if ( !g_pfnSetSystemTimeAdjustment + || !g_pfnGetSystemTimeAdjustment) + return false; + + DWORD dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwWinTimeIncrement; + BOOL fWinTimeAdjustmentDisabled; + if (g_pfnGetSystemTimeAdjustment(&dwWinTimeAdjustment, &dwWinTimeIncrement, &fWinTimeAdjustmentDisabled)) + { + DWORD dwDiffMax = g_dwWinTimeAdjustment * 0.50; + DWORD dwDiffNew = dwWinTimeAdjustment * 0.10; + + if (RTTimeSpecGetMilli(pDrift) > 0) + { + dwWinNewTimeAdjustment = dwWinTimeAdjustment + dwDiffNew; + if (dwWinNewTimeAdjustment > (g_dwWinTimeAdjustment + dwDiffMax)) + { + dwWinNewTimeAdjustment = g_dwWinTimeAdjustment + dwDiffMax; + dwDiffNew = dwDiffMax; + } + } + else + { + dwWinNewTimeAdjustment = dwWinTimeAdjustment - dwDiffNew; + if (dwWinNewTimeAdjustment < (g_dwWinTimeAdjustment - dwDiffMax)) + { + dwWinNewTimeAdjustment = g_dwWinTimeAdjustment - dwDiffMax; + dwDiffNew = dwDiffMax; + } + } + + vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: Drift=%lldms\n", RTTimeSpecGetMilli(pDrift)); + vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: OrgTA=%ld, CurTA=%ld, NewTA=%ld, DiffNew=%ld, DiffMax=%ld\n", + g_dwWinTimeAdjustment, dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwDiffNew, dwDiffMax); + if (g_pfnSetSystemTimeAdjustment(dwWinNewTimeAdjustment, FALSE /* Periodic adjustments enabled. */)) + { + g_cTimeSyncErrors = 0; + return true; + } + + if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncAdjust: SetSystemTimeAdjustment failed, error=%u\n", GetLastError()); + } + else if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncAdjust: GetSystemTimeAdjustment failed, error=%ld\n", GetLastError()); + +#elif defined(RT_OS_OS2) || defined(RT_OS_HAIKU) + /* No API for doing gradual time adjustments. */ + +#else /* PORTME */ + /* + * Try using adjtime(), most unix-like systems have this. + */ + struct timeval tv; + RTTimeSpecGetTimeval(pDrift, &tv); + if (adjtime(&tv, NULL) == 0) + { + vgsvcTimeSyncLog(1, "vgsvcTimeSyncAdjust: adjtime by %RDtimespec\n", pDrift); + g_cTimeSyncErrors = 0; + return true; + } +#endif + + /* failed */ + return false; +} + + +/** + * Cancels any pending time adjustment. + * + * Called when we've caught up and before calls to vgsvcTimeSyncSet. + */ +static void vgsvcTimeSyncCancelAdjust(void) +{ +#ifdef RT_OS_WINDOWS +/** @todo r=bird: g_hTokenProcess cannot be NULL here. See argumentation in + * vgsvcTimeSyncAdjust. */ + if (g_hTokenProcess == NULL) /* No process token (anymore)? */ + return; + if (!g_pfnSetSystemTimeAdjustment) + return; + if (g_pfnSetSystemTimeAdjustment(0, TRUE /* Periodic adjustments disabled. */)) + vgsvcTimeSyncLog(5, "vgsvcTimeSyncCancelAdjust: Windows Time Adjustment is now disabled.\n"); + else if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncCancelAdjust: SetSystemTimeAdjustment(,disable) failed, error=%u\n", GetLastError()); +#endif /* !RT_OS_WINDOWS */ +} + + +/** + * Set the wall clock to compensate for drift. + * + * @returns true on success, false on failure. + * + * @param pDrift The time adjustment. + */ +static void vgsvcTimeSyncSet(PCRTTIMESPEC pDrift) +{ + /* + * Query the current time, adjust it by adding the drift and set it. + */ + RTTIMESPEC NewGuestTime; + int rc = RTTimeSet(RTTimeSpecAdd(RTTimeNow(&NewGuestTime), pDrift)); + if (RT_SUCCESS(rc)) + { + /* Succeeded - reset the error count and log the change. */ + g_cTimeSyncErrors = 0; + + if (g_cTimeSyncVerbosity >= 1) + { + char sz[64]; + RTTIME Time; + vgsvcTimeSyncLog(1, "time set to %s\n", RTTimeToString(RTTimeExplode(&Time, &NewGuestTime), sz, sizeof(sz))); +#ifdef DEBUG + RTTIMESPEC Tmp; + vgsvcTimeSyncLog(3, " now %s\n", RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&Tmp)), sz, sizeof(sz))); +#endif + } + } + else if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncSet: RTTimeSet(%RDtimespec) failed: %Rrc\n", &NewGuestTime, rc); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +DECLCALLBACK(int) vgsvcTimeSyncWorker(bool volatile *pfShutdown) +{ + RTTIME Time; + int rc = VINF_SUCCESS; + + /* + * Tell the control thread that it can continue spawning services. + */ + RTThreadUserSignal(RTThreadSelf()); + + /* + * Initialize the last host and guest times to prevent log message. + * We also track whether we set the time in the previous loop. + */ + RTTIMESPEC HostLast; + if (RT_FAILURE(VbglR3GetHostTime(&HostLast))) + RTTimeSpecSetNano(&HostLast, 0); + RTTIMESPEC GuestLast; + RTTimeNow(&GuestLast); + bool fSetTimeLastLoop = false; + + /* + * The Work Loop. + */ + for (;;) + { + /* + * Try to get a reliable time reading. + */ + int cTries = 3; + do + { + /* + * Query the session id (first to keep lantency low) and the time. + */ + uint64_t idNewSession = g_idTimeSyncSession; + if (g_fTimeSyncSetOnRestore) + VbglR3GetSessionId(&idNewSession); + + RTTIMESPEC GuestNow0; + RTTimeNow(&GuestNow0); + + RTTIMESPEC HostNow; + int rc2 = VbglR3GetHostTime(&HostNow); + if (RT_FAILURE(rc2)) + { + if (g_cTimeSyncErrors++ < 10) + VGSvcError("vgsvcTimeSyncWorker: VbglR3GetHostTime failed; rc2=%Rrc\n", rc2); + break; + } + + RTTIMESPEC GuestNow; + RTTimeNow(&GuestNow); + + /* + * Calc latency and check if it's ok. + */ + RTTIMESPEC GuestElapsed = GuestNow; + RTTimeSpecSub(&GuestElapsed, &GuestNow0); + if ((uint32_t)RTTimeSpecGetMilli(&GuestElapsed) < g_cMsTimeSyncMaxLatency) + { + /* + * If we were just restored, set the adjustment threshold to zero to force a resync. + */ + uint32_t TimeSyncSetThreshold = g_TimeSyncSetThreshold; + if ( g_fTimeSyncSetOnRestore + && idNewSession != g_idTimeSyncSession) + { + vgsvcTimeSyncLog(2, "vgsvcTimeSyncWorker: The VM session ID changed, forcing resync.\n"); + g_idTimeSyncSession = idNewSession; + TimeSyncSetThreshold = 0; + } + + /* + * Calculate the adjustment threshold and the current drift. + */ + uint32_t MinAdjust = RTTimeSpecGetMilli(&GuestElapsed) * g_TimeSyncLatencyFactor; + if (MinAdjust < g_cMsTimeSyncMinAdjust) + MinAdjust = g_cMsTimeSyncMinAdjust; + + RTTIMESPEC Drift = HostNow; + RTTimeSpecSub(&Drift, &GuestNow); + if (RTTimeSpecGetMilli(&Drift) < 0) + MinAdjust += g_cMsTimeSyncMinAdjust; /* extra buffer against moving time backwards. */ + + RTTIMESPEC AbsDrift = Drift; + RTTimeSpecAbsolute(&AbsDrift); + + if (g_cTimeSyncVerbosity >= 4) + { + char sz1[64]; + char sz2[64]; + vgsvcTimeSyncLog(4, "vgsvcTimeSyncWorker: Host: %s (MinAdjust: %RU32 ms), Guest: %s => %RDtimespec drift\n", + RTTimeToString(RTTimeExplode(&Time, &HostNow), sz1, sizeof(sz1)), MinAdjust, + RTTimeToString(RTTimeExplode(&Time, &GuestNow), sz2, sizeof(sz2)), &Drift); + } + + bool fSetTimeInThisLoop = false; + uint64_t AbsDriftMilli = RTTimeSpecGetMilli(&AbsDrift); + if ( AbsDriftMilli > MinAdjust + || g_fTimeSyncSetOnStart) + { + /* + * Ok, the drift is above the threshold. + * + * Try a gradual adjustment first, if that fails or the drift is + * too big, fall back on just setting the time. + */ + if ( AbsDriftMilli > TimeSyncSetThreshold + || g_fTimeSyncSetOnStart + || !vgsvcTimeSyncAdjust(&Drift)) + { + vgsvcTimeSyncCancelAdjust(); + vgsvcTimeSyncSet(&Drift); + fSetTimeInThisLoop = true; + } + + /* + * Log radical host time changes. + */ + int64_t cNsHostDelta = RTTimeSpecGetNano(&HostNow) - RTTimeSpecGetNano(&HostLast); + if ((uint64_t)RT_ABS(cNsHostDelta) > RT_NS_1HOUR / 2) + vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical host time change: %'RI64ns (HostNow=%RDtimespec HostLast=%RDtimespec)\n", + cNsHostDelta, &HostNow, &HostLast); + } + else + vgsvcTimeSyncCancelAdjust(); + HostLast = HostNow; + + /* + * Log radical guest time changes (we could be the cause of these, mind). + * Note! Right now we don't care about an extra log line after we called + * vgsvcTimeSyncSet. fSetTimeLastLoop helps show it though. + */ + int64_t cNsGuestDelta = RTTimeSpecGetNano(&GuestNow) - RTTimeSpecGetNano(&GuestLast); + if ((uint64_t)RT_ABS(cNsGuestDelta) > RT_NS_1HOUR / 2) + vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical guest time change: %'RI64ns (GuestNow=%RDtimespec GuestLast=%RDtimespec fSetTimeLastLoop=%RTbool)\n", + cNsGuestDelta, &GuestNow, &GuestLast, fSetTimeLastLoop); + GuestLast = GuestNow; + fSetTimeLastLoop = fSetTimeInThisLoop; + break; + } + vgsvcTimeSyncLog(3, "vgsvcTimeSyncWorker: %RDtimespec: latency too high (%RDtimespec, max %ums) sleeping 1s\n", + &GuestNow, &GuestElapsed, g_cMsTimeSyncMaxLatency); + RTThreadSleep(1000); + } while (--cTries > 0); + + /* Clear the set-next/set-start flag. */ + g_fTimeSyncSetOnStart = false; + + /* + * Block for a while. + * + * The event semaphore takes care of ignoring interruptions and it + * allows us to implement service wakeup later. + */ + if (*pfShutdown) + break; + int rc2 = RTSemEventMultiWait(g_TimeSyncEvent, g_TimeSyncInterval); + if (*pfShutdown) + break; + if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) + { + VGSvcError("vgsvcTimeSyncWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); + rc = rc2; + break; + } + } + + vgsvcTimeSyncCancelAdjust(); + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcTimeSyncStop(void) +{ + if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI) + RTSemEventMultiSignal(g_TimeSyncEvent); +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcTimeSyncTerm(void) +{ +#ifdef RT_OS_WINDOWS + /* + * Restore the SE_SYSTEMTIME_NAME token privileges (if init succeeded). + */ + if (g_hTokenProcess) + { + if (!AdjustTokenPrivileges(g_hTokenProcess, FALSE, &g_TkOldPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) + { + DWORD dwErr = GetLastError(); + VGSvcError("vgsvcTimeSyncTerm: Restoring token privileges (SE_SYSTEMTIME_NAME) failed with code %u!\n", dwErr); + } + CloseHandle(g_hTokenProcess); + g_hTokenProcess = NULL; + } +#endif /* !RT_OS_WINDOWS */ + + if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(g_TimeSyncEvent); + g_TimeSyncEvent = NIL_RTSEMEVENTMULTI; + } +} + + +/** + * The 'timesync' service description. + */ +VBOXSERVICE g_TimeSync = +{ + /* pszName. */ + "timesync", + /* pszDescription. */ + "Time synchronization", + /* pszUsage. */ + " [--timesync-interval <ms>] [--timesync-min-adjust <ms>]\n" + " [--timesync-latency-factor <x>] [--timesync-max-latency <ms>]\n" + " [--timesync-set-threshold <ms>]\n" + " [--timesync-set-start|--timesync-no-set-start]\n" + " [--timesync-set-on-restore|--timesync-no-set-on-restore]\n" + " [--timesync-verbosity <level>]" + , + /* pszOptions. */ + " --timesync-interval Specifies the interval at which to synchronize the\n" + " time with the host. The default is 10000 ms.\n" + " --timesync-min-adjust The minimum absolute drift value measured in\n" + " milliseconds to make adjustments for.\n" + " The default is 1000 ms on OS/2 and 100 ms elsewhere.\n" + " --timesync-latency-factor\n" + " The factor to multiply the time query latency with\n" + " to calculate the dynamic minimum adjust time.\n" + " The default is 8 times.\n" + " --timesync-max-latency The max host timer query latency to accept.\n" + " The default is 250 ms.\n" + " --timesync-set-threshold\n" + " The absolute drift threshold, given as milliseconds,\n" + " where to start setting the time instead of trying to\n" + " adjust it. The default is 20 min.\n" + " --timesync-set-start, --timesync-no-set-start \n" + " Set the time when starting the time sync service.\n" +#ifdef RT_OS_OS2 + " Default: --timesync-set-start\n" +#else + " Default: --timesync-no-set-start\n" +#endif + " --timesync-set-on-restore, --timesync-no-set-on-restore\n" + " Whether to immediately set the time when the VM is\n" + " restored or not. Default: --timesync-set-on-restore\n" + " --timesync-verbosity Sets the verbosity level. Defaults to service wide\n" + " verbosity level.\n" + , + /* methods */ + vgsvcTimeSyncPreInit, + vgsvcTimeSyncOption, + vgsvcTimeSyncInit, + vgsvcTimeSyncWorker, + vgsvcTimeSyncStop, + vgsvcTimeSyncTerm +}; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp new file mode 100644 index 00000000..bc91b710 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp @@ -0,0 +1,1763 @@ +/* $Id: VBoxServiceToolBox.cpp $ */ +/** @file + * VBoxServiceToolbox - Internal (BusyBox-like) toolbox. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdio.h> + +#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; + + +/********************************************************************************************************************************* +* 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; + +typedef struct VBOXSERVICETOOLBOXDIRENTRY +{ + /** Our node. */ + RTLISTNODE Node; + /** The actual entry. */ + RTDIRENTRYEX dirEntry; +} VBOXSERVICETOOLBOXDIRENTRY, *PVBOXSERVICETOOLBOXDIRENTRY; + +/** 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" + "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n" + "All rights reserved.\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, 1 /* Binary mode */, -1 /* Current code set, not changed */); + if (RT_FAILURE(rc)) + RTMsgError("Unable to set stdout to binary mode, rc=%Rrc\n", rc); + + return rc; +} + + +/** + * Prints a parseable stream header which contains the actual tool + * which was called/used along with its stream version. + * + * @param pszToolName Name of the tool being used, e.g. "vbt_ls". + * @param uVersion Stream version name. Handy for distinguishing + * different stream versions later. + */ +static void vgsvcToolboxPrintStrmHeader(const char *pszToolName, uint32_t uVersion) +{ + AssertPtrReturnVoid(pszToolName); + RTPrintf("hdr_id=%s%chdr_ver=%u%c", pszToolName, 0, uVersion, 0); +} + + +/** + * Prints a standardized termination sequence indicating that the + * parseable stream just ended. + * + */ +static void vgsvcToolboxPrintStrmTermination() +{ + RTPrintf("%c%c%c%c", 0, 0, 0, 0); +} + + +/** + * Parse a file mode string from the command line (currently octal only) + * and print an error message and return an error if necessary. + */ +static int vgsvcToolboxParseMode(const char *pcszMode, RTFMODE *pfMode) +{ + int rc = RTStrToUInt32Ex(pcszMode, NULL, 8 /* Base */, pfMode); + if (RT_FAILURE(rc)) /* Only octet based values supported right now! */ + RTMsgError("Mode flag strings not implemented yet! Use octal numbers instead. (%s)\n", pcszMode); + return rc; +} + + +/** + * Destroys a path buffer list. + * + * @return IPRT status code. + * @param pList Pointer to list to destroy. + */ +static void vgsvcToolboxPathBufDestroy(PRTLISTNODE pList) +{ + AssertPtr(pList); + /** @todo use RTListForEachSafe */ + PVBOXSERVICETOOLBOXPATHENTRY pNode = RTListGetFirst(pList, VBOXSERVICETOOLBOXPATHENTRY, Node); + while (pNode) + { + PVBOXSERVICETOOLBOXPATHENTRY pNext = RTListNodeIsLast(pList, &pNode->Node) + ? NULL + : RTListNodeGetNext(&pNode->Node, VBOXSERVICETOOLBOXPATHENTRY, Node); + RTListNodeRemove(&pNode->Node); + + RTStrFree(pNode->pszName); + + RTMemFree(pNode); + pNode = pNext; + } +} + + +/** + * 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 + { + PCRTSTATUSMSG pMsg = RTErrGet(rc); + if (pMsg) + RTMsgError("Could not open input file '%s': %s\n", pNodeIt->pszName, pMsg->pszMsgFull); + else + RTMsgError("Could not open input file '%s', rc=%Rrc\n", pNodeIt->pszName, rc); + } + + if (RT_FAILURE(rc)) + break; + } + + /* If no input files were defined, process stdin. */ + if (RTListNodeIsFirst(&inputList, &inputList)) + rc = vgsvcToolboxCatOutput(hInput, hOutput); + } + } + + if (hOutput != NIL_RTFILE) + RTFileClose(hOutput); + vgsvcToolboxPathBufDestroy(&inputList); + + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_ACCESS_DENIED: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED; + + case VERR_FILE_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND; + + case VERR_PATH_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND; + + case VERR_SHARING_VIOLATION: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION; + + case VERR_IS_A_DIRECTORY: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_IS_A_DIRECTORY; + + default: +#ifdef DEBUG_andy + AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc)); +#endif + break; + } + + return RTEXITCODE_FAILURE; + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Resolves the UID to a name as best as we can. + * + * @returns Read-only name string. Only valid till the next cache call. + * @param pIdCache The ID cache. + * @param uid The UID to resolve. + * @param pszEntry The filename of the UID. + * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute. + */ +static const char *vgsvcToolboxIdCacheGetUidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTUID uid, + const char *pszEntry, const char *pszRelativeTo) +{ + /* Check cached entries. */ + for (uint32_t i = 0; i < pIdCache->cEntries; i++) + if ( pIdCache->aEntries[i].id == uid + && pIdCache->aEntries[i].fIsUid) + return pIdCache->aEntries[i].szName; + + /* Miss. */ + RTFSOBJINFO ObjInfo; + RT_ZERO(ObjInfo); /* shut up msc */ + int rc; + if (!pszRelativeTo) + rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK); + else + { + char szPath[RTPATH_MAX]; + rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry); + if (RT_SUCCESS(rc)) + rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK); + } + + if ( RT_SUCCESS(rc) + && ObjInfo.Attr.u.UnixOwner.uid == uid) + { + uint32_t i = pIdCache->cEntries; + if (i < RT_ELEMENTS(pIdCache->aEntries)) + pIdCache->cEntries = i + 1; + else + i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries); + pIdCache->aEntries[i].id = uid; + pIdCache->aEntries[i].fIsUid = true; + RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixOwner.szName); + return pIdCache->aEntries[i].szName; + } + return ""; +} + + +/** + * Resolves the GID to a name as best as we can. + * + * @returns Read-only name string. Only valid till the next cache call. + * @param pIdCache The ID cache. + * @param gid The GID to resolve. + * @param pszEntry The filename of the GID. + * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute. + */ +static const char *vgsvcToolboxIdCacheGetGidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTGID gid, + const char *pszEntry, const char *pszRelativeTo) +{ + /* Check cached entries. */ + for (uint32_t i = 0; i < pIdCache->cEntries; i++) + if ( pIdCache->aEntries[i].id == gid + && !pIdCache->aEntries[i].fIsUid) + return pIdCache->aEntries[i].szName; + + /* Miss. */ + RTFSOBJINFO ObjInfo; + RT_ZERO(ObjInfo); /* shut up msc */ + int rc; + if (!pszRelativeTo) + rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK); + else + { + char szPath[RTPATH_MAX]; + rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry); + if (RT_SUCCESS(rc)) + rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK); + } + + if ( RT_SUCCESS(rc) + && ObjInfo.Attr.u.UnixGroup.gid == gid) + { + uint32_t i = pIdCache->cEntries; + if (i < RT_ELEMENTS(pIdCache->aEntries)) + pIdCache->cEntries = i + 1; + else + i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries); + pIdCache->aEntries[i].id = gid; + pIdCache->aEntries[i].fIsUid = false; + RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixGroup.szName); + return pIdCache->aEntries[i].szName; + } + return ""; +} + + +/** + * Prints information (based on given flags) of a file system object (file/directory/...) + * to stdout. + * + * @return IPRT status code. + * @param pszName Object name. + * @param cchName Length of pszName. + * @param fOutputFlags Output / handling flags of type + * VBOXSERVICETOOLBOXOUTPUTFLAG. + * @param pszRelativeTo What pszName is relative to. + * @param pIdCache The ID cache. + * @param pObjInfo Pointer to object information. + */ +static int vgsvcToolboxPrintFsInfo(const char *pszName, size_t cchName, uint32_t fOutputFlags, const char *pszRelativeTo, + PVGSVCTOOLBOXIDCACHE pIdCache, PRTFSOBJINFO pObjInfo) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertReturn(cchName, VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER); + + RTFMODE fMode = pObjInfo->Attr.fMode; + char chFileType; + switch (fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_FIFO: chFileType = 'f'; break; + case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break; + case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break; + case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break; + case RTFS_TYPE_FILE: chFileType = '-'; break; + case RTFS_TYPE_SYMLINK: chFileType = 'l'; break; + case RTFS_TYPE_SOCKET: chFileType = 's'; break; + case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break; + default: chFileType = '?'; break; + } + /** @todo sticy bits++ */ + +/** @todo r=bird: turns out the host doesn't use or need cname_len, so perhaps we could drop it? */ + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_LONG)) + { + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + RTPrintf("ftype=%c%cnode_id=%RU64%inode_dev=%RU32%ccname_len=%zu%cname=%s%c", + chFileType, 0, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0, + (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0, cchName, 0, pszName, 0); + RTPrintf("%c%c", 0, 0); + } + else + RTPrintf("%c %#18llx %3zu %s\n", chFileType, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, cchName, pszName); + } + else + { + char szTimeBirth[RTTIME_STR_LEN]; + char szTimeChange[RTTIME_STR_LEN]; + char szTimeModification[RTTIME_STR_LEN]; + char szTimeAccess[RTTIME_STR_LEN]; + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + RTPrintf("ftype=%c%c", chFileType, 0); + if (pObjInfo->Attr.u.Unix.INodeId || pObjInfo->Attr.u.Unix.INodeIdDevice) + RTPrintf("node_id=%RU64%cinode_dev=%RU32%c", (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0, + (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0); + RTPrintf("owner_mask=%c%c%c%c", + fMode & RTFS_UNIX_IRUSR ? 'r' : '-', + fMode & RTFS_UNIX_IWUSR ? 'w' : '-', + fMode & RTFS_UNIX_IXUSR ? 'x' : '-', 0); + RTPrintf("group_mask=%c%c%c%c", + fMode & RTFS_UNIX_IRGRP ? 'r' : '-', + fMode & RTFS_UNIX_IWGRP ? 'w' : '-', + fMode & RTFS_UNIX_IXGRP ? 'x' : '-', 0); + RTPrintf("other_mask=%c%c%c%c", + fMode & RTFS_UNIX_IROTH ? 'r' : '-', + fMode & RTFS_UNIX_IWOTH ? 'w' : '-', + fMode & RTFS_UNIX_IXOTH ? 'x' : '-', 0); + /** @todo sticky bits. */ + RTPrintf("dos_mask=%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", + fMode & RTFS_DOS_READONLY ? 'R' : '-', + fMode & RTFS_DOS_HIDDEN ? 'H' : '-', + fMode & RTFS_DOS_SYSTEM ? 'S' : '-', + fMode & RTFS_DOS_DIRECTORY ? 'D' : '-', + fMode & RTFS_DOS_ARCHIVED ? 'A' : '-', + fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-', + fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-', + fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-', + fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-', + fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-', + fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-', + fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-', + fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-', + fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-', 0); + RTPrintf("hlinks=%RU32%cst_size=%RI64%calloc=%RI64%c", + pObjInfo->Attr.u.Unix.cHardlinks, 0, + pObjInfo->cbObject, 0, + pObjInfo->cbAllocated, 0); + RTPrintf("st_birthtime=%s%cst_ctime=%s%cst_mtime=%s%cst_atime=%s%c", + RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), 0, + RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), 0, + RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), 0, + RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)), 0); + if (pObjInfo->Attr.u.Unix.uid != NIL_RTUID) + RTPrintf("uid=%RU32%cusername=%s%c", pObjInfo->Attr.u.Unix.uid, 0, + vgsvcToolboxIdCacheGetUidName(pIdCache, pObjInfo->Attr.u.Unix.uid, pszName, pszRelativeTo), 0); + if (pObjInfo->Attr.u.Unix.gid != NIL_RTGID) + RTPrintf("gid=%RU32%cgroupname=%s%c", pObjInfo->Attr.u.Unix.gid, 0, + vgsvcToolboxIdCacheGetGidName(pIdCache, pObjInfo->Attr.u.Unix.gid, pszName, pszRelativeTo), 0); + if ( (RTFS_IS_DEV_BLOCK(pObjInfo->Attr.fMode) || RTFS_IS_DEV_CHAR(pObjInfo->Attr.fMode)) + && pObjInfo->Attr.u.Unix.Device) + RTPrintf("st_rdev=%RU32%c", pObjInfo->Attr.u.Unix.Device, 0); + if (pObjInfo->Attr.u.Unix.GenerationId) + RTPrintf("st_gen=%RU32%c", pObjInfo->Attr.u.Unix.GenerationId, 0); + if (pObjInfo->Attr.u.Unix.fFlags) + RTPrintf("st_flags=%RU32%c", pObjInfo->Attr.u.Unix.fFlags, 0); + RTPrintf("cname_len=%zu%cname=%s%c", cchName, 0, pszName, 0); + RTPrintf("%c%c", 0, 0); /* End of data block. */ + } + else + { + RTPrintf("%c", chFileType); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IRUSR ? 'r' : '-', + fMode & RTFS_UNIX_IWUSR ? 'w' : '-', + fMode & RTFS_UNIX_IXUSR ? 'x' : '-'); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IRGRP ? 'r' : '-', + fMode & RTFS_UNIX_IWGRP ? 'w' : '-', + fMode & RTFS_UNIX_IXGRP ? 'x' : '-'); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IROTH ? 'r' : '-', + fMode & RTFS_UNIX_IWOTH ? 'w' : '-', + fMode & RTFS_UNIX_IXOTH ? 'x' : '-'); + RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c", + fMode & RTFS_DOS_READONLY ? 'R' : '-', + fMode & RTFS_DOS_HIDDEN ? 'H' : '-', + fMode & RTFS_DOS_SYSTEM ? 'S' : '-', + fMode & RTFS_DOS_DIRECTORY ? 'D' : '-', + fMode & RTFS_DOS_ARCHIVED ? 'A' : '-', + fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-', + fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-', + fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-', + fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-', + fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-', + fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-', + fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-', + fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-', + fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-'); + RTPrintf(" %d %4d %4d %10lld %10lld", + pObjInfo->Attr.u.Unix.cHardlinks, + pObjInfo->Attr.u.Unix.uid, + pObjInfo->Attr.u.Unix.gid, + pObjInfo->cbObject, + pObjInfo->cbAllocated); + RTPrintf(" %s %s %s %s", + RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), + RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), + RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), + RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) ); + RTPrintf(" %2zu %s\n", cchName, pszName); + } + } + + return VINF_SUCCESS; +} + + +/** + * Helper routine for ls tool doing the actual parsing and output of + * a specified directory. + * + * @return IPRT status code. + * @param pszDir Directory (path) 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); + + if (fFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + RTPrintf("dname=%s%c", pszDir, 0); + else if (fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE) + RTPrintf("%s:\n", pszDir); + + char szPathAbs[RTPATH_MAX + 1]; + int rc = RTPathAbs(pszDir, szPathAbs, sizeof(szPathAbs)); + if (RT_FAILURE(rc)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Failed to retrieve absolute path of '%s', rc=%Rrc\n", pszDir, rc); + return rc; + } + + RTDIR hDir; + rc = RTDirOpen(&hDir, szPathAbs); + if (RT_FAILURE(rc)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Failed to open directory '%s', rc=%Rrc\n", szPathAbs, rc); + return rc; + } + + RTLISTANCHOR dirList; + RTListInit(&dirList); + + /* To prevent races we need to read in the directory entries once + * and process them afterwards: First loop is displaying the current + * directory's content and second loop is diving deeper into + * sub directories (if wanted). */ +/** @todo r=bird: Which races are these exactly??? Please, do considering that directory with half a + * million files in it, because this isn't going to fly well there (especially not in the recursive case)... + * So, this needs to be rewritten unless there is an actual race you're avoiding by doing this! */ + do + { + RTDIRENTRYEX DirEntry; + rc = RTDirReadEx(hDir, &DirEntry, NULL, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICETOOLBOXDIRENTRY pNode = (PVBOXSERVICETOOLBOXDIRENTRY)RTMemAlloc(sizeof(VBOXSERVICETOOLBOXDIRENTRY)); + if (pNode) + { + memcpy(&pNode->dirEntry, &DirEntry, sizeof(RTDIRENTRYEX)); + RTListAppend(&dirList, &pNode->Node); + } + else + rc = VERR_NO_MEMORY; + } + /** @todo r=bird: missing DirEntry overflow handling. */ + } while (RT_SUCCESS(rc)); + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + int rc2 = RTDirClose(hDir); + if (RT_FAILURE(rc2)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Failed to close dir '%s', rc=%Rrc\n", pszDir, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (RT_SUCCESS(rc)) + { + PVBOXSERVICETOOLBOXDIRENTRY pNodeIt; + RTListForEach(&dirList, pNodeIt, VBOXSERVICETOOLBOXDIRENTRY, Node) + { + rc = vgsvcToolboxPrintFsInfo(pNodeIt->dirEntry.szName, pNodeIt->dirEntry.cbName, fOutputFlags, + szPathAbs, pIdCache, &pNodeIt->dirEntry.Info); + if (RT_FAILURE(rc)) + break; + } + + /* If everything went fine we do the second run (if needed) ... */ + if ( RT_SUCCESS(rc) + && (fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE)) + { + /* Process all sub-directories. */ + RTListForEach(&dirList, pNodeIt, VBOXSERVICETOOLBOXDIRENTRY, Node) + { + RTFMODE fMode = pNodeIt->dirEntry.Info.Attr.fMode; + switch (fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_SYMLINK: + if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS)) + break; + RT_FALL_THRU(); + case RTFS_TYPE_DIRECTORY: + { + const char *pszName = pNodeIt->dirEntry.szName; + if ( !RTStrICmp(pszName, ".") /** @todo r=bird: Please do explain what the upper/lower casing of '.' is! I'm really curious. */ + || !RTStrICmp(pszName, "..")) /** @todo r=bird: There is a RTDir API for checking these. Use it! */ + { + /* Skip dot directories. */ + continue; + } + + char szPath[RTPATH_MAX]; /** @todo r=bird: This is going to kill your stack pretty quickly if deep + * directory nesting. There is another buffer further up the function too. + * You need to share the path buffer between recursions! There should be + * several examples of how to efficiently traverse a tree. */ + rc = RTPathJoin(szPath, sizeof(szPath), pszDir, pNodeIt->dirEntry.szName); + if (RT_SUCCESS(rc)) + rc = vgsvcToolboxLsHandleDir(szPath, fFlags, fOutputFlags, pIdCache); + break; + } + + default: /* Ignore the rest. */ + break; + } + if (RT_FAILURE(rc)) + break; + } + } + } + + /* Clean up the mess. */ + PVBOXSERVICETOOLBOXDIRENTRY pNode, pSafe; + RTListForEachSafe(&dirList, pNode, pSafe, VBOXSERVICETOOLBOXDIRENTRY, Node) + { + RTListNodeRemove(&pNode->Node); + RTMemFree(pNode); + } + return rc; +} + + +/** @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)) + && RT_SUCCESS(rc) /** @todo r=bird: WTF is this doing here? rc isn't set in the loop!! And there is an AssertRCReturn after the previous place it was set. */) + { + /* 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; + } + + if (RT_SUCCESS(rc)) /** @todo r=bird: WTF?!? The state handling here is certifiably insane. Crap like this drives me CRAZY!! */ + { + /* 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); + + ch = RTGetOpt(&GetState, &ValueUnion); + do + { + char *pszEntry = NULL; /** @todo r=bird: Bad name choice. pszEntry sounds like RTDIRENTRY::szName, i.e. no path. */ + + if (ch == 0) /* Use current directory if no element specified. */ + { + char szDirCur[RTPATH_MAX + 1]; /** @todo r=bird: Just put this outside the if(ch==0) and make pszEntry point to it. There is no need to duplicate any strings here! */ + rc = RTPathGetCurrent(szDirCur, sizeof(szDirCur)); + if (RT_FAILURE(rc)) + RTMsgError("Getting current directory failed, rc=%Rrc\n", rc); + + pszEntry = RTStrDup(szDirCur); + if (!pszEntry) + RTMsgError("Allocating current directory failed\n"); + } + else + { + pszEntry = RTStrDup(ValueUnion.psz); + if (!pszEntry) + RTMsgError("Allocating directory '%s' failed\n", ValueUnion.psz); + } + + /** @todo r=bird: RTFileExists == RTPathQueryInfo, so just do + * RTPathQueryInfoEx here! Also, you _need_ to figure out whether or + * not to follow "commandline" links! */ + if (RTFileExists(pszEntry)) + { + RTFSOBJINFO objInfo; + int rc2 = RTPathQueryInfoEx(pszEntry, &objInfo, + RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK /** @todo Follow link? */); + if (RT_FAILURE(rc2)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Cannot access '%s': No such file or directory\n", pszEntry); + rc = VERR_FILE_NOT_FOUND; + /* Do not break here -- process every element in the list + * and keep failing rc. */ + } + else + { + rc2 = vgsvcToolboxPrintFsInfo(pszEntry, strlen(pszEntry), fOutputFlags, NULL, &IdCache, &objInfo); + if (RT_FAILURE(rc2)) + rc = rc2; + } + } + else + { + int rc2 = vgsvcToolboxLsHandleDir(pszEntry, fFlags, fOutputFlags, &IdCache); + if (RT_FAILURE(rc2)) + rc = rc2; + } + + RTStrFree(pszEntry); + } while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0); + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + vgsvcToolboxPrintStrmTermination(); + } + else if (fVerbose) + RTMsgError("Failed with rc=%Rrc\n", rc); + + 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); + /** @todo r=bird: You're checking rc not rc2 here... */ + 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; + + 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")) + return false; + + /* No tool specified? Show toolbox help. */ + if (argc < 3) + { + vgsvcToolboxShowUsage(); + *prcExit = RTEXITCODE_SYNTAX; + return true; + } + + argc -= 2; + argv += 2; + pszTool = argv[0]; + pTool = vgsvcToolboxLookUp(pszTool); + if (!pTool) + { + *prcExit = RTEXITCODE_SUCCESS; + if (!strcmp(pszTool, "-V")) + { + vgsvcToolboxShowVersion(); + return true; + } + if ( strcmp(pszTool, "help") + && strcmp(pszTool, "--help") + && strcmp(pszTool, "-h")) + *prcExit = RTEXITCODE_SYNTAX; + vgsvcToolboxShowUsage(); + 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..f6c87aa7 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h @@ -0,0 +1,32 @@ +/* $Id: VBoxServiceToolBox.h $ */ +/** @file + * VBoxService - Toolbox header for sharing defines between toolbox binary and VBoxService. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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..81cb41ca --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp @@ -0,0 +1,401 @@ +/* $Id: VBoxServiceUtils.cpp $ */ +/** @file + * VBoxServiceUtils - Some utility functions. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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. + * + * @returns VBox status code, fully bitched. + * + * @param u32ClientId 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. Free it using RTStrFree(). Optional. + * @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 VGSvcReadProp(uint32_t u32ClientId, 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) + { + VGSvcError("Guest Property: Failed to allocate %zu bytes\n", cbBuf); + rc = VERR_NO_MEMORY; + break; + } + char *pszValue; + char *pszFlags; + uint64_t uTimestamp; + rc = VbglR3GuestPropRead(u32ClientId, 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; + } + if (rc == VERR_NOT_FOUND) + VGSvcVerbose(2, "Guest Property: %s not found\n", pszPropName); + else + VGSvcError("Guest Property: Failed to query '%s': %Rrc\n", pszPropName, rc); + break; + } + + VGSvcVerbose(2, "Guest Property: Read '%s' = '%s', timestamp %RU64n\n", pszPropName, pszValue, uTimestamp); + if (ppszValue) + { + *ppszValue = RTStrDup(pszValue); + if (!*ppszValue) + { + VGSvcError("Guest Property: RTStrDup failed for '%s'\n", pszValue); + rc = VERR_NO_MEMORY; + break; + } + } + + if (puTimestamp) + *puTimestamp = uTimestamp; + if (ppszFlags) + *ppszFlags = RTStrDup(pszFlags); + break; /* done */ + } + + if (pvBuf) + RTMemFree(pvBuf); + return rc; +} + + +/** + * 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 = VGSvcReadProp(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; +} + +/** + * Checks if @a pszPropName exists. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if it exists. + * @retval VERR_NOT_FOUND if not found. + * + * @param u32ClientId The HGCM client ID for the guest property session. + * @param pszPropName The property name. + */ +int VGSvcCheckPropExist(uint32_t u32ClientId, const char *pszPropName) +{ + return VGSvcReadProp(u32ClientId, pszPropName, NULL /*ppszValue*/, NULL /* ppszFlags */, NULL /* puTimestamp */); +} + + +/** + * 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 = VGSvcReadProp(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, PDWORD pdwMajor, PDWORD pdwMinor, PDWORD pdwBuildNumber, + PDWORD pdwRevisionNumber) +{ + UINT cchStrValue = 0; + LPTSTR pStrValue = NULL; + if (!VerQueryValueA(pVerData, "\\StringFileInfo\\040904b0\\FileVersion", (LPVOID *)&pStrValue, &cchStrValue)) + return false; + + /** @todo r=bird: get rid of this. Avoid sscanf like the plague! */ + if (sscanf(pStrValue, "%ld.%ld.%ld.%ld", pdwMajor, pdwMinor, pdwBuildNumber, pdwRevisionNumber) != 4) + return false; + + return true; +} + + +/** + * Worker for VGSvcUtilWinGetFileVersionString. + * + * @returns VBox status code. + * @param pszFilename ASCII & ANSI & UTF-8 compliant name. + * @param pdwMajor Where to return the major version number. + * @param pdwMinor Where to return the minor version number. + * @param pdwBuildNumber Where to return the build number. + * @param pdwRevisionNumber Where to return the revision number. + */ +static int vgsvcUtilGetFileVersion(const char *pszFilename, PDWORD pdwMajor, PDWORD pdwMinor, PDWORD pdwBuildNumber, + PDWORD pdwRevisionNumber) +{ + int rc; + + *pdwMajor = *pdwMinor = *pdwBuildNumber = *pdwRevisionNumber = 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, pdwMajor, pdwMinor, pdwBuildNumber, pdwRevisionNumber)) + rc = VINF_SUCCESS; + else + { + /* Fall back on VS_FIXEDFILEINFO */ + UINT cbFileInfoIgnored = 0; + VS_FIXEDFILEINFO *pFileInfo = NULL; + if (VerQueryValue(pVerData, "\\", (LPVOID *)&pFileInfo, &cbFileInfoIgnored)) + { + *pdwMajor = HIWORD(pFileInfo->dwFileVersionMS); + *pdwMinor = LOWORD(pFileInfo->dwFileVersionMS); + *pdwBuildNumber = HIWORD(pFileInfo->dwFileVersionLS); + *pdwRevisionNumber = 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)) + { + DWORD dwMajor, dwMinor, dwBuild, dwRev; + rc = vgsvcUtilGetFileVersion(szFullPath, &dwMajor, &dwMinor, &dwBuild, &dwRev); + if (RT_SUCCESS(rc)) + RTStrPrintf(pszVersion, cbVersion, "%u.%u.%ur%u", dwMajor, dwMinor, dwBuild, dwRev); + } + 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..21038b0b --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h @@ -0,0 +1,40 @@ +/* $Id: VBoxServiceUtils.h $ */ +/** @file + * VBoxServiceUtils - Guest Additions Services (Utilities). + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 VGSvcCheckPropExist(uint32_t u32ClientId, const char *pszPropName); +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..f46cb861 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp @@ -0,0 +1,1388 @@ +/* $Id: VBoxServiceVMInfo-win.cpp $ */ +/** @file + * VBoxService - Virtual Machine Information for the Host, Windows specifics. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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. */ + +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; + + +/********************************************************************************************************************************* +* 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 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; + +/** @} */ + +/** Windows version. */ +static OSVERSIONINFOEXA g_WinVersion; + + +/** + * An RTOnce callback function. + */ +static DECLCALLBACK(int) vgsvcWinVmInfoInitOnce(void *pvIgnored) +{ + RT_NOREF1(pvIgnored); + + /* SECUR32 */ + RTLDRMOD hLdrMod; + int rc = RTLdrLoadSystem("secur32.dll", true, &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(g_WinVersion.dwMajorVersion < 5); + } + + /* WTSAPI32 */ + rc = RTLdrLoadSystem("wtsapi32.dll", true, &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(g_WinVersion.dwMajorVersion < 5); + } + + /* PSAPI */ + rc = RTLdrLoadSystem("psapi.dll", true, &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(g_WinVersion.dwMajorVersion < 5); + } + + /* Kernel32: */ + rc = RTLdrLoadSystem("kernel32.dll", true, &hLdrMod); + AssertRCReturn(rc, rc); + rc = RTLdrGetSymbol(hLdrMod, "QueryFullProcessImageNameW", (void **)&g_pfnQueryFullProcessImageNameW); + if (RT_FAILURE(rc)) + { + Assert(g_WinVersion.dwMajorVersion < 6); + g_pfnQueryFullProcessImageNameW = NULL; + } + RTLdrClose(hLdrMod); + + /* + * Get the extended windows version once and for all. + */ + g_WinVersion.dwOSVersionInfoSize = sizeof(g_WinVersion); + if (!GetVersionExA((OSVERSIONINFO *)&g_WinVersion)) + { + RT_ZERO(g_WinVersion); + g_WinVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if (!GetVersionExA((OSVERSIONINFO *)&g_WinVersion)) + { + AssertFailed(); + RT_ZERO(g_WinVersion); + } + } + + return VINF_SUCCESS; +} + + +static bool vgsvcVMInfoSession0Separation(void) +{ + return g_WinVersion.dwPlatformId == VER_PLATFORM_WIN32_NT + && g_WinVersion.dwMajorVersion >= 6; /* Vista = 6.0 */ +} + + +/** + * Retrieves the module name of a given process. + * + * @return IPRT status code. + */ +static int vgsvcVMInfoWinProcessesGetModuleNameA(PVBOXSERVICEVMINFOPROC const pProc, PRTUTF16 *ppszName) +{ + AssertPtrReturn(pProc, VERR_INVALID_POINTER); + AssertPtrReturn(ppszName, VERR_INVALID_POINTER); + + /** @todo Only do this once. Later. */ + /* Platform other than NT (e.g. Win9x) not supported. */ + if (g_WinVersion.dwPlatformId != VER_PLATFORM_WIN32_NT) + return VERR_NOT_SUPPORTED; + + int rc = VINF_SUCCESS; + + DWORD dwFlags = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + if (g_WinVersion.dwMajorVersion >= 6 /* Vista or later */) + dwFlags = PROCESS_QUERY_LIMITED_INFORMATION; /* possible to do on more processes */ + + HANDLE h = OpenProcess(dwFlags, FALSE, pProc->id); + if (h == NULL) + { + DWORD dwErr = GetLastError(); + if (g_cVerbosity) + VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr); + rc = RTErrConvertFromWin32(dwErr); + } + else + { + /* 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. */ + WCHAR wszName[_1K]; + DWORD dwLen = sizeof(wszName); /** @todo r=bird: wrong? */ + + /* Use QueryFullProcessImageNameW if available (Vista+). */ + if (g_pfnQueryFullProcessImageNameW) + { + if (!g_pfnQueryFullProcessImageNameW(h, 0 /*PROCESS_NAME_NATIVE*/, wszName, &dwLen)) + rc = VERR_ACCESS_DENIED; + } + else if (!g_pfnGetModuleFileNameExW(h, NULL /* Get main executable */, wszName, dwLen)) + rc = VERR_ACCESS_DENIED; + + if ( RT_FAILURE(rc) + && g_cVerbosity > 3) + VGSvcError("Unable to retrieve process name for PID=%u, error=%u\n", pProc->id, GetLastError()); + else + { + PRTUTF16 pszName = RTUtf16Dup(wszName); + if (pszName) + *ppszName = pszName; + else + rc = VERR_NO_MEMORY; + } + + CloseHandle(h); + } + + 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 = vgsvcVMInfoWinProcessesGetModuleNameA(&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); + + /** + * Note: On certain Windows OSes WTSQuerySessionInformation leaks memory when used + * under a heavy stress situation. There are hotfixes available from Microsoft. + * + * See: http://support.microsoft.com/kb/970910 + */ + if (!s_fSkipRDPDetection) + { + /* Skip RDP detection on non-NT systems. */ + if (g_WinVersion.dwPlatformId != VER_PLATFORM_WIN32_NT) + s_fSkipRDPDetection = true; + + /* Skip RDP detection on Windows 2000. + * For Windows 2000 however we don't have any hotfixes, so just skip the + * RDP detection in any case. */ + if ( g_WinVersion.dwMajorVersion == 5 + && g_WinVersion.dwMinorVersion == 0) + s_fSkipRDPDetection = true; + + /* Skip if we don't have the WTS API. */ + if (!g_pfnWTSQuerySessionInformationA) + s_fSkipRDPDetection = true; + + 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, + /* .uHdrVersion = */ 0, + /* .uMsgType = */ VBOXTRAYIPCMSGTYPE_USERLASTINPUT, + /* .cbMsgData = */ 0 /* No msg */ + }; + + rc = RTLocalIpcSessionWrite(hSession, &ipcHdr, sizeof(ipcHdr)); + + if (RT_SUCCESS(rc)) + { + VBOXTRAYIPCRES_USERLASTINPUT ipcRes; + rc = RTLocalIpcSessionRead(hSession, &ipcRes, sizeof(ipcRes), 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. */ + && ipcRes.uLastInput != UINT32_MAX) + { + userState = (ipcRes.uLastInput * 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>", ipcRes.uLastInput, 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", ipcRes.uLastInputMs); + else + rc = vgsvcUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", NULL /* Delete property */); + + if (RT_SUCCESS(rc)) +#endif + } +#ifdef DEBUG + else if (RT_SUCCESS(rc) && ipcRes.uLastInput == 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 ( !wcscmp(userSession.wszUser, pCurUser->wszUser) + && !wcscmp(userSession.wszLogonDomain, pCurUser->wszLogonDomain) + && !wcscmp(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, "VBoxOGLarrayspu.dll" }, + { szSysWowDir, "VBoxOGLcrutil.dll" }, + { szSysWowDir, "VBoxOGLerrorspu.dll" }, + { szSysWowDir, "VBoxOGLpackspu.dll" }, + { szSysWowDir, "VBoxOGLpassthroughspu.dll" }, + { szSysWowDir, "VBoxOGLfeedbackspu.dll" }, + { szSysWowDir, "VBoxOGL.dll" }, +#else /* !RT_ARCH_AMD64 */ + { szSysDir, "VBoxOGLarrayspu.dll" }, + { szSysDir, "VBoxOGLcrutil.dll" }, + { szSysDir, "VBoxOGLerrorspu.dll" }, + { szSysDir, "VBoxOGLpackspu.dll" }, + { szSysDir, "VBoxOGLpassthroughspu.dll" }, + { szSysDir, "VBoxOGLfeedbackspu.dll" }, + { 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..308bd90e --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp @@ -0,0 +1,1689 @@ +/* $Id: VBoxServiceVMInfo.cpp $ */ +/** @file + * VBoxService - Virtual Machine Information for the Host. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/** @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) + { +/** @todo r=bird: RTStrAPrintf returns -1, not zero on failure! */ + if (!RTStrAPrintf(&pszName, "%s%s@%s/%s", g_pszPropCacheValUser, pszUser, pszDomain, pszKey)) + rc = VERR_NO_MEMORY; + } + else + { +/** @todo r=bird: RTStrAPrintf returns -1, not zero on failure! You got it + * right 5 lines further down... */ + if (!RTStrAPrintf(&pszName, "%s%s/%s", g_pszPropCacheValUser, pszUser, pszKey)) + 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 = strcmp(papszUsers[i], 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 + /* */ + if ( !g_pfnGetAdaptersInfo + && ( !g_pfnWSAIoctl + || !g_pfnWSASocketA + || !g_pfnWSAGetLastError + || !g_pfninet_ntoa + || !g_pfnclosesocket) ) + return VINF_SUCCESS; + + ULONG cbAdpInfo = sizeof(IP_ADAPTER_INFO); + IP_ADAPTER_INFO *pAdpInfo = (IP_ADAPTER_INFO *)RTMemAlloc(cbAdpInfo); + if (!pAdpInfo) + { + VGSvcError("VMInfo/Network: Failed to allocate IP_ADAPTER_INFO\n"); + return VERR_NO_MEMORY; + } + DWORD dwRet = g_pfnGetAdaptersInfo ? g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo) : ERROR_NO_DATA; + if (dwRet == ERROR_BUFFER_OVERFLOW) + { + IP_ADAPTER_INFO *pAdpInfoNew = (IP_ADAPTER_INFO*)RTMemRealloc(pAdpInfo, cbAdpInfo); + if (pAdpInfoNew) + { + pAdpInfo = pAdpInfoNew; + dwRet = g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo); + } + } + else if (dwRet == ERROR_NO_DATA) + { + VGSvcVerbose(3, "VMInfo/Network: No network adapters available\n"); + + /* If no network adapters available / present in the + * system we pretend success to not bail out too early. */ + dwRet = ERROR_SUCCESS; + } + if (dwRet != ERROR_SUCCESS) + { + RTMemFree(pAdpInfo); + VGSvcError("VMInfo/Network: Failed to get adapter info: Error %d\n", dwRet); + return RTErrConvertFromWin32(dwRet); + } + + SOCKET sd = g_pfnWSASocketA(AF_INET, SOCK_DGRAM, 0, 0, 0, 0); + if (sd == SOCKET_ERROR) /* Socket invalid. */ + { + int wsaErr = g_pfnWSAGetLastError(); + /* 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 (WSAENETDOWN == wsaErr) + { + VGSvcVerbose(0, "VMInfo/Network: Network is not up yet.\n"); + wsaErr = VINF_SUCCESS; + } + else + VGSvcError("VMInfo/Network: Failed to get a socket: Error %d\n", wsaErr); + if (pAdpInfo) + RTMemFree(pAdpInfo); + 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()); + if (pAdpInfo) + RTMemFree(pAdpInfo); + g_pfnclosesocket(sd); + return RTErrConvertFromWin32(g_pfnWSAGetLastError()); + } + g_pfnclosesocket(sd); + int cIfacesSystem = cbReturned / sizeof(INTERFACE_INFO); + + /** @todo Use GetAdaptersInfo() and GetAdapterAddresses (IPv4 + IPv6) for more information. */ + for (int i = 0; i < cIfacesSystem; ++i) + { + sockaddr_in *pAddress; + u_long nFlags = 0; + if (aInterfaces[i].iiFlags & IFF_LOOPBACK) /* Skip loopback device. */ + continue; + nFlags = aInterfaces[i].iiFlags; + pAddress = (sockaddr_in *)&(aInterfaces[i].iiAddress); + Assert(pAddress); + 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 = (sockaddr_in *) & (aInterfaces[i].iiBroadcastAddress); + 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, nFlags & 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++; + } + if (pAdpInfo) + 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(pCur->ifr_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..45922c78 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h @@ -0,0 +1,32 @@ +/* $Id: VBoxServiceVMInfo.h $ */ +/** @file + * VBoxServiceVMInfo.h - Internal VM info definitions. + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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..1161802b --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk @@ -0,0 +1,34 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for VBoxServicec test cases. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Target lists. +# +PROGRAMS.win += tstUserInfo + +# +# tstUserInfo +# +tstUserInfo_TEMPLATE = VBoxGuestR3Exe +tstUserInfo_SOURCES = \ + tstUserInfo.cpp + +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..452cf898 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp @@ -0,0 +1,76 @@ +/* $Id: tstUserInfo.cpp $ */ +/** @file + * Test case for correct user environment. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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/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", getenv("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..765754d4 --- /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-2019 Oracle Corporation +# +# 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..5cc7d6a0 --- /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-2019 Oracle Corporation + * + * 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..248e9d0b --- /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-2019 Oracle Corporation + * + * 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..38321eeb --- /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-2019 Oracle Corporation + * + * 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..0cbd7258 --- /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-2019 Oracle Corporation + * + * 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..e05ac630 --- /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-2019 Oracle Corporation + * + * 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/crOpenGL/.scm-settings b/src/VBox/Additions/common/crOpenGL/.scm-settings new file mode 100644 index 00000000..e6362722 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/.scm-settings @@ -0,0 +1,62 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for common/crOpenGL. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +# Much of this is externally licensed, but not all, sigh. +--external-copyright --no-convert-tabs + +/array/arrayspu.rc: --no-external-copyright --convert-tabs + +/feedback/feedbackspu.rc: --no-external-copyright --convert-tabs +/feedback/feedback_context.c: --no-external-copyright --convert-tabs + +/pack/packspu.rc: --no-external-copyright --convert-tabs +/pack/packspu_framebuffer.c: --no-external-copyright --convert-tabs +/pack/packspu_getshaders.c: --no-external-copyright --convert-tabs +/pack/packspu_glsl.c: --no-external-copyright --convert-tabs +/pack/packspu_texture.c: --no-external-copyright --convert-tabs + +/passthrough/passthroughspu.rc: --no-external-copyright --convert-tabs + +/.scm-settings: --no-external-copyright --convert-tabs +/dri_drv.c: --no-external-copyright --convert-tabs +/dri_drv.h: --no-external-copyright --convert-tabs +/dri_glx.h: --no-external-copyright --convert-tabs +/egl.c: --no-external-copyright --convert-tabs +/fakedri_drv.c: --no-external-copyright --convert-tabs +/fakedri_drv.h: --no-external-copyright --convert-tabs +/fakedri_glfuncsList.h: --no-external-copyright --convert-tabs --no-fix-header-guards +/fakedri_glxfuncsList.h: --no-external-copyright --convert-tabs --no-fix-header-guards +/glx.c: --no-external-copyright --convert-tabs +/glx_c_exports.c: --no-external-copyright --convert-tabs +/glx_proto.h: --no-external-copyright --convert-tabs +/icd_drv.c: --no-external-copyright --convert-tabs +/icd_drv.h: --no-external-copyright --convert-tabs +/Linux_i386_glxapi_exports.py: --no-external-copyright --convert-tabs +/Makefile.kmk: --no-external-copyright --convert-tabs +/SunOS_i386_exports.py: --no-external-copyright --convert-tabs +/SunOS_i386_exports_dri.py: --no-external-copyright --convert-tabs +/SunOS_i386_glxapi_exports.py: --no-external-copyright --convert-tabs +/VBoxCROGL.rc: --no-external-copyright --convert-tabs +/VBoxICDList.h: --no-external-copyright --no-fix-header-guards + +*_special: --treat-as Makefile + +# Ignore some stuff. +--filter-out-files /COPYRIGHT.LLNL +--filter-out-files /COPYRIGHT.REDHAT +--filter-out-files /LICENSE + diff --git a/src/VBox/Additions/common/crOpenGL/AIX_exports.py b/src/VBox/Additions/common/crOpenGL/AIX_exports.py new file mode 100644 index 00000000..2f55db33 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/AIX_exports.py @@ -0,0 +1,11 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +import entrypoints + +hacks = ["TexImage3D", "MultiDrawElementsEXT" ] + +entrypoints.GenerateEntrypoints(hacks) + diff --git a/src/VBox/Additions/common/crOpenGL/COPYRIGHT.LLNL b/src/VBox/Additions/common/crOpenGL/COPYRIGHT.LLNL new file mode 100644 index 00000000..5fc1a271 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/COPYRIGHT.LLNL @@ -0,0 +1,61 @@ +This Chromium distribution contains information and code which is +covered under the following notice: + +Copyright (c) 2002, The Regents of the University of California. +Produced at the Lawrence Livermore National Laboratory +For details, contact: Randall Frank (rjfrank@llnl.gov). +UCRL-CODE-2002-058 +All rights reserved. + +This file is part of Chromium. For details, see accompanying +documentation. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the disclaimer below. + +Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the disclaimer (as noted below) in the +documentation and/or other materials provided with the distribution. + +Neither the name of the UC/LLNL nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OF THE UNIVERSITY OF +CALIFORNIA, THE U.S. DEPARTMENT OF ENERGY OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +Additional BSD Notice + +1. This notice is required to be provided under our contract with the U.S. + Department of Energy (DOE). This work was produced at the University of + California, Lawrence Livermore National Laboratory under Contract No. + W-7405-ENG-48 with the DOE. + +2. Neither the United States Government nor the University of California + nor any of their employees, makes any warranty, express or implied, or + assumes any liability or responsibility for the accuracy, completeness, + or usefulness of any information, apparatus, product, or process + disclosed, or represents that its use would not infringe privately-owned + rights. + +3. Also, reference herein to any specific commercial products, process, or + services by trade name, trademark, manufacturer or otherwise does not + necessarily constitute or imply its endorsement, recommendation, or + favoring by the United States Government or the University of + California. The views and opinions of authors expressed herein do not + necessarily state or reflect those of the United States Government or + the University of California, and shall not be used for advertising or + product endorsement purposes. diff --git a/src/VBox/Additions/common/crOpenGL/COPYRIGHT.REDHAT b/src/VBox/Additions/common/crOpenGL/COPYRIGHT.REDHAT new file mode 100644 index 00000000..7812243e --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/COPYRIGHT.REDHAT @@ -0,0 +1,29 @@ +This Chromium distribution contains information and code which is +covered under the following notice: + +/* + * Copyright 2001,2002 Red Hat Inc., Durham, North Carolina. + * + * All Rights Reserved. + * + * 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 on 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 (including the + * next paragraph) 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 + * NON-INFRINGEMENT. IN NO EVENT SHALL RED HAT AND/OR THEIR SUPPLIERS + * 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. + */ diff --git a/src/VBox/Additions/common/crOpenGL/DD_glc.py b/src/VBox/Additions/common/crOpenGL/DD_glc.py new file mode 100755 index 00000000..33aee4da --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/DD_glc.py @@ -0,0 +1,139 @@ +from __future__ import print_function +print(""" +/** @file + * VBox OpenGL chromium functions header + */ + +/* + * Copyright (C) 2009-2016 """ """Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ +""") +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +import sys + +import apiutil + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - THIS FILE GENERATED BY THE DD_gl.py SCRIPT */ + +#include "chromium.h" +#include "cr_string.h" +#include "cr_version.h" +#include "stub.h" +#include "dri_drv.h" +#include "cr_gl.h" +""") + +commoncall_special = [ + "ArrayElement", + "Begin", + "CallList", + "CallLists", + "Color3f", + "Color3fv", + "Color4f", + "Color4fv", + "EdgeFlag", + "End", + "EvalCoord1f", + "EvalCoord1fv", + "EvalCoord2f", + "EvalCoord2fv", + "EvalPoint1", + "EvalPoint2", + "FogCoordfEXT", + "FogCoordfvEXT", + "Indexf", + "Indexfv", + "Materialfv", + "MultiTexCoord1fARB", + "MultiTexCoord1fvARB", + "MultiTexCoord2fARB", + "MultiTexCoord2fvARB", + "MultiTexCoord3fARB", + "MultiTexCoord3fvARB", + "MultiTexCoord4fARB", + "MultiTexCoord4fvARB", + "Normal3f", + "Normal3fv", + "SecondaryColor3fEXT", + "SecondaryColor3fvEXT", + "TexCoord1f", + "TexCoord1fv", + "TexCoord2f", + "TexCoord2fv", + "TexCoord3f", + "TexCoord3fv", + "TexCoord4f", + "TexCoord4fv", + "Vertex2f", + "Vertex2fv", + "Vertex3f", + "Vertex3fv", + "Vertex4f", + "Vertex4fv", + "VertexAttrib1fNV", + "VertexAttrib1fvNV", + "VertexAttrib2fNV", + "VertexAttrib2fvNV", + "VertexAttrib3fNV", + "VertexAttrib3fvNV", + "VertexAttrib4fNV", + "VertexAttrib4fvNV", + "VertexAttrib1fARB", + "VertexAttrib1fvARB", + "VertexAttrib2fARB", + "VertexAttrib2fvARB", + "VertexAttrib3fARB", + "VertexAttrib3fvARB", + "VertexAttrib4fARB", + "VertexAttrib4fvARB", + "EvalMesh1", + "EvalMesh2", + "Rectf", + "DrawArrays", + "DrawElements", + "DrawRangeElements" +] + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in keys: + if "Chromium" == apiutil.Category(func_name): + continue + if func_name == "BoundsInfoCR": + continue + + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + + if func_name in commoncall_special: + print("%s vboxDD_gl%s(%s)" % (return_type, func_name, apiutil.MakeDeclarationString(params) )) + else: + if apiutil.MakeDeclarationString(params)=="void": + print("%s vboxDD_gl%s(GLcontext *ctx)" % (return_type, func_name )) + else: + print("%s vboxDD_gl%s(GLcontext *ctx, %s)" % (return_type, func_name, apiutil.MakeDeclarationString(params) )) + print("{") + + if return_type != "void": + print("\treturn ", end=' ') + + print("\tcr_gl%s(%s);" % (func_name, apiutil.MakeCallString(params))) + print("}") + print("") + diff --git a/src/VBox/Additions/common/crOpenGL/DD_glh.py b/src/VBox/Additions/common/crOpenGL/DD_glh.py new file mode 100755 index 00000000..38d9ba60 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/DD_glh.py @@ -0,0 +1,136 @@ +from __future__ import print_function +print(""" +/** @file + * VBox OpenGL chromium functions header + */ + +/* + * Copyright (C) 2009-2016 """ """Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ +""") +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +import sys + +import apiutil + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - THIS FILE GENERATED BY THE DD_gl.py SCRIPT */ +#ifndef __DD_GL_H__ +#define __DD_GL_H__ + +#include "chromium.h" +#include "cr_string.h" +#include "cr_version.h" +#include "stub.h" + +""") + +commoncall_special = [ + "ArrayElement", + "Begin", + "CallList", + "CallLists", + "Color3f", + "Color3fv", + "Color4f", + "Color4fv", + "EdgeFlag", + "End", + "EvalCoord1f", + "EvalCoord1fv", + "EvalCoord2f", + "EvalCoord2fv", + "EvalPoint1", + "EvalPoint2", + "FogCoordfEXT", + "FogCoordfvEXT", + "Indexf", + "Indexfv", + "Materialfv", + "MultiTexCoord1fARB", + "MultiTexCoord1fvARB", + "MultiTexCoord2fARB", + "MultiTexCoord2fvARB", + "MultiTexCoord3fARB", + "MultiTexCoord3fvARB", + "MultiTexCoord4fARB", + "MultiTexCoord4fvARB", + "Normal3f", + "Normal3fv", + "SecondaryColor3fEXT", + "SecondaryColor3fvEXT", + "TexCoord1f", + "TexCoord1fv", + "TexCoord2f", + "TexCoord2fv", + "TexCoord3f", + "TexCoord3fv", + "TexCoord4f", + "TexCoord4fv", + "Vertex2f", + "Vertex2fv", + "Vertex3f", + "Vertex3fv", + "Vertex4f", + "Vertex4fv", + "VertexAttrib1fNV", + "VertexAttrib1fvNV", + "VertexAttrib2fNV", + "VertexAttrib2fvNV", + "VertexAttrib3fNV", + "VertexAttrib3fvNV", + "VertexAttrib4fNV", + "VertexAttrib4fvNV", + "VertexAttrib1fARB", + "VertexAttrib1fvARB", + "VertexAttrib2fARB", + "VertexAttrib2fvARB", + "VertexAttrib3fARB", + "VertexAttrib3fvARB", + "VertexAttrib4fARB", + "VertexAttrib4fvARB", + "EvalMesh1", + "EvalMesh2", + "Rectf", + "DrawArrays", + "DrawElements", + "DrawRangeElements" +] + +# Extern-like declarations +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in keys: + if "Chromium" == apiutil.Category(func_name): + continue + if func_name == "BoundsInfoCR": + continue + + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + + if func_name in commoncall_special: + print("extern %s vboxDD_gl%s(%s);" % (return_type, func_name, + apiutil.MakeDeclarationString( params ))) + else: + if apiutil.MakeDeclarationString(params)=="void": + print("extern %s vboxDD_gl%s(GLcontext *ctx);" % (return_type, func_name)) + else: + print("extern %s vboxDD_gl%s(GLcontext *ctx, %s);" % (return_type, func_name, + apiutil.MakeDeclarationString( params ))) + +print("#endif /* __DD_GL_H__ */") diff --git a/src/VBox/Additions/common/crOpenGL/Darwin_exports.py b/src/VBox/Additions/common/crOpenGL/Darwin_exports.py new file mode 100644 index 00000000..c3120e4d --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/Darwin_exports.py @@ -0,0 +1,12 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +import entrypoints + +hacks = ["TexImage3D", "TexImage2D", "TexImage1D", "MultiDrawArrays", + "BufferData", "BufferSubData", "GetBufferSubData" ] + +entrypoints.GenerateEntrypoints(hacks) + diff --git a/src/VBox/Additions/common/crOpenGL/FreeBSD_exports.py b/src/VBox/Additions/common/crOpenGL/FreeBSD_exports.py new file mode 100644 index 00000000..8f63e0a5 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/FreeBSD_exports.py @@ -0,0 +1,11 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +import entrypoints + +hacks = [] + +entrypoints.GenerateEntrypoints(hacks) + diff --git a/src/VBox/Additions/common/crOpenGL/IRIX64_exports.py b/src/VBox/Additions/common/crOpenGL/IRIX64_exports.py new file mode 100644 index 00000000..674efff1 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/IRIX64_exports.py @@ -0,0 +1,11 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +import entrypoints + +hacks = ["TexImage3D", "EdgeFlagPointer" ] + +entrypoints.GenerateEntrypoints(hacks) + diff --git a/src/VBox/Additions/common/crOpenGL/LICENSE b/src/VBox/Additions/common/crOpenGL/LICENSE new file mode 100644 index 00000000..d609a358 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/LICENSE @@ -0,0 +1,32 @@ +Copyright (c) 2002, Stanford University +All rights reserved. + +Some portions of Chromium are copyrighted by individual organizations. +Please see the files COPYRIGHT.LLNL and COPYRIGHT.REDHAT for more +information. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of Stanford University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/src/VBox/Additions/common/crOpenGL/Linux_exports.py b/src/VBox/Additions/common/crOpenGL/Linux_exports.py new file mode 100644 index 00000000..79dc8ddd --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/Linux_exports.py @@ -0,0 +1,10 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +import entrypoints + +hacks = [] + +entrypoints.GenerateEntrypoints(hacks) diff --git a/src/VBox/Additions/common/crOpenGL/Linux_i386_exports.py b/src/VBox/Additions/common/crOpenGL/Linux_i386_exports.py new file mode 100755 index 00000000..653b00a2 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/Linux_i386_exports.py @@ -0,0 +1,96 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + + +from __future__ import print_function +import sys + +import apiutil + + +def GenerateEntrypoints(): + + #apiutil.CopyrightC() + + # Get sorted list of dispatched functions. + # The order is very important - it must match cr_opcodes.h + # and spu_dispatch_table.h + print('%include "iprt/asmdefs.mac"') + print("") + print("%ifdef RT_ARCH_AMD64") + print("extern glim") + print("%else ; X86") + print("extern glim") + print("%endif") + print("") + + keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + + for index in range(len(keys)): + func_name = keys[index] + if apiutil.Category(func_name) == "Chromium": + continue + if apiutil.Category(func_name) == "VBox": + continue + + print("BEGINPROC_EXPORTED gl%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tmov \trax, qword glim+%d" % (8*index)) + print("\tjmp \t[rax]") + print("%else ; X86") + print("\tmov \teax, dword glim+%d" % (4*index)) + print("\tjmp \t[eax]") + print("%endif") + print("ENDPROC gl%s" % func_name) + print("") + + + print(';') + print('; Aliases') + print(';') + + # Now loop over all the functions and take care of any aliases + allkeys = apiutil.GetAllFunctions(sys.argv[1]+"/APIspec.txt") + for func_name in allkeys: + if "omit" in apiutil.ChromiumProps(func_name): + continue + + if func_name in keys: + # we already processed this function earlier + continue + + # alias is the function we're aliasing + alias = apiutil.Alias(func_name) + if alias: + # this dict lookup should never fail (raise an exception)! + index = keys.index(alias) + print("BEGINPROC_EXPORTED gl%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tmov \trax, qword glim+%d" % (8*index)) + print("\tjmp \t[rax]") + print("%else ; X86") + print("\tmov \teax, dword glim+%d" % (4*index)) + print("\tjmp \t[eax]") + print("%endif") + print("ENDPROC gl%s" % func_name) + print("") + + + print(';') + print('; No-op stubs') + print(';') + + # Now generate no-op stub functions + for func_name in allkeys: + if "stub" in apiutil.ChromiumProps(func_name): + print("BEGINPROC_EXPORTED gl%s" % func_name) + print("\tleave") + print("\tret") + print("ENDPROC gl%s" % func_name) + print("") + + +GenerateEntrypoints() + diff --git a/src/VBox/Additions/common/crOpenGL/Linux_i386_exports_dri.py b/src/VBox/Additions/common/crOpenGL/Linux_i386_exports_dri.py new file mode 100755 index 00000000..e212fb68 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/Linux_i386_exports_dri.py @@ -0,0 +1,96 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + + +from __future__ import print_function +import sys + +import apiutil + + +def GenerateEntrypoints(): + + #apiutil.CopyrightC() + + # Get sorted list of dispatched functions. + # The order is very important - it must match cr_opcodes.h + # and spu_dispatch_table.h + print('%include "iprt/asmdefs.mac"') + print("") + print("%ifdef RT_ARCH_AMD64") + print("extern glim") + print("%else ; X86") + print("extern glim") + print("%endif") + print("") + + keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + + for index in range(len(keys)): + func_name = keys[index] + if apiutil.Category(func_name) == "Chromium": + continue + if apiutil.Category(func_name) == "VBox": + continue + + print("BEGINPROC_EXPORTED cr_gl%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tmov \trax, qword glim+%d" % (8*index)) + print("\tjmp \t[rax]") + print("%else ; X86") + print("\tmov \teax, dword glim+%d" % (4*index)) + print("\tjmp \t[eax]") + print("%endif") + print("ENDPROC cr_gl%s" % func_name) + print("") + + + print(';') + print('; Aliases') + print(';') + + # Now loop over all the functions and take care of any aliases + allkeys = apiutil.GetAllFunctions(sys.argv[1]+"/APIspec.txt") + for func_name in allkeys: + if "omit" in apiutil.ChromiumProps(func_name): + continue + + if func_name in keys: + # we already processed this function earlier + continue + + # alias is the function we're aliasing + alias = apiutil.Alias(func_name) + if alias: + # this dict lookup should never fail (raise an exception)! + index = keys.index(alias) + print("BEGINPROC_EXPORTED cr_gl%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tmov \trax, qword glim+%d" % (8*index)) + print("\tjmp \t[rax]") + print("%else ; X86") + print("\tmov \teax, dword glim+%d" % (4*index)) + print("\tjmp \t[eax]") + print("%endif") + print("ENDPROC cr_gl%s" % func_name) + print("") + + + print(';') + print('; No-op stubs') + print(';') + + # Now generate no-op stub functions + for func_name in allkeys: + if "stub" in apiutil.ChromiumProps(func_name): + print("BEGINPROC_EXPORTED cr_gl%s" % func_name) + print("\tleave") + print("\tret") + print("ENDPROC cr_gl%s" % func_name) + print("") + + +GenerateEntrypoints() + diff --git a/src/VBox/Additions/common/crOpenGL/Linux_i386_glxapi_exports.py b/src/VBox/Additions/common/crOpenGL/Linux_i386_glxapi_exports.py new file mode 100755 index 00000000..f345e679 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/Linux_i386_glxapi_exports.py @@ -0,0 +1,109 @@ +from __future__ import print_function + +__copyright__ = \ +""" +Copyright (C) 2009-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +""" + +import sys + +#Note, this should match the fakedri_glxfuncsList.h order +glx_functions = [ +"CopyContext", +"UseXFont", +#"GetDriverConfig", +"GetProcAddress", +"QueryExtension", +"IsDirect", +"DestroyGLXPbufferSGIX", +"QueryGLXPbufferSGIX", +"CreateGLXPixmap", +"CreateGLXPixmapWithConfigSGIX", +"QueryContext", +"CreateContextWithConfigSGIX", +"SwapBuffers", +"CreateNewContext", +"SelectEventSGIX", +"GetCurrentDrawable", +"ChooseFBConfig", +"WaitGL", +"GetFBConfigs", +"CreatePixmap", +"GetSelectedEventSGIX", +"GetCurrentReadDrawable", +"GetCurrentDisplay", +"QueryServerString", +"CreateWindow", +"SelectEvent", +"GetVisualFromFBConfigSGIX", +"GetFBConfigFromVisualSGIX", +"QueryDrawable", +"CreateContext", +"GetConfig", +"CreateGLXPbufferSGIX", +"CreatePbuffer", +"ChooseFBConfigSGIX", +"WaitX", +"GetVisualFromFBConfig", +#"GetScreenDriver", +"GetFBConfigAttrib", +"GetCurrentContext", +"GetClientString", +"DestroyPixmap", +"MakeCurrent", +"DestroyContext", +"GetProcAddressARB", +"GetSelectedEvent", +"DestroyPbuffer", +"DestroyWindow", +"DestroyGLXPixmap", +"QueryVersion", +"ChooseVisual", +"MakeContextCurrent", +"QueryExtensionsString", +"GetFBConfigAttribSGIX", +"FreeMemoryMESA", +"QueryContextInfoEXT", +"ImportContextEXT", +"GetContextIDEXT", +"MakeCurrentReadSGI", +"AllocateMemoryMESA", +"GetMemoryOffsetMESA", +"CreateGLXPixmapMESA", +"GetCurrentDisplayEXT", +"FreeContextEXT" +]; + +print('%include "iprt/asmdefs.mac"') +print("") +print("%ifdef RT_ARCH_AMD64") +print("extern glxim") +print("%else ; X86") +print("extern glxim") +print("%endif") +print("") + +## r=bird: This could all be done with macros in the assembler. + +for index in range(len(glx_functions)): + func_name = glx_functions[index] + + print("BEGINPROC_EXPORTED vbox_glX%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tmov \trax, qword glxim+%d" % (8*index)) + print("\tjmp \t[rax]") + print("%else ; X86") + print("\tmov \teax, dword glxim+%d" % (4*index)) + print("\tjmp \t[eax]") + print("%endif") + print("ENDPROC vbox_glX%s" % func_name) + print("") + diff --git a/src/VBox/Additions/common/crOpenGL/Makefile.kmk b/src/VBox/Additions/common/crOpenGL/Makefile.kmk new file mode 100644 index 00000000..4d0011d8 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/Makefile.kmk @@ -0,0 +1,734 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the VirtualBox Guest OpenGL part +# + +# +# Copyright (C) 2008-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Target lists. +# +BLDDIRS += \ + $(VBOX_PATH_CROGL_GENFILES)/ + +if1of ($(KBUILD_TARGET), win linux solaris freebsd) + DLLS += \ + VBoxOGL \ + VBoxOGLarrayspu \ + VBoxOGLpassthroughspu \ + VBoxOGLpackspu \ + VBoxOGLfeedbackspu +endif + +VBOX_OGL_X86_GUEST_DLLS = \ + VBoxOGL-x86 \ + VBoxOGLarrayspu-x86 \ + VBoxOGLpassthroughspu-x86 \ + VBoxOGLpackspu-x86 \ + VBoxOGLfeedbackspu-x86 + +ifdef VBOX_WITH_WDDM + DLLS.win.amd64 += $(VBOX_OGL_X86_GUEST_DLLS) +endif + +if1of ($(KBUILD_TARGET), linux solaris freebsd) + #VBoxOGL_DRI = 1 + DLLS += VBoxEGL + ifn1of ($(KBUILD_TARGET),linux solaris) # No DRI on Solaris yet + VBoxOGL_FAKEDRI = 1 + endif + + # Only Solaris right now needs C stubs because I can't figure out how to + # generate the GOT based relocation ASM yet. + ifdef VBoxOGL_FAKEDRI + if1of ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),solaris.x86 solaris.amd64 linux.x86 linux.amd64 freebsd.x86 freebsd.amd64) + VBOX_OGL_GLX_USE_CSTUBS = 1 + endif + endif +endif + + +# +# VBoxOGL +# +VBoxOGL_TEMPLATE = VBOXCROGLR3GUESTDLL +VBoxOGL_INCS = . $(VBOX_GRAPHICS_INCS) +if1of ($(KBUILD_TARGET), linux solaris freebsd) + ifndef VBOX_USE_SYSTEM_GL_HEADERS + VBoxOGL_INCS += \ + $(VBOX_PATH_X11_ROOT)/libXdamage-1.1 \ + $(VBOX_PATH_X11_ROOT)/libXcomposite-0.4.0 \ + $(VBOX_PATH_X11_ROOT)/libXext-1.3.1 \ + $(VBOX_PATH_X11_ROOT)/libXfixes-4.0.3 \ + $(VBOX_PATH_X11_ROOT)/damageproto-1.1.0 \ + $(VBOX_PATH_X11_ROOT)/compositeproto-0.4 \ + $(VBOX_PATH_X11_ROOT)/fixesproto-4.0 \ + $(VBOX_PATH_X11_ROOT)/libx11-1.1.5-other \ + $(VBOX_PATH_X11_ROOT)/xextproto-7.1.1 \ + $(VBOX_PATH_X11_ROOT)/xproto-7.0.18 \ + $(VBOX_GL_INCS) + endif + VBoxOGL_DEFS += VBOX_NO_NATIVEGL +endif + +ifdef VBoxOGL_DRI + VBoxOGL_DEFS += VBOXOGL_DRI IN_DRI_DRIVER +else ifdef VBoxOGL_FAKEDRI + VBoxOGL_DEFS += VBOXOGL_FAKEDRI + ifdef VBOX_OGL_GLX_USE_CSTUBS + VBoxOGL_DEFS += VBOX_OGL_GLX_USE_CSTUBS + endif +endif + +ifdef VBOX_WITH_WDDM + VBoxOGL_DEFS.win += VBOX_WITH_WDDM + VBoxOGL_SDKS.win += $(VBOX_WINDDK_GST_WLH) +endif + +ifeq ($(KBUILD_TARGET),win) +#fixme?, INTERMEDIATES.win ain't working + VBoxOGL_INTERMEDIATES += $(VBOX_PATH_CROGL_GENFILES)/cr_gl.h + if defined(VBOX_SIGNING_MODE) && defined(VBOX_WITH_WDDM) + VBoxOGL_INSTTYPE.win = none + VBoxOGL_DEBUG_INSTTYPE.win = both + endif +endif +ifdef VBoxOGL_DRI + VBoxOGL_INTERMEDIATES += \ + $(VBOX_PATH_CROGL_GENFILES)/cr_gl.h \ + $(VBOX_PATH_CROGL_GENFILES)/DD_gl.h +else ifdef VBoxOGL_FAKEDRI + VBoxOGL_INTERMEDIATES += \ + $(VBOX_PATH_CROGL_GENFILES)/cr_gl.h +endif +VBoxOGL_SOURCES += \ + load.c \ + stub.c \ + context.c \ + $(VBOX_PATH_CROGL_GENFILES)/getprocaddress.c \ + $(VBOX_PATH_CROGL_GENFILES)/NULLfuncs.c \ + $(VBOX_PATH_CROGL_GENFILES)/tsfuncs.c + +if1of ($(KBUILD_TARGET), linux solaris freebsd) + VBoxOGL_SOURCES += \ + glx.c \ + xfont.c + ifdef VBOX_OGL_GLX_USE_CSTUBS + VBoxOGL_SOURCES += glx_c_exports.c + endif + + ifdef VBoxOGL_DRI + VBoxOGL_SOURCES += \ + $(VBOX_PATH_CROGL_GENFILES)/DD_gl.c \ + dri_drv.c + VBoxOGL_SOURCES.linux += \ + $(VBOX_PATH_CROGL_GENFILES)/linux_exports_dri.asm + VBoxOGL_SOURCES.solaris += \ + $(VBOX_PATH_CROGL_GENFILES)/solaris_exports_dri.asm + VBoxOGL_SOURCES.freebsd += \ + $(VBOX_PATH_CROGL_GENFILES)/freebsd_exports_dri.asm + else ifdef VBoxOGL_FAKEDRI + VBoxOGL_SOURCES += \ + fakedri_drv.c + ifndef VBOX_OGL_GLX_USE_CSTUBS + VBoxOGL_SOURCES.solaris += \ + $(VBOX_PATH_CROGL_GENFILES)/solaris_glxapi_exports.asm \ + $(VBOX_PATH_CROGL_GENFILES)/solaris_exports_dri.asm + VBoxOGL_SOURCES.linux += \ + $(VBOX_PATH_CROGL_GENFILES)/linux_glxapi_exports.asm \ + $(VBOX_PATH_CROGL_GENFILES)/linux_exports_dri.asm + VBoxOGL_SOURCES.freebsd += \ + $(VBOX_PATH_CROGL_GENFILES)/freebsd_glxapi_exports.asm \ + $(VBOX_PATH_CROGL_GENFILES)/freebsd_exports_dri.asm + else + VBoxOGL_SOURCES.solaris += \ + $(VBOX_PATH_CROGL_GENFILES)/solaris_exports.c + VBoxOGL_SOURCES.linux += \ + $(VBOX_PATH_CROGL_GENFILES)/linux_exports.c + VBoxOGL_SOURCES.freebsd += \ + $(VBOX_PATH_CROGL_GENFILES)/freebsd_exports.c + endif + else + VBoxOGL_SOURCES.linux += \ + $(VBOX_PATH_CROGL_GENFILES)/linux_exports.c + VBoxOGL_SOURCES.solaris += \ + $(VBOX_PATH_CROGL_GENFILES)/solaris_exports.c + VBoxOGL_SOURCES.freebsd += \ + $(VBOX_PATH_CROGL_GENFILES)/freebsd_exports.c + endif +endif + +VBoxOGL_SOURCES.win = \ + wgl.c \ + icd_drv.c \ + VBoxCROGL.rc \ + $(VBOX_PATH_CROGL_GENFILES)/windows_exports.asm \ + $(VBOX_PATH_CROGL_GENFILES)/cropengl.def +VBoxOGL_CLEAN = \ + $(VBOX_PATH_CROGL_GENFILES)/getprocaddress.c \ + $(VBOX_PATH_CROGL_GENFILES)/NULLfuncs.c \ + $(VBOX_PATH_CROGL_GENFILES)/tsfuncs.c +VBoxOGL_CLEAN.linux += \ + $(VBOX_PATH_CROGL_GENFILES)/linux_exports.c +VBoxOGL_CLEAN.solaris += \ + $(VBOX_PATH_CROGL_GENFILES)/solaris_exports.c +VBoxOGL_CLEAN.win = \ + $(VBOX_PATH_CROGL_GENFILES)/windows_exports.asm \ + $(VBOX_PATH_CROGL_GENFILES)/cropengl.def +if1of ($(KBUILD_TARGET), linux solaris) + ifdef VBoxOGL_DRI + VBoxOGL_CLEAN += \ + $(VBOX_PATH_CROGL_GENFILES)/cr_gl.h \ + $(VBOX_PATH_CROGL_GENFILES)/DD_gl.h \ + $(VBOX_PATH_CROGL_GENFILES)/DD_gl.c + else ifdef VBoxOGL_FAKEDRI + VBoxOGL_CLEAN += \ + $(VBOX_PATH_CROGL_GENFILES)/cr_gl.h + VBoxOGL_CLEAN.linux += \ + $(VBOX_PATH_CROGL_GENFILES)/linux_glxapi_exports.asm + VBoxOGL_CLEAN.solaris += \ + $(VBOX_PATH_CROGL_GENFILES)/solaris_glxapi_exports.asm + endif +endif +# VBoxOGL_LIBS = \ # VBOX_LIB_OGL_CRUTIL includes these and caused an interesting conflict. +# $(VBOX_LIB_IPRT_GUEST_R3_SHARED) \ +# $(VBOX_LIB_VBGL_R3_SHARED) \ + +VBoxOGL_LIBS = \ + $(VBOX_LIB_OGL_CRUTIL) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLspuload$(VBOX_SUFF_LIB) + +VBoxOGL_LIBS.win += \ + $(PATH_STAGE_LIB)/additions/VBoxDispMpLogger$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxWddmUmKmt$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxCrHgsmi$(VBOX_SUFF_LIB) + +if1of ($(KBUILD_TARGET), linux solaris freebsd) + ifdef VBOX_USE_SYSTEM_GL_HEADERS + VBoxOGL_LIBS += Xcomposite Xdamage Xfixes Xext + else + VBoxOGL_LIBS += \ + $(PATH_STAGE_LIB)/libXcomposite.so \ + $(PATH_STAGE_LIB)/libXdamage.so \ + $(PATH_STAGE_LIB)/libXfixes.so \ + $(PATH_STAGE_LIB)/libXext.so + endif + ifdef VBoxOGL_FAKEDRI + ifeq ($(KBUILD_TARGET), freebsd) + VBoxOGL_LIBS += \ + elf + else + VBoxOGL_LIBS += \ + dl + endif + else + VBoxOGL_SONAME.linux = libGL.so.1 + VBoxOGL_LDFLAGS.linux += -Wl,-e,LibMain + endif +endif +ifdef VBOX_WITH_CRHGSMI +VBoxOGL_DEFS.win += VBOX_WITH_CRHGSMI +endif +ifdef VBOX_WITH_WDDM +VBoxOGL_DEFS.win += VBOX_WITH_WDDM +endif +if1of ($(KBUILD_TARGET), linux) +VBoxOGL_LDFLAGS += -Wl,-z,nodelete +endif +ifdef VBOX_WITH_WDDM +# +# VBoxOGL-x86 - x86 VBoxOGL version built for amd64 build +# +VBoxOGL-x86_EXTENDS = VBoxOGL +VBoxOGL-x86_BLD_TRG_ARCH = x86 +VBoxOGL-x86_LIBS = $(VBOX_LIB_IPRT_GUEST_R3_SHARED_X86) \ + $(VBOX_LIB_VBGL_R3_SHARED_X86) \ + $(VBOX_LIB_OGL_CRUTIL_X86) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLspuload-x86$(VBOX_SUFF_LIB) + +VBoxOGL-x86_LIBS.win += \ + $(PATH_STAGE_LIB)/additions/VBoxDispMpLogger-x86$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxWddmUmKmt-x86$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxCrHgsmi-x86$(VBOX_SUFF_LIB) + + +VBoxOGL-x86_SOURCES.win = $(subst cropengl.def,cropengl-x86.def,$(VBoxOGL_SOURCES.win)) +VBoxOGL-x86_CLEAN.win = $(subst cropengl.def,cropengl-x86.def,$(VBoxOGL_CLEAN.win)) +VBoxOGL-x86_DEFS = $(VBoxOGL_DEFS) VBOX_WDDM_WOW64 +endif + +# +# Generate files for VBoxOGL. +# +$(VBOX_PATH_CROGL_GENFILES)/NULLfuncs.c: $(PATH_SUB_CURRENT)/NULLfuncs.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + +$(VBOX_PATH_CROGL_GENFILES)/tsfuncs.c: $(PATH_SUB_CURRENT)/tsfuncs.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + + +ifeq ($(KBUILD_TARGET),win) + # Windows +$(VBOX_PATH_CROGL_GENFILES)/getprocaddress.c: $(PATH_SUB_CURRENT)/windows_getprocaddress.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + + ifeq ($(KBUILD_TARGET_ARCH),amd64) +$(VBOX_PATH_CROGL_GENFILES)/cropengl.def: $(PATH_SUB_CURRENT)/defs64.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + else +$(VBOX_PATH_CROGL_GENFILES)/cropengl.def: $(PATH_SUB_CURRENT)/defs.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + endif + +$(VBOX_PATH_CROGL_GENFILES)/cr_gl.h: $(PATH_SUB_CURRENT)/cr_gl.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + +$(VBOX_PATH_CROGL_GENFILES)/windows_exports.asm: \ + $(PATH_SUB_CURRENT)/windows_i386_exports.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + + ifdef VBOX_WITH_WDDM + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),win.amd64) +$(VBOX_PATH_CROGL_GENFILES)/cropengl-x86.def: $(PATH_SUB_CURRENT)/defs.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + endif #ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),win.amd64) + endif #ifdef VBOX_WITH_WDDM + + +else if1of ($(KBUILD_TARGET), freebsd linux solaris) + # FreeBSD, Linux, Solaris +$(VBOX_PATH_CROGL_GENFILES)/getprocaddress.c: $(PATH_SUB_CURRENT)/getprocaddress.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + + if !defined(VBoxOGL_DRI) && !defined(VBoxOGL_FAKEDRI) + ifeq ($(KBUILD_TARGET),solaris) +$(VBOX_PATH_CROGL_GENFILES)/solaris_exports.c: \ + $(PATH_SUB_CURRENT)/SunOS_exports.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + + else ifeq ($(KBUILD_TARGET),freebsd) +$(VBOX_PATH_CROGL_GENFILES)/freebsd_exports.c: \ + $(PATH_SUB_CURRENT)/FreeBSD_exports.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + + else +$(VBOX_PATH_CROGL_GENFILES)/linux_exports.c: \ + $(PATH_SUB_CURRENT)/Linux_exports.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + endif + + else ifdef VBoxOGL_DRI +$(VBOX_PATH_CROGL_GENFILES)/cr_gl.h: $(PATH_SUB_CURRENT)/cr_gl.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + +$(VBOX_PATH_CROGL_GENFILES)/DD_gl.h: $(PATH_SUB_CURRENT)/DD_glh.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + +$(VBOX_PATH_CROGL_GENFILES)/DD_gl.c: $(PATH_SUB_CURRENT)/DD_glc.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + + ifeq ($(KBUILD_TARGET),solaris) +$(VBOX_PATH_CROGL_GENFILES)/solaris_exports_dri.asm: \ + $(PATH_SUB_CURRENT)/SunOS_i386_exports_dri.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + + else ifeq ($(KBUILD_TARGET),freebsd) +$(VBOX_PATH_CROGL_GENFILES)/freebsd_exports_dri.asm: \ + $(PATH_SUB_CURRENT)/FreeBSD_i386_exports_dri.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + + else +$(VBOX_PATH_CROGL_GENFILES)/linux_exports_dri.asm: \ + $(PATH_SUB_CURRENT)/Linux_i386_exports_dri.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + endif + + else ifdef VBoxOGL_FAKEDRI +$(VBOX_PATH_CROGL_GENFILES)/cr_gl.h: $(PATH_SUB_CURRENT)/cr_gl.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + + ifndef VBOX_OGL_GLX_USE_CSTUBS + ifeq ($(KBUILD_TARGET),solaris) +$(VBOX_PATH_CROGL_GENFILES)/solaris_exports_dri.asm: \ + $(PATH_SUB_CURRENT)/SunOS_i386_exports_dri.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) +$(VBOX_PATH_CROGL_GENFILES)/solaris_glxapi_exports.asm: $(PATH_SUB_CURRENT)/SunOS_i386_glxapi_exports.py | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< + + else +$(VBOX_PATH_CROGL_GENFILES)/linux_exports_dri.asm: \ + $(PATH_SUB_CURRENT)/Linux_i386_exports_dri.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) +$(VBOX_PATH_CROGL_GENFILES)/linux_glxapi_exports.asm: $(PATH_SUB_CURRENT)/Linux_i386_glxapi_exports.py | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< + endif + + else + ifeq ($(KBUILD_TARGET),solaris) +$(VBOX_PATH_CROGL_GENFILES)/solaris_exports.c: \ + $(PATH_SUB_CURRENT)/SunOS_exports.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + + else ifeq ($(KBUILD_TARGET),freebsd) +$(VBOX_PATH_CROGL_GENFILES)/freebsd_exports.c: \ + $(PATH_SUB_CURRENT)/FreeBSD_exports.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + else +$(VBOX_PATH_CROGL_GENFILES)/linux_exports.c: \ + $(PATH_SUB_CURRENT)/Linux_exports.py \ + $(VBOX_CROGL_API_FILES) $(PATH_SUB_CURRENT)/entrypoints.py \ + | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + endif + + endif # VBOX_OGL_GLX_USE_CSTUBS + endif +endif + +# +# VBoxOGLarrayspu +# +VBoxOGLarrayspu_TEMPLATE = VBOXCROGLR3GUESTDLL +VBoxOGLarrayspu_INCS = \ + array \ + $(VBOX_GRAPHICS_INCS) +if1of ($(KBUILD_TARGET), linux solaris freebsd) + VBoxOGLarrayspu_INCS += \ + $(VBOX_GL_INCS) +endif +if defined(VBOX_SIGNING_MODE) && defined(VBOX_WITH_WDDM) + VBoxOGLarrayspu_INSTTYPE.win = none + VBoxOGLarrayspu_DEBUG_INSTTYPE.win = both +endif +VBoxOGLarrayspu_INCS.darwin += $(PATH_OUT)/obj/VBoxOGL +VBoxOGLarrayspu_INTERMEDIATES = \ + $(VBOX_PATH_CROGL_GENFILES)/state/cr_currentpointers.h \ + $(VBOX_PATH_CROGL_GENFILES)/state/cr_statefuncs.h +VBoxOGLarrayspu_SOURCES = \ + array/arrayspu.c \ + array/arrayspu_config.c \ + array/arrayspu_init.c +VBoxOGLarrayspu_SOURCES.win = \ + array/arrayspu.def \ + array/arrayspu.rc +VBoxOGLarrayspu_LIBS = \ + $(VBOX_LIB_OGL_CRUTIL) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLspuload$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLcrstate$(VBOX_SUFF_LIB) +ifdef VBOX_WITH_CRHGSMI +VBoxOGLarrayspu_DEFS.win += VBOX_WITH_CRHGSMI +endif +ifdef VBOX_WITH_WDDM +VBoxOGLarrayspu_DEFS.win += VBOX_WITH_WDDM +endif + +ifdef VBOX_WITH_WDDM +# +# VBoxOGLarrayspu-x86 - x86 version of VBoxOGLarrayspu built for amd64 build +# +VBoxOGLarrayspu-x86_EXTENDS = VBoxOGLarrayspu +VBoxOGLarrayspu-x86_BLD_TRG_ARCH = x86 +VBoxOGLarrayspu-x86_LIBS = $(VBOX_LIB_OGL_CRUTIL_X86) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLspuload-x86$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLcrstate-x86$(VBOX_SUFF_LIB) +VBoxOGLarrayspu-x86_DEFS = $(VBoxOGLarrayspu_DEFS) VBOX_WDDM_WOW64 +endif + +# +# VBoxOGLpassthroughspu +# +VBoxOGLpassthroughspu_TEMPLATE = VBOXCROGLR3GUESTDLL +VBoxOGLpassthroughspu_INCS = \ + passthrough \ + $(VBOX_GRAPHICS_INCS) +if1of ($(KBUILD_TARGET), linux solaris freebsd) + VBoxOGLpassthroughspu_INCS += \ + $(VBOX_GL_INCS) +endif +if defined(VBOX_SIGNING_MODE) && defined(VBOX_WITH_WDDM) + VBoxOGLpassthroughspu_INSTTYPE.win = none + VBoxOGLpassthroughspu_DEBUG_INSTTYPE.win = both +endif +VBoxOGLpassthroughspu_SOURCES = \ + passthrough/passthroughspu_init.c \ + $(VBOX_PATH_CROGL_GENFILES)/passthroughspu.c +VBoxOGLpassthroughspu_SOURCES.win = \ + passthrough/passthrough.def \ + passthrough/passthroughspu.rc +VBoxOGLpassthroughspu_CLEAN = \ + $(VBOX_PATH_CROGL_GENFILES)/passthroughspu.c +VBoxOGLpassthroughspu_LIBS = \ + $(VBOX_LIB_OGL_CRUTIL) +ifdef VBOX_WITH_CRHGSMI +VBoxOGLpassthroughspu_DEFS.win += VBOX_WITH_CRHGSMI +endif +ifdef VBOX_WITH_WDDM +VBoxOGLpassthroughspu_DEFS.win += VBOX_WITH_WDDM +endif + +ifdef VBOX_WITH_WDDM +# +# VBoxOGLpassthroughspu-x86 - x86 version of VBoxOGLpassthroughspu built for amd64 build +# +VBoxOGLpassthroughspu-x86_EXTENDS = VBoxOGLpassthroughspu +VBoxOGLpassthroughspu-x86_BLD_TRG_ARCH = x86 +VBoxOGLpassthroughspu-x86_LIBS = $(VBOX_LIB_OGL_CRUTIL_X86) +VBoxOGLpassthroughspu-x86_DEFS = $(VBoxOGLpassthroughspu_DEFS) VBOX_WDDM_WOW64 +endif + +# +# Generate files for VBoxOGLpassthroughspu. +# +$(VBOX_PATH_CROGL_GENFILES)/passthroughspu.c: $(PATH_SUB_CURRENT)/passthrough/passthrough.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) + +# +# VBoxOGLpackspu +# +VBoxOGLpackspu_TEMPLATE = VBOXCROGLR3GUESTDLL +VBoxOGLpackspu_DEFS = TRACKS_STATE=1 PACKS=1 +VBoxOGLpackspu_INCS = \ + pack \ + $(VBOX_GRAPHICS_INCS) +if1of ($(KBUILD_TARGET), linux solaris freebsd) + VBoxOGLpackspu_INCS += \ + $(VBOX_GL_INCS) +endif +if defined(VBOX_SIGNING_MODE) && defined(VBOX_WITH_WDDM) + VBoxOGLpackspu_INSTTYPE.win = none + VBoxOGLpackspu_DEBUG_INSTTYPE.win = both +endif +VBoxOGLpackspu_INTERMEDIATES = \ + $(VBOX_PATH_CROGL_GENFILES)/packspu_proto.h \ + $(VBOX_PATH_CROGL_GENFILES)/cr_packfunctions.h +VBoxOGLpackspu_SOURCES = \ + pack/packspu_bufferobject.c \ + pack/packspu_client.c \ + pack/packspu_config.c \ + pack/packspu_context.c \ + pack/packspu_getstring.c \ + pack/packspu_init.c \ + pack/packspu_misc.c \ + pack/packspu_net.c \ + pack/packspu_swapbuf.c \ + pack/packspu_pixel.c \ + pack/packspu_texture.c \ + pack/packspu_getshaders.c \ + pack/packspu_glsl.c \ + pack/packspu_framebuffer.c \ + $(VBOX_PATH_CROGL_GENFILES)/packspu.c \ + $(VBOX_PATH_CROGL_GENFILES)/packspu_get.c \ + $(VBOX_PATH_CROGL_GENFILES)/packspu_flush.c \ + $(VBOX_PATH_CROGL_GENFILES)/packspu_beginend.c +VBoxOGLpackspu_SOURCES.win = \ + pack/pack.def \ + pack/packspu.rc +VBoxOGLpackspu_CLEAN = \ + $(VBOX_PATH_CROGL_GENFILES)/packspu_proto.h \ + $(VBOX_PATH_CROGL_GENFILES)/packspu.c \ + $(VBOX_PATH_CROGL_GENFILES)/packspu_get.c \ + $(VBOX_PATH_CROGL_GENFILES)/packspu_flush.c \ + $(VBOX_PATH_CROGL_GENFILES)/packspu_beginend.c +VBoxOGLpackspu_LIBS = \ + $(VBOX_LIB_OGL_CRUTIL) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLspuload$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLcrstate$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLcrpacker$(VBOX_SUFF_LIB) +VBoxOGLpackspu_LIBS.win += \ + $(PATH_STAGE_LIB)/additions/VBoxDispMpLogger$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxWddmUmKmt$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxCrHgsmi$(VBOX_SUFF_LIB) + +ifdef VBOX_WITH_CRHGSMI +VBoxOGLpackspu_DEFS.win += VBOX_WITH_CRHGSMI +endif +ifdef VBOX_WITH_CRDUMPER +VBoxOGLpackspu_DEFS += VBOX_WITH_CRDUMPER +endif +ifdef VBOX_WITH_CRPACKSPU_DUMPER +VBoxOGLpackspu_DEFS += VBOX_WITH_CRPACKSPU_DUMPER +endif +ifdef VBOX_WITH_WDDM +VBoxOGLpackspu_DEFS.win += VBOX_WITH_WDDM +endif + +ifdef VBOX_WITH_WDDM +# +# VBoxOGLpackspu-x86 - x86 version of VBoxOGLpackspu built for amd64 build +# +VBoxOGLpackspu-x86_EXTENDS = VBoxOGLpackspu +VBoxOGLpackspu-x86_BLD_TRG_ARCH = x86 +VBoxOGLpackspu-x86_LIBS = $(VBOX_LIB_OGL_CRUTIL_X86) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLspuload-x86$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLcrstate-x86$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLcrpacker-x86$(VBOX_SUFF_LIB) +VBoxOGLpackspu-x86_LIBS.win += \ + $(PATH_STAGE_LIB)/additions/VBoxDispMpLogger-x86$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxWddmUmKmt-x86$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxCrHgsmi-x86$(VBOX_SUFF_LIB) +VBoxOGLpackspu-x86_DEFS = $(VBoxOGLpackspu_DEFS) VBOX_WDDM_WOW64 +endif + +# +# Generate files for VBoxOGLpackspu. +# +$(VBOX_PATH_CROGL_GENFILES)/packspu.c: $(addprefix $(PATH_SUB_CURRENT)/pack/,pack.py packspu_special packspu_unimplemented_special) $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) $(<D) + +$(VBOX_PATH_CROGL_GENFILES)/packspu_get.c: $(PATH_SUB_CURRENT)/pack/packspu_get.py $(PATH_SUB_CURRENT)/pack/packspu_special $(PATH_ROOT)/src/VBox/HostServices/SharedOpenGL/crserverlib/get_sizes.py $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) $(<D) + +$(VBOX_PATH_CROGL_GENFILES)/packspu_flush.c: $(PATH_SUB_CURRENT)/pack/packspu_flush.py $(PATH_SUB_CURRENT)/pack/packspu_flush_special $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) $(<D) + +$(VBOX_PATH_CROGL_GENFILES)/packspu_beginend.c: $(PATH_SUB_CURRENT)/pack/packspu_beginend.py $(PATH_SUB_CURRENT)/pack/packspu_vertex_special $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) $(<D) + +$(VBOX_PATH_CROGL_GENFILES)/packspu_proto.h: $(addprefix $(PATH_SUB_CURRENT)/pack/,packspu_proto.py packspu_special packspu_unimplemented_special) $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) $(<D) + +# +# VBoxOGLfeedbackspu +# +VBoxOGLfeedbackspu_TEMPLATE = VBOXCROGLR3GUESTDLL +VBoxOGLfeedbackspu_INCS = \ + feedback \ + $(VBOX_GRAPHICS_INCS) +if1of ($(KBUILD_TARGET), linux solaris freebsd) + VBoxOGLfeedbackspu_INCS += \ + $(VBOX_GL_INCS) +endif +if defined(VBOX_SIGNING_MODE) && defined(VBOX_WITH_WDDM) + VBoxOGLfeedbackspu_INSTTYPE.win = none + VBoxOGLfeedbackspu_DEBUG_INSTTYPE.win = both +endif +VBoxOGLarrayspu_INTERMEDIATES = \ + $(VBOX_PATH_CROGL_GENFILES)/feedbackspu_proto.h +VBoxOGLfeedbackspu_SOURCES = \ + feedback/feedbackspu_config.c \ + feedback/feedbackspu_init.c \ + feedback/feedback_context.c \ + $(VBOX_PATH_CROGL_GENFILES)/feedbackspu.c \ + $(VBOX_PATH_CROGL_GENFILES)/feedbackspu_state.c +VBoxOGLfeedbackspu_SOURCES.win = \ + feedback/feedback.def \ + feedback/feedbackspu.rc +VBoxOGLfeedbackspu_CLEAN = \ + $(VBOX_PATH_CROGL_GENFILES)/feedbackspu_proto.h \ + $(VBOX_PATH_CROGL_GENFILES)/feedbackspu.c \ + $(VBOX_PATH_CROGL_GENFILES)/feedbackspu_state.c +VBoxOGLfeedbackspu_LIBS = \ + $(VBOX_LIB_OGL_CRUTIL) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLspuload$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLcrstate$(VBOX_SUFF_LIB) +ifdef VBOX_WITH_CRHGSMI +VBoxOGLfeedbackspu_DEFS.win += VBOX_WITH_CRHGSMI +endif +ifdef VBOX_WITH_WDDM +VBoxOGLfeedbackspu_DEFS.win += VBOX_WITH_WDDM +endif + +ifdef VBOX_WITH_WDDM +# +# VBoxOGLfeedbackspu-x86 - x86 version of VBoxOGLfeedbackspu built for amd64 build +# +VBoxOGLfeedbackspu-x86_EXTENDS = VBoxOGLfeedbackspu +VBoxOGLfeedbackspu-x86_BLD_TRG_ARCH = x86 +VBoxOGLfeedbackspu-x86_LIBS = $(VBOX_LIB_OGL_CRUTIL_X86) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLspuload-x86$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/additions/VBoxOGLcrstate-x86$(VBOX_SUFF_LIB) +VBoxOGLfeedbackspu-x86_DEFS = $(VBoxOGLfeedbackspu_DEFS) VBOX_WDDM_WOW64 +endif + +# +# Generate files for VBoxOGLfeedbackspu. +# +$(VBOX_PATH_CROGL_GENFILES)/feedbackspu.c: $(addprefix $(PATH_SUB_CURRENT)/feedback/,feedback.py feedback_special select_special feedback_state_special) $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) $(<D) + +$(VBOX_PATH_CROGL_GENFILES)/feedbackspu_state.c: $(addprefix $(PATH_SUB_CURRENT)/feedback/,feedback_state.py feedback_state_special) $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) $(<D) + +$(VBOX_PATH_CROGL_GENFILES)/feedbackspu_proto.h: $(addprefix $(PATH_SUB_CURRENT)/feedback/,feedbackspu_proto.py feedback_special select_special feedback_state_special) $(VBOX_CROGL_API_FILES) | $$(dir $$@) + $(call MSG_GENERATE,python,$@,$<) + $(QUIET)$(call VBOX_CROGL_PYTHON_ENV,$(VBOX_PATH_CROGL_PYTHON_INCLUDE),$@) $(VBOX_BLD_PYTHON) $< $(VBOX_PATH_CROGL_GLAPI) $(<D) + +VBoxEGL_TEMPLATE = VBOXCROGLR3GUESTDLL +VBoxEGL_SOURCES = egl.c +ifndef VBOX_USE_SYSTEM_GL_HEADERS + VBoxEGL_INCS = $(VBOX_PATH_X11_ROOT)/mesa-11.0.7 +endif +VBoxEGL_LIBS = $(VBOX_LIB_OGL) # $(VBOX_LIB_IPRT_GUEST_R3_SHARED) +VBoxEGL_SONAME.linux = libEGL.so.1 + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/crOpenGL/NULLfuncs.py b/src/VBox/Additions/common/crOpenGL/NULLfuncs.py new file mode 100755 index 00000000..a6427837 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/NULLfuncs.py @@ -0,0 +1,58 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + + +from __future__ import print_function +import sys + +import apiutil + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - THIS FILE GENERATED BY THE NULLfuncs.py SCRIPT */ + +#include "cr_error.h" +#include "stub.h" +""") + +for func_name in keys: + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + + print("static %s SPULOAD_APIENTRY NULL_%s(%s)" % (return_type, func_name, apiutil.MakeDeclarationString(params))) + print("{") + print("\t/* do nothing */") + print("\tcrWarning(\"YOU ARE CALLING A NULLED FUNCTION (%s)\");" % func_name) + for (name, type, vecSize) in params: + print("\t(void) %s;" % name) + if return_type != "void": + print("\treturn 0;") + print("}") + print("") + + +print("DECLEXPORT(SPUDispatchTable) stubNULLDispatch = {") +for func_name in keys: + print("\tNULL_%s," % (func_name)) +print("\tNULL, /* copyList */") +print("\tNULL, /* copy_of */") +print("\t0, /* mark */") +print("\tNULL /* server */") +print("};") + +print("") +print("/* Declare and initialize the glim dispatch table here so that we */") +print("/* can initialize all entries to no-op routines. */") +print("SPUDispatchTable glim = {") +for func_name in keys: + print("\tNULL_%s," % (func_name)) +print("\tNULL, /* copyList */") +print("\tNULL, /* copy_of */") +print("\t0, /* mark */") +print("\tNULL /* server */") +print("};") diff --git a/src/VBox/Additions/common/crOpenGL/OSF1_exports.py b/src/VBox/Additions/common/crOpenGL/OSF1_exports.py new file mode 100644 index 00000000..8f63e0a5 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/OSF1_exports.py @@ -0,0 +1,11 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +import entrypoints + +hacks = [] + +entrypoints.GenerateEntrypoints(hacks) + diff --git a/src/VBox/Additions/common/crOpenGL/SunOS_exports.py b/src/VBox/Additions/common/crOpenGL/SunOS_exports.py new file mode 100644 index 00000000..8f63e0a5 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/SunOS_exports.py @@ -0,0 +1,11 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +import entrypoints + +hacks = [] + +entrypoints.GenerateEntrypoints(hacks) + diff --git a/src/VBox/Additions/common/crOpenGL/SunOS_i386_exports.py b/src/VBox/Additions/common/crOpenGL/SunOS_i386_exports.py new file mode 100755 index 00000000..b3591339 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/SunOS_i386_exports.py @@ -0,0 +1,98 @@ +""" +Copyright (C) 2009-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +""" + +from __future__ import print_function +import sys + +import apiutil + + +def GenerateEntrypoints(): + + #apiutil.CopyrightC() + + # Get sorted list of dispatched functions. + # The order is very important - it must match cr_opcodes.h + # and spu_dispatch_table.h + print('%include "iprt/asmdefs.mac"') + print("") + print("%ifdef RT_ARCH_AMD64") + print("extern glim") + print("%else ; X86") + print("extern glim") + print("%endif") + print("") + + keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + + for index in range(len(keys)): + func_name = keys[index] + if apiutil.Category(func_name) == "Chromium": + continue + if apiutil.Category(func_name) == "VBox": + continue + + print("BEGINPROC_EXPORTED gl%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tjmp \t[glim+%d wrt rip wrt ..gotpcrel]" % (8*index)) + print("%else ; X86") + print("\tjmp \t[glim+%d wrt ..gotpc]" % (4*index)) + print("%endif") + print("ENDPROC gl%s" % func_name) + print("") + + + print(';') + print('; Aliases') + print(';') + + # Now loop over all the functions and take care of any aliases + allkeys = apiutil.GetAllFunctions(sys.argv[1]+"/APIspec.txt") + for func_name in allkeys: + if "omit" in apiutil.ChromiumProps(func_name): + continue + + if func_name in keys: + # we already processed this function earlier + continue + + # alias is the function we're aliasing + alias = apiutil.Alias(func_name) + if alias: + # this dict lookup should never fail (raise an exception)! + index = keys.index(alias) + print("BEGINPROC_EXPORTED gl%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tjmp \t[glim+%d wrt rip wrt ..gotpcrel]" % (8*index)) + print("%else ; X86") + print("\tjmp \t[glim+%d wrt ..gotpc]" % (4*index)) + print("%endif") + print("ENDPROC gl%s" % func_name) + print("") + + + print(';') + print('; No-op stubs') + print(';') + + # Now generate no-op stub functions + for func_name in allkeys: + if "stub" in apiutil.ChromiumProps(func_name): + print("BEGINPROC_EXPORTED gl%s" % func_name) + print("\tleave") + print("\tret") + print("ENDPROC gl%s" % func_name) + print("") + + +GenerateEntrypoints() + diff --git a/src/VBox/Additions/common/crOpenGL/SunOS_i386_exports_dri.py b/src/VBox/Additions/common/crOpenGL/SunOS_i386_exports_dri.py new file mode 100755 index 00000000..a8b951bd --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/SunOS_i386_exports_dri.py @@ -0,0 +1,98 @@ +""" +Copyright (C) 2009-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +""" + +from __future__ import print_function +import sys + +import apiutil + + +def GenerateEntrypoints(): + + #apiutil.CopyrightC() + + # Get sorted list of dispatched functions. + # The order is very important - it must match cr_opcodes.h + # and spu_dispatch_table.h + print('%include "iprt/asmdefs.mac"') + print("") + print("%ifdef RT_ARCH_AMD64") + print("extern glim") + print("%else ; X86") + print("extern glim") + print("%endif") + print("") + + keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + + for index in range(len(keys)): + func_name = keys[index] + if apiutil.Category(func_name) == "Chromium": + continue + if apiutil.Category(func_name) == "VBox": + continue + + print("BEGINPROC_EXPORTED cr_gl%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tjmp \t[glim+%d wrt rip wrt ..gotpcrel]" % (8*index)) + print("%else ; X86") + print("\tjmp \t[glim+%d wrt ..gotpc]" % (4*index)) + print("%endif") + print("ENDPROC cr_gl%s" % func_name) + print("") + + + print(';') + print('; Aliases') + print(';') + + # Now loop over all the functions and take care of any aliases + allkeys = apiutil.GetAllFunctions(sys.argv[1]+"/APIspec.txt") + for func_name in allkeys: + if "omit" in apiutil.ChromiumProps(func_name): + continue + + if func_name in keys: + # we already processed this function earlier + continue + + # alias is the function we're aliasing + alias = apiutil.Alias(func_name) + if alias: + # this dict lookup should never fail (raise an exception)! + index = keys.index(alias) + print("BEGINPROC_EXPORTED cr_gl%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tjmp \t[glim+%d wrt rip wrt ..gotpcrel]" % (8*index)) + print("%else ; X86") + print("\tjmp \t[glim+%d wrt ..gotpc]" % (4*index)) + print("%endif") + print("ENDPROC cr_gl%s" % func_name) + print("") + + + print(';') + print('; No-op stubs') + print(';') + + # Now generate no-op stub functions + for func_name in allkeys: + if "stub" in apiutil.ChromiumProps(func_name): + print("BEGINPROC_EXPORTED cr_gl%s" % func_name) + print("\tleave") + print("\tret") + print("ENDPROC cr_gl%s" % func_name) + print("") + + +GenerateEntrypoints() + diff --git a/src/VBox/Additions/common/crOpenGL/SunOS_i386_glxapi_exports.py b/src/VBox/Additions/common/crOpenGL/SunOS_i386_glxapi_exports.py new file mode 100755 index 00000000..24d7cc53 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/SunOS_i386_glxapi_exports.py @@ -0,0 +1,103 @@ +""" +Copyright (C) 2009-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +""" + +from __future__ import print_function +import sys + +#Note, this should match the fakedri_glxfuncsList.h order +glx_functions = [ +"CopyContext", +"UseXFont", +#"GetDriverConfig", +"GetProcAddress", +"QueryExtension", +"IsDirect", +"DestroyGLXPbufferSGIX", +"QueryGLXPbufferSGIX", +"CreateGLXPixmap", +"CreateGLXPixmapWithConfigSGIX", +"QueryContext", +"CreateContextWithConfigSGIX", +"SwapBuffers", +"CreateNewContext", +"SelectEventSGIX", +"GetCurrentDrawable", +"ChooseFBConfig", +"WaitGL", +"GetFBConfigs", +"CreatePixmap", +"GetSelectedEventSGIX", +"GetCurrentReadDrawable", +"GetCurrentDisplay", +"QueryServerString", +"CreateWindow", +"SelectEvent", +"GetVisualFromFBConfigSGIX", +"GetFBConfigFromVisualSGIX", +"QueryDrawable", +"CreateContext", +"GetConfig", +"CreateGLXPbufferSGIX", +"CreatePbuffer", +"ChooseFBConfigSGIX", +"WaitX", +"GetVisualFromFBConfig", +#"GetScreenDriver", +"GetFBConfigAttrib", +"GetCurrentContext", +"GetClientString", +"DestroyPixmap", +"MakeCurrent", +"DestroyContext", +"GetProcAddressARB", +"GetSelectedEvent", +"DestroyPbuffer", +"DestroyWindow", +"DestroyGLXPixmap", +"QueryVersion", +"ChooseVisual", +"MakeContextCurrent", +"QueryExtensionsString", +"GetFBConfigAttribSGIX", +"FreeMemoryMESA", +"QueryContextInfoEXT", +"ImportContextEXT", +"GetContextIDEXT", +"MakeCurrentReadSGI", +"AllocateMemoryMESA", +"GetMemoryOffsetMESA", +"CreateGLXPixmapMESA", +"GetCurrentDisplayEXT", +"FreeContextEXT" +]; + +print('%include "iprt/asmdefs.mac"') +print("") +print("%ifdef RT_ARCH_AMD64") +print("extern glxim") +print("%else ; X86") +print("extern glxim") +print("%endif") +print("") + +for index in range(len(glx_functions)): + func_name = glx_functions[index] + + print("BEGINPROC_EXPORTED vbox_glX%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tjmp \t[glxim+%d wrt rip wrt ..gotpcrel]" % (8*index)) + print("%else ; X86") + print("\tjmp \t[glxim+%d wrt ..gotpc]" % (4*index)) + print("%endif") + print("ENDPROC vbox_glX%s" % func_name) + print("") + diff --git a/src/VBox/Additions/common/crOpenGL/VBoxCROGL.rc b/src/VBox/Additions/common/crOpenGL/VBoxCROGL.rc new file mode 100644 index 00000000..1cafaca7 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/VBoxCROGL.rc @@ -0,0 +1,70 @@ +/* $Id: VBoxCROGL.rc $ */ +/** @file + * VBoxCROGL - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DRV + FILESUBTYPE VFT2_DRV_DISPLAY +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "VirtualBox crOpenGL ICD\0" + VALUE "InternalName", "VBoxCROGL\0" +#ifdef VBOX_WDDM_WOW64 + VALUE "OriginalFilename", "VBoxCROGL-x86.dll\0" +#else + VALUE "OriginalFilename", "VBoxCROGL.dll\0" +#endif + 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 + +1 RCDATA +BEGIN +// Machine dependent parameters + 17, // Height of vertical thumb + 17, // Width of horizontal thumb + 2, // Icon horiz compression factor + 2, // Icon vert compression factor + 1, // Cursor horz compression factor + 1, // Cursor vert compression factor + 0, // Kanji window height + 1, // cxBorder (thickness of vertical lines) + 1 // cyBorder (thickness of horizontal lines) +END diff --git a/src/VBox/Additions/common/crOpenGL/VBoxICDList.h b/src/VBox/Additions/common/crOpenGL/VBoxICDList.h new file mode 100644 index 00000000..dcc901da --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/VBoxICDList.h @@ -0,0 +1,385 @@ +/* $Id: VBoxICDList.h $ */ +/** @file + * VirtualBox Windows NT/2000/XP guest OpenGL ICD + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/* + * Mesa 3-D graphics library + * Version: 6.1 + * + * Copyright (C) 1999-2004 Brian Paul All Rights Reserved. + * + * 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 + * BRIAN PAUL 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. + */ + +/* + * File name: icd.c + * Author: Gregor Anich + * + * ICD (Installable Client Driver) interface. + * Based on the windows GDI/WGL driver. + */ + +ICD_ENTRY(NewList) /* 0 */ +ICD_ENTRY(EndList) /* 1 */ +ICD_ENTRY(CallList) /* 2 */ +ICD_ENTRY(CallLists) /* 3 */ +ICD_ENTRY(DeleteLists) /* 4 */ +ICD_ENTRY(GenLists) /* 5 */ +ICD_ENTRY(ListBase) /* 6 */ +ICD_ENTRY(Begin) /* 7 */ +ICD_ENTRY(Bitmap) /* 8 */ +ICD_ENTRY(Color3b) /* 9 */ +ICD_ENTRY(Color3bv) /* 10 */ +ICD_ENTRY(Color3d) /* 11 */ +ICD_ENTRY(Color3dv) /* 12 */ +ICD_ENTRY(Color3f) /* 13 */ +ICD_ENTRY(Color3fv) /* 14 */ +ICD_ENTRY(Color3i) /* 15 */ +ICD_ENTRY(Color3iv) /* 16 */ +ICD_ENTRY(Color3s) /* 17 */ +ICD_ENTRY(Color3sv) /* 18 */ +ICD_ENTRY(Color3ub) /* 19 */ +ICD_ENTRY(Color3ubv) /* 20 */ +ICD_ENTRY(Color3ui) /* 21 */ +ICD_ENTRY(Color3uiv) /* 22 */ +ICD_ENTRY(Color3us) /* 23 */ +ICD_ENTRY(Color3usv) /* 24 */ +ICD_ENTRY(Color4b) /* 25 */ +ICD_ENTRY(Color4bv) /* 26 */ +ICD_ENTRY(Color4d) /* 27 */ +ICD_ENTRY(Color4dv) /* 28 */ +ICD_ENTRY(Color4f) /* 29 */ +ICD_ENTRY(Color4fv) /* 30 */ +ICD_ENTRY(Color4i) /* 31 */ +ICD_ENTRY(Color4iv) /* 32 */ +ICD_ENTRY(Color4s) /* 33 */ +ICD_ENTRY(Color4sv) /* 34 */ +ICD_ENTRY(Color4ub) /* 35 */ +ICD_ENTRY(Color4ubv) /* 36 */ +ICD_ENTRY(Color4ui) /* 37 */ +ICD_ENTRY(Color4uiv) /* 38 */ +ICD_ENTRY(Color4us) /* 39 */ +ICD_ENTRY(Color4usv) /* 40 */ +ICD_ENTRY(EdgeFlag) /* 41 */ +ICD_ENTRY(EdgeFlagv) /* 42 */ +ICD_ENTRY(End) /* 43 */ +ICD_ENTRY(Indexd) /* 44 */ +ICD_ENTRY(Indexdv) /* 45 */ +ICD_ENTRY(Indexf) /* 46 */ +ICD_ENTRY(Indexfv) /* 47 */ +ICD_ENTRY(Indexi) /* 48 */ +ICD_ENTRY(Indexiv) /* 49 */ +ICD_ENTRY(Indexs) /* 50 */ +ICD_ENTRY(Indexsv) /* 51 */ +ICD_ENTRY(Normal3b) /* 52 */ +ICD_ENTRY(Normal3bv) /* 53 */ +ICD_ENTRY(Normal3d) /* 54 */ +ICD_ENTRY(Normal3dv) /* 55 */ +ICD_ENTRY(Normal3f) /* 56 */ +ICD_ENTRY(Normal3fv) /* 57 */ +ICD_ENTRY(Normal3i) /* 58 */ +ICD_ENTRY(Normal3iv) /* 59 */ +ICD_ENTRY(Normal3s) /* 60 */ +ICD_ENTRY(Normal3sv) /* 61 */ +ICD_ENTRY(RasterPos2d) /* 62 */ +ICD_ENTRY(RasterPos2dv) /* 63 */ +ICD_ENTRY(RasterPos2f) /* 64 */ +ICD_ENTRY(RasterPos2fv) /* 65 */ +ICD_ENTRY(RasterPos2i) /* 66 */ +ICD_ENTRY(RasterPos2iv) /* 67 */ +ICD_ENTRY(RasterPos2s) /* 68 */ +ICD_ENTRY(RasterPos2sv) /* 69 */ +ICD_ENTRY(RasterPos3d) /* 70 */ +ICD_ENTRY(RasterPos3dv) /* 71 */ +ICD_ENTRY(RasterPos3f) /* 72 */ +ICD_ENTRY(RasterPos3fv) /* 73 */ +ICD_ENTRY(RasterPos3i) /* 74 */ +ICD_ENTRY(RasterPos3iv) /* 75 */ +ICD_ENTRY(RasterPos3s) /* 76 */ +ICD_ENTRY(RasterPos3sv) /* 77 */ +ICD_ENTRY(RasterPos4d) /* 78 */ +ICD_ENTRY(RasterPos4dv) /* 79 */ +ICD_ENTRY(RasterPos4f) /* 80 */ +ICD_ENTRY(RasterPos4fv) /* 81 */ +ICD_ENTRY(RasterPos4i) /* 82 */ +ICD_ENTRY(RasterPos4iv) /* 83 */ +ICD_ENTRY(RasterPos4s) /* 84 */ +ICD_ENTRY(RasterPos4sv) /* 85 */ +ICD_ENTRY(Rectd) /* 86 */ +ICD_ENTRY(Rectdv) /* 87 */ +ICD_ENTRY(Rectf) /* 88 */ +ICD_ENTRY(Rectfv) /* 89 */ +ICD_ENTRY(Recti) /* 90 */ +ICD_ENTRY(Rectiv) /* 91 */ +ICD_ENTRY(Rects) /* 92 */ +ICD_ENTRY(Rectsv) /* 93 */ +ICD_ENTRY(TexCoord1d) /* 94 */ +ICD_ENTRY(TexCoord1dv) /* 95 */ +ICD_ENTRY(TexCoord1f) /* 96 */ +ICD_ENTRY(TexCoord1fv) /* 97 */ +ICD_ENTRY(TexCoord1i) /* 98 */ +ICD_ENTRY(TexCoord1iv) /* 99 */ +ICD_ENTRY(TexCoord1s) /* 100 */ +ICD_ENTRY(TexCoord1sv) /* 101 */ +ICD_ENTRY(TexCoord2d) /* 102 */ +ICD_ENTRY(TexCoord2dv) /* 103 */ +ICD_ENTRY(TexCoord2f) /* 104 */ +ICD_ENTRY(TexCoord2fv) /* 105 */ +ICD_ENTRY(TexCoord2i) /* 106 */ +ICD_ENTRY(TexCoord2iv) /* 107 */ +ICD_ENTRY(TexCoord2s) /* 108 */ +ICD_ENTRY(TexCoord2sv) /* 109 */ +ICD_ENTRY(TexCoord3d) /* 110 */ +ICD_ENTRY(TexCoord3dv) /* 111 */ +ICD_ENTRY(TexCoord3f) /* 112 */ +ICD_ENTRY(TexCoord3fv) /* 113 */ +ICD_ENTRY(TexCoord3i) /* 114 */ +ICD_ENTRY(TexCoord3iv) /* 115 */ +ICD_ENTRY(TexCoord3s) /* 116 */ +ICD_ENTRY(TexCoord3sv) /* 117 */ +ICD_ENTRY(TexCoord4d) /* 118 */ +ICD_ENTRY(TexCoord4dv) /* 119 */ +ICD_ENTRY(TexCoord4f) /* 120 */ +ICD_ENTRY(TexCoord4fv) /* 121 */ +ICD_ENTRY(TexCoord4i) /* 122 */ +ICD_ENTRY(TexCoord4iv) /* 123 */ +ICD_ENTRY(TexCoord4s) /* 124 */ +ICD_ENTRY(TexCoord4sv) /* 125 */ +ICD_ENTRY(Vertex2d) /* 126 */ +ICD_ENTRY(Vertex2dv) /* 127 */ +ICD_ENTRY(Vertex2f) /* 128 */ +ICD_ENTRY(Vertex2fv) /* 129 */ +ICD_ENTRY(Vertex2i) /* 130 */ +ICD_ENTRY(Vertex2iv) /* 131 */ +ICD_ENTRY(Vertex2s) /* 132 */ +ICD_ENTRY(Vertex2sv) /* 133 */ +ICD_ENTRY(Vertex3d) /* 134 */ +ICD_ENTRY(Vertex3dv) /* 135 */ +ICD_ENTRY(Vertex3f) /* 136 */ +ICD_ENTRY(Vertex3fv) /* 137 */ +ICD_ENTRY(Vertex3i) /* 138 */ +ICD_ENTRY(Vertex3iv) /* 139 */ +ICD_ENTRY(Vertex3s) /* 140 */ +ICD_ENTRY(Vertex3sv) /* 141 */ +ICD_ENTRY(Vertex4d) /* 142 */ +ICD_ENTRY(Vertex4dv) /* 143 */ +ICD_ENTRY(Vertex4f) /* 144 */ +ICD_ENTRY(Vertex4fv) /* 145 */ +ICD_ENTRY(Vertex4i) /* 146 */ +ICD_ENTRY(Vertex4iv) /* 147 */ +ICD_ENTRY(Vertex4s) /* 148 */ +ICD_ENTRY(Vertex4sv) /* 149 */ +ICD_ENTRY(ClipPlane) /* 150 */ +ICD_ENTRY(ColorMaterial) /* 151 */ +ICD_ENTRY(CullFace) /* 152 */ +ICD_ENTRY(Fogf) /* 153 */ +ICD_ENTRY(Fogfv) /* 154 */ +ICD_ENTRY(Fogi) /* 155 */ +ICD_ENTRY(Fogiv) /* 156 */ +ICD_ENTRY(FrontFace) /* 157 */ +ICD_ENTRY(Hint) /* 158 */ +ICD_ENTRY(Lightf) /* 159 */ +ICD_ENTRY(Lightfv) /* 160 */ +ICD_ENTRY(Lighti) /* 161 */ +ICD_ENTRY(Lightiv) /* 162 */ +ICD_ENTRY(LightModelf) /* 163 */ +ICD_ENTRY(LightModelfv) /* 164 */ +ICD_ENTRY(LightModeli) /* 165 */ +ICD_ENTRY(LightModeliv) /* 166 */ +ICD_ENTRY(LineStipple) /* 167 */ +ICD_ENTRY(LineWidth) /* 168 */ +ICD_ENTRY(Materialf) /* 169 */ +ICD_ENTRY(Materialfv) /* 170 */ +ICD_ENTRY(Materiali) /* 171 */ +ICD_ENTRY(Materialiv) /* 172 */ +ICD_ENTRY(PointSize) /* 173 */ +ICD_ENTRY(PolygonMode) /* 174 */ +ICD_ENTRY(PolygonStipple) /* 175 */ +ICD_ENTRY(Scissor) /* 176 */ +ICD_ENTRY(ShadeModel) /* 177 */ +ICD_ENTRY(TexParameterf) /* 178 */ +ICD_ENTRY(TexParameterfv) /* 179 */ +ICD_ENTRY(TexParameteri) /* 180 */ +ICD_ENTRY(TexParameteriv) /* 181 */ +ICD_ENTRY(TexImage1D) /* 182 */ +ICD_ENTRY(TexImage2D) /* 183 */ +ICD_ENTRY(TexEnvf) /* 184 */ +ICD_ENTRY(TexEnvfv) /* 185 */ +ICD_ENTRY(TexEnvi) /* 186 */ +ICD_ENTRY(TexEnviv) /* 187 */ +ICD_ENTRY(TexGend) /* 188 */ +ICD_ENTRY(TexGendv) /* 189 */ +ICD_ENTRY(TexGenf) /* 190 */ +ICD_ENTRY(TexGenfv) /* 191 */ +ICD_ENTRY(TexGeni) /* 192 */ +ICD_ENTRY(TexGeniv) /* 193 */ +ICD_ENTRY(FeedbackBuffer) /* 194 */ +ICD_ENTRY(SelectBuffer) /* 195 */ +ICD_ENTRY(RenderMode) /* 196 */ +ICD_ENTRY(InitNames) /* 197 */ +ICD_ENTRY(LoadName) /* 198 */ +ICD_ENTRY(PassThrough) /* 199 */ +ICD_ENTRY(PopName) /* 200 */ +ICD_ENTRY(PushName) /* 201 */ +ICD_ENTRY(DrawBuffer) /* 202 */ +ICD_ENTRY(Clear) /* 203 */ +ICD_ENTRY(ClearAccum) /* 204 */ +ICD_ENTRY(ClearIndex) /* 205 */ +ICD_ENTRY(ClearColor) /* 206 */ +ICD_ENTRY(ClearStencil) /* 207 */ +ICD_ENTRY(ClearDepth) /* 208 */ +ICD_ENTRY(StencilMask) /* 209 */ +ICD_ENTRY(ColorMask) /* 210 */ +ICD_ENTRY(DepthMask) /* 211 */ +ICD_ENTRY(IndexMask) /* 212 */ +ICD_ENTRY(Accum) /* 213 */ +ICD_ENTRY(Disable) /* 214 */ +ICD_ENTRY(Enable) /* 215 */ +ICD_ENTRY(Finish) /* 216 */ +ICD_ENTRY(Flush) /* 217 */ +ICD_ENTRY(PopAttrib) /* 218 */ +ICD_ENTRY(PushAttrib) /* 219 */ +ICD_ENTRY(Map1d) /* 220 */ +ICD_ENTRY(Map1f) /* 221 */ +ICD_ENTRY(Map2d) /* 222 */ +ICD_ENTRY(Map2f) /* 223 */ +ICD_ENTRY(MapGrid1d) /* 224 */ +ICD_ENTRY(MapGrid1f) /* 225 */ +ICD_ENTRY(MapGrid2d) /* 226 */ +ICD_ENTRY(MapGrid2f) /* 227 */ +ICD_ENTRY(EvalCoord1d) /* 228 */ +ICD_ENTRY(EvalCoord1dv) /* 229 */ +ICD_ENTRY(EvalCoord1f) /* 230 */ +ICD_ENTRY(EvalCoord1fv) /* 231 */ +ICD_ENTRY(EvalCoord2d) /* 232 */ +ICD_ENTRY(EvalCoord2dv) /* 233 */ +ICD_ENTRY(EvalCoord2f) /* 234 */ +ICD_ENTRY(EvalCoord2fv) /* 235 */ +ICD_ENTRY(EvalMesh1) /* 236 */ +ICD_ENTRY(EvalPoint1) /* 237 */ +ICD_ENTRY(EvalMesh2) /* 238 */ +ICD_ENTRY(EvalPoint2) /* 239 */ +ICD_ENTRY(AlphaFunc) /* 240 */ +ICD_ENTRY(BlendFunc) /* 241 */ +ICD_ENTRY(LogicOp) /* 242 */ +ICD_ENTRY(StencilFunc) /* 243 */ +ICD_ENTRY(StencilOp) /* 244 */ +ICD_ENTRY(DepthFunc) /* 245 */ +ICD_ENTRY(PixelZoom) /* 246 */ +ICD_ENTRY(PixelTransferf) /* 247 */ +ICD_ENTRY(PixelTransferi) /* 248 */ +ICD_ENTRY(PixelStoref) /* 249 */ +ICD_ENTRY(PixelStorei) /* 250 */ +ICD_ENTRY(PixelMapfv) /* 251 */ +ICD_ENTRY(PixelMapuiv) /* 252 */ +ICD_ENTRY(PixelMapusv) /* 253 */ +ICD_ENTRY(ReadBuffer) /* 254 */ +ICD_ENTRY(CopyPixels) /* 255 */ +ICD_ENTRY(ReadPixels) /* 256 */ +ICD_ENTRY(DrawPixels) /* 257 */ +ICD_ENTRY(GetBooleanv) /* 258 */ +ICD_ENTRY(GetClipPlane) /* 259 */ +ICD_ENTRY(GetDoublev) /* 260 */ +ICD_ENTRY(GetError) /* 261 */ +ICD_ENTRY(GetFloatv) /* 262 */ +ICD_ENTRY(GetIntegerv) /* 263 */ +ICD_ENTRY(GetLightfv) /* 264 */ +ICD_ENTRY(GetLightiv) /* 265 */ +ICD_ENTRY(GetMapdv) /* 266 */ +ICD_ENTRY(GetMapfv) /* 267 */ +ICD_ENTRY(GetMapiv) /* 268 */ +ICD_ENTRY(GetMaterialfv) /* 269 */ +ICD_ENTRY(GetMaterialiv) /* 270 */ +ICD_ENTRY(GetPixelMapfv) /* 271 */ +ICD_ENTRY(GetPixelMapuiv) /* 272 */ +ICD_ENTRY(GetPixelMapusv) /* 273 */ +ICD_ENTRY(GetPolygonStipple) /* 274 */ +ICD_ENTRY(GetString) /* 275 */ +ICD_ENTRY(GetTexEnvfv) /* 276 */ +ICD_ENTRY(GetTexEnviv) /* 277 */ +ICD_ENTRY(GetTexGendv) /* 278 */ +ICD_ENTRY(GetTexGenfv) /* 279 */ +ICD_ENTRY(GetTexGeniv) /* 280 */ +ICD_ENTRY(GetTexImage) /* 281 */ +ICD_ENTRY(GetTexParameterfv) /* 282 */ +ICD_ENTRY(GetTexParameteriv) /* 283 */ +ICD_ENTRY(GetTexLevelParameterfv) /* 284 */ +ICD_ENTRY(GetTexLevelParameteriv) /* 285 */ +ICD_ENTRY(IsEnabled) /* 286 */ +ICD_ENTRY(IsList) /* 287 */ +ICD_ENTRY(DepthRange) /* 288 */ +ICD_ENTRY(Frustum) /* 289 */ +ICD_ENTRY(LoadIdentity) /* 290 */ +ICD_ENTRY(LoadMatrixf) /* 291 */ +ICD_ENTRY(LoadMatrixd) /* 292 */ +ICD_ENTRY(MatrixMode) /* 293 */ +ICD_ENTRY(MultMatrixf) /* 294 */ +ICD_ENTRY(MultMatrixd) /* 295 */ +ICD_ENTRY(Ortho) /* 296 */ +ICD_ENTRY(PopMatrix) /* 297 */ +ICD_ENTRY(PushMatrix) /* 298 */ +ICD_ENTRY(Rotated) /* 299 */ +ICD_ENTRY(Rotatef) /* 300 */ +ICD_ENTRY(Scaled) /* 301 */ +ICD_ENTRY(Scalef) /* 302 */ +ICD_ENTRY(Translated) /* 303 */ +ICD_ENTRY(Translatef) /* 304 */ +ICD_ENTRY(Viewport) /* 305 */ +ICD_ENTRY(ArrayElement) /* 306 */ +ICD_ENTRY(BindTexture) /* 307 */ +ICD_ENTRY(ColorPointer) /* 308 */ +ICD_ENTRY(DisableClientState) /* 309 */ +ICD_ENTRY(DrawArrays) /* 310 */ +ICD_ENTRY(DrawElements) /* 311 */ +ICD_ENTRY(EdgeFlagPointer) /* 312 */ +ICD_ENTRY(EnableClientState) /* 313 */ +ICD_ENTRY(IndexPointer) /* 314 */ +ICD_ENTRY(Indexub) /* 315 */ +ICD_ENTRY(Indexubv) /* 316 */ +ICD_ENTRY(InterleavedArrays) /* 317 */ +ICD_ENTRY(NormalPointer) /* 318 */ +ICD_ENTRY(PolygonOffset) /* 319 */ +ICD_ENTRY(TexCoordPointer) /* 320 */ +ICD_ENTRY(VertexPointer) /* 321 */ +ICD_ENTRY(AreTexturesResident) /* 322 */ +ICD_ENTRY(CopyTexImage1D) /* 323 */ +ICD_ENTRY(CopyTexImage2D) /* 324 */ +ICD_ENTRY(CopyTexSubImage1D) /* 325 */ +ICD_ENTRY(CopyTexSubImage2D) /* 326 */ +ICD_ENTRY(DeleteTextures) /* 327 */ +ICD_ENTRY(GenTextures) /* 328 */ +ICD_ENTRY(GetPointerv) /* 329 */ +ICD_ENTRY(IsTexture) /* 330 */ +ICD_ENTRY(PrioritizeTextures) /* 331 */ +ICD_ENTRY(TexSubImage1D) /* 332 */ +ICD_ENTRY(TexSubImage2D) /* 333 */ +ICD_ENTRY(PopClientAttrib) /* 334 */ +ICD_ENTRY(PushClientAttrib) /* 335 */ diff --git a/src/VBox/Additions/common/crOpenGL/alias_exports.py b/src/VBox/Additions/common/crOpenGL/alias_exports.py new file mode 100644 index 00000000..5d16139a --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/alias_exports.py @@ -0,0 +1,168 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +aliases = [ + # GL_ARB_multitexture / OpenGL 1.2.1 + ('ActiveTexture', 'ActiveTextureARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('ClientActiveTexture', 'ClientActiveTextureARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord1d', 'MultiTexCoord1dARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord1dv','MultiTexCoord1dvARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord1f', 'MultiTexCoord1fARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord1fv','MultiTexCoord1fvARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord1i', 'MultiTexCoord1iARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord1iv','MultiTexCoord1ivARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord1s', 'MultiTexCoord1sARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord1sv','MultiTexCoord1svARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord2d', 'MultiTexCoord2dARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord2dv','MultiTexCoord2dvARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord2f', 'MultiTexCoord2fARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord2fv','MultiTexCoord2fvARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord2i', 'MultiTexCoord2iARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord2iv','MultiTexCoord2ivARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord2s', 'MultiTexCoord2sARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord2sv','MultiTexCoord2svARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord3d', 'MultiTexCoord3dARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord3dv','MultiTexCoord3dvARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord3f', 'MultiTexCoord3fARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord3fv','MultiTexCoord3fvARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord3i', 'MultiTexCoord3iARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord3iv','MultiTexCoord3ivARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord3s', 'MultiTexCoord3sARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord3sv','MultiTexCoord3svARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord4d', 'MultiTexCoord4dARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord4dv','MultiTexCoord4dvARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord4f', 'MultiTexCoord4fARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord4fv','MultiTexCoord4fvARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord4i', 'MultiTexCoord4iARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord4iv','MultiTexCoord4ivARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord4s', 'MultiTexCoord4sARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + ('MultiTexCoord4sv','MultiTexCoord4svARB', 'ARB_multitexture', 'CR_OPENGL_VERSION_1_2_1'), + # GL_ARB_transpose_matrix / OpenGL 1.3 + ('LoadTransposeMatrixf','LoadTransposeMatrixfARB', 'ARB_transpose_matrix', 'CR_OPENGL_VERSION_1_3'), + ('LoadTransposeMatrixd','LoadTransposeMatrixdARB', 'ARB_transpose_matrix', 'CR_OPENGL_VERSION_1_3'), + ('MultTransposeMatrixf','MultTransposeMatrixfARB', 'ARB_transpose_matrix', 'CR_OPENGL_VERSION_1_3'), + ('MultTransposeMatrixd','MultTransposeMatrixdARB', 'ARB_transpose_matrix', 'CR_OPENGL_VERSION_1_3'), + # GL_ARB_texture_compression / OpenGL 1.3 + ('CompressedTexImage3D', 'CompressedTexImage3DARB', 'ARB_texture_compression', 'CR_OPENGL_VERSION_1_3'), + ('CompressedTexImage2D', 'CompressedTexImage2DARB', 'ARB_texture_compression', 'CR_OPENGL_VERSION_1_3'), + ('CompressedTexImage1D', 'CompressedTexImage1DARB', 'ARB_texture_compression', 'CR_OPENGL_VERSION_1_3'), + ('CompressedTexSubImage3D', 'CompressedTexSubImage3DARB', 'ARB_texture_compression', 'CR_OPENGL_VERSION_1_3'), + ('CompressedTexSubImage2D', 'CompressedTexSubImage2DARB', 'ARB_texture_compression', 'CR_OPENGL_VERSION_1_3'), + ('CompressedTexSubImage1D', 'CompressedTexSubImage1DARB', 'ARB_texture_compression', 'CR_OPENGL_VERSION_1_3'), + ('GetCompressedTexImage', 'GetCompressedTexImageARB', 'ARB_texture_compression', 'CR_OPENGL_VERSION_1_3'), + # GL_ARB_multisample / OpenGL 1.3 + ('SampleCoverage', 'SampleCoverageARB', 'ARB_multisample', 'CR_OPENGL_VERSION_1_3'), + # GL_ARB_window_pos / OpenGL 1.4 + ('WindowPos2d', 'WindowPos2dARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + + ('WindowPos2dv', 'WindowPos2dvARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos2f', 'WindowPos2fARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos2fv', 'WindowPos2fvARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos2i', 'WindowPos2iARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos2iv', 'WindowPos2ivARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos2s', 'WindowPos2sARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos2sv', 'WindowPos2svARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos3d', 'WindowPos3dARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos3dv', 'WindowPos3dvARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos3f', 'WindowPos3fARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos3fv', 'WindowPos3fvARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos3i', 'WindowPos3iARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos3iv', 'WindowPos3ivARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos3s', 'WindowPos3sARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + ('WindowPos3sv', 'WindowPos3svARB', 'ARB_window_pos', 'CR_OPENGL_VERSION_1_4'), + # GL_ARB_point_parameters / OpenGL 1.4 + ('PointParameterf', 'PointParameterfARB', 'ARB_point_parameters', 'CR_OPENGL_VERSION_1_4'), + ('PointParameterfv', 'PointParameterfvARB', 'ARB_point_parameters', 'CR_OPENGL_VERSION_1_4'), + # GL_EXT_blend_color / OpenGL 1.4 + ('BlendColor', 'BlendColorEXT', 'EXT_blend_color', 'CR_OPENGL_VERSION_1_4'), + # GL_EXT_blend_func_separate / OpenGL 1.4 + ('BlendFuncSeparate', 'BlendFuncSeparateEXT', 'EXT_blend_func_separate', 'CR_OPENGL_VERSION_1_4'), + # GL_EXT_blend_equation / OpenGL 1.4 + ('BlendEquation', 'BlendEquationEXT', 'EXT_blend_equation', 'CR_OPENGL_VERSION_1_4'), + # GL_EXT_multi_draw_arrays / OpenGL 1.4 + ('MultiDrawArrays', 'MultiDrawArraysEXT', 'EXT_multi_draw_arrays', 'CR_OPENGL_VERSION_1_4'), + ('MultiDrawElements', 'MultiDrawElementsEXT', 'EXT_multi_draw_arrays', 'CR_OPENGL_VERSION_1_4'), + # GL_EXT_secondary_color / OpenGL 1.4 + ('SecondaryColor3b', 'SecondaryColor3bEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3bv', 'SecondaryColor3bvEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3d', 'SecondaryColor3dEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3dv', 'SecondaryColor3dvEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3f', 'SecondaryColor3fEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3fv', 'SecondaryColor3fvEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3i', 'SecondaryColor3iEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3iv', 'SecondaryColor3ivEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3s', 'SecondaryColor3sEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3sv', 'SecondaryColor3svEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3ub', 'SecondaryColor3ubEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3ubv', 'SecondaryColor3ubvEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3ui', 'SecondaryColor3uiEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3uiv', 'SecondaryColor3uivEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3us', 'SecondaryColor3usEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColor3usv', 'SecondaryColor3usvEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + ('SecondaryColorPointer', 'SecondaryColorPointerEXT', 'EXT_secondary_color', 'CR_OPENGL_VERSION_1_4'), + # GL_EXT_fog_coord / OpenGL 1.4 + ('FogCoordf', 'FogCoordfEXT', 'EXT_fog_coord', 'CR_OPENGL_VERSION_1_4'), + ('FogCoordfv', 'FogCoordfvEXT', 'EXT_fog_coord', 'CR_OPENGL_VERSION_1_4'), + ('FogCoordd', 'FogCoorddEXT', 'EXT_fog_coord', 'CR_OPENGL_VERSION_1_4'), + ('FogCoorddv', 'FogCoorddvEXT', 'EXT_fog_coord', 'CR_OPENGL_VERSION_1_4'), + ('FogCoordPointer', 'FogCoordPointerEXT', 'EXT_fog_coord', 'CR_OPENGL_VERSION_1_4'), + # GL_EXT_texture_object / OpenGL 1.1 + ('AreTexturesResidentEXT', 'AreTexturesResident', 'EXT_texture_object', 'CR_OPENGL_VERSION_1_1'), + ('BindTextureEXT', 'BindTexture', 'EXT_texture_object', 'CR_OPENGL_VERSION_1_1'), + ('DeleteTexturesEXT', 'DeleteTextures', 'EXT_texture_object', 'CR_OPENGL_VERSION_1_1'), + ('GenTexturesEXT', 'GenTextures', 'EXT_texture_object', 'CR_OPENGL_VERSION_1_1'), + ('IsTextureEXT', 'IsTexture', 'EXT_texture_object', 'CR_OPENGL_VERSION_1_1'), + ('PrioritizeTexturesEXT', 'PrioritizeTextures', 'EXT_texture_object', 'CR_OPENGL_VERSION_1_1'), + # GL_EXT_texture3D / OpenGL 1.2 + ('TexSubImage3DEXT', 'TexSubImage3D', 'EXT_texture3D', 'CR_OPENGL_VERSION_1_2'), + + # GL_NV_vertex_program / GL_ARB_vertex_program + ('VertexAttrib1sNV', 'VertexAttrib1sARB', 'ARB_vertex_program', ''), + ('VertexAttrib1svNV', 'VertexAttrib1svARB', 'ARB_vertex_program', ''), + ('VertexAttrib1fNV', 'VertexAttrib1fARB', 'ARB_vertex_program', ''), + ('VertexAttrib1fvNV', 'VertexAttrib1fvARB', 'ARB_vertex_program', ''), + ('VertexAttrib1dNV', 'VertexAttrib1dARB', 'ARB_vertex_program', ''), + ('VertexAttrib1dvNV', 'VertexAttrib1dvARB', 'ARB_vertex_program', ''), + ('VertexAttrib2sNV', 'VertexAttrib2sARB', 'ARB_vertex_program', ''), + ('VertexAttrib2svNV', 'VertexAttrib2svARB', 'ARB_vertex_program', ''), + ('VertexAttrib2fNV', 'VertexAttrib2fARB', 'ARB_vertex_program', ''), + ('VertexAttrib2fvNV', 'VertexAttrib2fvARB', 'ARB_vertex_program', ''), + ('VertexAttrib2dNV', 'VertexAttrib2dARB', 'ARB_vertex_program', ''), + ('VertexAttrib2dvNV', 'VertexAttrib2dvARB', 'ARB_vertex_program', ''), + ('VertexAttrib3sNV', 'VertexAttrib3sARB', 'ARB_vertex_program', ''), + ('VertexAttrib3svNV', 'VertexAttrib3svARB', 'ARB_vertex_program', ''), + ('VertexAttrib3fNV', 'VertexAttrib3fARB', 'ARB_vertex_program', ''), + ('VertexAttrib3fvNV', 'VertexAttrib3fvARB', 'ARB_vertex_program', ''), + ('VertexAttrib3dNV', 'VertexAttrib3dARB', 'ARB_vertex_program', ''), + ('VertexAttrib3dvNV', 'VertexAttrib3dvARB', 'ARB_vertex_program', ''), + ('VertexAttrib4sNV', 'VertexAttrib4sARB', 'ARB_vertex_program', ''), + ('VertexAttrib4svNV', 'VertexAttrib4svARB', 'ARB_vertex_program', ''), + ('VertexAttrib4fNV', 'VertexAttrib4fARB', 'ARB_vertex_program', ''), + ('VertexAttrib4fvNV', 'VertexAttrib4fvARB', 'ARB_vertex_program', ''), + ('VertexAttrib4dNV', 'VertexAttrib4dARB', 'ARB_vertex_program', ''), + ('VertexAttrib4dvNV', 'VertexAttrib4dvARB', 'ARB_vertex_program', ''), + ('VertexAttrib4ubNV', 'VertexAttrib4NubARB', 'ARB_vertex_program', ''), + ('VertexAttrib4ubvNV', 'VertexAttrib4NubvARB', 'ARB_vertex_program', ''), + ('DeleteProgramsNV', 'DeleteProgramsARB', 'ARB_vertex_program', ''), + ('IsProgramNV', 'IsProgramARB', 'ARB_vertex_program', '') +] + +def AliasMap( func_name ): + for (aliased_func_name, real_func_name, ext_define, gl_version) in aliases: + if real_func_name == func_name: + return aliased_func_name; + return None + +def ExtDefine( func_name ): + for (aliased_func_name, real_func_name, ext_define, gl_version) in aliases: + if real_func_name == func_name: + return ext_define; + return None + +def GLversion( func_name ): + for (aliased_func_name, real_func_name, ext_define, gl_version) in aliases: + if real_func_name == func_name: + return gl_version; + return None diff --git a/src/VBox/Additions/common/crOpenGL/array/Makefile.kup b/src/VBox/Additions/common/crOpenGL/array/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/array/Makefile.kup diff --git a/src/VBox/Additions/common/crOpenGL/array/arrayspu.c b/src/VBox/Additions/common/crOpenGL/array/arrayspu.c new file mode 100644 index 00000000..da230ee5 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/array/arrayspu.c @@ -0,0 +1,944 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include <stdio.h> +#include "cr_spu.h" +#include "cr_error.h" +#include "state/cr_limits.h" +#include "arrayspu.h" + +ArraySPU array_spu; + +#ifdef CHROMIUM_THREADSAFE +CRmutex _ArrayMutex; +#endif + +static void ARRAYSPU_APIENTRY arrayspu_ArrayElement( GLint index ) +{ + const CRClientState *c = &(crStateGetCurrent()->client); + const CRVertexArrays *array = &(c->array); + const GLboolean vpEnabled = crStateGetCurrent()->program.vpEnabled; + unsigned char *p; + unsigned int unit, attr; + + if (array->e.enabled) + { + p = array->e.p + index * array->e.stride; + +#ifdef CR_ARB_vertex_buffer_object + if (array->e.buffer && array->e.buffer->data) + { + p = (unsigned char *)(array->e.buffer->data) + (unsigned long)p; + } +#endif + + array_spu.self.EdgeFlagv(p); + } + + /* + * Vertex attribute arrays (GL_NV_vertex_program) have priority over + * the conventional vertex arrays. + */ + if (vpEnabled) + { + for (attr = 1; attr < VERT_ATTRIB_MAX; attr++) + { + if (array->a[attr].enabled) + { + GLint *iPtr; + p = array->a[attr].p + index * array->a[attr].stride; + +#ifdef CR_ARB_vertex_buffer_object + if (array->a[attr].buffer && array->a[attr].buffer->data) + { + p = (unsigned char *)(array->a[attr].buffer->data) + (unsigned long)p; + } +#endif + + switch (array->a[attr].type) + { + case GL_SHORT: + switch (array->a[attr].size) + { + case 1: array_spu.self.VertexAttrib1svARB(attr, (GLshort *)p); break; + case 2: array_spu.self.VertexAttrib2svARB(attr, (GLshort *)p); break; + case 3: array_spu.self.VertexAttrib3svARB(attr, (GLshort *)p); break; + case 4: array_spu.self.VertexAttrib4svARB(attr, (GLshort *)p); break; + } + break; + case GL_INT: + iPtr = (GLint *) p; + switch (array->a[attr].size) + { + case 1: array_spu.self.VertexAttrib1fARB(attr, p[0]); break; + case 2: array_spu.self.VertexAttrib2fARB(attr, p[0], p[1]); break; + case 3: array_spu.self.VertexAttrib3fARB(attr, p[0], p[1], p[2]); break; + case 4: array_spu.self.VertexAttrib4fARB(attr, p[0], p[1], p[2], p[3]); break; + } + break; + case GL_FLOAT: + switch (array->a[attr].size) + { + case 1: array_spu.self.VertexAttrib1fvARB(attr, (GLfloat *)p); break; + case 2: array_spu.self.VertexAttrib2fvARB(attr, (GLfloat *)p); break; + case 3: array_spu.self.VertexAttrib3fvARB(attr, (GLfloat *)p); break; + case 4: array_spu.self.VertexAttrib4fvARB(attr, (GLfloat *)p); break; + } + break; + case GL_DOUBLE: + switch (array->a[attr].size) + { + case 1: array_spu.self.VertexAttrib1dvARB(attr, (GLdouble *)p); break; + case 2: array_spu.self.VertexAttrib2dvARB(attr, (GLdouble *)p); break; + case 3: array_spu.self.VertexAttrib3dvARB(attr, (GLdouble *)p); break; + case 4: array_spu.self.VertexAttrib4dvARB(attr, (GLdouble *)p); break; + } + break; + default: + crWarning("Bad datatype for vertex attribute [%d] array: 0x%x\n", + attr, array->a[attr].type); + } + } + } + } + + /* Now do conventional arrays, unless overridden by generic arrays above */ + for (unit = 0 ; unit < crStateGetCurrent()->limits.maxTextureUnits ; unit++) + { + if (array->t[unit].enabled && !(vpEnabled && array->a[VERT_ATTRIB_TEX0+unit].enabled)) + { + p = array->t[unit].p + index * array->t[unit].stride; + +#ifdef CR_ARB_vertex_buffer_object + if (array->t[unit].buffer && array->t[unit].buffer->data) + { + p = (unsigned char *)(array->t[unit].buffer->data) + (unsigned long)p; + } +#endif + + switch (array->t[unit].type) + { + case GL_SHORT: + switch (array->t[unit].size) + { + case 1: array_spu.self.MultiTexCoord1svARB(GL_TEXTURE0_ARB + unit, (GLshort *)p); break; + case 2: array_spu.self.MultiTexCoord2svARB(GL_TEXTURE0_ARB + unit, (GLshort *)p); break; + case 3: array_spu.self.MultiTexCoord3svARB(GL_TEXTURE0_ARB + unit, (GLshort *)p); break; + case 4: array_spu.self.MultiTexCoord4svARB(GL_TEXTURE0_ARB + unit, (GLshort *)p); break; + } + break; + case GL_INT: + switch (array->t[unit].size) + { + case 1: array_spu.self.MultiTexCoord1ivARB(GL_TEXTURE0_ARB + unit, (GLint *)p); break; + case 2: array_spu.self.MultiTexCoord2ivARB(GL_TEXTURE0_ARB + unit, (GLint *)p); break; + case 3: array_spu.self.MultiTexCoord3ivARB(GL_TEXTURE0_ARB + unit, (GLint *)p); break; + case 4: array_spu.self.MultiTexCoord4ivARB(GL_TEXTURE0_ARB + unit, (GLint *)p); break; + } + break; + case GL_FLOAT: + switch (array->t[unit].size) + { + case 1: array_spu.self.MultiTexCoord1fvARB(GL_TEXTURE0_ARB + unit, (GLfloat *)p); break; + case 2: array_spu.self.MultiTexCoord2fvARB(GL_TEXTURE0_ARB + unit, (GLfloat *)p); break; + case 3: array_spu.self.MultiTexCoord3fvARB(GL_TEXTURE0_ARB + unit, (GLfloat *)p); break; + case 4: array_spu.self.MultiTexCoord4fvARB(GL_TEXTURE0_ARB + unit, (GLfloat *)p); break; + } + break; + case GL_DOUBLE: + switch (array->t[unit].size) + { + case 1: array_spu.self.MultiTexCoord1dvARB(GL_TEXTURE0_ARB + unit, (GLdouble *)p); break; + case 2: array_spu.self.MultiTexCoord2dvARB(GL_TEXTURE0_ARB + unit, (GLdouble *)p); break; + case 3: array_spu.self.MultiTexCoord3dvARB(GL_TEXTURE0_ARB + unit, (GLdouble *)p); break; + case 4: array_spu.self.MultiTexCoord4dvARB(GL_TEXTURE0_ARB + unit, (GLdouble *)p); break; + } + break; + } + } + } + if (array->i.enabled) + { + p = array->i.p + index * array->i.stride; + +#ifdef CR_ARB_vertex_buffer_object + if (array->i.buffer && array->i.buffer->data) + { + p = (unsigned char *)(array->i.buffer->data) + (unsigned long)p; + } +#endif + + switch (array->i.type) + { + case GL_SHORT: array_spu.self.Indexsv((GLshort *)p); break; + case GL_INT: array_spu.self.Indexiv((GLint *)p); break; + case GL_FLOAT: array_spu.self.Indexfv((GLfloat *)p); break; + case GL_DOUBLE: array_spu.self.Indexdv((GLdouble *)p); break; + } + } + if (array->c.enabled && !(vpEnabled && array->a[VERT_ATTRIB_COLOR0].enabled)) + { + p = array->c.p + index * array->c.stride; + +#ifdef CR_ARB_vertex_buffer_object + if (array->c.buffer && array->c.buffer->data) + { + p = (unsigned char *)(array->c.buffer->data) + (unsigned long)p; + } +#endif + + switch (array->c.type) + { + case GL_BYTE: + switch (array->c.size) + { + case 3: array_spu.self.Color3bv((GLbyte *)p); break; + case 4: array_spu.self.Color4bv((GLbyte *)p); break; + } + break; + case GL_UNSIGNED_BYTE: + switch (array->c.size) + { + case 3: array_spu.self.Color3ubv((GLubyte *)p); break; + case 4: array_spu.self.Color4ubv((GLubyte *)p); break; + } + break; + case GL_SHORT: + switch (array->c.size) + { + case 3: array_spu.self.Color3sv((GLshort *)p); break; + case 4: array_spu.self.Color4sv((GLshort *)p); break; + } + break; + case GL_UNSIGNED_SHORT: + switch (array->c.size) + { + case 3: array_spu.self.Color3usv((GLushort *)p); break; + case 4: array_spu.self.Color4usv((GLushort *)p); break; + } + break; + case GL_INT: + switch (array->c.size) + { + case 3: array_spu.self.Color3iv((GLint *)p); break; + case 4: array_spu.self.Color4iv((GLint *)p); break; + } + break; + case GL_UNSIGNED_INT: + switch (array->c.size) + { + case 3: array_spu.self.Color3uiv((GLuint *)p); break; + case 4: array_spu.self.Color4uiv((GLuint *)p); break; + } + break; + case GL_FLOAT: + switch (array->c.size) + { + case 3: array_spu.self.Color3fv((GLfloat *)p); break; + case 4: array_spu.self.Color4fv((GLfloat *)p); break; + } + break; + case GL_DOUBLE: + switch (array->c.size) + { + case 3: array_spu.self.Color3dv((GLdouble *)p); break; + case 4: array_spu.self.Color4dv((GLdouble *)p); break; + } + break; + } + } + if (array->n.enabled && !(vpEnabled && array->a[VERT_ATTRIB_NORMAL].enabled)) + { + p = array->n.p + index * array->n.stride; + +#ifdef CR_ARB_vertex_buffer_object + if (array->n.buffer && array->n.buffer->data) + { + p = (unsigned char *)(array->n.buffer->data) + (unsigned long)p; + } +#endif + + switch (array->n.type) + { + case GL_BYTE: array_spu.self.Normal3bv((GLbyte *)p); break; + case GL_SHORT: array_spu.self.Normal3sv((GLshort *)p); break; + case GL_INT: array_spu.self.Normal3iv((GLint *)p); break; + case GL_FLOAT: array_spu.self.Normal3fv((GLfloat *)p); break; + case GL_DOUBLE: array_spu.self.Normal3dv((GLdouble *)p); break; + } + } +#ifdef CR_EXT_secondary_color + if (array->s.enabled && !(vpEnabled && array->a[VERT_ATTRIB_COLOR1].enabled)) + { + p = array->s.p + index * array->s.stride; + +#ifdef CR_ARB_vertex_buffer_object + if (array->s.buffer && array->s.buffer->data) + { + p = (unsigned char *)(array->s.buffer->data) + (unsigned long)p; + } +#endif + + switch (array->s.type) + { + case GL_BYTE: + array_spu.self.SecondaryColor3bvEXT((GLbyte *)p); break; + case GL_UNSIGNED_BYTE: + array_spu.self.SecondaryColor3ubvEXT((GLubyte *)p); break; + case GL_SHORT: + array_spu.self.SecondaryColor3svEXT((GLshort *)p); break; + case GL_UNSIGNED_SHORT: + array_spu.self.SecondaryColor3usvEXT((GLushort *)p); break; + case GL_INT: + array_spu.self.SecondaryColor3ivEXT((GLint *)p); break; + case GL_UNSIGNED_INT: + array_spu.self.SecondaryColor3uivEXT((GLuint *)p); break; + case GL_FLOAT: + array_spu.self.SecondaryColor3fvEXT((GLfloat *)p); break; + case GL_DOUBLE: + array_spu.self.SecondaryColor3dvEXT((GLdouble *)p); break; + } + } +#endif // CR_EXT_secondary_color +#ifdef CR_EXT_fog_coord + if (array->f.enabled && !(vpEnabled && array->a[VERT_ATTRIB_FOG].enabled)) + { + p = array->f.p + index * array->f.stride; + +#ifdef CR_ARB_vertex_buffer_object + if (array->f.buffer && array->f.buffer->data) + { + p = (unsigned char *)(array->f.buffer->data) + (unsigned long)p; + } +#endif + + array_spu.self.FogCoordfEXT( *((GLfloat *) p) ); + } +#endif // CR_EXT_fog_coord + + /* Need to do attrib[0] / vertex position last */ + if (array->a[VERT_ATTRIB_POS].enabled) { + GLint *iPtr; + p = array->a[VERT_ATTRIB_POS].p + index * array->a[VERT_ATTRIB_POS].stride; + +#ifdef CR_ARB_vertex_buffer_object + if (array->a[VERT_ATTRIB_POS].buffer && array->a[VERT_ATTRIB_POS].buffer->data) + { + p = (unsigned char *)(array->a[VERT_ATTRIB_POS].buffer->data) + (unsigned long)p; + } +#endif + + switch (array->a[VERT_ATTRIB_POS].type) + { + case GL_SHORT: + switch (array->a[VERT_ATTRIB_POS].size) + { + case 1: array_spu.self.VertexAttrib1svARB(0, (GLshort *)p); break; + case 2: array_spu.self.VertexAttrib2svARB(0, (GLshort *)p); break; + case 3: array_spu.self.VertexAttrib3svARB(0, (GLshort *)p); break; + case 4: array_spu.self.VertexAttrib4svARB(0, (GLshort *)p); break; + } + break; + case GL_INT: + iPtr = (GLint *) p; + switch (array->a[VERT_ATTRIB_POS].size) + { + case 1: array_spu.self.VertexAttrib1fARB(0, p[0]); break; + case 2: array_spu.self.VertexAttrib2fARB(0, p[0], p[1]); break; + case 3: array_spu.self.VertexAttrib3fARB(0, p[0], p[1], p[2]); break; + case 4: array_spu.self.VertexAttrib4fARB(0, p[0], p[1], p[2], p[3]); break; + } + break; + case GL_FLOAT: + switch (array->a[VERT_ATTRIB_POS].size) + { + case 1: array_spu.self.VertexAttrib1fvARB(0, (GLfloat *)p); break; + case 2: array_spu.self.VertexAttrib2fvARB(0, (GLfloat *)p); break; + case 3: array_spu.self.VertexAttrib3fvARB(0, (GLfloat *)p); break; + case 4: array_spu.self.VertexAttrib4fvARB(0, (GLfloat *)p); break; + } + break; + case GL_DOUBLE: + switch (array->a[VERT_ATTRIB_POS].size) + { + case 1: array_spu.self.VertexAttrib1dvARB(0, (GLdouble *)p); break; + case 2: array_spu.self.VertexAttrib2dvARB(0, (GLdouble *)p); break; + case 3: array_spu.self.VertexAttrib3dvARB(0, (GLdouble *)p); break; + case 4: array_spu.self.VertexAttrib4dvARB(0, (GLdouble *)p); break; + } + break; + default: + crWarning("Bad datatype for vertex attribute [0] array: 0x%x\n", array->a[0].type); + } + } + else if (array->v.enabled) + { + p = array->v.p + index * array->v.stride; + +#ifdef CR_ARB_vertex_buffer_object + if (array->v.buffer && array->v.buffer->data) + { + p = (unsigned char *)(array->v.buffer->data) + (unsigned long)p; + } +#endif + + switch (array->v.type) + { + case GL_SHORT: + switch (array->v.size) + { + case 2: array_spu.self.Vertex2sv((GLshort *)p); break; + case 3: array_spu.self.Vertex3sv((GLshort *)p); break; + case 4: array_spu.self.Vertex4sv((GLshort *)p); break; + } + break; + case GL_INT: + switch (array->v.size) + { + case 2: array_spu.self.Vertex2iv((GLint *)p); break; + case 3: array_spu.self.Vertex3iv((GLint *)p); break; + case 4: array_spu.self.Vertex4iv((GLint *)p); break; + } + break; + case GL_FLOAT: + switch (array->v.size) + { + case 2: array_spu.self.Vertex2fv((GLfloat *)p); break; + case 3: array_spu.self.Vertex3fv((GLfloat *)p); break; + case 4: array_spu.self.Vertex4fv((GLfloat *)p); break; + } + break; + case GL_DOUBLE: + switch (array->v.size) + { + case 2: array_spu.self.Vertex2dv((GLdouble *)p); break; + case 3: array_spu.self.Vertex3dv((GLdouble *)p); break; + case 4: array_spu.self.Vertex4dv((GLdouble *)p); break; + } + break; + default: + crWarning("Bad datatype for vertex array: 0x%x\n", array->v.type); + } + } +} + +static void ARRAYSPU_APIENTRY arrayspu_DrawArrays(GLenum mode, GLint first, GLsizei count) +{ + int i; + + if (count < 0) + { + crError("array_spu.self.DrawArrays passed negative count: %d", count); + } + + if (mode > GL_POLYGON) + { + crError("array_spu.self.DrawArrays called with invalid mode: %d", mode); + } + + array_spu.self.Begin(mode); + for (i=0; i<count; i++) + { + array_spu.self.ArrayElement(first++); + } + array_spu.self.End(); +} + +static void ARRAYSPU_APIENTRY arrayspu_DrawElements(GLenum mode, GLsizei count, + GLenum type, const GLvoid *indices) +{ + int i; + GLubyte *p = (GLubyte *)indices; +#ifdef CR_ARB_vertex_buffer_object + CRBufferObject *elementsBuffer = crStateGetCurrent()->bufferobject.elementsBuffer; +#endif + + if (count < 0) + { + crError("array_spu.self.DrawElements passed negative count: %d", count); + } + + if (mode > GL_POLYGON) + { + crError("array_spu.self.DrawElements called with invalid mode: %d", mode); + } + + if (type != GL_UNSIGNED_BYTE && type != GL_UNSIGNED_SHORT && type != GL_UNSIGNED_INT) + { + crError("array_spu.self.DrawElements called with invalid type: %d", type); + } + +#ifdef CR_ARB_vertex_buffer_object + if (elementsBuffer && elementsBuffer->data) + { + p = (unsigned char *)(elementsBuffer->data) + (unsigned long)p; + } +#endif + //crDebug("arrayspu_DrawElements mode:0x%x, count:%d, type:0x%x", mode, count, type); + + + array_spu.self.Begin(mode); + switch (type) + { + case GL_UNSIGNED_BYTE: + for (i=0; i<count; i++) + { + array_spu.self.ArrayElement((GLint) *p++); + } + break; + case GL_UNSIGNED_SHORT: + for (i=0; i<count; i++) + { + array_spu.self.ArrayElement((GLint) * (GLushort *) p); + p+=sizeof (GLushort); + } + break; + case GL_UNSIGNED_INT: + for (i=0; i<count; i++) + { + array_spu.self.ArrayElement((GLint) * (GLuint *) p); + p+=sizeof (GLuint); + } + break; + default: + crError( "this can't happen: array_spu.self.DrawElements" ); + break; + } + array_spu.self.End(); +} + +static void ARRAYSPU_APIENTRY arrayspu_DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices) +{ + if (start>end) + { + crError("array_spu.self.arrayspu_DrawRangeElements start>end (%d>%d)", start, end); + } + + arrayspu_DrawElements(mode, count, type, indices); +} + +static void ARRAYSPU_APIENTRY arrayspu_ColorPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ + crStateColorPointer(size, type, stride, pointer); + array_spu.child.ColorPointer(size, type, stride, pointer); +} + +static void ARRAYSPU_APIENTRY arrayspu_SecondaryColorPointerEXT( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ + crStateSecondaryColorPointerEXT(size, type, stride, pointer); + array_spu.child.SecondaryColorPointerEXT(size, type, stride, pointer); +} + +static void ARRAYSPU_APIENTRY arrayspu_VertexPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ + crStateVertexPointer(size, type, stride, pointer); + array_spu.child.VertexPointer(size, type, stride, pointer); +} + +static void ARRAYSPU_APIENTRY arrayspu_TexCoordPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ + crStateTexCoordPointer(size, type, stride, pointer); + array_spu.child.TexCoordPointer(size, type, stride, pointer); +} + +static void ARRAYSPU_APIENTRY arrayspu_NormalPointer( GLenum type, GLsizei stride, const GLvoid *pointer ) +{ + crStateNormalPointer(type, stride, pointer); + array_spu.child.NormalPointer(type, stride, pointer); +} + +static void ARRAYSPU_APIENTRY arrayspu_IndexPointer( GLenum type, GLsizei stride, const GLvoid *pointer ) +{ + crStateIndexPointer(type, stride, pointer); + array_spu.child.IndexPointer(type, stride, pointer); +} + +static void ARRAYSPU_APIENTRY arrayspu_EdgeFlagPointer( GLsizei stride, const GLvoid *pointer ) +{ + crStateEdgeFlagPointer(stride, pointer); + array_spu.child.EdgeFlagPointer(stride, pointer); +} + +static void ARRAYSPU_APIENTRY arrayspu_VertexAttribPointerNV( GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid * pointer ) +{ + crStateVertexAttribPointerNV(index, size, type, stride, pointer); + array_spu.child.VertexAttribPointerNV(index, size, type, stride, pointer); +} + +static void ARRAYSPU_APIENTRY arrayspu_FogCoordPointerEXT( GLenum type, GLsizei stride, const GLvoid *pointer ) +{ + crStateFogCoordPointerEXT(type, stride, pointer); + array_spu.child.FogCoordPointerEXT(type, stride, pointer); +} + +static void ARRAYSPU_APIENTRY arrayspu_GetPointerv( GLenum pname, GLvoid **params ) +{ + crStateGetPointerv(pname, params); +} + +static void ARRAYSPU_APIENTRY arrayspu_EnableClientState( GLenum array ) +{ + crStateEnableClientState(array); + array_spu.child.EnableClientState(array); +} + +static void ARRAYSPU_APIENTRY arrayspu_DisableClientState( GLenum array ) +{ + crStateDisableClientState(array); + array_spu.child.DisableClientState(array); +} + +static void ARRAYSPU_APIENTRY arrayspu_ClientActiveTextureARB( GLenum texture ) +{ + crStateClientActiveTextureARB(texture); + array_spu.child.ClientActiveTextureARB(texture); +} + +static void ARRAYSPU_APIENTRY arrayspu_MultiDrawArraysEXT(GLenum mode, GLint *first, GLsizei *count, GLsizei primcount) +{ + int i; + + if (primcount < 0) + { + crError("array_spu.self.MultiDrawArraysEXT passed negative count: %d", primcount); + } + + if (mode > GL_POLYGON) + { + crError("array_spu.self.MultiDrawArraysEXT called with invalid mode: %d", mode); + } + + for (i = 0; i < primcount; i++) + { + array_spu.self.DrawArrays(mode, first[i], count[i]); + } +} + +static void ARRAYSPU_APIENTRY arrayspu_MultiDrawElementsEXT(GLenum mode, GLsizei *count, GLenum type, const GLvoid **indices, GLsizei primcount) +{ + int i; + + if (primcount < 0) + { + crError("array_spu.self.MultiDrawElementsEXT passed negative count: %d", primcount); + } + + if (mode > GL_POLYGON) + { + crError("array_spu.self.MultiDrawElementsEXT called with invalid mode: %d", mode); + } + + if (type != GL_UNSIGNED_BYTE && type != GL_UNSIGNED_SHORT && type != GL_UNSIGNED_INT) + { + crError("array_spu.self.MultiDrawElementsEXT called with invalid type: %d", type); + } + + for (i = 0; i < primcount; i++) + { + array_spu.self.DrawElements(mode, count[i], type, indices[i]); + } +} + +/* + * We need to know when vertex program mode is enabled/disabled + * in order to handle vertex attribute arrays correctly. + */ +static void ARRAYSPU_APIENTRY arrayspu_Enable(GLenum cap) +{ + if (cap == GL_VERTEX_PROGRAM_NV) { + crStateGetCurrent()->program.vpEnabled = GL_TRUE; + } + array_spu.child.Enable(cap); +} + + +static void ARRAYSPU_APIENTRY arrayspu_Disable(GLenum cap) +{ + if (cap == GL_VERTEX_PROGRAM_NV) { + crStateGetCurrent()->program.vpEnabled = GL_FALSE; + } + array_spu.child.Disable(cap); +} + +/** @todo it's a hack, as GLSL shouldn't blindly reuse this bit from nv_vertex_program*/ +static void ARRAYSPU_APIENTRY arrayspu_UseProgram(GLuint program) +{ + crStateGetCurrent()->program.vpEnabled = program>0; + array_spu.child.UseProgram(program); +} + +static void ARRAYSPU_APIENTRY +arrayspu_VertexAttribPointerARB(GLuint index, GLint size, GLenum type, + GLboolean normalized, GLsizei stride, + const GLvoid *pointer) +{ + crStateVertexAttribPointerARB(index, size, type, normalized, stride, pointer); + array_spu.child.VertexAttribPointerARB(index, size, type, normalized, stride, pointer); +} + + +static void ARRAYSPU_APIENTRY +arrayspu_EnableVertexAttribArrayARB(GLuint index) +{ + crStateEnableVertexAttribArrayARB(index); +} + + +static void ARRAYSPU_APIENTRY +arrayspu_DisableVertexAttribArrayARB(GLuint index) +{ + crStateDisableVertexAttribArrayARB(index); +} + + +/* We need to implement Push/PopClientAttrib here so that _our_ state + * tracker gets used. Also, pass the call onto the next SPU (in case + * it's the GL_CLIENT_PIXEL_STORE_BIT, etc). + */ +static void ARRAYSPU_APIENTRY +arrayspu_PushClientAttrib( GLbitfield mask ) +{ + crStatePushClientAttrib(mask); + array_spu.child.PushClientAttrib(mask); +} + + +static void ARRAYSPU_APIENTRY +arrayspu_PopClientAttrib( void ) +{ + crStatePopClientAttrib(); + array_spu.child.PopClientAttrib(); +} + + +static void ARRAYSPU_APIENTRY +arrayspu_GenBuffersARB( GLsizei n, GLuint * buffers ) +{ + array_spu.child.GenBuffersARB(n, buffers); +} + +static void ARRAYSPU_APIENTRY +arrayspu_DeleteBuffersARB( GLsizei n, const GLuint *buffers ) +{ + crStateDeleteBuffersARB(n, buffers); + array_spu.child.DeleteBuffersARB(n, buffers); +} + +static void ARRAYSPU_APIENTRY +arrayspu_BindBufferARB( GLenum target, GLuint buffer ) +{ + crStateBindBufferARB(target, buffer); + array_spu.child.BindBufferARB(target, buffer); +} + +static GLboolean ARRAYSPU_APIENTRY +arrayspu_IsBufferARB (GLuint buffer) +{ + return array_spu.child.IsBufferARB(buffer); +} + +static void ARRAYSPU_APIENTRY +arrayspu_BufferDataARB( GLenum target, GLsizeiptrARB size, const GLvoid * data, GLenum usage ) +{ + crStateBufferDataARB(target, size, data, usage); + array_spu.child.BufferDataARB(target, size, data, usage); +} + +static void ARRAYSPU_APIENTRY +arrayspu_BufferSubDataARB( GLenum target, GLintptrARB offset, + GLsizeiptrARB size, const GLvoid * data ) +{ + crStateBufferSubDataARB(target, offset, size, data); + array_spu.child.BufferSubDataARB(target, offset, size, data); +} + +static void ARRAYSPU_APIENTRY +arrayspu_GetBufferSubDataARB(GLenum target, GLintptrARB offset, GLsizeiptrARB size, void * data) +{ + crStateGetBufferSubDataARB(target, offset, size, data); +} + +static void * ARRAYSPU_APIENTRY +arrayspu_MapBufferARB(GLenum target, GLenum access) +{ + return crStateMapBufferARB(target, access); +} + +static GLboolean ARRAYSPU_APIENTRY +arrayspu_UnmapBufferARB(GLenum target) +{ + crStateUnmapBufferARB(target); + return array_spu.child.UnmapBufferARB(target); +} + +static void ARRAYSPU_APIENTRY +arrayspu_GetBufferParameterivARB(GLenum target, GLenum pname, GLint *params) +{ + crStateGetBufferParameterivARB(target, pname, params); +} + +static void ARRAYSPU_APIENTRY +arrayspu_GetBufferPointervARB(GLenum target, GLenum pname, GLvoid **params) +{ + crStateGetBufferPointervARB(target, pname, params); +} + +static void ARRAYSPU_APIENTRY +arrayspu_InterleavedArrays(GLenum format, GLsizei stride, const GLvoid *p) +{ + crStateInterleavedArrays(format, stride, p); +} + +static GLint ARRAYSPU_APIENTRY +arrayspu_CreateContext( const char *dpyName, GLint visual, GLint shareCtx ) +{ + GLint ctx, slot; + +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&_ArrayMutex); +#endif + + ctx = array_spu.child.CreateContext(dpyName, visual, shareCtx); + + /* find an empty context slot */ + for (slot = 0; slot < array_spu.numContexts; slot++) { + if (!array_spu.context[slot].clientState) { + /* found empty slot */ + break; + } + } + if (slot == array_spu.numContexts) { + array_spu.numContexts++; + } + + array_spu.context[slot].clientState = crStateCreateContext(NULL, visual, NULL); + array_spu.context[slot].clientCtx = ctx; +#ifdef CR_ARB_vertex_buffer_object + array_spu.context[slot].clientState->bufferobject.retainBufferData = GL_TRUE; +#endif + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&_ArrayMutex); +#endif + + return ctx; +} + +static void ARRAYSPU_APIENTRY +arrayspu_MakeCurrent( GLint window, GLint nativeWindow, GLint ctx ) +{ +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&_ArrayMutex); +#endif + array_spu.child.MakeCurrent(window, nativeWindow, ctx); + + if (ctx) { + int slot; + + for (slot=0; slot<array_spu.numContexts; ++slot) + if (array_spu.context[slot].clientCtx == ctx) break; + CRASSERT(slot < array_spu.numContexts); + + crStateMakeCurrent(array_spu.context[slot].clientState); + } + else + { + crStateMakeCurrent(NULL); + } + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&_ArrayMutex); +#endif +} + +static void ARRAYSPU_APIENTRY +arrayspu_DestroyContext( GLint ctx ) +{ +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&_ArrayMutex); +#endif + array_spu.child.DestroyContext(ctx); + + if (ctx) { + int slot; + + for (slot=0; slot<array_spu.numContexts; ++slot) + if (array_spu.context[slot].clientCtx == ctx) break; + CRASSERT(slot < array_spu.numContexts); + + crStateDestroyContext(array_spu.context[slot].clientState); + + array_spu.context[slot].clientState = NULL; + array_spu.context[slot].clientCtx = 0; + } + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&_ArrayMutex); +#endif +} + +static void ARRAYSPU_APIENTRY +arrayspu_VBoxAttachThread(void) +{ + crStateVBoxAttachThread(); + array_spu.child.VBoxAttachThread(); +} + +static void ARRAYSPU_APIENTRY +arrayspu_VBoxDetachThread(void) +{ + crStateVBoxDetachThread(); + array_spu.child.VBoxDetachThread(); +} + + +SPUNamedFunctionTable _cr_array_table[] = { + { "ArrayElement", (SPUGenericFunction) arrayspu_ArrayElement }, + { "DrawArrays", (SPUGenericFunction) arrayspu_DrawArrays}, + { "DrawElements", (SPUGenericFunction) arrayspu_DrawElements}, + { "DrawRangeElements", (SPUGenericFunction) arrayspu_DrawRangeElements}, + { "ColorPointer", (SPUGenericFunction) arrayspu_ColorPointer}, + { "SecondaryColorPointerEXT", (SPUGenericFunction) arrayspu_SecondaryColorPointerEXT}, + { "VertexPointer", (SPUGenericFunction) arrayspu_VertexPointer}, + { "TexCoordPointer", (SPUGenericFunction) arrayspu_TexCoordPointer}, + { "NormalPointer", (SPUGenericFunction) arrayspu_NormalPointer}, + { "IndexPointer", (SPUGenericFunction) arrayspu_IndexPointer}, + { "EdgeFlagPointer", (SPUGenericFunction) arrayspu_EdgeFlagPointer}, + { "VertexAttribPointerNV", (SPUGenericFunction) arrayspu_VertexAttribPointerNV}, + { "FogCoordPointerEXT", (SPUGenericFunction) arrayspu_FogCoordPointerEXT}, + { "GetPointerv", (SPUGenericFunction) arrayspu_GetPointerv}, + { "EnableClientState", (SPUGenericFunction) arrayspu_EnableClientState}, + { "DisableClientState", (SPUGenericFunction) arrayspu_DisableClientState}, + { "ClientActiveTextureARB", (SPUGenericFunction) arrayspu_ClientActiveTextureARB }, + { "MultiDrawArraysEXT", (SPUGenericFunction) arrayspu_MultiDrawArraysEXT }, + { "MultiDrawElementsEXT", (SPUGenericFunction) arrayspu_MultiDrawElementsEXT }, + { "Enable", (SPUGenericFunction) arrayspu_Enable }, + { "Disable", (SPUGenericFunction) arrayspu_Disable }, + { "PushClientAttrib", (SPUGenericFunction) arrayspu_PushClientAttrib }, + { "PopClientAttrib", (SPUGenericFunction) arrayspu_PopClientAttrib }, + { "VertexAttribPointerARB", (SPUGenericFunction) arrayspu_VertexAttribPointerARB }, + { "EnableVertexAttribArrayARB", (SPUGenericFunction) arrayspu_EnableVertexAttribArrayARB }, + { "DisableVertexAttribArrayARB", (SPUGenericFunction) arrayspu_DisableVertexAttribArrayARB }, + { "GenBuffersARB", (SPUGenericFunction) arrayspu_GenBuffersARB }, + { "DeleteBuffersARB", (SPUGenericFunction) arrayspu_DeleteBuffersARB }, + { "BindBufferARB", (SPUGenericFunction) arrayspu_BindBufferARB }, + { "IsBufferARB", (SPUGenericFunction) arrayspu_IsBufferARB }, + { "BufferDataARB", (SPUGenericFunction) arrayspu_BufferDataARB }, + { "BufferSubDataARB", (SPUGenericFunction) arrayspu_BufferSubDataARB }, + { "GetBufferSubDataARB", (SPUGenericFunction) arrayspu_GetBufferSubDataARB }, + { "MapBufferARB", (SPUGenericFunction) arrayspu_MapBufferARB }, + { "UnmapBufferARB", (SPUGenericFunction) arrayspu_UnmapBufferARB }, + { "GetBufferParameterivARB", (SPUGenericFunction) arrayspu_GetBufferParameterivARB}, + { "GetBufferPointervARB", (SPUGenericFunction) arrayspu_GetBufferPointervARB}, + { "InterleavedArrays", (SPUGenericFunction) arrayspu_InterleavedArrays}, + { "CreateContext", (SPUGenericFunction) arrayspu_CreateContext}, + { "MakeCurrent", (SPUGenericFunction) arrayspu_MakeCurrent}, + { "DestroyContext", (SPUGenericFunction) arrayspu_DestroyContext}, + { "UseProgram", (SPUGenericFunction) arrayspu_UseProgram}, + { "VBoxAttachThread", (SPUGenericFunction) arrayspu_VBoxAttachThread}, + { "VBoxDetachThread", (SPUGenericFunction) arrayspu_VBoxDetachThread}, + { NULL, NULL } +}; diff --git a/src/VBox/Additions/common/crOpenGL/array/arrayspu.def b/src/VBox/Additions/common/crOpenGL/array/arrayspu.def new file mode 100644 index 00000000..9edc7163 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/array/arrayspu.def @@ -0,0 +1,6 @@ +; Copyright (c) 2001, Stanford University +; All rights reserved. +; +; See the file LICENSE.txt for information on redistributing this software. +EXPORTS +SPULoad diff --git a/src/VBox/Additions/common/crOpenGL/array/arrayspu.h b/src/VBox/Additions/common/crOpenGL/array/arrayspu.h new file mode 100644 index 00000000..f31dea5c --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/array/arrayspu.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved. + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#ifndef GA_INCLUDED_SRC_common_crOpenGL_array_arrayspu_h +#define GA_INCLUDED_SRC_common_crOpenGL_array_arrayspu_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef WINDOWS +#define ARRAYSPU_APIENTRY __stdcall +#else +#define ARRAYSPU_APIENTRY +#endif + +#include "cr_spu.h" +#include "cr_glstate.h" + +void arrayspuSetVBoxConfiguration( void ); + +typedef struct context_info_t ContextInfo; + +struct context_info_t { + CRContext *clientState; /* used to store client-side GL state */ + GLint clientCtx; /* client context ID */ +}; + +typedef struct { + int id; + int has_child; + CRContext *defaultctx; + SPUDispatchTable self, child, super; + int numContexts; + ContextInfo context[CR_MAX_CONTEXTS]; +} ArraySPU; + +extern ArraySPU array_spu; + +#ifdef CHROMIUM_THREADSAFE +extern CRmutex _ArrayMutex; +#endif + +#endif /* !GA_INCLUDED_SRC_common_crOpenGL_array_arrayspu_h */ diff --git a/src/VBox/Additions/common/crOpenGL/array/arrayspu.rc b/src/VBox/Additions/common/crOpenGL/array/arrayspu.rc new file mode 100644 index 00000000..894a6584 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/array/arrayspu.rc @@ -0,0 +1,69 @@ +/* $Id: arrayspu.rc $ */ +/** @file + * VBoxOGLarrayspu - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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_DRV + FILESUBTYPE VFT2_DRV_DISPLAY +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "VirtualBox crOpenGL ICD\0" + VALUE "InternalName", "VBoxOGLarrayspu\0" +#ifdef VBOX_WDDM_WOW64 + VALUE "OriginalFilename", "VBoxOGLarrayspu-x86.dll\0" +#else + VALUE "OriginalFilename", "VBoxOGLarrayspu.dll\0" +#endif + 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 + +1 RCDATA +BEGIN +// Machine dependent parameters + 17, // Height of vertical thumb + 17, // Width of horizontal thumb + 2, // Icon horiz compression factor + 2, // Icon vert compression factor + 1, // Cursor horz compression factor + 1, // Cursor vert compression factor + 0, // Kanji window height + 1, // cxBorder (thickness of vertical lines) + 1 // cyBorder (thickness of horizontal lines) +END diff --git a/src/VBox/Additions/common/crOpenGL/array/arrayspu_config.c b/src/VBox/Additions/common/crOpenGL/array/arrayspu_config.c new file mode 100644 index 00000000..d2161739 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/array/arrayspu_config.c @@ -0,0 +1,27 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "arrayspu.h" + +#include "cr_string.h" + +#include <stdio.h> + +static void __setDefaults( void ) +{ +} + +/* No SPU options yet. + */ +SPUOptions arraySPUOptions[] = { + { NULL, CR_BOOL, 0, NULL, NULL, NULL, NULL, NULL }, +}; + + +void arrayspuSetVBoxConfiguration( void ) +{ + __setDefaults(); +} diff --git a/src/VBox/Additions/common/crOpenGL/array/arrayspu_init.c b/src/VBox/Additions/common/crOpenGL/array/arrayspu_init.c new file mode 100644 index 00000000..bd773f59 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/array/arrayspu_init.c @@ -0,0 +1,86 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_spu.h" +#include "arrayspu.h" +#include "cr_mem.h" +#include <stdio.h> + +extern SPUNamedFunctionTable _cr_array_table[]; + +extern SPUOptions arraySPUOptions[]; + +static SPUFunctions array_functions = { + NULL, /* CHILD COPY */ + NULL, /* DATA */ + _cr_array_table /* THE ACTUAL FUNCTIONS */ +}; + +static SPUFunctions *arraySPUInit( int id, SPU *child, SPU *self, + unsigned int context_id, + unsigned int num_contexts ) +{ + + (void) context_id; + (void) num_contexts; + +#ifdef CHROMIUM_THREADSAFE + crInitMutex(&_ArrayMutex); +#endif + + array_spu.id = id; + array_spu.has_child = 0; + if (child) + { + crSPUInitDispatchTable( &(array_spu.child) ); + crSPUCopyDispatchTable( &(array_spu.child), &(child->dispatch_table) ); + array_spu.has_child = 1; + } + crSPUInitDispatchTable( &(array_spu.super) ); + crSPUCopyDispatchTable( &(array_spu.super), &(self->superSPU->dispatch_table) ); + arrayspuSetVBoxConfiguration(); + + crStateInit(); +/** @todo seems default context ain't needed at all*/ + array_spu.defaultctx = crStateCreateContext( NULL, 0, NULL ); +#ifdef CR_ARB_vertex_buffer_object + array_spu.defaultctx->bufferobject.retainBufferData = GL_TRUE; +#endif + /* we call SetCurrent instead of MakeCurrent as the differencer + * isn't setup yet anyway */ + crStateSetCurrent( array_spu.defaultctx ); + + array_spu.numContexts = 0; + crMemZero(array_spu.context, CR_MAX_CONTEXTS * sizeof(ContextInfo)); + + return &array_functions; +} + +static void arraySPUSelfDispatch(SPUDispatchTable *self) +{ + crSPUInitDispatchTable( &(array_spu.self) ); + crSPUCopyDispatchTable( &(array_spu.self), self ); +} + +static int arraySPUCleanup(void) +{ + return 1; +} + +int SPULoad( char **name, char **super, SPUInitFuncPtr *init, + SPUSelfDispatchFuncPtr *self, SPUCleanupFuncPtr *cleanup, + SPUOptionsPtr *options, int *flags ) +{ + *name = "array"; + *super = "passthrough"; + *init = arraySPUInit; + *self = arraySPUSelfDispatch; + *cleanup = arraySPUCleanup; + *options = arraySPUOptions; + *flags = (SPU_NO_PACKER|SPU_NOT_TERMINAL|SPU_MAX_SERVERS_ZERO); + + return 1; +} diff --git a/src/VBox/Additions/common/crOpenGL/context.c b/src/VBox/Additions/common/crOpenGL/context.c new file mode 100644 index 00000000..29b93aa1 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/context.c @@ -0,0 +1,1463 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +/** + * \mainpage OpenGL_stub + * + * \section OpenGL_stubIntroduction Introduction + * + * Chromium consists of all the top-level files in the cr + * directory. The OpenGL_stub module basically takes care of API dispatch, + * and OpenGL state management. + * + */ + +/** + * This file manages OpenGL rendering contexts in the faker library. + * The big issue is switching between Chromium and native GL context + * management. This is where we support multiple client OpenGL + * windows. Typically, one window is handled by Chromium while any + * other windows are handled by the native OpenGL library. + */ + +#include "chromium.h" +#include "cr_error.h" +#include "cr_spu.h" +#include "cr_mem.h" +#include "cr_string.h" +#include "cr_environment.h" +#include "stub.h" + +/** + * This function should be called from MakeCurrent(). It'll detect if + * we're in a multi-thread situation, and do the right thing for dispatch. + */ +#ifdef CHROMIUM_THREADSAFE + static void +stubCheckMultithread( void ) +{ + static unsigned long knownID; + static GLboolean firstCall = GL_TRUE; + + if (stub.threadSafe) + return; /* nothing new, nothing to do */ + + if (firstCall) { + knownID = crThreadID(); + firstCall = GL_FALSE; + } + else if (knownID != crThreadID()) { + /* going thread-safe now! */ + stub.threadSafe = GL_TRUE; + crSPUCopyDispatchTable(&glim, &stubThreadsafeDispatch); + } +} +#endif + + +/** + * Install the given dispatch table as the table used for all gl* calls. + */ + static void +stubSetDispatch( SPUDispatchTable *table ) +{ + CRASSERT(table); + +#ifdef CHROMIUM_THREADSAFE + /* always set the per-thread dispatch pointer */ + crSetTSD(&stub.dispatchTSD, (void *) table); + if (stub.threadSafe) { + /* Do nothing - the thread-safe dispatch functions will call GetTSD() + * to get a pointer to the dispatch table, and jump through it. + */ + } + else +#endif + { + /* Single thread mode - just install the caller's dispatch table */ + /* This conditional is an optimization to try to avoid unnecessary + * copying. It seems to work with atlantis, multiwin, etc. but + * _could_ be a problem. (Brian) + */ + if (glim.copy_of != table->copy_of) + crSPUCopyDispatchTable(&glim, table); + } +} + +void stubForcedFlush(GLint con) +{ +#if 0 + GLint buffer; + stub.spu->dispatch_table.GetIntegerv(GL_DRAW_BUFFER, &buffer); + stub.spu->dispatch_table.DrawBuffer(GL_FRONT); + stub.spu->dispatch_table.Flush(); + stub.spu->dispatch_table.DrawBuffer(buffer); +#else + if (con) + { + stub.spu->dispatch_table.VBoxConFlush(con); + } + else + { + stub.spu->dispatch_table.Flush(); + } +#endif +} + +void stubConChromiumParameteriCR(GLint con, GLenum param, GLint value) +{ +// if (con) + stub.spu->dispatch_table.VBoxConChromiumParameteriCR(con, param, value); +// else +// crError("VBoxConChromiumParameteriCR called with null connection"); +} + +void stubConChromiumParametervCR(GLint con, GLenum target, GLenum type, GLsizei count, const GLvoid *values) +{ +// if (con) + stub.spu->dispatch_table.VBoxConChromiumParametervCR(con, target, type, count, values); +// else +// crError("VBoxConChromiumParameteriCR called with null connection"); +} + +void stubConFlush(GLint con) +{ + if (con) + stub.spu->dispatch_table.VBoxConFlush(con); + else + crError("stubConFlush called with null connection"); +} + +static void stubWindowCleanupForContextsCB(unsigned long key, void *data1, void *data2) +{ + ContextInfo *context = (ContextInfo *) data1; + RT_NOREF(key); + + CRASSERT(context); + + if (context->currentDrawable == data2) + context->currentDrawable = NULL; +} + +void stubDestroyWindow( GLint con, GLint window ) +{ + WindowInfo *winInfo = (WindowInfo *) + crHashtableSearch(stub.windowTable, (unsigned int) window); + if (winInfo && winInfo->type == CHROMIUM && stub.spu) + { + crHashtableLock(stub.windowTable); + + stub.spu->dispatch_table.VBoxWindowDestroy(con, winInfo->spuWindow ); + +#ifdef WINDOWS + if (winInfo->hVisibleRegion != INVALID_HANDLE_VALUE) + { + DeleteObject(winInfo->hVisibleRegion); + } +#elif defined(GLX) + if (winInfo->pVisibleRegions) + { + XFree(winInfo->pVisibleRegions); + } +# ifdef CR_NEWWINTRACK + if (winInfo->syncDpy) + { + XCloseDisplay(winInfo->syncDpy); + } +# endif +#endif + + stubForcedFlush(con); + + crHashtableWalk(stub.contextTable, stubWindowCleanupForContextsCB, winInfo); + + crHashtableDelete(stub.windowTable, window, crFree); + + crHashtableUnlock(stub.windowTable); + } +} + +/** + * Create a new _Chromium_ window, not GLX, WGL or CGL. + * Called by crWindowCreate() only. + */ + GLint +stubNewWindow( const char *dpyName, GLint visBits ) +{ + WindowInfo *winInfo; + GLint spuWin, size[2]; + + spuWin = stub.spu->dispatch_table.WindowCreate( dpyName, visBits ); + if (spuWin < 0) { + return -1; + } + + winInfo = (WindowInfo *) crCalloc(sizeof(WindowInfo)); + if (!winInfo) { + stub.spu->dispatch_table.WindowDestroy(spuWin); + return -1; + } + + winInfo->type = CHROMIUM; + + /* Ask the head SPU for the initial window size */ + size[0] = size[1] = 0; + stub.spu->dispatch_table.GetChromiumParametervCR(GL_WINDOW_SIZE_CR, 0, GL_INT, 2, size); + if (size[0] == 0 && size[1] == 0) { + /* use some reasonable defaults */ + size[0] = size[1] = 512; + } + winInfo->width = size[0]; + winInfo->height = size[1]; +#ifdef VBOX_WITH_WDDM + if (stub.bRunningUnderWDDM) + { + crError("Should not be here: WindowCreate/Destroy & VBoxPackGetInjectID require connection id!"); + winInfo->mapped = 0; + } + else +#endif + { + winInfo->mapped = 1; + } + + if (!dpyName) + dpyName = ""; + + crStrncpy(winInfo->dpyName, dpyName, MAX_DPY_NAME); + winInfo->dpyName[MAX_DPY_NAME-1] = 0; + + /* Use spuWin as the hash table index and GLX/WGL handle */ +#ifdef WINDOWS + winInfo->drawable = (HDC) spuWin; + winInfo->hVisibleRegion = INVALID_HANDLE_VALUE; +#elif defined(Darwin) + winInfo->drawable = (CGSWindowID) spuWin; +#elif defined(GLX) + winInfo->drawable = (GLXDrawable) spuWin; + winInfo->pVisibleRegions = NULL; + winInfo->cVisibleRegions = 0; +#endif +#ifdef CR_NEWWINTRACK + winInfo->u32ClientID = stub.spu->dispatch_table.VBoxPackGetInjectID(0); +#endif + winInfo->spuWindow = spuWin; + + crHashtableAdd(stub.windowTable, (unsigned int) spuWin, winInfo); + + return spuWin; +} + +#ifdef GLX +# if 0 /* unused */ +static XErrorHandler oldErrorHandler; +static unsigned char lastXError = Success; + +static int +errorHandler (Display *dpy, XErrorEvent *e) +{ + RT_NOREF(dpy); + + lastXError = e->error_code; + return 0; +} +# endif /* unused */ +#endif + +GLboolean +stubIsWindowVisible(WindowInfo *win) +{ +#if defined(WINDOWS) +# ifdef VBOX_WITH_WDDM + if (stub.bRunningUnderWDDM) + return win->mapped; +# endif + return GL_TRUE; +#elif defined(Darwin) + return GL_TRUE; +#elif defined(GLX) + Display *dpy = stubGetWindowDisplay(win); + if (dpy) + { + XWindowAttributes attr; + XLOCK(dpy); + XGetWindowAttributes(dpy, win->drawable, &attr); + XUNLOCK(dpy); + + if (attr.map_state == IsUnmapped) + { + return GL_FALSE; + } +# if 1 + return GL_TRUE; +# else + if (attr.override_redirect) + { + return GL_TRUE; + } + + if (!stub.bXExtensionsChecked) + { + stubCheckXExtensions(win); + } + + if (!stub.bHaveXComposite) + { + return GL_TRUE; + } + else + { + Pixmap p; + + crLockMutex(&stub.mutex); + + XLOCK(dpy); + XSync(dpy, false); + oldErrorHandler = XSetErrorHandler(errorHandler); + /** @todo this will create new pixmap for window every call*/ + p = XCompositeNameWindowPixmap(dpy, win->drawable); + XSync(dpy, false); + XSetErrorHandler(oldErrorHandler); + XUNLOCK(dpy); + + switch (lastXError) + { + case Success: + XFreePixmap(dpy, p); + crUnlockMutex(&stub.mutex); + return GL_FALSE; + break; + case BadMatch: + /*Window isn't redirected*/ + lastXError = Success; + break; + default: + crWarning("Unexpected XError %i", (int)lastXError); + lastXError = Success; + } + + crUnlockMutex(&stub.mutex); + + return GL_TRUE; + } +# endif + } + else { + /* probably created by crWindowCreate() */ + return win->mapped; + } +#endif +} + + +/** + * Given a Windows HDC or GLX Drawable, return the corresponding + * WindowInfo structure. Create a new one if needed. + */ +WindowInfo * +#ifdef WINDOWS + stubGetWindowInfo( HDC drawable ) +#elif defined(Darwin) + stubGetWindowInfo( CGSWindowID drawable ) +#elif defined(GLX) +stubGetWindowInfo( Display *dpy, GLXDrawable drawable ) +#endif +{ +#ifndef WINDOWS + WindowInfo *winInfo = (WindowInfo *) crHashtableSearch(stub.windowTable, (unsigned int) drawable); +#else + WindowInfo *winInfo; + HWND hwnd; + hwnd = WindowFromDC(drawable); + + if (!hwnd) + { + return NULL; + } + + winInfo = (WindowInfo *) crHashtableSearch(stub.windowTable, (unsigned int) hwnd); +#endif + if (!winInfo) { + winInfo = (WindowInfo *) crCalloc(sizeof(WindowInfo)); + if (!winInfo) + return NULL; +#ifdef GLX + crStrncpy(winInfo->dpyName, DisplayString(dpy), MAX_DPY_NAME); + winInfo->dpyName[MAX_DPY_NAME-1] = 0; + winInfo->dpy = dpy; + winInfo->pVisibleRegions = NULL; +#elif defined(Darwin) + winInfo->connection = _CGSDefaultConnection(); // store our connection as default +#elif defined(WINDOWS) + winInfo->hVisibleRegion = INVALID_HANDLE_VALUE; + winInfo->hWnd = hwnd; +#endif + winInfo->drawable = drawable; + winInfo->type = UNDECIDED; + winInfo->spuWindow = -1; +#ifdef VBOX_WITH_WDDM + if (stub.bRunningUnderWDDM) + winInfo->mapped = 0; + else +#endif + { + winInfo->mapped = -1; /* don't know */ + } + winInfo->pOwner = NULL; +#ifdef CR_NEWWINTRACK + winInfo->u32ClientID = -1; +#endif +#ifndef WINDOWS + crHashtableAdd(stub.windowTable, (unsigned int) drawable, winInfo); +#else + crHashtableAdd(stub.windowTable, (unsigned int) hwnd, winInfo); +#endif + } +#ifdef WINDOWS + else + { + winInfo->drawable = drawable; + } +#endif + return winInfo; +} + +static void stubWindowCheckOwnerCB(unsigned long key, void *data1, void *data2); + +static void +stubContextFree( ContextInfo *context ) +{ + crMemZero(context, sizeof(ContextInfo)); /* just to be safe */ + crFree(context); +} + +static void +stubDestroyContextLocked( ContextInfo *context ) +{ + unsigned long contextId = context->id; + if (context->type == NATIVE) { +#ifdef WINDOWS + stub.wsInterface.wglDeleteContext( context->hglrc ); +#elif defined(Darwin) + stub.wsInterface.CGLDestroyContext( context->cglc ); +#elif defined(GLX) + stub.wsInterface.glXDestroyContext( context->dpy, context->glxContext ); +#endif + } + else if (context->type == CHROMIUM) { + /* Have pack SPU or tilesort SPU, etc. destroy the context */ + CRASSERT(context->spuContext >= 0); + stub.spu->dispatch_table.DestroyContext( context->spuContext ); + crHashtableWalk(stub.windowTable, stubWindowCheckOwnerCB, context); +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + if (context->spuConnection) + { + stub.spu->dispatch_table.VBoxConDestroy(context->spuConnection); + context->spuConnection = 0; + } +#endif + } + +#ifdef GLX + crFreeHashtable(context->pGLXPixmapsHash, crFree); +#endif + + crHashtableDelete(stub.contextTable, contextId, NULL); +} + +#ifdef CHROMIUM_THREADSAFE +static DECLCALLBACK(void) stubContextDtor(void*pvContext) +{ + stubContextFree((ContextInfo*)pvContext); +} +#endif + +/** + * Allocate a new ContextInfo object, initialize it, put it into the + * context hash table. If type==CHROMIUM, call the head SPU's + * CreateContext() function too. + */ + ContextInfo * +stubNewContext(char *dpyName, GLint visBits, ContextType type, unsigned long shareCtx +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , struct VBOXUHGSMI *pHgsmi +#endif + ) +{ + GLint spuContext = -1, spuShareCtx = 0, spuConnection = 0; + ContextInfo *context; + + if (shareCtx > 0) { + /* translate shareCtx to a SPU context ID */ + context = (ContextInfo *) + crHashtableSearch(stub.contextTable, shareCtx); + if (context) + spuShareCtx = context->spuContext; + } + + if (type == CHROMIUM) { +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + if (pHgsmi) + { + spuConnection = stub.spu->dispatch_table.VBoxConCreate(pHgsmi); + if (!spuConnection) + { + crWarning("VBoxConCreate failed"); + return NULL; + } + } +#endif + spuContext + = stub.spu->dispatch_table.VBoxCreateContext(spuConnection, dpyName, visBits, spuShareCtx); + if (spuContext < 0) + { + crWarning("VBoxCreateContext failed"); +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + if (spuConnection) + stub.spu->dispatch_table.VBoxConDestroy(spuConnection); +#endif + return NULL; + } + } + + context = crCalloc(sizeof(ContextInfo)); + if (!context) { + stub.spu->dispatch_table.DestroyContext(spuContext); +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + if (spuConnection) + stub.spu->dispatch_table.VBoxConDestroy(spuConnection); +#endif + return NULL; + } + + if (!dpyName) + dpyName = ""; + + context->id = stub.freeContextNumber++; + context->type = type; + context->spuContext = spuContext; + context->visBits = visBits; + context->currentDrawable = NULL; + crStrncpy(context->dpyName, dpyName, MAX_DPY_NAME); + context->dpyName[MAX_DPY_NAME-1] = 0; + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + context->spuConnection = spuConnection; + context->pHgsmi = pHgsmi; +#endif + +#ifdef CHROMIUM_THREADSAFE + VBoxTlsRefInit(context, stubContextDtor); +#endif + +#if defined(GLX) || defined(DARWIN) + context->share = (ContextInfo *) + crHashtableSearch(stub.contextTable, (unsigned long) shareCtx); +#endif + +#ifdef GLX + context->pGLXPixmapsHash = crAllocHashtable(); + context->damageQueryFailed = GL_FALSE; + context->damageEventsBase = 0; +#endif + + crHashtableAdd(stub.contextTable, context->id, (void *) context); + + return context; +} + + +#ifdef Darwin + +#define SET_ATTR(l,i,a) ( (l)[(i)++] = (a) ) +#define SET_ATTR_V(l,i,a,v) ( SET_ATTR(l,i,a), SET_ATTR(l,i,v) ) + +void stubSetPFA( ContextInfo *ctx, CGLPixelFormatAttribute *attribs, int size, GLint *num ) { + GLuint visual = ctx->visBits; + int i = 0; + + CRASSERT(visual & CR_RGB_BIT); + + SET_ATTR_V(attribs, i, kCGLPFAColorSize, 8); + + if( visual & CR_DEPTH_BIT ) + SET_ATTR_V(attribs, i, kCGLPFADepthSize, 16); + + if( visual & CR_ACCUM_BIT ) + SET_ATTR_V(attribs, i, kCGLPFAAccumSize, 1); + + if( visual & CR_STENCIL_BIT ) + SET_ATTR_V(attribs, i, kCGLPFAStencilSize, 1); + + if( visual & CR_ALPHA_BIT ) + SET_ATTR_V(attribs, i, kCGLPFAAlphaSize, 1); + + if( visual & CR_DOUBLE_BIT ) + SET_ATTR(attribs, i, kCGLPFADoubleBuffer); + + if( visual & CR_STEREO_BIT ) + SET_ATTR(attribs, i, kCGLPFAStereo); + +/* SET_ATTR_V(attribs, i, kCGLPFASampleBuffers, 1); + SET_ATTR_V(attribs, i, kCGLPFASamples, 0); + SET_ATTR_V(attribs, i, kCGLPFADisplayMask, 0); */ + SET_ATTR(attribs, i, kCGLPFABackingStore); + //SET_ATTR(attribs, i, kCGLPFAWindow); // kCGLPFAWindow deprecated starting from OSX 10.7 + SET_ATTR_V(attribs, i, kCGLPFADisplayMask, ctx->disp_mask); + + SET_ATTR(attribs, i, 0); + + *num = i; +} + +#endif + +#ifndef GLX +/** + * This creates a native GLX/WGL context. + */ +static GLboolean +InstantiateNativeContext( WindowInfo *window, ContextInfo *context ) +{ +#ifdef WINDOWS + context->hglrc = stub.wsInterface.wglCreateContext( window->drawable ); + return context->hglrc ? GL_TRUE : GL_FALSE; +#elif defined(Darwin) + CGLContextObj shareCtx = NULL; + CGLPixelFormatObj pix; + long npix; + + CGLPixelFormatAttribute attribs[16]; + GLint ind = 0; + + if( context->share ) { + if( context->cglc != context->share->cglc ) { + crWarning("CGLCreateContext() is trying to share a non-existant " + "CGL context. Setting share context to zero."); + shareCtx = 0; + } + else + shareCtx = context->cglc; + } + + stubSetPFA( context, attribs, 16, &ind ); + + stub.wsInterface.CGLChoosePixelFormat( attribs, &pix, &npix ); + stub.wsInterface.CGLCreateContext( pix, shareCtx, &context->cglc ); + if( !context->cglc ) + crError("InstantiateNativeContext: Couldn't Create the context!"); + + stub.wsInterface.CGLDestroyPixelFormat( pix ); + + if( context->parambits ) { + /* Set the delayed parameters */ + if( context->parambits & VISBIT_SWAP_RECT ) + stub.wsInterface.CGLSetParameter( context->cglc, kCGLCPSwapRectangle, context->swap_rect ); + + if( context->parambits & VISBIT_SWAP_INTERVAL ) + stub.wsInterface.CGLSetParameter( context->cglc, kCGLCPSwapInterval, &(context->swap_interval) ); + + if( context->parambits & VISBIT_CLIENT_STORAGE ) + stub.wsInterface.CGLSetParameter( context->cglc, kCGLCPClientStorage, (long*)&(context->client_storage) ); + + context->parambits = 0; + } + + return context->cglc ? GL_TRUE : GL_FALSE; +#elif defined(GLX) + GLXContext shareCtx = 0; + + /* sort out context sharing here */ + if (context->share) { + if (context->glxContext != context->share->glxContext) { + crWarning("glXCreateContext() is trying to share a non-existant " + "GLX context. Setting share context to zero."); + shareCtx = 0; + } + else { + shareCtx = context->glxContext; + } + } + + context->glxContext = stub.wsInterface.glXCreateContext( window->dpy, + context->visual, shareCtx, context->direct ); + + return context->glxContext ? GL_TRUE : GL_FALSE; +#endif +} +#endif /* !GLX */ + + +/** + * Utility functions to get window size and titlebar text. + */ +#ifdef WINDOWS + +void +stubGetWindowGeometry(WindowInfo *window, int *x, int *y, + unsigned int *w, unsigned int *h ) +{ + RECT rect; + + if (!window->drawable || !window->hWnd) { + *w = *h = 0; + return; + } + + if (window->hWnd!=WindowFromDC(window->drawable)) + { + crWarning("Window(%i) DC is no longer valid", window->spuWindow); + return; + } + + if (!GetClientRect(window->hWnd, &rect)) + { + crWarning("GetClientRect failed for %p", window->hWnd); + *w = *h = 0; + return; + } + *w = rect.right - rect.left; + *h = rect.bottom - rect.top; + + if (!ClientToScreen( window->hWnd, (LPPOINT) &rect )) + { + crWarning("ClientToScreen failed for %p", window->hWnd); + *w = *h = 0; + return; + } + *x = rect.left; + *y = rect.top; +} + +static void +GetWindowTitle( const WindowInfo *window, char *title ) +{ + /* XXX - we don't handle recurseUp */ + if (window->hWnd) + GetWindowText(window->hWnd, title, 100); + else + title[0] = 0; +} + +static void +GetCursorPosition(WindowInfo *window, int pos[2]) +{ + RECT rect; + POINT point; + GLint size[2], x, y; + unsigned int NativeHeight, NativeWidth, ChromiumHeight, ChromiumWidth; + float WidthRatio, HeightRatio; + static int DebugFlag = 0; + + // apparently the "window" parameter passed to this + // function contains the native window information + HWND NATIVEhwnd = window->hWnd; + + if (NATIVEhwnd!=WindowFromDC(window->drawable)) + { + crWarning("Window(%i) DC is no longer valid", window->spuWindow); + return; + } + + // get the native window's height and width + stubGetWindowGeometry(window, &x, &y, &NativeWidth, &NativeHeight); + + // get the spu window's height and width + stub.spu->dispatch_table.GetChromiumParametervCR(GL_WINDOW_SIZE_CR, window->spuWindow, GL_INT, 2, size); + ChromiumWidth = size[0]; + ChromiumHeight = size[1]; + + // get the ratio of the size of the native window to the cr window + WidthRatio = (float)ChromiumWidth / (float)NativeWidth; + HeightRatio = (float)ChromiumHeight / (float)NativeHeight; + + // output some debug information at the beginning + if(DebugFlag) + { + DebugFlag = 0; + crDebug("Native Window Handle = %d", NATIVEhwnd); + crDebug("Native Width = %i", NativeWidth); + crDebug("Native Height = %i", NativeHeight); + crDebug("Chromium Width = %i", ChromiumWidth); + crDebug("Chromium Height = %i", ChromiumHeight); + } + + if (NATIVEhwnd) + { + GetClientRect( NATIVEhwnd, &rect ); + GetCursorPos (&point); + + // make sure these coordinates are relative to the native window, + // not the whole desktop + ScreenToClient(NATIVEhwnd, &point); + + // calculate the new position of the virtual cursor + pos[0] = (int)(point.x * WidthRatio); + pos[1] = (int)((NativeHeight - point.y) * HeightRatio); + } + else + { + pos[0] = 0; + pos[1] = 0; + } +} + +#elif defined(Darwin) + +extern OSStatus CGSGetScreenRectForWindow( CGSConnectionID cid, CGSWindowID wid, float *outRect ); +extern OSStatus CGSGetWindowBounds( CGSConnectionID cid, CGSWindowID wid, float *bounds ); + +void +stubGetWindowGeometry( const WindowInfo *window, int *x, int *y, unsigned int *w, unsigned int *h ) +{ + float rect[4]; + + if( !window || + !window->connection || + !window->drawable || + CGSGetWindowBounds( window->connection, window->drawable, rect ) != noErr ) + { + *x = *y = 0; + *w = *h = 0; + } else { + *x = (int) rect[0]; + *y = (int) rect[1]; + *w = (int) rect[2]; + *h = (int) rect[3]; + } +} + + +static void +GetWindowTitle( const WindowInfo *window, char *title ) +{ + /* XXX \todo Darwin window Title */ + title[0] = '\0'; +} + + +static void +GetCursorPosition( const WindowInfo *window, int pos[2] ) +{ + Point mouse_pos; + float window_rect[4]; + + GetMouse( &mouse_pos ); + CGSGetScreenRectForWindow( window->connection, window->drawable, window_rect ); + + pos[0] = mouse_pos.h - (int) window_rect[0]; + pos[1] = (int) window_rect[3] - (mouse_pos.v - (int) window_rect[1]); + + /*crDebug( "%i %i", pos[0], pos[1] );*/ +} + +#elif defined(GLX) + +void +stubGetWindowGeometry(WindowInfo *window, int *x, int *y, unsigned int *w, unsigned int *h) +{ + Window root, child; + unsigned int border, depth; + Display *dpy; + + dpy = stubGetWindowDisplay(window); + + /// @todo Performing those checks is expensive operation, especially for simple apps with high FPS. + // Disabling those triples glxgears fps, thus using xevents instead of per frame polling is much more preferred. + /// @todo Check similar on windows guests, though doubtful as there're no XSync like calls on windows. + if (window && dpy) + { + XLOCK(dpy); + } + + if (!window + || !dpy + || !window->drawable + || !XGetGeometry(dpy, window->drawable, &root, x, y, w, h, &border, &depth) + || !XTranslateCoordinates(dpy, window->drawable, root, 0, 0, x, y, &child)) + { + crWarning("Failed to get windows geometry for %p, try xwininfo", window); + *x = *y = 0; + *w = *h = 0; + } + + if (window && dpy) + { + XUNLOCK(dpy); + } +} + +static char * +GetWindowTitleHelper( Display *dpy, Window window, GLboolean recurseUp ) +{ + while (1) { + char *name; + if (!XFetchName(dpy, window, &name)) + return NULL; + if (name[0]) { + return name; + } + else if (recurseUp) { + /* This window has no name, try the parent */ + Status stat; + Window root, parent, *children; + unsigned int numChildren; + stat = XQueryTree( dpy, window, &root, &parent, + &children, &numChildren ); + if (!stat || window == root) + return NULL; + if (children) + XFree(children); + window = parent; + } + else { + XFree(name); + return NULL; + } + } +} + +static void +GetWindowTitle( const WindowInfo *window, char *title ) +{ + char *t = GetWindowTitleHelper(window->dpy, window->drawable, GL_TRUE); + if (t) { + crStrcpy(title, t); + XFree(t); + } + else { + title[0] = 0; + } +} + + +/** + *Return current cursor position in local window coords. + */ +static void +GetCursorPosition(WindowInfo *window, int pos[2] ) +{ + int rootX, rootY; + Window root, child; + unsigned int mask; + int x, y; + + XLOCK(window->dpy); + + Bool q = XQueryPointer(window->dpy, window->drawable, &root, &child, + &rootX, &rootY, &pos[0], &pos[1], &mask); + if (q) { + unsigned int w, h; + stubGetWindowGeometry( window, &x, &y, &w, &h ); + /* invert Y */ + pos[1] = (int) h - pos[1] - 1; + } + else { + pos[0] = pos[1] = 0; + } + + XUNLOCK(window->dpy); +} + +#endif + + +/** + * This function is called by MakeCurrent() and determines whether or + * not a new rendering context should be bound to Chromium or the native + * OpenGL. + * \return GL_FALSE if native OpenGL should be used, or GL_TRUE if Chromium + * should be used. + */ +static GLboolean +stubCheckUseChromium( WindowInfo *window ) +{ + int x, y; + unsigned int w, h; + + /* If the provided window is CHROMIUM, we're clearly intended + * to create a CHROMIUM context. + */ + if (window->type == CHROMIUM) + return GL_TRUE; + + if (stub.ignoreFreeglutMenus) { + const char *glutMenuTitle = "freeglut menu"; + char title[1000]; + GetWindowTitle(window, title); + if (crStrcmp(title, glutMenuTitle) == 0) { + crDebug("GL faker: Ignoring freeglut menu window"); + return GL_FALSE; + } + } + + /* If the user's specified a window count for Chromium, see if + * this window satisfies that criterium. + */ + stub.matchChromiumWindowCounter++; + if (stub.matchChromiumWindowCount > 0) { + if (stub.matchChromiumWindowCounter != stub.matchChromiumWindowCount) { + crDebug("Using native GL, app window doesn't meet match_window_count"); + return GL_FALSE; + } + } + + /* If the user's specified a window list to ignore, see if this + * window satisfies that criterium. + */ + if (stub.matchChromiumWindowID) { + GLuint i; + + for (i = 0; i <= stub.numIgnoreWindowID; i++) { + if (stub.matchChromiumWindowID[i] == stub.matchChromiumWindowCounter) { + crDebug("Ignore window ID %d, using native GL", stub.matchChromiumWindowID[i]); + return GL_FALSE; + } + } + } + + /* If the user's specified a minimum window size for Chromium, see if + * this window satisfies that criterium. + */ + if (stub.minChromiumWindowWidth > 0 && + stub.minChromiumWindowHeight > 0) { + stubGetWindowGeometry( window, &x, &y, &w, &h ); + if (w >= stub.minChromiumWindowWidth && + h >= stub.minChromiumWindowHeight) { + + /* Check for maximum sized window now too */ + if (stub.maxChromiumWindowWidth && + stub.maxChromiumWindowHeight) { + if (w < stub.maxChromiumWindowWidth && + h < stub.maxChromiumWindowHeight) + return GL_TRUE; + else + return GL_FALSE; + } + + return GL_TRUE; + } + crDebug("Using native GL, app window doesn't meet minimum_window_size"); + return GL_FALSE; + } + else if (stub.matchWindowTitle) { + /* If the user's specified a window title for Chromium, see if this + * window satisfies that criterium. + */ + GLboolean wildcard = GL_FALSE; + char title[1000]; + char *titlePattern; + int len; + /* check for leading '*' wildcard */ + if (stub.matchWindowTitle[0] == '*') { + titlePattern = crStrdup( stub.matchWindowTitle + 1 ); + wildcard = GL_TRUE; + } + else { + titlePattern = crStrdup( stub.matchWindowTitle ); + } + /* check for trailing '*' wildcard */ + len = crStrlen(titlePattern); + if (len > 0 && titlePattern[len - 1] == '*') { + titlePattern[len - 1] = '\0'; /* terminate here */ + wildcard = GL_TRUE; + } + + GetWindowTitle( window, title ); + if (title[0]) { + if (wildcard) { + if (crStrstr(title, titlePattern)) { + crFree(titlePattern); + return GL_TRUE; + } + } + else if (crStrcmp(title, titlePattern) == 0) { + crFree(titlePattern); + return GL_TRUE; + } + } + crFree(titlePattern); + crDebug("Using native GL, app window title doesn't match match_window_title string (\"%s\" != \"%s\")", title, stub.matchWindowTitle); + return GL_FALSE; + } + + /* Window title and size don't matter */ + CRASSERT(stub.minChromiumWindowWidth == 0); + CRASSERT(stub.minChromiumWindowHeight == 0); + CRASSERT(stub.matchWindowTitle == NULL); + + /* User hasn't specified a width/height or window title. + * We'll use chromium for this window (and context) if no other is. + */ + + return GL_TRUE; /* use Chromium! */ +} + +static void stubWindowCheckOwnerCB(unsigned long key, void *data1, void *data2) +{ + WindowInfo *pWindow = (WindowInfo *) data1; + ContextInfo *pCtx = (ContextInfo *) data2; + + RT_NOREF(key); + + + if (pWindow->pOwner == pCtx) + { +#ifdef WINDOWS + /* Note: can't use WindowFromDC(context->pOwnWindow->drawable) here + because GL context is already released from DC and actual guest window + could be destroyed. + */ + stubDestroyWindow(CR_CTX_CON(pCtx), (GLint)pWindow->hWnd); +#else + stubDestroyWindow(CR_CTX_CON(pCtx), (GLint)pWindow->drawable); +#endif + } +} + +GLboolean stubCtxCreate(ContextInfo *context) +{ + /* + * Create a Chromium context. + */ +#if defined(GLX) || defined(DARWIN) + GLint spuShareCtx = context->share ? context->share->spuContext : 0; +#else + GLint spuShareCtx = 0; +#endif + GLint spuConnection = 0; + CRASSERT(stub.spu); + CRASSERT(stub.spu->dispatch_table.CreateContext); + context->type = CHROMIUM; + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + if (context->pHgsmi) + { + spuConnection = stub.spu->dispatch_table.VBoxConCreate(context->pHgsmi); + if (!spuConnection) + { + crError("VBoxConCreate failed"); + return GL_FALSE; + } + context->spuConnection = spuConnection; + } +#endif + + context->spuContext + = stub.spu->dispatch_table.VBoxCreateContext(spuConnection, context->dpyName, + context->visBits, + spuShareCtx); + + return GL_TRUE; +} + +GLboolean stubCtxCheckCreate(ContextInfo *context) +{ + if (context->type == UNDECIDED) + return stubCtxCreate(context); + return CHROMIUM == context->type; +} + + +GLboolean +stubMakeCurrent( WindowInfo *window, ContextInfo *context ) +{ + GLboolean retVal = GL_FALSE; + + /* + * Get WindowInfo and ContextInfo pointers. + */ + + if (!context || !window) { + ContextInfo * currentContext = stubGetCurrentContext(); + if (currentContext) + currentContext->currentDrawable = NULL; + if (context) + context->currentDrawable = NULL; + stubSetCurrentContext(NULL); + return GL_TRUE; /* OK */ + } + +#ifdef CHROMIUM_THREADSAFE + stubCheckMultithread(); +#endif + + if (context->type == UNDECIDED) { + /* Here's where we really create contexts */ +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&stub.mutex); +#endif + + if (stubCheckUseChromium(window)) { + GLint spuConnection = 0; + + if (!stubCtxCreate(context)) + { + crWarning("stubCtxCreate failed"); + return GL_FALSE; + } + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + spuConnection = context->spuConnection; +#endif + + if (window->spuWindow == -1) + { + /*crDebug("(1)stubMakeCurrent ctx=%p(%i) window=%p(%i)", context, context->spuContext, window, window->spuWindow);*/ + window->spuWindow = stub.spu->dispatch_table.VBoxWindowCreate(spuConnection, window->dpyName, context->visBits ); +#ifdef CR_NEWWINTRACK + window->u32ClientID = stub.spu->dispatch_table.VBoxPackGetInjectID(spuConnection); +#endif + } + } +#ifndef GLX + else { + /* + * Create a native OpenGL context. + */ + if (!InstantiateNativeContext(window, context)) + { +# ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&stub.mutex); +# endif + return 0; /* false */ + } + context->type = NATIVE; + } +#endif /* !GLX */ + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&stub.mutex); +#endif + } + + + if (context->type == NATIVE) { + /* + * Native OpenGL MakeCurrent(). + */ +#ifdef WINDOWS + retVal = (GLboolean) stub.wsInterface.wglMakeCurrent( window->drawable, context->hglrc ); +#elif defined(Darwin) + // XXX \todo We need to differentiate between these two.. + retVal = ( stub.wsInterface.CGLSetSurface(context->cglc, window->connection, window->drawable, window->surface) == noErr ); + retVal = ( stub.wsInterface.CGLSetCurrentContext(context->cglc) == noErr ); +#elif defined(GLX) + retVal = (GLboolean) stub.wsInterface.glXMakeCurrent( window->dpy, window->drawable, context->glxContext ); +#endif + } + else { + /* + * SPU chain MakeCurrent(). + */ + CRASSERT(context->type == CHROMIUM); + CRASSERT(context->spuContext >= 0); + + /*if (context->currentDrawable && context->currentDrawable != window) + crDebug("Rebinding context %p to a different window", context);*/ + + if (window->type == NATIVE) { + crWarning("Can't rebind a chromium context to a native window\n"); + retVal = 0; + } + else { + if (window->spuWindow == -1) + { + /*crDebug("(2)stubMakeCurrent ctx=%p(%i) window=%p(%i)", context, context->spuContext, window, window->spuWindow);*/ + window->spuWindow = stub.spu->dispatch_table.VBoxWindowCreate( +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + context->spuConnection, +#else + 0, +#endif + window->dpyName, context->visBits ); +#ifdef CR_NEWWINTRACK + window->u32ClientID = stub.spu->dispatch_table.VBoxPackGetInjectID( +# if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + context->spuConnection +# else + 0 +# endif + ); +#endif + if (context->currentDrawable && context->currentDrawable->type==CHROMIUM + && context->currentDrawable->pOwner==context) + { +#ifdef WINDOWS + if (context->currentDrawable->hWnd!=WindowFromDC(context->currentDrawable->drawable)) + { + stubDestroyWindow(CR_CTX_CON(context), (GLint)context->currentDrawable->hWnd); + } +#else + Window root; + int x, y; + unsigned int border, depth, w, h; + + XLOCK(context->currentDrawable->dpy); + if (!XGetGeometry(context->currentDrawable->dpy, context->currentDrawable->drawable, &root, &x, &y, &w, &h, &border, &depth)) + { + stubDestroyWindow(CR_CTX_CON(context), (GLint)context->currentDrawable->drawable); + } + XUNLOCK(context->currentDrawable->dpy); +#endif + + } + } + + if (window->spuWindow != (GLint)window->drawable) + stub.spu->dispatch_table.MakeCurrent( window->spuWindow, (GLint) window->drawable, context->spuContext ); + else + stub.spu->dispatch_table.MakeCurrent( window->spuWindow, 0, /* native window handle */ context->spuContext ); + + retVal = 1; + } + } + + window->type = context->type; + window->pOwner = context; + context->currentDrawable = window; + stubSetCurrentContext(context); + + if (retVal) { + /* Now, if we've transitions from Chromium to native rendering, or + * vice versa, we have to change all the OpenGL entrypoint pointers. + */ + if (context->type == NATIVE) { + /* Switch to native API */ + /*printf(" Switching to native API\n");*/ + stubSetDispatch(&stub.nativeDispatch); + } + else if (context->type == CHROMIUM) { + /* Switch to stub (SPU) API */ + /*printf(" Switching to spu API\n");*/ + stubSetDispatch(&stub.spuDispatch); + } + else { + /* no API switch needed */ + } + } + + if (!window->width && window->type == CHROMIUM) { + /* One time window setup */ + int x, y; + unsigned int winW, winH; + + stubGetWindowGeometry( window, &x, &y, &winW, &winH ); + + /* If we're not using GLX/WGL (no app window) we'll always get + * a width and height of zero here. In that case, skip the viewport + * call since we're probably using a tilesort SPU with fake_window_dims + * which the tilesort SPU will use for the viewport. + */ + window->width = winW; + window->height = winH; +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + if (stubIsWindowVisible(window)) +#endif + { + if (stub.trackWindowSize) + stub.spuDispatch.WindowSize( window->spuWindow, winW, winH ); + if (stub.trackWindowPos) + stub.spuDispatch.WindowPosition(window->spuWindow, x, y); + if (winW > 0 && winH > 0) + stub.spu->dispatch_table.Viewport( 0, 0, winW, winH ); + } +#ifdef VBOX_WITH_WDDM + if (stub.trackWindowVisibleRgn) + stub.spu->dispatch_table.WindowVisibleRegion(window->spuWindow, 0, NULL); +#endif + } + + /* Update window mapping state. + * Basically, this lets us hide render SPU windows which correspond + * to unmapped application windows. Without this, "pertly" (for example) + * opens *lots* of temporary windows which otherwise clutter the screen. + */ + if (stub.trackWindowVisibility && window->type == CHROMIUM && window->drawable) { + const int mapped = stubIsWindowVisible(window); + if (mapped != window->mapped) { + crDebug("Dispatched: WindowShow(%i, %i)", window->spuWindow, mapped); + stub.spu->dispatch_table.WindowShow(window->spuWindow, mapped); + window->mapped = mapped; + } + } + + return retVal; +} + +void +stubDestroyContext( unsigned long contextId ) +{ + ContextInfo *context; + + if (!stub.contextTable) { + return; + } + + /* the lock order is windowTable->contextTable (see wglMakeCurrent_prox, glXMakeCurrent) + * this is why we need to take a windowTable lock since we will later do stub.windowTable access & locking */ + crHashtableLock(stub.windowTable); + crHashtableLock(stub.contextTable); + + context = (ContextInfo *) crHashtableSearch(stub.contextTable, contextId); + if (context) + stubDestroyContextLocked(context); + else + crError("No context."); + +#ifdef CHROMIUM_THREADSAFE + if (stubGetCurrentContext() == context) { + stubSetCurrentContext(NULL); + } + + VBoxTlsRefMarkDestroy(context); + VBoxTlsRefRelease(context); +#else + if (stubGetCurrentContext() == context) { + stubSetCurrentContext(NULL); + } + stubContextFree(context); +#endif + crHashtableUnlock(stub.contextTable); + crHashtableUnlock(stub.windowTable); +} + +void +stubSwapBuffers(WindowInfo *window, GLint flags) +{ + if (!window) + return; + + /* Determine if this window is being rendered natively or through + * Chromium. + */ + + if (window->type == NATIVE) { + /*printf("*** Swapping native window %d\n", (int) drawable);*/ +#ifdef WINDOWS + (void) stub.wsInterface.wglSwapBuffers( window->drawable ); +#elif defined(Darwin) + /* ...is this ok? */ +/* stub.wsInterface.CGLFlushDrawable( context->cglc ); */ + crDebug("stubSwapBuffers: unable to swap (no context!)"); +#elif defined(GLX) + stub.wsInterface.glXSwapBuffers( window->dpy, window->drawable ); +#endif + } + else if (window->type == CHROMIUM) { + /* Let the SPU do the buffer swap */ + /*printf("*** Swapping chromium window %d\n", (int) drawable);*/ + if (stub.appDrawCursor) { + int pos[2]; + GetCursorPosition(window, pos); + stub.spu->dispatch_table.ChromiumParametervCR(GL_CURSOR_POSITION_CR, GL_INT, 2, pos); + } + stub.spu->dispatch_table.SwapBuffers( window->spuWindow, flags ); + } + else { + crDebug("Calling SwapBuffers on a window we haven't seen before (no-op)."); + } +} diff --git a/src/VBox/Additions/common/crOpenGL/cr_gl.py b/src/VBox/Additions/common/crOpenGL/cr_gl.py new file mode 100755 index 00000000..12e332ad --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/cr_gl.py @@ -0,0 +1,65 @@ +from __future__ import print_function +print(""" +/** @file + * VBox OpenGL chromium functions header + */ + +/* + * Copyright (C) 2008-2016 """ """Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ +""") +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +import sys + +import apiutil + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - THIS FILE GENERATED BY THE cr_gl.py SCRIPT */ +#ifndef __CR_GL_H__ +#define __CR_GL_H__ + +#include "chromium.h" +#include "cr_string.h" +#include "cr_version.h" +#include "stub.h" + +#ifdef WINDOWS +#pragma warning( disable: 4055 ) +#endif + +""") + + +# Extern-like declarations +keys = apiutil.GetAllFunctions(sys.argv[1]+"/APIspec.txt") +for func_name in keys: + if "Chromium" == apiutil.Category(func_name): + continue + if "VBox" == apiutil.Category(func_name): + continue + if func_name == "BoundsInfoCR": + continue + if "GL_chromium" == apiutil.Category(func_name): + pass #continue + + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + + print("extern %s cr_gl%s(%s);" % (return_type, func_name, + apiutil.MakeDeclarationString( params ))) + +print("#endif /* __CR_GL_H__ */") diff --git a/src/VBox/Additions/common/crOpenGL/defs.py b/src/VBox/Additions/common/crOpenGL/defs.py new file mode 100755 index 00000000..c0703088 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/defs.py @@ -0,0 +1,505 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + +apiutil.CopyrightDef() + +# NOTE: if we need a LIBRARY statement, we would need to create a defs-x86.py to generate a .def file for VBoxOGL-x86 library +#print "LIBRARY VBoxOGL" +#print "DESCRIPTION \"\"" - warning LNK4017: DESCRIPTION statement not supported for the target platform; ignored +print("EXPORTS") + +# XXX can't these values be automatically computed by analyzing parameters? + +stack_sizes = { + 'Accum': 8, + 'AlphaFunc': 8, + 'AreTexturesResident': 12, + 'ArrayElement': 4, + 'Begin': 4, + 'BindTexture': 8, + 'Bitmap': 28, + 'BlendFunc': 8, + 'CallList': 4, + 'CallLists': 12, + 'Clear': 4, + 'ClearAccum': 16, + 'ClearColor': 16, + 'ClearDepth': 8, + 'ClearIndex': 4, + 'ClearStencil': 4, + 'ClipPlane': 8, + 'Color3b': 12, + 'Color3bv': 4, + 'Color3d': 24, + 'Color3dv': 4, + 'Color3f': 12, + 'Color3fv': 4, + 'Color3i': 12, + 'Color3iv': 4, + 'Color3s': 12, + 'Color3sv': 4, + 'Color3ub': 12, + 'Color3ubv': 4, + 'Color3ui': 12, + 'Color3uiv': 4, + 'Color3us': 12, + 'Color3usv': 4, + 'Color4b': 16, + 'Color4bv': 4, + 'Color4d': 32, + 'Color4dv': 4, + 'Color4f': 16, + 'Color4fv': 4, + 'Color4i': 16, + 'Color4iv': 4, + 'Color4s': 16, + 'Color4sv': 4, + 'Color4ub': 16, + 'Color4ubv': 4, + 'Color4ui': 16, + 'Color4uiv': 4, + 'Color4us': 16, + 'Color4usv': 4, + 'ColorMask': 16, + 'ColorMaterial': 8, + 'ColorPointer': 16, + 'CopyPixels': 20, + 'CopyTexImage1D': 28, + 'CopyTexImage2D': 32, + 'CopyTexSubImage1D': 24, + 'CopyTexSubImage2D': 32, + 'CullFace': 4, + 'DebugEntry': 8, + 'DeleteLists': 8, + 'DeleteTextures': 8, + 'DepthFunc': 4, + 'DepthMask': 4, + 'DepthRange': 16, + 'Disable': 4, + 'DisableClientState': 4, + 'DrawArrays': 12, + 'DrawBuffer': 4, + 'DrawElements': 16, + 'DrawPixels': 20, + 'EdgeFlag': 4, + 'EdgeFlagPointer': 8, + 'EdgeFlagv': 4, + 'Enable': 4, + 'EnableClientState': 4, + 'End': 0, + 'EndList': 0, + 'EvalCoord1d': 8, + 'EvalCoord1dv': 4, + 'EvalCoord1f': 4, + 'EvalCoord1fv': 4, + 'EvalCoord2d': 16, + 'EvalCoord2dv': 4, + 'EvalCoord2f': 8, + 'EvalCoord2fv': 4, + 'EvalMesh1': 12, + 'EvalMesh2': 20, + 'EvalPoint1': 4, + 'EvalPoint2': 8, + 'FeedbackBuffer': 12, + 'Finish': 0, + 'Flush': 0, + 'Fogf': 8, + 'Fogfv': 8, + 'Fogi': 8, + 'Fogiv': 8, + 'FrontFace': 4, + 'Frustum': 48, + 'GenLists': 4, + 'GenTextures': 8, + 'GetBooleanv': 8, + 'GetClipPlane': 8, + 'GetDoublev': 8, + 'GetError': 0, + 'GetFloatv': 8, + 'GetIntegerv': 8, + 'GetLightfv': 12, + 'GetLightiv': 12, + 'GetMapdv': 12, + 'GetMapfv': 12, + 'GetMapiv': 12, + 'GetMaterialfv': 12, + 'GetMaterialiv': 12, + 'GetPixelMapfv': 8, + 'GetPixelMapuiv': 8, + 'GetPixelMapusv': 8, + 'GetPointerv': 8, + 'GetPolygonStipple': 4, + 'GetString': 4, + 'GetTexEnvfv': 12, + 'GetTexEnviv': 12, + 'GetTexGendv': 12, + 'GetTexGenfv': 12, + 'GetTexGeniv': 12, + 'GetTexImage': 20, + 'GetTexLevelParameterfv': 16, + 'GetTexLevelParameteriv': 16, + 'GetTexParameterfv': 12, + 'GetTexParameteriv': 12, + 'Hint': 8, + 'IndexMask': 4, + 'IndexPointer': 12, + 'Indexd': 8, + 'Indexdv': 4, + 'Indexf': 4, + 'Indexfv': 4, + 'Indexi': 4, + 'Indexiv': 4, + 'Indexs': 4, + 'Indexsv': 4, + 'Indexub': 4, + 'Indexubv': 4, + 'InitNames': 0, + 'InterleavedArrays': 12, + 'IsEnabled': 4, + 'IsList': 4, + 'IsTexture': 4, + 'LightModelf': 8, + 'LightModelfv': 8, + 'LightModeli': 8, + 'LightModeliv': 8, + 'Lightf': 12, + 'Lightfv': 12, + 'Lighti': 12, + 'Lightiv': 12, + 'LineStipple': 8, + 'LineWidth': 4, + 'ListBase': 4, + 'LoadIdentity': 0, + 'LoadMatrixd': 4, + 'LoadMatrixf': 4, + 'LoadName': 4, + 'LogicOp': 4, + 'Map1d': 32, + 'Map1f': 24, + 'Map2d': 56, + 'Map2f': 40, + 'MapGrid1d': 20, + 'MapGrid1f': 12, + 'MapGrid2d': 40, + 'MapGrid2f': 24, + 'Materialf': 12, + 'Materialfv': 12, + 'Materiali': 12, + 'Materialiv': 12, + 'MatrixMode': 4, + 'MultMatrixd': 4, + 'MultMatrixf': 4, + 'NewList': 8, + 'Normal3b': 12, + 'Normal3bv': 4, + 'Normal3d': 24, + 'Normal3dv': 4, + 'Normal3f': 12, + 'Normal3fv': 4, + 'Normal3i': 12, + 'Normal3iv': 4, + 'Normal3s': 12, + 'Normal3sv': 4, + 'NormalPointer': 12, + 'Ortho': 48, + 'PassThrough': 4, + 'PixelMapfv': 12, + 'PixelMapuiv': 12, + 'PixelMapusv': 12, + 'PixelStoref': 8, + 'PixelStorei': 8, + 'PixelTransferf': 8, + 'PixelTransferi': 8, + 'PixelZoom': 8, + 'PointSize': 4, + 'PolygonMode': 8, + 'PolygonOffset': 8, + 'PolygonStipple': 4, + 'PopAttrib': 0, + 'PopClientAttrib': 0, + 'PopMatrix': 0, + 'PopName': 0, + 'PrioritizeTextures': 12, + 'PushAttrib': 4, + 'PushClientAttrib': 4, + 'PushMatrix': 0, + 'PushName': 4, + 'RasterPos2d': 16, + 'RasterPos2dv': 4, + 'RasterPos2f': 8, + 'RasterPos2fv': 4, + 'RasterPos2i': 8, + 'RasterPos2iv': 4, + 'RasterPos2s': 8, + 'RasterPos2sv': 4, + 'RasterPos3d': 24, + 'RasterPos3dv': 4, + 'RasterPos3f': 12, + 'RasterPos3fv': 4, + 'RasterPos3i': 12, + 'RasterPos3iv': 4, + 'RasterPos3s': 12, + 'RasterPos3sv': 4, + 'RasterPos4d': 32, + 'RasterPos4dv': 4, + 'RasterPos4f': 16, + 'RasterPos4fv': 4, + 'RasterPos4i': 16, + 'RasterPos4iv': 4, + 'RasterPos4s': 16, + 'RasterPos4sv': 4, + 'ReadBuffer': 4, + 'ReadPixels': 28, + 'Rectd': 32, + 'Rectdv': 8, + 'Rectf': 16, + 'Rectfv': 8, + 'Recti': 16, + 'Rectiv': 8, + 'Rects': 16, + 'Rectsv': 8, + 'RenderMode': 4, + 'Rotated': 32, + 'Rotatef': 16, + 'Scaled': 24, + 'Scalef': 12, + 'Scissor': 16, + 'SelectBuffer': 8, + 'ShadeModel': 4, + 'StencilFunc': 12, + 'StencilMask': 4, + 'StencilOp': 12, + 'TexCoord1d': 8, + 'TexCoord1dv': 4, + 'TexCoord1f': 4, + 'TexCoord1fv': 4, + 'TexCoord1i': 4, + 'TexCoord1iv': 4, + 'TexCoord1s': 4, + 'TexCoord1sv': 4, + 'TexCoord2d': 16, + 'TexCoord2dv': 4, + 'TexCoord2f': 8, + 'TexCoord2fv': 4, + 'TexCoord2i': 8, + 'TexCoord2iv': 4, + 'TexCoord2s': 8, + 'TexCoord2sv': 4, + 'TexCoord3d': 24, + 'TexCoord3dv': 4, + 'TexCoord3f': 12, + 'TexCoord3fv': 4, + 'TexCoord3i': 12, + 'TexCoord3iv': 4, + 'TexCoord3s': 12, + 'TexCoord3sv': 4, + 'TexCoord4d': 32, + 'TexCoord4dv': 4, + 'TexCoord4f': 16, + 'TexCoord4fv': 4, + 'TexCoord4i': 16, + 'TexCoord4iv': 4, + 'TexCoord4s': 16, + 'TexCoord4sv': 4, + 'TexCoordPointer': 16, + 'TexEnvf': 12, + 'TexEnvfv': 12, + 'TexEnvi': 12, + 'TexEnviv': 12, + 'TexGend': 16, + 'TexGendv': 12, + 'TexGenf': 12, + 'TexGenfv': 12, + 'TexGeni': 12, + 'TexGeniv': 12, + 'TexImage1D': 32, + 'TexImage2D': 36, + 'TexImage3D': 36, + 'TexParameterf': 12, + 'TexParameterfv': 12, + 'TexParameteri': 12, + 'TexParameteriv': 12, + 'TexSubImage1D': 28, + 'TexSubImage2D': 36, + 'Translated': 24, + 'Translatef': 12, + 'Vertex2d': 16, + 'Vertex2dv': 4, + 'Vertex2f': 8, + 'Vertex2fv': 4, + 'Vertex2i': 8, + 'Vertex2iv': 4, + 'Vertex2s': 8, + 'Vertex2sv': 4, + 'Vertex3d': 24, + 'Vertex3dv': 4, + 'Vertex3f': 12, + 'Vertex3fv': 4, + 'Vertex3i': 12, + 'Vertex3iv': 4, + 'Vertex3s': 12, + 'Vertex3sv': 4, + 'Vertex4d': 32, + 'Vertex4dv': 4, + 'Vertex4f': 16, + 'Vertex4fv': 4, + 'Vertex4i': 16, + 'Vertex4iv': 4, + 'Vertex4s': 16, + 'Vertex4sv': 4, + 'VertexPointer': 16, + 'Viewport': 16, + 'wglChoosePixelFormat': 8, + 'wglCopyContext': 12, + 'wglCreateContext': 4, + 'wglCreateLayerContext': 8, + 'wglDeleteContext': 4, + 'wglDescribeLayerPlane': 20, + 'wglDescribePixelFormat': 16, + 'wglGetCurrentContext': 0, + 'wglGetCurrentDC': 0, + 'wglGetDefaultProcAddress': 4, + 'wglGetLayerPaletteEntries': 20, + 'wglGetPixelFormat': 4, + 'wglGetProcAddress': 4, + 'wglMakeCurrent': 8, + 'wglRealizeLayerPalette': 12, + 'wglSetLayerPaletteEntries': 20, + 'wglSetPixelFormat': 12, + 'wglShareLists': 8, + 'wglSwapBuffers': 4, + 'wglSwapLayerBuffers': 8, + 'wglSwapMultipleBuffers': 8, + 'wglUseFontBitmapsA': 16, + 'wglUseFontBitmapsW': 16, + 'wglUseFontOutlinesA': 32, + 'wglUseFontOutlinesW': 32, + 'wglChoosePixelFormatEXT' : 24, + 'wglGetPixelFormatAttribivEXT' : 24, + 'wglGetPixelFormatAttribfvEXT' : 24, + 'wglGetExtensionsStringEXT' : 4, + 'CopyContext' : 12, + 'CreateContext' : 4, + 'CreateLayerContext' : 8, + 'DeleteContext' : 4, + 'DescribeLayerPlane' : 20, + 'DescribePixelFormat' : 16, + 'GetLayerPaletteEntries' : 20, + 'GetProcAddress' : 4, + 'RealizeLayerPalette' : 12, + 'ReleaseContext' : 4, + 'SetContext' : 12, + 'SetLayerPaletteEntries' : 20, + 'SetPixelFormat' : 12, + 'ShareLists' : 8, + 'SwapBuffers' : 4, + 'SwapLayerBuffers' : 8, + 'ValidateVersion' : 4, +} + +noexport_special = [ + "BoundsInfoCR", + "CreateContext", + "DestroyContext", + "MakeCurrent", + "WindowCreate", + "WindowDestroy", + "WindowSize", + "WindowPosition", + "WindowVisibleRegion", + "WindowShow", + "SwapBuffers" +] + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in keys: + if func_name in noexport_special: + continue + try: + print("gl%s@%d = cr_gl%s" % (func_name,stack_sizes[func_name],func_name)) + except KeyError: + pass + +for func_name in ( "wglChoosePixelFormat", + "wglCopyContext", + "wglCreateContext", + "wglCreateLayerContext", + "wglDeleteContext", + "wglDescribeLayerPlane", + "wglDescribePixelFormat", + "wglGetCurrentContext", + "wglGetCurrentDC", + "wglGetLayerPaletteEntries", + "wglGetPixelFormat", + "wglGetProcAddress", + "wglMakeCurrent", + "wglRealizeLayerPalette", + "wglSetLayerPaletteEntries", + "wglSetPixelFormat", + "wglShareLists", + "wglSwapBuffers", + "wglSwapLayerBuffers", + "wglSwapMultipleBuffers", + "wglUseFontBitmapsA", + "wglUseFontBitmapsW", + "wglUseFontOutlinesA", + "wglUseFontOutlinesW", + "wglChoosePixelFormatEXT", + "wglGetPixelFormatAttribivEXT", + "wglGetPixelFormatAttribfvEXT", + "wglGetExtensionsStringEXT"): + print("%s = %s_prox" % (func_name,func_name)) + +""" +for func_name in ( "CopyContext", + "CreateContext", + "CreateLayerContext", + "DeleteContext", + "DescribeLayerPlane", + "DescribePixelFormat", + "GetLayerPaletteEntries", + "GetProcAddress", + "RealizeLayerPalette", + "SetLayerPaletteEntries", + "ShareLists", + "SwapBuffers", + "SwapLayerBuffers"): + print "Drv%s@%d = wgl%s_prox" % (func_name,stack_sizes[func_name],func_name) +""" + +print("""DrvCopyContext +DrvCreateContext +DrvCreateLayerContext +DrvDeleteContext +DrvDescribeLayerPlane +DrvDescribePixelFormat +DrvGetLayerPaletteEntries +DrvGetProcAddress = wglGetProcAddress_prox +DrvRealizeLayerPalette +DrvSetLayerPaletteEntries +DrvShareLists +DrvSwapBuffers +DrvSwapLayerBuffers +DrvReleaseContext@4 = DrvReleaseContext +DrvSetContext@12 = DrvSetContext +DrvValidateVersion@4 = DrvValidateVersion +DrvSetPixelFormat@8 = DrvSetPixelFormat""") + +print("""crCreateContext +crMakeCurrent +crSwapBuffers +crGetProcAddress +VBoxCreateContext +VBoxCtxChromiumParameteriCR +VBoxGetWindowId +VBoxGetContextId +VBoxFlushToHost""") +#print "DllMain" diff --git a/src/VBox/Additions/common/crOpenGL/defs64.py b/src/VBox/Additions/common/crOpenGL/defs64.py new file mode 100755 index 00000000..58971b9b --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/defs64.py @@ -0,0 +1,485 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + +apiutil.CopyrightDef() + +#print "LIBRARY VBoxOGL" +#print "DESCRIPTION \"\"" - warning LNK4017: DESCRIPTION statement not supported for the target platform; ignored +print("EXPORTS") + +# XXX can't these values be automatically computed by analyzing parameters? + +exports_special = [ + 'Accum', + 'AlphaFunc', + 'AreTexturesResident', + 'ArrayElement', + 'Begin', + 'BindTexture', + 'Bitmap', + 'BlendFunc', + 'CallList', + 'CallLists', + 'Clear', + 'ClearAccum', + 'ClearColor', + 'ClearDepth', + 'ClearIndex', + 'ClearStencil', + 'ClipPlane', + 'Color3b', + 'Color3bv', + 'Color3d', + 'Color3dv', + 'Color3f', + 'Color3fv', + 'Color3i', + 'Color3iv', + 'Color3s', + 'Color3sv', + 'Color3ub', + 'Color3ubv', + 'Color3ui', + 'Color3uiv', + 'Color3us', + 'Color3usv', + 'Color4b', + 'Color4bv', + 'Color4d', + 'Color4dv', + 'Color4f', + 'Color4fv', + 'Color4i', + 'Color4iv', + 'Color4s', + 'Color4sv', + 'Color4ub', + 'Color4ubv', + 'Color4ui', + 'Color4uiv', + 'Color4us', + 'Color4usv', + 'ColorMask', + 'ColorMaterial', + 'ColorPointer', + 'CopyPixels', + 'CopyTexImage1D', + 'CopyTexImage2D', + 'CopyTexSubImage1D', + 'CopyTexSubImage2D', + 'CullFace', + 'DebugEntry', + 'DeleteLists', + 'DeleteTextures', + 'DepthFunc', + 'DepthMask', + 'DepthRange', + 'Disable', + 'DisableClientState', + 'DrawArrays', + 'DrawBuffer', + 'DrawElements', + 'DrawPixels', + 'EdgeFlag', + 'EdgeFlagPointer', + 'EdgeFlagv', + 'Enable', + 'EnableClientState', + 'End', + 'EndList', + 'EvalCoord1d', + 'EvalCoord1dv', + 'EvalCoord1f', + 'EvalCoord1fv', + 'EvalCoord2d', + 'EvalCoord2dv', + 'EvalCoord2f', + 'EvalCoord2fv', + 'EvalMesh1', + 'EvalMesh2', + 'EvalPoint1', + 'EvalPoint2', + 'FeedbackBuffer', + 'Finish', + 'Flush', + 'Fogf', + 'Fogfv', + 'Fogi', + 'Fogiv', + 'FrontFace', + 'Frustum', + 'GenLists', + 'GenTextures', + 'GetBooleanv', + 'GetClipPlane', + 'GetDoublev', + 'GetError', + 'GetFloatv', + 'GetIntegerv', + 'GetLightfv', + 'GetLightiv', + 'GetMapdv', + 'GetMapfv', + 'GetMapiv', + 'GetMaterialfv', + 'GetMaterialiv', + 'GetPixelMapfv', + 'GetPixelMapuiv', + 'GetPixelMapusv', + 'GetPointerv', + 'GetPolygonStipple', + 'GetString', + 'GetTexEnvfv', + 'GetTexEnviv', + 'GetTexGendv', + 'GetTexGenfv', + 'GetTexGeniv', + 'GetTexImage', + 'GetTexLevelParameterfv', + 'GetTexLevelParameteriv', + 'GetTexParameterfv', + 'GetTexParameteriv', + 'Hint', + 'IndexMask', + 'IndexPointer', + 'Indexd', + 'Indexdv', + 'Indexf', + 'Indexfv', + 'Indexi', + 'Indexiv', + 'Indexs', + 'Indexsv', + 'Indexub', + 'Indexubv', + 'InitNames', + 'InterleavedArrays', + 'IsEnabled', + 'IsList', + 'IsTexture', + 'LightModelf', + 'LightModelfv', + 'LightModeli', + 'LightModeliv', + 'Lightf', + 'Lightfv', + 'Lighti', + 'Lightiv', + 'LineStipple', + 'LineWidth', + 'ListBase', + 'LoadIdentity', + 'LoadMatrixd', + 'LoadMatrixf', + 'LoadName', + 'LogicOp', + 'Map1d', + 'Map1f', + 'Map2d', + 'Map2f', + 'MapGrid1d', + 'MapGrid1f', + 'MapGrid2d', + 'MapGrid2f', + 'Materialf', + 'Materialfv', + 'Materiali', + 'Materialiv', + 'MatrixMode', + 'MultMatrixd', + 'MultMatrixf', + 'NewList', + 'Normal3b', + 'Normal3bv', + 'Normal3d', + 'Normal3dv', + 'Normal3f', + 'Normal3fv', + 'Normal3i', + 'Normal3iv', + 'Normal3s', + 'Normal3sv', + 'NormalPointer', + 'Ortho', + 'PassThrough', + 'PixelMapfv', + 'PixelMapuiv', + 'PixelMapusv', + 'PixelStoref', + 'PixelStorei', + 'PixelTransferf', + 'PixelTransferi', + 'PixelZoom', + 'PointSize', + 'PolygonMode', + 'PolygonOffset', + 'PolygonStipple', + 'PopAttrib', + 'PopClientAttrib', + 'PopMatrix', + 'PopName', + 'PrioritizeTextures', + 'PushAttrib', + 'PushClientAttrib', + 'PushMatrix', + 'PushName', + 'RasterPos2d', + 'RasterPos2dv', + 'RasterPos2f', + 'RasterPos2fv', + 'RasterPos2i', + 'RasterPos2iv', + 'RasterPos2s', + 'RasterPos2sv', + 'RasterPos3d', + 'RasterPos3dv', + 'RasterPos3f', + 'RasterPos3fv', + 'RasterPos3i', + 'RasterPos3iv', + 'RasterPos3s', + 'RasterPos3sv', + 'RasterPos4d', + 'RasterPos4dv', + 'RasterPos4f', + 'RasterPos4fv', + 'RasterPos4i', + 'RasterPos4iv', + 'RasterPos4s', + 'RasterPos4sv', + 'ReadBuffer', + 'ReadPixels', + 'Rectd', + 'Rectdv', + 'Rectf', + 'Rectfv', + 'Recti', + 'Rectiv', + 'Rects', + 'Rectsv', + 'RenderMode', + 'Rotated', + 'Rotatef', + 'Scaled', + 'Scalef', + 'Scissor', + 'SelectBuffer', + 'ShadeModel', + 'StencilFunc', + 'StencilMask', + 'StencilOp', + 'TexCoord1d', + 'TexCoord1dv', + 'TexCoord1f', + 'TexCoord1fv', + 'TexCoord1i', + 'TexCoord1iv', + 'TexCoord1s', + 'TexCoord1sv', + 'TexCoord2d', + 'TexCoord2dv', + 'TexCoord2f', + 'TexCoord2fv', + 'TexCoord2i', + 'TexCoord2iv', + 'TexCoord2s', + 'TexCoord2sv', + 'TexCoord3d', + 'TexCoord3dv', + 'TexCoord3f', + 'TexCoord3fv', + 'TexCoord3i', + 'TexCoord3iv', + 'TexCoord3s', + 'TexCoord3sv', + 'TexCoord4d', + 'TexCoord4dv', + 'TexCoord4f', + 'TexCoord4fv', + 'TexCoord4i', + 'TexCoord4iv', + 'TexCoord4s', + 'TexCoord4sv', + 'TexCoordPointer', + 'TexEnvf', + 'TexEnvfv', + 'TexEnvi', + 'TexEnviv', + 'TexGend', + 'TexGendv', + 'TexGenf', + 'TexGenfv', + 'TexGeni', + 'TexGeniv', + 'TexImage1D', + 'TexImage2D', + 'TexImage3D', + 'TexParameterf', + 'TexParameterfv', + 'TexParameteri', + 'TexParameteriv', + 'TexSubImage1D', + 'TexSubImage2D', + 'Translated', + 'Translatef', + 'Vertex2d', + 'Vertex2dv', + 'Vertex2f', + 'Vertex2fv', + 'Vertex2i', + 'Vertex2iv', + 'Vertex2s', + 'Vertex2sv', + 'Vertex3d', + 'Vertex3dv', + 'Vertex3f', + 'Vertex3fv', + 'Vertex3i', + 'Vertex3iv', + 'Vertex3s', + 'Vertex3sv', + 'Vertex4d', + 'Vertex4dv', + 'Vertex4f', + 'Vertex4fv', + 'Vertex4i', + 'Vertex4iv', + 'Vertex4s', + 'Vertex4sv', + 'VertexPointer', + 'Viewport', + 'wglChoosePixelFormat', + 'wglCopyContext', + 'wglCreateContext', + 'wglCreateLayerContext', + 'wglDeleteContext', + 'wglDescribeLayerPlane', + 'wglDescribePixelFormat', + 'wglGetCurrentContext', + 'wglGetCurrentDC', + 'wglGetDefaultProcAddress', + 'wglGetLayerPaletteEntries', + 'wglGetPixelFormat', + 'wglGetProcAddress', + 'wglMakeCurrent', + 'wglRealizeLayerPalette', + 'wglSetLayerPaletteEntries', + 'wglSetPixelFormat', + 'wglShareLists', + 'wglSwapBuffers', + 'wglSwapLayerBuffers', + 'wglSwapMultipleBuffers', + 'wglUseFontBitmapsA', + 'wglUseFontBitmapsW', + 'wglUseFontOutlinesA', + 'wglUseFontOutlinesW', + 'wglChoosePixelFormatEXT', + 'wglGetPixelFormatAttribivEXT', + 'wglGetPixelFormatAttribfvEXT', + 'wglGetExtensionsStringEXT', + 'CopyContext', + 'CreateContext', + 'CreateLayerContext', + 'DeleteContext', + 'DescribeLayerPlane', + 'DescribePixelFormat', + 'GetLayerPaletteEntries', + 'GetProcAddress', + 'RealizeLayerPalette', + 'ReleaseContext', + 'SetContext', + 'SetLayerPaletteEntries', + 'SetPixelFormat', + 'ShareLists', + 'SwapBuffers', + 'SwapLayerBuffers', + 'ValidateVersion', +] + +noexport_special = [ + "BoundsInfoCR", + "CreateContext", + "DestroyContext", + "MakeCurrent", + "WindowCreate", + "WindowDestroy", + "WindowSize", + "WindowPosition", + "WindowVisibleRegion", + "WindowShow", + "SwapBuffers" +] + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in keys: + if func_name in noexport_special: + continue + if func_name in exports_special: + print("gl%s = cr_gl%s" % (func_name,func_name)) + +for func_name in ( "wglChoosePixelFormat", + "wglCopyContext", + "wglCreateContext", + "wglCreateLayerContext", + "wglDeleteContext", + "wglDescribeLayerPlane", + "wglDescribePixelFormat", + "wglGetCurrentContext", + "wglGetCurrentDC", + "wglGetLayerPaletteEntries", + "wglGetPixelFormat", + "wglGetProcAddress", + "wglMakeCurrent", + "wglRealizeLayerPalette", + "wglSetLayerPaletteEntries", + "wglSetPixelFormat", + "wglShareLists", + "wglSwapBuffers", + "wglSwapLayerBuffers", + "wglSwapMultipleBuffers", + "wglUseFontBitmapsA", + "wglUseFontBitmapsW", + "wglUseFontOutlinesA", + "wglUseFontOutlinesW", + "wglChoosePixelFormatEXT", + "wglGetPixelFormatAttribivEXT", + "wglGetPixelFormatAttribfvEXT", + "wglGetExtensionsStringEXT"): + print("%s = %s_prox" % (func_name,func_name)) + +print("""DrvCopyContext +DrvCreateContext +DrvCreateLayerContext +DrvDeleteContext +DrvDescribeLayerPlane +DrvDescribePixelFormat +DrvGetLayerPaletteEntries +DrvGetProcAddress = wglGetProcAddress_prox +DrvRealizeLayerPalette +DrvSetLayerPaletteEntries +DrvShareLists +DrvSwapBuffers +DrvSwapLayerBuffers +DrvReleaseContext = DrvReleaseContext +DrvSetContext = DrvSetContext +DrvValidateVersion = DrvValidateVersion +DrvSetPixelFormat = DrvSetPixelFormat""") + +print("""crCreateContext +crMakeCurrent +crSwapBuffers +crGetProcAddress +VBoxCreateContext +VBoxCtxChromiumParameteriCR +VBoxGetWindowId +VBoxGetContextId +VBoxFlushToHost""") +#print "DllMain" diff --git a/src/VBox/Additions/common/crOpenGL/dri_drv.c b/src/VBox/Additions/common/crOpenGL/dri_drv.c new file mode 100644 index 00000000..de206624 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/dri_drv.c @@ -0,0 +1,1012 @@ +/* $Id: dri_drv.c $ */ + +/** @file + * VBox OpenGL DRI driver functions + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * -------------------------------------------------------------------- + * + * This file is based in part on the tdfx driver from X.Org/Mesa, with the + * following copyright notice: + * + * Copyright 2000 VA Linux Systems Inc., Fremont, California. + * + * All Rights Reserved. + * + * 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 (including the next + * paragraph) 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 + * VA LINUX SYSTEMS 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. + * + * Original rewrite: + * Gareth Hughes <gareth@valinux.com>, 29 Sep - 1 Oct 2000 + * + * Authors: + * Gareth Hughes <gareth@valinux.com> + */ + +#include "cr_error.h" +#include "cr_gl.h" +#include "stub.h" +#include "dri_drv.h" +#include "DD_gl.h" + +/** @todo some of those are or'ed with GL_VERSIONS and ain't needed here*/ +#define need_GL_ARB_occlusion_query +#define need_GL_ARB_point_parameters +#define need_GL_NV_point_sprite +#define need_GL_ARB_texture_compression +#define need_GL_ARB_transpose_matrix +#define need_GL_ARB_vertex_buffer_object +#define need_GL_ARB_vertex_program +#define need_GL_ARB_window_pos +#define need_GL_EXT_blend_color +#define need_GL_EXT_blend_minmax +#define need_GL_EXT_blend_func_separate +#define need_GL_EXT_fog_coord +#define need_GL_EXT_multi_draw_arrays +#define need_GL_EXT_secondary_color +#define need_GL_EXT_texture_object +#define need_GL_EXT_texture3D +#define need_GL_VERSION_1_3 +#define need_GL_VERSION_1_4 +#define need_GL_VERSION_1_5 + +#include "drivers/dri/common/extension_helper.h" + +/** @todo add more which are supported by chromium like GL_NV_vertex_program etc.*/ +static const struct dri_extension vbox_extensions[] = { + { "GL_ARB_depth_texture", NULL }, + { "GL_ARB_fragment_program", NULL }, + { "GL_ARB_multitexture", NULL }, + { "GL_ARB_occlusion_query", GL_ARB_occlusion_query_functions }, + { "GL_ARB_point_parameters", GL_ARB_point_parameters_functions }, + { "GL_NV_point_sprite", GL_NV_point_sprite_functions }, + { "GL_ARB_shadow", NULL }, + { "GL_ARB_shadow_ambient", NULL }, + { "GL_ARB_texture_border_clamp", NULL }, + { "GL_ARB_texture_compression", GL_ARB_texture_compression_functions }, + { "GL_ARB_texture_cube_map", NULL }, + { "GL_ARB_texture_env_add", NULL }, + { "GL_ARB_texture_env_combine", NULL }, + { "GL_EXT_texture_env_combine", NULL }, + { "GL_ARB_texture_env_crossbar", NULL }, + { "GL_ARB_texture_env_dot3", NULL }, + { "GL_EXT_texture_env_dot3", NULL }, + { "GL_ARB_texture_mirrored_repeat", NULL }, + { "GL_ARB_texture_non_power_of_two", NULL }, + { "GL_ARB_transpose_matrix", GL_ARB_transpose_matrix_functions }, + { "GL_ARB_vertex_buffer_object", GL_ARB_vertex_buffer_object_functions }, + { "GL_ARB_vertex_program", GL_ARB_vertex_program_functions }, + { "GL_ARB_window_pos", GL_ARB_window_pos_functions }, + { "GL_EXT_blend_color", GL_EXT_blend_color_functions }, + { "GL_EXT_blend_minmax", GL_EXT_blend_minmax_functions }, + { "GL_EXT_blend_func_separate", GL_EXT_blend_func_separate_functions }, + { "GL_EXT_blend_subtract", NULL }, + { "GL_EXT_texture_env_add", NULL }, /** @todo that's an alias to GL_ARB version, remove it?*/ + { "GL_EXT_fog_coord", GL_EXT_fog_coord_functions }, + { "GL_EXT_multi_draw_arrays", GL_EXT_multi_draw_arrays_functions }, + { "GL_EXT_secondary_color", GL_EXT_secondary_color_functions }, + { "GL_EXT_shadow_funcs", NULL }, + { "GL_EXT_stencil_wrap", NULL }, + { "GL_EXT_texture_cube_map", NULL }, /** @todo another alias*/ + { "GL_EXT_texture_edge_clamp", NULL }, + { "GL_EXT_texture_filter_anisotropic", NULL }, + { "GL_EXT_texture_lod_bias", NULL }, + { "GL_EXT_texture_object", GL_EXT_texture_object_functions }, + { "GL_EXT_texture3D", GL_EXT_texture3D_functions }, + { "GL_NV_texgen_reflection", NULL }, + { "GL_ARB_texture_rectangle", NULL }, + { "GL_SGIS_generate_mipmap", NULL }, + { "GL_SGIS_texture_edge_clamp", NULL } /** @todo another alias*/ +}; + +static void +vboxdriInitExtensions(GLcontext * ctx) +{ + /** @todo have to check extensions supported by host here first */ + driInitExtensions(ctx, vbox_extensions, GL_FALSE); +} + +static GLvertexformat vboxdriTnlVtxFmt; + + +/* This callback tells us that Mesa's internal state has changed. We probably + * don't need to handle this ourselves, so just pass it on to other parts of + * Mesa we may be using, as the swrast driver and others do */ +static void +vboxDDUpdateState(GLcontext * ctx, GLuint new_state) +{ + _swrast_InvalidateState(ctx, new_state); + _swsetup_InvalidateState(ctx, new_state); + _vbo_InvalidateState(ctx, new_state); + _tnl_InvalidateState(ctx, new_state); +} + +#if 0 /* See comment in vboxdriInitFuncs */ +static void +vboxDDGetBufferSize(GLframebuffer *buffer, GLuint *width, GLuint *height) +{ + /*do something, note it's obsolete*/ +} + +static void +vboxDDResizeBuffer( GLcontext *ctx, GLframebuffer *fb, + GLuint width, GLuint height) +{ + /*do something, note it's obsolete*/ +} + +static void +vboxDDError(GLcontext *ctx) +{ + //__GLcontextRec::ErrorValue contains the error value. +} +#endif + +static void +vboxDDDrawPixels(GLcontext *ctx, + GLint x, GLint y, GLsizei width, GLsizei height, + GLenum format, GLenum type, + const struct gl_pixelstore_attrib *unpack, + const GLvoid *pixels) +{ +} + +static void +vboxDDReadPixels(GLcontext *ctx, + GLint x, GLint y, GLsizei width, GLsizei height, + GLenum format, GLenum type, + const struct gl_pixelstore_attrib *unpack, + GLvoid *dest) +{ +} + +static void +vboxDDCopyPixels(GLcontext *ctx, GLint srcx, GLint srcy, + GLsizei width, GLsizei height, + GLint dstx, GLint dsty, GLenum type) +{ +} + +static void +vboxDDBitmap(GLcontext *ctx, + GLint x, GLint y, GLsizei width, GLsizei height, + const struct gl_pixelstore_attrib *unpack, + const GLubyte *bitmap) +{ +} + +static void +vboxDDTexImage1D(GLcontext *ctx, GLenum target, GLint level, + GLint internalFormat, + GLint width, GLint border, + GLenum format, GLenum type, const GLvoid *pixels, + const struct gl_pixelstore_attrib *packing, + struct gl_texture_object *texObj, + struct gl_texture_image *texImage) +{ +} + +static void +vboxDDTexImage2D(GLcontext *ctx, GLenum target, GLint level, + GLint internalFormat, + GLint width, GLint height, GLint border, + GLenum format, GLenum type, const GLvoid *pixels, + const struct gl_pixelstore_attrib *packing, + struct gl_texture_object *texObj, + struct gl_texture_image *texImage) +{ +} + +static void +vboxDDTexImage3D(GLcontext *ctx, GLenum target, GLint level, + GLint internalFormat, + GLint width, GLint height, GLint depth, GLint border, + GLenum format, GLenum type, const GLvoid *pixels, + const struct gl_pixelstore_attrib *packing, + struct gl_texture_object *texObj, + struct gl_texture_image *texImage) +{ +} + +static void +vboxDDTexSubImage1D(GLcontext *ctx, GLenum target, GLint level, + GLint xoffset, GLsizei width, + GLenum format, GLenum type, + const GLvoid *pixels, + const struct gl_pixelstore_attrib *packing, + struct gl_texture_object *texObj, + struct gl_texture_image *texImage) +{ +} + +static void +vboxDDTexSubImage2D(GLcontext *ctx, GLenum target, GLint level, + GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, + GLenum format, GLenum type, + const GLvoid *pixels, + const struct gl_pixelstore_attrib *packing, + struct gl_texture_object *texObj, + struct gl_texture_image *texImage) +{ +} + + +static void +vboxDDTexSubImage3D(GLcontext *ctx, GLenum target, GLint level, + GLint xoffset, GLint yoffset, GLint zoffset, + GLsizei width, GLsizei height, GLint depth, + GLenum format, GLenum type, + const GLvoid *pixels, + const struct gl_pixelstore_attrib *packing, + struct gl_texture_object *texObj, + struct gl_texture_image *texImage) +{ +} + + +static void +vboxDDGetTexImage(GLcontext *ctx, GLenum target, GLint level, + GLenum format, GLenum type, GLvoid *pixels, + struct gl_texture_object *texObj, + struct gl_texture_image *texImage) +{ +} + +static void +vboxDDBindTexture(GLcontext *ctx, GLenum target, + struct gl_texture_object *tObj) +{ +} + +static GLboolean +vboxDDIsTextureResident(GLcontext *ctx, struct gl_texture_object *t) +{ +} + +static void +vboxDDPrioritizeTexture(GLcontext *ctx, struct gl_texture_object *t, + GLclampf priority) +{ +} + +static void +vboxDDBlendColor(GLcontext *ctx, const GLfloat color[4]) +{ +} + +static void +vboxDDClearColor(GLcontext *ctx, const GLfloat color[4]) +{ +} + +static void +vboxDDClearIndex(GLcontext *ctx, GLuint index) +{ +} + +static void +vboxDDClipPlane(GLcontext *ctx, GLenum plane, const GLfloat *equation) +{ +} + +/** @todo Enable or disable server-side gl capabilities, not related to glEnable? */ +static void +vboxDDEnable(GLcontext *ctx, GLenum cap, GLboolean state) +{ + if (state) + cr_glEnable(cap); + else + cr_glDisable(cap); +} + +static void +vboxDDRenderMode(GLcontext *ctx, GLenum mode) +{ + cr_glRenderMode(mode); +} + +static void +vboxDDTexParameter(GLcontext *ctx, GLenum target, + struct gl_texture_object *texObj, + GLenum pname, const GLfloat *params) +{ +} + +/*Note, checking glGetError before and after those calls is the only way + *to return if we succeeded to get value or not, but it will add 2 sync calls and + *will reset glGetError value returned in case application calls it explicitly + */ +static GLboolean +vboxDDGetBooleanv(GLcontext *ctx, GLenum pname, GLboolean *result) +{ + cr_glGetBooleanv(pname, result); + return GL_TRUE; +} + +static GLboolean +vboxDDGetDoublev(GLcontext *ctx, GLenum pname, GLdouble *result) +{ + cr_glGetDoublev(pname, result); + return GL_TRUE; +} + +static GLboolean +vboxDDGetFloatv(GLcontext *ctx, GLenum pname, GLfloat *result) +{ + cr_glGetFloatv(pname, result); + return GL_TRUE; +} + +static GLboolean +vboxDDGetIntegerv(GLcontext *ctx, GLenum pname, GLint *result) +{ + cr_glGetIntegerv(pname, result); + return GL_TRUE; +} + +static GLboolean +vboxDDGetPointerv(GLcontext *ctx, GLenum pname, GLvoid **result) +{ + cr_glGetPointerv(pname, result); + return GL_TRUE; +} + +/** @todo + * change stub's createcontext to reuse driver private part of mesa's ctx to store stub ctx info. + */ +#define VBOX_GL_FUNC(func) vboxDD_gl##func +static void +vboxdriInitFuncs(struct dd_function_table *driver) +{ + driver->GetString = VBOX_GL_FUNC(GetString); + driver->UpdateState = vboxDDUpdateState; +#if 0 + /* I assume that we don't need to change these. In that case, prefer the + * default implementation over a stub. */ + driver->GetBufferSize = vboxDDGetBufferSize; + driver->ResizeBuffers = vboxDDResizeBuffer; + driver->Error = vboxDDError; +#endif + + driver->Finish = VBOX_GL_FUNC(Finish); + driver->Flush = VBOX_GL_FUNC(Flush); + + /* framebuffer/image functions */ + driver->Clear = VBOX_GL_FUNC(Clear); + driver->Accum = VBOX_GL_FUNC(Accum); + // driver->RasterPos = VBOX_GL_FUNC(RasterPos); /* No such element in *driver */ + driver->DrawPixels = vboxDDDrawPixels; + driver->ReadPixels = vboxDDReadPixels; + driver->CopyPixels = vboxDDCopyPixels; + driver->Bitmap = vboxDDBitmap; + + /* Texture functions */ + /** @todo deal with texnames and gl_texture_object pointers which are passed here*/ + driver->ChooseTextureFormat = NULL; + driver->TexImage1D = vboxDDTexImage1D; + driver->TexImage2D = vboxDDTexImage2D; + driver->TexImage3D = vboxDDTexImage3D; + driver->TexSubImage1D = vboxDDTexSubImage1D; + driver->TexSubImage2D = vboxDDTexSubImage2D; + driver->TexSubImage3D = vboxDDTexSubImage3D; + driver->GetTexImage = vboxDDGetTexImage; + driver->CopyTexImage1D = VBOX_GL_FUNC(CopyTexImage1D); + driver->CopyTexImage2D = VBOX_GL_FUNC(CopyTexImage2D); + driver->CopyTexSubImage1D = VBOX_GL_FUNC(CopyTexSubImage1D); + driver->CopyTexSubImage2D = VBOX_GL_FUNC(CopyTexSubImage2D); + driver->CopyTexSubImage3D = VBOX_GL_FUNC(CopyTexSubImage3D); + // driver->GenerateMipmap = VBOX_GL_FUNC(GenerateMipmap); /** @todo or NULL */ + // driver->TestProxyTexImage = vboxDDTestProxyTexImage; /** @todo just pass to glTexImage as we take care or proxy textures there */ + // driver->CompressedTexImage1D = VBOX_GL_FUNC(CompressedTexImage1D); + // driver->CompressedTexImage2D = VBOX_GL_FUNC(CompressedTexImage2D); + // driver->CompressedTexImage3D = VBOX_GL_FUNC(CompressedTexImage3D); + // driver->CompressedTexSubImage1D = VBOX_GL_FUNC(CompressedTexSubImage1D); + // driver->CompressedTexSubImage2D = VBOX_GL_FUNC(CompressedTexSubImage2D); + // driver->CompressedTexSubImage3D = VBOX_GL_FUNC(CompressedTexSubImage3D); + // driver->GetCompressedTexImage = VBOX_GL_FUNC(GetCompressedTexImage); + // driver->CompressedTextureSize = NULL; /** @todo */ + driver->BindTexture = vboxDDBindTexture; + // driver->NewTextureObject = vboxDDNewTextureObject; /** @todo */ + // driver->DeleteTexture = vboxDDDeleteTexture; /** @todo */ + // driver->NewTextureImage = vboxDDNewTextureImage; /** @todo */ + // driver->FreeTexImageData = vboxDDFreeTexImageData; /** @todo */ + // driver->MapTexture = vboxDDMapTexture; /** @todo */ + // driver->UnmapTexture = vboxDDUnmapTexture; /** @todo */ + // driver->TextureMemCpy = vboxDDTextureMemCpy; /** @todo */ + driver->IsTextureResident = vboxDDIsTextureResident; + driver->PrioritizeTexture = vboxDDPrioritizeTexture; + driver->ActiveTexture = VBOX_GL_FUNC(ActiveTextureARB); + // driver->UpdateTexturePalette = vboxDDUpdateTexturePalette; /** @todo */ + + /* imaging */ + /*driver->CopyColorTable = _swrast_CopyColorTable; + driver->CopyColorSubTable = _swrast_CopyColorSubTable; + driver->CopyConvolutionFilter1D = _swrast_CopyConvolutionFilter1D; + driver->CopyConvolutionFilter2D = _swrast_CopyConvolutionFilter2D;*/ + + /* Vertex/fragment programs */ + driver->BindProgram = NULL; + // driver->NewProgram = _mesa_new_program; /** @todo */ + // driver->DeleteProgram = _mesa_delete_program; /** @todo */ + driver->ProgramStringNotify = NULL; +#if FEATURE_MESA_program_debug + // driver->GetProgramRegister = _mesa_get_program_register; /** @todo */ +#endif /* FEATURE_MESA_program_debug */ + driver->IsProgramNative = NULL; + + /* simple state commands */ + driver->AlphaFunc = VBOX_GL_FUNC(AlphaFunc); + driver->BlendColor = vboxDDBlendColor; + // driver->BlendEquationSeparate = VBOX_GL_FUNC(BlendEquationSeparate); /** @todo */ + driver->BlendFuncSeparate = VBOX_GL_FUNC(BlendFuncSeparateEXT); + driver->ClearColor = vboxDDClearColor; + driver->ClearDepth = VBOX_GL_FUNC(ClearDepth); + driver->ClearIndex = vboxDDClearIndex; + driver->ClearStencil = VBOX_GL_FUNC(ClearStencil); + driver->ClipPlane = vboxDDClipPlane; + driver->ColorMask = VBOX_GL_FUNC(ColorMask); + driver->ColorMaterial = VBOX_GL_FUNC(ColorMaterial); + driver->CullFace = VBOX_GL_FUNC(CullFace); + driver->DrawBuffer = VBOX_GL_FUNC(DrawBuffer); /** @todo */ + // driver->DrawBuffers = VBOX_GL_FUNC(DrawBuffers); /** @todo */ + driver->FrontFace = VBOX_GL_FUNC(FrontFace); + driver->DepthFunc = VBOX_GL_FUNC(DepthFunc); + driver->DepthMask = VBOX_GL_FUNC(DepthMask); + driver->DepthRange = VBOX_GL_FUNC(DepthRange); + driver->Enable = vboxDDEnable; + driver->Fogfv = VBOX_GL_FUNC(Fogfv); + driver->Hint = VBOX_GL_FUNC(Hint); + driver->IndexMask = VBOX_GL_FUNC(IndexMask); + driver->Lightfv = VBOX_GL_FUNC(Lightfv); + driver->LightModelfv = VBOX_GL_FUNC(LightModelfv); + driver->LineStipple = VBOX_GL_FUNC(LineStipple); + driver->LineWidth = VBOX_GL_FUNC(LineWidth); + // driver->LogicOpcode = VBOX_GL_FUNC(LogicOpcode); /** @todo */ + driver->PointParameterfv = VBOX_GL_FUNC(PointParameterfvARB); + driver->PointSize = VBOX_GL_FUNC(PointSize); + driver->PolygonMode = VBOX_GL_FUNC(PolygonMode); + driver->PolygonOffset = VBOX_GL_FUNC(PolygonOffset); + driver->PolygonStipple = VBOX_GL_FUNC(PolygonStipple); + driver->ReadBuffer = VBOX_GL_FUNC(ReadBuffer); + driver->RenderMode = vboxDDRenderMode; + driver->Scissor = VBOX_GL_FUNC(Scissor); + driver->ShadeModel = VBOX_GL_FUNC(ShadeModel); + // driver->StencilFuncSeparate = VBOX_GL_FUNC(StencilFuncSeparate); /** @todo */ + // driver->StencilOpSeparate = VBOX_GL_FUNC(StencilOpSeparate); /** @todo */ + // driver->StencilMaskSeparate = VBOX_GL_FUNC(StencilMaskSeparate); /** @todo */ + driver->TexGen = VBOX_GL_FUNC(TexGenfv); + driver->TexEnv = VBOX_GL_FUNC(TexEnvfv); + driver->TexParameter = vboxDDTexParameter; + // driver->TextureMatrix = VBOX_GL_FUNC(TextureMatrix); /** @todo */ + driver->Viewport = VBOX_GL_FUNC(Viewport); + + /* vertex arrays */ + driver->VertexPointer = VBOX_GL_FUNC(VertexPointer); + driver->NormalPointer = VBOX_GL_FUNC(NormalPointer); + driver->ColorPointer = VBOX_GL_FUNC(ColorPointer); + driver->FogCoordPointer = VBOX_GL_FUNC(FogCoordPointerEXT); + driver->IndexPointer = VBOX_GL_FUNC(IndexPointer); + driver->SecondaryColorPointer = VBOX_GL_FUNC(SecondaryColorPointerEXT); + driver->TexCoordPointer = VBOX_GL_FUNC(TexCoordPointer); + driver->EdgeFlagPointer = VBOX_GL_FUNC(EdgeFlagPointer); + // driver->VertexAttribPointer = VBOX_GL_FUNC(VertexAttribPointer); /** @todo */ + // driver->LockArraysEXT = VBOX_GL_FUNC(LockArraysEXT); /** @todo */ + // driver->UnlockArraysEXT = VBOX_GL_FUNC(UnlockArraysEXT); /** @todo */ + + /* state queries */ + driver->GetBooleanv = vboxDDGetBooleanv; + driver->GetDoublev = vboxDDGetDoublev; + driver->GetFloatv = vboxDDGetFloatv; + driver->GetIntegerv = vboxDDGetIntegerv; + driver->GetPointerv = vboxDDGetPointerv; + +/** @todo */ +#if FEATURE_ARB_vertex_buffer_object + // driver->NewBufferObject = _mesa_new_buffer_object; + // driver->DeleteBuffer = _mesa_delete_buffer_object; + // driver->BindBuffer = NULL; + // driver->BufferData = _mesa_buffer_data; + // driver->BufferSubData = _mesa_buffer_subdata; + // driver->GetBufferSubData = _mesa_buffer_get_subdata; + // driver->MapBuffer = _mesa_buffer_map; + // driver->UnmapBuffer = _mesa_buffer_unmap; +#endif + +/** @todo */ +#if FEATURE_EXT_framebuffer_object + // driver->NewFramebuffer = _mesa_new_framebuffer; + // driver->NewRenderbuffer = _mesa_new_soft_renderbuffer; + // driver->RenderTexture = _mesa_render_texture; + // driver->FinishRenderTexture = _mesa_finish_render_texture; + // driver->FramebufferRenderbuffer = _mesa_framebuffer_renderbuffer; +#endif + +/** @todo */ +#if FEATURE_EXT_framebuffer_blit + // driver->BlitFramebuffer = _swrast_BlitFramebuffer; +#endif + + /* query objects */ + // driver->NewQueryObject = VBOX_GL_FUNC(NewQueryObject); /** @todo */ + // driver->DeleteQuery = VBOX_GL_FUNC(DeleteQuery); /** @todo */ + // driver->BeginQuery = VBOX_GL_FUNC(BeginQuery); /** @todo */ + // driver->EndQuery = VBOX_GL_FUNC(EndQuery); /** @todo */ + // driver->WaitQuery = VBOX_GL_FUNC(WaitQuery); /** @todo */ + + /* APPLE_vertex_array_object */ +/* + driver->NewArrayObject = _mesa_new_array_object; + driver->DeleteArrayObject = _mesa_delete_array_object; + driver->BindArrayObject = NULL; +*/ + + /* T&L stuff */ + driver->NeedValidate = GL_FALSE; + driver->ValidateTnlModule = NULL; + driver->CurrentExecPrimitive = 0; + driver->CurrentSavePrimitive = 0; + driver->NeedFlush = 0; + driver->SaveNeedFlush = 0; + + // driver->ProgramStringNotify = _tnl_program_string; /** @todo */ + driver->FlushVertices = NULL; + driver->SaveFlushVertices = NULL; + driver->NotifySaveBegin = NULL; + driver->LightingSpaceChange = NULL; + + /* display list */ + driver->NewList = VBOX_GL_FUNC(NewList); + driver->EndList = VBOX_GL_FUNC(EndList); + // driver->BeginCallList = VBOX_GL_FUNC(BeginCallList); /** @todo */ + // driver->EndCallList = VBOX_GL_FUNC(EndCallList); /** @todo */ + + + /* shaders */ + /* + driver->AttachShader = _mesa_attach_shader; + driver->BindAttribLocation = _mesa_bind_attrib_location; + driver->CompileShader = _mesa_compile_shader; + driver->CreateProgram = _mesa_create_program; + driver->CreateShader = _mesa_create_shader; + driver->DeleteProgram2 = _mesa_delete_program2; + driver->DeleteShader = _mesa_delete_shader; + driver->DetachShader = _mesa_detach_shader; + driver->GetActiveAttrib = _mesa_get_active_attrib; + driver->GetActiveUniform = _mesa_get_active_uniform; + driver->GetAttachedShaders = _mesa_get_attached_shaders; + driver->GetAttribLocation = _mesa_get_attrib_location; + driver->GetHandle = _mesa_get_handle; + driver->GetProgramiv = _mesa_get_programiv; + driver->GetProgramInfoLog = _mesa_get_program_info_log; + driver->GetShaderiv = _mesa_get_shaderiv; + driver->GetShaderInfoLog = _mesa_get_shader_info_log; + driver->GetShaderSource = _mesa_get_shader_source; + driver->GetUniformfv = _mesa_get_uniformfv; + driver->GetUniformiv = _mesa_get_uniformiv; + driver->GetUniformLocation = _mesa_get_uniform_location; + driver->IsProgram = _mesa_is_program; + driver->IsShader = _mesa_is_shader; + driver->LinkProgram = _mesa_link_program; + driver->ShaderSource = _mesa_shader_source; + driver->Uniform = _mesa_uniform; + driver->UniformMatrix = _mesa_uniform_matrix; + driver->UseProgram = _mesa_use_program; + driver->ValidateProgram = _mesa_validate_program; + */ +} + +static void +vboxdriInitTnlVtxFmt(GLvertexformat *pVtxFmt) +{ + pVtxFmt->ArrayElement = VBOX_GL_FUNC(ArrayElement); + pVtxFmt->Begin = VBOX_GL_FUNC(Begin); + pVtxFmt->CallList = VBOX_GL_FUNC(CallList); + pVtxFmt->CallLists = VBOX_GL_FUNC(CallLists); + pVtxFmt->Color3f = VBOX_GL_FUNC(Color3f); + pVtxFmt->Color3fv = VBOX_GL_FUNC(Color3fv); + pVtxFmt->Color4f = VBOX_GL_FUNC(Color4f); + pVtxFmt->Color4fv = VBOX_GL_FUNC(Color4fv); + pVtxFmt->EdgeFlag = VBOX_GL_FUNC(EdgeFlag); + pVtxFmt->End = VBOX_GL_FUNC(End); + pVtxFmt->EvalCoord1f = VBOX_GL_FUNC(EvalCoord1f); + pVtxFmt->EvalCoord1fv = VBOX_GL_FUNC(EvalCoord1fv); + pVtxFmt->EvalCoord2f = VBOX_GL_FUNC(EvalCoord2f); + pVtxFmt->EvalCoord2fv = VBOX_GL_FUNC(EvalCoord2fv); + pVtxFmt->EvalPoint1 = VBOX_GL_FUNC(EvalPoint1); + pVtxFmt->EvalPoint2 = VBOX_GL_FUNC(EvalPoint2); + pVtxFmt->FogCoordfEXT = VBOX_GL_FUNC(FogCoordfEXT); + pVtxFmt->FogCoordfvEXT = VBOX_GL_FUNC(FogCoordfvEXT); + pVtxFmt->Indexf = VBOX_GL_FUNC(Indexf); + pVtxFmt->Indexfv = VBOX_GL_FUNC(Indexfv); + pVtxFmt->Materialfv = VBOX_GL_FUNC(Materialfv); + pVtxFmt->MultiTexCoord1fARB = VBOX_GL_FUNC(MultiTexCoord1fARB); + pVtxFmt->MultiTexCoord1fvARB = VBOX_GL_FUNC(MultiTexCoord1fvARB); + pVtxFmt->MultiTexCoord2fARB = VBOX_GL_FUNC(MultiTexCoord2fARB); + pVtxFmt->MultiTexCoord2fvARB = VBOX_GL_FUNC(MultiTexCoord2fvARB); + pVtxFmt->MultiTexCoord3fARB = VBOX_GL_FUNC(MultiTexCoord3fARB); + pVtxFmt->MultiTexCoord3fvARB = VBOX_GL_FUNC(MultiTexCoord3fvARB); + pVtxFmt->MultiTexCoord4fARB = VBOX_GL_FUNC(MultiTexCoord4fARB); + pVtxFmt->MultiTexCoord4fvARB = VBOX_GL_FUNC(MultiTexCoord4fvARB); + pVtxFmt->Normal3f = VBOX_GL_FUNC(Normal3f); + pVtxFmt->Normal3fv = VBOX_GL_FUNC(Normal3fv); + pVtxFmt->SecondaryColor3fEXT = VBOX_GL_FUNC(SecondaryColor3fEXT); + pVtxFmt->SecondaryColor3fvEXT = VBOX_GL_FUNC(SecondaryColor3fvEXT); + pVtxFmt->TexCoord1f = VBOX_GL_FUNC(TexCoord1f); + pVtxFmt->TexCoord1fv = VBOX_GL_FUNC(TexCoord1fv); + pVtxFmt->TexCoord2f = VBOX_GL_FUNC(TexCoord2f); + pVtxFmt->TexCoord2fv = VBOX_GL_FUNC(TexCoord2fv); + pVtxFmt->TexCoord3f = VBOX_GL_FUNC(TexCoord3f); + pVtxFmt->TexCoord3fv = VBOX_GL_FUNC(TexCoord3fv); + pVtxFmt->TexCoord4f = VBOX_GL_FUNC(TexCoord4f); + pVtxFmt->TexCoord4fv = VBOX_GL_FUNC(TexCoord4fv); + pVtxFmt->Vertex2f = VBOX_GL_FUNC(Vertex2f); + pVtxFmt->Vertex2fv = VBOX_GL_FUNC(Vertex2fv); + pVtxFmt->Vertex3f = VBOX_GL_FUNC(Vertex3f); + pVtxFmt->Vertex3fv = VBOX_GL_FUNC(Vertex3fv); + pVtxFmt->Vertex4f = VBOX_GL_FUNC(Vertex4f); + pVtxFmt->Vertex4fv = VBOX_GL_FUNC(Vertex4fv); + pVtxFmt->VertexAttrib1fNV = VBOX_GL_FUNC(VertexAttrib1fARB); + pVtxFmt->VertexAttrib1fvNV = VBOX_GL_FUNC(VertexAttrib1fvARB); + pVtxFmt->VertexAttrib2fNV = VBOX_GL_FUNC(VertexAttrib2fARB); + pVtxFmt->VertexAttrib2fvNV = VBOX_GL_FUNC(VertexAttrib2fvARB); + pVtxFmt->VertexAttrib3fNV = VBOX_GL_FUNC(VertexAttrib3fARB); + pVtxFmt->VertexAttrib3fvNV = VBOX_GL_FUNC(VertexAttrib3fvARB); + pVtxFmt->VertexAttrib4fNV = VBOX_GL_FUNC(VertexAttrib4fARB); + pVtxFmt->VertexAttrib4fvNV = VBOX_GL_FUNC(VertexAttrib4fvARB); + pVtxFmt->VertexAttrib1fARB = VBOX_GL_FUNC(VertexAttrib1fARB); + pVtxFmt->VertexAttrib1fvARB = VBOX_GL_FUNC(VertexAttrib1fvARB); + pVtxFmt->VertexAttrib2fARB = VBOX_GL_FUNC(VertexAttrib2fARB); + pVtxFmt->VertexAttrib2fvARB = VBOX_GL_FUNC(VertexAttrib2fvARB); + pVtxFmt->VertexAttrib3fARB = VBOX_GL_FUNC(VertexAttrib3fARB); + pVtxFmt->VertexAttrib3fvARB = VBOX_GL_FUNC(VertexAttrib3fvARB); + pVtxFmt->VertexAttrib4fARB = VBOX_GL_FUNC(VertexAttrib4fARB); + pVtxFmt->VertexAttrib4fvARB = VBOX_GL_FUNC(VertexAttrib4fvARB); + + pVtxFmt->EvalMesh1 = VBOX_GL_FUNC(EvalMesh1); + pVtxFmt->EvalMesh2 = VBOX_GL_FUNC(EvalMesh2); + pVtxFmt->Rectf = VBOX_GL_FUNC(Rectf); + + pVtxFmt->DrawArrays = VBOX_GL_FUNC(DrawArrays); + pVtxFmt->DrawElements = VBOX_GL_FUNC(DrawElements); + pVtxFmt->DrawRangeElements = VBOX_GL_FUNC(DrawRangeElements); +} +#undef VBOX_GL_FUNC + +static void +vboxdriExtSetTexOffset(__DRIcontext *pDRICtx, GLint texname, + unsigned long long offset, GLint depth, GLuint pitch) +{ +} + + +static void +vboxdriExtSetTexBuffer(__DRIcontext *pDRICtx, GLint target, __DRIdrawable *dPriv) +{ +} + +/** @todo not sure we need it from start*/ +static const __DRItexOffsetExtension vboxdriTexOffsetExtension = { + { __DRI_TEX_OFFSET }, + vboxdriExtSetTexOffset, +}; + +/* This DRI extension is required to support EXT_texture_from_pixmap, + * which in turn is required by compiz. + */ +static const __DRItexBufferExtension vboxdriTexBufferExtension = { + { __DRI_TEX_BUFFER, __DRI_TEX_BUFFER_VERSION }, + vboxdriExtSetTexBuffer, +}; + +/* List of DRI extensions supported by VBox DRI driver */ +static const __DRIextension *vboxdriExtensions[] = { + &vboxdriTexOffsetExtension.base, + &vboxdriTexBufferExtension.base, + NULL +}; + +static __GLcontextModes *vboxdriFillInModes(unsigned pixel_bits, + unsigned depth_bits, + unsigned stencil_bits, + GLboolean have_back_buffer) +{ + unsigned deep = (depth_bits > 17); + + /* Right now GLX_SWAP_COPY_OML isn't supported, but it would be easy + * enough to add support. Basically, if a context is created with an + * fbconfig where the swap method is GLX_SWAP_COPY_OML, pageflipping + * will never be used. + */ + + static const GLenum db_modes[2] = { GLX_NONE, GLX_SWAP_UNDEFINED_OML }; + uint8_t depth_bits_array[4]; + uint8_t stencil_bits_array[4]; + if(deep) { + depth_bits_array[0] = 0; + depth_bits_array[1] = 24; + stencil_bits_array[0] = 0; + stencil_bits_array[1] = 8; + } else { + depth_bits_array[0] = depth_bits; + depth_bits_array[1] = 0; + depth_bits_array[2] = depth_bits; + depth_bits_array[3] = 0; + stencil_bits_array[0] = 0; + stencil_bits_array[1] = 0; + stencil_bits_array[2] = 8; + stencil_bits_array[3] = 8; + } + + return driCreateConfigs( + deep ? GL_RGBA : GL_RGB, + deep ? GL_UNSIGNED_INT_8_8_8_8 : GL_UNSIGNED_SHORT_5_6_5, + depth_bits_array, + stencil_bits_array, + deep ? 2 : 4, + db_modes, 2); +} + +/** + * This is the driver specific part of the createNewScreen entry point. + * Called when using legacy DRI. + * + * return the __GLcontextModes supported by this driver + */ +static const __DRIconfig **vboxdriInitScreen(__DRIscreenPrivate *psp) +{ + static const __DRIversion ddx_expected = { 1, 1, 0 }; + static const __DRIversion dri_expected = { 4, 0, 0 }; + static const __DRIversion drm_expected = { 1, 0, 0 }; + //PVBoxDRI = (PVBoxDRI) psp->pDevPrivate; + + /* Initialise our call table in chromium. */ + if (!stubInit()) + { + crDebug("vboxdriInitScreen: stubInit failed"); + return NULL; + } + + if ( ! driCheckDriDdxDrmVersions2( "tdfx", + &psp->dri_version, & dri_expected, + &psp->ddx_version, & ddx_expected, + &psp->drm_version, & drm_expected ) ) + return NULL; + + /* Calling driInitExtensions here, with a NULL context pointer, + * does not actually enable the extensions. It just makes sure + * that all the dispatch offsets for all the extensions that + * *might* be enables are known. This is needed because the + * dispatch offsets need to be known when _mesa_context_create is + * called, but we can't enable the extensions until we have a + * context pointer. + * + * Hello chicken. Hello egg. How are you two today? + */ + vboxdriInitExtensions(NULL); + + /** @todo check size of DRIRec (passed from X.Org driver), allocate private + * structure if necessary, parse options if necessary, map VRAM if + * necessary. */ + psp->extensions = vboxdriExtensions; + + /* Initialise VtxFmt call table. */ + vboxdriInitTnlVtxFmt(&vboxdriTnlVtxFmt); + + /*return (const __DRIconfig **) + vboxdriFillInModes(psp, dri_priv->cpp * 8, + (dri_priv->cpp == 2) ? 16 : 24, + (dri_priv->cpp == 2) ? 0 : 8, 1);*/ + + /** @todo This should depend on what the host can do, not the guest. + * However, we will probably want to discover that in the X.Org driver, + * not here. */ + return (const __DRIconfig **) vboxdriFillInModes(psp, 24, 24, 8, 1); +} + +static void +vboxdriDestroyScreen(__DRIscreenPrivate * sPriv) +{ + crDebug("vboxdriDestroyScreen"); +#if 0 /* From the tdfx driver */ + /* free all option information */ + driDestroyOptionInfo (&sPriv->private->optionCache); + FREE(sPriv->private); + sPriv->private = NULL; +#endif +} + +static GLboolean +vboxdriCreateContext(const __GLcontextModes * mesaVis, + __DRIcontextPrivate * driContextPriv, + void *sharedContextPrivate) +{ + //__DRIscreenPrivate *sPriv = driContextPriv->driScreenPriv; + struct dd_function_table functions; + GLcontext *ctx, *shareCtx; + +#if 0 /* We shouldn't need this sort of thing. */ + XVisualInfo *vis; + vis->visual->visualid; + context->dpy = dpy; + context->visual = vis; + + GLXContext vboxctx = glXCreateContext(dpy, mesaVis->visualID, GL_TRUE); +#endif + /* We should be allocating a private context structure, where we will + * remember the Mesa context (ctx) among other things. The TDFX driver + * also saves importand information in driContextPriv in there - is this + * not always available to us? */ + //driContextPriv->driverPrivate = vboxctx; + + /* Initialise the default driver functions then plug in our vbox ones, + * which will actually replace most of the defaults. */ + /** @todo we should also pass some information from the visual back to the + * host. */ + _mesa_init_driver_functions(&functions); + vboxdriInitFuncs(&functions); + + /* Allocate context information for Mesa. */ + if (sharedContextPrivate) + shareCtx = ((tdfxContextPtr) sharedContextPrivate)->glCtx; + else + shareCtx = NULL; + /** @todo save ctx, or be more confident that we can don't need to. */ + ctx = _mesa_create_context(mesaVis, shareCtx, &functions, + driContextPriv->driverPrivate); + if (!ctx) + { + crDebug("vboxdriCreateContext: _mesa_create_context failed"); + return GL_FALSE; + } + + /* The TDFX driver parses its configuration files here, via + * driParseConfigFiles. We will probably get any information via guest + * properties. */ + + /* Set various context configuration. We take these values from the + * TDFX driver. */ + /** @r=Leonid, stub.spu->dispatch_table.GetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&value) etc. + * Those would be cached where possible, see include/state/cr_limits.h, VBoxOGLgen/packspu_get.c + * Note, that ctx->Const.MaxTextureImageUnits is *not* related to GL_MAX_TEXTURE_UNITS_ARB, + * use GL_MAX_TEXTURE_IMAGE_UNITS_ARB instead. + * Also, those could fail if we haven't made ctx in our stub yet. + */ + ctx->Const.MaxTextureLevels = 9; + ctx->Const.MaxTextureUnits = 1; + ctx->Const.MaxTextureImageUnits = ctx->Const.MaxTextureUnits; + ctx->Const.MaxTextureCoordUnits = ctx->Const.MaxTextureUnits; + + /* No wide points. + */ + ctx->Const.MinPointSize = 1.0; + ctx->Const.MinPointSizeAA = 1.0; + ctx->Const.MaxPointSize = 1.0; + ctx->Const.MaxPointSizeAA = 1.0; + + /* Disable wide lines as we can't antialias them correctly in + * hardware. + */ + /** @note That applies to the TDFX, not to us, but as I don't yet know + * what to use instead I am leaving the values for now. */ + ctx->Const.MinLineWidth = 1.0; + ctx->Const.MinLineWidthAA = 1.0; + ctx->Const.MaxLineWidth = 1.0; + ctx->Const.MaxLineWidthAA = 1.0; + ctx->Const.LineWidthGranularity = 1.0; + + /* Initialize the software rasterizer and helper modules - again, TDFX */ + _swrast_CreateContext( ctx ); + _vbo_CreateContext( ctx ); + _tnl_CreateContext( ctx ); + _swsetup_CreateContext( ctx ); + + /* Install the customized pipeline, TDFX */ + _tnl_destroy_pipeline( ctx ); + _tnl_install_pipeline( ctx, tdfx_pipeline ); + + /* Configure swrast and T&L to match hardware characteristics, TDFX */ + _swrast_allow_pixel_fog( ctx, GL_TRUE ); + _swrast_allow_vertex_fog( ctx, GL_FALSE ); + _tnl_allow_pixel_fog( ctx, GL_TRUE ); + _tnl_allow_vertex_fog( ctx, GL_FALSE ); + /*ctx->DriverCtx = ;*/ + + /* This was *not* in the TDFX driver. */ + _mesa_install_exec_vtxfmt(ctx, &vboxdriTnlVtxFmt); + + vboxdriInitExtensions(ctx); + + return GL_TRUE; +} + + +static void +vboxdriDestroyContext(__DRIcontextPrivate *driContextPriv) +{ + // glXDestroyContext(driContextPriv->driverPrivate); + //_mesa_destroy_context ? +} + +/** + * This is called when we need to set up GL rendering to a new X window. + */ +static GLboolean +vboxdriCreateBuffer(__DRIscreenPrivate * driScrnPriv, + __DRIdrawablePrivate * driDrawPriv, + const __GLcontextModes * mesaVis, GLboolean isPixmap) +{ + return GL_FALSE; +} + +static void +vboxdriDestroyBuffer(__DRIdrawablePrivate * driDrawPriv) +{ +} + +static void +vboxdriSwapBuffers(__DRIdrawablePrivate * dPriv) +{ +} + + +GLboolean +vboxdriMakeCurrent(__DRIcontextPrivate * driContextPriv, + __DRIdrawablePrivate * driDrawPriv, + __DRIdrawablePrivate * driReadPriv) +{ + return GL_FALSE; +} + +GLboolean +vboxdriUnbindContext(__DRIcontextPrivate * driContextPriv) +{ + return GL_TRUE; +} + + +/* This structure is used by dri_util from mesa, don't rename it! */ +DECLEXPORT(const struct __DriverAPIRec) driDriverAPI = { + .InitScreen = vboxdriInitScreen, + .DestroyScreen = vboxdriDestroyScreen, + .CreateContext = vboxdriCreateContext, + .DestroyContext = vboxdriDestroyContext, + .CreateBuffer = vboxdriCreateBuffer, + .DestroyBuffer = vboxdriDestroyBuffer, + .SwapBuffers = vboxdriSwapBuffers, + .MakeCurrent = vboxdriMakeCurrent, + .UnbindContext = vboxdriUnbindContext, + .GetSwapInfo = NULL, + .WaitForMSC = NULL, + .WaitForSBC = NULL, + .SwapBuffersMSC = NULL, + .CopySubBuffer = NULL, + .GetDrawableMSC = NULL, + .InitScreen2 = NULL +}; diff --git a/src/VBox/Additions/common/crOpenGL/dri_drv.h b/src/VBox/Additions/common/crOpenGL/dri_drv.h new file mode 100644 index 00000000..7c11c3ba --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/dri_drv.h @@ -0,0 +1,32 @@ +/* $Id: dri_drv.h $ */ + +/** @file + * + * VirtualBox guest OpenGL DRI header + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef GA_INCLUDED_SRC_common_crOpenGL_dri_drv_h +#define GA_INCLUDED_SRC_common_crOpenGL_dri_drv_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "src/mesa/main/mtypes.h" +#include "src/mesa/drivers/dri/common/dri_util.h" +#include "src/mesa/glapi/dispatch.h" +#include "src/mesa/main/dd.h" + +#endif /* !GA_INCLUDED_SRC_common_crOpenGL_dri_drv_h */ + diff --git a/src/VBox/Additions/common/crOpenGL/dri_glx.h b/src/VBox/Additions/common/crOpenGL/dri_glx.h new file mode 100644 index 00000000..237c93a3 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/dri_glx.h @@ -0,0 +1,165 @@ +/* $Id: dri_glx.h $ */ + +/** @file + * + * VirtualBox guest OpenGL DRI GLX header + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef GA_INCLUDED_SRC_common_crOpenGL_dri_glx_h +#define GA_INCLUDED_SRC_common_crOpenGL_dri_glx_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "chromium.h" +#include "stub.h" + +#if defined(VBOXOGL_FAKEDRI) || defined(VBOXOGL_DRI) + #define VBOXGLXTAG(Func) vboxstub_##Func + #define VBOXGLXENTRYTAG(Func) vbox_##Func + #define VBOXGLTAG(Func) cr_##Func +#else + #define VBOXGLXTAG(Func) Func + #define VBOXGLXENTRYTAG(Func) Func + #define VBOXGLTAG(Func) Func +#endif + +#ifdef VBOXOGL_FAKEDRI +extern DECLEXPORT(const char *) VBOXGLXTAG(glXGetDriverConfig)(const char *driverName); +extern DECLEXPORT(void) VBOXGLXTAG(glXFreeMemoryMESA)(Display *dpy, int scrn, void *pointer); +extern DECLEXPORT(GLXContext) VBOXGLXTAG(glXImportContextEXT)(Display *dpy, GLXContextID contextID); +extern DECLEXPORT(GLXContextID) VBOXGLXTAG(glXGetContextIDEXT)(const GLXContext ctx); +extern DECLEXPORT(Bool) VBOXGLXTAG(glXMakeCurrentReadSGI)(Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx); +extern DECLEXPORT(const char *) VBOXGLXTAG(glXGetScreenDriver)(Display *dpy, int scrNum); +extern DECLEXPORT(Display *) VBOXGLXTAG(glXGetCurrentDisplayEXT)(void); +extern DECLEXPORT(void) VBOXGLXTAG(glXFreeContextEXT)(Display *dpy, GLXContext ctx); +/*Mesa internal*/ +extern DECLEXPORT(int) VBOXGLXTAG(glXQueryContextInfoEXT)(Display *dpy, GLXContext ctx); +extern DECLEXPORT(void *) VBOXGLXTAG(glXAllocateMemoryMESA)(Display *dpy, int scrn, + size_t size, float readFreq, + float writeFreq, float priority); +extern DECLEXPORT(GLuint) VBOXGLXTAG(glXGetMemoryOffsetMESA)(Display *dpy, int scrn, const void *pointer ); +extern DECLEXPORT(GLXPixmap) VBOXGLXTAG(glXCreateGLXPixmapMESA)(Display *dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap); +#endif + +/*Common glX functions*/ +extern DECLEXPORT(void) VBOXGLXTAG(glXCopyContext)( Display *dpy, GLXContext src, GLXContext dst, +#if defined(SunOS) +unsigned long mask); +#else +unsigned long mask); +#endif +extern DECLEXPORT(void) VBOXGLXTAG(glXUseXFont)(Font font, int first, int count, int listBase); +extern DECLEXPORT(CR_GLXFuncPtr) VBOXGLXTAG(glXGetProcAddress)(const GLubyte *name); +extern DECLEXPORT(Bool) VBOXGLXTAG(glXQueryExtension)(Display *dpy, int *errorBase, int *eventBase); +extern DECLEXPORT(Bool) VBOXGLXTAG(glXIsDirect)(Display *dpy, GLXContext ctx); +extern DECLEXPORT(GLXPixmap) VBOXGLXTAG(glXCreateGLXPixmap)(Display *dpy, XVisualInfo *vis, Pixmap pixmap); +extern DECLEXPORT(void) VBOXGLXTAG(glXSwapBuffers)(Display *dpy, GLXDrawable drawable); +extern DECLEXPORT(GLXDrawable) VBOXGLXTAG(glXGetCurrentDrawable)(void); +extern DECLEXPORT(void) VBOXGLXTAG(glXWaitGL)(void); +extern DECLEXPORT(Display *) VBOXGLXTAG(glXGetCurrentDisplay)(void); +extern DECLEXPORT(const char *) VBOXGLXTAG(glXQueryServerString)(Display *dpy, int screen, int name); +extern DECLEXPORT(GLXContext) VBOXGLXTAG(glXCreateContext)(Display *dpy, XVisualInfo *vis, GLXContext share, Bool direct); +extern DECLEXPORT(int) VBOXGLXTAG(glXGetConfig)(Display *dpy, XVisualInfo *vis, int attrib, int *value); +extern DECLEXPORT(void) VBOXGLXTAG(glXWaitX)(void); +extern DECLEXPORT(GLXContext) VBOXGLXTAG(glXGetCurrentContext)(void); +extern DECLEXPORT(const char *) VBOXGLXTAG(glXGetClientString)(Display *dpy, int name); +extern DECLEXPORT(Bool) VBOXGLXTAG(glXMakeCurrent)(Display *dpy, GLXDrawable drawable, GLXContext ctx); +extern DECLEXPORT(void) VBOXGLXTAG(glXDestroyContext)(Display *dpy, GLXContext ctx); +extern DECLEXPORT(CR_GLXFuncPtr) VBOXGLXTAG(glXGetProcAddressARB)(const GLubyte *name); +extern DECLEXPORT(void) VBOXGLXTAG(glXDestroyGLXPixmap)(Display *dpy, GLXPixmap pix); +extern DECLEXPORT(Bool) VBOXGLXTAG(glXQueryVersion)(Display *dpy, int *major, int *minor); +extern DECLEXPORT(XVisualInfo *) VBOXGLXTAG(glXChooseVisual)(Display *dpy, int screen, int *attribList); +extern DECLEXPORT(const char *) VBOXGLXTAG(glXQueryExtensionsString)(Display *dpy, int screen); + +/** + * Set this to 1 if you want to build stub functions for the + * GL_SGIX_pbuffer and GLX_SGIX_fbconfig extensions. + * This used to be disabled, due to "messy compilation issues", + * according to the earlier comment; but they're needed just + * to resolve symbols for OpenInventor applications, and I + * haven't found any reference to exactly what the "messy compilation + * issues" are, so I'm re-enabling the code by default. + */ +#define GLX_EXTRAS 1 + +#define GLX_SGIX_video_resize 1 + +/** + * Prototypes, in case they're not in glx.h or glxext.h + * Unfortunately, there's some inconsistency between the extension + * specs, and the SGI, NVIDIA, XFree86 and common glxext.h header + * files. + */ +#if defined(GLX_GLXEXT_VERSION) +/* match glxext.h, XFree86, Mesa */ +#define ATTRIB_TYPE const int +#else +#define ATTRIB_TYPE int +#endif + +#if GLX_EXTRAS +extern DECLEXPORT(GLXPbufferSGIX) VBOXGLXTAG(glXCreateGLXPbufferSGIX) +(Display *dpy, GLXFBConfigSGIX config, unsigned int width, unsigned int height, int *attrib_list); + +extern DECLEXPORT(int) VBOXGLXTAG(glXQueryGLXPbufferSGIX) +(Display *dpy, GLXPbuffer pbuf, int attribute, unsigned int *value); + +extern DECLEXPORT(GLXFBConfigSGIX *) VBOXGLXTAG(glXChooseFBConfigSGIX) +(Display *dpy, int screen, int *attrib_list, int *nelements); + +extern DECLEXPORT(void) VBOXGLXTAG(glXDestroyGLXPbufferSGIX)(Display *dpy, GLXPbuffer pbuf); +extern DECLEXPORT(void) VBOXGLXTAG(glXSelectEventSGIX)(Display *dpy, GLXDrawable drawable, unsigned long mask); +extern DECLEXPORT(void) VBOXGLXTAG(glXGetSelectedEventSGIX)(Display *dpy, GLXDrawable drawable, unsigned long *mask); + +extern DECLEXPORT(GLXFBConfigSGIX) VBOXGLXTAG(glXGetFBConfigFromVisualSGIX)(Display *dpy, XVisualInfo *vis); +extern DECLEXPORT(XVisualInfo *) VBOXGLXTAG(glXGetVisualFromFBConfigSGIX)(Display *dpy, GLXFBConfig config); +extern DECLEXPORT(GLXContext) VBOXGLXTAG(glXCreateContextWithConfigSGIX) +(Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); + +extern DECLEXPORT(GLXPixmap) VBOXGLXTAG(glXCreateGLXPixmapWithConfigSGIX)(Display *dpy, GLXFBConfig config, Pixmap pixmap); +extern DECLEXPORT(int) VBOXGLXTAG(glXGetFBConfigAttribSGIX)(Display *dpy, GLXFBConfig config, int attribute, int *value); + +/* + * GLX 1.3 functions + */ +extern DECLEXPORT(GLXFBConfig *) VBOXGLXTAG(glXChooseFBConfig)(Display *dpy, int screen, ATTRIB_TYPE *attrib_list, int *nelements); +extern DECLEXPORT(GLXPbuffer) VBOXGLXTAG(glXCreatePbuffer)(Display *dpy, GLXFBConfig config, ATTRIB_TYPE *attrib_list); +extern DECLEXPORT(GLXPixmap) VBOXGLXTAG(glXCreatePixmap)(Display *dpy, GLXFBConfig config, Pixmap pixmap, ATTRIB_TYPE *attrib_list); +extern DECLEXPORT(GLXWindow) VBOXGLXTAG(glXCreateWindow)(Display *dpy, GLXFBConfig config, Window win, ATTRIB_TYPE *attrib_list); +extern DECLEXPORT(GLXContext) VBOXGLXTAG(glXCreateNewContext) +(Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); + +extern DECLEXPORT(void) VBOXGLXTAG(glXDestroyPbuffer)(Display *dpy, GLXPbuffer pbuf); +extern DECLEXPORT(void) VBOXGLXTAG(glXDestroyPixmap)(Display *dpy, GLXPixmap pixmap); +extern DECLEXPORT(void) VBOXGLXTAG(glXDestroyWindow)(Display *dpy, GLXWindow win); +extern DECLEXPORT(GLXDrawable) VBOXGLXTAG(glXGetCurrentReadDrawable)(void); +extern DECLEXPORT(int) VBOXGLXTAG(glXGetFBConfigAttrib)(Display *dpy, GLXFBConfig config, int attribute, int *value); +extern DECLEXPORT(GLXFBConfig *) VBOXGLXTAG(glXGetFBConfigs)(Display *dpy, int screen, int *nelements); +extern DECLEXPORT(void) VBOXGLXTAG(glXGetSelectedEvent)(Display *dpy, GLXDrawable draw, unsigned long *event_mask); +extern DECLEXPORT(XVisualInfo *) VBOXGLXTAG(glXGetVisualFromFBConfig)(Display *dpy, GLXFBConfig config); +extern DECLEXPORT(Bool) VBOXGLXTAG(glXMakeContextCurrent)(Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx); +extern DECLEXPORT(int) VBOXGLXTAG(glXQueryContext)(Display *dpy, GLXContext ctx, int attribute, int *value); +extern DECLEXPORT(void) VBOXGLXTAG(glXQueryDrawable)(Display *dpy, GLXDrawable draw, int attribute, unsigned int *value); +extern DECLEXPORT(void) VBOXGLXTAG(glXSelectEvent)(Display *dpy, GLXDrawable draw, unsigned long event_mask); + +#ifdef CR_EXT_texture_from_pixmap +extern DECLEXPORT(void) VBOXGLXTAG(glXBindTexImageEXT)(Display *dpy, GLXDrawable draw, int buffer, const int *attrib_list); +extern DECLEXPORT(void) VBOXGLXTAG(glXReleaseTexImageEXT)(Display *dpy, GLXDrawable draw, int buffer); +#endif + +#endif /* GLX_EXTRAS */ + +#endif /* !GA_INCLUDED_SRC_common_crOpenGL_dri_glx_h */ diff --git a/src/VBox/Additions/common/crOpenGL/dri_util.c b/src/VBox/Additions/common/crOpenGL/dri_util.c new file mode 100644 index 00000000..5041e07f --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/dri_util.c @@ -0,0 +1,1117 @@ +/* $XFree86: xc/lib/GL/dri/dri_util.c,v 1.7 2003/04/28 17:01:25 dawes Exp $ */ +/** + * \file dri_util.c + * DRI utility functions. + * + * This module acts as glue between GLX and the actual hardware driver. A DRI + * driver doesn't really \e have to use any of this - it's optional. But, some + * useful stuff is done here that otherwise would have to be duplicated in most + * drivers. + * + * Basically, these utility functions take care of some of the dirty details of + * screen initialization, context creation, context binding, DRM setup, etc. + * + * These functions are compiled into each DRI driver so libGL.so knows nothing + * about them. + */ + + +#include <assert.h> +#include <stdarg.h> +#include <unistd.h> +#include <sys/mman.h> +#include <stdio.h> + +#ifndef MAP_FAILED +#define MAP_FAILED ((void *)-1) +#endif + +#include "imports.h" +#define None 0 + +#include "dri_util.h" +#include "drm_sarea.h" +#include "utils.h" + +#ifndef GLX_OML_sync_control +typedef GLboolean ( * PFNGLXGETMSCRATEOMLPROC) (__DRIdrawable *drawable, int32_t *numerator, int32_t *denominator); +#endif + +/** + * This is just a token extension used to signal that the driver + * supports setting a read drawable. + */ +const __DRIextension driReadDrawableExtension = { + __DRI_READ_DRAWABLE, __DRI_READ_DRAWABLE_VERSION +}; + +/** + * Print message to \c stderr if the \c LIBGL_DEBUG environment variable + * is set. + * + * Is called from the drivers. + * + * \param f \c printf like format string. + */ +void +__driUtilMessage(const char *f, ...) +{ + va_list args; + + if (getenv("LIBGL_DEBUG")) { + fprintf(stderr, "libGL error: \n"); + va_start(args, f); + vfprintf(stderr, f, args); + va_end(args); + fprintf(stderr, "\n"); + } +} + +GLint +driIntersectArea( drm_clip_rect_t rect1, drm_clip_rect_t rect2 ) +{ + if (rect2.x1 > rect1.x1) rect1.x1 = rect2.x1; + if (rect2.x2 < rect1.x2) rect1.x2 = rect2.x2; + if (rect2.y1 > rect1.y1) rect1.y1 = rect2.y1; + if (rect2.y2 < rect1.y2) rect1.y2 = rect2.y2; + + if (rect1.x1 > rect1.x2 || rect1.y1 > rect1.y2) return 0; + + return (rect1.x2 - rect1.x1) * (rect1.y2 - rect1.y1); +} + +/*****************************************************************/ +/** \name Context (un)binding functions */ +/*****************************************************************/ +/*@{*/ + +/** + * Unbind context. + * + * \param scrn the screen. + * \param gc context. + * + * \return \c GL_TRUE on success, or \c GL_FALSE on failure. + * + * \internal + * This function calls __DriverAPIRec::UnbindContext, and then decrements + * __DRIdrawablePrivateRec::refcount which must be non-zero for a successful + * return. + * + * While casting the opaque private pointers associated with the parameters + * into their respective real types it also assures they are not \c NULL. + */ +static int driUnbindContext(__DRIcontext *pcp) +{ + __DRIscreen *psp; + __DRIdrawable *pdp; + __DRIdrawable *prp; + + /* + ** Assume error checking is done properly in glXMakeCurrent before + ** calling driUnbindContext. + */ + + if (pcp == NULL) + return GL_FALSE; + + psp = pcp->driScreenPriv; + pdp = pcp->driDrawablePriv; + prp = pcp->driReadablePriv; + + /* Let driver unbind drawable from context */ + (*psp->DriverAPI.UnbindContext)(pcp); + + if (pdp->refcount == 0) { + /* ERROR!!! */ + return GL_FALSE; + } + + pdp->refcount--; + + if (prp != pdp) { + if (prp->refcount == 0) { + /* ERROR!!! */ + return GL_FALSE; + } + + prp->refcount--; + } + + + /* XXX this is disabled so that if we call SwapBuffers on an unbound + * window we can determine the last context bound to the window and + * use that context's lock. (BrianP, 2-Dec-2000) + */ +#if 0 + /* Unbind the drawable */ + pcp->driDrawablePriv = NULL; + pdp->driContextPriv = &psp->dummyContextPriv; +#endif + + return GL_TRUE; +} + + +/** + * This function takes both a read buffer and a draw buffer. This is needed + * for \c glXMakeCurrentReadSGI or GLX 1.3's \c glXMakeContextCurrent + * function. + */ +static int driBindContext(__DRIcontext *pcp, + __DRIdrawable *pdp, + __DRIdrawable *prp) +{ + __DRIscreenPrivate *psp; + + /* + ** Assume error checking is done properly in glXMakeCurrent before + ** calling driBindContext. + */ + + if (pcp == NULL || pdp == None || prp == None) + return GL_FALSE; + + /* Bind the drawable to the context */ + pcp->driDrawablePriv = pdp; + pcp->driReadablePriv = prp; + pdp->driContextPriv = pcp; + pdp->refcount++; + if ( pdp != prp ) { + prp->refcount++; + } + + /* + ** Now that we have a context associated with this drawable, we can + ** initialize the drawable information if has not been done before. + */ + + psp = pcp->driScreenPriv; + if (psp->dri2.enabled) { + __driParseEvents(pcp, pdp); + __driParseEvents(pcp, prp); + } else { + if (!pdp->pStamp || *pdp->pStamp != pdp->lastStamp) { + DRM_SPINLOCK(&psp->pSAREA->drawable_lock, psp->drawLockID); + __driUtilUpdateDrawableInfo(pdp); + DRM_SPINUNLOCK(&psp->pSAREA->drawable_lock, psp->drawLockID); + } + + if ((pdp != prp) && (!prp->pStamp || *prp->pStamp != prp->lastStamp)) { + DRM_SPINLOCK(&psp->pSAREA->drawable_lock, psp->drawLockID); + __driUtilUpdateDrawableInfo(prp); + DRM_SPINUNLOCK(&psp->pSAREA->drawable_lock, psp->drawLockID); + } + } + + /* Call device-specific MakeCurrent */ + (*psp->DriverAPI.MakeCurrent)(pcp, pdp, prp); + + return GL_TRUE; +} + +/*@}*/ + + +/*****************************************************************/ +/** \name Drawable handling functions */ +/*****************************************************************/ +/*@{*/ + +/** + * Update private drawable information. + * + * \param pdp pointer to the private drawable information to update. + * + * This function basically updates the __DRIdrawablePrivate struct's + * cliprect information by calling \c __DRIinterfaceMethods::getDrawableInfo. + * This is usually called by the DRI_VALIDATE_DRAWABLE_INFO macro which + * compares the __DRIdrwablePrivate pStamp and lastStamp values. If + * the values are different that means we have to update the clipping + * info. + */ +void +__driUtilUpdateDrawableInfo(__DRIdrawablePrivate *pdp) +{ + __DRIscreenPrivate *psp = pdp->driScreenPriv; + __DRIcontextPrivate *pcp = pdp->driContextPriv; + + if (!pcp + || ((pdp != pcp->driDrawablePriv) && (pdp != pcp->driReadablePriv))) { + /* ERROR!!! + * ...but we must ignore it. There can be many contexts bound to a + * drawable. + */ + } + + if (pdp->pClipRects) { + _mesa_free(pdp->pClipRects); + pdp->pClipRects = NULL; + } + + if (pdp->pBackClipRects) { + _mesa_free(pdp->pBackClipRects); + pdp->pBackClipRects = NULL; + } + + DRM_SPINUNLOCK(&psp->pSAREA->drawable_lock, psp->drawLockID); + + if (! (*psp->getDrawableInfo->getDrawableInfo)(pdp, + &pdp->index, &pdp->lastStamp, + &pdp->x, &pdp->y, &pdp->w, &pdp->h, + &pdp->numClipRects, &pdp->pClipRects, + &pdp->backX, + &pdp->backY, + &pdp->numBackClipRects, + &pdp->pBackClipRects, + pdp->loaderPrivate)) { + /* Error -- eg the window may have been destroyed. Keep going + * with no cliprects. + */ + pdp->pStamp = &pdp->lastStamp; /* prevent endless loop */ + pdp->numClipRects = 0; + pdp->pClipRects = NULL; + pdp->numBackClipRects = 0; + pdp->pBackClipRects = NULL; + } + else + pdp->pStamp = &(psp->pSAREA->drawableTable[pdp->index].stamp); + + DRM_SPINLOCK(&psp->pSAREA->drawable_lock, psp->drawLockID); +} + + +int +__driParseEvents(__DRIcontextPrivate *pcp, __DRIdrawablePrivate *pdp) +{ + __DRIscreenPrivate *psp = pdp->driScreenPriv; + __DRIDrawableConfigEvent *dc, *last_dc; + __DRIBufferAttachEvent *ba, *last_ba; + unsigned int tail, mask, *p, end, total, size, changed; + unsigned char *data; + size_t rect_size; + + /* Check for wraparound. */ + if (pcp && psp->dri2.buffer->prealloc - pdp->dri2.tail > psp->dri2.buffer->size) { + /* If prealloc overlaps into what we just parsed, the + * server overwrote it and we have to reset our tail + * pointer. */ + DRM_UNLOCK(psp->fd, psp->lock, pcp->hHWContext); + (*psp->dri2.loader->reemitDrawableInfo)(pdp, &pdp->dri2.tail, + pdp->loaderPrivate); + DRM_LIGHT_LOCK(psp->fd, psp->lock, pcp->hHWContext); + } + + total = psp->dri2.buffer->head - pdp->dri2.tail; + mask = psp->dri2.buffer->size - 1; + end = psp->dri2.buffer->head; + data = psp->dri2.buffer->data; + + changed = 0; + last_dc = NULL; + last_ba = NULL; + + for (tail = pdp->dri2.tail; tail != end; tail += size) { + p = (unsigned int *) (data + (tail & mask)); + size = DRI2_EVENT_SIZE(*p); + if (size > total || (tail & mask) + size > psp->dri2.buffer->size) { + /* illegal data, bail out. */ + fprintf(stderr, "illegal event size\n"); + break; + } + + switch (DRI2_EVENT_TYPE(*p)) { + case DRI2_EVENT_DRAWABLE_CONFIG: + dc = (__DRIDrawableConfigEvent *) p; + if (dc->drawable == pdp->dri2.drawable_id) + last_dc = dc; + break; + + case DRI2_EVENT_BUFFER_ATTACH: + ba = (__DRIBufferAttachEvent *) p; + if (ba->drawable == pdp->dri2.drawable_id && + ba->buffer.attachment == DRI_DRAWABLE_BUFFER_FRONT_LEFT) + last_ba = ba; + break; + } + } + + if (last_dc) { + if (pdp->w != last_dc->width || pdp->h != last_dc->height) + changed = 1; + + pdp->x = last_dc->x; + pdp->y = last_dc->y; + pdp->w = last_dc->width; + pdp->h = last_dc->height; + + pdp->backX = 0; + pdp->backY = 0; + pdp->numBackClipRects = 1; + pdp->pBackClipRects[0].x1 = 0; + pdp->pBackClipRects[0].y1 = 0; + pdp->pBackClipRects[0].x2 = pdp->w; + pdp->pBackClipRects[0].y2 = pdp->h; + + pdp->numClipRects = last_dc->num_rects; + _mesa_free(pdp->pClipRects); + rect_size = last_dc->num_rects * sizeof last_dc->rects[0]; + pdp->pClipRects = _mesa_malloc(rect_size); + memcpy(pdp->pClipRects, last_dc->rects, rect_size); + } + + /* We only care about the most recent drawable config. */ + if (last_dc && changed) + (*psp->DriverAPI.HandleDrawableConfig)(pdp, pcp, last_dc); + + /* Front buffer attachments are special, they typically mean that + * we're rendering to a redirected window (or a child window of a + * redirected window) and that it got resized. Resizing the root + * window on randr events is a special case of this. Other causes + * may be a window transitioning between redirected and + * non-redirected, or a window getting reparented between parents + * with different window pixmaps (eg two redirected windows). + * These events are special in that the X server allocates the + * buffer and that the buffer may be shared by other child + * windows. When our window share the window pixmap with its + * parent, drawable config events doesn't affect the front buffer. + * We only care about the last such event in the buffer; in fact, + * older events will refer to invalid buffer objects.*/ + if (last_ba) + (*psp->DriverAPI.HandleBufferAttach)(pdp, pcp, last_ba); + + /* If there was a drawable config event in the buffer and it + * changed the size of the window, all buffer auxiliary buffer + * attachments prior to that are invalid (as opposed to the front + * buffer case discussed above). In that case we can start + * looking for buffer attachment after the last drawable config + * event. If there is no drawable config event in this batch of + * events, we have to assume that the last batch might have had + * one and process all buffer attach events.*/ + if (last_dc && changed) + tail = (unsigned char *) last_dc - data; + else + tail = pdp->dri2.tail; + + for ( ; tail != end; tail += size) { + ba = (__DRIBufferAttachEvent *) (data + (tail & mask)); + size = DRI2_EVENT_SIZE(ba->event_header); + + if (DRI2_EVENT_TYPE(ba->event_header) != DRI2_EVENT_BUFFER_ATTACH) + continue; + if (ba->drawable != pdp->dri2.drawable_id) + continue; + if (last_ba == ba) + continue; + + (*psp->DriverAPI.HandleBufferAttach)(pdp, pcp, ba); + changed = 1; + } + + pdp->dri2.tail = tail; + + return changed || last_ba; +} + +/*@}*/ + +/*****************************************************************/ +/** \name GLX callbacks */ +/*****************************************************************/ +/*@{*/ + +static void driReportDamage(__DRIdrawable *pdp, + struct drm_clip_rect *pClipRects, int numClipRects) +{ + __DRIscreen *psp = pdp->driScreenPriv; + + /* Check that we actually have the new damage report method */ + if (psp->dri2.enabled) { + (*psp->dri2.loader->postDamage)(pdp, + pClipRects, + numClipRects, + pdp->loaderPrivate); + } else if (psp->damage) { + /* Report the damage. Currently, all our drivers draw + * directly to the front buffer, so we report the damage there + * rather than to the backing storein (if any). + */ + (*psp->damage->reportDamage)(pdp, + pdp->x, pdp->y, + pClipRects, numClipRects, + GL_TRUE, pdp->loaderPrivate); + } +} + + +/** + * Swap buffers. + * + * \param drawablePrivate opaque pointer to the per-drawable private info. + * + * \internal + * This function calls __DRIdrawablePrivate::swapBuffers. + * + * Is called directly from glXSwapBuffers(). + */ +static void driSwapBuffers(__DRIdrawable *dPriv) +{ + __DRIscreen *psp = dPriv->driScreenPriv; + + if (!dPriv->numClipRects) + return; + + if (psp->dri2.enabled) + __driParseEvents(NULL, dPriv); + + psp->DriverAPI.SwapBuffers(dPriv); + + driReportDamage(dPriv, dPriv->pClipRects, dPriv->numClipRects); +} + +static int driDrawableGetMSC( __DRIscreen *sPriv, __DRIdrawable *dPriv, + int64_t *msc ) +{ + return sPriv->DriverAPI.GetDrawableMSC(sPriv, dPriv, msc); +} + + +static int driWaitForMSC(__DRIdrawable *dPriv, int64_t target_msc, + int64_t divisor, int64_t remainder, + int64_t * msc, int64_t * sbc) +{ + __DRIswapInfo sInfo; + int status; + + status = dPriv->driScreenPriv->DriverAPI.WaitForMSC( dPriv, target_msc, + divisor, remainder, + msc ); + + /* GetSwapInfo() may not be provided by the driver if GLX_SGI_video_sync + * is supported but GLX_OML_sync_control is not. Therefore, don't return + * an error value if GetSwapInfo() is not implemented. + */ + if ( status == 0 + && dPriv->driScreenPriv->DriverAPI.GetSwapInfo ) { + status = dPriv->driScreenPriv->DriverAPI.GetSwapInfo( dPriv, & sInfo ); + *sbc = sInfo.swap_count; + } + + return status; +} + + +const __DRImediaStreamCounterExtension driMediaStreamCounterExtension = { + { __DRI_MEDIA_STREAM_COUNTER, __DRI_MEDIA_STREAM_COUNTER_VERSION }, + driWaitForMSC, + driDrawableGetMSC, +}; + + +static void driCopySubBuffer(__DRIdrawable *dPriv, + int x, int y, int w, int h) +{ + drm_clip_rect_t rect; + + rect.x1 = x; + rect.y1 = dPriv->h - y - h; + rect.x2 = x + w; + rect.y2 = rect.y1 + h; + driReportDamage(dPriv, &rect, 1); + + dPriv->driScreenPriv->DriverAPI.CopySubBuffer(dPriv, x, y, w, h); +} + +const __DRIcopySubBufferExtension driCopySubBufferExtension = { + { __DRI_COPY_SUB_BUFFER, __DRI_COPY_SUB_BUFFER_VERSION }, + driCopySubBuffer +}; + +static void driSetSwapInterval(__DRIdrawable *dPriv, unsigned int interval) +{ + dPriv->swap_interval = interval; +} + +static unsigned int driGetSwapInterval(__DRIdrawable *dPriv) +{ + return dPriv->swap_interval; +} + +const __DRIswapControlExtension driSwapControlExtension = { + { __DRI_SWAP_CONTROL, __DRI_SWAP_CONTROL_VERSION }, + driSetSwapInterval, + driGetSwapInterval +}; + + +/** + * This is called via __DRIscreenRec's createNewDrawable pointer. + */ +static __DRIdrawable * +driCreateNewDrawable(__DRIscreen *psp, const __DRIconfig *config, + drm_drawable_t hwDrawable, int renderType, + const int *attrs, void *data) +{ + __DRIdrawable *pdp; + + /* Since pbuffers are not yet supported, no drawable attributes are + * supported either. + */ + (void) attrs; + + pdp = _mesa_malloc(sizeof *pdp); + if (!pdp) { + return NULL; + } + + pdp->loaderPrivate = data; + pdp->hHWDrawable = hwDrawable; + pdp->refcount = 0; + pdp->pStamp = NULL; + pdp->lastStamp = 0; + pdp->index = 0; + pdp->x = 0; + pdp->y = 0; + pdp->w = 0; + pdp->h = 0; + pdp->numClipRects = 0; + pdp->numBackClipRects = 0; + pdp->pClipRects = NULL; + pdp->pBackClipRects = NULL; + pdp->vblSeq = 0; + pdp->vblFlags = 0; + + pdp->driScreenPriv = psp; + pdp->driContextPriv = &psp->dummyContextPriv; + + if (!(*psp->DriverAPI.CreateBuffer)(psp, pdp, &config->modes, + renderType == GLX_PIXMAP_BIT)) { + _mesa_free(pdp); + return NULL; + } + + pdp->msc_base = 0; + + /* This special default value is replaced with the configured + * default value when the drawable is first bound to a direct + * rendering context. + */ + pdp->swap_interval = (unsigned)-1; + + return pdp; +} + + +static __DRIdrawable * +dri2CreateNewDrawable(__DRIscreen *screen, const __DRIconfig *config, + unsigned int drawable_id, unsigned int head, void *data) +{ + __DRIdrawable *pdraw; + + pdraw = driCreateNewDrawable(screen, config, 0, 0, NULL, data); + if (!pdraw) + return NULL; + + pdraw->dri2.drawable_id = drawable_id; + pdraw->dri2.tail = head; + pdraw->pBackClipRects = _mesa_malloc(sizeof *pdraw->pBackClipRects); + + return pdraw; +} + + +static void +driDestroyDrawable(__DRIdrawable *pdp) +{ + __DRIscreenPrivate *psp; + + if (pdp) { + psp = pdp->driScreenPriv; + (*psp->DriverAPI.DestroyBuffer)(pdp); + if (pdp->pClipRects) { + _mesa_free(pdp->pClipRects); + pdp->pClipRects = NULL; + } + if (pdp->pBackClipRects) { + _mesa_free(pdp->pBackClipRects); + pdp->pBackClipRects = NULL; + } + _mesa_free(pdp); + } +} + +/*@}*/ + + +/*****************************************************************/ +/** \name Context handling functions */ +/*****************************************************************/ +/*@{*/ + +/** + * Destroy the per-context private information. + * + * \internal + * This function calls __DriverAPIRec::DestroyContext on \p contextPrivate, calls + * drmDestroyContext(), and finally frees \p contextPrivate. + */ +static void +driDestroyContext(__DRIcontext *pcp) +{ + if (pcp) { + (*pcp->driScreenPriv->DriverAPI.DestroyContext)(pcp); + _mesa_free(pcp); + } +} + + +/** + * Create the per-drawable private driver information. + * + * \param render_type Type of rendering target. \c GLX_RGBA is the only + * type likely to ever be supported for direct-rendering. + * \param shared Context with which to share textures, etc. or NULL + * + * \returns An opaque pointer to the per-context private information on + * success, or \c NULL on failure. + * + * \internal + * This function allocates and fills a __DRIcontextPrivateRec structure. It + * performs some device independent initialization and passes all the + * relevant information to __DriverAPIRec::CreateContext to create the + * context. + * + */ +static __DRIcontext * +driCreateNewContext(__DRIscreen *psp, const __DRIconfig *config, + int render_type, __DRIcontext *shared, + drm_context_t hwContext, void *data) +{ + __DRIcontext *pcp; + void * const shareCtx = (shared != NULL) ? shared->driverPrivate : NULL; + + pcp = _mesa_malloc(sizeof *pcp); + if (!pcp) + return NULL; + + pcp->driScreenPriv = psp; + pcp->driDrawablePriv = NULL; + + /* When the first context is created for a screen, initialize a "dummy" + * context. + */ + + if (!psp->dri2.enabled && !psp->dummyContextPriv.driScreenPriv) { + psp->dummyContextPriv.hHWContext = psp->pSAREA->dummy_context; + psp->dummyContextPriv.driScreenPriv = psp; + psp->dummyContextPriv.driDrawablePriv = NULL; + psp->dummyContextPriv.driverPrivate = NULL; + /* No other fields should be used! */ + } + + pcp->hHWContext = hwContext; + + if ( !(*psp->DriverAPI.CreateContext)(&config->modes, pcp, shareCtx) ) { + _mesa_free(pcp); + return NULL; + } + + return pcp; +} + + +static __DRIcontext * +dri2CreateNewContext(__DRIscreen *screen, const __DRIconfig *config, + __DRIcontext *shared, void *data) +{ + drm_context_t hwContext; + DRM_CAS_RESULT(ret); + + /* DRI2 doesn't use kernel with context IDs, we just need an ID that's + * different from the kernel context ID to make drmLock() happy. */ + + do { + hwContext = screen->dri2.lock->next_id; + DRM_CAS(&screen->dri2.lock->next_id, hwContext, hwContext + 1, ret); + } while (ret); + + return driCreateNewContext(screen, config, 0, shared, hwContext, data); +} + + +static int +driCopyContext(__DRIcontext *dest, __DRIcontext *src, unsigned long mask) +{ + return GL_FALSE; +} + +/*@}*/ + + +/*****************************************************************/ +/** \name Screen handling functions */ +/*****************************************************************/ +/*@{*/ + +/** + * Destroy the per-screen private information. + * + * \internal + * This function calls __DriverAPIRec::DestroyScreen on \p screenPrivate, calls + * drmClose(), and finally frees \p screenPrivate. + */ +static void driDestroyScreen(__DRIscreen *psp) +{ + if (psp) { + /* No interaction with the X-server is possible at this point. This + * routine is called after XCloseDisplay, so there is no protocol + * stream open to the X-server anymore. + */ + + if (psp->DriverAPI.DestroyScreen) + (*psp->DriverAPI.DestroyScreen)(psp); + + if (psp->dri2.enabled) { +#ifdef TTM_API + drmBOUnmap(psp->fd, &psp->dri2.sareaBO); + drmBOUnreference(psp->fd, &psp->dri2.sareaBO); +#endif + } else { + (void)drmUnmap((drmAddress)psp->pSAREA, SAREA_MAX); + (void)drmUnmap((drmAddress)psp->pFB, psp->fbSize); + (void)drmCloseOnce(psp->fd); + } + + _mesa_free(psp); + } +} + +static void +setupLoaderExtensions(__DRIscreen *psp, + const __DRIextension **extensions) +{ + int i; + + for (i = 0; extensions[i]; i++) { + if (strcmp(extensions[i]->name, __DRI_GET_DRAWABLE_INFO) == 0) + psp->getDrawableInfo = (__DRIgetDrawableInfoExtension *) extensions[i]; + if (strcmp(extensions[i]->name, __DRI_DAMAGE) == 0) + psp->damage = (__DRIdamageExtension *) extensions[i]; + if (strcmp(extensions[i]->name, __DRI_SYSTEM_TIME) == 0) + psp->systemTime = (__DRIsystemTimeExtension *) extensions[i]; + if (strcmp(extensions[i]->name, __DRI_LOADER) == 0) + psp->dri2.loader = (__DRIloaderExtension *) extensions[i]; + } +} + +/** + * This is the bootstrap function for the driver. libGL supplies all of the + * requisite information about the system, and the driver initializes itself. + * This routine also fills in the linked list pointed to by \c driver_modes + * with the \c __GLcontextModes that the driver can support for windows or + * pbuffers. + * + * For legacy DRI. + * + * \param scrn Index of the screen + * \param ddx_version Version of the 2D DDX. This may not be meaningful for + * all drivers. + * \param dri_version Version of the "server-side" DRI. + * \param drm_version Version of the kernel DRM. + * \param frame_buffer Data describing the location and layout of the + * framebuffer. + * \param pSAREA Pointer to the SAREA. + * \param fd Device handle for the DRM. + * \param extensions ?? + * \param driver_modes Returns modes supported by the driver + * \param loaderPrivate ?? + * + * \note There is no need to check the minimum API version in this + * function. Since the name of this function is versioned, it is + * impossible for a loader that is too old to even load this driver. + */ +static __DRIscreen * +driCreateNewScreen(int scrn, + const __DRIversion *ddx_version, + const __DRIversion *dri_version, + const __DRIversion *drm_version, + const __DRIframebuffer *frame_buffer, + drmAddress pSAREA, int fd, + const __DRIextension **extensions, + const __DRIconfig ***driver_modes, + void *loaderPrivate) +{ + static const __DRIextension *emptyExtensionList[] = { NULL }; + __DRIscreen *psp; + + psp = _mesa_malloc(sizeof *psp); + if (!psp) + return NULL; + + setupLoaderExtensions(psp, extensions); + + /* + ** NOT_DONE: This is used by the X server to detect when the client + ** has died while holding the drawable lock. The client sets the + ** drawable lock to this value. + */ + psp->drawLockID = 1; + + psp->drm_version = *drm_version; + psp->ddx_version = *ddx_version; + psp->dri_version = *dri_version; + + psp->pSAREA = pSAREA; + psp->lock = (drmLock *) &psp->pSAREA->lock; + + psp->pFB = frame_buffer->base; + psp->fbSize = frame_buffer->size; + psp->fbStride = frame_buffer->stride; + psp->fbWidth = frame_buffer->width; + psp->fbHeight = frame_buffer->height; + psp->devPrivSize = frame_buffer->dev_priv_size; + psp->pDevPriv = frame_buffer->dev_priv; + psp->fbBPP = psp->fbStride * 8 / frame_buffer->width; + + psp->extensions = emptyExtensionList; + psp->fd = fd; + psp->myNum = scrn; + psp->dri2.enabled = GL_FALSE; + + /* + ** Do not init dummy context here; actual initialization will be + ** done when the first DRI context is created. Init screen priv ptr + ** to NULL to let CreateContext routine that it needs to be inited. + */ + psp->dummyContextPriv.driScreenPriv = NULL; + + psp->DriverAPI = driDriverAPI; + + *driver_modes = driDriverAPI.InitScreen(psp); + if (*driver_modes == NULL) { + _mesa_free(psp); + return NULL; + } + + return psp; +} + + +/** + * DRI2 + */ +static __DRIscreen * +dri2CreateNewScreen(int scrn, int fd, unsigned int sarea_handle, + const __DRIextension **extensions, + const __DRIconfig ***driver_configs, void *data) +{ +#ifdef TTM_API + static const __DRIextension *emptyExtensionList[] = { NULL }; + __DRIscreen *psp; + unsigned int *p; + drmVersionPtr version; + + if (driDriverAPI.InitScreen2 == NULL) + return NULL; + + psp = _mesa_malloc(sizeof(*psp)); + if (!psp) + return NULL; + + setupLoaderExtensions(psp, extensions); + + version = drmGetVersion(fd); + if (version) { + psp->drm_version.major = version->version_major; + psp->drm_version.minor = version->version_minor; + psp->drm_version.patch = version->version_patchlevel; + drmFreeVersion(version); + } + + psp->extensions = emptyExtensionList; + psp->fd = fd; + psp->myNum = scrn; + psp->dri2.enabled = GL_TRUE; + + if (drmBOReference(psp->fd, sarea_handle, &psp->dri2.sareaBO)) { + fprintf(stderr, "Failed to reference DRI2 sarea BO\n"); + _mesa_free(psp); + return NULL; + } + + if (drmBOMap(psp->fd, &psp->dri2.sareaBO, + DRM_BO_FLAG_READ | DRM_BO_FLAG_WRITE, 0, &psp->dri2.sarea)) { + drmBOUnreference(psp->fd, &psp->dri2.sareaBO); + _mesa_free(psp); + return NULL; + } + + p = psp->dri2.sarea; + while (DRI2_SAREA_BLOCK_TYPE(*p)) { + switch (DRI2_SAREA_BLOCK_TYPE(*p)) { + case DRI2_SAREA_BLOCK_LOCK: + psp->dri2.lock = (__DRILock *) p; + break; + case DRI2_SAREA_BLOCK_EVENT_BUFFER: + psp->dri2.buffer = (__DRIEventBuffer *) p; + break; + } + p = DRI2_SAREA_BLOCK_NEXT(p); + } + + psp->lock = (drmLock *) &psp->dri2.lock->lock; + + psp->DriverAPI = driDriverAPI; + *driver_configs = driDriverAPI.InitScreen2(psp); + if (*driver_configs == NULL) { + drmBOUnmap(psp->fd, &psp->dri2.sareaBO); + drmBOUnreference(psp->fd, &psp->dri2.sareaBO); + _mesa_free(psp); + return NULL; + } + + psp->DriverAPI = driDriverAPI; + + return psp; +#else + return NULL; +#endif +} + +static const __DRIextension **driGetExtensions(__DRIscreen *psp) +{ + return psp->extensions; +} + +/** Legacy DRI interface */ +const __DRIlegacyExtension driLegacyExtension = { + { __DRI_LEGACY, __DRI_LEGACY_VERSION }, + driCreateNewScreen, + driCreateNewDrawable, + driCreateNewContext +}; + +/** DRI2 interface */ +const __DRIcoreExtension driCoreExtension = { + { __DRI_CORE, __DRI_CORE_VERSION }, + dri2CreateNewScreen, + driDestroyScreen, + driGetExtensions, + driGetConfigAttrib, + driIndexConfigAttrib, + dri2CreateNewDrawable, + driDestroyDrawable, + driSwapBuffers, + dri2CreateNewContext, + driCopyContext, + driDestroyContext, + driBindContext, + driUnbindContext +}; + +/* This is the table of extensions that the loader will dlsym() for. */ +PUBLIC const __DRIextension *__driDriverExtensions[] = { + &driCoreExtension.base, + &driLegacyExtension.base, + NULL +}; + +static int +driFrameTracking(__DRIdrawable *drawable, GLboolean enable) +{ + return GLX_BAD_CONTEXT; +} + +static int +driQueryFrameTracking(__DRIdrawable *dpriv, + int64_t * sbc, int64_t * missedFrames, + float * lastMissedUsage, float * usage) +{ + __DRIswapInfo sInfo; + int status; + int64_t ust; + __DRIscreenPrivate *psp = dpriv->driScreenPriv; + + status = dpriv->driScreenPriv->DriverAPI.GetSwapInfo( dpriv, & sInfo ); + if ( status == 0 ) { + *sbc = sInfo.swap_count; + *missedFrames = sInfo.swap_missed_count; + *lastMissedUsage = sInfo.swap_missed_usage; + + (*psp->systemTime->getUST)( & ust ); + *usage = driCalculateSwapUsage( dpriv, sInfo.swap_ust, ust ); + } + + return status; +} + +const __DRIframeTrackingExtension driFrameTrackingExtension = { + { __DRI_FRAME_TRACKING, __DRI_FRAME_TRACKING_VERSION }, + driFrameTracking, + driQueryFrameTracking +}; + +/** + * Calculate amount of swap interval used between GLX buffer swaps. + * + * The usage value, on the range [0,max], is the fraction of total swap + * interval time used between GLX buffer swaps is calculated. + * + * \f$p = t_d / (i * t_r)\f$ + * + * Where \f$t_d\f$ is the time since the last GLX buffer swap, \f$i\f$ is the + * swap interval (as set by \c glXSwapIntervalSGI), and \f$t_r\f$ time + * required for a single vertical refresh period (as returned by \c + * glXGetMscRateOML). + * + * See the documentation for the GLX_MESA_swap_frame_usage extension for more + * details. + * + * \param dPriv Pointer to the private drawable structure. + * \return If less than a single swap interval time period was required + * between GLX buffer swaps, a number greater than 0 and less than + * 1.0 is returned. If exactly one swap interval time period is + * required, 1.0 is returned, and if more than one is required then + * a number greater than 1.0 will be returned. + * + * \sa glXSwapIntervalSGI glXGetMscRateOML + * + * \todo Instead of caching the \c glXGetMscRateOML function pointer, would it + * be possible to cache the sync rate? + */ +float +driCalculateSwapUsage( __DRIdrawablePrivate *dPriv, int64_t last_swap_ust, + int64_t current_ust ) +{ + int32_t n; + int32_t d; + int interval; + float usage = 1.0; + __DRIscreenPrivate *psp = dPriv->driScreenPriv; + + if ( (*psp->systemTime->getMSCRate)(dPriv, &n, &d, dPriv->loaderPrivate) ) { + interval = (dPriv->swap_interval != 0) ? dPriv->swap_interval : 1; + + + /* We want to calculate + * (current_UST - last_swap_UST) / (interval * us_per_refresh). We get + * current_UST by calling __glXGetUST. last_swap_UST is stored in + * dPriv->swap_ust. interval has already been calculated. + * + * The only tricky part is us_per_refresh. us_per_refresh is + * 1000000 / MSC_rate. We know the MSC_rate is n / d. We can flip it + * around and say us_per_refresh = 1000000 * d / n. Since this goes in + * the denominator of the final calculation, we calculate + * (interval * 1000000 * d) and move n into the numerator. + */ + + usage = (current_ust - last_swap_ust); + usage *= n; + usage /= (interval * d); + usage /= 1000000.0; + } + + return usage; +} + +/*@}*/ diff --git a/src/VBox/Additions/common/crOpenGL/drirenderbuffer.c b/src/VBox/Additions/common/crOpenGL/drirenderbuffer.c new file mode 100644 index 00000000..d36af3e5 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/drirenderbuffer.c @@ -0,0 +1,215 @@ + +#include "mtypes.h" +#include "drirenderbuffer.h" +#include "framebuffer.h" +#include "renderbuffer.h" +#include "imports.h" + + +/** + * This will get called when a window (gl_framebuffer) is resized (probably + * via driUpdateFramebufferSize(), below). + * Just update width, height and internal format fields for now. + * There's usually no memory allocation above because the present + * DRI drivers use statically-allocated full-screen buffers. If that's not + * the case for a DRI driver, a different AllocStorage method should + * be used. + */ +static GLboolean +driRenderbufferStorage(GLcontext *ctx, struct gl_renderbuffer *rb, + GLenum internalFormat, GLuint width, GLuint height) +{ + rb->Width = width; + rb->Height = height; + rb->InternalFormat = internalFormat; + return GL_TRUE; +} + + +static void +driDeleteRenderbuffer(struct gl_renderbuffer *rb) +{ + /* don't free rb->Data Chances are it's a memory mapped region for + * the dri drivers. + */ + _mesa_free(rb); +} + + +/** + * Allocate a new driRenderbuffer object. + * Individual drivers are free to implement different versions of + * this function. + * + * At this time, this function can only be used for window-system + * renderbuffers, not user-created RBOs. + * + * \param format Either GL_RGBA, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, + * GL_DEPTH_COMPONENT32, or GL_STENCIL_INDEX8_EXT (for now). + * \param addr address in main memory of the buffer. Probably a memory + * mapped region. + * \param cpp chars or bytes per pixel + * \param offset start of renderbuffer with respect to start of framebuffer + * \param pitch pixels per row + */ +driRenderbuffer * +driNewRenderbuffer(GLenum format, GLvoid *addr, + GLint cpp, GLint offset, GLint pitch, + __DRIdrawablePrivate *dPriv) +{ + driRenderbuffer *drb; + + assert(format == GL_RGBA || + format == GL_RGB5 || + format == GL_RGBA8 || + format == GL_DEPTH_COMPONENT16 || + format == GL_DEPTH_COMPONENT24 || + format == GL_DEPTH_COMPONENT32 || + format == GL_STENCIL_INDEX8_EXT); + + assert(cpp > 0); + assert(pitch > 0); + + drb = _mesa_calloc(sizeof(driRenderbuffer)); + if (drb) { + const GLuint name = 0; + + _mesa_init_renderbuffer(&drb->Base, name); + + /* Make sure we're using a null-valued GetPointer routine */ + assert(drb->Base.GetPointer(NULL, &drb->Base, 0, 0) == NULL); + + drb->Base.InternalFormat = format; + + if (format == GL_RGBA || format == GL_RGB5 || format == GL_RGBA8) { + /* Color */ + drb->Base._BaseFormat = GL_RGBA; + drb->Base.DataType = GL_UNSIGNED_BYTE; + if (format == GL_RGB5) { + drb->Base.RedBits = 5; + drb->Base.GreenBits = 6; + drb->Base.BlueBits = 5; + } + else { + drb->Base.RedBits = + drb->Base.GreenBits = + drb->Base.BlueBits = + drb->Base.AlphaBits = 8; + } + } + else if (format == GL_DEPTH_COMPONENT16) { + /* Depth */ + drb->Base._BaseFormat = GL_DEPTH_COMPONENT; + /* we always Get/Put 32-bit Z values */ + drb->Base.DataType = GL_UNSIGNED_INT; + drb->Base.DepthBits = 16; + } + else if (format == GL_DEPTH_COMPONENT24) { + /* Depth */ + drb->Base._BaseFormat = GL_DEPTH_COMPONENT; + /* we always Get/Put 32-bit Z values */ + drb->Base.DataType = GL_UNSIGNED_INT; + drb->Base.DepthBits = 24; + } + else if (format == GL_DEPTH_COMPONENT32) { + /* Depth */ + drb->Base._BaseFormat = GL_DEPTH_COMPONENT; + /* we always Get/Put 32-bit Z values */ + drb->Base.DataType = GL_UNSIGNED_INT; + drb->Base.DepthBits = 32; + } + else { + /* Stencil */ + ASSERT(format == GL_STENCIL_INDEX8_EXT); + drb->Base._BaseFormat = GL_STENCIL_INDEX; + drb->Base.DataType = GL_UNSIGNED_BYTE; + drb->Base.StencilBits = 8; + } + + /* XXX if we were allocating a user-created renderbuffer, we'd have + * to fill in the Red/Green/Blue/.../Bits values too. + */ + + drb->Base.AllocStorage = driRenderbufferStorage; + drb->Base.Delete = driDeleteRenderbuffer; + + drb->Base.Data = addr; + + /* DRI renderbuffer-specific fields: */ + drb->dPriv = dPriv; + drb->offset = offset; + drb->pitch = pitch; + drb->cpp = cpp; + + /* may be changed if page flipping is active: */ + drb->flippedOffset = offset; + drb->flippedPitch = pitch; + drb->flippedData = addr; + } + return drb; +} + + +/** + * Update the front and back renderbuffers' flippedPitch/Offset/Data fields. + * If stereo, flip both the left and right pairs. + * This is used when we do double buffering via page flipping. + * \param fb the framebuffer we're page flipping + * \param flipped if true, set flipped values, else set non-flipped values + */ +void +driFlipRenderbuffers(struct gl_framebuffer *fb, GLboolean flipped) +{ + const GLuint count = fb->Visual.stereoMode ? 2 : 1; + GLuint lr; /* left or right */ + + /* we shouldn't really call this function if single-buffered, but + * play it safe. + */ + if (!fb->Visual.doubleBufferMode) + return; + + for (lr = 0; lr < count; lr++) { + GLuint frontBuf = (lr == 0) ? BUFFER_FRONT_LEFT : BUFFER_FRONT_RIGHT; + GLuint backBuf = (lr == 0) ? BUFFER_BACK_LEFT : BUFFER_BACK_RIGHT; + driRenderbuffer *front_drb + = (driRenderbuffer *) fb->Attachment[frontBuf].Renderbuffer; + driRenderbuffer *back_drb + = (driRenderbuffer *) fb->Attachment[backBuf].Renderbuffer; + + if (flipped) { + front_drb->flippedOffset = back_drb->offset; + front_drb->flippedPitch = back_drb->pitch; + front_drb->flippedData = back_drb->Base.Data; + back_drb->flippedOffset = front_drb->offset; + back_drb->flippedPitch = front_drb->pitch; + back_drb->flippedData = front_drb->Base.Data; + } + else { + front_drb->flippedOffset = front_drb->offset; + front_drb->flippedPitch = front_drb->pitch; + front_drb->flippedData = front_drb->Base.Data; + back_drb->flippedOffset = back_drb->offset; + back_drb->flippedPitch = back_drb->pitch; + back_drb->flippedData = back_drb->Base.Data; + } + } +} + + +/** + * Check that the gl_framebuffer associated with dPriv is the right size. + * Resize the gl_framebuffer if needed. + * It's expected that the dPriv->driverPrivate member points to a + * gl_framebuffer object. + */ +void +driUpdateFramebufferSize(GLcontext *ctx, const __DRIdrawablePrivate *dPriv) +{ + struct gl_framebuffer *fb = (struct gl_framebuffer *) dPriv->driverPrivate; + if (fb && (dPriv->w != fb->Width || dPriv->h != fb->Height)) { + ctx->Driver.ResizeBuffers(ctx, fb, dPriv->w, dPriv->h); + assert(fb->Width == dPriv->w); + assert(fb->Height == dPriv->h); + } +} diff --git a/src/VBox/Additions/common/crOpenGL/egl.c b/src/VBox/Additions/common/crOpenGL/egl.c new file mode 100644 index 00000000..3d8a636d --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/egl.c @@ -0,0 +1,967 @@ +/* $Id: egl.c $ */ + +/** @file + * VBox OpenGL EGL implentation. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cdefs.h> +#include <iprt/types.h> + +#include <EGL/egl.h> +#include <GL/glx.h> +#include <X11/Xlib.h> + +#include <dlfcn.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define EGL_ASSERT(expr) \ + if (!(expr)) { printf("Assertion failed: %s\n", #expr); exit(1); } + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +struct VBEGLTLS +{ + /** The last EGL error. */ + EGLint cErr; + /** The EGL API currently bound to this thread. */ + EGLenum enmAPI; + /** The current context. */ + EGLContext hCurrent; + /** The display bound to the current context. */ + EGLDisplay hCurrentDisplay; + /** The draw surface bound to the current context. */ + EGLSurface hCurrentDraw; + /** The read surface bound to the current context. */ + EGLSurface hCurrentRead; +}; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @note IDs returned for surfaces should always be lower than these constants. + */ +/** This is OR-ed with a surface ID to mark it as a window, as GLX needs to + * know. */ +#define VBEGL_WINDOW_SURFACE 0x20000000 +/** This is OR-ed with a surface ID to mark it as a pbuffer, as GLX needs to + * know. */ +#define VBEGL_PBUFFER_SURFACE 0x40000000 +/** This is OR-ed with a surface ID to mark it as a pixmap, as GLX needs to + * know. */ +#define VBEGL_PIXMAP_SURFACE 0x80000000 +#define VBEGL_ANY_SURFACE (VBEGL_WINDOW_SURFACE | VBEGL_PBUFFER_SURFACE | VBEGL_PIXMAP_SURFACE) + + +/********************************************************************************************************************************* +* Global variables * +*********************************************************************************************************************************/ + +static pthread_key_t g_tls; +static pthread_once_t g_tlsOnce = PTHREAD_ONCE_INIT; +static Display *g_pDefaultDisplay = NULL; +static pthread_once_t g_defaultDisplayOnce = PTHREAD_ONCE_INIT; + +static void tlsInitOnce(void) +{ + pthread_key_create(&g_tls, NULL); +} + +static struct VBEGLTLS *getTls(void) +{ + struct VBEGLTLS *pTls; + + pthread_once(&g_tlsOnce, tlsInitOnce); + pTls = (struct VBEGLTLS *)pthread_getspecific(g_tls); + if (RT_LIKELY(pTls)) + return pTls; + pTls = (struct VBEGLTLS *)malloc(sizeof(*pTls)); + if (!pTls) + return NULL; + pTls->cErr = EGL_SUCCESS; + pTls->enmAPI = EGL_NONE; + pTls->hCurrent = EGL_NO_CONTEXT; + pTls->hCurrentDisplay = EGL_NO_DISPLAY; + pTls->hCurrentDraw = EGL_NO_SURFACE; + pTls->hCurrentRead = EGL_NO_SURFACE; + if (pthread_setspecific(g_tls, pTls) == 0) + return pTls; + free(pTls); + return NULL; +} + +static void defaultDisplayInitOnce(void) +{ + g_pDefaultDisplay = XOpenDisplay(NULL); +} + +static EGLBoolean clearEGLError(void) +{ + struct VBEGLTLS *pTls = getTls(); + + if (!VALID_PTR(pTls)) + return EGL_FALSE; + pTls->cErr = EGL_SUCCESS; + return EGL_TRUE; +} + +static EGLBoolean setEGLError(EGLint cErr) +{ + struct VBEGLTLS *pTls = getTls(); + + if (pTls) + pTls->cErr = cErr; + return EGL_FALSE; +} + +static EGLBoolean testValidDisplay(EGLNativeDisplayType hDisplay) +{ + void *pSymbol = dlsym(NULL, "gbm_create_device"); + + if (hDisplay == EGL_DEFAULT_DISPLAY) + return EGL_TRUE; + if ((void *)hDisplay == NULL) + return EGL_FALSE; + /* This is the test that Mesa uses to see if this is a GBM "display". Not + * very pretty, but since no one can afford to break Mesa it should be + * safe. We need this to detect when the X server tries to load us. */ + if (pSymbol != NULL && *(void **)hDisplay == pSymbol) + return EGL_FALSE; + return EGL_TRUE; +} + +DECLEXPORT(EGLDisplay) eglGetDisplay(EGLNativeDisplayType hDisplay) +{ + Display *pDisplay; + + if (!testValidDisplay(hDisplay)) + return EGL_NO_DISPLAY; + if (!clearEGLError()) /* Set up our tls. */ + return EGL_NO_DISPLAY; + if (hDisplay != EGL_DEFAULT_DISPLAY) + pDisplay = hDisplay; + else + { + pthread_once(&g_defaultDisplayOnce, defaultDisplayInitOnce); + pDisplay = g_pDefaultDisplay; + } + if (pDisplay && !strcmp(glXGetClientString(pDisplay, GLX_VENDOR), "Chromium")) + return (EGLDisplay) pDisplay; + return EGL_NO_DISPLAY; +} + +DECLEXPORT(EGLint) eglGetError(void) +{ + struct VBEGLTLS *pTls = getTls(); + + if (pTls) + return pTls->cErr; + return EGL_NOT_INITIALIZED; +} + +DECLEXPORT(EGLBoolean) eglInitialize (EGLDisplay hDisplay, EGLint *pcMajor, EGLint *pcMinor) +{ + if (hDisplay == EGL_NO_DISPLAY) + return EGL_FALSE; + if (!VALID_PTR(hDisplay)) + return setEGLError(EGL_BAD_DISPLAY); + if (pcMajor) + *pcMajor = 1; + if (pcMinor) + *pcMinor = 4; + return clearEGLError(); +} + +/** @todo This function should terminate all allocated resources. */ +DECLEXPORT(EGLBoolean) eglTerminate(EGLDisplay hDisplay) +{ + if (!VALID_PTR(hDisplay)) + return EGL_FALSE; + return EGL_TRUE; +} + +DECLEXPORT(const char *) eglQueryString(EGLDisplay hDisplay, EGLint name) +{ + RT_NOREF(hDisplay); + switch (name) + { + case EGL_CLIENT_APIS: + return "OpenGL"; + case EGL_VENDOR: + return "Chromium"; + case EGL_VERSION: + return "1.4 Chromium"; + case EGL_EXTENSIONS: + return ""; + default: + return NULL; + } +} + +DECLEXPORT(EGLBoolean) eglGetConfigs (EGLDisplay hDisplay, EGLConfig *paConfigs, EGLint caConfigs, EGLint *pcaConfigs) +{ + Display *pDisplay = (Display *)hDisplay; + GLXFBConfig *paFBConfigs; + int caFBConfigs, i; + + if (!VALID_PTR(pDisplay)) + return setEGLError(EGL_NOT_INITIALIZED); + if (!VALID_PTR(pcaConfigs)) + return setEGLError(EGL_BAD_PARAMETER); + if (caConfigs > 0 && !VALID_PTR(paConfigs)) + return setEGLError(EGL_BAD_PARAMETER); + paFBConfigs = glXGetFBConfigs(pDisplay, DefaultScreen(pDisplay), &caFBConfigs); + if (!VALID_PTR(paFBConfigs)) + return setEGLError(EGL_BAD_PARAMETER); + if (caFBConfigs > caConfigs) + caFBConfigs = caConfigs; + *pcaConfigs = caFBConfigs; + for (i = 0; i < caFBConfigs; ++i) + paConfigs[i] = (EGLConfig)paFBConfigs[i]; + XFree(paFBConfigs); + return clearEGLError(); +} + +static int convertEGLAttribToGLX(EGLint a_EGLAttrib) +{ + switch (a_EGLAttrib) + { + case EGL_BUFFER_SIZE: + return GLX_BUFFER_SIZE; + case EGL_RED_SIZE: + return GLX_RED_SIZE; + case EGL_GREEN_SIZE: + return GLX_GREEN_SIZE; + case EGL_BLUE_SIZE: + return GLX_BLUE_SIZE; + case EGL_LUMINANCE_SIZE: + return GLX_RED_SIZE; + case EGL_ALPHA_SIZE: + return GLX_ALPHA_SIZE; + /* case EGL_ALPHA_MASK_SIZE: */ + /* case EGL_BIND_TO_TEXTURE_RGB: */ + /* case EGL_BIND_TO_TEXTURE_RGBA: */ + /* case EGL_COLOR_BUFFER_TYPE: */ + /* case EGL_CONFIG_CAVEAT: */ + case EGL_CONFIG_ID: + return GLX_FBCONFIG_ID; + /* case EGL_CONFORMANT: */ + case EGL_DEPTH_SIZE: + return GLX_DEPTH_SIZE; + case EGL_LEVEL: + return GLX_LEVEL; + case EGL_MAX_PBUFFER_WIDTH: + return GLX_MAX_PBUFFER_WIDTH; + case EGL_MAX_PBUFFER_HEIGHT: + return GLX_MAX_PBUFFER_HEIGHT; + case EGL_MAX_PBUFFER_PIXELS: + return GLX_MAX_PBUFFER_PIXELS; + /* case EGL_MATCH_NATIVE_PIXMAP: */ + /* case EGL_MAX_SWAP_INTERVAL: */ + /* case EGL_MIN_SWAP_INTERVAL: */ + case EGL_NATIVE_RENDERABLE: + return GLX_X_RENDERABLE; + case EGL_NATIVE_VISUAL_ID: + return GLX_VISUAL_ID; + /* case EGL_NATIVE_VISUAL_TYPE: */ + /* case EGL_RENDERABLE_TYPE: */ + case EGL_SAMPLE_BUFFERS: + return GLX_SAMPLE_BUFFERS; + case EGL_SAMPLES: + return GLX_SAMPLES; + case EGL_STENCIL_SIZE: + return GLX_STENCIL_SIZE; + /* case EGL_SURFACE_TYPE: */ + /* case EGL_TRANSPARENT_TYPE: */ + case EGL_TRANSPARENT_RED_VALUE: + return GLX_TRANSPARENT_RED_VALUE; + case EGL_TRANSPARENT_GREEN_VALUE: + return GLX_TRANSPARENT_GREEN_VALUE; + case EGL_TRANSPARENT_BLUE_VALUE: + return GLX_TRANSPARENT_BLUE_VALUE; + default: + return None; + } +} + +DECLEXPORT(EGLBoolean) eglChooseConfig (EGLDisplay hDisplay, const EGLint *paAttribs, EGLConfig *paConfigs, EGLint caConfigs, + EGLint *pcConfigs) +{ + Display *pDisplay = (Display *)hDisplay; + int aAttribList[256]; /* The list cannot be this long. */ + unsigned cAttribs = 0, i; + const EGLint *pAttrib, *pAttrib2; + EGLint cRenderableType = EGL_OPENGL_ES_BIT; + unsigned cConfigCaveat = GLX_DONT_CARE, cConformant = GLX_DONT_CARE; + GLXFBConfig *paFBConfigs; + int caFBConfigs; + + if (!VALID_PTR(hDisplay)) + return setEGLError(EGL_NOT_INITIALIZED); + if (!VALID_PTR(pcConfigs)) + return setEGLError(EGL_BAD_PARAMETER); + if (caConfigs > 0 && !VALID_PTR(paConfigs)) + return setEGLError(EGL_BAD_PARAMETER); + for (pAttrib = paAttribs; pAttrib != NULL && *pAttrib != EGL_NONE; pAttrib += 2) + { + bool fSkip = false; + int cGLXAttrib; + + /* Check for illegal values. */ + if ((*pAttrib == EGL_LEVEL || *pAttrib == EGL_MATCH_NATIVE_PIXMAP) && pAttrib[1] == EGL_DONT_CARE) + return setEGLError(EGL_BAD_ATTRIBUTE); + /* Check for values we can't handle. */ + if ( (*pAttrib == EGL_ALPHA_MASK_SIZE) + && pAttrib[1] != EGL_DONT_CARE && pAttrib[1] != 0) + return setEGLError(EGL_BAD_ACCESS); + /** @todo try creating a pixmap from a native one with the configurations returned. */ + if (*pAttrib == EGL_MATCH_NATIVE_PIXMAP) + return setEGLError(EGL_BAD_ACCESS); + if ( ( *pAttrib == EGL_MIN_SWAP_INTERVAL || *pAttrib == EGL_MAX_SWAP_INTERVAL + || *pAttrib == EGL_BIND_TO_TEXTURE_RGB || *pAttrib == EGL_BIND_TO_TEXTURE_RGBA) + && pAttrib[1] != EGL_DONT_CARE) + return setEGLError(EGL_BAD_ACCESS); + /* Ignore attributes which are repeated later. */ + for (pAttrib2 = pAttrib + 2; *pAttrib2 != EGL_NONE; pAttrib2 += 2) + if (*pAttrib2 == *pAttrib) + { + fSkip = true; + break; + } + + if (fSkip) + continue; + cGLXAttrib = convertEGLAttribToGLX(*pAttrib); + if (cGLXAttrib != None) + { + aAttribList[cAttribs] = cGLXAttrib; + if (pAttrib[1] == EGL_DONT_CARE) + aAttribList[cAttribs + 1] = GLX_DONT_CARE; + else + aAttribList[cAttribs + 1] = pAttrib[1]; + cAttribs += 2; + } + else + { + switch (*pAttrib) + { + case EGL_COLOR_BUFFER_TYPE: + aAttribList[cAttribs] = GLX_X_VISUAL_TYPE; + aAttribList[cAttribs + 1] = pAttrib[1] == EGL_DONT_CARE ? GLX_DONT_CARE + : pAttrib[1] == EGL_RGB_BUFFER ? GLX_TRUE_COLOR + : pAttrib[1] == EGL_LUMINANCE_BUFFER ? GLX_GRAY_SCALE + : GL_FALSE; + if ( *pAttrib == EGL_COLOR_BUFFER_TYPE + && pAttrib[1] != EGL_DONT_CARE && pAttrib[1] != EGL_RGB_BUFFER) + return setEGLError(EGL_BAD_ACCESS); + break; + case EGL_CONFIG_CAVEAT: + cConfigCaveat = pAttrib[1] == EGL_DONT_CARE ? GLX_DONT_CARE + : pAttrib[1] == EGL_NONE ? GLX_NONE + : pAttrib[1] == EGL_SLOW_CONFIG ? GLX_SLOW_CONFIG + : pAttrib[1] == EGL_NON_CONFORMANT_CONFIG ? GLX_NON_CONFORMANT_CONFIG + : GL_FALSE; + if (!cConfigCaveat) + return setEGLError(EGL_BAD_ATTRIBUTE); + cAttribs -= 2; + break; + case EGL_CONFORMANT: + if (pAttrib[1] != EGL_OPENGL_BIT && pAttrib[1] != 0) + return setEGLError(EGL_BAD_ACCESS); + cConformant = pAttrib[1] == EGL_OPENGL_BIT ? GL_TRUE : GL_FALSE; + cAttribs -= 2; + break; + case EGL_NATIVE_VISUAL_TYPE: + aAttribList[cAttribs] = GLX_X_VISUAL_TYPE; + aAttribList[cAttribs + 1] = pAttrib[1] == EGL_DONT_CARE ? GLX_DONT_CARE + : pAttrib[1] == StaticGray ? GLX_STATIC_GRAY + : pAttrib[1] == StaticColor ? GLX_STATIC_COLOR + : pAttrib[1] == TrueColor ? GLX_TRUE_COLOR + : pAttrib[1] == GrayScale ? GLX_GRAY_SCALE + : pAttrib[1] == PseudoColor ? GLX_PSEUDO_COLOR + : pAttrib[1] == DirectColor ? GLX_DIRECT_COLOR + : GL_FALSE; + break; + case EGL_RENDERABLE_TYPE: + cRenderableType = pAttrib[1]; + cAttribs -= 2; /* We did not add anything to the list. */ + break; + case EGL_SURFACE_TYPE: + if (pAttrib[1] & ~(EGL_PBUFFER_BIT | EGL_PIXMAP_BIT | EGL_WINDOW_BIT)) + return setEGLError(EGL_BAD_ACCESS); + aAttribList[cAttribs] = GLX_DRAWABLE_TYPE; + aAttribList[cAttribs + 1] = (pAttrib[1] & EGL_PBUFFER_BIT ? GLX_PBUFFER_BIT : 0) + | (pAttrib[1] & EGL_PIXMAP_BIT ? GLX_PIXMAP_BIT : 0) + | (pAttrib[1] & EGL_WINDOW_BIT ? GLX_WINDOW_BIT : 0); + break; + case EGL_TRANSPARENT_TYPE: + aAttribList[cAttribs] = GLX_TRANSPARENT_TYPE; + aAttribList[cAttribs + 1] = pAttrib[1] == EGL_DONT_CARE ? GLX_DONT_CARE + : pAttrib[1] == EGL_NONE ? GLX_NONE + : pAttrib[1] == EGL_TRANSPARENT_RGB ? GLX_TRANSPARENT_RGB + : GL_FALSE; + break; + default: + return setEGLError(EGL_BAD_ATTRIBUTE); + } + cAttribs += 2; + } + } + if (cConfigCaveat != GLX_DONT_CARE || cConformant != GLX_DONT_CARE) + { + aAttribList[cAttribs] = GLX_CONFIG_CAVEAT; + aAttribList[cAttribs + 1] = cConformant == GL_FALSE ? GLX_NON_CONFORMANT_CONFIG + : cConfigCaveat == EGL_SLOW_CONFIG ? GLX_SLOW_CONFIG + : GLX_NONE; + cAttribs += 2; + } + aAttribList[cAttribs] = GLX_RENDER_TYPE; + aAttribList[cAttribs + 1] = GLX_RGBA_BIT; + cAttribs += 2; + if (paAttribs != NULL) + { + aAttribList[cAttribs] = None; + EGL_ASSERT(cAttribs < RT_ELEMENTS(aAttribList)); + if (!(cRenderableType & EGL_OPENGL_BIT)) + return setEGLError(EGL_BAD_ACCESS); + } + paFBConfigs = glXChooseFBConfig(pDisplay, DefaultScreen(pDisplay), paAttribs != NULL ? aAttribList : NULL, &caFBConfigs); + if (paFBConfigs == NULL) + return setEGLError(EGL_BAD_ACCESS); + *pcConfigs = caFBConfigs; + for (i = 0; (GLint)i < caConfigs && (GLint)i < caFBConfigs; ++i) + paConfigs[i] = (EGLConfig)paFBConfigs[i]; + XFree(paFBConfigs); + return clearEGLError(); +} + +DECLEXPORT(EGLBoolean) eglGetConfigAttrib (EGLDisplay hDisplay, EGLConfig cConfig, EGLint cAttribute, EGLint *pValue) +{ + Display *pDisplay = (Display *)hDisplay; + int cGLXAttribute = convertEGLAttribToGLX(cAttribute); + int cValue; + + if (!VALID_PTR(hDisplay)) + return setEGLError(EGL_NOT_INITIALIZED); + if (!VALID_PTR(pValue)) + return setEGLError(EGL_BAD_PARAMETER); + if (glXGetFBConfigAttrib(pDisplay, (GLXFBConfig)cConfig, GLX_FBCONFIG_ID, &cValue)) + return setEGLError(EGL_BAD_CONFIG); + if (cGLXAttribute != None) + { + if (glXGetFBConfigAttrib(pDisplay, (GLXFBConfig)cConfig, cGLXAttribute, &cValue)) + return setEGLError(EGL_BAD_ACCESS); + *pValue = cValue; + return clearEGLError(); + } + switch (cAttribute) + { + case EGL_ALPHA_MASK_SIZE: + *pValue = 0; + return clearEGLError(); + case EGL_LUMINANCE_SIZE: + if (glXGetFBConfigAttrib(pDisplay, (GLXFBConfig)cConfig, GLX_X_VISUAL_TYPE, &cValue)) + return setEGLError(EGL_BAD_ACCESS); + if (cValue == GLX_STATIC_GRAY || cValue == GLX_GRAY_SCALE) + { + if (glXGetFBConfigAttrib(pDisplay, (GLXFBConfig)cConfig, GLX_RED_SIZE, &cValue)) + return setEGLError(EGL_BAD_ACCESS); + *pValue = cValue; + } + else + *pValue = 0; + return clearEGLError(); + case EGL_COLOR_BUFFER_TYPE: + if (glXGetFBConfigAttrib(pDisplay, (GLXFBConfig)cConfig, GLX_X_VISUAL_TYPE, &cValue)) + return setEGLError(EGL_BAD_ACCESS); + if (cValue == GLX_STATIC_GRAY || cValue == GLX_GRAY_SCALE) + *pValue = EGL_LUMINANCE_BUFFER; + else + *pValue = EGL_RGB_BUFFER; + return clearEGLError(); + case EGL_CONFIG_CAVEAT: + if (glXGetFBConfigAttrib(pDisplay, (GLXFBConfig)cConfig, GLX_CONFIG_CAVEAT, &cValue)) + return setEGLError(EGL_BAD_ACCESS); + *pValue = cValue == GLX_NONE ? EGL_NONE : cValue == GLX_SLOW_CONFIG ? EGL_SLOW_CONFIG : GLX_NON_CONFORMANT_CONFIG; + return clearEGLError(); + case EGL_CONFORMANT: + if (glXGetFBConfigAttrib(pDisplay, (GLXFBConfig)cConfig, GLX_CONFIG_CAVEAT, &cValue)) + return setEGLError(EGL_BAD_ACCESS); + *pValue = cValue == GLX_NON_CONFORMANT_CONFIG ? 0 : EGL_OPENGL_BIT; + return clearEGLError(); + case EGL_MATCH_NATIVE_PIXMAP: + case EGL_MIN_SWAP_INTERVAL: + case EGL_MAX_SWAP_INTERVAL: + return setEGLError(EGL_BAD_ACCESS); + case EGL_NATIVE_VISUAL_TYPE: + if (glXGetFBConfigAttrib(pDisplay, (GLXFBConfig)cConfig, GLX_X_VISUAL_TYPE, &cValue)) + return setEGLError(EGL_BAD_ACCESS); + *pValue = cValue == GLX_STATIC_GRAY ? StaticGray + : cValue == GLX_STATIC_COLOR ? StaticColor + : cValue == GLX_TRUE_COLOR ? TrueColor + : cValue == GLX_GRAY_SCALE ? GrayScale + : cValue == GLX_PSEUDO_COLOR ? PseudoColor + : cValue == GLX_DIRECT_COLOR ? DirectColor + : -1; + return clearEGLError(); + case EGL_RENDERABLE_TYPE: + *pValue = EGL_OPENGL_BIT; + return clearEGLError(); + case EGL_SURFACE_TYPE: + if (glXGetFBConfigAttrib(pDisplay, (GLXFBConfig)cConfig, GLX_DRAWABLE_TYPE, &cValue)) + return setEGLError(EGL_BAD_ACCESS); + *pValue = (cValue & GLX_PBUFFER_BIT ? EGL_PBUFFER_BIT : 0) + | (cValue & GLX_PIXMAP_BIT ? EGL_PIXMAP_BIT : 0) + | (cValue & GLX_WINDOW_BIT ? EGL_WINDOW_BIT : 0); + return clearEGLError(); + case EGL_TRANSPARENT_TYPE: + if (glXGetFBConfigAttrib(pDisplay, (GLXFBConfig)cConfig, GLX_TRANSPARENT_TYPE, &cValue)) + return setEGLError(EGL_BAD_ACCESS); + *pValue = cValue == GLX_NONE ? EGL_NONE + : cValue == GLX_TRANSPARENT_RGB ? EGL_TRANSPARENT_RGB + : EGL_FALSE; + return *pValue != EGL_FALSE ? clearEGLError() : setEGLError(EGL_BAD_ACCESS); + default: + return setEGLError(EGL_BAD_ATTRIBUTE); + } + return clearEGLError(); +} + +DECLEXPORT(EGLSurface) eglCreateWindowSurface(EGLDisplay hDisplay, EGLConfig config, EGLNativeWindowType hWindow, + const EGLint *paAttributes) +{ + Display *pDisplay = (Display *)hDisplay; + GLXWindow hGLXWindow; + + if (!VALID_PTR(hDisplay)) + { + setEGLError(EGL_NOT_INITIALIZED); + return EGL_NO_SURFACE; + } + if (paAttributes != NULL) /* Sanity test only. */ + while (*paAttributes != EGL_NONE) + { + if (*paAttributes != EGL_RENDER_BUFFER) + { + setEGLError(EGL_BAD_MATCH); + return EGL_NO_SURFACE; + } + paAttributes += 2; + } + hGLXWindow = glXCreateWindow(pDisplay, (GLXFBConfig)config, (Window)hWindow, NULL); + if (hGLXWindow == None) + { + setEGLError(EGL_BAD_ALLOC); + return EGL_NO_SURFACE; + } + EGL_ASSERT(hGLXWindow < VBEGL_WINDOW_SURFACE); /* Greater than the maximum XID. */ + clearEGLError(); + return (EGLSurface)(hGLXWindow | VBEGL_WINDOW_SURFACE); +} + +static void setAttribute(int *pcStoreIndex, int *pcCurIndex, int *paGLXAttributes, int cAttribute, int cValue) +{ + if (*pcStoreIndex < 0) + { + *pcStoreIndex = *pcCurIndex; + *pcCurIndex += 2; + paGLXAttributes[*pcStoreIndex] = cAttribute; + } + paGLXAttributes[*pcStoreIndex + 1] = cValue; +} + +DECLEXPORT(EGLSurface) eglCreatePbufferSurface(EGLDisplay hDisplay, EGLConfig config, EGLint const *paAttributes) +{ + Display *pDisplay = (Display *)hDisplay; + enum { CPS_WIDTH = 0, CPS_HEIGHT, CPS_LARGEST, CPS_PRESERVED, CPS_TEX_FORMAT, CPS_TEX_TARGET, CPS_MIPMAP_TEX, CPS_END }; + int acIndices[CPS_END]; + int aAttributes[CPS_END * 2]; + int cIndex = 0; + unsigned i; + GLXPbuffer hPbuffer; + + if (!VALID_PTR(hDisplay)) + { + setEGLError(EGL_NOT_INITIALIZED); + return EGL_NO_SURFACE; + } + for (i = 0; i < RT_ELEMENTS(acIndices); ++i) + acIndices[i] = -1; + if (paAttributes != NULL) + while (*paAttributes != EGL_NONE) + { + switch (*paAttributes) + { + case EGL_WIDTH: + setAttribute(&acIndices[CPS_WIDTH], &cIndex, aAttributes, GLX_PBUFFER_WIDTH, paAttributes[1]); + break; + case EGL_HEIGHT: + setAttribute(&acIndices[CPS_HEIGHT], &cIndex, aAttributes, GLX_LARGEST_PBUFFER, paAttributes[1]); + break; + case EGL_LARGEST_PBUFFER: + setAttribute(&acIndices[CPS_LARGEST], &cIndex, aAttributes, GLX_PBUFFER_HEIGHT, paAttributes[1]); + break; + case EGL_BUFFER_PRESERVED: + setAttribute(&acIndices[CPS_PRESERVED], &cIndex, aAttributes, GLX_PRESERVED_CONTENTS, paAttributes[1]); + break; + case EGL_TEXTURE_FORMAT: + setAttribute(&acIndices[CPS_TEX_FORMAT], &cIndex, aAttributes, GLX_TEXTURE_FORMAT_EXT, paAttributes[1]); + break; + case EGL_TEXTURE_TARGET: + setAttribute(&acIndices[CPS_TEX_TARGET], &cIndex, aAttributes, GLX_TEXTURE_TARGET_EXT, paAttributes[1]); + break; + case EGL_MIPMAP_TEXTURE: + setAttribute(&acIndices[CPS_MIPMAP_TEX], &cIndex, aAttributes, GLX_MIPMAP_TEXTURE_EXT, paAttributes[1]); + break; + case EGL_VG_ALPHA_FORMAT: + case EGL_VG_COLORSPACE: + { + setEGLError(EGL_BAD_MATCH); + return EGL_NO_SURFACE; + } + } + paAttributes += 2; + } + EGL_ASSERT((unsigned)cIndex < RT_ELEMENTS(aAttributes) - 1U); + aAttributes[cIndex + 1] = None; + hPbuffer = glXCreatePbuffer(pDisplay, (GLXFBConfig)config, aAttributes); + if (hPbuffer == None) + { + setEGLError(EGL_BAD_ALLOC); + return EGL_NO_SURFACE; + } + EGL_ASSERT(hPbuffer < VBEGL_WINDOW_SURFACE); /* Greater than the maximum XID. */ + clearEGLError(); + return (EGLSurface)(hPbuffer | VBEGL_PBUFFER_SURFACE); +} + +DECLEXPORT(EGLSurface) eglCreatePixmapSurface(EGLDisplay hDisplay, EGLConfig config, EGLNativePixmapType hPixmap, + const EGLint *paAttributes) +{ + Display *pDisplay = (Display *)hDisplay; + GLXPixmap hGLXPixmap; + + if (!VALID_PTR(hDisplay)) + { + setEGLError(EGL_NOT_INITIALIZED); + return EGL_NO_SURFACE; + } + if (paAttributes != NULL) /* Sanity test only. */ + if (*paAttributes != EGL_NONE) + { + if (*paAttributes == EGL_VG_COLORSPACE || *paAttributes == EGL_VG_ALPHA_FORMAT) + { + setEGLError(EGL_BAD_MATCH); + return EGL_NO_SURFACE; + } + else + { + setEGLError(EGL_BAD_ATTRIBUTE); + return EGL_NO_SURFACE; + } + } + hGLXPixmap = glXCreatePixmap(pDisplay, (GLXFBConfig)config, (Pixmap)hPixmap, NULL); + if (hGLXPixmap == None) + { + setEGLError(EGL_BAD_MATCH); + return EGL_NO_SURFACE; + } + EGL_ASSERT(hGLXPixmap < VBEGL_WINDOW_SURFACE); /* Greater than the maximum XID. */ + clearEGLError(); + return (EGLSurface)(hGLXPixmap | VBEGL_PIXMAP_SURFACE); +} + +DECLEXPORT(EGLBoolean) eglDestroySurface(EGLDisplay hDisplay, EGLSurface hSurface) +{ + Display *pDisplay = (Display *)hDisplay; + + if (!VALID_PTR(hDisplay)) + return setEGLError(EGL_NOT_INITIALIZED); + switch ((GLXDrawable)hSurface & VBEGL_ANY_SURFACE) + { + case VBEGL_WINDOW_SURFACE: + glXDestroyWindow(pDisplay, (GLXWindow)hSurface & ~VBEGL_WINDOW_SURFACE); + return clearEGLError(); + case VBEGL_PBUFFER_SURFACE: + glXDestroyPbuffer(pDisplay, (GLXPbuffer)hSurface & ~VBEGL_PBUFFER_SURFACE); + return clearEGLError(); + case VBEGL_PIXMAP_SURFACE: + glXDestroyPixmap(pDisplay, (GLXPixmap)hSurface & ~VBEGL_PIXMAP_SURFACE); + return clearEGLError(); + default: + return setEGLError(EGL_BAD_SURFACE); + } +} + +DECLEXPORT(EGLBoolean) eglSurfaceAttrib(EGLDisplay hDisplay, EGLSurface hSurface, EGLint cAttribute, EGLint cValue) +{ + NOREF(hDisplay); + NOREF(hSurface); + NOREF(cValue); + switch (cAttribute) + { + case EGL_MIPMAP_LEVEL: + case EGL_MULTISAMPLE_RESOLVE: + case EGL_SWAP_BEHAVIOR: + return setEGLError(EGL_BAD_MATCH); + default: + return setEGLError(EGL_BAD_ATTRIBUTE); + } +} + +DECLEXPORT(EGLBoolean) eglQuerySurface(EGLDisplay hDisplay, EGLSurface hSurface, EGLint cAttribute, EGLint *cValue) +{ + NOREF(hDisplay); + NOREF(hSurface); + NOREF(cAttribute); + NOREF(cValue); + return setEGLError(EGL_BAD_MATCH); +} + +DECLEXPORT(EGLBoolean) eglBindTexImage(EGLDisplay hDisplay, EGLSurface hSurface, EGLint cBuffer) +{ + NOREF(hDisplay); + NOREF(hSurface); + NOREF(cBuffer); + return setEGLError(EGL_BAD_MATCH); +} + +DECLEXPORT(EGLBoolean) eglReleaseTexImage(EGLDisplay hDisplay, EGLSurface hSurface, EGLint cBuffer) +{ + NOREF(hDisplay); + NOREF(hSurface); + NOREF(cBuffer); + return setEGLError(EGL_BAD_MATCH); +} + +DECLEXPORT(EGLBoolean) eglBindAPI(EGLenum enmApi) +{ + return enmApi == EGL_OPENGL_API ? clearEGLError() : setEGLError(EGL_BAD_PARAMETER); +} + +DECLEXPORT(EGLenum) eglQueryAPI(void) +{ + return EGL_OPENGL_API; +} + +DECLEXPORT(EGLContext) eglCreateContext(EGLDisplay hDisplay, EGLConfig hConfig, EGLContext hSharedContext, + const EGLint *paAttribs) +{ + Display *pDisplay = (Display *)hDisplay; + GLXContext hNewContext; + + if (!VALID_PTR(hDisplay)) + { + setEGLError(EGL_NOT_INITIALIZED); + return EGL_NO_CONTEXT; + } + if (paAttribs != NULL && *paAttribs != EGL_NONE) + { + setEGLError(EGL_BAD_ATTRIBUTE); + return EGL_NO_CONTEXT; + } + hNewContext = glXCreateNewContext(pDisplay, (GLXFBConfig)hConfig, GLX_RGBA_TYPE, (GLXContext)hSharedContext, true); + if (hNewContext) + { + clearEGLError(); + return (EGLContext)hNewContext; + } + setEGLError(EGL_BAD_MATCH); + return EGL_NO_CONTEXT; +} + +DECLEXPORT(EGLBoolean) eglDestroyContext(EGLDisplay hDisplay, EGLContext hContext) +{ + Display *pDisplay = (Display *)hDisplay; + + if (!VALID_PTR(hDisplay)) + return setEGLError(EGL_NOT_INITIALIZED); + glXDestroyContext(pDisplay, (GLXContext) hContext); + return clearEGLError(); +} + +DECLEXPORT(EGLBoolean) eglMakeCurrent(EGLDisplay hDisplay, EGLSurface hDraw, EGLSurface hRead, EGLContext hContext) +{ + Display *pDisplay = (Display *)hDisplay; + GLXDrawable hGLXDraw = hDraw == EGL_NO_SURFACE ? None : (GLXDrawable)hDraw & ~VBEGL_ANY_SURFACE; + GLXDrawable hGLXRead = hRead == EGL_NO_SURFACE ? None : (GLXDrawable)hRead & ~VBEGL_ANY_SURFACE; + GLXContext hGLXContext = hContext == EGL_NO_CONTEXT ? None : (GLXContext)hContext; + struct VBEGLTLS *pTls = getTls(); + + if (!VALID_PTR(hDisplay) || !VALID_PTR(pTls)) + return setEGLError(EGL_NOT_INITIALIZED); + if (glXMakeContextCurrent(pDisplay, hGLXDraw, hGLXRead, hGLXContext)) + { + pTls->hCurrent = hContext; + pTls->hCurrentDraw = hDraw; + pTls->hCurrentRead = hRead; + return clearEGLError(); + } + else + return setEGLError(EGL_BAD_MATCH); +} + +DECLEXPORT(EGLContext) eglGetCurrentContext(void) +{ + struct VBEGLTLS *pTls = getTls(); + + if (!VALID_PTR(pTls)) + return EGL_NO_CONTEXT; + clearEGLError(); + return pTls->hCurrent; +} + +DECLEXPORT(EGLSurface) eglGetCurrentSurface(EGLint cOp) +{ + struct VBEGLTLS *pTls = getTls(); + + if (!VALID_PTR(pTls)) + return EGL_NO_SURFACE; + clearEGLError(); + switch (cOp) + { + case EGL_DRAW: + return pTls->hCurrentDraw; + case EGL_READ: + return pTls->hCurrentRead; + default: + setEGLError(EGL_BAD_PARAMETER); + return EGL_NO_SURFACE; + } +} + +DECLEXPORT(EGLDisplay) eglGetCurrentDisplay(void) +{ + struct VBEGLTLS *pTls; + + pTls = getTls(); + if (!VALID_PTR(pTls)) + return EGL_NO_DISPLAY; + clearEGLError(); + return pTls->hCurrentDisplay; +} + +DECLEXPORT(EGLBoolean) eglQueryContext(EGLDisplay hDisplay, EGLContext hContext, EGLint cAttribute, EGLint *pcValue) +{ + Display *pDisplay = (Display *)hDisplay; + + if (!VALID_PTR(hDisplay)) + return setEGLError(EGL_NOT_INITIALIZED); + if (!VALID_PTR(pcValue)) + return setEGLError(EGL_BAD_PARAMETER); + switch (cAttribute) + { + case EGL_CONFIG_ID: + { + int cValue = 0; + + if (glXQueryContext(pDisplay, (GLXContext)hContext, GLX_FBCONFIG_ID, &cValue) == Success) + { + *pcValue = cValue; + return clearEGLError(); + } + return setEGLError(EGL_BAD_MATCH); + } + case EGL_CONTEXT_CLIENT_TYPE: + *pcValue = EGL_OPENGL_API; + return clearEGLError(); + case EGL_CONTEXT_CLIENT_VERSION: + *pcValue = 0; + return clearEGLError(); + case EGL_RENDER_BUFFER: + *pcValue = EGL_BACK_BUFFER; + return clearEGLError(); + default: + return setEGLError(EGL_BAD_ATTRIBUTE); + } +} + +DECLEXPORT(EGLBoolean) eglWaitClient(void) +{ + glXWaitGL(); + return clearEGLError(); +} + +DECLEXPORT(EGLBoolean) eglWaitGL(void) +{ + return setEGLError(EGL_BAD_PARAMETER); /* OpenGL ES only. */ +} + +DECLEXPORT(EGLBoolean) eglWaitNative(EGLint cEngine) +{ + if (cEngine != EGL_CORE_NATIVE_ENGINE) + return setEGLError(EGL_BAD_PARAMETER); + glXWaitX(); + return clearEGLError(); +} + +DECLEXPORT(EGLBoolean) eglSwapBuffers(EGLDisplay hDisplay, EGLSurface hSurface) +{ + Display *pDisplay = (Display *)hDisplay; + + if (!VALID_PTR(hDisplay)) + return setEGLError(EGL_NOT_INITIALIZED); + glXSwapBuffers(pDisplay, (GLXDrawable)hSurface & ~VBEGL_ANY_SURFACE); + return clearEGLError(); +} + +/** @todo Work out how this fits over what Chromium has to offer. */ +DECLEXPORT(EGLBoolean) eglCopyBuffers(EGLDisplay hDisplay, EGLSurface hSurface, EGLNativePixmapType hPixmap) +{ + Display *pDisplay = (Display *)hDisplay; + + if (!VALID_PTR(pDisplay)) + return setEGLError(EGL_NOT_INITIALIZED); + + NOREF(hSurface); + NOREF(hPixmap); + return setEGLError(EGL_BAD_MATCH); +} + +DECLEXPORT(EGLBoolean) eglSwapInterval (EGLDisplay dpy, EGLint interval) +{ + NOREF(dpy); + NOREF(interval); + return EGL_TRUE; +} + +typedef void (*VBEGLFuncPtr)(void); +DECLEXPORT(VBEGLFuncPtr)eglGetProcAddress(const char *pszName) +{ + clearEGLError(); + return glXGetProcAddress((const GLubyte *)pszName); +} + +DECLEXPORT(EGLBoolean) eglReleaseThread() +{ + struct VBEGLTLS *pTls = getTls(); + + if (!(pTls)) + return EGL_TRUE; + free(pTls); + /* Can this fail with ENOMEM? */ + pthread_setspecific(g_tls, NULL); + return EGL_TRUE; +} diff --git a/src/VBox/Additions/common/crOpenGL/entrypoints.py b/src/VBox/Additions/common/crOpenGL/entrypoints.py new file mode 100755 index 00000000..bdd93e75 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/entrypoints.py @@ -0,0 +1,180 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + + +""" +This module generates C entrypoints for all the OpenGL functions +and the special Chromium meta/glue functions. +""" + + +from __future__ import print_function +import sys + +import apiutil + + +def GenerateEntrypoints(hacks = []): + """Emit code for all the OpenGL/Chromium entrypoints. + hacks is an optional list of functions which are special cased. + """ + + apiutil.CopyrightC() + + print('#define GL_GLEXT_PROTOTYPES') + print('#include <stdio.h>') + print('#include <stdlib.h>') + print('#include <GL/gl.h>') + print('#include "chromium.h"') + print('#include "stub.h"') + print('#include "dri_glx.h"') + print('') + print('#ifdef __GNUC__') + print('# if (__GNUC__ << 16) + __GNUC_MINOR__ >= 0x40002') + print('# pragma GCC diagnostic ignored "-Wunused-parameter"') + print('# endif') + print('#endif') + + + # Get sorted list of dispatched functions. + # The order is very important - it must match cr_opcodes.h + # and spu_dispatch_table.h + keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + + for index in range(len(keys)): + func_name = keys[index] + if apiutil.Category(func_name) == "Chromium": + # this function is defined in stub.c + continue + + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + + if func_name in hacks: + print("/* hacked entrypoint: %s */" % func_name) + if func_name == "TexImage3D": + # Pretty common: internalformat is GLenum, not GLint + print("void glTexImage3D( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels )") + print("{") + print("\tglim.TexImage3D( target, level, (GLint) internalformat, width, height, depth, border, format, type, pixels );") + print("}") + elif func_name == "TexImage2D": + # Pretty common: internalformat is GLenum, not GLint + print("void glTexImage2D( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels )") + print("{") + print("\tglim.TexImage2D( target, level, (GLint) internalformat, width, height, border, format, type, pixels );") + print("}") + elif func_name == "TexImage1D": + # Pretty common: internalformat is GLenum, not GLint + print("void glTexImage1D( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels )") + print("{") + print("\tglim.TexImage1D( target, level, (GLint) internalformat, width, border, format, type, pixels );") + print("}") + elif func_name == "EdgeFlagPointer": + # second arg is GLboolean instead of GLvoid + print("void glEdgeFlagPointer( GLsizei stride, const GLboolean *pointer )") + print("{") + print("\tglim.EdgeFlagPointer( stride, pointer );") + print("}") + elif func_name == "ProgramParameters4fvNV": + print("void glProgramParameters4fvNV( GLenum target, GLuint index, GLuint num, const GLfloat *params )") + print("{") + print("\tglim.ProgramParameters4fvNV( target, index, num, params );") + print("}") + elif func_name == "MultiDrawElementsEXT": + print("void glMultiDrawElementsEXT(GLenum mode, GLsizei *count, GLenum type, const GLvoid **indices, GLsizei primcount)") + print("{") + print("\tglim.MultiDrawElementsEXT(mode, count,type, indices, primcount);") + print("}") + elif func_name == "ProgramParameters4dvNV": + print("void glProgramParameters4dvNV( GLenum target, GLuint index, GLuint num, const GLdouble *params )") + print("{") + print("\tglim.ProgramParameters4dvNV( target, index, num, params );") + print("}") + else: + # the usual path + print("%s VBOXGLTAG(gl%s)(%s);" % (return_type, func_name, apiutil.MakeDeclarationString(params))) + print("") + print("%s VBOXGLTAG(gl%s)(%s)" % (return_type, func_name, apiutil.MakeDeclarationString(params))) + print("{") + print("\t", end="") + if return_type != "void": + print("return ", end=" ") + print("glim.%s(%s);" % (func_name, apiutil.MakeCallString(params))) + print("}") + print("") + + print('/*') + print('* Aliases') + print('*/') + + # Now loop over all the functions and take care of any aliases + allkeys = apiutil.GetAllFunctions(sys.argv[1]+"/APIspec.txt") + for func_name in allkeys: + if "omit" in apiutil.ChromiumProps(func_name): + continue + + if func_name in keys: + # we already processed this function earlier + continue + + # alias is the function we're aliasing + alias = apiutil.Alias(func_name) + if alias: + if func_name in hacks: + print("/* hacked entrypoint: %s */" % func_name) + if func_name == "MultiDrawArrays": + print("void glMultiDrawArrays( GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount )") + print("{") + print("\tglim.MultiDrawArraysEXT( mode, (GLint*)first, (GLsizei*)count, primcount );") + print("}") + elif func_name == "BufferData": + print("void glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage)") + print("{") + print("\tglim.BufferDataARB(target, size, data, usage);") + print("}") + elif func_name == "BufferSubData": + print("void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data)") + print("{") + print("\tglim.BufferSubDataARB(target, offset, size, data);") + print("}") + elif func_name == "GetBufferSubData": + print("void glGetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data)") + print("{") + print("\tglim.GetBufferSubDataARB(target, offset, size, data);") + print("}") + else: + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + print("%s VBOXGLTAG(gl%s)(%s);" % (return_type, func_name, apiutil.MakeDeclarationString(params))) + print("") + print("%s VBOXGLTAG(gl%s)(%s)" % (return_type, func_name, apiutil.MakeDeclarationString(params))) + print("{") + print("\t", end="") + if return_type != "void": + print("return ", end=" ") + print("glim.%s(%s);" % (alias, apiutil.MakeCallString(params))) + print("}") + print("") + + print('/*') + print('* No-op stubs') + print('*/') + + # Now generate no-op stub functions + for func_name in allkeys: + if "stub" in apiutil.ChromiumProps(func_name): + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + + print("%s VBOXGLTAG(gl%s)(%s);" % (return_type, func_name, apiutil.MakeDeclarationString(params))) + print("") + print("%s VBOXGLTAG(gl%s)(%s)" % (return_type, func_name, apiutil.MakeDeclarationString(params))) + print("{") + if return_type != "void": + print("return (%s) 0" % return_type) + print("}") + print("") + diff --git a/src/VBox/Additions/common/crOpenGL/fakedri_drv.c b/src/VBox/Additions/common/crOpenGL/fakedri_drv.c new file mode 100644 index 00000000..1fab45b0 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/fakedri_drv.c @@ -0,0 +1,878 @@ +/* $Id: fakedri_drv.c $ */ +/** @file + * VBox OpenGL DRI driver functions + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define _GNU_SOURCE 1 + +#include "cr_error.h" +#include "cr_gl.h" +#include "cr_mem.h" +#include "stub.h" +#include "fakedri_drv.h" +#include "dri_glx.h" +#include "iprt/mem.h" +#include <iprt/errcore.h> +#include <dlfcn.h> +#include <elf.h> +#include <unistd.h> + +#if defined(RT_OS_FREEBSD) +#include <sys/param.h> +#include <fcntl.h> +#include <gelf.h> +#include <libelf.h> +#include <string.h> +#endif + +/** X server message type definitions. */ +typedef enum { + X_PROBED, /* Value was probed */ + X_CONFIG, /* Value was given in the config file */ + X_DEFAULT, /* Value is a default */ + X_CMDLINE, /* Value was given on the command line */ + X_NOTICE, /* Notice */ + X_ERROR, /* Error message */ + X_WARNING, /* Warning message */ + X_INFO, /* Informational message */ + X_NONE, /* No prefix */ + X_NOT_IMPLEMENTED, /* Not implemented */ + X_UNKNOWN = -1 /* unknown -- this must always be last */ +} MessageType; + +#define VBOX_NO_MESA_PATCH_REPORTS + +//#define DEBUG_DRI_CALLS + +/// @todo this could be different... +#ifdef RT_ARCH_AMD64 +# ifdef RT_OS_FREEBSD +# define DRI_DEFAULT_DRIVER_DIR "/usr/local/lib/dri" +# define DRI_XORG_DRV_DIR "/usr/local/lib/xorg/modules/drivers/" +# else +# define DRI_DEFAULT_DRIVER_DIR "/usr/lib64/dri:/usr/lib/dri:/usr/lib/x86_64-linux-gnu/dri:/usr/lib/xorg/modules/dri" +# define DRI_XORG_DRV_DIR "/usr/lib/xorg/modules/drivers/" +# endif +#else +# ifdef RT_OS_FREEBSD +# define DRI_DEFAULT_DRIVER_DIR "/usr/local/lib/dri" +# define DRI_XORG_DRV_DIR "/usr/local/lib/xorg/modules/drivers/" +# else +# define DRI_DEFAULT_DRIVER_DIR "/usr/lib/dri:/usr/lib/i386-linux-gnu/dri:/usr/lib/xorg/modules/dri" +# define DRI_XORG_DRV_DIR "/usr/lib/xorg/modules/drivers/" +# endif +#endif + +#ifdef DEBUG_DRI_CALLS + #define SWDRI_SHOWNAME(pext, func) \ + crDebug("SWDRI: sc %s->%s", #pext, #func) +#else + #define SWDRI_SHOWNAME(pext, func) +#endif + +#define SWDRI_SAFECALL(pext, func, ...) \ + SWDRI_SHOWNAME(pext, func); \ + if (pext && pext->func){ \ + (*pext->func)(__VA_ARGS__); \ + } else { \ + crDebug("swcore_call NULL for "#func); \ + } + +#define SWDRI_SAFERET(pext, func, ...) \ + SWDRI_SHOWNAME(pext, func); \ + if (pext && pext->func){ \ + return (*pext->func)(__VA_ARGS__); \ + } else { \ + crDebug("swcore_call NULL for "#func); \ + return 0; \ + } + +#define SWDRI_SAFERET_CORE(func, ...) SWDRI_SAFERET(gpSwDriCoreExternsion, func, __VA_ARGS__) +#define SWDRI_SAFECALL_CORE(func, ...) SWDRI_SAFECALL(gpSwDriCoreExternsion, func, __VA_ARGS__) +#define SWDRI_SAFERET_SWRAST(func, ...) SWDRI_SAFERET(gpSwDriSwrastExtension, func, __VA_ARGS__) +#define SWDRI_SAFECALL_SWRAST(func, ...) SWDRI_SAFECALL(gpSwDriSwrastExtension, func, __VA_ARGS__) + +#ifndef PAGESIZE +#define PAGESIZE 4096 +#endif + +#ifdef RT_ARCH_AMD64 +# define DRI_ELFSYM Elf64_Sym +#else +# define DRI_ELFSYM Elf32_Sym +#endif + +#ifdef RT_ARCH_AMD64 +typedef struct _FAKEDRI_PatchNode +{ + const char* psFuncName; + void *pDstStart, *pDstEnd; + const void *pSrcStart, *pSrcEnd; + + struct _FAKEDRI_PatchNode *pNext; +} FAKEDRI_PatchNode; +static FAKEDRI_PatchNode *g_pFreeList=NULL, *g_pRepatchList=NULL; +#endif + +static struct _glapi_table* vbox_glapi_table = NULL; +fakedri_glxapi_table glxim; + +static const __DRIextension **gppSwDriExternsion = NULL; +static const __DRIcoreExtension *gpSwDriCoreExternsion = NULL; +static const __DRIswrastExtension *gpSwDriSwrastExtension = NULL; + +extern const __DRIextension * __driDriverExtensions[]; + +#define VBOX_SET_MESA_FUNC(table, name, func) \ + if (_glapi_get_proc_offset(name)>=0) SET_by_offset(table, _glapi_get_proc_offset(name), func); \ + else crWarning("%s not found in mesa table", name) + +#define GLAPI_ENTRY(Func) VBOX_SET_MESA_FUNC(vbox_glapi_table, "gl"#Func, cr_gl##Func); + +static void +vboxPatchMesaExport(const char* psFuncName, const void *pStart, const void *pEnd); + +static void +vboxPatchMesaGLAPITable() +{ + void *pGLTable; + + pGLTable = (void *)_glapi_get_dispatch(); + vbox_glapi_table = crAlloc(_glapi_get_dispatch_table_size() * sizeof (void *)); + if (!vbox_glapi_table) + { + crError("Not enough memory to allocate dispatch table"); + } + crMemcpy(vbox_glapi_table, pGLTable, _glapi_get_dispatch_table_size() * sizeof (void *)); + + #include "fakedri_glfuncsList.h" + + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glBlendEquationSeparateEXT", cr_glBlendEquationSeparate); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glSampleMaskSGIS", cr_glSampleMaskEXT); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glSamplePatternSGIS", cr_glSamplePatternEXT); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos2dMESA", cr_glWindowPos2d); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos2dvMESA", cr_glWindowPos2dv); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos2fMESA", cr_glWindowPos2f); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos2fvMESA", cr_glWindowPos2fv); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos2iMESA", cr_glWindowPos2i); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos2ivMESA", cr_glWindowPos2iv); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos2sMESA", cr_glWindowPos2s); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos2svMESA", cr_glWindowPos2sv); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos3dMESA", cr_glWindowPos3d); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos3dvMESA", cr_glWindowPos3dv); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos3fMESA", cr_glWindowPos3f); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos3fvMESA", cr_glWindowPos3fv); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos3iMESA", cr_glWindowPos3i); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos3ivMESA", cr_glWindowPos3iv); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos3sMESA", cr_glWindowPos3s); + VBOX_SET_MESA_FUNC(vbox_glapi_table, "glWindowPos3svMESA", cr_glWindowPos3sv); + + _glapi_set_dispatch(vbox_glapi_table); +}; +#undef GLAPI_ENTRY + +#define GLXAPI_ENTRY(Func) pGLXTable->Func = VBOXGLXTAG(glX##Func); +static void +vboxFillGLXAPITable(fakedri_glxapi_table *pGLXTable) +{ + #include "fakedri_glxfuncsList.h" +} +#undef GLXAPI_ENTRY + +static void +vboxApplyPatch(const char* psFuncName, void *pDst, const void *pSrc, unsigned long size) +{ + void *alPatch; + int rv; + + /* Get aligned start address we're going to patch*/ + alPatch = (void*) ((uintptr_t)pDst & ~(uintptr_t)(PAGESIZE-1)); + +#ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("MProtecting: %p, %li", alPatch, pDst-alPatch+size); +#endif + + /* Get write access to mesa functions */ + rv = RTMemProtect(alPatch, pDst-alPatch+size, RTMEM_PROT_READ|RTMEM_PROT_WRITE|RTMEM_PROT_EXEC); + if (RT_FAILURE(rv)) + { + crError("mprotect failed with %x (%s)", rv, psFuncName); + } + +#ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("Writing %li bytes to %p from %p", size, pDst, pSrc); +#endif + + crMemcpy(pDst, pSrc, size); + + /** @todo Restore the protection, probably have to check what was it before us...*/ + rv = RTMemProtect(alPatch, pDst-alPatch+size, RTMEM_PROT_READ|RTMEM_PROT_EXEC); + if (RT_FAILURE(rv)) + { + crError("mprotect2 failed with %x (%s)", rv, psFuncName); + } +} + +#define FAKEDRI_JMP64_PATCH_SIZE 13 + +#if defined(RT_OS_FREEBSD) +/* Provide basic dladdr1 flags */ +enum { + RTLD_DL_SYMENT = 1 +}; + +/* Provide a minimal local version of dladdr1 */ +static int +dladdr1(const void *address, Dl_info *dlip, void **info, int flags) +{ + static DRI_ELFSYM desym; + GElf_Sym sym; + GElf_Shdr shdr; + Elf *elf; + Elf_Scn *scn; + Elf_Data *data; + int ret, fd, count, i; + + /* Initialize variables */ + fd = -1; + elf = NULL; + + /* Call dladdr first */ + ret = dladdr(address, dlip); + if (ret == 0) goto err_exit; + + /* Check for supported flags */ + if (flags != RTLD_DL_SYMENT) return 1; + + /* Open shared library's ELF file */ + if (elf_version(EV_CURRENT) == EV_NONE) goto err_exit; + fd = open(dlip->dli_fname, O_RDONLY); + if (fd < 0) goto err_exit; + elf = elf_begin(fd, ELF_C_READ, NULL); + if (elf == NULL) goto err_exit; + + /* Find the '.dynsym' section */ + scn = elf_nextscn(elf, NULL); + while (scn != NULL) { + if (gelf_getshdr(scn, &shdr) == NULL) goto err_exit; + if (shdr.sh_type == SHT_DYNSYM) break; + scn = elf_nextscn(elf, scn); + } + if (scn == NULL) goto err_exit; + + /* Search for the requested symbol by name and offset */ + data = elf_getdata(scn, NULL); + count = shdr.sh_size / shdr.sh_entsize; + for (i = 0; i < count; i++) { + gelf_getsym(data, i, &sym); + if ((strcmp(dlip->dli_sname, + elf_strptr(elf, shdr.sh_link, sym.st_name)) == 0) && + (sym.st_value == (dlip->dli_saddr - dlip->dli_fbase))) { + break; + } + } + + /* Close ELF file */ + elf_end(elf); + close(fd); + + /* Return symbol entry in native format */ + desym.st_name = sym.st_name; + desym.st_info = sym.st_info; + desym.st_other = sym.st_other; + desym.st_shndx = sym.st_shndx; + desym.st_value = sym.st_value; + desym.st_size = sym.st_size; + *info = &desym; + return 1; + + /* Error handler */ +err_exit: + if (elf != NULL) elf_end(elf); + if (fd >= 0) close(fd); + return 0; +} +#endif + +static void +vboxPatchMesaExport(const char* psFuncName, const void *pStart, const void *pEnd) +{ + Dl_info dlip; + DRI_ELFSYM* sym=0; + int rv; + void *alPatch; + void *pMesaEntry; + char patch[FAKEDRI_JMP64_PATCH_SIZE]; + void *shift; + int ignore_size=false; + +#ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("\nvboxPatchMesaExport: %s", psFuncName); +#endif + + pMesaEntry = dlsym(RTLD_DEFAULT, psFuncName); + + if (!pMesaEntry) + { + crDebug("%s not defined in current scope, are we being loaded by mesa's libGL.so?", psFuncName); + return; + } + + rv = dladdr1(pMesaEntry, &dlip, (void**)&sym, RTLD_DL_SYMENT); + if (!rv || !sym) + { + crError("Failed to get size for %p(%s)", pMesaEntry, psFuncName); + return; + } + +#if VBOX_OGL_GLX_USE_CSTUBS + { + Dl_info dlip1; + DRI_ELFSYM* sym1=0; + int rv; + + rv = dladdr1(pStart, &dlip1, (void**)&sym1, RTLD_DL_SYMENT); + if (!rv || !sym1) + { + crError("Failed to get size for vbox %p", pStart); + return; + } + + pEnd = pStart + sym1->st_size; +# ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("VBox Entry: %p, start: %p(%s:%s), size: %li", pStart, dlip1.dli_saddr, dlip1.dli_fname, dlip1.dli_sname, sym1->st_size); +# endif + } +#endif + +#ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("Mesa Entry: %p, start: %p(%s:%s), size: %li", pMesaEntry, dlip.dli_saddr, dlip.dli_fname, dlip.dli_sname, sym->st_size); + crDebug("VBox code: start: %p, end %p, size: %li", pStart, pEnd, pEnd-pStart); +#endif + +#ifndef VBOX_OGL_GLX_USE_CSTUBS + if (sym->st_size<(pEnd-pStart)) +#endif + { +#ifdef RT_ARCH_AMD64 + int64_t offset; +#endif + /* Try to insert 5 bytes jmp/jmpq to our stub code */ + + if (sym->st_size<5) + { + /** @todo we don't really know the size of targeted static function, but it's long enough in practice. We will also patch same place twice, but it's ok.*/ + if (!crStrcmp(psFuncName, "glXDestroyContext") || !crStrcmp(psFuncName, "glXFreeContextEXT")) + { + if (((unsigned char*)dlip.dli_saddr)[0]==0xEB) + { + /*it's a rel8 jmp, so we're going to patch the place it targets instead of jmp itself*/ + dlip.dli_saddr = (void*) ((intptr_t)dlip.dli_saddr + ((char*)dlip.dli_saddr)[1] + 2); + ignore_size = true; + } + else + { + crError("Can't patch size is too small.(%s)", psFuncName); + return; + } + } + else if (!crStrcmp(psFuncName, "glXCreateGLXPixmapMESA")) + { + /** @todo it's just a return 0, which we're fine with for now*/ + return; + } + else + { + crError("Can't patch size is too small.(%s)", psFuncName); + return; + } + } + + shift = (void*)((intptr_t)pStart-((intptr_t)dlip.dli_saddr+5)); +#ifdef RT_ARCH_AMD64 + offset = (intptr_t)shift; + if (offset>INT32_MAX || offset<INT32_MIN) + { + /*try to insert 64bit abs jmp*/ + if (sym->st_size>=FAKEDRI_JMP64_PATCH_SIZE || ignore_size) + { +# ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("Inserting movq/jmp instead"); +# endif + /*add 64bit abs jmp*/ + patch[0] = 0x49; /*movq %r11,imm64*/ + patch[1] = 0xBB; + crMemcpy(&patch[2], &pStart, 8); + patch[10] = 0x41; /*jmp *%r11*/ + patch[11] = 0xFF; + patch[12] = 0xE3; + pStart = &patch[0]; + pEnd = &patch[FAKEDRI_JMP64_PATCH_SIZE]; + } + else + { + FAKEDRI_PatchNode *pNode; +# ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("Can't patch offset is too big. Pushing for 2nd pass(%s)", psFuncName); +# endif + /*Add patch node to repatch with chain jmps in 2nd pass*/ + pNode = (FAKEDRI_PatchNode *)crAlloc(sizeof(FAKEDRI_PatchNode)); + if (!pNode) + { + crError("Not enough memory."); + return; + } + pNode->psFuncName = psFuncName; + pNode->pDstStart = dlip.dli_saddr; + pNode->pDstEnd = dlip.dli_saddr+sym->st_size; + pNode->pSrcStart = pStart; + pNode->pSrcEnd = pEnd; + pNode->pNext = g_pRepatchList; + g_pRepatchList = pNode; + return; + } + } + else +#endif + { +#ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("Inserting jmp[q] with shift %p instead", shift); +#endif + patch[0] = 0xE9; + crMemcpy(&patch[1], &shift, 4); + pStart = &patch[0]; + pEnd = &patch[5]; + } + } + + vboxApplyPatch(psFuncName, dlip.dli_saddr, pStart, pEnd-pStart); + +#ifdef RT_ARCH_AMD64 + /*Add rest of mesa function body to free list*/ + if (sym->st_size-(pEnd-pStart)>=FAKEDRI_JMP64_PATCH_SIZE) + { + FAKEDRI_PatchNode *pNode = (FAKEDRI_PatchNode *)crAlloc(sizeof(FAKEDRI_PatchNode)); + if (pNode) + { + pNode->psFuncName = psFuncName; + pNode->pDstStart = dlip.dli_saddr+(pEnd-pStart); + pNode->pDstEnd = dlip.dli_saddr+sym->st_size; + pNode->pSrcStart = dlip.dli_saddr; + pNode->pSrcEnd = NULL; + pNode->pNext = g_pFreeList; + g_pFreeList = pNode; +# ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("Added free node %s, func start=%p, free start=%p, size=%#lx", + psFuncName, pNode->pSrcStart, pNode->pDstStart, pNode->pDstEnd-pNode->pDstStart); +# endif + } + } +#endif +} + +#ifdef RT_ARCH_AMD64 +static void +vboxRepatchMesaExports(void) +{ + FAKEDRI_PatchNode *pFreeNode, *pPatchNode; + int64_t offset; + char patch[FAKEDRI_JMP64_PATCH_SIZE]; + + pPatchNode = g_pRepatchList; + while (pPatchNode) + { +# ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("\nvboxRepatchMesaExports %s", pPatchNode->psFuncName); +# endif + /*find free place in mesa functions, to place 64bit jump to our stub code*/ + pFreeNode = g_pFreeList; + while (pFreeNode) + { + if (pFreeNode->pDstEnd-pFreeNode->pDstStart>=FAKEDRI_JMP64_PATCH_SIZE) + { + offset = ((intptr_t)pFreeNode->pDstStart-((intptr_t)pPatchNode->pDstStart+5)); + if (offset<=INT32_MAX && offset>=INT32_MIN) + { + break; + } + } + pFreeNode=pFreeNode->pNext; + } + + if (!pFreeNode) + { + crError("Failed to find free space, to place repatch for %s.", pPatchNode->psFuncName); + return; + } + + /*add 32bit rel jmp, from mesa orginal function to free space in other mesa function*/ + patch[0] = 0xE9; + crMemcpy(&patch[1], &offset, 4); +# ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("Adding jmp from mesa %s to mesa %s+%#lx", pPatchNode->psFuncName, pFreeNode->psFuncName, + pFreeNode->pDstStart-pFreeNode->pSrcStart); +# endif + vboxApplyPatch(pPatchNode->psFuncName, pPatchNode->pDstStart, &patch[0], 5); + + /*add 64bit abs jmp, from free space to our stub code*/ + patch[0] = 0x49; /*movq %r11,imm64*/ + patch[1] = 0xBB; + crMemcpy(&patch[2], &pPatchNode->pSrcStart, 8); + patch[10] = 0x41; /*jmp *%r11*/ + patch[11] = 0xFF; + patch[12] = 0xE3; +# ifndef VBOX_NO_MESA_PATCH_REPORTS + crDebug("Adding jmp from mesa %s+%#lx to vbox %s", pFreeNode->psFuncName, pFreeNode->pDstStart-pFreeNode->pSrcStart, + pPatchNode->psFuncName); +# endif + vboxApplyPatch(pFreeNode->psFuncName, pFreeNode->pDstStart, &patch[0], FAKEDRI_JMP64_PATCH_SIZE); + /*mark this space as used*/ + pFreeNode->pDstStart = pFreeNode->pDstStart+FAKEDRI_JMP64_PATCH_SIZE; + + pPatchNode = pPatchNode->pNext; + } +} + +static void +vboxFakeDriFreeList(FAKEDRI_PatchNode *pList) +{ + FAKEDRI_PatchNode *pNode; + + while (pList) + { + pNode=pList; + pList=pNode->pNext; + crFree(pNode); + } +} +#endif + +#ifdef VBOX_OGL_GLX_USE_CSTUBS +static void +# define GLXAPI_ENTRY(Func) vboxPatchMesaExport("glX"#Func, &vbox_glX##Func, NULL); +vboxPatchMesaExports() +#else +static void +# define GLXAPI_ENTRY(Func) vboxPatchMesaExport("glX"#Func, &vbox_glX##Func, &vbox_glX##Func##_EndProc); +vboxPatchMesaExports() +#endif +{ + crDebug("Patching mesa glx entries"); + #include "fakedri_glxfuncsList.h" + +#ifdef RT_ARCH_AMD64 + vboxRepatchMesaExports(); + vboxFakeDriFreeList(g_pRepatchList); + g_pRepatchList = NULL; + vboxFakeDriFreeList(g_pFreeList); + g_pFreeList = NULL; +#endif +} +#undef GLXAPI_ENTRY + +bool vbox_load_sw_dri() +{ + const char *libPaths, *p, *next;; + char realDriverName[200]; + void *handle; + int len, i; + + /*code from Mesa-7.2/src/glx/x11/dri_common.c:driOpenDriver*/ + + libPaths = NULL; + if (geteuid() == getuid()) { + /* don't allow setuid apps to use LIBGL_DRIVERS_PATH */ + libPaths = getenv("LIBGL_DRIVERS_PATH"); + if (!libPaths) + libPaths = getenv("LIBGL_DRIVERS_DIR"); /* deprecated */ + } + if (libPaths == NULL) + libPaths = DRI_DEFAULT_DRIVER_DIR; + + handle = NULL; + for (p = libPaths; *p; p = next) + { + next = strchr(p, ':'); + if (next == NULL) + { + len = strlen(p); + next = p + len; + } + else + { + len = next - p; + next++; + } + + snprintf(realDriverName, sizeof realDriverName, "%.*s/%s_dri.so", len, p, "swrast"); + crDebug("trying %s", realDriverName); + handle = dlopen(realDriverName, RTLD_NOW | RTLD_LOCAL); + if (handle) break; + } + + /*end code*/ + + if (handle) gppSwDriExternsion = dlsym(handle, "__driDriverExtensions"); + + if (!gppSwDriExternsion) + { + crDebug("%s doesn't export __driDriverExtensions", realDriverName); + return false; + } + crDebug("loaded %s", realDriverName); + + for (i = 0; gppSwDriExternsion[i]; i++) + { + if (strcmp(gppSwDriExternsion[i]->name, __DRI_CORE) == 0) + gpSwDriCoreExternsion = (__DRIcoreExtension *) gppSwDriExternsion[i]; + if (strcmp(gppSwDriExternsion[i]->name, __DRI_SWRAST) == 0) + gpSwDriSwrastExtension = (__DRIswrastExtension *) gppSwDriExternsion[i]; + } + + return gpSwDriCoreExternsion && gpSwDriSwrastExtension; +} + +void __attribute__ ((constructor)) vbox_install_into_mesa(void) +{ + { +#ifdef _X_ATTRIBUTE_PRINTF + void (*pxf86Msg)(MessageType type, const char *format, ...) _X_ATTRIBUTE_PRINTF(2,3); +#else + void (*pxf86Msg)(MessageType type, const char *format, ...) _printf_attribute(2,3); +#endif + + pxf86Msg = dlsym(RTLD_DEFAULT, "xf86Msg"); + if (pxf86Msg) + { + pxf86Msg(X_INFO, "Next line is added to allow vboxvideo_drv.so to appear as whitelisted driver\n"); + pxf86Msg(X_INFO, "The file referenced, is *NOT* loaded\n"); + pxf86Msg(X_INFO, "Loading %s/ati_drv.so\n", DRI_XORG_DRV_DIR); + + /* we're failing to proxy software dri driver calls for certain xservers, so just make sure we're unloaded for now */ + __driDriverExtensions[0] = NULL; + return; + } + } + + if (!stubInit()) + { + crDebug("vboxdriInitScreen: stubInit failed"); + return; + } + + /* Load swrast_dri.so to proxy dri related calls there. */ + if (!vbox_load_sw_dri()) + { + crDebug("vboxdriInitScreen: vbox_load_sw_dri failed...going to fail badly"); + return; + } + + /* Handle gl api. + * In the end application call would look like this: + * app call glFoo->(mesa asm dispatch stub)->cr_glFoo(vbox asm dispatch stub)->SPU Foo function(packspuFoo or alike) + * Note, we don't need to install extension functions via _glapi_add_dispatch, because we'd override glXGetProcAddress. + */ + /* Mesa's dispatch table is different across library versions, have to modify mesa's table using offset info functions*/ + vboxPatchMesaGLAPITable(); + + /* Handle glx api. + * In the end application call would look like this: + * app call glxFoo->(mesa asm dispatch stub patched with vbox_glXFoo:jmp glxim[Foo's index])->VBOXGLXTAG(glxFoo) + */ + /* Fill structure used by our assembly stubs */ + vboxFillGLXAPITable(&glxim); + /* Now patch functions exported by libGL.so */ + vboxPatchMesaExports(); +} + +/* + * @todo we're missing first glx related call from the client application. + * Luckily, this doesn't add much problems, except for some cases. + */ + +/* __DRIcoreExtension */ + +static __DRIscreen * +vboxdriCreateNewScreen(int screen, int fd, unsigned int sarea_handle, + const __DRIextension **extensions, const __DRIconfig ***driverConfigs, + void *loaderPrivate) +{ + (void) fd; + (void) sarea_handle; + SWDRI_SAFERET_SWRAST(createNewScreen, screen, extensions, driverConfigs, loaderPrivate); +} + +static void +vboxdriDestroyScreen(__DRIscreen *screen) +{ + SWDRI_SAFECALL_CORE(destroyScreen, screen); +} + +static const __DRIextension ** +vboxdriGetExtensions(__DRIscreen *screen) +{ + SWDRI_SAFERET_CORE(getExtensions, screen); +} + +static int +vboxdriGetConfigAttrib(const __DRIconfig *config, + unsigned int attrib, + unsigned int *value) +{ + SWDRI_SAFERET_CORE(getConfigAttrib, config, attrib, value); +} + +static int +vboxdriIndexConfigAttrib(const __DRIconfig *config, int index, + unsigned int *attrib, unsigned int *value) +{ + SWDRI_SAFERET_CORE(indexConfigAttrib, config, index, attrib, value); +} + +static __DRIdrawable * +vboxdriCreateNewDrawable(__DRIscreen *screen, + const __DRIconfig *config, + unsigned int drawable_id, + unsigned int head, + void *loaderPrivate) +{ + (void) drawable_id; + (void) head; + SWDRI_SAFERET_SWRAST(createNewDrawable, screen, config, loaderPrivate); +} + +static void +vboxdriDestroyDrawable(__DRIdrawable *drawable) +{ + SWDRI_SAFECALL_CORE(destroyDrawable, drawable); +} + +static void +vboxdriSwapBuffers(__DRIdrawable *drawable) +{ + SWDRI_SAFECALL_CORE(swapBuffers, drawable); +} + +static __DRIcontext * +vboxdriCreateNewContext(__DRIscreen *screen, + const __DRIconfig *config, + __DRIcontext *shared, + void *loaderPrivate) +{ + SWDRI_SAFERET_CORE(createNewContext, screen, config, shared, loaderPrivate); +} + +static int +vboxdriCopyContext(__DRIcontext *dest, + __DRIcontext *src, + unsigned long mask) +{ + SWDRI_SAFERET_CORE(copyContext, dest, src, mask); +} + +static void +vboxdriDestroyContext(__DRIcontext *context) +{ + SWDRI_SAFECALL_CORE(destroyContext, context); +} + +static int +vboxdriBindContext(__DRIcontext *ctx, + __DRIdrawable *pdraw, + __DRIdrawable *pread) +{ + SWDRI_SAFERET_CORE(bindContext, ctx, pdraw, pread); +} + +static int +vboxdriUnbindContext(__DRIcontext *ctx) +{ + SWDRI_SAFERET_CORE(unbindContext, ctx) +} + +/* __DRIlegacyExtension */ + +static __DRIscreen * +vboxdriCreateNewScreen_Legacy(int scrn, + const __DRIversion *ddx_version, + const __DRIversion *dri_version, + const __DRIversion *drm_version, + const __DRIframebuffer *frame_buffer, + drmAddress pSAREA, int fd, + const __DRIextension **extensions, + const __DRIconfig ***driver_modes, + void *loaderPrivate) +{ + (void) ddx_version; + (void) dri_version; + (void) frame_buffer; + (void) pSAREA; + (void) fd; + SWDRI_SAFERET_SWRAST(createNewScreen, scrn, extensions, driver_modes, loaderPrivate); +} + +static __DRIdrawable * +vboxdriCreateNewDrawable_Legacy(__DRIscreen *psp, const __DRIconfig *config, + drm_drawable_t hwDrawable, int renderType, + const int *attrs, void *data) +{ + (void) hwDrawable; + (void) renderType; + (void) attrs; + (void) data; + SWDRI_SAFERET_SWRAST(createNewDrawable, psp, config, data); +} + +static __DRIcontext * +vboxdriCreateNewContext_Legacy(__DRIscreen *psp, const __DRIconfig *config, + int render_type, __DRIcontext *shared, + drm_context_t hwContext, void *data) +{ + (void) render_type; + (void) hwContext; + return vboxdriCreateNewContext(psp, config, shared, data); +} + + +static const __DRIlegacyExtension vboxdriLegacyExtension = { + { __DRI_LEGACY, __DRI_LEGACY_VERSION }, + vboxdriCreateNewScreen_Legacy, + vboxdriCreateNewDrawable_Legacy, + vboxdriCreateNewContext_Legacy +}; + +static const __DRIcoreExtension vboxdriCoreExtension = { + { __DRI_CORE, __DRI_CORE_VERSION }, + vboxdriCreateNewScreen, /* driCreateNewScreen */ + vboxdriDestroyScreen, + vboxdriGetExtensions, + vboxdriGetConfigAttrib, + vboxdriIndexConfigAttrib, + vboxdriCreateNewDrawable, /* driCreateNewDrawable */ + vboxdriDestroyDrawable, + vboxdriSwapBuffers, + vboxdriCreateNewContext, + vboxdriCopyContext, + vboxdriDestroyContext, + vboxdriBindContext, + vboxdriUnbindContext +}; + +/* This structure is used by dri_util from mesa, don't rename it! */ +DECLEXPORT(const __DRIextension *) __driDriverExtensions[] = { + &vboxdriLegacyExtension.base, + &vboxdriCoreExtension.base, + NULL +}; diff --git a/src/VBox/Additions/common/crOpenGL/fakedri_drv.h b/src/VBox/Additions/common/crOpenGL/fakedri_drv.h new file mode 100644 index 00000000..5f77bd85 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/fakedri_drv.h @@ -0,0 +1,123 @@ +/* $Id: fakedri_drv.h $ */ +/** @file + * VirtualBox guest OpenGL DRI header + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef GA_INCLUDED_SRC_common_crOpenGL_fakedri_drv_h +#define GA_INCLUDED_SRC_common_crOpenGL_fakedri_drv_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "src/mesa/main/mtypes.h" +#include "src/mesa/main/dd.h" +#include "src/mesa/glapi/dispatch.h" +#include "src/mesa/glapi/glapi.h" +#include "src/mesa/glapi/glapitable.h" +#include "src/mesa/glapi/glapioffsets.h" +#include "src/mesa/drivers/dri/common/dri_util.h" +#include "GL/internal/dri_interface.h" + +#include "glx_proto.h" + +#ifdef VBOX_OGL_GLX_USE_CSTUBS +# include "dri_glx.h" +#endif + +typedef struct _vbox_glxapi_table +{ + #define GLXAPI_ENTRY(Func) PGLXFUNC_##Func Func; + #include "fakedri_glxfuncsList.h" + #undef GLXAPI_ENTRY +} fakedri_glxapi_table; + +extern fakedri_glxapi_table glxim; + +#ifdef VBOX_OGL_GLX_USE_CSTUBS +/* Extern declarations for our C stubs */ +extern void VBOXGLXENTRYTAG(glXFreeMemoryMESA)(Display *dpy, int scrn, void *pointer) ; +extern GLXContext VBOXGLXENTRYTAG(glXImportContextEXT)(Display *dpy, GLXContextID contextID) ; +extern GLXContextID VBOXGLXENTRYTAG(glXGetContextIDEXT)(const GLXContext ctx) ; +extern Bool VBOXGLXENTRYTAG(glXMakeCurrentReadSGI)(Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx) ; +extern Display * VBOXGLXENTRYTAG(glXGetCurrentDisplayEXT)(void) ; +extern void VBOXGLXENTRYTAG(glXFreeContextEXT)(Display *dpy, GLXContext ctx) ; +extern int VBOXGLXENTRYTAG(glXQueryContextInfoEXT)(Display *dpy, GLXContext ctx) ; +extern void * VBOXGLXENTRYTAG(glXAllocateMemoryMESA)(Display *dpy, int scrn, + size_t size, float readFreq, + float writeFreq, float priority); +extern GLuint VBOXGLXENTRYTAG(glXGetMemoryOffsetMESA)(Display *dpy, int scrn, const void *pointer ) ; +extern GLXPixmap VBOXGLXENTRYTAG(glXCreateGLXPixmapMESA)(Display *dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap) ; +extern void VBOXGLXENTRYTAG(glXCopyContext)( Display *dpy, GLXContext src, GLXContext dst, unsigned long mask); +extern void VBOXGLXENTRYTAG(glXUseXFont)(Font font, int first, int count, int listBase) ; +extern CR_GLXFuncPtr VBOXGLXENTRYTAG(glXGetProcAddress)(const GLubyte *name) ; +extern Bool VBOXGLXENTRYTAG(glXQueryExtension)(Display *dpy, int *errorBase, int *eventBase) ; +extern Bool VBOXGLXENTRYTAG(glXIsDirect)(Display *dpy, GLXContext ctx) ; +extern GLXPixmap VBOXGLXENTRYTAG(glXCreateGLXPixmap)(Display *dpy, XVisualInfo *vis, Pixmap pixmap) ; +extern void VBOXGLXENTRYTAG(glXSwapBuffers)(Display *dpy, GLXDrawable drawable) ; +extern GLXDrawable VBOXGLXENTRYTAG(glXGetCurrentDrawable)(void) ; +extern void VBOXGLXENTRYTAG(glXWaitGL)(void) ; +extern Display * VBOXGLXENTRYTAG(glXGetCurrentDisplay)(void) ; +extern const char * VBOXGLXENTRYTAG(glXQueryServerString)(Display *dpy, int screen, int name) ; +extern GLXContext VBOXGLXENTRYTAG(glXCreateContext)(Display *dpy, XVisualInfo *vis, GLXContext share, Bool direct) ; +extern int VBOXGLXENTRYTAG(glXGetConfig)(Display *dpy, XVisualInfo *vis, int attrib, int *value) ; +extern void VBOXGLXENTRYTAG(glXWaitX)(void) ; +extern GLXContext VBOXGLXENTRYTAG(glXGetCurrentContext)(void) ; +extern const char * VBOXGLXENTRYTAG(glXGetClientString)(Display *dpy, int name) ; +extern Bool VBOXGLXENTRYTAG(glXMakeCurrent)(Display *dpy, GLXDrawable drawable, GLXContext ctx) ; +extern void VBOXGLXENTRYTAG(glXDestroyContext)(Display *dpy, GLXContext ctx) ; +extern CR_GLXFuncPtr VBOXGLXENTRYTAG(glXGetProcAddressARB)(const GLubyte *name) ; +extern void VBOXGLXENTRYTAG(glXDestroyGLXPixmap)(Display *dpy, GLXPixmap pix) ; +extern Bool VBOXGLXENTRYTAG(glXQueryVersion)(Display *dpy, int *major, int *minor) ; +extern XVisualInfo * VBOXGLXENTRYTAG(glXChooseVisual)(Display *dpy, int screen, int *attribList) ; +extern const char * VBOXGLXENTRYTAG(glXQueryExtensionsString)(Display *dpy, int screen) ; +extern GLXPbufferSGIX VBOXGLXENTRYTAG(glXCreateGLXPbufferSGIX)(Display *dpy, GLXFBConfigSGIX config, unsigned int width, unsigned int height, int *attrib_list); +extern int VBOXGLXENTRYTAG(glXQueryGLXPbufferSGIX)(Display *dpy, GLXPbuffer pbuf, int attribute, unsigned int *value); +extern GLXFBConfigSGIX * VBOXGLXENTRYTAG(glXChooseFBConfigSGIX)(Display *dpy, int screen, int *attrib_list, int *nelements); +extern void VBOXGLXENTRYTAG(glXDestroyGLXPbufferSGIX)(Display *dpy, GLXPbuffer pbuf) ; +extern void VBOXGLXENTRYTAG(glXSelectEventSGIX)(Display *dpy, GLXDrawable drawable, unsigned long mask) ; +extern void VBOXGLXENTRYTAG(glXGetSelectedEventSGIX)(Display *dpy, GLXDrawable drawable, unsigned long *mask) ; +extern GLXFBConfigSGIX VBOXGLXENTRYTAG(glXGetFBConfigFromVisualSGIX)(Display *dpy, XVisualInfo *vis) ; +extern XVisualInfo * VBOXGLXENTRYTAG(glXGetVisualFromFBConfigSGIX)(Display *dpy, GLXFBConfig config) ; +extern GLXContext VBOXGLXENTRYTAG(glXCreateContextWithConfigSGIX)(Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); +extern GLXPixmap VBOXGLXENTRYTAG(glXCreateGLXPixmapWithConfigSGIX)(Display *dpy, GLXFBConfig config, Pixmap pixmap) ; +extern int VBOXGLXENTRYTAG(glXGetFBConfigAttribSGIX)(Display *dpy, GLXFBConfig config, int attribute, int *value) ; +extern GLXFBConfig * VBOXGLXENTRYTAG(glXChooseFBConfig)(Display *dpy, int screen, ATTRIB_TYPE *attrib_list, int *nelements) ; +extern GLXPbuffer VBOXGLXENTRYTAG(glXCreatePbuffer)(Display *dpy, GLXFBConfig config, ATTRIB_TYPE *attrib_list) ; +extern GLXPixmap VBOXGLXENTRYTAG(glXCreatePixmap)(Display *dpy, GLXFBConfig config, Pixmap pixmap, const ATTRIB_TYPE *attrib_list) ; +extern GLXWindow VBOXGLXENTRYTAG(glXCreateWindow)(Display *dpy, GLXFBConfig config, Window win, ATTRIB_TYPE *attrib_list) ; +extern GLXContext VBOXGLXENTRYTAG(glXCreateNewContext)(Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); +extern void VBOXGLXENTRYTAG(glXDestroyPbuffer)(Display *dpy, GLXPbuffer pbuf) ; +extern void VBOXGLXENTRYTAG(glXDestroyPixmap)(Display *dpy, GLXPixmap pixmap) ; +extern void VBOXGLXENTRYTAG(glXDestroyWindow)(Display *dpy, GLXWindow win) ; +extern GLXDrawable VBOXGLXENTRYTAG(glXGetCurrentReadDrawable)(void) ; +extern int VBOXGLXENTRYTAG(glXGetFBConfigAttrib)(Display *dpy, GLXFBConfig config, int attribute, int *value) ; +extern GLXFBConfig * VBOXGLXENTRYTAG(glXGetFBConfigs)(Display *dpy, int screen, int *nelements) ; +extern void VBOXGLXENTRYTAG(glXGetSelectedEvent)(Display *dpy, GLXDrawable draw, unsigned long *event_mask) ; +extern XVisualInfo * VBOXGLXENTRYTAG(glXGetVisualFromFBConfig)(Display *dpy, GLXFBConfig config) ; +extern Bool VBOXGLXENTRYTAG(glXMakeContextCurrent)(Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx) ; +extern int VBOXGLXENTRYTAG(glXQueryContext)(Display *dpy, GLXContext ctx, int attribute, int *value) ; +extern void VBOXGLXENTRYTAG(glXQueryDrawable)(Display *dpy, GLXDrawable draw, int attribute, unsigned int *value) ; +extern void VBOXGLXENTRYTAG(glXSelectEvent)(Display *dpy, GLXDrawable draw, unsigned long event_mask) ; +#else +/* Extern declarations for our asm stubs */ +# define GLXAPI_ENTRY(Func) \ + extern void vbox_glX##Func;\ + extern void vbox_glX##Func##_EndProc; +# include "fakedri_glxfuncsList.h" +# undef GLXAPI_ENTRY +#endif + +#endif /* !GA_INCLUDED_SRC_common_crOpenGL_fakedri_drv_h */ + diff --git a/src/VBox/Additions/common/crOpenGL/fakedri_glfuncsList.h b/src/VBox/Additions/common/crOpenGL/fakedri_glfuncsList.h new file mode 100644 index 00000000..6928a2ab --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/fakedri_glfuncsList.h @@ -0,0 +1,730 @@ +/* $Id: fakedri_glfuncsList.h $ */ +/** @file + * VBox OpenGL list of opengl functions common in Mesa and vbox opengl stub + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef GLAPI_ENTRY +#error GLAPI_ENTRY should be defined. +#endif + +GLAPI_ENTRY(NewList) +GLAPI_ENTRY(EndList) +GLAPI_ENTRY(CallList) +GLAPI_ENTRY(CallLists) +GLAPI_ENTRY(DeleteLists) +GLAPI_ENTRY(GenLists) +GLAPI_ENTRY(ListBase) +GLAPI_ENTRY(Begin) +GLAPI_ENTRY(Bitmap) +GLAPI_ENTRY(Color3b) +GLAPI_ENTRY(Color3bv) +GLAPI_ENTRY(Color3d) +GLAPI_ENTRY(Color3dv) +GLAPI_ENTRY(Color3f) +GLAPI_ENTRY(Color3fv) +GLAPI_ENTRY(Color3i) +GLAPI_ENTRY(Color3iv) +GLAPI_ENTRY(Color3s) +GLAPI_ENTRY(Color3sv) +GLAPI_ENTRY(Color3ub) +GLAPI_ENTRY(Color3ubv) +GLAPI_ENTRY(Color3ui) +GLAPI_ENTRY(Color3uiv) +GLAPI_ENTRY(Color3us) +GLAPI_ENTRY(Color3usv) +GLAPI_ENTRY(Color4b) +GLAPI_ENTRY(Color4bv) +GLAPI_ENTRY(Color4d) +GLAPI_ENTRY(Color4dv) +GLAPI_ENTRY(Color4f) +GLAPI_ENTRY(Color4fv) +GLAPI_ENTRY(Color4i) +GLAPI_ENTRY(Color4iv) +GLAPI_ENTRY(Color4s) +GLAPI_ENTRY(Color4sv) +GLAPI_ENTRY(Color4ub) +GLAPI_ENTRY(Color4ubv) +GLAPI_ENTRY(Color4ui) +GLAPI_ENTRY(Color4uiv) +GLAPI_ENTRY(Color4us) +GLAPI_ENTRY(Color4usv) +GLAPI_ENTRY(EdgeFlag) +GLAPI_ENTRY(EdgeFlagv) +GLAPI_ENTRY(End) +GLAPI_ENTRY(Indexd) +GLAPI_ENTRY(Indexdv) +GLAPI_ENTRY(Indexf) +GLAPI_ENTRY(Indexfv) +GLAPI_ENTRY(Indexi) +GLAPI_ENTRY(Indexiv) +GLAPI_ENTRY(Indexs) +GLAPI_ENTRY(Indexsv) +GLAPI_ENTRY(Normal3b) +GLAPI_ENTRY(Normal3bv) +GLAPI_ENTRY(Normal3d) +GLAPI_ENTRY(Normal3dv) +GLAPI_ENTRY(Normal3f) +GLAPI_ENTRY(Normal3fv) +GLAPI_ENTRY(Normal3i) +GLAPI_ENTRY(Normal3iv) +GLAPI_ENTRY(Normal3s) +GLAPI_ENTRY(Normal3sv) +GLAPI_ENTRY(RasterPos2d) +GLAPI_ENTRY(RasterPos2dv) +GLAPI_ENTRY(RasterPos2f) +GLAPI_ENTRY(RasterPos2fv) +GLAPI_ENTRY(RasterPos2i) +GLAPI_ENTRY(RasterPos2iv) +GLAPI_ENTRY(RasterPos2s) +GLAPI_ENTRY(RasterPos2sv) +GLAPI_ENTRY(RasterPos3d) +GLAPI_ENTRY(RasterPos3dv) +GLAPI_ENTRY(RasterPos3f) +GLAPI_ENTRY(RasterPos3fv) +GLAPI_ENTRY(RasterPos3i) +GLAPI_ENTRY(RasterPos3iv) +GLAPI_ENTRY(RasterPos3s) +GLAPI_ENTRY(RasterPos3sv) +GLAPI_ENTRY(RasterPos4d) +GLAPI_ENTRY(RasterPos4dv) +GLAPI_ENTRY(RasterPos4f) +GLAPI_ENTRY(RasterPos4fv) +GLAPI_ENTRY(RasterPos4i) +GLAPI_ENTRY(RasterPos4iv) +GLAPI_ENTRY(RasterPos4s) +GLAPI_ENTRY(RasterPos4sv) +GLAPI_ENTRY(Rectd) +GLAPI_ENTRY(Rectdv) +GLAPI_ENTRY(Rectf) +GLAPI_ENTRY(Rectfv) +GLAPI_ENTRY(Recti) +GLAPI_ENTRY(Rectiv) +GLAPI_ENTRY(Rects) +GLAPI_ENTRY(Rectsv) +GLAPI_ENTRY(TexCoord1d) +GLAPI_ENTRY(TexCoord1dv) +GLAPI_ENTRY(TexCoord1f) +GLAPI_ENTRY(TexCoord1fv) +GLAPI_ENTRY(TexCoord1i) +GLAPI_ENTRY(TexCoord1iv) +GLAPI_ENTRY(TexCoord1s) +GLAPI_ENTRY(TexCoord1sv) +GLAPI_ENTRY(TexCoord2d) +GLAPI_ENTRY(TexCoord2dv) +GLAPI_ENTRY(TexCoord2f) +GLAPI_ENTRY(TexCoord2fv) +GLAPI_ENTRY(TexCoord2i) +GLAPI_ENTRY(TexCoord2iv) +GLAPI_ENTRY(TexCoord2s) +GLAPI_ENTRY(TexCoord2sv) +GLAPI_ENTRY(TexCoord3d) +GLAPI_ENTRY(TexCoord3dv) +GLAPI_ENTRY(TexCoord3f) +GLAPI_ENTRY(TexCoord3fv) +GLAPI_ENTRY(TexCoord3i) +GLAPI_ENTRY(TexCoord3iv) +GLAPI_ENTRY(TexCoord3s) +GLAPI_ENTRY(TexCoord3sv) +GLAPI_ENTRY(TexCoord4d) +GLAPI_ENTRY(TexCoord4dv) +GLAPI_ENTRY(TexCoord4f) +GLAPI_ENTRY(TexCoord4fv) +GLAPI_ENTRY(TexCoord4i) +GLAPI_ENTRY(TexCoord4iv) +GLAPI_ENTRY(TexCoord4s) +GLAPI_ENTRY(TexCoord4sv) +GLAPI_ENTRY(Vertex2d) +GLAPI_ENTRY(Vertex2dv) +GLAPI_ENTRY(Vertex2f) +GLAPI_ENTRY(Vertex2fv) +GLAPI_ENTRY(Vertex2i) +GLAPI_ENTRY(Vertex2iv) +GLAPI_ENTRY(Vertex2s) +GLAPI_ENTRY(Vertex2sv) +GLAPI_ENTRY(Vertex3d) +GLAPI_ENTRY(Vertex3dv) +GLAPI_ENTRY(Vertex3f) +GLAPI_ENTRY(Vertex3fv) +GLAPI_ENTRY(Vertex3i) +GLAPI_ENTRY(Vertex3iv) +GLAPI_ENTRY(Vertex3s) +GLAPI_ENTRY(Vertex3sv) +GLAPI_ENTRY(Vertex4d) +GLAPI_ENTRY(Vertex4dv) +GLAPI_ENTRY(Vertex4f) +GLAPI_ENTRY(Vertex4fv) +GLAPI_ENTRY(Vertex4i) +GLAPI_ENTRY(Vertex4iv) +GLAPI_ENTRY(Vertex4s) +GLAPI_ENTRY(Vertex4sv) +GLAPI_ENTRY(ClipPlane) +GLAPI_ENTRY(ColorMaterial) +GLAPI_ENTRY(CullFace) +GLAPI_ENTRY(Fogf) +GLAPI_ENTRY(Fogfv) +GLAPI_ENTRY(Fogi) +GLAPI_ENTRY(Fogiv) +GLAPI_ENTRY(FrontFace) +GLAPI_ENTRY(Hint) +GLAPI_ENTRY(Lightf) +GLAPI_ENTRY(Lightfv) +GLAPI_ENTRY(Lighti) +GLAPI_ENTRY(Lightiv) +GLAPI_ENTRY(LightModelf) +GLAPI_ENTRY(LightModelfv) +GLAPI_ENTRY(LightModeli) +GLAPI_ENTRY(LightModeliv) +GLAPI_ENTRY(LineStipple) +GLAPI_ENTRY(LineWidth) +GLAPI_ENTRY(Materialf) +GLAPI_ENTRY(Materialfv) +GLAPI_ENTRY(Materiali) +GLAPI_ENTRY(Materialiv) +GLAPI_ENTRY(PointSize) +GLAPI_ENTRY(PolygonMode) +GLAPI_ENTRY(PolygonStipple) +GLAPI_ENTRY(Scissor) +GLAPI_ENTRY(ShadeModel) +GLAPI_ENTRY(TexParameterf) +GLAPI_ENTRY(TexParameterfv) +GLAPI_ENTRY(TexParameteri) +GLAPI_ENTRY(TexParameteriv) +GLAPI_ENTRY(TexImage1D) +GLAPI_ENTRY(TexImage2D) +GLAPI_ENTRY(TexEnvf) +GLAPI_ENTRY(TexEnvfv) +GLAPI_ENTRY(TexEnvi) +GLAPI_ENTRY(TexEnviv) +GLAPI_ENTRY(TexGend) +GLAPI_ENTRY(TexGendv) +GLAPI_ENTRY(TexGenf) +GLAPI_ENTRY(TexGenfv) +GLAPI_ENTRY(TexGeni) +GLAPI_ENTRY(TexGeniv) +GLAPI_ENTRY(FeedbackBuffer) +GLAPI_ENTRY(SelectBuffer) +GLAPI_ENTRY(RenderMode) +GLAPI_ENTRY(InitNames) +GLAPI_ENTRY(LoadName) +GLAPI_ENTRY(PassThrough) +GLAPI_ENTRY(PopName) +GLAPI_ENTRY(PushName) +GLAPI_ENTRY(DrawBuffer) +GLAPI_ENTRY(Clear) +GLAPI_ENTRY(ClearAccum) +GLAPI_ENTRY(ClearIndex) +GLAPI_ENTRY(ClearColor) +GLAPI_ENTRY(ClearStencil) +GLAPI_ENTRY(ClearDepth) +GLAPI_ENTRY(StencilMask) +GLAPI_ENTRY(ColorMask) +GLAPI_ENTRY(DepthMask) +GLAPI_ENTRY(IndexMask) +GLAPI_ENTRY(Accum) +GLAPI_ENTRY(Disable) +GLAPI_ENTRY(Enable) +GLAPI_ENTRY(Finish) +GLAPI_ENTRY(Flush) +GLAPI_ENTRY(PopAttrib) +GLAPI_ENTRY(PushAttrib) +GLAPI_ENTRY(Map1d) +GLAPI_ENTRY(Map1f) +GLAPI_ENTRY(Map2d) +GLAPI_ENTRY(Map2f) +GLAPI_ENTRY(MapGrid1d) +GLAPI_ENTRY(MapGrid1f) +GLAPI_ENTRY(MapGrid2d) +GLAPI_ENTRY(MapGrid2f) +GLAPI_ENTRY(EvalCoord1d) +GLAPI_ENTRY(EvalCoord1dv) +GLAPI_ENTRY(EvalCoord1f) +GLAPI_ENTRY(EvalCoord1fv) +GLAPI_ENTRY(EvalCoord2d) +GLAPI_ENTRY(EvalCoord2dv) +GLAPI_ENTRY(EvalCoord2f) +GLAPI_ENTRY(EvalCoord2fv) +GLAPI_ENTRY(EvalMesh1) +GLAPI_ENTRY(EvalPoint1) +GLAPI_ENTRY(EvalMesh2) +GLAPI_ENTRY(EvalPoint2) +GLAPI_ENTRY(AlphaFunc) +GLAPI_ENTRY(BlendFunc) +GLAPI_ENTRY(LogicOp) +GLAPI_ENTRY(StencilFunc) +GLAPI_ENTRY(StencilOp) +GLAPI_ENTRY(DepthFunc) +GLAPI_ENTRY(PixelZoom) +GLAPI_ENTRY(PixelTransferf) +GLAPI_ENTRY(PixelTransferi) +GLAPI_ENTRY(PixelStoref) +GLAPI_ENTRY(PixelStorei) +GLAPI_ENTRY(PixelMapfv) +GLAPI_ENTRY(PixelMapuiv) +GLAPI_ENTRY(PixelMapusv) +GLAPI_ENTRY(ReadBuffer) +GLAPI_ENTRY(CopyPixels) +GLAPI_ENTRY(ReadPixels) +GLAPI_ENTRY(DrawPixels) +GLAPI_ENTRY(GetBooleanv) +GLAPI_ENTRY(GetClipPlane) +GLAPI_ENTRY(GetDoublev) +GLAPI_ENTRY(GetError) +GLAPI_ENTRY(GetFloatv) +GLAPI_ENTRY(GetIntegerv) +GLAPI_ENTRY(GetLightfv) +GLAPI_ENTRY(GetLightiv) +GLAPI_ENTRY(GetMapdv) +GLAPI_ENTRY(GetMapfv) +GLAPI_ENTRY(GetMapiv) +GLAPI_ENTRY(GetMaterialfv) +GLAPI_ENTRY(GetMaterialiv) +GLAPI_ENTRY(GetPixelMapfv) +GLAPI_ENTRY(GetPixelMapuiv) +GLAPI_ENTRY(GetPixelMapusv) +GLAPI_ENTRY(GetPolygonStipple) +GLAPI_ENTRY(GetString) +GLAPI_ENTRY(GetTexEnvfv) +GLAPI_ENTRY(GetTexEnviv) +GLAPI_ENTRY(GetTexGendv) +GLAPI_ENTRY(GetTexGenfv) +GLAPI_ENTRY(GetTexGeniv) +GLAPI_ENTRY(GetTexImage) +GLAPI_ENTRY(GetTexParameterfv) +GLAPI_ENTRY(GetTexParameteriv) +GLAPI_ENTRY(GetTexLevelParameterfv) +GLAPI_ENTRY(GetTexLevelParameteriv) +GLAPI_ENTRY(IsEnabled) +GLAPI_ENTRY(IsList) +GLAPI_ENTRY(DepthRange) +GLAPI_ENTRY(Frustum) +GLAPI_ENTRY(LoadIdentity) +GLAPI_ENTRY(LoadMatrixf) +GLAPI_ENTRY(LoadMatrixd) +GLAPI_ENTRY(MatrixMode) +GLAPI_ENTRY(MultMatrixf) +GLAPI_ENTRY(MultMatrixd) +GLAPI_ENTRY(Ortho) +GLAPI_ENTRY(PopMatrix) +GLAPI_ENTRY(PushMatrix) +GLAPI_ENTRY(Rotated) +GLAPI_ENTRY(Rotatef) +GLAPI_ENTRY(Scaled) +GLAPI_ENTRY(Scalef) +GLAPI_ENTRY(Translated) +GLAPI_ENTRY(Translatef) +GLAPI_ENTRY(Viewport) +GLAPI_ENTRY(ArrayElement) +GLAPI_ENTRY(BindTexture) +GLAPI_ENTRY(ColorPointer) +GLAPI_ENTRY(DisableClientState) +GLAPI_ENTRY(DrawArrays) +GLAPI_ENTRY(DrawElements) +GLAPI_ENTRY(EdgeFlagPointer) +GLAPI_ENTRY(EnableClientState) +GLAPI_ENTRY(IndexPointer) +GLAPI_ENTRY(Indexub) +GLAPI_ENTRY(Indexubv) +GLAPI_ENTRY(InterleavedArrays) +GLAPI_ENTRY(NormalPointer) +GLAPI_ENTRY(PolygonOffset) +GLAPI_ENTRY(TexCoordPointer) +GLAPI_ENTRY(VertexPointer) +GLAPI_ENTRY(AreTexturesResident) +GLAPI_ENTRY(CopyTexImage1D) +GLAPI_ENTRY(CopyTexImage2D) +GLAPI_ENTRY(CopyTexSubImage1D) +GLAPI_ENTRY(CopyTexSubImage2D) +GLAPI_ENTRY(DeleteTextures) +GLAPI_ENTRY(GenTextures) +GLAPI_ENTRY(GetPointerv) +GLAPI_ENTRY(IsTexture) +GLAPI_ENTRY(PrioritizeTextures) +GLAPI_ENTRY(TexSubImage1D) +GLAPI_ENTRY(TexSubImage2D) +GLAPI_ENTRY(PopClientAttrib) +GLAPI_ENTRY(PushClientAttrib) +GLAPI_ENTRY(BlendColor) +GLAPI_ENTRY(BlendEquation) +GLAPI_ENTRY(DrawRangeElements) +GLAPI_ENTRY(ColorTable) +GLAPI_ENTRY(ColorTableParameterfv) +GLAPI_ENTRY(ColorTableParameteriv) +GLAPI_ENTRY(CopyColorTable) +GLAPI_ENTRY(GetColorTable) +GLAPI_ENTRY(GetColorTableParameterfv) +GLAPI_ENTRY(GetColorTableParameteriv) +GLAPI_ENTRY(ColorSubTable) +GLAPI_ENTRY(CopyColorSubTable) +GLAPI_ENTRY(ConvolutionFilter1D) +GLAPI_ENTRY(ConvolutionFilter2D) +GLAPI_ENTRY(ConvolutionParameterf) +GLAPI_ENTRY(ConvolutionParameterfv) +GLAPI_ENTRY(ConvolutionParameteri) +GLAPI_ENTRY(ConvolutionParameteriv) +GLAPI_ENTRY(CopyConvolutionFilter1D) +GLAPI_ENTRY(CopyConvolutionFilter2D) +GLAPI_ENTRY(GetConvolutionFilter) +GLAPI_ENTRY(GetConvolutionParameterfv) +GLAPI_ENTRY(GetConvolutionParameteriv) +GLAPI_ENTRY(GetSeparableFilter) +GLAPI_ENTRY(SeparableFilter2D) +GLAPI_ENTRY(GetHistogram) +GLAPI_ENTRY(GetHistogramParameterfv) +GLAPI_ENTRY(GetHistogramParameteriv) +GLAPI_ENTRY(GetMinmax) +GLAPI_ENTRY(GetMinmaxParameterfv) +GLAPI_ENTRY(GetMinmaxParameteriv) +GLAPI_ENTRY(Histogram) +GLAPI_ENTRY(Minmax) +GLAPI_ENTRY(ResetHistogram) +GLAPI_ENTRY(ResetMinmax) +GLAPI_ENTRY(TexImage3D) +GLAPI_ENTRY(TexSubImage3D) +GLAPI_ENTRY(CopyTexSubImage3D) +GLAPI_ENTRY(ActiveTextureARB) +GLAPI_ENTRY(ClientActiveTextureARB) +GLAPI_ENTRY(MultiTexCoord1dARB) +GLAPI_ENTRY(MultiTexCoord1dvARB) +GLAPI_ENTRY(MultiTexCoord1fARB) +GLAPI_ENTRY(MultiTexCoord1fvARB) +GLAPI_ENTRY(MultiTexCoord1iARB) +GLAPI_ENTRY(MultiTexCoord1ivARB) +GLAPI_ENTRY(MultiTexCoord1sARB) +GLAPI_ENTRY(MultiTexCoord1svARB) +GLAPI_ENTRY(MultiTexCoord2dARB) +GLAPI_ENTRY(MultiTexCoord2dvARB) +GLAPI_ENTRY(MultiTexCoord2fARB) +GLAPI_ENTRY(MultiTexCoord2fvARB) +GLAPI_ENTRY(MultiTexCoord2iARB) +GLAPI_ENTRY(MultiTexCoord2ivARB) +GLAPI_ENTRY(MultiTexCoord2sARB) +GLAPI_ENTRY(MultiTexCoord2svARB) +GLAPI_ENTRY(MultiTexCoord3dARB) +GLAPI_ENTRY(MultiTexCoord3dvARB) +GLAPI_ENTRY(MultiTexCoord3fARB) +GLAPI_ENTRY(MultiTexCoord3fvARB) +GLAPI_ENTRY(MultiTexCoord3iARB) +GLAPI_ENTRY(MultiTexCoord3ivARB) +GLAPI_ENTRY(MultiTexCoord3sARB) +GLAPI_ENTRY(MultiTexCoord3svARB) +GLAPI_ENTRY(MultiTexCoord4dARB) +GLAPI_ENTRY(MultiTexCoord4dvARB) +GLAPI_ENTRY(MultiTexCoord4fARB) +GLAPI_ENTRY(MultiTexCoord4fvARB) +GLAPI_ENTRY(MultiTexCoord4iARB) +GLAPI_ENTRY(MultiTexCoord4ivARB) +GLAPI_ENTRY(MultiTexCoord4sARB) +GLAPI_ENTRY(MultiTexCoord4svARB) +GLAPI_ENTRY(AttachShader) +GLAPI_ENTRY(CreateProgram) +GLAPI_ENTRY(CreateShader) +GLAPI_ENTRY(DeleteProgram) +GLAPI_ENTRY(DeleteShader) +GLAPI_ENTRY(DetachShader) +GLAPI_ENTRY(GetAttachedShaders) +GLAPI_ENTRY(GetProgramInfoLog) +GLAPI_ENTRY(GetProgramiv) +GLAPI_ENTRY(GetShaderInfoLog) +GLAPI_ENTRY(GetShaderiv) +GLAPI_ENTRY(IsProgram) +GLAPI_ENTRY(IsShader) +GLAPI_ENTRY(StencilFuncSeparate) +GLAPI_ENTRY(StencilMaskSeparate) +GLAPI_ENTRY(StencilOpSeparate) +GLAPI_ENTRY(UniformMatrix2x3fv) +GLAPI_ENTRY(UniformMatrix2x4fv) +GLAPI_ENTRY(UniformMatrix3x2fv) +GLAPI_ENTRY(UniformMatrix3x4fv) +GLAPI_ENTRY(UniformMatrix4x2fv) +GLAPI_ENTRY(UniformMatrix4x3fv) +GLAPI_ENTRY(LoadTransposeMatrixdARB) +GLAPI_ENTRY(LoadTransposeMatrixfARB) +GLAPI_ENTRY(MultTransposeMatrixdARB) +GLAPI_ENTRY(MultTransposeMatrixfARB) +GLAPI_ENTRY(SampleCoverageARB) +GLAPI_ENTRY(CompressedTexImage1DARB) +GLAPI_ENTRY(CompressedTexImage2DARB) +GLAPI_ENTRY(CompressedTexImage3DARB) +GLAPI_ENTRY(CompressedTexSubImage1DARB) +GLAPI_ENTRY(CompressedTexSubImage2DARB) +GLAPI_ENTRY(CompressedTexSubImage3DARB) +GLAPI_ENTRY(GetCompressedTexImageARB) +GLAPI_ENTRY(DisableVertexAttribArrayARB) +GLAPI_ENTRY(EnableVertexAttribArrayARB) +GLAPI_ENTRY(GetProgramEnvParameterdvARB) +GLAPI_ENTRY(GetProgramEnvParameterfvARB) +GLAPI_ENTRY(GetProgramLocalParameterdvARB) +GLAPI_ENTRY(GetProgramLocalParameterfvARB) +GLAPI_ENTRY(GetProgramStringARB) +GLAPI_ENTRY(GetProgramivARB) +GLAPI_ENTRY(GetVertexAttribdvARB) +GLAPI_ENTRY(GetVertexAttribfvARB) +GLAPI_ENTRY(GetVertexAttribivARB) +GLAPI_ENTRY(ProgramEnvParameter4dARB) +GLAPI_ENTRY(ProgramEnvParameter4dvARB) +GLAPI_ENTRY(ProgramEnvParameter4fARB) +GLAPI_ENTRY(ProgramEnvParameter4fvARB) +GLAPI_ENTRY(ProgramLocalParameter4dARB) +GLAPI_ENTRY(ProgramLocalParameter4dvARB) +GLAPI_ENTRY(ProgramLocalParameter4fARB) +GLAPI_ENTRY(ProgramLocalParameter4fvARB) +GLAPI_ENTRY(ProgramStringARB) +GLAPI_ENTRY(VertexAttrib1dARB) +GLAPI_ENTRY(VertexAttrib1dvARB) +GLAPI_ENTRY(VertexAttrib1fARB) +GLAPI_ENTRY(VertexAttrib1fvARB) +GLAPI_ENTRY(VertexAttrib1sARB) +GLAPI_ENTRY(VertexAttrib1svARB) +GLAPI_ENTRY(VertexAttrib2dARB) +GLAPI_ENTRY(VertexAttrib2dvARB) +GLAPI_ENTRY(VertexAttrib2fARB) +GLAPI_ENTRY(VertexAttrib2fvARB) +GLAPI_ENTRY(VertexAttrib2sARB) +GLAPI_ENTRY(VertexAttrib2svARB) +GLAPI_ENTRY(VertexAttrib3dARB) +GLAPI_ENTRY(VertexAttrib3dvARB) +GLAPI_ENTRY(VertexAttrib3fARB) +GLAPI_ENTRY(VertexAttrib3fvARB) +GLAPI_ENTRY(VertexAttrib3sARB) +GLAPI_ENTRY(VertexAttrib3svARB) +GLAPI_ENTRY(VertexAttrib4NbvARB) +GLAPI_ENTRY(VertexAttrib4NivARB) +GLAPI_ENTRY(VertexAttrib4NsvARB) +GLAPI_ENTRY(VertexAttrib4NubARB) +GLAPI_ENTRY(VertexAttrib4NubvARB) +GLAPI_ENTRY(VertexAttrib4NuivARB) +GLAPI_ENTRY(VertexAttrib4NusvARB) +GLAPI_ENTRY(VertexAttrib4bvARB) +GLAPI_ENTRY(VertexAttrib4dARB) +GLAPI_ENTRY(VertexAttrib4dvARB) +GLAPI_ENTRY(VertexAttrib4fARB) +GLAPI_ENTRY(VertexAttrib4fvARB) +GLAPI_ENTRY(VertexAttrib4ivARB) +GLAPI_ENTRY(VertexAttrib4sARB) +GLAPI_ENTRY(VertexAttrib4svARB) +GLAPI_ENTRY(VertexAttrib4ubvARB) +GLAPI_ENTRY(VertexAttrib4uivARB) +GLAPI_ENTRY(VertexAttrib4usvARB) +GLAPI_ENTRY(VertexAttribPointerARB) +GLAPI_ENTRY(BindBufferARB) +GLAPI_ENTRY(BufferDataARB) +GLAPI_ENTRY(BufferSubDataARB) +GLAPI_ENTRY(DeleteBuffersARB) +GLAPI_ENTRY(GenBuffersARB) +GLAPI_ENTRY(GetBufferParameterivARB) +GLAPI_ENTRY(GetBufferPointervARB) +GLAPI_ENTRY(GetBufferSubDataARB) +GLAPI_ENTRY(IsBufferARB) +GLAPI_ENTRY(MapBufferARB) +GLAPI_ENTRY(UnmapBufferARB) +GLAPI_ENTRY(BeginQueryARB) +GLAPI_ENTRY(DeleteQueriesARB) +GLAPI_ENTRY(EndQueryARB) +GLAPI_ENTRY(GenQueriesARB) +GLAPI_ENTRY(GetQueryObjectivARB) +GLAPI_ENTRY(GetQueryObjectuivARB) +GLAPI_ENTRY(GetQueryivARB) +GLAPI_ENTRY(IsQueryARB) +GLAPI_ENTRY(AttachObjectARB) +GLAPI_ENTRY(CompileShaderARB) +GLAPI_ENTRY(CreateProgramObjectARB) +GLAPI_ENTRY(CreateShaderObjectARB) +GLAPI_ENTRY(DeleteObjectARB) +GLAPI_ENTRY(DetachObjectARB) +GLAPI_ENTRY(GetActiveUniformARB) +GLAPI_ENTRY(GetAttachedObjectsARB) +GLAPI_ENTRY(GetHandleARB) +GLAPI_ENTRY(GetInfoLogARB) +GLAPI_ENTRY(GetObjectParameterfvARB) +GLAPI_ENTRY(GetObjectParameterivARB) +GLAPI_ENTRY(GetShaderSourceARB) +GLAPI_ENTRY(GetUniformLocationARB) +GLAPI_ENTRY(GetUniformfvARB) +GLAPI_ENTRY(GetUniformivARB) +GLAPI_ENTRY(LinkProgramARB) +GLAPI_ENTRY(ShaderSourceARB) +GLAPI_ENTRY(Uniform1fARB) +GLAPI_ENTRY(Uniform1fvARB) +GLAPI_ENTRY(Uniform1iARB) +GLAPI_ENTRY(Uniform1ivARB) +GLAPI_ENTRY(Uniform2fARB) +GLAPI_ENTRY(Uniform2fvARB) +GLAPI_ENTRY(Uniform2iARB) +GLAPI_ENTRY(Uniform2ivARB) +GLAPI_ENTRY(Uniform3fARB) +GLAPI_ENTRY(Uniform3fvARB) +GLAPI_ENTRY(Uniform3iARB) +GLAPI_ENTRY(Uniform3ivARB) +GLAPI_ENTRY(Uniform4fARB) +GLAPI_ENTRY(Uniform4fvARB) +GLAPI_ENTRY(Uniform4iARB) +GLAPI_ENTRY(Uniform4ivARB) +GLAPI_ENTRY(UniformMatrix2fvARB) +GLAPI_ENTRY(UniformMatrix3fvARB) +GLAPI_ENTRY(UniformMatrix4fvARB) +GLAPI_ENTRY(UseProgramObjectARB) +GLAPI_ENTRY(ValidateProgramARB) +GLAPI_ENTRY(BindAttribLocationARB) +GLAPI_ENTRY(GetActiveAttribARB) +GLAPI_ENTRY(GetAttribLocationARB) +//GLAPI_ENTRY(DrawBuffersARB) +//GLAPI_ENTRY(PolygonOffsetEXT) +//GLAPI_ENTRY(GetPixelTexGenParameterfvSGIS) +//GLAPI_ENTRY(GetPixelTexGenParameterivSGIS) +//GLAPI_ENTRY(PixelTexGenParameterfSGIS) +//GLAPI_ENTRY(PixelTexGenParameterfvSGIS) +//GLAPI_ENTRY(PixelTexGenParameteriSGIS) +//GLAPI_ENTRY(PixelTexGenParameterivSGIS) +GLAPI_ENTRY(PointParameterfEXT) +GLAPI_ENTRY(PointParameterfvEXT) +GLAPI_ENTRY(LockArraysEXT) +GLAPI_ENTRY(UnlockArraysEXT) +//GLAPI_ENTRY(CullParameterdvEXT) +//GLAPI_ENTRY(CullParameterfvEXT) +GLAPI_ENTRY(SecondaryColor3bEXT) +GLAPI_ENTRY(SecondaryColor3bvEXT) +GLAPI_ENTRY(SecondaryColor3dEXT) +GLAPI_ENTRY(SecondaryColor3dvEXT) +GLAPI_ENTRY(SecondaryColor3fEXT) +GLAPI_ENTRY(SecondaryColor3fvEXT) +GLAPI_ENTRY(SecondaryColor3iEXT) +GLAPI_ENTRY(SecondaryColor3ivEXT) +GLAPI_ENTRY(SecondaryColor3sEXT) +GLAPI_ENTRY(SecondaryColor3svEXT) +GLAPI_ENTRY(SecondaryColor3ubEXT) +GLAPI_ENTRY(SecondaryColor3ubvEXT) +GLAPI_ENTRY(SecondaryColor3uiEXT) +GLAPI_ENTRY(SecondaryColor3uivEXT) +GLAPI_ENTRY(SecondaryColor3usEXT) +GLAPI_ENTRY(SecondaryColor3usvEXT) +GLAPI_ENTRY(SecondaryColorPointerEXT) +GLAPI_ENTRY(MultiDrawArraysEXT) +GLAPI_ENTRY(MultiDrawElementsEXT) +GLAPI_ENTRY(FogCoordPointerEXT) +GLAPI_ENTRY(FogCoorddEXT) +GLAPI_ENTRY(FogCoorddvEXT) +GLAPI_ENTRY(FogCoordfEXT) +GLAPI_ENTRY(FogCoordfvEXT) +//GLAPI_ENTRY(PixelTexGenSGIX) +GLAPI_ENTRY(BlendFuncSeparateEXT) +GLAPI_ENTRY(FlushVertexArrayRangeNV) +GLAPI_ENTRY(VertexArrayRangeNV) +GLAPI_ENTRY(CombinerInputNV) +GLAPI_ENTRY(CombinerOutputNV) +GLAPI_ENTRY(CombinerParameterfNV) +GLAPI_ENTRY(CombinerParameterfvNV) +GLAPI_ENTRY(CombinerParameteriNV) +GLAPI_ENTRY(CombinerParameterivNV) +GLAPI_ENTRY(FinalCombinerInputNV) +GLAPI_ENTRY(GetCombinerInputParameterfvNV) +GLAPI_ENTRY(GetCombinerInputParameterivNV) +GLAPI_ENTRY(GetCombinerOutputParameterfvNV) +GLAPI_ENTRY(GetCombinerOutputParameterivNV) +GLAPI_ENTRY(GetFinalCombinerInputParameterfvNV) +GLAPI_ENTRY(GetFinalCombinerInputParameterivNV) +GLAPI_ENTRY(DeleteFencesNV) +GLAPI_ENTRY(FinishFenceNV) +GLAPI_ENTRY(GenFencesNV) +GLAPI_ENTRY(GetFenceivNV) +GLAPI_ENTRY(IsFenceNV) +GLAPI_ENTRY(SetFenceNV) +GLAPI_ENTRY(TestFenceNV) +GLAPI_ENTRY(AreProgramsResidentNV) +GLAPI_ENTRY(BindProgramNV) +GLAPI_ENTRY(DeleteProgramsNV) +GLAPI_ENTRY(ExecuteProgramNV) +GLAPI_ENTRY(GenProgramsNV) +GLAPI_ENTRY(GetProgramParameterdvNV) +GLAPI_ENTRY(GetProgramParameterfvNV) +GLAPI_ENTRY(GetProgramStringNV) +GLAPI_ENTRY(GetProgramivNV) +GLAPI_ENTRY(GetTrackMatrixivNV) +GLAPI_ENTRY(GetVertexAttribPointervNV) +GLAPI_ENTRY(GetVertexAttribdvNV) +GLAPI_ENTRY(GetVertexAttribfvNV) +GLAPI_ENTRY(GetVertexAttribivNV) +GLAPI_ENTRY(IsProgramNV) +GLAPI_ENTRY(LoadProgramNV) +GLAPI_ENTRY(ProgramParameters4dvNV) +GLAPI_ENTRY(ProgramParameters4fvNV) +GLAPI_ENTRY(RequestResidentProgramsNV) +GLAPI_ENTRY(TrackMatrixNV) +GLAPI_ENTRY(VertexAttrib1dNV) +GLAPI_ENTRY(VertexAttrib1dvNV) +GLAPI_ENTRY(VertexAttrib1fNV) +GLAPI_ENTRY(VertexAttrib1fvNV) +GLAPI_ENTRY(VertexAttrib1sNV) +GLAPI_ENTRY(VertexAttrib1svNV) +GLAPI_ENTRY(VertexAttrib2dNV) +GLAPI_ENTRY(VertexAttrib2dvNV) +GLAPI_ENTRY(VertexAttrib2fNV) +GLAPI_ENTRY(VertexAttrib2fvNV) +GLAPI_ENTRY(VertexAttrib2sNV) +GLAPI_ENTRY(VertexAttrib2svNV) +GLAPI_ENTRY(VertexAttrib3dNV) +GLAPI_ENTRY(VertexAttrib3dvNV) +GLAPI_ENTRY(VertexAttrib3fNV) +GLAPI_ENTRY(VertexAttrib3fvNV) +GLAPI_ENTRY(VertexAttrib3sNV) +GLAPI_ENTRY(VertexAttrib3svNV) +GLAPI_ENTRY(VertexAttrib4dNV) +GLAPI_ENTRY(VertexAttrib4dvNV) +GLAPI_ENTRY(VertexAttrib4fNV) +GLAPI_ENTRY(VertexAttrib4fvNV) +GLAPI_ENTRY(VertexAttrib4sNV) +GLAPI_ENTRY(VertexAttrib4svNV) +GLAPI_ENTRY(VertexAttrib4ubNV) +GLAPI_ENTRY(VertexAttrib4ubvNV) +GLAPI_ENTRY(VertexAttribPointerNV) +GLAPI_ENTRY(VertexAttribs1dvNV) +GLAPI_ENTRY(VertexAttribs1fvNV) +GLAPI_ENTRY(VertexAttribs1svNV) +GLAPI_ENTRY(VertexAttribs2dvNV) +GLAPI_ENTRY(VertexAttribs2fvNV) +GLAPI_ENTRY(VertexAttribs2svNV) +GLAPI_ENTRY(VertexAttribs3dvNV) +GLAPI_ENTRY(VertexAttribs3fvNV) +GLAPI_ENTRY(VertexAttribs3svNV) +GLAPI_ENTRY(VertexAttribs4dvNV) +GLAPI_ENTRY(VertexAttribs4fvNV) +GLAPI_ENTRY(VertexAttribs4svNV) +GLAPI_ENTRY(VertexAttribs4ubvNV) +GLAPI_ENTRY(PointParameteriNV) +GLAPI_ENTRY(PointParameterivNV) +GLAPI_ENTRY(GetProgramNamedParameterdvNV) +GLAPI_ENTRY(GetProgramNamedParameterfvNV) +GLAPI_ENTRY(ProgramNamedParameter4dNV) +GLAPI_ENTRY(ProgramNamedParameter4dvNV) +GLAPI_ENTRY(ProgramNamedParameter4fNV) +GLAPI_ENTRY(ProgramNamedParameter4fvNV) +//GLAPI_ENTRY(BlendEquationSeparateEXT) +GLAPI_ENTRY(BindFramebufferEXT) +GLAPI_ENTRY(BindRenderbufferEXT) +GLAPI_ENTRY(CheckFramebufferStatusEXT) +GLAPI_ENTRY(DeleteFramebuffersEXT) +GLAPI_ENTRY(DeleteRenderbuffersEXT) +GLAPI_ENTRY(FramebufferRenderbufferEXT) +GLAPI_ENTRY(FramebufferTexture1DEXT) +GLAPI_ENTRY(FramebufferTexture2DEXT) +GLAPI_ENTRY(FramebufferTexture3DEXT) +GLAPI_ENTRY(GenFramebuffersEXT) +GLAPI_ENTRY(GenRenderbuffersEXT) +GLAPI_ENTRY(GenerateMipmapEXT) +GLAPI_ENTRY(GetFramebufferAttachmentParameterivEXT) +GLAPI_ENTRY(GetRenderbufferParameterivEXT) +GLAPI_ENTRY(IsFramebufferEXT) +GLAPI_ENTRY(IsRenderbufferEXT) +GLAPI_ENTRY(RenderbufferStorageEXT) + diff --git a/src/VBox/Additions/common/crOpenGL/fakedri_glxfuncsList.h b/src/VBox/Additions/common/crOpenGL/fakedri_glxfuncsList.h new file mode 100644 index 00000000..c1e7274d --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/fakedri_glxfuncsList.h @@ -0,0 +1,93 @@ +/* $Id: fakedri_glxfuncsList.h $ */ +/** @file + * VBox OpenGL list of opengl functions common in Mesa and vbox opengl stub + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef GLXAPI_ENTRY +#error GLXAPI_ENTRY should be defined. +#endif + +/*This should match glX* entries which are exported by Mesa's libGL.so, + * use something like the following to get the list: + * objdump -T libGL.so|grep glX|grep -v __|awk '{print $7;};'|sed 's/glX//'|awk '{OFS=""; print "GLXAPI_ENTRY(",$1,")"}' + */ + +/* #######Note: if you change the list, don't forget to change Linux_i386_glxapi_exports.py####### */ + +GLXAPI_ENTRY(CopyContext) +GLXAPI_ENTRY(UseXFont) +/*GLXAPI_ENTRY(GetDriverConfig)*/ +GLXAPI_ENTRY(GetProcAddress) +GLXAPI_ENTRY(QueryExtension) +GLXAPI_ENTRY(IsDirect) +GLXAPI_ENTRY(DestroyGLXPbufferSGIX) +GLXAPI_ENTRY(QueryGLXPbufferSGIX) +GLXAPI_ENTRY(CreateGLXPixmap) +GLXAPI_ENTRY(CreateGLXPixmapWithConfigSGIX) +GLXAPI_ENTRY(QueryContext) +GLXAPI_ENTRY(CreateContextWithConfigSGIX) +GLXAPI_ENTRY(SwapBuffers) +GLXAPI_ENTRY(CreateNewContext) +GLXAPI_ENTRY(SelectEventSGIX) +GLXAPI_ENTRY(GetCurrentDrawable) +GLXAPI_ENTRY(ChooseFBConfig) +GLXAPI_ENTRY(WaitGL) +GLXAPI_ENTRY(GetFBConfigs) +GLXAPI_ENTRY(CreatePixmap) +GLXAPI_ENTRY(GetSelectedEventSGIX) +GLXAPI_ENTRY(GetCurrentReadDrawable) +GLXAPI_ENTRY(GetCurrentDisplay) +GLXAPI_ENTRY(QueryServerString) +GLXAPI_ENTRY(CreateWindow) +GLXAPI_ENTRY(SelectEvent) +GLXAPI_ENTRY(GetVisualFromFBConfigSGIX) +GLXAPI_ENTRY(GetFBConfigFromVisualSGIX) +GLXAPI_ENTRY(QueryDrawable) +GLXAPI_ENTRY(CreateContext) +GLXAPI_ENTRY(GetConfig) +GLXAPI_ENTRY(CreateGLXPbufferSGIX) +GLXAPI_ENTRY(CreatePbuffer) +GLXAPI_ENTRY(ChooseFBConfigSGIX) +GLXAPI_ENTRY(WaitX) +GLXAPI_ENTRY(GetVisualFromFBConfig) +/*GLXAPI_ENTRY(GetScreenDriver)*/ +GLXAPI_ENTRY(GetFBConfigAttrib) +GLXAPI_ENTRY(GetCurrentContext) +GLXAPI_ENTRY(GetClientString) +GLXAPI_ENTRY(DestroyPixmap) +GLXAPI_ENTRY(MakeCurrent) +GLXAPI_ENTRY(DestroyContext) +GLXAPI_ENTRY(GetProcAddressARB) +GLXAPI_ENTRY(GetSelectedEvent) +GLXAPI_ENTRY(DestroyPbuffer) +GLXAPI_ENTRY(DestroyWindow) +GLXAPI_ENTRY(DestroyGLXPixmap) +GLXAPI_ENTRY(QueryVersion) +GLXAPI_ENTRY(ChooseVisual) +GLXAPI_ENTRY(MakeContextCurrent) +GLXAPI_ENTRY(QueryExtensionsString) +GLXAPI_ENTRY(GetFBConfigAttribSGIX) +#ifdef VBOXOGL_FAKEDRI +GLXAPI_ENTRY(FreeMemoryMESA) +GLXAPI_ENTRY(QueryContextInfoEXT) +GLXAPI_ENTRY(ImportContextEXT) +GLXAPI_ENTRY(GetContextIDEXT) +GLXAPI_ENTRY(MakeCurrentReadSGI) +GLXAPI_ENTRY(AllocateMemoryMESA) +GLXAPI_ENTRY(GetMemoryOffsetMESA) +GLXAPI_ENTRY(CreateGLXPixmapMESA) +GLXAPI_ENTRY(GetCurrentDisplayEXT) +GLXAPI_ENTRY(FreeContextEXT) +#endif diff --git a/src/VBox/Additions/common/crOpenGL/feedback/Makefile.kup b/src/VBox/Additions/common/crOpenGL/feedback/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/Makefile.kup diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedback.def b/src/VBox/Additions/common/crOpenGL/feedback/feedback.def new file mode 100644 index 00000000..9edc7163 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedback.def @@ -0,0 +1,6 @@ +; Copyright (c) 2001, Stanford University +; All rights reserved. +; +; See the file LICENSE.txt for information on redistributing this software. +EXPORTS +SPULoad diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedback.py b/src/VBox/Additions/common/crOpenGL/feedback/feedback.py new file mode 100755 index 00000000..181b23fd --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedback.py @@ -0,0 +1,270 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - generated by feedback.py */ +#include <stdio.h> +#include "cr_spu.h" +#include "feedbackspu.h" +#include "feedbackspu_proto.h" +#include "cr_packfunctions.h" +#include "cr_glstate.h" + +""") + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in keys: + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + if apiutil.FindSpecial( "feedback", func_name ): + print('static %s FEEDBACKSPU_APIENTRY feedbackspu_%s(%s)' % ( return_type, func_name, apiutil.MakeDeclarationString(params) )) + print('{') + print('\tfeedback_spu.super.%s(%s);' % ( func_name, apiutil.MakeCallString(params) )) + print('}') + + + +print(""" +#define CHANGE(name, func) crSPUChangeInterface((void *)&(feedback_spu.self), (void *)feedback_spu.self.name, (void *)((SPUGenericFunction) func)) +#define CHANGESWAP(name, swapfunc, regfunc) crSPUChangeInterface( (void *)&(feedback_spu.self), (void *)feedback_spu.self.name, (void *)((SPUGenericFunction) (feedback_spu.swap ? swapfunc: regfunc ))) + +static void __loadFeedbackAPI( void ) +{ +""") +for func_name in keys: + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + if apiutil.FindSpecial( "feedback", func_name ): + print('\tCHANGE(%s, crStateFeedback%s);' % (func_name, func_name )) +print(""" +} + +static void __loadSelectAPI( void ) +{ +""") +for func_name in keys: + if apiutil.FindSpecial( "select", func_name ): + print('\tCHANGE(%s, crStateSelect%s);' % (func_name, func_name )) + elif apiutil.FindSpecial( "feedback", func_name ): + print('\tCHANGE(%s, feedbackspu_%s);' % (func_name, func_name )) +print(""" +} + +static void __loadRenderAPI( void ) +{ +""") + +for func_name in keys: + return_type = apiutil.ReturnType(func_name) + if apiutil.FindSpecial( "feedback", func_name ) or apiutil.FindSpecial( "select", func_name ): + print('\tCHANGE(%s, feedbackspu_%s);' % (func_name, func_name )) +print(""" +} +""") + +print(""" +static GLint FEEDBACKSPU_APIENTRY feedbackspu_RenderMode ( GLenum mode ) +{ + feedback_spu.render_mode = mode; + + switch (mode) { + case GL_FEEDBACK: + /*printf("Switching to Feedback API\\n");*/ + __loadFeedbackAPI( ); + break; + case GL_SELECT: + /*printf("Switching to Selection API\\n");*/ + __loadSelectAPI( ); + break; + case GL_RENDER: + /*printf("Switching to Render API\\n");*/ + __loadRenderAPI( ); + break; + } + + return crStateRenderMode( mode ); +} + +static void FEEDBACKSPU_APIENTRY feedbackspu_Begin ( GLenum mode ) +{ + if (feedback_spu.render_mode == GL_FEEDBACK) + crStateFeedbackBegin( mode ); + else if (feedback_spu.render_mode == GL_SELECT) + crStateSelectBegin( mode ); + else + { + crStateBegin( mode ); + feedback_spu.super.Begin( mode ); + } +} + +static void FEEDBACKSPU_APIENTRY feedbackspu_End ( void ) +{ + if (feedback_spu.render_mode == GL_FEEDBACK) + crStateFeedbackEnd( ); + else if (feedback_spu.render_mode == GL_SELECT) + crStateSelectEnd( ); + else + { + crStateEnd( ); + feedback_spu.super.End( ); + } +} + +static void FEEDBACKSPU_APIENTRY feedbackspu_Bitmap ( GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap ) +{ + crStateBitmap( width, height, xorig, yorig, xmove, ymove, bitmap ); + + if (feedback_spu.render_mode == GL_FEEDBACK) + crStateFeedbackBitmap( width, height, xorig, yorig, xmove, ymove, bitmap ); + else if (feedback_spu.render_mode == GL_SELECT) + crStateSelectBitmap( width, height, xorig, yorig, xmove, ymove, bitmap ); + else + feedback_spu.super.Bitmap( width, height, xorig, yorig, xmove, ymove, bitmap ); +} + +static void FEEDBACKSPU_APIENTRY feedbackspu_CopyPixels( GLint x, GLint y, GLsizei width, GLsizei height, GLenum type ) +{ + if (feedback_spu.render_mode == GL_FEEDBACK) + crStateFeedbackCopyPixels( x, y, width, height, type ); + else if (feedback_spu.render_mode == GL_SELECT) + crStateSelectCopyPixels( x, y, width, height, type ); + else + feedback_spu.super.CopyPixels( x, y, width, height, type ); +} + +static void FEEDBACKSPU_APIENTRY feedbackspu_DrawPixels( GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels ) +{ + if (feedback_spu.render_mode == GL_FEEDBACK) + crStateFeedbackDrawPixels( width, height, format, type, pixels ); + else if (feedback_spu.render_mode == GL_SELECT) + crStateSelectDrawPixels( width, height, format, type, pixels ); + else + feedback_spu.super.DrawPixels( width, height, format, type, pixels ); +} + +static void FEEDBACKSPU_APIENTRY feedbackspu_GetBooleanv( GLenum pname, GLboolean *params ) + +{ + if (pname == GL_FEEDBACK_BUFFER_SIZE || + pname == GL_FEEDBACK_BUFFER_TYPE || + pname == GL_SELECTION_BUFFER_SIZE) + crStateFeedbackGetBooleanv( pname, params ); + else + if (pname == GL_VIEWPORT && feedback_spu.default_viewport) + crStateGetBooleanv( pname, params ); + else + feedback_spu.super.GetBooleanv( pname, params ); +} + +static void FEEDBACKSPU_APIENTRY feedbackspu_GetDoublev( GLenum pname, GLdouble *params ) + +{ + if (pname == GL_FEEDBACK_BUFFER_SIZE || + pname == GL_FEEDBACK_BUFFER_TYPE || + pname == GL_SELECTION_BUFFER_SIZE) + crStateFeedbackGetDoublev( pname, params ); + else + if (pname == GL_VIEWPORT && feedback_spu.default_viewport) + crStateGetDoublev( pname, params ); + else + feedback_spu.super.GetDoublev( pname, params ); +} + +static void FEEDBACKSPU_APIENTRY feedbackspu_GetFloatv( GLenum pname, GLfloat *params ) + +{ + if (pname == GL_FEEDBACK_BUFFER_SIZE || + pname == GL_FEEDBACK_BUFFER_TYPE || + pname == GL_SELECTION_BUFFER_SIZE) + crStateFeedbackGetFloatv( pname, params ); + else + if (pname == GL_VIEWPORT && feedback_spu.default_viewport) + crStateGetFloatv( pname, params ); + else + feedback_spu.super.GetFloatv( pname, params ); +} + +static void FEEDBACKSPU_APIENTRY feedbackspu_GetIntegerv( GLenum pname, GLint *params ) + +{ + if (pname == GL_FEEDBACK_BUFFER_SIZE || + pname == GL_FEEDBACK_BUFFER_TYPE || + pname == GL_SELECTION_BUFFER_SIZE) + crStateFeedbackGetIntegerv( pname, params ); + else + if (pname == GL_VIEWPORT && feedback_spu.default_viewport) + crStateGetIntegerv( pname, params ); + else + feedback_spu.super.GetIntegerv( pname, params ); +} + +SPUNamedFunctionTable _cr_feedback_table[] = { +""") + +for func_name in keys: + if apiutil.FindSpecial( "feedback_state", func_name ): + print('\t{ "%s", (SPUGenericFunction) feedbackspu_%s }, ' % ( func_name, func_name )) +print(""" + { "GetBooleanv", (SPUGenericFunction) feedbackspu_GetBooleanv }, + { "GetDoublev", (SPUGenericFunction) feedbackspu_GetDoublev }, + { "GetFloatv", (SPUGenericFunction) feedbackspu_GetFloatv }, + { "GetIntegerv", (SPUGenericFunction) feedbackspu_GetIntegerv }, + { "FeedbackBuffer", (SPUGenericFunction) crStateFeedbackBuffer }, + { "SelectBuffer", (SPUGenericFunction) crStateSelectBuffer }, + { "InitNames", (SPUGenericFunction) crStateInitNames }, + { "LoadName", (SPUGenericFunction) crStateLoadName }, + { "PushName", (SPUGenericFunction) crStatePushName }, + { "PopName", (SPUGenericFunction) crStatePopName }, + { "Begin", (SPUGenericFunction) feedbackspu_Begin }, + { "End", (SPUGenericFunction) feedbackspu_End }, + { "Bitmap", (SPUGenericFunction) feedbackspu_Bitmap }, + { "CopyPixels", (SPUGenericFunction) feedbackspu_CopyPixels }, + { "DrawPixels", (SPUGenericFunction) feedbackspu_DrawPixels }, + { "TexCoord1d", (SPUGenericFunction) feedbackspu_TexCoord1d }, + { "TexCoord1dv", (SPUGenericFunction) feedbackspu_TexCoord1dv }, + { "TexCoord1f", (SPUGenericFunction) feedbackspu_TexCoord1f }, + { "TexCoord1fv", (SPUGenericFunction) feedbackspu_TexCoord1fv }, + { "TexCoord1s", (SPUGenericFunction) feedbackspu_TexCoord1s }, + { "TexCoord1sv", (SPUGenericFunction) feedbackspu_TexCoord1sv }, + { "TexCoord1i", (SPUGenericFunction) feedbackspu_TexCoord1i }, + { "TexCoord1iv", (SPUGenericFunction) feedbackspu_TexCoord1iv }, + { "TexCoord2d", (SPUGenericFunction) feedbackspu_TexCoord2d }, + { "TexCoord2dv", (SPUGenericFunction) feedbackspu_TexCoord2dv }, + { "TexCoord2f", (SPUGenericFunction) feedbackspu_TexCoord2f }, + { "TexCoord2fv", (SPUGenericFunction) feedbackspu_TexCoord2fv }, + { "TexCoord2s", (SPUGenericFunction) feedbackspu_TexCoord2s }, + { "TexCoord2sv", (SPUGenericFunction) feedbackspu_TexCoord2sv }, + { "TexCoord2i", (SPUGenericFunction) feedbackspu_TexCoord2i }, + { "TexCoord2iv", (SPUGenericFunction) feedbackspu_TexCoord2iv }, + { "TexCoord3d", (SPUGenericFunction) feedbackspu_TexCoord3d }, + { "TexCoord3dv", (SPUGenericFunction) feedbackspu_TexCoord3dv }, + { "TexCoord3f", (SPUGenericFunction) feedbackspu_TexCoord3f }, + { "TexCoord3fv", (SPUGenericFunction) feedbackspu_TexCoord3fv }, + { "TexCoord3s", (SPUGenericFunction) feedbackspu_TexCoord3s }, + { "TexCoord3sv", (SPUGenericFunction) feedbackspu_TexCoord3sv }, + { "TexCoord3i", (SPUGenericFunction) feedbackspu_TexCoord3i }, + { "TexCoord3iv", (SPUGenericFunction) feedbackspu_TexCoord3iv }, + { "TexCoord4d", (SPUGenericFunction) feedbackspu_TexCoord4d }, + { "TexCoord4dv", (SPUGenericFunction) feedbackspu_TexCoord4dv }, + { "TexCoord4f", (SPUGenericFunction) feedbackspu_TexCoord4f }, + { "TexCoord4fv", (SPUGenericFunction) feedbackspu_TexCoord4fv }, + { "TexCoord4s", (SPUGenericFunction) feedbackspu_TexCoord4s }, + { "TexCoord4sv", (SPUGenericFunction) feedbackspu_TexCoord4sv }, + { "TexCoord4i", (SPUGenericFunction) feedbackspu_TexCoord4i }, + { "TexCoord4iv", (SPUGenericFunction) feedbackspu_TexCoord4iv }, + { "RenderMode", (SPUGenericFunction) feedbackspu_RenderMode }, + { NULL, NULL } +}; +""") diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedback_context.c b/src/VBox/Additions/common/crOpenGL/feedback/feedback_context.c new file mode 100644 index 00000000..11158b5a --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedback_context.c @@ -0,0 +1,131 @@ +/* $Id: feedback_context.c $ */ +/** @file + * VBox feedback spu, context tracking. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "cr_spu.h" +#include "cr_error.h" +#include "feedbackspu.h" + +/** @todo r=bird: None of the code here is referenced externally, so I've + * just prototyped the function here at the top of the file to make + * the compiler happy. */ +GLint FEEDBACKSPU_APIENTRY feedbackspu_VBoxCreateContext( GLint con, const char *dpyName, GLint visual, GLint shareCtx ); +GLint FEEDBACKSPU_APIENTRY feedbackspu_CreateContext( const char *dpyName, GLint visual, GLint shareCtx ); +void FEEDBACKSPU_APIENTRY feedbackspu_MakeCurrent( GLint window, GLint nativeWindow, GLint ctx ); +void FEEDBACKSPU_APIENTRY feedbackspu_DestroyContext( GLint ctx ); + + +/** @todo Multithreading case. (See feedback_spu.self.RenderMode)*/ + +GLint FEEDBACKSPU_APIENTRY +feedbackspu_VBoxCreateContext( GLint con, const char *dpyName, GLint visual, GLint shareCtx ) +{ + GLint ctx, slot; + +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&feedback_spu.mutex); +#endif + + ctx = feedback_spu.child.VBoxCreateContext(con, dpyName, visual, shareCtx); + + /* find an empty context slot */ + for (slot = 0; slot < feedback_spu.numContexts; slot++) { + if (!feedback_spu.context[slot].clientState) { + /* found empty slot */ + break; + } + } + if (slot == feedback_spu.numContexts) { + feedback_spu.numContexts++; + } + + feedback_spu.context[slot].clientState = crStateCreateContext(NULL, visual, NULL); + feedback_spu.context[slot].clientCtx = ctx; + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&feedback_spu.mutex); +#endif + + return ctx; +} + +GLint FEEDBACKSPU_APIENTRY +feedbackspu_CreateContext( const char *dpyName, GLint visual, GLint shareCtx ) +{ + return feedbackspu_VBoxCreateContext( 0, dpyName, visual, shareCtx ); +} + +void FEEDBACKSPU_APIENTRY +feedbackspu_MakeCurrent( GLint window, GLint nativeWindow, GLint ctx ) +{ +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&feedback_spu.mutex); +#endif + feedback_spu.child.MakeCurrent(window, nativeWindow, ctx); + + if (ctx) { + int slot; + GLint oldmode; + + for (slot=0; slot<feedback_spu.numContexts; ++slot) + if (feedback_spu.context[slot].clientCtx == ctx) break; + CRASSERT(slot < feedback_spu.numContexts); + + crStateMakeCurrent(feedback_spu.context[slot].clientState); + + crStateGetIntegerv(GL_RENDER_MODE, &oldmode); + + if (oldmode!=feedback_spu.render_mode) + { + feedback_spu.self.RenderMode(oldmode); + } + } + else + { + crStateMakeCurrent(NULL); + } + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&feedback_spu.mutex); +#endif +} + +void FEEDBACKSPU_APIENTRY +feedbackspu_DestroyContext( GLint ctx ) +{ +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&feedback_spu.mutex); +#endif + feedback_spu.child.DestroyContext(ctx); + + if (ctx) { + int slot; + + for (slot=0; slot<feedback_spu.numContexts; ++slot) + if (feedback_spu.context[slot].clientCtx == ctx) break; + CRASSERT(slot < feedback_spu.numContexts); + + crStateDestroyContext(feedback_spu.context[slot].clientState); + + feedback_spu.context[slot].clientState = NULL; + feedback_spu.context[slot].clientCtx = 0; + } + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&feedback_spu.mutex); +#endif +} + diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedback_funcs.py b/src/VBox/Additions/common/crOpenGL/feedback/feedback_funcs.py new file mode 100755 index 00000000..d3c40833 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedback_funcs.py @@ -0,0 +1,40 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - THIS FILE AUTOMATICALLY GENERATED BY feedback_funcs.py SCRIPT */ +#ifndef CR_STATE_FEEDBACK_FUNCS_H +#define CR_STATE_FEEDBACK_FUNCS_H + +#include "cr_error.h" + +#if defined(WINDOWS) +#define STATE_APIENTRY __stdcall +#else +#define STATE_APIENTRY +#endif + +#define STATE_UNUSED(x) ((void)x)""") + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in apiutil.AllSpecials( "feedback" ): + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + print('%s STATE_APIENTRY crStateFeedback%s(%s);' % (return_type, func_name, apiutil.MakeDeclarationString(params))) + +for func_name in apiutil.AllSpecials( "select" ): + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + print('%s STATE_APIENTRY crStateSelect%s(%s);' % (return_type, func_name, apiutil.MakeDeclarationString(params))) +print('\n#endif /* CR_STATE_FEEDBACK_FUNCS_H */') diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedback_special b/src/VBox/Additions/common/crOpenGL/feedback/feedback_special new file mode 100644 index 00000000..75eb7f8f --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedback_special @@ -0,0 +1,64 @@ +Vertex2d +Vertex2dv +Vertex2f +Vertex2fv +Vertex2i +Vertex2iv +Vertex2s +Vertex2sv +Vertex3d +Vertex3dv +Vertex3f +Vertex3fv +Vertex3i +Vertex3iv +Vertex3s +Vertex3sv +Vertex4d +Vertex4dv +Vertex4f +Vertex4fv +Vertex4i +Vertex4iv +Vertex4s +Vertex4sv +Rectf +Recti +Rectd +Rects +Rectiv +Rectfv +Rectdv +Rectsv +TexCoord1d +TexCoord1dv +TexCoord1f +TexCoord1fv +TexCoord1i +TexCoord1iv +TexCoord1s +TexCoord1sv +TexCoord2d +TexCoord2dv +TexCoord2f +TexCoord2fv +TexCoord2i +TexCoord2iv +TexCoord2s +TexCoord2sv +TexCoord3d +TexCoord3dv +TexCoord3f +TexCoord3fv +TexCoord3i +TexCoord3iv +TexCoord3s +TexCoord3sv +TexCoord4d +TexCoord4dv +TexCoord4f +TexCoord4fv +TexCoord4i +TexCoord4iv +TexCoord4s +TexCoord4sv diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedback_state.py b/src/VBox/Additions/common/crOpenGL/feedback/feedback_state.py new file mode 100755 index 00000000..01bab425 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedback_state.py @@ -0,0 +1,34 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + + +apiutil.CopyrightC() + +print(""" +#include "cr_server.h" +#include "feedbackspu.h" +#include "feedbackspu_proto.h" +""") +custom = ["CreateContext", "VBoxCreateContext", "MakeCurrent", "DestroyContext"] + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in keys: + if apiutil.FindSpecial( "feedback_state", func_name ): + if func_name in custom: + continue + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + print('%s FEEDBACKSPU_APIENTRY feedbackspu_%s(%s)' % (return_type, func_name, apiutil.MakeDeclarationString(params))) + print('{') + print('\tcrState%s(%s);' % (func_name, apiutil.MakeCallString(params))) + print('') + print('\tfeedback_spu.super.%s(%s);' % (func_name, apiutil.MakeCallString(params))) + print('}') diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedback_state_special b/src/VBox/Additions/common/crOpenGL/feedback/feedback_state_special new file mode 100644 index 00000000..15bec65d --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedback_state_special @@ -0,0 +1,66 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. +ClipPlane +MatrixMode +LoadIdentity +PopMatrix +PushMatrix +LoadMatrixf +LoadMatrixd +MultMatrixf +MultMatrixd +LoadTransposeMatrixfARB +LoadTransposeMatrixdARB +MultTransposeMatrixfARB +MultTransposeMatrixdARB +Translatef +Translated +Rotatef +Rotated +Scalef +Scaled +Frustum +Ortho +Viewport +DepthRange +Scissor +PushAttrib +PopAttrib +PassThrough +PolygonMode +Color4f +Color4fv +Color3f +Color3fv +RasterPos2d +RasterPos2dv +RasterPos2f +RasterPos2fv +RasterPos2i +RasterPos2iv +RasterPos2s +RasterPos2sv +RasterPos3d +RasterPos3dv +RasterPos3f +RasterPos3fv +RasterPos3i +RasterPos3iv +RasterPos3s +RasterPos3sv +RasterPos4d +RasterPos4dv +RasterPos4f +RasterPos4fv +RasterPos4i +RasterPos4iv +RasterPos4s +RasterPos4sv +CreateContext +MakeCurrent +DestroyContext +VBoxAttachThread +VBoxDetachThread +VBoxCreateContext diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu.h b/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu.h new file mode 100644 index 00000000..7d72a70b --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu.h @@ -0,0 +1,58 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved. + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#ifndef GA_INCLUDED_SRC_common_crOpenGL_feedback_feedbackspu_h +#define GA_INCLUDED_SRC_common_crOpenGL_feedback_feedbackspu_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef WINDOWS +#define FEEDBACKSPU_APIENTRY __stdcall +#else +#define FEEDBACKSPU_APIENTRY +#endif + +#include "cr_spu.h" +#include "cr_timer.h" +#include "cr_glstate.h" + +typedef struct context_info_t ContextInfo; + +struct context_info_t { + CRContext *clientState; /* used to store client-side GL state */ + GLint clientCtx; /* client context ID */ +}; + +typedef struct { + int id; + int has_child; + SPUDispatchTable self, child, super; + + int render_mode; + + int default_viewport; + + CRCurrentStatePointers current; + + CRContext *defaultctx; + int numContexts; + ContextInfo context[CR_MAX_CONTEXTS]; + +#ifdef CHROMIUM_THREADSAFE + CRmutex mutex; +#endif +} feedbackSPU; + +extern feedbackSPU feedback_spu; + +extern SPUNamedFunctionTable _cr_feedback_table[]; + +extern SPUOptions feedbackSPUOptions[]; + +extern void feedbackspuGatherConfiguration( void ); + +#endif /* !GA_INCLUDED_SRC_common_crOpenGL_feedback_feedbackspu_h */ diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu.rc b/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu.rc new file mode 100644 index 00000000..b9db8122 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu.rc @@ -0,0 +1,69 @@ +/* $Id: feedbackspu.rc $ */ +/** @file + * VBoxOGLfeedbackspu - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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_DRV + FILESUBTYPE VFT2_DRV_DISPLAY +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "VirtualBox crOpenGL ICD\0" + VALUE "InternalName", "VBoxOGLfeedbackpu\0" +#ifdef VBOX_WDDM_WOW64 + VALUE "OriginalFilename", "VBoxOGLfeedbackpu-x86.dll\0" +#else + VALUE "OriginalFilename", "VBoxOGLfeedbackpu.dll\0" +#endif + 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 + +1 RCDATA +BEGIN +// Machine dependent parameters + 17, // Height of vertical thumb + 17, // Width of horizontal thumb + 2, // Icon horiz compression factor + 2, // Icon vert compression factor + 1, // Cursor horz compression factor + 1, // Cursor vert compression factor + 0, // Kanji window height + 1, // cxBorder (thickness of vertical lines) + 1 // cyBorder (thickness of horizontal lines) +END diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu_config.c b/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu_config.c new file mode 100644 index 00000000..9449a2bb --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu_config.c @@ -0,0 +1,44 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_string.h" +#include "cr_environment.h" +#include "cr_error.h" +#include "cr_mem.h" +#include "feedbackspu.h" + +#include <stdio.h> +#ifndef WINDOWS +#include <unistd.h> +#endif + +static void __setDefaults( void ) +{ + feedback_spu.render_mode = GL_RENDER; +} + +static void set_default_viewport( void *foo, const char *response ) +{ + (void) foo; + sscanf( response, "%d", &(feedback_spu.default_viewport) ); +} + +/* option, type, nr, default, min, max, title, callback + */ +SPUOptions feedbackSPUOptions[] = { + + { "default_viewport", CR_BOOL, 1, "0", "0", "1", + "Return default viewport parameters", (SPUOptionCB)set_default_viewport }, + + { NULL, CR_BOOL, 0, NULL, NULL, NULL, NULL, NULL }, + +}; + + +void feedbackspuGatherConfiguration( void ) +{ + __setDefaults(); +} diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu_init.c b/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu_init.c new file mode 100644 index 00000000..f264763c --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu_init.c @@ -0,0 +1,86 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_spu.h" +#include "cr_environment.h" +#include "cr_string.h" +#include "cr_error.h" +#include "cr_mem.h" +#include "cr_server.h" +#include "feedbackspu.h" +#include <fcntl.h> +#ifndef WINDOWS +#include <unistd.h> +#endif + +feedbackSPU feedback_spu; + +static SPUFunctions feedback_functions = { + NULL, /* CHILD COPY */ + NULL, /* DATA */ + _cr_feedback_table /* THE ACTUAL FUNCTIONS */ +}; + +static SPUFunctions *feedbackSPUInit( int id, SPU *child, SPU *self, + unsigned int context_id, + unsigned int num_contexts ) +{ + (void) context_id; + (void) num_contexts; + +#ifdef CHROMIUM_THREADSAFE + crInitMutex(&feedback_spu.mutex); +#endif + + feedback_spu.id = id; + feedback_spu.has_child = 0; + if (child) + { + crSPUInitDispatchTable( &(feedback_spu.child) ); + crSPUCopyDispatchTable( &(feedback_spu.child), &(child->dispatch_table) ); + feedback_spu.has_child = 1; + } + crSPUInitDispatchTable( &(feedback_spu.super) ); + crSPUCopyDispatchTable( &(feedback_spu.super), &(self->superSPU->dispatch_table) ); + feedbackspuGatherConfiguration(); + + /* create/init default state tracker */ + crStateInit(); + + feedback_spu.defaultctx = crStateCreateContext(NULL, 0, NULL); + crStateSetCurrent(feedback_spu.defaultctx); + + feedback_spu.numContexts = 0; + crMemZero(feedback_spu.context, CR_MAX_CONTEXTS * sizeof(ContextInfo)); + + return &feedback_functions; +} + +static void feedbackSPUSelfDispatch(SPUDispatchTable *self) +{ + crSPUInitDispatchTable( &(feedback_spu.self) ); + crSPUCopyDispatchTable( &(feedback_spu.self), self ); +} + +static int feedbackSPUCleanup(void) +{ + return 1; +} + +int SPULoad( char **name, char **super, SPUInitFuncPtr *init, + SPUSelfDispatchFuncPtr *self, SPUCleanupFuncPtr *cleanup, + SPUOptionsPtr *options, int *flags ) +{ + *name = "feedback"; + *super = "passthrough"; + *init = feedbackSPUInit; + *self = feedbackSPUSelfDispatch; + *cleanup = feedbackSPUCleanup; + *options = feedbackSPUOptions; + *flags = (SPU_NO_PACKER|SPU_NOT_TERMINAL|SPU_MAX_SERVERS_ZERO); + + return 1; +} diff --git a/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu_proto.py b/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu_proto.py new file mode 100755 index 00000000..92a5c1ce --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/feedbackspu_proto.py @@ -0,0 +1,35 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - generated by feedback.py */ + +#ifndef FEEDBACKSPU_PROTO_H +#define FEEDBACKSPU_PROTO_H + +#include "feedbackspu.h" + +""") + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in keys: + if apiutil.FindSpecial( "feedback_state", func_name ): + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + print('extern %s FEEDBACKSPU_APIENTRY feedbackspu_%s(%s);' % (return_type, func_name, apiutil.MakeDeclarationString(params))) + + +print(""" +#endif +""") diff --git a/src/VBox/Additions/common/crOpenGL/feedback/select_special b/src/VBox/Additions/common/crOpenGL/feedback/select_special new file mode 100644 index 00000000..f35dba0a --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/feedback/select_special @@ -0,0 +1,56 @@ +Vertex2d +Vertex2dv +Vertex2f +Vertex2fv +Vertex2i +Vertex2iv +Vertex2s +Vertex2sv +Vertex3d +Vertex3dv +Vertex3f +Vertex3fv +Vertex3i +Vertex3iv +Vertex3s +Vertex3sv +Vertex4d +Vertex4dv +Vertex4f +Vertex4fv +Vertex4i +Vertex4iv +Vertex4s +Vertex4sv +Rectf +Recti +Rectd +Rects +Rectiv +Rectfv +Rectdv +Rectsv +RasterPos2d +RasterPos2dv +RasterPos2f +RasterPos2fv +RasterPos2i +RasterPos2iv +RasterPos2s +RasterPos2sv +RasterPos3d +RasterPos3dv +RasterPos3f +RasterPos3fv +RasterPos3i +RasterPos3iv +RasterPos3s +RasterPos3sv +RasterPos4d +RasterPos4dv +RasterPos4f +RasterPos4fv +RasterPos4i +RasterPos4iv +RasterPos4s +RasterPos4sv diff --git a/src/VBox/Additions/common/crOpenGL/getprocaddress.py b/src/VBox/Additions/common/crOpenGL/getprocaddress.py new file mode 100755 index 00000000..39cfdf94 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/getprocaddress.py @@ -0,0 +1,125 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - THIS FILE GENERATED BY THE getprocaddress.py SCRIPT */ + +#include "chromium.h" +#include "cr_error.h" +#include "cr_string.h" +#include "cr_version.h" +#include "stub.h" +#include "dri_glx.h" +#if defined(VBOXOGL_DRI) || defined(VBOXOGL_FAKEDRI) +#include "cr_gl.h" +#include "fakedri_drv.h" +#endif + +struct name_address { + const char *name; + CR_PROC address; +}; + +static struct name_address functions[] = { +""") + + +keys = apiutil.GetAllFunctions(sys.argv[1]+"/APIspec.txt") +for func_name in keys: + if "Chromium" == apiutil.Category(func_name): + continue + if "VBox" == apiutil.Category(func_name): + continue + if func_name == "BoundsInfoCR": + continue + if "GL_chromium" == apiutil.Category(func_name): + pass #continue + + wrap = apiutil.GetCategoryWrapper(func_name) + name = "gl" + func_name + address = "VBOXGLTAG(gl" + func_name + ")" + if wrap: + print('#ifdef CR_%s' % wrap) + print('\t{ "%s", (CR_PROC) %s },' % (name, address)) + if wrap: + print('#endif') + + +print("\t/* Chromium binding/glue functions */") + +for func_name in keys: + if (func_name == "Writeback" or + func_name == "BoundsInfoCR" or + func_name == "GetUniformsLocations" or + func_name == "GetAttribsLocations"): + continue + if apiutil.Category(func_name) == "Chromium": + print('\t{ "cr%s", (CR_PROC) cr%s },' % (func_name, func_name)) + + +print(""" + { NULL, NULL } +}; + +CR_PROC CR_APIENTRY crGetProcAddress( const char *name ) +{ + int i; + stubInit(); + + for (i = 0; functions[i].name; i++) { + if (crStrcmp(name, functions[i].name) == 0) { + return functions[i].address; + } + } + + +#define GLXAPI_ENTRY(Func) if (!crStrcmp(name, "glX"#Func)) return (CR_PROC) &VBOXGLXENTRYTAG(glX##Func); +#include "fakedri_glxfuncsList.h" +#undef GLXAPI_ENTRY + + /*CR_EXT_texture_from_pixmap*/ + if (!crStrcmp(name, "glXBindTexImageEXT")) return (CR_PROC) VBOXGLXTAG(glXBindTexImageEXT); + if (!crStrcmp(name, "glXReleaseTexImageEXT")) return (CR_PROC) VBOXGLXTAG(glXReleaseTexImageEXT); + +#if defined(Linux) && defined(CR_EXT_framebuffer_blit) + /* Hacky way to make gnome3 happy on ubuntu 11.04, even though glBlitFramebuffer is part of OpenGL 3.0 spec, + * it expects to find glBlitFramebuffer and not glBlitFramebufferEXT after checking for EXT_framebuffer_blit support. + * Untill 3.0 support, it's better to go this way instead of adding an alias to src/VBox/GuestHost/OpenGL/glapi_parser/apispec.txt. + */ + if (!crStrcmp(name, "glBlitFramebuffer")) return crGetProcAddress("glBlitFramebufferEXT"); +#endif + + if (name) crDebug("Returning NULL for %s", name); + return NULL; +} + +""") + + + +# XXX should crGetProcAddress really handle WGL/GLX functions??? + +print_foo = """ +/* As these are Windows specific (i.e. wgl), define these now.... */ +#ifdef WINDOWS + { + wglGetExtensionsStringEXTFunc_t wglGetExtensionsStringEXT = NULL; + wglChoosePixelFormatFunc_t wglChoosePixelFormatEXT = NULL; + wglGetPixelFormatAttribivEXTFunc_t wglGetPixelFormatAttribivEXT = NULL; + wglGetPixelFormatAttribfvEXTFunc_t wglGetPixelFormatAttribfvEXT = NULL; + if (!crStrcmp(name, "wglGetExtensionsStringEXT")) return (CR_PROC) wglGetExtensionsStringEXT; + if (!crStrcmp(name, "wglChoosePixelFormatEXT")) return (CR_PROC) wglChoosePixelFormatEXT; + if (!crStrcmp(name, "wglGetPixelFormatAttribivEXT")) return (CR_PROC) wglGetPixelFormatAttribivEXT; + if (!crStrcmp(name, "wglGetPixelFormatAttribfvEXT")) return (CR_PROC) wglGetPixelFormatAttribfvEXT; + } +#endif +""" diff --git a/src/VBox/Additions/common/crOpenGL/glx.c b/src/VBox/Additions/common/crOpenGL/glx.c new file mode 100644 index 00000000..eb102877 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/glx.c @@ -0,0 +1,2113 @@ +/* $Id: glx.c $ */ +/** @file + * VBox OpenGL GLX implementation + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * -------------------------------------------------------------------- + * Original copyright notice: + * + * Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +/* opengl_stub/glx.c */ +#include "chromium.h" +#include "cr_error.h" +#include "cr_spu.h" +#include "cr_mem.h" +#include "cr_string.h" +#include "stub.h" +#include "dri_glx.h" +#include "GL/internal/glcore.h" +#include "cr_glstate.h" + +#include <X11/Xregion.h> + +/* Force full pixmap update if there're more damaged regions than this number*/ +#define CR_MAX_DAMAGE_REGIONS_TRACKED 50 + +/* Force "bigger" update (full or clip) if it's reducing number of regions updated + * but doesn't increase updated area more than given number + */ +#define CR_MIN_DAMAGE_PROFIT_SIZE 64*64 + +/** @todo combine it in some other place*/ +/* Size of pack spu buffer - some delta for commands packing, see pack/packspu_config.c*/ + +/** Ramshankar: Solaris compiz fix */ +#ifdef RT_OS_SOLARIS +# define CR_MAX_TRANSFER_SIZE 20*1024*1024 +#else +# define CR_MAX_TRANSFER_SIZE 4*1024*1024 +#endif + +/** For optimizing glXMakeCurrent */ +static Display *currentDisplay = NULL; +static GLXDrawable currentDrawable = 0; +static GLXDrawable currentReadDrawable = 0; + +static void stubXshmUpdateImageRect(Display *dpy, GLXDrawable draw, GLX_Pixmap_t *pGlxPixmap, XRectangle *pRect); +static void stubQueryXDamageExtension(Display *dpy, ContextInfo *pContext); + +static bool isGLXVisual(Display *dpy, XVisualInfo *vis) +{ + return vis->visualid == XVisualIDFromVisual(DefaultVisual(dpy, vis->screen)); +} + +static GLXFBConfig fbConfigFromVisual(Display *dpy, XVisualInfo *vis) +{ + (void)dpy; + + if (!isGLXVisual(dpy, vis)) + return 0; + return (GLXFBConfig)vis->visualid; +} + +static GLXFBConfig defaultFBConfigForScreen(Display *dpy, int screen) +{ + return (GLXFBConfig) XVisualIDFromVisual(DefaultVisual(dpy, screen)); +} + +static XVisualInfo *visualInfoFromFBConfig(Display *dpy, GLXFBConfig config) +{ + XVisualInfo info, *pret; + int nret; + + info.visualid = (VisualID)config; + pret = XGetVisualInfo(dpy, VisualIDMask, &info, &nret); + if (nret == 1) + return pret; + XFree(pret); + return NULL; +} + +DECLEXPORT(XVisualInfo *) +VBOXGLXTAG(glXChooseVisual)( Display *dpy, int screen, int *attribList ) +{ + bool useRGBA = false; + int *attrib; + XVisualInfo searchvis, *pret; + int nvisuals; + stubInit(); + + for (attrib = attribList; *attrib != None; attrib++) + { + switch (*attrib) + { + case GLX_USE_GL: + /* ignored, this is mandatory */ + break; + + case GLX_BUFFER_SIZE: + /* this is for color-index visuals, which we don't support */ + attrib++; + break; + + case GLX_LEVEL: + if (attrib[1] != 0) + goto err_exit; + attrib++; + break; + + case GLX_RGBA: + useRGBA = true; + break; + + case GLX_STEREO: + goto err_exit; + /* + crWarning( "glXChooseVisual: stereo unsupported" ); + return NULL; + */ + break; + + case GLX_AUX_BUFFERS: + if (attrib[1] != 0) + goto err_exit; + attrib++; + break; + + case GLX_RED_SIZE: + case GLX_GREEN_SIZE: + case GLX_BLUE_SIZE: + if (attrib[1] > 8) + goto err_exit; + attrib++; + break; + + case GLX_ALPHA_SIZE: + if (attrib[1] > 8) + goto err_exit; + attrib++; + break; + + case GLX_DEPTH_SIZE: + if (attrib[1] > 24) + goto err_exit; + attrib++; + break; + + case GLX_STENCIL_SIZE: + if (attrib[1] > 8) + goto err_exit; + attrib++; + break; + + case GLX_ACCUM_RED_SIZE: + case GLX_ACCUM_GREEN_SIZE: + case GLX_ACCUM_BLUE_SIZE: + case GLX_ACCUM_ALPHA_SIZE: + if (attrib[1] > 16) + goto err_exit; + attrib++; + break; + + case GLX_SAMPLE_BUFFERS_SGIS: /* aka GLX_SAMPLES_ARB */ + if (attrib[1] > 0) + goto err_exit; + attrib++; + break; + case GLX_SAMPLES_SGIS: /* aka GLX_SAMPLES_ARB */ + if (attrib[1] > 0) + goto err_exit; + attrib++; + break; + + case GLX_DOUBLEBUFFER: /* @todo, check if we support it */ + break; + +#ifdef GLX_VERSION_1_3 + case GLX_X_VISUAL_TYPE: + case GLX_TRANSPARENT_TYPE_EXT: + case GLX_TRANSPARENT_INDEX_VALUE_EXT: + case GLX_TRANSPARENT_RED_VALUE_EXT: + case GLX_TRANSPARENT_GREEN_VALUE_EXT: + case GLX_TRANSPARENT_BLUE_VALUE_EXT: + case GLX_TRANSPARENT_ALPHA_VALUE_EXT: + /* ignore */ + crWarning("glXChooseVisual: ignoring attribute 0x%x", *attrib); + attrib++; + break; +#endif + + default: + crWarning( "glXChooseVisual: bad attrib=0x%x, ignoring", *attrib ); + attrib++; + //return NULL; + } + } + + if (!useRGBA) + return NULL; + + XLOCK(dpy); + searchvis.visualid = XVisualIDFromVisual(DefaultVisual(dpy, screen)); + pret = XGetVisualInfo(dpy, VisualIDMask, &searchvis, &nvisuals); + XUNLOCK(dpy); + + if (nvisuals!=1) crWarning("glXChooseVisual: XGetVisualInfo returned %i visuals for %x", nvisuals, (unsigned int) searchvis.visualid); + if (pret) + crDebug("glXChooseVisual returned %x depth=%i", (unsigned int)pret->visualid, pret->depth); + return pret; + +err_exit: + crDebug("glXChooseVisual returning NULL, due to attrib=0x%x, next=0x%x", attrib[0], attrib[1]); + return NULL; +} + +/** + ** There is a problem with glXCopyContext. + ** IRIX and Mesa both define glXCopyContext + ** to have the mask argument being a + ** GLuint. XFree 4 and oss.sgi.com + ** define it to be an unsigned long. + ** Solution: We don't support + ** glXCopyContext anyway so we'll just + ** \#ifdef out the code. + */ +DECLEXPORT(void) +VBOXGLXTAG(glXCopyContext)( Display *dpy, GLXContext src, GLXContext dst, +#if defined(AIX) || defined(PLAYSTATION2) +GLuint mask +#elif defined(SunOS) +unsigned long mask +#else +unsigned long mask +#endif +) +{ + (void) dpy; + (void) src; + (void) dst; + (void) mask; + crWarning( "Unsupported GLX Call: glXCopyContext()" ); +} + + +/** + * Get the display string for the given display pointer. + * Never return just ":0.0". In that case, prefix with our host name. + */ +static void +stubGetDisplayString( Display *dpy, char *nameResult, int maxResult ) +{ + const char *dpyName = DisplayString(dpy); + char host[1000]; + + host[0] = 0; + if (crStrlen(host) + crStrlen(dpyName) >= maxResult - 1) + { + /* return null string */ + crWarning("Very long host / display name string in stubDisplayString!"); + nameResult[0] = 0; + } + else + { + /* return host concatenated with dpyName */ + crStrcpy(nameResult, host); + crStrcat(nameResult, dpyName); + } +} + + + +DECLEXPORT(GLXContext) +VBOXGLXTAG(glXCreateContext)(Display *dpy, XVisualInfo *vis, GLXContext share, Bool direct) +{ + char dpyName[MAX_DPY_NAME]; + ContextInfo *context; + int visBits = CR_RGB_BIT | CR_DOUBLE_BIT | CR_DEPTH_BIT; /* default vis */ + + (void)vis; + stubInit(); + + CRASSERT(stub.contextTable); + + /* + { + int i, numExt; + char **list; + + list = XListExtensions(dpy, &numExt); + crDebug("X extensions [%i]:", numExt); + for (i=0; i<numExt; ++i) + { + crDebug("%s", list[i]); + } + XFreeExtensionList(list); + } + */ + + stubGetDisplayString(dpy, dpyName, MAX_DPY_NAME); + + context = stubNewContext(dpyName, visBits, UNDECIDED, (unsigned long) share); + if (!context) + return 0; + + context->dpy = dpy; + context->direct = direct; + + stubQueryXDamageExtension(dpy, context); + + return (GLXContext) context->id; +} + + +DECLEXPORT(void) VBOXGLXTAG(glXDestroyContext)( Display *dpy, GLXContext ctx ) +{ + (void) dpy; + stubDestroyContext( (unsigned long) ctx ); +} + +typedef struct _stubFindPixmapParms_t { + ContextInfo *pCtx; + GLX_Pixmap_t *pGlxPixmap; + GLXDrawable draw; +} stubFindPixmapParms_t; + +static void stubFindPixmapCB(unsigned long key, void *data1, void *data2) +{ + ContextInfo *pCtx = (ContextInfo *) data1; + stubFindPixmapParms_t *pParms = (stubFindPixmapParms_t *) data2; + GLX_Pixmap_t *pGlxPixmap = (GLX_Pixmap_t *) crHashtableSearch(pCtx->pGLXPixmapsHash, (unsigned int) pParms->draw); + (void)key; + + if (pGlxPixmap) + { + pParms->pCtx = pCtx; + pParms->pGlxPixmap = pGlxPixmap; + } +} + +DECLEXPORT(Bool) VBOXGLXTAG(glXMakeCurrent)( Display *dpy, GLXDrawable drawable, GLXContext ctx ) +{ + ContextInfo *context; + WindowInfo *window; + Bool retVal; + + /*crDebug("glXMakeCurrent(%p, 0x%x, 0x%x)", (void *) dpy, (int) drawable, (int) ctx);*/ + + /*check if passed drawable is GLXPixmap and not X Window*/ + if (drawable) + { + GLX_Pixmap_t *pGlxPixmap = (GLX_Pixmap_t *) crHashtableSearch(stub.pGLXPixmapsHash, (unsigned int) drawable); + + if (!pGlxPixmap) + { + stubFindPixmapParms_t parms; + parms.pGlxPixmap = NULL; + parms.draw = drawable; + crHashtableWalk(stub.contextTable, stubFindPixmapCB, &parms); + pGlxPixmap = parms.pGlxPixmap; + } + + if (pGlxPixmap) + { + /** @todo */ + crWarning("Unimplemented glxMakeCurrent call with GLXPixmap passed, unexpected things might happen."); + } + } + + if (ctx && drawable) + { + crHashtableLock(stub.windowTable); + crHashtableLock(stub.contextTable); + + context = (ContextInfo *) crHashtableSearch(stub.contextTable, (unsigned long) ctx); + window = stubGetWindowInfo(dpy, drawable); + + if (context && context->type == UNDECIDED) { + XLOCK(dpy); + XSync(dpy, 0); /* sync to force window creation on the server */ + XUNLOCK(dpy); + } + } + else + { + dpy = NULL; + window = NULL; + context = NULL; + } + + currentDisplay = dpy; + currentDrawable = drawable; + + retVal = stubMakeCurrent(window, context); + + if (ctx && drawable) + { + crHashtableUnlock(stub.contextTable); + crHashtableUnlock(stub.windowTable); + } + + return retVal; +} + +DECLEXPORT(GLXPixmap) VBOXGLXTAG(glXCreateGLXPixmap)( Display *dpy, XVisualInfo *vis, Pixmap pixmap ) +{ + stubInit(); + return VBOXGLXTAG(glXCreatePixmap)(dpy, fbConfigFromVisual(dpy, vis), pixmap, NULL); +} + +DECLEXPORT(void) VBOXGLXTAG(glXDestroyGLXPixmap)( Display *dpy, GLXPixmap pix ) +{ + VBOXGLXTAG(glXDestroyPixmap)(dpy, pix); +} + +DECLEXPORT(int) VBOXGLXTAG(glXGetConfig)( Display *dpy, XVisualInfo *vis, int attrib, int *value ) +{ + if (!vis) { + /* SGI OpenGL Performer hits this */ + crWarning("glXGetConfig called with NULL XVisualInfo"); + return GLX_BAD_VISUAL; + } + + stubInit(); + + *value = 0; /* For sanity */ + + switch ( attrib ) { + + case GLX_USE_GL: + *value = isGLXVisual(dpy, vis); + break; + + case GLX_BUFFER_SIZE: + *value = 32; + break; + + case GLX_LEVEL: + *value = 0; /* for now */ + break; + + case GLX_RGBA: + *value = 1; + break; + + case GLX_DOUBLEBUFFER: + *value = 1; + break; + + case GLX_STEREO: + *value = 1; + break; + + case GLX_AUX_BUFFERS: + *value = 0; + break; + + case GLX_RED_SIZE: + *value = 8; + break; + + case GLX_GREEN_SIZE: + *value = 8; + break; + + case GLX_BLUE_SIZE: + *value = 8; + break; + + case GLX_ALPHA_SIZE: + *value = 8; + break; + + case GLX_DEPTH_SIZE: + *value = 24; + break; + + case GLX_STENCIL_SIZE: + *value = 8; + break; + + case GLX_ACCUM_RED_SIZE: + *value = 16; + break; + + case GLX_ACCUM_GREEN_SIZE: + *value = 16; + break; + + case GLX_ACCUM_BLUE_SIZE: + *value = 16; + break; + + case GLX_ACCUM_ALPHA_SIZE: + *value = 16; + break; + + case GLX_SAMPLE_BUFFERS_SGIS: + *value = 0; /* fix someday */ + break; + + case GLX_SAMPLES_SGIS: + *value = 0; /* fix someday */ + break; + + case GLX_VISUAL_CAVEAT_EXT: + *value = GLX_NONE_EXT; + break; +#if defined(SunOS) || 1 + /* + I don't think this is even a valid attribute for glxGetConfig. + No idea why this gets called under SunOS but we simply ignore it + -- jw + */ + case GLX_X_VISUAL_TYPE: + crWarning ("Ignoring Unsupported GLX Call: glxGetConfig with attrib 0x%x", attrib); + break; +#endif + + case GLX_TRANSPARENT_TYPE: + *value = GLX_NONE_EXT; + break; + case GLX_TRANSPARENT_INDEX_VALUE: + *value = 0; + break; + case GLX_TRANSPARENT_RED_VALUE: + *value = 0; + break; + case GLX_TRANSPARENT_GREEN_VALUE: + *value = 0; + break; + case GLX_TRANSPARENT_BLUE_VALUE: + *value = 0; + break; + case GLX_TRANSPARENT_ALPHA_VALUE: + *value = 0; + break; + case GLX_DRAWABLE_TYPE: + *value = GLX_WINDOW_BIT; + break; + default: + crWarning( "Unsupported GLX Call: glXGetConfig with attrib 0x%x, ignoring...", attrib ); + //return GLX_BAD_ATTRIBUTE; + *value = 0; + } + + return 0; +} + +DECLEXPORT(GLXContext) VBOXGLXTAG(glXGetCurrentContext)( void ) +{ + ContextInfo *context = stubGetCurrentContext(); + if (context) + return (GLXContext) context->id; + else + return (GLXContext) NULL; +} + +DECLEXPORT(GLXDrawable) VBOXGLXTAG(glXGetCurrentDrawable)(void) +{ + return currentDrawable; +} + +DECLEXPORT(Display *) VBOXGLXTAG(glXGetCurrentDisplay)(void) +{ + return currentDisplay; +} + +DECLEXPORT(Bool) VBOXGLXTAG(glXIsDirect)(Display *dpy, GLXContext ctx) +{ + (void) dpy; + (void) ctx; + crDebug("->glXIsDirect"); + return True; +} + +DECLEXPORT(Bool) VBOXGLXTAG(glXQueryExtension)(Display *dpy, int *errorBase, int *eventBase) +{ + (void) dpy; + (void) errorBase; + (void) eventBase; + return 1; /* You BET we do... */ +} + +DECLEXPORT(Bool) VBOXGLXTAG(glXQueryVersion)( Display *dpy, int *major, int *minor ) +{ + (void) dpy; + *major = 1; + *minor = 3; + return 1; +} + +DECLEXPORT(void) VBOXGLXTAG(glXSwapBuffers)( Display *dpy, GLXDrawable drawable ) +{ + WindowInfo *window = stubGetWindowInfo(dpy, drawable); + stubSwapBuffers( window, 0 ); +} + +DECLEXPORT(void) VBOXGLXTAG(glXUseXFont)( Font font, int first, int count, int listBase ) +{ + ContextInfo *context = stubGetCurrentContext(); + Display *dpy = context->dpy; + if (dpy) { + stubUseXFont( dpy, font, first, count, listBase ); + } + else { + dpy = XOpenDisplay(NULL); + if (!dpy) + return; + stubUseXFont( dpy, font, first, count, listBase ); + XCloseDisplay(dpy); + } +} + +DECLEXPORT(void) VBOXGLXTAG(glXWaitGL)( void ) +{ + static int first_call = 1; + + if ( first_call ) + { + crDebug( "Ignoring unsupported GLX call: glXWaitGL()" ); + first_call = 0; + } +} + +DECLEXPORT(void) VBOXGLXTAG(glXWaitX)( void ) +{ + static int first_call = 1; + + if ( first_call ) + { + crDebug( "Ignoring unsupported GLX call: glXWaitX()" ); + first_call = 0; + } +} + +DECLEXPORT(const char *) VBOXGLXTAG(glXQueryExtensionsString)( Display *dpy, int screen ) +{ + /* XXX maybe also advertise GLX_SGIS_multisample? */ + + static const char *retval = "GLX_ARB_multisample GLX_EXT_texture_from_pixmap GLX_SGIX_fbconfig GLX_ARB_get_proc_address"; + + (void) dpy; + (void) screen; + + crDebug("->glXQueryExtensionsString"); + return retval; +} + +DECLEXPORT(const char *) VBOXGLXTAG(glXGetClientString)( Display *dpy, int name ) +{ + const char *retval; + (void) dpy; + (void) name; + + switch ( name ) { + + case GLX_VENDOR: + retval = "Chromium"; + break; + + case GLX_VERSION: + retval = "1.3 Chromium"; + break; + + case GLX_EXTENSIONS: + /** @todo should be a screen not a name...but it's not used anyway*/ + retval = glXQueryExtensionsString(dpy, name); + break; + + default: + retval = NULL; + } + + return retval; +} + +DECLEXPORT(const char *) VBOXGLXTAG(glXQueryServerString)( Display *dpy, int screen, int name ) +{ + const char *retval; + (void) dpy; + (void) screen; + + switch ( name ) { + + case GLX_VENDOR: + retval = "Chromium"; + break; + + case GLX_VERSION: + retval = "1.3 Chromium"; + break; + + case GLX_EXTENSIONS: + retval = glXQueryExtensionsString(dpy, screen); + break; + + default: + retval = NULL; + } + + return retval; +} + +DECLEXPORT(CR_GLXFuncPtr) VBOXGLXTAG(glXGetProcAddressARB)( const GLubyte *name ) +{ + return (CR_GLXFuncPtr) crGetProcAddress( (const char *) name ); +} + +DECLEXPORT(CR_GLXFuncPtr) VBOXGLXTAG(glXGetProcAddress)( const GLubyte *name ) +{ + return (CR_GLXFuncPtr) crGetProcAddress( (const char *) name ); +} + + +#if GLX_EXTRAS + +DECLEXPORT(GLXPbufferSGIX) +VBOXGLXTAG(glXCreateGLXPbufferSGIX)(Display *dpy, GLXFBConfigSGIX config, + unsigned int width, unsigned int height, + int *attrib_list) +{ + (void) dpy; + (void) config; + (void) width; + (void) height; + (void) attrib_list; + crWarning("glXCreateGLXPbufferSGIX not implemented by Chromium"); + return 0; +} + +DECLEXPORT(void) VBOXGLXTAG(glXDestroyGLXPbufferSGIX)(Display *dpy, GLXPbuffer pbuf) +{ + (void) dpy; + (void) pbuf; + crWarning("glXDestroyGLXPbufferSGIX not implemented by Chromium"); +} + +DECLEXPORT(void) VBOXGLXTAG(glXSelectEventSGIX)(Display *dpy, GLXDrawable drawable, unsigned long mask) +{ + (void) dpy; + (void) drawable; + (void) mask; +} + +DECLEXPORT(void) VBOXGLXTAG(glXGetSelectedEventSGIX)(Display *dpy, GLXDrawable drawable, unsigned long *mask) +{ + (void) dpy; + (void) drawable; + (void) mask; +} + +DECLEXPORT(int) VBOXGLXTAG(glXQueryGLXPbufferSGIX)(Display *dpy, GLXPbuffer pbuf, + int attribute, unsigned int *value) +{ + (void) dpy; + (void) pbuf; + (void) attribute; + (void) value; + crWarning("glXQueryGLXPbufferSGIX not implemented by Chromium"); + return 0; +} + +DECLEXPORT(int) VBOXGLXTAG(glXGetFBConfigAttribSGIX)(Display *dpy, GLXFBConfig config, + int attribute, int *value) +{ + return VBOXGLXTAG(glXGetFBConfigAttrib)(dpy, config, attribute, value); +} + +DECLEXPORT(GLXFBConfigSGIX *) +VBOXGLXTAG(glXChooseFBConfigSGIX)(Display *dpy, int screen, + int *attrib_list, int *nelements) +{ + return VBOXGLXTAG(glXChooseFBConfig)(dpy, screen, attrib_list, nelements); +} + +DECLEXPORT(GLXPixmap) +VBOXGLXTAG(glXCreateGLXPixmapWithConfigSGIX)(Display *dpy, + GLXFBConfig config, + Pixmap pixmap) +{ + return VBOXGLXTAG(glXCreatePixmap)(dpy, config, pixmap, NULL); +} + +DECLEXPORT(GLXContext) +VBOXGLXTAG(glXCreateContextWithConfigSGIX)(Display *dpy, GLXFBConfig config, + int render_type, + GLXContext share_list, + Bool direct) +{ + (void)config; + if (render_type!=GLX_RGBA_TYPE_SGIX) + { + crWarning("glXCreateContextWithConfigSGIX: Unsupported render type %i", render_type); + return NULL; + } + return VBOXGLXTAG(glXCreateContext)(dpy, NULL, share_list, direct); +} + +DECLEXPORT(XVisualInfo *) +VBOXGLXTAG(glXGetVisualFromFBConfigSGIX)(Display *dpy, + GLXFBConfig config) +{ + return visualInfoFromFBConfig(dpy, config); +} + +DECLEXPORT(GLXFBConfigSGIX) +VBOXGLXTAG(glXGetFBConfigFromVisualSGIX)(Display *dpy, XVisualInfo *vis) +{ + if (!vis) + { + return NULL; + } + /*Note: Caller is supposed to call XFree on returned value, so can't just return (GLXFBConfig)vis->visualid*/ + return (GLXFBConfigSGIX) visualInfoFromFBConfig(dpy, fbConfigFromVisual(dpy, vis)); +} + +/* + * GLX 1.3 functions + */ +DECLEXPORT(GLXFBConfig *) +VBOXGLXTAG(glXChooseFBConfig)(Display *dpy, int screen, ATTRIB_TYPE *attrib_list, int *nelements) +{ + ATTRIB_TYPE *attrib; + intptr_t fbconfig = 0; + + stubInit(); + + if (!attrib_list) + { + return VBOXGLXTAG(glXGetFBConfigs)(dpy, screen, nelements); + } + + for (attrib = attrib_list; *attrib != None; attrib++) + { + switch (*attrib) + { + case GLX_FBCONFIG_ID: + fbconfig = attrib[1]; + attrib++; + break; + + case GLX_BUFFER_SIZE: + /* this is ignored except for color-index visuals, which we don't support */ + attrib++; + break; + + case GLX_LEVEL: + if (attrib[1] != 0) + goto err_exit; + attrib++; + break; + + case GLX_AUX_BUFFERS: + if (attrib[1] != 0) + goto err_exit; + attrib++; + break; + + case GLX_DOUBLEBUFFER: /* @todo, check if we support it */ + attrib++; + break; + + case GLX_STEREO: + if (attrib[1] != 0) + goto err_exit; + attrib++; + break; + + case GLX_RED_SIZE: + case GLX_GREEN_SIZE: + case GLX_BLUE_SIZE: + case GLX_ALPHA_SIZE: + if (attrib[1] > 8) + goto err_exit; + attrib++; + break; + + case GLX_DEPTH_SIZE: + if (attrib[1] > 24) + goto err_exit; + attrib++; + break; + + case GLX_STENCIL_SIZE: + if (attrib[1] > 8) + goto err_exit; + attrib++; + break; + + case GLX_ACCUM_RED_SIZE: + case GLX_ACCUM_GREEN_SIZE: + case GLX_ACCUM_BLUE_SIZE: + case GLX_ACCUM_ALPHA_SIZE: + if (attrib[1] > 16) + goto err_exit; + attrib++; + break; + + case GLX_X_RENDERABLE: + case GLX_CONFIG_CAVEAT: + attrib++; + break; + + case GLX_RENDER_TYPE: + if (attrib[1]!=GLX_RGBA_BIT) + goto err_exit; + attrib++; + break; + + case GLX_DRAWABLE_TYPE: + if ( !(attrib[1] & GLX_WINDOW_BIT) + && !(attrib[1] & GLX_PIXMAP_BIT)) + goto err_exit; + attrib++; + break; + + case GLX_X_VISUAL_TYPE: + case GLX_TRANSPARENT_TYPE_EXT: + case GLX_TRANSPARENT_INDEX_VALUE_EXT: + case GLX_TRANSPARENT_RED_VALUE_EXT: + case GLX_TRANSPARENT_GREEN_VALUE_EXT: + case GLX_TRANSPARENT_BLUE_VALUE_EXT: + case GLX_TRANSPARENT_ALPHA_VALUE_EXT: + /* ignore */ + crWarning("glXChooseVisual: ignoring attribute 0x%x", *attrib); + attrib++; + break; + + break; + default: + crWarning( "glXChooseVisual: bad attrib=0x%x, ignoring", *attrib ); + attrib++; + break; + } + } + + if (fbconfig) + { + GLXFBConfig *pGLXFBConfigs; + + *nelements = 1; + pGLXFBConfigs = (GLXFBConfig *) crAlloc(*nelements * sizeof(GLXFBConfig)); + pGLXFBConfigs[0] = (GLXFBConfig)fbconfig; + return pGLXFBConfigs; + } + else + { + return VBOXGLXTAG(glXGetFBConfigs)(dpy, screen, nelements); + } + +err_exit: + crWarning("glXChooseFBConfig returning NULL, due to attrib=0x%x, next=0x%x", attrib[0], attrib[1]); + return NULL; +} + +DECLEXPORT(GLXContext) +VBOXGLXTAG(glXCreateNewContext)(Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct) +{ + (void) dpy; + (void) config; + (void) render_type; + (void) share_list; + (void) direct; + + if (render_type != GLX_RGBA_TYPE) + { + crWarning("glXCreateNewContext, unsupported render_type %x", render_type); + return NULL; + } + + return VBOXGLXTAG(glXCreateContext)(dpy, NULL, share_list, direct); +} + +DECLEXPORT(GLXPbuffer) +VBOXGLXTAG(glXCreatePbuffer)(Display *dpy, GLXFBConfig config, ATTRIB_TYPE *attrib_list) +{ + (void) dpy; + (void) config; + (void) attrib_list; + crWarning("glXCreatePbuffer not implemented by Chromium"); + return 0; +} + +/* Note: there're examples where glxpixmaps are created without current context, so can't do much of the work here. + * Instead we'd do necessary initialization on first use of those pixmaps. + */ +DECLEXPORT(GLXPixmap) +VBOXGLXTAG(glXCreatePixmap)(Display *dpy, GLXFBConfig config, Pixmap pixmap, ATTRIB_TYPE *attrib_list) +{ + ATTRIB_TYPE *attrib; + GLX_Pixmap_t *pGlxPixmap; + (void) dpy; + (void) config; + +#if 0 + { + int x, y; + unsigned int w, h; + unsigned int border; + unsigned int depth; + Window root; + + crDebug("glXCreatePixmap called for %lu", pixmap); + + XLOCK(dpy); + if (!XGetGeometry(dpy, pixmap, &root, &x, &y, &w, &h, &border, &depth)) + { + XSync(dpy, False); + if (!XGetGeometry(dpy, pixmap, &root, &x, &y, &w, &h, &border, &depth)) + { + crDebug("fail"); + } + } + crDebug("root: %lu, [%i,%i %u,%u]", root, x, y, w, h); + XUNLOCK(dpy); + } +#endif + + pGlxPixmap = crCalloc(sizeof(GLX_Pixmap_t)); + if (!pGlxPixmap) + { + crWarning("glXCreatePixmap failed to allocate memory"); + return 0; + } + + pGlxPixmap->format = GL_RGBA; + pGlxPixmap->target = GL_TEXTURE_2D; + + if (attrib_list) + { + for (attrib = attrib_list; *attrib != None; attrib++) + { + switch (*attrib) + { + case GLX_TEXTURE_FORMAT_EXT: + attrib++; + switch (*attrib) + { + case GLX_TEXTURE_FORMAT_RGBA_EXT: + pGlxPixmap->format = GL_RGBA; + break; + case GLX_TEXTURE_FORMAT_RGB_EXT: + pGlxPixmap->format = GL_RGB; + break; + default: + crDebug("Unexpected GLX_TEXTURE_FORMAT_EXT 0x%x", (unsigned int) *attrib); + } + break; + case GLX_TEXTURE_TARGET_EXT: + attrib++; + switch (*attrib) + { + case GLX_TEXTURE_2D_EXT: + pGlxPixmap->target = GL_TEXTURE_2D; + break; + case GLX_TEXTURE_RECTANGLE_EXT: + pGlxPixmap->target = GL_TEXTURE_RECTANGLE_NV; + break; + default: + crDebug("Unexpected GLX_TEXTURE_TARGET_EXT 0x%x", (unsigned int) *attrib); + } + break; + default: attrib++; + } + } + } + + crHashtableAdd(stub.pGLXPixmapsHash, (unsigned int) pixmap, pGlxPixmap); + return (GLXPixmap) pixmap; +} + +DECLEXPORT(GLXWindow) +VBOXGLXTAG(glXCreateWindow)(Display *dpy, GLXFBConfig config, Window win, ATTRIB_TYPE *attrib_list) +{ + GLXFBConfig *realcfg; + int nconfigs; + (void) config; + + if (stub.wsInterface.glXGetFBConfigs) + { + realcfg = stub.wsInterface.glXGetFBConfigs(dpy, 0, &nconfigs); + if (!realcfg || nconfigs<1) + { + crWarning("glXCreateWindow !realcfg || nconfigs<1"); + return 0; + } + else + { + return stub.wsInterface.glXCreateWindow(dpy, realcfg[0], win, attrib_list); + } + } + else + { + if (attrib_list && *attrib_list!=None) + { + crWarning("Non empty attrib list in glXCreateWindow"); + return 0; + } + return (GLXWindow)win; + } +} + +DECLEXPORT(void) VBOXGLXTAG(glXDestroyPbuffer)(Display *dpy, GLXPbuffer pbuf) +{ + (void) dpy; + (void) pbuf; + crWarning("glXDestroyPbuffer not implemented by Chromium"); +} + +DECLEXPORT(void) VBOXGLXTAG(glXDestroyPixmap)(Display *dpy, GLXPixmap pixmap) +{ + stubFindPixmapParms_t parms; + + if (crHashtableSearch(stub.pGLXPixmapsHash, (unsigned int) pixmap)) + { + /*it's valid but never used glxpixmap, so simple free stored ptr*/ + crHashtableDelete(stub.pGLXPixmapsHash, (unsigned int) pixmap, crFree); + return; + } + else + { + /*it's either invalid glxpixmap or one which was already initialized, so it's stored in appropriate ctx hash*/ + parms.pCtx = NULL; + parms.pGlxPixmap = NULL; + parms.draw = pixmap; + crHashtableWalk(stub.contextTable, stubFindPixmapCB, &parms); + } + + if (!parms.pGlxPixmap) + { + crWarning("glXDestroyPixmap called for unknown glxpixmap 0x%x", (unsigned int) pixmap); + return; + } + + XLOCK(dpy); + if (parms.pGlxPixmap->gc) + { + XFreeGC(dpy, parms.pGlxPixmap->gc); + } + + if (parms.pGlxPixmap->hShmPixmap>0) + { + XFreePixmap(dpy, parms.pGlxPixmap->hShmPixmap); + } + XUNLOCK(dpy); + + if (parms.pGlxPixmap->hDamage>0) + { + //crDebug("Destroy: Damage for drawable 0x%x, handle 0x%x", (unsigned int) pixmap, (unsigned int) parms.pGlxPixmap->damage); + XDamageDestroy(dpy, parms.pGlxPixmap->hDamage); + } + + if (parms.pGlxPixmap->pDamageRegion) + { + XDestroyRegion(parms.pGlxPixmap->pDamageRegion); + } + + crHashtableDelete(parms.pCtx->pGLXPixmapsHash, (unsigned int) pixmap, crFree); +} + +DECLEXPORT(void) VBOXGLXTAG(glXDestroyWindow)(Display *dpy, GLXWindow win) +{ + (void) dpy; + (void) win; + /*crWarning("glXDestroyWindow not implemented by Chromium");*/ +} + +DECLEXPORT(GLXDrawable) VBOXGLXTAG(glXGetCurrentReadDrawable)(void) +{ + return currentReadDrawable; +} + +DECLEXPORT(int) VBOXGLXTAG(glXGetFBConfigAttrib)(Display *dpy, GLXFBConfig config, int attribute, int *value) +{ + XVisualInfo * pVisual; + const char * pExt; + + switch (attribute) + { + case GLX_DRAWABLE_TYPE: + *value = GLX_PIXMAP_BIT | GLX_WINDOW_BIT; + break; + case GLX_BIND_TO_TEXTURE_TARGETS_EXT: + *value = GLX_TEXTURE_2D_BIT_EXT; + pExt = (const char *) stub.spu->dispatch_table.GetString(GL_EXTENSIONS); + if (crStrstr(pExt, "GL_NV_texture_rectangle") + || crStrstr(pExt, "GL_ARB_texture_rectangle") + || crStrstr(pExt, "GL_EXT_texture_rectangle")) + { + *value |= GLX_TEXTURE_RECTANGLE_BIT_EXT; + } + break; + case GLX_BIND_TO_TEXTURE_RGBA_EXT: + *value = True; + break; + case GLX_BIND_TO_TEXTURE_RGB_EXT: + *value = True; + break; + case GLX_DOUBLEBUFFER: + //crDebug("attribute=GLX_DOUBLEBUFFER"); + *value = True; + break; + case GLX_Y_INVERTED_EXT: + *value = True; + break; + case GLX_ALPHA_SIZE: + //crDebug("attribute=GLX_ALPHA_SIZE"); + *value = 8; + break; + case GLX_BUFFER_SIZE: + //crDebug("attribute=GLX_BUFFER_SIZE"); + *value = 32; + break; + case GLX_STENCIL_SIZE: + //crDebug("attribute=GLX_STENCIL_SIZE"); + *value = 8; + break; + case GLX_DEPTH_SIZE: + *value = 24; + //crDebug("attribute=GLX_DEPTH_SIZE"); + break; + case GLX_BIND_TO_MIPMAP_TEXTURE_EXT: + *value = 0; + break; + case GLX_RENDER_TYPE: + //crDebug("attribute=GLX_RENDER_TYPE"); + *value = GLX_RGBA_BIT; + break; + case GLX_CONFIG_CAVEAT: + //crDebug("attribute=GLX_CONFIG_CAVEAT"); + *value = GLX_NONE; + break; + case GLX_VISUAL_ID: + //crDebug("attribute=GLX_VISUAL_ID"); + pVisual = visualInfoFromFBConfig(dpy, config); + if (!pVisual) + { + crWarning("glXGetFBConfigAttrib for %p, failed to get XVisualInfo", config); + return GLX_BAD_ATTRIBUTE; + } + *value = pVisual->visualid; + XFree(pVisual); + break; + case GLX_FBCONFIG_ID: + *value = (int)(intptr_t)config; + break; + case GLX_RED_SIZE: + case GLX_GREEN_SIZE: + case GLX_BLUE_SIZE: + *value = 8; + break; + case GLX_LEVEL: + *value = 0; + break; + case GLX_STEREO: + *value = false; + break; + case GLX_AUX_BUFFERS: + *value = 0; + break; + case GLX_ACCUM_RED_SIZE: + case GLX_ACCUM_GREEN_SIZE: + case GLX_ACCUM_BLUE_SIZE: + case GLX_ACCUM_ALPHA_SIZE: + *value = 0; + break; + case GLX_X_VISUAL_TYPE: + *value = GLX_TRUE_COLOR; + break; + case GLX_TRANSPARENT_TYPE: + *value = GLX_NONE; + break; + case GLX_SAMPLE_BUFFERS: + case GLX_SAMPLES: + *value = 1; + break; + case GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT: + *value = 0; + break; + default: + crDebug("glXGetFBConfigAttrib: unknown attribute=0x%x", attribute); + return GLX_BAD_ATTRIBUTE; + } + + return Success; +} + +DECLEXPORT(GLXFBConfig *) VBOXGLXTAG(glXGetFBConfigs)(Display *dpy, int screen, int *nelements) +{ + int i; + + GLXFBConfig *pGLXFBConfigs = crAlloc(sizeof(GLXFBConfig)); + + *nelements = 1; + XLOCK(dpy); + *pGLXFBConfigs = defaultFBConfigForScreen(dpy, screen); + XUNLOCK(dpy); + + crDebug("glXGetFBConfigs returned %i configs", *nelements); + for (i=0; i<*nelements; ++i) + { + crDebug("glXGetFBConfigs[%i]=0x%x", i, (unsigned)(uintptr_t) pGLXFBConfigs[i]); + } + return pGLXFBConfigs; +} + +DECLEXPORT(void) VBOXGLXTAG(glXGetSelectedEvent)(Display *dpy, GLXDrawable draw, unsigned long *event_mask) +{ + (void) dpy; + (void) draw; + (void) event_mask; + crWarning("glXGetSelectedEvent not implemented by Chromium"); +} + +DECLEXPORT(XVisualInfo *) VBOXGLXTAG(glXGetVisualFromFBConfig)(Display *dpy, GLXFBConfig config) +{ + return visualInfoFromFBConfig(dpy, config); +} + +DECLEXPORT(Bool) VBOXGLXTAG(glXMakeContextCurrent)(Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx) +{ + currentReadDrawable = read; + return VBOXGLXTAG(glXMakeCurrent)(display, draw, ctx); +} + +DECLEXPORT(int) VBOXGLXTAG(glXQueryContext)(Display *dpy, GLXContext ctx, int attribute, int *value) +{ + (void) dpy; + (void) ctx; + (void) attribute; + (void) value; + crWarning("glXQueryContext not implemented by Chromium"); + return 0; +} + +DECLEXPORT(void) VBOXGLXTAG(glXQueryDrawable)(Display *dpy, GLXDrawable draw, int attribute, unsigned int *value) +{ + (void) dpy; + (void) draw; + (void) attribute; + (void) value; + crWarning("glXQueryDrawable not implemented by Chromium"); +} + +DECLEXPORT(void) VBOXGLXTAG(glXSelectEvent)(Display *dpy, GLXDrawable draw, unsigned long event_mask) +{ + (void) dpy; + (void) draw; + (void) event_mask; + crWarning("glXSelectEvent not implemented by Chromium"); +} + +#ifdef CR_EXT_texture_from_pixmap +/*typedef struct +{ + int x, y; + unsigned int w, h, border, depth; + Window root; + void *data; +} pminfo;*/ + +static void stubInitXSharedMemory(Display *dpy) +{ + int vma, vmi; + Bool pixmaps; + + if (stub.bShmInitFailed || stub.xshmSI.shmid>=0) + return; + + stub.bShmInitFailed = GL_TRUE; + + /* Check for extension and pixmaps format */ + XLOCK(dpy); + if (!XShmQueryExtension(dpy)) + { + crWarning("No XSHM extension"); + XUNLOCK(dpy); + return; + } + + if (!XShmQueryVersion(dpy, &vma, &vmi, &pixmaps) || !pixmaps) + { + crWarning("XSHM extension doesn't support pixmaps"); + XUNLOCK(dpy); + return; + } + + if (XShmPixmapFormat(dpy)!=ZPixmap) + { + crWarning("XSHM extension doesn't support ZPixmap format"); + XUNLOCK(dpy); + return; + } + XUNLOCK(dpy); + + /* Alloc shared memory, so far using hardcoded value...could fail for bigger displays one day */ + stub.xshmSI.readOnly = false; + stub.xshmSI.shmid = shmget(IPC_PRIVATE, 4*4096*2048, IPC_CREAT | 0600); + if (stub.xshmSI.shmid<0) + { + crWarning("XSHM Failed to create shared segment"); + return; + } + + stub.xshmSI.shmaddr = (char*) shmat(stub.xshmSI.shmid, NULL, 0); + if (stub.xshmSI.shmaddr==(void*)-1) + { + crWarning("XSHM Failed to attach shared segment"); + shmctl(stub.xshmSI.shmid, IPC_RMID, 0); + return; + } + + XLOCK(dpy); + if (!XShmAttach(dpy, &stub.xshmSI)) + { + crWarning("XSHM Failed to attach shared segment to XServer"); + shmctl(stub.xshmSI.shmid, IPC_RMID, 0); + shmdt(stub.xshmSI.shmaddr); + XUNLOCK(dpy); + return; + } + XUNLOCK(dpy); + + stub.bShmInitFailed = GL_FALSE; + crInfo("Using XSHM for GLX_EXT_texture_from_pixmap"); + + /*Anyway mark to be deleted when our process detaches it, in case of segfault etc*/ + +/* Ramshankar: Solaris compiz fix */ +#ifndef RT_OS_SOLARIS + shmctl(stub.xshmSI.shmid, IPC_RMID, 0); +#endif +} + +void stubQueryXDamageExtension(Display *dpy, ContextInfo *pContext) +{ + int erb, vma, vmi; + + CRASSERT(pContext); + + if (pContext->damageQueryFailed) + return; + + pContext->damageQueryFailed = True; + + if (!XDamageQueryExtension(dpy, &pContext->damageEventsBase, &erb) + || !XDamageQueryVersion(dpy, &vma, &vmi)) + { + crWarning("XDamage not found or old version (%i.%i), going to run *very* slow", vma, vmi); + return; + } + + crDebug("XDamage %i.%i", vma, vmi); + pContext->damageQueryFailed = False; +} + +static void stubFetchDamageOnDrawable(Display *dpy, GLX_Pixmap_t *pGlxPixmap) +{ + Damage damage = pGlxPixmap->hDamage; + + if (damage) + { + XRectangle *returnRects; + int nReturnRects; + + /* Get the damage region as a server region */ + XserverRegion serverDamageRegion = XFixesCreateRegion (dpy, NULL, 0); + + /* Unite damage region with server region and clear damage region */ + XDamageSubtract (dpy, + damage, + None, /* subtract all damage from this region */ + serverDamageRegion /* save in serverDamageRegion */); + + /* Fetch damage rectangles */ + returnRects = XFixesFetchRegion (dpy, serverDamageRegion, &nReturnRects); + + /* Delete region */ + XFixesDestroyRegion (dpy, serverDamageRegion); + + if (pGlxPixmap->pDamageRegion) + { + /* If it's dirty and regions are empty, it marked for full update, so do nothing.*/ + if (!pGlxPixmap->bPixmapImageDirty || !XEmptyRegion(pGlxPixmap->pDamageRegion)) + { + int i = 0; + for (; i < nReturnRects; ++i) + { + if (CR_MAX_DAMAGE_REGIONS_TRACKED <= pGlxPixmap->pDamageRegion->numRects) + { + /* Mark for full update */ + EMPTY_REGION(pGlxPixmap->pDamageRegion); + } + else + { + /* Add to damage regions */ + XUnionRectWithRegion(&returnRects[i], pGlxPixmap->pDamageRegion, pGlxPixmap->pDamageRegion); + } + } + } + } + + XFree(returnRects); + + pGlxPixmap->bPixmapImageDirty = True; + } +} + +static const CRPixelPackState defaultPacking = +{ + 0, /*rowLength*/ + 0, /*skipRows*/ + 0, /*skipPixels*/ + 1, /*alignment*/ + 0, /*imageHeight*/ + 0, /*skipImages*/ + GL_FALSE, /*swapBytes*/ + GL_FALSE /*lsbFirst*/ +}; + +static void stubGetUnpackState(CRPixelPackState *pUnpackState) +{ + stub.spu->dispatch_table.GetIntegerv(GL_UNPACK_ROW_LENGTH, &pUnpackState->rowLength); + stub.spu->dispatch_table.GetIntegerv(GL_UNPACK_SKIP_ROWS, &pUnpackState->skipRows); + stub.spu->dispatch_table.GetIntegerv(GL_UNPACK_SKIP_PIXELS, &pUnpackState->skipPixels); + stub.spu->dispatch_table.GetIntegerv(GL_UNPACK_ALIGNMENT, &pUnpackState->alignment); + stub.spu->dispatch_table.GetBooleanv(GL_UNPACK_SWAP_BYTES, &pUnpackState->swapBytes); + stub.spu->dispatch_table.GetBooleanv(GL_UNPACK_LSB_FIRST, &pUnpackState->psLSBFirst); +} + +static void stubSetUnpackState(const CRPixelPackState *pUnpackState) +{ + stub.spu->dispatch_table.PixelStorei(GL_UNPACK_ROW_LENGTH, pUnpackState->rowLength); + stub.spu->dispatch_table.PixelStorei(GL_UNPACK_SKIP_ROWS, pUnpackState->skipRows); + stub.spu->dispatch_table.PixelStorei(GL_UNPACK_SKIP_PIXELS, pUnpackState->skipPixels); + stub.spu->dispatch_table.PixelStorei(GL_UNPACK_ALIGNMENT, pUnpackState->alignment); + stub.spu->dispatch_table.PixelStorei(GL_UNPACK_SWAP_BYTES, pUnpackState->swapBytes); + stub.spu->dispatch_table.PixelStorei(GL_UNPACK_LSB_FIRST, pUnpackState->psLSBFirst); +} + +static GLX_Pixmap_t* stubInitGlxPixmap(GLX_Pixmap_t* pCreateInfoPixmap, Display *dpy, GLXDrawable draw, ContextInfo *pContext) +{ + int x, y; + unsigned int w, h; + unsigned int border; + unsigned int depth; + Window root; + GLX_Pixmap_t *pGlxPixmap; + + CRASSERT(pContext && pCreateInfoPixmap); + + XLOCK(dpy); + if (!XGetGeometry(dpy, (Pixmap)draw, &root, &x, &y, &w, &h, &border, &depth)) + { + XSync(dpy, False); + if (!XGetGeometry(dpy, (Pixmap)draw, &root, &x, &y, &w, &h, &border, &depth)) + { + crWarning("stubInitGlxPixmap failed in call to XGetGeometry for 0x%x", (int) draw); + XUNLOCK(dpy); + return NULL; + } + } + + pGlxPixmap = crAlloc(sizeof(GLX_Pixmap_t)); + if (!pGlxPixmap) + { + crWarning("stubInitGlxPixmap failed to allocate memory"); + XUNLOCK(dpy); + return NULL; + } + + pGlxPixmap->x = x; + pGlxPixmap->y = y; + pGlxPixmap->w = w; + pGlxPixmap->h = h; + pGlxPixmap->border = border; + pGlxPixmap->depth = depth; + pGlxPixmap->root = root; + pGlxPixmap->format = pCreateInfoPixmap->format; + pGlxPixmap->target = pCreateInfoPixmap->target; + + /* Try to allocate shared memory + * As we're allocating huge chunk of memory, do it in this function, only if this extension is really used + */ + if (!stub.bShmInitFailed && stub.xshmSI.shmid<0) + { + stubInitXSharedMemory(dpy); + } + + if (stub.xshmSI.shmid>=0) + { + XGCValues xgcv; + xgcv.graphics_exposures = False; + xgcv.subwindow_mode = IncludeInferiors; + pGlxPixmap->gc = XCreateGC(dpy, (Pixmap)draw, GCGraphicsExposures|GCSubwindowMode, &xgcv); + + pGlxPixmap->hShmPixmap = XShmCreatePixmap(dpy, pGlxPixmap->root, stub.xshmSI.shmaddr, &stub.xshmSI, + pGlxPixmap->w, pGlxPixmap->h, pGlxPixmap->depth); + } + else + { + pGlxPixmap->gc = NULL; + pGlxPixmap->hShmPixmap = 0; + } + XUNLOCK(dpy); + + /* If there's damage extension, then get handle for damage events related to this pixmap */ + if (!pContext->damageQueryFailed) + { + pGlxPixmap->hDamage = XDamageCreate(dpy, (Pixmap)draw, XDamageReportNonEmpty); + /*crDebug("Create: Damage for drawable 0x%x, handle 0x%x (level=%i)", + (unsigned int) draw, (unsigned int) pGlxPixmap->damage, (int) XDamageReportRawRectangles);*/ + pGlxPixmap->pDamageRegion = XCreateRegion(); + if (!pGlxPixmap->pDamageRegion) + { + crWarning("stubInitGlxPixmap failed to create empty damage region for drawable 0x%x", (unsigned int) draw); + } + + /*We have never seen this pixmap before, so mark it as dirty for first use*/ + pGlxPixmap->bPixmapImageDirty = True; + } + else + { + pGlxPixmap->hDamage = 0; + pGlxPixmap->pDamageRegion = NULL; + } + + /* glTexSubImage2D generates GL_INVALID_OP if texture array hasn't been defined by a call to glTexImage2D first. + * It's fine for small textures which would be updated in stubXshmUpdateWholeImage, but we'd never call glTexImage2D for big ones. + * Note that we're making empty texture by passing NULL as pixels pointer, so there's no overhead transferring data to host.*/ + if (CR_MAX_TRANSFER_SIZE < 4*pGlxPixmap->w*pGlxPixmap->h) + { + stub.spu->dispatch_table.TexImage2D(pGlxPixmap->target, 0, pGlxPixmap->format, pGlxPixmap->w, pGlxPixmap->h, 0, + GL_BGRA, GL_UNSIGNED_BYTE, NULL); + } + + crHashtableAdd(pContext->pGLXPixmapsHash, (unsigned int) draw, pGlxPixmap); + crHashtableDelete(stub.pGLXPixmapsHash, (unsigned int) draw, crFree); + + return pGlxPixmap; +} + +static void stubXshmUpdateWholeImage(Display *dpy, GLXDrawable draw, GLX_Pixmap_t *pGlxPixmap) +{ + /* To limit the size of transferring buffer, split bigger texture into regions + * which fit into connection buffer. Could be done in hgcm or packspu but implementation in this place allows to avoid + * unnecessary memcpy. + * This also workarounds guest driver failures when sending 6+mb texture buffers on linux. + */ + if (CR_MAX_TRANSFER_SIZE < 4*pGlxPixmap->w*pGlxPixmap->h) + { + XRectangle rect; + + rect.x = pGlxPixmap->x; + rect.y = pGlxPixmap->y; + rect.width = pGlxPixmap->w; + rect.height = CR_MAX_TRANSFER_SIZE/(4*pGlxPixmap->w); + + /*crDebug("Texture size too big, splitting in lower sized chunks. [%i,%i,%i,%i] (%i)", + pGlxPixmap->x, pGlxPixmap->y, pGlxPixmap->w, pGlxPixmap->h, rect.height);*/ + + for (; (rect.y+rect.height)<=(pGlxPixmap->y+(int)pGlxPixmap->h); rect.y+=rect.height) + { + stubXshmUpdateImageRect(dpy, draw, pGlxPixmap, &rect); + } + + if (rect.y!=(pGlxPixmap->y+(int)pGlxPixmap->h)) + { + rect.height=pGlxPixmap->h-rect.y; + stubXshmUpdateImageRect(dpy, draw, pGlxPixmap, &rect); + } + } + else + { + CRPixelPackState unpackState; + + XLOCK(dpy); + XCopyArea(dpy, (Pixmap)draw, pGlxPixmap->hShmPixmap, pGlxPixmap->gc, + pGlxPixmap->x, pGlxPixmap->y, pGlxPixmap->w, pGlxPixmap->h, 0, 0); + /* Have to make sure XCopyArea is processed */ + XSync(dpy, False); + XUNLOCK(dpy); + + stubGetUnpackState(&unpackState); + stubSetUnpackState(&defaultPacking); + stub.spu->dispatch_table.TexImage2D(pGlxPixmap->target, 0, pGlxPixmap->format, pGlxPixmap->w, pGlxPixmap->h, 0, + GL_BGRA, GL_UNSIGNED_BYTE, stub.xshmSI.shmaddr); + stubSetUnpackState(&unpackState); + /*crDebug("Sync texture for drawable 0x%x(dmg handle 0x%x) [%i,%i,%i,%i]", + (unsigned int) draw, (unsigned int)pGlxPixmap->hDamage, + pGlxPixmap->x, pGlxPixmap->y, pGlxPixmap->w, pGlxPixmap->h);*/ + } +} + +static void stubXshmUpdateImageRect(Display *dpy, GLXDrawable draw, GLX_Pixmap_t *pGlxPixmap, XRectangle *pRect) +{ + /* See comment in stubXshmUpdateWholeImage */ + if (CR_MAX_TRANSFER_SIZE < 4*pRect->width*pRect->height) + { + XRectangle rect; + + rect.x = pRect->x; + rect.y = pRect->y; + rect.width = pRect->width; + rect.height = CR_MAX_TRANSFER_SIZE/(4*pRect->width); + + /*crDebug("Region size too big, splitting in lower sized chunks. [%i,%i,%i,%i] (%i)", + pRect->x, pRect->y, pRect->width, pRect->height, rect.height);*/ + + for (; (rect.y+rect.height)<=(pRect->y+pRect->height); rect.y+=rect.height) + { + stubXshmUpdateImageRect(dpy, draw, pGlxPixmap, &rect); + } + + if (rect.y!=(pRect->y+pRect->height)) + { + rect.height=pRect->y+pRect->height-rect.y; + stubXshmUpdateImageRect(dpy, draw, pGlxPixmap, &rect); + } + } + else + { + CRPixelPackState unpackState; + + XLOCK(dpy); + XCopyArea(dpy, (Pixmap)draw, pGlxPixmap->hShmPixmap, pGlxPixmap->gc, + pRect->x, pRect->y, pRect->width, pRect->height, 0, 0); + /* Have to make sure XCopyArea is processed */ + XSync(dpy, False); + XUNLOCK(dpy); + + stubGetUnpackState(&unpackState); + stubSetUnpackState(&defaultPacking); + if (pRect->width!=pGlxPixmap->w) + { + stub.spu->dispatch_table.PixelStorei(GL_UNPACK_ROW_LENGTH, pGlxPixmap->w); + } + stub.spu->dispatch_table.TexSubImage2D(pGlxPixmap->target, 0, pRect->x, pRect->y, pRect->width, pRect->height, + GL_BGRA, GL_UNSIGNED_BYTE, stub.xshmSI.shmaddr); + stubSetUnpackState(&unpackState); + + /*crDebug("Region sync texture for drawable 0x%x(dmg handle 0x%x) [%i,%i,%i,%i]", + (unsigned int) draw, (unsigned int)pGlxPixmap->hDamage, + pRect->x, pRect->y, pRect->width, pRect->height);*/ + } +} + +#if 0 +Bool checkevents(Display *display, XEvent *event, XPointer arg) +{ + //crDebug("got type: 0x%x", event->type); + if (event->type==damage_evb+XDamageNotify) + { + ContextInfo *context = stubGetCurrentContext(); + XDamageNotifyEvent *e = (XDamageNotifyEvent *) event; + /* we're interested in pixmaps only...and those have e->drawable set to 0 or other strange value for some odd reason + * so have to walk glxpixmaps hashtable to find if we have damage event handle assigned to some pixmap + */ + /*crDebug("Event: Damage for drawable 0x%x, handle 0x%x (level=%i) [%i,%i,%i,%i]", + (unsigned int) e->drawable, (unsigned int) e->damage, (int) e->level, + e->area.x, e->area.y, e->area.width, e->area.height);*/ + CRASSERT(context); + crHashtableWalk(context->pGLXPixmapsHash, checkdamageCB, e); + } + return False; +} +#endif + +/** @todo check what error codes could we throw for failures here*/ +DECLEXPORT(void) VBOXGLXTAG(glXBindTexImageEXT)(Display *dpy, GLXDrawable draw, int buffer, const int *attrib_list) +{ + ContextInfo *context = stubGetCurrentContext(); + GLX_Pixmap_t *pGlxPixmap; + RT_NOREF(buffer, attrib_list); + + if (!context) + { + crWarning("glXBindTexImageEXT called without current context"); + return; + } + + pGlxPixmap = (GLX_Pixmap_t *) crHashtableSearch(context->pGLXPixmapsHash, (unsigned int) draw); + if (!pGlxPixmap) + { + pGlxPixmap = (GLX_Pixmap_t *) crHashtableSearch(stub.pGLXPixmapsHash, (unsigned int) draw); + if (!pGlxPixmap) + { + crDebug("Unknown drawable 0x%x in glXBindTexImageEXT!", (unsigned int) draw); + return; + } + pGlxPixmap = stubInitGlxPixmap(pGlxPixmap, dpy, draw, context); + if (!pGlxPixmap) + { + crDebug("glXBindTexImageEXT failed to get pGlxPixmap"); + return; + } + } + + /* If there's damage extension, then process incoming events as we need the information right now */ + if (!context->damageQueryFailed) + { + /* Sync connection */ + XLOCK(dpy); + XSync(dpy, False); + XUNLOCK(dpy); + + stubFetchDamageOnDrawable(dpy, pGlxPixmap); + } + + /* No shared memory? Rollback to use slow x protocol then */ + if (stub.xshmSI.shmid<0) + { + /** @todo add damage support here too*/ + XImage *pxim; + CRPixelPackState unpackState; + + XLOCK(dpy); + pxim = XGetImage(dpy, (Pixmap)draw, pGlxPixmap->x, pGlxPixmap->y, pGlxPixmap->w, pGlxPixmap->h, AllPlanes, ZPixmap); + XUNLOCK(dpy); + /*if (pxim) + { + if (!ptextable) + { + ptextable = crAllocHashtable(); + } + pm = crHashtableSearch(ptextable, (unsigned int) draw); + if (!pm) + { + pm = crCalloc(sizeof(pminfo)); + crHashtableAdd(ptextable, (unsigned int) draw, pm); + } + pm->w = w; + pm->h = h; + if (pm->data) crFree(pm->data); + pm->data = crAlloc(4*w*h); + crMemcpy(pm->data, (void*)(&(pxim->data[0])), 4*w*h); + }*/ + + if (NULL==pxim) + { + crWarning("Failed, to get pixmap data for 0x%x", (unsigned int) draw); + return; + } + + stubGetUnpackState(&unpackState); + stubSetUnpackState(&defaultPacking); + stub.spu->dispatch_table.TexImage2D(pGlxPixmap->target, 0, pGlxPixmap->format, pxim->width, pxim->height, 0, + GL_BGRA, GL_UNSIGNED_BYTE, (void*)(&(pxim->data[0]))); + stubSetUnpackState(&unpackState); + XDestroyImage(pxim); + } + else /* Use shm to get pixmap data */ + { + /* Check if we have damage extension */ + if (!context->damageQueryFailed) + { + if (pGlxPixmap->bPixmapImageDirty) + { + /* Either we failed to allocate damage region or this pixmap is marked for full update */ + if (!pGlxPixmap->pDamageRegion || XEmptyRegion(pGlxPixmap->pDamageRegion)) + { + /*crDebug("**FULL** update for 0x%x", (unsigned int)draw);*/ + stubXshmUpdateWholeImage(dpy, draw, pGlxPixmap); + } + else + { + long fullArea, damageArea=0, clipdamageArea, i; + XRectangle damageClipBox; + + fullArea = pGlxPixmap->w * pGlxPixmap->h; + XClipBox(pGlxPixmap->pDamageRegion, &damageClipBox); + clipdamageArea = damageClipBox.width * damageClipBox.height; + + //crDebug("FullSize [%i,%i,%i,%i]", pGlxPixmap->x, pGlxPixmap->y, pGlxPixmap->w, pGlxPixmap->h); + //crDebug("Clip [%i,%i,%i,%i]", damageClipBox.x, damageClipBox.y, damageClipBox.width, damageClipBox.height); + + for (i=0; i<pGlxPixmap->pDamageRegion->numRects; ++i) + { + BoxPtr pBox = &pGlxPixmap->pDamageRegion->rects[i]; + damageArea += (pBox->x2-pBox->x1)*(pBox->y2-pBox->y1); + //crDebug("Damage rect [%i,%i,%i,%i]", pBox->x1, pBox->y1, pBox->x2, pBox->y2); + } + + if (damageArea>clipdamageArea || clipdamageArea>fullArea) + { + crWarning("glXBindTexImageEXT, damage regions seems to be broken, forcing full update"); + /*crDebug("**FULL** update for 0x%x, numRect=%li, *FS*=%li, CS=%li, DS=%li", + (unsigned int)draw, pGlxPixmap->pDamageRegion->numRects, fullArea, clipdamageArea, damageArea);*/ + stubXshmUpdateWholeImage(dpy, draw, pGlxPixmap); + } + else /*We have corect damage info*/ + { + if (CR_MIN_DAMAGE_PROFIT_SIZE > (fullArea-damageArea)) + { + /*crDebug("**FULL** update for 0x%x, numRect=%li, *FS*=%li, CS=%li, DS=%li", + (unsigned int)draw, pGlxPixmap->pDamageRegion->numRects, fullArea, clipdamageArea, damageArea);*/ + stubXshmUpdateWholeImage(dpy, draw, pGlxPixmap); + } + else if (CR_MIN_DAMAGE_PROFIT_SIZE > (clipdamageArea-damageArea)) + { + /*crDebug("**PARTIAL** update for 0x%x, numRect=%li, FS=%li, *CS*=%li, DS=%li", + (unsigned int)draw, pGlxPixmap->pDamageRegion->numRects, fullArea, clipdamageArea, damageArea);*/ + stubXshmUpdateImageRect(dpy, draw, pGlxPixmap, &damageClipBox); + } + else + { + /*crDebug("**PARTIAL** update for 0x%x, numRect=*%li*, FS=%li, CS=%li, *DS*=%li", + (unsigned int)draw, pGlxPixmap->pDamageRegion->numRects, fullArea, clipdamageArea, damageArea);*/ + for (i=0; i<pGlxPixmap->pDamageRegion->numRects; ++i) + { + XRectangle rect; + BoxPtr pBox = &pGlxPixmap->pDamageRegion->rects[i]; + + rect.x = pBox->x1; + rect.y = pBox->y1; + rect.width = pBox->x2-pBox->x1; + rect.height = pBox->y2-pBox->y1; + + stubXshmUpdateImageRect(dpy, draw, pGlxPixmap, &rect); + } + } + } + } + + /* Clean dirty flag and damage region */ + pGlxPixmap->bPixmapImageDirty = False; + if (pGlxPixmap->pDamageRegion) + EMPTY_REGION(pGlxPixmap->pDamageRegion); + } + } + else + { + stubXshmUpdateWholeImage(dpy, draw, pGlxPixmap); + } + } +} + +DECLEXPORT(void) VBOXGLXTAG(glXReleaseTexImageEXT)(Display *dpy, GLXDrawable draw, int buffer) +{ + (void) dpy; + (void) draw; + (void) buffer; + //crDebug("glXReleaseTexImageEXT 0x%x", (unsigned int)draw); +} +#endif + +#endif /* GLX_EXTRAS */ + + +#ifdef GLX_SGIX_video_resize +/* more dummy funcs. These help when linking with older GLUTs */ + +DECLEXPORT(int) VBOXGLXTAG(glXBindChannelToWindowSGIX)(Display *dpy, int scrn, int chan, Window w) +{ + (void) dpy; + (void) scrn; + (void) chan; + (void) w; + crDebug("glXBindChannelToWindowSGIX"); + return 0; +} + +DECLEXPORT(int) VBOXGLXTAG(glXChannelRectSGIX)(Display *dpy, int scrn, int chan, int x , int y, int w, int h) +{ + (void) dpy; + (void) scrn; + (void) chan; + (void) x; + (void) y; + (void) w; + (void) h; + crDebug("glXChannelRectSGIX"); + return 0; +} + +DECLEXPORT(int) VBOXGLXTAG(glXQueryChannelRectSGIX)(Display *dpy, int scrn, int chan, int *x, int *y, int *w, int *h) +{ + (void) dpy; + (void) scrn; + (void) chan; + (void) x; + (void) y; + (void) w; + (void) h; + crDebug("glXQueryChannelRectSGIX"); + return 0; +} + +DECLEXPORT(int) VBOXGLXTAG(glXQueryChannelDeltasSGIX)(Display *dpy, int scrn, int chan, int *dx, int *dy, int *dw, int *dh) +{ + (void) dpy; + (void) scrn; + (void) chan; + (void) dx; + (void) dy; + (void) dw; + (void) dh; + crDebug("glXQueryChannelDeltasSGIX"); + return 0; +} + +DECLEXPORT(int) VBOXGLXTAG(glXChannelRectSyncSGIX)(Display *dpy, int scrn, int chan, GLenum synctype) +{ + (void) dpy; + (void) scrn; + (void) chan; + (void) synctype; + crDebug("glXChannelRectSyncSGIX"); + return 0; +} + +#endif /* GLX_SGIX_video_resize */ + +#ifdef VBOXOGL_FAKEDRI +DECLEXPORT(const char *) VBOXGLXTAG(glXGetDriverConfig)(const char *driverName) +{ + return NULL; +} + +DECLEXPORT(void) VBOXGLXTAG(glXFreeMemoryMESA)(Display *dpy, int scrn, void *pointer) +{ + (void) dpy; + (void) scrn; + (void) pointer; +} + +DECLEXPORT(GLXContext) VBOXGLXTAG(glXImportContextEXT)(Display *dpy, GLXContextID contextID) +{ + (void) dpy; + (void) contextID; + return NULL; +} + +DECLEXPORT(GLXContextID) VBOXGLXTAG(glXGetContextIDEXT)(const GLXContext ctx) +{ + (void) ctx; + return 0; +} + +DECLEXPORT(Bool) VBOXGLXTAG(glXMakeCurrentReadSGI)(Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx) +{ + return VBOXGLXTAG(glXMakeContextCurrent)(display, draw, read, ctx); +} + +DECLEXPORT(const char *) VBOXGLXTAG(glXGetScreenDriver)(Display *dpy, int scrNum) +{ + static char *screendriver = "vboxvideo"; + return screendriver; +} + +DECLEXPORT(Display *) VBOXGLXTAG(glXGetCurrentDisplayEXT)(void) +{ + return VBOXGLXTAG(glXGetCurrentDisplay()); +} + +DECLEXPORT(void) VBOXGLXTAG(glXFreeContextEXT)(Display *dpy, GLXContext ctx) +{ + VBOXGLXTAG(glXDestroyContext(dpy, ctx)); +} + +/*Mesa internal*/ +DECLEXPORT(int) VBOXGLXTAG(glXQueryContextInfoEXT)(Display *dpy, GLXContext ctx) +{ + (void) dpy; + (void) ctx; + return 0; +} + +DECLEXPORT(void *) VBOXGLXTAG(glXAllocateMemoryMESA)(Display *dpy, int scrn, + size_t size, float readFreq, + float writeFreq, float priority) +{ + (void) dpy; + (void) scrn; + (void) size; + (void) readFreq; + (void) writeFreq; + (void) priority; + return NULL; +} + +DECLEXPORT(GLuint) VBOXGLXTAG(glXGetMemoryOffsetMESA)(Display *dpy, int scrn, const void *pointer) +{ + (void) dpy; + (void) scrn; + (void) pointer; + return 0; +} + +DECLEXPORT(GLXPixmap) VBOXGLXTAG(glXCreateGLXPixmapMESA)(Display *dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap) +{ + (void) dpy; + (void) visual; + (void) pixmap; + (void) cmap; + return 0; +} + +#endif /*VBOXOGL_FAKEDRI*/ diff --git a/src/VBox/Additions/common/crOpenGL/glx_c_exports.c b/src/VBox/Additions/common/crOpenGL/glx_c_exports.c new file mode 100644 index 00000000..c8068375 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/glx_c_exports.c @@ -0,0 +1,375 @@ +/* $Id: glx_c_exports.c $ */ +/** @file + * VirtualBox guest OpenGL DRI GLX C stubs + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "stub.h" +#include "dri_glx.h" +#include "fakedri_drv.h" + + +#ifdef VBOXOGL_FAKEDRI +/*DECLEXPORT(void) VBOXGLXENTRYTAG(glXGetDriverConfig)(const char *driverName) +{ + return glxim.GetDriverConfig(driverName); +}*/ + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXFreeMemoryMESA)(Display *dpy, int scrn, void *pointer) +{ + return glxim.FreeMemoryMESA(dpy, scrn, pointer); +} + +DECLEXPORT(GLXContext) VBOXGLXENTRYTAG(glXImportContextEXT)(Display *dpy, GLXContextID contextID) +{ + return glxim.ImportContextEXT(dpy, contextID); +} + +DECLEXPORT(GLXContextID) VBOXGLXENTRYTAG(glXGetContextIDEXT)(const GLXContext ctx) +{ + return glxim.GetContextIDEXT(ctx); +} + +DECLEXPORT(Bool) VBOXGLXENTRYTAG(glXMakeCurrentReadSGI)(Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx) +{ + return glxim.MakeCurrentReadSGI(display, draw, read, ctx); +} + + +/*const char * VBOXGLXENTRYTAG(glXGetScreenDriver)(Display *dpy, int scrNum) +{ + return glxim.GetScreenDriver(dpy, scrNum); +}*/ + + +DECLEXPORT(Display*) VBOXGLXENTRYTAG(glXGetCurrentDisplayEXT)(void) +{ + return glxim.GetCurrentDisplayEXT(); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXFreeContextEXT)(Display *dpy, GLXContext ctx) +{ + return glxim.FreeContextEXT(dpy, ctx); +} + +/*Mesa internal*/ +DECLEXPORT(int) VBOXGLXENTRYTAG(glXQueryContextInfoEXT)(Display *dpy, GLXContext ctx) +{ + return glxim.QueryContextInfoEXT(dpy, ctx); +} + +DECLEXPORT(void *) VBOXGLXENTRYTAG(glXAllocateMemoryMESA)(Display *dpy, int scrn, + size_t size, float readFreq, + float writeFreq, float priority) +{ + return glxim.AllocateMemoryMESA(dpy, scrn, size, readFreq, writeFreq, priority); +} + +DECLEXPORT(GLuint) VBOXGLXENTRYTAG(glXGetMemoryOffsetMESA)(Display *dpy, int scrn, const void *pointer ) +{ + return glxim.GetMemoryOffsetMESA(dpy, scrn, pointer); +} + + +DECLEXPORT(GLXPixmap) VBOXGLXENTRYTAG(glXCreateGLXPixmapMESA)(Display *dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap) +{ + return glxim.CreateGLXPixmapMESA(dpy, visual, pixmap, cmap); +} +#endif + +/*Common glX functions*/ +DECLEXPORT(void) VBOXGLXENTRYTAG(glXCopyContext)( Display *dpy, GLXContext src, GLXContext dst, unsigned long mask) +{ + return glxim.CopyContext(dpy, src, dst, mask); +} + + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXUseXFont)(Font font, int first, int count, int listBase) +{ + return glxim.UseXFont(font, first, count, listBase); +} + +DECLEXPORT(CR_GLXFuncPtr) VBOXGLXENTRYTAG(glXGetProcAddress)(const GLubyte *name) +{ + return glxim.GetProcAddress(name); +} + +DECLEXPORT(Bool) VBOXGLXENTRYTAG(glXQueryExtension)(Display *dpy, int *errorBase, int *eventBase) +{ + return glxim.QueryExtension(dpy, errorBase, eventBase); +} + +DECLEXPORT(Bool) VBOXGLXENTRYTAG(glXIsDirect)(Display *dpy, GLXContext ctx) +{ + return glxim.IsDirect(dpy, ctx); +} + +DECLEXPORT(GLXPixmap) VBOXGLXENTRYTAG(glXCreateGLXPixmap)(Display *dpy, XVisualInfo *vis, Pixmap pixmap) +{ + return glxim.CreateGLXPixmap(dpy, vis, pixmap); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXSwapBuffers)(Display *dpy, GLXDrawable drawable) +{ + return glxim.SwapBuffers(dpy, drawable); +} + + +DECLEXPORT(GLXDrawable) VBOXGLXENTRYTAG(glXGetCurrentDrawable)(void) +{ + return glxim.GetCurrentDrawable(); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXWaitGL)(void) +{ + return glxim.WaitGL(); +} + +DECLEXPORT(Display *) VBOXGLXENTRYTAG(glXGetCurrentDisplay)(void) +{ + return glxim.GetCurrentDisplay(); +} + +DECLEXPORT(const char *) VBOXGLXENTRYTAG(glXQueryServerString)(Display *dpy, int screen, int name) +{ + return glxim.QueryServerString(dpy, screen, name); +} + +DECLEXPORT(GLXContext) VBOXGLXENTRYTAG(glXCreateContext)(Display *dpy, XVisualInfo *vis, GLXContext share, Bool direct) +{ + return glxim.CreateContext(dpy, vis, share, direct); +} + +DECLEXPORT(int) VBOXGLXENTRYTAG(glXGetConfig)(Display *dpy, XVisualInfo *vis, int attrib, int *value) +{ + return glxim.GetConfig(dpy, vis, attrib, value); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXWaitX)(void) +{ + return glxim.WaitX(); +} + +DECLEXPORT(GLXContext) VBOXGLXENTRYTAG(glXGetCurrentContext)(void) +{ + return glxim.GetCurrentContext(); +} + +DECLEXPORT(const char *) VBOXGLXENTRYTAG(glXGetClientString)(Display *dpy, int name) +{ + return glxim.GetClientString(dpy, name); +} + +DECLEXPORT(Bool) VBOXGLXENTRYTAG(glXMakeCurrent)(Display *dpy, GLXDrawable drawable, GLXContext ctx) +{ + return glxim.MakeCurrent(dpy, drawable, ctx); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXDestroyContext)(Display *dpy, GLXContext ctx) +{ + return glxim.DestroyContext(dpy, ctx); +} + +DECLEXPORT(CR_GLXFuncPtr) VBOXGLXENTRYTAG(glXGetProcAddressARB)(const GLubyte *name) +{ + return glxim.GetProcAddressARB(name); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXDestroyGLXPixmap)(Display *dpy, GLXPixmap pix) +{ + return glxim.DestroyGLXPixmap(dpy, pix); +} + +DECLEXPORT(Bool) VBOXGLXENTRYTAG(glXQueryVersion)(Display *dpy, int *major, int *minor) +{ + return glxim.QueryVersion(dpy, major, minor); +} + +DECLEXPORT(XVisualInfo *) VBOXGLXENTRYTAG(glXChooseVisual)(Display *dpy, int screen, int *attribList) +{ + return glxim.ChooseVisual(dpy, screen, attribList); +} + +DECLEXPORT(const char *) VBOXGLXENTRYTAG(glXQueryExtensionsString)(Display *dpy, int screen) +{ + return glxim.QueryExtensionsString(dpy, screen); +} + +#if GLX_EXTRAS +DECLEXPORT(GLXPbufferSGIX) VBOXGLXENTRYTAG(glXCreateGLXPbufferSGIX) +(Display *dpy, GLXFBConfigSGIX config, unsigned int width, unsigned int height, int *attrib_list) +{ + return glxim.CreateGLXPbufferSGIX(dpy, config, width, height, attrib_list); +} + +DECLEXPORT(int) VBOXGLXENTRYTAG(glXQueryGLXPbufferSGIX) +(Display *dpy, GLXPbuffer pbuf, int attribute, unsigned int *value) +{ + return glxim.QueryGLXPbufferSGIX(dpy, pbuf, attribute, value); +} + +DECLEXPORT(GLXFBConfigSGIX *) VBOXGLXENTRYTAG(glXChooseFBConfigSGIX) +(Display *dpy, int screen, int *attrib_list, int *nelements) +{ + return glxim.ChooseFBConfigSGIX(dpy, screen, attrib_list, nelements); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXDestroyGLXPbufferSGIX)(Display *dpy, GLXPbuffer pbuf) +{ + return glxim.DestroyGLXPbufferSGIX(dpy, pbuf); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXSelectEventSGIX)(Display *dpy, GLXDrawable drawable, unsigned long mask) +{ + return glxim.SelectEventSGIX(dpy, drawable, mask); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXGetSelectedEventSGIX)(Display *dpy, GLXDrawable drawable, unsigned long *mask) +{ + return glxim.GetSelectedEventSGIX(dpy, drawable, mask); +} + +DECLEXPORT(GLXFBConfigSGIX) VBOXGLXENTRYTAG(glXGetFBConfigFromVisualSGIX)(Display *dpy, XVisualInfo *vis) +{ + return glxim.GetFBConfigFromVisualSGIX(dpy, vis); +} + +DECLEXPORT(XVisualInfo *) VBOXGLXENTRYTAG(glXGetVisualFromFBConfigSGIX)(Display *dpy, GLXFBConfig config) +{ + return glxim.GetVisualFromFBConfigSGIX(dpy, config); +} + +DECLEXPORT(GLXContext) VBOXGLXENTRYTAG(glXCreateContextWithConfigSGIX) +(Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct) +{ + return glxim.CreateContextWithConfigSGIX(dpy, config, render_type, share_list, direct); +} + +DECLEXPORT(GLXPixmap) VBOXGLXENTRYTAG(glXCreateGLXPixmapWithConfigSGIX)(Display *dpy, GLXFBConfig config, Pixmap pixmap) +{ + return glxim.CreateGLXPixmapWithConfigSGIX(dpy, config, pixmap); +} + +DECLEXPORT(int) VBOXGLXENTRYTAG(glXGetFBConfigAttribSGIX)(Display *dpy, GLXFBConfig config, int attribute, int *value) +{ + return glxim.GetFBConfigAttribSGIX(dpy, config, attribute, value); +} + + +/* + * GLX 1.3 functions + */ +DECLEXPORT(GLXFBConfig *) VBOXGLXENTRYTAG(glXChooseFBConfig)(Display *dpy, int screen, ATTRIB_TYPE *attrib_list, int *nelements) +{ + return glxim.ChooseFBConfig(dpy, screen, attrib_list, nelements); +} + +DECLEXPORT(GLXPbuffer) VBOXGLXENTRYTAG(glXCreatePbuffer)(Display *dpy, GLXFBConfig config, ATTRIB_TYPE *attrib_list) +{ + return glxim.CreatePbuffer(dpy, config, attrib_list); +} + +DECLEXPORT(GLXPixmap) VBOXGLXENTRYTAG(glXCreatePixmap)(Display *dpy, GLXFBConfig config, Pixmap pixmap, const ATTRIB_TYPE *attrib_list) +{ + return glxim.CreatePixmap(dpy, config, pixmap, attrib_list); +} + +DECLEXPORT(GLXWindow) VBOXGLXENTRYTAG(glXCreateWindow)(Display *dpy, GLXFBConfig config, Window win, ATTRIB_TYPE *attrib_list) +{ + return glxim.CreateWindow(dpy, config, win, attrib_list); +} + + +DECLEXPORT(GLXContext) VBOXGLXENTRYTAG(glXCreateNewContext) +(Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct) +{ + return glxim.CreateNewContext(dpy, config, render_type, share_list, direct); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXDestroyPbuffer)(Display *dpy, GLXPbuffer pbuf) +{ + return glxim.DestroyPbuffer(dpy, pbuf); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXDestroyPixmap)(Display *dpy, GLXPixmap pixmap) +{ + return glxim.DestroyPixmap(dpy, pixmap); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXDestroyWindow)(Display *dpy, GLXWindow win) +{ + return glxim.DestroyWindow(dpy, win); +} + +DECLEXPORT(GLXDrawable) VBOXGLXENTRYTAG(glXGetCurrentReadDrawable)(void) +{ + return glxim.GetCurrentReadDrawable(); +} + +DECLEXPORT(int) VBOXGLXENTRYTAG(glXGetFBConfigAttrib)(Display *dpy, GLXFBConfig config, int attribute, int *value) +{ + return glxim.GetFBConfigAttrib(dpy, config, attribute, value); +} + +DECLEXPORT(GLXFBConfig *) VBOXGLXENTRYTAG(glXGetFBConfigs)(Display *dpy, int screen, int *nelements) +{ + return glxim.GetFBConfigs(dpy, screen, nelements); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXGetSelectedEvent)(Display *dpy, GLXDrawable draw, unsigned long *event_mask) +{ + return glxim.GetSelectedEvent(dpy, draw, event_mask); +} + +DECLEXPORT(XVisualInfo *) VBOXGLXENTRYTAG(glXGetVisualFromFBConfig)(Display *dpy, GLXFBConfig config) +{ + return glxim.GetVisualFromFBConfig(dpy, config); +} + +DECLEXPORT(Bool) VBOXGLXENTRYTAG(glXMakeContextCurrent)(Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx) +{ + return glxim.MakeContextCurrent(display, draw, read, ctx); +} + +DECLEXPORT(int) VBOXGLXENTRYTAG(glXQueryContext)(Display *dpy, GLXContext ctx, int attribute, int *value) +{ + return glxim.QueryContext(dpy, ctx, attribute, value); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXQueryDrawable)(Display *dpy, GLXDrawable draw, int attribute, unsigned int *value) +{ + return glxim.QueryDrawable(dpy, draw, attribute, value); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXSelectEvent)(Display *dpy, GLXDrawable draw, unsigned long event_mask) +{ + return glxim.SelectEvent(dpy, draw, event_mask); +} + +/* +#ifdef CR_EXT_texture_from_pixmap +DECLEXPORT(void) VBOXGLXENTRYTAG(glXBindTexImageEXT)(Display *dpy, GLXDrawable draw, int buffer, const int *attrib_list) +{ + return glxim.BindTexImageEXT(dpy, draw, buffer, attrib_list); +} + +DECLEXPORT(void) VBOXGLXENTRYTAG(glXReleaseTexImageEXT)(Display *dpy, GLXDrawable draw, int buffer) +{ + return glxim.ReleaseTexImageEXT(dpy, draw, buffer); +} +#endif +*/ + +#endif /* GLX_EXTRAS */ + diff --git a/src/VBox/Additions/common/crOpenGL/glx_proto.h b/src/VBox/Additions/common/crOpenGL/glx_proto.h new file mode 100644 index 00000000..ee35dce1 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/glx_proto.h @@ -0,0 +1,149 @@ +/* $Id: glx_proto.h $ */ +/** @file + * VirtualBox guest OpenGL DRI GLX header C prototypes + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef GA_INCLUDED_SRC_common_crOpenGL_glx_proto_h +#define GA_INCLUDED_SRC_common_crOpenGL_glx_proto_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "chromium.h" +#include "stub.h" + +#if defined(VBOXOGL_FAKEDRI) || defined(VBOXOGL_DRI) +typedef const char * (*PGLXFUNC_GetDriverConfig)(const char *driverName); +typedef void (*PGLXFUNC_FreeMemoryMESA)(Display *dpy, int scrn, void *pointer); +typedef GLXContext (*PGLXFUNC_ImportContextEXT)(Display *dpy, GLXContextID contextID); +typedef GLXContextID (*PGLXFUNC_GetContextIDEXT)(const GLXContext ctx); +typedef Bool (*PGLXFUNC_MakeCurrentReadSGI)(Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx); +typedef const char * (*PGLXFUNC_GetScreenDriver)(Display *dpy, int scrNum); +typedef Display * (*PGLXFUNC_GetCurrentDisplayEXT)(void); +typedef void (*PGLXFUNC_FreeContextEXT)(Display *dpy, GLXContext ctx); + +/*Mesa internal*/ +typedef int (*PGLXFUNC_QueryContextInfoEXT)(Display *dpy, GLXContext ctx); +typedef void * (*PGLXFUNC_AllocateMemoryMESA)(Display *dpy, int scrn, + size_t size, float readFreq, + float writeFreq, float priority); +typedef GLuint (*PGLXFUNC_GetMemoryOffsetMESA)(Display *dpy, int scrn, const void *pointer ); +typedef GLXPixmap (*PGLXFUNC_CreateGLXPixmapMESA)(Display *dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap); +#endif + +/*Common glX functions*/ +typedef void (*PGLXFUNC_CopyContext)(Display *dpy, GLXContext src, GLXContext dst,unsigned long mask); +typedef void (*PGLXFUNC_UseXFont)(Font font, int first, int count, int listBase); +typedef CR_GLXFuncPtr (*PGLXFUNC_GetProcAddress)(const GLubyte *name); +typedef Bool (*PGLXFUNC_QueryExtension)(Display *dpy, int *errorBase, int *eventBase); +typedef Bool (*PGLXFUNC_IsDirect)(Display *dpy, GLXContext ctx); +typedef GLXPixmap (*PGLXFUNC_CreateGLXPixmap)(Display *dpy, XVisualInfo *vis, Pixmap pixmap); +typedef void (*PGLXFUNC_SwapBuffers)(Display *dpy, GLXDrawable drawable); +typedef GLXDrawable (*PGLXFUNC_GetCurrentDrawable)(void); +typedef void (*PGLXFUNC_WaitGL)(void); +typedef Display * (*PGLXFUNC_GetCurrentDisplay)(void); +typedef const char * (*PGLXFUNC_QueryServerString)(Display *dpy, int screen, int name); +typedef GLXContext (*PGLXFUNC_CreateContext)(Display *dpy, XVisualInfo *vis, GLXContext share, Bool direct); +typedef int (*PGLXFUNC_GetConfig)(Display *dpy, XVisualInfo *vis, int attrib, int *value); +typedef void (*PGLXFUNC_WaitX)(void); +typedef GLXContext (*PGLXFUNC_GetCurrentContext)(void); +typedef const char * (*PGLXFUNC_GetClientString)(Display *dpy, int name); +typedef Bool (*PGLXFUNC_MakeCurrent)(Display *dpy, GLXDrawable drawable, GLXContext ctx); +typedef void (*PGLXFUNC_DestroyContext)(Display *dpy, GLXContext ctx); +typedef CR_GLXFuncPtr (*PGLXFUNC_GetProcAddressARB)(const GLubyte *name); +typedef void (*PGLXFUNC_DestroyGLXPixmap)(Display *dpy, GLXPixmap pix); +typedef Bool (*PGLXFUNC_QueryVersion)(Display *dpy, int *major, int *minor); +typedef XVisualInfo * (*PGLXFUNC_ChooseVisual)(Display *dpy, int screen, int *attribList); +typedef const char * (*PGLXFUNC_QueryExtensionsString)(Display *dpy, int screen); + +/** + * Set this to 1 if you want to build stub functions for the + * GL_SGIX_pbuffer and GLX_SGIX_fbconfig extensions. + * This used to be disabled, due to "messy compilation issues", + * according to the earlier comment; but they're needed just + * to resolve symbols for OpenInventor applications, and I + * haven't found any reference to exactly what the "messy compilation + * issues" are, so I'm re-enabling the code by default. + */ +#define GLX_EXTRAS 1 + +#define GLX_SGIX_video_resize 1 + +/** + * Prototypes, in case they're not in glx.h or glxext.h + * Unfortunately, there's some inconsistency between the extension + * specs, and the SGI, NVIDIA, XFree86 and common glxext.h header + * files. + */ +#if defined(GLX_GLXEXT_VERSION) +/* match glxext.h, XFree86, Mesa */ +#define ATTRIB_TYPE const int +#else +#define ATTRIB_TYPE int +#endif + +#if GLX_EXTRAS +typedef GLXPbufferSGIX (*PGLXFUNC_CreateGLXPbufferSGIX) +(Display *dpy, GLXFBConfigSGIX config, unsigned int width, unsigned int height, int *attrib_list); + +typedef int (*PGLXFUNC_QueryGLXPbufferSGIX) +(Display *dpy, GLXPbuffer pbuf, int attribute, unsigned int *value); + +typedef GLXFBConfigSGIX * (*PGLXFUNC_ChooseFBConfigSGIX) +(Display *dpy, int screen, int *attrib_list, int *nelements); + +typedef void (*PGLXFUNC_DestroyGLXPbufferSGIX)(Display *dpy, GLXPbuffer pbuf); +typedef void (*PGLXFUNC_SelectEventSGIX)(Display *dpy, GLXDrawable drawable, unsigned long mask); +typedef void (*PGLXFUNC_GetSelectedEventSGIX)(Display *dpy, GLXDrawable drawable, unsigned long *mask); + +typedef GLXFBConfigSGIX (*PGLXFUNC_GetFBConfigFromVisualSGIX)(Display *dpy, XVisualInfo *vis); +typedef XVisualInfo * (*PGLXFUNC_GetVisualFromFBConfigSGIX)(Display *dpy, GLXFBConfig config); +typedef GLXContext (*PGLXFUNC_CreateContextWithConfigSGIX) +(Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); + +typedef GLXPixmap (*PGLXFUNC_CreateGLXPixmapWithConfigSGIX)(Display *dpy, GLXFBConfig config, Pixmap pixmap); +typedef int (*PGLXFUNC_GetFBConfigAttribSGIX)(Display *dpy, GLXFBConfig config, int attribute, int *value); + +/* + * GLX 1.3 functions + */ +typedef GLXFBConfig * (*PGLXFUNC_ChooseFBConfig)(Display *dpy, int screen, ATTRIB_TYPE *attrib_list, int *nelements); +typedef GLXPbuffer (*PGLXFUNC_CreatePbuffer)(Display *dpy, GLXFBConfig config, ATTRIB_TYPE *attrib_list); +typedef GLXPixmap (*PGLXFUNC_CreatePixmap)(Display *dpy, GLXFBConfig config, Pixmap pixmap, const ATTRIB_TYPE *attrib_list); +typedef GLXWindow (*PGLXFUNC_CreateWindow)(Display *dpy, GLXFBConfig config, Window win, ATTRIB_TYPE *attrib_list); +typedef GLXContext (*PGLXFUNC_CreateNewContext) +(Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); + +typedef void (*PGLXFUNC_DestroyPbuffer)(Display *dpy, GLXPbuffer pbuf); +typedef void (*PGLXFUNC_DestroyPixmap)(Display *dpy, GLXPixmap pixmap); +typedef void (*PGLXFUNC_DestroyWindow)(Display *dpy, GLXWindow win); +typedef GLXDrawable (*PGLXFUNC_GetCurrentReadDrawable)(void); +typedef int (*PGLXFUNC_GetFBConfigAttrib)(Display *dpy, GLXFBConfig config, int attribute, int *value); +typedef GLXFBConfig * (*PGLXFUNC_GetFBConfigs)(Display *dpy, int screen, int *nelements); +typedef void (*PGLXFUNC_GetSelectedEvent)(Display *dpy, GLXDrawable draw, unsigned long *event_mask); +typedef XVisualInfo * (*PGLXFUNC_GetVisualFromFBConfig)(Display *dpy, GLXFBConfig config); +typedef Bool (*PGLXFUNC_MakeContextCurrent)(Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx); +typedef int (*PGLXFUNC_QueryContext)(Display *dpy, GLXContext ctx, int attribute, int *value); +typedef void (*PGLXFUNC_QueryDrawable)(Display *dpy, GLXDrawable draw, int attribute, unsigned int *value); +typedef void (*PGLXFUNC_SelectEvent)(Display *dpy, GLXDrawable draw, unsigned long event_mask); + +#ifdef CR_EXT_texture_from_pixmap +typedef void (*PGLXFUNC_BindTexImageEXT)(Display *dpy, GLXDrawable draw, int buffer, const int *attrib_list); +typedef void (*PGLXFUNC_ReleaseTexImageEXT)(Display *dpy, GLXDrawable draw, int buffer); +#endif + +#endif /* GLX_EXTRAS */ + +#endif /* !GA_INCLUDED_SRC_common_crOpenGL_glx_proto_h */ diff --git a/src/VBox/Additions/common/crOpenGL/icd_drv.c b/src/VBox/Additions/common/crOpenGL/icd_drv.c new file mode 100644 index 00000000..3c54bb2f --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/icd_drv.c @@ -0,0 +1,398 @@ +/* $Id: icd_drv.c $ */ +/** @file + * VBox OpenGL windows ICD driver functions + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "cr_error.h" +#include "icd_drv.h" +#include "cr_gl.h" +#include "stub.h" +#include "cr_mem.h" + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) +# include <VBoxCrHgsmi.h> +# include <VBoxUhgsmi.h> +#endif + +#include <iprt/win/windows.h> + +/// @todo consider +/* We can modify chronium dispatch table functions order to match the one required by ICD, + * but it'd render us incompatible with other chromium SPUs and require more changes. + * In current state, we can use unmodified binary chromium SPUs. Question is do we need it? +*/ + +#define GL_FUNC(func) cr_gl##func + +static ICDTABLE icdTable = { 336, { +#define ICD_ENTRY(func) (PROC)GL_FUNC(func), +#include "VBoxICDList.h" +#undef ICD_ENTRY +} }; + +/* Currently host part will misbehave re-creating context with proper visual bits + * if contexts with alternative visual bits is requested. + * For now we just report a superset of all visual bits to avoid that. + * Better to it on the host side as well? + * We could also implement properly multiple pixel formats, + * which should be done by implementing offscreen rendering or multiple host contexts. + * */ +#define VBOX_CROGL_USE_VBITS_SUPERSET + +#ifdef VBOX_CROGL_USE_VBITS_SUPERSET +static GLuint desiredVisual = CR_RGB_BIT | CR_ALPHA_BIT | CR_DEPTH_BIT | CR_STENCIL_BIT | CR_ACCUM_BIT | CR_DOUBLE_BIT; +#else +static GLuint desiredVisual = CR_RGB_BIT; +#endif + +#ifndef VBOX_CROGL_USE_VBITS_SUPERSET +/** + * Compute a mask of CR_*_BIT flags which reflects the attributes of + * the pixel format of the given hdc. + */ +static GLuint ComputeVisBits( HDC hdc ) +{ + PIXELFORMATDESCRIPTOR pfd; + int iPixelFormat; + GLuint b = 0; + + iPixelFormat = GetPixelFormat( hdc ); + + DescribePixelFormat( hdc, iPixelFormat, sizeof(pfd), &pfd ); + + if (pfd.cDepthBits > 0) + b |= CR_DEPTH_BIT; + if (pfd.cAccumBits > 0) + b |= CR_ACCUM_BIT; + if (pfd.cColorBits > 8) + b |= CR_RGB_BIT; + if (pfd.cStencilBits > 0) + b |= CR_STENCIL_BIT; + if (pfd.cAlphaBits > 0) + b |= CR_ALPHA_BIT; + if (pfd.dwFlags & PFD_DOUBLEBUFFER) + b |= CR_DOUBLE_BIT; + if (pfd.dwFlags & PFD_STEREO) + b |= CR_STEREO_BIT; + + return b; +} +#endif + +void APIENTRY DrvReleaseContext(HGLRC hglrc) +{ + CR_DDI_PROLOGUE(); + /*crDebug( "DrvReleaseContext(0x%x) called", hglrc );*/ + stubMakeCurrent( NULL, NULL ); +} + +BOOL APIENTRY DrvValidateVersion(DWORD version) +{ + CR_DDI_PROLOGUE(); + if (stubInit()) { + crDebug("DrvValidateVersion %x -> TRUE\n", version); + return TRUE; + } + + crDebug("DrvValidateVersion %x -> FALSE, going to use system default opengl32.dll\n", version); + return FALSE; +} + +//we're not going to change icdTable at runtime, so callback is unused +PICDTABLE APIENTRY DrvSetContext(HDC hdc, HGLRC hglrc, void *callback) +{ + ContextInfo *pContext; + WindowInfo *pWindowInfo; + BOOL ret = false; + + CR_DDI_PROLOGUE(); + + (void) (callback); + + crHashtableLock(stub.windowTable); + crHashtableLock(stub.contextTable); + + pContext = (ContextInfo *) crHashtableSearch(stub.contextTable, (unsigned long) hglrc); + if (pContext) + { + pWindowInfo = stubGetWindowInfo(hdc); + if (pWindowInfo) + ret = stubMakeCurrent(pWindowInfo, pContext); + else + crError("no window info available."); + } + else + crError("No context found."); + + crHashtableUnlock(stub.contextTable); + crHashtableUnlock(stub.windowTable); + + return ret ? &icdTable : NULL; +} + +BOOL APIENTRY DrvSetPixelFormat(HDC hdc, int iPixelFormat) +{ + CR_DDI_PROLOGUE(); + crDebug( "DrvSetPixelFormat(0x%x, %i) called.", hdc, iPixelFormat ); + + if ( (iPixelFormat<1) || (iPixelFormat>2) ) { + crError( "wglSetPixelFormat: iPixelFormat=%d?", iPixelFormat ); + } + + return 1; +} + +HGLRC APIENTRY DrvCreateContext(HDC hdc) +{ + char dpyName[MAX_DPY_NAME]; + ContextInfo *context; +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + PVBOXUHGSMI pHgsmi = NULL; +#endif + + CR_DDI_PROLOGUE(); + + crDebug( "DrvCreateContext(0x%x) called.", hdc); + + stubInit(); + + CRASSERT(stub.contextTable); + + sprintf(dpyName, "%p", hdc); +#ifndef VBOX_CROGL_USE_VBITS_SUPERSET + if (stub.haveNativeOpenGL) + desiredVisual |= ComputeVisBits( hdc ); +#endif + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + pHgsmi = VBoxCrHgsmiCreate(); +#endif + + context = stubNewContext(dpyName, desiredVisual, UNDECIDED, 0 +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , pHgsmi +#endif + ); + if (!context) + return 0; + + return (HGLRC) context->id; +} + +HGLRC APIENTRY DrvCreateLayerContext(HDC hdc, int iLayerPlane) +{ + CR_DDI_PROLOGUE(); + crDebug( "DrvCreateLayerContext(0x%x, %i) called.", hdc, iLayerPlane); + //We don't support more than 1 layers. + if (iLayerPlane == 0) { + return DrvCreateContext(hdc); + } else { + crError( "DrvCreateLayerContext (%x,%x): unsupported", hdc, iLayerPlane); + return NULL; + } + +} + +BOOL APIENTRY DrvDescribeLayerPlane(HDC hdc,int iPixelFormat, + int iLayerPlane, UINT nBytes, + LPLAYERPLANEDESCRIPTOR plpd) +{ + CR_DDI_PROLOGUE(); + crWarning( "DrvDescribeLayerPlane: unimplemented" ); + CRASSERT(false); + return 0; +} + +int APIENTRY DrvGetLayerPaletteEntries(HDC hdc, int iLayerPlane, + int iStart, int cEntries, + COLORREF *pcr) +{ + CR_DDI_PROLOGUE(); + crWarning( "DrvGetLayerPaletteEntries: unsupported" ); + CRASSERT(false); + return 0; +} + +int APIENTRY DrvDescribePixelFormat(HDC hdc, int iPixelFormat, UINT nBytes, LPPIXELFORMATDESCRIPTOR pfd) +{ + CR_DDI_PROLOGUE(); + if ( !pfd ) { + return 2; + } + + if ( nBytes != sizeof(*pfd) ) { + crWarning( "DrvDescribePixelFormat: nBytes=%u?", nBytes ); + return 2; + } + + if (iPixelFormat==1) + { + crMemZero(pfd, sizeof(*pfd)); + + pfd->nSize = sizeof(*pfd); + pfd->nVersion = 1; + pfd->dwFlags = (PFD_DRAW_TO_WINDOW | + PFD_SUPPORT_OPENGL | + PFD_DOUBLEBUFFER); + + pfd->dwFlags |= 0x8000; /* <- Needed for VSG Open Inventor to be happy */ + + pfd->iPixelType = PFD_TYPE_RGBA; + pfd->cColorBits = 32; + pfd->cRedBits = 8; + pfd->cRedShift = 24; + pfd->cGreenBits = 8; + pfd->cGreenShift = 16; + pfd->cBlueBits = 8; + pfd->cBlueShift = 8; + pfd->cAlphaBits = 8; + pfd->cAlphaShift = 0; + pfd->cAccumBits = 0; + pfd->cAccumRedBits = 0; + pfd->cAccumGreenBits = 0; + pfd->cAccumBlueBits = 0; + pfd->cAccumAlphaBits = 0; + pfd->cDepthBits = 32; + pfd->cStencilBits = 8; + pfd->cAuxBuffers = 0; + pfd->iLayerType = PFD_MAIN_PLANE; + pfd->bReserved = 0; + pfd->dwLayerMask = 0; + pfd->dwVisibleMask = 0; + pfd->dwDamageMask = 0; + } + else + { + crMemZero(pfd, sizeof(*pfd)); + pfd->nVersion = 1; + pfd->dwFlags = (PFD_DRAW_TO_WINDOW| + PFD_SUPPORT_OPENGL); + + pfd->iPixelType = PFD_TYPE_RGBA; + pfd->cColorBits = 32; + pfd->cRedBits = 8; + pfd->cRedShift = 16; + pfd->cGreenBits = 8; + pfd->cGreenShift = 8; + pfd->cBlueBits = 8; + pfd->cBlueShift = 0; + pfd->cAlphaBits = 0; + pfd->cAlphaShift = 0; + pfd->cAccumBits = 64; + pfd->cAccumRedBits = 16; + pfd->cAccumGreenBits = 16; + pfd->cAccumBlueBits = 16; + pfd->cAccumAlphaBits = 0; + pfd->cDepthBits = 16; + pfd->cStencilBits = 8; + pfd->cAuxBuffers = 0; + pfd->iLayerType = PFD_MAIN_PLANE; + pfd->bReserved = 0; + pfd->dwLayerMask = 0; + pfd->dwVisibleMask = 0; + pfd->dwDamageMask = 0; + } + + /* the max PFD index */ + return 2; +} + +BOOL APIENTRY DrvDeleteContext(HGLRC hglrc) +{ +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + ContextInfo *pContext; + PVBOXUHGSMI pHgsmi = NULL; +#endif + + CR_DDI_PROLOGUE(); + crDebug( "DrvDeleteContext(0x%x) called", hglrc ); + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + crHashtableLock(stub.contextTable); + + pContext = (ContextInfo *) crHashtableSearch(stub.contextTable, (unsigned long) hglrc); + if (pContext) + pHgsmi = pContext->pHgsmi; + + crHashtableUnlock(stub.contextTable); +#endif + + stubDestroyContext( (unsigned long) hglrc ); + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + if (pHgsmi) + VBoxCrHgsmiDestroy(pHgsmi); +#endif + + return true; +} + +BOOL APIENTRY DrvCopyContext(HGLRC hglrcSrc, HGLRC hglrcDst, UINT mask) +{ + CR_DDI_PROLOGUE(); + crWarning( "DrvCopyContext: unsupported" ); + return 0; +} + +DECLEXPORT(BOOL) WINAPI wglShareLists_prox( HGLRC hglrc1, HGLRC hglrc2 ); + +BOOL APIENTRY DrvShareLists(HGLRC hglrc1, HGLRC hglrc2) +{ + return wglShareLists_prox(hglrc1, hglrc2); +} + +int APIENTRY DrvSetLayerPaletteEntries(HDC hdc, int iLayerPlane, + int iStart, int cEntries, + CONST COLORREF *pcr) +{ + CR_DDI_PROLOGUE(); + crWarning( "DrvSetLayerPaletteEntries: unsupported" ); + return 0; +} + + +BOOL APIENTRY DrvRealizeLayerPalette(HDC hdc, int iLayerPlane, BOOL bRealize) +{ + CR_DDI_PROLOGUE(); + crWarning( "DrvRealizeLayerPalette: unsupported" ); + return 0; +} + +BOOL APIENTRY DrvSwapLayerBuffers(HDC hdc, UINT fuPlanes) +{ + CR_DDI_PROLOGUE(); + if (fuPlanes == 1) + { + return DrvSwapBuffers(hdc); + } + else + { + crWarning( "DrvSwapLayerBuffers: unsupported" ); + CRASSERT(false); + return 0; + } +} + +BOOL APIENTRY DrvSwapBuffers(HDC hdc) +{ + WindowInfo *window; + + CR_DDI_PROLOGUE(); + /*crDebug( "DrvSwapBuffers(0x%x) called", hdc );*/ + window = stubGetWindowInfo(hdc); + stubSwapBuffers( window, 0 ); + return 1; +} + diff --git a/src/VBox/Additions/common/crOpenGL/icd_drv.h b/src/VBox/Additions/common/crOpenGL/icd_drv.h new file mode 100644 index 00000000..97004bdd --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/icd_drv.h @@ -0,0 +1,55 @@ +/* $Id: icd_drv.h $ */ +/** @file + * VirtualBox Windows NT/2000/XP guest OpenGL ICD header + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef GA_INCLUDED_SRC_common_crOpenGL_icd_drv_h +#define GA_INCLUDED_SRC_common_crOpenGL_icd_drv_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/win/windows.h> + +typedef struct ICDTABLE +{ + DWORD size; + PROC table[336]; +} ICDTABLE, *PICDTABLE; + +void APIENTRY DrvReleaseContext(HGLRC hglrc); +BOOL APIENTRY DrvValidateVersion(DWORD version); +PICDTABLE APIENTRY DrvSetContext(HDC hdc, HGLRC hglrc, void *callback); +BOOL APIENTRY DrvSetPixelFormat(HDC hdc, int iPixelFormat); +HGLRC APIENTRY DrvCreateContext(HDC hdc); +HGLRC APIENTRY DrvCreateLayerContext(HDC hdc, int iLayerPlane); +BOOL APIENTRY DrvDescribeLayerPlane(HDC hdc,int iPixelFormat, + int iLayerPlane, UINT nBytes, + LPLAYERPLANEDESCRIPTOR plpd); +int APIENTRY DrvGetLayerPaletteEntries(HDC hdc, int iLayerPlane, + int iStart, int cEntries, + COLORREF *pcr); +int APIENTRY DrvDescribePixelFormat(HDC hdc, int iPixelFormat, UINT nBytes, LPPIXELFORMATDESCRIPTOR pfd); +BOOL APIENTRY DrvDeleteContext(HGLRC hglrc); +BOOL APIENTRY DrvCopyContext(HGLRC hglrcSrc, HGLRC hglrcDst, UINT mask); +BOOL APIENTRY DrvShareLists(HGLRC hglrc1, HGLRC hglrc2); +int APIENTRY DrvSetLayerPaletteEntries(HDC hdc, int iLayerPlane, + int iStart, int cEntries, + CONST COLORREF *pcr); +BOOL APIENTRY DrvRealizeLayerPalette(HDC hdc, int iLayerPlane, BOOL bRealize); +BOOL APIENTRY DrvSwapLayerBuffers(HDC hdc, UINT fuPlanes); +BOOL APIENTRY DrvSwapBuffers(HDC hdc); + +#endif /* !GA_INCLUDED_SRC_common_crOpenGL_icd_drv_h */ diff --git a/src/VBox/Additions/common/crOpenGL/load.c b/src/VBox/Additions/common/crOpenGL/load.c new file mode 100644 index 00000000..a4b0cf8c --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/load.c @@ -0,0 +1,1526 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_spu.h" +#include "cr_net.h" +#include "cr_error.h" +#include "cr_mem.h" +#include "cr_string.h" +#include "cr_net.h" +#include "cr_environment.h" +#include "cr_process.h" +#include "cr_rand.h" +#include "cr_netserver.h" +#include "stub.h" +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <iprt/initterm.h> +#include <iprt/thread.h> +#include <iprt/errcore.h> +#include <iprt/asm.h> +#ifndef WINDOWS +# include <sys/types.h> +# include <unistd.h> +#endif + +#ifdef VBOX_WITH_WDDM +#include <d3d9types.h> +#include <D3dumddi.h> +#endif + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) +# include <VBoxCrHgsmi.h> +#endif + +/** + * If you change this, see the comments in tilesortspu_context.c + */ +#define MAGIC_CONTEXT_BASE 500 + +#define CONFIG_LOOKUP_FILE ".crconfigs" + +#ifdef WINDOWS +#define PYTHON_EXE "python.exe" +#else +#define PYTHON_EXE "python" +#endif + +static bool stub_initialized = 0; +#ifdef WINDOWS +static CRmutex stub_init_mutex; +#define STUB_INIT_LOCK() do { crLockMutex(&stub_init_mutex); } while (0) +#define STUB_INIT_UNLOCK() do { crUnlockMutex(&stub_init_mutex); } while (0) +#else +#define STUB_INIT_LOCK() do { } while (0) +#define STUB_INIT_UNLOCK() do { } while (0) +#endif + +/* NOTE: 'SPUDispatchTable glim' is declared in NULLfuncs.py now */ +/* NOTE: 'SPUDispatchTable stubThreadsafeDispatch' is declared in tsfuncs.c */ +Stub stub; +#ifdef CHROMIUM_THREADSAFE +static bool g_stubIsCurrentContextTSDInited; +CRtsd g_stubCurrentContextTSD; +#endif + + +#ifndef VBOX_NO_NATIVEGL +static void stubInitNativeDispatch( void ) +{ +# define MAX_FUNCS 1000 + SPUNamedFunctionTable gl_funcs[MAX_FUNCS]; + int numFuncs; + + numFuncs = crLoadOpenGL( &stub.wsInterface, gl_funcs ); + + stub.haveNativeOpenGL = (numFuncs > 0); + + /* XXX call this after context binding */ + numFuncs += crLoadOpenGLExtensions( &stub.wsInterface, gl_funcs + numFuncs ); + + CRASSERT(numFuncs < MAX_FUNCS); + + crSPUInitDispatchTable( &stub.nativeDispatch ); + crSPUInitDispatch( &stub.nativeDispatch, gl_funcs ); + crSPUInitDispatchNops( &stub.nativeDispatch ); +# undef MAX_FUNCS +} +#endif /* !VBOX_NO_NATIVEGL */ + + +/** Pointer to the SPU's real glClear and glViewport functions */ +static ClearFunc_t origClear; +static ViewportFunc_t origViewport; +static SwapBuffersFunc_t origSwapBuffers; +static DrawBufferFunc_t origDrawBuffer; +static ScissorFunc_t origScissor; + +static void stubCheckWindowState(WindowInfo *window, GLboolean bFlushOnChange) +{ + bool bForceUpdate = false; + bool bChanged = false; + +#ifdef WINDOWS + /** @todo install hook and track for WM_DISPLAYCHANGE */ + { + DEVMODE devMode; + + devMode.dmSize = sizeof(DEVMODE); + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode); + + if (devMode.dmPelsWidth!=window->dmPelsWidth || devMode.dmPelsHeight!=window->dmPelsHeight) + { + crDebug("Resolution changed(%d,%d), forcing window Pos/Size update", devMode.dmPelsWidth, devMode.dmPelsHeight); + window->dmPelsWidth = devMode.dmPelsWidth; + window->dmPelsHeight = devMode.dmPelsHeight; + bForceUpdate = true; + } + } +#endif + + bChanged = stubUpdateWindowGeometry(window, bForceUpdate) || bForceUpdate; + +#if defined(GLX) || defined (WINDOWS) + if (stub.trackWindowVisibleRgn) + { + bChanged = stubUpdateWindowVisibileRegions(window) || bChanged; + } +#endif + + if (stub.trackWindowVisibility && window->type == CHROMIUM && window->drawable) { + const int mapped = stubIsWindowVisible(window); + if (mapped != window->mapped) { + crDebug("Dispatched: WindowShow(%i, %i)", window->spuWindow, mapped); + stub.spu->dispatch_table.WindowShow(window->spuWindow, mapped); + window->mapped = mapped; + bChanged = true; + } + } + + if (bFlushOnChange && bChanged) + { + stub.spu->dispatch_table.Flush(); + } +} + +static bool stubSystemWindowExist(WindowInfo *pWindow) +{ +#ifdef WINDOWS + if (pWindow->hWnd!=WindowFromDC(pWindow->drawable)) + { + return false; + } +#else + Window root; + int x, y; + unsigned int border, depth, w, h; + Display *dpy; + + dpy = stubGetWindowDisplay(pWindow); + + XLOCK(dpy); + if (!XGetGeometry(dpy, pWindow->drawable, &root, &x, &y, &w, &h, &border, &depth)) + { + XUNLOCK(dpy); + return false; + } + XUNLOCK(dpy); +#endif + + return true; +} + +static void stubCheckWindowsCB(unsigned long key, void *data1, void *data2) +{ + WindowInfo *pWindow = (WindowInfo *) data1; + ContextInfo *pCtx = (ContextInfo *) data2; + (void)key; + + if (pWindow == pCtx->currentDrawable + || pWindow->type!=CHROMIUM + || pWindow->pOwner!=pCtx) + { + return; + } + + if (!stubSystemWindowExist(pWindow)) + { +#ifdef WINDOWS + stubDestroyWindow(CR_CTX_CON(pCtx), (GLint)pWindow->hWnd); +#else + stubDestroyWindow(CR_CTX_CON(pCtx), (GLint)pWindow->drawable); +#endif + return; + } + + stubCheckWindowState(pWindow, GL_FALSE); +} + +static void stubCheckWindowsState(void) +{ + ContextInfo *context = stubGetCurrentContext(); + + CRASSERT(stub.trackWindowSize || stub.trackWindowPos); + + if (!context) + return; + +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + if (stub.bRunningUnderWDDM) + return; +#endif + + /* Try to keep a consistent locking order. */ + crHashtableLock(stub.windowTable); +#if defined(CR_NEWWINTRACK) && !defined(WINDOWS) + crLockMutex(&stub.mutex); +#endif + + stubCheckWindowState(context->currentDrawable, GL_TRUE); + crHashtableWalkUnlocked(stub.windowTable, stubCheckWindowsCB, context); + +#if defined(CR_NEWWINTRACK) && !defined(WINDOWS) + crUnlockMutex(&stub.mutex); +#endif + crHashtableUnlock(stub.windowTable); +} + + +/** + * Override the head SPU's glClear function. + * We're basically trapping this function so that we can poll the + * application window size at a regular interval. + */ +static void SPU_APIENTRY trapClear(GLbitfield mask) +{ + stubCheckWindowsState(); + /* call the original SPU glClear function */ + origClear(mask); +} + +/** + * As above, but for glViewport. Most apps call glViewport before + * glClear when a window is resized. + */ +static void SPU_APIENTRY trapViewport(GLint x, GLint y, GLsizei w, GLsizei h) +{ + stubCheckWindowsState(); + /* call the original SPU glViewport function */ + origViewport(x, y, w, h); +} + +/*static void SPU_APIENTRY trapSwapBuffers(GLint window, GLint flags) +{ + stubCheckWindowsState(); + origSwapBuffers(window, flags); +} + +static void SPU_APIENTRY trapDrawBuffer(GLenum buf) +{ + stubCheckWindowsState(); + origDrawBuffer(buf); +}*/ + +#if 0 /* unused */ +static void SPU_APIENTRY trapScissor(GLint x, GLint y, GLsizei w, GLsizei h) +{ + int winX, winY; + unsigned int winW, winH; + WindowInfo *pWindow; + ContextInfo *context = stubGetCurrentContext(); + (void)x; (void)y; (void)w; (void)h; + + pWindow = context->currentDrawable; + stubGetWindowGeometry(pWindow, &winX, &winY, &winW, &winH); + origScissor(0, 0, winW, winH); +} +#endif /* unused */ + +/** + * Use the GL function pointers in \<spu\> to initialize the static glim + * dispatch table. + */ +static void stubInitSPUDispatch(SPU *spu) +{ + crSPUInitDispatchTable( &stub.spuDispatch ); + crSPUCopyDispatchTable( &stub.spuDispatch, &(spu->dispatch_table) ); + + if (stub.trackWindowSize || stub.trackWindowPos || stub.trackWindowVisibleRgn) { + /* patch-in special glClear/Viewport function to track window sizing */ + origClear = stub.spuDispatch.Clear; + origViewport = stub.spuDispatch.Viewport; + origSwapBuffers = stub.spuDispatch.SwapBuffers; + origDrawBuffer = stub.spuDispatch.DrawBuffer; + origScissor = stub.spuDispatch.Scissor; + stub.spuDispatch.Clear = trapClear; + stub.spuDispatch.Viewport = trapViewport; + + /*stub.spuDispatch.SwapBuffers = trapSwapBuffers; + stub.spuDispatch.DrawBuffer = trapDrawBuffer;*/ + } + + crSPUCopyDispatchTable( &glim, &stub.spuDispatch ); +} + +#if 0 /** @todo stubSPUTearDown & stubSPUTearDownLocked are not referenced */ + +// Callback function, used to destroy all created contexts +static void hsWalkStubDestroyContexts(unsigned long key, void *data1, void *data2) +{ + (void)data1; (void)data2; + stubDestroyContext(key); +} + +/** + * This is called when we exit. + * We call all the SPU's cleanup functions. + */ +static void stubSPUTearDownLocked(void) +{ + crDebug("stubSPUTearDownLocked"); + +#ifdef WINDOWS +# ifndef CR_NEWWINTRACK + stubUninstallWindowMessageHook(); +# endif +#endif + +#ifdef CR_NEWWINTRACK + ASMAtomicWriteBool(&stub.bShutdownSyncThread, true); +#endif + + //delete all created contexts + stubMakeCurrent( NULL, NULL); + + /* the lock order is windowTable->contextTable (see wglMakeCurrent_prox, glXMakeCurrent) + * this is why we need to take a windowTable lock since we will later do stub.windowTable access & locking */ + crHashtableLock(stub.windowTable); + crHashtableWalk(stub.contextTable, hsWalkStubDestroyContexts, NULL); + crHashtableUnlock(stub.windowTable); + + /* shutdown, now trap any calls to a NULL dispatcher */ + crSPUCopyDispatchTable(&glim, &stubNULLDispatch); + + crSPUUnloadChain(stub.spu); + stub.spu = NULL; + +#ifndef Linux + crUnloadOpenGL(); +#endif + +#ifndef WINDOWS + crNetTearDown(); +#endif + +#ifdef GLX + if (stub.xshmSI.shmid>=0) + { + shmctl(stub.xshmSI.shmid, IPC_RMID, 0); + shmdt(stub.xshmSI.shmaddr); + } + crFreeHashtable(stub.pGLXPixmapsHash, crFree); +#endif + + crFreeHashtable(stub.windowTable, crFree); + crFreeHashtable(stub.contextTable, NULL); + + crMemset(&stub, 0, sizeof(stub)); + +} + +/** + * This is called when we exit. + * We call all the SPU's cleanup functions. + */ +static void stubSPUTearDown(void) +{ + STUB_INIT_LOCK(); + if (stub_initialized) + { + stubSPUTearDownLocked(); + stub_initialized = 0; + } + STUB_INIT_UNLOCK(); +} + +#endif /** @todo stubSPUTearDown & stubSPUTearDownLocked are not referenced */ + +static void stubSPUSafeTearDown(void) +{ +#ifdef CHROMIUM_THREADSAFE + CRmutex *mutex; +#endif + + if (!stub_initialized) return; + stub_initialized = 0; + +#ifdef CHROMIUM_THREADSAFE + mutex = &stub.mutex; + crLockMutex(mutex); +#endif + crDebug("stubSPUSafeTearDown"); + +#ifdef WINDOWS +# ifndef CR_NEWWINTRACK + stubUninstallWindowMessageHook(); +# endif +#endif + +#if defined(CR_NEWWINTRACK) + crUnlockMutex(mutex); +# if defined(WINDOWS) + if (stub.hSyncThread && RTThreadGetState(stub.hSyncThread)!=RTTHREADSTATE_TERMINATED) + { + HANDLE hNative; + DWORD ec=0; + + hNative = OpenThread(SYNCHRONIZE|THREAD_QUERY_INFORMATION|THREAD_TERMINATE, + false, RTThreadGetNative(stub.hSyncThread)); + if (!hNative) + { + crWarning("Failed to get handle for sync thread(%#x)", GetLastError()); + } + else + { + crDebug("Got handle %p for thread %#x", hNative, RTThreadGetNative(stub.hSyncThread)); + } + + ASMAtomicWriteBool(&stub.bShutdownSyncThread, true); + + if (PostThreadMessage(RTThreadGetNative(stub.hSyncThread), WM_QUIT, 0, 0)) + { + RTThreadWait(stub.hSyncThread, 1000, NULL); + + /*Same issue as on linux, RTThreadWait exits before system thread is terminated, which leads + * to issues as our dll goes to be unloaded. + *@todo + *We usually call this function from DllMain which seems to be holding some lock and thus we have to + * kill thread via TerminateThread. + */ + if (WaitForSingleObject(hNative, 100)==WAIT_TIMEOUT) + { + crDebug("Wait failed, terminating"); + if (!TerminateThread(hNative, 1)) + { + crDebug("TerminateThread failed"); + } + } + if (GetExitCodeThread(hNative, &ec)) + { + crDebug("Thread %p exited with ec=%i", hNative, ec); + } + else + { + crDebug("GetExitCodeThread failed(%#x)", GetLastError()); + } + } + else + { + crDebug("Sync thread killed before DLL_PROCESS_DETACH"); + } + + if (hNative) + { + CloseHandle(hNative); + } + } +#else + if (stub.hSyncThread!=NIL_RTTHREAD) + { + ASMAtomicWriteBool(&stub.bShutdownSyncThread, true); + { + int rc = RTThreadWait(stub.hSyncThread, RT_INDEFINITE_WAIT, NULL); + if (RT_FAILURE(rc)) + { + WARN(("RTThreadWait_join failed %i", rc)); + } + } + } +#endif + crLockMutex(mutex); +#endif + +#ifndef WINDOWS + crNetTearDown(); +#endif + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(mutex); + crFreeMutex(mutex); +#endif + crMemset(&stub, 0, sizeof(stub)); +} + + +static void stubExitHandler(void) +{ + stubSPUSafeTearDown(); + signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); +} + +/** + * Called when we receive a SIGTERM signal. + */ +static void stubSignalHandler(int signo) +{ + (void)signo; + stubSPUSafeTearDown(); + exit(0); /* this causes stubExitHandler() to be called */ +} + +#ifndef RT_OS_WINDOWS +# ifdef CHROMIUM_THREADSAFE +static void stubThreadTlsDtor(void *pvValue) +{ + ContextInfo *pCtx = (ContextInfo*)pvValue; + VBoxTlsRefRelease(pCtx); +} +# endif +#endif + + +/** + * Init variables in the stub structure, install signal handler. + */ +static void stubInitVars(void) +{ + WindowInfo *defaultWin; + +#ifdef CHROMIUM_THREADSAFE + crInitMutex(&stub.mutex); +#endif + + /* At the very least we want CR_RGB_BIT. */ + stub.haveNativeOpenGL = GL_FALSE; + stub.spu = NULL; + stub.appDrawCursor = 0; + stub.minChromiumWindowWidth = 0; + stub.minChromiumWindowHeight = 0; + stub.maxChromiumWindowWidth = 0; + stub.maxChromiumWindowHeight = 0; + stub.matchChromiumWindowCount = 0; + stub.matchChromiumWindowID = NULL; + stub.matchWindowTitle = NULL; + stub.ignoreFreeglutMenus = 0; + stub.threadSafe = GL_FALSE; + stub.trackWindowSize = 0; + stub.trackWindowPos = 0; + stub.trackWindowVisibility = 0; + stub.trackWindowVisibleRgn = 0; + stub.mothershipPID = 0; + stub.spu_dir = NULL; + + stub.freeContextNumber = MAGIC_CONTEXT_BASE; + stub.contextTable = crAllocHashtable(); +#ifndef RT_OS_WINDOWS +# ifdef CHROMIUM_THREADSAFE + if (!g_stubIsCurrentContextTSDInited) + { + crInitTSDF(&g_stubCurrentContextTSD, stubThreadTlsDtor); + g_stubIsCurrentContextTSDInited = true; + } +# endif +#endif + stubSetCurrentContext(NULL); + + stub.windowTable = crAllocHashtable(); + +#ifdef CR_NEWWINTRACK + stub.bShutdownSyncThread = false; + stub.hSyncThread = NIL_RTTHREAD; +#endif + + defaultWin = (WindowInfo *) crCalloc(sizeof(WindowInfo)); + defaultWin->type = CHROMIUM; + defaultWin->spuWindow = 0; /* window 0 always exists */ +#ifdef WINDOWS + defaultWin->hVisibleRegion = INVALID_HANDLE_VALUE; +#elif defined(GLX) + defaultWin->pVisibleRegions = NULL; + defaultWin->cVisibleRegions = 0; +#endif + crHashtableAdd(stub.windowTable, 0, defaultWin); + +#if 1 + atexit(stubExitHandler); + signal(SIGTERM, stubSignalHandler); + signal(SIGINT, stubSignalHandler); +#ifndef WINDOWS + signal(SIGPIPE, SIG_IGN); /* the networking code should catch this */ +#endif +#else + (void) stubExitHandler; + (void) stubSignalHandler; +#endif +} + + +#if 0 /* unused */ + +/** + * Return a free port number for the mothership to use, or -1 if we + * can't find one. + */ +static int +GenerateMothershipPort(void) +{ + const int MAX_PORT = 10100; + unsigned short port; + + /* generate initial port number randomly */ + crRandAutoSeed(); + port = (unsigned short) crRandInt(10001, MAX_PORT); + +#ifdef WINDOWS + /* XXX should implement a free port check here */ + return port; +#else + /* + * See if this port number really is free, try another if needed. + */ + { + struct sockaddr_in servaddr; + int so_reuseaddr = 1; + int sock, k; + + /* create socket */ + sock = socket(AF_INET, SOCK_STREAM, 0); + CRASSERT(sock > 2); + + /* deallocate socket/port when we exit */ + k = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char *) &so_reuseaddr, sizeof(so_reuseaddr)); + CRASSERT(k == 0); + + /* initialize the servaddr struct */ + crMemset(&servaddr, 0, sizeof(servaddr) ); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_ANY); + + while (port < MAX_PORT) { + /* Bind to the given port number, return -1 if we fail */ + servaddr.sin_port = htons((unsigned short) port); + k = bind(sock, (struct sockaddr *) &servaddr, sizeof(servaddr)); + if (k) { + /* failed to create port. try next one. */ + port++; + } + else { + /* free the socket/port now so mothership can make it */ + close(sock); + return port; + } + } + } +#endif /* WINDOWS */ + return -1; +} + + +/** + * Try to determine which mothership configuration to use for this program. + */ +static char ** +LookupMothershipConfig(const char *procName) +{ + const int procNameLen = crStrlen(procName); + FILE *f; + const char *home; + char configPath[1000]; + + /* first, check if the CR_CONFIG env var is set */ + { + const char *conf = crGetenv("CR_CONFIG"); + if (conf && crStrlen(conf) > 0) + return crStrSplit(conf, " "); + } + + /* second, look up config name from config file */ + home = crGetenv("HOME"); + if (home) + sprintf(configPath, "%s/%s", home, CONFIG_LOOKUP_FILE); + else + crStrcpy(configPath, CONFIG_LOOKUP_FILE); /* from current dir */ + /* Check if the CR_CONFIG_PATH env var is set. */ + { + const char *conf = crGetenv("CR_CONFIG_PATH"); + if (conf) + crStrcpy(configPath, conf); /* from env var */ + } + + f = fopen(configPath, "r"); + if (!f) { + return NULL; + } + + while (!feof(f)) { + char line[1000]; + char **args; + fgets(line, 999, f); + line[crStrlen(line) - 1] = 0; /* remove trailing newline */ + if (crStrncmp(line, procName, procNameLen) == 0 && + (line[procNameLen] == ' ' || line[procNameLen] == '\t')) + { + crWarning("Using Chromium configuration for %s from %s", + procName, configPath); + args = crStrSplit(line + procNameLen + 1, " "); + return args; + } + } + fclose(f); + return NULL; +} + + +static int Mothership_Awake = 0; + + +/** + * Signal handler to determine when mothership is ready. + */ +static void +MothershipPhoneHome(int signo) +{ + crDebug("Got signal %d: mothership is awake!", signo); + Mothership_Awake = 1; +} + +#endif /* 0 */ + +static void stubSetDefaultConfigurationOptions(void) +{ + unsigned char key[16]= {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + + stub.appDrawCursor = 0; + stub.minChromiumWindowWidth = 0; + stub.minChromiumWindowHeight = 0; + stub.maxChromiumWindowWidth = 0; + stub.maxChromiumWindowHeight = 0; + stub.matchChromiumWindowID = NULL; + stub.numIgnoreWindowID = 0; + stub.matchWindowTitle = NULL; + stub.ignoreFreeglutMenus = 0; + stub.trackWindowSize = 1; + stub.trackWindowPos = 1; + stub.trackWindowVisibility = 1; + stub.trackWindowVisibleRgn = 1; + stub.matchChromiumWindowCount = 0; + stub.spu_dir = NULL; + crNetSetRank(0); + crNetSetContextRange(32, 35); + crNetSetNodeRange("iam0", "iamvis20"); + crNetSetKey(key,sizeof(key)); + stub.force_pbuffers = 0; + +#ifdef WINDOWS +# ifdef VBOX_WITH_WDDM + stub.bRunningUnderWDDM = false; +# endif +#endif +} + +#ifdef CR_NEWWINTRACK +# ifdef VBOX_WITH_WDDM +static void stubDispatchVisibleRegions(WindowInfo *pWindow) +{ + DWORD dwCount; + LPRGNDATA lpRgnData; + + dwCount = GetRegionData(pWindow->hVisibleRegion, 0, NULL); + lpRgnData = crAlloc(dwCount); + + if (lpRgnData) + { + GetRegionData(pWindow->hVisibleRegion, dwCount, lpRgnData); + crDebug("Dispatched WindowVisibleRegion (%i, cRects=%i)", pWindow->spuWindow, lpRgnData->rdh.nCount); + stub.spuDispatch.WindowVisibleRegion(pWindow->spuWindow, lpRgnData->rdh.nCount, (GLint*) lpRgnData->Buffer); + crFree(lpRgnData); + } + else crWarning("GetRegionData failed, VisibleRegions update failed"); +} + +# endif /* VBOX_WITH_WDDM */ + +static void stubSyncTrCheckWindowsCB(unsigned long key, void *data1, void *data2) +{ + WindowInfo *pWindow = (WindowInfo *) data1; + (void)key; (void) data2; + + if (pWindow->type!=CHROMIUM || pWindow->spuWindow==0) + { + return; + } + + stub.spu->dispatch_table.VBoxPackSetInjectID(pWindow->u32ClientID); + + if (!stubSystemWindowExist(pWindow)) + { +#ifdef WINDOWS + stubDestroyWindow(0, (GLint)pWindow->hWnd); +#else + stubDestroyWindow(0, (GLint)pWindow->drawable); +#endif + /*No need to flush here as crWindowDestroy does it*/ + return; + } + +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + if (stub.bRunningUnderWDDM) + return; +#endif + stubCheckWindowState(pWindow, GL_TRUE); +} + +static DECLCALLBACK(int) stubSyncThreadProc(RTTHREAD ThreadSelf, void *pvUser) +{ +#ifdef WINDOWS + MSG msg; +# ifdef VBOX_WITH_WDDM + HMODULE hVBoxD3D = NULL; + GLint spuConnection = 0; +# endif +#endif + + (void) pvUser; + + crDebug("Sync thread started"); +#ifdef WINDOWS + PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); +# ifdef VBOX_WITH_WDDM + hVBoxD3D = NULL; + if (!GetModuleHandleEx(0, VBOX_MODNAME_DISPD3D, &hVBoxD3D)) + { + crDebug("GetModuleHandleEx failed err %d", GetLastError()); + hVBoxD3D = NULL; + } + + if (hVBoxD3D) + { + crDebug("running with " VBOX_MODNAME_DISPD3D); + stub.trackWindowVisibleRgn = 0; + stub.bRunningUnderWDDM = true; + } +# endif /* VBOX_WITH_WDDM */ +#endif /* WINDOWS */ + + crLockMutex(&stub.mutex); +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + spuConnection = +#endif + stub.spu->dispatch_table.VBoxPackSetInjectThread(NULL); +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + if (stub.bRunningUnderWDDM && !spuConnection) + { + crError("VBoxPackSetInjectThread failed!"); + } +#endif + crUnlockMutex(&stub.mutex); + + RTThreadUserSignal(ThreadSelf); + + while(!stub.bShutdownSyncThread) + { +#ifdef WINDOWS + if (!PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) + { +# ifdef VBOX_WITH_WDDM + if (stub.bRunningUnderWDDM) + { + + } + else +# endif + { + crHashtableWalk(stub.windowTable, stubSyncTrCheckWindowsCB, NULL); + RTThreadSleep(50); + } + } + else + { + if (WM_QUIT==msg.message) + { + crDebug("Sync thread got WM_QUIT"); + break; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +#else + /* Try to keep a consistent locking order. */ + crHashtableLock(stub.windowTable); + crLockMutex(&stub.mutex); + crHashtableWalkUnlocked(stub.windowTable, stubSyncTrCheckWindowsCB, NULL); + crUnlockMutex(&stub.mutex); + crHashtableUnlock(stub.windowTable); + RTThreadSleep(50); +#endif + } + +#ifdef VBOX_WITH_WDDM + if (spuConnection) + { + stub.spu->dispatch_table.VBoxConDestroy(spuConnection); + } + if (hVBoxD3D) + { + FreeLibrary(hVBoxD3D); + } +#endif + crDebug("Sync thread stopped"); + return 0; +} +#endif /* CR_NEWWINTRACK */ + +/** + * Do one-time initializations for the faker. + * Returns TRUE on success, FALSE otherwise. + */ +static bool +stubInitLocked(void) +{ + /* Here is where we contact the mothership to find out what we're supposed + * to be doing. Networking code in a DLL initializer. I sure hope this + * works :) + * + * HOW can I pass the mothership address to this if I already know it? + */ + + char response[1024]; + char **spuchain; + int num_spus; + int *spu_ids; + char **spu_names; + const char *app_id; + int i; + int disable_sync = 0; +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + HMODULE hVBoxD3D = NULL; +#endif + + stubInitVars(); + + crGetProcName(response, 1024); + crDebug("Stub launched for %s", response); + +#if defined(CR_NEWWINTRACK) && !defined(WINDOWS) + /** @todo when vm boots with compiz turned on, new code causes hang in xcb_wait_for_reply in the sync thread + * as at the start compiz runs our code under XGrabServer. + */ + if (!crStrcmp(response, "compiz") || !crStrcmp(response, "compiz_real") || !crStrcmp(response, "compiz.real") + || !crStrcmp(response, "compiz-bin")) + { + disable_sync = 1; + } +#endif + + /** @todo check if it'd be of any use on other than guests, no use for windows */ + app_id = crGetenv( "CR_APPLICATION_ID_NUMBER" ); + + crNetInit( NULL, NULL ); + +#ifndef WINDOWS + { + CRNetServer ns; + + ns.name = "vboxhgcm://host:0"; + ns.buffer_size = 1024; + crNetServerConnect(&ns +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , NULL +#endif + ); + if (!ns.conn) + { + crWarning("Failed to connect to host. Make sure 3D acceleration is enabled for this VM."); +# ifdef VBOXOGL_FAKEDRI + return false; +# else + exit(1); +# endif + } + else + { + crNetFreeConnection(ns.conn); + } + } +#endif + + strcpy(response, "2 0 feedback 1 pack"); + spuchain = crStrSplit( response, " " ); + num_spus = crStrToInt( spuchain[0] ); + spu_ids = (int *) crAlloc( num_spus * sizeof( *spu_ids ) ); + spu_names = (char **) crAlloc( num_spus * sizeof( *spu_names ) ); + for (i = 0 ; i < num_spus ; i++) + { + spu_ids[i] = crStrToInt( spuchain[2*i+1] ); + spu_names[i] = crStrdup( spuchain[2*i+2] ); + crDebug( "SPU %d/%d: (%d) \"%s\"", i+1, num_spus, spu_ids[i], spu_names[i] ); + } + + stubSetDefaultConfigurationOptions(); + +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + hVBoxD3D = NULL; + if (!GetModuleHandleEx(0, VBOX_MODNAME_DISPD3D, &hVBoxD3D)) + { + crDebug("GetModuleHandleEx failed err %d", GetLastError()); + hVBoxD3D = NULL; + } + + if (hVBoxD3D) + { + disable_sync = 1; + crDebug("running with %s", VBOX_MODNAME_DISPD3D); + stub.trackWindowVisibleRgn = 0; + /** @todo should we enable that? */ + stub.trackWindowSize = 0; + stub.trackWindowPos = 0; + stub.trackWindowVisibility = 0; + stub.bRunningUnderWDDM = true; + } +#endif + + stub.spu = crSPULoadChain( num_spus, spu_ids, spu_names, stub.spu_dir, NULL ); + + crFree( spuchain ); + crFree( spu_ids ); + for (i = 0; i < num_spus; ++i) + crFree(spu_names[i]); + crFree( spu_names ); + + // spu chain load failed somewhere + if (!stub.spu) { + return false; + } + + crSPUInitDispatchTable( &glim ); + + /* This is unlikely to change -- We still want to initialize our dispatch + * table with the functions of the first SPU in the chain. */ + stubInitSPUDispatch( stub.spu ); + + /* we need to plug one special stub function into the dispatch table */ + glim.GetChromiumParametervCR = stub_GetChromiumParametervCR; + +#if !defined(VBOX_NO_NATIVEGL) + /* Load pointers to native OpenGL functions into stub.nativeDispatch */ + stubInitNativeDispatch(); +#endif + +/*crDebug("stub init"); +raise(SIGINT);*/ + +#ifdef WINDOWS +# ifndef CR_NEWWINTRACK + stubInstallWindowMessageHook(); +# endif +#endif + +#ifdef CR_NEWWINTRACK + { + int rc; + + RTR3InitDll(RTR3INIT_FLAGS_UNOBTRUSIVE); + + if (!disable_sync) + { + crDebug("Starting sync thread"); + + rc = RTThreadCreate(&stub.hSyncThread, stubSyncThreadProc, NULL, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "Sync"); + if (RT_FAILURE(rc)) + { + crError("Failed to start sync thread! (%x)", rc); + } + RTThreadUserWait(stub.hSyncThread, 60 * 1000); + RTThreadUserReset(stub.hSyncThread); + + crDebug("Going on"); + } + } +#endif + +#ifdef GLX + stub.xshmSI.shmid = -1; + stub.bShmInitFailed = GL_FALSE; + stub.pGLXPixmapsHash = crAllocHashtable(); + + stub.bXExtensionsChecked = GL_FALSE; + stub.bHaveXComposite = GL_FALSE; + stub.bHaveXFixes = GL_FALSE; +#endif + + return true; +} + +/** + * Do one-time initializations for the faker. + * Returns TRUE on success, FALSE otherwise. + */ +bool +stubInit(void) +{ + bool bRc = true; + /* we need to serialize the initialization, otherwise racing is possible + * for XPDM-based d3d when a d3d switcher is testing the gl lib in two or more threads + * NOTE: the STUB_INIT_LOCK/UNLOCK is a NOP for non-win currently */ + STUB_INIT_LOCK(); + if (!stub_initialized) + bRc = stub_initialized = stubInitLocked(); + STUB_INIT_UNLOCK(); + return bRc; +} + +/* Sigh -- we can't do initialization at load time, since Windows forbids + * the loading of other libraries from DLLMain. */ + +#ifdef WINDOWS +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#if 1//def DEBUG_misha + /* debugging: this is to be able to catch first-chance notifications + * for exceptions other than EXCEPTION_BREAKPOINT in kernel debugger */ +# define VDBG_VEHANDLER +#endif + +#ifdef VDBG_VEHANDLER +# include <dbghelp.h> +# include <cr_string.h> +static PVOID g_VBoxVehHandler = NULL; +static DWORD g_VBoxVehEnable = 0; + +/* generate a crash dump on exception */ +#define VBOXVEH_F_DUMP 0x00000001 +/* generate a debugger breakpoint exception */ +#define VBOXVEH_F_BREAK 0x00000002 +/* exit on exception */ +#define VBOXVEH_F_EXIT 0x00000004 + +static DWORD g_VBoxVehFlags = 0; + +typedef BOOL WINAPI FNVBOXDBG_MINIDUMPWRITEDUMP(HANDLE hProcess, + DWORD ProcessId, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + PMINIDUMP_CALLBACK_INFORMATION CallbackParam); +typedef FNVBOXDBG_MINIDUMPWRITEDUMP *PFNVBOXDBG_MINIDUMPWRITEDUMP; + +static HMODULE g_hVBoxMdDbgHelp = NULL; +static PFNVBOXDBG_MINIDUMPWRITEDUMP g_pfnVBoxMdMiniDumpWriteDump = NULL; +static size_t g_cVBoxMdFilePrefixLen = 0; +static WCHAR g_aszwVBoxMdFilePrefix[MAX_PATH]; +static WCHAR g_aszwVBoxMdDumpCount = 0; +static MINIDUMP_TYPE g_enmVBoxMdDumpType = MiniDumpNormal + | MiniDumpWithDataSegs + | MiniDumpWithFullMemory + | MiniDumpWithHandleData +//// | MiniDumpFilterMemory +//// | MiniDumpScanMemory +// | MiniDumpWithUnloadedModules +//// | MiniDumpWithIndirectlyReferencedMemory +//// | MiniDumpFilterModulePaths +// | MiniDumpWithProcessThreadData +// | MiniDumpWithPrivateReadWriteMemory +//// | MiniDumpWithoutOptionalData +// | MiniDumpWithFullMemoryInfo +// | MiniDumpWithThreadInfo +// | MiniDumpWithCodeSegs +// | MiniDumpWithFullAuxiliaryState +// | MiniDumpWithPrivateWriteCopyMemory +// | MiniDumpIgnoreInaccessibleMemory +// | MiniDumpWithTokenInformation +//// | MiniDumpWithModuleHeaders +//// | MiniDumpFilterTriage + ; + + + +#define VBOXMD_DUMP_DIR_DEFAULT "C:\\dumps" +#define VBOXMD_DUMP_NAME_PREFIX_W L"VBoxDmp_" + +static HMODULE loadSystemDll(const char *pszName) +{ +#ifndef DEBUG + char szPath[MAX_PATH]; + UINT cchPath = GetSystemDirectoryA(szPath, sizeof(szPath)); + size_t cbName = strlen(pszName) + 1; + if (cchPath + 1 + cbName > sizeof(szPath)) + { + SetLastError(ERROR_FILENAME_EXCED_RANGE); + return NULL; + } + szPath[cchPath] = '\\'; + memcpy(&szPath[cchPath + 1], pszName, cbName); + return LoadLibraryA(szPath); +#else + return LoadLibraryA(pszName); +#endif +} + +static DWORD vboxMdMinidumpCreate(struct _EXCEPTION_POINTERS *pExceptionInfo) +{ + WCHAR aszwMdFileName[MAX_PATH]; + HANDLE hProcess = GetCurrentProcess(); + DWORD ProcessId = GetCurrentProcessId(); + MINIDUMP_EXCEPTION_INFORMATION ExceptionInfo; + HANDLE hFile; + DWORD winErr = ERROR_SUCCESS; + + if (!g_pfnVBoxMdMiniDumpWriteDump) + { + if (!g_hVBoxMdDbgHelp) + { + g_hVBoxMdDbgHelp = loadSystemDll("DbgHelp.dll"); + if (!g_hVBoxMdDbgHelp) + return GetLastError(); + } + + g_pfnVBoxMdMiniDumpWriteDump = (PFNVBOXDBG_MINIDUMPWRITEDUMP)GetProcAddress(g_hVBoxMdDbgHelp, "MiniDumpWriteDump"); + if (!g_pfnVBoxMdMiniDumpWriteDump) + return GetLastError(); + } + + ++g_aszwVBoxMdDumpCount; + + memcpy(aszwMdFileName, g_aszwVBoxMdFilePrefix, g_cVBoxMdFilePrefixLen * sizeof (g_aszwVBoxMdFilePrefix[0])); + swprintf(aszwMdFileName + g_cVBoxMdFilePrefixLen, RT_ELEMENTS(aszwMdFileName) - g_cVBoxMdFilePrefixLen, L"%d_%d.dmp", ProcessId, g_aszwVBoxMdDumpCount); + + hFile = CreateFileW(aszwMdFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + return GetLastError(); + + ExceptionInfo.ThreadId = GetCurrentThreadId(); + ExceptionInfo.ExceptionPointers = pExceptionInfo; + ExceptionInfo.ClientPointers = FALSE; + + if (!g_pfnVBoxMdMiniDumpWriteDump(hProcess, ProcessId, hFile, g_enmVBoxMdDumpType, &ExceptionInfo, NULL, NULL)) + winErr = GetLastError(); + + CloseHandle(hFile); + return winErr; +} + +LONG WINAPI vboxVDbgVectoredHandler(struct _EXCEPTION_POINTERS *pExceptionInfo) +{ + PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; + PCONTEXT pContextRecord = pExceptionInfo->ContextRecord; + switch (pExceptionRecord->ExceptionCode) + { + case EXCEPTION_BREAKPOINT: + case EXCEPTION_ACCESS_VIOLATION: + case EXCEPTION_STACK_OVERFLOW: + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_FLT_INVALID_OPERATION: + case EXCEPTION_INT_DIVIDE_BY_ZERO: + case EXCEPTION_ILLEGAL_INSTRUCTION: + if (g_VBoxVehFlags & VBOXVEH_F_BREAK) + { + BOOL fBreak = TRUE; +#ifndef DEBUG_misha + if (pExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) + { + HANDLE hProcess = GetCurrentProcess(); + BOOL fDebuggerPresent = FALSE; + /* we do not want to generate breakpoint exceptions recursively, so do it only when running under debugger */ + if (CheckRemoteDebuggerPresent(hProcess, &fDebuggerPresent)) + fBreak = !!fDebuggerPresent; + else + fBreak = FALSE; /* <- the function has failed, don't break for sanity */ + } +#endif + + if (fBreak) + { + RT_BREAKPOINT(); + } + } + + if (g_VBoxVehFlags & VBOXVEH_F_DUMP) + vboxMdMinidumpCreate(pExceptionInfo); + + if (g_VBoxVehFlags & VBOXVEH_F_EXIT) + exit(1); + break; + default: + break; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +void vboxVDbgVEHandlerRegister() +{ + CRASSERT(!g_VBoxVehHandler); + g_VBoxVehHandler = AddVectoredExceptionHandler(1,vboxVDbgVectoredHandler); + CRASSERT(g_VBoxVehHandler); +} + +void vboxVDbgVEHandlerUnregister() +{ + ULONG uResult; + if (g_VBoxVehHandler) + { + uResult = RemoveVectoredExceptionHandler(g_VBoxVehHandler); + CRASSERT(uResult); + g_VBoxVehHandler = NULL; + } +} +#endif + +/* Windows crap */ +BOOL WINAPI DllMain(HINSTANCE hDLLInst, DWORD fdwReason, LPVOID lpvReserved) +{ + (void) lpvReserved; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + { + CRNetServer ns; + const char * env; +#if defined(DEBUG_misha) + HMODULE hCrUtil; + char aName[MAX_PATH]; + + GetModuleFileNameA(hDLLInst, aName, RT_ELEMENTS(aName)); + crDbgCmdSymLoadPrint(aName, hDLLInst); + + hCrUtil = GetModuleHandleA("VBoxOGLcrutil.dll"); + Assert(hCrUtil); + crDbgCmdSymLoadPrint("VBoxOGLcrutil.dll", hCrUtil); +#endif +#ifdef CHROMIUM_THREADSAFE + crInitTSD(&g_stubCurrentContextTSD); +#endif + + crInitMutex(&stub_init_mutex); + +#ifdef VDBG_VEHANDLER + env = crGetenv("CR_DBG_VEH_ENABLE"); + g_VBoxVehEnable = crStrParseI32(env, +# ifdef DEBUG_misha + 1 +# else + 0 +# endif + ); + + if (g_VBoxVehEnable) + { + char procName[1024]; + size_t cProcName; + size_t cChars; + + env = crGetenv("CR_DBG_VEH_FLAGS"); + g_VBoxVehFlags = crStrParseI32(env, + 0 +# ifdef DEBUG_misha + | VBOXVEH_F_BREAK +# else + | VBOXVEH_F_DUMP +# endif + ); + + env = crGetenv("CR_DBG_VEH_DUMP_DIR"); + if (!env) + env = VBOXMD_DUMP_DIR_DEFAULT; + + g_cVBoxMdFilePrefixLen = strlen(env); + + if (RT_ELEMENTS(g_aszwVBoxMdFilePrefix) <= g_cVBoxMdFilePrefixLen + 26 + (sizeof (VBOXMD_DUMP_NAME_PREFIX_W) - sizeof (WCHAR)) / sizeof (WCHAR)) + { + g_cVBoxMdFilePrefixLen = 0; + env = ""; + } + + mbstowcs_s(&cChars, g_aszwVBoxMdFilePrefix, g_cVBoxMdFilePrefixLen + 1, env, _TRUNCATE); + + Assert(cChars == g_cVBoxMdFilePrefixLen + 1); + + g_cVBoxMdFilePrefixLen = cChars - 1; + + if (g_cVBoxMdFilePrefixLen && g_aszwVBoxMdFilePrefix[g_cVBoxMdFilePrefixLen - 1] != L'\\') + g_aszwVBoxMdFilePrefix[g_cVBoxMdFilePrefixLen++] = L'\\'; + + memcpy(g_aszwVBoxMdFilePrefix + g_cVBoxMdFilePrefixLen, VBOXMD_DUMP_NAME_PREFIX_W, sizeof (VBOXMD_DUMP_NAME_PREFIX_W) - sizeof (WCHAR)); + g_cVBoxMdFilePrefixLen += (sizeof (VBOXMD_DUMP_NAME_PREFIX_W) - sizeof (WCHAR)) / sizeof (WCHAR); + + crGetProcName(procName, RT_ELEMENTS(procName)); + cProcName = strlen(procName); + + if (RT_ELEMENTS(g_aszwVBoxMdFilePrefix) > g_cVBoxMdFilePrefixLen + cProcName + 1 + 26) + { + mbstowcs_s(&cChars, g_aszwVBoxMdFilePrefix + g_cVBoxMdFilePrefixLen, cProcName + 1, procName, _TRUNCATE); + Assert(cChars == cProcName + 1); + g_cVBoxMdFilePrefixLen += cChars - 1; + g_aszwVBoxMdFilePrefix[g_cVBoxMdFilePrefixLen++] = L'_'; + } + + /* sanity */ + g_aszwVBoxMdFilePrefix[g_cVBoxMdFilePrefixLen] = L'\0'; + + env = crGetenv("CR_DBG_VEH_DUMP_TYPE"); + + g_enmVBoxMdDumpType = crStrParseI32(env, + MiniDumpNormal + | MiniDumpWithDataSegs + | MiniDumpWithFullMemory + | MiniDumpWithHandleData + //// | MiniDumpFilterMemory + //// | MiniDumpScanMemory + // | MiniDumpWithUnloadedModules + //// | MiniDumpWithIndirectlyReferencedMemory + //// | MiniDumpFilterModulePaths + // | MiniDumpWithProcessThreadData + // | MiniDumpWithPrivateReadWriteMemory + //// | MiniDumpWithoutOptionalData + // | MiniDumpWithFullMemoryInfo + // | MiniDumpWithThreadInfo + // | MiniDumpWithCodeSegs + // | MiniDumpWithFullAuxiliaryState + // | MiniDumpWithPrivateWriteCopyMemory + // | MiniDumpIgnoreInaccessibleMemory + // | MiniDumpWithTokenInformation + //// | MiniDumpWithModuleHeaders + //// | MiniDumpFilterTriage + ); + + vboxVDbgVEHandlerRegister(); + } +#endif + + crNetInit(NULL, NULL); + ns.name = "vboxhgcm://host:0"; + ns.buffer_size = 1024; + crNetServerConnect(&ns +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , NULL +#endif +); + if (!ns.conn) + { + crDebug("Failed to connect to host (is guest 3d acceleration enabled?), aborting ICD load."); +#ifdef VDBG_VEHANDLER + if (g_VBoxVehEnable) + vboxVDbgVEHandlerUnregister(); +#endif + return FALSE; + } + else + { + crNetFreeConnection(ns.conn); + } + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + VBoxCrHgsmiInit(); +#endif + break; + } + + case DLL_PROCESS_DETACH: + { + /* do exactly the same thing as for DLL_THREAD_DETACH since + * DLL_THREAD_DETACH is not called for the thread doing DLL_PROCESS_DETACH according to msdn docs */ + stubSetCurrentContext(NULL); + if (stub_initialized) + { + CRASSERT(stub.spu); + stub.spu->dispatch_table.VBoxDetachThread(); + } + + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + VBoxCrHgsmiTerm(); +#endif + + stubSPUSafeTearDown(); + +#ifdef CHROMIUM_THREADSAFE + crFreeTSD(&g_stubCurrentContextTSD); +#endif + +#ifdef VDBG_VEHANDLER + if (g_VBoxVehEnable) + vboxVDbgVEHandlerUnregister(); +#endif + break; + } + + case DLL_THREAD_ATTACH: + { + if (stub_initialized) + { + CRASSERT(stub.spu); + stub.spu->dispatch_table.VBoxAttachThread(); + } + break; + } + + case DLL_THREAD_DETACH: + { + stubSetCurrentContext(NULL); + if (stub_initialized) + { + CRASSERT(stub.spu); + stub.spu->dispatch_table.VBoxDetachThread(); + } + break; + } + + default: + break; + } + + return TRUE; +} +#endif diff --git a/src/VBox/Additions/common/crOpenGL/pack/Makefile.kup b/src/VBox/Additions/common/crOpenGL/pack/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/Makefile.kup diff --git a/src/VBox/Additions/common/crOpenGL/pack/pack.def b/src/VBox/Additions/common/crOpenGL/pack/pack.def new file mode 100644 index 00000000..9edc7163 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/pack.def @@ -0,0 +1,6 @@ +; Copyright (c) 2001, Stanford University +; All rights reserved. +; +; See the file LICENSE.txt for information on redistributing this software. +EXPORTS +SPULoad diff --git a/src/VBox/Additions/common/crOpenGL/pack/pack.py b/src/VBox/Additions/common/crOpenGL/pack/pack.py new file mode 100755 index 00000000..e25066be --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/pack.py @@ -0,0 +1,57 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - THIS FILE AUTOMATICALLY GENERATED BY pack.py SCRIPT */ +#include <stdio.h> +#include "cr_string.h" +#include "cr_spu.h" +#include "packspu.h" +#include "cr_packfunctions.h" +#include "packspu_proto.h" +""") + +num_funcs = len(keys) - len(apiutil.AllSpecials('packspu_unimplemented')) +print('SPUNamedFunctionTable _cr_pack_table[%d];' % (num_funcs+1)) + +print(""" +static void __fillin(int offset, char *name, SPUGenericFunction func) +{ + _cr_pack_table[offset].name = crStrdup(name); + _cr_pack_table[offset].fn = func; +}""") + +pack_specials = [] + +for func_name in keys: + if ("get" in apiutil.Properties(func_name) or + apiutil.FindSpecial( "packspu", func_name ) or + apiutil.FindSpecial( "packspu_flush", func_name ) or + apiutil.FindSpecial( "packspu_vertex", func_name )): + pack_specials.append( func_name ) + +print('\nvoid packspuCreateFunctions( void )') +print('{') +for index in range(len(keys)): + func_name = keys[index] + if apiutil.FindSpecial( "packspu_unimplemented", func_name ): + continue + if func_name in pack_specials: + print('\t__fillin(%3d, "%s", (SPUGenericFunction) packspu_%s);' % (index, func_name, func_name )) + else: + print('\t__fillin(%3d, "%s", (SPUGenericFunction) (pack_spu.swap ? crPack%sSWAP : crPack%s));' % (index, func_name, func_name, func_name )) +print('\t__fillin(%3d, NULL, NULL);' % num_funcs) +print('}') diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu.h b/src/VBox/Additions/common/crOpenGL/pack/packspu.h new file mode 100644 index 00000000..9774e35b --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu.h @@ -0,0 +1,185 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved. + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#ifndef GA_INCLUDED_SRC_common_crOpenGL_pack_packspu_h +#define GA_INCLUDED_SRC_common_crOpenGL_pack_packspu_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef WINDOWS +#define PACKSPU_APIENTRY __stdcall +#else +#define PACKSPU_APIENTRY +#endif + +#include "cr_glstate.h" +#include "cr_netserver.h" +#include "cr_pack.h" +#include "cr_spu.h" +#include "cr_threads.h" +#include "state/cr_client.h" +#ifdef VBOX_WITH_CRPACKSPU_DUMPER +# include "cr_dump.h" +#endif + +extern uint32_t g_u32VBoxHostCaps; + +typedef struct thread_info_t ThreadInfo; +typedef struct context_info_t ContextInfo; +typedef struct zvabuffer_info_t ZvaBufferInfo; + +struct zvabuffer_info_t +{ + /* GL_ARRAY_BUFFER_ARB buffer */ + GLuint idBuffer; + /* buffer length */ + GLuint cbBuffer; + /* number of values stored in the buffer currently */ + GLuint cValues; + /* current buffer value */ + union + { + GLfloat f[4]; + GLuint ui[4]; + GLubyte ub[4]; + GLshort s[4]; + GLushort us[4]; + } Value; +}; + +struct thread_info_t { + unsigned long id; + CRNetServer netServer; + CRPackBuffer buffer; + CRPackBuffer normBuffer; + CRPackBuffer BeginEndBuffer; + GLenum BeginEndMode; + int BeginEndState; + ContextInfo *currentContext; + CRPackContext *packer; + int writeback; + GLboolean bInjectThread; + GLboolean inUse; +}; + +struct context_info_t { + CRContext *clientState; /* used to store client-side GL state */ + GLint serverCtx; /* context ID returned by server */ + GLboolean fAutoFlush; + GLboolean fCheckZerroVertAttr; + ThreadInfo *currentThread; + ZvaBufferInfo zvaBufferInfo; + GLubyte glVersion[100]; /* GL_VERSION string */ + GLubyte pszRealVendor[100]; + GLubyte pszRealVersion[100]; + GLubyte pszRealRenderer[100]; +}; + +typedef struct { + int id; + int swap; + + /* config options */ + int emit_GATHER_POST_SWAPBUFFERS; + int swapbuffer_sync; + + int ReadPixels; + + char *name; + int buffer_size; + + int numThreads; /*number of used threads in the next array, doesn't need to be cont*/ + ThreadInfo thread[MAX_THREADS]; + int idxThreadInUse; /*index of any used thread*/ + +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + bool bIsWDDMCrHgsmi; +#endif + + SPUDispatchTable self; + +#ifdef VBOX_WITH_CRPACKSPU_DUMPER + CR_RECORDER Recorder; + CR_DBGPRINT_DUMPER Dumper; +#endif + + int numContexts; + ContextInfo context[CR_MAX_CONTEXTS]; +} PackSPU; + +extern PackSPU pack_spu; + +#define THREAD_OFFSET_MAGIC 2000 + +#ifdef CHROMIUM_THREADSAFE +extern CRmutex _PackMutex; +extern CRtsd _PackTSD; +#define GET_THREAD_VAL() (crGetTSD(&_PackTSD)) +#define GET_THREAD_IDX(_id) ((_id) - THREAD_OFFSET_MAGIC) +#define GET_THREAD_VAL_ID(_id) (&(pack_spu.thread[GET_THREAD_IDX(_id)])) +#else +#define GET_THREAD_VAL() (&(pack_spu.thread[0])) +#endif +#define GET_THREAD(T) ThreadInfo *T = GET_THREAD_VAL() +#define GET_THREAD_ID(T, _id) ThreadInfo *T = GET_THREAD_VAL_ID(_id) + + + +#define GET_CONTEXT(C) \ + GET_THREAD(thread); \ + ContextInfo *C = thread->currentContext + +#ifdef DEBUG_misha +# define CRPACKSPU_WRITEBACK_ASSERT_ZERO(_writeback) Assert(!(_writeback)) +#else +# define CRPACKSPU_WRITEBACK_ASSERT_ZERO(_writeback) do {} while (0) +#endif + +#define CRPACKSPU_WRITEBACK_WAIT(_thread, _writeback) do {\ + if (g_u32VBoxHostCaps & CR_VBOX_CAP_CMDVBVA) { \ + CRPACKSPU_WRITEBACK_ASSERT_ZERO(_writeback); \ + (_writeback) = 0; \ + break; \ + } \ + CR_WRITEBACK_WAIT((_thread)->netServer.conn, _writeback); \ + } while (0) + +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) && defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) +# define CRPACKSPU_IS_WDDM_CRHGSMI() (pack_spu.bIsWDDMCrHgsmi) +#else +# define CRPACKSPU_IS_WDDM_CRHGSMI() (GL_FALSE) +#endif + +extern void packspuCreateFunctions( void ); +extern void packspuSetVBoxConfiguration( const SPU *child_spu ); +extern void packspuConnectToServer( CRNetServer *server +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , struct VBOXUHGSMI *pHgsmi +#endif + ); +extern void packspuFlush( void *arg ); +extern void packspuHuge( CROpcode opcode, void *buf ); + +extern void packspuInitStrings(void); + +extern GLboolean packspuSyncOnFlushes(void); + +extern ThreadInfo *packspuNewThread( +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + struct VBOXUHGSMI *pHgsmi +#else + void +#endif + ); + +extern ThreadInfo *packspuNewCtxThread( struct VBOXUHGSMI *pHgsmi ); + + + +#define MAGIC_OFFSET 3000 + +#endif /* !GA_INCLUDED_SRC_common_crOpenGL_pack_packspu_h */ diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu.rc b/src/VBox/Additions/common/crOpenGL/pack/packspu.rc new file mode 100644 index 00000000..b120f236 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu.rc @@ -0,0 +1,69 @@ +/* $Id: packspu.rc $ */ +/** @file + * VBoxOGLpackspu - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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_DRV + FILESUBTYPE VFT2_DRV_DISPLAY +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "VirtualBox crOpenGL ICD\0" + VALUE "InternalName", "VBoxOGLpackspu\0" +#ifdef VBOX_WDDM_WOW64 + VALUE "OriginalFilename", "VBoxOGLpackspu-x86.dll\0" +#else + VALUE "OriginalFilename", "VBoxOGLpackspu.dll\0" +#endif + 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 + +1 RCDATA +BEGIN +// Machine dependent parameters + 17, // Height of vertical thumb + 17, // Width of horizontal thumb + 2, // Icon horiz compression factor + 2, // Icon vert compression factor + 1, // Cursor horz compression factor + 1, // Cursor vert compression factor + 0, // Kanji window height + 1, // cxBorder (thickness of vertical lines) + 1 // cyBorder (thickness of horizontal lines) +END diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_beginend.py b/src/VBox/Additions/common/crOpenGL/pack/packspu_beginend.py new file mode 100755 index 00000000..2195cab5 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_beginend.py @@ -0,0 +1,177 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + + +apiutil.CopyrightC() + +print("""/* DO NOT EDIT - AUTOMATICALLY GENERATED BY packspu_beginend.py */ +#include "packspu.h" +#include "assert.h" +#include "cr_packfunctions.h" +#include "packspu_proto.h" + +void PACKSPU_APIENTRY packspu_Begin( GLenum mode ) +{ + CRPackBuffer *buf; +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); +#else + GET_THREAD(thread); +#endif + + buf = &thread->BeginEndBuffer; + + /* XXX comparing mode >= 0 here is not needed since mode is unsigned */ + CRASSERT( /*mode >= GL_POINTS && */mode <= GL_POLYGON ); + +#if CR_ARB_vertex_buffer_object + { + GLboolean serverArrays = GL_FALSE; + if (ctx->clientState->extensions.ARB_vertex_buffer_object) + serverArrays = crStateUseServerArrays(); + if (serverArrays) { + CRClientState *clientState = &(ctx->clientState->client); + if (clientState->array.locked && !clientState->array.synced) + { + crPackLockArraysEXT(clientState->array.lockFirst, clientState->array.lockCount); + clientState->array.synced = GL_TRUE; + } + } + } +#endif + + if (pack_spu.swap) + { + crPackBeginSWAP( mode ); + } + else + { + crPackBegin( mode ); + } + + if ( thread->netServer.conn->Barf ) { + thread->BeginEndMode = mode; + thread->BeginEndState = -1; + if ( mode == GL_LINES || mode == GL_TRIANGLES || mode == GL_QUADS || mode == GL_POLYGON ) + { + CRASSERT(!buf->pack); + + crPackReleaseBuffer( thread->packer ); + buf->pack = crNetAlloc( thread->netServer.conn ); + crPackInitBuffer( buf, buf->pack, thread->netServer.conn->buffer_size, thread->netServer.conn->mtu ); + buf->holds_BeginEnd = 1; + buf->in_BeginEnd = 1; + crPackSetBuffer( thread->packer, buf ); + + thread->BeginEndState = 0; + } + } +} + +void PACKSPU_APIENTRY packspu_End( void ) +{ + GET_THREAD(thread); + CRPackBuffer *buf = &thread->BeginEndBuffer; + + if ( thread->netServer.conn->Barf && + (thread->BeginEndMode == GL_LINES + || thread->BeginEndMode == GL_TRIANGLES + || thread->BeginEndMode == GL_QUADS + || thread->BeginEndMode == GL_POLYGON ) ) + { + CRASSERT(buf->pack); + + crPackReleaseBuffer( thread->packer ); + crPackSetBuffer( thread->packer, &thread->normBuffer ); + if ( !crPackCanHoldBuffer( buf ) ) + packspuFlush( (void *) thread ); + + crPackAppendBuffer( buf ); + crNetFree( thread->netServer.conn, buf->pack ); + buf->pack = NULL; + } + + if (pack_spu.swap) + { + crPackEndSWAP(); + } + else + { + crPackEnd(); + } +} + +static void DoVertex( void ) +{ + GET_THREAD(thread); + CRPackBuffer *buf = &thread->BeginEndBuffer; + CRPackBuffer *gbuf = &thread->normBuffer; + int num_data; + int num_opcode; + + /*crDebug( "really doing Vertex" );*/ + crPackReleaseBuffer( thread->packer ); + num_data = buf->data_current - buf->data_start; + num_opcode = buf->opcode_start - buf->opcode_current; + crPackSetBuffer( thread->packer, gbuf ); + if ( !crPackCanHoldBuffer( buf ) ) + /* doesn't hold, first flush gbuf*/ + packspuFlush( (void *) thread ); + + crPackAppendBuffer( buf ); + crPackReleaseBuffer( thread->packer ); + crPackSetBuffer( thread->packer, buf ); + crPackResetPointers(thread->packer); +} + +static void RunState( void ) +{ + GET_THREAD(thread); + if (! thread->netServer.conn->Barf ) return; + if (thread->BeginEndState == -1) return; + switch(thread->BeginEndMode) { + case GL_POLYGON: + return; + case GL_LINES: + thread->BeginEndState = (thread->BeginEndState + 1) % 2; + if (thread->BeginEndState) + return; + break; + case GL_TRIANGLES: + thread->BeginEndState = (thread->BeginEndState + 1) % 3; + if (thread->BeginEndState) + return; + break; + case GL_QUADS: + thread->BeginEndState = (thread->BeginEndState + 1) % 4; + if (thread->BeginEndState) + return; + break; + } + DoVertex(); +} +""") + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in apiutil.AllSpecials( "packspu_vertex" ): + params = apiutil.Parameters(func_name) + print('void PACKSPU_APIENTRY packspu_%s(%s)' % ( func_name, apiutil.MakeDeclarationString(params) )) + print('{') + print('\tif (pack_spu.swap)') + print('\t{') + print('\t\tcrPack%sSWAP(%s);' % ( func_name, apiutil.MakeCallString( params ) )) + print('\t}') + print('\telse') + print('\t{') + print('\t\tcrPack%s(%s);' % ( func_name, apiutil.MakeCallString( params ) )) + print('\t}') + print('\tRunState();') + print('}') diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_bufferobject.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_bufferobject.c new file mode 100644 index 00000000..65f64bd7 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_bufferobject.c @@ -0,0 +1,160 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_error.h" +#include "cr_mem.h" +#include "cr_string.h" +#include "packspu.h" +#include "packspu_proto.h" + +static void packspu_GetHostBufferSubDataARB( GLenum target, GLintptrARB offset, GLsizeiptrARB size, void * data ) +{ + GET_THREAD(thread); + int writeback = 1; + + crPackGetBufferSubDataARB(target, offset, size, data, &writeback); + + packspuFlush((void *) thread); + + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); +} + +void * PACKSPU_APIENTRY +packspu_MapBufferARB( GLenum target, GLenum access ) +{ + GET_CONTEXT(ctx); + void *buffer; + CRBufferObject *pBufObj; + + CRASSERT(GL_TRUE == ctx->clientState->bufferobject.retainBufferData); + buffer = crStateMapBufferARB(target, access); + +#ifdef CR_ARB_pixel_buffer_object + if (buffer) + { + pBufObj = crStateGetBoundBufferObject(target, &ctx->clientState->bufferobject); + CRASSERT(pBufObj); + + if (pBufObj->bResyncOnRead && + access != GL_WRITE_ONLY_ARB) + { + /*fetch data from host side*/ + packspu_GetHostBufferSubDataARB(target, 0, pBufObj->size, buffer); + } + } +#endif + + return buffer; +} + +void PACKSPU_APIENTRY packspu_GetBufferSubDataARB( GLenum target, GLintptrARB offset, GLsizeiptrARB size, void * data ) +{ + GET_CONTEXT(ctx); + +#ifdef CR_ARB_pixel_buffer_object + CRBufferObject *pBufObj; + + pBufObj = crStateGetBoundBufferObject(target, &ctx->clientState->bufferobject); + + if (pBufObj && pBufObj->bResyncOnRead) + { + packspu_GetHostBufferSubDataARB(target, offset, size, data); + return; + } +#endif + + crStateGetBufferSubDataARB(target, offset, size, data); +} + + +GLboolean PACKSPU_APIENTRY +packspu_UnmapBufferARB( GLenum target ) +{ + GET_CONTEXT(ctx); + +#if CR_ARB_vertex_buffer_object + CRBufferObject *bufObj; + + bufObj = crStateGetBoundBufferObject(target, &ctx->clientState->bufferobject); + + /* send new buffer contents to server */ + crPackBufferDataARB( target, bufObj->size, bufObj->pointer, bufObj->usage ); +#endif + + CRASSERT(GL_TRUE == ctx->clientState->bufferobject.retainBufferData); + crStateUnmapBufferARB( target ); + + return GL_TRUE; +} + + +void PACKSPU_APIENTRY +packspu_BufferDataARB(GLenum target, GLsizeiptrARB size, const GLvoid * data, GLenum usage) +{ + /*crDebug("packspu_BufferDataARB size:%d", size);*/ + crStateBufferDataARB(target, size, data, usage); + crPackBufferDataARB(target, size, data, usage); +} + +void PACKSPU_APIENTRY +packspu_BufferSubDataARB(GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid * data) +{ + /*crDebug("packspu_BufferSubDataARB size:%d", size);*/ + crStateBufferSubDataARB(target, offset, size, data); + crPackBufferSubDataARB(target, offset, size, data); +} + + +void PACKSPU_APIENTRY +packspu_GetBufferPointervARB(GLenum target, GLenum pname, GLvoid **params) +{ + crStateGetBufferPointervARB( target, pname, params ); +} + + +void PACKSPU_APIENTRY +packspu_GetBufferParameterivARB( GLenum target, GLenum pname, GLint * params ) +{ + crStateGetBufferParameterivARB( target, pname, params ); +} + +/* + * Need to update our local state for vertex arrays. + */ +void PACKSPU_APIENTRY +packspu_BindBufferARB( GLenum target, GLuint buffer ) +{ + crStateBindBufferARB(target, buffer); + crPackBindBufferARB(target, buffer); +} + +void PACKSPU_APIENTRY packspu_GenBuffersARB( GLsizei n, GLuint * buffer ) +{ + GET_THREAD(thread); + int writeback = 1; + if (!CRPACKSPU_IS_WDDM_CRHGSMI() && !(pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network)) + { + crError( "packspu_GenBuffersARB doesn't work when there's no actual network involved!\nTry using the simplequery SPU in your chain!" ); + } + if (pack_spu.swap) + { + crPackGenBuffersARBSWAP( n, buffer, &writeback ); + } + else + { + crPackGenBuffersARB( n, buffer, &writeback ); + } + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + crStateRegBuffers(n, buffer); +} + +void PACKSPU_APIENTRY packspu_DeleteBuffersARB( GLsizei n, const GLuint * buffer ) +{ + crStateDeleteBuffersARB( n, buffer ); + crPackDeleteBuffersARB(n, buffer); +} diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_client.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_client.c new file mode 100644 index 00000000..19c11880 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_client.c @@ -0,0 +1,910 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "packspu.h" +#include "cr_packfunctions.h" +#include "cr_glstate.h" +#include "packspu_proto.h" +#include "cr_mem.h" + +void PACKSPU_APIENTRY packspu_FogCoordPointerEXT( GLenum type, GLsizei stride, const GLvoid *pointer ) +{ +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); + if (ctx->clientState->extensions.ARB_vertex_buffer_object) { + if (pack_spu.swap) + crPackFogCoordPointerEXTSWAP( type, stride, pointer ); + else + crPackFogCoordPointerEXT( type, stride, pointer ); + } +#endif + crStateFogCoordPointerEXT( type, stride, pointer ); +} + +void PACKSPU_APIENTRY packspu_ColorPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); + if (ctx->clientState->extensions.ARB_vertex_buffer_object) { + if (pack_spu.swap) + crPackColorPointerSWAP( size, type, stride, pointer ); + else + crPackColorPointer( size, type, stride, pointer ); + } +#endif + crStateColorPointer( size, type, stride, pointer ); +} + +void PACKSPU_APIENTRY packspu_SecondaryColorPointerEXT( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); + if (ctx->clientState->extensions.ARB_vertex_buffer_object) { + if (pack_spu.swap) + crPackSecondaryColorPointerEXTSWAP( size, type, stride, pointer ); + else + crPackSecondaryColorPointerEXT( size, type, stride, pointer ); + } +#endif + crStateSecondaryColorPointerEXT( size, type, stride, pointer ); +} + +void PACKSPU_APIENTRY packspu_VertexPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); + CRASSERT(ctx->clientState->extensions.ARB_vertex_buffer_object); + if (ctx->clientState->extensions.ARB_vertex_buffer_object) { + if (pack_spu.swap) + crPackVertexPointerSWAP( size, type, stride, pointer ); + else + crPackVertexPointer( size, type, stride, pointer ); + } +#endif + crStateVertexPointer( size, type, stride, pointer ); +} + +void PACKSPU_APIENTRY packspu_TexCoordPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); + if (ctx->clientState->extensions.ARB_vertex_buffer_object) { + if (pack_spu.swap) + crPackTexCoordPointerSWAP( size, type, stride, pointer ); + else + crPackTexCoordPointer( size, type, stride, pointer ); + } +#endif + crStateTexCoordPointer( size, type, stride, pointer ); +} + +void PACKSPU_APIENTRY packspu_NormalPointer( GLenum type, GLsizei stride, const GLvoid *pointer ) +{ +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); + if (ctx->clientState->extensions.ARB_vertex_buffer_object) { + if (pack_spu.swap) + crPackNormalPointerSWAP( type, stride, pointer ); + else + crPackNormalPointer( type, stride, pointer ); + } +#endif + crStateNormalPointer( type, stride, pointer ); +} + +void PACKSPU_APIENTRY packspu_EdgeFlagPointer( GLsizei stride, const GLvoid *pointer ) +{ +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); + if (ctx->clientState->extensions.ARB_vertex_buffer_object) { + if (pack_spu.swap) + crPackEdgeFlagPointerSWAP( stride, pointer ); + else + crPackEdgeFlagPointer( stride, pointer ); + } +#endif + crStateEdgeFlagPointer( stride, pointer ); +} + +void PACKSPU_APIENTRY packspu_VertexAttribPointerARB( GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer ) +{ +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); + if (ctx->clientState->extensions.ARB_vertex_buffer_object) { + if (pack_spu.swap) + crPackVertexAttribPointerARBSWAP( index, size, type, normalized, stride, pointer ); + else + crPackVertexAttribPointerARB( index, size, type, normalized, stride, pointer ); + } +#endif + crStateVertexAttribPointerARB( index, size, type, normalized, stride, pointer ); +} + +void PACKSPU_APIENTRY packspu_VertexAttribPointerNV( GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); + if (ctx->clientState->extensions.ARB_vertex_buffer_object) { + if (pack_spu.swap) + crPackVertexAttribPointerNVSWAP( index, size, type, stride, pointer ); + else + crPackVertexAttribPointerNV( index, size, type, stride, pointer ); + } +#endif + crStateVertexAttribPointerNV( index, size, type, stride, pointer ); +} + +void PACKSPU_APIENTRY packspu_IndexPointer( GLenum type, GLsizei stride, const GLvoid *pointer ) +{ +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); + if (ctx->clientState->extensions.ARB_vertex_buffer_object) { + if (pack_spu.swap) + crPackIndexPointerSWAP( type, stride, pointer ); + else + crPackIndexPointer( type, stride, pointer ); + } +#endif + crStateIndexPointer(type, stride, pointer); +} + +void PACKSPU_APIENTRY packspu_GetPointerv( GLenum pname, GLvoid **params ) +{ + crStateGetPointerv( pname, params ); +} + +void PACKSPU_APIENTRY packspu_InterleavedArrays( GLenum format, GLsizei stride, const GLvoid *pointer ) +{ +#if CR_ARB_vertex_buffer_object + GET_CONTEXT(ctx); + if (ctx->clientState->extensions.ARB_vertex_buffer_object) { + if (pack_spu.swap) + crPackInterleavedArraysSWAP( format, stride, pointer ); + else + crPackInterleavedArrays( format, stride, pointer ); + } +#endif + + /*crDebug("packspu_InterleavedArrays");*/ + + crStateInterleavedArrays( format, stride, pointer ); +} + +#ifdef DEBUG_misha +/* debugging */ +//# define CR_FORCE_ZVA_SERVER_ARRAY +#endif +# define CR_FORCE_ZVA_EXPAND + + +static GLboolean packspuZvaCreate(ContextInfo *pCtx, const GLfloat *pValue, GLuint cValues) +{ + ZvaBufferInfo *pInfo = &pCtx->zvaBufferInfo; + GLuint cbValue = 4 * sizeof (*pValue); + GLuint cbValues = cValues * cbValue; + GLfloat *pBuffer; + uint8_t *pu8Buf; + GLuint i; + + /* quickly sort out if we can use the current value */ + if (pInfo->idBuffer + && pInfo->cValues >= cValues + && !crMemcmp(pValue, &pInfo->Value, cbValue)) + return GL_FALSE; + + pBuffer = (GLfloat*)crAlloc(cbValues); + if (!pBuffer) + { + WARN(("crAlloc for pBuffer failed")); + return GL_FALSE; + } + + pu8Buf = (uint8_t *)pBuffer; + for (i = 0; i < cValues; ++i) + { + crMemcpy(pu8Buf, pValue, cbValue); + pu8Buf += cbValue; + } + + /* */ + if (!pInfo->idBuffer) + { + pack_spu.self.GenBuffersARB(1, &pInfo->idBuffer); + Assert(pInfo->idBuffer); + } + + pack_spu.self.BindBufferARB(GL_ARRAY_BUFFER_ARB, pInfo->idBuffer); + + if (pInfo->cbBuffer < cbValues) + { + pack_spu.self.BufferDataARB(GL_ARRAY_BUFFER_ARB, cbValues, pBuffer, GL_DYNAMIC_DRAW_ARB); + pInfo->cbBuffer = cbValues; + } + else + { + pack_spu.self.BufferSubDataARB(GL_ARRAY_BUFFER_ARB, 0, cbValues, pBuffer); + } + + pInfo->cValues = cValues; + crMemcpy(&pInfo->Value, pValue, cbValue); + + crFree(pBuffer); + + return GL_TRUE; +} + +typedef struct +{ + ContextInfo *pCtx; + GLuint idBuffer; + CRClientPointer cp; +} CR_ZVA_RESTORE_CTX; + +static void packspuZvaEnable(ContextInfo *pCtx, const GLfloat *pValue, GLuint cValues, CR_ZVA_RESTORE_CTX *pRestoreCtx) +{ + CRContext *g = pCtx->clientState; + + Assert(0); + +#ifdef DEBUG + { + CRContext *pCurState = crStateGetCurrent(); + + Assert(g == pCurState); + } +#endif + + pRestoreCtx->pCtx = pCtx; + pRestoreCtx->idBuffer = g->bufferobject.arrayBuffer ? g->bufferobject.arrayBuffer->id : 0; + pRestoreCtx->cp = g->client.array.a[0]; + + Assert(!pRestoreCtx->cp.enabled); + + /* buffer ref count mechanism does not work actually atm, + * still ensure the buffer does not get destroyed if we fix it in the future */ + if (pRestoreCtx->cp.buffer) + pRestoreCtx->cp.buffer->refCount++; + + packspuZvaCreate(pCtx, pValue, cValues); + + pack_spu.self.BindBufferARB(GL_ARRAY_BUFFER_ARB, pCtx->zvaBufferInfo.idBuffer); + + pack_spu.self.VertexAttribPointerARB(0, 4, GL_FLOAT, + GL_FALSE, /*normalized*/ + 0, /*stride*/ + NULL /*addr*/); + + pack_spu.self.EnableVertexAttribArrayARB(0); +} + +static void packspuZvaDisable(CR_ZVA_RESTORE_CTX *pRestoreCtx) +{ + if (pRestoreCtx->cp.buffer) + pack_spu.self.BindBufferARB(GL_ARRAY_BUFFER_ARB, pRestoreCtx->cp.buffer->id); + else + pack_spu.self.BindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + + pack_spu.self.VertexAttribPointerARB(0, pRestoreCtx->cp.size, pRestoreCtx->cp.type, + pRestoreCtx->cp.normalized, /*normalized*/ + pRestoreCtx->cp.stride, /*stride*/ + pRestoreCtx->cp.p); + + if (pRestoreCtx->cp.enabled) + pack_spu.self.EnableVertexAttribArrayARB(0); + else + pack_spu.self.DisableVertexAttribArrayARB(0); + + if (pRestoreCtx->cp.buffer) + { + if (pRestoreCtx->cp.buffer->id != pRestoreCtx->idBuffer) + pack_spu.self.BindBufferARB(GL_ARRAY_BUFFER_ARB, pRestoreCtx->idBuffer); + + /* we have increased the refcount above, decrease it back */ + pRestoreCtx->cp.buffer->refCount--; + } + else + { + if (pRestoreCtx->idBuffer) + pack_spu.self.BindBufferARB(GL_ARRAY_BUFFER_ARB, pRestoreCtx->idBuffer); + } + +#ifdef DEBUG + { + CRContext *g = pRestoreCtx->pCtx->clientState; + CRContext *pCurState = crStateGetCurrent(); + + Assert(g == pCurState); + + Assert(pRestoreCtx->cp.p == g->client.array.a[0].p); + Assert(pRestoreCtx->cp.size == g->client.array.a[0].size); + Assert(pRestoreCtx->cp.type == g->client.array.a[0].type); + Assert(pRestoreCtx->cp.stride == g->client.array.a[0].stride); + Assert(pRestoreCtx->cp.enabled == g->client.array.a[0].enabled); + Assert(pRestoreCtx->cp.normalized == g->client.array.a[0].normalized); + Assert(pRestoreCtx->cp.bytesPerIndex == g->client.array.a[0].bytesPerIndex); +# ifdef CR_ARB_vertex_buffer_object + Assert(pRestoreCtx->cp.buffer == g->client.array.a[0].buffer); +# endif +# ifdef CR_EXT_compiled_vertex_array + Assert(pRestoreCtx->cp.locked == g->client.array.a[0].locked); +# endif + Assert(pRestoreCtx->idBuffer == (g->bufferobject.arrayBuffer ? g->bufferobject.arrayBuffer->id : 0)); + } +#endif +} + +void PACKSPU_APIENTRY +packspu_ArrayElement( GLint index ) +{ +/** @todo cash guest/host pointers calculation and use appropriate path here without crStateUseServerArrays call*/ +#if 1 + GLboolean serverArrays = GL_FALSE; + GLuint cZvaValues = 0; + GLfloat aAttrib[4]; + +#if CR_ARB_vertex_buffer_object + { + GET_CONTEXT(ctx); + /*crDebug("packspu_ArrayElement index:%i", index);*/ + if (ctx->clientState->extensions.ARB_vertex_buffer_object) + { + serverArrays = crStateUseServerArrays(); + if (ctx->fCheckZerroVertAttr) + cZvaValues = crStateNeedDummyZeroVertexArray(thread->currentContext->clientState, &thread->packer->current, aAttrib); + } + } +#endif + + if (serverArrays +#ifdef CR_FORCE_ZVA_EXPAND + && !cZvaValues +#endif + ) { + CR_ZVA_RESTORE_CTX RestoreCtx; + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + Assert(cZvaValues < UINT32_MAX/2); + + /* LockArraysEXT can not be executed between glBegin/glEnd pair, it also + * leads to vertexpointers being adjusted on the host side between glBegin/glEnd calls which + * produces unpredictable results. Locking is done before the glBegin call instead. + */ + CRASSERT(!clientState->array.locked || clientState->array.synced); + + if (cZvaValues) + packspuZvaEnable(ctx, aAttrib, cZvaValues, &RestoreCtx); + + /* Send the DrawArrays command over the wire */ + if (pack_spu.swap) + crPackArrayElementSWAP( index ); + else + crPackArrayElement( index ); + + if (cZvaValues) + packspuZvaDisable(&RestoreCtx); + } + else { + /* evaluate locally */ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + +#ifdef CR_FORCE_ZVA_SERVER_ARRAY + CR_ZVA_RESTORE_CTX RestoreCtx; + + if (cZvaValues && cZvaValues < UINT32_MAX/2) + packspuZvaEnable(ctx, aAttrib, cZvaValues, &RestoreCtx); +#endif + + if (pack_spu.swap) + crPackExpandArrayElementSWAP( index, clientState, cZvaValues ? aAttrib : NULL ); + else + crPackExpandArrayElement( index, clientState, cZvaValues ? aAttrib : NULL ); + +#ifdef CR_FORCE_ZVA_SERVER_ARRAY + if (cZvaValues && cZvaValues < UINT32_MAX/2) + packspuZvaDisable(&RestoreCtx); +#endif + } +#else + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + crPackExpandArrayElement(index, clientState, NULL); +#endif +} + +/*#define CR_USE_LOCKARRAYS*/ +#ifdef CR_USE_LOCKARRAYS +# error "check Zero Vertex Attrib hack is supported properly!" +#endif + +void PACKSPU_APIENTRY +packspu_DrawElements( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices ) +{ + GLboolean serverArrays = GL_FALSE; + GLuint cZvaValues = 0; + GLfloat aAttrib[4]; + +#if CR_ARB_vertex_buffer_object + GLboolean lockedArrays = GL_FALSE; + CRBufferObject *elementsBuffer; + { + GET_CONTEXT(ctx); + elementsBuffer = crStateGetCurrent()->bufferobject.elementsBuffer; + /*crDebug("DrawElements count=%d, indices=%p", count, indices);*/ + if (ctx->clientState->extensions.ARB_vertex_buffer_object) + { + serverArrays = crStateUseServerArrays(); + if (ctx->fCheckZerroVertAttr) + cZvaValues = crStateNeedDummyZeroVertexArray(thread->currentContext->clientState, &thread->packer->current, aAttrib); + } + } + +# ifdef CR_USE_LOCKARRAYS + if (!serverArrays && !ctx->clientState->client.array.locked && (count>3) + && (!elementsBuffer || !elementsBuffer->id)) + { + GLuint min, max; + GLsizei i; + + switch (type) + { + case GL_UNSIGNED_BYTE: + { + GLubyte *pIdx = (GLubyte *)indices; + min = max = pIdx[0]; + for (i=0; i<count; ++i) + { + if (pIdx[i]<min) min = pIdx[i]; + else if (pIdx[i]>max) max = pIdx[i]; + } + break; + } + case GL_UNSIGNED_SHORT: + { + GLushort *pIdx = (GLushort *)indices; + min = max = pIdx[0]; + for (i=0; i<count; ++i) + { + if (pIdx[i]<min) min = pIdx[i]; + else if (pIdx[i]>max) max = pIdx[i]; + } + break; + } + case GL_UNSIGNED_INT: + { + GLuint *pIdx = (GLuint *)indices; + min = max = pIdx[0]; + for (i=0; i<count; ++i) + { + if (pIdx[i]<min) min = pIdx[i]; + else if (pIdx[i]>max) max = pIdx[i]; + } + break; + } + default: crError("Unknown type 0x%x", type); + } + + if ((max-min)<(GLuint)(2*count)) + { + crStateLockArraysEXT(min, max-min+1); + + serverArrays = crStateUseServerArrays(); + if (serverArrays) + { + lockedArrays = GL_TRUE; + } + else + { + crStateUnlockArraysEXT(); + } + } + } +# endif +#endif + + if (serverArrays +#ifdef CR_FORCE_ZVA_EXPAND + && !cZvaValues +#endif + ) { + CR_ZVA_RESTORE_CTX RestoreCtx; + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + Assert(cZvaValues < UINT32_MAX/2); + + if (cZvaValues) + packspuZvaEnable(ctx, aAttrib, cZvaValues, &RestoreCtx); + + /*Note the comment in packspu_LockArraysEXT*/ + if (clientState->array.locked && !clientState->array.synced) + { + crPackLockArraysEXT(clientState->array.lockFirst, clientState->array.lockCount); + clientState->array.synced = GL_TRUE; + } + + /* Send the DrawArrays command over the wire */ + if (pack_spu.swap) + crPackDrawElementsSWAP( mode, count, type, indices ); + else + crPackDrawElements( mode, count, type, indices ); + + if (cZvaValues) + packspuZvaDisable(&RestoreCtx); + } + else { + /* evaluate locally */ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + +#ifdef CR_FORCE_ZVA_SERVER_ARRAY + CR_ZVA_RESTORE_CTX RestoreCtx; + + if (cZvaValues && cZvaValues < UINT32_MAX/2) + packspuZvaEnable(ctx, aAttrib, cZvaValues, &RestoreCtx); +#endif + + if (pack_spu.swap) + crPackExpandDrawElementsSWAP( mode, count, type, indices, clientState, cZvaValues ? aAttrib : NULL ); + else + { + //packspu_Begin(mode); + crPackExpandDrawElements( mode, count, type, indices, clientState, cZvaValues ? aAttrib : NULL ); + //packspu_End(); + } + +#ifdef CR_FORCE_ZVA_SERVER_ARRAY + if (cZvaValues && cZvaValues < UINT32_MAX/2) + packspuZvaDisable(&RestoreCtx); +#endif + } + +#if CR_ARB_vertex_buffer_object + if (lockedArrays) + { + packspu_UnlockArraysEXT(); + } +#endif +} + + +void PACKSPU_APIENTRY +packspu_DrawRangeElements( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices ) +{ + GLboolean serverArrays = GL_FALSE; + GLuint cZvaValues = 0; + GLfloat aAttrib[4]; + +#if CR_ARB_vertex_buffer_object + { + GET_CONTEXT(ctx); + /*crDebug("DrawRangeElements count=%d", count);*/ + if (ctx->clientState->extensions.ARB_vertex_buffer_object) + { + serverArrays = crStateUseServerArrays(); + if (ctx->fCheckZerroVertAttr) + cZvaValues = crStateNeedDummyZeroVertexArray(thread->currentContext->clientState, &thread->packer->current, aAttrib); + } + } +#endif + + if (serverArrays +#ifdef CR_FORCE_ZVA_EXPAND + && !cZvaValues +#endif + ) { + CR_ZVA_RESTORE_CTX RestoreCtx; + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + Assert(cZvaValues < UINT32_MAX/2); + + if (cZvaValues) + packspuZvaEnable(ctx, aAttrib, cZvaValues, &RestoreCtx); + + /*Note the comment in packspu_LockArraysEXT*/ + if (clientState->array.locked && !clientState->array.synced) + { + crPackLockArraysEXT(clientState->array.lockFirst, clientState->array.lockCount); + clientState->array.synced = GL_TRUE; + } + + /* Send the DrawRangeElements command over the wire */ + if (pack_spu.swap) + crPackDrawRangeElementsSWAP( mode, start, end, count, type, indices ); + else + crPackDrawRangeElements( mode, start, end, count, type, indices ); + + if (cZvaValues) + packspuZvaDisable(&RestoreCtx); + } + else { + /* evaluate locally */ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); +#ifdef CR_FORCE_ZVA_SERVER_ARRAY + CR_ZVA_RESTORE_CTX RestoreCtx; + + if (cZvaValues && cZvaValues < UINT32_MAX/2) + packspuZvaEnable(ctx, aAttrib, cZvaValues, &RestoreCtx); +#endif + + if (pack_spu.swap) + crPackExpandDrawRangeElementsSWAP( mode, start, end, count, type, indices, clientState, cZvaValues ? aAttrib : NULL ); + else + { + crPackExpandDrawRangeElements( mode, start, end, count, type, indices, clientState, cZvaValues ? aAttrib : NULL ); + } + +#ifdef CR_FORCE_ZVA_SERVER_ARRAY + if (cZvaValues && cZvaValues < UINT32_MAX/2) + packspuZvaDisable(&RestoreCtx); +#endif + } +} + + +void PACKSPU_APIENTRY +packspu_DrawArrays( GLenum mode, GLint first, GLsizei count ) +{ + GLboolean serverArrays = GL_FALSE; + GLuint cZvaValues = 0; + GLfloat aAttrib[4]; + +#if CR_ARB_vertex_buffer_object + GLboolean lockedArrays = GL_FALSE; + { + GET_CONTEXT(ctx); + /*crDebug("DrawArrays count=%d", count);*/ + if (ctx->clientState->extensions.ARB_vertex_buffer_object) + { + serverArrays = crStateUseServerArrays(); + if (ctx->fCheckZerroVertAttr) + cZvaValues = crStateNeedDummyZeroVertexArray(thread->currentContext->clientState, &thread->packer->current, aAttrib); + } + } + +# ifdef CR_USE_LOCKARRAYS + if (!serverArrays && !ctx->clientState->client.array.locked && (count>3)) + { + crStateLockArraysEXT(first, count); + serverArrays = crStateUseServerArrays(); + if (serverArrays) + { + lockedArrays = GL_TRUE; + } + else + { + crStateUnlockArraysEXT(); + } + } +# endif +#endif + + if (serverArrays +#ifdef CR_FORCE_ZVA_EXPAND + && !cZvaValues +#endif + ) + { + CR_ZVA_RESTORE_CTX RestoreCtx; + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + Assert(cZvaValues < UINT32_MAX/2); + + if (cZvaValues) + packspuZvaEnable(ctx, aAttrib, cZvaValues, &RestoreCtx); + + /*Note the comment in packspu_LockArraysEXT*/ + if (clientState->array.locked && !clientState->array.synced) + { + crPackLockArraysEXT(clientState->array.lockFirst, clientState->array.lockCount); + clientState->array.synced = GL_TRUE; + } + + /* Send the DrawArrays command over the wire */ + if (pack_spu.swap) + crPackDrawArraysSWAP( mode, first, count ); + else + crPackDrawArrays( mode, first, count ); + + if (cZvaValues) + packspuZvaDisable(&RestoreCtx); + } + else + { + /* evaluate locally */ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); +#ifdef CR_FORCE_ZVA_SERVER_ARRAY + CR_ZVA_RESTORE_CTX RestoreCtx; + + if (cZvaValues && cZvaValues < UINT32_MAX/2) + packspuZvaEnable(ctx, aAttrib, cZvaValues, &RestoreCtx); +#endif + + if (pack_spu.swap) + crPackExpandDrawArraysSWAP( mode, first, count, clientState, cZvaValues ? aAttrib : NULL ); + else + crPackExpandDrawArrays( mode, first, count, clientState, cZvaValues ? aAttrib : NULL ); + +#ifdef CR_FORCE_ZVA_SERVER_ARRAY + if (cZvaValues && cZvaValues < UINT32_MAX/2) + packspuZvaDisable(&RestoreCtx); +#endif + + } + +#if CR_ARB_vertex_buffer_object + if (lockedArrays) + { + packspu_UnlockArraysEXT(); + } +#endif +} + + +#ifdef CR_EXT_multi_draw_arrays +void PACKSPU_APIENTRY packspu_MultiDrawArraysEXT( GLenum mode, GLint *first, GLsizei *count, GLsizei primcount ) +{ + GLint i; + for (i = 0; i < primcount; i++) { + if (count[i] > 0) { + packspu_DrawArrays(mode, first[i], count[i]); + } + } +} + +void PACKSPU_APIENTRY packspu_MultiDrawElementsEXT( GLenum mode, const GLsizei *count, GLenum type, const GLvoid **indices, GLsizei primcount ) +{ + GLint i; + for (i = 0; i < primcount; i++) { + if (count[i] > 0) { + packspu_DrawElements(mode, count[i], type, indices[i]); + } + } +} +#endif + + +void PACKSPU_APIENTRY packspu_EnableClientState( GLenum array ) +{ + crStateEnableClientState(array); + crPackEnableClientState(array); +} + +void PACKSPU_APIENTRY packspu_DisableClientState( GLenum array ) +{ + crStateDisableClientState(array); + crPackDisableClientState(array); +} + +void PACKSPU_APIENTRY packspu_ClientActiveTextureARB( GLenum texUnit ) +{ + crStateClientActiveTextureARB(texUnit); + crPackClientActiveTextureARB(texUnit); +} + +void PACKSPU_APIENTRY packspu_EnableVertexAttribArrayARB(GLuint index) +{ + crStateEnableVertexAttribArrayARB(index); + crPackEnableVertexAttribArrayARB(index); +} + + +void PACKSPU_APIENTRY packspu_DisableVertexAttribArrayARB(GLuint index) +{ + crStateDisableVertexAttribArrayARB(index); + crPackDisableVertexAttribArrayARB(index); +} + +void PACKSPU_APIENTRY packspu_Enable( GLenum cap ) +{ + if (cap!=GL_LIGHT_MODEL_TWO_SIDE) + { + crStateEnable(cap); + + if (pack_spu.swap) + crPackEnableSWAP(cap); + else + crPackEnable(cap); + } + else + { + static int g_glmts1_warn=0; + if (!g_glmts1_warn) + { + crWarning("glEnable(GL_LIGHT_MODEL_TWO_SIDE) converted to valid glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1)"); + g_glmts1_warn=1; + } + crStateLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1); + crPackLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1); + } +} + + +void PACKSPU_APIENTRY packspu_Disable( GLenum cap ) +{ + if (cap!=GL_LIGHT_MODEL_TWO_SIDE) + { + crStateDisable(cap); + + if (pack_spu.swap) + crPackDisableSWAP(cap); + else + crPackDisable(cap); + } + else + { + static int g_glmts0_warn=0; + if (!g_glmts0_warn) + { + crWarning("glDisable(GL_LIGHT_MODEL_TWO_SIDE) converted to valid glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,0)"); + g_glmts0_warn=1; + } + crStateLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0); + crPackLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0); + } +} + +GLboolean PACKSPU_APIENTRY packspu_IsEnabled(GLenum cap) +{ + GLboolean res = crStateIsEnabled(cap); +#ifdef DEBUG + { + GET_THREAD(thread); + int writeback = 1; + GLboolean return_val = (GLboolean) 0; + crPackIsEnabled(cap, &return_val, &writeback); + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + CRASSERT(return_val==res); + } +#endif + + return res; +} + +void PACKSPU_APIENTRY packspu_PushClientAttrib( GLbitfield mask ) +{ + crStatePushClientAttrib(mask); + crPackPushClientAttrib(mask); +} + +void PACKSPU_APIENTRY packspu_PopClientAttrib( void ) +{ + crStatePopClientAttrib(); + crPackPopClientAttrib(); +} + +void PACKSPU_APIENTRY packspu_LockArraysEXT(GLint first, GLint count) +{ + if (first>=0 && count>0) + { + crStateLockArraysEXT(first, count); + /*Note: this is a workaround for quake3 based apps. + It's modifying vertex data between glLockArraysEXT and glDrawElements calls, + so we'd pass data to host right before the glDrawSomething or glBegin call. + */ + /*crPackLockArraysEXT(first, count);*/ + } + else crDebug("Ignoring packspu_LockArraysEXT: first:%i, count:%i", first, count); +} + +void PACKSPU_APIENTRY packspu_UnlockArraysEXT() +{ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + if (clientState->array.locked && clientState->array.synced) + { + crPackUnlockArraysEXT(); + } + + crStateUnlockArraysEXT(); +} diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_config.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_config.c new file mode 100644 index 00000000..70d6f01c --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_config.c @@ -0,0 +1,60 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "packspu.h" +#include "cr_string.h" +#include "cr_error.h" +#include "cr_spu.h" +#include "cr_mem.h" + +#include <stdio.h> + +static void __setDefaults( void ) +{ + crMemZero(pack_spu.context, CR_MAX_CONTEXTS * sizeof(ContextInfo)); + pack_spu.numContexts = 0; + + crMemZero(pack_spu.thread, MAX_THREADS * sizeof(ThreadInfo)); + pack_spu.numThreads = 0; +} + + +static void set_emit( void *foo, const char *response ) +{ + RT_NOREF(foo); + sscanf( response, "%d", &(pack_spu.emit_GATHER_POST_SWAPBUFFERS) ); +} + +static void set_swapbuffer_sync( void *foo, const char *response ) +{ + RT_NOREF(foo); + sscanf( response, "%d", &(pack_spu.swapbuffer_sync) ); +} + + + +/* No SPU options yet. Well.. not really.. + */ +SPUOptions packSPUOptions[] = { + { "emit_GATHER_POST_SWAPBUFFERS", CR_BOOL, 1, "0", NULL, NULL, + "Emit a parameter after SwapBuffers", (SPUOptionCB)set_emit }, + + { "swapbuffer_sync", CR_BOOL, 1, "1", NULL, NULL, + "Sync on SwapBuffers", (SPUOptionCB) set_swapbuffer_sync }, + + { NULL, CR_BOOL, 0, NULL, NULL, NULL, NULL, NULL }, +}; + + +void packspuSetVBoxConfiguration( const SPU *child_spu ) +{ + RT_NOREF(child_spu); + __setDefaults(); + pack_spu.emit_GATHER_POST_SWAPBUFFERS = 0; + pack_spu.swapbuffer_sync = 0; + pack_spu.name = crStrdup("vboxhgcm://llp:7000"); + pack_spu.buffer_size = 5 * 1024 * 1024; +} diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_context.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_context.c new file mode 100644 index 00000000..26e81dbd --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_context.c @@ -0,0 +1,617 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "packspu.h" +#include "cr_mem.h" +#include "cr_packfunctions.h" +#include "cr_string.h" +#include "packspu_proto.h" + +/* + * Allocate a new ThreadInfo structure, setup a connection to the + * server, allocate/init a packer context, bind this ThreadInfo to + * the calling thread with crSetTSD(). + * We'll always call this function at least once even if we're not + * using threads. + */ +ThreadInfo *packspuNewThread( +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + struct VBOXUHGSMI *pHgsmi +#else + void +#endif +) +{ + ThreadInfo *thread=NULL; + int i; + +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&_PackMutex); +#else + CRASSERT(pack_spu.numThreads == 0); +#endif + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + CRASSERT(!CRPACKSPU_IS_WDDM_CRHGSMI() == !pHgsmi); +#endif + + CRASSERT(pack_spu.numThreads < MAX_THREADS); + for (i=0; i<MAX_THREADS; ++i) + { + if (!pack_spu.thread[i].inUse) + { + thread = &pack_spu.thread[i]; + break; + } + } + CRASSERT(thread); + + thread->inUse = GL_TRUE; + if (!CRPACKSPU_IS_WDDM_CRHGSMI()) + thread->id = crThreadID(); + else + thread->id = THREAD_OFFSET_MAGIC + i; + thread->currentContext = NULL; + thread->bInjectThread = GL_FALSE; + + /* connect to the server */ + thread->netServer.name = crStrdup( pack_spu.name ); + thread->netServer.buffer_size = pack_spu.buffer_size; + packspuConnectToServer( &(thread->netServer) +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , pHgsmi +#endif + ); + CRASSERT(thread->netServer.conn); + /* packer setup */ + CRASSERT(thread->packer == NULL); + thread->packer = crPackNewContext( pack_spu.swap ); + CRASSERT(thread->packer); + crPackInitBuffer( &(thread->buffer), crNetAlloc(thread->netServer.conn), + thread->netServer.conn->buffer_size, thread->netServer.conn->mtu ); + thread->buffer.canBarf = thread->netServer.conn->Barf ? GL_TRUE : GL_FALSE; + crPackSetBuffer( thread->packer, &thread->buffer ); + crPackFlushFunc( thread->packer, packspuFlush ); + crPackFlushArg( thread->packer, (void *) thread ); + crPackSendHugeFunc( thread->packer, packspuHuge ); + + if (!CRPACKSPU_IS_WDDM_CRHGSMI()) + { + crPackSetContext( thread->packer ); + } + + +#ifdef CHROMIUM_THREADSAFE + if (!CRPACKSPU_IS_WDDM_CRHGSMI()) + { + crSetTSD(&_PackTSD, thread); + } +#endif + + pack_spu.numThreads++; + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&_PackMutex); +#endif + return thread; +} + +GLint PACKSPU_APIENTRY +packspu_VBoxConCreate(struct VBOXUHGSMI *pHgsmi) +{ +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + ThreadInfo * thread; + CRASSERT(CRPACKSPU_IS_WDDM_CRHGSMI()); + CRASSERT(pHgsmi); + + thread = packspuNewThread(pHgsmi); + + if (thread) + { + CRASSERT(thread->id); + CRASSERT(thread->id - THREAD_OFFSET_MAGIC < RT_ELEMENTS(pack_spu.thread) + && GET_THREAD_VAL_ID(thread->id) == thread); + return thread->id; + } + crError("packspuNewThread failed"); +#else + RT_NOREF(pHgsmi); +#endif + return 0; +} + +void PACKSPU_APIENTRY +packspu_VBoxConFlush(GLint con) +{ +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + GET_THREAD_ID(thread, con); + CRASSERT(con); + CRASSERT(CRPACKSPU_IS_WDDM_CRHGSMI()); + CRASSERT(thread->packer); + packspuFlush((void *) thread); +#else + RT_NOREF(con); + crError("VBoxConFlush not implemented!"); +#endif +} + +void PACKSPU_APIENTRY +packspu_VBoxConDestroy(GLint con) +{ +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + GET_THREAD_ID(thread, con); + CRASSERT(con); + CRASSERT(CRPACKSPU_IS_WDDM_CRHGSMI()); + CRASSERT(pack_spu.numThreads>0); + CRASSERT(thread->packer); + packspuFlush((void *) thread); + + crLockMutex(&_PackMutex); + + crPackDeleteContext(thread->packer); + + if (thread->buffer.pack) + { + crNetFree(thread->netServer.conn, thread->buffer.pack); + thread->buffer.pack = NULL; + } + + crNetFreeConnection(thread->netServer.conn); + + if (thread->netServer.name) + crFree(thread->netServer.name); + + pack_spu.numThreads--; + /*note can't shift the array here, because other threads have TLS references to array elements*/ + crMemZero(thread, sizeof(ThreadInfo)); + +#if 0 + if (&pack_spu.thread[pack_spu.idxThreadInUse]==thread) + { + int i; + crError("Should not be here since idxThreadInUse should be always 0 for the dummy connection created in packSPUInit!"); + for (i=0; i<MAX_THREADS; ++i) + { + if (pack_spu.thread[i].inUse) + { + pack_spu.idxThreadInUse=i; + break; + } + } + } +#endif + crUnlockMutex(&_PackMutex); +#else + RT_NOREF(con); +#endif +} + +GLvoid PACKSPU_APIENTRY +packspu_VBoxConChromiumParameteriCR(GLint con, GLenum param, GLint value) +{ + GET_THREAD(thread); + CRPackContext * curPacker = crPackGetContext(); + ThreadInfo *curThread = thread; + + CRASSERT(!curThread == !curPacker); + CRASSERT(!curThread || !curPacker || curThread->packer == curPacker); +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&_PackMutex); +#endif + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + CRASSERT(!con == !CRPACKSPU_IS_WDDM_CRHGSMI()); +#endif + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + if (!con) + { + crError("connection should be specified!"); + return; + } + thread = GET_THREAD_VAL_ID(con); + } + else + { + CRASSERT(!con); + if (!thread) + { + thread = packspuNewThread( +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + NULL +#endif + ); + } + } + CRASSERT(thread); + CRASSERT(thread->packer); + + crPackSetContext( thread->packer ); + + packspu_ChromiumParameteriCR(param, value); + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&_PackMutex); +#endif + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + /* restore the packer context to the tls */ + crPackSetContext(curPacker); + } +} + +GLvoid PACKSPU_APIENTRY +packspu_VBoxConChromiumParametervCR(GLint con, GLenum target, GLenum type, GLsizei count, const GLvoid *values) +{ + GET_THREAD(thread); + CRPackContext * curPacker = crPackGetContext(); + ThreadInfo *curThread = thread; + + CRASSERT(!curThread == !curPacker); + CRASSERT(!curThread || !curPacker || curThread->packer == curPacker); +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&_PackMutex); +#endif + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + CRASSERT(!con == !CRPACKSPU_IS_WDDM_CRHGSMI()); +#endif + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + if (!con) + { + crError("connection should be specified!"); + return; + } + thread = GET_THREAD_VAL_ID(con); + } + else + { + CRASSERT(!con); + if (!thread) + { + thread = packspuNewThread( +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + NULL +#endif + ); + } + } + CRASSERT(thread); + CRASSERT(thread->packer); + + crPackSetContext( thread->packer ); + + packspu_ChromiumParametervCR(target, type, count, values); + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&_PackMutex); +#endif + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + /* restore the packer context to the tls */ + crPackSetContext(curPacker); + } +} + +GLint PACKSPU_APIENTRY +packspu_VBoxCreateContext( GLint con, const char *dpyName, GLint visual, GLint shareCtx ) +{ + GET_THREAD(thread); + CRPackContext * curPacker = crPackGetContext(); + ThreadInfo *curThread = thread; + int writeback = 1; + GLint serverCtx = (GLint) -1; + int slot; + + CRASSERT(!curThread == !curPacker); + CRASSERT(!curThread || !curPacker || curThread->packer == curPacker); +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&_PackMutex); +#endif + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + CRASSERT(!con == !CRPACKSPU_IS_WDDM_CRHGSMI()); +#endif + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + if (!con) + { + crError("connection should be specified!"); + return -1; + } + thread = GET_THREAD_VAL_ID(con); + } + else + { + CRASSERT(!con); + if (!thread) + { + thread = packspuNewThread( +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + NULL +#endif + ); + } + } + CRASSERT(thread); + CRASSERT(thread->packer); + + if (shareCtx > 0) { + /* translate to server ctx id */ + shareCtx -= MAGIC_OFFSET; + if (shareCtx >= 0 && shareCtx < pack_spu.numContexts) { + shareCtx = pack_spu.context[shareCtx].serverCtx; + } + } + + crPackSetContext( thread->packer ); + + /* Pack the command */ + if (pack_spu.swap) + crPackCreateContextSWAP( dpyName, visual, shareCtx, &serverCtx, &writeback ); + else + crPackCreateContext( dpyName, visual, shareCtx, &serverCtx, &writeback ); + + /* Flush buffer and get return value */ + packspuFlush(thread); + if (!(thread->netServer.conn->actual_network)) + { + /* HUMUNGOUS HACK TO MATCH SERVER NUMBERING + * + * The hack exists solely to make file networking work for now. This + * is totally gross, but since the server expects the numbers to start + * from 5000, we need to write them out this way. This would be + * marginally less gross if the numbers (500 and 5000) were maybe + * some sort of #define'd constants somewhere so the client and the + * server could be aware of how each other were numbering things in + * cases like file networking where they actually + * care. + * + * -Humper + * + */ + serverCtx = 5000; + } + else { + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + if (pack_spu.swap) { + serverCtx = (GLint) SWAP32(serverCtx); + } + if (serverCtx < 0) { +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&_PackMutex); +#endif + crWarning("Failure in packspu_CreateContext"); + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + /* restore the packer context to the tls */ + crPackSetContext(curPacker); + } + return -1; /* failed */ + } + } + + /* find an empty context slot */ + for (slot = 0; slot < pack_spu.numContexts; slot++) { + if (!pack_spu.context[slot].clientState) { + /* found empty slot */ + break; + } + } + if (slot == pack_spu.numContexts) { + pack_spu.numContexts++; + } + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + thread->currentContext = &pack_spu.context[slot]; + pack_spu.context[slot].currentThread = thread; + } + + /* Fill in the new context info */ + /* XXX fix-up sharedCtx param here */ + pack_spu.context[slot].clientState = crStateCreateContext(NULL, visual, NULL); + pack_spu.context[slot].clientState->bufferobject.retainBufferData = GL_TRUE; + pack_spu.context[slot].serverCtx = serverCtx; + +#ifdef CHROMIUM_THREADSAFE + crUnlockMutex(&_PackMutex); +#endif + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + /* restore the packer context to the tls */ + crPackSetContext(curPacker); + } + + return MAGIC_OFFSET + slot; +} + +GLint PACKSPU_APIENTRY +packspu_CreateContext( const char *dpyName, GLint visual, GLint shareCtx ) +{ + return packspu_VBoxCreateContext( 0, dpyName, visual, shareCtx ); +} + + +void PACKSPU_APIENTRY packspu_DestroyContext( GLint ctx ) +{ + GET_THREAD(thread); + ThreadInfo *curThread = thread; + const int slot = ctx - MAGIC_OFFSET; + ContextInfo *context, *curContext; + CRPackContext * curPacker = crPackGetContext(); + + CRASSERT(slot >= 0); + CRASSERT(slot < pack_spu.numContexts); + + context = (slot >= 0 && slot < pack_spu.numContexts) ? &(pack_spu.context[slot]) : NULL; + curContext = curThread ? curThread->currentContext : NULL; + + if (context) + { + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + thread = context->currentThread; + if (thread) + { + crPackSetContext(thread->packer); + CRASSERT(!(thread->packer == curPacker) == !(thread == curThread)); + } + } + + if (pack_spu.swap) + crPackDestroyContextSWAP( context->serverCtx ); + else + crPackDestroyContext( context->serverCtx ); + + crStateDestroyContext( context->clientState ); + + context->clientState = NULL; + context->serverCtx = 0; + context->currentThread = NULL; + + crMemset (&context->zvaBufferInfo, 0, sizeof (context->zvaBufferInfo)); + } + + if (curContext == context) + { + if (!CRPACKSPU_IS_WDDM_CRHGSMI()) + { + curThread->currentContext = NULL; + } + else + { + CRASSERT(thread == curThread); + crSetTSD(&_PackTSD, NULL); + crPackSetContext(NULL); + } + crStateMakeCurrent( NULL ); + } + else + { + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + crPackSetContext(curPacker); + } + } +} + +void PACKSPU_APIENTRY packspu_MakeCurrent( GLint window, GLint nativeWindow, GLint ctx ) +{ + ThreadInfo *thread = NULL; + GLint serverCtx; + ContextInfo *newCtx = NULL; + + if (!CRPACKSPU_IS_WDDM_CRHGSMI()) + { + thread = GET_THREAD_VAL(); + if (!thread) { + thread = packspuNewThread( +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + NULL +#endif + ); + } + CRASSERT(thread); + CRASSERT(thread->packer); + } + + if (ctx) { + const int slot = ctx - MAGIC_OFFSET; + + CRASSERT(slot >= 0); + CRASSERT(slot < pack_spu.numContexts); + + newCtx = &pack_spu.context[slot]; + CRASSERT(newCtx); + CRASSERT(newCtx->clientState); /* verify valid */ + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + thread = newCtx->currentThread; + CRASSERT(thread); + crSetTSD(&_PackTSD, thread); + crPackSetContext( thread->packer ); + } + else + { + CRASSERT(thread); + if (newCtx->fAutoFlush) + { + if (newCtx->currentThread && newCtx->currentThread != thread) + { + crLockMutex(&_PackMutex); + /* do a flush for the previously assigned thread + * to ensure all commands issued there are submitted */ + if (newCtx->currentThread + && newCtx->currentThread->inUse + && newCtx->currentThread->netServer.conn + && newCtx->currentThread->packer && newCtx->currentThread->packer->currentBuffer) + { + packspuFlush((void *) newCtx->currentThread); + } + crUnlockMutex(&_PackMutex); + } + newCtx->currentThread = thread; + } + + if (thread->currentContext && newCtx != thread->currentContext && thread->currentContext->fCheckZerroVertAttr) + crStateCurrentRecoverNew(thread->currentContext->clientState, &thread->packer->current); + + thread->currentContext = newCtx; + crPackSetContext( thread->packer ); + } + + crStateMakeCurrent( newCtx->clientState ); + //crStateSetCurrentPointers(newCtx->clientState, &thread->packer->current); + serverCtx = pack_spu.context[slot].serverCtx; + } + else { + crStateMakeCurrent( NULL ); + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + thread = GET_THREAD_VAL(); + if (!thread) + { + CRASSERT(crPackGetContext() == NULL); + return; + } + CRASSERT(thread->currentContext); + CRASSERT(thread->packer == crPackGetContext()); + } + else + { + thread->currentContext = NULL; + } + newCtx = NULL; + serverCtx = 0; + } + + if (pack_spu.swap) + crPackMakeCurrentSWAP( window, nativeWindow, serverCtx ); + else + crPackMakeCurrent( window, nativeWindow, serverCtx ); + + if (serverCtx) + { + packspuInitStrings(); + } + + { + GET_THREAD(t); + (void) t; + CRASSERT(t); + } +} diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_flush.py b/src/VBox/Additions/common/crOpenGL/pack/packspu_flush.py new file mode 100755 index 00000000..5742bcb0 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_flush.py @@ -0,0 +1,42 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - this file generated by packspu_flush.py script */ + +/* These are otherwise ordinary functions which require that the buffer be + * flushed immediately after packing the function. + */ +#include "cr_glstate.h" +#include "cr_packfunctions.h" +#include "packspu.h" +#include "packspu_proto.h" +""") + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in apiutil.AllSpecials( "packspu_flush" ): + params = apiutil.Parameters(func_name) + print('void PACKSPU_APIENTRY packspu_%s(%s)' % ( func_name, apiutil.MakeDeclarationString(params))) + print('{') + print('\tGET_THREAD(thread);') + print('\tif (pack_spu.swap)') + print('\t{') + print('\t\tcrPack%sSWAP(%s);' % ( func_name, apiutil.MakeCallString( params ) )) + print('\t}') + print('\telse') + print('\t{') + print('\t\tcrPack%s(%s);' % ( func_name, apiutil.MakeCallString( params ) )) + print('\t}') + print('\tpackspuFlush( (void *) thread );') + print('}\n') diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_flush_special b/src/VBox/Additions/common/crOpenGL/pack/packspu_flush_special new file mode 100644 index 00000000..a4896826 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_flush_special @@ -0,0 +1,9 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. +BarrierCreateCR +BarrierExecCR +SemaphoreCreateCR +SemaphorePCR +SemaphoreVCR diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_framebuffer.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_framebuffer.c new file mode 100644 index 00000000..e94c0444 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_framebuffer.c @@ -0,0 +1,143 @@ +/* $Id: packspu_framebuffer.c $ */ + +/** @file + * VBox OpenGL FBO related functions + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "packspu.h" +#include "cr_packfunctions.h" +#include "cr_net.h" +#include "packspu_proto.h" + +void PACKSPU_APIENTRY +packspu_FramebufferTexture1DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) +{ + crStateFramebufferTexture1DEXT(target, attachment, textarget, texture, level); + crPackFramebufferTexture1DEXT(target, attachment, textarget, texture, level); +} + +void PACKSPU_APIENTRY +packspu_FramebufferTexture2DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) +{ + crStateFramebufferTexture2DEXT(target, attachment, textarget, texture, level); + crPackFramebufferTexture2DEXT(target, attachment, textarget, texture, level); +} + +void PACKSPU_APIENTRY +packspu_FramebufferTexture3DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) +{ + crStateFramebufferTexture3DEXT(target, attachment, textarget, texture, level, zoffset); + crPackFramebufferTexture3DEXT(target, attachment, textarget, texture, level, zoffset); +} + +void PACKSPU_APIENTRY +packspu_BindFramebufferEXT(GLenum target, GLuint framebuffer) +{ + crStateBindFramebufferEXT(target, framebuffer); + crPackBindFramebufferEXT(target, framebuffer); +} + +void PACKSPU_APIENTRY +packspu_DeleteFramebuffersEXT(GLsizei n, const GLuint * framebuffers) +{ + crStateDeleteFramebuffersEXT(n, framebuffers); + crPackDeleteFramebuffersEXT(n, framebuffers); +} + +void PACKSPU_APIENTRY +packspu_DeleteRenderbuffersEXT(GLsizei n, const GLuint * renderbuffers) +{ + crStateDeleteRenderbuffersEXT(n, renderbuffers); + crPackDeleteRenderbuffersEXT(n, renderbuffers); +} + +void PACKSPU_APIENTRY +packspu_FramebufferRenderbufferEXT(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) +{ + crStateFramebufferRenderbufferEXT(target, attachment, renderbuffertarget, renderbuffer); + crPackFramebufferRenderbufferEXT(target, attachment, renderbuffertarget, renderbuffer); +} + +void PACKSPU_APIENTRY +packspu_BindRenderbufferEXT(GLenum target, GLuint renderbuffer) +{ + crStateBindRenderbufferEXT(target, renderbuffer); + crPackBindRenderbufferEXT(target, renderbuffer); +} + +GLenum PACKSPU_APIENTRY +packspu_CheckFramebufferStatusEXT(GLenum target) +{ + GET_THREAD(thread); + int writeback = 1; + GLenum status = crStateCheckFramebufferStatusEXT(target); + + if (status!=GL_FRAMEBUFFER_UNDEFINED) + { + return status; + } + + crPackCheckFramebufferStatusEXT(target, &status, &writeback); + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + crStateSetFramebufferStatus(target, status); + return status; +} + +void PACKSPU_APIENTRY packspu_GenFramebuffersEXT( GLsizei n, GLuint * framebuffers ) +{ + GET_THREAD(thread); + int writeback = 1; + if (!CRPACKSPU_IS_WDDM_CRHGSMI() && !(pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network)) + { + crError( "packspu_GenFramebuffersEXT doesn't work when there's no actual network involved!\nTry using the simplequery SPU in your chain!" ); + } + if (pack_spu.swap) + { + crPackGenFramebuffersEXTSWAP( n, framebuffers, &writeback ); + } + else + { + crPackGenFramebuffersEXT( n, framebuffers, &writeback ); + } + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + crStateRegFramebuffers(n, framebuffers); +} + +void PACKSPU_APIENTRY packspu_GenRenderbuffersEXT( GLsizei n, GLuint * renderbuffers ) +{ + GET_THREAD(thread); + int writeback = 1; + if (!CRPACKSPU_IS_WDDM_CRHGSMI() && !(pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network)) + { + crError( "packspu_GenRenderbuffersEXT doesn't work when there's no actual network involved!\nTry using the simplequery SPU in your chain!" ); + } + if (pack_spu.swap) + { + crPackGenRenderbuffersEXTSWAP( n, renderbuffers, &writeback ); + } + else + { + crPackGenRenderbuffersEXT( n, renderbuffers, &writeback ); + } + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + crStateRegRenderbuffers(n, renderbuffers); +} diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_get.py b/src/VBox/Additions/common/crOpenGL/pack/packspu_get.py new file mode 100755 index 00000000..371abeaa --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_get.py @@ -0,0 +1,250 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - THIS FILE AUTOMATICALLY GENERATED BY packspu_get.py SCRIPT */ +#include "packspu.h" +#include "cr_packfunctions.h" +#include "cr_net.h" +#include "cr_mem.h" +#include "packspu_proto.h" +""") + +print(""" +static GLboolean crPackIsPixelStoreParm(GLenum pname) +{ + if (pname == GL_UNPACK_ALIGNMENT + || pname == GL_UNPACK_ROW_LENGTH + || pname == GL_UNPACK_SKIP_PIXELS + || pname == GL_UNPACK_LSB_FIRST + || pname == GL_UNPACK_SWAP_BYTES +#ifdef CR_OPENGL_VERSION_1_2 + || pname == GL_UNPACK_IMAGE_HEIGHT +#endif + || pname == GL_UNPACK_SKIP_ROWS + || pname == GL_PACK_ALIGNMENT + || pname == GL_PACK_ROW_LENGTH + || pname == GL_PACK_SKIP_PIXELS + || pname == GL_PACK_LSB_FIRST + || pname == GL_PACK_SWAP_BYTES +#ifdef CR_OPENGL_VERSION_1_2 + || pname == GL_PACK_IMAGE_HEIGHT +#endif + || pname == GL_PACK_SKIP_ROWS) + { + return GL_TRUE; + } + return GL_FALSE; +} +""") + +from get_sizes import * + +easy_swaps = { + 'GenTextures': '(unsigned int) n', + 'GetClipPlane': '4', + 'GetPolygonStipple': '0' +} + +simple_funcs = [ 'GetIntegerv', 'GetFloatv', 'GetDoublev', 'GetBooleanv' ] +simple_swaps = [ 'SWAP32', 'SWAPFLOAT', 'SWAPDOUBLE', '(GLboolean) SWAP32' ] + +vertattr_get_funcs = [ 'GetVertexAttribdv' 'GetVertexAttribfv' 'GetVertexAttribiv' ] + +hard_funcs = { + 'GetLightfv': 'SWAPFLOAT', + 'GetLightiv': 'SWAP32', + 'GetMaterialfv': 'SWAPFLOAT', + 'GetMaterialiv': 'SWAP32', + 'GetTexEnvfv': 'SWAPFLOAT', + 'GetTexEnviv': 'SWAP32', + 'GetTexGendv': 'SWAPDOUBLE', + 'GetTexGenfv': 'SWAPFLOAT', + 'GetTexGeniv': 'SWAP32', + 'GetTexLevelParameterfv': 'SWAPFLOAT', + 'GetTexLevelParameteriv': 'SWAP32', + 'GetTexParameterfv': 'SWAPFLOAT', + 'GetTexParameteriv': 'SWAP32' } + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in keys: + params = apiutil.Parameters(func_name) + return_type = apiutil.ReturnType(func_name) + if apiutil.FindSpecial( "packspu", func_name ): + continue + + if "get" in apiutil.Properties(func_name): + print('%s PACKSPU_APIENTRY packspu_%s(%s)' % ( return_type, func_name, apiutil.MakeDeclarationString( params ) )) + print('{') + print('\tGET_THREAD(thread);') + print('\tint writeback = 1;') + if return_type != 'void': + print('\t%s return_val = (%s) 0;' % (return_type, return_type)) + params.append( ("&return_val", "foo", 0) ) + if (func_name in easy_swaps and easy_swaps[func_name] != '0') or func_name in simple_funcs or func_name in hard_funcs: + print('\tunsigned int i;') + print('\tif (!CRPACKSPU_IS_WDDM_CRHGSMI() && !(pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network))') + print('\t{') + print('\t\tcrError( "packspu_%s doesn\'t work when there\'s no actual network involved!\\nTry using the simplequery SPU in your chain!" );' % func_name) + print('\t}') + if func_name in simple_funcs: + print(""" + if (crPackIsPixelStoreParm(pname) + || pname == GL_DRAW_BUFFER +#ifdef CR_OPENGL_VERSION_1_3 + || pname == GL_ACTIVE_TEXTURE +#endif +#ifdef CR_ARB_multitexture + || pname == GL_ACTIVE_TEXTURE_ARB +#endif + || pname == GL_TEXTURE_BINDING_1D + || pname == GL_TEXTURE_BINDING_2D +#ifdef CR_NV_texture_rectangle + || pname == GL_TEXTURE_BINDING_RECTANGLE_NV +#endif +#ifdef CR_ARB_texture_cube_map + || pname == GL_TEXTURE_BINDING_CUBE_MAP_ARB +#endif +#ifdef CR_ARB_vertex_program + || pname == GL_MAX_VERTEX_ATTRIBS_ARB +#endif +#ifdef GL_EXT_framebuffer_object + || pname == GL_FRAMEBUFFER_BINDING_EXT + || pname == GL_READ_FRAMEBUFFER_BINDING_EXT + || pname == GL_DRAW_FRAMEBUFFER_BINDING_EXT +#endif + || pname == GL_ARRAY_BUFFER_BINDING + || pname == GL_ELEMENT_ARRAY_BUFFER_BINDING + || pname == GL_PIXEL_PACK_BUFFER_BINDING + || pname == GL_PIXEL_UNPACK_BUFFER_BINDING + ) + { +#ifdef DEBUG + if (!crPackIsPixelStoreParm(pname) +#ifdef CR_ARB_vertex_program + && (pname!=GL_MAX_VERTEX_ATTRIBS_ARB) +#endif + ) + { + %s localparams; + localparams = (%s) crAlloc(__numValues(pname) * sizeof(*localparams)); + crState%s(pname, localparams); + crPack%s(%s, &writeback); + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + for (i=0; i<__numValues(pname); ++i) + { + if (localparams[i] != params[i]) + { + crWarning("Incorrect local state in %s for %%x param %%i", pname, i); + crWarning("Expected %%i but got %%i", (int)localparams[i], (int)params[i]); + } + } + crFree(localparams); + return; + } + else +#endif + { + crState%s(pname, params); + return; + } + + } + """ % (params[-1][1], params[-1][1], func_name, func_name, apiutil.MakeCallString(params), func_name, func_name)) + + if func_name in vertattr_get_funcs: + print(""" + if (pname != GL_CURRENT_VERTEX_ATTRIB_ARB) + { +#ifdef DEBUG + %s localparams; + localparams = (%s) crAlloc(__numValues(pname) * sizeof(*localparams)); + crState%s(index, pname, localparams); + crPack%s(index, %s, &writeback); + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + for (i=0; i<crStateHlpComponentsCount(pname); ++i) + { + if (localparams[i] != params[i]) + { + crWarning("Incorrect local state in %s for %%x param %%i", pname, i); + crWarning("Expected %%i but got %%i", (int)localparams[i], (int)params[i]); + } + } + crFree(localparams); +#else + crState%s(pname, params); +#endif + return; + } + """ % (params[-1][1], params[-1][1], func_name, func_name, apiutil.MakeCallString(params), func_name, func_name)) + + params.append( ("&writeback", "foo", 0) ) + print('\tif (pack_spu.swap)') + print('\t{') + print('\t\tcrPack%sSWAP(%s);' % (func_name, apiutil.MakeCallString( params ) )) + print('\t}') + print('\telse') + print('\t{') + print('\t\tcrPack%s(%s);' % (func_name, apiutil.MakeCallString( params ) )) + print('\t}') + print('\tpackspuFlush( (void *) thread );') + print('\tCRPACKSPU_WRITEBACK_WAIT(thread, writeback);') + + + + lastParamName = params[-2][0] + if return_type != 'void': + print('\tif (pack_spu.swap)') + print('\t{') + print('\t\treturn_val = (%s) SWAP32(return_val);' % return_type) + print('\t}') + print('\treturn return_val;') + if func_name in easy_swaps and easy_swaps[func_name] != '0': + limit = easy_swaps[func_name] + print('\tif (pack_spu.swap)') + print('\t{') + print('\t\tfor (i = 0; i < %s; i++)' % limit) + print('\t\t{') + if params[-2][1].find( "double" ) > -1: + print('\t\t\t%s[i] = SWAPDOUBLE(%s[i]);' % (lastParamName, lastParamName)) + else: + print('\t\t\t%s[i] = SWAP32(%s[i]);' % (lastParamName, lastParamName)) + print('\t\t}') + print('\t}') + for index in range(len(simple_funcs)): + if simple_funcs[index] == func_name: + print('\tif (pack_spu.swap)') + print('\t{') + print('\t\tfor (i = 0; i < __numValues(pname); i++)') + print('\t\t{') + if simple_swaps[index] == 'SWAPDOUBLE': + print('\t\t\t%s[i] = %s(%s[i]);' % (lastParamName, simple_swaps[index], lastParamName)) + else: + print('\t\t\t((GLuint *) %s)[i] = %s(%s[i]);' % (lastParamName, simple_swaps[index], lastParamName)) + print('\t\t}') + print('\t}') + if func_name in hard_funcs: + print('\tif (pack_spu.swap)') + print('\t{') + print('\t\tfor (i = 0; i < crStateHlpComponentsCount(pname); i++)') + print('\t\t{') + if hard_funcs[func_name] == 'SWAPDOUBLE': + print('\t\t\t%s[i] = %s(%s[i]);' % (lastParamName, hard_funcs[func_name], lastParamName)) + else: + print('\t\t\t((GLuint *) %s)[i] = %s(%s[i]);' % (lastParamName, hard_funcs[func_name], lastParamName)) + print('\t\t}') + print('\t}') + print('}\n') diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_getshaders.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_getshaders.c new file mode 100644 index 00000000..97150222 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_getshaders.c @@ -0,0 +1,214 @@ +/* $Id: packspu_getshaders.c $ */ + +/** @file + * VBox OpenGL GLSL related functions + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "packspu.h" +#include "cr_packfunctions.h" +#include "cr_net.h" +#include "packspu_proto.h" +#include "cr_mem.h" +#include <iprt/assert.h> + +/** @todo combine with the one from server_getshaders.c*/ +typedef struct _crGetActive_t +{ + GLsizei length; + GLint size; + GLenum type; +} crGetActive_t; + +void PACKSPU_APIENTRY packspu_GetActiveAttrib(GLuint program, GLuint index, GLsizei bufSize, GLsizei * length, GLint * size, GLenum * type, char * name) +{ + GET_THREAD(thread); + int writeback = 1; + crGetActive_t *pLocal; + + if (!size || !type || !name) return; + + pLocal = (crGetActive_t*) crAlloc(bufSize+sizeof(crGetActive_t)); + if (!pLocal) return; + + crPackGetActiveAttrib(program, index, bufSize, (GLsizei*)pLocal, NULL, NULL, NULL, &writeback); + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + if (length) *length = pLocal->length; + *size = pLocal->size; + *type = pLocal->type; + crMemcpy(name, (char*)&pLocal[1], pLocal->length+1); + crFree(pLocal); +} + +void PACKSPU_APIENTRY packspu_GetActiveUniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei * length, GLint * size, GLenum * type, char * name) +{ + GET_THREAD(thread); + int writeback = 1; + crGetActive_t *pLocal; + + if (!size || !type || !name) return; + + pLocal = (crGetActive_t*) crAlloc(bufSize+sizeof(crGetActive_t)); + if (!pLocal) return; + + crPackGetActiveUniform(program, index, bufSize, (GLsizei*)pLocal, NULL, NULL, NULL, &writeback); + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + if (length) *length = pLocal->length; + *size = pLocal->size; + *type = pLocal->type; + crMemcpy(name, &pLocal[1], pLocal->length+1); + crFree(pLocal); +} + +void PACKSPU_APIENTRY packspu_GetAttachedShaders(GLuint program, GLsizei maxCount, GLsizei * count, GLuint * shaders) +{ + GET_THREAD(thread); + int writeback = 1; + GLsizei *pLocal; + + if (!shaders) return; + + pLocal = (GLsizei*) crAlloc(maxCount*sizeof(GLuint)+sizeof(GLsizei)); + if (!pLocal) return; + + crPackGetAttachedShaders(program, maxCount, pLocal, NULL, &writeback); + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + if (count) *count=*pLocal; + crMemcpy(shaders, &pLocal[1], *pLocal*sizeof(GLuint)); + crFree(pLocal); +} + +void PACKSPU_APIENTRY packspu_GetAttachedObjectsARB(VBoxGLhandleARB containerObj, GLsizei maxCount, GLsizei * count, VBoxGLhandleARB * obj) +{ + GET_THREAD(thread); + int writeback = 1; + GLsizei *pLocal; + + if (!obj) return; + + pLocal = (GLsizei*) crAlloc(maxCount*sizeof(VBoxGLhandleARB)+sizeof(GLsizei)); + if (!pLocal) return; + + crPackGetAttachedObjectsARB(containerObj, maxCount, pLocal, NULL, &writeback); + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + if (count) *count=*pLocal; + crMemcpy(obj, &pLocal[1], *pLocal*sizeof(VBoxGLhandleARB)); + crFree(pLocal); +} + +AssertCompile(sizeof(GLsizei) == 4); + +void PACKSPU_APIENTRY packspu_GetInfoLogARB(VBoxGLhandleARB obj, GLsizei maxLength, GLsizei * length, GLcharARB * infoLog) +{ + GET_THREAD(thread); + int writeback = 1; + GLsizei *pLocal; + + if (!infoLog) return; + + pLocal = (GLsizei*) crAlloc(maxLength+sizeof(GLsizei)); + if (!pLocal) return; + + crPackGetInfoLogARB(obj, maxLength, pLocal, NULL, &writeback); + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + CRASSERT((pLocal[0]) <= maxLength); + + if (length) *length=*pLocal; + crMemcpy(infoLog, &pLocal[1], (maxLength >= (pLocal[0])) ? pLocal[0] : maxLength); + crFree(pLocal); +} + +void PACKSPU_APIENTRY packspu_GetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei * length, char * infoLog) +{ + GET_THREAD(thread); + int writeback = 1; + GLsizei *pLocal; + + if (!infoLog) return; + + pLocal = (GLsizei*) crAlloc(bufSize+sizeof(GLsizei)); + if (!pLocal) return; + + crPackGetProgramInfoLog(program, bufSize, pLocal, NULL, &writeback); + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + if (length) *length=*pLocal; + crMemcpy(infoLog, &pLocal[1], (bufSize >= pLocal[0]) ? pLocal[0] : bufSize); + crFree(pLocal); +} + +void PACKSPU_APIENTRY packspu_GetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei * length, char * infoLog) +{ + GET_THREAD(thread); + int writeback = 1; + GLsizei *pLocal; + + if (!infoLog) return; + + pLocal = (GLsizei*) crAlloc(bufSize+sizeof(GLsizei)); + if (!pLocal) return; + + crPackGetShaderInfoLog(shader, bufSize, pLocal, NULL, &writeback); + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + if (length) *length=*pLocal; + crMemcpy(infoLog, &pLocal[1], (bufSize >= pLocal[0]) ? pLocal[0] : bufSize); + crFree(pLocal); +} + +void PACKSPU_APIENTRY packspu_GetShaderSource(GLuint shader, GLsizei bufSize, GLsizei * length, char * source) +{ + GET_THREAD(thread); + int writeback = 1; + GLsizei *pLocal; + + if (!source) return; + + pLocal = (GLsizei*) crAlloc(bufSize+sizeof(GLsizei)); + if (!pLocal) return; + + crPackGetShaderSource(shader, bufSize, pLocal, NULL, &writeback); + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + if (length) *length=*pLocal; + crMemcpy(source, &pLocal[1], (bufSize >= pLocal[0]) ? pLocal[0] : bufSize); + + if (bufSize > pLocal[0]) + { + source[pLocal[0]] = 0; + } + + crFree(pLocal); +} diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_getstring.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_getstring.c new file mode 100644 index 00000000..c5890bfe --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_getstring.c @@ -0,0 +1,211 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "packspu.h" +#include "cr_packfunctions.h" +#include "state/cr_statefuncs.h" +#include "cr_string.h" +#include "packspu_proto.h" +#include "cr_mem.h" +#include <locale.h> + +static GLubyte gpszExtensions[10000]; +#ifdef CR_OPENGL_VERSION_2_0 +static GLubyte gpszShadingVersion[255]=""; +#endif + +static void GetString(GLenum name, GLubyte *pszStr) +{ + GET_THREAD(thread); + int writeback = 1; + + if (pack_spu.swap) + crPackGetStringSWAP(name, pszStr, &writeback); + else + crPackGetString(name, pszStr, &writeback); + packspuFlush( (void *) thread ); + + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); +} + +static GLfloat +GetVersionString(void) +{ + static GLboolean fInitialized = GL_FALSE; + static GLfloat version = 0.; + + if (!fInitialized) + { + GLubyte return_value[100]; + + GetString(GL_VERSION, return_value); + CRASSERT(crStrlen((char *)return_value) < 100); + + version = crStrToFloat((char *) return_value); + version = crStateComputeVersion(version); + + fInitialized = GL_TRUE; + } + + return version; +} + +static const GLubyte * +GetExtensions(void) +{ + static GLboolean fInitialized = GL_FALSE; + if (!fInitialized) + { + GLubyte return_value[10*1000]; + const GLubyte *extensions, *ext; + GET_THREAD(thread); + int writeback = 1; + + if (pack_spu.swap) + { + crPackGetStringSWAP( GL_EXTENSIONS, return_value, &writeback ); + } + else + { + crPackGetString( GL_EXTENSIONS, return_value, &writeback ); + } + packspuFlush( (void *) thread ); + + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + CRASSERT(crStrlen((char *)return_value) < 10*1000); + + /* OK, we got the result from the server. Now we have to + * intersect is with the set of extensions that Chromium understands + * and tack on the Chromium-specific extensions. + */ + extensions = return_value; + ext = crStateMergeExtensions(1, &extensions); + +#ifdef Linux + /** @todo + *That's a hack to allow running Unity, it uses libnux which is calling extension functions + *without checking if it's being supported/exported. + *glActiveStencilFaceEXT seems to be actually supported but the extension string isn't exported (for ex. on ATI HD4870), + *which leads to libglew setting function pointer to NULL and crashing Unity. + */ + sprintf((char*)gpszExtensions, "%s GL_EXT_stencil_two_side", ext); +#else + sprintf((char*)gpszExtensions, "%s", ext); +#endif + fInitialized = GL_TRUE; + } + + return gpszExtensions; +} + +#ifdef WINDOWS +static bool packspuRunningUnderWine(void) +{ + return NULL != GetModuleHandle("wined3d.dll") || NULL != GetModuleHandle("wined3dwddm.dll") || NULL != GetModuleHandle("wined3dwddm-x86.dll"); +} +#endif + +const GLubyte * PACKSPU_APIENTRY packspu_GetString( GLenum name ) +{ + GET_CONTEXT(ctx); + + switch(name) + { + case GL_EXTENSIONS: + return GetExtensions(); + case GL_VERSION: +#if 0 && defined(WINDOWS) + if (packspuRunningUnderWine()) + { + GetString(GL_REAL_VERSION, ctx->pszRealVersion); + return ctx->pszRealVersion; + } + else +#endif + { + char *oldlocale; + float version; + + oldlocale = setlocale(LC_NUMERIC, NULL); + oldlocale = crStrdup(oldlocale); + setlocale(LC_NUMERIC, "C"); + + version = GetVersionString(); + sprintf((char*)ctx->glVersion, "%.1f Chromium %s", version, CR_VERSION_STRING); + + if (oldlocale) + { + setlocale(LC_NUMERIC, oldlocale); + crFree(oldlocale); + } + + return ctx->glVersion; + } + case GL_VENDOR: +#ifdef WINDOWS + if (packspuRunningUnderWine()) + { + GetString(GL_REAL_VENDOR, ctx->pszRealVendor); + return ctx->pszRealVendor; + } + else +#endif + { + return crStateGetString(name); + } + case GL_RENDERER: +#ifdef WINDOWS + if (packspuRunningUnderWine()) + { + GetString(GL_REAL_RENDERER, ctx->pszRealRenderer); + return ctx->pszRealRenderer; + } + else +#endif + { + return crStateGetString(name); + } + +#ifdef CR_OPENGL_VERSION_2_0 + case GL_SHADING_LANGUAGE_VERSION: + { + static GLboolean fInitialized = GL_FALSE; + if (!fInitialized) + { + GetString(GL_SHADING_LANGUAGE_VERSION, gpszShadingVersion); + fInitialized = GL_TRUE; + } + return gpszShadingVersion; + } +#endif +#ifdef GL_CR_real_vendor_strings + case GL_REAL_VENDOR: + GetString(GL_REAL_VENDOR, ctx->pszRealVendor); + return ctx->pszRealVendor; + case GL_REAL_VERSION: + GetString(GL_REAL_VERSION, ctx->pszRealVersion); + return ctx->pszRealVersion; + case GL_REAL_RENDERER: + GetString(GL_REAL_RENDERER, ctx->pszRealRenderer); + return ctx->pszRealRenderer; +#endif + default: + return crStateGetString(name); + } +} + +void packspuInitStrings() +{ + static GLboolean fInitialized = GL_FALSE; + + if (!fInitialized) + { + packspu_GetString(GL_EXTENSIONS); + packspu_GetString(GL_VERSION); + fInitialized = GL_TRUE; + } +} diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_glsl.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_glsl.c new file mode 100644 index 00000000..1116b7d5 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_glsl.c @@ -0,0 +1,275 @@ +/* $Id: packspu_glsl.c $ */ + +/** @file + * VBox OpenGL GLSL related functions + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "packspu.h" +#include "cr_packfunctions.h" +#include "cr_net.h" +#include "packspu_proto.h" +#include "cr_mem.h" + + +GLuint PACKSPU_APIENTRY packspu_CreateProgram(void) +{ + GET_THREAD(thread); + int writeback = 1; + GLuint return_val = (GLuint) 0; + if (!CRPACKSPU_IS_WDDM_CRHGSMI() && !(pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network)) + { + crError("packspu_CreateProgram doesn't work when there's no actual network involved!\nTry using the simplequery SPU in your chain!"); + } + if (pack_spu.swap) + { + crPackCreateProgramSWAP(&return_val, &writeback); + } + else + { + crPackCreateProgram(&return_val, &writeback); + } + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + if (pack_spu.swap) + { + return_val = (GLuint) SWAP32(return_val); + } + + crStateCreateProgram(return_val); + + return return_val; +} + +static GLint packspu_GetUniformLocationUncached(GLuint program, const char * name) +{ + GET_THREAD(thread); + int writeback = 1; + GLint return_val = (GLint) 0; + if (!CRPACKSPU_IS_WDDM_CRHGSMI() && !(pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network)) + { + crError("packspu_GetUniformLocation doesn't work when there's no actual network involved!\nTry using the simplequery SPU in your chain!"); + } + if (pack_spu.swap) + { + crPackGetUniformLocationSWAP(program, name, &return_val, &writeback); + } + else + { + crPackGetUniformLocation(program, name, &return_val, &writeback); + } + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + if (pack_spu.swap) + { + return_val = (GLint) SWAP32(return_val); + } + return return_val; +} + +GLint PACKSPU_APIENTRY packspu_GetUniformLocation(GLuint program, const char * name) +{ + if (!crStateIsProgramUniformsCached(program)) + { + GET_THREAD(thread); + int writeback = 1; + GLsizei maxcbData; + GLsizei *pData; + GLint mu; + + packspu_GetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB, &mu); + maxcbData = 16*mu*sizeof(char); + + pData = (GLsizei *) crAlloc(maxcbData+sizeof(GLsizei)); + if (!pData) + { + crWarning("packspu_GetUniformLocation: not enough memory, fallback to single query"); + return packspu_GetUniformLocationUncached(program, name); + } + + crPackGetUniformsLocations(program, maxcbData, pData, NULL, &writeback); + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + crStateGLSLProgramCacheUniforms(program, pData[0], &pData[1]); + + CRASSERT(crStateIsProgramUniformsCached(program)); + + crFree(pData); + } + + /*crDebug("packspu_GetUniformLocation(%d, %s)=%i", program, name, crStateGetUniformLocation(program, name));*/ + return crStateGetUniformLocation(program, name); +} + +static GLint PACKSPU_APIENTRY packspu_GetAttribLocationUnchached( GLuint program, const char * name ) +{ + GET_THREAD(thread); + int writeback = 1; + GLint return_val = (GLint) 0; + if (!CRPACKSPU_IS_WDDM_CRHGSMI() && !(pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network)) + { + crError( "packspu_GetAttribLocation doesn't work when there's no actual network involved!\nTry using the simplequery SPU in your chain!" ); + } + if (pack_spu.swap) + { + crPackGetAttribLocationSWAP( program, name, &return_val, &writeback ); + } + else + { + crPackGetAttribLocation( program, name, &return_val, &writeback ); + } + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + if (pack_spu.swap) + { + return_val = (GLint) SWAP32(return_val); + } + return return_val; +} + +GLint PACKSPU_APIENTRY packspu_GetAttribLocation(GLuint program, const char * name) +{ + if (!(CR_VBOX_CAP_GETATTRIBSLOCATIONS & g_u32VBoxHostCaps)) + return packspu_GetAttribLocationUnchached(program, name); + + if (!crStateIsProgramAttribsCached(program)) + { + GET_THREAD(thread); + int writeback = 1; + GLsizei maxcbData; + GLsizei *pData; + GLint mu; + + packspu_GetIntegerv(GL_MAX_VERTEX_ATTRIBS, &mu); + maxcbData = 4*32*mu*sizeof(char); + + pData = (GLsizei *) crAlloc(maxcbData+sizeof(GLsizei)); + if (!pData) + { + crWarning("packspu_GetAttribLocation: not enough memory, fallback to single query"); + return packspu_GetAttribLocationUnchached(program, name); + } + + crPackGetAttribsLocations(program, maxcbData, pData, NULL, &writeback); + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + crStateGLSLProgramCacheAttribs(program, pData[0], &pData[1]); + + CRASSERT(crStateIsProgramAttribsCached(program)); + + crFree(pData); + } + + /*crDebug("packspu_GetAttribLocation(%d, %s)=%i", program, name, crStateGetAttribLocation(program, name));*/ + return crStateGetAttribLocation(program, name); +} + +void PACKSPU_APIENTRY packspu_GetUniformsLocations(GLuint program, GLsizei maxcbData, GLsizei * cbData, GLvoid * pData) +{ + (void) program; + (void) maxcbData; + (void) cbData; + (void) pData; + WARN(("packspu_GetUniformsLocations shouldn't be called directly")); +} + +void PACKSPU_APIENTRY packspu_GetAttribsLocations(GLuint program, GLsizei maxcbData, GLsizei * cbData, GLvoid * pData) +{ + (void) program; + (void) maxcbData; + (void) cbData; + (void) pData; + WARN(("packspu_GetAttribsLocations shouldn't be called directly")); +} + +void PACKSPU_APIENTRY packspu_DeleteProgram(GLuint program) +{ + crStateDeleteProgram(program); + crPackDeleteProgram(program); +} + +void PACK_APIENTRY packspu_DeleteObjectARB(VBoxGLhandleARB obj) +{ + GLuint hwid = crStateGetProgramHWID(obj); + + CRASSERT(obj); + + /* we do not track shader creation inside guest since it is not needed currently. + * this is why we only care about programs here */ + if (hwid) + { + crStateDeleteProgram(obj); + } + + crPackDeleteObjectARB(obj); +} + +#ifdef VBOX_WITH_CRPACKSPU_DUMPER +static void packspu_RecCheckInitRec() +{ + if (pack_spu.Recorder.pDumper) + return; + + crDmpDbgPrintInit(&pack_spu.Dumper); + + crRecInit(&pack_spu.Recorder, NULL /*pBlitter: we do not support blitter operations here*/, &pack_spu.self, &pack_spu.Dumper.Base); +} +#endif + +void PACKSPU_APIENTRY packspu_LinkProgram(GLuint program) +{ +#ifdef VBOX_WITH_CRPACKSPU_DUMPER + GLint linkStatus = 0; +#endif + + crStateLinkProgram(program); + crPackLinkProgram(program); + +#ifdef VBOX_WITH_CRPACKSPU_DUMPER + pack_spu.self.GetObjectParameterivARB(program, GL_OBJECT_LINK_STATUS_ARB, &linkStatus); + Assert(linkStatus); + if (!linkStatus) + { + CRContext *ctx = crStateGetCurrent(); + packspu_RecCheckInitRec(); + crRecDumpProgram(&pack_spu.Recorder, ctx, program, program); + } +#endif +} + +void PACKSPU_APIENTRY packspu_CompileShader(GLuint shader) +{ +#ifdef VBOX_WITH_CRPACKSPU_DUMPER + GLint compileStatus = 0; +#endif + +// crStateCompileShader(shader); + crPackCompileShader(shader); + +#ifdef VBOX_WITH_CRPACKSPU_DUMPER + pack_spu.self.GetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &compileStatus); + Assert(compileStatus); + if (!compileStatus) + { + CRContext *ctx = crStateGetCurrent(); + packspu_RecCheckInitRec(); + crRecDumpShader(&pack_spu.Recorder, ctx, shader, shader); + } +#endif +} + diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_init.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_init.c new file mode 100644 index 00000000..d5219950 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_init.c @@ -0,0 +1,152 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_mem.h" +#include "cr_spu.h" +#include "cr_glstate.h" +#include "packspu.h" +#include "cr_packfunctions.h" +#include <stdio.h> + +extern SPUNamedFunctionTable _cr_pack_table[]; + +SPUFunctions pack_functions = { + NULL, /* CHILD COPY */ + NULL, /* DATA */ + _cr_pack_table /* THE ACTUAL FUNCTIONS */ +}; + +PackSPU pack_spu; + +#ifdef CHROMIUM_THREADSAFE +CRtsd _PackTSD; +CRmutex _PackMutex; +#endif + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) +# include <VBoxCrHgsmi.h> +# include <VBoxUhgsmi.h> +#endif + +#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_WDDM) +static bool isVBoxWDDMCrHgsmi(void) +{ +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + PVBOXUHGSMI pHgsmi = VBoxCrHgsmiCreate(); + if (pHgsmi) + { + VBoxCrHgsmiDestroy(pHgsmi); + return true; + } +#endif + return false; +} +#endif /* RT_OS_WINDOWS && VBOX_WITH_WDDM */ + +static SPUFunctions * +packSPUInit( int id, SPU *child, SPU *self, + unsigned int context_id, + unsigned int num_contexts ) +{ + ThreadInfo *thread; + + (void) context_id; + (void) num_contexts; + (void) child; + (void) self; + +#if defined(CHROMIUM_THREADSAFE) && !defined(WINDOWS) + crInitMutex(&_PackMutex); +#endif + +#ifdef CHROMIUM_THREADSAFE + crInitTSD(&_PackerTSD); + crInitTSD(&_PackTSD); +#endif + + pack_spu.id = id; + + packspuSetVBoxConfiguration( child ); + +#if defined(WINDOWS) && defined(VBOX_WITH_WDDM) + pack_spu.bIsWDDMCrHgsmi = isVBoxWDDMCrHgsmi(); +#endif + +#ifdef VBOX_WITH_CRPACKSPU_DUMPER + memset(&pack_spu.Dumper, 0, sizeof (pack_spu.Dumper)); +#endif + + if (!CRPACKSPU_IS_WDDM_CRHGSMI()) + { + /* This connects to the server, sets up the packer, etc. */ + thread = packspuNewThread( +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + NULL +#endif + ); + + if (!thread) { + return NULL; + } + CRASSERT( thread == &(pack_spu.thread[0]) ); + pack_spu.idxThreadInUse = 0; + } + + packspuCreateFunctions(); + crStateInit(); + + return &pack_functions; +} + +static void +packSPUSelfDispatch(SPUDispatchTable *self) +{ + crSPUInitDispatchTable( &(pack_spu.self) ); + crSPUCopyDispatchTable( &(pack_spu.self), self ); +} + +static int +packSPUCleanup(void) +{ + int i; +#ifdef CHROMIUM_THREADSAFE + crLockMutex(&_PackMutex); +#endif + for (i=0; i<MAX_THREADS; ++i) + { + if (pack_spu.thread[i].inUse && pack_spu.thread[i].packer) + { + crPackDeleteContext(pack_spu.thread[i].packer); + } + } + +#ifdef CHROMIUM_THREADSAFE + crFreeTSD(&_PackerTSD); + crFreeTSD(&_PackTSD); + crUnlockMutex(&_PackMutex); +# ifndef WINDOWS + crFreeMutex(&_PackMutex); +# endif +#endif /* CHROMIUM_THREADSAFE */ + return 1; +} + +extern SPUOptions packSPUOptions[]; + +int SPULoad( char **name, char **super, SPUInitFuncPtr *init, + SPUSelfDispatchFuncPtr *self, SPUCleanupFuncPtr *cleanup, + SPUOptionsPtr *options, int *flags ) +{ + *name = "pack"; + *super = NULL; + *init = packSPUInit; + *self = packSPUSelfDispatch; + *cleanup = packSPUCleanup; + *options = packSPUOptions; + *flags = (SPU_HAS_PACKER|SPU_IS_TERMINAL|SPU_MAX_SERVERS_ONE); + + return 1; +} diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_misc.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_misc.c new file mode 100644 index 00000000..0b65be90 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_misc.c @@ -0,0 +1,856 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_packfunctions.h" +#include "packspu.h" +#include "packspu_proto.h" +#include "cr_mem.h" + +void PACKSPU_APIENTRY packspu_ChromiumParametervCR(GLenum target, GLenum type, GLsizei count, const GLvoid *values) +{ + + CRMessage msg; + int len; + GLint ai32ServerValues[2]; + GLboolean fFlush = GL_FALSE; + GET_THREAD(thread); + + + switch(target) + { + case GL_GATHER_PACK_CR: + /* flush the current pack buffer */ + packspuFlush( (void *) thread ); + + /* the connection is thread->server.conn */ + msg.header.type = CR_MESSAGE_GATHER; + msg.gather.offset = 69; + len = sizeof(CRMessageGather); + crNetSend(thread->netServer.conn, NULL, &msg, len); + return; + + case GL_SHARE_LISTS_CR: + { + ContextInfo *pCtx[2]; + GLint *ai32Values; + int i; + if (count != 2) + { + WARN(("GL_SHARE_LISTS_CR invalid cound %d", count)); + return; + } + + if (type != GL_UNSIGNED_INT && type != GL_INT) + { + WARN(("GL_SHARE_LISTS_CR invalid type %d", type)); + return; + } + + ai32Values = (GLint*)values; + + for (i = 0; i < 2; ++i) + { + const int slot = ai32Values[i] - MAGIC_OFFSET; + + if (slot < 0 || slot >= pack_spu.numContexts) + { + WARN(("GL_SHARE_LISTS_CR invalid value[%d] %d", i, ai32Values[i])); + return; + } + + pCtx[i] = &pack_spu.context[slot]; + if (!pCtx[i]->clientState) + { + WARN(("GL_SHARE_LISTS_CR invalid pCtx1 for value[%d] %d", i, ai32Values[i])); + return; + } + + ai32ServerValues[i] = pCtx[i]->serverCtx; + } + + crStateShareLists(pCtx[0]->clientState, pCtx[1]->clientState); + + values = ai32ServerValues; + + fFlush = GL_TRUE; + + break; + } + + default: + break; + } + + if (pack_spu.swap) + crPackChromiumParametervCRSWAP(target, type, count, values); + else + crPackChromiumParametervCR(target, type, count, values); + + if (fFlush) + packspuFlush( (void *) thread ); +} + +GLboolean packspuSyncOnFlushes(void) +{ +#if 1 /*Seems to still cause issues, always sync for now*/ + return 1; +#else + GLint buffer; + + crStateGetIntegerv(GL_DRAW_BUFFER, &buffer); + /*Usually buffer==GL_BACK, so put this extra check to simplify boolean eval on runtime*/ + return (buffer != GL_BACK) + && (buffer == GL_FRONT_LEFT + || buffer == GL_FRONT_RIGHT + || buffer == GL_FRONT + || buffer == GL_FRONT_AND_BACK + || buffer == GL_LEFT + || buffer == GL_RIGHT); +#endif +} + +void PACKSPU_APIENTRY packspu_DrawBuffer(GLenum mode) +{ + GLboolean hadtoflush; + + hadtoflush = packspuSyncOnFlushes(); + + crStateDrawBuffer(mode); + crPackDrawBuffer(mode); + + if (hadtoflush && !packspuSyncOnFlushes()) + packspu_Flush(); +} + +void PACKSPU_APIENTRY packspu_Finish( void ) +{ + GET_THREAD(thread); + GLint writeback = CRPACKSPU_IS_WDDM_CRHGSMI() ? 1 : pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network; + + if (pack_spu.swap) + { + crPackFinishSWAP(); + } + else + { + crPackFinish(); + } + + if (packspuSyncOnFlushes()) + { + if (writeback) + { + if (pack_spu.swap) + crPackWritebackSWAP(&writeback); + else + crPackWriteback(&writeback); + + packspuFlush( (void *) thread ); + + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + } + } +} + +void PACKSPU_APIENTRY packspu_Flush( void ) +{ + GET_THREAD(thread); + int writeback=1; + int found=0; + + if (!thread->bInjectThread) + { + crPackFlush(); + if (packspuSyncOnFlushes()) + { + crPackWriteback(&writeback); + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + } + } + else + { + int i; + + crLockMutex(&_PackMutex); + + /*Make sure we process commands in order they should appear, so flush other threads first*/ + for (i=0; i<MAX_THREADS; ++i) + { + if (pack_spu.thread[i].inUse + && (thread != &pack_spu.thread[i]) && pack_spu.thread[i].netServer.conn + && pack_spu.thread[i].packer && pack_spu.thread[i].packer->currentBuffer) + { + packspuFlush((void *) &pack_spu.thread[i]); + + if (pack_spu.thread[i].netServer.conn->u32ClientID == thread->netServer.conn->u32InjectClientID) + { + found=1; + } + + } + } + + if (!found) + { + /*Thread we're supposed to inject commands for has been detached, + so there's nothing to sync with and we should just pass commands through our own connection. + */ + thread->netServer.conn->u32InjectClientID=0; + } + + packspuFlush((void *) thread); + + crUnlockMutex(&_PackMutex); + } +} + +void PACKSPU_APIENTRY packspu_NewList(GLuint list, GLenum mode) +{ + crStateNewList(list, mode); + crPackNewList(list, mode); +} + +void PACKSPU_APIENTRY packspu_EndList() +{ + crStateEndList(); + crPackEndList(); +} + +void PACKSPU_APIENTRY packspu_VBoxWindowDestroy( GLint con, GLint window ) +{ + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + GET_THREAD(thread); + if (con) + { + CRPackContext * curPacker = crPackGetContext(); + CRASSERT(!thread || !thread->bInjectThread); + thread = GET_THREAD_VAL_ID(con); + crPackSetContext(thread->packer); + crPackWindowDestroy(window); + if (curPacker != thread->packer) + crPackSetContext(curPacker); + return; + } + CRASSERT(thread); + CRASSERT(thread->bInjectThread); + } + crPackWindowDestroy(window); +} + +GLint PACKSPU_APIENTRY packspu_VBoxWindowCreate( GLint con, const char *dpyName, GLint visBits ) +{ + GET_THREAD(thread); + static int num_calls = 0; + int writeback = CRPACKSPU_IS_WDDM_CRHGSMI() ? 1 : pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network; + GLint return_val = (GLint) 0; + ThreadInfo *curThread = thread; + GLint retVal; + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + if (!con) + { + crError("connection expected!"); + return 0; + } + thread = GET_THREAD_VAL_ID(con); + } + else + { + CRASSERT(!con); + if (!thread) { + thread = packspuNewThread( +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + NULL +#endif + ); + } + } + CRASSERT(thread); + CRASSERT(thread->packer); + CRASSERT(crPackGetContext() == (curThread ? curThread->packer : NULL)); + + crPackSetContext(thread->packer); + + if (pack_spu.swap) + { + crPackWindowCreateSWAP( dpyName, visBits, &return_val, &writeback ); + } + else + { + crPackWindowCreate( dpyName, visBits, &return_val, &writeback ); + } + packspuFlush(thread); + if (!(thread->netServer.conn->actual_network)) + { + retVal = num_calls++; + } + else + { + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + if (pack_spu.swap) + { + return_val = (GLint) SWAP32(return_val); + } + retVal = return_val; + } + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + if (thread != curThread) + { + if (curThread) + crPackSetContext(curThread->packer); + else + crPackSetContext(NULL); + } + } + + return retVal; +} + +GLint PACKSPU_APIENTRY packspu_WindowCreate( const char *dpyName, GLint visBits ) +{ + return packspu_VBoxWindowCreate( 0, dpyName, visBits ); +} + +GLboolean PACKSPU_APIENTRY +packspu_AreTexturesResident( GLsizei n, const GLuint * textures, + GLboolean * residences ) +{ + GET_THREAD(thread); + int writeback = 1; + GLboolean return_val = GL_TRUE; + GLsizei i; + + if (!CRPACKSPU_IS_WDDM_CRHGSMI() && !(pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network)) + { + crError( "packspu_AreTexturesResident doesn't work when there's no actual network involved!\nTry using the simplequery SPU in your chain!" ); + } + + if (pack_spu.swap) + { + crPackAreTexturesResidentSWAP( n, textures, residences, &return_val, &writeback ); + } + else + { + crPackAreTexturesResident( n, textures, residences, &return_val, &writeback ); + } + packspuFlush( (void *) thread ); + + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + /* Since the Chromium packer/unpacker can't return both 'residences' + * and the function's return value, compute the return value here. + */ + for (i = 0; i < n; i++) { + if (!residences[i]) { + return_val = GL_FALSE; + break; + } + } + + return return_val; +} + + +GLboolean PACKSPU_APIENTRY +packspu_AreProgramsResidentNV( GLsizei n, const GLuint * ids, + GLboolean * residences ) +{ + GET_THREAD(thread); + int writeback = 1; + GLboolean return_val = GL_TRUE; + GLsizei i; + + if (!CRPACKSPU_IS_WDDM_CRHGSMI() && !(pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network)) + { + crError( "packspu_AreProgramsResidentNV doesn't work when there's no actual network involved!\nTry using the simplequery SPU in your chain!" ); + } + if (pack_spu.swap) + { + crPackAreProgramsResidentNVSWAP( n, ids, residences, &return_val, &writeback ); + } + else + { + crPackAreProgramsResidentNV( n, ids, residences, &return_val, &writeback ); + } + packspuFlush( (void *) thread ); + + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + /* Since the Chromium packer/unpacker can't return both 'residences' + * and the function's return value, compute the return value here. + */ + for (i = 0; i < n; i++) { + if (!residences[i]) { + return_val = GL_FALSE; + break; + } + } + + return return_val; +} + +void PACKSPU_APIENTRY packspu_GetPolygonStipple( GLubyte * mask ) +{ + GET_THREAD(thread); + int writeback = 1; + + if (pack_spu.swap) + { + crPackGetPolygonStippleSWAP( mask, &writeback ); + } + else + { + crPackGetPolygonStipple( mask, &writeback ); + } + +#ifdef CR_ARB_pixel_buffer_object + if (!crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) +#endif + { + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + } +} + +void PACKSPU_APIENTRY packspu_GetPixelMapfv( GLenum map, GLfloat * values ) +{ + GET_THREAD(thread); + int writeback = 1; + + if (pack_spu.swap) + { + crPackGetPixelMapfvSWAP( map, values, &writeback ); + } + else + { + crPackGetPixelMapfv( map, values, &writeback ); + } + +#ifdef CR_ARB_pixel_buffer_object + if (!crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) +#endif + { + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + } +} + +void PACKSPU_APIENTRY packspu_GetPixelMapuiv( GLenum map, GLuint * values ) +{ + GET_THREAD(thread); + int writeback = 1; + + if (pack_spu.swap) + { + crPackGetPixelMapuivSWAP( map, values, &writeback ); + } + else + { + crPackGetPixelMapuiv( map, values, &writeback ); + } + +#ifdef CR_ARB_pixel_buffer_object + if (!crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) +#endif + { + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + } +} + +void PACKSPU_APIENTRY packspu_GetPixelMapusv( GLenum map, GLushort * values ) +{ + GET_THREAD(thread); + int writeback = 1; + + if (pack_spu.swap) + { + crPackGetPixelMapusvSWAP( map, values, &writeback ); + } + else + { + crPackGetPixelMapusv( map, values, &writeback ); + } + +#ifdef CR_ARB_pixel_buffer_object + if (!crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) +#endif + { + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + } +} + +static void packspuFluchOnThreadSwitch(GLboolean fEnable) +{ + GET_THREAD(thread); + if (thread->currentContext->fAutoFlush == fEnable) + return; + + thread->currentContext->fAutoFlush = fEnable; + thread->currentContext->currentThread = fEnable ? thread : NULL; +} + +static void packspuCheckZerroVertAttr(GLboolean fEnable) +{ + GET_THREAD(thread); + + thread->currentContext->fCheckZerroVertAttr = fEnable; +} + +void PACKSPU_APIENTRY packspu_ChromiumParameteriCR(GLenum target, GLint value) +{ + switch (target) + { + case GL_FLUSH_ON_THREAD_SWITCH_CR: + /* this is a pure packspu state, don't propagate it any further */ + packspuFluchOnThreadSwitch(value); + return; + case GL_CHECK_ZERO_VERT_ARRT: + packspuCheckZerroVertAttr(value); + return; + case GL_SHARE_CONTEXT_RESOURCES_CR: + crStateShareContext(value); + break; + case GL_RCUSAGE_TEXTURE_SET_CR: + { + Assert(value); + crStateSetTextureUsed(value, GL_TRUE); + break; + } + case GL_RCUSAGE_TEXTURE_CLEAR_CR: + { + Assert(value); +#ifdef DEBUG + { + CRContext *pCurState = crStateGetCurrent(); + CRTextureObj *tobj = (CRTextureObj*)crHashtableSearch(pCurState->shared->textureTable, value); + Assert(tobj); + } +#endif + crStateSetTextureUsed(value, GL_FALSE); + break; + } + default: + break; + } + crPackChromiumParameteriCR(target, value); +} + +GLenum PACKSPU_APIENTRY packspu_GetError( void ) +{ + GET_THREAD(thread); + int writeback = 1; + GLenum return_val = (GLenum) 0; + CRContext *pCurState = crStateGetCurrent(); + NOREF(pCurState); /* it's unused, but I don't know about side effects.. */ + + if (!CRPACKSPU_IS_WDDM_CRHGSMI() && !(pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network)) + { + crError( "packspu_GetError doesn't work when there's no actual network involved!\nTry using the simplequery SPU in your chain!" ); + } + if (pack_spu.swap) + { + crPackGetErrorSWAP( &return_val, &writeback ); + } + else + { + crPackGetError( &return_val, &writeback ); + } + + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + + if (pack_spu.swap) + { + return_val = (GLenum) SWAP32(return_val); + } + + return return_val; +} + +#ifdef CHROMIUM_THREADSAFE +GLint PACKSPU_APIENTRY packspu_VBoxPackSetInjectThread(struct VBOXUHGSMI *pHgsmi) +{ + GLint con = 0; + int i; + GET_THREAD(thread); + CRASSERT(!thread); + RT_NOREF(pHgsmi); + crLockMutex(&_PackMutex); + { + CRASSERT(CRPACKSPU_IS_WDDM_CRHGSMI() || (pack_spu.numThreads>0)); + CRASSERT(pack_spu.numThreads<MAX_THREADS); + for (i=0; i<MAX_THREADS; ++i) + { + if (!pack_spu.thread[i].inUse) + { + thread = &pack_spu.thread[i]; + break; + } + } + CRASSERT(thread); + + thread->inUse = GL_TRUE; + if (!CRPACKSPU_IS_WDDM_CRHGSMI()) + thread->id = crThreadID(); + else + thread->id = THREAD_OFFSET_MAGIC + i; + thread->currentContext = NULL; + thread->bInjectThread = GL_TRUE; + + thread->netServer.name = crStrdup(pack_spu.name); + thread->netServer.buffer_size = 64 * 1024; + + packspuConnectToServer(&(thread->netServer) +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , pHgsmi +#endif + ); + CRASSERT(thread->netServer.conn); + + CRASSERT(thread->packer == NULL); + thread->packer = crPackNewContext( pack_spu.swap ); + CRASSERT(thread->packer); + crPackInitBuffer(&(thread->buffer), crNetAlloc(thread->netServer.conn), + thread->netServer.conn->buffer_size, thread->netServer.conn->mtu); + thread->buffer.canBarf = thread->netServer.conn->Barf ? GL_TRUE : GL_FALSE; + + crPackSetBuffer( thread->packer, &thread->buffer ); + crPackFlushFunc( thread->packer, packspuFlush ); + crPackFlushArg( thread->packer, (void *) thread ); + crPackSendHugeFunc( thread->packer, packspuHuge ); + crPackSetContext( thread->packer ); + + crSetTSD(&_PackTSD, thread); + + pack_spu.numThreads++; + } + crUnlockMutex(&_PackMutex); + + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + CRASSERT(thread->id - THREAD_OFFSET_MAGIC < RT_ELEMENTS(pack_spu.thread) + && GET_THREAD_VAL_ID(thread->id) == thread); + con = thread->id; + } + return con; +} + +GLuint PACKSPU_APIENTRY packspu_VBoxPackGetInjectID(GLint con) +{ + GLuint ret; + + crLockMutex(&_PackMutex); + { + ThreadInfo *thread = NULL; + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + if (!con) + { + crError("connection expected!"); + return 0; + } + thread = GET_THREAD_VAL_ID(con); + } + else + { + CRASSERT(!con); + thread = GET_THREAD_VAL(); + } + CRASSERT(thread && thread->netServer.conn && thread->netServer.conn->type==CR_VBOXHGCM); + ret = thread->netServer.conn->u32ClientID; + } + crUnlockMutex(&_PackMutex); + + return ret; +} + +void PACKSPU_APIENTRY packspu_VBoxPackSetInjectID(GLuint id) +{ + crLockMutex(&_PackMutex); + { + GET_THREAD(thread); + + CRASSERT(thread && thread->netServer.conn && thread->netServer.conn->type==CR_VBOXHGCM && thread->bInjectThread); + thread->netServer.conn->u32InjectClientID = id; + } + crUnlockMutex(&_PackMutex); +} + +void PACKSPU_APIENTRY packspu_VBoxAttachThread() +{ +#if 0 + int i; + GET_THREAD(thread); + + for (i=0; i<MAX_THREADS; ++i) + { + if (pack_spu.thread[i].inUse && thread==&pack_spu.thread[i] && thread->id==crThreadID()) + { + crError("2nd attach to same thread"); + } + } +#endif + + crSetTSD(&_PackTSD, NULL); + + crStateVBoxAttachThread(); +} + +void PACKSPU_APIENTRY packspu_VBoxDetachThread() +{ + if (CRPACKSPU_IS_WDDM_CRHGSMI()) + { + crPackSetContext(NULL); + crSetTSD(&_PackTSD, NULL); + } + else + { + int i; + GET_THREAD(thread); + if (thread) + { + crLockMutex(&_PackMutex); + + for (i=0; i<MAX_THREADS; ++i) + { + if (pack_spu.thread[i].inUse && thread==&pack_spu.thread[i] + && thread->id==crThreadID() && thread->netServer.conn) + { + CRASSERT(pack_spu.numThreads>0); + + packspuFlush((void *) thread); + + if (pack_spu.thread[i].packer) + { + CR_LOCK_PACKER_CONTEXT(thread->packer); + crPackSetContext(NULL); + CR_UNLOCK_PACKER_CONTEXT(thread->packer); + crPackDeleteContext(pack_spu.thread[i].packer); + + if (pack_spu.thread[i].buffer.pack) + { + crNetFree(pack_spu.thread[i].netServer.conn, pack_spu.thread[i].buffer.pack); + pack_spu.thread[i].buffer.pack = NULL; + } + } + crNetFreeConnection(pack_spu.thread[i].netServer.conn); + + if (pack_spu.thread[i].netServer.name) + crFree(pack_spu.thread[i].netServer.name); + + pack_spu.numThreads--; + /*note can't shift the array here, because other threads have TLS references to array elements*/ + crMemZero(&pack_spu.thread[i], sizeof(ThreadInfo)); + + crSetTSD(&_PackTSD, NULL); + + if (i==pack_spu.idxThreadInUse) + { + for (i=0; i<MAX_THREADS; ++i) + { + if (pack_spu.thread[i].inUse) + { + pack_spu.idxThreadInUse=i; + break; + } + } + } + + break; + } + } + + for (i=0; i<CR_MAX_CONTEXTS; ++i) + { + ContextInfo *ctx = &pack_spu.context[i]; + if (ctx->currentThread == thread) + { + CRASSERT(ctx->fAutoFlush); + ctx->currentThread = NULL; + } + } + + crUnlockMutex(&_PackMutex); + } + } + + crStateVBoxDetachThread(); +} + +#ifdef WINDOWS +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +BOOL WINAPI DllMain(HINSTANCE hDLLInst, DWORD fdwReason, LPVOID lpvReserved) +{ + (void) lpvReserved; + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + { + crInitMutex(&_PackMutex); + break; + } + + case DLL_PROCESS_DETACH: + { + crFreeMutex(&_PackMutex); + crNetTearDown(); + break; + } + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + default: + break; + } + + return TRUE; +} +#endif + +#else /*ifdef CHROMIUM_THREADSAFE*/ +GLint PACKSPU_APIENTRY packspu_VBoxPackSetInjectThread(struct VBOXUHGSMI *pHgsmi) +{ +} + +GLuint PACKSPU_APIENTRY packspu_VBoxPackGetInjectID(GLint con) +{ + return 0; +} + +void PACKSPU_APIENTRY packspu_VBoxPackSetInjectID(GLuint id) +{ + (void) id; +} + +void PACKSPU_APIENTRY packspu_VBoxPackAttachThread() +{ +} + +void PACKSPU_APIENTRY packspu_VBoxPackDetachThread() +{ +} +#endif /*CHROMIUM_THREADSAFE*/ + +void PACKSPU_APIENTRY packspu_VBoxPresentComposition(GLint win, const struct VBOXVR_SCR_COMPOSITOR * pCompositor, + const struct VBOXVR_SCR_COMPOSITOR_ENTRY *pChangedEntry) +{ + RT_NOREF(win, pCompositor, pChangedEntry); +} + +void PACKSPU_APIENTRY packspu_StringMarkerGREMEDY(GLsizei len, const GLvoid *string) +{ + RT_NOREF(len, string); +} + diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_net.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_net.c new file mode 100644 index 00000000..6d5d41d6 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_net.c @@ -0,0 +1,284 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_pack.h" +#include "cr_mem.h" +#include "cr_net.h" +#include "cr_pixeldata.h" +#include "cr_protocol.h" +#include "cr_error.h" +#include "packspu.h" +#include "packspu_proto.h" + +uint32_t g_u32VBoxHostCaps = 0; + +static void +packspuWriteback( const CRMessageWriteback *wb ) +{ + int *writeback; + crMemcpy( &writeback, &(wb->writeback_ptr), sizeof( writeback ) ); + *writeback = 0; +} + +/** + * XXX Note that this routine is identical to crNetRecvReadback except + * we set *writeback=0 instead of decrementing it. Hmmm. + */ +static void +packspuReadback( const CRMessageReadback *rb, unsigned int len ) +{ + /* minus the header, the destination pointer, + * *and* the implicit writeback pointer at the head. */ + + int payload_len = len - sizeof( *rb ); + int *writeback; + void *dest_ptr; + crMemcpy( &writeback, &(rb->writeback_ptr), sizeof( writeback ) ); + crMemcpy( &dest_ptr, &(rb->readback_ptr), sizeof( dest_ptr ) ); + + *writeback = 0; + crMemcpy( dest_ptr, ((char *)rb) + sizeof(*rb), payload_len ); +} + +static void +packspuReadPixels( const CRMessageReadPixels *rp, unsigned int len ) +{ + crNetRecvReadPixels( rp, len ); + --pack_spu.ReadPixels; +} + +static int +packspuReceiveData( CRConnection *conn, CRMessage *msg, unsigned int len ) +{ + RT_NOREF(conn); + if (msg->header.type == CR_MESSAGE_REDIR_PTR) + msg = (CRMessage*) msg->redirptr.pMessage; + + switch( msg->header.type ) + { + case CR_MESSAGE_READ_PIXELS: + packspuReadPixels( &(msg->readPixels), len ); + break; + case CR_MESSAGE_WRITEBACK: + packspuWriteback( &(msg->writeback) ); + break; + case CR_MESSAGE_READBACK: + packspuReadback( &(msg->readback), len ); + break; + default: + /*crWarning( "Why is the pack SPU getting a message of type 0x%x?", msg->type ); */ + return 0; /* NOT HANDLED */ + } + return 1; /* HANDLED */ +} + +static CRMessageOpcodes * +__prependHeader( CRPackBuffer *buf, unsigned int *len, unsigned int senderID ) +{ + int num_opcodes; + CRMessageOpcodes *hdr; + RT_NOREF(senderID); + + CRASSERT( buf ); + CRASSERT( buf->opcode_current < buf->opcode_start ); + CRASSERT( buf->opcode_current >= buf->opcode_end ); + CRASSERT( buf->data_current > buf->data_start ); + CRASSERT( buf->data_current <= buf->data_end ); + + num_opcodes = buf->opcode_start - buf->opcode_current; + hdr = (CRMessageOpcodes *) + ( buf->data_start - ( ( num_opcodes + 3 ) & ~0x3 ) - sizeof(*hdr) ); + + CRASSERT( (void *) hdr >= buf->pack ); + + if (pack_spu.swap) + { + hdr->header.type = (CRMessageType) SWAP32(CR_MESSAGE_OPCODES); + hdr->numOpcodes = SWAP32(num_opcodes); + } + else + { + hdr->header.type = CR_MESSAGE_OPCODES; + hdr->numOpcodes = num_opcodes; + } + + *len = buf->data_current - (unsigned char *) hdr; + + return hdr; +} + + +/* + * This is called from either the Pack SPU and the packer library whenever + * we need to send a data buffer to the server. + */ +void packspuFlush(void *arg ) +{ + ThreadInfo *thread = (ThreadInfo *) arg; + ContextInfo *ctx; + unsigned int len; + CRMessageOpcodes *hdr; + CRPackBuffer *buf; + + /* we should _always_ pass a valid <arg> value */ + CRASSERT(thread && thread->inUse); +#ifdef CHROMIUM_THREADSAFE + CR_LOCK_PACKER_CONTEXT(thread->packer); +#endif + ctx = thread->currentContext; + buf = &(thread->buffer); + CRASSERT(buf); + + if (ctx && ctx->fCheckZerroVertAttr) + crStateCurrentRecoverNew(ctx->clientState, &thread->packer->current); + + /* We're done packing into the current buffer, unbind it */ + crPackReleaseBuffer( thread->packer ); + + /* + printf("%s thread=%p thread->id = %d thread->pc=%p t2->id=%d t2->pc=%p packbuf=%p packbuf=%p\n", + __FUNCTION__, (void*) thread, (int) thread->id, thread->packer, + (int) t2->id, t2->packer, + buf->pack, thread->packer->buffer.pack); + */ + + if ( buf->opcode_current == buf->opcode_start ) { + /* + printf("%s early return\n", __FUNCTION__); + */ + /* XXX these calls seem to help, but might be appropriate */ + crPackSetBuffer( thread->packer, buf ); + crPackResetPointers(thread->packer); +#ifdef CHROMIUM_THREADSAFE + CR_UNLOCK_PACKER_CONTEXT(thread->packer); +#endif + return; + } + + hdr = __prependHeader( buf, &len, 0 ); + + CRASSERT( thread->netServer.conn ); + + if ( buf->holds_BeginEnd ) + { + /*crDebug("crNetBarf %d, (%d)", len, buf->size);*/ + crNetBarf( thread->netServer.conn, &(buf->pack), hdr, len ); + } + else + { + /*crDebug("crNetSend %d, (%d)", len, buf->size);*/ + crNetSend( thread->netServer.conn, &(buf->pack), hdr, len ); + } + + buf->pack = crNetAlloc( thread->netServer.conn ); + + /* The network may have found a new mtu */ + buf->mtu = thread->netServer.conn->mtu; + + crPackSetBuffer( thread->packer, buf ); + + crPackResetPointers(thread->packer); + +#ifdef CHROMIUM_THREADSAFE + CR_UNLOCK_PACKER_CONTEXT(thread->packer); +#endif +} + + +/** + * XXX NOTE: there's a lot of duplicate code here common to the + * pack, tilesort and replicate SPUs. Try to simplify someday! + */ +void packspuHuge( CROpcode opcode, void *buf ) +{ + GET_THREAD(thread); + unsigned int len; + unsigned char *src; + CRMessageOpcodes *msg; + + CRASSERT(thread); + + /* packet length is indicated by the variable length field, and + includes an additional word for the opcode (with alignment) and + a header */ + len = ((unsigned int *) buf)[-1]; + if (pack_spu.swap) + { + /* It's already been swapped, swap it back. */ + len = SWAP32(len); + } + len += 4 + sizeof(CRMessageOpcodes); + + /* write the opcode in just before the length */ + ((unsigned char *) buf)[-5] = (unsigned char) opcode; + + /* fix up the pointer to the packet to include the length & opcode + & header */ + src = (unsigned char *) buf - 8 - sizeof(CRMessageOpcodes); + + msg = (CRMessageOpcodes *) src; + + if (pack_spu.swap) + { + msg->header.type = (CRMessageType) SWAP32(CR_MESSAGE_OPCODES); + msg->numOpcodes = SWAP32(1); + } + else + { + msg->header.type = CR_MESSAGE_OPCODES; + msg->numOpcodes = 1; + } + + CRASSERT( thread->netServer.conn ); + crNetSend( thread->netServer.conn, NULL, src, len ); +} + +static void packspuFirstConnectToServer( CRNetServer *server +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , struct VBOXUHGSMI *pHgsmi +#endif + ) +{ + crNetInit( packspuReceiveData, NULL ); + crNetServerConnect( server +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , pHgsmi +#endif + ); + if (server->conn) + { + g_u32VBoxHostCaps = crNetHostCapsGet(); + crPackCapsSet(g_u32VBoxHostCaps); + } +} + +void packspuConnectToServer( CRNetServer *server +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , struct VBOXUHGSMI *pHgsmi +#endif + ) +{ + if (pack_spu.numThreads == 0) { + packspuFirstConnectToServer( server +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , pHgsmi +#endif + ); + if (!server->conn) { + crError("packspuConnectToServer: no connection on first create!"); + return; + } + pack_spu.swap = server->conn->swap; + } + else { + /* a new pthread */ + crNetNewClient(server +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , pHgsmi +#endif + ); + } +} diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_pixel.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_pixel.c new file mode 100644 index 00000000..06734b1e --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_pixel.c @@ -0,0 +1,727 @@ +/* Copyright (c) 2001, Stanford University + All rights reserved. + + See the file LICENSE.txt for information on redistributing this software. */ + +#include "cr_packfunctions.h" +#include "cr_glstate.h" +#include "cr_pixeldata.h" +#include "cr_version.h" +#include "packspu.h" +#include "packspu_proto.h" + +static GLboolean packspu_CheckTexImageFormat(GLenum format) +{ + if (format!=GL_COLOR_INDEX + && format!=GL_RED + && format!=GL_GREEN + && format!=GL_BLUE + && format!=GL_ALPHA + && format!=GL_RGB + && format!=GL_BGR + && format!=GL_RGBA + && format!=GL_BGRA + && format!=GL_LUMINANCE + && format!=GL_LUMINANCE_ALPHA + && format!=GL_DEPTH_COMPONENT + && format!=GL_DEPTH_STENCIL) + { + /*crWarning("crPackCheckTexImageFormat FAILED format 0x%x isn't valid", format);*/ + return GL_FALSE; + } + + return GL_TRUE; +} + +static GLboolean packspu_CheckTexImageType(GLenum type) +{ + if (type!=GL_UNSIGNED_BYTE + && type!=GL_BYTE + && type!=GL_BITMAP + && type!=GL_UNSIGNED_SHORT + && type!=GL_SHORT + && type!=GL_UNSIGNED_INT + && type!=GL_INT + && type!=GL_FLOAT + && type!=GL_UNSIGNED_BYTE_3_3_2 + && type!=GL_UNSIGNED_BYTE_2_3_3_REV + && type!=GL_UNSIGNED_SHORT_5_6_5 + && type!=GL_UNSIGNED_SHORT_5_6_5_REV + && type!=GL_UNSIGNED_SHORT_4_4_4_4 + && type!=GL_UNSIGNED_SHORT_4_4_4_4_REV + && type!=GL_UNSIGNED_SHORT_5_5_5_1 + && type!=GL_UNSIGNED_SHORT_1_5_5_5_REV + && type!=GL_UNSIGNED_INT_8_8_8_8 + && type!=GL_UNSIGNED_INT_8_8_8_8_REV + && type!=GL_UNSIGNED_INT_10_10_10_2 + && type!=GL_UNSIGNED_INT_2_10_10_10_REV + && type!=GL_UNSIGNED_INT_24_8) + { + /*crWarning("crPackCheckTexImageType FAILED type 0x%x isn't valid", type);*/ + return GL_FALSE; + } + + return GL_TRUE; +} + +static GLboolean packspu_CheckTexImageInternalFormat(GLint internalformat) +{ + if (internalformat!=1 + && internalformat!=2 + && internalformat!=3 + && internalformat!=4 + && internalformat!=GL_ALPHA + && internalformat!=GL_ALPHA4 + && internalformat!=GL_ALPHA8 + && internalformat!=GL_ALPHA12 + && internalformat!=GL_ALPHA16 + && internalformat!=GL_COMPRESSED_ALPHA + && internalformat!=GL_COMPRESSED_LUMINANCE + && internalformat!=GL_COMPRESSED_LUMINANCE_ALPHA + && internalformat!=GL_COMPRESSED_INTENSITY + && internalformat!=GL_COMPRESSED_RGB + && internalformat!=GL_COMPRESSED_RGBA + && internalformat!=GL_DEPTH_COMPONENT + && internalformat!=GL_DEPTH_COMPONENT16 + && internalformat!=GL_DEPTH_COMPONENT24 + && internalformat!=GL_DEPTH_COMPONENT32 + && internalformat!=GL_DEPTH24_STENCIL8 + && internalformat!=GL_LUMINANCE + && internalformat!=GL_LUMINANCE4 + && internalformat!=GL_LUMINANCE8 + && internalformat!=GL_LUMINANCE12 + && internalformat!=GL_LUMINANCE16 + && internalformat!=GL_LUMINANCE_ALPHA + && internalformat!=GL_LUMINANCE4_ALPHA4 + && internalformat!=GL_LUMINANCE6_ALPHA2 + && internalformat!=GL_LUMINANCE8_ALPHA8 + && internalformat!=GL_LUMINANCE12_ALPHA4 + && internalformat!=GL_LUMINANCE12_ALPHA12 + && internalformat!=GL_LUMINANCE16_ALPHA16 + && internalformat!=GL_INTENSITY + && internalformat!=GL_INTENSITY4 + && internalformat!=GL_INTENSITY8 + && internalformat!=GL_INTENSITY12 + && internalformat!=GL_INTENSITY16 + && internalformat!=GL_R3_G3_B2 + && internalformat!=GL_RGB + && internalformat!=GL_RGB4 + && internalformat!=GL_RGB5 + && internalformat!=GL_RGB8 + && internalformat!=GL_RGB10 + && internalformat!=GL_RGB12 + && internalformat!=GL_RGB16 + && internalformat!=GL_RGBA + && internalformat!=GL_RGBA2 + && internalformat!=GL_RGBA4 + && internalformat!=GL_RGB5_A1 + && internalformat!=GL_RGBA8 + && internalformat!=GL_RGB10_A2 + && internalformat!=GL_RGBA12 + && internalformat!=GL_RGBA16 + && internalformat!=GL_SLUMINANCE + && internalformat!=GL_SLUMINANCE8 + && internalformat!=GL_SLUMINANCE_ALPHA + && internalformat!=GL_SLUMINANCE8_ALPHA8 + && internalformat!=GL_SRGB + && internalformat!=GL_SRGB8 + && internalformat!=GL_SRGB_ALPHA + && internalformat!=GL_SRGB8_ALPHA8 +#ifdef CR_EXT_texture_compression_s3tc + && internalformat!=GL_COMPRESSED_RGB_S3TC_DXT1_EXT + && internalformat!=GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + && internalformat!=GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + && internalformat!=GL_COMPRESSED_RGBA_S3TC_DXT5_EXT +# ifdef CR_EXT_texture_sRGB + && internalformat!=GL_COMPRESSED_SRGB_S3TC_DXT1_EXT + && internalformat!=GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT + && internalformat!=GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT + && internalformat!=GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT +# endif +#endif + /** @todo ARB_texture_float*/ + && internalformat!=GL_RGBA32F_ARB + && internalformat!=GL_RGB32F_ARB + && internalformat!=GL_ALPHA32F_ARB + && internalformat!=GL_INTENSITY32F_ARB + && internalformat!=GL_LUMINANCE32F_ARB + && internalformat!=GL_LUMINANCE_ALPHA32F_ARB + && internalformat!=GL_RGBA16F_ARB + && internalformat!=GL_RGB16F_ARB + && internalformat!=GL_ALPHA16F_ARB + && internalformat!=GL_INTENSITY16F_ARB + && internalformat!=GL_LUMINANCE16F_ARB + && internalformat!=GL_LUMINANCE_ALPHA16F_ARB + ) + { + /*crWarning("crPackCheckTexImageInternalFormat FAILED internalformat 0x%x isn't valid", internalformat);*/ + return GL_FALSE; + } + + return GL_TRUE; +} + +static GLboolean packspu_CheckTexImageParams(GLint internalformat, GLenum format, GLenum type) +{ + return packspu_CheckTexImageFormat(format) + && packspu_CheckTexImageType(type) + && packspu_CheckTexImageInternalFormat(internalformat); +} + +static GLboolean packspu_CheckTexImageFormatType(GLenum format, GLenum type) +{ + return packspu_CheckTexImageFormat(format) + && packspu_CheckTexImageType(type); +} + +static const CRPixelPackState _defaultPacking = { + 0, /* rowLength */ + 0, /* skipRows */ + 0, /* skipPixels */ + 1, /* alignment */ + 0, /* imageHeight */ + 0, /* skipImages */ + GL_FALSE, /* swapBytes */ + GL_FALSE, /* psLSBFirst */ +}; + +#define APPLY_IF_NEQ(state, field, enum) \ + if (state.field != _defaultPacking.field) \ + { \ + crPackPixelStorei(enum, state.field); \ + } + +#define RESTORE_IF_NEQ(state, field, enum) \ + if (state.field != _defaultPacking.field) \ + { \ + crPackPixelStorei(enum, _defaultPacking.field); \ + } + +static void packspu_ApplyUnpackState(void) +{ + GET_THREAD(thread); + ContextInfo *ctx = thread->currentContext; + CRClientState *clientState = &(ctx->clientState->client); + + APPLY_IF_NEQ(clientState->unpack, rowLength, GL_UNPACK_ROW_LENGTH); + APPLY_IF_NEQ(clientState->unpack, skipRows, GL_UNPACK_SKIP_ROWS); + APPLY_IF_NEQ(clientState->unpack, skipPixels, GL_UNPACK_SKIP_PIXELS); + APPLY_IF_NEQ(clientState->unpack, alignment, GL_UNPACK_ALIGNMENT); + APPLY_IF_NEQ(clientState->unpack, imageHeight, GL_UNPACK_IMAGE_HEIGHT); + APPLY_IF_NEQ(clientState->unpack, skipImages, GL_UNPACK_SKIP_IMAGES); + APPLY_IF_NEQ(clientState->unpack, swapBytes, GL_UNPACK_SWAP_BYTES); + APPLY_IF_NEQ(clientState->unpack, psLSBFirst, GL_UNPACK_LSB_FIRST); +} + +static void packspu_RestoreUnpackState(void) +{ + GET_THREAD(thread); + ContextInfo *ctx = thread->currentContext; + CRClientState *clientState = &(ctx->clientState->client); + + RESTORE_IF_NEQ(clientState->unpack, rowLength, GL_UNPACK_ROW_LENGTH); + RESTORE_IF_NEQ(clientState->unpack, skipRows, GL_UNPACK_SKIP_ROWS); + RESTORE_IF_NEQ(clientState->unpack, skipPixels, GL_UNPACK_SKIP_PIXELS); + RESTORE_IF_NEQ(clientState->unpack, alignment, GL_UNPACK_ALIGNMENT); + RESTORE_IF_NEQ(clientState->unpack, imageHeight, GL_UNPACK_IMAGE_HEIGHT); + RESTORE_IF_NEQ(clientState->unpack, skipImages, GL_UNPACK_SKIP_IMAGES); + RESTORE_IF_NEQ(clientState->unpack, swapBytes, GL_UNPACK_SWAP_BYTES); + RESTORE_IF_NEQ(clientState->unpack, psLSBFirst, GL_UNPACK_LSB_FIRST); +} + +static void packspu_ApplyPackState(void) +{ + GET_THREAD(thread); + ContextInfo *ctx = thread->currentContext; + CRClientState *clientState = &(ctx->clientState->client); + + APPLY_IF_NEQ(clientState->pack, rowLength, GL_PACK_ROW_LENGTH); + APPLY_IF_NEQ(clientState->pack, skipRows, GL_PACK_SKIP_ROWS); + APPLY_IF_NEQ(clientState->pack, skipPixels, GL_PACK_SKIP_PIXELS); + APPLY_IF_NEQ(clientState->pack, alignment, GL_PACK_ALIGNMENT); + APPLY_IF_NEQ(clientState->pack, imageHeight, GL_PACK_IMAGE_HEIGHT); + APPLY_IF_NEQ(clientState->pack, skipImages, GL_PACK_SKIP_IMAGES); + APPLY_IF_NEQ(clientState->pack, swapBytes, GL_PACK_SWAP_BYTES); + APPLY_IF_NEQ(clientState->pack, psLSBFirst, GL_PACK_LSB_FIRST); +} + +static void packspu_RestorePackState(void) +{ + GET_THREAD(thread); + ContextInfo *ctx = thread->currentContext; + CRClientState *clientState = &(ctx->clientState->client); + + RESTORE_IF_NEQ(clientState->pack, rowLength, GL_PACK_ROW_LENGTH); + RESTORE_IF_NEQ(clientState->pack, skipRows, GL_PACK_SKIP_ROWS); + RESTORE_IF_NEQ(clientState->pack, skipPixels, GL_PACK_SKIP_PIXELS); + RESTORE_IF_NEQ(clientState->pack, alignment, GL_PACK_ALIGNMENT); + RESTORE_IF_NEQ(clientState->pack, imageHeight, GL_PACK_IMAGE_HEIGHT); + RESTORE_IF_NEQ(clientState->pack, skipImages, GL_PACK_SKIP_IMAGES); + RESTORE_IF_NEQ(clientState->pack, swapBytes, GL_PACK_SWAP_BYTES); + RESTORE_IF_NEQ(clientState->pack, psLSBFirst, GL_PACK_LSB_FIRST); +} + +void PACKSPU_APIENTRY packspu_PixelStoref( GLenum pname, GLfloat param ) +{ + /* NOTE: we do not send pixel store parameters to the server! + * When we pack a glDrawPixels or glTexImage2D image we interpret + * the user's pixel store parameters at that time and pack the + * image in a canonical layout (see util/pixel.c). + */ + crStatePixelStoref( pname, param ); +} + +void PACKSPU_APIENTRY packspu_PixelStorei( GLenum pname, GLint param ) +{ + crStatePixelStorei( pname, param ); +} + +void PACKSPU_APIENTRY packspu_DrawPixels( GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels ) +{ + GET_THREAD(thread); + ContextInfo *ctx = thread->currentContext; + CRClientState *clientState = &(ctx->clientState->client); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + crPackDrawPixels( width, height, format, type, pixels, &(clientState->unpack) ); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} + +void PACKSPU_APIENTRY packspu_ReadPixels( GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels ) +{ + GET_THREAD(thread); + ContextInfo *ctx = thread->currentContext; + CRClientState *clientState = &(ctx->clientState->client); + int writeback; + + if (crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) + { + packspu_ApplyPackState(); + } + + crPackReadPixels(x, y, width, height, format, type, pixels, &(clientState->pack), &writeback); + + if (crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) + { + packspu_RestorePackState(); + } + +#ifdef CR_ARB_pixel_buffer_object + if (!crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) +#endif + { + pack_spu.ReadPixels++; + + packspuFlush((void *) thread); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + } +} + +/** @todo check with pbo's*/ +void PACKSPU_APIENTRY packspu_CopyPixels( GLint x, GLint y, GLsizei width, GLsizei height, GLenum type ) +{ + GET_THREAD(thread); + if (pack_spu.swap) + crPackCopyPixelsSWAP( x, y, width, height, type ); + else + crPackCopyPixels( x, y, width, height, type ); + /* XXX why flush here? */ + packspuFlush( (void *) thread ); +} + +void PACKSPU_APIENTRY packspu_Bitmap( GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap ) +{ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + crPackBitmap(width, height, xorig, yorig, xmove, ymove, bitmap, &(clientState->unpack)); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} + +void PACKSPU_APIENTRY packspu_TexImage1D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels ) +{ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + if (!packspu_CheckTexImageParams(internalformat, format, type)) + { + if (pixels || crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + crWarning("packspu_TexImage1D invalid internalFormat(%x)/format(%x)/type(%x)", internalformat, format, type); + return; + } + internalformat = packspu_CheckTexImageInternalFormat(internalformat) ? internalformat:GL_RGBA; + format = packspu_CheckTexImageFormat(format) ? format:GL_RGBA; + type = packspu_CheckTexImageType(type) ? type:GL_UNSIGNED_BYTE; + } + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + if (pack_spu.swap) + crPackTexImage1DSWAP( target, level, internalformat, width, border, format, type, pixels, &(clientState->unpack) ); + else + crPackTexImage1D( target, level, internalformat, width, border, format, type, pixels, &(clientState->unpack) ); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} + +void PACKSPU_APIENTRY packspu_TexImage2D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels ) +{ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + if (!packspu_CheckTexImageParams(internalformat, format, type)) + { + if (pixels || crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + crWarning("packspu_TexImage2D invalid internalFormat(%x)/format(%x)/type(%x)", internalformat, format, type); + return; + } + internalformat = packspu_CheckTexImageInternalFormat(internalformat) ? internalformat:GL_RGBA; + format = packspu_CheckTexImageFormat(format) ? format:GL_RGBA; + type = packspu_CheckTexImageType(type) ? type:GL_UNSIGNED_BYTE; + } + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + if (pack_spu.swap) + crPackTexImage2DSWAP( target, level, internalformat, width, height, border, format, type, pixels, &(clientState->unpack) ); + else + crPackTexImage2D( target, level, internalformat, width, height, border, format, type, pixels, &(clientState->unpack) ); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} + +#ifdef GL_EXT_texture3D +void PACKSPU_APIENTRY packspu_TexImage3DEXT( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels ) +{ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + if (pack_spu.swap) + crPackTexImage3DEXTSWAP( target, level, internalformat, width, height, depth, border, format, type, pixels, &(clientState->unpack) ); + else + crPackTexImage3DEXT( target, level, internalformat, width, height, depth, border, format, type, pixels, &(clientState->unpack) ); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} +#endif + +#ifdef CR_OPENGL_VERSION_1_2 +void PACKSPU_APIENTRY packspu_TexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels ) +{ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + if (pack_spu.swap) + crPackTexImage3DSWAP( target, level, internalformat, width, height, depth, border, format, type, pixels, &(clientState->unpack) ); + else + crPackTexImage3D( target, level, internalformat, width, height, depth, border, format, type, pixels, &(clientState->unpack) ); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} +#endif /* CR_OPENGL_VERSION_1_2 */ + +void PACKSPU_APIENTRY packspu_TexSubImage1D( GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels ) +{ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + if (!packspu_CheckTexImageFormatType(format, type)) + { + crWarning("packspu_TexSubImage1D invalid format(%x)/type(%x)", format, type); + return; + } + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + if (pack_spu.swap) + crPackTexSubImage1DSWAP( target, level, xoffset, width, format, type, pixels, &(clientState->unpack) ); + else + crPackTexSubImage1D( target, level, xoffset, width, format, type, pixels, &(clientState->unpack) ); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} + +void PACKSPU_APIENTRY packspu_TexSubImage2D( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels ) +{ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + if (!packspu_CheckTexImageFormatType(format, type)) + { + crWarning("packspu_TexSubImage2D invalid format(%x)/type(%x)", format, type); + return; + } + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + if (pack_spu.swap) + crPackTexSubImage2DSWAP( target, level, xoffset, yoffset, width, height, format, type, pixels, &(clientState->unpack) ); + else + crPackTexSubImage2D( target, level, xoffset, yoffset, width, height, format, type, pixels, &(clientState->unpack) ); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} + +#ifdef CR_OPENGL_VERSION_1_2 +void PACKSPU_APIENTRY packspu_TexSubImage3D( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels ) +{ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + if (pack_spu.swap) + crPackTexSubImage3DSWAP( target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels, &(clientState->unpack) ); + else + crPackTexSubImage3D( target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels, &(clientState->unpack) ); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} +#endif /* CR_OPENGL_VERSION_1_2 */ + +void PACKSPU_APIENTRY packspu_ZPixCR( GLsizei width, GLsizei height, GLenum format, GLenum type, GLenum ztype, GLint zparm, GLint length, const GLvoid *pixels ) +{ + GET_CONTEXT(ctx); + CRClientState *clientState = &(ctx->clientState->client); + if (pack_spu.swap) + crPackZPixCRSWAP( width, height, format, type, ztype, zparm, length, pixels, &(clientState->unpack) ); + else + crPackZPixCR( width, height, format, type, ztype, zparm, length, pixels, &(clientState->unpack) ); +} + +void PACKSPU_APIENTRY packspu_GetTexImage (GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels) +{ + GET_THREAD(thread); + ContextInfo *ctx = thread->currentContext; + CRClientState *clientState = &(ctx->clientState->client); + int writeback = 1; + + /* XXX note: we're not observing the pixel pack parameters here unless PACK PBO is bound + * To do so, we'd have to allocate a temporary image buffer (how large???) + * and copy the image to the user's buffer using the pixel pack params. + */ + + if (crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) + { + packspu_ApplyPackState(); + } + + if (pack_spu.swap) + crPackGetTexImageSWAP( target, level, format, type, pixels, &(clientState->pack), &writeback ); + else + crPackGetTexImage( target, level, format, type, pixels, &(clientState->pack), &writeback ); + + if (crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) + { + packspu_RestorePackState(); + } + +#ifdef CR_ARB_pixel_buffer_object + if (!crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) +#endif + { + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + } +} + +void PACKSPU_APIENTRY packspu_GetCompressedTexImageARB( GLenum target, GLint level, GLvoid * img ) +{ + GET_THREAD(thread); + int writeback = 1; + + if (crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) + { + packspu_ApplyPackState(); + } + + if (pack_spu.swap) + { + crPackGetCompressedTexImageARBSWAP( target, level, img, &writeback ); + } + else + { + crPackGetCompressedTexImageARB( target, level, img, &writeback ); + } + + if (crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) + { + packspu_RestorePackState(); + } + +#ifdef CR_ARB_pixel_buffer_object + if (!crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB)) +#endif + { + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + } +} + +void PACKSPU_APIENTRY +packspu_CompressedTexImage1DARB(GLenum target, GLint level, GLenum internalformat, GLsizei width, + GLint border, GLsizei imagesize, const GLvoid *data) +{ + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + crPackCompressedTexImage1DARB(target, level, internalformat, width, border, imagesize, data); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} + +void PACKSPU_APIENTRY +packspu_CompressedTexImage2DARB(GLenum target, GLint level, GLenum internalformat, GLsizei width, + GLsizei height, GLint border, GLsizei imagesize, const GLvoid *data) +{ + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + crPackCompressedTexImage2DARB(target, level, internalformat, width, height, border, imagesize, data); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} + +void PACKSPU_APIENTRY +packspu_CompressedTexImage3DARB(GLenum target, GLint level, GLenum internalformat, GLsizei width, + GLsizei height, GLsizei depth, GLint border, GLsizei imagesize, const GLvoid *data) +{ + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + crPackCompressedTexImage3DARB(target, level, internalformat, width, height, depth, border, imagesize, data); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} + +void PACKSPU_APIENTRY +packspu_CompressedTexSubImage1DARB(GLenum target, GLint level, GLint xoffset, GLsizei width, + GLenum format, GLsizei imagesize, const GLvoid *data) +{ + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + crPackCompressedTexSubImage1DARB(target, level, xoffset, width, format, imagesize, data); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} + +void PACKSPU_APIENTRY +packspu_CompressedTexSubImage2DARB(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLsizei width, GLsizei height, GLenum format, GLsizei imagesize, const GLvoid *data) +{ + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + crPackCompressedTexSubImage2DARB(target, level, xoffset, yoffset, width, height, format, imagesize, data); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} + +void PACKSPU_APIENTRY +packspu_CompressedTexSubImage3DARB(GLenum target, GLint level, GLint xoffset, GLint yoffset, + GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, + GLsizei imagesize, const GLvoid *data) +{ + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_ApplyUnpackState(); + } + + crPackCompressedTexSubImage3DARB(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imagesize, data); + + if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB)) + { + packspu_RestoreUnpackState(); + } +} diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_proto.py b/src/VBox/Additions/common/crOpenGL/pack/packspu_proto.py new file mode 100755 index 00000000..5feed9a9 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_proto.py @@ -0,0 +1,50 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - THIS FILE AUTOMATICALLY GENERATED BY packspu_proto.py SCRIPT */ + +#ifndef PACKSPU_FUNCTIONS_H +#define PACKSPU_FUNCTIONS_H 1 + +#include <stdio.h> +#include "cr_string.h" +#include "cr_spu.h" +#include "packspu.h" +#include "cr_packfunctions.h" +""") + + +pack_specials = [] + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +# make list of special functions +for func_name in keys: + if ("get" in apiutil.Properties(func_name) or + apiutil.FindSpecial( "packspu", func_name ) or + apiutil.FindSpecial( "packspu_flush", func_name ) or + apiutil.FindSpecial( "packspu_vertex", func_name )): + pack_specials.append( func_name ) + +for func_name in keys: + if apiutil.FindSpecial( "packspu_unimplemented", func_name ): + continue + if func_name in pack_specials: + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + print('extern %s PACKSPU_APIENTRY packspu_%s(%s);' % ( return_type, func_name, apiutil.MakeDeclarationString(params) )) + + +print(""" +#endif +""") diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_special b/src/VBox/Additions/common/crOpenGL/pack/packspu_special new file mode 100644 index 00000000..13c3e0a2 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_special @@ -0,0 +1,138 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. +AreTexturesResident +AreProgramsResidentNV +GetString +GetTexImage +EnableClientState +DisableClientState +Enable +Disable +ClientActiveTextureARB +ColorPointer +FogCoordPointerEXT +SecondaryColorPointerEXT +VertexAttribPointerARB +VertexAttribPointerNV +GetPointerv +VertexPointer +NormalPointer +TexCoordPointer +EdgeFlagPointer +IndexPointer +ArrayElement +DrawArrays +DrawElements +DrawRangeElements +InterleavedArrays +ReadPixels +DrawPixels +CopyPixels +Bitmap +SwapBuffers +Flush +Finish +PixelStorei +PixelStoref +PushClientAttrib +PopClientAttrib +CreateContext +WindowCreate +MakeCurrent +DestroyContext +Begin +End +TexImage1D +TexSubImage1D +TexImage2D +TexSubImage2D +TexImage3D +TexImage3DEXT +TexSubImage3D +ChromiumParametervCR +MultiDrawArraysEXT +MultiDrawElementsEXT +GetBufferPointervARB +GetBufferParameterivARB +MapBufferARB +UnmapBufferARB +BindBufferARB +BufferDataARB +ZPixCR +ActiveTextureARB +DrawBuffer +Flush +GetActiveAttrib +GetActiveUniform +GetAttachedShaders +GetShaderInfoLog +GetProgramInfoLog +GetShaderSource +GetAttachedObjectsARB +GetInfoLogARB +BufferSubDataARB +EnableVertexAttribArrayARB +DisableVertexAttribArrayARB +GetBufferSubDataARB +IsEnabled +LockArraysEXT +UnlockArraysEXT +CreateProgram +LinkProgram +DeleteProgram +GetUniformLocation +GetAttribLocation +GetUniformsLocations +GetAttribsLocations +BindFramebufferEXT +DeleteObjectARB +BindFramebufferEXT +DeleteFramebuffersEXT +FramebufferTexture1DEXT +FramebufferTexture2DEXT +FramebufferTexture3DEXT +FramebufferRenderbufferEXT +CheckFramebufferStatusEXT +BindTexture +DeleteTextures +GetPolygonStipple +GetPixelMapfv +GetPixelMapuiv +GetPixelMapusv +GetCompressedTexImageARB +BindRenderbufferEXT +VBoxPackSetInjectThread +VBoxPackGetInjectID +VBoxPackSetInjectID +VBoxAttachThread +VBoxDetachThread +VBoxCreateContext +VBoxConChromiumParameteriCR +VBoxConChromiumParametervCR +VBoxWindowCreate +VBoxWindowDestroy +VBoxConCreate +VBoxConDestroy +VBoxConFlush +VBoxPresentComposition +ChromiumParameteriCR +CompressedTexImage1DARB +CompressedTexImage2DARB +CompressedTexImage3DARB +CompressedTexSubImage1DARB +CompressedTexSubImage2DARB +CompressedTexSubImage3DARB +GenFramebuffersEXT +GenRenderbuffersEXT +DeleteFramebuffersEXT +DeleteRenderbuffersEXT +GenBuffersARB +DeleteBuffersARB +StringMarkerGREMEDY +GenTextures +CompileShader +NewList +EndList +GetError diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_swapbuf.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_swapbuf.c new file mode 100644 index 00000000..bc1895fe --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_swapbuf.c @@ -0,0 +1,102 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_packfunctions.h" +#include "cr_error.h" +#include "cr_net.h" +#include "packspu.h" +#include "packspu_proto.h" + +#if 0 + +void PACKSPU_APIENTRY packspu_SwapBuffers( GLint window, GLint flags ) +{ + GET_THREAD(thread); + if (pack_spu.swap) + { + crPackSwapBuffersSWAP( window, flags ); + } + else + { + crPackSwapBuffers( window, flags ); + } + packspuFlush( (void *) thread ); +} + + +#else + +void PACKSPU_APIENTRY packspu_SwapBuffers( GLint window, GLint flags ) +{ + GET_THREAD(thread); + + if (pack_spu.swap) + { + crPackSwapBuffersSWAP( window, flags ); + } + else + { + crPackSwapBuffers( window, flags ); + } + packspuFlush( (void *) thread ); + + if (!(thread->netServer.conn->actual_network)) + { + /* no synchronization needed */ + return; + } + + if (pack_spu.swapbuffer_sync) { + /* This won't block unless there has been more than 1 frame + * since we received a writeback acknowledgement. In the + * normal case there's no performance penalty for doing this + * (beyond the cost of packing the writeback request into the + * stream and receiving the reply), but it eliminates the + * problem of runaway rendering that can occur, eg when + * rendering frames consisting of a single large display list + * in a tight loop. + * + * Note that this is *not* the same as doing a sync after each + * swapbuffers, which would force a round-trip 'bubble' into + * the network stream under normal conditions. + * + * This is complicated because writeback in the pack spu is + * overridden to always set the value to zero when the + * reply is received, rather than decrementing it: + */ + switch( thread->writeback ) { + case 0: + /* Request writeback. + */ + thread->writeback = 1; + if (pack_spu.swap) + { + crPackWritebackSWAP( (GLint *) &thread->writeback ); + } + else + { + crPackWriteback( (GLint *) &thread->writeback ); + } + break; + case 1: + /* Make sure writeback from previous frame has been received. + */ + CRPACKSPU_WRITEBACK_WAIT(thread, thread->writeback); + break; + } + } + + /* want to emit a parameter here */ + if (pack_spu.emit_GATHER_POST_SWAPBUFFERS) + { + if (pack_spu.swap) + crPackChromiumParameteriCRSWAP(GL_GATHER_POST_SWAPBUFFERS_CR, 1); + else + crPackChromiumParameteriCR(GL_GATHER_POST_SWAPBUFFERS_CR, 1); + } +} + +#endif diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_texture.c b/src/VBox/Additions/common/crOpenGL/pack/packspu_texture.c new file mode 100644 index 00000000..5057bcb3 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_texture.c @@ -0,0 +1,70 @@ +/* $Id: packspu_texture.c $ */ + +/** @file + * VBox OpenGL DRI driver functions + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "packspu.h" +#include "cr_packfunctions.h" +#include "cr_glstate.h" +#include "packspu_proto.h" + +void PACKSPU_APIENTRY packspu_ActiveTextureARB(GLenum texture) +{ + crStateActiveTextureARB(texture); + crPackActiveTextureARB(texture); +} + +void PACKSPU_APIENTRY packspu_BindTexture(GLenum target, GLuint texture) +{ + crStateBindTexture(target, texture); + crPackBindTexture(target, texture); +} + +void PACKSPU_APIENTRY packspu_DeleteTextures(GLsizei n, const GLuint * textures) +{ + crStateDeleteTextures(n, textures); + crPackDeleteTextures(n, textures); +} + +void PACKSPU_APIENTRY packspu_GenTextures( GLsizei n, GLuint * textures ) +{ + GET_THREAD(thread); + int writeback = 1; + unsigned int i; + if (!CRPACKSPU_IS_WDDM_CRHGSMI() && !(pack_spu.thread[pack_spu.idxThreadInUse].netServer.conn->actual_network)) + { + crError( "packspu_GenTextures doesn't work when there's no actual network involved!\nTry using the simplequery SPU in your chain!" ); + } + if (pack_spu.swap) + { + crPackGenTexturesSWAP( n, textures, &writeback ); + } + else + { + crPackGenTextures( n, textures, &writeback ); + } + packspuFlush( (void *) thread ); + CRPACKSPU_WRITEBACK_WAIT(thread, writeback); + if (pack_spu.swap) + { + for (i = 0 ; i < (unsigned int) n ; i++) + { + textures[i] = SWAP32(textures[i]); + } + } + + crStateRegTextures(n, textures); +} diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_unimplemented_special b/src/VBox/Additions/common/crOpenGL/pack/packspu_unimplemented_special new file mode 100644 index 00000000..f71300c9 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_unimplemented_special @@ -0,0 +1,4 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. diff --git a/src/VBox/Additions/common/crOpenGL/pack/packspu_vertex_special b/src/VBox/Additions/common/crOpenGL/pack/packspu_vertex_special new file mode 100644 index 00000000..bbbc13d0 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/pack/packspu_vertex_special @@ -0,0 +1,28 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. +Vertex2d +Vertex2dv +Vertex2f +Vertex2fv +Vertex2i +Vertex2iv +Vertex2s +Vertex2sv +Vertex3d +Vertex3dv +Vertex3f +Vertex3fv +Vertex3i +Vertex3iv +Vertex3s +Vertex3sv +Vertex4d +Vertex4dv +Vertex4f +Vertex4fv +Vertex4i +Vertex4iv +Vertex4s +Vertex4sv diff --git a/src/VBox/Additions/common/crOpenGL/passthrough/Makefile.kup b/src/VBox/Additions/common/crOpenGL/passthrough/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/passthrough/Makefile.kup diff --git a/src/VBox/Additions/common/crOpenGL/passthrough/passthrough.def b/src/VBox/Additions/common/crOpenGL/passthrough/passthrough.def new file mode 100644 index 00000000..9edc7163 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/passthrough/passthrough.def @@ -0,0 +1,6 @@ +; Copyright (c) 2001, Stanford University +; All rights reserved. +; +; See the file LICENSE.txt for information on redistributing this software. +EXPORTS +SPULoad diff --git a/src/VBox/Additions/common/crOpenGL/passthrough/passthrough.py b/src/VBox/Additions/common/crOpenGL/passthrough/passthrough.py new file mode 100755 index 00000000..ec7e27c7 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/passthrough/passthrough.py @@ -0,0 +1,40 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + + +apiutil.CopyrightC() + +print("""#include <stdio.h> +#include "cr_error.h" +#include "cr_string.h" +#include "cr_spu.h" +#include "passthroughspu.h" +""") + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + + +print('SPUNamedFunctionTable _cr_passthrough_table[%d];' % ( len(keys) + 1 )) + +print(""" +static void __fillin(int offset, char *name, SPUGenericFunction func) +{ + _cr_passthrough_table[offset].name = crStrdup(name); + _cr_passthrough_table[offset].fn = func; +} + +void BuildPassthroughTable( SPU *child ) +{""") + +for index in range(len(keys)): + func_name = keys[index] + print('\t__fillin(%3d, "%s", (SPUGenericFunction) child->dispatch_table.%s);' % (index, func_name, func_name )) +print('\t__fillin(%3d, NULL, NULL);' % len(keys)) +print('}') diff --git a/src/VBox/Additions/common/crOpenGL/passthrough/passthroughspu.h b/src/VBox/Additions/common/crOpenGL/passthrough/passthroughspu.h new file mode 100644 index 00000000..a0e6b942 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/passthrough/passthroughspu.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#ifndef GA_INCLUDED_SRC_common_crOpenGL_passthrough_passthroughspu_h +#define GA_INCLUDED_SRC_common_crOpenGL_passthrough_passthroughspu_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +#include "cr_spu.h" + + +extern SPUNamedFunctionTable _cr_passthrough_table[]; +extern void BuildPassthroughTable( SPU *child ); + + +#endif /* !GA_INCLUDED_SRC_common_crOpenGL_passthrough_passthroughspu_h */ diff --git a/src/VBox/Additions/common/crOpenGL/passthrough/passthroughspu.rc b/src/VBox/Additions/common/crOpenGL/passthrough/passthroughspu.rc new file mode 100644 index 00000000..ad2034e5 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/passthrough/passthroughspu.rc @@ -0,0 +1,69 @@ +/* $Id: passthroughspu.rc $ */ +/** @file + * VBoxOGLpassthroughspu - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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_DRV + FILESUBTYPE VFT2_DRV_DISPLAY +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "VirtualBox crOpenGL ICD\0" + VALUE "InternalName", "VBoxOGLpassthroughspu\0" +#ifdef VBOX_WDDM_WOW64 + VALUE "OriginalFilename", "VBoxOGLpassthroughspu-x86.dll\0" +#else + VALUE "OriginalFilename", "VBoxOGLpassthroughspu.dll\0" +#endif + 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 + +1 RCDATA +BEGIN +// Machine dependent parameters + 17, // Height of vertical thumb + 17, // Width of horizontal thumb + 2, // Icon horiz compression factor + 2, // Icon vert compression factor + 1, // Cursor horz compression factor + 1, // Cursor vert compression factor + 0, // Kanji window height + 1, // cxBorder (thickness of vertical lines) + 1 // cyBorder (thickness of horizontal lines) +END diff --git a/src/VBox/Additions/common/crOpenGL/passthrough/passthroughspu_init.c b/src/VBox/Additions/common/crOpenGL/passthrough/passthroughspu_init.c new file mode 100644 index 00000000..c08a17a4 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/passthrough/passthroughspu_init.c @@ -0,0 +1,64 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_error.h" +#include "passthroughspu.h" + +static SPUFunctions passthrough_functions = { + NULL, /* CHILD COPY */ + NULL, /* DATA */ + _cr_passthrough_table /* THE ACTUAL FUNCTIONS */ +}; + +static SPUFunctions * +passthroughSPUInit( int id, SPU *child, SPU *self, + unsigned int context_id, + unsigned int num_contexts ) +{ + (void) id; + (void) self; + (void) context_id; + (void) num_contexts; + + if (child == NULL) + { + crError( "You can't load the passthrough SPU as the last SPU in a chain!" ); + } + BuildPassthroughTable( child ); + return &passthrough_functions; +} + +static void +passthroughSPUSelfDispatch(SPUDispatchTable *parent) +{ + (void)parent; +} + +static int +passthroughSPUCleanup(void) +{ + return 1; +} + +static SPUOptions passthroughSPUOptions[] = { + { NULL, CR_BOOL, 0, NULL, NULL, NULL, NULL, NULL }, +}; + + +int SPULoad( char **name, char **super, SPUInitFuncPtr *init, + SPUSelfDispatchFuncPtr *self, SPUCleanupFuncPtr *cleanup, + SPUOptionsPtr *options, int *flags ) +{ + *name = "passthrough"; + *super = NULL; + *init = passthroughSPUInit; + *self = passthroughSPUSelfDispatch; + *cleanup = passthroughSPUCleanup; + *options = passthroughSPUOptions; + *flags = (SPU_NO_PACKER|SPU_NOT_TERMINAL|SPU_MAX_SERVERS_ZERO); + + return 1; +} diff --git a/src/VBox/Additions/common/crOpenGL/stub.c b/src/VBox/Additions/common/crOpenGL/stub.c new file mode 100644 index 00000000..f5369c6e --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/stub.c @@ -0,0 +1,548 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_spu.h" +#include "cr_error.h" +#include "cr_mem.h" +#include "stub.h" +#include <iprt/thread.h> + +#ifdef GLX +Display* stubGetWindowDisplay(WindowInfo *pWindow) +{ +#if defined(CR_NEWWINTRACK) + if ((NIL_RTTHREAD!=stub.hSyncThread) && (RTThreadNativeSelf()==RTThreadGetNative(stub.hSyncThread))) + { + if (pWindow && pWindow->dpy && !pWindow->syncDpy) + { + crDebug("going to XOpenDisplay(%s)", pWindow->dpyName); + pWindow->syncDpy = XOpenDisplay(pWindow->dpyName); + if (!pWindow->syncDpy) + { + crWarning("Failed to open display %s", pWindow->dpyName); + } + return pWindow->syncDpy; + } + else + { + return pWindow ? pWindow->syncDpy:NULL; + } + } + else +#endif + { + return pWindow ? pWindow->dpy:NULL; + } +} +#endif + +/** + * Returns -1 on error + */ +GLint APIENTRY crCreateContext(char *dpyName, GLint visBits) +{ + ContextInfo *context; + stubInit(); + /* XXX in Chromium 1.5 and earlier, the last parameter was UNDECIDED. + * That didn't seem right so it was changed to CHROMIUM. (Brian) + */ + context = stubNewContext(dpyName, visBits, CHROMIUM, 0 +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , NULL +#endif + ); + return context ? (int) context->id : -1; +} + +void APIENTRY crDestroyContext( GLint context ) +{ + stubDestroyContext(context); +} + +void APIENTRY crMakeCurrent( GLint window, GLint context ) +{ + WindowInfo *winInfo = (WindowInfo *) + crHashtableSearch(stub.windowTable, (unsigned int) window); + ContextInfo *contextInfo = (ContextInfo *) + crHashtableSearch(stub.contextTable, context); + if (contextInfo && contextInfo->type == NATIVE) { + crWarning("Can't call crMakeCurrent with native GL context"); + return; + } + + stubMakeCurrent(winInfo, contextInfo); +} + +GLint APIENTRY crGetCurrentContext( void ) +{ + ContextInfo *context; + stubInit(); + context = stubGetCurrentContext(); + if (context) + return (GLint) context->id; + else + return 0; +} + +GLint APIENTRY crGetCurrentWindow( void ) +{ + ContextInfo *context; + stubInit(); + context = stubGetCurrentContext(); + if (context && context->currentDrawable) + return context->currentDrawable->spuWindow; + else + return -1; +} + +void APIENTRY crSwapBuffers( GLint window, GLint flags ) +{ + WindowInfo *winInfo = (WindowInfo *) + crHashtableSearch(stub.windowTable, (unsigned int) window); + if (winInfo) + stubSwapBuffers(winInfo, flags); +} + +/** + * Returns -1 on error + */ +GLint APIENTRY crWindowCreate( const char *dpyName, GLint visBits ) +{ + stubInit(); + return stubNewWindow( dpyName, visBits ); +} + +void APIENTRY crWindowDestroy( GLint window ) +{ + stubDestroyWindow( 0, window ); +} + +void APIENTRY crWindowSize( GLint window, GLint w, GLint h ) +{ + const WindowInfo *winInfo = (const WindowInfo *) + crHashtableSearch(stub.windowTable, (unsigned int) window); + if (winInfo && winInfo->type == CHROMIUM) + { + crDebug("Dispatched crWindowSize (%i)", window); + stub.spu->dispatch_table.WindowSize( window, w, h ); + } +} + +void APIENTRY crWindowPosition( GLint window, GLint x, GLint y ) +{ + const WindowInfo *winInfo = (const WindowInfo *) + crHashtableSearch(stub.windowTable, (unsigned int) window); + if (winInfo && winInfo->type == CHROMIUM) + { + crDebug("Dispatched crWindowPosition (%i)", window); + stub.spu->dispatch_table.WindowPosition( window, x, y ); + } +} + +void APIENTRY crWindowVisibleRegion( GLint window, GLint cRects, const void *pRects ) +{ + const WindowInfo *winInfo = (const WindowInfo *) + crHashtableSearch(stub.windowTable, (unsigned int) window); + if (winInfo && winInfo->type == CHROMIUM) + { + crDebug("Dispatched crWindowVisibleRegion (%i, cRects=%i)", window, cRects); + stub.spu->dispatch_table.WindowVisibleRegion( window, cRects, pRects ); + } +} + +void APIENTRY crVBoxTexPresent(GLuint texture, GLuint cfg, GLint xPos, GLint yPos, GLint cRects, const GLint *pRects) +{ + RT_NOREF(texture, cfg, xPos, yPos, cRects, pRects); + crError("not expected!"); +} + +void APIENTRY crWindowShow( GLint window, GLint flag ) +{ + WindowInfo *winInfo = (WindowInfo *) + crHashtableSearch(stub.windowTable, (unsigned int) window); + if (winInfo && winInfo->type == CHROMIUM) + stub.spu->dispatch_table.WindowShow( window, flag ); + winInfo->mapped = flag ? GL_TRUE : GL_FALSE; +} + +void APIENTRY stub_GetChromiumParametervCR( GLenum target, GLuint index, GLenum type, GLsizei count, GLvoid *values ) +{ + char **ret; + switch( target ) + { + case GL_HEAD_SPU_NAME_CR: + ret = (char **) values; + *ret = stub.spu->name; + return; + default: + stub.spu->dispatch_table.GetChromiumParametervCR( target, index, type, count, values ); + break; + } +} + +/* + * Updates geometry info for given spu window. + * Returns GL_TRUE if it changed since last call, GL_FALSE otherwise. + * bForceUpdate - forces dispatching of geometry info even if it's unchanged + */ +GLboolean stubUpdateWindowGeometry(WindowInfo *pWindow, GLboolean bForceUpdate) +{ + int winX, winY; + unsigned int winW, winH; + GLboolean res = GL_FALSE; + + CRASSERT(pWindow); + + stubGetWindowGeometry(pWindow, &winX, &winY, &winW, &winH); + + /** @todo remove "if (winW && winH)"?*/ + if (winW && winH) { + if (stub.trackWindowSize) { + if (bForceUpdate || winW != pWindow->width || winH != pWindow->height) { + crDebug("Dispatched WindowSize (%i)", pWindow->spuWindow); +#ifdef VBOX_WITH_WDDM + if (!stub.bRunningUnderWDDM || pWindow->mapped) +#endif + { + stub.spuDispatch.WindowSize(pWindow->spuWindow, winW, winH); + } + pWindow->width = winW; + pWindow->height = winH; + res = GL_TRUE; + } + } + if (stub.trackWindowPos) { + if (bForceUpdate || winX != pWindow->x || winY != pWindow->y) { + crDebug("Dispatched WindowPosition (%i)", pWindow->spuWindow); +#ifdef VBOX_WITH_WDDM + if (!stub.bRunningUnderWDDM || pWindow->mapped) +#endif + { + stub.spuDispatch.WindowPosition(pWindow->spuWindow, winX, winY); + } + pWindow->x = winX; + pWindow->y = winY; + res = GL_TRUE; + } + } + } + + return res; +} + +#ifdef WINDOWS +/* + * Updates visible regions for given spu window. + * Returns GL_TRUE if regions changed since last call, GL_FALSE otherwise. + */ +GLboolean stubUpdateWindowVisibileRegions(WindowInfo *pWindow) +{ + HRGN hVisRgn; + HWND hwnd; + DWORD dwCount; + LPRGNDATA lpRgnData; + POINT pt; + int iret; + + if (!pWindow) return GL_FALSE; + hwnd = pWindow->hWnd; + if (!hwnd) return GL_FALSE; + + if (hwnd!=WindowFromDC(pWindow->drawable)) + { + crWarning("Window(%i) DC is no longer valid", pWindow->spuWindow); + return GL_FALSE; + } + + hVisRgn = CreateRectRgn(0,0,0,0); + iret = GetRandomRgn(pWindow->drawable, hVisRgn, SYSRGN); + + if (iret==1) + { + /** @todo check win95/win98 here, as rects should be already in client space there*/ + /* Convert screen related rectangles to client related rectangles */ + pt.x = 0; + pt.y = 0; + ScreenToClient(hwnd, &pt); + OffsetRgn(hVisRgn, pt.x, pt.y); + + /* + dwCount = GetRegionData(hVisRgn, 0, NULL); + lpRgnData = crAlloc(dwCount); + crDebug("GetRandomRgn returned 1, dwCount=%d", dwCount); + GetRegionData(hVisRgn, dwCount, lpRgnData); + crDebug("Region consists of %d rects", lpRgnData->rdh.nCount); + + pRects = (RECT*) lpRgnData->Buffer; + for (i=0; i<lpRgnData->rdh.nCount; ++i) + { + crDebug("Rgn[%d] = (%d, %d, %d, %d)", i, pRects[i].left, pRects[i].top, pRects[i].right, pRects[i].bottom); + } + crFree(lpRgnData); + */ + + if (pWindow->hVisibleRegion==INVALID_HANDLE_VALUE + || !EqualRgn(pWindow->hVisibleRegion, hVisRgn)) + { + DeleteObject(pWindow->hVisibleRegion); + pWindow->hVisibleRegion = hVisRgn; + + dwCount = GetRegionData(hVisRgn, 0, NULL); + lpRgnData = crAlloc(dwCount); + + if (lpRgnData) + { + GetRegionData(hVisRgn, dwCount, lpRgnData); + crDebug("Dispatched WindowVisibleRegion (%i, cRects=%i)", pWindow->spuWindow, lpRgnData->rdh.nCount); + stub.spuDispatch.WindowVisibleRegion(pWindow->spuWindow, lpRgnData->rdh.nCount, (GLint*) lpRgnData->Buffer); + crFree(lpRgnData); + return GL_TRUE; + } + else crWarning("GetRegionData failed, VisibleRegions update failed"); + } + else + { + DeleteObject(hVisRgn); + } + } + else + { + crWarning("GetRandomRgn returned (%d) instead of (1), VisibleRegions update failed", iret); + DeleteObject(hVisRgn); + } + + return GL_FALSE; +} + +# ifndef CR_NEWWINTRACK +static void stubCBCheckWindowsInfo(unsigned long key, void *data1, void *data2) +{ + WindowInfo *winInfo = (WindowInfo *) data1; + CWPRETSTRUCT *pMsgInfo = (PCWPRETSTRUCT) data2; + + (void) key; + + if (winInfo && pMsgInfo && winInfo->type == CHROMIUM) + { + switch (pMsgInfo->message) + { + case WM_MOVING: + case WM_SIZING: + case WM_MOVE: + case WM_CREATE: + case WM_SIZE: + { + GLboolean changed = stub.trackWindowVisibleRgn && stubUpdateWindowVisibileRegions(winInfo); + + if (stubUpdateWindowGeometry(winInfo, GL_FALSE) || changed) + { + stubForcedFlush(0); + } + break; + } + + case WM_SHOWWINDOW: + case WM_ACTIVATEAPP: + case WM_PAINT: + case WM_NCPAINT: + case WM_NCACTIVATE: + case WM_ERASEBKGND: + { + if (stub.trackWindowVisibleRgn && stubUpdateWindowVisibileRegions(winInfo)) + { + stubForcedFlush(0); + } + break; + } + + default: + { + if (stub.trackWindowVisibleRgn && stubUpdateWindowVisibileRegions(winInfo)) + { + crDebug("Visibility info updated due to unknown hooked message (%d)", pMsgInfo->message); + stubForcedFlush(0); + } + break; + } + } + } +} + +LRESULT CALLBACK stubCBWindowMessageHookProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + CWPRETSTRUCT *pMsgInfo = (PCWPRETSTRUCT) lParam; + + if (nCode>=0 && pMsgInfo) + { + switch (pMsgInfo->message) + { + case WM_MOVING: + case WM_SIZING: + case WM_MOVE: + case WM_ACTIVATEAPP: + case WM_NCPAINT: + case WM_NCACTIVATE: + case WM_ERASEBKGND: + case WM_CREATE: + case WM_SIZE: + case WM_SHOWWINDOW: + { + crHashtableWalk(stub.windowTable, stubCBCheckWindowsInfo, (void *) lParam); + break; + } + + /** @todo remove it*/ + default: + { + /*crDebug("hook: unknown message (%d)", pMsgInfo->message);*/ + crHashtableWalk(stub.windowTable, stubCBCheckWindowsInfo, (void *) lParam); + break; + } + } + } + + return CallNextHookEx(stub.hMessageHook, nCode, wParam, lParam); +} + +void stubInstallWindowMessageHook() +{ + stub.hMessageHook = SetWindowsHookEx(WH_CALLWNDPROCRET, stubCBWindowMessageHookProc, 0, crThreadID()); + + if (!stub.hMessageHook) + crWarning("Window message hook install failed! (not fatal)"); +} + +void stubUninstallWindowMessageHook() +{ + if (stub.hMessageHook) + UnhookWindowsHookEx(stub.hMessageHook); +} +# endif /*# ifndef CR_NEWWINTRACK*/ + +#elif defined(GLX) //#ifdef WINDOWS +void stubCheckXExtensions(WindowInfo *pWindow) +{ + int evb, erb, vmi=0, vma=0; + Display *dpy = stubGetWindowDisplay(pWindow); + + stub.bXExtensionsChecked = GL_TRUE; + stub.trackWindowVisibleRgn = 0; + + XLOCK(dpy); + if (XCompositeQueryExtension(dpy, &evb, &erb) + && XCompositeQueryVersion(dpy, &vma, &vmi) + && (vma>0 || vmi>=4)) + { + stub.bHaveXComposite = GL_TRUE; + crDebug("XComposite %i.%i", vma, vmi); + vma=0; + vmi=0; + if (XFixesQueryExtension(dpy, &evb, &erb) + && XFixesQueryVersion(dpy, &vma, &vmi) + && vma>=2) + { + crDebug("XFixes %i.%i", vma, vmi); + stub.bHaveXFixes = GL_TRUE; + stub.trackWindowVisibleRgn = 1; + XUNLOCK(dpy); + return; + } + else + { + crWarning("XFixes not found or old version (%i.%i), no VisibilityTracking", vma, vmi); + } + } + else + { + crWarning("XComposite not found or old version (%i.%i), no VisibilityTracking", vma, vmi); + } + XUNLOCK(dpy); + return; +} + +/* + * Updates visible regions for given spu window. + * Returns GL_TRUE if regions changed since last call, GL_FALSE otherwise. + */ +GLboolean stubUpdateWindowVisibileRegions(WindowInfo *pWindow) +{ + XserverRegion xreg; + int cRects, i; + XRectangle *pXRects; + GLint* pGLRects; + Display *dpy; + bool bNoUpdate = false; + + if (!stub.bXExtensionsChecked) + { + stubCheckXExtensions(pWindow); + if (!stub.trackWindowVisibleRgn) + { + return GL_FALSE; + } + } + + dpy = stubGetWindowDisplay(pWindow); + + /** @todo see comment regarding size/position updates and XSync, same applies to those functions but + * it seems there's no way to get even based updates for this. Or I've failed to find the appropriate extension. + */ + XLOCK(dpy); + xreg = XCompositeCreateRegionFromBorderClip(dpy, pWindow->drawable); + pXRects = XFixesFetchRegion(dpy, xreg, &cRects); + XFixesDestroyRegion(dpy, xreg); + XUNLOCK(dpy); + + /* Check for compiz main window */ + if (!pWindow->pVisibleRegions && !cRects) + { + bNoUpdate = true; + } + + if (!bNoUpdate + && (!pWindow->pVisibleRegions + || pWindow->cVisibleRegions!=cRects + || (pWindow->pVisibleRegions && crMemcmp(pWindow->pVisibleRegions, pXRects, cRects * sizeof(XRectangle))))) + { + if (pWindow->pVisibleRegions) + { + XFree(pWindow->pVisibleRegions); + } + + pWindow->pVisibleRegions = pXRects; + pWindow->cVisibleRegions = cRects; + + pGLRects = crAlloc(cRects ? 4*cRects*sizeof(GLint) : 4*sizeof(GLint)); + if (!pGLRects) + { + crWarning("stubUpdateWindowVisibileRegions: failed to allocate %lu bytes", + (unsigned long)(4*cRects*sizeof(GLint))); + return GL_FALSE; + } + + //crDebug("Got %i rects.", cRects); + for (i=0; i<cRects; ++i) + { + pGLRects[4*i+0] = pXRects[i].x; + pGLRects[4*i+1] = pXRects[i].y; + pGLRects[4*i+2] = pXRects[i].x+pXRects[i].width; + pGLRects[4*i+3] = pXRects[i].y+pXRects[i].height; + //crDebug("Rect[%i]=(%i,%i,%i,%i)", i, pGLRects[4*i+0], pGLRects[4*i+1], pGLRects[4*i+2], pGLRects[4*i+3]); + } + + crDebug("Dispatched WindowVisibleRegion (%i, cRects=%i)", pWindow->spuWindow, cRects); + stub.spuDispatch.WindowVisibleRegion(pWindow->spuWindow, cRects, pGLRects); + crFree(pGLRects); + return GL_TRUE; + } + else + { + XFree(pXRects); + } + + return GL_FALSE; +} +#endif //#ifdef WINDOWS diff --git a/src/VBox/Additions/common/crOpenGL/stub.h b/src/VBox/Additions/common/crOpenGL/stub.h new file mode 100644 index 00000000..1dd5faa2 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/stub.h @@ -0,0 +1,370 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + + +/* + * How this all works... + * + * This directory implements three different interfaces to Chromium: + * + * 1. the Chromium interface - this is defined by the functions that start + * with the "cr" prefix and are defined in chromium.h and implemented in + * stub.c. Typically, this is used by parallel apps (like psubmit). + * + * 2. GLX emulation interface - the glX*() functions are emulated here. + * When glXCreateContext() is called we may either create a real, native + * GLX context or a Chromium context (depending on match_window_title and + * minimum_window_size). + * + * 3. WGL emulation interface - the wgl*() functions are emulated here. + * When wglCreateContext() is called we may either create a real, native + * WGL context or a Chromium context (depending on match_window_title and + * minimum_window_size). + * + * + */ + +#ifndef GA_INCLUDED_SRC_common_crOpenGL_stub_h +#define GA_INCLUDED_SRC_common_crOpenGL_stub_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "chromium.h" +#include "cr_version.h" +#include "cr_hash.h" +#include "cr_process.h" +#include "cr_spu.h" +#include "cr_threads.h" +#include "spu_dispatch_table.h" + +#ifdef GLX +#include <X11/extensions/XShm.h> +#include <sys/shm.h> +#include <X11/extensions/Xdamage.h> +#include <X11/extensions/Xcomposite.h> +#include <X11/extensions/Xfixes.h> +#endif + +#if defined(WINDOWS) || defined(Linux) || defined(SunOS) +# define CR_NEWWINTRACK +#endif + +#if !defined(CHROMIUM_THREADSAFE) && defined(CR_NEWWINTRACK) +# error CHROMIUM_THREADSAFE have to be defined +#endif + +#ifdef CHROMIUM_THREADSAFE +# include <cr_threads.h> +#endif + +#if 0 && defined(CR_NEWWINTRACK) && !defined(WINDOWS) +#define XLOCK(dpy) XLockDisplay(dpy) +#define XUNLOCK(dpy) XUnlockDisplay(dpy) +#else +#define XLOCK(dpy) +#define XUNLOCK(dpy) +#endif + +/* When we first create a rendering context we can't be sure whether + * it'll be handled by Chromium or as a native GLX/WGL context. So in + * CreateContext() we'll mark the ContextInfo object as UNDECIDED then + * switch it to either NATIVE or CHROMIUM the first time MakeCurrent() + * is called. In MakeCurrent() we can use a criteria like window size + * or window title to decide between CHROMIUM and NATIVE. + */ +typedef enum +{ + UNDECIDED, + CHROMIUM, + NATIVE +} ContextType; + +#define MAX_DPY_NAME 1000 + +typedef struct context_info_t ContextInfo; +typedef struct window_info_t WindowInfo; + +#ifdef GLX +typedef struct glxpixmap_info_t GLX_Pixmap_t; + +struct glxpixmap_info_t +{ + int x, y; + unsigned int w, h, border, depth; + GLenum format; + Window root; + GLenum target; + GC gc; + Pixmap hShmPixmap; /* Shared memory pixmap object, if it's supported*/ + Damage hDamage; /* damage xserver handle*/ + Bool bPixmapImageDirty; + Region pDamageRegion; +}; +#endif + +struct context_info_t +{ + char dpyName[MAX_DPY_NAME]; + GLint spuContext; /* returned by head SPU's CreateContext() */ + ContextType type; /* CHROMIUM, NATIVE or UNDECIDED */ + unsigned long id; /* the client-visible handle */ + GLint visBits; + WindowInfo *currentDrawable; + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + GLint spuConnection; + struct VBOXUHGSMI *pHgsmi; +#endif + +#ifdef CHROMIUM_THREADSAFE + VBOXTLSREFDATA +#endif + +#ifdef WINDOWS + HGLRC hglrc; +#elif defined(DARWIN) + ContextInfo *share; + CGLContextObj cglc; + + /* CGLContextEnable (CGLEnable, CGLDisable, and CGLIsEnabled) */ + unsigned int options; + + /* CGLContextParameter (CGLSetParameter and CGLGetParameter) */ + GLint parambits; + long swap_rect[4], swap_interval; + unsigned long client_storage; + long surf_order, surf_opacy; + + long disp_mask; +#elif defined(GLX) + Display *dpy; + ContextInfo *share; + Bool direct; + GLXContext glxContext; + CRHashTable *pGLXPixmapsHash; + Bool damageQueryFailed; + int damageEventsBase; +#endif +}; + +#ifdef DARWIN +enum { + VISBIT_SWAP_RECT, + VISBIT_SWAP_INTERVAL, + VISBIT_CLIENT_STORAGE +}; +#endif + +struct window_info_t +{ + char dpyName[MAX_DPY_NAME]; + int x, y; + unsigned int width, height; + ContextType type; + GLint spuWindow; /* returned by head SPU's WindowCreate() */ + ContextInfo *pOwner; /* ctx which created this window */ + GLboolean mapped; +#ifdef WINDOWS + HDC drawable; + HRGN hVisibleRegion; + DWORD dmPelsWidth; + DWORD dmPelsHeight; + HWND hWnd; +#elif defined(DARWIN) + CGSConnectionID connection; + CGSWindowID drawable; + CGSSurfaceID surface; +#elif defined(GLX) + Display *dpy; +# ifdef CR_NEWWINTRACK + Display *syncDpy; +# endif + GLXDrawable drawable; + XRectangle *pVisibleRegions; + GLint cVisibleRegions; +#endif +#ifdef CR_NEWWINTRACK + uint32_t u32ClientID; +#endif +}; + +/* "Global" variables for the stub library */ +typedef struct { + /* the first SPU in the SPU chain on this app node */ + SPU *spu; + + /* OpenGL/SPU dispatch tables */ + crOpenGLInterface wsInterface; + SPUDispatchTable spuDispatch; + SPUDispatchTable nativeDispatch; + GLboolean haveNativeOpenGL; + + /* config options */ + int appDrawCursor; + GLuint minChromiumWindowWidth; + GLuint minChromiumWindowHeight; + GLuint maxChromiumWindowWidth; + GLuint maxChromiumWindowHeight; + GLuint matchChromiumWindowCount; + GLuint matchChromiumWindowCounter; + GLuint *matchChromiumWindowID; + GLuint numIgnoreWindowID; + char *matchWindowTitle; + int ignoreFreeglutMenus; + int trackWindowSize; + int trackWindowPos; + int trackWindowVisibility; + int trackWindowVisibleRgn; + char *spu_dir; + int force_pbuffers; + + /* thread safety stuff */ + GLboolean threadSafe; +#ifdef CHROMIUM_THREADSAFE + CRtsd dispatchTSD; + CRmutex mutex; +#endif + + CRpid mothershipPID; + + /* contexts */ + int freeContextNumber; + CRHashTable *contextTable; +#ifndef CHROMIUM_THREADSAFE + ContextInfo *currentContext; /* may be NULL */ +#endif + + /* windows */ + CRHashTable *windowTable; + +#ifdef GLX + /* Shared memory, used to transfer XServer pixmaps data into client memory */ + XShmSegmentInfo xshmSI; + GLboolean bShmInitFailed; + + CRHashTable *pGLXPixmapsHash; + + GLboolean bXExtensionsChecked; + GLboolean bHaveXComposite; + GLboolean bHaveXFixes; +#endif + +#ifdef WINDOWS +# ifndef CR_NEWWINTRACK + HHOOK hMessageHook; +# endif +# ifdef VBOX_WITH_WDDM + bool bRunningUnderWDDM; +# endif +#endif + +#ifdef CR_NEWWINTRACK + RTTHREAD hSyncThread; + bool volatile bShutdownSyncThread; +#endif + +} Stub; + +#ifdef CHROMIUM_THREADSAFE +/* we place the g_stubCurrentContextTLS outside the Stub data because Stub data is inited by the client's call, + * while we need g_stubCurrentContextTLS the g_stubCurrentContextTLS to be valid at any time to be able to handle + * THREAD_DETACH cleanup on windows. + * Note that we can not do + * STUB_INIT_LOCK(); + * if (stub_initialized) stubSetCurrentContext(NULL); + * STUB_INIT_UNLOCK(); + * on THREAD_DETACH since it may cause deadlock, i.e. in this situation loader lock is acquired first and then the init lock, + * but since we use GetModuleFileName in crGetProcName called from stubInitLocked, the lock order might be the oposite. + * Note that GetModuleFileName acquires the loader lock. + * */ +extern CRtsd g_stubCurrentContextTSD; + +DECLINLINE(ContextInfo*) stubGetCurrentContext(void) +{ + ContextInfo* ctx; + VBoxTlsRefGetCurrentFunctional(ctx, ContextInfo, &g_stubCurrentContextTSD); + return ctx; +} +# define stubSetCurrentContext(_ctx) VBoxTlsRefSetCurrent(ContextInfo, &g_stubCurrentContextTSD, _ctx) +#else +# define stubGetCurrentContext() (stub.currentContext) +# define stubSetCurrentContext(_ctx) do { stub.currentContext = (_ctx); } while (0) +#endif + +extern Stub stub; + +extern DECLEXPORT(SPUDispatchTable) glim; +extern SPUDispatchTable stubThreadsafeDispatch; +extern DECLEXPORT(SPUDispatchTable) stubNULLDispatch; + +#if defined(GLX) || defined (WINDOWS) +extern GLboolean stubUpdateWindowVisibileRegions(WindowInfo *pWindow); +#endif + +#ifdef WINDOWS + +/* WGL versions */ +extern WindowInfo *stubGetWindowInfo( HDC drawable ); + +extern void stubInstallWindowMessageHook(); +extern void stubUninstallWindowMessageHook(); + +#elif defined(DARWIN) + +extern CGSConnectionID _CGSDefaultConnection(void); +extern OSStatus CGSGetWindowLevel( CGSConnectionID cid, CGSWindowID wid, CGWindowLevel *level ); +extern OSStatus CGSSetWindowAlpha( const CGSConnectionID cid, CGSWindowID wid, float alpha ); + +/* These don't seem to be included in the OSX glext.h ... */ +extern void glPointParameteri( GLenum pname, GLint param ); +extern void glPointParameteriv( GLenum pname, const GLint * param ); + +extern WindowInfo *stubGetWindowInfo( CGSWindowID drawable ); + +#elif defined(GLX) + +/* GLX versions */ +extern WindowInfo *stubGetWindowInfo( Display *dpy, GLXDrawable drawable ); +extern void stubUseXFont( Display *dpy, Font font, int first, int count, int listbase ); +extern Display* stubGetWindowDisplay(WindowInfo *pWindow); + +extern void stubCheckXExtensions(WindowInfo *pWindow); +#endif + + +extern ContextInfo *stubNewContext(char *dpyName, GLint visBits, ContextType type, unsigned long shareCtx +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , struct VBOXUHGSMI *pHgsmi +#endif + ); +extern void stubConChromiumParameteriCR(GLint con, GLenum param, GLint value); +extern void stubConChromiumParametervCR(GLint con, GLenum target, GLenum type, GLsizei count, const GLvoid *values); +extern GLboolean stubCtxCreate(ContextInfo *context); +extern GLboolean stubCtxCheckCreate(ContextInfo *context); +extern void stubDestroyContext( unsigned long contextId ); +extern GLboolean stubMakeCurrent( WindowInfo *window, ContextInfo *context ); +extern GLint stubNewWindow( const char *dpyName, GLint visBits ); +extern void stubDestroyWindow( GLint con, GLint window ); +extern void stubSwapBuffers(WindowInfo *window, GLint flags); +extern void stubGetWindowGeometry(WindowInfo *win, int *x, int *y, unsigned int *w, unsigned int *h); +extern GLboolean stubUpdateWindowGeometry(WindowInfo *pWindow, GLboolean bForceUpdate); +extern GLboolean stubIsWindowVisible(WindowInfo *win); +extern bool stubInit(void); + +extern void stubForcedFlush(GLint con); +extern void stubConFlush(GLint con); +extern void APIENTRY stub_GetChromiumParametervCR( GLenum target, GLuint index, GLenum type, GLsizei count, GLvoid *values ); + +extern void APIENTRY glBoundsInfoCR(const CRrecti *, const GLbyte *, GLint, GLint); + +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) +# define CR_CTX_CON(_pCtx) ((_pCtx)->spuConnection) +#else +# define CR_CTX_CON(_pCtx) (0) +#endif + +#endif /* !GA_INCLUDED_SRC_common_crOpenGL_stub_h */ diff --git a/src/VBox/Additions/common/crOpenGL/stub_common.py b/src/VBox/Additions/common/crOpenGL/stub_common.py new file mode 100755 index 00000000..05127343 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/stub_common.py @@ -0,0 +1,282 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys +curver = sys.version_info[0] + sys.version_info[1]/10.0 +if curver < 2.2: + print("Your python is version %g. Chromium requires at least"%(curver), file=sys.stderr) + print("version 2.2. Please upgrade your python installation.", file=sys.stderr) + sys.exit(1) + +import string; +import re; + +def CopyrightC( ): + print("""/* Copyright (c) 2001, Stanford University + All rights reserved. + + See the file LICENSE.txt for information on redistributing this software. */ + """) + +def CopyrightDef( ): + print("""; Copyright (c) 2001, Stanford University + ; All rights reserved. + ; + ; See the file LICENSE.txt for information on redistributing this software. + """) + +def DecoderName( glName ): + return "crUnpack" + glName + +def OpcodeName( glName ): + # This is a bit of a hack. We want to implement the glVertexAttrib*NV + # functions in terms of the glVertexAttrib*ARB opcodes. + m = re.search( "VertexAttrib([1234](ub|b|us|s|ui|i|f|d|)v?)NV", glName ) + if m: + dataType = m.group(1) + if dataType == "4ub": + dataType = "4Nub" + elif dataType == "4ubv": + dataType = "4Nubv" + glName = "VertexAttrib" + dataType + "ARB" + return "CR_" + string.upper( glName ) + "_OPCODE" + +def ExtendedOpcodeName( glName ): + return "CR_" + string.upper( glName ) + "_EXTEND_OPCODE" + +def PackFunction( glName ): + return "crPack" + glName + +def DoPackFunctionMapping( glName ): + return "__glpack_" + glName + +def DoStateFunctionMapping( glName ): + return "__glstate_" + glName + +def DoImmediateMapping( glName ): + return "__glim_" + glName + + + +# Annotations are a generalization of Specials (below). +# Each line of an annotation file is a set of words. +# The first word is a key; the remainder are all annotations +# for that key. This is a useful model for grouping keys +# (like GL function names) into overlapping subsets, all in +# a single file. +annotations = {} +def LoadAnnotations(filename): + table = {} + try: + f = open(filename, "r") + except: + annotations[filename] = {} + return {} + + for line in f.readlines(): + line = line.strip() + if line == "" or line[0] == '#': + continue + subtable = {} + words = line.split() + for word in words[1:]: + subtable[word] = 1 + table[words[0]] = subtable + + annotations[filename] = table + return table + +def GetAnnotations( filename, key ): + table = {} + try: + table = annotations[filename] + except KeyError: + table = LoadAnnotations(filename) + + try: + subtable = table[key] + except KeyError: + return [] + + return sorted(subtable.keys()) + +def FindAnnotation( filename, key, subkey ): + table = {} + try: + table = annotations[filename] + except KeyError: + table = LoadAnnotations(filename) + + try: + subtable = table[key] + except KeyError: + return 0 + + try: + return subtable[subkey] + except KeyError: + return 0 + + + +specials = {} + +def LoadSpecials( filename ): + table = {} + try: + f = open( filename, "r" ) + except: + specials[filename] = {} + return {} + + for line in f.readlines(): + line = string.strip(line) + if line == "" or line[0] == '#': + continue + table[line] = 1 + + specials[filename] = table + return table + +def FindSpecial( table_file, glName ): + table = {} + filename = table_file + "_special" + try: + table = specials[filename] + except KeyError: + table = LoadSpecials( filename ) + + try: + if (table[glName] == 1): + return 1 + else: + return 0 #should never happen + except KeyError: + return 0 + +def AllSpecials( table_file ): + table = {} + filename = table_file + "_special" + try: + table = specials[filename] + except KeyError: + table = LoadSpecials( filename ) + + return sorted(table.keys()) + +def AllSpecials( table_file ): + filename = table_file + "_special" + + table = {} + try: + table = specials[filename] + except KeyError: + table = LoadSpecials(filename) + + return sorted(table.keys()) + +def NumSpecials( table_file ): + filename = table_file + "_special" + + table = {} + try: + table = specials[filename] + except KeyError: + table = LoadSpecials(filename) + + return len(table.keys()) + +lengths = {} +lengths['GLbyte'] = 1 +lengths['GLubyte'] = 1 +lengths['GLshort'] = 2 +lengths['GLushort'] = 2 +lengths['GLint'] = 4 +lengths['GLuint'] = 4 +lengths['GLfloat'] = 4 +lengths['GLclampf'] = 4 +lengths['GLdouble'] = 8 +lengths['GLclampd'] = 8 + +lengths['GLenum'] = 4 +lengths['GLboolean'] = 1 +lengths['GLsizei'] = 4 +lengths['GLbitfield'] = 4 + +lengths['void'] = 0 +lengths['int'] = 4 + +align_types = 1 + +def FixAlignment( pos, alignment ): + # if we want double-alignment take word-alignment instead, + # yes, this is super-lame, but we know what we are doing + if alignment > 4: + alignment = 4 + if align_types and alignment and ( pos % alignment ): + pos += alignment - ( pos % alignment ) + return pos + +def WordAlign( pos ): + return FixAlignment( pos, 4 ) + +def PointerSize(): + return 8 # Leave room for a 64 bit pointer + +def PacketLength( arg_types ): + len = 0 + for arg in arg_types: + if string.find( arg, '*') != -1: + size = PointerSize() + else: + temp_arg = re.sub("const ", "", arg) + size = lengths[temp_arg] + len = FixAlignment( len, size ) + size + len = WordAlign( len ) + return len + +def InternalArgumentString( arg_names, arg_types ): + """Return string of C-style function declaration arguments.""" + output = '' + for index in range(0,len(arg_names)): + if len(arg_names) != 1 and arg_names[index] == '': + continue + output += arg_types[index] + if arg_types[index][-1:] != '*' and arg_names[index] != '': + output += " "; + output += arg_names[index] + if index != len(arg_names) - 1: + output += ", " + return output + +def ArgumentString( arg_names, arg_types ): + """Return InternalArgumentString inside parenthesis.""" + output = '( ' + InternalArgumentString(arg_names, arg_types) + ' )' + return output + +def InternalCallString( arg_names ): + output = '' + for index in range(0,len(arg_names)): + output += arg_names[index] + if arg_names[index] != '' and index != len(arg_names) - 1: + output += ", " + return output + +def CallString( arg_names ): + output = '( ' + output += InternalCallString(arg_names) + output += " )" + return output + +def IsVector ( func_name ) : + m = re.search( r"^(SecondaryColor|Color|EdgeFlag|EvalCoord|Index|Normal|TexCoord|MultiTexCoord|Vertex|RasterPos|VertexAttrib|FogCoord|WindowPos|ProgramParameter)([1234]?)N?(ub|b|us|s|ui|i|f|d|)v(ARB|EXT|NV)?$", func_name ) + if m : + if m.group(2) : + return string.atoi( m.group(2) ) + else: + return 1 + else: + return 0 diff --git a/src/VBox/Additions/common/crOpenGL/tsfuncs.py b/src/VBox/Additions/common/crOpenGL/tsfuncs.py new file mode 100755 index 00000000..020b5cc7 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/tsfuncs.py @@ -0,0 +1,47 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - THIS FILE GENERATED BY THE tsfuncs.py SCRIPT */ + +#include "stub.h" +""") + +keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + +for func_name in keys: + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + + print("static %s SPULOAD_APIENTRY ts_%s(%s)" % (return_type, func_name, apiutil.MakeDeclarationString(params) )) + print("{") + print("\tSPUDispatchTable *tab = (SPUDispatchTable *) crGetTSD(&stub.dispatchTSD);") + + if return_type != "void": + print("\treturn ", end=" ") + + print("\ttab->%s(%s);" % (func_name, apiutil.MakeCallString(params))) + print("}") + print("") + + +print("SPUDispatchTable stubThreadsafeDispatch = {") + +for func_name in keys: + print("\tts_%s," % func_name) + +print("\tNULL, /* copyList */") +print("\tNULL, /* copy_of */") +print("\t0, /* mark */") +print("\tNULL /* server */") +print("};") diff --git a/src/VBox/Additions/common/crOpenGL/utils.c b/src/VBox/Additions/common/crOpenGL/utils.c new file mode 100644 index 00000000..a220d09d --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/utils.c @@ -0,0 +1,876 @@ +/* + * (C) Copyright IBM Corporation 2002, 2004 + * All Rights Reserved. + * + * 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 + * on the rights to use, copy, modify, merge, publish, distribute, sub + * license, 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 (including the next + * paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEM, IBM AND/OR THEIR SUPPLIERS 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. + */ + +/** + * \file utils.c + * Utility functions for DRI drivers. + * + * \author Ian Romanick <idr@us.ibm.com> + */ + +#include <string.h> +#include <stdlib.h> +#include "mtypes.h" +#include "extensions.h" +#include "utils.h" +#include "dispatch.h" + +int driDispatchRemapTable[ driDispatchRemapTable_size ]; + +#if defined(USE_X86_ASM) +#include "x86/common_x86_asm.h" +#endif + +#if defined(USE_PPC_ASM) +#include "ppc/common_ppc_features.h" +#endif + +unsigned +driParseDebugString( const char * debug, + const struct dri_debug_control * control ) +{ + unsigned flag; + + + flag = 0; + if ( debug != NULL ) { + while( control->string != NULL ) { + if ( !strcmp( debug, "all" ) || + strstr( debug, control->string ) != NULL ) { + flag |= control->flag; + } + + control++; + } + } + + return flag; +} + + + +/** + * Create the \c GL_RENDERER string for DRI drivers. + * + * Almost all DRI drivers use a \c GL_RENDERER string of the form: + * + * "Mesa DRI <chip> <driver date> <AGP speed) <CPU information>" + * + * Using the supplied chip name, driver data, and AGP speed, this function + * creates the string. + * + * \param buffer Buffer to hold the \c GL_RENDERER string. + * \param hardware_name Name of the hardware. + * \param driver_date Driver date. + * \param agp_mode AGP mode (speed). + * + * \returns + * The length of the string stored in \c buffer. This does \b not include + * the terminating \c NUL character. + */ +unsigned +driGetRendererString( char * buffer, const char * hardware_name, + const char * driver_date, GLuint agp_mode ) +{ +#define MAX_INFO 4 + const char * cpu[MAX_INFO]; + unsigned next = 0; + unsigned i; + unsigned offset; + + + offset = sprintf( buffer, "Mesa DRI %s %s", hardware_name, driver_date ); + + /* Append any AGP-specific information. + */ + switch ( agp_mode ) { + case 1: + case 2: + case 4: + case 8: + offset += sprintf( & buffer[ offset ], " AGP %ux", agp_mode ); + break; + + default: + break; + } + + /* Append any CPU-specific information. + */ +#ifdef USE_X86_ASM + if ( _mesa_x86_cpu_features ) { + cpu[next] = " x86"; + next++; + } +# ifdef USE_MMX_ASM + if ( cpu_has_mmx ) { + cpu[next] = (cpu_has_mmxext) ? "/MMX+" : "/MMX"; + next++; + } +# endif +# ifdef USE_3DNOW_ASM + if ( cpu_has_3dnow ) { + cpu[next] = (cpu_has_3dnowext) ? "/3DNow!+" : "/3DNow!"; + next++; + } +# endif +# ifdef USE_SSE_ASM + if ( cpu_has_xmm ) { + cpu[next] = (cpu_has_xmm2) ? "/SSE2" : "/SSE"; + next++; + } +# endif + +#elif defined(USE_SPARC_ASM) + + cpu[0] = " SPARC"; + next = 1; + +#elif defined(USE_PPC_ASM) + if ( _mesa_ppc_cpu_features ) { + cpu[next] = (cpu_has_64) ? " PowerPC 64" : " PowerPC"; + next++; + } + +# ifdef USE_VMX_ASM + if ( cpu_has_vmx ) { + cpu[next] = "/Altivec"; + next++; + } +# endif + + if ( ! cpu_has_fpu ) { + cpu[next] = "/No FPU"; + next++; + } +#endif + + for ( i = 0 ; i < next ; i++ ) { + const size_t len = strlen( cpu[i] ); + + strncpy( & buffer[ offset ], cpu[i], len ); + offset += len; + } + + return offset; +} + + + + +#define need_GL_ARB_multisample +#define need_GL_ARB_transpose_matrix +#define need_GL_ARB_window_pos +#define need_GL_EXT_compiled_vertex_array +#define need_GL_EXT_polygon_offset +#define need_GL_EXT_texture_object +#define need_GL_EXT_vertex_array +#define need_GL_MESA_window_pos + +/* These are needed in *all* drivers because Mesa internally implements + * certain functionality in terms of functions provided by these extensions. + * For example, glBlendFunc is implemented by calling glBlendFuncSeparateEXT. + */ +#define need_GL_EXT_blend_func_separate +#define need_GL_NV_vertex_program + +#include "extension_helper.h" + +static const struct dri_extension all_mesa_extensions[] = { + { "GL_ARB_multisample", GL_ARB_multisample_functions }, + { "GL_ARB_transpose_matrix", GL_ARB_transpose_matrix_functions }, + { "GL_ARB_window_pos", GL_ARB_window_pos_functions }, + { "GL_EXT_blend_func_separate", GL_EXT_blend_func_separate_functions }, + { "GL_EXT_compiled_vertex_array", GL_EXT_compiled_vertex_array_functions }, + { "GL_EXT_polygon_offset", GL_EXT_polygon_offset_functions }, + { "GL_EXT_texture_object", GL_EXT_texture_object_functions }, + { "GL_EXT_vertex_array", GL_EXT_vertex_array_functions }, + { "GL_MESA_window_pos", GL_MESA_window_pos_functions }, + { "GL_NV_vertex_program", GL_NV_vertex_program_functions }, + { NULL, NULL } +}; + + +/** + * Enable extensions supported by the driver. + * + * \bug + * ARB_imaging isn't handled properly. In Mesa, enabling ARB_imaging also + * enables all the sub-extensions that are folded into it. This means that + * we need to add entry-points (via \c driInitSingleExtension) for those + * new functions here. + */ +void driInitExtensions( GLcontext * ctx, + const struct dri_extension * extensions_to_enable, + GLboolean enable_imaging ) +{ + static int first_time = 1; + unsigned i; + + if ( first_time ) { + for ( i = 0 ; i < driDispatchRemapTable_size ; i++ ) { + driDispatchRemapTable[i] = -1; + } + + first_time = 0; + driInitExtensions( ctx, all_mesa_extensions, GL_FALSE ); + } + + if ( (ctx != NULL) && enable_imaging ) { + _mesa_enable_imaging_extensions( ctx ); + } + + for ( i = 0 ; extensions_to_enable[i].name != NULL ; i++ ) { + driInitSingleExtension( ctx, & extensions_to_enable[i] ); + } +} + + + + +/** + * Enable and add dispatch functions for a single extension + * + * \param ctx Context where extension is to be enabled. + * \param ext Extension that is to be enabled. + * + * \sa driInitExtensions, _mesa_enable_extension, _glapi_add_entrypoint + * + * \todo + * Determine if it would be better to use \c strlen instead of the hardcoded + * for-loops. + */ +void driInitSingleExtension( GLcontext * ctx, + const struct dri_extension * ext ) +{ + unsigned i; + + + if ( ext->functions != NULL ) { + for ( i = 0 ; ext->functions[i].strings != NULL ; i++ ) { + const char * functions[16]; + const char * parameter_signature; + const char * str = ext->functions[i].strings; + unsigned j; + unsigned offset; + + + /* Separate the parameter signature from the rest of the string. + * If the parameter signature is empty (i.e., the string starts + * with a NUL character), then the function has a void parameter + * list. + */ + parameter_signature = str; + while ( str[0] != '\0' ) { + str++; + } + str++; + + + /* Divide the string into the substrings that name each + * entry-point for the function. + */ + for ( j = 0 ; j < 16 ; j++ ) { + if ( str[0] == '\0' ) { + functions[j] = NULL; + break; + } + + functions[j] = str; + + while ( str[0] != '\0' ) { + str++; + } + str++; + } + + + /* Add each entry-point to the dispatch table. + */ + offset = _glapi_add_dispatch( functions, parameter_signature ); + if (offset == -1) { + fprintf(stderr, "DISPATCH ERROR! _glapi_add_dispatch failed " + "to add %s!\n", functions[0]); + } + else if (ext->functions[i].remap_index != -1) { + driDispatchRemapTable[ ext->functions[i].remap_index ] = + offset; + } + else if (ext->functions[i].offset != offset) { + fprintf(stderr, "DISPATCH ERROR! %s -> %u != %u\n", + functions[0], offset, ext->functions[i].offset); + } + } + } + + if ( ctx != NULL ) { + _mesa_enable_extension( ctx, ext->name ); + } +} + + +/** + * Utility function used by drivers to test the versions of other components. + * + * If one of the version requirements is not met, a message is logged using + * \c __driUtilMessage. + * + * \param driver_name Name of the driver. Used in error messages. + * \param driActual Actual DRI version supplied __driCreateNewScreen. + * \param driExpected Minimum DRI version required by the driver. + * \param ddxActual Actual DDX version supplied __driCreateNewScreen. + * \param ddxExpected Minimum DDX minor and range of DDX major version required by the driver. + * \param drmActual Actual DRM version supplied __driCreateNewScreen. + * \param drmExpected Minimum DRM version required by the driver. + * + * \returns \c GL_TRUE if all version requirements are met. Otherwise, + * \c GL_FALSE is returned. + * + * \sa __driCreateNewScreen, driCheckDriDdxDrmVersions2, __driUtilMessage + * + * \todo + * Now that the old \c driCheckDriDdxDrmVersions function is gone, this + * function and \c driCheckDriDdxDrmVersions2 should be renamed. + */ +GLboolean +driCheckDriDdxDrmVersions3(const char * driver_name, + const __DRIversion * driActual, + const __DRIversion * driExpected, + const __DRIversion * ddxActual, + const __DRIutilversion2 * ddxExpected, + const __DRIversion * drmActual, + const __DRIversion * drmExpected) +{ + static const char format[] = "%s DRI driver expected %s version %d.%d.x " + "but got version %d.%d.%d\n"; + static const char format2[] = "%s DRI driver expected %s version %d-%d.%d.x " + "but got version %d.%d.%d\n"; + + + /* Check the DRI version */ + if ( (driActual->major != driExpected->major) + || (driActual->minor < driExpected->minor) ) { + fprintf(stderr, format, driver_name, "DRI", + driExpected->major, driExpected->minor, + driActual->major, driActual->minor, driActual->patch); + return GL_FALSE; + } + + /* Check that the DDX driver version is compatible */ + /* for miniglx we pass in -1 so we can ignore the DDX version */ + if ( (ddxActual->major != -1) && ((ddxActual->major < ddxExpected->major_min) + || (ddxActual->major > ddxExpected->major_max) + || (ddxActual->minor < ddxExpected->minor)) ) { + fprintf(stderr, format2, driver_name, "DDX", + ddxExpected->major_min, ddxExpected->major_max, ddxExpected->minor, + ddxActual->major, ddxActual->minor, ddxActual->patch); + return GL_FALSE; + } + + /* Check that the DRM driver version is compatible */ + if ( (drmActual->major != drmExpected->major) + || (drmActual->minor < drmExpected->minor) ) { + fprintf(stderr, format, driver_name, "DRM", + drmExpected->major, drmExpected->minor, + drmActual->major, drmActual->minor, drmActual->patch); + return GL_FALSE; + } + + return GL_TRUE; +} + +GLboolean +driCheckDriDdxDrmVersions2(const char * driver_name, + const __DRIversion * driActual, + const __DRIversion * driExpected, + const __DRIversion * ddxActual, + const __DRIversion * ddxExpected, + const __DRIversion * drmActual, + const __DRIversion * drmExpected) +{ + __DRIutilversion2 ddx_expected; + ddx_expected.major_min = ddxExpected->major; + ddx_expected.major_max = ddxExpected->major; + ddx_expected.minor = ddxExpected->minor; + ddx_expected.patch = ddxExpected->patch; + return driCheckDriDdxDrmVersions3(driver_name, driActual, + driExpected, ddxActual, & ddx_expected, + drmActual, drmExpected); +} + +GLboolean driClipRectToFramebuffer( const GLframebuffer *buffer, + GLint *x, GLint *y, + GLsizei *width, GLsizei *height ) +{ + /* left clipping */ + if (*x < buffer->_Xmin) { + *width -= (buffer->_Xmin - *x); + *x = buffer->_Xmin; + } + + /* right clipping */ + if (*x + *width > buffer->_Xmax) + *width -= (*x + *width - buffer->_Xmax - 1); + + if (*width <= 0) + return GL_FALSE; + + /* bottom clipping */ + if (*y < buffer->_Ymin) { + *height -= (buffer->_Ymin - *y); + *y = buffer->_Ymin; + } + + /* top clipping */ + if (*y + *height > buffer->_Ymax) + *height -= (*y + *height - buffer->_Ymax - 1); + + if (*height <= 0) + return GL_FALSE; + + return GL_TRUE; +} + +/** + * Creates a set of \c __GLcontextModes that a driver will expose. + * + * A set of \c __GLcontextModes will be created based on the supplied + * parameters. The number of modes processed will be 2 * + * \c num_depth_stencil_bits * \c num_db_modes. + * + * For the most part, data is just copied from \c depth_bits, \c stencil_bits, + * \c db_modes, and \c visType into each \c __GLcontextModes element. + * However, the meanings of \c fb_format and \c fb_type require further + * explanation. The \c fb_format specifies which color components are in + * each pixel and what the default order is. For example, \c GL_RGB specifies + * that red, green, blue are available and red is in the "most significant" + * position and blue is in the "least significant". The \c fb_type specifies + * the bit sizes of each component and the actual ordering. For example, if + * \c GL_UNSIGNED_SHORT_5_6_5_REV is specified with \c GL_RGB, bits [15:11] + * are the blue value, bits [10:5] are the green value, and bits [4:0] are + * the red value. + * + * One subtle issue is the combination of \c GL_RGB or \c GL_BGR and either + * of the \c GL_UNSIGNED_INT_8_8_8_8 modes. The resulting mask values in the + * \c __GLcontextModes structure is \b identical to the \c GL_RGBA or + * \c GL_BGRA case, except the \c alphaMask is zero. This means that, as + * far as this routine is concerned, \c GL_RGB with \c GL_UNSIGNED_INT_8_8_8_8 + * still uses 32-bits. + * + * If in doubt, look at the tables used in the function. + * + * \param ptr_to_modes Pointer to a pointer to a linked list of + * \c __GLcontextModes. Upon completion, a pointer to + * the next element to be process will be stored here. + * If the function fails and returns \c GL_FALSE, this + * value will be unmodified, but some elements in the + * linked list may be modified. + * \param fb_format Format of the framebuffer. Currently only \c GL_RGB, + * \c GL_RGBA, \c GL_BGR, and \c GL_BGRA are supported. + * \param fb_type Type of the pixels in the framebuffer. Currently only + * \c GL_UNSIGNED_SHORT_5_6_5, + * \c GL_UNSIGNED_SHORT_5_6_5_REV, + * \c GL_UNSIGNED_INT_8_8_8_8, and + * \c GL_UNSIGNED_INT_8_8_8_8_REV are supported. + * \param depth_bits Array of depth buffer sizes to be exposed. + * \param stencil_bits Array of stencil buffer sizes to be exposed. + * \param num_depth_stencil_bits Number of entries in both \c depth_bits and + * \c stencil_bits. + * \param db_modes Array of buffer swap modes. If an element has a + * value of \c GLX_NONE, then it represents a + * single-buffered mode. Other valid values are + * \c GLX_SWAP_EXCHANGE_OML, \c GLX_SWAP_COPY_OML, and + * \c GLX_SWAP_UNDEFINED_OML. See the + * GLX_OML_swap_method extension spec for more details. + * \param num_db_modes Number of entries in \c db_modes. + * \param visType GLX visual type. Usually either \c GLX_TRUE_COLOR or + * \c GLX_DIRECT_COLOR. + * + * \returns + * \c GL_TRUE on success or \c GL_FALSE on failure. Currently the only + * cause of failure is a bad parameter (i.e., unsupported \c fb_format or + * \c fb_type). + * + * \todo + * There is currently no way to support packed RGB modes (i.e., modes with + * exactly 3 bytes per pixel) or floating-point modes. This could probably + * be done by creating some new, private enums with clever names likes + * \c GL_UNSIGNED_3BYTE_8_8_8, \c GL_4FLOAT_32_32_32_32, + * \c GL_4HALF_16_16_16_16, etc. We can cross that bridge when we come to it. + */ +__DRIconfig ** +driCreateConfigs(GLenum fb_format, GLenum fb_type, + const u_int8_t * depth_bits, const u_int8_t * stencil_bits, + unsigned num_depth_stencil_bits, + const GLenum * db_modes, unsigned num_db_modes) +{ + static const u_int8_t bits_table[4][4] = { + /* R G B A */ + { 3, 3, 2, 0 }, /* Any GL_UNSIGNED_BYTE_3_3_2 */ + { 5, 6, 5, 0 }, /* Any GL_UNSIGNED_SHORT_5_6_5 */ + { 8, 8, 8, 0 }, /* Any RGB with any GL_UNSIGNED_INT_8_8_8_8 */ + { 8, 8, 8, 8 } /* Any RGBA with any GL_UNSIGNED_INT_8_8_8_8 */ + }; + + static const u_int32_t masks_table_rgb[6][4] = { + { 0x000000E0, 0x0000001C, 0x00000003, 0x00000000 }, /* 3_3_2 */ + { 0x00000007, 0x00000038, 0x000000C0, 0x00000000 }, /* 2_3_3_REV */ + { 0x0000F800, 0x000007E0, 0x0000001F, 0x00000000 }, /* 5_6_5 */ + { 0x0000001F, 0x000007E0, 0x0000F800, 0x00000000 }, /* 5_6_5_REV */ + { 0xFF000000, 0x00FF0000, 0x0000FF00, 0x00000000 }, /* 8_8_8_8 */ + { 0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000 } /* 8_8_8_8_REV */ + }; + + static const u_int32_t masks_table_rgba[6][4] = { + { 0x000000E0, 0x0000001C, 0x00000003, 0x00000000 }, /* 3_3_2 */ + { 0x00000007, 0x00000038, 0x000000C0, 0x00000000 }, /* 2_3_3_REV */ + { 0x0000F800, 0x000007E0, 0x0000001F, 0x00000000 }, /* 5_6_5 */ + { 0x0000001F, 0x000007E0, 0x0000F800, 0x00000000 }, /* 5_6_5_REV */ + { 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF }, /* 8_8_8_8 */ + { 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 }, /* 8_8_8_8_REV */ + }; + + static const u_int32_t masks_table_bgr[6][4] = { + { 0x00000007, 0x00000038, 0x000000C0, 0x00000000 }, /* 3_3_2 */ + { 0x000000E0, 0x0000001C, 0x00000003, 0x00000000 }, /* 2_3_3_REV */ + { 0x0000001F, 0x000007E0, 0x0000F800, 0x00000000 }, /* 5_6_5 */ + { 0x0000F800, 0x000007E0, 0x0000001F, 0x00000000 }, /* 5_6_5_REV */ + { 0x0000FF00, 0x00FF0000, 0xFF000000, 0x00000000 }, /* 8_8_8_8 */ + { 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000 }, /* 8_8_8_8_REV */ + }; + + static const u_int32_t masks_table_bgra[6][4] = { + { 0x00000007, 0x00000038, 0x000000C0, 0x00000000 }, /* 3_3_2 */ + { 0x000000E0, 0x0000001C, 0x00000003, 0x00000000 }, /* 2_3_3_REV */ + { 0x0000001F, 0x000007E0, 0x0000F800, 0x00000000 }, /* 5_6_5 */ + { 0x0000F800, 0x000007E0, 0x0000001F, 0x00000000 }, /* 5_6_5_REV */ + { 0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF }, /* 8_8_8_8 */ + { 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000 }, /* 8_8_8_8_REV */ + }; + + static const u_int8_t bytes_per_pixel[6] = { + 1, /* 3_3_2 */ + 1, /* 2_3_3_REV */ + 2, /* 5_6_5 */ + 2, /* 5_6_5_REV */ + 4, /* 8_8_8_8 */ + 4 /* 8_8_8_8_REV */ + }; + + const u_int8_t * bits; + const u_int32_t * masks; + int index; + __DRIconfig **configs, **c; + __GLcontextModes *modes; + unsigned i; + unsigned j; + unsigned k; + unsigned num_modes; + unsigned num_accum_bits = 2; + + switch ( fb_type ) { + case GL_UNSIGNED_BYTE_3_3_2: + index = 0; + break; + case GL_UNSIGNED_BYTE_2_3_3_REV: + index = 1; + break; + case GL_UNSIGNED_SHORT_5_6_5: + index = 2; + break; + case GL_UNSIGNED_SHORT_5_6_5_REV: + index = 3; + break; + case GL_UNSIGNED_INT_8_8_8_8: + index = 4; + break; + case GL_UNSIGNED_INT_8_8_8_8_REV: + index = 5; + break; + default: + fprintf( stderr, "[%s:%d] Unknown framebuffer type 0x%04x.\n", + __FUNCTION__, __LINE__, fb_type ); + return NULL; + } + + + /* Valid types are GL_UNSIGNED_SHORT_5_6_5 and GL_UNSIGNED_INT_8_8_8_8 and + * the _REV versions. + * + * Valid formats are GL_RGBA, GL_RGB, and GL_BGRA. + */ + + switch ( fb_format ) { + case GL_RGB: + masks = masks_table_rgb[ index ]; + break; + + case GL_RGBA: + masks = masks_table_rgba[ index ]; + break; + + case GL_BGR: + masks = masks_table_bgr[ index ]; + break; + + case GL_BGRA: + masks = masks_table_bgra[ index ]; + break; + + default: + fprintf( stderr, "[%s:%d] Unknown framebuffer format 0x%04x.\n", + __FUNCTION__, __LINE__, fb_format ); + return NULL; + } + + switch ( bytes_per_pixel[ index ] ) { + case 1: + bits = bits_table[0]; + break; + case 2: + bits = bits_table[1]; + break; + default: + bits = ((fb_format == GL_RGB) || (fb_format == GL_BGR)) + ? bits_table[2] + : bits_table[3]; + break; + } + + num_modes = num_depth_stencil_bits * num_db_modes * num_accum_bits; + configs = _mesa_calloc((num_modes + 1) * sizeof *configs); + if (configs == NULL) + return NULL; + + c = configs; + for ( k = 0 ; k < num_depth_stencil_bits ; k++ ) { + for ( i = 0 ; i < num_db_modes ; i++ ) { + for ( j = 0 ; j < num_accum_bits ; j++ ) { + *c = _mesa_malloc (sizeof **c); + modes = &(*c)->modes; + c++; + + memset(modes, 0, sizeof *modes); + modes->redBits = bits[0]; + modes->greenBits = bits[1]; + modes->blueBits = bits[2]; + modes->alphaBits = bits[3]; + modes->redMask = masks[0]; + modes->greenMask = masks[1]; + modes->blueMask = masks[2]; + modes->alphaMask = masks[3]; + modes->rgbBits = modes->redBits + modes->greenBits + + modes->blueBits + modes->alphaBits; + + modes->accumRedBits = 16 * j; + modes->accumGreenBits = 16 * j; + modes->accumBlueBits = 16 * j; + modes->accumAlphaBits = (masks[3] != 0) ? 16 * j : 0; + modes->visualRating = (j == 0) ? GLX_NONE : GLX_SLOW_CONFIG; + + modes->stencilBits = stencil_bits[k]; + modes->depthBits = depth_bits[k]; + + modes->transparentPixel = GLX_NONE; + modes->transparentRed = GLX_DONT_CARE; + modes->transparentGreen = GLX_DONT_CARE; + modes->transparentBlue = GLX_DONT_CARE; + modes->transparentAlpha = GLX_DONT_CARE; + modes->transparentIndex = GLX_DONT_CARE; + modes->visualType = GLX_DONT_CARE; + modes->renderType = GLX_RGBA_BIT; + modes->drawableType = GLX_WINDOW_BIT; + modes->rgbMode = GL_TRUE; + + if ( db_modes[i] == GLX_NONE ) { + modes->doubleBufferMode = GL_FALSE; + } + else { + modes->doubleBufferMode = GL_TRUE; + modes->swapMethod = db_modes[i]; + } + + modes->haveAccumBuffer = ((modes->accumRedBits + + modes->accumGreenBits + + modes->accumBlueBits + + modes->accumAlphaBits) > 0); + modes->haveDepthBuffer = (modes->depthBits > 0); + modes->haveStencilBuffer = (modes->stencilBits > 0); + + modes->bindToTextureRgb = GL_TRUE; + modes->bindToTextureRgba = GL_TRUE; + modes->bindToMipmapTexture = GL_FALSE; + modes->bindToTextureTargets = modes->rgbMode ? + __DRI_ATTRIB_TEXTURE_1D_BIT | + __DRI_ATTRIB_TEXTURE_2D_BIT | + __DRI_ATTRIB_TEXTURE_RECTANGLE_BIT : + 0; + } + } + } + *c = NULL; + + return configs; +} + +const __DRIconfig **driConcatConfigs(__DRIconfig **a, __DRIconfig **b) +{ + const __DRIconfig **all; + int i, j, index; + + i = 0; + while (a[i] != NULL) + i++; + j = 0; + while (b[j] != NULL) + j++; + + all = _mesa_malloc((i + j + 1) * sizeof *all); + index = 0; + for (i = 0; a[i] != NULL; i++) + all[index++] = a[i]; + for (j = 0; b[j] != NULL; j++) + all[index++] = b[j]; + all[index++] = NULL; + + _mesa_free(a); + _mesa_free(b); + + return all; +} + +#define __ATTRIB(attrib, field) \ + { attrib, offsetof(__GLcontextModes, field) } + +static const struct { unsigned int attrib, offset; } attribMap[] = { + __ATTRIB(__DRI_ATTRIB_BUFFER_SIZE, rgbBits), + __ATTRIB(__DRI_ATTRIB_LEVEL, level), + __ATTRIB(__DRI_ATTRIB_RED_SIZE, redBits), + __ATTRIB(__DRI_ATTRIB_GREEN_SIZE, greenBits), + __ATTRIB(__DRI_ATTRIB_BLUE_SIZE, blueBits), + __ATTRIB(__DRI_ATTRIB_ALPHA_SIZE, alphaBits), + __ATTRIB(__DRI_ATTRIB_DEPTH_SIZE, depthBits), + __ATTRIB(__DRI_ATTRIB_STENCIL_SIZE, stencilBits), + __ATTRIB(__DRI_ATTRIB_ACCUM_RED_SIZE, accumRedBits), + __ATTRIB(__DRI_ATTRIB_ACCUM_GREEN_SIZE, accumGreenBits), + __ATTRIB(__DRI_ATTRIB_ACCUM_BLUE_SIZE, accumBlueBits), + __ATTRIB(__DRI_ATTRIB_ACCUM_ALPHA_SIZE, accumAlphaBits), + __ATTRIB(__DRI_ATTRIB_SAMPLE_BUFFERS, sampleBuffers), + __ATTRIB(__DRI_ATTRIB_SAMPLES, samples), + __ATTRIB(__DRI_ATTRIB_DOUBLE_BUFFER, doubleBufferMode), + __ATTRIB(__DRI_ATTRIB_STEREO, stereoMode), + __ATTRIB(__DRI_ATTRIB_AUX_BUFFERS, numAuxBuffers), + __ATTRIB(__DRI_ATTRIB_TRANSPARENT_TYPE, transparentPixel), + __ATTRIB(__DRI_ATTRIB_TRANSPARENT_INDEX_VALUE, transparentPixel), + __ATTRIB(__DRI_ATTRIB_TRANSPARENT_RED_VALUE, transparentRed), + __ATTRIB(__DRI_ATTRIB_TRANSPARENT_GREEN_VALUE, transparentGreen), + __ATTRIB(__DRI_ATTRIB_TRANSPARENT_BLUE_VALUE, transparentBlue), + __ATTRIB(__DRI_ATTRIB_TRANSPARENT_ALPHA_VALUE, transparentAlpha), + __ATTRIB(__DRI_ATTRIB_FLOAT_MODE, floatMode), + __ATTRIB(__DRI_ATTRIB_RED_MASK, redMask), + __ATTRIB(__DRI_ATTRIB_GREEN_MASK, greenMask), + __ATTRIB(__DRI_ATTRIB_BLUE_MASK, blueMask), + __ATTRIB(__DRI_ATTRIB_ALPHA_MASK, alphaMask), + __ATTRIB(__DRI_ATTRIB_MAX_PBUFFER_WIDTH, maxPbufferWidth), + __ATTRIB(__DRI_ATTRIB_MAX_PBUFFER_HEIGHT, maxPbufferHeight), + __ATTRIB(__DRI_ATTRIB_MAX_PBUFFER_PIXELS, maxPbufferPixels), + __ATTRIB(__DRI_ATTRIB_OPTIMAL_PBUFFER_WIDTH, optimalPbufferWidth), + __ATTRIB(__DRI_ATTRIB_OPTIMAL_PBUFFER_HEIGHT, optimalPbufferHeight), + __ATTRIB(__DRI_ATTRIB_SWAP_METHOD, swapMethod), + __ATTRIB(__DRI_ATTRIB_BIND_TO_TEXTURE_RGB, bindToTextureRgb), + __ATTRIB(__DRI_ATTRIB_BIND_TO_TEXTURE_RGBA, bindToTextureRgba), + __ATTRIB(__DRI_ATTRIB_BIND_TO_MIPMAP_TEXTURE, bindToMipmapTexture), + __ATTRIB(__DRI_ATTRIB_BIND_TO_TEXTURE_TARGETS, bindToTextureTargets), + __ATTRIB(__DRI_ATTRIB_YINVERTED, yInverted), + + /* The struct field doesn't matter here, these are handled by the + * switch in driGetConfigAttribIndex. We need them in the array + * so the iterator includes them though.*/ + __ATTRIB(__DRI_ATTRIB_RENDER_TYPE, level), + __ATTRIB(__DRI_ATTRIB_CONFIG_CAVEAT, level), + __ATTRIB(__DRI_ATTRIB_SWAP_METHOD, level) +}; + +#define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0])) + +static int +driGetConfigAttribIndex(const __DRIconfig *config, + unsigned int index, unsigned int *value) +{ + switch (attribMap[index].attrib) { + case __DRI_ATTRIB_RENDER_TYPE: + if (config->modes.rgbMode) + *value = __DRI_ATTRIB_RGBA_BIT; + else + *value = __DRI_ATTRIB_COLOR_INDEX_BIT; + break; + case __DRI_ATTRIB_CONFIG_CAVEAT: + if (config->modes.visualRating == GLX_NON_CONFORMANT_CONFIG) + *value = __DRI_ATTRIB_NON_CONFORMANT_CONFIG; + else if (config->modes.visualRating == GLX_SLOW_CONFIG) + *value = __DRI_ATTRIB_SLOW_BIT; + else + *value = 0; + break; + case __DRI_ATTRIB_SWAP_METHOD: + break; + + case __DRI_ATTRIB_FLOAT_MODE: + *value = config->modes.floatMode; + break; + + default: + *value = *(unsigned int *) + ((char *) &config->modes + attribMap[index].offset); + + break; + } + + return GL_TRUE; +} + +int +driGetConfigAttrib(const __DRIconfig *config, + unsigned int attrib, unsigned int *value) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(attribMap); i++) + if (attribMap[i].attrib == attrib) + return driGetConfigAttribIndex(config, i, value); + + return GL_FALSE; +} + +int +driIndexConfigAttrib(const __DRIconfig *config, int index, + unsigned int *attrib, unsigned int *value) +{ + if (index >= 0 && index < ARRAY_SIZE(attribMap)) { + *attrib = attribMap[index].attrib; + return driGetConfigAttribIndex(config, index, value); + } + + return GL_FALSE; +} diff --git a/src/VBox/Additions/common/crOpenGL/vboxdri_drv.c b/src/VBox/Additions/common/crOpenGL/vboxdri_drv.c new file mode 100644 index 00000000..e82975e3 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/vboxdri_drv.c @@ -0,0 +1,694 @@ +/* + * Mesa 3-D graphics library + * Version: 6.3 + * + * Copyright (C) 1999-2005 Brian Paul All Rights Reserved. + * + * 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 + * BRIAN PAUL 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. + */ + +/* Minimal swrast-based dri loadable driver. + * + * Todo: + * -- Use malloced (rather than framebuffer) memory for backbuffer + * -- 32bpp is hardwired -- fix + * + * NOTES: + * -- No mechanism for cliprects or resize notification -- + * assumes this is a fullscreen device. + * -- No locking -- assumes this is the only driver accessing this + * device. + * -- Doesn't (yet) make use of any acceleration or other interfaces + * provided by fb. Would be entirely happy working against any + * fullscreen interface. + * -- HOWEVER: only a small number of pixelformats are supported, and + * the mechanism for choosing between them makes some assumptions + * that may not be valid everywhere. + */ + +#include "driver.h" +#include "drm.h" +#include "utils.h" +#include "drirenderbuffer.h" + +#include "buffers.h" +#include "extensions.h" +#include "framebuffer.h" +#include "renderbuffer.h" +#include "vbo/vbo.h" +#include "swrast/swrast.h" +#include "swrast_setup/swrast_setup.h" +#include "tnl/tnl.h" +#include "tnl/t_context.h" +#include "tnl/t_pipeline.h" +#include "drivers/common/driverfuncs.h" + +#define need_GL_VERSION_1_3 +#define need_GL_VERSION_1_4 +#define need_GL_VERSION_1_5 +#define need_GL_VERSION_2_0 +#define need_GL_VERSION_2_1 + +/* sw extensions for imaging */ +#define need_GL_EXT_blend_color +#define need_GL_EXT_blend_minmax +#define need_GL_EXT_convolution +#define need_GL_EXT_histogram +#define need_GL_SGI_color_table + +/* sw extensions not associated with some GL version */ +#define need_GL_ARB_shader_objects +#define need_GL_ARB_vertex_program +#define need_GL_APPLE_vertex_array_object +#define need_GL_ATI_fragment_shader +#define need_GL_EXT_depth_bounds_test +#define need_GL_EXT_framebuffer_object +#define need_GL_EXT_framebuffer_blit +#define need_GL_EXT_gpu_program_parameters +#define need_GL_EXT_paletted_texture +#define need_GL_IBM_multimode_draw_arrays +#define need_GL_MESA_resize_buffers +#define need_GL_NV_vertex_program +#define need_GL_NV_fragment_program + +#include "extension_helper.h" + +const struct dri_extension card_extensions[] = +{ + { "GL_VERSION_1_3", GL_VERSION_1_3_functions }, + { "GL_VERSION_1_4", GL_VERSION_1_4_functions }, + { "GL_VERSION_1_5", GL_VERSION_1_5_functions }, + { "GL_VERSION_2_0", GL_VERSION_2_0_functions }, + { "GL_VERSION_2_1", GL_VERSION_2_1_functions }, + + { "GL_EXT_blend_color", GL_EXT_blend_color_functions }, + { "GL_EXT_blend_minmax", GL_EXT_blend_minmax_functions }, + { "GL_EXT_convolution", GL_EXT_convolution_functions }, + { "GL_EXT_histogram", GL_EXT_histogram_functions }, + { "GL_SGI_color_table", GL_SGI_color_table_functions }, + + { "GL_ARB_shader_objects", GL_ARB_shader_objects_functions }, + { "GL_ARB_vertex_program", GL_ARB_vertex_program_functions }, + { "GL_APPLE_vertex_array_object", GL_APPLE_vertex_array_object_functions }, + { "GL_ATI_fragment_shader", GL_ATI_fragment_shader_functions }, + { "GL_EXT_depth_bounds_test", GL_EXT_depth_bounds_test_functions }, + { "GL_EXT_framebuffer_object", GL_EXT_framebuffer_object_functions }, + { "GL_EXT_framebuffer_blit", GL_EXT_framebuffer_blit_functions }, + { "GL_EXT_gpu_program_parameters", GL_EXT_gpu_program_parameters_functions }, + { "GL_EXT_paletted_texture", GL_EXT_paletted_texture_functions }, + { "GL_IBM_multimode_draw_arrays", GL_IBM_multimode_draw_arrays_functions }, + { "GL_MESA_resize_buffers", GL_MESA_resize_buffers_functions }, + { "GL_NV_vertex_program", GL_NV_vertex_program_functions }, + { "GL_NV_fragment_program", GL_NV_fragment_program_functions }, + { NULL, NULL } +}; + +void fbSetSpanFunctions(driRenderbuffer *drb, const GLvisual *vis); + +typedef struct { + GLcontext *glCtx; /* Mesa context */ + + struct { + __DRIcontextPrivate *context; + __DRIscreenPrivate *screen; + __DRIdrawablePrivate *drawable; /* drawable bound to this ctx */ + } dri; + +} fbContext, *fbContextPtr; + +#define FB_CONTEXT(ctx) ((fbContextPtr)(ctx->DriverCtx)) + + +static const GLubyte * +get_string(GLcontext *ctx, GLenum pname) +{ + (void) ctx; + switch (pname) { + case GL_RENDERER: + return (const GLubyte *) "Mesa dumb framebuffer"; + default: + return NULL; + } +} + + +static void +update_state( GLcontext *ctx, GLuint new_state ) +{ + /* not much to do here - pass it on */ + _swrast_InvalidateState( ctx, new_state ); + _swsetup_InvalidateState( ctx, new_state ); + _vbo_InvalidateState( ctx, new_state ); + _tnl_InvalidateState( ctx, new_state ); +} + + +/** + * Called by ctx->Driver.GetBufferSize from in core Mesa to query the + * current framebuffer size. + */ +static void +get_buffer_size( GLframebuffer *buffer, GLuint *width, GLuint *height ) +{ + GET_CURRENT_CONTEXT(ctx); + fbContextPtr fbmesa = FB_CONTEXT(ctx); + + *width = fbmesa->dri.drawable->w; + *height = fbmesa->dri.drawable->h; +} + + +static void +updateFramebufferSize(GLcontext *ctx) +{ + fbContextPtr fbmesa = FB_CONTEXT(ctx); + struct gl_framebuffer *fb = ctx->WinSysDrawBuffer; + if (fbmesa->dri.drawable->w != fb->Width || + fbmesa->dri.drawable->h != fb->Height) { + driUpdateFramebufferSize(ctx, fbmesa->dri.drawable); + } +} + +static void +viewport(GLcontext *ctx, GLint x, GLint y, GLsizei w, GLsizei h) +{ + /* XXX this should be called after we acquire the DRI lock, not here */ + updateFramebufferSize(ctx); +} + + +static void +init_core_functions( struct dd_function_table *functions ) +{ + functions->GetString = get_string; + functions->UpdateState = update_state; + functions->GetBufferSize = get_buffer_size; + functions->Viewport = viewport; + + functions->Clear = _swrast_Clear; /* could accelerate with blits */ +} + + +/* + * Generate code for span functions. + */ + +/* 24-bit BGR */ +#define NAME(PREFIX) PREFIX##_B8G8R8 +#define RB_TYPE GLubyte +#define SPAN_VARS \ + driRenderbuffer *drb = (driRenderbuffer *) rb; +#define INIT_PIXEL_PTR(P, X, Y) \ + GLubyte *P = (GLubyte *)drb->Base.Data + (drb->Base.Height - (Y)) * drb->pitch + (X) * 3; +#define INC_PIXEL_PTR(P) P += 3 +#define STORE_PIXEL(DST, X, Y, VALUE) \ + DST[0] = VALUE[BCOMP]; \ + DST[1] = VALUE[GCOMP]; \ + DST[2] = VALUE[RCOMP] +#define FETCH_PIXEL(DST, SRC) \ + DST[RCOMP] = SRC[2]; \ + DST[GCOMP] = SRC[1]; \ + DST[BCOMP] = SRC[0]; \ + DST[ACOMP] = 0xff + +#include "swrast/s_spantemp.h" + + +/* 32-bit BGRA */ +#define NAME(PREFIX) PREFIX##_B8G8R8A8 +#define RB_TYPE GLubyte +#define SPAN_VARS \ + driRenderbuffer *drb = (driRenderbuffer *) rb; +#define INIT_PIXEL_PTR(P, X, Y) \ + GLubyte *P = (GLubyte *)drb->Base.Data + (drb->Base.Height - (Y)) * drb->pitch + (X) * 4; +#define INC_PIXEL_PTR(P) P += 4 +#define STORE_PIXEL(DST, X, Y, VALUE) \ + DST[0] = VALUE[BCOMP]; \ + DST[1] = VALUE[GCOMP]; \ + DST[2] = VALUE[RCOMP]; \ + DST[3] = VALUE[ACOMP] +#define STORE_PIXEL_RGB(DST, X, Y, VALUE) \ + DST[0] = VALUE[BCOMP]; \ + DST[1] = VALUE[GCOMP]; \ + DST[2] = VALUE[RCOMP]; \ + DST[3] = 0xff +#define FETCH_PIXEL(DST, SRC) \ + DST[RCOMP] = SRC[2]; \ + DST[GCOMP] = SRC[1]; \ + DST[BCOMP] = SRC[0]; \ + DST[ACOMP] = SRC[3] + +#include "swrast/s_spantemp.h" + + +/* 16-bit BGR (XXX implement dithering someday) */ +#define NAME(PREFIX) PREFIX##_B5G6R5 +#define RB_TYPE GLubyte +#define SPAN_VARS \ + driRenderbuffer *drb = (driRenderbuffer *) rb; +#define INIT_PIXEL_PTR(P, X, Y) \ + GLushort *P = (GLushort *)drb->Base.Data + (drb->Base.Height - (Y)) * drb->pitch + (X) * 2; +#define INC_PIXEL_PTR(P) P += 1 +#define STORE_PIXEL(DST, X, Y, VALUE) \ + DST[0] = ( (((VALUE[RCOMP]) & 0xf8) << 8) | (((VALUE[GCOMP]) & 0xfc) << 3) | ((VALUE[BCOMP]) >> 3) ) +#define FETCH_PIXEL(DST, SRC) \ + DST[RCOMP] = ( (((SRC[0]) >> 8) & 0xf8) | (((SRC[0]) >> 11) & 0x7) ); \ + DST[GCOMP] = ( (((SRC[0]) >> 3) & 0xfc) | (((SRC[0]) >> 5) & 0x3) ); \ + DST[BCOMP] = ( (((SRC[0]) << 3) & 0xf8) | (((SRC[0]) ) & 0x7) ); \ + DST[ACOMP] = 0xff + +#include "swrast/s_spantemp.h" + + +/* 15-bit BGR (XXX implement dithering someday) */ +#define NAME(PREFIX) PREFIX##_B5G5R5 +#define RB_TYPE GLubyte +#define SPAN_VARS \ + driRenderbuffer *drb = (driRenderbuffer *) rb; +#define INIT_PIXEL_PTR(P, X, Y) \ + GLushort *P = (GLushort *)drb->Base.Data + (drb->Base.Height - (Y)) * drb->pitch + (X) * 2; +#define INC_PIXEL_PTR(P) P += 1 +#define STORE_PIXEL(DST, X, Y, VALUE) \ + DST[0] = ( (((VALUE[RCOMP]) & 0xf8) << 7) | (((VALUE[GCOMP]) & 0xf8) << 2) | ((VALUE[BCOMP]) >> 3) ) +#define FETCH_PIXEL(DST, SRC) \ + DST[RCOMP] = ( (((SRC[0]) >> 7) & 0xf8) | (((SRC[0]) >> 10) & 0x7) ); \ + DST[GCOMP] = ( (((SRC[0]) >> 2) & 0xf8) | (((SRC[0]) >> 5) & 0x7) ); \ + DST[BCOMP] = ( (((SRC[0]) << 3) & 0xf8) | (((SRC[0]) ) & 0x7) ); \ + DST[ACOMP] = 0xff + +#include "swrast/s_spantemp.h" + + +/* 8-bit color index */ +#define NAME(PREFIX) PREFIX##_CI8 +#define CI_MODE +#define RB_TYPE GLubyte +#define SPAN_VARS \ + driRenderbuffer *drb = (driRenderbuffer *) rb; +#define INIT_PIXEL_PTR(P, X, Y) \ + GLubyte *P = (GLubyte *)drb->Base.Data + (drb->Base.Height - (Y)) * drb->pitch + (X); +#define INC_PIXEL_PTR(P) P += 1 +#define STORE_PIXEL(DST, X, Y, VALUE) \ + *DST = VALUE[0] +#define FETCH_PIXEL(DST, SRC) \ + DST = SRC[0] + +#include "swrast/s_spantemp.h" + + + +void +fbSetSpanFunctions(driRenderbuffer *drb, const GLvisual *vis) +{ + ASSERT(drb->Base.InternalFormat == GL_RGBA); + if (drb->Base.InternalFormat == GL_RGBA) { + if (vis->redBits == 5 && vis->greenBits == 6 && vis->blueBits == 5) { + drb->Base.GetRow = get_row_B5G6R5; + drb->Base.GetValues = get_values_B5G6R5; + drb->Base.PutRow = put_row_B5G6R5; + drb->Base.PutMonoRow = put_mono_row_B5G6R5; + drb->Base.PutRowRGB = put_row_rgb_B5G6R5; + drb->Base.PutValues = put_values_B5G6R5; + drb->Base.PutMonoValues = put_mono_values_B5G6R5; + } + else if (vis->redBits == 5 && vis->greenBits == 5 && vis->blueBits == 5) { + drb->Base.GetRow = get_row_B5G5R5; + drb->Base.GetValues = get_values_B5G5R5; + drb->Base.PutRow = put_row_B5G5R5; + drb->Base.PutMonoRow = put_mono_row_B5G5R5; + drb->Base.PutRowRGB = put_row_rgb_B5G5R5; + drb->Base.PutValues = put_values_B5G5R5; + drb->Base.PutMonoValues = put_mono_values_B5G5R5; + } + else if (vis->redBits == 8 && vis->greenBits == 8 && vis->blueBits == 8 + && vis->alphaBits == 8) { + drb->Base.GetRow = get_row_B8G8R8A8; + drb->Base.GetValues = get_values_B8G8R8A8; + drb->Base.PutRow = put_row_B8G8R8A8; + drb->Base.PutMonoRow = put_mono_row_B8G8R8A8; + drb->Base.PutRowRGB = put_row_rgb_B8G8R8A8; + drb->Base.PutValues = put_values_B8G8R8A8; + drb->Base.PutMonoValues = put_mono_values_B8G8R8A8; + } + else if (vis->redBits == 8 && vis->greenBits == 8 && vis->blueBits == 8 + && vis->alphaBits == 0) { + drb->Base.GetRow = get_row_B8G8R8; + drb->Base.GetValues = get_values_B8G8R8; + drb->Base.PutRow = put_row_B8G8R8; + drb->Base.PutMonoRow = put_mono_row_B8G8R8; + drb->Base.PutRowRGB = put_row_rgb_B8G8R8; + drb->Base.PutValues = put_values_B8G8R8; + drb->Base.PutMonoValues = put_mono_values_B8G8R8; + } + else if (vis->indexBits == 8) { + drb->Base.GetRow = get_row_CI8; + drb->Base.GetValues = get_values_CI8; + drb->Base.PutRow = put_row_CI8; + drb->Base.PutMonoRow = put_mono_row_CI8; + drb->Base.PutValues = put_values_CI8; + drb->Base.PutMonoValues = put_mono_values_CI8; + } + } + else { + /* hardware z/stencil/etc someday */ + } +} + + +static void +fbDestroyScreen( __DRIscreenPrivate *sPriv ) +{ +} + + +static const __DRIconfig **fbFillInModes(unsigned pixel_bits, + unsigned depth_bits, + unsigned stencil_bits, + GLboolean have_back_buffer) +{ + unsigned deep = (depth_bits > 17); + + /* Right now GLX_SWAP_COPY_OML isn't supported, but it would be easy + * enough to add support. Basically, if a context is created with an + * fbconfig where the swap method is GLX_SWAP_COPY_OML, pageflipping + * will never be used. + */ + + static const GLenum db_modes[2] = { GLX_NONE, GLX_SWAP_UNDEFINED_OML }; + uint8_t depth_bits_array[4]; + uint8_t stencil_bits_array[4]; + if(deep) { + depth_bits_array[0] = 0; + depth_bits_array[1] = 24; + stencil_bits_array[0] = 0; + stencil_bits_array[1] = 8; + } else { + depth_bits_array[0] = depth_bits; + depth_bits_array[1] = 0; + depth_bits_array[2] = depth_bits; + depth_bits_array[3] = 0; + stencil_bits_array[0] = 0; + stencil_bits_array[1] = 0; + stencil_bits_array[2] = 8; + stencil_bits_array[3] = 8; + } + + return driCreateConfigs( + deep ? GL_RGBA : GL_RGB, + deep ? GL_UNSIGNED_INT_8_8_8_8 : GL_UNSIGNED_SHORT_5_6_5, + depth_bits_array, + stencil_bits_array, + deep ? 2 : 4, + db_modes, 2); +} + +/** + * This is the driver specific part of the createNewScreen entry point. + * Called when using legacy DRI. + * + * return the __GLcontextModes supported by this driver + */ +static const __DRIconfig **fbInitScreen(__DRIscreenPrivate *psp) +{ + static const __DRIversion ddx_expected = { 1, 0, 0 }; + static const __DRIversion dri_expected = { 4, 0, 0 }; + static const __DRIversion drm_expected = { 1, 0, 0 }; + + + if ( ! driCheckDriDdxDrmVersions2( "vboxvideo", + & psp->dri_version, & dri_expected, + & psp->ddx_version, & ddx_expected, + & psp->drm_version, & drm_expected ) ) { + return NULL; + } + + driInitExtensions( NULL, card_extensions, GL_FALSE ); + + return fbFillInModes( psp->fbBPP, + (psp->fbBPP == 16) ? 16 : 24, + (psp->fbBPP == 16) ? 0 : 8, + 1); +} + +/* Create the device specific context. + */ +static GLboolean +fbCreateContext( const __GLcontextModes *glVisual, + __DRIcontextPrivate *driContextPriv, + void *sharedContextPrivate) +{ + fbContextPtr fbmesa; + GLcontext *ctx, *shareCtx; + struct dd_function_table functions; + + assert(glVisual); + assert(driContextPriv); + + /* Allocate the Fb context */ + fbmesa = (fbContextPtr) _mesa_calloc( sizeof(*fbmesa) ); + if ( !fbmesa ) + return GL_FALSE; + + /* Init default driver functions then plug in our FBdev-specific functions + */ + _mesa_init_driver_functions(&functions); + init_core_functions(&functions); + + /* Allocate the Mesa context */ + if (sharedContextPrivate) + shareCtx = ((fbContextPtr) sharedContextPrivate)->glCtx; + else + shareCtx = NULL; + + ctx = fbmesa->glCtx = _mesa_create_context(glVisual, shareCtx, + &functions, (void *) fbmesa); + if (!fbmesa->glCtx) { + _mesa_free(fbmesa); + return GL_FALSE; + } + driContextPriv->driverPrivate = fbmesa; + + /* Create module contexts */ + _swrast_CreateContext( ctx ); + _vbo_CreateContext( ctx ); + _tnl_CreateContext( ctx ); + _swsetup_CreateContext( ctx ); + _swsetup_Wakeup( ctx ); + + + /* use default TCL pipeline */ + { + TNLcontext *tnl = TNL_CONTEXT(ctx); + tnl->Driver.RunPipeline = _tnl_run_pipeline; + } + + _mesa_enable_sw_extensions(ctx); + + return GL_TRUE; +} + + +static void +fbDestroyContext( __DRIcontextPrivate *driContextPriv ) +{ + GET_CURRENT_CONTEXT(ctx); + fbContextPtr fbmesa = (fbContextPtr) driContextPriv->driverPrivate; + fbContextPtr current = ctx ? FB_CONTEXT(ctx) : NULL; + + /* check if we're deleting the currently bound context */ + if (fbmesa == current) { + _mesa_make_current(NULL, NULL, NULL); + } + + /* Free fb context resources */ + if ( fbmesa ) { + _swsetup_DestroyContext( fbmesa->glCtx ); + _tnl_DestroyContext( fbmesa->glCtx ); + _vbo_DestroyContext( fbmesa->glCtx ); + _swrast_DestroyContext( fbmesa->glCtx ); + + /* free the Mesa context */ + fbmesa->glCtx->DriverCtx = NULL; + _mesa_destroy_context( fbmesa->glCtx ); + + _mesa_free( fbmesa ); + } +} + + +/* Create and initialize the Mesa and driver specific pixmap buffer + * data. + */ +static GLboolean +fbCreateBuffer( __DRIscreenPrivate *driScrnPriv, + __DRIdrawablePrivate *driDrawPriv, + const __GLcontextModes *mesaVis, + GLboolean isPixmap ) +{ + struct gl_framebuffer *mesa_framebuffer; + + if (isPixmap) { + return GL_FALSE; /* not implemented */ + } + else { + const GLboolean swDepth = mesaVis->depthBits > 0; + const GLboolean swAlpha = mesaVis->alphaBits > 0; + const GLboolean swAccum = mesaVis->accumRedBits > 0; + const GLboolean swStencil = mesaVis->stencilBits > 0; + + mesa_framebuffer = _mesa_create_framebuffer(mesaVis); + if (!mesa_framebuffer) + return 0; + + /* XXX double-check these parameters (bpp vs cpp, etc) */ + { + driRenderbuffer *drb = driNewRenderbuffer(GL_RGBA, + driScrnPriv->pFB, + driScrnPriv->fbBPP / 8, + driScrnPriv->fbOrigin, + driScrnPriv->fbStride, + driDrawPriv); + fbSetSpanFunctions(drb, mesaVis); + _mesa_add_renderbuffer(mesa_framebuffer, + BUFFER_FRONT_LEFT, &drb->Base); + } + if (mesaVis->doubleBufferMode) { + /* XXX what are the correct origin/stride values? */ + GLvoid *backBuf = _mesa_malloc(driScrnPriv->fbStride + * driScrnPriv->fbHeight); + driRenderbuffer *drb = driNewRenderbuffer(GL_RGBA, + backBuf, + driScrnPriv->fbBPP /8, + driScrnPriv->fbOrigin, + driScrnPriv->fbStride, + driDrawPriv); + fbSetSpanFunctions(drb, mesaVis); + _mesa_add_renderbuffer(mesa_framebuffer, + BUFFER_BACK_LEFT, &drb->Base); + } + + _mesa_add_soft_renderbuffers(mesa_framebuffer, + GL_FALSE, /* color */ + swDepth, + swStencil, + swAccum, + swAlpha, /* or always zero? */ + GL_FALSE /* aux */); + + driDrawPriv->driverPrivate = mesa_framebuffer; + + return 1; + } +} + + +static void +fbDestroyBuffer(__DRIdrawablePrivate *driDrawPriv) +{ + _mesa_unreference_framebuffer((GLframebuffer **)(&(driDrawPriv->driverPrivate))); +} + + + +/* If the backbuffer is on a videocard, this is extraordinarily slow! + */ +static void +fbSwapBuffers( __DRIdrawablePrivate *dPriv ) +{ + struct gl_framebuffer *mesa_framebuffer = (struct gl_framebuffer *)dPriv->driverPrivate; + struct gl_renderbuffer * front_renderbuffer = mesa_framebuffer->Attachment[BUFFER_FRONT_LEFT].Renderbuffer; + void *frontBuffer = front_renderbuffer->Data; + int currentPitch = ((driRenderbuffer *)front_renderbuffer)->pitch; + void *backBuffer = mesa_framebuffer->Attachment[BUFFER_BACK_LEFT].Renderbuffer->Data; + + if (dPriv->driContextPriv && dPriv->driContextPriv->driverPrivate) { + fbContextPtr fbmesa = (fbContextPtr) dPriv->driContextPriv->driverPrivate; + GLcontext *ctx = fbmesa->glCtx; + + if (ctx->Visual.doubleBufferMode) { + int i; + int offset = 0; + char *tmp = _mesa_malloc(currentPitch); + + _mesa_notifySwapBuffers( ctx ); /* flush pending rendering commands */ + + ASSERT(frontBuffer); + ASSERT(backBuffer); + + for (i = 0; i < dPriv->h; i++) { + _mesa_memcpy(tmp, (char *) backBuffer + offset, + currentPitch); + _mesa_memcpy((char *) frontBuffer + offset, tmp, + currentPitch); + offset += currentPitch; + } + + _mesa_free(tmp); + } + } + else { + /* XXX this shouldn't be an error but we can't handle it for now */ + _mesa_problem(NULL, "fbSwapBuffers: drawable has no context!\n"); + } +} + + +/* Force the context `c' to be the current context and associate with it + * buffer `b'. + */ +static GLboolean +fbMakeCurrent( __DRIcontextPrivate *driContextPriv, + __DRIdrawablePrivate *driDrawPriv, + __DRIdrawablePrivate *driReadPriv ) +{ + if ( driContextPriv ) { + fbContextPtr newFbCtx = + (fbContextPtr) driContextPriv->driverPrivate; + + newFbCtx->dri.drawable = driDrawPriv; + + _mesa_make_current( newFbCtx->glCtx, + driDrawPriv->driverPrivate, + driReadPriv->driverPrivate); + } else { + _mesa_make_current( NULL, NULL, NULL ); + } + + return GL_TRUE; +} + + +/* Force the context `c' to be unbound from its buffer. + */ +static GLboolean +fbUnbindContext( __DRIcontextPrivate *driContextPriv ) +{ + return GL_TRUE; +} + +const struct __DriverAPIRec driDriverAPI = { + .InitScreen = fbInitScreen, + .DestroyScreen = fbDestroyScreen, + .CreateContext = fbCreateContext, + .DestroyContext = fbDestroyContext, + .CreateBuffer = fbCreateBuffer, + .DestroyBuffer = fbDestroyBuffer, + .SwapBuffers = fbSwapBuffers, + .MakeCurrent = fbMakeCurrent, + .UnbindContext = fbUnbindContext, +}; diff --git a/src/VBox/Additions/common/crOpenGL/wgl.c b/src/VBox/Additions/common/crOpenGL/wgl.c new file mode 100644 index 00000000..fd2ea0a3 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/wgl.c @@ -0,0 +1,1015 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "cr_error.h" +#include "cr_spu.h" +#include "cr_environment.h" +#include "cr_mem.h" +#include "stub.h" + +/* I *know* most of the parameters are unused, dammit. */ +#pragma warning( disable: 4100 ) + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <stdio.h> + +#include <iprt/cdefs.h> + +/* Currently host part will misbehave re-creating context with proper visual bits + * if contexts with alternative visual bits is requested. + * For now we just report a superset of all visual bits to avoid that. + * Better to it on the host side as well? + * We could also implement properly multiple pixel formats, + * which should be done by implementing offscreen rendering or multiple host contexts. + * */ +#define VBOX_CROGL_USE_VBITS_SUPERSET + +#ifdef VBOX_CROGL_USE_VBITS_SUPERSET +static GLuint desiredVisual = CR_RGB_BIT | CR_ALPHA_BIT | CR_DEPTH_BIT | CR_STENCIL_BIT | CR_ACCUM_BIT | CR_DOUBLE_BIT; +#else +static GLuint desiredVisual = CR_RGB_BIT; +#endif + +#ifndef VBOX_CROGL_USE_VBITS_SUPERSET +/** + * Compute a mask of CR_*_BIT flags which reflects the attributes of + * the pixel format of the given hdc. + */ +static GLuint ComputeVisBits( HDC hdc ) +{ + PIXELFORMATDESCRIPTOR pfd; + int iPixelFormat; + GLuint b = 0; + + iPixelFormat = GetPixelFormat( hdc ); + + DescribePixelFormat( hdc, iPixelFormat, sizeof(pfd), &pfd ); + + if (pfd.cDepthBits > 0) + b |= CR_DEPTH_BIT; + if (pfd.cAccumBits > 0) + b |= CR_ACCUM_BIT; + if (pfd.cColorBits > 8) + b |= CR_RGB_BIT; + if (pfd.cStencilBits > 0) + b |= CR_STENCIL_BIT; + if (pfd.cAlphaBits > 0) + b |= CR_ALPHA_BIT; + if (pfd.dwFlags & PFD_DOUBLEBUFFER) + b |= CR_DOUBLE_BIT; + if (pfd.dwFlags & PFD_STEREO) + b |= CR_STEREO_BIT; + + return b; +} +#endif + +DECLEXPORT(int) WINAPI wglChoosePixelFormat_prox( HDC hdc, CONST PIXELFORMATDESCRIPTOR *pfd ) +{ + DWORD okayFlags; + + CR_DDI_PROLOGUE(); + + stubInit(); + + /* + * NOTE!!! + * Here we're telling the renderspu not to use the GDI + * equivalent's of ChoosePixelFormat/DescribePixelFormat etc + * There are subtle differences in the use of these calls. + */ + crSetenv("CR_WGL_DO_NOT_USE_GDI", "yes"); + + if ( pfd->nSize != sizeof(*pfd) || pfd->nVersion != 1 ) { + crError( "wglChoosePixelFormat: bad pfd\n" ); + return 0; + } + + okayFlags = ( PFD_DRAW_TO_WINDOW | + PFD_SUPPORT_GDI | + PFD_SUPPORT_OPENGL | + PFD_DOUBLEBUFFER | + PFD_DOUBLEBUFFER_DONTCARE | + PFD_SWAP_EXCHANGE | + PFD_SWAP_COPY | + /** @todo this is disabled due to VSG Open Inventor interop issues + * it does not make any sense actually since reporting this + * as well as choosing a pixel format with this cap would not do anything + * since ICD stuff has its own pixelformat state var */ +// PFD_STEREO | + PFD_STEREO_DONTCARE | + PFD_DEPTH_DONTCARE ); + if ( pfd->dwFlags & ~okayFlags ) { + crWarning( "wglChoosePixelFormat: only support flags=0x%x, but you gave me flags=0x%x", okayFlags, pfd->dwFlags ); + return 0; + } + + if ( pfd->iPixelType != PFD_TYPE_RGBA ) { + crError( "wglChoosePixelFormat: only support RGBA\n" ); + } + + if ( pfd->cColorBits > 32 || + pfd->cRedBits > 8 || + pfd->cGreenBits > 8 || + pfd->cBlueBits > 8 || + pfd->cAlphaBits > 8 ) { + crWarning( "wglChoosePixelFormat: too much color precision requested\n" ); + } + + if ( pfd->dwFlags & PFD_DOUBLEBUFFER ) + desiredVisual |= CR_DOUBLE_BIT; + + if ( pfd->dwFlags & PFD_STEREO ) + desiredVisual |= CR_STEREO_BIT; + + if ( pfd->cColorBits > 8) + desiredVisual |= CR_RGB_BIT; + + if ( pfd->cAccumBits > 0 || + pfd->cAccumRedBits > 0 || + pfd->cAccumGreenBits > 0 || + pfd->cAccumBlueBits > 0 || + pfd->cAccumAlphaBits > 0 ) { + crWarning( "wglChoosePixelFormat: asked for accumulation buffer, ignoring\n" ); + } + + if ( pfd->cAccumBits > 0 ) + desiredVisual |= CR_ACCUM_BIT; + + if ( pfd->cDepthBits > 32 ) { + crError( "wglChoosePixelFormat; asked for too many depth bits\n" ); + } + + if ( pfd->cDepthBits > 0 ) + desiredVisual |= CR_DEPTH_BIT; + + if ( pfd->cStencilBits > 8 ) { + crError( "wglChoosePixelFormat: asked for too many stencil bits\n" ); + } + + if ( pfd->cStencilBits > 0 ) + desiredVisual |= CR_STENCIL_BIT; + + if ( pfd->cAuxBuffers > 0 ) { + crError( "wglChoosePixelFormat: asked for aux buffers\n" ); + } + + if ( pfd->iLayerType != PFD_MAIN_PLANE ) { + crError( "wglChoosePixelFormat: asked for a strange layer\n" ); + } + + return 1; +} + +DECLEXPORT(BOOL) WINAPI wglSetPixelFormat_prox( HDC hdc, int pixelFormat, + CONST PIXELFORMATDESCRIPTOR *pdf ) +{ + CR_DDI_PROLOGUE(); + + if ( pixelFormat != 1 ) { + crError( "wglSetPixelFormat: pixelFormat=%d?\n", pixelFormat ); + } + + return 1; +} + +DECLEXPORT(BOOL) WINAPI wglDeleteContext_prox( HGLRC hglrc ) +{ + CR_DDI_PROLOGUE(); + stubDestroyContext( (unsigned long) hglrc ); + return 1; +} + +DECLEXPORT(BOOL) WINAPI wglMakeCurrent_prox( HDC hdc, HGLRC hglrc ) +{ + ContextInfo *context; + WindowInfo *window; + BOOL ret; + + CR_DDI_PROLOGUE(); + + crHashtableLock(stub.windowTable); + crHashtableLock(stub.contextTable); + + context = (ContextInfo *) crHashtableSearch(stub.contextTable, (unsigned long) hglrc); + window = stubGetWindowInfo(hdc); + + if (hglrc!=0 && !context) + { + crWarning("wglMakeCurrent got unexpected hglrc 0x%x", hglrc); + } + + ret = stubMakeCurrent( window, context ); + + crHashtableUnlock(stub.contextTable); + crHashtableUnlock(stub.windowTable); + + return ret; +} + +DECLEXPORT(HGLRC) WINAPI wglGetCurrentContext_prox( void ) +{ + ContextInfo *context = stubGetCurrentContext(); + CR_DDI_PROLOGUE(); + return (HGLRC) (context ? context->id : 0); +} + +DECLEXPORT(HDC) WINAPI wglGetCurrentDC_prox( void ) +{ + ContextInfo *context = stubGetCurrentContext(); + CR_DDI_PROLOGUE(); + if (context && context->currentDrawable) + return (HDC) context->currentDrawable->drawable; + else + return (HDC) NULL; +} + +DECLEXPORT(int) WINAPI wglGetPixelFormat_prox( HDC hdc ) +{ + CR_DDI_PROLOGUE(); + /* this is what we call our generic pixelformat, regardless of the HDC */ + return 1; +} + +DECLEXPORT(int) WINAPI wglDescribePixelFormat_prox( HDC hdc, int pixelFormat, UINT nBytes, + LPPIXELFORMATDESCRIPTOR pfd ) +{ + CR_DDI_PROLOGUE(); + +/* if ( pixelFormat != 1 ) { + * crError( "wglDescribePixelFormat: pixelFormat=%d?\n", pixelFormat ); + * return 0; + * } */ + + if ( !pfd ) { + crWarning( "wglDescribePixelFormat: pfd=NULL\n" ); + return 1; /* There's only one, baby */ + } + + if ( nBytes != sizeof(*pfd) ) { + crWarning( "wglDescribePixelFormat: nBytes=%u?\n", nBytes ); + return 1; /* There's only one, baby */ + } + + pfd->nSize = sizeof(*pfd); + pfd->nVersion = 1; + pfd->dwFlags = ( PFD_DRAW_TO_WINDOW | + PFD_SUPPORT_GDI | + PFD_SUPPORT_OPENGL | + PFD_DOUBLEBUFFER ); + pfd->iPixelType = PFD_TYPE_RGBA; + pfd->cColorBits = 32; + pfd->cRedBits = 8; + pfd->cRedShift = 24; + pfd->cGreenBits = 8; + pfd->cGreenShift = 16; + pfd->cBlueBits = 8; + pfd->cBlueShift = 8; + pfd->cAlphaBits = 8; + pfd->cAlphaShift = 0; + pfd->cAccumBits = 0; + pfd->cAccumRedBits = 0; + pfd->cAccumGreenBits = 0; + pfd->cAccumBlueBits = 0; + pfd->cAccumAlphaBits = 0; + pfd->cDepthBits = 32; + pfd->cStencilBits = 8; + pfd->cAuxBuffers = 0; + pfd->iLayerType = PFD_MAIN_PLANE; + pfd->bReserved = 0; + pfd->dwLayerMask = 0; + pfd->dwVisibleMask = 0; + pfd->dwDamageMask = 0; + + /* the max PFD index */ + return 1; +} + +DECLEXPORT(void) WINAPI VBoxCtxChromiumParameteriCR(HGLRC hglrc, GLenum param, GLint value) +{ + ContextInfo *context; + + CR_DDI_PROLOGUE(); + +// crHashtableLock(stub.windowTable); + crHashtableLock(stub.contextTable); + + context = (ContextInfo *) crHashtableSearch(stub.contextTable, (unsigned long) hglrc); + + if (context) + { + stubCtxCheckCreate(context); + stubConChromiumParameteriCR(CR_CTX_CON(context), param, value); + } + else + crWarning("invalid context %#x", hglrc); + + crHashtableUnlock(stub.contextTable); +// crHashtableUnlock(stub.windowTable); +} + +DECLEXPORT(BOOL) WINAPI wglShareLists_prox( HGLRC hglrc1, HGLRC hglrc2 ) +{ + ContextInfo *context1, *context2; + GLint aSpuContexts[2]; + + CR_DDI_PROLOGUE(); + +// crHashtableLock(stub.windowTable); + crHashtableLock(stub.contextTable); + + context1 = (ContextInfo *) crHashtableSearch(stub.contextTable, (unsigned long) hglrc1); + + if (!context1) + { + WARN(("invalid hglrc1")); + return FALSE; + } + + stubCtxCheckCreate(context1); + + context2 = (ContextInfo *) crHashtableSearch(stub.contextTable, (unsigned long) hglrc2); + + if (!context2) + { + WARN(("invalid hglrc2")); + return FALSE; + } + + stubCtxCheckCreate(context2); + + aSpuContexts[0] = context1->spuContext; + aSpuContexts[1] = context2->spuContext; + + stubConChromiumParametervCR(CR_CTX_CON(context2), GL_SHARE_LISTS_CR, GL_INT, 2, aSpuContexts); + + crHashtableUnlock(stub.contextTable); + + return TRUE; +} + +DECLEXPORT(HGLRC) WINAPI VBoxCreateContext( HDC hdc, struct VBOXUHGSMI *pHgsmi ) +{ + char *dpyName; + ContextInfo *context; + + CR_DDI_PROLOGUE(); + + stubInit(); + + CRASSERT(stub.contextTable); + + dpyName = crCalloc(MAX_DPY_NAME); + if (dpyName) + { + crMemset(dpyName, 0, MAX_DPY_NAME); + sprintf(dpyName, "%p", hdc); + } +#ifndef VBOX_CROGL_USE_VBITS_SUPERSET + if (stub.haveNativeOpenGL) + desiredVisual |= ComputeVisBits( hdc ); +#endif + + context = stubNewContext(dpyName, desiredVisual, UNDECIDED, 0 +#if defined(VBOX_WITH_CRHGSMI) && defined(IN_GUEST) + , pHgsmi +#else + , NULL +#endif + ); + /* Not needed any more. */ + crFree(dpyName); + + if (!context) + return 0; + + return (HGLRC) context->id; +} + +DECLEXPORT(GLint) WINAPI VBoxGetWindowId( HDC hdc ) +{ + WindowInfo *window; + GLint winid = 0; + + CR_DDI_PROLOGUE(); + + crHashtableLock(stub.windowTable); + + window = stubGetWindowInfo(hdc); + if (!window) + { + crWarning("stubGetWindowInfo: window not found!"); + goto end; + } + if (!window->spuWindow) + { + crWarning("stubGetWindowInfo: window is null!"); + goto end; + } + + winid = window->spuWindow; + +end: + crHashtableUnlock(stub.windowTable); + return winid; +} + +DECLEXPORT(GLint) WINAPI VBoxGetContextId( HGLRC hglrc ) +{ + ContextInfo *context; + GLint ctxid = 0; + + CR_DDI_PROLOGUE(); + +// crHashtableLock(stub.windowTable); + crHashtableLock(stub.contextTable); + + context = (ContextInfo *) crHashtableSearch(stub.contextTable, (unsigned long) hglrc); + if (!context) + { + crWarning("crHashtableSearch: context not found!"); + goto end; + } + + if (context->type != CHROMIUM) + { + crWarning("unexpected context type %d", context->type); + goto end; + } + + if (context->spuContext <= 0) + { + crWarning("no spuSontext defined"); + goto end; + } + + ctxid = context->spuContext; + +end: + crHashtableUnlock(stub.contextTable); + return ctxid; +} + + +DECLEXPORT(HGLRC) WINAPI wglCreateContext_prox( HDC hdc ) +{ + return VBoxCreateContext(hdc, NULL); +} + +DECLEXPORT(void) WINAPI VBoxFlushToHost ( HGLRC hglrc ) +{ + ContextInfo *context; + + CR_DDI_PROLOGUE(); + +// crHashtableLock(stub.windowTable); + crHashtableLock(stub.contextTable); + + context = (ContextInfo *) crHashtableSearch(stub.contextTable, (unsigned long) hglrc); + + if (context) + stubConFlush(CR_CTX_CON(context)); + else + crWarning("invalid context %#x", hglrc); + + crHashtableUnlock(stub.contextTable); +// crHashtableUnlock(stub.windowTable); +} + +DECLEXPORT(BOOL) WINAPI +wglSwapBuffers_prox( HDC hdc ) +{ + WindowInfo *window = stubGetWindowInfo(hdc); + CR_DDI_PROLOGUE(); + stubSwapBuffers( window, 0 ); + return 1; +} + +DECLEXPORT(BOOL) WINAPI wglCopyContext_prox( HGLRC src, HGLRC dst, UINT mask ) +{ + CR_DDI_PROLOGUE(); + crWarning( "wglCopyContext: unsupported" ); + return 0; +} + +DECLEXPORT(HGLRC) WINAPI wglCreateLayerContext_prox( HDC hdc, int layerPlane ) +{ + CR_DDI_PROLOGUE(); + stubInit(); + crWarning( "wglCreateLayerContext: unsupported" ); + return 0; +} + +DECLEXPORT(PROC) WINAPI wglGetProcAddress_prox( LPCSTR name ) +{ + CR_DDI_PROLOGUE(); + return (PROC) crGetProcAddress( name ); +} + +DECLEXPORT(BOOL) WINAPI wglUseFontBitmapsA_prox( HDC hdc, DWORD first, DWORD count, DWORD listBase ) +{ + CR_DDI_PROLOGUE(); + crWarning( "wglUseFontBitmapsA: unsupported" ); + return 0; +} + +DECLEXPORT(BOOL) WINAPI wglUseFontBitmapsW_prox( HDC hdc, DWORD first, DWORD count, DWORD listBase ) +{ + CR_DDI_PROLOGUE(); + crWarning( "wglUseFontBitmapsW: unsupported" ); + return 0; +} + +DECLEXPORT(BOOL) WINAPI wglDescribeLayerPlane_prox( HDC hdc, int pixelFormat, int layerPlane, + UINT nBytes, LPLAYERPLANEDESCRIPTOR lpd ) +{ + CR_DDI_PROLOGUE(); + crWarning( "wglDescribeLayerPlane: unimplemented" ); + return 0; +} + +DECLEXPORT(int) WINAPI wglSetLayerPaletteEntries_prox( HDC hdc, int layerPlane, int start, + int entries, CONST COLORREF *cr ) +{ + CR_DDI_PROLOGUE(); + crWarning( "wglSetLayerPaletteEntries: unsupported" ); + return 0; +} + +DECLEXPORT(int) WINAPI wglGetLayerPaletteEntries_prox( HDC hdc, int layerPlane, int start, + int entries, COLORREF *cr ) +{ + CR_DDI_PROLOGUE(); + crWarning( "wglGetLayerPaletteEntries: unsupported" ); + return 0; +} + +DECLEXPORT(BOOL) WINAPI wglRealizeLayerPalette_prox( HDC hdc, int layerPlane, BOOL realize ) +{ + CR_DDI_PROLOGUE(); + crWarning( "wglRealizeLayerPalette: unsupported" ); + return 0; +} + +DECLEXPORT(DWORD) WINAPI wglSwapMultipleBuffers_prox( UINT a, CONST void *b ) +{ + CR_DDI_PROLOGUE(); + crWarning( "wglSwapMultipleBuffer: unsupported" ); + return 0; +} + +DECLEXPORT(BOOL) WINAPI wglUseFontOutlinesA_prox( HDC hdc, DWORD first, DWORD count, DWORD listBase, + FLOAT deviation, FLOAT extrusion, int format, + LPGLYPHMETRICSFLOAT gmf ) +{ + CR_DDI_PROLOGUE(); + crWarning( "wglUseFontOutlinesA: unsupported" ); + return 0; +} + +DECLEXPORT(BOOL) WINAPI wglUseFontOutlinesW_prox( HDC hdc, DWORD first, DWORD count, DWORD listBase, + FLOAT deviation, FLOAT extrusion, int format, + LPGLYPHMETRICSFLOAT gmf ) +{ + CR_DDI_PROLOGUE(); + crWarning( "wglUseFontOutlinesW: unsupported" ); + return 0; +} + +DECLEXPORT(BOOL) WINAPI wglSwapLayerBuffers_prox( HDC hdc, UINT planes ) +{ + CR_DDI_PROLOGUE(); + if (planes == WGL_SWAP_MAIN_PLANE) + { + return wglSwapBuffers_prox(hdc); + } + else + { + crWarning( "wglSwapLayerBuffers: unsupported" ); + return 0; + } +} + +DECLEXPORT(BOOL) WINAPI wglChoosePixelFormatEXT_prox +(HDC hdc, const int *piAttributes, const FLOAT *pfAttributes, UINT nMaxFormats, int *piFormats, UINT *nNumFormats) +{ + int *pi; + int wants_rgb = 0; + + CR_DDI_PROLOGUE(); + + stubInit(); + + /** @todo : Need to check pfAttributes too ! */ + + for ( pi = (int *)piAttributes; *pi != 0; pi++ ) + { + switch ( *pi ) + { + case WGL_COLOR_BITS_EXT: + if (pi[1] > 8) + wants_rgb = 1; + pi++; + break; + + case WGL_RED_BITS_EXT: + case WGL_GREEN_BITS_EXT: + case WGL_BLUE_BITS_EXT: + if (pi[1] > 3) + wants_rgb = 1; + pi++; + break; + + case WGL_ACCUM_ALPHA_BITS_EXT: + case WGL_ALPHA_BITS_EXT: + if (pi[1] > 0) + desiredVisual |= CR_ALPHA_BIT; + pi++; + break; + + case WGL_DOUBLE_BUFFER_EXT: + if (pi[1] > 0) + desiredVisual |= CR_DOUBLE_BIT; + pi++; + break; + + case WGL_STEREO_EXT: + if (pi[1] > 0) + { + /** @todo this is disabled due to VSG Open Inventor interop issues + * it does not make any sense actually since reporting this + * as well as choosing a pixel format with this cap would not do anything + * since ICD stuff has its own pixelformat state var */ + crWarning("WGL_STEREO_EXT not supporteed!"); + return 0; +// desiredVisual |= CR_STEREO_BIT; + } + pi++; + break; + + case WGL_DEPTH_BITS_EXT: + if (pi[1] > 0) + desiredVisual |= CR_DEPTH_BIT; + pi++; + break; + + case WGL_STENCIL_BITS_EXT: + if (pi[1] > 0) + desiredVisual |= CR_STENCIL_BIT; + pi++; + break; + + case WGL_ACCUM_RED_BITS_EXT: + case WGL_ACCUM_GREEN_BITS_EXT: + case WGL_ACCUM_BLUE_BITS_EXT: + if (pi[1] > 0) + desiredVisual |= CR_ACCUM_BIT; + pi++; + break; + + case WGL_SAMPLE_BUFFERS_EXT: + case WGL_SAMPLES_EXT: + if (pi[1] > 0) + { + /** @todo this is disabled due to VSG Open Inventor interop issues + * it does not make any sense actually since reporting this + * as well as choosing a pixel format with this cap would not do anything + * since ICD stuff has its own pixelformat state var */ + crWarning("WGL_SAMPLE_BUFFERS_EXT & WGL_SAMPLES_EXT not supporteed!"); + return 0; +// desiredVisual |= CR_MULTISAMPLE_BIT; + } + pi++; + break; + + case WGL_SUPPORT_OPENGL_ARB: + case WGL_DRAW_TO_WINDOW_ARB: + case WGL_ACCELERATION_ARB: + pi++; + break; + + case WGL_PIXEL_TYPE_ARB: + if(pi[1]!=WGL_TYPE_RGBA_ARB) + { + crWarning("WGL_PIXEL_TYPE 0x%x not supported!", pi[1]); + return 0; + } + pi++; + break; + + default: + crWarning( "wglChoosePixelFormatEXT: bad pi=0x%x", *pi ); + return 0; + } + } + + if (nNumFormats) *nNumFormats = 1; + if (nMaxFormats>0 && piFormats) + { + piFormats[0] = 1; + } + + return 1; +} + +DECLEXPORT(BOOL) WINAPI wglGetPixelFormatAttribivEXT_prox +(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, int *pValues) +{ + UINT i; + + CR_DDI_PROLOGUE(); + + if (!pValues || !piAttributes) return 0; + + if ((nAttributes!=1) || (piAttributes && piAttributes[0]!=WGL_NUMBER_PIXEL_FORMATS_ARB)) + { + if (iPixelFormat!=1) + { + crDebug("wglGetPixelFormatAttribivARB: bad pf:%i", iPixelFormat); + return 0; + } + } + + for (i=0; i<nAttributes; ++i) + { + switch (piAttributes[i]) + { + case WGL_NUMBER_PIXEL_FORMATS_ARB: + pValues[i] = 1; + break; + case WGL_DRAW_TO_WINDOW_ARB: + case WGL_SUPPORT_OPENGL_ARB: + case WGL_DOUBLE_BUFFER_ARB: + pValues[i] = 1; + break; + case WGL_STEREO_ARB: + /** @todo this is disabled due to VSG Open Inventor interop issues + * it does not make any sense actually since reporting this + * as well as choosing a pixel format with this cap would not do anything + * since ICD stuff has its own pixelformat state var */ + pValues[i] = 0; + break; + case WGL_DRAW_TO_BITMAP_ARB: + case WGL_NEED_PALETTE_ARB: + case WGL_NEED_SYSTEM_PALETTE_ARB: + case WGL_SWAP_LAYER_BUFFERS_ARB: + case WGL_NUMBER_OVERLAYS_ARB: + case WGL_NUMBER_UNDERLAYS_ARB: + case WGL_TRANSPARENT_ARB: + case WGL_TRANSPARENT_RED_VALUE_ARB: + case WGL_TRANSPARENT_GREEN_VALUE_ARB: + case WGL_TRANSPARENT_BLUE_VALUE_ARB: + case WGL_TRANSPARENT_ALPHA_VALUE_ARB: + case WGL_TRANSPARENT_INDEX_VALUE_ARB: + case WGL_SHARE_DEPTH_ARB: + case WGL_SHARE_STENCIL_ARB: + case WGL_SHARE_ACCUM_ARB: + case WGL_SUPPORT_GDI_ARB: + pValues[i] = 0; + break; + case WGL_ACCELERATION_ARB: + pValues[i] = WGL_FULL_ACCELERATION_ARB; + break; + case WGL_SWAP_METHOD_ARB: + pValues[i] = WGL_SWAP_UNDEFINED_ARB; + break; + case WGL_PIXEL_TYPE_ARB: + pValues[i] = WGL_TYPE_RGBA_ARB; + break; + case WGL_COLOR_BITS_ARB: + pValues[i] = 32; + break; + case WGL_RED_BITS_ARB: + case WGL_GREEN_BITS_ARB: + case WGL_BLUE_BITS_ARB: + case WGL_ALPHA_BITS_ARB: + pValues[i] = 8; + break; + case WGL_RED_SHIFT_ARB: + pValues[i] = 24; + break; + case WGL_GREEN_SHIFT_ARB: + pValues[i] = 16; + break; + case WGL_BLUE_SHIFT_ARB: + pValues[i] = 8; + break; + case WGL_ALPHA_SHIFT_ARB: + pValues[i] = 0; + break; + case WGL_ACCUM_BITS_ARB: + pValues[i] = 0; + break; + case WGL_ACCUM_RED_BITS_ARB: + pValues[i] = 0; + break; + case WGL_ACCUM_GREEN_BITS_ARB: + pValues[i] = 0; + break; + case WGL_ACCUM_BLUE_BITS_ARB: + pValues[i] = 0; + break; + case WGL_ACCUM_ALPHA_BITS_ARB: + pValues[i] = 0; + break; + case WGL_DEPTH_BITS_ARB: + pValues[i] = 32; + break; + case WGL_STENCIL_BITS_ARB: + pValues[i] = 8; + break; + case WGL_AUX_BUFFERS_ARB: + pValues[i] = 0; + break; + case WGL_SAMPLE_BUFFERS_EXT: + /** @todo this is disabled due to VSG Open Inventor interop issues + * it does not make any sense actually since reporting this + * as well as choosing a pixel format with this cap would not do anything + * since ICD stuff has its own pixelformat state var */ + pValues[i] = 0; + break; + case WGL_SAMPLES_EXT: + /** @todo this is disabled due to VSG Open Inventor interop issues + * it does not make any sense actually since reporting this + * as well as choosing a pixel format with this cap would not do anything + * since ICD stuff has its own pixelformat state var */ + pValues[i] = 0; + break; + case 0x202d: /* <- WGL_DRAW_TO_PBUFFER_ARB this is to make VSG Open Inventor happy */ + pValues[i] = 0; + break; + default: + crWarning("wglGetPixelFormatAttribivARB: bad attrib=0x%x", piAttributes[i]); + return 0; + } + } + + return 1; +} + +DECLEXPORT(BOOL) WINAPI wglGetPixelFormatAttribfvEXT_prox +(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, float *pValues) +{ + UINT i; + + CR_DDI_PROLOGUE(); + + if (!pValues || !piAttributes) return 0; + + if ((nAttributes!=1) || (piAttributes && piAttributes[0]!=WGL_NUMBER_PIXEL_FORMATS_ARB)) + { + if (iPixelFormat!=1) + { + crDebug("wglGetPixelFormatAttribivARB: bad pf:%i", iPixelFormat); + return 0; + } + } + + for (i=0; i<nAttributes; ++i) + { + switch (piAttributes[i]) + { + case WGL_NUMBER_PIXEL_FORMATS_ARB: + pValues[i] = 1.f; + break; + case WGL_DRAW_TO_WINDOW_ARB: + case WGL_SUPPORT_OPENGL_ARB: + case WGL_DOUBLE_BUFFER_ARB: + pValues[i] = 1.f; + break; + case WGL_STEREO_ARB: + /** @todo this is disabled due to VSG Open Inventor interop issues + * it does not make any sense actually since reporting this + * as well as choosing a pixel format with this cap would not do anything + * since ICD stuff has its own pixelformat state var */ + pValues[i] = 0.f; + break; + case WGL_DRAW_TO_BITMAP_ARB: + case WGL_NEED_PALETTE_ARB: + case WGL_NEED_SYSTEM_PALETTE_ARB: + case WGL_SWAP_LAYER_BUFFERS_ARB: + case WGL_NUMBER_OVERLAYS_ARB: + case WGL_NUMBER_UNDERLAYS_ARB: + case WGL_TRANSPARENT_ARB: + case WGL_TRANSPARENT_RED_VALUE_ARB: + case WGL_TRANSPARENT_GREEN_VALUE_ARB: + case WGL_TRANSPARENT_BLUE_VALUE_ARB: + case WGL_TRANSPARENT_ALPHA_VALUE_ARB: + case WGL_TRANSPARENT_INDEX_VALUE_ARB: + case WGL_SHARE_DEPTH_ARB: + case WGL_SHARE_STENCIL_ARB: + case WGL_SHARE_ACCUM_ARB: + case WGL_SUPPORT_GDI_ARB: + pValues[i] = 0.f; + break; + case WGL_ACCELERATION_ARB: + pValues[i] = WGL_FULL_ACCELERATION_ARB; + break; + case WGL_SWAP_METHOD_ARB: + pValues[i] = WGL_SWAP_UNDEFINED_ARB; + break; + case WGL_PIXEL_TYPE_ARB: + pValues[i] = WGL_TYPE_RGBA_ARB; + break; + case WGL_COLOR_BITS_ARB: + pValues[i] = 32.f; + break; + case WGL_RED_BITS_ARB: + case WGL_GREEN_BITS_ARB: + case WGL_BLUE_BITS_ARB: + case WGL_ALPHA_BITS_ARB: + pValues[i] = 8.f; + break; + case WGL_RED_SHIFT_ARB: + pValues[i] = 24.f; + break; + case WGL_GREEN_SHIFT_ARB: + pValues[i] = 16.f; + break; + case WGL_BLUE_SHIFT_ARB: + pValues[i] = 8.f; + break; + case WGL_ALPHA_SHIFT_ARB: + pValues[i] = 0.f; + break; + case WGL_ACCUM_BITS_ARB: + pValues[i] = 0.f; + break; + case WGL_ACCUM_RED_BITS_ARB: + pValues[i] = 0.f; + break; + case WGL_ACCUM_GREEN_BITS_ARB: + pValues[i] = 0.f; + break; + case WGL_ACCUM_BLUE_BITS_ARB: + pValues[i] = 0.f; + break; + case WGL_ACCUM_ALPHA_BITS_ARB: + pValues[i] = 0.f; + break; + case WGL_DEPTH_BITS_ARB: + pValues[i] = 32.f; + break; + case WGL_STENCIL_BITS_ARB: + pValues[i] = 8.f; + break; + case WGL_AUX_BUFFERS_ARB: + pValues[i] = 0.f; + break; + case WGL_SAMPLE_BUFFERS_EXT: + /** @todo this is disabled due to VSG Open Inventor interop issues + * it does not make any sense actually since reporting this + * as well as choosing a pixel format with this cap would not do anything + * since ICD stuff has its own pixelformat state var */ + pValues[i] = 0.f; + break; + case WGL_SAMPLES_EXT: + /** @todo this is disabled due to VSG Open Inventor interop issues + * it does not make any sense actually since reporting this + * as well as choosing a pixel format with this cap would not do anything + * since ICD stuff has its own pixelformat state var */ + pValues[i] = 0.f; + break; + case 0x202d: /* <- WGL_DRAW_TO_PBUFFER_ARB this is to make VSG Open Inventor happy */ + pValues[i] = 0.f; + break; + default: + crWarning("wglGetPixelFormatAttribivARB: bad attrib=0x%x", piAttributes[i]); + return 0; + } + } + + return 1; +} + +DECLEXPORT(BOOL) WINAPI wglSwapIntervalEXT_prox(int interval) +{ + CR_DDI_PROLOGUE(); + return TRUE; +} + +DECLEXPORT(int) WINAPI wglGetSwapIntervalEXT_prox() +{ + CR_DDI_PROLOGUE(); + return 1; +} + +static GLubyte *gsz_wgl_extensions = "WGL_EXT_pixel_format WGL_ARB_pixel_format WGL_ARB_multisample"; + +DECLEXPORT(const GLubyte *) WINAPI wglGetExtensionsStringEXT_prox() +{ + CR_DDI_PROLOGUE(); + return gsz_wgl_extensions; +} + +DECLEXPORT(const GLubyte *) WINAPI wglGetExtensionsStringARB_prox(HDC hdc) +{ + CR_DDI_PROLOGUE(); + (void) hdc; + + return gsz_wgl_extensions; +} diff --git a/src/VBox/Additions/common/crOpenGL/windows_exports.py b/src/VBox/Additions/common/crOpenGL/windows_exports.py new file mode 100755 index 00000000..974d482e --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/windows_exports.py @@ -0,0 +1,98 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + + +from __future__ import print_function +import sys + +import apiutil + + +def GenerateEntrypoints(): + + apiutil.CopyrightC() + + print('#include "chromium.h"') + print('#include "stub.h"') + print('') + print('#define NAKED __declspec(naked)') + print('#define UNUSED(x) ((void)(x))') + print('') + + + # Get sorted list of dispatched functions. + # The order is very important - it must match cr_opcodes.h + # and spu_dispatch_table.h + keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + + for index in range(len(keys)): + func_name = keys[index] + if apiutil.Category(func_name) == "Chromium": + continue + if apiutil.Category(func_name) == "VBox": + continue + + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + + print("NAKED %s cr_gl%s(%s)" % (return_type, func_name, + apiutil.MakeDeclarationString( params ))) + print("{") + print("\t__asm jmp [glim.%s]" % func_name) + for (name, type, vecSize) in params: + print("\tUNUSED(%s);" % name) + print("}") + print("") + + print('/*') + print('* Aliases') + print('*/') + + # Now loop over all the functions and take care of any aliases + allkeys = apiutil.GetAllFunctions(sys.argv[1]+"/APIspec.txt") + for func_name in allkeys: + if "omit" in apiutil.ChromiumProps(func_name): + continue + + if func_name in keys: + # we already processed this function earlier + continue + + # alias is the function we're aliasing + alias = apiutil.Alias(func_name) + if alias: + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + print("NAKED %s cr_gl%s(%s)" % (return_type, func_name, + apiutil.MakeDeclarationString( params ))) + print("{") + print("\t__asm jmp [glim.%s]" % alias) + for (name, type, vecSize) in params: + print("\tUNUSED(%s);" % name) + print("}") + print("") + + + print('/*') + print('* No-op stubs') + print('*/') + + # Now generate no-op stub functions + for func_name in allkeys: + if "stub" in apiutil.ChromiumProps(func_name): + return_type = apiutil.ReturnType(func_name) + params = apiutil.Parameters(func_name) + print("NAKED %s cr_gl%s(%s)" % (return_type, func_name, apiutil.MakeDeclarationString(params))) + print("{") + if return_type != "void": + print("return (%s) 0" % return_type) + print("}") + print("") + + + + +GenerateEntrypoints() + diff --git a/src/VBox/Additions/common/crOpenGL/windows_getprocaddress.py b/src/VBox/Additions/common/crOpenGL/windows_getprocaddress.py new file mode 100755 index 00000000..0899d9d9 --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/windows_getprocaddress.py @@ -0,0 +1,171 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + +from __future__ import print_function +import sys + +import apiutil + +apiutil.CopyrightC() + +print(""" +/* DO NOT EDIT - THIS FILE GENERATED BY THE getprocaddress.py SCRIPT */ +#include "chromium.h" +#include "cr_string.h" +#include "cr_version.h" +#include "stub.h" +#include "icd_drv.h" +#include "cr_gl.h" +#include "cr_error.h" + +#ifdef WINDOWS +#pragma warning( disable: 4055 ) +#endif + +""") + +print(""" +struct name_address { + const char *name; + CR_PROC address; +}; + +PROC WINAPI wglGetProcAddress_prox( LPCSTR name ); + +static struct name_address functions[] = { +""") + + +keys = apiutil.GetAllFunctionsAndOmittedAliases(sys.argv[1]+"/APIspec.txt") +for func_name in keys: + if "Chromium" == apiutil.Category(func_name): + continue + if "VBox" == apiutil.Category(func_name): + continue + if func_name == "BoundsInfoCR": + continue + if "GL_chromium" == apiutil.Category(func_name): + pass #continue + + # alias is the function we're aliasing + proc_name = func_name + if "omit" in apiutil.ChromiumProps(func_name): + alias = apiutil.Alias(func_name) + if alias: + proc_name = alias + + wrap = apiutil.GetCategoryWrapper(func_name) + name = "gl" + func_name + address = "cr_gl" + proc_name + if wrap: + print('#ifdef CR_%s' % wrap) + print('\t{ "%s", (CR_PROC) %s },' % (name, address)) + if wrap: + print('#endif') + + +print("\t/* Chromium binding/glue functions */") + +for func_name in keys: + if (func_name == "Writeback" or + func_name == "BoundsInfoCR" or + func_name == "GetUniformsLocations" or + func_name == "GetAttribsLocations"): + continue + if apiutil.Category(func_name) == "Chromium": + print('\t{ "cr%s", (CR_PROC) cr%s },' % (func_name, func_name)) + +print("\t/* Windows ICD functions */") + +for func_name in ( "CopyContext", + "CreateContext", + "CreateLayerContext", + "DeleteContext", + "DescribeLayerPlane", + "DescribePixelFormat", + "GetLayerPaletteEntries", + "RealizeLayerPalette", + "SetLayerPaletteEntries", + "SetPixelFormat", + "ShareLists", + "SwapBuffers", + "SwapLayerBuffers", + "ReleaseContext", + "SetContext", + "ValidateVersion"): + print('\t{ "Drv%s", (CR_PROC) Drv%s },' % (func_name, func_name)) + +print('\t{ "DrvGetProcAddress", (CR_PROC) wglGetProcAddress_prox },') + +print(""" + { NULL, NULL } +}; + +extern const GLubyte * WINAPI wglGetExtensionsStringEXT_prox(void); +extern const GLubyte * WINAPI wglGetExtensionsStringARB_prox(HDC hdc); +extern BOOL WINAPI wglChoosePixelFormatEXT_prox(HDC hdc, const int *piAttributes, const FLOAT *pfAttributes, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +extern BOOL WINAPI wglGetPixelFormatAttribivEXT_prox(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, int *pValues); +extern BOOL WINAPI wglGetPixelFormatAttribfvEXT_prox(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, float *pValues); + +BOOL WINAPI wglSwapIntervalEXT(int interval) +{ + return false; +} + +CR_PROC CR_APIENTRY crGetProcAddress( const char *name ) +{ + int i; + wglGetExtensionsStringEXTFunc_t wglGetExtensionsStringEXT = wglGetExtensionsStringEXT_prox; + wglGetExtensionsStringARBFunc_t wglGetExtensionsStringARB = wglGetExtensionsStringARB_prox; + wglChoosePixelFormatEXTFunc_t wglChoosePixelFormatEXT = wglChoosePixelFormatEXT_prox; + wglGetPixelFormatAttribivEXTFunc_t wglGetPixelFormatAttribivEXT = wglGetPixelFormatAttribivEXT_prox; + wglGetPixelFormatAttribfvEXTFunc_t wglGetPixelFormatAttribfvEXT = wglGetPixelFormatAttribfvEXT_prox; + + stubInit(); + + for (i = 0; functions[i].name; i++) { + if (crStrcmp(name, functions[i].name) == 0) { + /*crDebug("crGetProcAddress(%s) returns %p", name, functions[i].address);*/ + return functions[i].address; + } + } + + if (!crStrcmp(name, "wglGetExtensionsStringEXT")) return (CR_PROC) wglGetExtensionsStringEXT; + if (!crStrcmp(name, "wglGetExtensionsStringARB")) return (CR_PROC) wglGetExtensionsStringARB; + + if (!crStrcmp(name, "wglChoosePixelFormatEXT")) return (CR_PROC) wglChoosePixelFormatEXT; + if (!crStrcmp(name, "wglGetPixelFormatAttribivEXT")) return (CR_PROC) wglGetPixelFormatAttribivEXT; + if (!crStrcmp(name, "wglGetPixelFormatAttribfvEXT")) return (CR_PROC) wglGetPixelFormatAttribfvEXT; + + if (!crStrcmp(name, "wglChoosePixelFormatARB")) return (CR_PROC) wglChoosePixelFormatEXT; + if (!crStrcmp(name, "wglGetPixelFormatAttribivARB")) return (CR_PROC) wglGetPixelFormatAttribivEXT; + if (!crStrcmp(name, "wglGetPixelFormatAttribfvARB")) return (CR_PROC) wglGetPixelFormatAttribfvEXT; + + if (!crStrcmp(name, "wglSwapIntervalEXT")) return (CR_PROC) wglSwapIntervalEXT; + + crDebug("Returning GetProcAddress:NULL for %s", name); + return NULL; +} + +""") + + + +# XXX should crGetProcAddress really handle WGL/GLX functions??? +print_foo = """ +/* As these are Windows specific (i.e. wgl), define these now.... */ +#ifdef WINDOWS + { + wglGetExtensionsStringEXTFunc_t wglGetExtensionsStringEXT = NULL; + wglChoosePixelFormatFunc_t wglChoosePixelFormatEXT = NULL; + wglGetPixelFormatAttribivEXTFunc_t wglGetPixelFormatAttribivEXT = NULL; + wglGetPixelFormatAttribfvEXTFunc_t wglGetPixelFormatAttribfvEXT = NULL; + if (!crStrcmp(name, "wglGetExtensionsStringEXT")) return (CR_PROC) wglGetExtensionsStringEXT; + if (!crStrcmp(name, "wglChoosePixelFormatEXT")) return (CR_PROC) wglChoosePixelFormatEXT; + if (!crStrcmp(name, "wglGetPixelFormatAttribivEXT")) return (CR_PROC) wglGetPixelFormatAttribivEXT; + if (!crStrcmp(name, "wglGetPixelFormatAttribfvEXT")) return (CR_PROC) wglGetPixelFormatAttribfvEXT; + } +#endif +""" diff --git a/src/VBox/Additions/common/crOpenGL/windows_i386_exports.py b/src/VBox/Additions/common/crOpenGL/windows_i386_exports.py new file mode 100755 index 00000000..5a4e744e --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/windows_i386_exports.py @@ -0,0 +1,95 @@ +# Copyright (c) 2001, Stanford University +# All rights reserved. +# +# See the file LICENSE.txt for information on redistributing this software. + + +from __future__ import print_function +import sys + +import apiutil + + +def GenerateEntrypoints(): + + #apiutil.CopyrightC() + print('%include "iprt/asmdefs.mac"') + print("") + print("%ifdef RT_ARCH_AMD64") + print("extern glim") + print("%else ; X86") + print("extern _glim") + print("%endif") + print("") + + # Get sorted list of dispatched functions. + # The order is very important - it must match cr_opcodes.h + # and spu_dispatch_table.h + keys = apiutil.GetDispatchedFunctions(sys.argv[1]+"/APIspec.txt") + + for index in range(len(keys)): + func_name = keys[index] + if apiutil.Category(func_name) == "Chromium": + continue + if apiutil.Category(func_name) == "VBox": + continue + + print("BEGINPROC_EXPORTED cr_gl%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tmov \trax, qword glim+%d" % (8*index)) + print("\tjmp \t[rax]") + print("%else ; X86") + print("\tmov \teax, dword _glim+%d" % (4*index)) + print("\tjmp \t[eax]") + print("%endif") + print("ENDPROC cr_gl%s" % func_name) + print("") + + + print(';') + print('; Aliases') + print(';') + + # Now loop over all the functions and take care of any aliases + allkeys = apiutil.GetAllFunctions(sys.argv[1]+"/APIspec.txt") + for func_name in allkeys: + if "omit" in apiutil.ChromiumProps(func_name): + continue + + if func_name in keys: + # we already processed this function earlier + continue + + # alias is the function we're aliasing + alias = apiutil.Alias(func_name) + if alias: + # this dict lookup should never fail (raise an exception)! + index = keys.index(alias) + print("BEGINPROC_EXPORTED cr_gl%s" % func_name) + print("%ifdef RT_ARCH_AMD64") + print("\tmov \trax, qword glim+%d" % (8*index)) + print("\tjmp \t[rax]") + print("%else ; X86") + print("\tmov \teax, dword _glim+%d" % (4*index)) + print("\tjmp \t[eax]") + print("%endif") + print("ENDPROC cr_gl%s" % func_name) + print("") + + + print(';') + print('; No-op stubs') + print(';') + + # Now generate no-op stub functions + for func_name in allkeys: + if "stub" in apiutil.ChromiumProps(func_name): + print("BEGINPROC_EXPORTED cr_gl%s" % func_name) + print("\tleave") + print("\tret") + print("ENDPROC cr_gl%s" % func_name) + print("") + + +GenerateEntrypoints() + diff --git a/src/VBox/Additions/common/crOpenGL/xfont.c b/src/VBox/Additions/common/crOpenGL/xfont.c new file mode 100644 index 00000000..9451fa4a --- /dev/null +++ b/src/VBox/Additions/common/crOpenGL/xfont.c @@ -0,0 +1,244 @@ +/* Copyright (c) 2001, Stanford University + * All rights reserved + * + * See the file LICENSE.txt for information on redistributing this software. + */ + +#include "chromium.h" +#include "cr_error.h" +#include "cr_mem.h" +#include "stub.h" + +/** code borrowed from Mesa */ + + +/** Fill a BITMAP with a character C from thew current font + in the graphics context GC. WIDTH is the width in bytes + and HEIGHT is the height in bits. + + Note that the generated bitmaps must be used with + + glPixelStorei (GL_UNPACK_SWAP_BYTES, GL_FALSE); + glPixelStorei (GL_UNPACK_LSB_FIRST, GL_FALSE); + glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei (GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei (GL_UNPACK_ALIGNMENT, 1); + + Possible optimizations: + + * use only one reusable pixmap with the maximum dimensions. + * draw the entire font into a single pixmap (careful with + proportional fonts!). +*/ + + +/** + * Generate OpenGL-compatible bitmap. + */ +static void +fill_bitmap(Display *dpy, Window win, GC gc, + unsigned int width, unsigned int height, + int x0, int y0, unsigned int c, GLubyte *bitmap) +{ + XImage *image; + unsigned int x, y; + Pixmap pixmap; + XChar2b char2b; + + pixmap = XCreatePixmap(dpy, win, 8*width, height, 1); + XSetForeground(dpy, gc, 0); + XFillRectangle(dpy, pixmap, gc, 0, 0, 8*width, height); + XSetForeground(dpy, gc, 1); + + char2b.byte1 = (c >> 8) & 0xff; + char2b.byte2 = (c & 0xff); + + XDrawString16(dpy, pixmap, gc, x0, y0, &char2b, 1); + + image = XGetImage(dpy, pixmap, 0, 0, 8*width, height, 1, XYPixmap); + if (image) { + /* Fill the bitmap (X11 and OpenGL are upside down wrt each other). */ + for (y = 0; y < height; y++) + for (x = 0; x < 8*width; x++) + if (XGetPixel(image, x, y)) + bitmap[width*(height - y - 1) + x/8] |= (1 << (7 - (x % 8))); + XDestroyImage(image); + } + + XFreePixmap(dpy, pixmap); +} + +/* + * determine if a given glyph is valid and return the + * corresponding XCharStruct. + */ +static XCharStruct *isvalid(XFontStruct *fs, unsigned int which) +{ + unsigned int rows, pages; + unsigned int byte1 = 0, byte2 = 0; + int i, valid = 1; + + rows = fs->max_byte1 - fs->min_byte1 + 1; + pages = fs->max_char_or_byte2 - fs->min_char_or_byte2 + 1; + + if (rows == 1) { + /* "linear" fonts */ + if ((fs->min_char_or_byte2 > which) || + (fs->max_char_or_byte2 < which)) valid = 0; + } + else { + /* "matrix" fonts */ + byte2 = which & 0xff; + byte1 = which >> 8; + if ((fs->min_char_or_byte2 > byte2) || + (fs->max_char_or_byte2 < byte2) || + (fs->min_byte1 > byte1) || + (fs->max_byte1 < byte1)) valid = 0; + } + + if (valid) { + if (fs->per_char) { + if (rows == 1) { + /* "linear" fonts */ + return fs->per_char + (which-fs->min_char_or_byte2); + } + else { + /* "matrix" fonts */ + i = ((byte1 - fs->min_byte1) * pages) + + (byte2 - fs->min_char_or_byte2); + return fs->per_char + i; + } + } + else { + return &fs->min_bounds; + } + } + return NULL; +} + + +void stubUseXFont( Display *dpy, Font font, int first, int count, int listbase ) +{ + Window win; + Pixmap pixmap; + GC gc; + XGCValues values; + unsigned long valuemask; + XFontStruct *fs; + GLint swapbytes, lsbfirst, rowlength; + GLint skiprows, skippixels, alignment; + unsigned int max_width, max_height, max_bm_width, max_bm_height; + GLubyte *bm; + int i; + + win = RootWindow(dpy, DefaultScreen(dpy)); + + fs = XQueryFont(dpy, font); + if (!fs) { + crWarning("Couldn't get font structure information"); + return; + } + + /* Allocate a bitmap that can fit all characters. */ + max_width = fs->max_bounds.rbearing - fs->min_bounds.lbearing; + max_height = fs->max_bounds.ascent + fs->max_bounds.descent; + max_bm_width = (max_width + 7) / 8; + max_bm_height = max_height; + + bm = (GLubyte *) crAlloc((max_bm_width * max_bm_height) * sizeof(GLubyte)); + if (!bm) { + XFreeFontInfo( NULL, fs, 1 ); + crWarning("Couldn't allocate bitmap in glXUseXFont()"); + return; + } + + /* Save the current packing mode for bitmaps. */ + glGetIntegerv(GL_UNPACK_SWAP_BYTES, &swapbytes); + glGetIntegerv(GL_UNPACK_LSB_FIRST, &lsbfirst); + glGetIntegerv(GL_UNPACK_ROW_LENGTH, &rowlength); + glGetIntegerv(GL_UNPACK_SKIP_ROWS, &skiprows); + glGetIntegerv(GL_UNPACK_SKIP_PIXELS, &skippixels); + glGetIntegerv(GL_UNPACK_ALIGNMENT, &alignment); + + /* Enforce a standard packing mode which is compatible with + fill_bitmap() from above. This is actually the default mode, + except for the (non)alignment. */ + glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_FALSE); + glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + pixmap = XCreatePixmap(dpy, win, 10, 10, 1); + values.foreground = BlackPixel(dpy, DefaultScreen (dpy)); + values.background = WhitePixel(dpy, DefaultScreen (dpy)); + values.font = fs->fid; + valuemask = GCForeground | GCBackground | GCFont; + gc = XCreateGC(dpy, pixmap, valuemask, &values); + XFreePixmap(dpy, pixmap); + + for (i = 0; i < count; i++) { + unsigned int width, height, bm_width, bm_height; + GLfloat x0, y0, dx, dy; + XCharStruct *ch; + int x, y; + unsigned int c = first + i; + int list = listbase + i; + int valid; + + /* check on index validity and get the bounds */ + ch = isvalid(fs, c); + if (!ch) { + ch = &fs->max_bounds; + valid = 0; + } + else { + valid = 1; + } + + /* glBitmap()' parameters: + straight from the glXUseXFont(3) manpage. */ + width = ch->rbearing - ch->lbearing; + height = ch->ascent + ch->descent; + x0 = -ch->lbearing; + y0 = ch->descent - 0; /* XXX used to subtract 1 here */ + /* but that caused a conformance failure */ + dx = ch->width; + dy = 0; + + /* X11's starting point. */ + x = -ch->lbearing; + y = ch->ascent; + + /* Round the width to a multiple of eight. We will use this also + for the pixmap for capturing the X11 font. This is slightly + inefficient, but it makes the OpenGL part real easy. */ + bm_width = (width + 7) / 8; + bm_height = height; + + glNewList(list, GL_COMPILE); + if (valid && (bm_width > 0) && (bm_height > 0)) { + crMemset(bm, '\0', bm_width * bm_height); + fill_bitmap(dpy, win, gc, bm_width, bm_height, x, y, c, bm); + glBitmap(width, height, x0, y0, dx, dy, bm); + } + else { + glBitmap(0, 0, 0.0, 0.0, dx, dy, NULL); + } + glEndList(); + } + + crFree(bm); + XFreeFontInfo(NULL, fs, 1); + XFreeGC(dpy, gc); + + /* Restore saved packing modes. */ + glPixelStorei(GL_UNPACK_SWAP_BYTES, swapbytes); + glPixelStorei(GL_UNPACK_LSB_FIRST, lsbfirst); + glPixelStorei(GL_UNPACK_ROW_LENGTH, rowlength); + glPixelStorei(GL_UNPACK_SKIP_ROWS, skiprows); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, skippixels); + glPixelStorei(GL_UNPACK_ALIGNMENT, alignment); +} diff --git a/src/VBox/Additions/common/pam/Makefile.kmk b/src/VBox/Additions/common/pam/Makefile.kmk new file mode 100644 index 00000000..06384ae1 --- /dev/null +++ b/src/VBox/Additions/common/pam/Makefile.kmk @@ -0,0 +1,35 @@ +# $Id: Makefile.kmk $ +## @file +# Makefile for VBox PAM module for automated logons. +# + +# +# Copyright (C) 2011-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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 += \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) +pam_vbox_SOURCES = pam_vbox.cpp +pam_vbox_LIBS = \ + pam \ + $(VBOX_LIB_IPRT_GUEST_R3_SHARED) \ + $(VBOX_LIB_VBGL_R3_SHARED) \ + $(VBOX_LIB_IPRT_GUEST_R3_SHARED) + +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..b3ad83e5 --- /dev/null +++ b/src/VBox/Additions/common/pam/pam_vbox.cpp @@ -0,0 +1,898 @@ +/* $Id: pam_vbox.cpp $ */ +/** @file + * pam_vbox - PAM module for auto logons. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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/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> +#ifdef VBOX_WITH_GUEST_PROPS +# include <VBox/HostServices/GuestPropertySvc.h> +#endif + +#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 +RTDECL(int) pam_sm_authenticate(pam_handle_t *hPAM, int iFlags, int argc, const char **argv); +RTDECL(int) pam_sm_setcred(pam_handle_t *hPAM, int iFlags, int argc, const char **argv); +RTDECL(int) pam_sm_acct_mgmt(pam_handle_t *hPAM, int iFlags, int argc, const char **argv); +RTDECL(int) pam_sm_open_session(pam_handle_t *hPAM, int iFlags, int argc, const char **argv); +RTDECL(int) pam_sm_close_session(pam_handle_t *hPAM, int iFlags, int argc, const char **argv); +RTDECL(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. + * + * @return IPRT status code. + * @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; +} + + +#ifdef VBOX_WITH_GUEST_PROPS +/** + * 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 < 10; i++) + { + void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf); + if (pvTmpBuf) + { + pvBuf = pvTmpBuf; + rc = VbglR3GuestPropRead(uClientID, pszKey, pvBuf, cbBuf, + &pszValTemp, &u64Timestamp, &pszFlags, + &cbBuf); + } + else + rc = VERR_NO_MEMORY; + + switch (rc) + { + case VERR_BUFFER_OVERFLOW: + { + /* Buffer too small, try it with a bigger one next time. */ + cbBuf += _1K; + continue; /* Try next round. */ + } + + default: + break; + } + + /* Everything except VERR_BUFFER_OVERLOW makes us bail out ... */ + break; + } + + 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); + } + } + + 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 < 10; 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); + } + else + rc = VERR_NO_MEMORY; + + if (rc == VERR_BUFFER_OVERFLOW) + { + /* Buffer too small, try it with a bigger one next time. */ + cbBuf += _1K; + continue; /* Try next round. */ + } + + /* Everything except VERR_BUFFER_OVERLOW makes us bail out ... */ + break; + } + + return rc; +} +#endif + +/** + * 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(); + +#ifdef VBOX_WITH_GUEST_PROPS + 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); +#endif + for (;;) + { +#ifdef VBOX_WITH_GUEST_PROPS + 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; + } + } +#endif + 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) ... */ +#ifndef VBOX_WITH_GUEST_PROPS + RTThreadSleep(500); /* Wait 500 ms. */ +#endif + } + 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; + } + } +#ifdef VBOX_WITH_GUEST_PROPS + } + VbglR3GuestPropDisconnect(uClientID); +#endif + + /* 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; + +#ifdef VBOX_WITH_GUEST_PROPS + 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); + } +#endif /* VBOX_WITH_GUEST_PROPS */ + + 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 +DECLEXPORT(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..8c632651 --- /dev/null +++ b/src/VBox/Additions/common/testcase/Makefile.kmk @@ -0,0 +1,35 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Cross Platform Guest Addition test cases. +# + +# +# Copyright (C) 2007-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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 + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/testcase/tstPageFusion.cpp b/src/VBox/Additions/common/testcase/tstPageFusion.cpp new file mode 100644 index 00000000..8ce52f94 --- /dev/null +++ b/src/VBox/Additions/common/testcase/tstPageFusion.cpp @@ -0,0 +1,379 @@ +/* $Id: tstPageFusion.cpp $ */ +/** @file + * VBoxService - Guest page sharing testcase + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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; +} + |