diff options
Diffstat (limited to '')
5 files changed, 2605 insertions, 0 deletions
diff --git a/src/VBox/Additions/common/VBoxControl/Makefile.kmk b/src/VBox/Additions/common/VBoxControl/Makefile.kmk new file mode 100644 index 00000000..432ca1c0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/Makefile.kmk @@ -0,0 +1,61 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Guest Additions Command Line Management Interface. +# + +# +# Copyright (C) 2010-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile(s). +include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# VBoxControl +# +PROGRAMS += VBoxControl +VBoxControl_TEMPLATE = VBoxGuestR3Exe +if "$(KBUILD_TARGET)" == "win" && defined(VBOX_SIGNING_MODE) && defined(VBOX_SIGN_ADDITIONS) # (See the main Windows Additions makefile.) + VBoxControl_INSTTYPE = none + VBoxControl_DEBUG_INSTTYPE = both +endif +VBoxControl_DEFS += \ + $(if $(VBOX_WITH_HGCM),VBOX_WITH_HGCM,) \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \ + $(if $(VBOX_WITH_SHARED_FOLDERS),VBOX_WITH_SHARED_FOLDERS,) +VBoxControl_DEFS.win += \ + $(if $(VBOX_WITH_DPC_LATENCY_CHECKER),VBOX_WITH_DPC_LATENCY_CHECKER,) +VBoxControl_SDKS = VBoxZlibStatic +VBoxControl_SOURCES = \ + VBoxControl.cpp +VBoxControl_SOURCES.win = \ + VBoxControl.rc +VBoxControl_LDFLAGS.darwin = -framework IOKit +VBoxControl_LIBS.netbsd = crypt +VBoxControl_USES.win += vboximportchecker +VBoxControl_VBOX_IMPORT_CHECKER.win.x86 = nt31 +VBoxControl_VBOX_IMPORT_CHECKER.win.amd64 = xp64 + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/VBoxControl/VBoxControl.cpp b/src/VBox/Additions/common/VBoxControl/VBoxControl.cpp new file mode 100644 index 00000000..6646e9d2 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/VBoxControl.cpp @@ -0,0 +1,2209 @@ +/* $Id: VBoxControl.cpp $ */ +/** @file + * VBoxControl - Guest Additions Command Line Management Interface. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/alloca.h> +#include <iprt/cpp/autores.h> +#include <iprt/buildconfig.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/zip.h> +#include <VBox/log.h> +#include <VBox/version.h> +#include <VBox/VBoxGuestLib.h> +#ifdef RT_OS_WINDOWS +# include <iprt/win/windows.h> +#endif +#ifdef VBOX_WITH_GUEST_PROPS +# include <VBox/HostServices/GuestPropertySvc.h> +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS +# include <VBox/shflsvc.h> +# ifdef RT_OS_OS2 +# define OS2EMX_PLAIN_CHAR +# define INCL_ERRORS +# define INCL_DOSFILEMGR +# include <os2emx.h> +# endif +#endif +#ifdef VBOX_WITH_DPC_LATENCY_CHECKER +# include <VBox/VBoxGuest.h> +# include "../VBoxGuest/lib/VBoxGuestR3LibInternal.h" /* HACK ALERT! Using vbglR3DoIOCtl directly!! */ +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The program name (derived from argv[0]). */ +char const *g_pszProgName = ""; +/** The current verbosity level. */ +int g_cVerbosity = 0; + + +/** @name Displays the program usage message. + * @{ + */ + +/** + * Helper function that does indentation. + * + * @param pszLine Text. + * @param pszName Program name. + * @param pszCommand Command/option syntax. + */ +static void doUsage(char const *pszLine, char const *pszName = "", char const *pszCommand = "") +{ + /* Allow for up to 15 characters command name length (VBoxControl.exe) with + * perfect column alignment. Beyond that there's at least one space between + * the command if there are command line parameters. */ + RTPrintf("%s %-*s%s%s\n", + pszName, + *pszLine ? 35 - strlen(pszName) : 1, pszCommand, + *pszLine ? " " : "", pszLine); +} + +/** Enumerate the different parts of the usage we might want to print out */ +enum VBoxControlUsage +{ +#ifdef RT_OS_WINDOWS + GET_VIDEO_ACCEL, + SET_VIDEO_ACCEL, + VIDEO_FLAGS, + LIST_CUST_MODES, + ADD_CUST_MODE, + REMOVE_CUST_MODE, + SET_VIDEO_MODE, +#endif +#ifdef VBOX_WITH_GUEST_PROPS + GUEST_PROP, +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS + GUEST_SHAREDFOLDERS, +#endif +#if !defined(VBOX_CONTROL_TEST) + WRITE_CORE_DUMP, +#endif + WRITE_LOG, + TAKE_SNAPSHOT, + SAVE_STATE, + SUSPEND, + POWER_OFF, + VERSION, + HELP, + USAGE_ALL = UINT32_MAX +}; + +static RTEXITCODE usage(enum VBoxControlUsage eWhich = USAGE_ALL) +{ + RTPrintf("Usage:\n\n"); + doUsage("print version number and exit", g_pszProgName, "[-V|--version]"); + doUsage("suppress the logo", g_pszProgName, "--nologo ..."); + RTPrintf("\n"); + + /* Exclude the Windows bits from the test version. Anyone who needs to + test them can fix this. */ +#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST) + if (eWhich == GET_VIDEO_ACCEL || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "getvideoacceleration"); + if (eWhich == SET_VIDEO_ACCEL || eWhich == USAGE_ALL) + doUsage("<on|off>", g_pszProgName, "setvideoacceleration"); + if (eWhich == VIDEO_FLAGS || eWhich == USAGE_ALL) + doUsage("<get|set|clear|delete> [hex mask]", g_pszProgName, "videoflags"); + if (eWhich == LIST_CUST_MODES || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "listcustommodes"); + if (eWhich == ADD_CUST_MODE || eWhich == USAGE_ALL) + doUsage("<width> <height> <bpp>", g_pszProgName, "addcustommode"); + if (eWhich == REMOVE_CUST_MODE || eWhich == USAGE_ALL) + doUsage("<width> <height> <bpp>", g_pszProgName, "removecustommode"); + if (eWhich == SET_VIDEO_MODE || eWhich == USAGE_ALL) + doUsage("<width> <height> <bpp> <screen>", g_pszProgName, "setvideomode"); +#endif +#ifdef VBOX_WITH_GUEST_PROPS + if (eWhich == GUEST_PROP || eWhich == USAGE_ALL) + { + doUsage("get <property> [--verbose]", g_pszProgName, "guestproperty"); + doUsage("set <property> [<value> [--flags <flags>]]", g_pszProgName, "guestproperty"); + doUsage("delete|unset <property>", g_pszProgName, "guestproperty"); + doUsage("enumerate [--patterns <patterns>]", g_pszProgName, "guestproperty"); + doUsage("wait <patterns>", g_pszProgName, "guestproperty"); + doUsage("[--timestamp <last timestamp>]"); + doUsage("[--timeout <timeout in ms>"); + } +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS + if (eWhich == GUEST_SHAREDFOLDERS || eWhich == USAGE_ALL) + { + doUsage("list [--automount]", g_pszProgName, "sharedfolder"); +# ifdef RT_OS_OS2 + doUsage("use <drive> <folder>", g_pszProgName, "sharedfolder"); + doUsage("unuse <drive>", g_pszProgName, "sharedfolder"); +# endif + } +#endif + +#if !defined(VBOX_CONTROL_TEST) + if (eWhich == WRITE_CORE_DUMP || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "writecoredump"); +#endif + if (eWhich == WRITE_LOG || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "writelog [-n|--no-newline] [--] <msg>"); + if (eWhich == TAKE_SNAPSHOT || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "takesnapshot"); + if (eWhich == SAVE_STATE || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "savestate"); + if (eWhich == SUSPEND || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "suspend"); + if (eWhich == POWER_OFF || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "poweroff"); + if (eWhich == HELP || eWhich == USAGE_ALL) + doUsage("[command]", g_pszProgName, "help"); + if (eWhich == VERSION || eWhich == USAGE_ALL) + doUsage("", g_pszProgName, "version"); + + return RTEXITCODE_SUCCESS; +} + +/** @} */ + + +/** + * Implementation of the '--version' option. + * + * @returns RTEXITCODE_SUCCESS + */ +static RTEXITCODE printVersion(void) +{ + RTPrintf("%sr%u\n", VBOX_VERSION_STRING, RTBldCfgRevision()); + return RTEXITCODE_SUCCESS; +} + + +/** + * Displays an error message. + * + * @returns RTEXITCODE_FAILURE. + * @param pszFormat The message text. No newline. + * @param ... Format arguments. + */ +static RTEXITCODE VBoxControlError(const char *pszFormat, ...) +{ + /** @todo prefix with current command. */ + va_list va; + va_start(va, pszFormat); + RTMsgErrorV(pszFormat, va); + va_end(va); + return RTEXITCODE_FAILURE; +} + + +/** + * Displays a getopt error. + * + * @returns RTEXITCODE_FAILURE. + * @param ch The RTGetOpt return value. + * @param pValueUnion The RTGetOpt return data. + */ +static RTEXITCODE VBoxCtrlGetOptError(int ch, PCRTGETOPTUNION pValueUnion) +{ + /** @todo prefix with current command. */ + return RTGetOptPrintError(ch, pValueUnion); +} + + +/** + * Displays an syntax error message. + * + * @returns RTEXITCODE_FAILURE. + * @param pszFormat The message text. No newline. + * @param ... Format arguments. + */ +static RTEXITCODE VBoxControlSyntaxError(const char *pszFormat, ...) +{ + /** @todo prefix with current command. */ + va_list va; + va_start(va, pszFormat); + RTMsgErrorV(pszFormat, va); + va_end(va); + return RTEXITCODE_SYNTAX; +} + +#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST) + +decltype(ChangeDisplaySettingsExA) *g_pfnChangeDisplaySettingsExA; +decltype(ChangeDisplaySettings) *g_pfnChangeDisplaySettingsA; +decltype(EnumDisplaySettingsA) *g_pfnEnumDisplaySettingsA; + +static unsigned nextAdjacentRectXP(RECTL const *paRects, unsigned cRects, unsigned iRect) +{ + for (unsigned i = 0; i < cRects; i++) + if (paRects[iRect].right == paRects[i].left) + return i; + return ~0U; +} + +static unsigned nextAdjacentRectXN(RECTL const *paRects, unsigned cRects, unsigned iRect) +{ + for (unsigned i = 0; i < cRects; i++) + if (paRects[iRect].left == paRects[i].right) + return i; + return ~0U; +} + +static unsigned nextAdjacentRectYP(RECTL const *paRects, unsigned cRects, unsigned iRect) +{ + for (unsigned i = 0; i < cRects; i++) + if (paRects[iRect].bottom == paRects[i].top) + return i; + return ~0U; +} + +unsigned nextAdjacentRectYN(RECTL const *paRects, unsigned cRects, unsigned iRect) +{ + for (unsigned i = 0; i < cRects; i++) + if (paRects[iRect].top == paRects[i].bottom) + return i; + return ~0U; +} + +void resizeRect(RECTL *paRects, unsigned cRects, unsigned iPrimary, unsigned iResized, int NewWidth, int NewHeight) +{ + RECTL *paNewRects = (RECTL *)alloca(sizeof (RECTL) * cRects); + memcpy (paNewRects, paRects, sizeof(RECTL) * cRects); + paNewRects[iResized].right += NewWidth - (paNewRects[iResized].right - paNewRects[iResized].left); + paNewRects[iResized].bottom += NewHeight - (paNewRects[iResized].bottom - paNewRects[iResized].top); + + /* Verify all pairs of originally adjacent rectangles for all 4 directions. + * If the pair has a "good" delta (that is the first rectangle intersects the second) + * at a direction and the second rectangle is not primary one (which can not be moved), + * move the second rectangle to make it adjacent to the first one. + */ + + /* X positive. */ + unsigned iRect; + for (iRect = 0; iRect < cRects; iRect++) + { + /* Find the next adjacent original rect in x positive direction. */ + unsigned iNextRect = nextAdjacentRectXP (paRects, cRects, iRect); + Log(("next %d -> %d\n", iRect, iNextRect)); + + if (iNextRect == ~0 || iNextRect == iPrimary) + { + continue; + } + + /* Check whether there is an X intersection between these adjacent rects in the new rectangles + * and fix the intersection if delta is "good". + */ + int delta = paNewRects[iRect].right - paNewRects[iNextRect].left; + + if (delta > 0) + { + Log(("XP intersection right %d left %d, diff %d\n", + paNewRects[iRect].right, paNewRects[iNextRect].left, + delta)); + + paNewRects[iNextRect].left += delta; + paNewRects[iNextRect].right += delta; + } + } + + /* X negative. */ + for (iRect = 0; iRect < cRects; iRect++) + { + /* Find the next adjacent original rect in x negative direction. */ + unsigned iNextRect = nextAdjacentRectXN (paRects, cRects, iRect); + Log(("next %d -> %d\n", iRect, iNextRect)); + + if (iNextRect == ~0 || iNextRect == iPrimary) + { + continue; + } + + /* Check whether there is an X intersection between these adjacent rects in the new rectangles + * and fix the intersection if delta is "good". + */ + int delta = paNewRects[iRect].left - paNewRects[iNextRect].right; + + if (delta < 0) + { + Log(("XN intersection left %d right %d, diff %d\n", + paNewRects[iRect].left, paNewRects[iNextRect].right, + delta)); + + paNewRects[iNextRect].left += delta; + paNewRects[iNextRect].right += delta; + } + } + + /* Y positive (in the computer sense, top->down). */ + for (iRect = 0; iRect < cRects; iRect++) + { + /* Find the next adjacent original rect in y positive direction. */ + unsigned iNextRect = nextAdjacentRectYP (paRects, cRects, iRect); + Log(("next %d -> %d\n", iRect, iNextRect)); + + if (iNextRect == ~0 || iNextRect == iPrimary) + { + continue; + } + + /* Check whether there is an Y intersection between these adjacent rects in the new rectangles + * and fix the intersection if delta is "good". + */ + int delta = paNewRects[iRect].bottom - paNewRects[iNextRect].top; + + if (delta > 0) + { + Log(("YP intersection bottom %d top %d, diff %d\n", + paNewRects[iRect].bottom, paNewRects[iNextRect].top, + delta)); + + paNewRects[iNextRect].top += delta; + paNewRects[iNextRect].bottom += delta; + } + } + + /* Y negative (in the computer sense, down->top). */ + for (iRect = 0; iRect < cRects; iRect++) + { + /* Find the next adjacent original rect in x negative direction. */ + unsigned iNextRect = nextAdjacentRectYN (paRects, cRects, iRect); + Log(("next %d -> %d\n", iRect, iNextRect)); + + if (iNextRect == ~0 || iNextRect == iPrimary) + { + continue; + } + + /* Check whether there is an Y intersection between these adjacent rects in the new rectangles + * and fix the intersection if delta is "good". + */ + int delta = paNewRects[iRect].top - paNewRects[iNextRect].bottom; + + if (delta < 0) + { + Log(("YN intersection top %d bottom %d, diff %d\n", + paNewRects[iRect].top, paNewRects[iNextRect].bottom, + delta)); + + paNewRects[iNextRect].top += delta; + paNewRects[iNextRect].bottom += delta; + } + } + + memcpy (paRects, paNewRects, sizeof (RECTL) * cRects); + return; +} + +/* Returns TRUE to try again. */ +static BOOL ResizeDisplayDevice(ULONG Id, DWORD Width, DWORD Height, DWORD BitsPerPixel) +{ + BOOL fModeReset = (Width == 0 && Height == 0 && BitsPerPixel == 0); + + DISPLAY_DEVICE DisplayDevice; + RT_ZERO(DisplayDevice); + DisplayDevice.cb = sizeof(DisplayDevice); + + /* Find out how many display devices the system has */ + DWORD NumDevices = 0; + DWORD i = 0; + while (EnumDisplayDevices(NULL, i, &DisplayDevice, 0)) + { + Log(("[%d] %s\n", i, DisplayDevice.DeviceName)); + + if (DisplayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) + { + Log(("Found primary device. err %d\n", GetLastError())); + NumDevices++; + } + else if (!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) + { + + Log(("Found secondary device. err %d\n", GetLastError())); + NumDevices++; + } + + RT_ZERO(DisplayDevice); + DisplayDevice.cb = sizeof(DisplayDevice); + i++; + } + + Log(("Found total %d devices. err %d\n", NumDevices, GetLastError())); + + if (NumDevices == 0 || Id >= NumDevices) + { + Log(("Requested identifier %d is invalid. err %d\n", Id, GetLastError())); + return FALSE; + } + + DISPLAY_DEVICE *paDisplayDevices = (DISPLAY_DEVICE *)alloca(sizeof (DISPLAY_DEVICE) * NumDevices); + DEVMODE *paDeviceModes = (DEVMODE *)alloca(sizeof (DEVMODE) * NumDevices); + RECTL *paRects = (RECTL *)alloca(sizeof (RECTL) * NumDevices); + + /* Fetch information about current devices and modes. */ + DWORD DevNum = 0; + DWORD DevPrimaryNum = 0; + + RT_ZERO(DisplayDevice); + DisplayDevice.cb = sizeof(DISPLAY_DEVICE); + + i = 0; + while (EnumDisplayDevices (NULL, i, &DisplayDevice, 0)) + { + Log(("[%d(%d)] %s\n", i, DevNum, DisplayDevice.DeviceName)); + + BOOL fFetchDevice = FALSE; + + if (DisplayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) + { + Log(("Found primary device. err %d\n", GetLastError())); + DevPrimaryNum = DevNum; + fFetchDevice = TRUE; + } + else if (!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) + { + + Log(("Found secondary device. err %d\n", GetLastError())); + fFetchDevice = TRUE; + } + + if (fFetchDevice) + { + if (DevNum >= NumDevices) + { + Log(("%d >= %d\n", NumDevices, DevNum)); + return FALSE; + } + + paDisplayDevices[DevNum] = DisplayDevice; + + RT_BZERO(&paDeviceModes[DevNum], sizeof(DEVMODE)); + paDeviceModes[DevNum].dmSize = sizeof(DEVMODE); + if (!g_pfnEnumDisplaySettingsA((LPSTR)DisplayDevice.DeviceName, ENUM_REGISTRY_SETTINGS, &paDeviceModes[DevNum])) + { + Log(("EnumDisplaySettings err %d\n", GetLastError())); + return FALSE; + } + + Log(("%dx%d at %d,%d\n", + paDeviceModes[DevNum].dmPelsWidth, + paDeviceModes[DevNum].dmPelsHeight, + paDeviceModes[DevNum].dmPosition.x, + paDeviceModes[DevNum].dmPosition.y)); + + paRects[DevNum].left = paDeviceModes[DevNum].dmPosition.x; + paRects[DevNum].top = paDeviceModes[DevNum].dmPosition.y; + paRects[DevNum].right = paDeviceModes[DevNum].dmPosition.x + paDeviceModes[DevNum].dmPelsWidth; + paRects[DevNum].bottom = paDeviceModes[DevNum].dmPosition.y + paDeviceModes[DevNum].dmPelsHeight; + DevNum++; + } + + RT_ZERO(DisplayDevice); + DisplayDevice.cb = sizeof(DISPLAY_DEVICE); + i++; + } + + if (Width == 0) + Width = paRects[Id].right - paRects[Id].left; + + if (Height == 0) + Height = paRects[Id].bottom - paRects[Id].top; + + /* Check whether a mode reset or a change is requested. */ + if ( !fModeReset + && paRects[Id].right - paRects[Id].left == (LONG)Width + && paRects[Id].bottom - paRects[Id].top == (LONG)Height + && paDeviceModes[Id].dmBitsPerPel == BitsPerPixel) + { + Log(("VBoxDisplayThread : already at desired resolution.\n")); + return FALSE; + } + + resizeRect(paRects, NumDevices, DevPrimaryNum, Id, Width, Height); +#ifdef LOG_ENABLED + for (i = 0; i < NumDevices; i++) + Log(("[%d]: %d,%d %dx%d\n", + i, paRects[i].left, paRects[i].top, + paRects[i].right - paRects[i].left, + paRects[i].bottom - paRects[i].top)); +#endif /* Log */ + + /* Without this, Windows will not ask the miniport for its + * mode table but uses an internal cache instead. + */ + DEVMODE tempDevMode; + RT_ZERO(tempDevMode); + tempDevMode.dmSize = sizeof(DEVMODE); + g_pfnEnumDisplaySettingsA(NULL, 0xffffff, &tempDevMode); + + /* Assign the new rectangles to displays. */ + for (i = 0; i < NumDevices; i++) + { + paDeviceModes[i].dmPosition.x = paRects[i].left; + paDeviceModes[i].dmPosition.y = paRects[i].top; + paDeviceModes[i].dmPelsWidth = paRects[i].right - paRects[i].left; + paDeviceModes[i].dmPelsHeight = paRects[i].bottom - paRects[i].top; + + paDeviceModes[i].dmFields = DM_POSITION | DM_PELSHEIGHT | DM_PELSWIDTH; + + if ( i == Id + && BitsPerPixel != 0) + { + paDeviceModes[i].dmFields |= DM_BITSPERPEL; + paDeviceModes[i].dmBitsPerPel = BitsPerPixel; + } + Log(("calling pfnChangeDisplaySettingsEx %p\n", RT_CB_LOG_CAST(g_pfnChangeDisplaySettingsExA))); + g_pfnChangeDisplaySettingsExA((LPSTR)paDisplayDevices[i].DeviceName, + &paDeviceModes[i], NULL, CDS_NORESET | CDS_UPDATEREGISTRY, NULL); + Log(("ChangeDisplaySettingsEx position err %d\n", GetLastError())); + } + + /* A second call to ChangeDisplaySettings updates the monitor. */ + LONG status = g_pfnChangeDisplaySettingsA(NULL, 0); + Log(("ChangeDisplaySettings update status %d\n", status)); + if (status == DISP_CHANGE_SUCCESSFUL || status == DISP_CHANGE_BADMODE) + { + /* Successfully set new video mode or our driver can not set the requested mode. Stop trying. */ + return FALSE; + } + + /* Retry the request. */ + return TRUE; +} + +static DECLCALLBACK(RTEXITCODE) handleSetVideoMode(int argc, char *argv[]) +{ + if (argc != 3 && argc != 4) + { + usage(SET_VIDEO_MODE); + return RTEXITCODE_FAILURE; + } + + DWORD xres = RTStrToUInt32(argv[0]); + DWORD yres = RTStrToUInt32(argv[1]); + DWORD bpp = RTStrToUInt32(argv[2]); + DWORD scr = 0; + if (argc == 4) + scr = RTStrToUInt32(argv[3]); + + HMODULE hmodUser = GetModuleHandle("user32.dll"); + if (hmodUser) + { + /* ChangeDisplaySettingsExA was probably added in W2K, whereas ChangeDisplaySettingsA + and EnumDisplaySettingsA was added in NT 3.51. */ + g_pfnChangeDisplaySettingsExA = (decltype(g_pfnChangeDisplaySettingsExA))GetProcAddress(hmodUser, "ChangeDisplaySettingsExA"); + g_pfnChangeDisplaySettingsA = (decltype(g_pfnChangeDisplaySettingsA)) GetProcAddress(hmodUser, "ChangeDisplaySettingsA"); + g_pfnEnumDisplaySettingsA = (decltype(g_pfnEnumDisplaySettingsA)) GetProcAddress(hmodUser, "EnumDisplaySettingsA"); + + Log(("VBoxService: g_pfnChangeDisplaySettingsExA=%p g_pfnChangeDisplaySettingsA=%p g_pfnEnumDisplaySettingsA=%p\n", + RT_CB_LOG_CAST(g_pfnChangeDisplaySettingsExA), RT_CB_LOG_CAST(g_pfnChangeDisplaySettingsA), + RT_CB_LOG_CAST(g_pfnEnumDisplaySettingsA))); + + if ( g_pfnChangeDisplaySettingsExA + && g_pfnChangeDisplaySettingsA + && g_pfnEnumDisplaySettingsA) + { + /* The screen index is 0 based in the ResizeDisplayDevice call. */ + scr = scr > 0 ? scr - 1 : 0; + + /* Horizontal resolution must be a multiple of 8, round down. */ + xres &= ~0x7; + + RTPrintf("Setting resolution of display %d to %dx%dx%d ...", scr, xres, yres, bpp); + ResizeDisplayDevice(scr, xres, yres, bpp); + RTPrintf("done.\n"); + } + else + VBoxControlError("Error retrieving API for display change!"); + } + else + VBoxControlError("Error retrieving handle to user32.dll!"); + + return RTEXITCODE_SUCCESS; +} + +static int checkVBoxVideoKey(HKEY hkeyVideo) +{ + RTUTF16 wszValue[128]; + DWORD cbValue = sizeof(wszValue); + DWORD dwKeyType; + LONG status = RegQueryValueExW(hkeyVideo, L"Device Description", NULL, &dwKeyType, (LPBYTE)wszValue, &cbValue); + if (status == ERROR_SUCCESS) + { + /* WDDM has additional chars after "Adapter" */ + static char s_szDeviceDescription[] = "VirtualBox Graphics Adapter"; + wszValue[sizeof(s_szDeviceDescription) - 1] = '\0'; + if (RTUtf16ICmpAscii(wszValue, s_szDeviceDescription) == 0) + return VINF_SUCCESS; + } + + return VERR_NOT_FOUND; +} + +static HKEY getVideoKey(bool writable) +{ + HKEY hkeyDeviceMap = 0; + LONG status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\VIDEO", 0, KEY_READ, &hkeyDeviceMap); + if (status != ERROR_SUCCESS || !hkeyDeviceMap) + { + VBoxControlError("Error opening video device map registry key!\n"); + return 0; + } + + HKEY hkeyVideo = 0; + ULONG iDevice; + DWORD dwKeyType; + + /* + * Scan all '\Device\VideoX' REG_SZ keys to find VBox video driver entry. + * 'ObjectNumberList' REG_BINARY is an array of 32 bit device indexes (X). + */ + + /* Get the 'ObjectNumberList' */ + ULONG cDevices = 0; + DWORD adwObjectNumberList[256]; + DWORD cbValue = sizeof(adwObjectNumberList); + status = RegQueryValueExA(hkeyDeviceMap, "ObjectNumberList", NULL, &dwKeyType, (LPBYTE)&adwObjectNumberList[0], &cbValue); + + if ( status == ERROR_SUCCESS + && dwKeyType == REG_BINARY) + cDevices = cbValue / sizeof(DWORD); + else + { + /* The list might not exists. Use 'MaxObjectNumber' REG_DWORD and build a list. */ + DWORD dwMaxObjectNumber = 0; + cbValue = sizeof(dwMaxObjectNumber); + status = RegQueryValueExA(hkeyDeviceMap, "MaxObjectNumber", NULL, &dwKeyType, (LPBYTE)&dwMaxObjectNumber, &cbValue); + if ( status == ERROR_SUCCESS + && dwKeyType == REG_DWORD) + { + /* 'MaxObjectNumber' is inclusive. */ + cDevices = RT_MIN(dwMaxObjectNumber + 1, RT_ELEMENTS(adwObjectNumberList)); + for (iDevice = 0; iDevice < cDevices; iDevice++) + adwObjectNumberList[iDevice] = iDevice; + } + } + + if (cDevices == 0) + { + /* Always try '\Device\Video0' as the old code did. Enum can be used in this case in principle. */ + adwObjectNumberList[0] = 0; + cDevices = 1; + } + + /* Scan device entries */ + for (iDevice = 0; iDevice < cDevices; iDevice++) + { + RTUTF16 wszValueName[64]; + RTUtf16Printf(wszValueName, RT_ELEMENTS(wszValueName), "\\Device\\Video%u", adwObjectNumberList[iDevice]); + + RTUTF16 wszVideoLocation[256]; + cbValue = sizeof(wszVideoLocation); + status = RegQueryValueExW(hkeyDeviceMap, wszValueName, NULL, &dwKeyType, (LPBYTE)&wszVideoLocation[0], &cbValue); + + /* This value starts with '\REGISTRY\Machine' */ + if ( status == ERROR_SUCCESS + && dwKeyType == REG_SZ + && RTUtf16NICmpAscii(wszVideoLocation, RT_STR_TUPLE("\\REGISTRY\\Machine")) == 0) + { + status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, &wszVideoLocation[18], 0, + KEY_READ | (writable ? KEY_WRITE : 0), &hkeyVideo); + if (status == ERROR_SUCCESS) + { + int rc = checkVBoxVideoKey(hkeyVideo); + if (RT_SUCCESS(rc)) + { + /* Found, return hkeyVideo to the caller. */ + break; + } + + RegCloseKey(hkeyVideo); + hkeyVideo = 0; + } + } + } + + if (hkeyVideo == 0) + { + VBoxControlError("Error opening video registry key!\n"); + } + + RegCloseKey(hkeyDeviceMap); + return hkeyVideo; +} + +static DECLCALLBACK(RTEXITCODE) handleGetVideoAcceleration(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); + ULONG status; + HKEY hkeyVideo = getVideoKey(false); + + if (hkeyVideo) + { + /* query the actual value */ + DWORD fAcceleration = 1; + DWORD cbValue = sizeof(fAcceleration); + DWORD dwKeyType; + status = RegQueryValueExA(hkeyVideo, "EnableVideoAccel", NULL, &dwKeyType, (LPBYTE)&fAcceleration, &cbValue); + if (status != ERROR_SUCCESS) + RTPrintf("Video acceleration: default\n"); + else + RTPrintf("Video acceleration: %s\n", fAcceleration ? "on" : "off"); + RegCloseKey(hkeyVideo); + } + return RTEXITCODE_SUCCESS; +} + +static DECLCALLBACK(RTEXITCODE) handleSetVideoAcceleration(int argc, char *argv[]) +{ + ULONG status; + HKEY hkeyVideo; + + /* must have exactly one argument: the new offset */ + if ( (argc != 1) + || ( RTStrICmp(argv[0], "on") + && RTStrICmp(argv[0], "off"))) + { + usage(SET_VIDEO_ACCEL); + return RTEXITCODE_FAILURE; + } + + hkeyVideo = getVideoKey(true); + + if (hkeyVideo) + { + int fAccel = 0; + if (RTStrICmp(argv[0], "on") == 0) + fAccel = 1; + /* set a new value */ + status = RegSetValueExA(hkeyVideo, "EnableVideoAccel", 0, REG_DWORD, (LPBYTE)&fAccel, sizeof(fAccel)); + if (status != ERROR_SUCCESS) + { + VBoxControlError("Error %d writing video acceleration status!\n", status); + } + RegCloseKey(hkeyVideo); + } + return RTEXITCODE_SUCCESS; +} + +static DECLCALLBACK(RTEXITCODE) videoFlagsGet(void) +{ + HKEY hkeyVideo = getVideoKey(false); + + if (hkeyVideo) + { + DWORD dwFlags = 0; + DWORD cbValue = sizeof(dwFlags); + DWORD dwKeyType; + ULONG status = RegQueryValueExA(hkeyVideo, "VBoxVideoFlags", NULL, &dwKeyType, (LPBYTE)&dwFlags, &cbValue); + if (status != ERROR_SUCCESS) + RTPrintf("Video flags: default\n"); + else + RTPrintf("Video flags: 0x%08X\n", dwFlags); + RegCloseKey(hkeyVideo); + return RTEXITCODE_SUCCESS; + } + + return RTEXITCODE_FAILURE; +} + +static DECLCALLBACK(RTEXITCODE) videoFlagsDelete(void) +{ + HKEY hkeyVideo = getVideoKey(true); + + if (hkeyVideo) + { + ULONG status = RegDeleteValueA(hkeyVideo, "VBoxVideoFlags"); + if (status != ERROR_SUCCESS) + VBoxControlError("Error %d deleting video flags.\n", status); + RegCloseKey(hkeyVideo); + return RTEXITCODE_SUCCESS; + } + + return RTEXITCODE_FAILURE; +} + +static DECLCALLBACK(RTEXITCODE) videoFlagsModify(bool fSet, int argc, char *argv[]) +{ + if (argc != 1) + { + VBoxControlError("Mask required.\n"); + return RTEXITCODE_FAILURE; + } + + uint32_t u32Mask = 0; + int rc = RTStrToUInt32Full(argv[0], 16, &u32Mask); + if (RT_FAILURE(rc)) + { + VBoxControlError("Invalid video flags mask.\n"); + return RTEXITCODE_FAILURE; + } + + RTEXITCODE exitCode = RTEXITCODE_SUCCESS; + + HKEY hkeyVideo = getVideoKey(true); + if (hkeyVideo) + { + DWORD dwFlags = 0; + DWORD cbValue = sizeof(dwFlags); + DWORD dwKeyType; + ULONG status = RegQueryValueExA(hkeyVideo, "VBoxVideoFlags", NULL, &dwKeyType, (LPBYTE)&dwFlags, &cbValue); + if (status != ERROR_SUCCESS) + dwFlags = 0; + + dwFlags = fSet ? dwFlags | u32Mask : dwFlags & ~u32Mask; + + status = RegSetValueExA(hkeyVideo, "VBoxVideoFlags", 0, REG_DWORD, (LPBYTE)&dwFlags, sizeof(dwFlags)); + if (status != ERROR_SUCCESS) + { + VBoxControlError("Error %d writing video flags.\n", status); + exitCode = RTEXITCODE_FAILURE; + } + + RegCloseKey(hkeyVideo); + } + else + { + exitCode = RTEXITCODE_FAILURE; + } + + return exitCode; +} + +static DECLCALLBACK(RTEXITCODE) handleVideoFlags(int argc, char *argv[]) +{ + /* Must have a keyword and optional value (32 bit hex string). */ + if (argc != 1 && argc != 2) + { + VBoxControlError("Invalid number of arguments.\n"); + usage(VIDEO_FLAGS); + return RTEXITCODE_FAILURE; + } + + RTEXITCODE exitCode = RTEXITCODE_SUCCESS; + + if (RTStrICmp(argv[0], "get") == 0) + { + exitCode = videoFlagsGet(); + } + else if (RTStrICmp(argv[0], "delete") == 0) + { + exitCode = videoFlagsDelete(); + } + else if (RTStrICmp(argv[0], "set") == 0) + { + exitCode = videoFlagsModify(true, argc - 1, &argv[1]); + } + else if (RTStrICmp(argv[0], "clear") == 0) + { + exitCode = videoFlagsModify(false, argc - 1, &argv[1]); + } + else + { + VBoxControlError("Invalid command.\n"); + exitCode = RTEXITCODE_FAILURE; + } + + if (exitCode != RTEXITCODE_SUCCESS) + { + usage(VIDEO_FLAGS); + } + + return exitCode; +} + +#define MAX_CUSTOM_MODES 128 + +/* the table of custom modes */ +struct +{ + DWORD xres; + DWORD yres; + DWORD bpp; +} customModes[MAX_CUSTOM_MODES] = {{0}}; + +void getCustomModes(HKEY hkeyVideo) +{ + ULONG status; + int curMode = 0; + + /* null out the table */ + RT_ZERO(customModes); + + do + { + char valueName[20]; + DWORD xres, yres, bpp = 0; + DWORD dwType; + DWORD dwLen = sizeof(DWORD); + + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", curMode); + status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&xres, &dwLen); + if (status != ERROR_SUCCESS) + break; + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", curMode); + status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&yres, &dwLen); + if (status != ERROR_SUCCESS) + break; + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", curMode); + status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&bpp, &dwLen); + if (status != ERROR_SUCCESS) + break; + + /* check if the mode is OK */ + if ( (xres > (1 << 16)) + || (yres > (1 << 16)) + || ( (bpp != 16) + && (bpp != 24) + && (bpp != 32))) + break; + + /* add mode to table */ + customModes[curMode].xres = xres; + customModes[curMode].yres = yres; + customModes[curMode].bpp = bpp; + + ++curMode; + + if (curMode >= MAX_CUSTOM_MODES) + break; + } while(1); +} + +void writeCustomModes(HKEY hkeyVideo) +{ + ULONG status; + int tableIndex = 0; + int modeIndex = 0; + + /* first remove all values */ + for (int i = 0; i < MAX_CUSTOM_MODES; i++) + { + char valueName[20]; + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", i); + RegDeleteValueA(hkeyVideo, valueName); + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", i); + RegDeleteValueA(hkeyVideo, valueName); + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", i); + RegDeleteValueA(hkeyVideo, valueName); + } + + do + { + if (tableIndex >= MAX_CUSTOM_MODES) + break; + + /* is the table entry present? */ + if ( (!customModes[tableIndex].xres) + || (!customModes[tableIndex].yres) + || (!customModes[tableIndex].bpp)) + { + tableIndex++; + continue; + } + + RTPrintf("writing mode %d (%dx%dx%d)\n", modeIndex, customModes[tableIndex].xres, customModes[tableIndex].yres, customModes[tableIndex].bpp); + char valueName[20]; + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", modeIndex); + status = RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].xres, + sizeof(customModes[tableIndex].xres)); + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", modeIndex); + RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].yres, + sizeof(customModes[tableIndex].yres)); + RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", modeIndex); + RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].bpp, + sizeof(customModes[tableIndex].bpp)); + + modeIndex++; + tableIndex++; + + } while(1); + +} + +static DECLCALLBACK(RTEXITCODE) handleListCustomModes(int argc, char *argv[]) +{ + RT_NOREF1(argv); + if (argc != 0) + { + usage(LIST_CUST_MODES); + return RTEXITCODE_FAILURE; + } + + HKEY hkeyVideo = getVideoKey(false); + + if (hkeyVideo) + { + getCustomModes(hkeyVideo); + for (int i = 0; i < (sizeof(customModes) / sizeof(customModes[0])); i++) + { + if ( !customModes[i].xres + || !customModes[i].yres + || !customModes[i].bpp) + continue; + + RTPrintf("Mode: %d x %d x %d\n", + customModes[i].xres, customModes[i].yres, customModes[i].bpp); + } + RegCloseKey(hkeyVideo); + } + return RTEXITCODE_SUCCESS; +} + +static DECLCALLBACK(RTEXITCODE) handleAddCustomMode(int argc, char *argv[]) +{ + if (argc != 3) + { + usage(ADD_CUST_MODE); + return RTEXITCODE_FAILURE; + } + + DWORD xres = RTStrToUInt32(argv[0]); + DWORD yres = RTStrToUInt32(argv[1]); + DWORD bpp = RTStrToUInt32(argv[2]); + + /** @todo better check including xres mod 8 = 0! */ + if ( (xres > (1 << 16)) + || (yres > (1 << 16)) + || ( (bpp != 16) + && (bpp != 24) + && (bpp != 32))) + { + VBoxControlError("invalid mode specified!\n"); + return RTEXITCODE_FAILURE; + } + + HKEY hkeyVideo = getVideoKey(true); + + if (hkeyVideo) + { + int i; + int fModeExists = 0; + getCustomModes(hkeyVideo); + for (i = 0; i < MAX_CUSTOM_MODES; i++) + { + /* mode exists? */ + if ( customModes[i].xres == xres + && customModes[i].yres == yres + && customModes[i].bpp == bpp + ) + { + fModeExists = 1; + } + } + if (!fModeExists) + { + for (i = 0; i < MAX_CUSTOM_MODES; i++) + { + /* item free? */ + if (!customModes[i].xres) + { + customModes[i].xres = xres; + customModes[i].yres = yres; + customModes[i].bpp = bpp; + break; + } + } + writeCustomModes(hkeyVideo); + } + RegCloseKey(hkeyVideo); + } + return RTEXITCODE_SUCCESS; +} + +static DECLCALLBACK(RTEXITCODE) handleRemoveCustomMode(int argc, char *argv[]) +{ + if (argc != 3) + { + usage(REMOVE_CUST_MODE); + return RTEXITCODE_FAILURE; + } + + DWORD xres = RTStrToUInt32(argv[0]); + DWORD yres = RTStrToUInt32(argv[1]); + DWORD bpp = RTStrToUInt32(argv[2]); + + HKEY hkeyVideo = getVideoKey(true); + + if (hkeyVideo) + { + getCustomModes(hkeyVideo); + for (int i = 0; i < MAX_CUSTOM_MODES; i++) + { + /* correct item? */ + if ( (customModes[i].xres == xres) + && (customModes[i].yres == yres) + && (customModes[i].bpp == bpp)) + { + RTPrintf("found mode at index %d\n", i); + RT_ZERO(customModes[i]); + break; + } + } + writeCustomModes(hkeyVideo); + RegCloseKey(hkeyVideo); + } + + return RTEXITCODE_SUCCESS; +} + +#endif /* RT_OS_WINDOWS */ + +#ifdef VBOX_WITH_GUEST_PROPS +/** + * Retrieves a value from the guest property store. + * This is accessed through the "VBoxGuestPropSvc" HGCM service. + * + * @returns Command exit code. + * @note see the command line API description for parameters + */ +static RTEXITCODE getGuestProperty(int argc, char **argv) +{ + bool fVerbose = false; + if ( argc == 2 + && ( strcmp(argv[1], "-verbose") == 0 + || strcmp(argv[1], "--verbose") == 0) + ) + fVerbose = true; + else if (argc != 1) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + + uint32_t u32ClientId = 0; + int rc = VINF_SUCCESS; + + rc = VbglR3GuestPropConnect(&u32ClientId); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc); + + /* + * Here we actually retrieve the value from the host. + */ + const char *pszName = argv[0]; + char *pszValue = NULL; + uint64_t u64Timestamp = 0; + char *pszFlags = NULL; + /* The buffer for storing the data and its initial size. We leave a bit + * of space here in case the maximum values are raised. */ + void *pvBuf = NULL; + uint32_t cbBuf = GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN + 1024; + if (RT_SUCCESS(rc)) + { + /* Because there is a race condition between our reading the size of a + * property and the guest updating it, we loop a few times here and + * hope. Actually this should never go wrong, as we are generous + * enough with buffer space. */ + bool fFinished = false; + for (unsigned i = 0; i < 10 && !fFinished; ++i) + { + void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf); + if (NULL == pvTmpBuf) + { + rc = VERR_NO_MEMORY; + VBoxControlError("Out of memory\n"); + } + else + { + pvBuf = pvTmpBuf; + rc = VbglR3GuestPropRead(u32ClientId, pszName, pvBuf, cbBuf, + &pszValue, &u64Timestamp, &pszFlags, + &cbBuf); + } + if (VERR_BUFFER_OVERFLOW == rc) + /* Leave a bit of extra space to be safe */ + cbBuf += 1024; + else + fFinished = true; + } + if (VERR_TOO_MUCH_DATA == rc) + VBoxControlError("Temporarily unable to retrieve the property\n"); + else if (RT_FAILURE(rc) && rc != VERR_NOT_FOUND) + VBoxControlError("Failed to retrieve the property value, error %Rrc\n", rc); + } + + /* + * And display it on the guest console. + */ + if (VERR_NOT_FOUND == rc) + RTPrintf("No value set!\n"); + else if (RT_SUCCESS(rc)) + { + RTPrintf("Value: %s\n", pszValue); + if (fVerbose) + { + RTPrintf("Timestamp: %lld ns\n", u64Timestamp); + RTPrintf("Flags: %s\n", pszFlags); + } + } + + if (u32ClientId != 0) + VbglR3GuestPropDisconnect(u32ClientId); + RTMemFree(pvBuf); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Writes a value to the guest property store. + * This is accessed through the "VBoxGuestPropSvc" HGCM service. + * + * @returns Command exit code. + * @note see the command line API description for parameters + */ +static RTEXITCODE setGuestProperty(int argc, char *argv[]) +{ + /* + * Check the syntax. We can deduce the correct syntax from the number of + * arguments. + */ + bool fUsageOK = true; + const char *pszName = NULL; + const char *pszValue = NULL; + const char *pszFlags = NULL; + if (2 == argc) + { + pszValue = argv[1]; + } + else if (3 == argc) + fUsageOK = false; + else if (4 == argc) + { + pszValue = argv[1]; + if ( strcmp(argv[2], "-flags") != 0 + && strcmp(argv[2], "--flags") != 0) + fUsageOK = false; + pszFlags = argv[3]; + } + else if (argc != 1) + fUsageOK = false; + if (!fUsageOK) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + /* This is always needed. */ + pszName = argv[0]; + + /* + * Do the actual setting. + */ + uint32_t u32ClientId = 0; + int rc = VINF_SUCCESS; + rc = VbglR3GuestPropConnect(&u32ClientId); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc); + else + { + if (pszFlags != NULL) + rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, pszFlags); + else + rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, pszValue); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to store the property value, error %Rrc\n", rc); + } + + if (u32ClientId != 0) + VbglR3GuestPropDisconnect(u32ClientId); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Deletes a guest property from the guest property store. + * This is accessed through the "VBoxGuestPropSvc" HGCM service. + * + * @returns Command exit code. + * @note see the command line API description for parameters + */ +static RTEXITCODE deleteGuestProperty(int argc, char *argv[]) +{ + /* + * Check the syntax. We can deduce the correct syntax from the number of + * arguments. + */ + bool fUsageOK = true; + const char *pszName = NULL; + if (argc < 1) + fUsageOK = false; + if (!fUsageOK) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + /* This is always needed. */ + pszName = argv[0]; + + /* + * Do the actual setting. + */ + uint32_t u32ClientId = 0; + int rc = VbglR3GuestPropConnect(&u32ClientId); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc); + else + { + rc = VbglR3GuestPropDelete(u32ClientId, pszName); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to delete the property value, error %Rrc\n", rc); + } + + if (u32ClientId != 0) + VbglR3GuestPropDisconnect(u32ClientId); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Enumerates the properties in the guest property store. + * This is accessed through the "VBoxGuestPropSvc" HGCM service. + * + * @returns Command exit code. + * @note see the command line API description for parameters + */ +static RTEXITCODE enumGuestProperty(int argc, char *argv[]) +{ + /* + * Check the syntax. We can deduce the correct syntax from the number of + * arguments. + */ + char const * const *papszPatterns = NULL; + uint32_t cPatterns = 0; + if ( argc > 1 + && ( strcmp(argv[0], "-patterns") == 0 + || strcmp(argv[0], "--patterns") == 0)) + { + papszPatterns = (char const * const *)&argv[1]; + cPatterns = argc - 1; + } + else if (argc != 0) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + + /* + * Do the actual enumeration. + */ + uint32_t u32ClientId = 0; + int rc = VbglR3GuestPropConnect(&u32ClientId); + if (RT_SUCCESS(rc)) + { + PVBGLR3GUESTPROPENUM pHandle; + const char *pszName, *pszValue, *pszFlags; + uint64_t u64Timestamp; + + rc = VbglR3GuestPropEnum(u32ClientId, papszPatterns, cPatterns, &pHandle, + &pszName, &pszValue, &u64Timestamp, &pszFlags); + if (RT_SUCCESS(rc)) + { + while (RT_SUCCESS(rc) && pszName) + { + RTPrintf("Name: %s, value: %s, timestamp: %lld, flags: %s\n", + pszName, pszValue ? pszValue : "", u64Timestamp, pszFlags); + + rc = VbglR3GuestPropEnumNext(pHandle, &pszName, &pszValue, &u64Timestamp, &pszFlags); + if (RT_FAILURE(rc)) + VBoxControlError("Error while enumerating guest properties: %Rrc\n", rc); + } + + VbglR3GuestPropEnumFree(pHandle); + } + else if (VERR_NOT_FOUND == rc) + RTPrintf("No properties found.\n"); + else + VBoxControlError("Failed to enumerate the guest properties! Error: %Rrc\n", rc); + VbglR3GuestPropDisconnect(u32ClientId); + } + else + VBoxControlError("Failed to connect to the guest property service! Error: %Rrc\n", rc); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Waits for notifications of changes to guest properties. + * This is accessed through the "VBoxGuestPropSvc" HGCM service. + * + * @returns Command exit code. + * @note see the command line API description for parameters + */ +static RTEXITCODE waitGuestProperty(int argc, char **argv) +{ + /* + * Handle arguments + */ + const char *pszPatterns = NULL; + uint64_t u64TimestampIn = 0; + uint32_t u32Timeout = RT_INDEFINITE_WAIT; + bool fUsageOK = true; + if (argc < 1) + fUsageOK = false; + pszPatterns = argv[0]; + for (int i = 1; fUsageOK && i < argc; ++i) + { + if ( strcmp(argv[i], "-timeout") == 0 + || strcmp(argv[i], "--timeout") == 0) + { + if ( i + 1 >= argc + || RTStrToUInt32Full(argv[i + 1], 10, &u32Timeout) + != VINF_SUCCESS + ) + fUsageOK = false; + else + ++i; + } + else if ( strcmp(argv[i], "-timestamp") == 0 + || strcmp(argv[i], "--timestamp") == 0) + { + if ( i + 1 >= argc + || RTStrToUInt64Full(argv[i + 1], 10, &u64TimestampIn) + != VINF_SUCCESS + ) + fUsageOK = false; + else + ++i; + } + else + fUsageOK = false; + } + if (!fUsageOK) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + + /* + * Connect to the service + */ + uint32_t u32ClientId = 0; + int rc = VbglR3GuestPropConnect(&u32ClientId); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc); + + /* + * Retrieve the notification from the host + */ + char *pszName = NULL; + char *pszValue = NULL; + uint64_t u64TimestampOut = 0; + char *pszFlags = NULL; + bool fWasDeleted = false; + /* The buffer for storing the data and its initial size. We leave a bit + * of space here in case the maximum values are raised. */ + void *pvBuf = NULL; + uint32_t cbBuf = GUEST_PROP_MAX_NAME_LEN + GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN + _1K; + /* Because there is a race condition between our reading the size of a + * property and the guest updating it, we loop a few times here and + * hope. Actually this should never go wrong, as we are generous + * enough with buffer space. */ + for (unsigned iTry = 0; ; iTry++) + { + pvBuf = RTMemRealloc(pvBuf, cbBuf); + if (pvBuf != NULL) + { + rc = VbglR3GuestPropWait(u32ClientId, pszPatterns, pvBuf, cbBuf, + u64TimestampIn, u32Timeout, + &pszName, &pszValue, &u64TimestampOut, + &pszFlags, &cbBuf, &fWasDeleted); + if (rc == VERR_BUFFER_OVERFLOW && iTry < 10) + { + cbBuf += _1K; /* Add a bit of extra space to be on the safe side. */ + continue; + } + if (rc == VERR_TOO_MUCH_DATA) + VBoxControlError("Temporarily unable to get a notification\n"); + else if (rc == VERR_INTERRUPTED) + VBoxControlError("The request timed out or was interrupted\n"); + else if (RT_FAILURE(rc) && rc != VERR_NOT_FOUND) + VBoxControlError("Failed to get a notification, error %Rrc\n", rc); + } + else + { + VBoxControlError("Out of memory\n"); + rc = VERR_NO_MEMORY; + } + break; + } + + /* + * And display it on the guest console. + */ + if (VERR_NOT_FOUND == rc) + RTPrintf("No value set!\n"); + else if (rc == VERR_BUFFER_OVERFLOW) + RTPrintf("Internal error: unable to determine the size of the data!\n"); + else if (RT_SUCCESS(rc)) + { + if (fWasDeleted) + { + RTPrintf("Property %s was deleted\n", pszName); + } + else + { + RTPrintf("Name: %s\n", pszName); + RTPrintf("Value: %s\n", pszValue); + RTPrintf("Timestamp: %lld ns\n", u64TimestampOut); + RTPrintf("Flags: %s\n", pszFlags); + } + } + + if (u32ClientId != 0) + VbglR3GuestPropDisconnect(u32ClientId); + RTMemFree(pvBuf); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Access the guest property store through the "VBoxGuestPropSvc" HGCM + * service. + * + * @returns 0 on success, 1 on failure + * @note see the command line API description for parameters + */ +static DECLCALLBACK(RTEXITCODE) handleGuestProperty(int argc, char *argv[]) +{ + if (argc == 0) + { + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; + } + if (!strcmp(argv[0], "get")) + return getGuestProperty(argc - 1, argv + 1); + if (!strcmp(argv[0], "set")) + return setGuestProperty(argc - 1, argv + 1); + if (!strcmp(argv[0], "delete") || !strcmp(argv[0], "unset")) + return deleteGuestProperty(argc - 1, argv + 1); + if (!strcmp(argv[0], "enumerate")) + return enumGuestProperty(argc - 1, argv + 1); + if (!strcmp(argv[0], "wait")) + return waitGuestProperty(argc - 1, argv + 1); + /* unknown cmd */ + usage(GUEST_PROP); + return RTEXITCODE_FAILURE; +} + +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS + +/** + * Lists the Shared Folders provided by the host. + */ +static RTEXITCODE sharedFolder_list(int argc, char **argv) +{ + bool fUsageOK = true; + bool fOnlyShowAutoMount = false; + if (argc == 1) + { + if (!strcmp(argv[0], "--automount")) + fOnlyShowAutoMount = true; + else + fUsageOK = false; + } + else if (argc > 1) + fUsageOK = false; + if (!fUsageOK) + { + usage(GUEST_SHAREDFOLDERS); + return RTEXITCODE_SYNTAX; + } + + uint32_t u32ClientId; + int rc = VbglR3SharedFolderConnect(&u32ClientId); + if (RT_FAILURE(rc)) + VBoxControlError("Failed to connect to the shared folder service, error %Rrc\n", rc); + else + { + PVBGLR3SHAREDFOLDERMAPPING paMappings; + uint32_t cMappings; + rc = VbglR3SharedFolderGetMappings(u32ClientId, fOnlyShowAutoMount, &paMappings, &cMappings); + if (RT_SUCCESS(rc)) + { + if (fOnlyShowAutoMount) + RTPrintf("Auto-mounted Shared Folder mappings (%u):\n\n", cMappings); + else + RTPrintf("Shared Folder mappings (%u):\n\n", cMappings); + + for (uint32_t i = 0; i < cMappings; i++) + { + char *pszName; + char *pszMntPt; + uint64_t fFlags; + uint32_t uRootIdVer; + rc = VbglR3SharedFolderQueryFolderInfo(u32ClientId, paMappings[i].u32Root, 0, + &pszName, &pszMntPt, &fFlags, &uRootIdVer); + if (RT_SUCCESS(rc)) + { + RTPrintf("%02u - %s [idRoot=%u", i + 1, pszName, paMappings[i].u32Root); + if (fFlags & SHFL_MIF_WRITABLE) + RTPrintf(" writable"); + else + RTPrintf(" readonly"); + if (fFlags & SHFL_MIF_AUTO_MOUNT) + RTPrintf(" auto-mount"); + if (fFlags & SHFL_MIF_SYMLINK_CREATION) + RTPrintf(" create-symlink"); + if (fFlags & SHFL_MIF_HOST_ICASE) + RTPrintf(" host-icase"); + if (fFlags & SHFL_MIF_GUEST_ICASE) + RTPrintf(" guest-icase"); + if (*pszMntPt) + RTPrintf(" mnt-pt=%s", pszMntPt); + RTPrintf("]"); +# ifdef RT_OS_OS2 + /* Show drive letters: */ + const char *pszOn = " on"; + for (char chDrive = 'A'; chDrive <= 'Z'; chDrive++) + { + char szDrive[4] = { chDrive, ':', '\0', '\0' }; + union + { + FSQBUFFER2 FsQueryBuf; + char achPadding[512]; + } uBuf; + RT_ZERO(uBuf); + ULONG cbBuf = sizeof(uBuf) - 2; + APIRET rcOs2 = DosQueryFSAttach(szDrive, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf); + if (rcOs2 == NO_ERROR) + { + const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1]; + if ( uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV + && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0) + { + const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1]; + if (RTStrICmp(pszMountedName, pszName) == 0) + { + const char *pszTag = pszMountedName + strlen(pszMountedName) + 1; /* safe */ + if (*pszTag != '\0') + RTPrintf("%s %s (%s)", pszOn, szDrive, pszTag); + else + RTPrintf("%s %s", pszOn, szDrive); + pszOn = ","; + } + } + } + } +# endif + RTPrintf("\n"); + + RTStrFree(pszName); + RTStrFree(pszMntPt); + } + else + VBoxControlError("Error while getting the shared folder name for root node = %u, rc = %Rrc\n", + paMappings[i].u32Root, rc); + } + if (!cMappings) + RTPrintf("No Shared Folders available.\n"); + VbglR3SharedFolderFreeMappings(paMappings); + } + else + VBoxControlError("Error while getting the shared folder mappings, rc = %Rrc\n", rc); + VbglR3SharedFolderDisconnect(u32ClientId); + } + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +# ifdef RT_OS_OS2 +/** + * Attaches a shared folder to a drive letter. + */ +static RTEXITCODE sharedFolder_use(int argc, char **argv) +{ + /* + * Takes a drive letter and a share name as arguments. + */ + if (argc != 2) + return VBoxControlSyntaxError("sharedfolder use: expected a drive letter and a shared folder name\n"); + + const char *pszDrive = argv[0]; + if (!RT_C_IS_ALPHA(pszDrive[0]) || pszDrive[1] != ':' || pszDrive[2] != '\0') + return VBoxControlSyntaxError("sharedfolder use: not a drive letter: %s\n", pszDrive); + + static const char s_szTag[] = "VBoxControl"; + char szzNameAndTag[256]; + const char *pszName = argv[1]; + size_t cchName = strlen(pszName); + if (cchName < 1) + return VBoxControlSyntaxError("sharedfolder use: shared folder name cannot be empty!\n"); + if (cchName + 1 + sizeof(s_szTag) >= sizeof(szzNameAndTag)) + return VBoxControlSyntaxError("sharedfolder use: shared folder name is too long! (%s)\n", pszName); + + /* + * Do the attaching. + */ + memcpy(szzNameAndTag, pszName, cchName); + szzNameAndTag[cchName] = '\0'; + memcpy(&szzNameAndTag[cchName + 1], s_szTag, sizeof(s_szTag)); + + APIRET rcOs2 = DosFSAttach(pszDrive, "VBOXSF", szzNameAndTag, cchName + 1 + sizeof(s_szTag), FS_ATTACH); + if (rcOs2 == NO_ERROR) + return RTEXITCODE_SUCCESS; + if (rcOs2 == ERROR_INVALID_FSD_NAME) + return VBoxControlError("Shared folders IFS not installed?\n"); + return VBoxControlError("DosFSAttach/FS_ATTACH failed to attach '%s' to '%s': %u\n", pszName, pszDrive, rcOs2); +} + +/** + * Detaches a shared folder from a drive letter. + */ +static RTEXITCODE sharedFolder_unuse(int argc, char **argv) +{ + /* + * Only takes a drive letter as argument. + */ + if (argc != 1) + return VBoxControlSyntaxError("sharedfolder unuse: expected drive letter\n"); + const char *pszDrive = argv[0]; + if (!RT_C_IS_ALPHA(pszDrive[0]) || pszDrive[1] != ':' || pszDrive[2] != '\0') + return VBoxControlSyntaxError("sharedfolder unuse: not a drive letter: %s\n", pszDrive); + + /* + * Do the detaching. + */ + APIRET rcOs2 = DosFSAttach(pszDrive, "VBOXSF", NULL, 0, FS_DETACH); + if (rcOs2 == NO_ERROR) + return RTEXITCODE_SUCCESS; + return VBoxControlError("DosFSAttach/FS_DETACH failed on '%s': %u\n", pszDrive, rcOs2); +} + +# endif /* RT_OS_OS2 */ + + +/** + * Handles Shared Folders control. + * + * @returns 0 on success, 1 on failure + * @note see the command line API description for parameters + * (r=bird: yeah, right. The API description contains nil about params) + */ +static DECLCALLBACK(RTEXITCODE) handleSharedFolder(int argc, char *argv[]) +{ + if (argc == 0) + { + usage(GUEST_SHAREDFOLDERS); + return RTEXITCODE_FAILURE; + } + if (!strcmp(argv[0], "list")) + return sharedFolder_list(argc - 1, argv + 1); +# ifdef RT_OS_OS2 + if (!strcmp(argv[0], "use")) + return sharedFolder_use(argc - 1, argv + 1); + if (!strcmp(argv[0], "unuse")) + return sharedFolder_unuse(argc - 1, argv + 1); +# endif + + usage(GUEST_SHAREDFOLDERS); + return RTEXITCODE_FAILURE; +} + +#endif +#if !defined(VBOX_CONTROL_TEST) + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: writecoredump} + */ +static DECLCALLBACK(RTEXITCODE) handleWriteCoreDump(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); + int rc = VbglR3WriteCoreDump(); + if (RT_SUCCESS(rc)) + { + RTPrintf("Guest core dump successful.\n"); + return RTEXITCODE_SUCCESS; + } + else + { + VBoxControlError("Error while taking guest core dump. rc=%Rrc\n", rc); + return RTEXITCODE_FAILURE; + } +} + +#endif +#ifdef VBOX_WITH_DPC_LATENCY_CHECKER + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: help} + */ +static DECLCALLBACK(RTEXITCODE) handleDpc(int argc, char *argv[]) +{ + RT_NOREF(argc, argv); + int rc = VERR_NOT_IMPLEMENTED; +# ifndef VBOX_CONTROL_TEST + for (int i = 0; i < 30; i++) + { + VBGLREQHDR Req; + VBGLREQHDR_INIT(&Req, DPC_LATENCY_CHECKER); + rc = vbglR3DoIOCtl(VBGL_IOCTL_DPC_LATENCY_CHECKER, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + RTPrintf("%d\n", i); + else + break; + } +# endif + if (RT_FAILURE(rc)) + return VBoxControlError("Error. rc=%Rrc\n", rc); + RTPrintf("Samples collection completed.\n"); + return RTEXITCODE_SUCCESS; +} +#endif /* VBOX_WITH_DPC_LATENCY_CHECKER */ + + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: writelog} + */ +static DECLCALLBACK(RTEXITCODE) handleWriteLog(int argc, char *argv[]) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--no-newline", 'n', RTGETOPT_REQ_NOTHING }, + }; + bool fNoNewline = false; + + RTGETOPTSTATE GetOptState; + int rc = RTGetOptInit(&GetOptState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), + 0 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + if (RT_SUCCESS(rc)) + { + RTGETOPTUNION ValueUnion; + int ch; + while ((ch = RTGetOpt(&GetOptState, &ValueUnion)) != 0) + { + switch (ch) + { + case VINF_GETOPT_NOT_OPTION: + { + size_t cch = strlen(ValueUnion.psz); + if ( fNoNewline + || (cch > 0 && ValueUnion.psz[cch - 1] == '\n') ) + rc = VbglR3WriteLog(ValueUnion.psz, cch); + else + { + char *pszDup = (char *)RTMemDupEx(ValueUnion.psz, cch, 2); + if (RT_SUCCESS(rc)) + { + pszDup[cch++] = '\n'; + pszDup[cch] = '\0'; + rc = VbglR3WriteLog(pszDup, cch); + RTMemFree(pszDup); + } + else + rc = VERR_NO_MEMORY; + } + if (RT_FAILURE(rc)) + return VBoxControlError("VbglR3WriteLog: %Rrc", rc); + break; + } + + case 'n': + fNoNewline = true; + break; + + case 'h': return usage(WRITE_LOG); + case 'V': return printVersion(); + default: + return VBoxCtrlGetOptError(ch, &ValueUnion); + } + } + } + else + return VBoxControlError("RTGetOptInit: %Rrc", rc); + return RTEXITCODE_SUCCESS; +} + + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: takesnapshot} + */ +static DECLCALLBACK(RTEXITCODE) handleTakeSnapshot(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); //VbglR3VmTakeSnapshot(argv[0], argv[1]); + return VBoxControlError("not implemented"); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: savestate} + */ +static DECLCALLBACK(RTEXITCODE) handleSaveState(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); //VbglR3VmSaveState(); + return VBoxControlError("not implemented"); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: suspend|pause} + */ +static DECLCALLBACK(RTEXITCODE) handleSuspend(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); //VbglR3VmSuspend(); + return VBoxControlError("not implemented"); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: poweroff|powerdown} + */ +static DECLCALLBACK(RTEXITCODE) handlePowerOff(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); //VbglR3VmPowerOff(); + return VBoxControlError("not implemented"); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: version} + */ +static DECLCALLBACK(RTEXITCODE) handleVersion(int argc, char *argv[]) +{ + RT_NOREF1(argv); + if (argc) + return VBoxControlSyntaxError("getversion does not take any arguments"); + return printVersion(); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: help} + */ +static DECLCALLBACK(RTEXITCODE) handleHelp(int argc, char *argv[]) +{ + RT_NOREF2(argc, argv); /* ignore arguments for now. */ + usage(); + return RTEXITCODE_SUCCESS; +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: ls} + */ +static DECLCALLBACK(RTEXITCODE) handleLs(int argc, char *argv[]) +{ + return RTFsCmdLs(argc + 1, argv - 1); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: tar} + */ +static DECLCALLBACK(RTEXITCODE) handleTar(int argc, char *argv[]) +{ + return RTZipTarCmd(argc + 1, argv - 1); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: tar} + */ +static DECLCALLBACK(RTEXITCODE) handleGzip(int argc, char *argv[]) +{ + return RTZipGzipCmd(argc + 1, argv - 1); +} + +/** + * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: unzip} + */ +static DECLCALLBACK(RTEXITCODE) handleUnzip(int argc, char *argv[]) +{ + return RTZipUnzipCmd(argc + 1, argv - 1); +} + + +/** command handler type */ +typedef DECLCALLBACKTYPE(RTEXITCODE, FNVBOXCTRLCMDHANDLER,(int argc, char *argv[])); +typedef FNVBOXCTRLCMDHANDLER *PFNVBOXCTRLCMDHANDLER; + +/** The table of all registered command handlers. */ +struct COMMANDHANDLER +{ + const char *pszCommand; + PFNVBOXCTRLCMDHANDLER pfnHandler; + bool fNeedDevice; +} g_aCommandHandlers[] = +{ +#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST) + { "getvideoacceleration", handleGetVideoAcceleration, true }, + { "setvideoacceleration", handleSetVideoAcceleration, true }, + { "videoflags", handleVideoFlags, true }, + { "listcustommodes", handleListCustomModes, true }, + { "addcustommode", handleAddCustomMode, true }, + { "removecustommode", handleRemoveCustomMode, true }, + { "setvideomode", handleSetVideoMode, true }, +#endif +#ifdef VBOX_WITH_GUEST_PROPS + { "guestproperty", handleGuestProperty, true }, +#endif +#ifdef VBOX_WITH_SHARED_FOLDERS + { "sharedfolder", handleSharedFolder, true }, +#endif +#if !defined(VBOX_CONTROL_TEST) + { "writecoredump", handleWriteCoreDump, true }, +#endif +#ifdef VBOX_WITH_DPC_LATENCY_CHECKER + { "dpc", handleDpc, true }, +#endif + { "writelog", handleWriteLog, true }, + { "takesnapshot", handleTakeSnapshot, true }, + { "savestate", handleSaveState, true }, + { "suspend", handleSuspend, true }, + { "pause", handleSuspend, true }, + { "poweroff", handlePowerOff, true }, + { "powerdown", handlePowerOff, true }, + { "getversion", handleVersion, false }, + { "version", handleVersion, false }, + { "help", handleHelp, false }, + /* Hany tricks that doesn't cost much space: */ + { "gzip", handleGzip, false }, + { "ls", handleLs, false }, + { "tar", handleTar, false }, + { "unzip", handleUnzip, false }, +}; + +/** Main function */ +int main(int argc, char **argv) +{ + /** The application's global return code */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + /** An IPRT return code for local use */ + int rrc = VINF_SUCCESS; + /** The index of the command line argument we are currently processing */ + int iArg = 1; + /** Should we show the logo text? */ + bool fShowLogo = true; + /** Should we print the usage after the logo? For the -help switch. */ + bool fDoHelp = false; + /** Will we be executing a command or just printing information? */ + bool fOnlyInfo = false; + + rrc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rrc)) + return RTMsgInitFailure(rrc); + + /* + * Start by handling command line switches + */ + /** @todo RTGetOpt conversion of the whole file. */ + bool done = false; /**< Are we finished with handling switches? */ + while (!done && (iArg < argc)) + { + if ( !strcmp(argv[iArg], "-V") + || !strcmp(argv[iArg], "-v") + || !strcmp(argv[iArg], "--version") + || !strcmp(argv[iArg], "-version") + ) + { + /* Print version number, and do nothing else. */ + printVersion(); + fOnlyInfo = true; + fShowLogo = false; + done = true; + } + else if ( !strcmp(argv[iArg], "-nologo") + || !strcmp(argv[iArg], "--nologo")) + fShowLogo = false; + else if ( !strcmp(argv[iArg], "-help") + || !strcmp(argv[iArg], "--help")) + { + fOnlyInfo = true; + fDoHelp = true; + done = true; + } + else + /* We have found an argument which isn't a switch. Exit to the + * command processing bit. */ + done = true; + if (!done) + ++iArg; + } + + /* + * Find the application name, show our logo if the user hasn't suppressed it, + * and show the usage if the user asked us to + */ + g_pszProgName = RTPathFilename(argv[0]); + if (fShowLogo) + RTPrintf(VBOX_PRODUCT " Guest Additions Command Line Management Interface Version " + VBOX_VERSION_STRING "\n" + "Copyright (C) 2008-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n"); + if (fDoHelp) + usage(); + + /* + * Now look for an actual command in the argument list and handle it. + */ + if (!fOnlyInfo && rcExit == RTEXITCODE_SUCCESS) + { + if (argc > iArg) + { + /* + * Try locate the command and execute it, complain if not found. + */ + unsigned i; + for (i = 0; i < RT_ELEMENTS(g_aCommandHandlers); i++) + if (!strcmp(argv[iArg], g_aCommandHandlers[i].pszCommand)) + { + if (g_aCommandHandlers[i].fNeedDevice) + { + rrc = VbglR3Init(); + if (RT_FAILURE(rrc)) + { + VBoxControlError("Could not contact the host system. Make sure that you are running this\n" + "application inside a VirtualBox guest system, and that you have sufficient\n" + "user permissions.\n"); + rcExit = RTEXITCODE_FAILURE; + } + } + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = g_aCommandHandlers[i].pfnHandler(argc - iArg - 1, argv + iArg + 1); + break; + } + if (i >= RT_ELEMENTS(g_aCommandHandlers)) + { + usage(); + rcExit = RTEXITCODE_SYNTAX; + } + } + else + { + /* The user didn't specify a command. */ + usage(); + rcExit = RTEXITCODE_SYNTAX; + } + } + + /* + * And exit, returning the status + */ + return rcExit; +} + diff --git a/src/VBox/Additions/common/VBoxControl/VBoxControl.rc b/src/VBox/Additions/common/VBoxControl/VBoxControl.rc new file mode 100644 index 00000000..9a454882 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/VBoxControl.rc @@ -0,0 +1,61 @@ +/* $Id: VBoxControl.rc $ */ +/** @file + * VBoxControl - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "VirtualBox Guest Additions Utility\0" + VALUE "InternalName", "VBoxControl\0" + VALUE "OriginalFilename", "VBoxControl.exe\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_GA_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/Additions/common/VBoxControl/testcase/Makefile.kmk b/src/VBox/Additions/common/VBoxControl/testcase/Makefile.kmk new file mode 100644 index 00000000..bee6c0a0 --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/testcase/Makefile.kmk @@ -0,0 +1,48 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the VBoxControl testcases. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) + + # + # Dummy CLI testcase. + # + PROGRAMS += tstVBoxControl + tstVBoxControl_TEMPLATE = VBoxR3TstExe + tstVBoxControl_DEFS = VBOX_CONTROL_TEST + tstVBoxControl_SOURCES = tstVBoxControl.cpp ../VBoxControl.cpp + tstVBoxControl_LIBS = $(LIB_RUNTIME) + tstVBoxControl_DEFS += \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS VBOX_WITH_HGCM,) + +endif # VBOX_WITH_TESTCASES + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/common/VBoxControl/testcase/tstVBoxControl.cpp b/src/VBox/Additions/common/VBoxControl/testcase/tstVBoxControl.cpp new file mode 100644 index 00000000..6211813e --- /dev/null +++ b/src/VBox/Additions/common/VBoxControl/testcase/tstVBoxControl.cpp @@ -0,0 +1,226 @@ +/* $Id: tstVBoxControl.cpp $ */ +/** @file + * VBoxControl - Guest Additions Command Line Management Interface, test case + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cpp/autores.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <VBox/log.h> +#include <VBox/version.h> +#include <VBox/VBoxGuestLib.h> +#ifdef VBOX_WITH_GUEST_PROPS +# include <VBox/HostServices/GuestPropertySvc.h> +#endif + +VBGLR3DECL(int) VbglR3Init(void) +{ + RTPrintf("Initialising guest library...\n"); + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropConnect(HGCMCLIENTID *pidClient) +{ + AssertPtrReturn(pidClient, VERR_INVALID_POINTER); + RTPrintf("Connect to guest property service...\n"); + *pidClient = 1; + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropDisconnect(HGCMCLIENTID idClient) +{ + RTPrintf("Disconnect client %d from guest property service...\n", idClient); + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropWrite(HGCMCLIENTID idClient, + const char *pszName, + const char *pszValue, + const char *pszFlags) +{ + RTPrintf("Called SET_PROP, client %d, name %s, value %s, flags %s...\n", + idClient, pszName, pszValue, pszFlags); + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropWriteValue(HGCMCLIENTID idClient, + const char *pszName, + const char *pszValue) +{ + RTPrintf("Called SET_PROP_VALUE, client %d, name %s, value %s...\n", + idClient, pszName, pszValue); + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_GUEST_PROPS +VBGLR3DECL(int) VbglR3GuestPropRead(HGCMCLIENTID idClient, + const char *pszName, + void *pvBuf, + uint32_t cbBuf, + char **ppszValue, + uint64_t *pu64Timestamp, + char **ppszFlags, + uint32_t *pcbBufActual) +{ + RT_NOREF2(pvBuf, cbBuf); + RTPrintf("Called GET_PROP, client %d, name %s...\n", + idClient, pszName); + static char szValue[] = "Value"; + static char szFlags[] = "TRANSIENT"; + if (ppszValue) + *ppszValue = szValue; + if (pu64Timestamp) + *pu64Timestamp = 12345; + if (ppszFlags) + *ppszFlags = szFlags; + if (pcbBufActual) + *pcbBufActual = 256; + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropDelete(HGCMCLIENTID idClient, + const char *pszName) +{ + RTPrintf("Called DEL_PROP, client %d, name %s...\n", + idClient, pszName); + return VINF_SUCCESS; +} + +struct VBGLR3GUESTPROPENUM +{ + uint32_t u32; +}; + +VBGLR3DECL(int) VbglR3GuestPropEnum(HGCMCLIENTID idClient, + char const * const *ppaszPatterns, + uint32_t cPatterns, + PVBGLR3GUESTPROPENUM *ppHandle, + char const **ppszName, + char const **ppszValue, + uint64_t *pu64Timestamp, + char const **ppszFlags) +{ + RT_NOREF2(ppaszPatterns, cPatterns); + RTPrintf("Called ENUM_PROPS, client %d...\n", idClient); + AssertPtrReturn(ppHandle, VERR_INVALID_POINTER); + static VBGLR3GUESTPROPENUM Handle = { 0 }; + static char szName[] = "Name"; + static char szValue[] = "Value"; + static char szFlags[] = "TRANSIENT"; + *ppHandle = &Handle; + if (ppszName) + *ppszName = szName; + if (ppszValue) + *ppszValue = szValue; + if (pu64Timestamp) + *pu64Timestamp = 12345; + if (ppszFlags) + *ppszFlags = szFlags; + return VINF_SUCCESS; +} + +VBGLR3DECL(int) VbglR3GuestPropEnumNext(PVBGLR3GUESTPROPENUM pHandle, + char const **ppszName, + char const **ppszValue, + uint64_t *pu64Timestamp, + char const **ppszFlags) +{ + RT_NOREF1(pHandle); + RTPrintf("Called enumerate next...\n"); + AssertReturn(RT_VALID_PTR(ppszName) || RT_VALID_PTR(ppszValue) || RT_VALID_PTR(ppszFlags), + VERR_INVALID_POINTER); + if (ppszName) + *ppszName = NULL; + if (ppszValue) + *ppszValue = NULL; + if (pu64Timestamp) + *pu64Timestamp = 0; + if (ppszFlags) + *ppszFlags = NULL; + return VINF_SUCCESS; +} + +VBGLR3DECL(void) VbglR3GuestPropEnumFree(PVBGLR3GUESTPROPENUM pHandle) +{ + RT_NOREF1(pHandle); + RTPrintf("Called enumerate free...\n"); +} + +VBGLR3DECL(int) VbglR3GuestPropWait(HGCMCLIENTID idClient, + const char *pszPatterns, + void *pvBuf, + uint32_t cbBuf, + uint64_t u64Timestamp, + uint32_t u32Timeout, + char ** ppszName, + char **ppszValue, + uint64_t *pu64Timestamp, + char **ppszFlags, + uint32_t *pcbBufActual, + bool *pfWasDeleted) +{ + RT_NOREF2(pvBuf, cbBuf); + if (u32Timeout == RT_INDEFINITE_WAIT) + RTPrintf("Called GET_NOTIFICATION, client %d, patterns %s, timestamp %llu,\n" + " timeout RT_INDEFINITE_WAIT...\n", + idClient, pszPatterns, u64Timestamp); + else + RTPrintf("Called GET_NOTIFICATION, client %d, patterns %s, timestamp %llu,\n" + " timeout %u...\n", + idClient, pszPatterns, u64Timestamp, u32Timeout); + static char szName[] = "Name"; + static char szValue[] = "Value"; + static char szFlags[] = "TRANSIENT"; + if (ppszName) + *ppszName = szName; + if (ppszValue) + *ppszValue = szValue; + if (pu64Timestamp) + *pu64Timestamp = 12345; + if (ppszFlags) + *ppszFlags = szFlags; + if (pcbBufActual) + *pcbBufActual = 256; + if (pfWasDeleted) + *pfWasDeleted = false; + return VINF_SUCCESS; +} + +#endif + +VBGLR3DECL(int) VbglR3WriteLog(const char *pch, size_t cch) +{ + NOREF(pch); NOREF(cch); + return VINF_SUCCESS; +} + |