diff options
Diffstat (limited to 'src/VBox/Additions/common/VBoxService')
27 files changed, 6531 insertions, 3399 deletions
diff --git a/src/VBox/Additions/common/VBoxService/Makefile.kmk b/src/VBox/Additions/common/VBoxService/Makefile.kmk index e00f66c6..8a42bab1 100644 --- a/src/VBox/Additions/common/VBoxService/Makefile.kmk +++ b/src/VBox/Additions/common/VBoxService/Makefile.kmk @@ -4,7 +4,7 @@ # # -# Copyright (C) 2007-2012 Oracle Corporation +# Copyright (C) 2007-2013 Oracle Corporation # # This file is part of VirtualBox Open Source Edition (OSE), as # available from http://www.virtualbox.org. This file is free software; @@ -38,6 +38,9 @@ VBoxService_DEFS += \ VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\" VBoxService_DEFS.win += _WIN32_WINNT=0x0501 VBoxService_DEFS.os2 = VBOX_WITH_HGCM VBOXSERVICE_CLIPBOARD +ifdef VBOX_WITH_DBUS + VBoxService_DEFS += VBOX_WITH_DBUS +endif ifdef VBOX_WITH_GUEST_PROPS VBoxService_DEFS += VBOX_WITH_GUEST_PROPS VBOXSERVICE_VMINFO endif @@ -48,7 +51,10 @@ ifdef VBOX_WITH_MEMBALLOON VBoxService_DEFS += VBOX_WITH_MEMBALLOON endif if1of ($(KBUILD_TARGET), win) - VBoxService_DEFS += VBOX_WITH_PAGE_SHARING + VBoxService_DEFS += VBOXSERVICE_PAGE_SHARING + ifdef VBOX_WITH_MMR + VBoxService_DEFS += VBOX_WITH_MMR + endif endif if1of ($(KBUILD_TARGET), linux) VBoxService_DEFS += VBOXSERVICE_CPUHOTPLUG @@ -70,7 +76,8 @@ VBoxService_SOURCES = \ ifdef VBOX_WITH_GUEST_CONTROL VBoxService_SOURCES += \ VBoxServiceControl.cpp \ - VBoxServiceControlThread.cpp + VBoxServiceControlProcess.cpp \ + VBoxServiceControlSession.cpp endif ifdef VBOX_WITH_MEMBALLOON @@ -112,6 +119,8 @@ 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) \ @@ -120,6 +129,12 @@ 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 \ diff --git a/src/VBox/Additions/common/VBoxService/VBoxService-os2.def b/src/VBox/Additions/common/VBoxService/VBoxService-os2.def index 2c14ceed..aed84c68 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxService-os2.def +++ b/src/VBox/Additions/common/VBoxService/VBoxService-os2.def @@ -3,7 +3,7 @@ ; ; -; Copyright (C) 2007 Oracle Corporation +; Copyright (C) 2007-2010 Oracle Corporation ; ; This file is part of VirtualBox Open Source Edition (OSE), as ; available from http://www.virtualbox.org. This file is free software; diff --git a/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp b/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp index e09173c1..eb970fa8 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2009-2010 Oracle Corporation + * Copyright (C) 2009-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -21,6 +21,7 @@ *******************************************************************************/ #include <iprt/assert.h> #include <iprt/err.h> +#include <iprt/system.h> /* For querying OS version. */ #include <VBox/VBoxGuestLib.h> #include "VBoxServiceInternal.h" @@ -132,18 +133,40 @@ static BOOL vboxServiceWinSetStatus(DWORD dwStatus, DWORD dwCheckPoint) g_dwWinServiceLastStatus = dwStatus; SERVICE_STATUS ss; + RT_ZERO(ss); + ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS; ss.dwCurrentState = dwStatus; - ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; + /* Don't accept controls when in start pending state. */ + if (ss.dwCurrentState != SERVICE_START_PENDING) + { + ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; #ifndef TARGET_NT4 - ss.dwControlsAccepted |= SERVICE_ACCEPT_SESSIONCHANGE; + /* Don't use SERVICE_ACCEPT_SESSIONCHANGE on Windows 2000. + * 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 + VBoxServiceError("Error determining OS version, rc=%Rrc\n", rc); #endif + } + ss.dwWin32ExitCode = NO_ERROR; ss.dwServiceSpecificExitCode = 0; /* Not used */ ss.dwCheckPoint = dwCheckPoint; ss.dwWaitHint = 3000; - return SetServiceStatus(g_hWinServiceStatus, &ss); + BOOL fStatusSet = SetServiceStatus(g_hWinServiceStatus, &ss); + if (!fStatusSet) + VBoxServiceError("Error reporting service status=%ld (controls=%x, checkpoint=%ld) to SCM: %ld\n", + dwStatus, ss.dwControlsAccepted, dwCheckPoint, GetLastError()); + return fStatusSet; } @@ -339,6 +362,8 @@ static int vboxServiceWinStart(void) if (RT_SUCCESS(rc)) { + vboxServiceWinSetStatus(SERVICE_START_PENDING, 0); + rc = VBoxServiceStartServices(); if (RT_SUCCESS(rc)) { diff --git a/src/VBox/Additions/common/VBoxService/VBoxService.cpp b/src/VBox/Additions/common/VBoxService/VBoxService.cpp index 639b97f7..8ce44dae 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxService.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxService.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2007-2011 Oracle Corporation + * Copyright (C) 2007-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -56,6 +56,9 @@ #include <VBox/log.h> #include "VBoxServiceInternal.h" +#ifdef VBOX_WITH_GUEST_CONTROL +# include "VBoxServiceControl.h" +#endif /******************************************************************************* @@ -65,6 +68,7 @@ char *g_pszProgName = (char *)""; /** The current verbosity level. */ int g_cVerbosity = 0; +char g_szLogFile[RTPATH_MAX + 128] = ""; /** Logging parameters. */ /** @todo Make this configurable later. */ static PRTLOGGER g_pLoggerRelease = NULL; @@ -126,7 +130,7 @@ static struct # endif { &g_VMStatistics, NIL_RTTHREAD, false, false, false, false, true }, #endif -#if defined(VBOX_WITH_PAGE_SHARING) && defined(RT_OS_WINDOWS) +#if defined(VBOXSERVICE_PAGE_SHARING) { &g_PageSharing, NIL_RTTHREAD, false, false, false, false, true }, #endif #ifdef VBOX_WITH_SHARED_FOLDERS @@ -211,11 +215,12 @@ static void VBoxServiceLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmP /** * Creates the default release logger outputting to the specified file. + * Pass NULL for disabled logging. * * @return IPRT status code. * @param pszLogFile Filename for log output. Optional. */ -static int VBoxServiceLogCreate(const char *pszLogFile) +int VBoxServiceLogCreate(const char *pszLogFile) { /* Create release logger (stdout + file). */ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; @@ -241,7 +246,8 @@ static int VBoxServiceLogCreate(const char *pszLogFile) return rc; } -static void VBoxServiceLogDestroy(void) + +void VBoxServiceLogDestroy(void) { RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); } @@ -633,6 +639,8 @@ int VBoxServiceStopServices(void) g_aServices[j].pDesc->pfnStop(); } + VBoxServiceVerbose(3, "All stop functions for services called\n"); + /* * Wait for all the service threads to complete. */ @@ -745,6 +753,7 @@ void VBoxServiceMainWait(void) int main(int argc, char **argv) { RTEXITCODE rcExit; + bool fUserSession = false; /* * Init globals and such. @@ -769,14 +778,32 @@ int main(int argc, char **argv) return rcExit; #endif +#ifdef VBOX_WITH_GUEST_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. */ - VBoxServiceVerbose(2, "Calling VbgR3Init()\n"); - rc = VbglR3Init(); + if (fUserSession) + { + VBoxServiceVerbose(2, "Calling VbgR3InitUser()\n"); + rc = VbglR3InitUser(); + } + else + { + VBoxServiceVerbose(2, "Calling VbgR3Init()\n"); + rc = VbglR3Init(); + } if (RT_FAILURE(rc)) { if (rc == VERR_ACCESS_DENIED) @@ -790,12 +817,19 @@ int main(int argc, char **argv) * Check if we're the specially spawned VBoxService.exe process that * handles page fusion. This saves an extra executable. */ - if ( argc == 2 - && !strcmp(argv[1], "--pagefusionfork")) + if ( argc == 2 + && !RTStrICmp(argv[1], "pagefusion")) return VBoxServicePageSharingInitFork(); #endif - char szLogFile[RTPATH_MAX + 128] = ""; +#ifdef VBOX_WITH_GUEST_CONTROL + /* + * Check if we're the specially spawned VBoxService.exe process that + * handles a guest control session. + */ + if (fUserSession) + return VBoxServiceControlSessionForkInit(argc, argv); +#endif /* * Parse the arguments. @@ -845,17 +879,17 @@ int main(int argc, char **argv) { bool fFound = false; - if (cch > sizeof("enable-") && !memcmp(psz, "enable-", sizeof("enable-") - 1)) + 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, "disable-", sizeof("disable-") - 1)) + 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, "only-", sizeof("only-") - 1)) + 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); @@ -925,7 +959,7 @@ int main(int argc, char **argv) case 'l': { rc = VBoxServiceArgString(argc, argv, psz + 1, &i, - szLogFile, sizeof(szLogFile)); + g_szLogFile, sizeof(g_szLogFile)); if (rc) return rc; psz = NULL; @@ -960,10 +994,10 @@ int main(int argc, char **argv) if (vboxServiceCountEnabledServices() == 0) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "At least one service must be enabled\n"); - rc = VBoxServiceLogCreate(strlen(szLogFile) ? szLogFile : NULL); + rc = VBoxServiceLogCreate(strlen(g_szLogFile) ? g_szLogFile : NULL); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create release log (%s, %Rrc)", - strlen(szLogFile) ? szLogFile : "<None>", rc); + strlen(g_szLogFile) ? g_szLogFile : "<None>", rc); /* Call pre-init if we didn't do it already. */ rcExit = vboxServiceLazyPreInit(); diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp index 06d172e4..190418b4 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2010-2011 Oracle Corporation + * Copyright (C) 2010-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -243,22 +243,32 @@ static int VBoxServiceAutoMountSharedFolder(const char *pszShareName, const char int rc = VINF_SUCCESS; char szAlreadyMountedTo[RTPATH_MAX]; - /* If a Shared Folder already is mounted but not to our desired mount point, - * do an unmount first! */ - if ( VBoxServiceAutoMountShareIsMounted(pszShareName, szAlreadyMountedTo, sizeof(szAlreadyMountedTo)) - && RTStrICmp(pszMountPoint, szAlreadyMountedTo)) + bool fSkip = false; + + /* Already mounted? */ + if (VBoxServiceAutoMountShareIsMounted(pszShareName, szAlreadyMountedTo, sizeof(szAlreadyMountedTo))) { - VBoxServiceVerbose(3, "VBoxServiceAutoMountWorker: Shared folder \"%s\" already mounted to \"%s\", unmounting ...\n", - pszShareName, szAlreadyMountedTo); - rc = VBoxServiceAutoMountUnmount(szAlreadyMountedTo); - if (RT_FAILURE(rc)) - VBoxServiceError("VBoxServiceAutoMountWorker: Failed to unmount \"%s\", %s (%d)!\n", - szAlreadyMountedTo, strerror(errno), errno); + fSkip = true; + /* Do if it not mounted to our desired mount point */ + if (RTStrICmp(pszMountPoint, szAlreadyMountedTo)) + { + VBoxServiceVerbose(3, "VBoxServiceAutoMountWorker: Shared folder \"%s\" already mounted to \"%s\", unmounting ...\n", + pszShareName, szAlreadyMountedTo); + rc = VBoxServiceAutoMountUnmount(szAlreadyMountedTo); + if (RT_FAILURE(rc)) + VBoxServiceError("VBoxServiceAutoMountWorker: Failed to unmount \"%s\", %s (%d)!\n", + szAlreadyMountedTo, strerror(errno), errno); + else + fSkip = false; + } + if (fSkip) + VBoxServiceVerbose(3, "VBoxServiceAutoMountWorker: Shared folder \"%s\" already mounted to \"%s\", skipping\n", + pszShareName, szAlreadyMountedTo); } - if (RT_SUCCESS(rc)) + if (!fSkip && RT_SUCCESS(rc)) rc = VBoxServiceAutoMountPrepareMountPoint(pszMountPoint, pszShareName, pOpts); - if (RT_SUCCESS(rc)) + if (!fSkip && RT_SUCCESS(rc)) { #ifdef RT_OS_SOLARIS char achOptBuf[MAX_MNTOPT_STR] = { '\0', }; diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp index ecfd743c..3d5d0a86 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2006-2010 Oracle Corporation + * Copyright (C) 2006-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp index 60b96671..3f88642a 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp @@ -957,8 +957,8 @@ static DECLCALLBACK(int) VBoxServiceClipboardOS2Worker(bool volatile *pfShutdown while (WinGetMsg(g_habWorker, &qmsg, NULLHANDLE, NULLHANDLE, 0)) { if (qmsg.msg != WM_TIMER) - VBoxServiceVerbose(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); + VBoxServiceVerbose(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); } VBoxServiceVerbose(2, "clipboard: Exited PM message loop. *pfShutdown=%RTbool\n", *pfShutdown); diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp index 07778cd7..cd6d5ee6 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2012 Oracle Corporation + * Copyright (C) 2012-2013 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -21,15 +21,18 @@ *******************************************************************************/ #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/VBoxGuestLib.h> #include <VBox/HostServices/GuestControlSvc.h> #include "VBoxServiceInternal.h" +#include "VBoxServiceControl.h" #include "VBoxServiceUtils.h" using namespace guestControl; @@ -41,89 +44,45 @@ using namespace guestControl; static uint32_t g_uControlIntervalMS = 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. */ static uint32_t g_uControlSvcClientID = 0; /** 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; -#ifdef DEBUG - static bool g_fControlDumpStdErr = false; - static bool g_fControlDumpStdOut = false; -#endif -/** List of active guest control threads (VBOXSERVICECTRLTHREAD). */ -static RTLISTANCHOR g_lstControlThreadsActive; -/** List of inactive guest control threads (VBOXSERVICECTRLTHREAD). */ -static RTLISTANCHOR g_lstControlThreadsInactive; -/** Critical section protecting g_GuestControlExecThreads. */ -static RTCRITSECT g_csControlThreads; -/** List of guest control files (VBOXSERVICECTRLFILE). - **@todo Use a map (later). */ -static RTLISTANCHOR g_lstControlFiles; -/** The internal file count for building our internal file handles. - * Should be enough for now. */ -static uint32_t g_uControlFileCount = 0; - +/** 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; /******************************************************************************* * Internal Functions * *******************************************************************************/ -/** @todo Shorten "VBoxServiceControl" to "gstsvcCntl". */ -static int VBoxServiceControlReapThreads(void); -static int VBoxServiceControlStartAllowed(bool *pbAllowed); -static int VBoxServiceControlHandleCmdStartProc(uint32_t u32ClientId, uint32_t uNumParms); -static int VBoxServiceControlHandleCmdSetInput(uint32_t u32ClientId, uint32_t uNumParms, void *pvScratchBuf, size_t cbScratchBuf); -static int VBoxServiceControlHandleCmdGetOutput(uint32_t u32ClientId, uint32_t uNumParms); -static int VBoxServiceControlHandleFileOpen(uint32_t idClient, uint32_t cParms); -static int VBoxServiceControlHandleFileClose(uint32_t idClient, uint32_t cParms); -static int VBoxServiceControlHandleFileRead(uint32_t idClient, uint32_t cParms); -static int VBoxServiceControlHandleFileWrite(uint32_t idClient, uint32_t cParms, void *pvScratchBuf, size_t cbScratchBuf); -static int VBoxServiceControlHandleFileSeek(uint32_t idClient, uint32_t cParms); -static int VBoxServiceControlHandleFileTell(uint32_t idClient, uint32_t cParms); - -#ifdef DEBUG -static int vboxServiceControlDump(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)) - { - VBoxServiceVerbose(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 +static int gstcntlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static void VBoxServiceControlShutdown(void); /** @copydoc VBOXSERVICE::pfnPreInit */ static DECLCALLBACK(int) VBoxServiceControlPreInit(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; - int rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID); + rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID); if (RT_FAILURE(rc)) { if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ @@ -132,23 +91,27 @@ static DECLCALLBACK(int) VBoxServiceControlPreInit(void) rc = VINF_SUCCESS; } else - VBoxServiceError("Failed to connect to the guest property service! Error: %Rrc\n", rc); + VBoxServiceError("Failed to connect to the guest property service, rc=%Rrc\n", rc); } else { - rc = VBoxServiceReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--control-procs-max-kept", - &g_uControlProcsMaxKept, 0, UINT32_MAX - 1); - 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; + rc = VINF_SUCCESS; #endif + + if (RT_SUCCESS(rc)) + { + /* Init session object. */ + rc = GstCntlSessionInit(&g_Session, 0 /* Flags */); + } + + return rc; } @@ -161,18 +124,15 @@ static DECLCALLBACK(int) VBoxServiceControlOption(const char **ppszShort, int ar else if (!strcmp(argv[*pi], "--control-interval")) rc = VBoxServiceArgUInt32(argc, argv, "", pi, &g_uControlIntervalMS, 1, UINT32_MAX - 1); - else if (!strcmp(argv[*pi], "--control-procs-max-kept")) - rc = VBoxServiceArgUInt32(argc, argv, "", pi, - &g_uControlProcsMaxKept, 0, UINT32_MAX - 1); #ifdef DEBUG - else if (!strcmp(argv[*pi], "--control-dump-stderr")) + else if (!strcmp(argv[*pi], "--control-dump-stdout")) { - g_fControlDumpStdErr = true; + g_Session.uFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT; rc = 0; /* Flag this command as parsed. */ } - else if (!strcmp(argv[*pi], "--control-dump-stdout")) + else if (!strcmp(argv[*pi], "--control-dump-stderr")) { - g_fControlDumpStdOut = true; + g_Session.uFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR; rc = 0; /* Flag this command as parsed. */ } #endif @@ -193,19 +153,18 @@ static DECLCALLBACK(int) VBoxServiceControlInit(void) int rc = RTSemEventMultiCreate(&g_hControlEvent); AssertRCReturn(rc, rc); - rc = VbglR3GuestCtrlConnect(&g_uControlSvcClientID); + VbglR3GetSessionId(&g_idControlSession); + /* The status code is ignored as this information is not available with VBox < 3.2.10. */ + + if (RT_SUCCESS(rc)) + rc = VbglR3GuestCtrlConnect(&g_uControlSvcClientID); if (RT_SUCCESS(rc)) { - VBoxServiceVerbose(3, "Service client ID: %#x\n", g_uControlSvcClientID); - - /* Init lists. */ - RTListInit(&g_lstControlThreadsActive); - RTListInit(&g_lstControlThreadsInactive); - RTListInit(&g_lstControlFiles); + VBoxServiceVerbose(3, "Guest control service client ID=%RU32\n", + g_uControlSvcClientID); - /* Init critical section for protecting the thread lists. */ - rc = RTCritSectInit(&g_csControlThreads); - AssertRC(rc); + /* Init session thread list. */ + RTListInit(&g_lstControlSessionThreads); } else { @@ -244,75 +203,91 @@ DECLCALLBACK(int) VBoxServiceControlWorker(bool volatile *pfShutdown) uint8_t *pvScratchBuf = (uint8_t*)RTMemAlloc(cbScratchBuf); AssertPtrReturn(pvScratchBuf, VERR_NO_MEMORY); - /* - * Execution loop. - * - * @todo - */ + VBGLR3GUESTCTRLCMDCTX ctxHost = { g_uControlSvcClientID }; + /* Set default protocol version to 1. */ + ctxHost.uProtocol = 1; + for (;;) { VBoxServiceVerbose(3, "Waiting for host msg ...\n"); uint32_t uMsg = 0; uint32_t cParms = 0; - rc = VbglR3GuestCtrlWaitForHostMsg(g_uControlSvcClientID, &uMsg, &cParms); + rc = VbglR3GuestCtrlMsgWaitFor(g_uControlSvcClientID, &uMsg, &cParms); if (rc == VERR_TOO_MUCH_DATA) { +#ifdef DEBUG VBoxServiceVerbose(4, "Message requires %ld parameters, but only 2 supplied -- retrying request (no error!)...\n", cParms); +#endif rc = VINF_SUCCESS; /* Try to get "real" message in next block below. */ } else if (RT_FAILURE(rc)) VBoxServiceVerbose(3, "Getting host message failed with %Rrc\n", rc); /* VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */ if (RT_SUCCESS(rc)) { - VBoxServiceVerbose(3, "Msg=%u (%u parms) retrieved\n", uMsg, cParms); - switch (uMsg) - { - case HOST_CANCEL_PENDING_WAITS: - VBoxServiceVerbose(3, "Host asked us to quit ...\n"); - break; - - case HOST_EXEC_CMD: - rc = VBoxServiceControlHandleCmdStartProc(g_uControlSvcClientID, cParms); - break; - - case HOST_EXEC_SET_INPUT: - rc = VBoxServiceControlHandleCmdSetInput(g_uControlSvcClientID, cParms, - pvScratchBuf, cbScratchBuf); - break; - - case HOST_EXEC_GET_OUTPUT: - rc = VBoxServiceControlHandleCmdGetOutput(g_uControlSvcClientID, cParms); - break; + VBoxServiceVerbose(4, "Msg=%RU32 (%RU32 parms) retrieved\n", uMsg, cParms); - case HOST_FILE_OPEN: - rc = VBoxServiceControlHandleFileOpen(g_uControlSvcClientID, cParms); - break; + /* Set number of parameters for current host context. */ + ctxHost.uNumParms = cParms; - case HOST_FILE_CLOSE: - rc = VBoxServiceControlHandleFileClose(g_uControlSvcClientID, cParms); - break; + /* Check for VM session change. */ + uint64_t idNewSession = g_idControlSession; + int rc2 = VbglR3GetSessionId(&idNewSession); + if ( RT_SUCCESS(rc2) + && (idNewSession != g_idControlSession)) + { + VBoxServiceVerbose(1, "The VM session ID changed\n"); + g_idControlSession = idNewSession; - case HOST_FILE_READ: - rc = VBoxServiceControlHandleFileRead(g_uControlSvcClientID, cParms); - break; + /* Close all opened guest sessions -- all context IDs, sessions etc. + * are now invalid. */ + rc2 = GstCntlSessionClose(&g_Session); + AssertRC(rc2); + } - case HOST_FILE_WRITE: - rc = VBoxServiceControlHandleFileWrite(g_uControlSvcClientID, cParms, - pvScratchBuf, cbScratchBuf); + switch (uMsg) + { + case HOST_CANCEL_PENDING_WAITS: + VBoxServiceVerbose(1, "We were asked to quit ...\n"); break; - case HOST_FILE_SEEK: - rc = VBoxServiceControlHandleFileSeek(g_uControlSvcClientID, cParms); + case HOST_SESSION_CREATE: + rc = gstcntlHandleSessionOpen(&ctxHost); break; - case HOST_FILE_TELL: - rc = VBoxServiceControlHandleFileTell(g_uControlSvcClientID, cParms); + case HOST_SESSION_CLOSE: + rc = gstcntlHandleSessionClose(&ctxHost); break; default: - VBoxServiceVerbose(3, "Unsupported message from host! Msg=%u\n", uMsg); - /* Don't terminate here; just wait for the next message. */ + { + /* + * Protocol v1 did not have support for (dedicated) + * guest sessions, so all actions need to be performed + * under behalf of VBoxService's main executable. + * + * The global session object then acts as a host for all + * started guest processes which bring all their + * credentials with them with the actual guest process + * execution call. + */ + if (ctxHost.uProtocol == 1) + { + rc = GstCntlSessionHandler(&g_Session, uMsg, &ctxHost, + pvScratchBuf, cbScratchBuf, pfShutdown); + } + else + { + /* + * ... on newer protocols handling all other commands is + * up to the guest session fork of VBoxService, so just + * skip all not wanted messages here. + */ + rc = VbglR3GuestCtrlMsgSkip(g_uControlSvcClientID); + VBoxServiceVerbose(3, "Skipping uMsg=%RU32, cParms=%RU32, rc=%Rrc\n", + uMsg, cParms, rc); + } break; + } } } @@ -320,7 +295,6 @@ DECLCALLBACK(int) VBoxServiceControlWorker(bool volatile *pfShutdown) if ( *pfShutdown || (RT_SUCCESS(rc) && uMsg == HOST_CANCEL_PENDING_WAITS)) { - rc = VINF_SUCCESS; break; } @@ -328,630 +302,96 @@ DECLCALLBACK(int) VBoxServiceControlWorker(bool volatile *pfShutdown) RTThreadYield(); } + VBoxServiceVerbose(0, "Guest control service stopped\n"); + /* Delete scratch buffer. */ if (pvScratchBuf) RTMemFree(pvScratchBuf); + VBoxServiceVerbose(0, "Guest control worker returned with rc=%Rrc\n", rc); return rc; } -/** - * Handles starting processes on the guest. - * - * @returns IPRT status code. - * @param uClientID The HGCM client session ID. - * @param cParms The number of parameters the host is offering. - */ -static int VBoxServiceControlHandleCmdStartProc(uint32_t uClientID, uint32_t cParms) -{ - uint32_t uContextID = 0; - - int rc; - bool fStartAllowed = false; /* Flag indicating whether starting a process is allowed or not. */ - if (cParms == 11) - { - VBOXSERVICECTRLPROCESS proc; - RT_ZERO(proc); - - /* 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. */ - proc.cbEnv = sizeof(proc.szEnv); - - rc = VbglR3GuestCtrlExecGetHostCmdExec(uClientID, - cParms, - &uContextID, - /* Command */ - proc.szCmd, sizeof(proc.szCmd), - /* Flags */ - &proc.uFlags, - /* Arguments */ - proc.szArgs, sizeof(proc.szArgs), &proc.uNumArgs, - /* Environment */ - proc.szEnv, &proc.cbEnv, &proc.uNumEnvVars, - /* Credentials */ - proc.szUser, sizeof(proc.szUser), - proc.szPassword, sizeof(proc.szPassword), - /* Timelimit */ - &proc.uTimeLimitMS); - if (RT_SUCCESS(rc)) - { - VBoxServiceVerbose(3, "Request to start process szCmd=%s, uFlags=0x%x, szArgs=%s, szEnv=%s, szUser=%s, szPassword=%s, uTimeout=%u\n", - proc.szCmd, proc.uFlags, - proc.uNumArgs ? proc.szArgs : "<None>", - proc.uNumEnvVars ? proc.szEnv : "<None>", - proc.szUser, -#ifdef DEBUG - proc.szPassword, -#else - "XXX", /* Never show passwords in release mode. */ -#endif - proc.uTimeLimitMS); - - rc = VBoxServiceControlReapThreads(); - if (RT_FAILURE(rc)) - VBoxServiceError("Reaping stopped processes failed with rc=%Rrc\n", rc); - /* Keep going. */ - - rc = VBoxServiceControlStartAllowed(&fStartAllowed); - if (RT_SUCCESS(rc)) - { - if (fStartAllowed) - { - rc = VBoxServiceControlThreadStart(uContextID, &proc); - } - else - rc = VERR_MAX_PROCS_REACHED; /* Maximum number of processes reached. */ - } - } - } - else - rc = VERR_INVALID_PARAMETER; /* Incorrect number of parameters. */ - - /* In case of an error we need to notify the host to not wait forever for our response. */ - if (RT_FAILURE(rc)) - { - VBoxServiceError("Starting process failed with rc=%Rrc\n", rc); - - /* - * Note: The context ID can be 0 because we mabye weren't able to fetch the command - * from the host. The host in case has to deal with that! - */ - int rc2 = VbglR3GuestCtrlExecReportStatus(uClientID, uContextID /* Might be 0 */, 0 /* PID, invalid */, - PROC_STS_ERROR, rc, - NULL /* pvData */, 0 /* cbData */); - if (RT_FAILURE(rc2)) - { - VBoxServiceError("Error sending start process status to host, rc=%Rrc\n", rc2); - if (RT_SUCCESS(rc)) - rc = rc2; - } - } - - return rc; -} - - -/** - * Gets output from stdout/stderr of a specified guest process. - * - * @return IPRT status code. - * @param uPID PID of process to retrieve the output from. - * @param uHandleId Stream ID (stdout = 0, stderr = 2) to get the output from. - * @param uTimeout Timeout (in ms) to wait for output becoming available. - * @param pvBuf Pointer to a pre-allocated buffer to store the output. - * @param cbBuf Size (in bytes) of the pre-allocated buffer. - * @param pcbRead Pointer to number of bytes read. Optional. - */ -int VBoxServiceControlExecGetOutput(uint32_t uPID, uint32_t uCID, - uint32_t uHandleId, uint32_t uTimeout, - void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +static int gstcntlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx) { - AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); - AssertReturn(cbBuf, VERR_INVALID_PARAMETER); - /* pcbRead is optional. */ - - int rc = VINF_SUCCESS; - VBOXSERVICECTRLREQUESTTYPE reqType; - switch (uHandleId) - { - case OUTPUT_HANDLE_ID_STDERR: - reqType = VBOXSERVICECTRLREQUEST_STDERR_READ; - break; - - case OUTPUT_HANDLE_ID_STDOUT: - case OUTPUT_HANDLE_ID_STDOUT_DEPRECATED: - reqType = VBOXSERVICECTRLREQUEST_STDOUT_READ; - break; - - default: - rc = VERR_INVALID_PARAMETER; - break; - } - - PVBOXSERVICECTRLREQUEST pRequest; - if (RT_SUCCESS(rc)) - { - rc = VBoxServiceControlThreadRequestAllocEx(&pRequest, reqType, - pvBuf, cbBuf, uCID); - if (RT_SUCCESS(rc)) - rc = VBoxServiceControlThreadPerform(uPID, pRequest); - - if (RT_SUCCESS(rc)) - { - if (pcbRead) - *pcbRead = pRequest->cbData; - } - - VBoxServiceControlThreadRequestFree(pRequest); - } - - return rc; -} - - -/** - * Sets the specified guest thread to a certain list. - * - * @return IPRT status code. - * @param enmList List to move thread to. - * @param pThread Thread to set inactive. - */ -int VBoxServiceControlListSet(VBOXSERVICECTRLTHREADLISTTYPE enmList, - PVBOXSERVICECTRLTHREAD pThread) -{ - AssertReturn(enmList > VBOXSERVICECTRLTHREADLIST_UNKNOWN, VERR_INVALID_PARAMETER); - AssertPtrReturn(pThread, VERR_INVALID_POINTER); - - int rc = RTCritSectEnter(&g_csControlThreads); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + 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.uFlags, &ssInfo.uSessionID); if (RT_SUCCESS(rc)) { - VBoxServiceVerbose(3, "Setting thread (PID %u) to list %d\n", - pThread->uPID, enmList); - - PRTLISTANCHOR pAnchor = NULL; - switch (enmList) - { - case VBOXSERVICECTRLTHREADLIST_STOPPED: - pAnchor = &g_lstControlThreadsInactive; - break; - - case VBOXSERVICECTRLTHREADLIST_RUNNING: - pAnchor = &g_lstControlThreadsActive; - break; - - default: - AssertMsgFailed(("Unknown list type: %u", enmList)); - break; - } - - if (!pAnchor) - rc = VERR_INVALID_PARAMETER; - - if (RT_SUCCESS(rc)) - { - if (pThread->pAnchor != NULL) - { - /* If thread was assigned to a list before, - * remove the thread from the old list first. */ - /* rc = */ RTListNodeRemove(&pThread->Node); - } + /* The session open call has the protocol version the host + * wants to use. So update the current protocol version with the one the + * host wants to use in subsequent calls. */ + pHostCtx->uProtocol = ssInfo.uProtocol; + VBoxServiceVerbose(3, "Client ID=%RU32 now is using protocol %RU32\n", + pHostCtx->uClientID, pHostCtx->uProtocol); - /* Add thread to desired list. */ - /* rc = */ RTListAppend(pAnchor, &pThread->Node); - pThread->pAnchor = pAnchor; - } - - int rc2 = RTCritSectLeave(&g_csControlThreads); - if (RT_SUCCESS(rc)) - rc = rc2; + rc = GstCntlSessionThreadCreate(&g_lstControlSessionThreads, + &ssInfo, NULL /* ppSessionThread */); } - return VINF_SUCCESS; -} - - -/** - * Injects input to a specified running process. - * - * @return IPRT status code. - * @param uPID PID of process to set the input for. - * @param fPendingClose Flag indicating whether this is the last input block sent to the process. - * @param pvBuf Pointer to a buffer containing the actual input data. - * @param cbBuf Size (in bytes) of the input buffer data. - * @param pcbWritten Pointer to number of bytes written to the process. Optional. - */ -int VBoxServiceControlSetInput(uint32_t uPID, uint32_t uCID, - bool fPendingClose, - void *pvBuf, uint32_t cbBuf, - uint32_t *pcbWritten) -{ - /* pvBuf is optional. */ - /* cbBuf is optional. */ - /* pcbWritten is optional. */ - - PVBOXSERVICECTRLREQUEST pRequest; - int rc = VBoxServiceControlThreadRequestAllocEx(&pRequest, - fPendingClose - ? VBOXSERVICECTRLREQUEST_STDIN_WRITE_EOF - : VBOXSERVICECTRLREQUEST_STDIN_WRITE, - pvBuf, cbBuf, uCID); - if (RT_SUCCESS(rc)) - { - rc = VBoxServiceControlThreadPerform(uPID, pRequest); - if (RT_SUCCESS(rc)) - { - if (pcbWritten) - *pcbWritten = pRequest->cbData; - } - - VBoxServiceControlThreadRequestFree(pRequest); - } - - return rc; -} - - -/** - * Handles input for a started process by copying the received data into its - * stdin pipe. - * - * @returns IPRT status code. - * @param idClient The HGCM client session ID. - * @param cParms The number of parameters the host is - * offering. - * @param pvScratchBuf The scratch buffer. - * @param cbScratchBuf The scratch buffer size for retrieving the input data. - */ -static int VBoxServiceControlHandleCmdSetInput(uint32_t idClient, uint32_t cParms, - void *pvScratchBuf, size_t cbScratchBuf) -{ - AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER); - AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER); - - uint32_t uContextID; - uint32_t uPID; - uint32_t uFlags; - uint32_t cbSize; - - uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status sent back to the host. */ - uint32_t cbWritten = 0; /* Number of bytes written to the guest. */ - - /* - * Ask the host for the input data. - */ - int rc = VbglR3GuestCtrlExecGetHostCmdInput(idClient, cParms, - &uContextID, &uPID, &uFlags, - pvScratchBuf, cbScratchBuf, &cbSize); if (RT_FAILURE(rc)) { - VBoxServiceError("[PID %u]: Failed to retrieve exec input command! Error: %Rrc\n", - uPID, rc); - } - else if (cbSize > cbScratchBuf) - { - VBoxServiceError("[PID %u]: Too much input received! cbSize=%u, cbScratchBuf=%u\n", - uPID, cbSize, cbScratchBuf); - rc = VERR_INVALID_PARAMETER; - } - else - { - /* - * Is this the last input block we need to deliver? Then let the pipe know ... - */ - bool fPendingClose = false; - if (uFlags & INPUT_FLAG_EOF) - { - fPendingClose = true; - VBoxServiceVerbose(4, "[PID %u]: Got last input block of size %u ...\n", - uPID, cbSize); - } - - rc = VBoxServiceControlSetInput(uPID, uContextID, fPendingClose, pvScratchBuf, - cbSize, &cbWritten); - VBoxServiceVerbose(4, "[PID %u]: Written input, CID=%u, rc=%Rrc, uFlags=0x%x, fPendingClose=%d, cbSize=%u, cbWritten=%u\n", - uPID, uContextID, rc, uFlags, fPendingClose, cbSize, cbWritten); - if (RT_SUCCESS(rc)) - { - uStatus = INPUT_STS_WRITTEN; - uFlags = 0; /* No flags at the moment. */ - } - else - { - if (rc == VERR_BAD_PIPE) - uStatus = INPUT_STS_TERMINATED; - else if (rc == VERR_BUFFER_OVERFLOW) - uStatus = INPUT_STS_OVERFLOW; - } - } - - /* - * 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; - uFlags = 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 /* uint32_t vs. int */); + if (RT_FAILURE(rc2)) + VBoxServiceError("Reporting session error status on open failed with rc=%Rrc\n", rc2); } - Assert(uStatus > INPUT_STS_UNDEFINED); - - VBoxServiceVerbose(3, "[PID %u]: Input processed, CID=%u, uStatus=%u, uFlags=0x%x, cbWritten=%u\n", - uPID, uContextID, uStatus, uFlags, cbWritten); - /* Note: Since the context ID is unique the request *has* to be completed here, - * regardless whether we got data or not! Otherwise the progress object - * on the host never will get completed! */ - rc = VbglR3GuestCtrlExecReportStatusIn(idClient, uContextID, uPID, - uStatus, uFlags, (uint32_t)cbWritten); - - if (RT_FAILURE(rc)) - VBoxServiceError("[PID %u]: Failed to report input status! Error: %Rrc\n", - uPID, rc); + VBoxServiceVerbose(3, "Opening a new guest session returned rc=%Rrc\n", rc); return rc; } -static PVBOXSERVICECTRLFILE VBoxControlGetFile(uint32_t uHandle) +static int gstcntlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx) { - PVBOXSERVICECTRLFILE pFileCur = NULL; - /** @todo Use a map later! */ - RTListForEach(&g_lstControlFiles, pFileCur, VBOXSERVICECTRLFILE, Node) - { - if (pFileCur->uHandle == uHandle) - return pFileCur; - } - - return NULL; -} + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); - -static int VBoxServiceControlHandleFileOpen(uint32_t idClient, uint32_t cParms) -{ - uint32_t uContextID; - - char szFile[RTPATH_MAX]; - char szOpenMode[64]; - char szDisposition[64]; - uint32_t uCreationMode; - uint64_t uOffset; - - int rc = VbglR3GuestCtrlFileGetHostCmdOpen(idClient, cParms, &uContextID, - /* File to open. */ - szFile, sizeof(szFile), - /* Open mode. */ - szOpenMode, sizeof(szOpenMode), - /* Disposition. */ - szDisposition, sizeof(szDisposition), - /* Creation mode. */ - &uCreationMode, - /* Offset. */ - &uOffset); + uint32_t uSessionID, uFlags; + int rc = VbglR3GuestCtrlSessionGetClose(pHostCtx, &uFlags, &uSessionID); if (RT_SUCCESS(rc)) { - PVBOXSERVICECTRLFILE pFile = (PVBOXSERVICECTRLFILE)RTMemAlloc(sizeof(VBOXSERVICECTRLFILE)); - if (!pFile) - return VERR_NO_MEMORY; - - if (!RTStrPrintf(pFile->szName, sizeof(pFile->szName), "%s", szFile)) - rc = VERR_BUFFER_UNDERFLOW; + rc = VERR_NOT_FOUND; - if (RT_SUCCESS(rc)) + PVBOXSERVICECTRLSESSIONTHREAD pThread; + RTListForEach(&g_lstControlSessionThreads, pThread, VBOXSERVICECTRLSESSIONTHREAD, Node) { - uint64_t fFlags = RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE; /** @todo Modes! */ - rc = RTFileOpen(&pFile->hFile, pFile->szName, fFlags); - if ( RT_SUCCESS(rc) - && uOffset) + if (pThread->StartupInfo.uSessionID == uSessionID) { - /* Seeking is optional. */ - int rc2 = RTFileSeek(pFile->hFile, (int64_t)uOffset, RTFILE_SEEK_BEGIN, NULL /* Current offset */); - if (RT_FAILURE(rc2)) - VBoxServiceVerbose(3, "[File %s]: Seeking to offset %RU64 failed; rc=%Rrc\n", - pFile->szName, uOffset, rc); + rc = GstCntlSessionThreadDestroy(pThread, uFlags); + break; } - else - VBoxServiceVerbose(3, "[File %s]: Opening failed; rc=%Rrc\n", - pFile->szName, rc); - } - - uint32_t uHandle = 0; - if (RT_SUCCESS(rc)) - { - VBoxServiceVerbose(3, "[File %s]: Opened.\n", pFile->szName); - - uHandle = g_uControlFileCount++; - pFile->uHandle = uHandle; - /* rc = */ RTListAppend(&g_lstControlFiles, &pFile->Node); } - +#if 0 if (RT_FAILURE(rc)) - RTMemFree(pFile); - - /* Report back in any case. */ - int rc2 = VbglR3GuestCtrlFileNotify(idClient, uContextID, uHandle, - GUESTFILENOTIFYTYPE_OPEN, &rc, sizeof(rc)); - if (RT_FAILURE(rc2)) - VBoxServiceError("[File %s]: Failed to report open status, rc=%Rrc\n", - szFile, rc2); - if (RT_SUCCESS(rc)) - rc = rc2; - } - return rc; -} - - -static int VBoxServiceControlHandleFileClose(uint32_t idClient, uint32_t cParms) -{ - uint32_t uContextID; - uint32_t uHandle; - - int rc = VbglR3GuestCtrlFileGetHostCmdClose(idClient, cParms, &uContextID, - /* File handle to close. */ - &uHandle); - if (RT_SUCCESS(rc)) - { - PVBOXSERVICECTRLFILE pFile = VBoxControlGetFile(uHandle); - if (pFile) - { - rc = RTFileClose(pFile->hFile); - } - else - rc = VERR_NOT_FOUND; - - /* Report back in any case. */ - int rc2 = VbglR3GuestCtrlFileNotify(idClient, uContextID, uHandle, - GUESTFILENOTIFYTYPE_CLOSE, &rc, sizeof(rc)); - if (RT_FAILURE(rc2)) - VBoxServiceError("Failed to report close status, rc=%Rrc\n", rc2); - if (RT_SUCCESS(rc)) - rc = rc2; - } - return rc; -} - - -static int VBoxServiceControlHandleFileRead(uint32_t idClient, uint32_t cParms) -{ - uint32_t uContextID; - uint32_t uHandle; - uint32_t cbToRead; - - int rc = VbglR3GuestCtrlFileGetHostCmdRead(idClient, cParms, &uContextID, - &uHandle, &cbToRead); - if (RT_SUCCESS(rc)) - { - - } - return rc; -} - - -static int VBoxServiceControlHandleFileWrite(uint32_t idClient, uint32_t cParms, - void *pvScratchBuf, size_t cbScratchBuf) -{ - AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER); - AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER); - - uint32_t uContextID; - uint32_t uHandle; - uint32_t cbToWrite; - - int rc = VbglR3GuestCtrlFileGetHostCmdWrite(idClient, cParms, &uContextID, - &uHandle, pvScratchBuf, cbScratchBuf, - &cbToWrite); - if (RT_SUCCESS(rc)) - { - - } - return rc; -} - - -static int VBoxServiceControlHandleFileSeek(uint32_t idClient, uint32_t cParms) -{ - uint32_t uContextID; - uint32_t uHandle; - uint32_t uSeekMethod; - uint64_t uOffset; /* Will be converted to int64_t. */ - - int rc = VbglR3GuestCtrlFileGetHostCmdSeek(idClient, cParms, &uContextID, - &uHandle, &uSeekMethod, &uOffset); - if (RT_SUCCESS(rc)) - { - - } - return rc; -} - - -static int VBoxServiceControlHandleFileTell(uint32_t idClient, uint32_t cParms) -{ - uint32_t uContextID; - uint32_t uHandle; - - int rc = VbglR3GuestCtrlFileGetHostCmdTell(idClient, cParms, &uContextID, - &uHandle); - if (RT_SUCCESS(rc)) - { - - } - return rc; -} - - -/** - * Handles the guest control output command. - * - * @return IPRT status code. - * @param idClient The HGCM client session ID. - * @param cParms The number of parameters the host is offering. - */ -static int VBoxServiceControlHandleCmdGetOutput(uint32_t idClient, uint32_t cParms) -{ - uint32_t uContextID; - uint32_t uPID; - uint32_t uHandleID; - uint32_t uFlags; - - int rc = VbglR3GuestCtrlExecGetHostCmdOutput(idClient, cParms, - &uContextID, &uPID, &uHandleID, &uFlags); - if (RT_SUCCESS(rc)) - { - uint8_t *pBuf = (uint8_t*)RTMemAlloc(_64K); - if (pBuf) { - uint32_t cbRead = 0; - rc = VBoxServiceControlExecGetOutput(uPID, uContextID, uHandleID, RT_INDEFINITE_WAIT /* Timeout */, - pBuf, _64K /* cbSize */, &cbRead); - VBoxServiceVerbose(3, "[PID %u]: Got output, rc=%Rrc, CID=%u, cbRead=%u, uHandle=%u, uFlags=%u\n", - uPID, rc, uContextID, cbRead, uHandleID, uFlags); - -#ifdef DEBUG - if ( g_fControlDumpStdErr - && uHandleID == OUTPUT_HANDLE_ID_STDERR) + /* 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)) { - char szPID[RTPATH_MAX]; - if (!RTStrPrintf(szPID, sizeof(szPID), "VBoxService_PID%u_StdOut.txt", uPID)) - rc = VERR_BUFFER_UNDERFLOW; + VBoxServiceError("Reporting session error status on close failed with rc=%Rrc\n", rc2); if (RT_SUCCESS(rc)) - rc = vboxServiceControlDump(szPID, pBuf, cbRead); + rc = rc2; } - else if ( g_fControlDumpStdOut - && ( uHandleID == OUTPUT_HANDLE_ID_STDOUT - || uHandleID == OUTPUT_HANDLE_ID_STDOUT_DEPRECATED)) - { - char szPID[RTPATH_MAX]; - if (!RTStrPrintf(szPID, sizeof(szPID), "VBoxService_PID%u_StdOut.txt", uPID)) - rc = VERR_BUFFER_UNDERFLOW; - if (RT_SUCCESS(rc)) - rc = vboxServiceControlDump(szPID, pBuf, cbRead); - AssertRC(rc); - } -#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 progress object - * on the host never will get completed! */ - int rc2 = VbglR3GuestCtrlExecSendOut(idClient, uContextID, uPID, uHandleID, uFlags, - pBuf, cbRead); - if (RT_SUCCESS(rc)) - rc = rc2; - else if (rc == VERR_NOT_FOUND) /* It's not critical if guest process (PID) is not found. */ - rc = VINF_SUCCESS; - - RTMemFree(pBuf); } - else - rc = VERR_NO_MEMORY; +#endif + VBoxServiceVerbose(2, "Closing guest session %RU32 returned rc=%Rrc\n", + uSessionID, rc); } - - if (RT_FAILURE(rc)) - VBoxServiceError("[PID %u]: Error handling output command! Error: %Rrc\n", - uPID, rc); + else + VBoxServiceError("Closing guest session %RU32 failed with rc=%Rrc\n", + uSessionID, rc); return rc; } @@ -967,8 +407,8 @@ static DECLCALLBACK(void) VBoxServiceControlStop(void) RTSemEventMultiSignal(g_hControlEvent); /* - * Ask the host service to cancel all pending requests so that we can - * shutdown properly here. + * Ask the host service to cancel all pending requests for the main + * control thread so that we can shutdown properly here. */ if (g_uControlSvcClientID) { @@ -983,124 +423,20 @@ static DECLCALLBACK(void) VBoxServiceControlStop(void) /** - * Reaps all inactive guest process threads. - * - * @return IPRT status code. - */ -static int VBoxServiceControlReapThreads(void) -{ - int rc = RTCritSectEnter(&g_csControlThreads); - if (RT_SUCCESS(rc)) - { - PVBOXSERVICECTRLTHREAD pThread = - RTListGetFirst(&g_lstControlThreadsInactive, VBOXSERVICECTRLTHREAD, Node); - while (pThread) - { - PVBOXSERVICECTRLTHREAD pNext = RTListNodeGetNext(&pThread->Node, VBOXSERVICECTRLTHREAD, Node); - bool fLast = RTListNodeIsLast(&g_lstControlThreadsInactive, &pThread->Node); - int rc2 = VBoxServiceControlThreadWait(pThread, 30 * 1000 /* 30 seconds max. */, - NULL /* rc */); - if (RT_SUCCESS(rc2)) - { - RTListNodeRemove(&pThread->Node); - - rc2 = VBoxServiceControlThreadFree(pThread); - if (RT_FAILURE(rc2)) - { - VBoxServiceError("Freeing guest process thread failed with rc=%Rrc\n", rc2); - if (RT_SUCCESS(rc)) /* Keep original failure. */ - rc = rc2; - } - } - else - VBoxServiceError("Waiting on guest process thread failed with rc=%Rrc\n", rc2); - /* Keep going. */ - - if (fLast) - break; - - pThread = pNext; - } - - int rc2 = RTCritSectLeave(&g_csControlThreads); - if (RT_SUCCESS(rc)) - rc = rc2; - } - - VBoxServiceVerbose(4, "Reaping threads returned with rc=%Rrc\n", rc); - return rc; -} - - -/** * Destroys all guest process threads which are still active. */ static void VBoxServiceControlShutdown(void) { VBoxServiceVerbose(2, "Shutting down ...\n"); - /* Signal all threads in the active list that we want to shutdown. */ - PVBOXSERVICECTRLTHREAD pThread; - RTListForEach(&g_lstControlThreadsActive, pThread, VBOXSERVICECTRLTHREAD, Node) - VBoxServiceControlThreadStop(pThread); - - /* Wait for all active threads to shutdown and destroy the active thread list. */ - pThread = RTListGetFirst(&g_lstControlThreadsActive, VBOXSERVICECTRLTHREAD, Node); - while (pThread) - { - PVBOXSERVICECTRLTHREAD pNext = RTListNodeGetNext(&pThread->Node, VBOXSERVICECTRLTHREAD, Node); - bool fLast = RTListNodeIsLast(&g_lstControlThreadsActive, &pThread->Node); - - int rc2 = VBoxServiceControlThreadWait(pThread, - 30 * 1000 /* Wait 30 seconds max. */, - NULL /* rc */); - if (RT_FAILURE(rc2)) - VBoxServiceError("Guest process thread failed to stop; rc=%Rrc\n", rc2); - - if (fLast) - break; - - pThread = pNext; - } - - int rc2 = VBoxServiceControlReapThreads(); + int rc2 = GstCntlSessionThreadDestroyAll(&g_lstControlSessionThreads, + 0 /* Flags */); if (RT_FAILURE(rc2)) - VBoxServiceError("Reaping inactive threads failed with rc=%Rrc\n", rc2); - - AssertMsg(RTListIsEmpty(&g_lstControlThreadsActive), - ("Guest process active thread list still contains entries when it should not\n")); - AssertMsg(RTListIsEmpty(&g_lstControlThreadsInactive), - ("Guest process inactive thread list still contains entries when it should not\n")); - - /* Destroy critical section. */ - RTCritSectDelete(&g_csControlThreads); - - /* Close all left guest files. */ - PVBOXSERVICECTRLFILE pFile; - pFile = RTListGetFirst(&g_lstControlFiles, VBOXSERVICECTRLFILE, Node); - while (pFile) - { - PVBOXSERVICECTRLFILE pNext = RTListNodeGetNext(&pFile->Node, VBOXSERVICECTRLFILE, Node); - bool fLast = RTListNodeIsLast(&g_lstControlFiles, &pFile->Node); - - rc2 = RTFileClose(pFile->hFile); - if (RT_FAILURE(rc2)) - { - VBoxServiceError("Unable to close file \"%s\"; rc=%Rrc\n", - pFile->szName, rc2); - /* Keep going. */ - } - - RTListNodeRemove(&pFile->Node); - - if (fLast) - break; + VBoxServiceError("Closing session threads failed with rc=%Rrc\n", rc2); - pFile = pNext; - } - - AssertMsg(RTListIsEmpty(&g_lstControlFiles), - ("Guest file list still contains entries when it should not\n")); + rc2 = GstCntlSessionClose(&g_Session); + if (RT_FAILURE(rc2)) + VBoxServiceError("Closing session failed with rc=%Rrc\n", rc2); VBoxServiceVerbose(2, "Shutting down complete\n"); } @@ -1127,152 +463,6 @@ static DECLCALLBACK(void) VBoxServiceControlTerm(void) /** - * Determines whether starting a new guest process according to the - * maximum number of concurrent guest processes defined is allowed or not. - * - * @return IPRT status code. - * @param pbAllowed True if starting (another) guest process - * is allowed, false if not. - */ -static int VBoxServiceControlStartAllowed(bool *pbAllowed) -{ - AssertPtrReturn(pbAllowed, VERR_INVALID_POINTER); - - int rc = RTCritSectEnter(&g_csControlThreads); - 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 (g_uControlProcsMaxKept) /* If we allow unlimited processes (=0), take a shortcut. */ - { - uint32_t uProcsRunning = 0; - PVBOXSERVICECTRLTHREAD pThread; - RTListForEach(&g_lstControlThreadsActive, pThread, VBOXSERVICECTRLTHREAD, Node) - uProcsRunning++; - - VBoxServiceVerbose(3, "Maximum served guest processes set to %u, running=%u\n", - g_uControlProcsMaxKept, uProcsRunning); - - int32_t iProcsLeft = (g_uControlProcsMaxKept - uProcsRunning - 1); - if (iProcsLeft < 0) - { - VBoxServiceVerbose(3, "Maximum running guest processes reached (%u)\n", - g_uControlProcsMaxKept); - fLimitReached = true; - } - } - - *pbAllowed = !fLimitReached; - - int rc2 = RTCritSectLeave(&g_csControlThreads); - if (RT_SUCCESS(rc)) - rc = rc2; - } - - return rc; -} - - -/** - * Finds a (formerly) started process given by its PID and locks it. Must be unlocked - * by the caller with VBoxServiceControlThreadUnlock(). - * - * @return PVBOXSERVICECTRLTHREAD Process structure if found, otherwise NULL. - * @param uPID PID to search for. - */ -PVBOXSERVICECTRLTHREAD VBoxServiceControlLockThread(uint32_t uPID) -{ - PVBOXSERVICECTRLTHREAD pThread = NULL; - int rc = RTCritSectEnter(&g_csControlThreads); - if (RT_SUCCESS(rc)) - { - PVBOXSERVICECTRLTHREAD pThreadCur; - RTListForEach(&g_lstControlThreadsActive, pThreadCur, VBOXSERVICECTRLTHREAD, Node) - { - if (pThreadCur->uPID == uPID) - { - rc = RTCritSectEnter(&pThreadCur->CritSect); - if (RT_SUCCESS(rc)) - pThread = pThreadCur; - break; - } - } - - int rc2 = RTCritSectLeave(&g_csControlThreads); - if (RT_SUCCESS(rc)) - rc = rc2; - } - - return pThread; -} - - -/** - * Unlocks a previously locked guest process thread. - * - * @param pThread Thread to unlock. - */ -void VBoxServiceControlUnlockThread(const PVBOXSERVICECTRLTHREAD pThread) -{ - AssertPtr(pThread); - - int rc = RTCritSectLeave(&pThread->CritSect); - AssertRC(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 pThread Thread to assign PID to. - * @param uPID PID to assign to the specified guest control execution thread. - */ -int VBoxServiceControlAssignPID(PVBOXSERVICECTRLTHREAD pThread, uint32_t uPID) -{ - AssertPtrReturn(pThread, VERR_INVALID_POINTER); - AssertReturn(uPID, VERR_INVALID_PARAMETER); - - int rc = RTCritSectEnter(&g_csControlThreads); - if (RT_SUCCESS(rc)) - { - /* Search old threads using the desired PID and shut them down completely -- it's - * not used anymore. */ - PVBOXSERVICECTRLTHREAD pThreadCur; - bool fTryAgain = false; - do - { - RTListForEach(&g_lstControlThreadsActive, pThreadCur, VBOXSERVICECTRLTHREAD, Node) - { - if (pThreadCur->uPID == uPID) - { - Assert(pThreadCur != pThread); /* can't happen */ - uint32_t uTriedPID = uPID; - uPID += 391939; - VBoxServiceVerbose(2, "PID %u was used before, trying again with %u ...\n", - uTriedPID, uPID); - fTryAgain = true; - break; - } - } - } while (fTryAgain); - - /* Assign PID to current thread. */ - pThread->uPID = uPID; - - rc = RTCritSectLeave(&g_csControlThreads); - AssertRC(rc); - } - - return rc; -} - - -/** * The 'vminfo' service description. */ VBOXSERVICE g_Control = @@ -1285,8 +475,7 @@ VBOXSERVICE g_Control = #ifdef DEBUG " [--control-dump-stderr] [--control-dump-stdout]\n" #endif - " [--control-interval <ms>] [--control-procs-max-kept <x>]\n" - " [--control-procs-mem-std[in|out|err] <KB>]" + " [--control-interval <ms>]" , /* pszOptions. */ #ifdef DEBUG @@ -1297,9 +486,6 @@ VBOXSERVICE g_Control = #endif " --control-interval Specifies the interval at which to check for\n" " new control commands. The default is 1000 ms.\n" - " --control-procs-max-kept\n" - " Specifies how many started guest processes are\n" - " kept into memory to work with. Default is 256.\n" , /* methods */ VBoxServiceControlPreInit, diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h new file mode 100644 index 00000000..7afe7cb4 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h @@ -0,0 +1,325 @@ +/* $Id: VBoxServiceControl.h $ */ +/** @file + * VBoxServiceControl.h - Internal guest control definitions. + */ + +/* + * Copyright (C) 2013 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 ___VBoxServiceControl_h +#define ___VBoxServiceControl_h + +#include <iprt/critsect.h> +#include <iprt/list.h> +#include <iprt/req.h> + +#include <VBox/VBoxGuestLib.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 uFlags; +} 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; + /** The worker thread. */ + RTTHREAD Thread; + /** Critical section for thread-safe use. */ + RTCRITSECT CritSect; + /** 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 +} VBOXSERVICECTRLSESSIONTHREAD; +/** Pointer to thread data. */ +typedef VBOXSERVICECTRLSESSIONTHREAD *PVBOXSERVICECTRLSESSIONTHREAD; + +/** Flag indicating that this session has been forked from + * the main executable. */ +#define VBOXSERVICECTRLSESSION_FLAG_FORK 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 uFlags; + /** 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]; + /** 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; + /** Client ID. */ + uint32_t uClientID; + /** 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 notification pipe associated with this guest process. + * This is NIL_RTPIPE for output pipes. */ + RTPIPE hNotificationPipeW; + /** The other end of hNotificationPipeW. */ + RTPIPE hNotificationPipeR; +} VBOXSERVICECTRLPROCESS; +/** Pointer to thread data. */ +typedef VBOXSERVICECTRLPROCESS *PVBOXSERVICECTRLPROCESS; + +RT_C_DECLS_BEGIN + +/** + * Note on naming conventions: + * - VBoxServiceControl* is named everything sub service module related, e.g. + * everything which is callable by main() and/or the service dispatcher(s). + * - GstCntl* is named everything which declared extern and thus can be called + * by different guest control modules as needed. + * - gstcntl (all lowercase) is a purely static function to split up functionality + * inside a module. + */ + +/* Guest session thread handling. */ +extern int GstCntlSessionThreadCreate(PRTLISTANCHOR pList, const PVBOXSERVICECTRLSESSIONSTARTUPINFO pSessionStartupInfo, PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread); +extern int GstCntlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pSession, uint32_t uFlags); +extern int GstCntlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t uFlags); +extern int GstCntlSessionThreadTerminate(PVBOXSERVICECTRLSESSIONTHREAD pSession); +extern RTEXITCODE VBoxServiceControlSessionForkInit(int argc, char **argv); +/* Per-session functions. */ +extern PVBOXSERVICECTRLPROCESS GstCntlSessionRetainProcess(PVBOXSERVICECTRLSESSION pSession, uint32_t uPID); +extern int GstCntlSessionClose(PVBOXSERVICECTRLSESSION pSession); +extern int GstCntlSessionDestroy(PVBOXSERVICECTRLSESSION pSession); +extern int GstCntlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t uFlags); +extern int GstCntlSessionHandler(PVBOXSERVICECTRLSESSION pSession, uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx, void *pvScratchBuf, size_t cbScratchBuf, volatile bool *pfShutdown); +extern int GstCntlSessionProcessAdd(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess); +extern int GstCntlSessionProcessRemove(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess); +extern int GstCntlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession, bool *pbAllowed); +extern int GstCntlSessionReapProcesses(PVBOXSERVICECTRLSESSION pSession); +/* Per-guest process functions. */ +extern int GstCntlProcessFree(PVBOXSERVICECTRLPROCESS pProcess); +extern int GstCntlProcessHandleInput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fPendingClose, void *pvBuf, uint32_t cbBuf); +extern int GstCntlProcessHandleOutput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags); +extern int GstCntlProcessHandleTerm(PVBOXSERVICECTRLPROCESS pProcess); +extern void GstCntlProcessRelease(PVBOXSERVICECTRLPROCESS pProcess); +extern int GstCntlProcessStart(const PVBOXSERVICECTRLSESSION pSession, const PVBOXSERVICECTRLPROCSTARTUPINFO pStartupInfo, uint32_t uContext); +extern int GstCntlProcessStop(PVBOXSERVICECTRLPROCESS pProcess); +extern int GstCntlProcessWait(const PVBOXSERVICECTRLPROCESS pProcess, RTMSINTERVAL msTimeout, int *pRc); + +RT_C_DECLS_END + +#endif /* ___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..c355b581 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp @@ -0,0 +1,2141 @@ +/* $Id: VBoxServiceControlProcess.cpp $ */ +/** @file + * VBoxServiceControlThread - Guest process handling. + */ + +/* + * Copyright (C) 2012-2013 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" + +using namespace guestControl; + +/******************************************************************************* +* Internal Functions * +*******************************************************************************/ +static int gstcntlProcessAssignPID(PVBOXSERVICECTRLPROCESS pThread, uint32_t uPID); +static int gstcntlProcessLock(PVBOXSERVICECTRLPROCESS pProcess); +static int gstcntlProcessRequest(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, PFNRT pfnFunction, unsigned cArgs, ...); +static int gstcntlProcessSetupPipe(const char *pszHowTo, int fd, PRTHANDLE ph, PRTHANDLE *pph, PRTPIPE phPipe); +static int gstcntlProcessUnlock(PVBOXSERVICECTRLPROCESS pProcess); +/* Request handlers. */ +static DECLCALLBACK(int) gstcntlProcessOnInput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fPendingClose, void *pvBuf, uint32_t cbBuf); +static DECLCALLBACK(int) gstcntlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags); +static DECLCALLBACK(int) gstcntlProcessOnTerm(PVBOXSERVICECTRLPROCESS pThis); + +/** + * 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 gstcntlProcessInit(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. */ + GstCntlProcessFree(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 GstCntlProcessFree(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + VBoxServiceVerbose(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 = GstCntlSessionProcessRemove(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 GstCntlProcessStop(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + VBoxServiceVerbose(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 GstCntlProcessHandleTerm(pProcess); +} + + +/** + * Releases a previously acquired guest process (decreases the refcount). + * + * @param pProcess Process to unlock. + */ +void GstCntlProcessRelease(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) + GstCntlProcessFree(pProcess); +} + + +/** + * Wait for a guest process thread to shut down. + * + * @return IPRT status code. + * @param pProcess Process to wait shutting down for. + * @param RTMSINTERVAL Timeout in ms to wait for shutdown. + * @param pRc Where to store the thread's return code. Optional. + */ +int GstCntlProcessWait(const PVBOXSERVICECTRLPROCESS pProcess, + RTMSINTERVAL msTimeout, int *pRc) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + /* pRc is optional. */ + + int rc = gstcntlProcessLock(pProcess); + if (RT_SUCCESS(rc)) + { + VBoxServiceVerbose(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 = gstcntlProcessUnlock(pProcess); + AssertRC(rc); + + /* Do the actual waiting. */ + int rcThread; + Assert(pProcess->Thread != NIL_RTTHREAD); + rc = RTThreadWait(pProcess->Thread, msTimeout, &rcThread); + if (RT_FAILURE(rc)) + { + VBoxServiceError("[PID %RU32]: Waiting for shutting down thread returned error rc=%Rrc\n", + pProcess->uPID, rc); + } + else + { + VBoxServiceVerbose(3, "[PID %RU32]: Thread shutdown complete, thread rc=%Rrc\n", + pProcess->uPID, rcThread); + if (pRc) + *pRc = rcThread; + } + } + else + { + VBoxServiceVerbose(3, "[PID %RU32]: Thread already shut down, no waiting needed\n", + pProcess->uPID); + + int rc2 = gstcntlProcessUnlock(pProcess); + AssertRC(rc2); + } + } + + VBoxServiceVerbose(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 hPollSet The polling set. + * @param phStdInW The standard input pipe handle. + */ +static int gstcntlProcessPollsetCloseInput(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; +} + + +static const char* gstcntlProcessPollHandleToString(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: + break; + } + + return "unknown"; +} + + +/** + * 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 gstcntlProcessPollsetOnInput(PVBOXSERVICECTRLPROCESS pProcess, + uint32_t fPollEvt, PRTPIPE phStdInW) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + NOREF(fPollEvt); + + return gstcntlProcessPollsetCloseInput(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 gstcntlProcessHandleOutputError(PVBOXSERVICECTRLPROCESS pProcess, + uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + if (!phPipeR) + return VINF_SUCCESS; + +#ifdef DEBUG + VBoxServiceVerbose(4, "[PID %RU32]: Output error: idPollHnd=%s, fPollEvt=0x%x\n", + pProcess->uPID, gstcntlProcessPollHandleToString(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 + VBoxServiceVerbose(3, "[PID %RU32]: idPollHnd=%s has %zu bytes left, vetoing close\n", + pProcess->uPID, gstcntlProcessPollHandleToString(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 + VBoxServiceVerbose(3, "[PID %RU32]: idPollHnd=%s will be closed\n", + pProcess->uPID, gstcntlProcessPollHandleToString(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 gstcntlProcessPollsetOnOutput(PVBOXSERVICECTRLPROCESS pProcess, + uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + +#ifdef DEBUG + VBoxServiceVerbose(4, "[PID %RU32]: Output event phPipeR=%p, idPollHnd=%s, fPollEvt=0x%x\n", + pProcess->uPID, phPipeR, gstcntlProcessPollHandleToString(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) + { + VBoxServiceVerbose(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); + VBoxServiceVerbose(4, "GstCntlProcessHandleOutputEvent 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 = gstcntlProcessHandleOutputError(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 gstcntlProcessProcLoop(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 = gstcntlProcessAssignPID(pProcess, pProcess->hProcess /* Opaque PID handle */); + if (RT_FAILURE(rc)) + { + VBoxServiceError("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. + */ + VBoxServiceVerbose(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 = { pProcess->uClientID, pProcess->uContextID }; + rc = VbglR3GuestCtrlProcCbStatus(&ctxStart, + pProcess->uPID, PROC_STS_STARTED, 0 /* u32Flags */, + NULL /* pvData */, 0 /* cbData */); + if (RT_FAILURE(rc)) + VBoxServiceError("[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 = gstcntlProcessPollsetOnInput(pProcess, fPollEvt, + &pProcess->hPipeStdInW); + break; + + case VBOXSERVICECTRLPIPEID_STDOUT: + rc = gstcntlProcessPollsetOnOutput(pProcess, fPollEvt, + &pProcess->hPipeStdOutR, idPollHnd); + break; + + case VBOXSERVICECTRLPIPEID_STDERR: + rc = gstcntlProcessPollsetOnOutput(pProcess, fPollEvt, + &pProcess->hPipeStdOutR, idPollHnd); + break; + + case VBOXSERVICECTRLPIPEID_IPC_NOTIFY: +#ifdef DEBUG_andy + VBoxServiceVerbose(4, "[PID %RU32]: IPC notify\n", pProcess->uPID); +#endif + rc2 = gstcntlProcessLock(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)) + VBoxServiceError("Draining IPC notification pipe failed with rc=%Rrc\n", rc2); + + /* Process all pending requests. */ + VBoxServiceVerbose(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) + VBoxServiceError("Processing requests failed with with rc=%Rrc\n", rc2); + + int rc3 = gstcntlProcessUnlock(pProcess); + AssertRC(rc3); +#ifdef DEBUG + VBoxServiceVerbose(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 + VBoxServiceVerbose(4, "[PID %RU32]: Polling done, pollRc=%Rrc, pollCnt=%RU32, idPollHnd=%s, rc=%Rrc, fProcessAlive=%RTbool, fShutdown=%RTbool\n", + pProcess->uPID, rc2, RTPollSetGetCount(hPollSet), gstcntlProcessPollHandleToString(idPollHnd), rc, fProcessAlive, pProcess->fShutdown); + VBoxServiceVerbose(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 0 + VBoxServiceVerbose(4, "[PID %RU32]: RTProcWaitNoResume=%Rrc\n", + pProcess->uPID, rc2); +#endif + 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) + ) + { + 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. */ + + VBoxServiceVerbose(3, "[PID %RU32]: Timed out (%RU64ms elapsed > %RU32ms timeout), killing ...\n", + pProcess->uPID, cMsElapsed, pProcess->StartupInfo.uTimeLimitMS); + + rc2 = RTProcTerminate(pProcess->hProcess); + VBoxServiceVerbose(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; + } + + VBoxServiceVerbose(3, "[PID %RU32]: Loop ended: rc=%Rrc, fShutdown=%RTbool, fProcessAlive=%RTbool, fProcessTimedOut=%RTbool, MsProcessKilled=%RU64\n", + pProcess->uPID, rc, pProcess->fShutdown, fProcessAlive, fProcessTimedOut, MsProcessKilled, MsProcessKilled); + VBoxServiceVerbose(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. */ + ASMAtomicXchgBool(&pProcess->fShutdown, true); + + /* + * Try killing the process if it's still alive at this point. + */ + if (fProcessAlive) + { + if (MsProcessKilled == UINT64_MAX) + { + VBoxServiceVerbose(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)) + VBoxServiceError("PID %RU32]: Killing process failed with rc=%Rrc\n", + pProcess->uPID, rc2); + RTThreadSleep(500); + } + + for (int i = 0; i < 10 && fProcessAlive; i++) + { + VBoxServiceVerbose(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)) + { + VBoxServiceVerbose(4, "[PID %RU32]: Kill attempt %d/10: Exited\n", + pProcess->uPID, i + 1); + fProcessAlive = false; + break; + } + if (i >= 5) + { + VBoxServiceVerbose(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) + VBoxServiceError("PID %RU32]: Killing process failed with rc=%Rrc\n", + pProcess->uPID, rc2); + } + RTThreadSleep(i >= 5 ? 2000 : 500); + } + + if (fProcessAlive) + VBoxServiceError("[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 = gstcntlProcessLock(pProcess); + if (RT_SUCCESS(rc2)) + { + VBoxServiceVerbose(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) + VBoxServiceError("[PID %RU32]: Processing outstanding requests failed with with rc=%Rrc\n", + pProcess->uPID, rc2); + + VBoxServiceVerbose(3, "[PID %RU32]: Processing outstanding requests done, rc=%Rrc\n", + pProcess->uPID, rc2); + + rc2 = gstcntlProcessUnlock(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 uFlags = 0; + + if ( fProcessTimedOut && !fProcessAlive && MsProcessKilled != UINT64_MAX) + { + VBoxServiceVerbose(3, "[PID %RU32]: Timed out and got killed\n", + pProcess->uPID); + uStatus = PROC_STS_TOK; + } + else if (fProcessTimedOut && fProcessAlive && MsProcessKilled != UINT64_MAX) + { + VBoxServiceVerbose(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)) + { + VBoxServiceVerbose(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. */ + uFlags = pProcess->StartupInfo.uFlags; /* Return handed-in execution flags back to the host. */ + } + else if (fProcessAlive) + { + VBoxServiceError("[PID %RU32]: Is alive when it should not!\n", + pProcess->uPID); + } + else if (MsProcessKilled != UINT64_MAX) + { + VBoxServiceError("[PID %RU32]: Has been killed when it should not!\n", + pProcess->uPID); + } + else if (ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL) + { + VBoxServiceVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_NORMAL (Exit code: %d)\n", + pProcess->uPID, ProcessStatus.iStatus); + + uStatus = PROC_STS_TEN; + uFlags = ProcessStatus.iStatus; + } + else if (ProcessStatus.enmReason == RTPROCEXITREASON_SIGNAL) + { + VBoxServiceVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_SIGNAL (Signal: %u)\n", + pProcess->uPID, ProcessStatus.iStatus); + + uStatus = PROC_STS_TES; + uFlags = ProcessStatus.iStatus; + } + else if (ProcessStatus.enmReason == RTPROCEXITREASON_ABEND) + { + /* ProcessStatus.iStatus will be undefined. */ + VBoxServiceVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_ABEND\n", + pProcess->uPID); + + uStatus = PROC_STS_TEA; + uFlags = ProcessStatus.iStatus; + } + else + VBoxServiceVerbose(1, "[PID %RU32]: Handling process status %u not implemented\n", + pProcess->uPID, ProcessStatus.enmReason); + + VBoxServiceVerbose(2, "[PID %RU32]: Ended, ClientID=%u, CID=%u, Status=%u, Flags=0x%x\n", + pProcess->uPID, pProcess->uClientID, pProcess->uContextID, uStatus, uFlags); + + VBGLR3GUESTCTRLCMDCTX ctxEnd = { pProcess->uClientID, pProcess->uContextID }; + rc2 = VbglR3GuestCtrlProcCbStatus(&ctxEnd, + pProcess->uPID, uStatus, uFlags, + NULL /* pvData */, 0 /* cbData */); + if ( RT_FAILURE(rc2) + && rc2 == VERR_NOT_FOUND) + VBoxServiceError("[PID %RU32]: Error reporting final status to host; rc=%Rrc\n", + pProcess->uPID, rc2); + } + + VBoxServiceVerbose(3, "[PID %RU32]: Process loop returned with rc=%Rrc\n", + pProcess->uPID, rc); + return rc; +} + + +/** + * 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 gstcntlProcessInitPipe(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; +} + + +/** + * 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 gstcntlProcessSetupPipe(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 gstcntlProcessMakeFullPath(const char *pszPath, char *pszExpanded, size_t cbExpanded) +{ + int rc = VINF_SUCCESS; +#ifdef RT_OS_WINDOWS + if (!ExpandEnvironmentStrings(pszPath, pszExpanded, cbExpanded)) + rc = RTErrConvertFromWin32(GetLastError()); +#else + /* No expansion for non-Windows yet. */ + rc = RTStrCopy(pszExpanded, cbExpanded, pszPath); +#endif +#ifdef DEBUG + VBoxServiceVerbose(3, "VBoxServiceControlExecMakeFullPath: %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 gstcntlProcessResolveExecutable(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 = gstcntlProcessMakeFullPath(szPathToResolve, pszResolved, cbResolved); + if (RT_SUCCESS(rc)) + VBoxServiceVerbose(3, "Looked up executable: %s -> %s\n", + pszFileName, pszResolved); + } + + if (RT_FAILURE(rc)) + VBoxServiceError("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 ppapszArgv Pointer to a pointer with the new argv command line. + * Needs to be freed with RTGetOptArgvFree. + */ +static int gstcntlProcessAllocateArgv(const char *pszArgv0, + const char * const *papszArgs, + bool fExpandArgs, char ***ppapszArgv) +{ + AssertPtrReturn(ppapszArgv, VERR_INVALID_POINTER); + + VBoxServiceVerbose(3, "GstCntlProcessPrepareArgv: pszArgv0=%p, papszArgs=%p, fExpandArgs=%RTbool, ppapszArgv=%p\n", + pszArgv0, papszArgs, fExpandArgs, 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 + VBoxServiceVerbose(3, "GstCntlProcessAllocateArgv: cbSize=%RU32, cArgs=%RU32\n", + cbSize, cArgs); +#endif + + size_t i = 0; /* Keep the argument counter in scope for cleaning up on failure. */ + + rc = RTStrDupEx(&papszNewArgv[0], pszArgv0); + if (RT_SUCCESS(rc)) + { + for (; i < cArgs; i++) + { + char *pszArg; +#if 0 /* Arguments expansion -- untested. */ + if (fExpandArgs) + { + /* 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; + } + } + + if (RT_FAILURE(rc)) + { + for (i; 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. + */ +int gstcntlProcessAssignPID(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. */ + PVBOXSERVICECTRLPROCESS pProcessCur; + bool fTryAgain; + do + { + fTryAgain = false; + RTListForEach(&pProcess->pSession->lstProcesses, pProcessCur, VBOXSERVICECTRLPROCESS, Node) + { + if (pProcessCur->uPID == uPID) + { + Assert(pProcessCur != pProcess); /* can't happen */ + uint32_t uTriedPID = uPID; + uPID += 391939; + VBoxServiceVerbose(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; +} + + +void gstcntlProcessFreeArgv(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 phProcess Pointer which will receive the process handle after + * successful process start. + */ +static int gstcntlProcessCreateProcess(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags, + PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser, + const char *pszPassword, PRTPROCESS phProcess) +{ + AssertPtrReturn(pszExec, VERR_INVALID_PARAMETER); + AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER); + AssertPtrReturn(phProcess, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + char szExecExp[RTPATH_MAX]; + + /* Do we need to expand environment variables in arguments? */ + bool fExpandArgs = (fFlags & EXECUTEPROCESSFLAG_EXPAND_ARGUMENTS) ? true : false; + +#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)) + VBoxServiceVerbose(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)) + VBoxServiceError("Failed to detect sysrep location, rc=%Rrc\n", rc); + } + else if (!fRet) + VBoxServiceError("Failed to retrieve OS information, last error=%ld\n", GetLastError()); + + VBoxServiceVerbose(3, "Sysprep executable is: %s\n", szSysprepCmd); + + if (RT_SUCCESS(rc)) + { + char **papszArgsExp; + rc = gstcntlProcessAllocateArgv(szSysprepCmd /* argv0 */, papszArgs, + fExpandArgs, &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); + gstcntlProcessFreeArgv(papszArgsExp); + } + } + + if (RT_FAILURE(rc)) + VBoxServiceVerbose(3, "Starting sysprep returned rc=%Rrc\n", rc); + + return rc; + } +#endif /* RT_OS_WINDOWS */ + +#ifdef 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 = gstcntlProcessResolveExecutable(VBOXSERVICE_NAME, szExecExp, sizeof(szExecExp)); + } + else + { +#endif + /* + * Do the environment variables expansion on executable and arguments. + */ + rc = gstcntlProcessResolveExecutable(pszExec, szExecExp, sizeof(szExecExp)); +#ifdef VBOXSERVICE_TOOLBOX + } +#endif + if (RT_SUCCESS(rc)) + { + char **papszArgsExp; + rc = gstcntlProcessAllocateArgv(pszExec /* Always use the unmodified executable name as argv0. */, + papszArgs /* Append the rest of the argument vector (if any). */, + fExpandArgs, &papszArgsExp); + if (RT_FAILURE(rc)) + { + /* Don't print any arguments -- may contain passwords or other sensible data! */ + VBoxServiceError("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_NO_PROFILE) + uProcFlags |= RTPROC_FLAGS_NO_PROFILE; + } + + /* 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 + VBoxServiceVerbose(3, "Command: %s\n", szExecExp); + for (size_t i = 0; papszArgsExp[i]; i++) + VBoxServiceVerbose(3, "\targv[%ld]: %s\n", i, papszArgsExp[i]); +#endif + VBoxServiceVerbose(3, "Starting process \"%s\" ...\n", szExecExp); + + /* Do normal execution. */ + rc = RTProcCreateEx(szExecExp, papszArgsExp, hEnv, uProcFlags, + phStdIn, phStdOut, phStdErr, + pszAsUser && *pszAsUser ? pszAsUser : NULL, + pszPassword && *pszPassword ? pszPassword : NULL, + phProcess); + + VBoxServiceVerbose(3, "Starting process \"%s\" returned rc=%Rrc\n", + szExecExp, rc); + + gstcntlProcessFreeArgv(papszArgsExp); + } + } + return rc; +} + + +#ifdef DEBUG +static int gstcntlProcessDumpToFile(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)) + { + VBoxServiceVerbose(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 + + +/** + * The actual worker routine (loop) for a started guest process. + * + * @return IPRT status code. + * @param PVBOXSERVICECTRLPROCESS Guest process. + */ +static int gstcntlProcessProcessWorker(PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + VBoxServiceVerbose(3, "Thread of process pThread=0x%p = \"%s\" started\n", + pProcess, pProcess->StartupInfo.szCmd); + + int rc = VbglR3GuestCtrlConnect(&pProcess->uClientID); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Process thread \"%s\" (%p) failed to connect to the guest control service, rc=%Rrc\n", + pProcess->StartupInfo.szCmd, pProcess, rc); + RTThreadUserSignal(RTThreadSelf()); + return rc; + } + + VBoxServiceVerbose(3, "Guest process \"%s\" got client ID=%u, flags=0x%x\n", + pProcess->StartupInfo.szCmd, pProcess->uClientID, pProcess->StartupInfo.uFlags); + + /* The process thread is not interested in receiving any commands; + * tell the host service. */ + rc = VbglR3GuestCtrlMsgFilterSet(pProcess->uClientID, 0 /* Skip all */, + 0 /* Filter mask to add */, 0 /* Filter mask to remove */); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Unable to set message filter, rc=%Rrc\n", rc); + /* Non-critical. */ + } + + rc = GstCntlSessionProcessAdd(pProcess->pSession, pProcess); + if (RT_FAILURE(rc)) + { + VBoxServiceError("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; + uint32_t uNumArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */ + rc = RTGetOptArgvFromString(&papszArgs, (int*)&uNumArgs, + (pProcess->StartupInfo.uNumArgs > 0) ? pProcess->StartupInfo.szArgs : "", NULL); + /* Did we get the same result? */ + Assert(pProcess->StartupInfo.uNumArgs == uNumArgs); + + /* + * Prepare environment variables list. + */ + 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. + */ + RTENV hEnv; + if (RT_SUCCESS(rc)) + 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 = gstcntlProcessSetupPipe("|", 0 /*STDIN_FILENO*/, + &hStdIn, &phStdIn, &pProcess->hPipeStdInW); + if (RT_SUCCESS(rc)) + { + RTHANDLE hStdOut; + PRTHANDLE phStdOut; + rc = gstcntlProcessSetupPipe( (pProcess->StartupInfo.uFlags & EXECUTEPROCESSFLAG_WAIT_STDOUT) + ? "|" : "/dev/null", + 1 /*STDOUT_FILENO*/, + &hStdOut, &phStdOut, &pProcess->hPipeStdOutR); + if (RT_SUCCESS(rc)) + { + RTHANDLE hStdErr; + PRTHANDLE phStdErr; + rc = gstcntlProcessSetupPipe( (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->uFlags & VBOXSERVICECTRLSESSION_FLAG_FORK); + + rc = gstcntlProcessCreateProcess(pProcess->StartupInfo.szCmd, papszArgs, hEnv, pProcess->StartupInfo.uFlags, + phStdIn, phStdOut, phStdErr, + fNeedsImpersonation ? pProcess->StartupInfo.szUser : NULL, + fNeedsImpersonation ? pProcess->StartupInfo.szPassword : NULL, + &pProcess->hProcess); + if (RT_FAILURE(rc)) + VBoxServiceError("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 = gstcntlProcessProcLoop(pProcess); + + /* + * The handles that are no longer in the set have + * been closed by the above call in order to prevent + * the guest from getting stuck accessing them. + * So, NIL the handles to avoid closing them again. + */ + if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet, + VBOXSERVICECTRLPIPEID_IPC_NOTIFY, NULL))) + { + pProcess->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 (pProcess->uClientID) + { + if (RT_FAILURE(rc)) + { + VBGLR3GUESTCTRLCMDCTX ctx = { pProcess->uClientID, pProcess->uContextID }; + int rc2 = VbglR3GuestCtrlProcCbStatus(&ctx, + pProcess->uPID, PROC_STS_ERROR, rc, + NULL /* pvData */, 0 /* cbData */); + if ( RT_FAILURE(rc2) + && rc2 != VERR_NOT_FOUND) + VBoxServiceError("[PID %RU32]: Could not report process failure error; rc=%Rrc (process error %Rrc)\n", + pProcess->uPID, rc2, rc); + } + + /* Disconnect this client from the guest control service. This also cancels all + * outstanding host requests. */ + VBoxServiceVerbose(3, "[PID %RU32]: Disconnecting (client ID=%u) ...\n", + pProcess->uPID, pProcess->uClientID); + VbglR3GuestCtrlDisconnect(pProcess->uClientID); + pProcess->uClientID = 0; + } + + /* Free argument + environment variable lists. */ + if (uNumEnvVars) + { + for (uint32_t i = 0; i < uNumEnvVars; i++) + RTStrFree(papszEnv[i]); + RTMemFree(papszEnv); + } + if (uNumArgs) + 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()); + + VBoxServiceVerbose(3, "[PID %RU32]: Thread of process \"%s\" ended with rc=%Rrc\n", + pProcess->uPID, pProcess->StartupInfo.szCmd, rc); + + /* Finally, update stopped status. */ + ASMAtomicXchgBool(&pProcess->fStopped, true); + + return rc; +} + + +static int gstcntlProcessLock(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 RTTHREAD Pointer to the thread's data. + * @param void* User-supplied argument pointer. + * + */ +static DECLCALLBACK(int) gstcntlProcessThread(RTTHREAD ThreadSelf, void *pvUser) +{ + PVBOXSERVICECTRLPROCESS pProcess = (VBOXSERVICECTRLPROCESS*)pvUser; + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + return gstcntlProcessProcessWorker(pProcess); +} + + +static int gstcntlProcessUnlock(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 GstCntlProcessStart(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 = gstcntlProcessInit(pProcess, pSession, pStartupInfo, uContextID); + if (RT_SUCCESS(rc)) + { + static uint32_t s_uCtrlExecThread = 0; + if (s_uCtrlExecThread++ == UINT32_MAX) + s_uCtrlExecThread = 0; /* Wrap around to not let IPRT freak out. */ + rc = RTThreadCreateF(&pProcess->Thread, gstcntlProcessThread, + pProcess /*pvUser*/, 0 /*cbStack*/, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctl%u", s_uCtrlExecThread); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Creating thread for guest process \"%s\" failed: rc=%Rrc, pProcess=%p\n", + pStartupInfo->szCmd, rc, pProcess); + + GstCntlProcessFree(pProcess); + } + else + { + VBoxServiceVerbose(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) + || RT_FAILURE(rc)) + { + VBoxServiceError("Thread for process \"%s\" failed to start, rc=%Rrc\n", + pStartupInfo->szCmd, rc); + + GstCntlProcessFree(pProcess); + } + else + { + ASMAtomicXchgBool(&pProcess->fStarted, true); + } + } + } + + return rc; +} + + +static DECLCALLBACK(int) gstcntlProcessOnInput(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 = gstcntlProcessPollsetCloseInput(pThis, &pThis->hPipeStdInW); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status to send back to the host. */ + uint32_t uFlags = 0; /* No flags at the moment. */ + if (RT_SUCCESS(rc)) + { + VBoxServiceVerbose(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; + uFlags = rc; + } + Assert(uStatus > INPUT_STS_UNDEFINED); + +#ifdef DEBUG + +#endif + int rc2 = VbglR3GuestCtrlProcCbStatusInput(pHostCtx, pThis->uPID, + uStatus, uFlags, (uint32_t)cbWritten); + if (RT_SUCCESS(rc)) + rc = rc2; + +#ifdef DEBUG + VBoxServiceVerbose(3, "[PID %RU32]: gstcntlProcessOnInput 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) gstcntlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis, + const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags) +{ + 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->uFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT + && ( uHandle == OUTPUT_HANDLE_ID_STDOUT + || uHandle == OUTPUT_HANDLE_ID_STDOUT_DEPRECATED) + ) + { + 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 = gstcntlProcessDumpToFile(szDumpFile, pvBuf, cbRead); + AssertRC(rc); + } + else if ( pSession->uFlags & 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 = gstcntlProcessDumpToFile(szDumpFile, pvBuf, cbRead); + AssertRC(rc); + } + } +#endif + + if (RT_SUCCESS(rc)) + { +#ifdef DEBUG + VBoxServiceVerbose(3, "[PID %RU32]: Read %RU32 bytes output: uHandle=%RU32, CID=%RU32, uFlags=%x\n", + pThis->uPID, cbRead, uHandle, pHostCtx->uContextID, uFlags); +#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! */ + rc = VbglR3GuestCtrlProcCbOutput(pHostCtx, pThis->uPID, uHandle, uFlags, + pvBuf, 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 + VBoxServiceVerbose(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) gstcntlProcessOnTerm(PVBOXSERVICECTRLPROCESS pThis) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + if (!ASMAtomicXchgBool(&pThis->fShutdown, true)) + { + VBoxServiceVerbose(3, "[PID %RU32]: Setting shutdown flag ...\n", + pThis->uPID); + } + + return VINF_SUCCESS; /** @todo Return rc here as soon as RTReqQueue todos are fixed. */ +} + + +int gstcntlProcessRequestExV(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, + bool fAsync, RTMSINTERVAL uTimeoutMS, PRTREQ pReq, PFNRT pfnFunction, + unsigned cArgs, va_list Args) +{ + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + /* pHostCtx is optional. */ + AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER); + if (!fAsync) + AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER); + + int rc = gstcntlProcessLock(pProcess); + if (RT_SUCCESS(rc)) + { +#ifdef DEBUG + VBoxServiceVerbose(3, "[PID %RU32]: gstcntlProcessRequestExV fAsync=%RTbool, uTimeoutMS=%RU32, cArgs=%u\n", + pProcess->uPID, fAsync, uTimeoutMS, cArgs); +#endif + uint32_t uFlags = RTREQFLAGS_IPRT_STATUS; + if (fAsync) + { + Assert(uTimeoutMS == 0); + uFlags |= RTREQFLAGS_NO_WAIT; + } + + rc = RTReqQueueCallV(pProcess->hReqQueue, &pReq, uTimeoutMS, uFlags, + pfnFunction, cArgs, Args); + if (RT_SUCCESS(rc)) + { + /* Wake up the process' notification pipe to get + * the request being processed. */ + Assert(pProcess->hNotificationPipeW != NIL_RTPIPE); + size_t cbWritten = 0; + rc = RTPipeWrite(pProcess->hNotificationPipeW, "i", 1, &cbWritten); + if ( RT_SUCCESS(rc) + && cbWritten != 1) + { + VBoxServiceError("[PID %RU32]: Notification pipe got %zu bytes instead of 1\n", + pProcess->uPID, cbWritten); + } + else if (RT_UNLIKELY(RT_FAILURE(rc))) + VBoxServiceError("[PID %RU32]: Writing to notification pipe failed, rc=%Rrc\n", + pProcess->uPID, rc); + } + else + VBoxServiceError("[PID %RU32]: RTReqQueueCallV failed, rc=%Rrc\n", + pProcess->uPID, rc); + + int rc2 = gstcntlProcessUnlock(pProcess); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(3, "[PID %RU32]: gstcntlProcessRequestExV returned rc=%Rrc\n", + pProcess->uPID, rc); +#endif + return rc; +} + + +int gstcntlProcessRequestAsync(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 = gstcntlProcessRequestExV(pProcess, pHostCtx, true /* fAsync */, 0 /* uTimeoutMS */, + NULL /* pReq */, pfnFunction, cArgs, va); + va_end(va); + + return rc; +} + + +int gstcntlProcessRequestWait(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 = gstcntlProcessRequestExV(pProcess, pHostCtx, false /* fAsync */, uTimeoutMS, + pReq, pfnFunction, cArgs, va); + va_end(va); + + return rc; +} + + +int GstCntlProcessHandleInput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + bool fPendingClose, void *pvBuf, uint32_t cbBuf) +{ + if (!ASMAtomicReadBool(&pProcess->fShutdown)) + return gstcntlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)gstcntlProcessOnInput, + 5 /* cArgs */, pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf); + + return gstcntlProcessOnInput(pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf); +} + + +int GstCntlProcessHandleOutput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags) +{ + if (!ASMAtomicReadBool(&pProcess->fShutdown)) + return gstcntlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)gstcntlProcessOnOutput, + 5 /* cArgs */, pProcess, pHostCtx, uHandle, cbToRead, uFlags); + + return gstcntlProcessOnOutput(pProcess, pHostCtx, uHandle, cbToRead, uFlags); +} + + +int GstCntlProcessHandleTerm(PVBOXSERVICECTRLPROCESS pProcess) +{ + if (!ASMAtomicReadBool(&pProcess->fShutdown)) + return gstcntlProcessRequestAsync(pProcess, NULL /* pHostCtx */, (PFNRT)gstcntlProcessOnTerm, + 1 /* cArgs */, pProcess); + + return gstcntlProcessOnTerm(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..d96b13ad --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp @@ -0,0 +1,2364 @@ +/* $Id: VBoxServiceControlSession.cpp $ */ +/** @file + * VBoxServiceControlSession - Guest session handling. Also handles + * the forked session processes. + */ + +/* + * Copyright (C) 2013 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 "VBoxServiceInternal.h" +#include "VBoxServiceUtils.h" +#include "VBoxServiceControl.h" + +using namespace guestControl; + +/******************************************************************************* +* Externals * +*******************************************************************************/ +extern RTLISTANCHOR g_lstControlSessionThreads; +extern VBOXSERVICECTRLSESSION g_Session; + +extern int VBoxServiceLogCreate(const char *pszLogFile); +extern void VBoxServiceLogDestroy(void); + +/******************************************************************************* +* Internal Functions * +*******************************************************************************/ +static int gstcntlSessionFileDestroy(PVBOXSERVICECTRLFILE pFile); +static int gstcntlSessionFileAdd(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLFILE pFile); +static PVBOXSERVICECTRLFILE gstcntlSessionFileGetLocked(const PVBOXSERVICECTRLSESSION pSession, uint32_t uHandle); +static DECLCALLBACK(int) gstcntlSessionThread(RTTHREAD ThreadSelf, void *pvUser); +/* Host -> Guest handlers. */ +static int gstcntlSessionHandleDirRemove(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleFileOpen(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleFileClose(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleFileRead(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleFileWrite(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, void *pvScratchBuf, size_t cbScratchBuf); +static int gstcntlSessionHandleFileSeek(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleFileTell(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandlePathRename(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleProcExec(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleProcInput(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx, void *pvScratchBuf, size_t cbScratchBuf); +static int gstcntlSessionHandleProcOutput(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleProcTerminate(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); +static int gstcntlSessionHandleProcWaitFor(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx); + + +/** Generic option indices for session fork arguments. */ +enum +{ + VBOXSERVICESESSIONOPT_FIRST = 1000, /* For initialization. */ +#ifdef DEBUG + VBOXSERVICESESSIONOPT_DUMP_STDOUT, + VBOXSERVICESESSIONOPT_DUMP_STDERR, +#endif + VBOXSERVICESESSIONOPT_LOG_FILE, + VBOXSERVICESESSIONOPT_USERNAME, + VBOXSERVICESESSIONOPT_SESSION_ID, + VBOXSERVICESESSIONOPT_SESSION_PROTO, + VBOXSERVICESESSIONOPT_THREAD_ID +}; + + +static int gstcntlSessionFileDestroy(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 gstcntlSessionFileGetLocked(const PVBOXSERVICECTRLSESSION pSession, + uint32_t uHandle) +{ + AssertPtrReturn(pSession, NULL); + + PVBOXSERVICECTRLFILE pFileCur = NULL; + /** @todo Use a map later! */ + RTListForEach(&pSession->lstFiles, pFileCur, VBOXSERVICECTRLFILE, Node) + { + if (pFileCur->uHandle == uHandle) + return pFileCur; + } + + return NULL; +} + + +static int gstcntlSessionHandleDirRemove(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + char szDir[RTPATH_MAX]; + uint32_t uFlags = 0; + + int rc = VbglR3GuestCtrlDirGetRemove(pHostCtx, + /* Directory to remove. */ + szDir, sizeof(szDir), + /* Flags of type DIRREMOVE_FLAG_. */ + &uFlags); + if (RT_SUCCESS(rc)) + { + uint32_t uFlagsRemRec = 0; + bool fRecursive = false; + + if (!(uFlags & ~DIRREMOVE_FLAG_VALID_MASK)) + { + if (uFlags & DIRREMOVE_FLAG_RECURSIVE) + { + /* Note: DIRREMOVE_FLAG_RECURSIVE must be set explicitly. + * Play safe here. */ + fRecursive = true; + } + + if (uFlags & DIRREMOVE_FLAG_CONTENT_AND_DIR) + { + /* Setting direct value is intentional. */ + uFlagsRemRec = RTDIRRMREC_F_CONTENT_AND_DIR; + } + + if (uFlags & DIRREMOVE_FLAG_CONTENT_ONLY) + { + /* Setting direct value is intentional. */ + uFlagsRemRec |= RTDIRRMREC_F_CONTENT_ONLY; + } + } + else + rc = VERR_NOT_SUPPORTED; + + VBoxServiceVerbose(4, "[Dir %s]: Removing with uFlags=0x%x, fRecursive=%RTbool\n", + szDir, uFlags, fRecursive); + + if (RT_SUCCESS(rc)) + { + /** @todo Add own recursive function (or a new IPRT function w/ callback?) to + * provide guest-to-host progress reporting. */ + if (fRecursive) + rc = RTDirRemoveRecursive(szDir, uFlagsRemRec); + else + rc = RTDirRemove(szDir); + } + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc); + if (RT_FAILURE(rc2)) + VBoxServiceError("[Dir %s]: Failed to report removing status, rc=%Rrc\n", + szDir, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Removing directory \"%s\" returned rc=%Rrc\n", + szDir, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileOpen(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + char szFile[RTPATH_MAX]; + char szAccess[64]; + char szDisposition[64]; + char szSharing[64]; + uint32_t uCreationMode = 0; + uint64_t uOffset = 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. */ + &uOffset); + VBoxServiceVerbose(4, "[File %s]: szAccess=%s, szDisposition=%s, szSharing=%s, uOffset=%RU64, rc=%Rrc\n", + szFile, szAccess, szDisposition, szSharing, uOffset, rc); + + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLFILE pFile = (PVBOXSERVICECTRLFILE)RTMemAllocZ(sizeof(VBOXSERVICECTRLFILE)); + if (pFile) + { + if (!strlen(szFile)) + rc = VERR_INVALID_PARAMETER; + + if ( RT_SUCCESS(rc) + && !RTStrPrintf(pFile->szName, sizeof(pFile->szName), "%s", szFile)) + rc = VERR_NO_MEMORY; + + if (RT_SUCCESS(rc)) + { + uint64_t fFlags; + rc = RTFileModeToFlagsEx(szAccess, szDisposition, + NULL /* pszSharing, not used yet */, &fFlags); + VBoxServiceVerbose(4, "[File %s]: Opening flags=0x%x, rc=%Rrc\n", + pFile->szName, fFlags, rc); + if (RT_SUCCESS(rc)) + rc = RTFileOpen(&pFile->hFile, pFile->szName, fFlags); + if ( RT_SUCCESS(rc) + && uOffset) + { + /* Seeking is optional. However, the whole operation + * will fail if we don't succeed seeking to the wanted position. */ + rc = RTFileSeek(pFile->hFile, (int64_t)uOffset, RTFILE_SEEK_BEGIN, NULL /* Current offset */); + if (RT_FAILURE(rc)) + VBoxServiceError("[File %s]: Seeking to offset %RU64 failed; rc=%Rrc\n", + pFile->szName, uOffset, rc); + } + else if (RT_FAILURE(rc)) + VBoxServiceError("[File %s]: Opening failed; rc=%Rrc\n", + pFile->szName, rc); + } + + if (RT_SUCCESS(rc)) + { + uHandle = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pHostCtx->uContextID); + pFile->uHandle = uHandle; + + /* rc = */ RTListAppend(&pSession->lstFiles, &pFile->Node); + + VBoxServiceVerbose(3, "[File %s]: Opened (ID=%RU32)\n", + pFile->szName, pFile->uHandle); + } + + if (RT_FAILURE(rc)) + { + if (pFile->hFile) + RTFileClose(pFile->hFile); + RTMemFree(pFile); + } + } + else + rc = VERR_NO_MEMORY; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbOpen(pHostCtx, rc, uHandle); + if (RT_FAILURE(rc2)) + VBoxServiceError("[File %s]: Failed to report file open status, rc=%Rrc\n", + szFile, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Opening file \"%s\" (open mode=\"%s\", disposition=\"%s\", creation mode=0x%x returned rc=%Rrc\n", + szFile, szAccess, szDisposition, uCreationMode, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileClose(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + int rc = VbglR3GuestCtrlFileGetClose(pHostCtx, &uHandle /* File handle to close */); + if (RT_SUCCESS(rc)) + { + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = gstcntlSessionFileDestroy(pFile); + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbClose(pHostCtx, rc); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file close status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Closing file \"%s\" (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileRead(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint32_t cbToRead; + int rc = VbglR3GuestCtrlFileGetRead(pHostCtx, &uHandle, &cbToRead); + if (RT_SUCCESS(rc)) + { + void *pvDataRead = pvScratchBuf; + size_t cbRead = 0; + + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + if (cbToRead) + { + if (cbToRead > cbScratchBuf) + { + pvDataRead = RTMemAlloc(cbToRead); + if (!pvDataRead) + rc = VERR_NO_MEMORY; + } + + if (RT_LIKELY(RT_SUCCESS(rc))) + rc = RTFileRead(pFile->hFile, pvDataRead, cbToRead, &cbRead); + } + else + rc = VERR_BUFFER_UNDERFLOW; + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, pvDataRead, (uint32_t)cbRead); + if ( cbToRead > cbScratchBuf + && pvDataRead) + RTMemFree(pvDataRead); + + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file read status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Reading file \"%s\" (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileReadAt(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint32_t cbToRead; int64_t iOffset; + + int rc = VbglR3GuestCtrlFileGetReadAt(pHostCtx, + &uHandle, &cbToRead, (uint64_t *)&iOffset); + if (RT_SUCCESS(rc)) + { + void *pvDataRead = pvScratchBuf; + size_t cbRead = 0; + + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + if (cbToRead) + { + if (cbToRead > cbScratchBuf) + { + pvDataRead = RTMemAlloc(cbToRead); + if (!pvDataRead) + rc = VERR_NO_MEMORY; + } + + if (RT_LIKELY(RT_SUCCESS(rc))) + rc = RTFileReadAt(pFile->hFile, iOffset, pvDataRead, cbToRead, &cbRead); + } + else + rc = VERR_BUFFER_UNDERFLOW; + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, pvDataRead, (uint32_t)cbRead); + if ( cbToRead > cbScratchBuf + && pvDataRead) + RTMemFree(pvDataRead); + + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file read status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Reading file \"%s\" at offset (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileWrite(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER); + AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint32_t cbToWrite; + + int rc = VbglR3GuestCtrlFileGetWrite(pHostCtx, &uHandle, + pvScratchBuf, cbScratchBuf, + &cbToWrite); + if (RT_SUCCESS(rc)) + { + size_t cbWritten = 0; + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileWrite(pFile->hFile, pvScratchBuf, cbToWrite, &cbWritten); +#ifdef DEBUG + VBoxServiceVerbose(4, "[File %s]: Writing pvScratchBuf=%p, cbToWrite=%RU32, cbWritten=%zu, rc=%Rrc\n", + pFile->szName, pvScratchBuf, cbToWrite, cbWritten, rc); +#endif + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file write status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Writing file \"%s\" (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileWriteAt(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER); + AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint32_t cbToWrite; int64_t iOffset; + + int rc = VbglR3GuestCtrlFileGetWriteAt(pHostCtx, &uHandle, + pvScratchBuf, cbScratchBuf, + &cbToWrite, (uint64_t *)&iOffset); + if (RT_SUCCESS(rc)) + { + size_t cbWritten = 0; + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + rc = RTFileWriteAt(pFile->hFile, iOffset, + pvScratchBuf, cbToWrite, &cbWritten); +#ifdef DEBUG + VBoxServiceVerbose(4, "[File %s]: Writing iOffset=%RI64, pvScratchBuf=%p, cbToWrite=%RU32, cbWritten=%zu, rc=%Rrc\n", + pFile->szName, iOffset, pvScratchBuf, cbToWrite, cbWritten, rc); +#endif + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file write status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Writing file \"%s\" at offset (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileSeek(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint32_t uSeekMethod; + uint64_t uOffset; /* Will be converted to int64_t. */ + + uint64_t uOffsetActual = 0; + + int rc = VbglR3GuestCtrlFileGetSeek(pHostCtx, &uHandle, + &uSeekMethod, &uOffset); + if (RT_SUCCESS(rc)) + { + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + unsigned uSeekMethodIPRT; + switch (uSeekMethod) + { + case GUEST_FILE_SEEKTYPE_BEGIN: + uSeekMethodIPRT = RTFILE_SEEK_BEGIN; + break; + + case GUEST_FILE_SEEKTYPE_CURRENT: + uSeekMethodIPRT = RTFILE_SEEK_CURRENT; + break; + + case GUEST_FILE_SEEKTYPE_END: + uSeekMethodIPRT = RTFILE_SEEK_END; + break; + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + if (RT_SUCCESS(rc)) + { + rc = RTFileSeek(pFile->hFile, (int64_t)uOffset, + uSeekMethodIPRT, &uOffsetActual); +#ifdef DEBUG + VBoxServiceVerbose(4, "[File %s]: Seeking to iOffset=%RI64, uSeekMethodIPRT=%RU16, rc=%Rrc\n", + pFile->szName, (int64_t)uOffset, uSeekMethodIPRT, rc); +#endif + } + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbSeek(pHostCtx, rc, uOffsetActual); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file seek status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Seeking file \"%s\" (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandleFileTell(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + PVBOXSERVICECTRLFILE pFile = NULL; + + uint32_t uHandle = 0; + uint64_t uOffsetActual = 0; + + int rc = VbglR3GuestCtrlFileGetTell(pHostCtx, &uHandle); + if (RT_SUCCESS(rc)) + { + pFile = gstcntlSessionFileGetLocked(pSession, uHandle); + if (pFile) + { + uOffsetActual = RTFileTell(pFile->hFile); +#ifdef DEBUG + VBoxServiceVerbose(4, "[File %s]: Telling uOffsetActual=%RU64\n", + pFile->szName, uOffsetActual); +#endif + } + else + rc = VERR_NOT_FOUND; + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlFileCbTell(pHostCtx, rc, uOffsetActual); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report file tell status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Telling file \"%s\" (handle=%RU32) returned rc=%Rrc\n", + pFile ? pFile->szName : "<Not found>", uHandle, rc); +#endif + return rc; +} + + +static int gstcntlSessionHandlePathRename(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + char szSource[RTPATH_MAX]; + char szDest[RTPATH_MAX]; + uint32_t uFlags = 0; + + int rc = VbglR3GuestCtrlPathGetRename(pHostCtx, + szSource, sizeof(szSource), + szDest, sizeof(szDest), + /* Flags of type PATHRENAME_FLAG_. */ + &uFlags); + if (RT_SUCCESS(rc)) + { + if (uFlags & ~PATHRENAME_FLAG_VALID_MASK) + rc = VERR_NOT_SUPPORTED; + + VBoxServiceVerbose(4, "Renaming \"%s\" to \"%s\", uFlags=0x%x, rc=%Rrc\n", + szSource, szDest, uFlags, rc); + + if (RT_SUCCESS(rc)) + { + if (uFlags & PATHRENAME_FLAG_NO_REPLACE) + uFlags |= RTPATHRENAME_FLAGS_NO_REPLACE; + + if (uFlags & PATHRENAME_FLAG_REPLACE) + uFlags |= RTPATHRENAME_FLAGS_REPLACE; + + if (uFlags & PATHRENAME_FLAG_NO_SYMLINKS) + uFlags |= RTPATHRENAME_FLAGS_NO_SYMLINKS; + + rc = RTPathRename(szSource, szDest, uFlags); + } + + /* Report back in any case. */ + int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to report renaming status, rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Renaming \"%s\" to \"%s\" returned rc=%Rrc\n", + szSource, szDest, rc); +#endif + return rc; +} + + +/** + * Handles starting a guest processes. + * + * @returns IPRT status code. + * @param pSession Guest session. + * @param pHostCtx Host context. + */ +int gstcntlSessionHandleProcExec(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + bool fStartAllowed = false; /* Flag indicating whether starting a process is allowed or not. */ + + switch (pHostCtx->uProtocol) + { + case 1: /* Guest Additions < 4.3. */ + if (pHostCtx->uNumParms != 11) + rc = VERR_NOT_SUPPORTED; + break; + + case 2: /* Guest Additions >= 4.3. */ + if (pHostCtx->uNumParms != 12) + rc = VERR_NOT_SUPPORTED; + break; + + default: + rc = VERR_NOT_SUPPORTED; + break; + } + + if (RT_SUCCESS(rc)) + { + VBOXSERVICECTRLPROCSTARTUPINFO startupInfo; + RT_ZERO(startupInfo); + + /* 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. */ + startupInfo.cbEnv = sizeof(startupInfo.szEnv); + + 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 protocl 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)) + { + VBoxServiceVerbose(3, "Request to start process szCmd=%s, uFlags=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); + + rc = GstCntlSessionProcessStartAllowed(pSession, &fStartAllowed); + if (RT_SUCCESS(rc)) + { + if (fStartAllowed) + { + rc = GstCntlProcessStart(pSession, &startupInfo, pHostCtx->uContextID); + } + else + rc = VERR_MAX_PROCS_REACHED; /* Maximum number of processes reached. */ + } + } + } + + /* In case of an error we need to notify the host to not wait forever for our response. */ + if (RT_FAILURE(rc)) + { + VBoxServiceError("Starting process failed with rc=%Rrc, protocol=%RU32, parameters=%RU32\n", + rc, pHostCtx->uProtocol, pHostCtx->uNumParms); + + /* Don't report back if we didn't supply sufficient buffer for getting + * the actual command -- we don't have the matching context ID. */ + if (rc != VERR_TOO_MUCH_DATA) + { + /* + * Note: The context ID can be 0 because we mabye weren't able to fetch the command + * from the host. The host in case has to deal with that! + */ + int rc2 = VbglR3GuestCtrlProcCbStatus(pHostCtx, 0 /* PID, invalid */, + PROC_STS_ERROR, rc, + NULL /* pvData */, 0 /* cbData */); + if (RT_FAILURE(rc2)) + VBoxServiceError("Error sending start process status to host, rc=%Rrc\n", rc2); + } + } + + return rc; +} + + +/** + * Sends stdin input to a specific guest process. + * + * @returns IPRT status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + * @param pvScratchBuf The scratch buffer. + * @param cbScratchBuf The scratch buffer size for retrieving the input data. + */ +int gstcntlSessionHandleProcInput(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + AssertPtrReturn(cbScratchBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER); + + uint32_t uPID; + uint32_t uFlags; + uint32_t cbSize; + + uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status sent back to the host. */ + uint32_t cbWritten = 0; /* Number of bytes written to the guest. */ + + /* + * Ask the host for the input data. + */ + int rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &uFlags, + pvScratchBuf, cbScratchBuf, &cbSize); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Failed to retrieve process input command for PID=%RU32, rc=%Rrc\n", + uPID, rc); + } + else if (cbSize > cbScratchBuf) + { + VBoxServiceError("Too much process input received, rejecting: uPID=%RU32, cbSize=%RU32, cbScratchBuf=%RU32\n", + uPID, cbSize, cbScratchBuf); + rc = VERR_TOO_MUCH_DATA; + } + else + { + /* + * Is this the last input block we need to deliver? Then let the pipe know ... + */ + bool fPendingClose = false; + if (uFlags & INPUT_FLAG_EOF) + { + fPendingClose = true; +#ifdef DEBUG + VBoxServiceVerbose(4, "Got last process input block for PID=%RU32 (%RU32 bytes) ...\n", + uPID, cbSize); +#endif + } + + PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = GstCntlProcessHandleInput(pProcess, pHostCtx, fPendingClose, + pvScratchBuf, cbSize); + if (RT_FAILURE(rc)) + VBoxServiceError("Error handling input command for PID=%RU32, rc=%Rrc\n", + uPID, rc); + GstCntlProcessRelease(pProcess); + } + else + rc = VERR_NOT_FOUND; + } + +#ifdef DEBUG + VBoxServiceVerbose(4, "Setting input for PID=%RU32 resulted in rc=%Rrc\n", + uPID, rc); +#endif + return rc; +} + + +/** + * Gets stdout/stderr output of a specific guest process. + * + * @return IPRT status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + */ +int gstcntlSessionHandleProcOutput(PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + uint32_t uPID; + uint32_t uHandleID; + uint32_t uFlags; + + int rc = VbglR3GuestCtrlProcGetOutput(pHostCtx, &uPID, &uHandleID, &uFlags); +#ifdef DEBUG_andy + VBoxServiceVerbose(4, "Getting output for PID=%RU32, CID=%RU32, uHandleID=%RU32, uFlags=%RU32\n", + uPID, pHostCtx->uContextID, uHandleID, uFlags); +#endif + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = GstCntlProcessHandleOutput(pProcess, pHostCtx, + uHandleID, _64K /* cbToRead */, uFlags); + if (RT_FAILURE(rc)) + VBoxServiceError("Error getting output for PID=%RU32, rc=%Rrc\n", + uPID, rc); + GstCntlProcessRelease(pProcess); + } + else + rc = VERR_NOT_FOUND; + } + +#ifdef DEBUG_andy + VBoxServiceVerbose(4, "Getting output for PID=%RU32 resulted in rc=%Rrc\n", + uPID, rc); +#endif + return rc; +} + + +/** + * Tells a guest process to terminate. + * + * @return IPRT status code. + * @param pSession The session which is in charge. + * @param pHostCtx The host context to use. + */ +int gstcntlSessionHandleProcTerminate(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + uint32_t uPID; + int rc = VbglR3GuestCtrlProcGetTerminate(pHostCtx, &uPID); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = GstCntlProcessHandleTerm(pProcess); + + GstCntlProcessRelease(pProcess); + } + else + rc = VERR_NOT_FOUND; + } + +#ifdef DEBUG_andy + VBoxServiceVerbose(4, "Terminating PID=%RU32 resulted in rc=%Rrc\n", + uPID, rc); +#endif + return rc; +} + + +int gstcntlSessionHandleProcWaitFor(const PVBOXSERVICECTRLSESSION pSession, + PVBGLR3GUESTCTRLCMDCTX pHostCtx) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + + uint32_t uPID; + uint32_t uWaitFlags; uint32_t uTimeoutMS; + + int rc = VbglR3GuestCtrlProcGetWaitFor(pHostCtx, &uPID, &uWaitFlags, &uTimeoutMS); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICECTRLPROCESS pProcess = GstCntlSessionRetainProcess(pSession, uPID); + if (pProcess) + { + rc = VERR_NOT_IMPLEMENTED; /** @todo */ + GstCntlProcessRelease(pProcess); + } + else + rc = VERR_NOT_FOUND; + } + + return rc; +} + + +int GstCntlSessionHandler(PVBOXSERVICECTRLSESSION pSession, + uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx, + void *pvScratchBuf, size_t cbScratchBuf, + volatile bool *pfShutdown) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvScratchBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pfShutdown, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + /** + * Only anonymous sessions (that is, sessions which run with local + * service privileges) or forked session processes can do certain + * operations. + */ + bool fImpersonated = ( pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_FORK + || pSession->uFlags & VBOXSERVICECTRLSESSION_FLAG_ANONYMOUS); + + switch (uMsg) + { + case HOST_SESSION_CLOSE: + /* Shutdown (this fork). */ + rc = GstCntlSessionClose(pSession); + *pfShutdown = true; /* Shutdown in any case. */ + break; + + case HOST_DIR_REMOVE: + rc = fImpersonated + ? gstcntlSessionHandleDirRemove(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + case HOST_EXEC_CMD: + rc = gstcntlSessionHandleProcExec(pSession, pHostCtx); + break; + + case HOST_EXEC_SET_INPUT: + rc = gstcntlSessionHandleProcInput(pSession, pHostCtx, + pvScratchBuf, cbScratchBuf); + break; + + case HOST_EXEC_GET_OUTPUT: + rc = gstcntlSessionHandleProcOutput(pSession, pHostCtx); + break; + + case HOST_EXEC_TERMINATE: + rc = gstcntlSessionHandleProcTerminate(pSession, pHostCtx); + break; + + case HOST_EXEC_WAIT_FOR: + rc = gstcntlSessionHandleProcWaitFor(pSession, pHostCtx); + break; + + case HOST_FILE_OPEN: + rc = fImpersonated + ? gstcntlSessionHandleFileOpen(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_CLOSE: + rc = fImpersonated + ? gstcntlSessionHandleFileClose(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_READ: + rc = fImpersonated + ? gstcntlSessionHandleFileRead(pSession, pHostCtx, + pvScratchBuf, cbScratchBuf) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_READ_AT: + rc = fImpersonated + ? gstcntlSessionHandleFileReadAt(pSession, pHostCtx, + pvScratchBuf, cbScratchBuf) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_WRITE: + rc = fImpersonated + ? gstcntlSessionHandleFileWrite(pSession, pHostCtx, + pvScratchBuf, cbScratchBuf) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_WRITE_AT: + rc = fImpersonated + ? gstcntlSessionHandleFileWriteAt(pSession, pHostCtx, + pvScratchBuf, cbScratchBuf) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_SEEK: + rc = fImpersonated + ? gstcntlSessionHandleFileSeek(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + case HOST_FILE_TELL: + rc = fImpersonated + ? gstcntlSessionHandleFileTell(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + case HOST_PATH_RENAME: + rc = fImpersonated + ? gstcntlSessionHandlePathRename(pSession, pHostCtx) + : VERR_NOT_SUPPORTED; + break; + + default: + rc = VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID); + VBoxServiceVerbose(3, "Unsupported message (uMsg=%RU32, cParms=%RU32) from host, skipping\n", + uMsg, pHostCtx->uNumParms); + break; + } + + if (RT_FAILURE(rc)) + VBoxServiceError("Error while handling message (uMsg=%RU32, cParms=%RU32), rc=%Rrc\n", + uMsg, pHostCtx->uNumParms, rc); + + return rc; +} + + +/** + * Thread main routine for a forked guest session process. + * This thread runs in the main executable to control the forked + * session process. + * + * @return IPRT status code. + * @param RTTHREAD Pointer to the thread's data. + * @param void* User-supplied argument pointer. + * + */ +static DECLCALLBACK(int) gstcntlSessionThread(RTTHREAD ThreadSelf, void *pvUser) +{ + PVBOXSERVICECTRLSESSIONTHREAD pThread = (PVBOXSERVICECTRLSESSIONTHREAD)pvUser; + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + + uint32_t uSessionID = pThread->StartupInfo.uSessionID; + + uint32_t uClientID; + int rc = VbglR3GuestCtrlConnect(&uClientID); + if (RT_SUCCESS(rc)) + { + VBoxServiceVerbose(3, "Session ID=%RU32 thread running, client ID=%RU32\n", + uSessionID, uClientID); + + /* The session thread is not interested in receiving any commands; + * tell the host service. */ + rc = VbglR3GuestCtrlMsgFilterSet(uClientID, 0 /* Skip all */, + 0 /* Filter mask to add */, 0 /* Filter mask to remove */); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Unable to set message filter, rc=%Rrc\n", rc); + /* Non-critical. */ + rc = VINF_SUCCESS; + } + } + else + VBoxServiceError("Error connecting to guest control service, rc=%Rrc\n", rc); + + if (RT_FAILURE(rc)) + pThread->fShutdown = true; + + /* Let caller know that we're done initializing, regardless of the result. */ + int rc2 = RTThreadUserSignal(RTThreadSelf()); + AssertRC(rc2); + + if (RT_FAILURE(rc)) + return rc; + + bool fProcessAlive = true; + RTPROCSTATUS ProcessStatus; + RT_ZERO(ProcessStatus); + + int rcWait; + if (RT_SUCCESS(rc)) + { + uint32_t uTimeoutsMS = 30 * 1000; /** @todo Make this configurable. Later. */ + uint64_t u64TimeoutStart = 0; + + for (;;) + { + rcWait = RTProcWaitNoResume(pThread->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, + &ProcessStatus); + if (RT_UNLIKELY(rcWait == VERR_INTERRUPTED)) + continue; + else if ( rcWait == VINF_SUCCESS + || rcWait == VERR_PROCESS_NOT_FOUND) + { + fProcessAlive = false; + break; + } + else + AssertMsgBreak(rcWait == VERR_PROCESS_RUNNING, + ("Got unexpected rc=%Rrc while waiting for session process termination\n", rcWait)); + + if (ASMAtomicReadBool(&pThread->fShutdown)) + { + if (!u64TimeoutStart) + { + VBoxServiceVerbose(3, "Notifying guest session process (PID=%RU32, session ID=%RU32) ...\n", + pThread->hProcess, uSessionID); + + VBGLR3GUESTCTRLCMDCTX hostCtx = { uClientID, + VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(uSessionID), + pThread->StartupInfo.uProtocol, 2 /* uNumParms */ }; + rc = VbglR3GuestCtrlSessionClose(&hostCtx, 0 /* uFlags */); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Unable to notify guest session process (PID=%RU32, session ID=%RU32), rc=%Rrc\n", + pThread->hProcess, uSessionID, rc); + + if (rc == VERR_NOT_SUPPORTED) + { + /* Terminate guest session process in case it's not supported by a too old host. */ + rc = RTProcTerminate(pThread->hProcess); + VBoxServiceVerbose(3, "Terminating guest session process (PID=%RU32) ended with rc=%Rrc\n", + pThread->hProcess, rc); + } + break; + } + + VBoxServiceVerbose(3, "Guest session ID=%RU32 thread was asked to terminate, waiting for session process to exit (%RU32ms timeout) ...\n", + uSessionID, uTimeoutsMS); + u64TimeoutStart = RTTimeMilliTS(); + + continue; /* Don't waste time on waiting. */ + } + if (RTTimeMilliTS() - u64TimeoutStart > uTimeoutsMS) + { + VBoxServiceVerbose(3, "Guest session ID=%RU32 process did not shut down within time\n", + uSessionID); + break; + } + } + + RTThreadSleep(100); /* Wait a bit. */ + } + + if (!fProcessAlive) + { + VBoxServiceVerbose(2, "Guest session ID=%RU32 process terminated with rc=%Rrc, reason=%ld, status=%d\n", + uSessionID, rcWait, + ProcessStatus.enmReason, ProcessStatus.iStatus); + } + } + + 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++) + { + VBoxServiceVerbose(2, "Guest session ID=%RU32 process still alive, killing attempt %d/3\n", + uSessionID, i + 1); + + rc = RTProcTerminate(pThread->hProcess); + if (RT_SUCCESS(rc)) + break; + RTThreadSleep(3000); + } + + VBoxServiceVerbose(2, "Guest session ID=%RU32 process termination resulted in rc=%Rrc\n", + uSessionID, rc); + + uSessionStatus = RT_SUCCESS(rc) + ? 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 (%ld)\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; + } + } + + VBoxServiceVerbose(3, "Guest session ID=%RU32 thread ended with sessionStatus=%RU32, sessionRc=%Rrc\n", + uSessionID, uSessionStatus, uSessionRc); + + /* Report final status. */ + Assert(uSessionStatus != GUEST_SESSION_NOTIFYTYPE_UNDEFINED); + VBGLR3GUESTCTRLCMDCTX ctx = { uClientID, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(uSessionID) }; + rc2 = VbglR3GuestCtrlSessionNotify(&ctx, + uSessionStatus, uSessionRc); + if (RT_FAILURE(rc2)) + VBoxServiceError("Reporting session ID=%RU32 final status failed with rc=%Rrc\n", + uSessionID, rc2); + + VbglR3GuestCtrlDisconnect(uClientID); + + VBoxServiceVerbose(3, "Session ID=%RU32 thread ended with rc=%Rrc\n", + uSessionID, rc); + return rc; +} + + +RTEXITCODE gstcntlSessionForkWorker(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, RTEXITCODE_FAILURE); + + bool fSessionFilter = true; + + VBoxServiceVerbose(0, "Hi, this is guest session ID=%RU32\n", + pSession->StartupInfo.uSessionID); + + uint32_t uClientID; + int rc = VbglR3GuestCtrlConnect(&uClientID); + if (RT_SUCCESS(rc)) + { + /* Set session filter. This prevents the guest control + * host service to send messages which belong to another + * session we don't want to handle. */ + uint32_t uFilterAdd = + VBOX_GUESTCTRL_FILTER_BY_SESSION(pSession->StartupInfo.uSessionID); + rc = VbglR3GuestCtrlMsgFilterSet(uClientID, + VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(pSession->StartupInfo.uSessionID), + uFilterAdd, 0 /* Filter remove */); + VBoxServiceVerbose(3, "Setting message filterAdd=0x%x returned %Rrc\n", + uFilterAdd, rc); + + if ( RT_FAILURE(rc) + && rc == VERR_NOT_SUPPORTED) + { + /* No session filter available. Skip. */ + fSessionFilter = false; + + rc = VINF_SUCCESS; + } + + VBoxServiceVerbose(1, "Using client ID=%RU32\n", uClientID); + } + else + VBoxServiceError("Error connecting to guest control service, rc=%Rrc\n", rc); + + /* Report started status. */ + VBGLR3GUESTCTRLCMDCTX ctx = { uClientID, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(pSession->StartupInfo.uSessionID) }; + int rc2 = VbglR3GuestCtrlSessionNotify(&ctx, + GUEST_SESSION_NOTIFYTYPE_STARTED, VINF_SUCCESS); + if (RT_FAILURE(rc2)) + { + VBoxServiceError("Reporting session ID=%RU32 started status failed with rc=%Rrc\n", + pSession->StartupInfo.uSessionID, rc2); + + /* + * If session status cannot be posted to the host for + * some reason, bail out. + */ + if (RT_SUCCESS(rc)) + rc = rc2; + } + + /* Allocate a scratch buffer for commands 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), RTEXITCODE_FAILURE); + uint8_t *pvScratchBuf = NULL; + + if (RT_SUCCESS(rc)) + { + pvScratchBuf = (uint8_t*)RTMemAlloc(cbScratchBuf); + if (!pvScratchBuf) + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + bool fShutdown = false; + + VBGLR3GUESTCTRLCMDCTX ctxHost = { uClientID, 0 /* Context ID, zeroed */, + pSession->StartupInfo.uProtocol }; + for (;;) + { + VBoxServiceVerbose(3, "Waiting for host msg ...\n"); + uint32_t uMsg = 0; + uint32_t cParms = 0; + rc = VbglR3GuestCtrlMsgWaitFor(uClientID, &uMsg, &cParms); + if (rc == VERR_TOO_MUCH_DATA) + { +#ifdef DEBUG + VBoxServiceVerbose(4, "Message requires %RU32 parameters, but only 2 supplied -- retrying request (no error!)...\n", cParms); +#endif + rc = VINF_SUCCESS; /* Try to get "real" message in next block below. */ + } + else if (RT_FAILURE(rc)) + VBoxServiceVerbose(3, "Getting host message failed with %Rrc\n", rc); /* VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */ + if (RT_SUCCESS(rc)) + { + VBoxServiceVerbose(4, "Msg=%RU32 (%RU32 parms) retrieved\n", uMsg, cParms); + + /* Set number of parameters for current host context. */ + ctxHost.uNumParms = cParms; + + /* ... and pass it on to the session handler. */ + rc = GstCntlSessionHandler(pSession, uMsg, &ctxHost, + pvScratchBuf, cbScratchBuf, &fShutdown); + } + + if (fShutdown) + break; + + /* Let's sleep for a bit and let others run ... */ + RTThreadYield(); + } + } + + VBoxServiceVerbose(0, "Session %RU32 ended\n", pSession->StartupInfo.uSessionID); + + if (pvScratchBuf) + RTMemFree(pvScratchBuf); + + if (uClientID) + { + VBoxServiceVerbose(3, "Disconnecting client ID=%RU32 ...\n", uClientID); + VbglR3GuestCtrlDisconnect(uClientID); + } + + VBoxServiceVerbose(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 GstCntlProcessRelease(). + * Note: This does *not lock the process! + * + * @return PVBOXSERVICECTRLTHREAD Guest process if found, otherwise NULL. + * @param PVBOXSERVICECTRLSESSION Pointer to guest session where to search process in. + * @param uPID PID to search for. + */ +PVBOXSERVICECTRLPROCESS GstCntlSessionRetainProcess(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 GstCntlSessionClose(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + VBoxServiceVerbose(0, "Session %RU32 is about to close ...\n", + pSession->StartupInfo.uSessionID); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Close all guest processes. + */ + VBoxServiceVerbose(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) + { + GstCntlProcessStop(pProcess); + cProcesses++; + } + + VBoxServiceVerbose(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 = GstCntlProcessWait(pProcess, + 30 * 1000 /* Wait 30 seconds max. */, + NULL /* rc */); + + int rc3 = RTCritSectEnter(&pSession->CritSect); + AssertRC(rc3); + + if (RT_SUCCESS(rc2)) + GstCntlProcessFree(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); + + VBoxServiceVerbose(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. + */ + VBoxServiceVerbose(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 = gstcntlSessionFileDestroy(pFile); + if (RT_FAILURE(rc2)) + { + VBoxServiceError("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 GstCntlSessionDestroy(PVBOXSERVICECTRLSESSION pSession) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + int rc = GstCntlSessionClose(pSession); + + /* Destroy critical section. */ + RTCritSectDelete(&pSession->CritSect); + + return rc; +} + + +int GstCntlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t uFlags) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + RTListInit(&pSession->lstProcesses); + RTListInit(&pSession->lstFiles); + + pSession->uFlags = uFlags; + + /* 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 IPRT status code. + * @param pSession Guest session to add process to. + * @param pProcess Guest process to add. + */ +int GstCntlSessionProcessAdd(PVBOXSERVICECTRLSESSION pSession, + PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + VBoxServiceVerbose(3, "Adding process (PID %RU32) to session ID=%RU32\n", + pProcess->uPID, pSession->StartupInfo.uSessionID); + + /* Add process to session list. */ + /* rc = */ 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 IPRT status code. + * @param pSession Guest session to remove process from. + * @param pProcess Guest process to remove. + */ +int GstCntlSessionProcessRemove(PVBOXSERVICECTRLSESSION pSession, + PVBOXSERVICECTRLPROCESS pProcess) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + + int rc = RTCritSectEnter(&pSession->CritSect); + if (RT_SUCCESS(rc)) + { + VBoxServiceVerbose(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 IPRT status code. + * @param pbAllowed True if starting (another) guest process + * is allowed, false if not. + */ +int GstCntlSessionProcessStartAllowed(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++; + + VBoxServiceVerbose(3, "Maximum served guest processes set to %u, running=%u\n", + pSession->uProcsMaxKept, uProcsRunning); + + int32_t iProcsLeft = (pSession->uProcsMaxKept - uProcsRunning - 1); + if (iProcsLeft < 0) + { + VBoxServiceVerbose(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 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 IPRT 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 GstCntlSessionThreadCreate(PRTLISTANCHOR pList, + const PVBOXSERVICECTRLSESSIONSTARTUPINFO pSessionStartupInfo, + PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread) +{ + AssertPtrReturn(pList, VERR_INVALID_POINTER); + AssertPtrReturn(pSessionStartupInfo, VERR_INVALID_POINTER); + /* ppSessionThread is optional. */ + +#ifdef DEBUG + PVBOXSERVICECTRLSESSIONTHREAD pSessionCur; + /* Check for existing session in debug mode. Should never happen because of + * Main consistency. */ + RTListForEach(pList, pSessionCur, VBOXSERVICECTRLSESSIONTHREAD, Node) + { + if (pSessionCur->StartupInfo.uSessionID == pSessionStartupInfo->uSessionID) + { + AssertMsgFailed(("Guest session thread ID=%RU32 (%p) already exists when it should not\n", + pSessionCur->StartupInfo.uSessionID, pSessionCur)); + return VERR_ALREADY_EXISTS; + } + } +#endif + int rc = VINF_SUCCESS; + + /* Static counter to help tracking session thread <-> process relations. */ + static uint32_t s_uCtrlSessionThread = 0; + if (s_uCtrlSessionThread++ == UINT32_MAX) + s_uCtrlSessionThread = 0; /* Wrap around to not let IPRT freak out. */ + + PVBOXSERVICECTRLSESSIONTHREAD pSessionThread = + (PVBOXSERVICECTRLSESSIONTHREAD)RTMemAllocZ(sizeof(VBOXSERVICECTRLSESSIONTHREAD)); + if (pSessionThread) + { + /* Copy over session startup info. */ + memcpy(&pSessionThread->StartupInfo, pSessionStartupInfo, + sizeof(VBOXSERVICECTRLSESSIONSTARTUPINFO)); + + pSessionThread->fShutdown = false; + pSessionThread->fStarted = false; + pSessionThread->fStopped = false; + + /* Is this an anonymous session? */ + /* Anonymous sessions run with the same privileges as the main VBoxService executable. */ + bool fAnonymous = !RT_BOOL(strlen(pSessionThread->StartupInfo.szUser)); + if (fAnonymous) + { + Assert(!strlen(pSessionThread->StartupInfo.szPassword)); + Assert(!strlen(pSessionThread->StartupInfo.szDomain)); + + VBoxServiceVerbose(3, "New anonymous guest session ID=%RU32 created, uFlags=%x, using protocol %RU32\n", + pSessionStartupInfo->uSessionID, + pSessionStartupInfo->uFlags, + pSessionStartupInfo->uProtocol); + } + else + { + VBoxServiceVerbose(3, "Forking new guest session ID=%RU32, szUser=%s, szPassword=%s, szDomain=%s, uFlags=%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->uFlags, + pSessionStartupInfo->uProtocol); + } + + rc = RTCritSectInit(&pSessionThread->CritSect); + AssertRC(rc); + + /* Fork child doing the actual session handling. */ + char szExeName[RTPATH_MAX]; + char *pszExeName = RTProcGetExecutablePath(szExeName, sizeof(szExeName)); + if (pszExeName) + { + char szParmUserName[GUESTPROCESS_MAX_USER_LEN + 32]; + if (!fAnonymous) + { + if (!RTStrPrintf(szParmUserName, sizeof(szParmUserName), "--user=%s", pSessionThread->StartupInfo.szUser)) + rc = VERR_BUFFER_OVERFLOW; + } + char szParmSessionID[32]; + if (RT_SUCCESS(rc) && !RTStrPrintf(szParmSessionID, sizeof(szParmSessionID), "--session-id=%RU32", + pSessionThread->StartupInfo.uSessionID)) + { + rc = VERR_BUFFER_OVERFLOW; + } + char szParmSessionProto[32]; + if (RT_SUCCESS(rc) && !RTStrPrintf(szParmSessionProto, sizeof(szParmSessionProto), "--session-proto=%RU32", + pSessionThread->StartupInfo.uProtocol)) + { + rc = VERR_BUFFER_OVERFLOW; + } +#ifdef DEBUG + char szParmThreadId[32]; + if (RT_SUCCESS(rc) && !RTStrPrintf(szParmThreadId, sizeof(szParmThreadId), "--thread-id=%RU32", + s_uCtrlSessionThread)) + { + rc = VERR_BUFFER_OVERFLOW; + } +#endif /* DEBUG */ + if (RT_SUCCESS(rc)) + { + int iOptIdx = 0; /* Current index in argument vector. */ + + char const *papszArgs[16]; + papszArgs[iOptIdx++] = pszExeName; + papszArgs[iOptIdx++] = "guestsession"; + papszArgs[iOptIdx++] = szParmSessionID; + papszArgs[iOptIdx++] = szParmSessionProto; +#ifdef DEBUG + papszArgs[iOptIdx++] = szParmThreadId; +#endif /* DEBUG */ + if (!fAnonymous) + papszArgs[iOptIdx++] = szParmUserName; + + /* Add same verbose flags as parent process. */ + int rc2 = VINF_SUCCESS; + char szParmVerbose[32] = { 0 }; + for (int i = 0; i < g_cVerbosity && RT_SUCCESS(rc2); i++) + { + if (i == 0) + rc2 = RTStrCat(szParmVerbose, sizeof(szParmVerbose), "-"); + if (RT_FAILURE(rc2)) + break; + rc2 = RTStrCat(szParmVerbose, sizeof(szParmVerbose), "v"); + } + if (RT_SUCCESS(rc2)) + papszArgs[iOptIdx++] = szParmVerbose; + + /* Add log file handling. Each session will have an own + * log file, naming based on the parent log file. */ + char szParmLogFile[RTPATH_MAX]; + if ( RT_SUCCESS(rc2) + && strlen(g_szLogFile)) + { + char *pszLogFile = RTStrDup(g_szLogFile); + if (pszLogFile) + { + char *pszLogExt = NULL; + if (RTPathHasExt(pszLogFile)) + pszLogExt = RTStrDup(RTPathExt(pszLogFile)); + RTPathStripExt(pszLogFile); + char *pszLogSuffix; +#ifndef DEBUG + if (RTStrAPrintf(&pszLogSuffix, "-%RU32-%s", + pSessionStartupInfo->uSessionID, + pSessionStartupInfo->szUser) < 0) + { + rc2 = VERR_NO_MEMORY; + } +#else + if (RTStrAPrintf(&pszLogSuffix, "-%RU32-%RU32-%s", + pSessionStartupInfo->uSessionID, + s_uCtrlSessionThread, + pSessionStartupInfo->szUser) < 0) + { + rc2 = VERR_NO_MEMORY; + } +#endif /* DEBUG */ + else + { + rc2 = RTStrAAppend(&pszLogFile, pszLogSuffix); + if (RT_SUCCESS(rc2) && pszLogExt) + rc2 = RTStrAAppend(&pszLogFile, pszLogExt); + if (RT_SUCCESS(rc2)) + { + if (!RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), + "--logfile=%s", pszLogFile)) + { + rc2 = VERR_BUFFER_OVERFLOW; + } + } + RTStrFree(pszLogSuffix); + } + if (RT_FAILURE(rc2)) + VBoxServiceError("Error building session logfile string for session %RU32 (user %s), rc=%Rrc\n", + pSessionStartupInfo->uSessionID, pSessionStartupInfo->szUser, rc2); + if (pszLogExt) + RTStrFree(pszLogExt); + RTStrFree(pszLogFile); + } + if (RT_SUCCESS(rc2)) + papszArgs[iOptIdx++] = szParmLogFile; + + rc = rc2; + } + else if (RT_FAILURE(rc2)) + rc = rc2; +#ifdef DEBUG + VBoxServiceVerbose(4, "Argv building rc=%Rrc, session flags=%x\n", + rc, g_Session.uFlags); + char szParmDumpStdOut[32]; + if ( RT_SUCCESS(rc) + && g_Session.uFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT) + { + if (!RTStrPrintf(szParmDumpStdOut, sizeof(szParmDumpStdOut), "--dump-stdout")) + rc = VERR_BUFFER_OVERFLOW; + if (RT_SUCCESS(rc)) + papszArgs[iOptIdx++] = szParmDumpStdOut; + } + char szParmDumpStdErr[32]; + if ( RT_SUCCESS(rc) + && g_Session.uFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR) + { + if (!RTStrPrintf(szParmDumpStdErr, sizeof(szParmDumpStdErr), "--dump-stderr")) + rc = VERR_BUFFER_OVERFLOW; + if (RT_SUCCESS(rc)) + papszArgs[iOptIdx++] = szParmDumpStdErr; + } +#endif + papszArgs[iOptIdx++] = NULL; + + if (g_cVerbosity > 3) + { + VBoxServiceVerbose(4, "Forking parameters:\n"); + + iOptIdx = 0; + while (papszArgs[iOptIdx]) + VBoxServiceVerbose(4, "\t%s\n", papszArgs[iOptIdx++]); + } + + uint32_t uProcFlags = RTPROC_FLAGS_SERVICE + | RTPROC_FLAGS_HIDDEN; /** @todo More flags from startup info? */ + +#if 0 /* Pipe handling not needed (yet). */ + /* Setup pipes. */ + rc = GstcntlProcessSetupPipe("|", 0 /*STDIN_FILENO*/, + &pSession->StdIn.hChild, &pSession->StdIn.phChild, &pSession->hStdInW); + if (RT_SUCCESS(rc)) + { + rc = GstcntlProcessSetupPipe("|", 1 /*STDOUT_FILENO*/, + &pSession->StdOut.hChild, &pSession->StdOut.phChild, &pSession->hStdOutR); + if (RT_SUCCESS(rc)) + { + rc = GstcntlProcessSetupPipe("|", 2 /*STDERR_FILENO*/, + &pSession->StdErr.hChild, &pSession->StdErr.phChild, &pSession->hStdErrR); + if (RT_SUCCESS(rc)) + { + rc = RTPollSetCreate(&pSession->hPollSet); + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pSession->hPollSet, pSession->hStdInW, RTPOLL_EVT_ERROR, + VBOXSERVICECTRLPIPEID_STDIN); + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pSession->hPollSet, pSession->hStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + VBOXSERVICECTRLPIPEID_STDOUT); + if (RT_SUCCESS(rc)) + rc = RTPollSetAddPipe(pSession->hPollSet, pSession->hStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, + VBOXSERVICECTRLPIPEID_STDERR); + } + + if (RT_SUCCESS(rc)) + { + /* Fork the thing. */ + /** @todo Do we need a custom environment block? */ + rc = RTProcCreateEx(pszExeName, papszArgs, RTENV_DEFAULT, uProcFlags, + pSession->StdIn.phChild, pSession->StdOut.phChild, pSession->StdErr.phChild, + !fAnonymous ? pSession->StartupInfo.szUser : NULL, + !fAnonymous ? pSession->StartupInfo.szPassword : NULL, + &pSession->hProcess); + } + + if (RT_SUCCESS(rc)) + { + /* + * Close the child ends of any pipes and redirected files. + */ + int rc2 = RTHandleClose(pSession->StdIn.phChild); AssertRC(rc2); + pSession->StdIn.phChild = NULL; + rc2 = RTHandleClose(pSession->StdOut.phChild); AssertRC(rc2); + pSession->StdOut.phChild = NULL; + rc2 = RTHandleClose(pSession->StdErr.phChild); AssertRC(rc2); + pSession->StdErr.phChild = NULL; + } + } + } +#else + RTHANDLE hStdIn; + if (RT_SUCCESS(rc)) + rc = RTFileOpenBitBucket(&hStdIn.u.hFile, RTFILE_O_READ); + if (RT_SUCCESS(rc)) + { + hStdIn.enmType = RTHANDLETYPE_FILE; + + RTHANDLE hStdOutAndErr; + rc = RTFileOpenBitBucket(&hStdOutAndErr.u.hFile, RTFILE_O_WRITE); + if (RT_SUCCESS(rc)) + { + hStdOutAndErr.enmType = RTHANDLETYPE_FILE; + + /** @todo Set custom/cloned guest session environment block. */ + rc = RTProcCreateEx(pszExeName, papszArgs, RTENV_DEFAULT, uProcFlags, + &hStdIn, &hStdOutAndErr, &hStdOutAndErr, + !fAnonymous ? pSessionThread->StartupInfo.szUser : NULL, + !fAnonymous ? pSessionThread->StartupInfo.szPassword : NULL, + &pSessionThread->hProcess); + + RTFileClose(hStdOutAndErr.u.hFile); + } + + RTFileClose(hStdIn.u.hFile); + } +#endif + } + } + else + rc = VERR_FILE_NOT_FOUND; + + if (RT_SUCCESS(rc)) + { + /* Start session thread. */ + rc = RTThreadCreateF(&pSessionThread->Thread, gstcntlSessionThread, + pSessionThread /*pvUser*/, 0 /*cbStack*/, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "sess%u", s_uCtrlSessionThread); + if (RT_FAILURE(rc)) + { + VBoxServiceError("Creating session thread failed, rc=%Rrc\n", rc); + } + else + { + /* Wait for the thread to initialize. */ + rc = RTThreadUserWait(pSessionThread->Thread, 60 * 1000 /* 60s timeout */); + if ( ASMAtomicReadBool(&pSessionThread->fShutdown) + || RT_FAILURE(rc)) + { + VBoxServiceError("Thread for session ID=%RU32 failed to start, rc=%Rrc\n", + pSessionThread->StartupInfo.uSessionID, rc); + if (RT_SUCCESS(rc)) + rc = VERR_CANT_CREATE; /** @todo Find a better rc. */ + } + else + { + VBoxServiceVerbose(2, "Thread for session ID=%RU32 started\n", + pSessionThread->StartupInfo.uSessionID); + + ASMAtomicXchgBool(&pSessionThread->fStarted, true); + + /* Add session to list. */ + /* rc = */ RTListAppend(pList, &pSessionThread->Node); + if (ppSessionThread) /* Return session if wanted. */ + *ppSessionThread = pSessionThread; + } + } + } + + if (RT_FAILURE(rc)) + { + RTMemFree(pSessionThread); + } + } + else + rc = VERR_NO_MEMORY; + + VBoxServiceVerbose(3, "Forking session thread returned returned rc=%Rrc\n", rc); + return rc; +} + + +/** + * Waits for a formerly opened guest session process to close. + * + * @return IPRT status code. + * @param pThread Guest session thread to wait for. + * @param uTimeoutMS Waiting timeout (in ms). + * @param uFlags Closing flags. + */ +int GstCntlSessionThreadWait(PVBOXSERVICECTRLSESSIONTHREAD pThread, + uint32_t uTimeoutMS, uint32_t uFlags) +{ + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + /** @todo Validate closing flags. */ + + if (pThread->Thread == NIL_RTTHREAD) + { + AssertMsgFailed(("Guest session thread of session %p does not exist when it should\n", + pThread)); + return VERR_NOT_FOUND; + } + + int rc = VINF_SUCCESS; + + /* + * The fork 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); + + VBoxServiceVerbose(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_FAILURE(rc)) + { + VBoxServiceError("Waiting for session thread ID=%RU32 to close failed with rc=%Rrc\n", + pThread->StartupInfo.uSessionID, rc); + } + else + VBoxServiceVerbose(3, "Session thread ID=%RU32 ended with rc=%Rrc\n", + pThread->StartupInfo.uSessionID, rcThread); + } + + return rc; +} + +/** + * Waits for the specified session thread to end and remove + * it from the session thread list. + * + * @return IPRT status code. + * @param pThread Session thread to destroy. + * @param uFlags Closing flags. + */ +int GstCntlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pThread, uint32_t uFlags) +{ + AssertPtrReturn(pThread, VERR_INVALID_POINTER); + + int rc = GstCntlSessionThreadWait(pThread, + 5 * 60 * 1000 /* 5 minutes timeout */, uFlags); + + /* Remove session from list and destroy object. */ + RTListNodeRemove(&pThread->Node); + + RTMemFree(pThread); + pThread = NULL; + + return rc; +} + +/** + * Close all formerly opened guest session threads. + * Note: Caller is responsible for locking! + * + * @return IPRT status code. + * @param pList Which list to close the session threads for. + * @param uFlags Closing flags. + */ +int GstCntlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t uFlags) +{ + AssertPtrReturn(pList, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + /*int rc = VbglR3GuestCtrlClose + if (RT_FAILURE(rc)) + VBoxServiceError("Cancelling pending waits failed; rc=%Rrc\n", rc);*/ + + PVBOXSERVICECTRLSESSIONTHREAD pSessionThread + = RTListGetFirst(pList, VBOXSERVICECTRLSESSIONTHREAD, Node); + while (pSessionThread) + { + PVBOXSERVICECTRLSESSIONTHREAD pSessionThreadNext = + RTListGetNext(pList, pSessionThread, VBOXSERVICECTRLSESSIONTHREAD, Node); + bool fLast = RTListNodeIsLast(pList, &pSessionThread->Node); + + int rc2 = GstCntlSessionThreadDestroy(pSessionThread, uFlags); + if (RT_FAILURE(rc2)) + { + VBoxServiceError("Closing session thread failed with rc=%Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + /* Keep going. */ + } + + if (fLast) + break; + + pSessionThread = pSessionThreadNext; + } + + return rc; +} + +RTEXITCODE VBoxServiceControlSessionForkInit(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { +#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 } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, + s_aOptions, RT_ELEMENTS(s_aOptions), + 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + uint32_t uSessionFlags = VBOXSERVICECTRLSESSION_FLAG_FORK; + + /* Protocol and session ID must be specified explicitly. */ + g_Session.StartupInfo.uProtocol = UINT32_MAX; + g_Session.StartupInfo.uSessionID = UINT32_MAX; + + int rc = VINF_SUCCESS; + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case VBOXSERVICESESSIONOPT_LOG_FILE: + if (!RTStrPrintf(g_szLogFile, sizeof(g_szLogFile), "%s", ValueUnion.psz)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unable to set logfile name to '%s'", + ValueUnion.psz); + break; +#ifdef DEBUG + case VBOXSERVICESESSIONOPT_DUMP_STDOUT: + uSessionFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT; + break; + + case VBOXSERVICESESSIONOPT_DUMP_STDERR: + uSessionFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR; + break; +#endif + case VBOXSERVICESESSIONOPT_USERNAME: + /** @todo Information not needed right now, skip. */ + break; + + 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. */ + break; +#endif + /** @todo Implement help? */ + + case 'v': + g_cVerbosity++; + break; + + case VINF_GETOPT_NOT_OPTION: + /* Ignore; might be "guestsession" main command. */ + break; + + default: + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown command '%s'", ValueUnion.psz); + break; /* Never reached. */ + } + } + + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Initialization failed with rc=%Rrc", rc); + + 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. */ + rc = GstCntlSessionInit(&g_Session, uSessionFlags); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize session object, rc=%Rrc\n", rc); + + rc = VBoxServiceLogCreate(strlen(g_szLogFile) ? g_szLogFile : NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create release log (%s, %Rrc)", + strlen(g_szLogFile) ? g_szLogFile : "<None>", rc); + + RTEXITCODE rcExit = gstcntlSessionForkWorker(&g_Session); + + VBoxServiceLogDestroy(); + return rcExit; +} + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControlThread.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControlThread.cpp deleted file mode 100644 index 78ec20a8..00000000 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceControlThread.cpp +++ /dev/null @@ -1,1794 +0,0 @@ -/* $Id: VBoxServiceControlThread.cpp $ */ -/** @file - * VBoxServiceControlExecThread - Thread for every started guest process. - */ - -/* - * Copyright (C) 2012 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" - -using namespace guestControl; - -/* Internal functions. */ -static int vboxServiceControlThreadRequestCancel(PVBOXSERVICECTRLREQUEST pThread); - -/** - * Initialies the passed in thread data structure with the parameters given. - * - * @return IPRT status code. - * @param pThread The thread's handle to allocate the data for. - * @param u32ContextID The context ID bound to this request / command. - @ @param pProcess Process information. - */ -static int gstsvcCntlExecThreadInit(PVBOXSERVICECTRLTHREAD pThread, - PVBOXSERVICECTRLPROCESS pProcess, - uint32_t u32ContextID) -{ - AssertPtrReturn(pThread, VERR_INVALID_POINTER); - AssertPtrReturn(pProcess, VERR_INVALID_POINTER); - - /* General stuff. */ - pThread->pAnchor = NULL; - pThread->Node.pPrev = NULL; - pThread->Node.pNext = NULL; - - pThread->fShutdown = false; - pThread->fStarted = false; - pThread->fStopped = false; - - pThread->uContextID = u32ContextID; - /* 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(&pThread->CritSect); - if (RT_FAILURE(rc)) - return rc; - - pThread->uPID = 0; /* Don't have a PID yet. */ - pThread->pRequest = NULL; /* No request assigned yet. */ - pThread->uFlags = pProcess->uFlags; - pThread->uTimeLimitMS = ( pProcess->uTimeLimitMS == UINT32_MAX - || pProcess->uTimeLimitMS == 0) - ? RT_INDEFINITE_WAIT : pProcess->uTimeLimitMS; - - /* Prepare argument list. */ - pThread->uNumArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */ - rc = RTGetOptArgvFromString(&pThread->papszArgs, (int*)&pThread->uNumArgs, - (pProcess->uNumArgs > 0) ? pProcess->szArgs : "", NULL); - /* Did we get the same result? */ - Assert(pProcess->uNumArgs == pThread->uNumArgs); - - if (RT_SUCCESS(rc)) - { - /* Prepare environment list. */ - pThread->uNumEnvVars = 0; - if (pProcess->uNumEnvVars) - { - pThread->papszEnv = (char **)RTMemAlloc(pProcess->uNumEnvVars * sizeof(char*)); - AssertPtr(pThread->papszEnv); - pThread->uNumEnvVars = pProcess->uNumEnvVars; - - const char *pszCur = pProcess->szEnv; - uint32_t i = 0; - uint32_t cbLen = 0; - while (cbLen < pProcess->cbEnv) - { - /* sanity check */ - if (i >= pProcess->uNumEnvVars) - { - rc = VERR_INVALID_PARAMETER; - break; - } - int cbStr = RTStrAPrintf(&pThread->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 == pThread->uNumEnvVars); - } - - /* The actual command to execute. */ - pThread->pszCmd = RTStrDup(pProcess->szCmd); - AssertPtr(pThread->pszCmd); - - /* User management. */ - pThread->pszUser = RTStrDup(pProcess->szUser); - AssertPtr(pThread->pszUser); - pThread->pszPassword = RTStrDup(pProcess->szPassword); - AssertPtr(pThread->pszPassword); - } - - if (RT_FAILURE(rc)) /* Clean up on failure. */ - VBoxServiceControlThreadFree(pThread); - return rc; -} - - -/** - * Frees a guest thread. - * - * @return IPRT status code. - * @param pThread Thread to shut down. - */ -int VBoxServiceControlThreadFree(PVBOXSERVICECTRLTHREAD pThread) -{ - AssertPtrReturn(pThread, VERR_INVALID_POINTER); - - VBoxServiceVerbose(3, "[PID %u]: Freeing ...\n", - pThread->uPID); - - int rc = RTCritSectEnter(&pThread->CritSect); - if (RT_SUCCESS(rc)) - { - if (pThread->uNumEnvVars) - { - for (uint32_t i = 0; i < pThread->uNumEnvVars; i++) - RTStrFree(pThread->papszEnv[i]); - RTMemFree(pThread->papszEnv); - } - RTGetOptArgvFree(pThread->papszArgs); - - RTStrFree(pThread->pszCmd); - RTStrFree(pThread->pszUser); - RTStrFree(pThread->pszPassword); - - VBoxServiceVerbose(3, "[PID %u]: Setting stopped state\n", - pThread->uPID); - - rc = RTCritSectLeave(&pThread->CritSect); - AssertRC(rc); - } - - /* - * Destroy other thread data. - */ - if (RTCritSectIsInitialized(&pThread->CritSect)) - RTCritSectDelete(&pThread->CritSect); - - /* - * Destroy thread structure as final step. - */ - RTMemFree(pThread); - pThread = NULL; - - return rc; -} - - -/** - * Signals a guest process thread that we want it to shut down in - * a gentle way. - * - * @return IPRT status code. - * @param pThread Thread to shut down. - */ -int VBoxServiceControlThreadStop(const PVBOXSERVICECTRLTHREAD pThread) -{ - AssertPtrReturn(pThread, VERR_INVALID_POINTER); - - VBoxServiceVerbose(3, "[PID %u]: Stopping ...\n", - pThread->uPID); - - int rc = vboxServiceControlThreadRequestCancel(pThread->pRequest); - if (RT_FAILURE(rc)) - VBoxServiceError("[PID %u]: Signalling request event failed, rc=%Rrc\n", - pThread->uPID, rc); - - /* Do *not* set pThread->fShutdown or other stuff here! - * The guest thread loop will do that as soon as it processes the quit message. */ - - PVBOXSERVICECTRLREQUEST pRequest; - rc = VBoxServiceControlThreadRequestAlloc(&pRequest, VBOXSERVICECTRLREQUEST_QUIT); - if (RT_SUCCESS(rc)) - { - rc = VBoxServiceControlThreadPerform(pThread->uPID, pRequest); - if (RT_FAILURE(rc)) - VBoxServiceVerbose(3, "[PID %u]: Sending quit request failed with rc=%Rrc\n", - pThread->uPID, rc); - - VBoxServiceControlThreadRequestFree(pRequest); - } - return rc; -} - - -/** - * Wait for a guest process thread to shut down. - * - * @return IPRT status code. - * @param pThread Thread to wait shutting down for. - * @param RTMSINTERVAL Timeout in ms to wait for shutdown. - * @param prc Where to store the thread's return code. Optional. - */ -int VBoxServiceControlThreadWait(const PVBOXSERVICECTRLTHREAD pThread, - RTMSINTERVAL msTimeout, int *prc) -{ - AssertPtrReturn(pThread, VERR_INVALID_POINTER); - /* prc is optional. */ - - int rc = VINF_SUCCESS; - if ( pThread->Thread != NIL_RTTHREAD - && ASMAtomicReadBool(&pThread->fStarted)) - { - VBoxServiceVerbose(2, "[PID %u]: Waiting for shutdown of pThread=0x%p = \"%s\"...\n", - pThread->uPID, pThread, pThread->pszCmd); - - /* Wait a bit ... */ - int rcThread; - rc = RTThreadWait(pThread->Thread, msTimeout, &rcThread); - if (RT_FAILURE(rc)) - { - VBoxServiceError("[PID %u]: Waiting for shutting down thread returned error rc=%Rrc\n", - pThread->uPID, rc); - } - else - { - VBoxServiceVerbose(3, "[PID %u]: Thread reported exit code=%Rrc\n", - pThread->uPID, rcThread); - if (prc) - *prc = rcThread; - } - } - return rc; -} - - -/** - * Closes the stdin pipe of a guest process. - * - * @return IPRT status code. - * @param hPollSet The polling set. - * @param phStdInW The standard input pipe handle. - */ -static int VBoxServiceControlThreadCloseStdIn(RTPOLLSET hPollSet, PRTPIPE phStdInW) -{ - AssertPtrReturn(phStdInW, VERR_INVALID_POINTER); - - int rc = RTPollSetRemove(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; -} - - -/** - * Handle an error event on standard input. - * - * @return IPRT status code. - * @param hPollSet The polling set. - * @param fPollEvt The event mask returned by RTPollNoResume. - * @param phStdInW The standard input pipe handle. - */ -static int VBoxServiceControlThreadHandleStdInErrorEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, PRTPIPE phStdInW) -{ - NOREF(fPollEvt); - - return VBoxServiceControlThreadCloseStdIn(hPollSet, phStdInW); -} - - -/** - * Handle pending output data or error on standard out or standard error. - * - * @returns IPRT status code from client send. - * @param hPollSet The polling set. - * @param fPollEvt The event mask returned by RTPollNoResume. - * @param phPipeR The pipe handle. - * @param idPollHnd The pipe ID to handle. - * - */ -static int VBoxServiceControlThreadHandleOutputError(RTPOLLSET hPollSet, uint32_t fPollEvt, - PRTPIPE phPipeR, uint32_t idPollHnd) -{ - AssertPtrReturn(phPipeR, VERR_INVALID_POINTER); - -#ifdef DEBUG - VBoxServiceVerbose(4, "VBoxServiceControlThreadHandleOutputError: fPollEvt=0x%x, idPollHnd=%u\n", - fPollEvt, idPollHnd); -#endif - - /* Remove pipe from poll set. */ - int rc2 = RTPollSetRemove(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. */ - size_t cbReadable; - rc2 = RTPipeQueryReadable(*phPipeR, &cbReadable); - if ( RT_SUCCESS(rc2) - && cbReadable) - { - VBoxServiceVerbose(3, "VBoxServiceControlThreadHandleOutputError: idPollHnd=%u has %ld bytes left, vetoing close\n", - idPollHnd, cbReadable); - - /* 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; - } - else - VBoxServiceVerbose(3, "VBoxServiceControlThreadHandleOutputError: idPollHnd=%u will be closed\n", - idPollHnd); - - 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 hPollSet The polling set. - * @param fPollEvt The event mask returned by RTPollNoResume. - * @param phPipeR The pipe handle. - * @param idPollHnd The pipe ID to handle. - * - */ -static int VBoxServiceControlThreadHandleOutputEvent(RTPOLLSET hPollSet, uint32_t fPollEvt, - PRTPIPE phPipeR, uint32_t idPollHnd) -{ -#if 0 - VBoxServiceVerbose(4, "VBoxServiceControlThreadHandleOutputEvent: fPollEvt=0x%x, idPollHnd=%u\n", - fPollEvt, idPollHnd); -#endif - - int rc = VINF_SUCCESS; - -#ifdef DEBUG - size_t cbReadable; - rc = RTPipeQueryReadable(*phPipeR, &cbReadable); - if ( RT_SUCCESS(rc) - && cbReadable) - { - VBoxServiceVerbose(4, "VBoxServiceControlThreadHandleOutputEvent: cbReadable=%ld\n", - cbReadable); - } -#endif - -#if 0 - //if (fPollEvt & RTPOLL_EVT_READ) - { - size_t cbRead = 0; - uint8_t byData[_64K]; - rc = RTPipeRead(*phPipeR, - byData, sizeof(byData), &cbRead); - VBoxServiceVerbose(4, "VBoxServiceControlThreadHandleOutputEvent 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 = VBoxServiceControlThreadHandleOutputError(hPollSet, fPollEvt, - phPipeR, idPollHnd); - return rc; -} - - -static int VBoxServiceControlThreadHandleRequest(RTPOLLSET hPollSet, uint32_t fPollEvt, - PRTPIPE phStdInW, PRTPIPE phStdOutR, PRTPIPE phStdErrR, - PVBOXSERVICECTRLTHREAD pThread) -{ - AssertPtrReturn(pThread, VERR_INVALID_POINTER); - AssertPtrReturn(phStdInW, VERR_INVALID_POINTER); - AssertPtrReturn(phStdOutR, VERR_INVALID_POINTER); - AssertPtrReturn(phStdErrR, VERR_INVALID_POINTER); - - /* Drain the notification pipe. */ - uint8_t abBuf[8]; - size_t cbIgnore; - int rc = RTPipeRead(pThread->hNotificationPipeR, abBuf, sizeof(abBuf), &cbIgnore); - if (RT_FAILURE(rc)) - VBoxServiceError("Draining IPC notification pipe failed with rc=%Rrc\n", rc); - - int rcReq = VINF_SUCCESS; /* Actual request result. */ - - PVBOXSERVICECTRLREQUEST pRequest = pThread->pRequest; - if (!pRequest) - { - VBoxServiceError("IPC request is invalid\n"); - return VERR_INVALID_POINTER; - } - - switch (pRequest->enmType) - { - case VBOXSERVICECTRLREQUEST_QUIT: /* Main control asked us to quit. */ - { - /** @todo Check for some conditions to check to - * veto quitting. */ - ASMAtomicXchgBool(&pThread->fShutdown, true); - rcReq = VERR_CANCELLED; - break; - } - - case VBOXSERVICECTRLREQUEST_STDIN_WRITE: - case VBOXSERVICECTRLREQUEST_STDIN_WRITE_EOF: - { - size_t cbWritten = 0; - if (pRequest->cbData) - { - AssertPtrReturn(pRequest->pvData, VERR_INVALID_POINTER); - if (*phStdInW != NIL_RTPIPE) - { - rcReq = RTPipeWrite(*phStdInW, - pRequest->pvData, pRequest->cbData, &cbWritten); - } - else - rcReq = VINF_EOF; - } - - /* - * 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 ( pRequest->enmType == VBOXSERVICECTRLREQUEST_STDIN_WRITE_EOF - && pRequest->cbData == cbWritten) - { - rc = VBoxServiceControlThreadCloseStdIn(hPollSet, phStdInW); - } - - /* Report back actual data written (if any). */ - pRequest->cbData = cbWritten; - break; - } - - case VBOXSERVICECTRLREQUEST_STDOUT_READ: - case VBOXSERVICECTRLREQUEST_STDERR_READ: - { - AssertPtrReturn(pRequest->pvData, VERR_INVALID_POINTER); - AssertReturn(pRequest->cbData, VERR_INVALID_PARAMETER); - - PRTPIPE pPipeR = pRequest->enmType == VBOXSERVICECTRLREQUEST_STDERR_READ - ? phStdErrR : phStdOutR; - AssertPtr(pPipeR); - - size_t cbRead = 0; - if (*pPipeR != NIL_RTPIPE) - { - rcReq = RTPipeRead(*pPipeR, - pRequest->pvData, pRequest->cbData, &cbRead); - if (RT_FAILURE(rcReq)) - { - RTPollSetRemove(hPollSet, pRequest->enmType == VBOXSERVICECTRLREQUEST_STDERR_READ - ? VBOXSERVICECTRLPIPEID_STDERR : VBOXSERVICECTRLPIPEID_STDOUT); - RTPipeClose(*pPipeR); - *pPipeR = NIL_RTPIPE; - if (rcReq == VERR_BROKEN_PIPE) - rcReq = VINF_EOF; - } - } - else - rcReq = VINF_EOF; - - /* Report back actual data read (if any). */ - pRequest->cbData = cbRead; - break; - } - - default: - rcReq = VERR_NOT_IMPLEMENTED; - break; - } - - /* Assign overall result. */ - pRequest->rc = RT_SUCCESS(rc) - ? rcReq : rc; - - VBoxServiceVerbose(2, "[PID %u]: Handled req=%u, CID=%u, rc=%Rrc, cbData=%u\n", - pThread->uPID, pRequest->enmType, pRequest->uCID, pRequest->rc, pRequest->cbData); - - /* In any case, regardless of the result, we notify - * the main guest control to unblock it. */ - int rc2 = RTSemEventMultiSignal(pRequest->Event); - AssertRC(rc2); - - /* No access to pRequest here anymore -- could be out of scope - * or modified already! */ - pThread->pRequest = pRequest = NULL; - - 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 pThread The process' thread handle. - * @param hProcess The actual process handle. - * @param cMsTimeout Time limit (in ms) of the process' life time. - * @param hPollSet The poll set to use. - * @param hStdInW Handle to the process' stdin write end. - * @param hStdOutR Handle to the process' stdout read end. - * @param hStdErrR Handle to the process' stderr read end. - */ -static int VBoxServiceControlThreadProcLoop(PVBOXSERVICECTRLTHREAD pThread, - RTPROCESS hProcess, RTMSINTERVAL cMsTimeout, RTPOLLSET hPollSet, - PRTPIPE phStdInW, PRTPIPE phStdOutR, PRTPIPE phStdErrR) -{ - AssertPtrReturn(pThread, VERR_INVALID_POINTER); - AssertPtrReturn(phStdInW, VERR_INVALID_PARAMETER); - /* Rest is optional. */ - - int rc; - int rc2; - uint64_t const MsStart = RTTimeMilliTS(); - RTPROCSTATUS ProcessStatus = { 254, RTPROCEXITREASON_ABEND }; - bool fProcessAlive = true; - bool fProcessTimedOut = false; - uint64_t MsProcessKilled = UINT64_MAX; - RTMSINTERVAL const cMsPollBase = *phStdInW != 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 = VBoxServiceControlAssignPID(pThread, hProcess); - if (RT_FAILURE(rc)) - { - VBoxServiceError("Unable to assign PID=%u, to new thread, rc=%Rrc\n", - 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. - */ - VBoxServiceVerbose(2, "[PID %u]: Process \"%s\" started, CID=%u, User=%s\n", - pThread->uPID, pThread->pszCmd, pThread->uContextID, pThread->pszUser); - rc = VbglR3GuestCtrlExecReportStatus(pThread->uClientID, pThread->uContextID, - pThread->uPID, PROC_STS_STARTED, 0 /* u32Flags */, - NULL /* pvData */, 0 /* cbData */); - - /* - * Process input, output, the test pipe and client requests. - */ - while ( RT_SUCCESS(rc) - && RT_UNLIKELY(!pThread->fShutdown)) - { - /* - * Wait/Process all pending events. - */ - uint32_t idPollHnd; - uint32_t fPollEvt; - rc2 = RTPollNoResume(hPollSet, cMsPollCur, &fPollEvt, &idPollHnd); - if (pThread->fShutdown) - continue; - - cMsPollCur = 0; /* No rest until we've checked everything. */ - - if (RT_SUCCESS(rc2)) - { - /*VBoxServiceVerbose(4, "[PID %u}: RTPollNoResume idPollHnd=%u\n", - pThread->uPID, idPollHnd);*/ - switch (idPollHnd) - { - case VBOXSERVICECTRLPIPEID_STDIN: - rc = VBoxServiceControlThreadHandleStdInErrorEvent(hPollSet, fPollEvt, phStdInW); - break; - - case VBOXSERVICECTRLPIPEID_STDOUT: - rc = VBoxServiceControlThreadHandleOutputEvent(hPollSet, fPollEvt, - phStdOutR, idPollHnd); - break; - - case VBOXSERVICECTRLPIPEID_STDERR: - rc = VBoxServiceControlThreadHandleOutputEvent(hPollSet, fPollEvt, - phStdErrR, idPollHnd); - break; - - case VBOXSERVICECTRLPIPEID_IPC_NOTIFY: - rc = VBoxServiceControlThreadHandleRequest(hPollSet, fPollEvt, - phStdInW, phStdOutR, phStdErrR, pThread); - break; - } - - if (RT_FAILURE(rc) || rc == VINF_EOF) - break; /* Abort command, or client dead or something. */ - - if (RT_UNLIKELY(pThread->fShutdown)) - break; /* We were asked to shutdown. */ - - continue; - } - -#if 0 - VBoxServiceVerbose(4, "[PID %u]: Polling done, pollRC=%Rrc, pollCnt=%u, rc=%Rrc, fShutdown=%RTbool\n", - pThread->uPID, rc2, RTPollSetGetCount(hPollSet), rc, pThread->fShutdown); -#endif - /* - * Check for process death. - */ - if (fProcessAlive) - { - rc2 = RTProcWaitNoResume(hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus); - if (RT_SUCCESS_NP(rc2)) - { - fProcessAlive = false; - continue; - } - 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 - && *phStdOutR == NIL_RTPIPE - && *phStdErrR == NIL_RTPIPE) - break; - - /* - * Check for timed out, killing the process. - */ - uint32_t cMilliesLeft = RT_INDEFINITE_WAIT; - if (cMsTimeout != RT_INDEFINITE_WAIT) - { - uint64_t u64Now = RTTimeMilliTS(); - uint64_t cMsElapsed = u64Now - MsStart; - if (cMsElapsed >= cMsTimeout) - { - VBoxServiceVerbose(3, "[PID %u]: Timed out (%ums elapsed > %ums timeout), killing ...", - pThread->uPID, cMsElapsed, cMsTimeout); - - fProcessTimedOut = true; - if ( MsProcessKilled == UINT64_MAX - || u64Now - MsProcessKilled > 1000) - { - if (u64Now - MsProcessKilled > 20*60*1000) - break; /* Give up after 20 mins. */ - RTProcTerminate(hProcess); - MsProcessKilled = u64Now; - continue; - } - cMilliesLeft = 10000; - } - else - cMilliesLeft = cMsTimeout - (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; - - /* - * Need to exit? - */ - if (pThread->fShutdown) - break; - } - - rc2 = RTCritSectEnter(&pThread->CritSect); - if (RT_SUCCESS(rc2)) - { - ASMAtomicXchgBool(&pThread->fShutdown, true); - - rc2 = RTCritSectLeave(&pThread->CritSect); - AssertRC(rc2); - } - - /* - * Try kill the process if it's still alive at this point. - */ - if (fProcessAlive) - { - if (MsProcessKilled == UINT64_MAX) - { - VBoxServiceVerbose(3, "[PID %u]: Is still alive and not killed yet\n", - pThread->uPID); - - MsProcessKilled = RTTimeMilliTS(); - RTProcTerminate(hProcess); - RTThreadSleep(500); - } - - for (size_t i = 0; i < 10; i++) - { - VBoxServiceVerbose(4, "[PID %u]: Kill attempt %d/10: Waiting to exit ...\n", - pThread->uPID, i + 1); - rc2 = RTProcWait(hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus); - if (RT_SUCCESS(rc2)) - { - VBoxServiceVerbose(4, "[PID %u]: Kill attempt %d/10: Exited\n", - pThread->uPID, i + 1); - fProcessAlive = false; - break; - } - if (i >= 5) - { - VBoxServiceVerbose(4, "[PID %u]: Kill attempt %d/10: Trying to terminate ...\n", - pThread->uPID, i + 1); - RTProcTerminate(hProcess); - } - RTThreadSleep(i >= 5 ? 2000 : 500); - } - - if (fProcessAlive) - VBoxServiceVerbose(3, "[PID %u]: Could not be killed\n", pThread->uPID); - } - - /* - * 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 uFlags = 0; - - if ( fProcessTimedOut && !fProcessAlive && MsProcessKilled != UINT64_MAX) - { - VBoxServiceVerbose(3, "[PID %u]: Timed out and got killed\n", - pThread->uPID); - uStatus = PROC_STS_TOK; - } - else if (fProcessTimedOut && fProcessAlive && MsProcessKilled != UINT64_MAX) - { - VBoxServiceVerbose(3, "[PID %u]: Timed out and did *not* get killed\n", - pThread->uPID); - uStatus = PROC_STS_TOA; - } - else if (pThread->fShutdown && (fProcessAlive || MsProcessKilled != UINT64_MAX)) - { - VBoxServiceVerbose(3, "[PID %u]: Got terminated because system/service is about to shutdown\n", - pThread->uPID); - uStatus = PROC_STS_DWN; /* Service is stopping, process was killed. */ - uFlags = pThread->uFlags; /* Return handed-in execution flags back to the host. */ - } - else if (fProcessAlive) - { - VBoxServiceError("[PID %u]: Is alive when it should not!\n", - pThread->uPID); - } - else if (MsProcessKilled != UINT64_MAX) - { - VBoxServiceError("[PID %u]: Has been killed when it should not!\n", - pThread->uPID); - } - else if (ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL) - { - VBoxServiceVerbose(3, "[PID %u]: Ended with RTPROCEXITREASON_NORMAL (Exit code: %u)\n", - pThread->uPID, ProcessStatus.iStatus); - - uStatus = PROC_STS_TEN; - uFlags = ProcessStatus.iStatus; - } - else if (ProcessStatus.enmReason == RTPROCEXITREASON_SIGNAL) - { - VBoxServiceVerbose(3, "[PID %u]: Ended with RTPROCEXITREASON_SIGNAL (Signal: %u)\n", - pThread->uPID, ProcessStatus.iStatus); - - uStatus = PROC_STS_TES; - uFlags = ProcessStatus.iStatus; - } - else if (ProcessStatus.enmReason == RTPROCEXITREASON_ABEND) - { - /* ProcessStatus.iStatus will be undefined. */ - VBoxServiceVerbose(3, "[PID %u]: Ended with RTPROCEXITREASON_ABEND\n", - pThread->uPID); - - uStatus = PROC_STS_TEA; - uFlags = ProcessStatus.iStatus; - } - else - VBoxServiceVerbose(1, "[PID %u]: Handling process status %u not implemented\n", - pThread->uPID, ProcessStatus.enmReason); - - VBoxServiceVerbose(2, "[PID %u]: Ended, ClientID=%u, CID=%u, Status=%u, Flags=0x%x\n", - pThread->uPID, pThread->uClientID, pThread->uContextID, uStatus, uFlags); - rc = VbglR3GuestCtrlExecReportStatus(pThread->uClientID, pThread->uContextID, - pThread->uPID, uStatus, uFlags, - NULL /* pvData */, 0 /* cbData */); - if (RT_FAILURE(rc)) - VBoxServiceError("[PID %u]: Error reporting final status to host; rc=%Rrc\n", - pThread->uPID, rc); - - VBoxServiceVerbose(3, "[PID %u]: Process loop ended with rc=%Rrc\n", - pThread->uPID, rc); - } - else - VBoxServiceError("[PID %u]: Loop failed with rc=%Rrc\n", - pThread->uPID, rc); - return rc; -} - - -/** - * 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 vboxServiceControlThreadInitPipe(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; -} - - -/** - * Allocates a guest thread request with the specified request data. - * - * @return IPRT status code. - * @param ppReq Pointer that will receive the newly allocated request. - * Must be freed later with VBoxServiceControlThreadRequestFree(). - * @param enmType Request type. - * @param pbData Payload data, based on request type. - * @param cbData Size of payload data (in bytes). - * @param uCID Context ID to which this request belongs to. - */ -int VBoxServiceControlThreadRequestAllocEx(PVBOXSERVICECTRLREQUEST *ppReq, - VBOXSERVICECTRLREQUESTTYPE enmType, - void* pbData, - size_t cbData, - uint32_t uCID) -{ - AssertPtrReturn(ppReq, VERR_INVALID_POINTER); - - PVBOXSERVICECTRLREQUEST pReq = (PVBOXSERVICECTRLREQUEST) - RTMemAlloc(sizeof(VBOXSERVICECTRLREQUEST)); - AssertPtrReturn(pReq, VERR_NO_MEMORY); - - RT_ZERO(*pReq); - pReq->enmType = enmType; - pReq->uCID = uCID; - pReq->cbData = cbData; - pReq->pvData = (uint8_t*)pbData; - - /* Set request result to some defined state in case - * it got cancelled. */ - pReq->rc = VERR_CANCELLED; - - int rc = RTSemEventMultiCreate(&pReq->Event); - AssertRC(rc); - - if (RT_SUCCESS(rc)) - { - *ppReq = pReq; - return VINF_SUCCESS; - } - - RTMemFree(pReq); - return rc; -} - - -/** - * Allocates a guest thread request with the specified request data. - * - * @return IPRT status code. - * @param ppReq Pointer that will receive the newly allocated request. - * Must be freed later with VBoxServiceControlThreadRequestFree(). - * @param enmType Request type. - */ -int VBoxServiceControlThreadRequestAlloc(PVBOXSERVICECTRLREQUEST *ppReq, - VBOXSERVICECTRLREQUESTTYPE enmType) -{ - return VBoxServiceControlThreadRequestAllocEx(ppReq, enmType, - NULL /* pvData */, 0 /* cbData */, - 0 /* ContextID */); -} - - -/** - * Cancels a previously fired off guest thread request. - * - * Note: Does *not* do locking since VBoxServiceControlThreadRequestWait() - * holds the lock (critsect); so only trigger the signal; the owner - * needs to clean up afterwards. - * - * @return IPRT status code. - * @param pReq Request to cancel. - */ -static int vboxServiceControlThreadRequestCancel(PVBOXSERVICECTRLREQUEST pReq) -{ - if (!pReq) /* Silently skip non-initialized requests. */ - return VINF_SUCCESS; - - VBoxServiceVerbose(4, "Cancelling request=0x%p\n", pReq); - - return RTSemEventMultiSignal(pReq->Event); -} - - -/** - * Frees a formerly allocated guest thread request. - * - * @return IPRT status code. - * @param pReq Request to free. - */ -void VBoxServiceControlThreadRequestFree(PVBOXSERVICECTRLREQUEST pReq) -{ - AssertPtrReturnVoid(pReq); - - VBoxServiceVerbose(4, "Freeing request=0x%p (event=%RTsem)\n", - pReq, &pReq->Event); - - int rc = RTSemEventMultiDestroy(pReq->Event); - AssertRC(rc); - - RTMemFree(pReq); - pReq = NULL; -} - - -/** - * Waits for a guest thread's event to get triggered. - * - * @return IPRT status code. - * @param pReq Request to wait for. - */ -int VBoxServiceControlThreadRequestWait(PVBOXSERVICECTRLREQUEST pReq) -{ - AssertPtrReturn(pReq, VERR_INVALID_POINTER); - - /* Wait on the request to get completed (or we are asked to abort/shutdown). */ - int rc = RTSemEventMultiWait(pReq->Event, RT_INDEFINITE_WAIT); - if (RT_SUCCESS(rc)) - { - VBoxServiceVerbose(4, "Performed request with rc=%Rrc, cbData=%u\n", - pReq->rc, pReq->cbData); - - /* Give back overall request result. */ - rc = pReq->rc; - } - else - VBoxServiceError("Waiting for request failed, rc=%Rrc\n", rc); - - return rc; -} - - -/** - * 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 VBoxServiceControlThreadSetupPipe(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 VBoxServiceControlThreadMakeFullPath(const char *pszPath, char *pszExpanded, size_t cbExpanded) -{ - int rc = VINF_SUCCESS; -#ifdef RT_OS_WINDOWS - if (!ExpandEnvironmentStrings(pszPath, pszExpanded, cbExpanded)) - rc = RTErrConvertFromWin32(GetLastError()); -#else - /* No expansion for non-Windows yet. */ - rc = RTStrCopy(pszExpanded, cbExpanded, pszPath); -#endif -#ifdef DEBUG - VBoxServiceVerbose(3, "VBoxServiceControlExecMakeFullPath: %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 VBoxServiceControlThreadResolveExecutable(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 = VBoxServiceControlThreadMakeFullPath(szPathToResolve, pszResolved, cbResolved); - if (RT_SUCCESS(rc)) - VBoxServiceVerbose(3, "Looked up executable: %s -> %s\n", - pszFileName, pszResolved); - } - - if (RT_FAILURE(rc)) - VBoxServiceError("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 ppapszArgv Pointer to a pointer with the new argv command line. - * Needs to be freed with RTGetOptArgvFree. - */ -static int VBoxServiceControlThreadAllocateArgv(const char *pszArgv0, - const char * const *papszArgs, - bool fExpandArgs, char ***ppapszArgv) -{ - AssertPtrReturn(ppapszArgv, VERR_INVALID_POINTER); - - VBoxServiceVerbose(3, "VBoxServiceControlThreadPrepareArgv: pszArgv0=%p, papszArgs=%p, fExpandArgs=%RTbool, ppapszArgv=%p\n", - pszArgv0, papszArgs, fExpandArgs, 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 - VBoxServiceVerbose(3, "VBoxServiceControlThreadPrepareArgv: cbSize=%RU32, cArgs=%RU32\n", - cbSize, cArgs); -#endif - - size_t i = 0; /* Keep the argument counter in scope for cleaning up on failure. */ - - rc = RTStrDupEx(&papszNewArgv[0], pszArgv0); - if (RT_SUCCESS(rc)) - { - for (; i < cArgs; i++) - { - char *pszArg; -#if 0 /* Arguments expansion -- untested. */ - if (fExpandArgs) - { - /* 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; - } - } - - if (RT_FAILURE(rc)) - { - for (i; i > 0; i--) - RTStrFree(papszNewArgv[i]); - RTMemFree(papszNewArgv); - } - - return rc; -} - - -void VBoxServiceControlThreadFreeArgv(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 phProcess Pointer which will receive the process handle after - * successful process start. - */ -static int VBoxServiceControlThreadCreateProcess(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags, - PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr, const char *pszAsUser, - const char *pszPassword, PRTPROCESS phProcess) -{ - AssertPtrReturn(pszExec, VERR_INVALID_PARAMETER); - AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER); - AssertPtrReturn(phProcess, VERR_INVALID_PARAMETER); - - int rc = VINF_SUCCESS; - char szExecExp[RTPATH_MAX]; - - /* Do we need to expand environment variables in arguments? */ - bool fExpandArgs = (fFlags & EXECUTEPROCESSFLAG_EXPAND_ARGUMENTS) ? true : false; - -#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"; - - /* - * 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); - if ( GetVersionEx((LPOSVERSIONINFO) &OSInfoEx) - && OSInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT - && OSInfoEx.dwMajorVersion >= 6 /* Vista or later */) - { - rc = RTEnvGetEx(RTENV_DEFAULT, "windir", szSysprepCmd, sizeof(szSysprepCmd), NULL); - if (RT_SUCCESS(rc)) - rc = RTPathAppend(szSysprepCmd, sizeof(szSysprepCmd), "system32\\sysprep\\sysprep.exe"); - } - - if (RT_SUCCESS(rc)) - { - char **papszArgsExp; - rc = VBoxServiceControlThreadAllocateArgv(szSysprepCmd /* argv0 */, papszArgs, - fExpandArgs, &papszArgsExp); - if (RT_SUCCESS(rc)) - { - rc = RTProcCreateEx(szSysprepCmd, papszArgsExp, hEnv, 0 /* fFlags */, - phStdIn, phStdOut, phStdErr, NULL /* pszAsUser */, - NULL /* pszPassword */, phProcess); - VBoxServiceControlThreadFreeArgv(papszArgsExp); - } - } - - if (RT_FAILURE(rc)) - VBoxServiceVerbose(3, "Starting sysprep returned rc=%Rrc\n", rc); - - return rc; - } -#endif /* RT_OS_WINDOWS */ - -#ifdef 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 = VBoxServiceControlThreadResolveExecutable(VBOXSERVICE_NAME, szExecExp, sizeof(szExecExp)); - } - else - { -#endif - /* - * Do the environment variables expansion on executable and arguments. - */ - rc = VBoxServiceControlThreadResolveExecutable(pszExec, szExecExp, sizeof(szExecExp)); -#ifdef VBOXSERVICE_TOOLBOX - } -#endif - if (RT_SUCCESS(rc)) - { - char **papszArgsExp; - rc = VBoxServiceControlThreadAllocateArgv(pszExec /* Always use the unmodified executable name as argv0. */, - papszArgs /* Append the rest of the argument vector (if any). */, - fExpandArgs, &papszArgsExp); - if (RT_FAILURE(rc)) - { - /* Don't print any arguments -- may contain passwords or other sensible data! */ - VBoxServiceError("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_NO_PROFILE) - uProcFlags |= RTPROC_FLAGS_NO_PROFILE; - } - - /* 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) - uProcFlags |= RTPROC_FLAGS_SERVICE; -#ifdef DEBUG - VBoxServiceVerbose(3, "Command: %s\n", szExecExp); - for (size_t i = 0; papszArgsExp[i]; i++) - VBoxServiceVerbose(3, "\targv[%ld]: %s\n", i, papszArgsExp[i]); -#endif - VBoxServiceVerbose(3, "Starting process \"%s\" ...\n", szExecExp); - - /* Do normal execution. */ - rc = RTProcCreateEx(szExecExp, papszArgsExp, hEnv, uProcFlags, - phStdIn, phStdOut, phStdErr, - *pszAsUser ? pszAsUser : NULL, - *pszPassword ? pszPassword : NULL, - phProcess); - - VBoxServiceVerbose(3, "Starting process \"%s\" returned rc=%Rrc\n", - szExecExp, rc); - - VBoxServiceControlThreadFreeArgv(papszArgsExp); - } - } - return rc; -} - -/** - * The actual worker routine (loop) for a started guest process. - * - * @return IPRT status code. - * @param PVBOXSERVICECTRLTHREAD Thread data associated with a started process. - */ -static int VBoxServiceControlThreadProcessWorker(PVBOXSERVICECTRLTHREAD pThread) -{ - AssertPtrReturn(pThread, VERR_INVALID_POINTER); - VBoxServiceVerbose(3, "Thread of process pThread=0x%p = \"%s\" started\n", - pThread, pThread->pszCmd); - - int rc = VBoxServiceControlListSet(VBOXSERVICECTRLTHREADLIST_RUNNING, pThread); - AssertRC(rc); - - rc = VbglR3GuestCtrlConnect(&pThread->uClientID); - if (RT_FAILURE(rc)) - { - VBoxServiceError("Thread failed to connect to the guest control service, aborted! Error: %Rrc\n", rc); - RTThreadUserSignal(RTThreadSelf()); - return rc; - } - VBoxServiceVerbose(3, "Guest process \"%s\" got client ID=%u, flags=0x%x\n", - pThread->pszCmd, pThread->uClientID, pThread->uFlags); - - bool fSignalled = false; /* Indicator whether we signalled the thread user event already. */ - - /* - * Create the environment. - */ - RTENV hEnv; - rc = RTEnvClone(&hEnv, RTENV_DEFAULT); - if (RT_SUCCESS(rc)) - { - size_t i; - for (i = 0; i < pThread->uNumEnvVars && pThread->papszEnv; i++) - { - rc = RTEnvPutEx(hEnv, pThread->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 = VBoxServiceControlThreadSetupPipe("|", 0 /*STDIN_FILENO*/, - &hStdIn, &phStdIn, &pThread->pipeStdInW); - if (RT_SUCCESS(rc)) - { - RTHANDLE hStdOut; - PRTHANDLE phStdOut; - RTPIPE pipeStdOutR; - rc = VBoxServiceControlThreadSetupPipe( (pThread->uFlags & EXECUTEPROCESSFLAG_WAIT_STDOUT) - ? "|" : "/dev/null", - 1 /*STDOUT_FILENO*/, - &hStdOut, &phStdOut, &pipeStdOutR); - if (RT_SUCCESS(rc)) - { - RTHANDLE hStdErr; - PRTHANDLE phStdErr; - RTPIPE pipeStdErrR; - rc = VBoxServiceControlThreadSetupPipe( (pThread->uFlags & EXECUTEPROCESSFLAG_WAIT_STDERR) - ? "|" : "/dev/null", - 2 /*STDERR_FILENO*/, - &hStdErr, &phStdErr, &pipeStdErrR); - if (RT_SUCCESS(rc)) - { - /* - * Create a poll set for the pipes and let the - * transport layer add stuff to it as well. - */ - RTPOLLSET hPollSet; - rc = RTPollSetCreate(&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(hPollSet, pThread->pipeStdInW, RTPOLL_EVT_ERROR, VBOXSERVICECTRLPIPEID_STDIN); - /* Stdout. */ - if (RT_SUCCESS(rc)) - rc = RTPollSetAddPipe(hPollSet, pipeStdOutR, uFlags, VBOXSERVICECTRLPIPEID_STDOUT); - /* Stderr. */ - if (RT_SUCCESS(rc)) - rc = RTPollSetAddPipe(hPollSet, pipeStdErrR, uFlags, VBOXSERVICECTRLPIPEID_STDERR); - /* IPC notification pipe. */ - if (RT_SUCCESS(rc)) - rc = RTPipeCreate(&pThread->hNotificationPipeR, &pThread->hNotificationPipeW, 0 /* Flags */); - if (RT_SUCCESS(rc)) - rc = RTPollSetAddPipe(hPollSet, pThread->hNotificationPipeR, RTPOLL_EVT_READ, VBOXSERVICECTRLPIPEID_IPC_NOTIFY); - - if (RT_SUCCESS(rc)) - { - RTPROCESS hProcess; - rc = VBoxServiceControlThreadCreateProcess(pThread->pszCmd, pThread->papszArgs, hEnv, pThread->uFlags, - phStdIn, phStdOut, phStdErr, - pThread->pszUser, pThread->pszPassword, - &hProcess); - if (RT_FAILURE(rc)) - VBoxServiceError("Error starting process, rc=%Rrc\n", rc); - /* - * Tell the control thread that it can continue - * spawning services. 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 loop. */ - rc = VBoxServiceControlThreadProcLoop(pThread, - hProcess, pThread->uTimeLimitMS, hPollSet, - &pThread->pipeStdInW, &pipeStdOutR, &pipeStdErrR); - - /* - * The handles that are no longer in the set have - * been closed by the above call in order to prevent - * the guest from getting stuck accessing them. - * So, NIL the handles to avoid closing them again. - */ - if (RT_FAILURE(RTPollSetQueryHandle(hPollSet, VBOXSERVICECTRLPIPEID_IPC_NOTIFY, NULL))) - { - pThread->hNotificationPipeR = NIL_RTPIPE; - pThread->hNotificationPipeW = NIL_RTPIPE; - } - if (RT_FAILURE(RTPollSetQueryHandle(hPollSet, VBOXSERVICECTRLPIPEID_STDERR, NULL))) - pipeStdErrR = NIL_RTPIPE; - if (RT_FAILURE(RTPollSetQueryHandle(hPollSet, VBOXSERVICECTRLPIPEID_STDOUT, NULL))) - pipeStdOutR = NIL_RTPIPE; - if (RT_FAILURE(RTPollSetQueryHandle(hPollSet, VBOXSERVICECTRLPIPEID_STDIN, NULL))) - pThread->pipeStdInW = NIL_RTPIPE; - } - } - RTPollSetDestroy(hPollSet); - - RTPipeClose(pThread->hNotificationPipeR); - pThread->hNotificationPipeR = NIL_RTPIPE; - RTPipeClose(pThread->hNotificationPipeW); - pThread->hNotificationPipeW = NIL_RTPIPE; - } - RTPipeClose(pipeStdErrR); - pipeStdErrR = NIL_RTPIPE; - RTHandleClose(phStdErr); - if (phStdErr) - RTHandleClose(phStdErr); - } - RTPipeClose(pipeStdOutR); - pipeStdOutR = NIL_RTPIPE; - RTHandleClose(&hStdOut); - if (phStdOut) - RTHandleClose(phStdOut); - } - RTPipeClose(pThread->pipeStdInW); - pThread->pipeStdInW = NIL_RTPIPE; - RTHandleClose(phStdIn); - } - } - RTEnvDestroy(hEnv); - } - - /* Move thread to stopped thread list. */ - int rc2 = VBoxServiceControlListSet(VBOXSERVICECTRLTHREADLIST_STOPPED, pThread); - AssertRC(rc2); - - if (pThread->uClientID) - { - if (RT_FAILURE(rc)) - { - rc2 = VbglR3GuestCtrlExecReportStatus(pThread->uClientID, pThread->uContextID, pThread->uPID, - PROC_STS_ERROR, rc, - NULL /* pvData */, 0 /* cbData */); - if (RT_FAILURE(rc2)) - VBoxServiceError("Could not report process failure error; rc=%Rrc (process error %Rrc)\n", - rc2, rc); - } - - VBoxServiceVerbose(3, "[PID %u]: Cancelling pending host requests (client ID=%u)\n", - pThread->uPID, pThread->uClientID); - rc2 = VbglR3GuestCtrlCancelPendingWaits(pThread->uClientID); - if (RT_FAILURE(rc2)) - { - VBoxServiceError("[PID %u]: Cancelling pending host requests failed; rc=%Rrc\n", - pThread->uPID, rc2); - if (RT_SUCCESS(rc)) - rc = rc2; - } - - /* Disconnect from guest control service. */ - VBoxServiceVerbose(3, "[PID %u]: Disconnecting (client ID=%u) ...\n", - pThread->uPID, pThread->uClientID); - VbglR3GuestCtrlDisconnect(pThread->uClientID); - pThread->uClientID = 0; - } - - VBoxServiceVerbose(3, "[PID %u]: Thread of process \"%s\" ended with rc=%Rrc\n", - pThread->uPID, pThread->pszCmd, rc); - - /* Update started/stopped status. */ - ASMAtomicXchgBool(&pThread->fStopped, true); - ASMAtomicXchgBool(&pThread->fStarted, false); - - /* - * 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()); - - return rc; -} - - -/** - * Thread main routine for a started process. - * - * @return IPRT status code. - * @param RTTHREAD Pointer to the thread's data. - * @param void* User-supplied argument pointer. - * - */ -static DECLCALLBACK(int) VBoxServiceControlThread(RTTHREAD ThreadSelf, void *pvUser) -{ - PVBOXSERVICECTRLTHREAD pThread = (VBOXSERVICECTRLTHREAD*)pvUser; - AssertPtrReturn(pThread, VERR_INVALID_POINTER); - return VBoxServiceControlThreadProcessWorker(pThread); -} - - -/** - * 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 uContextID Context ID to associate the process to start with. - * @param pProcess Process info. - */ -int VBoxServiceControlThreadStart(uint32_t uContextID, - PVBOXSERVICECTRLPROCESS pProcess) -{ - AssertPtrReturn(pProcess, VERR_INVALID_POINTER); - - /* - * Allocate new thread data and assign it to our thread list. - */ - PVBOXSERVICECTRLTHREAD pThread = (PVBOXSERVICECTRLTHREAD)RTMemAlloc(sizeof(VBOXSERVICECTRLTHREAD)); - if (!pThread) - return VERR_NO_MEMORY; - - int rc = gstsvcCntlExecThreadInit(pThread, pProcess, uContextID); - if (RT_SUCCESS(rc)) - { - static uint32_t s_uCtrlExecThread = 0; - if (s_uCtrlExecThread++ == UINT32_MAX) - s_uCtrlExecThread = 0; /* Wrap around to not let IPRT freak out. */ - rc = RTThreadCreateF(&pThread->Thread, VBoxServiceControlThread, - pThread /*pvUser*/, 0 /*cbStack*/, - RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctl%u", s_uCtrlExecThread); - if (RT_FAILURE(rc)) - { - VBoxServiceError("RTThreadCreate failed, rc=%Rrc\n, pThread=%p\n", - rc, pThread); - } - else - { - VBoxServiceVerbose(4, "Waiting for thread to initialize ...\n"); - - /* Wait for the thread to initialize. */ - rc = RTThreadUserWait(pThread->Thread, 60 * 1000 /* 60 seconds max. */); - AssertRC(rc); - if ( ASMAtomicReadBool(&pThread->fShutdown) - || RT_FAILURE(rc)) - { - VBoxServiceError("Thread for process \"%s\" failed to start, rc=%Rrc\n", - pProcess->szCmd, rc); - } - else - { - ASMAtomicXchgBool(&pThread->fStarted, true); - } - } - } - - if (RT_FAILURE(rc)) - RTMemFree(pThread); - - return rc; -} - - -/** - * Performs a request to a specific (formerly started) guest process and waits - * for its response. - * - * @return IPRT status code. - * @param uPID PID of guest process to perform a request to. - * @param pRequest Pointer to request to perform. - */ -int VBoxServiceControlThreadPerform(uint32_t uPID, PVBOXSERVICECTRLREQUEST pRequest) -{ - AssertPtrReturn(pRequest, VERR_INVALID_POINTER); - AssertReturn(pRequest->enmType > VBOXSERVICECTRLREQUEST_UNKNOWN, VERR_INVALID_PARAMETER); - /* Rest in pRequest is optional (based on the request type). */ - - int rc = VINF_SUCCESS; - PVBOXSERVICECTRLTHREAD pThread = VBoxServiceControlLockThread(uPID); - if (pThread) - { - if (ASMAtomicReadBool(&pThread->fShutdown)) - { - rc = VERR_CANCELLED; - } - else - { - /* Set request structure pointer. */ - pThread->pRequest = pRequest; - - /** @todo To speed up simultaneous guest process handling we could add a worker threads - * or queue in order to wait for the request to happen. Later. */ - /* Wake up guest thrad by sending a wakeup byte to the notification pipe so - * that RTPoll unblocks (returns) and we then can do our requested operation. */ - Assert(pThread->hNotificationPipeW != NIL_RTPIPE); - size_t cbWritten; - if (RT_SUCCESS(rc)) - rc = RTPipeWrite(pThread->hNotificationPipeW, "i", 1, &cbWritten); - - if ( RT_SUCCESS(rc) - && cbWritten) - { - VBoxServiceVerbose(3, "[PID %u]: Waiting for response on enmType=%u, pvData=0x%p, cbData=%u\n", - uPID, pRequest->enmType, pRequest->pvData, pRequest->cbData); - - rc = VBoxServiceControlThreadRequestWait(pRequest); - } - } - - VBoxServiceControlUnlockThread(pThread); - } - else /* PID not found! */ - rc = VERR_NOT_FOUND; - - VBoxServiceVerbose(3, "[PID %u]: Performed enmType=%u, uCID=%u, pvData=0x%p, cbData=%u, rc=%Rrc\n", - uPID, pRequest->enmType, pRequest->uCID, pRequest->pvData, pRequest->cbData, rc); - return rc; -} - diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp index 53dc1675..ba3a64cf 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2010 Oracle Corporation + * Copyright (C) 2010-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -120,6 +120,18 @@ SYSFSCPUPATH g_aAcpiCpuPath[] = /** 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. + */ +const char *g_apszTopologyPath[] = +{ + "sysdev", + "physical_node" +}; #endif #ifdef RT_OS_LINUX @@ -277,10 +289,25 @@ static int VBoxServiceCpuHotPlugGetACPIDevicePath(char **ppszPath, uint32_t idCp if (iLvlCurr == RT_ELEMENTS(g_aAcpiCpuPath) - 1) { /* Get the sysdev */ - uint32_t idCore = RTLinuxSysFsReadIntFile(10, "%s/sysdev/topology/core_id", - pszPathCurr); - uint32_t idPackage = RTLinuxSysFsReadIntFile(10, "%s/sysdev/topology/physical_package_id", - pszPathCurr); + uint32_t idCore = 0; + uint32_t idPackage = 0; + + for (unsigned i = 0; i < RT_ELEMENTS(g_apszTopologyPath); i++) + { + int64_t i64Core = RTLinuxSysFsReadIntFile(10, "%s/%s/topology/core_id", + pszPathCurr, g_apszTopologyPath[i]); + int64_t i64Package = RTLinuxSysFsReadIntFile(10, "%s/%s/topology/physical_package_id", + pszPathCurr, g_apszTopologyPath[i]); + + if ( i64Core != -1 + && i64Package != -1) + { + idCore = (uint32_t)i64Core; + idPackage = (uint32_t)i64Package; + break; + } + } + if ( idCore == idCpuCore && idPackage == idCpuPackage) { diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h b/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h index 6a21cad9..a3f09fb6 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2007-2012 Oracle Corporation + * Copyright (C) 2007-2013 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -107,213 +107,7 @@ typedef VBOXSERVICE const *PCVBOXSERVICE; # define STATUS_SUCCESS ((NTSTATUS)0x00000000L) #endif /* RT_OS_WINDOWS */ -#ifdef VBOX_WITH_GUEST_CONTROL -/** - * 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; - -/** - * Request types to perform on a started guest process. - */ -typedef enum VBOXSERVICECTRLREQUESTTYPE -{ - /** Unknown request. */ - VBOXSERVICECTRLREQUEST_UNKNOWN = 0, - /** Main control thread asked used to quit. */ - VBOXSERVICECTRLREQUEST_QUIT = 1, - /** Performs reading from stdout. */ - VBOXSERVICECTRLREQUEST_STDOUT_READ = 50, - /** Performs reading from stderr. */ - VBOXSERVICECTRLREQUEST_STDERR_READ = 60, - /** Performs writing to stdin. */ - VBOXSERVICECTRLREQUEST_STDIN_WRITE = 70, - /** Same as VBOXSERVICECTRLREQUEST_STDIN_WRITE, but - * marks the end of input. */ - VBOXSERVICECTRLREQUEST_STDIN_WRITE_EOF = 71, - /** Kill/terminate process. - * @todo Implement this! */ - VBOXSERVICECTRLREQUEST_KILL = 90, - /** Gently ask process to terminate. - * @todo Implement this! */ - VBOXSERVICECTRLREQUEST_HANGUP = 91, - /** Ask the process in which status it - * currently is. - * @todo Implement this! */ - VBOXSERVICECTRLREQUEST_STATUS = 100 -} VBOXSERVICECTRLREQUESTTYPE; - -/** - * Thread list types. - */ -typedef enum VBOXSERVICECTRLTHREADLISTTYPE -{ - /** Unknown list -- uncool to use. */ - VBOXSERVICECTRLTHREADLIST_UNKNOWN = 0, - /** Stopped list: Here all guest threads end up - * when they reached the stopped state and can - * be shut down / free'd safely. */ - VBOXSERVICECTRLTHREADLIST_STOPPED = 1, - /** - * Started list: Here all threads are registered - * when they're up and running (that is, accepting - * commands). - */ - VBOXSERVICECTRLTHREADLIST_RUNNING = 2 -} VBOXSERVICECTRLTHREADLISTTYPE; - -/** - * Structure to perform a request on a started guest - * process. Needed for letting the main guest control thread - * to communicate (and wait) for a certain operation which - * will be done in context of the started guest process thread. - */ -typedef struct VBOXSERVICECTRLREQUEST -{ - /** Event semaphore to serialize access. */ - RTSEMEVENTMULTI Event; - /** The request type to handle. */ - VBOXSERVICECTRLREQUESTTYPE enmType; - /** Payload size; on input, this contains the (maximum) amount - * of data the caller wants to write or to read. On output, - * this show the actual amount of data read/written. */ - size_t cbData; - /** Payload data; a pre-allocated data buffer for input/output. */ - void *pvData; - /** The context ID which is required to complete the - * request. Not used at the moment. */ - uint32_t uCID; - /** The overall result of the operation. */ - int rc; -} VBOXSERVICECTRLREQUEST; -/** Pointer to request. */ -typedef VBOXSERVICECTRLREQUEST *PVBOXSERVICECTRLREQUEST; - -/** - * Structure holding information for starting a guest - * process. - */ -typedef struct VBOXSERVICECTRLPROCESS -{ - /** 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]; - /** Time limit (in ms) of the process' life time. */ - uint32_t uTimeLimitMS; -} VBOXSERVICECTRLPROCESS; -/** Pointer to a guest process block. */ -typedef VBOXSERVICECTRLPROCESS *PVBOXSERVICECTRLPROCESS; - -/** - * Structure for holding data for one (started) guest process. - */ -typedef struct VBOXSERVICECTRLTHREAD -{ - /** Pointer to list archor of following - * list node. - * @todo Would be nice to have a RTListGetAnchor(). */ - PRTLISTANCHOR pAnchor; - /** Node. */ - RTLISTNODE Node; - /** The worker thread. */ - RTTHREAD Thread; - /** 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 service was started or not. */ - bool fStarted; - /** Client ID. */ - uint32_t uClientID; - /** Context ID. */ - uint32_t uContextID; - /** Critical section for thread-safe use. */ - RTCRITSECT CritSect; - /** @todo Document me! */ - uint32_t uPID; - char *pszCmd; - uint32_t uFlags; - char **papszArgs; - uint32_t uNumArgs; - char **papszEnv; - uint32_t uNumEnvVars; - /** Name of specified user account to run the - * guest process under. */ - char *pszUser; - /** Password of specified user account. */ - char *pszPassword; - /** Overall time limit (in ms) that the guest process - * is allowed to run. 0 for indefinite time. */ - uint32_t uTimeLimitMS; - /** Pointer to the current IPC request being - * processed. */ - PVBOXSERVICECTRLREQUEST pRequest; - /** StdIn pipe for addressing writes to the - * guest process' stdin.*/ - RTPIPE pipeStdInW; - /** The notification pipe associated with this guest process. - * This is NIL_RTPIPE for output pipes. */ - RTPIPE hNotificationPipeW; - /** The other end of hNotificationPipeW. */ - RTPIPE hNotificationPipeR; -} VBOXSERVICECTRLTHREAD; -/** Pointer to thread data. */ -typedef VBOXSERVICECTRLTHREAD *PVBOXSERVICECTRLTHREAD; - -/** - * 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. */ - 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; -#endif /* VBOX_WITH_GUEST_CONTROL */ #ifdef VBOX_WITH_GUEST_PROPS - /** * A guest property cache. */ @@ -358,6 +152,7 @@ RT_C_DECLS_BEGIN extern char *g_pszProgName; extern int g_cVerbosity; +extern char g_szLogFile[RTPATH_MAX + 128]; extern uint32_t g_DefaultInterval; extern VBOXSERVICE g_TimeSync; extern VBOXSERVICE g_Clipboard; @@ -368,7 +163,7 @@ extern VBOXSERVICE g_CpuHotPlug; extern VBOXSERVICE g_MemBalloon; extern VBOXSERVICE g_VMStatistics; #endif -#ifdef VBOX_WITH_PAGE_SHARING +#ifdef VBOXSERVICE_PAGE_SHARING extern VBOXSERVICE g_PageSharing; #endif #ifdef VBOX_WITH_SHARED_FOLDERS @@ -378,64 +173,37 @@ extern VBOXSERVICE g_AutoMount; extern RTCRITSECT g_csLog; /* For guest process stdout dumping. */ #endif -extern RTEXITCODE VBoxServiceSyntax(const char *pszFormat, ...); -extern RTEXITCODE VBoxServiceError(const char *pszFormat, ...); -extern void VBoxServiceVerbose(int iLevel, const char *pszFormat, ...); -extern int VBoxServiceArgUInt32(int argc, char **argv, const char *psz, int *pi, uint32_t *pu32, - uint32_t u32Min, uint32_t u32Max); -extern int VBoxServiceStartServices(void); -extern int VBoxServiceStopServices(void); -extern void VBoxServiceMainWait(void); -extern int VBoxServiceReportStatus(VBoxGuestFacilityStatus enmStatus); +extern RTEXITCODE VBoxServiceSyntax(const char *pszFormat, ...); +extern RTEXITCODE VBoxServiceError(const char *pszFormat, ...); +extern void VBoxServiceVerbose(int iLevel, const char *pszFormat, ...); +extern int VBoxServiceArgUInt32(int argc, char **argv, const char *psz, int *pi, uint32_t *pu32, + uint32_t u32Min, uint32_t u32Max); +extern int VBoxServiceStartServices(void); +extern int VBoxServiceStopServices(void); +extern void VBoxServiceMainWait(void); +extern int VBoxServiceReportStatus(VBoxGuestFacilityStatus enmStatus); #ifdef RT_OS_WINDOWS -extern RTEXITCODE VBoxServiceWinInstall(void); -extern RTEXITCODE VBoxServiceWinUninstall(void); -extern RTEXITCODE VBoxServiceWinEnterCtrlDispatcher(void); -extern void VBoxServiceWinSetStopPendingStatus(uint32_t uCheckPoint); +extern RTEXITCODE VBoxServiceWinInstall(void); +extern RTEXITCODE VBoxServiceWinUninstall(void); +extern RTEXITCODE VBoxServiceWinEnterCtrlDispatcher(void); +extern void VBoxServiceWinSetStopPendingStatus(uint32_t uCheckPoint); #endif #ifdef VBOXSERVICE_TOOLBOX -extern bool VBoxServiceToolboxMain(int argc, char **argv, RTEXITCODE *prcExit); +extern bool VBoxServiceToolboxMain(int argc, char **argv, RTEXITCODE *prcExit); #endif #ifdef RT_OS_WINDOWS # ifdef VBOX_WITH_GUEST_PROPS -extern int VBoxServiceVMInfoWinWriteUsers(char **ppszUserList, uint32_t *pcUsersInList); -extern int VBoxServiceWinGetComponentVersions(uint32_t uiClientID); +extern int VBoxServiceVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, char **ppszUserList, uint32_t *pcUsersInList); +extern int VBoxServiceWinGetComponentVersions(uint32_t uiClientID); # endif /* VBOX_WITH_GUEST_PROPS */ #endif /* RT_OS_WINDOWS */ -#ifdef VBOX_WITH_GUEST_CONTROL -/* Guest control main thread functions. */ -extern int VBoxServiceControlAssignPID(PVBOXSERVICECTRLTHREAD pThread, uint32_t uPID); -extern int VBoxServiceControlListSet(VBOXSERVICECTRLTHREADLISTTYPE enmList, - PVBOXSERVICECTRLTHREAD pThread); -extern PVBOXSERVICECTRLTHREAD VBoxServiceControlLockThread(uint32_t uPID); -extern void VBoxServiceControlUnlockThread(const PVBOXSERVICECTRLTHREAD pThread); -extern int VBoxServiceControlSetInactive(PVBOXSERVICECTRLTHREAD pThread); -/* Per-thread guest process functions. */ -extern int VBoxServiceControlThreadStart(uint32_t uContext, - PVBOXSERVICECTRLPROCESS pProcess); -extern int VBoxServiceControlThreadPerform(uint32_t uPID, PVBOXSERVICECTRLREQUEST pRequest); -extern int VBoxServiceControlThreadStop(const PVBOXSERVICECTRLTHREAD pThread); -extern int VBoxServiceControlThreadWait(const PVBOXSERVICECTRLTHREAD pThread, - RTMSINTERVAL msTimeout, int *prc); -extern int VBoxServiceControlThreadFree(PVBOXSERVICECTRLTHREAD pThread); -/* Request handling. */ -extern int VBoxServiceControlThreadRequestAlloc(PVBOXSERVICECTRLREQUEST *ppReq, - VBOXSERVICECTRLREQUESTTYPE enmType); -extern int VBoxServiceControlThreadRequestAllocEx(PVBOXSERVICECTRLREQUEST *ppReq, - VBOXSERVICECTRLREQUESTTYPE enmType, - void* pbData, - size_t cbData, - uint32_t uCID); -extern void VBoxServiceControlThreadRequestFree(PVBOXSERVICECTRLREQUEST pReq); -#endif /* VBOX_WITH_GUEST_CONTROL */ - #ifdef VBOXSERVICE_MANAGEMENT extern uint32_t VBoxServiceBalloonQueryPages(uint32_t cbPage); #endif -#if defined(VBOX_WITH_PAGE_SHARING) && defined(RT_OS_WINDOWS) +#if defined(VBOXSERVICE_PAGE_SHARING) extern RTEXITCODE VBoxServicePageSharingInitFork(void); #endif extern int VBoxServiceVMInfoSignal(void); diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp b/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp index 39c9912d..c954d5c9 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2006-2011 Oracle Corporation + * Copyright (C) 2006-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -23,6 +23,7 @@ #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> @@ -80,8 +81,7 @@ typedef struct _RTL_PROCESS_MODULES } RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES; typedef NTSTATUS (WINAPI *PFNZWQUERYSYSTEMINFORMATION)(ULONG, PVOID, ULONG, PULONG); -static PFNZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL; -static HMODULE hNtdll = 0; +static PFNZWQUERYSYSTEMINFORMATION g_pfnZwQuerySystemInformation = NULL; static DECLCALLBACK(int) VBoxServicePageSharingEmptyTreeCallback(PAVLPVNODECORE pNode, void *pvUser); @@ -365,13 +365,13 @@ void VBoxServicePageSharingInspectGuest() CloseHandle(hSnapshot); /* Check all loaded kernel modules. */ - if (ZwQuerySystemInformation) + if (g_pfnZwQuerySystemInformation) { ULONG cbBuffer = 0; PVOID pBuffer = NULL; PRTL_PROCESS_MODULES pSystemModules; - NTSTATUS ret = ZwQuerySystemInformation(SystemModuleInformation, (PVOID)&cbBuffer, 0, &cbBuffer); + NTSTATUS ret = g_pfnZwQuerySystemInformation(SystemModuleInformation, (PVOID)&cbBuffer, 0, &cbBuffer); if (!cbBuffer) { VBoxServiceVerbose(1, "ZwQuerySystemInformation returned length 0\n"); @@ -382,7 +382,7 @@ void VBoxServicePageSharingInspectGuest() if (!pBuffer) goto skipkernelmodules; - ret = ZwQuerySystemInformation(SystemModuleInformation, pBuffer, cbBuffer, &cbBuffer); + ret = g_pfnZwQuerySystemInformation(SystemModuleInformation, pBuffer, cbBuffer, &cbBuffer); if (ret != STATUS_SUCCESS) { VBoxServiceVerbose(1, "ZwQuerySystemInformation returned %x (1)\n", ret); @@ -552,12 +552,9 @@ static DECLCALLBACK(int) VBoxServicePageSharingInit(void) AssertRCReturn(rc, rc); #if defined(RT_OS_WINDOWS) && !defined(TARGET_NT4) - hNtdll = LoadLibrary("ntdll.dll"); + g_pfnZwQuerySystemInformation = (PFNZWQUERYSYSTEMINFORMATION)RTLdrGetSystemSymbol("ntdll.dll", "ZwQuerySystemInformation"); - if (hNtdll) - ZwQuerySystemInformation = (PFNZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll, "ZwQuerySystemInformation"); - - rc = VbglR3GetSessionId(&g_idSession); + rc = VbglR3GetSessionId(&g_idSession); if (RT_FAILURE(rc)) { if (rc == VERR_IO_GEN_FAILURE) @@ -695,7 +692,7 @@ DECLCALLBACK(int) VBoxServicePageSharingWorkerProcess(bool volatile *pfShutdown) { char const *papszArgs[3]; papszArgs[0] = pszExeName; - papszArgs[1] = "--pagefusionfork"; + papszArgs[1] = "pagefusion"; papszArgs[2] = NULL; rc = RTProcCreate(pszExeName, papszArgs, RTENV_DEFAULT, 0 /* normal child */, &hProcess); if (RT_FAILURE(rc)) @@ -737,12 +734,6 @@ DECLCALLBACK(int) VBoxServicePageSharingWorkerProcess(bool volatile *pfShutdown) static DECLCALLBACK(void) VBoxServicePageSharingTerm(void) { VBoxServiceVerbose(3, "VBoxServicePageSharingTerm\n"); - -#if defined(RT_OS_WINDOWS) && !defined(TARGET_NT4) - if (hNtdll) - FreeLibrary(hNtdll); -#endif - return; } diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp index 347faa95..1e691c95 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2010 Oracle Corporation + * Copyright (C) 2010-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -38,8 +38,9 @@ PVBOXSERVICEVEPROPCACHEENTRY vboxServicePropCacheInsertEntryInternal(PVBOXSERVIC /** @todo Docs */ PVBOXSERVICEVEPROPCACHEENTRY vboxServicePropCacheFindInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, uint32_t uFlags) { - AssertPtr(pCache); - AssertPtr(pszName); + 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 @@ -65,11 +66,18 @@ PVBOXSERVICEVEPROPCACHEENTRY vboxServicePropCacheFindInternal(PVBOXSERVICEVEPROP /** @todo Docs */ PVBOXSERVICEVEPROPCACHEENTRY vboxServicePropCacheInsertEntryInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName) { - AssertPtr(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; @@ -88,7 +96,8 @@ PVBOXSERVICEVEPROPCACHEENTRY vboxServicePropCacheInsertEntryInternal(PVBOXSERVIC /** @todo Docs */ int vboxServicePropCacheWritePropF(uint32_t u32ClientId, const char *pszName, uint32_t fFlags, const char *pszValueFormat, ...) { - AssertPtr(pszName); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + int rc; if (pszValueFormat != NULL) { @@ -141,7 +150,7 @@ int vboxServicePropCacheWritePropF(uint32_t u32ClientId, const char *pszName, ui */ int VBoxServicePropCacheCreate(PVBOXSERVICEVEPROPCACHE pCache, uint32_t uClientId) { - AssertPtr(pCache); + AssertPtrReturn(pCache, VERR_INVALID_POINTER); /** @todo Prevent init the cache twice! * r=bird: Use a magic. */ RTListInit(&pCache->NodeHead); @@ -164,8 +173,8 @@ int VBoxServicePropCacheCreate(PVBOXSERVICEVEPROPCACHE pCache, uint32_t uClientI int VBoxServicePropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, uint32_t fFlags, const char *pszValueReset) { - AssertPtr(pCache); - AssertPtr(pszName); + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); PVBOXSERVICEVEPROPCACHEENTRY pNode = vboxServicePropCacheFindInternal(pCache, pszName, 0); if (pNode == NULL) pNode = vboxServicePropCacheInsertEntryInternal(pCache, pszName); @@ -182,6 +191,7 @@ int VBoxServicePropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, if (pNode->pszValueReset) RTStrFree(pNode->pszValueReset); pNode->pszValueReset = RTStrDup(pszValueReset); + AssertPtr(pNode->pszValueReset); } rc = RTCritSectLeave(&pCache->CritSect); } @@ -205,9 +215,10 @@ int VBoxServicePropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, */ int VBoxServicePropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, const char *pszValueFormat, ...) { - AssertPtr(pCache); + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + Assert(pCache->uClientID); - AssertPtr(pszName); /* * Format the value first. @@ -250,8 +261,15 @@ int VBoxServicePropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszNa { /* Write the update. */ rc = vboxServicePropCacheWritePropF(pCache->uClientID, pNode->pszName, pNode->fFlags, pszValue); - RTStrFree(pNode->pszValue); - pNode->pszValue = RTStrDup(pszValue); + VBoxServiceVerbose(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. */ @@ -261,11 +279,16 @@ int VBoxServicePropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszNa /* No value specified. Deletion (or no action required). */ if (pNode->pszValue) /* Did we have a value before? Then the value needs to be deleted. */ { - /* Delete property (but do not remove from cache) if not deleted yet. */ - RTStrFree(pNode->pszValue); - pNode->pszValue = NULL; rc = vboxServicePropCacheWritePropF(pCache->uClientID, pNode->pszName, 0, /* Flags */ NULL /* Value */); + VBoxServiceVerbose(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. */ @@ -275,6 +298,9 @@ int VBoxServicePropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszNa RTCritSectLeave(&pCache->CritSect); } + VBoxServiceVerbose(4, "[PropCache %p]: Updating \"%s\" resulted in rc=%Rrc\n", + pCache, pszName, rc); + /* Delete temp stuff. */ RTStrFree(pszValue); return rc; @@ -295,8 +321,9 @@ int VBoxServicePropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszNa */ int VBoxServicePropCacheUpdateByPath(PVBOXSERVICEVEPROPCACHE pCache, const char *pszValue, uint32_t fFlags, const char *pszPathFormat, ...) { - AssertPtr(pCache); - AssertPtr(pszPathFormat); + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszPathFormat, VERR_INVALID_POINTER); + int rc = VERR_NOT_FOUND; PVBOXSERVICEVEPROPCACHEENTRY pNodeIt = NULL; if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect))) @@ -341,7 +368,8 @@ int VBoxServicePropCacheUpdateByPath(PVBOXSERVICEVEPROPCACHE pCache, const char */ int VBoxServicePropCacheFlush(PVBOXSERVICEVEPROPCACHE pCache) { - AssertPtr(pCache); + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + int rc = VINF_SUCCESS; PVBOXSERVICEVEPROPCACHEENTRY pNodeIt = NULL; if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect))) @@ -366,7 +394,7 @@ int VBoxServicePropCacheFlush(PVBOXSERVICEVEPROPCACHE pCache) */ void VBoxServicePropCacheDestroy(PVBOXSERVICEVEPROPCACHE pCache) { - AssertPtr(pCache); + AssertPtrReturnVoid(pCache); Assert(pCache->uClientID); /* Lock the cache. */ diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h index 430f9a7d..eb3ac648 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h +++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2010 Oracle Corporation + * Copyright (C) 2010-2011 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h b/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h index cf932cbe..60296c55 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2006-2007 Oracle Corporation + * Copyright (C) 2006-2010 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp index c7c259ae..8083e84a 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2006-2010 Oracle Corporation + * Copyright (C) 2006-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -43,6 +43,7 @@ #include <iprt/assert.h> #include <iprt/mem.h> +#include <iprt/ldr.h> #include <VBox/param.h> #include <iprt/semaphore.h> #include <iprt/string.h> @@ -123,45 +124,32 @@ static DECLCALLBACK(int) VBoxServiceVMStatsInit(void) VBoxServiceVerbose(3, "VBoxStatsInit: DeviceIoControl failed with %d\n", rc); #ifdef RT_OS_WINDOWS - /** @todo Use RTLdr instead of LoadLibrary/GetProcAddress here! */ - - /* NtQuerySystemInformation might be dropped in future releases, so load it dynamically as per Microsoft's recommendation */ - HMODULE hMod = LoadLibrary("NTDLL.DLL"); - if (hMod) + /* NtQuerySystemInformation might be dropped in future releases, so load + it dynamically as per Microsoft's recommendation. */ + *(void **)&gCtx.pfnNtQuerySystemInformation = RTLdrGetSystemSymbol("ntdll.dll", "NtQuerySystemInformation"); + if (gCtx.pfnNtQuerySystemInformation) + VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.pfnNtQuerySystemInformation = %x\n", gCtx.pfnNtQuerySystemInformation); + else { - *(uintptr_t *)&gCtx.pfnNtQuerySystemInformation = (uintptr_t)GetProcAddress(hMod, "NtQuerySystemInformation"); - if (gCtx.pfnNtQuerySystemInformation) - VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.pfnNtQuerySystemInformation = %x\n", gCtx.pfnNtQuerySystemInformation); - else - { - VBoxServiceVerbose(3, "VBoxStatsInit: NTDLL.NtQuerySystemInformation not found!\n"); - return VERR_SERVICE_DISABLED; - } + VBoxServiceVerbose(3, "VBoxStatsInit: ntdll.NtQuerySystemInformation not found!\n"); + return VERR_SERVICE_DISABLED; } /* GlobalMemoryStatus is win2k and up, so load it dynamically */ - hMod = LoadLibrary("KERNEL32.DLL"); - if (hMod) + *(void **)&gCtx.pfnGlobalMemoryStatusEx = RTLdrGetSystemSymbol("kernel32.dll", "GlobalMemoryStatusEx"); + if (gCtx.pfnGlobalMemoryStatusEx) + VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.GlobalMemoryStatusEx = %x\n", gCtx.pfnGlobalMemoryStatusEx); + else { - *(uintptr_t *)&gCtx.pfnGlobalMemoryStatusEx = (uintptr_t)GetProcAddress(hMod, "GlobalMemoryStatusEx"); - if (gCtx.pfnGlobalMemoryStatusEx) - VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.GlobalMemoryStatusEx = %x\n", gCtx.pfnGlobalMemoryStatusEx); - else - { - /** @todo Now fails in NT4; do we care? */ - VBoxServiceVerbose(3, "VBoxStatsInit: KERNEL32.GlobalMemoryStatusEx not found!\n"); - return VERR_SERVICE_DISABLED; - } + /** @todo Now fails in NT4; do we care? */ + VBoxServiceVerbose(3, "VBoxStatsInit: kernel32.GlobalMemoryStatusEx not found!\n"); + return VERR_SERVICE_DISABLED; } + /* GetPerformanceInfo is xp and up, so load it dynamically */ - hMod = LoadLibrary("PSAPI.DLL"); - if (hMod) - { - *(uintptr_t *)&gCtx.pfnGetPerformanceInfo = (uintptr_t)GetProcAddress(hMod, "GetPerformanceInfo"); - if (gCtx.pfnGetPerformanceInfo) - VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.pfnGetPerformanceInfo= %x\n", gCtx.pfnGetPerformanceInfo); - /* failure is not fatal */ - } + *(void **)&gCtx.pfnGetPerformanceInfo = RTLdrGetSystemSymbol("psapi.dll", "GetPerformanceInfo"); + if (gCtx.pfnGetPerformanceInfo) + VBoxServiceVerbose(3, "VBoxStatsInit: gCtx.pfnGetPerformanceInfo= %x\n", gCtx.pfnGetPerformanceInfo); #endif /* RT_OS_WINDOWS */ return VINF_SUCCESS; @@ -354,6 +342,7 @@ static void VBoxServiceVMStatsReport(void) 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 = VBoxServiceBalloonQueryPages(_4K); @@ -464,30 +453,30 @@ static void VBoxServiceVMStatsReport(void) */ uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0; int rc = -1; - kstat_t *pStatPages = kstat_lookup(pStatKern, "unix", 0 /* instance */, "system_pages"); + 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, "pagestotal"); + pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"pagestotal"); if (pStat) u64Total = pStat->value.ul; - pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, "freemem"); + pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"freemem"); if (pStat) u64Free = pStat->value.ul; } } - kstat_t *pStatZFS = kstat_lookup(pStatKern, "zfs", 0 /* instance */, "arcstats"); + 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, "size"); + kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(pStatZFS, (char *)"size"); if (pStat) u64Cached = pStat->value.ul; } @@ -497,14 +486,14 @@ static void VBoxServiceVMStatsReport(void) * 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, "unix", 0 /* instance */, "sysinfo"); + 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, "unix", 0 /* instance */, "vminfo"); + kstat_t *pStatVMInfo = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"vminfo"); if (pStatVMInfo) { vminfo_t VMInfo; @@ -534,6 +523,7 @@ static void VBoxServiceVMStatsReport(void) 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 = VBoxServiceBalloonQueryPages(_4K); @@ -726,3 +716,4 @@ VBOXSERVICE g_VMStatistics = VBoxServiceVMStatsStop, VBoxServiceVMStatsTerm }; + diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp index 3371cb2f..5c9ba83e 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2007 Oracle Corporation + * Copyright (C) 2007-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -410,7 +410,7 @@ static bool VBoxServiceTimeSyncAdjust(PCRTTIMESPEC pDrift) else if (g_cTimeSyncErrors++ < 10) VBoxServiceError("VBoxServiceTimeSyncAdjust: GetSystemTimeAdjustment failed, error=%ld\n", GetLastError()); -#elif defined(RT_OS_OS2) +#elif defined(RT_OS_OS2) || defined(RT_OS_HAIKU) /* No API for doing gradual time adjustments. */ #else /* PORTME */ diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp index 0110dea3..6dbb74bd 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp @@ -632,10 +632,10 @@ static int VBoxServiceToolboxPrintFsInfo(const char *pszName, uint16_t cbName, pObjInfo->Attr.u.Unix.gid, pObjInfo->cbObject, pObjInfo->cbAllocated, - pObjInfo->BirthTime, - pObjInfo->ChangeTime, - pObjInfo->ModificationTime, - pObjInfo->AccessTime); + RTTimeSpecGetNano(&pObjInfo->BirthTime), /** @todo really ns? */ + RTTimeSpecGetNano(&pObjInfo->ChangeTime), /** @todo really ns? */ + RTTimeSpecGetNano(&pObjInfo->ModificationTime), /** @todo really ns? */ + RTTimeSpecGetNano(&pObjInfo->AccessTime)); /** @todo really ns? */ RTPrintf(" %2d %s\n", cbName, pszName); } } @@ -1525,7 +1525,7 @@ static RTEXITCODE VBoxServiceToolboxStat(int argc, char **argv) RTListInit(&fileList); while ( (ch = RTGetOpt(&GetState, &ValueUnion)) - && RT_SUCCESS(rc)) + && RT_SUCCESS(rc)) { /* For options that require an argument, ValueUnion has received the value. */ switch (ch) diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp index 6dcff3a8..9302a843 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2009-2010 Oracle Corporation + * Copyright (C) 2009-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -47,8 +47,12 @@ * @param puTimestamp Where to return the timestamp. This is only set * on success. Optional. */ -int VBoxServiceReadProp(uint32_t u32ClientId, const char *pszPropName, char **ppszValue, char **ppszFlags, uint64_t *puTimestamp) +int VBoxServiceReadProp(uint32_t u32ClientId, const char *pszPropName, + char **ppszValue, char **ppszFlags, uint64_t *puTimestamp) { + AssertPtrReturn(pszPropName, VERR_INVALID_POINTER); + AssertPtrReturn(ppszValue, VERR_INVALID_POINTER); + uint32_t cbBuf = _1K; void *pvBuf = NULL; int rc; @@ -106,7 +110,8 @@ int VBoxServiceReadProp(uint32_t u32ClientId, const char *pszPropName, char **pp break; /* done */ } - RTMemFree(pvBuf); + if (pvBuf) + RTMemFree(pvBuf); return rc; } @@ -121,14 +126,14 @@ int VBoxServiceReadProp(uint32_t u32ClientId, const char *pszPropName, char **pp * @param pu32 Where to store the 32-bit value. * */ -int VBoxServiceReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max) +int VBoxServiceReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, + uint32_t *pu32, uint32_t u32Min, uint32_t u32Max) { char *pszValue; int rc = VBoxServiceReadProp(u32ClientId, pszPropName, &pszValue, - NULL /* ppszFlags */, NULL /* puTimestamp */); + NULL /* ppszFlags */, NULL /* puTimestamp */); if (RT_SUCCESS(rc)) { - AssertPtr(pu32); char *pszNext; rc = RTStrToUInt32Ex(pszValue, &pszNext, 0, pu32); if ( RT_SUCCESS(rc) @@ -144,6 +149,63 @@ int VBoxServiceReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, uin /** + * 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 VBoxServiceReadHostProp(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 = VBoxServiceReadProp(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. * diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h index 7cd6dfc9..69ea9a9f 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2009 Oracle Corporation + * Copyright (C) 2009-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -23,6 +23,7 @@ #ifdef VBOX_WITH_GUEST_PROPS int VBoxServiceReadProp(uint32_t u32ClientId, const char *pszPropName, char **ppszValue, char **ppszFlags, uint64_t *puTimestamp); int VBoxServiceReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max); +int VBoxServiceReadHostProp(uint32_t u32ClientId, const char *pszPropName, bool fReadOnly, char **ppszValue, char **ppszFlags, uint64_t *puTimestamp); int VBoxServiceWritePropF(uint32_t u32ClientId, const char *pszName, const char *pszValueFormat, ...); #endif diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp index a23128a5..e9191a10 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2009-2010 Oracle Corporation + * Copyright (C) 2009-2013 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -24,12 +24,13 @@ # define _WIN32_WINNT 0x0502 /* CachedRemoteInteractive in recent SDKs. */ #endif #include <Windows.h> -#include <wtsapi32.h> /* For WTS* calls. */ -#include <psapi.h> /* EnumProcesses. */ -#include <Ntsecapi.h> /* Needed for process security information. */ +#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/thread.h> #include <iprt/string.h> @@ -39,38 +40,47 @@ #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 +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 number. This + /** Last (highest) session ID. This * is needed for distinguishing old session * process counts from new (current) session * ones. */ - ULONG ulSession; + ULONG ulLastSession; } VBOXSERVICEVMINFOUSER, *PVBOXSERVICEVMINFOUSER; /** Structure for the file information lookup. */ -typedef struct +typedef struct VBOXSERVICEVMINFOFILE { char *pszFilePath; char *pszFileName; } VBOXSERVICEVMINFOFILE, *PVBOXSERVICEVMINFOFILE; /** Structure for process information lookup. */ -typedef struct +typedef struct VBOXSERVICEVMINFOPROC { /** The PID. */ DWORD id; + /** The SID. */ + PSID pSid; /** The LUID. */ LUID luid; /** Interactive process. */ @@ -84,7 +94,8 @@ typedef struct uint32_t VBoxServiceVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs); bool VBoxServiceVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER a_pUserInfo, PLUID a_pSession); int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppProc, DWORD *pdwCount); -void VBoxServiceVMInfoWinProcessesFree(PVBOXSERVICEVMINFOPROC paProcs); +void VBoxServiceVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs); +int vboxServiceVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain); typedef BOOL WINAPI FNQUERYFULLPROCESSIMAGENAME(HANDLE, DWORD, LPTSTR, PDWORD); typedef FNQUERYFULLPROCESSIMAGENAME *PFNQUERYFULLPROCESSIMAGENAME; @@ -92,6 +103,28 @@ typedef FNQUERYFULLPROCESSIMAGENAME *PFNQUERYFULLPROCESSIMAGENAME; #ifndef TARGET_NT4 +static bool vboxServiceVMInfoSession0Separation(void) +{ + /** @todo Only do this once. Later. */ + OSVERSIONINFOEX OSInfoEx; + RT_ZERO(OSInfoEx); + OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + if ( !GetVersionEx((LPOSVERSIONINFO) &OSInfoEx) + || OSInfoEx.dwPlatformId != VER_PLATFORM_WIN32_NT) + { + /* Platform other than NT (e.g. Win9x) not supported. */ + return false; + } + + if ( OSInfoEx.dwMajorVersion >= 6 + && OSInfoEx.dwMinorVersion >= 0) + { + return true; + } + + return false; +} + /** * Retrieves the module name of a given process. * @@ -107,6 +140,7 @@ static int VBoxServiceVMInfoWinProcessesGetModuleName(PVBOXSERVICEVMINFOPROC con AssertPtrReturn(pszName, VERR_INVALID_POINTER); AssertReturn(cbName, VERR_INVALID_PARAMETER); + /** @todo Only do this once. Later. */ OSVERSIONINFOEX OSInfoEx; RT_ZERO(OSInfoEx); OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); @@ -142,20 +176,19 @@ static int VBoxServiceVMInfoWinProcessesGetModuleName(PVBOXSERVICEVMINFOPROC con { /* Loading the module and getting the symbol for each and every process is expensive * -- since this function (at the moment) only is used for debugging purposes it's okay. */ - RTLDRMOD hMod; - rc = RTLdrLoad("kernel32.dll", &hMod); - if (RT_SUCCESS(rc)) + PFNQUERYFULLPROCESSIMAGENAME pfnQueryFullProcessImageName; + pfnQueryFullProcessImageName = (PFNQUERYFULLPROCESSIMAGENAME) + RTLdrGetSystemSymbol("kernel32.dll", "QueryFullProcessImageNameA"); + /** @todo r=bird: WTF don't we query the UNICODE name? */ + if (pfnQueryFullProcessImageName) { - PFNQUERYFULLPROCESSIMAGENAME pfnQueryFullProcessImageName; - rc = RTLdrGetSymbol(hMod, "QueryFullProcessImageNameA", (void **)&pfnQueryFullProcessImageName); - if (RT_SUCCESS(rc)) - { - DWORD dwLen = cbName / sizeof(TCHAR); - if (!pfnQueryFullProcessImageName(h, 0 /*PROCESS_NAME_NATIVE*/, pszName, &dwLen)) - rc = VERR_ACCESS_DENIED; - } - - RTLdrClose(hMod); + /** @todo r=bird: Completely bogus use of TCHAR. + * !!ALL USE OF TCHAR IS HEREWITH BANNED IN ALL VBOX SOURCES!! + * We use WCHAR when talking to windows, everything else is WRONG. (We don't + * want Chinese MBCS being treated as UTF-8.) */ + DWORD dwLen = cbName / sizeof(TCHAR); + if (!pfnQueryFullProcessImageName(h, 0 /*PROCESS_NAME_NATIVE*/, pszName, &dwLen)) + rc = VERR_ACCESS_DENIED; } } else @@ -211,10 +244,13 @@ static int VBoxServiceVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pPro case TokenGroups: dwTokenInfoSize = 0; - /* Allocating will follow in a second step. */ + /* Allocation will follow in a second step. */ break; - /** @todo Implement more token classes here. */ + case TokenUser: + dwTokenInfoSize = 0; + /* Allocation will follow in a second step. */ + break; default: VBoxServiceError("Token class not implemented: %ld", tkClass); @@ -242,6 +278,14 @@ static int VBoxServiceVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pPro 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; @@ -264,6 +308,7 @@ static int VBoxServiceVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pPro 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; @@ -309,6 +354,42 @@ static int VBoxServiceVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pPro 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) + { + VBoxServiceError("Error retrieving SID of process PID=%ld: %ld\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; @@ -383,6 +464,7 @@ int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDW break; } } while (cProcesses <= _32K); /* Should be enough; see: http://blogs.technet.com/markrussinovich/archive/2009/07/08/3261309.aspx */ + if (RT_SUCCESS(rc)) { /* @@ -396,23 +478,22 @@ int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDW for (DWORD i = 0; i < cProcesses; i++) { paProcs[i].id = paPID[i]; - rc = VBoxServiceVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenGroups); - if (RT_FAILURE(rc)) - { - /* Because some processes cannot be opened/parsed on - Windows, we should not consider to be this an error here. */ - rc = VINF_SUCCESS; - } - else - { - rc = VBoxServiceVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenStatistics); - if (RT_FAILURE(rc)) - { - /* Because some processes cannot be opened/parsed on - Windows, we should not consider to be this an error here. */ - rc = VINF_SUCCESS; - } - } + paProcs[i].pSid = NULL; + + int rc2 = VBoxServiceVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenUser); + if (RT_FAILURE(rc2) && g_cVerbosity) + VBoxServiceError("Get token class \"user\" for process %ld failed, rc=%Rrc\n", + paProcs[i].id, rc2); + + rc2 = VBoxServiceVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenGroups); + if (RT_FAILURE(rc2) && g_cVerbosity) + VBoxServiceError("Get token class \"groups\" for process %ld failed, rc=%Rrc\n", + paProcs[i].id, rc2); + + rc2 = VBoxServiceVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenStatistics); + if (RT_FAILURE(rc2) && g_cVerbosity) + VBoxServiceError("Get token class \"statistics\" for process %ld failed, rc=%Rrc\n", + paProcs[i].id, rc2); } /* Save number of processes */ @@ -422,7 +503,7 @@ int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDW *ppaProcs = paProcs; } else - RTMemFree(paProcs); + VBoxServiceVMInfoWinProcessesFree(cProcesses, paProcs); } else rc = VERR_NO_MEMORY; @@ -438,8 +519,17 @@ int VBoxServiceVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDW * * @param paProcs What */ -void VBoxServiceVMInfoWinProcessesFree(PVBOXSERVICEVMINFOPROC paProcs) +void VBoxServiceVMInfoWinProcessesFree(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); } @@ -447,14 +537,14 @@ void VBoxServiceVMInfoWinProcessesFree(PVBOXSERVICEVMINFOPROC paProcs) * Determines whether the specified session has processes on the system. * * @returns Number of processes found for a specified session. - * @param pSession The session. + * @param pSession The current user's SID. * @param paProcs The process snapshot. * @param cProcs The number of processes in the snaphot. * @param puSession Looked up session number. Optional. */ uint32_t VBoxServiceVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs, - PULONG puSession) + PULONG puTerminalSession) { if (!pSession) { @@ -466,10 +556,20 @@ uint32_t VBoxServiceVMInfoWinSessionHasProcesses(PLUID pSession, NTSTATUS rcNt = LsaGetLogonSessionData(pSession, &pSessionData); if (rcNt != STATUS_SUCCESS) { - VBoxServiceError("Could not get logon session data! rcNt=%#x", rcNt); + VBoxServiceError("Could not get logon session data! rcNt=%#x\n", rcNt); return 0; } + if (!IsValidSid(pSessionData->Sid)) + { + VBoxServiceError("User SID=%p is not valid\n", pSessionData->Sid); + if (pSessionData) + LsaFreeReturnBuffer(pSessionData); + return 0; + } + + int rc = VINF_SUCCESS; + /* * 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 @@ -478,40 +578,35 @@ uint32_t VBoxServiceVMInfoWinSessionHasProcesses(PLUID pSession, uint32_t cNumProcs = 0; for (DWORD i = 0; i < cProcs; i++) { - VBoxServiceVerbose(4, "PID=%ld: (Interactive: %RTbool) %ld:%ld <-> %ld:%ld\n", - paProcs[i].id, paProcs[i].fInteractive, - paProcs[i].luid.HighPart, paProcs[i].luid.LowPart, - pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart); if (g_cVerbosity) { TCHAR szModule[_1K]; - int rc2 = VBoxServiceVMInfoWinProcessesGetModuleName(&paProcs[i], szModule, sizeof(szModule)); - if (RT_SUCCESS(rc2)) + rc = VBoxServiceVMInfoWinProcessesGetModuleName(&paProcs[i], szModule, sizeof(szModule)); + if (RT_SUCCESS(rc)) VBoxServiceVerbose(4, "PID=%ld: %s\n", paProcs[i].id, szModule); } - if ( paProcs[i].fInteractive - && ( paProcs[i].luid.HighPart == pSessionData->LogonId.HighPart - && paProcs[i].luid.LowPart == pSessionData->LogonId.LowPart)) + PSID pProcSID = paProcs[i].pSid; + if ( RT_SUCCESS(rc) + && pProcSID + && IsValidSid(pProcSID)) { - cNumProcs++; - if (!g_cVerbosity) /* We want a bit more info on higher verbosity. */ - break; + if ( EqualSid(pSessionData->Sid, paProcs[i].pSid) + && paProcs[i].fInteractive) + { + cNumProcs++; + if (!g_cVerbosity) /* We want a bit more info on higher verbosity. */ + break; + } } } - if (g_cVerbosity) - VBoxServiceVerbose(3, "Session %u has %u processes total\n", - pSessionData->Session, cNumProcs); - else - VBoxServiceVerbose(3, "Session %u has at least one process\n", - pSessionData->Session); - - if (puSession) - *puSession = pSessionData->Session; + if (puTerminalSession) + *puTerminalSession = pSessionData->Session; LsaFreeReturnBuffer(pSessionData); + return cNumProcs; } @@ -586,12 +681,25 @@ bool VBoxServiceVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER pUserInfo, PLUID pSes return false; } - VBoxServiceVerbose(3, "Session data: Name=%ls, Session=%u, LogonID=%ld,%ld, LogonType=%ld\n", + VBoxServiceVerbose(3, "Session data: Name=%ls, SessionID=%RU32, LogonID=%ld,%ld, LogonType=%ld\n", pSessionData->UserName.Buffer, pSessionData->Session, pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart, pSessionData->LogonType); + if (vboxServiceVMInfoSession0Separation()) + { + /* 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) + { + LsaFreeReturnBuffer(pSessionData); + return false; + } + } + /* * Only handle users which can login interactively or logged in * remotely over native RDP. @@ -651,55 +759,90 @@ bool VBoxServiceVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER pUserInfo, PLUID pSes pSessionData->LogonId.LowPart, pUserInfo->wszAuthenticationPackage, pUserInfo->wszLogonDomain); - /* Detect RDP sessions as well. */ - LPTSTR pBuffer = NULL; - DWORD cbRet = 0; - int iState = -1; - if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, - pSessionData->Session, - WTSConnectState, - &pBuffer, - &cbRet)) + /** + * 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) { - if (cbRet) - iState = *pBuffer; - VBoxServiceVerbose(3, "Account User=%ls, WTSConnectState=%d (%ld)\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 Only do this once. Later. */ + OSVERSIONINFOEX OSInfoEx; + RT_ZERO(OSInfoEx); + OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + /* Skip RDP detection on non-NT systems. */ + if ( !GetVersionEx((LPOSVERSIONINFO) &OSInfoEx) + || OSInfoEx.dwPlatformId != VER_PLATFORM_WIN32_NT) { - /** @todo On Vista and W2K, always "old" user name are still - * there. Filter out the old one! */ - VBoxServiceVerbose(3, "Account User=%ls using TCS/RDP, state=%d \n", - pUserInfo->wszUser, iState); - fFoundUser = true; + s_fSkipRDPDetection = true; } - if (pBuffer) - WTSFreeMemory(pBuffer); + /* 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 ( OSInfoEx.dwMajorVersion == 5 + && OSInfoEx.dwMinorVersion == 0) + { + s_fSkipRDPDetection = true; + } + + if (s_fSkipRDPDetection) + VBoxServiceVerbose(0, "Detection of logged-in users via RDP is disabled\n"); } - else + + if (!s_fSkipRDPDetection) { - DWORD dwLastErr = GetLastError(); - switch (dwLastErr) + /* Detect RDP sessions as well. */ + LPTSTR pBuffer = NULL; + DWORD cbRet = 0; + int iState = -1; + if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, + pSessionData->Session, + WTSConnectState, + &pBuffer, + &cbRet)) { - /* - * 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: - VBoxServiceVerbose(3, "No WinStation found for user=%ls\n", - pUserInfo->wszUser); - break; - - default: - VBoxServiceVerbose(3, "Cannot query WTS connection state for user=%ls, error=%ld\n", - pUserInfo->wszUser, dwLastErr); - break; + if (cbRet) + iState = *pBuffer; + VBoxServiceVerbose(3, "Account User=%ls, WTSConnectState=%d (%ld)\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! */ + VBoxServiceVerbose(3, "Account User=%ls using TCS/RDP, state=%d \n", + pUserInfo->wszUser, iState); + fFoundUser = true; + } + if (pBuffer) + WTSFreeMemory(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: + VBoxServiceVerbose(3, "No WinStation found for user=%ls\n", + pUserInfo->wszUser); + break; + + default: + VBoxServiceVerbose(3, "Cannot query WTS connection state for user=%ls, error=%ld\n", + pUserInfo->wszUser, dwLastErr); + break; + } - fFoundUser = true; + fFoundUser = true; + } } } else @@ -711,27 +854,163 @@ bool VBoxServiceVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER pUserInfo, PLUID pSes pUserInfo->wszUser, fFoundUser ? "is" : "is not"); } + if (fFoundUser) + pUserInfo->ulLastSession = pSessionData->Session; + LsaFreeReturnBuffer(pSessionData); return fFoundUser; } +static int vboxServiceVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, + const char *pszUser, const char *pszDomain) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszUser, VERR_INVALID_POINTER); + /* pszDomain is optional. */ + + int rc = VINF_SUCCESS; + + char szPipeName[255]; + if (RTStrPrintf(szPipeName, sizeof(szPipeName), "%s%s", + VBOXTRAY_IPC_PIPE_PREFIX, pszUser)) + { + bool fReportToHost = false; + VBoxGuestUserState userState = VBoxGuestUserState_Unknown; + + RTLOCALIPCSESSION hSession; + rc = RTLocalIpcSessionConnect(&hSession, szPipeName, 0 /* Flags */); + if (RT_SUCCESS(rc)) + { + VBOXTRAYIPCHEADER ipcHdr = { VBOXTRAY_IPC_HDR_MAGIC, 0 /* Header version */, + VBOXTRAYIPCMSGTYPE_USERLASTINPUT, 0 /* No msg */ }; + + rc = RTLocalIpcSessionWrite(hSession, &ipcHdr, sizeof(ipcHdr)); + + VBOXTRAYIPCRES_USERLASTINPUT ipcRes; + if (RT_SUCCESS(rc)) + 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 = vboxServiceUserUpdateF(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; + VBoxServiceVerbose(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 = vboxServiceUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", + "%RU32", ipcRes.uLastInputMs); + else + rc = vboxServiceUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", + NULL /* Delete property */); + + if (RT_SUCCESS(rc)) +#endif + } +#ifdef DEBUG + else if (ipcRes.uLastInput == UINT32_MAX) + VBoxServiceVerbose(4, "Last input for user \"%s\" is not supported, skipping\n", + pszUser, rc); + + VBoxServiceVerbose(4, "Getting last input for user \"%s\" ended with rc=%Rrc\n", + pszUser, rc); +#endif + int rc2 = RTLocalIpcSessionClose(hSession); + if (RT_SUCCESS(rc)) + 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. */ + VBoxServiceVerbose(4, "VBoxTray for user \"%s\" not running (anymore), no last input available\n", + pszUser); + + /* Overwrite rc from above. */ + rc = vboxServiceUserUpdateF(pCache, pszUser, pszDomain, + "UsageState", "Idle"); + + fReportToHost = rc == VINF_SUCCESS; + if (fReportToHost) + userState = VBoxGuestUserState_Idle; + break; + } + + default: + VBoxServiceError("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)) + VBoxServiceError("Error reporting usage state %ld 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 pCachce 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 VBoxServiceVMInfoWinWriteUsers(char **ppszUserList, uint32_t *pcUsersInList) +int VBoxServiceVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, + char **ppszUserList, uint32_t *pcUsersInList) { - PLUID paSessions = NULL; - ULONG cSessions = 0; + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(ppszUserList, VERR_INVALID_POINTER); + AssertPtrReturn(pcUsersInList, VERR_INVALID_POINTER); + + int rc2 = VbglR3GuestPropConnect(&s_uDebugGuestPropClientID); + AssertRC(rc2); + + 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 = LsaEnumerateLogonSessions(&cSessions, &paSessions); if (rcNt != STATUS_SUCCESS) { @@ -750,10 +1029,13 @@ int VBoxServiceVMInfoWinWriteUsers(char **ppszUserList, uint32_t *pcUsersInList) break; default: - VBoxServiceError("LsaEnumerate failed with error %u\n", ulError); + VBoxServiceError("LsaEnumerate failed with error %RU32\n", ulError); break; } + if (paSessions) + LsaFreeReturnBuffer(paSessions); + return RTErrConvertFromWin32(ulError); } VBoxServiceVerbose(3, "Found %ld sessions\n", cSessions); @@ -777,56 +1059,73 @@ int VBoxServiceVMInfoWinWriteUsers(char **ppszUserList, uint32_t *pcUsersInList) else { ULONG cUniqueUsers = 0; + + /** + * Note: The cSessions loop variable does *not* correlate with + * the Windows session ID! + */ for (ULONG i = 0; i < cSessions; i++) { - VBoxServiceVerbose(3, "Handling session %u\n", i); + VBoxServiceVerbose(3, "Handling session %RU32 (of %RU32)\n", i + 1, cSessions); - VBOXSERVICEVMINFOUSER UserInfo; - if (VBoxServiceVMInfoWinIsLoggedIn(&UserInfo, &paSessions[i])) + VBOXSERVICEVMINFOUSER userSession; + if (VBoxServiceVMInfoWinIsLoggedIn(&userSession, &paSessions[i])) { - VBoxServiceVerbose(4, "Handling user=%ls, domain=%ls, package=%ls\n", - UserInfo.wszUser, UserInfo.wszLogonDomain, UserInfo.wszAuthenticationPackage); + VBoxServiceVerbose(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. */ - ULONG ulSession; - uint32_t cSessionProcs = VBoxServiceVMInfoWinSessionHasProcesses(&paSessions[i], paProcs, cProcs, &ulSession); + uint32_t cCurSessionProcs = VBoxServiceVMInfoWinSessionHasProcesses(&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); + VBoxServiceWritePropF(s_uDebugGuestPropClientID, szDebugSessionPath, + "#%RU32: cSessionProcs=%RU32 (of %RU32 procs total)", s_uDebugIter, cCurSessionProcs, cProcs); + } + bool fFoundUser = false; - for (ULONG i = 0; i < cUniqueUsers; i++) + for (ULONG a = 0; a < cUniqueUsers; a++) { - if ( !wcscmp(UserInfo.wszUser, pUserInfo[i].wszUser) - && !wcscmp(UserInfo.wszLogonDomain, pUserInfo[i].wszLogonDomain) - && !wcscmp(UserInfo.wszAuthenticationPackage, pUserInfo[i].wszAuthenticationPackage) - && cSessionProcs) + 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 (ulSession > pUserInfo[i].ulSession) + if (userSession.ulLastSession > pCurUser->ulLastSession) { - VBoxServiceVerbose(4, "Updating user=%ls to %u processes (last session: %u)\n", - UserInfo.wszUser, cSessionProcs, ulSession); + VBoxServiceVerbose(4, "Updating user=%ls to %u processes (last used session: %RU32)\n", + pCurUser->wszUser, cCurSessionProcs, userSession.ulLastSession); - pUserInfo[i].ulNumProcs = cSessionProcs; - pUserInfo[i].ulSession = ulSession; + if (!cCurSessionProcs) + VBoxServiceVerbose(3, "Stale session for user=%ls detected! Processes: %RU32 -> %RU32, Session: %RU32 -> %RU32\n", + pCurUser->wszUser, + pCurUser->ulNumProcs, cCurSessionProcs, + pCurUser->ulLastSession, userSession.ulLastSession); - if (!cSessionProcs) - VBoxServiceVerbose(3, "Stale session for user=%ls detected! Old processes: %u, new: %u\n", - pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, cSessionProcs); + 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 (pUserInfo[i].ulSession == ulSession) + else if (pCurUser->ulLastSession == userSession.ulLastSession) { - VBoxServiceVerbose(4, "Adding %u processes to user=%ls (session %u)\n", - cSessionProcs, UserInfo.wszUser, ulSession); + VBoxServiceVerbose(4, "Updating processes for user=%ls (old procs=%RU32, new procs=%RU32, session=%RU32)\n", + pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs, pCurUser->ulLastSession); - pUserInfo[i].ulNumProcs += cSessionProcs; - pUserInfo[i].ulSession = ulSession; + pCurUser->ulNumProcs = cCurSessionProcs; } fFoundUser = true; @@ -836,55 +1135,93 @@ int VBoxServiceVMInfoWinWriteUsers(char **ppszUserList, uint32_t *pcUsersInList) if (!fFoundUser) { - VBoxServiceVerbose(4, "Adding new user=%ls (session %u) with %u processes\n", - UserInfo.wszUser, ulSession, cSessionProcs); + VBoxServiceVerbose(4, "Adding new user=%ls (session=%RU32) with %RU32 processes\n", + userSession.wszUser, userSession.ulLastSession, cCurSessionProcs); - memcpy(&pUserInfo[cUniqueUsers], &UserInfo, sizeof(VBOXSERVICEVMINFOUSER)); - pUserInfo[cUniqueUsers].ulNumProcs = cSessionProcs; - pUserInfo[cUniqueUsers].ulSession = ulSession; + memcpy(&pUserInfo[cUniqueUsers], &userSession, sizeof(VBOXSERVICEVMINFOUSER)); + pUserInfo[cUniqueUsers].ulNumProcs = cCurSessionProcs; cUniqueUsers++; Assert(cUniqueUsers <= cSessions); } } } + if (g_cVerbosity > 3) + VBoxServiceWritePropF(s_uDebugGuestPropClientID, "/VirtualBox/GuestInfo/Debug/LSA", + "#%RU32: cSessions=%RU32, cProcs=%RU32, cUniqueUsers=%RU32", + s_uDebugIter, cSessions, cProcs, cUniqueUsers); + VBoxServiceVerbose(3, "Found %u unique logged-in user(s)\n", cUniqueUsers); - *pcUsersInList = 0; 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); + VBoxServiceWritePropF(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) { - VBoxServiceVerbose(3, "User %ls has %ld processes (session %u)\n", - pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, pUserInfo[i].ulSession); + VBoxServiceVerbose(3, "User \"%ls\" has %RU32 interactive processes (session=%RU32)\n", + pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, pUserInfo[i].ulLastSession); - if (*pcUsersInList > 0) + if (cUsersInList > 0) { - rc = RTStrAAppend(ppszUserList, ","); - AssertRCBreakStmt(rc, RTStrFree(*ppszUserList)); + rc = RTStrAAppend(&pszUserList, ","); + AssertRCBreakStmt(rc, RTStrFree(pszUserList)); } - *pcUsersInList += 1; + cUsersInList += 1; - char *pszTemp; - int rc2 = RTUtf16ToUtf8(pUserInfo[i].wszUser, &pszTemp); - if (RT_SUCCESS(rc2)) + 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)) { - rc = RTStrAAppend(ppszUserList, pszTemp); - RTMemFree(pszTemp); + /* Append user to users list. */ + rc = RTStrAAppend(&pszUserList, pszUser); + + /* Do idle detection. */ + if (RT_SUCCESS(rc)) + rc = vboxServiceVMInfoWinWriteLastInput(pCache, pszUser, pszDomain); } else - rc = RTStrAAppend(ppszUserList, "<string-conversion-error>"); - AssertRCBreakStmt(rc, RTStrFree(*ppszUserList)); + rc = RTStrAAppend(&pszUserList, "<string-conversion-error>"); + + RTStrFree(pszUser); + RTStrFree(pszDomain); + + AssertRCBreakStmt(rc, RTStrFree(pszUserList)); } } RTMemFree(pUserInfo); } - VBoxServiceVMInfoWinProcessesFree(paProcs); + VBoxServiceVMInfoWinProcessesFree(cProcs, paProcs); } - LsaFreeReturnBuffer(paSessions); + if (paSessions) + LsaFreeReturnBuffer(paSessions); + + if (RT_SUCCESS(rc)) + { + *ppszUserList = pszUserList; + *pcUsersInList = cUsersInList; + } + + s_uDebugIter++; + VbglR3GuestPropDisconnect(s_uDebugGuestPropClientID); + return rc; } @@ -918,10 +1255,16 @@ int VBoxServiceWinGetComponentVersions(uint32_t uClientID) { szSysDir, "VBoxTray.exe" }, { szSysDir, "VBoxGINA.dll" }, { szSysDir, "VBoxCredProv.dll" }, +# ifdef VBOX_WITH_MMR + { szSysDir, "VBoxMMR.exe" }, +# endif /* VBOX_WITH_MMR */ /* 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 +# ifdef VBOX_WITH_MMR + { szSysWowDir, "VBoxMMRHook.dll" }, +# endif /* VBOX_WITH_MMR */ { szSysWowDir, "VBoxOGLarrayspu.dll" }, { szSysWowDir, "VBoxOGLcrutil.dll" }, { szSysWowDir, "VBoxOGLerrorspu.dll" }, @@ -930,6 +1273,9 @@ int VBoxServiceWinGetComponentVersions(uint32_t uClientID) { szSysWowDir, "VBoxOGLfeedbackspu.dll" }, { szSysWowDir, "VBoxOGL.dll" }, # else /* !RT_ARCH_AMD64 */ +# ifdef VBOX_WITH_MMR + { szSysDir, "VBoxMMRHook.dll" }, +# endif /* VBOX_WITH_MMR */ { szSysDir, "VBoxOGLarrayspu.dll" }, { szSysDir, "VBoxOGLcrutil.dll" }, { szSysDir, "VBoxOGLerrorspu.dll" }, diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp index 25fc36c3..d8dbe7c0 100644 --- a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2009-2010 Oracle Corporation + * Copyright (C) 2009-2013 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -38,21 +38,23 @@ # include <sys/ioctl.h> # include <sys/socket.h> # include <net/if.h> +# include <pwd.h> /* getpwuid */ # include <unistd.h> -# ifndef RT_OS_OS2 -# ifndef RT_OS_FREEBSD -# include <utmpx.h> /* @todo FreeBSD 9 should have this. */ -# endif +# 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_SOLARIS # include <sys/sockio.h> # include <net/if_arp.h> # endif -# ifdef RT_OS_FREEBSD +# if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) # 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> @@ -69,6 +71,20 @@ #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 * *******************************************************************************/ @@ -78,12 +94,43 @@ static uint32_t g_cMsVMInfoInterval = 0; static RTSEMEVENTMULTI g_hVMInfoEvent = NIL_RTSEMEVENTMULTI; /** The guest property service client ID. */ static uint32_t g_uVMInfoGuestPropSvcClientID = 0; -/** Number of logged in users in OS. */ -static uint32_t g_cVMInfoLoggedInUsers = UINT32_MAX; +/** 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 +/** 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 @@ -114,12 +161,17 @@ static DECLCALLBACK(int) VBoxServiceVMInfoPreInit(void) /** @copydoc VBOXSERVICE::pfnOption */ static DECLCALLBACK(int) VBoxServiceVMInfoOption(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 = VBoxServiceArgUInt32(argc, argv, "", pi, &g_cMsVMInfoInterval, 1, UINT32_MAX - 1); + else if (!strcmp(argv[*pi], "--vminfo-user-idle-threshold")) + rc = VBoxServiceArgUInt32(argc, argv, "", pi, + &g_uVMInfoUserIdleThresholdMS, 1, UINT32_MAX - 1); return rc; } @@ -134,7 +186,10 @@ static DECLCALLBACK(int) VBoxServiceVMInfoInit(void) if (!g_cMsVMInfoInterval) g_cMsVMInfoInterval = g_DefaultInterval * 1000; if (!g_cMsVMInfoInterval) - g_cMsVMInfoInterval = 10 * 1000; + { + /* Set it to 5s by default for location awareness checks. */ + g_cMsVMInfoInterval = 5 * 1000; + } int rc = RTSemEventMultiCreate(&g_hVMInfoEvent); AssertRCReturn(rc, rc); @@ -142,20 +197,23 @@ static DECLCALLBACK(int) VBoxServiceVMInfoInit(void) 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)) - VBoxServiceVerbose(3, "VMInfo: Property Service Client ID: %#x\n", g_uVMInfoGuestPropSvcClientID); + VBoxServiceVerbose(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. */ { - VBoxServiceVerbose(0, "VMInfo: Guest property service is not available, disabling the service\n"); + VBoxServiceVerbose(0, "Guest property service is not available, disabling the service\n"); rc = VERR_SERVICE_DISABLED; } else - VBoxServiceError("VMInfo: Failed to connect to the guest property service! Error: %Rrc\n", rc); + VBoxServiceError("Failed to connect to the guest property service! Error: %Rrc\n", rc); RTSemEventMultiDestroy(g_hVMInfoEvent); g_hVMInfoEvent = NIL_RTSEMEVENTMULTI; } @@ -167,15 +225,204 @@ static DECLCALLBACK(int) VBoxServiceVMInfoInit(void) /* * Declare some guest properties with flags and reset values. */ - VBoxServicePropCacheUpdateEntry(&g_VMInfoPropCache, "/VirtualBox/GuestInfo/OS/LoggedInUsersList", - VBOXSERVICEPROPCACHEFLAG_TEMPORARY | VBOXSERVICEPROPCACHEFLAG_TRANSIENT, NULL /* Delete on exit */); - VBoxServicePropCacheUpdateEntry(&g_VMInfoPropCache, "/VirtualBox/GuestInfo/OS/LoggedInUsers", - VBOXSERVICEPROPCACHEFLAG_TEMPORARY | VBOXSERVICEPROPCACHEFLAG_TRANSIENT, "0"); - VBoxServicePropCacheUpdateEntry(&g_VMInfoPropCache, "/VirtualBox/GuestInfo/OS/NoLoggedInUsers", - VBOXSERVICEPROPCACHEFLAG_TEMPORARY | VBOXSERVICEPROPCACHEFLAG_TRANSIENT, "true"); - VBoxServicePropCacheUpdateEntry(&g_VMInfoPropCache, "/VirtualBox/GuestInfo/Net/Count", - VBOXSERVICEPROPCACHEFLAG_TEMPORARY | VBOXSERVICEPROPCACHEFLAG_ALWAYS_UPDATE, NULL /* Delete on exit */); + int rc2 = VBoxServicePropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, + VBOXSERVICEPROPCACHEFLAG_TEMPORARY | VBOXSERVICEPROPCACHEFLAG_TRANSIENT, NULL /* Delete on exit */); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to init property cache value \"%s\", rc=%Rrc\n", g_pszPropCacheValLoggedInUsersList, rc2); + + rc2 = VBoxServicePropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers, + VBOXSERVICEPROPCACHEFLAG_TEMPORARY | VBOXSERVICEPROPCACHEFLAG_TRANSIENT, "0"); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to init property cache value \"%s\", rc=%Rrc\n", g_pszPropCacheValLoggedInUsers, rc2); + + rc2 = VBoxServicePropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers, + VBOXSERVICEPROPCACHEFLAG_TEMPORARY | VBOXSERVICEPROPCACHEFLAG_TRANSIENT, "true"); + if (RT_FAILURE(rc2)) + VBoxServiceError("Failed to init property cache value \"%s\", rc=%Rrc\n", g_pszPropCacheValNoLoggedInUsers, rc2); + + rc2 = VBoxServicePropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNetCount, + VBOXSERVICEPROPCACHEFLAG_TEMPORARY | VBOXSERVICEPROPCACHEFLAG_ALWAYS_UPDATE, NULL /* Delete on exit */); + if (RT_FAILURE(rc2)) + VBoxServiceError("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 = VBoxServiceReadHostProp(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 vboxServiceGetLAClientValue(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]; + if (RTStrPrintf(pszClientPath, sizeof(pszClientPath), + "/VirtualBox/HostInfo/VRDP/Client/%RU32/%s", uClientID, pszProperty)) + { + rc = VBoxServiceReadHostProp(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 vboxServiceGetLAClientInfo(uint32_t uClientID, PVBOXSERVICELACLIENTINFO pClient) +{ + AssertReturn(uClientID, VERR_INVALID_PARAMETER); + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + int rc = vboxServiceGetLAClientValue(uClientID, "Name", &pClient->pszName, + NULL /* Timestamp */); + if (RT_SUCCESS(rc)) + { + char *pszAttach; + rc = vboxServiceGetLAClientValue(uClientID, "Attach", &pszAttach, + &pClient->uAttachedTS); + if (RT_SUCCESS(rc)) + { + AssertPtr(pszAttach); + pClient->fAttached = !RTStrICmp(pszAttach, "1") ? true : false; + + RTStrFree(pszAttach); + } + } + if (RT_SUCCESS(rc)) + rc = vboxServiceGetLAClientValue(uClientID, "Location", &pClient->pszLocation, + NULL /* Timestamp */); + if (RT_SUCCESS(rc)) + rc = vboxServiceGetLAClientValue(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 vboxServiceFreeLAClientInfo(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 vboxServiceUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain, + const char *pszKey, const char *pszValueFormat, ...) +{ + AssertPtrReturn(pCache, VERR_INVALID_POINTER); + AssertPtrReturn(pszUser, VERR_INVALID_POINTER); + /* pszDomain is optional. */ + AssertPtrReturn(pszKey, VERR_INVALID_POINTER); + /* pszValueFormat is optional. */ + + int rc = VINF_SUCCESS; + + char *pszName; + if (pszDomain) + { + if (!RTStrAPrintf(&pszName, "%s%s@%s/%s", g_pszPropCacheValUser, pszUser, pszDomain, pszKey)) + rc = VERR_NO_MEMORY; + } + else + { + 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 = VBoxServicePropCacheUpdate(pCache, pszName, pszValue); + if (rc == VINF_SUCCESS) /* VBoxServicePropCacheUpdate will also return VINF_NO_CHANGE. */ + { + /** @todo Combine updating flags w/ updating the actual value. */ + rc = VBoxServicePropCacheUpdateEntry(pCache, pszName, + VBOXSERVICEPROPCACHEFLAG_TEMPORARY | VBOXSERVICEPROPCACHEFLAG_TRANSIENT, + NULL /* Delete on exit */); + } + + RTStrFree(pszValue); + RTStrFree(pszName); return rc; } @@ -242,6 +489,23 @@ static void vboxserviceVMInfoWriteFixedProperties(void) #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. @@ -254,7 +518,8 @@ static int vboxserviceVMInfoWriteUsers(void) #ifdef RT_OS_WINDOWS # ifndef TARGET_NT4 - rc = VBoxServiceVMInfoWinWriteUsers(&pszUserList, &cUsersInList); + rc = VBoxServiceVMInfoWinWriteUsers(&g_VMInfoPropCache, + &pszUserList, &cUsersInList); # else rc = VERR_NOT_IMPLEMENTED; # endif @@ -265,6 +530,10 @@ static int vboxserviceVMInfoWriteUsers(void) * 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; @@ -279,12 +548,18 @@ static int vboxserviceVMInfoWriteUsers(void) if (papszUsers == NULL) rc = VERR_NO_MEMORY; - /* Process all entries in the utmp file. */ + /* Process all entries in the utmp file. + * Note: This only handles */ while ( (ut_user = getutxent()) && RT_SUCCESS(rc)) { - VBoxServiceVerbose(4, "Found logged in user \"%s\" (type: %d)\n", - ut_user->ut_user, ut_user->ut_type); +#ifdef RT_OS_DARWIN /* No ut_user->ut_session on Darwin */ + VBoxServiceVerbose(4, "Found entry \"%s\" (type: %d, PID: %RU32)\n", + ut_user->ut_user, ut_user->ut_type, ut_user->ut_pid); +#else + VBoxServiceVerbose(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; @@ -294,8 +569,8 @@ static int vboxserviceVMInfoWriteUsers(void) } /* Make sure we don't add user names which are not - * part of type USER_PROCESS. */ - if (ut_user->ut_type == USER_PROCESS) + * 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++) @@ -314,25 +589,230 @@ static int vboxserviceVMInfoWriteUsers(void) } } +#ifdef VBOX_WITH_DBUS +# if defined(RT_OS_LINUX) /* Not yet for Solaris/FreeBSB. */ + DBusError dbErr; + DBusConnection *pConnection = NULL; + int rc2 = RTDBusLoadLib(); + if (RT_SUCCESS(rc2)) + { + /* Handle desktop sessions using ConsoleKit. */ + VBoxServiceVerbose(4, "Checking ConsoleKit sessions ...\n"); + + dbus_error_init(&dbErr); + pConnection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbErr); + } + + if ( pConnection + && !dbus_error_is_set(&dbErr)) + { + /* Get all available sessions. */ + 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 */)) + { + VBoxServiceVerbose(4, "ConsoleKit: retrieved %RU16 session(s)\n", cSessions); + + char **ppszCurSession = ppszSessions; + for (ppszCurSession; + ppszCurSession && *ppszCurSession; ppszCurSession++) + { + VBoxServiceVerbose(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 /* 30s timeout */, + &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); + } + + VBoxServiceVerbose(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_uid >= uid_min /* Only respect users, not daemons etc. */ + && ppwEntry->pw_name) + { + VBoxServiceVerbose(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) + { + VBoxServiceVerbose(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 + VBoxServiceError("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 + VBoxServiceError("ConsoleKit: unable to retrieve user for session '%s' (msg type=%d): %s", + *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 + { + VBoxServiceError("ConsoleKit: unable to retrieve session parameters (msg type=%d): %s", + 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) + VBoxServiceError("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) + VBoxServiceError("Unable to connect to system D-Bus (%d/3): %s\n", s_iBitchedAboutDBus, + pConnection && dbus_error_is_set(&dbErr) ? dbErr.message : "D-Bus not installed"); + } + + if ( pConnection + && 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; - for (uint32_t i = 0; i < cUsersInList; i++) - cchUserList += (i != 0) + strlen(papszUsers[i]); - - /* Build the user list. */ - rc = RTStrAllocEx(&pszUserList, cchUserList + 1); if (RT_SUCCESS(rc)) { - char *psz = pszUserList; 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)) { - if (i != 0) - *psz++ = ','; - size_t cch = strlen(papszUsers[i]); - memcpy(psz, papszUsers[i], cch); - psz += cch; + 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'; } - *psz = '\0'; } /* Cleanup. */ @@ -354,30 +834,42 @@ static int vboxserviceVMInfoWriteUsers(void) { static int s_iVMInfoBitchedOOM = 0; if (s_iVMInfoBitchedOOM++ < 3) - VBoxServiceVerbose(0, "Warning: Not enough memory available to enumerate users! Keeping old value (%u)\n", + VBoxServiceVerbose(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; VBoxServiceVerbose(4, "cUsersInList=%RU32, pszUserList=%s, rc=%Rrc\n", cUsersInList, pszUserList ? pszUserList : "<NULL>", rc); - if (pszUserList && cUsersInList > 0) - VBoxServicePropCacheUpdate(&g_VMInfoPropCache, "/VirtualBox/GuestInfo/OS/LoggedInUsersList", "%s", pszUserList); - else - VBoxServicePropCacheUpdate(&g_VMInfoPropCache, "/VirtualBox/GuestInfo/OS/LoggedInUsersList", NULL); - VBoxServicePropCacheUpdate(&g_VMInfoPropCache, "/VirtualBox/GuestInfo/OS/LoggedInUsers", "%u", cUsersInList); - if (g_cVMInfoLoggedInUsers != cUsersInList) + if (pszUserList) { - VBoxServicePropCacheUpdate(&g_VMInfoPropCache, "/VirtualBox/GuestInfo/OS/NoLoggedInUsers", - cUsersInList == 0 ? "true" : "false"); - g_cVMInfoLoggedInUsers = cUsersInList; + AssertMsg(cUsersInList, ("pszUserList contains users whereas cUsersInList is 0\n")); + rc = VBoxServicePropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, "%s", pszUserList); } - if (RT_SUCCESS(rc) && pszUserList) + else + rc = VBoxServicePropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, NULL); + if (RT_FAILURE(rc)) + VBoxServiceError("Error writing logged in users list, rc=%Rrc\n", rc); + + rc = VBoxServicePropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers, "%RU32", cUsersInList); + if (RT_FAILURE(rc)) + VBoxServiceError("Error writing logged in users count, rc=%Rrc\n", rc); + + rc = VBoxServicePropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers, + cUsersInList == 0 ? "true" : "false"); + if (RT_FAILURE(rc)) + VBoxServiceError("Error writing no logged in users beacon, rc=%Rrc\n", rc); + + if (pszUserList) RTStrFree(pszUserList); + + VBoxServiceVerbose(4, "Writing users returned with rc=%Rrc\n", rc); return rc; } @@ -479,18 +971,18 @@ static int vboxserviceVMInfoWriteNetwork(void) Assert(pAddress); char szIp[32]; RTStrPrintf(szIp, sizeof(szIp), "%s", inet_ntoa(pAddress->sin_addr)); - RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%u/V4/IP", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szIp); pAddress = (sockaddr_in *) & (InterfaceList[i].iiBroadcastAddress); - RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%u/V4/Broadcast", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr)); pAddress = (sockaddr_in *)&(InterfaceList[i].iiNetmask); - RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%u/V4/Netmask", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr)); - RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%u/Status", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, nFlags & IFF_UP ? "Up" : "Down"); # ifndef TARGET_NT4 @@ -499,7 +991,7 @@ static int vboxserviceVMInfoWriteNetwork(void) if (!strcmp(pAdp->IpAddressList.IpAddress.String, szIp)) break; - RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%u/MAC", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfacesReport); if (pAdp) { char szMac[32]; @@ -519,7 +1011,11 @@ static int vboxserviceVMInfoWriteNetwork(void) if (sd >= 0) closesocket(sd); -#elif defined(RT_OS_FREEBSD) +#elif defined(RT_OS_HAIKU) + /** @todo Haiku: implement network info. retreival */ + return VERR_NOT_IMPLEMENTED; + +#elif defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) struct ifaddrs *pIfHead = NULL; /* Get all available interfaces */ @@ -546,19 +1042,19 @@ static int vboxserviceVMInfoWriteNetwork(void) 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/%u/V4/IP", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfacesReport); VBoxServicePropCacheUpdate(&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/%u/V4/Broadcast", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfacesReport); VBoxServicePropCacheUpdate(&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/%u/V4/Netmask", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr); /* Search for the AF_LINK interface of the current AF_INET one and get the mac. */ @@ -575,13 +1071,13 @@ static int vboxserviceVMInfoWriteNetwork(void) 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/%u/MAC", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac); break; } } - RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%u/Status", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, pIfCurr->ifa_flags & IFF_UP ? "Up" : "Down"); cIfacesReport++; @@ -630,7 +1126,7 @@ static int vboxserviceVMInfoWriteNetwork(void) bool fIfUp = !!(ifrequest[i].ifr_flags & IFF_UP); pAddress = ((sockaddr_in *)&ifrequest[i].ifr_addr); Assert(pAddress); - RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%u/V4/IP", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr)); if (ioctl(sd, SIOCGIFBRDADDR, &ifrequest[i]) < 0) @@ -640,7 +1136,7 @@ static int vboxserviceVMInfoWriteNetwork(void) break; } pAddress = (sockaddr_in *)&ifrequest[i].ifr_broadaddr; - RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%u/V4/Broadcast", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr)); if (ioctl(sd, SIOCGIFNETMASK, &ifrequest[i]) < 0) @@ -655,7 +1151,7 @@ static int vboxserviceVMInfoWriteNetwork(void) pAddress = (sockaddr_in *)&ifrequest[i].ifr_netmask; # endif - RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%u/V4/Netmask", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr)); # if defined(RT_OS_SOLARIS) @@ -712,18 +1208,18 @@ static int vboxserviceVMInfoWriteNetwork(void) # endif 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/%u/MAC", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac); # endif /* !OS/2*/ - RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%u/Status", cIfacesReport); + RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfacesReport); VBoxServicePropCacheUpdate(&g_VMInfoPropCache, szPropPath, fIfUp ? "Up" : "Down"); cIfacesReport++; } /* For all interfaces */ close(sd); if (RT_FAILURE(rc)) - VBoxServiceError("VMInfo/Network: Network enumeration for interface %u failed with error %Rrc\n", cIfacesReport, rc); + VBoxServiceError("VMInfo/Network: Network enumeration for interface %RU32 failed with error %Rrc\n", cIfacesReport, rc); #endif /* !RT_OS_WINDOWS */ @@ -735,19 +1231,19 @@ static int vboxserviceVMInfoWriteNetwork(void) /* Get former count. */ uint32_t cIfacesReportOld; - rc = VBoxServiceReadPropUInt32(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/Net/Count", &cIfacesReportOld, + rc = VBoxServiceReadPropUInt32(g_uVMInfoGuestPropSvcClientID, g_pszPropCacheValNetCount, &cIfacesReportOld, 0 /* Min */, UINT32_MAX /* Max */); if ( RT_SUCCESS(rc) && cIfacesReportOld > cIfacesReport) /* Are some ifaces not around anymore? */ { - VBoxServiceVerbose(3, "VMInfo/Network: Stale interface data detected (%u old vs. %u current)\n", + VBoxServiceVerbose(3, "VMInfo/Network: Stale interface data detected (%RU32 old vs. %RU32 current)\n", cIfacesReportOld, cIfacesReport); uint32_t uIfaceDeleteIdx = cIfacesReport; do { VBoxServiceVerbose(3, "VMInfo/Network: Deleting stale data of interface %d ...\n", uIfaceDeleteIdx); - rc = VBoxServicePropCacheUpdateByPath(&g_VMInfoPropCache, NULL /* Value, delete */, 0 /* Flags */, "/VirtualBox/GuestInfo/Net/%u", uIfaceDeleteIdx++); + rc = VBoxServicePropCacheUpdateByPath(&g_VMInfoPropCache, NULL /* Value, delete */, 0 /* Flags */, "/VirtualBox/GuestInfo/Net/%RU32", uIfaceDeleteIdx++); } while (RT_SUCCESS(rc)); } else if ( RT_FAILURE(rc) @@ -762,7 +1258,7 @@ static int vboxserviceVMInfoWriteNetwork(void) * does not change. If this property is missing, the host assumes that all other GuestInfo * properties are no longer valid. */ - VBoxServicePropCacheUpdate(&g_VMInfoPropCache, "/VirtualBox/GuestInfo/Net/Count", "%d", + VBoxServicePropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNetCount, "%RU32", cIfacesReport); /* Don't fail here; just report everything we got. */ @@ -806,6 +1302,83 @@ DECLCALLBACK(int) VBoxServiceVMInfoWorker(bool volatile *pfShutdown) 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 = VBoxServiceReadHostProp(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 = vboxServiceGetLAClientValue(uLAClientID, "Attach", &pszAttach, + &uLAClientAttachedTS); + + if ( RT_SUCCESS(rc2) + && ( !g_LAClientAttachedTS + || (g_LAClientAttachedTS != uLAClientAttachedTS))) + { + vboxServiceFreeLAClientInfo(&g_LAClientInfo); + + /* Note: There is a race between setting the guest properties by the host and getting them by + * the guest. */ + rc2 = vboxServiceGetLAClientInfo(uLAClientID, &g_LAClientInfo); + if (RT_SUCCESS(rc2)) + { + VBoxServiceVerbose(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) + VBoxServiceError("Error getting active location awareness client info, rc=%Rrc\n", rc2); + } + } + else if (RT_FAILURE(rc2)) + VBoxServiceError("Error getting attached value of location awareness client %RU32, rc=%Rrc\n", + uLAClientID, rc2); + if (pszAttach) + RTStrFree(pszAttach); + } + else + { + VBoxServiceVerbose(1, "VRDP: UTTSC disconnected from VRDP server\n"); + vboxServiceFreeLAClientInfo(&g_LAClientInfo); + } + + RTStrFree(pszLAClientID); + } + else + { + static int s_iBitchedAboutLAClient = 0; + if ( (rc2 != VERR_NOT_FOUND) /* No location awareness installed, skip. */ + && s_iBitchedAboutLAClient++ < 3) + VBoxServiceError("VRDP: Querying connected location awareness client failed with rc=%Rrc\n", rc2); + } + + VBoxServiceVerbose(3, "VRDP: Handling location awareness done\n"); + /* * Flush all properties if we were restored. */ @@ -813,7 +1386,7 @@ DECLCALLBACK(int) VBoxServiceVMInfoWorker(bool volatile *pfShutdown) VbglR3GetSessionId(&idNewSession); if (idNewSession != g_idVMInfoSession) { - VBoxServiceVerbose(3, "VMInfo: The VM session ID changed, flushing all properties\n"); + VBoxServiceVerbose(3, "The VM session ID changed, flushing all properties\n"); vboxserviceVMInfoWriteFixedProperties(); VBoxServicePropCacheFlush(&g_VMInfoPropCache); g_idVMInfoSession = idNewSession; @@ -827,12 +1400,13 @@ DECLCALLBACK(int) VBoxServiceVMInfoWorker(bool volatile *pfShutdown) */ if (*pfShutdown) break; - int rc2 = RTSemEventMultiWait(g_hVMInfoEvent, g_cMsVMInfoInterval); + if (fWait) + rc2 = RTSemEventMultiWait(g_hVMInfoEvent, g_cMsVMInfoInterval); if (*pfShutdown) break; if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) { - VBoxServiceError("VMInfo: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); + VBoxServiceError("RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); rc = rc2; break; } @@ -841,7 +1415,7 @@ DECLCALLBACK(int) VBoxServiceVMInfoWorker(bool volatile *pfShutdown) /* Reset event semaphore if it got triggered. */ rc2 = RTSemEventMultiReset(g_hVMInfoEvent); if (RT_FAILURE(rc2)) - rc2 = VBoxServiceError("VMInfo: RTSemEventMultiReset failed; rc2=%Rrc\n", rc2); + rc2 = VBoxServiceError("RTSemEventMultiReset failed; rc2=%Rrc\n", rc2); } } @@ -880,13 +1454,16 @@ static DECLCALLBACK(void) VBoxServiceVMInfoTerm(void) const char *apszPat[1] = { "/VirtualBox/GuestInfo/Net/*" }; int rc = VbglR3GuestPropDelSet(g_uVMInfoGuestPropSvcClientID, &apszPat[0], RT_ELEMENTS(apszPat)); + /* Destroy LA client info. */ + vboxServiceFreeLAClientInfo(&g_LAClientInfo); + /* Destroy property cache. */ VBoxServicePropCacheDestroy(&g_VMInfoPropCache); /* Disconnect from guest properties service. */ rc = VbglR3GuestPropDisconnect(g_uVMInfoGuestPropSvcClientID); if (RT_FAILURE(rc)) - VBoxServiceError("VMInfo: Failed to disconnect from guest property service! Error: %Rrc\n", rc); + VBoxServiceError("Failed to disconnect from guest property service! Error: %Rrc\n", rc); g_uVMInfoGuestPropSvcClientID = 0; RTSemEventMultiDestroy(g_hVMInfoEvent); @@ -905,11 +1482,15 @@ VBOXSERVICE g_VMInfo = /* pszDescription. */ "Virtual Machine Information", /* pszUsage. */ - " [--vminfo-interval <ms>]" + " [--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 */ VBoxServiceVMInfoPreInit, diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h new file mode 100644 index 00000000..38984fa6 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h @@ -0,0 +1,31 @@ +/* $Id: VBoxServiceVMInfo.h $ */ +/** @file + * VBoxServiceVMInfo.h - Internal VM info definitions. + */ + +/* + * Copyright (C) 2013 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 ___VBoxServiceVMInfo_h +#define ___VBoxServiceVMInfo_h + +//RT_C_DECLS_BEGIN + +extern int vboxServiceUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain, + const char *pszKey, const char *pszValueFormat, ...); + +//RT_C_DECLS_END + +extern uint32_t g_uVMInfoUserIdleThresholdMS; + +#endif /* ___VBoxServiceVMInfo_h */ + diff --git a/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp b/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp index 84b18dfa..29f2ff88 100644 --- a/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp +++ b/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2010 Oracle Corporation + * Copyright (C) 2010-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; |
