summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-global
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-global')
-rw-r--r--src/VBox/Main/src-global/Makefile.kup0
-rw-r--r--src/VBox/Main/src-global/win/Makefile.kup0
-rw-r--r--src/VBox/Main/src-global/win/VBoxSDS.cpp1048
-rw-r--r--src/VBox/Main/src-global/win/VBoxSDS.rc88
-rw-r--r--src/VBox/Main/src-global/win/VirtualBoxSDSImpl.cpp1371
5 files changed, 2507 insertions, 0 deletions
diff --git a/src/VBox/Main/src-global/Makefile.kup b/src/VBox/Main/src-global/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-global/Makefile.kup
diff --git a/src/VBox/Main/src-global/win/Makefile.kup b/src/VBox/Main/src-global/win/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-global/win/Makefile.kup
diff --git a/src/VBox/Main/src-global/win/VBoxSDS.cpp b/src/VBox/Main/src-global/win/VBoxSDS.cpp
new file mode 100644
index 00000000..6bc7506f
--- /dev/null
+++ b/src/VBox/Main/src-global/win/VBoxSDS.cpp
@@ -0,0 +1,1048 @@
+/* $Id: VBoxSDS.cpp $ */
+/** @file
+ * VBoxSDS - COM global service main entry (System Directory Service)
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/** @page pg_VBoxSDS VBoxSDS - Per user CLSID_VirtualBox coordinater
+ *
+ * VBoxSDS is short for VirtualBox System Directory Service (SDS). Its purpose
+ * is to make sure there is only one CLSID_VirtualBox object running for each
+ * user using VirtualBox on a Windows host system.
+ *
+ *
+ * @section sec_vboxsds_backgroud Background
+ *
+ * COM is desktop oriented when it comes to activate-as-activator (AAA) COM
+ * servers. This means that if the users has two logins to the same box (e.g.
+ * physical console, RDP, SSHD) and tries to use an AAA COM server, a new server
+ * will be instantiated for each login. With the introduction of User Account
+ * Control (UAC) in Windows Vista, this was taken a step further and a user
+ * would talk different AAA COM server instances depending on the elevation
+ * level too.
+ *
+ * VBoxSVC is a service affected by this issue. Using VirtualBox across logins
+ * or between user elevation levels was impossible to do simultaneously. This
+ * was confusing and illogical to the user.
+ *
+ *
+ * @section sec_vboxsds_how How it works
+ *
+ * VBoxSDS assists in working around this problem by tracking which VBoxSVC
+ * server is currently providing CLSID_VirtualBox for a user. Each VBoxSVC
+ * instance will register itself with VBoxSDS when the CLSID_VirtualBox object
+ * is requested via their class factory. The first VBoxSVC registering for a
+ * given user will be allowed to instantate CLSID_VirtualBox. We will call this
+ * the chosen one. Subsequent VBoxSVC instance for the given user, regardless
+ * of elevation, session, windows station, or whatever else, will be told to use
+ * the instance from the first VBoxSVC.
+ *
+ * The registration call passes along an IVBoxSVCRegistration interface from
+ * VBoxSVC. VBoxSDS keeps this around for the chosen one only. When other
+ * VBoxSVC instances for the same user tries to register, VBoxSDS will ask the
+ * choosen one for its CLSID_VirtualBox object and return it to the new
+ * registrant.
+ *
+ * The chosen one will deregister with VBoxSDS before it terminates. Should it
+ * terminate abnormally, VBoxSDS will (probably) notice the next time it tries
+ * to request CLSID_VirtualBox from it and replace it as the chosen one with the
+ * new registrant.
+ *
+ *
+ * @section sec_vboxsds_locking Locking
+ *
+ * VBoxSDS stores data in a map indexed by the stringified secure identifier
+ * (SID) for each user. The map is protected by a shared critical section, so
+ * only inserting new users requires exclusive access.
+ *
+ * Each user data entry has it own lock (regular, not shared), so that it won't
+ * be necessary to hold down the map lock while accessing per user data. Thus
+ * preventing a user from blocking all others from using VirtualBox by
+ * suspending or debugging their chosen VBoxSVC process.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_VIRTUALBOXSDS
+#include <iprt/win/windows.h>
+#include <iprt/win/shlobj.h>
+
+#include "VBox/com/defs.h"
+#include "VBox/com/com.h"
+#include "VBox/com/VirtualBox.h"
+
+#include "VirtualBoxSDSImpl.h"
+#include "LoggingNew.h"
+
+#include <iprt/errcore.h>
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/path.h>
+#include <iprt/message.h>
+#include <iprt/string.h>
+
+#include <VBox/com/microatl.h>
+
+#define _ATL_FREE_THREADED /** @todo r=bird: WTF? */
+
+/**
+ * Implements Windows Service
+ */
+class ATL_NO_VTABLE CWindowsServiceModule
+{
+protected:
+ // data members
+ WCHAR m_wszServiceName[256];
+ WCHAR m_wszServiceDisplayName[256];
+ WCHAR m_wszServiceDescription[256];
+ SERVICE_STATUS_HANDLE m_hServiceStatus;
+ SERVICE_STATUS m_Status;
+ DWORD m_dwThreadID;
+
+ /** Pointer to the instance, for use by staticServiceMain and staticHandler. */
+ static CWindowsServiceModule *s_pInstance;
+
+public:
+ CWindowsServiceModule() throw()
+ {
+ // set up the initial service status
+ m_hServiceStatus = NULL;
+ m_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ m_Status.dwCurrentState = SERVICE_STOPPED;
+ m_Status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ m_Status.dwWin32ExitCode = 0;
+ m_Status.dwServiceSpecificExitCode = 0;
+ m_Status.dwCheckPoint = 0;
+ m_Status.dwWaitHint = 3000;
+
+ s_pInstance = this;
+ }
+
+ virtual ~CWindowsServiceModule()
+ {
+ s_pInstance = NULL;
+ }
+
+ HRESULT startService(int /*nShowCmd*/) throw()
+ {
+ SERVICE_TABLE_ENTRY aServiceTable[] =
+ {
+ { m_wszServiceName, staticServiceMain },
+ { NULL, NULL }
+ };
+
+ if (::StartServiceCtrlDispatcher(aServiceTable) == 0)
+ {
+ m_Status.dwWin32ExitCode = ::GetLastError();
+ LogRelFunc(("Error: Cannot start service in console mode. Code: %u\n", m_Status.dwWin32ExitCode));
+ }
+
+ return m_Status.dwWin32ExitCode;
+ }
+
+ virtual HRESULT registerService() throw()
+ {
+ HRESULT hrc;
+ if (uninstallService())
+ {
+ hrc = onRegisterService();
+ if (SUCCEEDED(hrc))
+ {
+ if (installService())
+ hrc = S_OK;
+ else
+ hrc = E_FAIL;
+ }
+ }
+ else
+ hrc = E_FAIL;
+ return hrc;
+ }
+
+ virtual HRESULT unregisterService() throw()
+ {
+ HRESULT hrc = E_FAIL;
+ if (uninstallService())
+ hrc = onUnregisterService();
+ return hrc;
+ }
+
+private:
+ void serviceMain(DWORD, LPTSTR *) throw()
+ {
+ LogFunc(("Enter into serviceMain\n"));
+ // Register the control request handler
+ m_Status.dwCurrentState = SERVICE_START_PENDING;
+ m_dwThreadID = ::GetCurrentThreadId();
+ m_hServiceStatus = ::RegisterServiceCtrlHandler(m_wszServiceName, staticHandler);
+ if (m_hServiceStatus == NULL)
+ {
+ LogWarnFunc(("Handler not installed\n"));
+ return;
+ }
+ setServiceStatus(SERVICE_START_PENDING);
+
+ m_Status.dwWin32ExitCode = S_OK;
+ m_Status.dwCheckPoint = 0;
+ m_Status.dwWaitHint = 0;
+
+ // When the Run function returns, the service has stopped.
+ m_Status.dwWin32ExitCode = runService(SW_HIDE);
+
+ setServiceStatus(SERVICE_STOPPED);
+ LogFunc(("Windows Service stopped\n"));
+ }
+
+ /** Service table callback. */
+ static void WINAPI staticServiceMain(DWORD cArgs, LPTSTR *papwszArgs) throw()
+ {
+ AssertPtrReturnVoid(s_pInstance);
+ s_pInstance->serviceMain(cArgs, papwszArgs);
+ }
+
+ HRESULT runService(int nShowCmd = SW_HIDE) throw()
+ {
+ HRESULT hr = preMessageLoop(nShowCmd);
+
+ if (hr == S_OK)
+ runMessageLoop();
+
+ if (SUCCEEDED(hr))
+ hr = postMessageLoop();
+
+ return hr;
+ }
+
+protected:
+ /** Hook that's called before the message loop starts.
+ * Must return S_OK for it to start. */
+ virtual HRESULT preMessageLoop(int /*nShowCmd*/) throw()
+ {
+ LogFunc(("Enter\n"));
+ if (::InterlockedCompareExchange(&m_Status.dwCurrentState, SERVICE_RUNNING, SERVICE_START_PENDING) == SERVICE_START_PENDING)
+ {
+ LogFunc(("VBoxSDS Service started/resumed without delay\n"));
+ ::SetServiceStatus(m_hServiceStatus, &m_Status);
+ }
+ return S_OK;
+ }
+
+ /** Your typical windows message loop. */
+ virtual void runMessageLoop()
+ {
+ MSG msg;
+ while (::GetMessage(&msg, 0, 0, 0) > 0)
+ {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+ }
+
+ /** Hook that's called after the message loop ends. */
+ virtual HRESULT postMessageLoop()
+ {
+ return S_OK;
+ }
+
+ /** @name Overridable status change handlers
+ * @{ */
+ virtual void onStop() throw()
+ {
+ setServiceStatus(SERVICE_STOP_PENDING);
+ ::PostThreadMessage(m_dwThreadID, WM_QUIT, 0, 0);
+ LogFunc(("Windows Service stopped\n"));
+ }
+
+ virtual void onPause() throw()
+ {
+ }
+
+ virtual void onContinue() throw()
+ {
+ }
+
+ virtual void onInterrogate() throw()
+ {
+ }
+
+ virtual void onShutdown() throw()
+ {
+ }
+
+ virtual void onUnknownRequest(DWORD dwOpcode) throw()
+ {
+ LogRelFunc(("Bad service request: %u (%#x)\n", dwOpcode, dwOpcode));
+ }
+
+ virtual HRESULT onRegisterService()
+ {
+ return S_OK;
+ }
+
+ virtual HRESULT onUnregisterService()
+ {
+ return S_OK;
+ }
+ /** @} */
+
+private:
+ void handler(DWORD dwOpcode) throw()
+ {
+
+ switch (dwOpcode)
+ {
+ case SERVICE_CONTROL_STOP:
+ onStop();
+ break;
+ case SERVICE_CONTROL_PAUSE:
+ onPause();
+ break;
+ case SERVICE_CONTROL_CONTINUE:
+ onContinue();
+ break;
+ case SERVICE_CONTROL_INTERROGATE:
+ onInterrogate();
+ break;
+ case SERVICE_CONTROL_SHUTDOWN:
+ onShutdown();
+ break;
+ default:
+ onUnknownRequest(dwOpcode);
+ }
+ }
+
+ static void WINAPI staticHandler(DWORD dwOpcode) throw()
+ {
+ AssertPtrReturnVoid(s_pInstance);
+ s_pInstance->handler(dwOpcode);
+ }
+
+protected:
+ void setServiceStatus(DWORD dwState) throw()
+ {
+ uint32_t const uPrevState = ASMAtomicXchgU32((uint32_t volatile *)&m_Status.dwCurrentState, dwState);
+ if (!::SetServiceStatus(m_hServiceStatus, &m_Status))
+ LogRel(("Error: SetServiceStatus(,%u) failed: %u (uPrevState=%u)\n",
+ dwState, GetLastError(), uPrevState));
+ }
+
+
+public:
+ /** @note unused */
+ BOOL IsInstalled() throw()
+ {
+ BOOL fResult = FALSE;
+
+ SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+ if (hSCM != NULL)
+ {
+ SC_HANDLE hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_QUERY_CONFIG);
+ if (hService != NULL)
+ {
+ fResult = TRUE;
+ ::CloseServiceHandle(hService);
+ }
+ ::CloseServiceHandle(hSCM);
+ }
+
+ return fResult;
+ }
+
+ BOOL installService() throw()
+ {
+ BOOL fResult = FALSE;
+ SC_HANDLE hSCM = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
+ if (hSCM != NULL)
+ {
+ SC_HANDLE hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_QUERY_CONFIG);
+ if (hService != NULL)
+ {
+ fResult = TRUE; /* Already installed. */
+
+ ::CloseServiceHandle(hService);
+ }
+ else
+ {
+ // Get the executable file path and quote it.
+ const int QUOTES_SPACE = 2;
+ WCHAR wszFilePath[MAX_PATH + QUOTES_SPACE];
+ DWORD cwcFilePath = ::GetModuleFileNameW(NULL, wszFilePath + 1, MAX_PATH);
+ if (cwcFilePath != 0 && cwcFilePath < MAX_PATH)
+ {
+ wszFilePath[0] = L'\"';
+ wszFilePath[cwcFilePath + 1] = L'\"';
+ wszFilePath[cwcFilePath + 2] = L'\0';
+
+ hService = ::CreateServiceW(hSCM, m_wszServiceName, m_wszServiceDisplayName,
+ SERVICE_CHANGE_CONFIG,
+ SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
+ wszFilePath, NULL, NULL, L"RPCSS\0", NULL, NULL);
+ if (hService != NULL)
+ {
+ SERVICE_DESCRIPTIONW sd;
+ sd.lpDescription = m_wszServiceDescription;
+ if (!::ChangeServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, &sd))
+ AssertLogRelMsgFailed(("Error: could not set service description: %u\n", GetLastError()));
+
+ fResult = TRUE;
+
+ ::CloseServiceHandle(hService);
+ }
+ else
+ AssertLogRelMsgFailed(("Error: Could not create service '%ls': %u\n", m_wszServiceName, GetLastError()));
+ }
+ else
+ AssertLogRelMsgFailed(("Error: GetModuleFileNameW returned %u: %u\n", cwcFilePath, GetLastError()));
+ }
+ }
+ else
+ AssertLogRelMsgFailed(("Error: Could not open the service control manager: %u\n", GetLastError()));
+ return fResult;
+ }
+
+ BOOL uninstallService() throw()
+ {
+ BOOL fResult = FALSE;
+ SC_HANDLE hSCM = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
+ if (hSCM != NULL)
+ {
+ SC_HANDLE hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_STOP | DELETE);
+ if (hService == NULL)
+ {
+ DWORD dwErr = GetLastError();
+ hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_QUERY_CONFIG);
+ if (hService == NULL)
+ fResult = TRUE; /* Probably not installed or some access problem. */
+ else
+ {
+ ::CloseServiceHandle(hService);
+ AssertLogRelMsgFailed(("Error: Failed to open '%ls' for stopping and deletion: %u\n", m_wszServiceName, dwErr));
+ }
+ }
+ else
+ {
+ /* Try stop it. */
+ SERVICE_STATUS status;
+ RT_ZERO(status);
+ if (!::ControlService(hService, SERVICE_CONTROL_STOP, &status))
+ {
+ DWORD dwErr = GetLastError();
+ AssertLogRelMsg( dwErr == ERROR_SERVICE_NOT_ACTIVE
+ || ( dwErr == ERROR_SERVICE_CANNOT_ACCEPT_CTRL
+ && status.dwCurrentState == SERVICE_STOP_PENDING)
+ , ("Error: Failed to stop serive '%ls': dwErr=%u dwCurrentState=%u\n",
+ m_wszServiceName, dwErr, status.dwCurrentState));
+ }
+
+ /* Try delete it. */
+ fResult = ::DeleteService(hService);
+ AssertLogRelMsg(fResult, ("Error: Failed to delete serivce '%ls': %u\n", m_wszServiceName, GetLastError()));
+
+ ::CloseServiceHandle(hService);
+ }
+ ::CloseServiceHandle(hSCM);
+ }
+ else
+ AssertLogRelMsgFailed(("Error: Could not open the service control manager: %u\n", GetLastError()));
+ return fResult;
+ }
+};
+
+/*static*/ CWindowsServiceModule *CWindowsServiceModule::s_pInstance = NULL;
+
+
+/**
+ * Implements COM Module that used within Windows Service.
+ *
+ * It is derived from ComModule to intercept Unlock() and derived from
+ * CWindowsServiceModule to implement Windows Service
+ */
+class CComServiceModule : public CWindowsServiceModule, public ATL::CComModule
+{
+private:
+ /** Tracks whether Init() has been called for debug purposes. */
+ bool m_fInitialized;
+ /** Tracks COM init status for no visible purpose other than debugging. */
+ bool m_fComInitialized;
+ /** Part of the shutdown monitoring logic. */
+ bool volatile m_fActivity;
+#ifdef WITH_WATCHER
+ /** Part of the shutdown monitoring logic. */
+ bool volatile m_fHasClients;
+#endif
+ /** Auto reset event for communicating with the shutdown thread.
+ * This is created by startMonitor(). */
+ HANDLE m_hEventShutdown;
+ /** The main thread ID.
+ * The monitorShutdown code needs this to post a WM_QUIT message. */
+ DWORD m_dwMainThreadID;
+
+public:
+ /** Time for EXE to be idle before shutting down.
+ * Can be decreased at system shutdown phase. */
+ volatile uint32_t m_cMsShutdownTimeOut;
+
+ /** The service module instance. */
+ static CComServiceModule * volatile s_pInstance;
+
+public:
+ /**
+ * Constructor.
+ *
+ * @param cMsShutdownTimeout Number of milliseconds to idle without clients
+ * before autoamtically shutting down the service.
+ *
+ * The default is 2 seconds, because VBoxSVC (our
+ * only client) already does 5 seconds making the
+ * effective idle time 7 seconds from clients like
+ * VBoxManage's point of view. We consider single
+ * user and development as the dominant usage
+ * patterns here, not configuration activity by
+ * multiple users via VBoxManage.
+ */
+ CComServiceModule(DWORD cMsShutdownTimeout = 2000)
+ : m_fInitialized(false)
+ , m_fComInitialized(false)
+ , m_fActivity(false)
+#ifdef WITH_WATCHER
+ , m_fHasClients(false)
+#endif
+ , m_hEventShutdown(INVALID_HANDLE_VALUE)
+ , m_dwMainThreadID(~(DWORD)42)
+ , m_cMsShutdownTimeOut(cMsShutdownTimeout)
+ {
+ }
+
+ /**
+ * Initialization function.
+ */
+ HRESULT init(ATL::_ATL_OBJMAP_ENTRY *p, HINSTANCE h, const GUID *pLibID,
+ wchar_t const *p_wszServiceName, wchar_t const *p_wszDisplayName, wchar_t const *p_wszDescription)
+ {
+ HRESULT hrc = ATL::CComModule::Init(p, h, pLibID);
+ if (SUCCEEDED(hrc))
+ {
+ // copy service name
+ int vrc = ::RTUtf16Copy(m_wszServiceName, sizeof(m_wszServiceName), p_wszServiceName);
+ AssertRCReturn(vrc, E_NOT_SUFFICIENT_BUFFER);
+ vrc = ::RTUtf16Copy(m_wszServiceDisplayName, sizeof(m_wszServiceDisplayName), p_wszDisplayName);
+ AssertRCReturn(vrc, E_NOT_SUFFICIENT_BUFFER);
+ vrc = ::RTUtf16Copy(m_wszServiceDescription, sizeof(m_wszServiceDescription), p_wszDescription);
+ AssertRCReturn(vrc, E_NOT_SUFFICIENT_BUFFER);
+
+ m_fInitialized = true;
+ }
+
+ return hrc;
+ }
+
+ /**
+ * Overload CAtlModule::Unlock to trigger delayed automatic shutdown action.
+ */
+ virtual LONG Unlock() throw()
+ {
+ LONG cLocks = ATL::CComModule::Unlock();
+ LogFunc(("Unlock() called. Ref=%d\n", cLocks));
+ if (cLocks == 0)
+ {
+ ::ASMAtomicWriteBool(&m_fActivity, true);
+ ::SetEvent(m_hEventShutdown); // tell monitor that we transitioned to zero
+ }
+ return cLocks;
+ }
+
+ /**
+ * Overload CAtlModule::Lock to untrigger automatic shutdown.
+ */
+ virtual LONG Lock() throw()
+ {
+ LONG cLocks = ATL::CComModule::Lock();
+ LogFunc(("Lock() called. Ref=%d\n", cLocks));
+#ifdef WITH_WATCHER
+ ::ASMAtomicWriteBool(&m_fActivity, true);
+ ::SetEvent(m_hEventShutdown); /* reset the timeout interval */
+#endif
+ return cLocks;
+ }
+
+#ifdef WITH_WATCHER
+
+ /** Called to start the automatic shutdown behaviour based on client count
+ * rather than lock count.. */
+ void notifyZeroClientConnections()
+ {
+ m_fHasClients = false;
+ ::ASMAtomicWriteBool(&m_fActivity, true);
+ ::SetEvent(m_hEventShutdown);
+ }
+
+ /** Called to make sure automatic shutdown is cancelled. */
+ void notifyHasClientConnections()
+ {
+ m_fHasClients = true;
+ ::ASMAtomicWriteBool(&m_fActivity, true);
+ }
+
+#endif /* WITH_WATCHER */
+
+protected:
+
+ bool hasActiveConnection()
+ {
+#ifdef WITH_WATCHER
+ return m_fActivity || (m_fHasClients && GetLockCount() > 0);
+#else
+ return m_fActivity || GetLockCount() > 0;
+#endif
+ }
+
+ void monitorShutdown() throw()
+ {
+ for (;;)
+ {
+ ::WaitForSingleObject(m_hEventShutdown, INFINITE);
+ DWORD dwWait;
+ do
+ {
+ m_fActivity = false;
+ dwWait = ::WaitForSingleObject(m_hEventShutdown, m_cMsShutdownTimeOut);
+ } while (dwWait == WAIT_OBJECT_0);
+
+ /* timed out */
+ if (!hasActiveConnection()) /* if no activity let's really bail */
+ {
+ ::CoSuspendClassObjects();
+
+ /* Disable log rotation at this point, worst case a log file becomes slightly
+ bigger than it should. Avoids quirks with log rotation: There might be
+ another API service process running at this point which would rotate the
+ logs concurrently, creating a mess. */
+ PRTLOGGER pReleaseLogger = ::RTLogRelGetDefaultInstance();
+ if (pReleaseLogger)
+ {
+ char szDest[1024];
+ int vrc = ::RTLogQueryDestinations(pReleaseLogger, szDest, sizeof(szDest));
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = ::RTStrCat(szDest, sizeof(szDest), " nohistory");
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = ::RTLogDestinations(pReleaseLogger, szDest);
+ AssertRC(vrc);
+ }
+ }
+ }
+
+ if (!hasActiveConnection())
+ break;
+ LogRel(("Still got active connection(s)...\n"));
+ }
+ }
+
+ LogRel(("Shutting down\n"));
+ if (m_hEventShutdown)
+ {
+ ::CloseHandle(m_hEventShutdown);
+ m_hEventShutdown = NULL;
+ }
+ ::PostThreadMessage(m_dwMainThreadID, WM_QUIT, 0, 0);
+ }
+
+ static DECLCALLBACK(int) monitorThreadProc(RTTHREAD hThreadSelf, void *pvUser) throw()
+ {
+ RT_NOREF(hThreadSelf);
+ CComServiceModule *p = static_cast<CComServiceModule *>(pvUser);
+ p->monitorShutdown();
+ return VINF_SUCCESS;
+ }
+
+ void startMonitor()
+ {
+ m_dwMainThreadID = ::GetCurrentThreadId();
+ m_hEventShutdown = ::CreateEvent(NULL, false, false, NULL);
+ AssertLogRelMsg(m_hEventShutdown != NULL, ("GetLastError => %u\n", GetLastError()));
+
+ int vrc = RTThreadCreate(NULL, monitorThreadProc, this, 0 /*cbStack*/, RTTHREADTYPE_DEFAULT, 0 /*fFlags*/, "MonShdwn");
+ if (RT_FAILURE(vrc))
+ {
+ ::CloseHandle(m_hEventShutdown);
+ m_hEventShutdown = NULL;
+ LogRel(("Error: RTThreadCreate failed to create shutdown monitor thread: %Rrc\n", vrc));
+ }
+ }
+
+ virtual HRESULT preMessageLoop(int nShowCmd) throw()
+ {
+ Assert(m_fInitialized);
+ LogFunc(("Enter\n"));
+
+ HRESULT hrc = com::Initialize();
+ if (SUCCEEDED(hrc))
+ {
+ m_fComInitialized = true;
+ hrc = ATL::CComModule::RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED);
+ if (SUCCEEDED(hrc))
+ {
+ // Start Shutdown monitor here
+ startMonitor();
+
+ hrc = CWindowsServiceModule::preMessageLoop(nShowCmd);
+ if (FAILED(hrc))
+ LogRelFunc(("Warning: preMessageLoop failed: %Rhrc\n", hrc));
+
+ hrc = CoResumeClassObjects();
+ if (FAILED(hrc))
+ {
+ ATL::CComModule::RevokeClassObjects();
+ LogRelFunc(("Error: CoResumeClassObjects failed: %Rhrc\n", hrc));
+ }
+ }
+ else
+ LogRel(("Error: ATL::CComModule::RegisterClassObjects: %Rhrc\n", hrc));
+ }
+ else
+ LogRel(("Error: com::Initialize failed\n", hrc));
+ return hrc;
+ }
+
+ virtual HRESULT postMessageLoop()
+ {
+ com::Shutdown();
+ m_fComInitialized = false;
+ return S_OK;
+ }
+};
+
+/*static*/ CComServiceModule * volatile CComServiceModule::s_pInstance = NULL;
+
+
+#ifdef WITH_WATCHER
+/**
+ * Go-between for CComServiceModule and VirtualBoxSDS.
+ */
+void VBoxSDSNotifyClientCount(uint32_t cClients)
+{
+ CComServiceModule *pInstance = CComServiceModule::s_pInstance;
+ if (pInstance)
+ {
+ if (cClients == 0)
+ pInstance->notifyZeroClientConnections();
+ else
+ pInstance->notifyHasClientConnections();
+ }
+}
+#endif
+
+
+/**
+ * Main function for the VBoxSDS process.
+ *
+ * @param hInstance The process instance.
+ * @param hPrevInstance Previous instance (not used here).
+ * @param nShowCmd The show flags.
+ * @param lpCmdLine The command line (not used here, we get it from the
+ * C runtime library).
+ *
+ * @return Exit code
+ */
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
+{
+ RT_NOREF(hPrevInstance, lpCmdLine);
+ int argc = __argc;
+ char **argv = __argv;
+
+ /*
+ * Initialize the VBox runtime without loading the support driver.
+ */
+ RTR3InitExe(argc, &argv, 0);
+
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--unregservice", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-unregservice", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/unregservice", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--regservice", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-regservice", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/regservice", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--reregservice", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "-reregservice", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "/reregservice", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
+ { "--logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "-logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "/logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
+ { "--logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "-logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "/logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "--logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
+ { "-logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
+ { "/logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
+ { "--loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "-loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ { "/loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
+ };
+
+ bool fRun = true;
+ bool fRegister = false;
+ bool fUnregister = false;
+ const char *pszLogFile = NULL;
+ uint32_t cHistory = 10; // enable log rotation, 10 files
+ uint32_t uHistoryFileTime = RT_SEC_1DAY; // max 1 day per file
+ uint64_t uHistoryFileSize = 100 * _1M; // max 100MB per file
+
+ RTGETOPTSTATE GetOptState;
+ int vrc = RTGetOptInit(&GetOptState, argc, argv, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
+ AssertRC(vrc);
+
+ RTGETOPTUNION ValueUnion;
+ while ((vrc = RTGetOpt(&GetOptState, &ValueUnion)))
+ {
+ switch (vrc)
+ {
+ case 'e':
+ break;
+
+ case 'u':
+ fUnregister = true;
+ fRun = false;
+ break;
+
+ case 'r':
+ fRegister = true;
+ fRun = false;
+ break;
+
+ case 'f':
+ fUnregister = true;
+ fRegister = true;
+ fRun = false;
+ break;
+
+ case 'F':
+ pszLogFile = ValueUnion.psz;
+ break;
+
+ case 'R':
+ cHistory = ValueUnion.u32;
+ break;
+
+ case 'S':
+ uHistoryFileSize = ValueUnion.u64;
+ break;
+
+ case 'I':
+ uHistoryFileTime = ValueUnion.u32;
+ break;
+
+ case 'h':
+ {
+ static WCHAR const s_wszHelpText[] =
+ L"Options:\n"
+ L"\n"
+ L"/RegService\t" L"register COM out-of-proc service\n"
+ L"/UnregService\t" L"unregister COM out-of-proc service\n"
+ L"/ReregService\t" L"unregister and register COM service\n"
+ L"no options\t" L"run the service";
+ MessageBoxW(NULL, s_wszHelpText, L"VBoxSDS - Usage", MB_OK);
+ return 0;
+ }
+
+ case 'V':
+ {
+ char *pszText = NULL;
+ RTStrAPrintf(&pszText, "%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+
+ PRTUTF16 pwszText = NULL;
+ RTStrToUtf16(pszText, &pwszText);
+
+ MessageBoxW(NULL, pwszText, L"VBoxSDS - Version", MB_OK);
+
+ RTStrFree(pszText);
+ RTUtf16Free(pwszText);
+ return 0;
+ }
+
+ default:
+ {
+ char szTmp[256];
+ RTGetOptFormatError(szTmp, sizeof(szTmp), vrc, &ValueUnion);
+
+ PRTUTF16 pwszText = NULL;
+ RTStrToUtf16(szTmp, &pwszText);
+
+ MessageBoxW(NULL, pwszText, L"VBoxSDS - Syntax error", MB_OK | MB_ICONERROR);
+
+ RTUtf16Free(pwszText);
+ return RTEXITCODE_SYNTAX;
+ }
+ }
+ }
+
+ /*
+ * Default log location is %ProgramData%\VirtualBox\VBoxSDS.log, falling back
+ * on %_CWD%\VBoxSDS.log (where _CWD typicaly is 'C:\Windows\System32').
+ *
+ * We change the current directory to %ProgramData%\VirtualBox\ if possible.
+ *
+ * We only create the log file when running VBoxSDS normally, but not
+ * when registering/unregistering, at least for now.
+ */
+ if (fRun)
+ {
+ char szLogFile[RTPATH_MAX];
+ if (!pszLogFile || !*pszLogFile)
+ {
+ WCHAR wszAppData[MAX_PATH + 16];
+ if (SHGetSpecialFolderPathW(NULL, wszAppData, CSIDL_COMMON_APPDATA, TRUE /*fCreate*/))
+ {
+ char *pszConv = szLogFile;
+ vrc = RTUtf16ToUtf8Ex(wszAppData, RTSTR_MAX, &pszConv, sizeof(szLogFile) - 12, NULL);
+ }
+ else
+ vrc = RTEnvGetUtf8("ProgramData", szLogFile, sizeof(szLogFile) - sizeof("VBoxSDS.log"), NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPathAppend(szLogFile, sizeof(szLogFile), "VirtualBox\\");
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure it exists. */
+ if (!RTDirExists(szLogFile))
+ vrc = RTDirCreate(szLogFile, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Change into it. */
+ RTPathSetCurrent(szLogFile);
+ }
+ }
+ }
+ if (RT_FAILURE(vrc)) /* ignore any failure above */
+ szLogFile[0] = '\0';
+ vrc = RTStrCat(szLogFile, sizeof(szLogFile), "VBoxSDS.log");
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct release log filename: %Rrc", vrc);
+ pszLogFile = szLogFile;
+ }
+
+ RTERRINFOSTATIC ErrInfo;
+ vrc = com::VBoxLogRelCreate("COM Service", pszLogFile,
+ RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
+ VBOXSDS_LOG_DEFAULT, "VBOXSDS_RELEASE_LOG",
+ RTLOGDEST_FILE | RTLOGDEST_FIXED_FILE | RTLOGDEST_FIXED_DIR,
+ UINT32_MAX /* cMaxEntriesPerGroup */,
+ cHistory, uHistoryFileTime, uHistoryFileSize,
+ RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(vrc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, vrc);
+ }
+
+
+ /*
+ * Initialize COM.
+ */
+ HRESULT hrcExit = com::Initialize();
+ if (SUCCEEDED(hrcExit))
+ {
+ HRESULT hrcSec = CoInitializeSecurity(NULL,
+ -1,
+ NULL,
+ NULL,
+ RPC_C_AUTHN_LEVEL_DEFAULT,
+ RPC_C_IMP_LEVEL_IMPERSONATE,//RPC_C_IMP_LEVEL_IMPERSONATE, RPC_C_IMP_LEVEL_DELEGATE
+ NULL,
+ EOAC_NONE, //EOAC_DYNAMIC_CLOAKING,//EOAC_STATIC_CLOAKING, //EOAC_NONE,
+ NULL);
+ LogRelFunc(("VBoxSDS: InitializeSecurity: %x\n", hrcSec));
+
+ /*
+ * Instantiate our COM service class.
+ */
+ CComServiceModule *pServiceModule = new CComServiceModule();
+ if (pServiceModule)
+ {
+ BEGIN_OBJECT_MAP(s_aObjectMap)
+ OBJECT_ENTRY(CLSID_VirtualBoxSDS, VirtualBoxSDS)
+ END_OBJECT_MAP()
+ hrcExit = pServiceModule->init(s_aObjectMap, hInstance, &LIBID_VirtualBox,
+ L"VBoxSDS",
+ L"VirtualBox system service",
+ L"Used as a COM server for VirtualBox API.");
+
+ if (SUCCEEDED(hrcExit))
+ {
+ if (!fRun)
+ {
+ /*
+ * Do registration work and quit.
+ */
+ /// @todo The VBoxProxyStub should do all work for COM registration
+ if (fUnregister)
+ hrcExit = pServiceModule->unregisterService();
+ if (fRegister)
+ hrcExit = pServiceModule->registerService();
+ }
+ else
+ {
+ /*
+ * Run service.
+ */
+ CComServiceModule::s_pInstance = pServiceModule;
+ hrcExit = pServiceModule->startService(nShowCmd);
+ LogRelFunc(("VBoxSDS: Calling _ServiceModule.RevokeClassObjects()...\n"));
+ CComServiceModule::s_pInstance = NULL;
+ pServiceModule->RevokeClassObjects();
+ }
+
+ LogRelFunc(("VBoxSDS: Calling _ServiceModule.Term()...\n"));
+ pServiceModule->Term();
+ }
+ else
+ LogRelFunc(("VBoxSDS: new CComServiceModule::Init failed: %Rhrc\n", hrcExit));
+
+ LogRelFunc(("VBoxSDS: deleting pServiceModule\n"));
+ delete pServiceModule;
+ pServiceModule = NULL;
+ }
+ else
+ LogRelFunc(("VBoxSDS: new CComServiceModule() failed\n"));
+
+ LogRelFunc(("VBoxSDS: Calling com::Shutdown\n"));
+ com::Shutdown();
+ }
+ else
+ LogRelFunc(("VBoxSDS: COM initialization failed: %Rrc\n", hrcExit));
+
+ LogRelFunc(("VBoxSDS: COM service process ends: hrcExit=%Rhrc (%#x)\n", hrcExit, hrcExit));
+ return (int)hrcExit;
+}
diff --git a/src/VBox/Main/src-global/win/VBoxSDS.rc b/src/VBox/Main/src-global/win/VBoxSDS.rc
new file mode 100644
index 00000000..49b72494
--- /dev/null
+++ b/src/VBox/Main/src-global/win/VBoxSDS.rc
@@ -0,0 +1,88 @@
+/* $Id: VBoxSDS.rc $ */
+/** @file
+ * VBoxSDS - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+#include "win/resource.h"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_APP
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4" // Lang=US English, CharSet=Windows Multilingual
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Global Interface\0"
+ VALUE "InternalName", "VBoxSDS\0"
+ VALUE "OriginalFilename", "VBoxSDS.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+
+ VALUE "OLESelfRegister", "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+/* Creates the application icon. */
+#include "VBoxSDS-icon.rc"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// REGISTRY
+//
+
+// IDR_VIRTUALBOX REGISTRY "VBoxSDS.rgs"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE DISCARDABLE
+BEGIN
+ IDS_SERVICENAME "VBoxSDS"
+ -100 "VirtualBox Global Service"
+ -101 "Workaround..."
+END
+
+1 TYPELIB "VirtualBox.tlb"
diff --git a/src/VBox/Main/src-global/win/VirtualBoxSDSImpl.cpp b/src/VBox/Main/src-global/win/VirtualBoxSDSImpl.cpp
new file mode 100644
index 00000000..1b98c629
--- /dev/null
+++ b/src/VBox/Main/src-global/win/VirtualBoxSDSImpl.cpp
@@ -0,0 +1,1371 @@
+/* $Id: VirtualBoxSDSImpl.cpp $ */
+/** @file
+ * VBox Global COM Class implementation.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_MAIN_VIRTUALBOXSDS
+#include <VBox/com/VirtualBox.h>
+#include <VBox/com/utils.h>
+#include "VirtualBoxSDSImpl.h"
+
+#include "AutoCaller.h"
+#include "LoggingNew.h"
+#include "Wrapper.h" /* for ArrayBSTRInConverter */
+
+#include <iprt/errcore.h>
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/system.h>
+
+#include <rpcasync.h>
+#include <rpcdcep.h>
+#include <sddl.h>
+#include <lmcons.h> /* UNLEN */
+
+#include "MachineLaunchVMCommonWorker.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define INTERACTIVE_SID_FLAG 0x1
+#define LOCAL_SID_FLAG 0x2
+#define LOGON_SID_FLAG 0x4
+#define IS_INTERACTIVE (LOCAL_SID_FLAG|INTERACTIVE_SID_FLAG|LOGON_SID_FLAG)
+
+
+/**
+ * Per user data.
+ *
+ * @note We never delete instances of this class, except in case of an insertion
+ * race. This allows us to separate the map lock from the user data lock
+ * and avoid DoS issues.
+ */
+class VBoxSDSPerUserData
+{
+public:
+ /** The SID (secure identifier) for the user. This is the key. */
+ com::Utf8Str m_strUserSid;
+ /** The user name (if we could get it). */
+ com::Utf8Str m_strUsername;
+ /** The VBoxSVC chosen to instantiate CLSID_VirtualBox.
+ * This is NULL if not set. */
+ ComPtr<IVBoxSVCRegistration> m_ptrTheChosenOne;
+ /** The PID of the chosen one. */
+ RTPROCESS m_pidTheChosenOne;
+ /** The tick count when the process in Windows session 0 started */
+ uint32_t m_tickTheChosenOne;
+ /** The current watcher thread index, UINT32_MAX if not watched. */
+ uint32_t m_iWatcher;
+ /** The chosen one revision number.
+ * This is used to detect races while waiting for a full watcher queue. */
+ uint32_t volatile m_iTheChosenOneRevision;
+private:
+ /** Reference count to make destruction safe wrt hung callers.
+ * (References are retain while holding the map lock in some form, but
+ * released while holding no locks.) */
+ uint32_t volatile m_cRefs;
+ /** Critical section protecting everything here. */
+ RTCRITSECT m_Lock;
+
+public:
+ VBoxSDSPerUserData(com::Utf8Str const &a_rStrUserSid, com::Utf8Str const &a_rStrUsername)
+ : m_strUserSid(a_rStrUserSid)
+ , m_strUsername(a_rStrUsername)
+ , m_pidTheChosenOne(NIL_RTPROCESS)
+ , m_tickTheChosenOne(0)
+#ifdef WITH_WATCHER
+ , m_iWatcher(UINT32_MAX)
+ , m_iTheChosenOneRevision(0)
+#endif
+ , m_cRefs(1)
+ {
+ RTCritSectInit(&m_Lock);
+ }
+
+ ~VBoxSDSPerUserData()
+ {
+ RTCritSectDelete(&m_Lock);
+ i_unchooseTheOne(true /*fIrregular*/);
+ }
+
+ uint32_t i_retain()
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&m_cRefs);
+ Assert(cRefs > 1);
+ return cRefs;
+ }
+
+ uint32_t i_release()
+ {
+ uint32_t cRefs = ASMAtomicDecU32(&m_cRefs);
+ Assert(cRefs < _1K);
+ if (cRefs == 0)
+ delete this;
+ return cRefs;
+ }
+
+ void i_lock()
+ {
+ RTCritSectEnter(&m_Lock);
+ }
+
+ void i_unlock()
+ {
+ RTCritSectLeave(&m_Lock);
+ }
+
+ /** Reset the chosen one. */
+ void i_unchooseTheOne(bool fIrregular)
+ {
+ if (m_ptrTheChosenOne.isNotNull())
+ {
+ if (!fIrregular)
+ m_ptrTheChosenOne.setNull();
+ else
+ {
+ LogRel(("i_unchooseTheOne: Irregular release ... (pid=%d (%#x) user=%s sid=%s)\n",
+ m_pidTheChosenOne, m_pidTheChosenOne, m_strUsername.c_str(), m_strUserSid.c_str()));
+ m_ptrTheChosenOne.setNull();
+ LogRel(("i_unchooseTheOne: ... done.\n"));
+ }
+ }
+ m_pidTheChosenOne = NIL_RTPROCESS;
+ m_tickTheChosenOne = 0;
+ }
+
+};
+
+
+
+/*********************************************************************************************************************************
+* VirtualBoxSDS - constructor / destructor *
+*********************************************************************************************************************************/
+
+VirtualBoxSDS::VirtualBoxSDS()
+ : m_cVBoxSvcProcesses(0)
+#ifdef WITH_WATCHER
+ , m_cWatchers(0)
+ , m_papWatchers(NULL)
+#endif
+{
+}
+
+
+VirtualBoxSDS::~VirtualBoxSDS()
+{
+#ifdef WITH_WATCHER
+ i_shutdownAllWatchers();
+ RTMemFree(m_papWatchers);
+ m_papWatchers = NULL;
+ m_cWatchers = 0;
+#endif
+}
+
+
+HRESULT VirtualBoxSDS::FinalConstruct()
+{
+ LogRelFlowThisFuncEnter();
+
+ int vrc = RTCritSectRwInit(&m_MapCritSect);
+ AssertLogRelRCReturn(vrc, E_FAIL);
+
+#ifdef WITH_WATCHER
+ vrc = RTCritSectInit(&m_WatcherCritSect);
+ AssertLogRelRCReturn(vrc, E_FAIL);
+#endif
+
+ LogRelFlowThisFuncLeave();
+ return S_OK;
+}
+
+
+void VirtualBoxSDS::FinalRelease()
+{
+ LogRelFlowThisFuncEnter();
+
+#ifdef WITH_WATCHER
+ i_shutdownAllWatchers();
+ RTCritSectDelete(&m_WatcherCritSect);
+#endif
+
+ RTCritSectRwDelete(&m_MapCritSect);
+
+ for (UserDataMap_T::iterator it = m_UserDataMap.begin(); it != m_UserDataMap.end(); ++it)
+ {
+ VBoxSDSPerUserData *pUserData = it->second;
+ if (pUserData)
+ {
+ it->second = NULL;
+ pUserData->i_release();
+ }
+ }
+
+ LogRelFlowThisFuncLeave();
+}
+
+/* static */
+bool VirtualBoxSDS::i_isFeatureEnabled(wchar_t const *a_pwszFeature)
+{
+ HKEY hKey;
+ LSTATUS lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Oracle\\VirtualBox\\VBoxSDS", 0, KEY_READ, &hKey);
+ /* Treat any errors as the feature is off. Because the actual error value doesn't matter. */
+ if (lrc != ERROR_SUCCESS)
+ return false;
+
+ DWORD dwType = 0;
+ DWORD dwValue = 0;
+ DWORD cbValue = sizeof(DWORD);
+ lrc = RegQueryValueExW(hKey, a_pwszFeature, NULL, &dwType, (LPBYTE)&dwValue, &cbValue);
+
+ bool const fEnabled = lrc == ERROR_SUCCESS
+ && dwType == REG_DWORD
+ && dwValue != 0;
+
+ RegCloseKey(hKey);
+ return fEnabled;
+}
+
+
+/*********************************************************************************************************************************
+* VirtualBoxSDS - IVirtualBoxSDS methods *
+*********************************************************************************************************************************/
+
+/* SDS plan B interfaces: */
+STDMETHODIMP VirtualBoxSDS::RegisterVBoxSVC(IVBoxSVCRegistration *aVBoxSVC, LONG aPid, IUnknown **aExistingVirtualBox)
+{
+ LogRel(("registerVBoxSVC: aPid=%u (%#x)\n", aPid, aPid));
+
+ /*
+ * Get the caller PID so we can validate the aPid parameter with the other two.
+ * The V2 structure requires Vista or later, so fake it if older.
+ */
+ RPC_CALL_ATTRIBUTES_V2_W CallAttribs = { RPC_CALL_ATTRIBUTES_VERSION, RPC_QUERY_CLIENT_PID | RPC_QUERY_IS_CLIENT_LOCAL };
+ RPC_STATUS rcRpc;
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0))
+ rcRpc = RpcServerInqCallAttributesW(NULL, &CallAttribs);
+ else
+ {
+ CallAttribs.ClientPID = (HANDLE)(intptr_t)aPid;
+ rcRpc = RPC_S_OK;
+ }
+
+ HRESULT hrc;
+ if ( RT_VALID_PTR(aVBoxSVC)
+ && RT_VALID_PTR(aExistingVirtualBox)
+ && rcRpc == RPC_S_OK
+ && (intptr_t)CallAttribs.ClientPID == aPid)
+ {
+ *aExistingVirtualBox = NULL;
+
+ /*
+ * Get the client user SID and name.
+ */
+ com::Utf8Str strSid;
+ com::Utf8Str strUsername;
+ if (i_getClientUserSid(&strSid, &strUsername))
+ {
+ VBoxSDSPerUserData *pUserData = i_lookupOrCreatePerUserData(strSid, strUsername); /* (returns holding the lock) */
+ if (pUserData)
+ {
+ /*
+ * If there already is a chosen one, ask it for a IVirtualBox instance
+ * to return to the caller. Should it be dead or unresponsive, the caller
+ * takes its place.
+ */
+ if (pUserData->m_ptrTheChosenOne.isNotNull())
+ {
+ try
+ {
+ hrc = pUserData->m_ptrTheChosenOne->GetVirtualBox(aExistingVirtualBox);
+ /* seems the VBoxSVC in windows session 0 is not yet finished object creation.
+ * Give it a time. */
+ if (FAILED(hrc) && GetTickCount() - pUserData->m_tickTheChosenOne < 60 * 1000)
+ hrc = E_PENDING;
+ }
+ catch (...)
+ {
+ LogRel(("registerVBoxSVC: Unexpected exception calling GetVirtualBox!!\n"));
+ hrc = E_FAIL;
+ }
+ if (FAILED_DEAD_INTERFACE(hrc))
+ {
+ LogRel(("registerVBoxSVC: Seems VBoxSVC instance died. Dropping it and letting caller take over. (hrc=%Rhrc)\n", hrc));
+#ifdef WITH_WATCHER
+ i_stopWatching(pUserData, pUserData->m_pidTheChosenOne);
+#endif
+ pUserData->i_unchooseTheOne(true /*fIrregular*/);
+ hrc = S_OK;
+ }
+ }
+ else
+ hrc = S_OK;
+
+ /* No chosen one? Make the caller the new chosen one! */
+ if (SUCCEEDED(hrc) && pUserData->m_ptrTheChosenOne.isNull())
+ {
+#ifdef VBOX_WITH_VBOXSVC_SESSION_0
+ DWORD dwSessionId = 0;
+ if (VirtualBoxSDS::i_isFeatureEnabled(L"ServerSession0"))
+ {
+ /* Get user token. */
+ HANDLE hThreadToken = NULL;
+ hrc = CoImpersonateClient();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = E_FAIL;
+ if (OpenThreadToken(GetCurrentThread(),
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE
+ | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE,
+ TRUE /* OpenAsSelf - for impersonation at SecurityIdentification level */,
+ &hThreadToken))
+ {
+ HANDLE hNewToken;
+ if (DuplicateTokenEx(hThreadToken, MAXIMUM_ALLOWED, NULL /*SecurityAttribs*/,
+ SecurityIdentification, TokenPrimary, &hNewToken))
+ {
+ CloseHandle(hThreadToken);
+ hThreadToken = hNewToken;
+ hrc = S_OK;
+ }
+ else
+ LogRel(("registerVBoxSVC: DuplicateTokenEx failed: %ld\n", GetLastError()));
+ }
+ else
+ LogRel(("registerVBoxSVC: OpenThreadToken failed: %ld\n", GetLastError()));
+
+ CoRevertToSelf();
+ }
+ else
+ LogRel(("registerVBoxSVC: CoImpersonateClient failed: %Rhrc\n", hrc));
+
+ /* check windows session */
+ if (SUCCEEDED(hrc) && hThreadToken != NULL)
+ {
+ hrc = E_FAIL;
+ DWORD cbSessionId = sizeof(DWORD);
+ if (GetTokenInformation(hThreadToken, TokenSessionId, (LPVOID)&dwSessionId, cbSessionId, &cbSessionId))
+ {
+ if (cbSessionId == sizeof(DWORD))
+ hrc = S_OK;
+ else
+ LogRel(("registerVBoxSVC: GetTokenInformation return value has invalid size\n"));
+ }
+ else
+ LogRel(("registerVBoxSVC: GetTokenInformation failed: %Rwc\n", GetLastError()));
+ }
+
+ /* Either the "VBoxSVC in windows session 0" feature is off or the request from VBoxSVC running
+ * in windows session 0. */
+ if (SUCCEEDED(hrc) && dwSessionId != 0)
+ {
+ /* if VBoxSVC in the Windows session 0 is not started or if it did not
+ * registered during a minute, start new one */
+ if ( pUserData->m_pidTheChosenOne == NIL_RTPROCESS
+ || GetTickCount() - pUserData->m_tickTheChosenOne > 60 * 1000)
+ {
+ uint32_t uSessionId = 0;
+ if (SetTokenInformation(hThreadToken, TokenSessionId, &uSessionId, sizeof(uint32_t)))
+ {
+ /*
+ * Start VBoxSVC process
+ */
+ char szPath[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArch(szPath, sizeof(szPath));
+ AssertRCReturn(vrc, vrc);
+
+ size_t cbBufLeft = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath));
+ AssertReturn(cbBufLeft > 0, VERR_FILENAME_TOO_LONG);
+
+ char *pszNamePart = &szPath[cbBufLeft];
+ cbBufLeft = sizeof(szPath) - cbBufLeft;
+
+ static const char s_szVirtualBox_exe[] = "VBoxSVC.exe";
+ vrc = RTStrCopy(pszNamePart, cbBufLeft, s_szVirtualBox_exe);
+ AssertRCReturn(vrc, vrc);
+
+ const char *apszArgs[] =
+ {
+ szPath,
+ "--registervbox",
+ NULL
+ };
+
+ RTPROCESS pid;
+ vrc = RTProcCreateEx(szPath,
+ apszArgs,
+ RTENV_DEFAULT,
+ RTPROC_FLAGS_TOKEN_SUPPLIED,
+ NULL, NULL, NULL, NULL, NULL, &hThreadToken, &pid);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pUserData->m_pidTheChosenOne = pid;
+ pUserData->m_tickTheChosenOne = GetTickCount();
+ hrc = E_PENDING;
+ }
+ else
+ LogRel(("registerVBoxSVC: Create VBoxSVC process failed: %Rrc\n", vrc));
+ }
+ else
+ {
+ hrc = E_FAIL;
+ LogRel(("registerVBoxSVC: SetTokenInformation failed: %ld\n", GetLastError()));
+ }
+ }
+ else /* the VBoxSVC in Windows session 0 already started */
+ hrc = E_PENDING;
+ }
+ CloseHandle(hThreadToken);
+ } /* Feature enabled */
+
+ if (SUCCEEDED(hrc) && dwSessionId == 0)
+ {
+#endif
+ LogRel(("registerVBoxSVC: Making aPid=%u (%#x) the chosen one for user %s (%s)!\n",
+ aPid, aPid, pUserData->m_strUserSid.c_str(), pUserData->m_strUsername.c_str()));
+#ifdef WITH_WATCHER
+ /* Open the process so we can watch it. */
+ HANDLE hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE /*fInherit*/, aPid);
+ if (hProcess == NULL)
+ hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE /*fInherit*/, aPid);
+ if (hProcess == NULL)
+ hProcess = OpenProcess(SYNCHRONIZE, FALSE /*fInherit*/, aPid);
+ if (hProcess != NULL)
+ {
+ if (i_watchIt(pUserData, hProcess, aPid))
+#endif
+ {
+ /* Make it official... */
+ pUserData->m_ptrTheChosenOne = aVBoxSVC;
+ pUserData->m_pidTheChosenOne = aPid;
+ hrc = S_OK;
+ }
+#ifdef WITH_WATCHER
+ else
+ {
+
+ LogRel(("registerVBoxSVC: i_watchIt failed!\n"));
+ hrc = RPC_E_OUT_OF_RESOURCES;
+ }
+ }
+ else
+ {
+ LogRel(("registerVBoxSVC: OpenProcess() failed: %Rwc\n", GetLastError()));
+ hrc = E_ACCESSDENIED;
+ }
+#endif
+#ifdef VBOX_WITH_VBOXSVC_SESSION_0
+ }
+#endif
+ }
+ pUserData->i_unlock();
+ pUserData->i_release();
+ }
+ else
+ hrc = E_OUTOFMEMORY;
+ }
+ else
+ hrc = E_FAIL;
+ }
+ else if ( !RT_VALID_PTR(aVBoxSVC)
+ || !RT_VALID_PTR(aExistingVirtualBox))
+ hrc = E_INVALIDARG;
+ else if (rcRpc != RPC_S_OK)
+ {
+ LogRel(("registerVBoxSVC: rcRpc=%d (%#x)!\n", rcRpc, rcRpc));
+ hrc = E_UNEXPECTED;
+ }
+ else
+ {
+ LogRel(("registerVBoxSVC: Client PID mismatch: aPid=%d (%#x), RPC ClientPID=%zd (%#zx)\n",
+ aPid, aPid, CallAttribs.ClientPID, CallAttribs.ClientPID));
+ hrc = E_INVALIDARG;
+ }
+ LogRel2(("VirtualBoxSDS::registerVBoxSVC: returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+
+STDMETHODIMP VirtualBoxSDS::DeregisterVBoxSVC(IVBoxSVCRegistration *aVBoxSVC, LONG aPid)
+{
+ LogRel(("deregisterVBoxSVC: aPid=%u (%#x)\n", aPid, aPid));
+ HRESULT hrc;
+ if (RT_VALID_PTR(aVBoxSVC))
+ {
+ /* Get the client user SID and name. */
+ com::Utf8Str strSid;
+ com::Utf8Str strUsername;
+ if (i_getClientUserSid(&strSid, &strUsername))
+ {
+ VBoxSDSPerUserData *pUserData = i_lookupPerUserData(strSid);
+ if (pUserData)
+ {
+ if (aVBoxSVC == (IVBoxSVCRegistration *)pUserData->m_ptrTheChosenOne)
+ {
+ LogRel(("deregisterVBoxSVC: It's the chosen one for %s (%s)!\n",
+ pUserData->m_strUserSid.c_str(), pUserData->m_strUsername.c_str()));
+#ifdef WITH_WATCHER
+ i_stopWatching(pUserData, pUserData->m_pidTheChosenOne);
+#endif
+ pUserData->i_unchooseTheOne(false /*fIrregular*/);
+ }
+ else
+ LogRel(("deregisterVBoxSVC: not the chosen one\n"));
+ pUserData->i_unlock();
+ pUserData->i_release();
+
+ hrc = S_OK;
+ }
+ else
+ {
+ LogRel(("deregisterVBoxSVC: Found no user data for %s (%s) (pid %u)\n",
+ strSid.c_str(), strUsername.c_str(), aPid));
+ hrc = S_OK;
+ }
+ }
+ else
+ hrc = E_FAIL;
+ }
+ else
+ hrc = E_INVALIDARG;
+ LogRel2(("VirtualBoxSDS::deregisterVBoxSVC: returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+
+STDMETHODIMP VirtualBoxSDS::LaunchVMProcess(IN_BSTR aMachine, IN_BSTR aComment, IN_BSTR aFrontend,
+ ComSafeArrayIn(IN_BSTR, aEnvironmentChanges),
+ IN_BSTR aCmdOptions, ULONG aSessionId, ULONG *aPid)
+{
+ /*
+ * Convert parameters to UTF-8.
+ */
+ Utf8Str strMachine(aMachine);
+ Utf8Str strComment(aComment);
+ Utf8Str strFrontend(aFrontend);
+ ArrayBSTRInConverter aStrEnvironmentChanges(ComSafeArrayInArg(aEnvironmentChanges));
+ Utf8Str strCmdOptions(aCmdOptions);
+
+ /*
+ * Impersonate the caller.
+ */
+ HRESULT hrc = CoImpersonateClient();
+ if (SUCCEEDED(hrc))
+ {
+ try
+ {
+ /*
+ * Try launch the VM process as the client.
+ */
+ RTPROCESS pid;
+ AssertCompile(sizeof(aSessionId) == sizeof(uint32_t));
+ int vrc = ::MachineLaunchVMCommonWorker(strMachine, strComment, strFrontend, aStrEnvironmentChanges.array(),
+ strCmdOptions, Utf8Str(),
+ RTPROC_FLAGS_AS_IMPERSONATED_TOKEN | RTPROC_FLAGS_SERVICE
+ | RTPROC_FLAGS_PROFILE | RTPROC_FLAGS_DESIRED_SESSION_ID,
+ &aSessionId, pid);
+ if (RT_SUCCESS(vrc))
+ {
+ *aPid = (ULONG)pid;
+ LogRel(("VirtualBoxSDS::LaunchVMProcess: launchVM succeeded\n"));
+ }
+ else if (vrc == VERR_INVALID_PARAMETER)
+ {
+ hrc = E_INVALIDARG;
+ LogRel(("VirtualBoxSDS::LaunchVMProcess: launchVM failed: %Rhrc\n", hrc));
+ }
+ else
+ {
+ hrc = VBOX_E_IPRT_ERROR;
+ LogRel(("VirtualBoxSDS::LaunchVMProcess: launchVM failed: %Rhrc (%Rrc)\n", hrc));
+ }
+ }
+ catch (...)
+ {
+ hrc = E_UNEXPECTED;
+ }
+ CoRevertToSelf();
+ }
+ else
+ LogRel(("VirtualBoxSDS::LaunchVMProcess: CoImpersonateClient failed: %Rhrc\n", hrc));
+ return hrc;
+}
+
+
+/*********************************************************************************************************************************
+* VirtualBoxSDS - Internal Methods *
+*********************************************************************************************************************************/
+
+/*static*/ bool VirtualBoxSDS::i_getClientUserSid(com::Utf8Str *a_pStrSid, com::Utf8Str *a_pStrUsername)
+{
+ bool fRet = false;
+ a_pStrSid->setNull();
+ a_pStrUsername->setNull();
+
+ HRESULT hrc = CoImpersonateClient();
+ if (SUCCEEDED(hrc))
+ {
+ HANDLE hToken = INVALID_HANDLE_VALUE;
+ if (::OpenThreadToken(GetCurrentThread(), TOKEN_READ, TRUE /*OpenAsSelf*/, &hToken))
+ {
+ CoRevertToSelf();
+
+ union
+ {
+ TOKEN_USER TokenUser;
+ uint8_t abPadding[SECURITY_MAX_SID_SIZE + 256];
+ WCHAR wszUsername[UNLEN + 1];
+ } uBuf;
+ RT_ZERO(uBuf);
+ DWORD cbActual = 0;
+ if (::GetTokenInformation(hToken, TokenUser, &uBuf, sizeof(uBuf), &cbActual))
+ {
+ WCHAR *pwszString;
+ if (ConvertSidToStringSidW(uBuf.TokenUser.User.Sid, &pwszString))
+ {
+ try
+ {
+ *a_pStrSid = pwszString;
+ a_pStrSid->toUpper(); /* (just to be on the safe side) */
+ fRet = true;
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRel(("i_GetClientUserSID: std::bad_alloc setting rstrSid.\n"));
+ }
+ LocalFree((HLOCAL)pwszString);
+
+ /*
+ * Get the username too. We don't care if this step fails.
+ */
+ if (fRet)
+ {
+ WCHAR wszUsername[UNLEN * 2 + 1];
+ DWORD cwcUsername = RT_ELEMENTS(wszUsername);
+ WCHAR wszDomain[UNLEN * 2 + 1];
+ DWORD cwcDomain = RT_ELEMENTS(wszDomain);
+ SID_NAME_USE enmNameUse;
+ if (LookupAccountSidW(NULL, uBuf.TokenUser.User.Sid, wszUsername, &cwcUsername,
+ wszDomain, &cwcDomain, &enmNameUse))
+ {
+ wszUsername[RT_ELEMENTS(wszUsername) - 1] = '\0';
+ wszDomain[RT_ELEMENTS(wszDomain) - 1] = '\0';
+ try
+ {
+ *a_pStrUsername = wszDomain;
+ a_pStrUsername->append('/');
+ a_pStrUsername->append(Utf8Str(wszUsername));
+ }
+ catch (std::bad_alloc &)
+ {
+ LogRel(("i_GetClientUserSID: std::bad_alloc setting rStrUsername.\n"));
+ a_pStrUsername->setNull();
+ }
+ }
+ else
+ LogRel(("i_GetClientUserSID: LookupAccountSidW failed: %u/%x (cwcUsername=%u, cwcDomain=%u)\n",
+ GetLastError(), cwcUsername, cwcDomain));
+ }
+ }
+ else
+ LogRel(("i_GetClientUserSID: ConvertSidToStringSidW failed: %u\n", GetLastError()));
+ }
+ else
+ LogRel(("i_GetClientUserSID: GetTokenInformation/TokenUser failed: %u\n", GetLastError()));
+ CloseHandle(hToken);
+ }
+ else
+ {
+ CoRevertToSelf();
+ LogRel(("i_GetClientUserSID: OpenThreadToken failed: %u\n", GetLastError()));
+ }
+ }
+ else
+ LogRel(("i_GetClientUserSID: CoImpersonateClient failed: %Rhrc\n", hrc));
+ return fRet;
+}
+
+
+/**
+ * Looks up the given user.
+ *
+ * @returns Pointer to the LOCKED and RETAINED per user data.
+ * NULL if not found.
+ * @param a_rStrUserSid The user SID.
+ */
+VBoxSDSPerUserData *VirtualBoxSDS::i_lookupPerUserData(com::Utf8Str const &a_rStrUserSid)
+{
+ int vrc = RTCritSectRwEnterShared(&m_MapCritSect);
+ if (RT_SUCCESS(vrc))
+ {
+
+ UserDataMap_T::iterator it = m_UserDataMap.find(a_rStrUserSid);
+ if (it != m_UserDataMap.end())
+ {
+ VBoxSDSPerUserData *pUserData = it->second;
+ pUserData->i_retain();
+
+ RTCritSectRwLeaveShared(&m_MapCritSect);
+
+ pUserData->i_lock();
+ return pUserData;
+ }
+
+ RTCritSectRwLeaveShared(&m_MapCritSect);
+ }
+ return NULL;
+}
+
+
+/**
+ * Looks up the given user, creating it if not found
+ *
+ * @returns Pointer to the LOCKED and RETAINED per user data.
+ * NULL on allocation error.
+ * @param a_rStrUserSid The user SID.
+ * @param a_rStrUsername The user name if available.
+ */
+VBoxSDSPerUserData *VirtualBoxSDS::i_lookupOrCreatePerUserData(com::Utf8Str const &a_rStrUserSid,
+ com::Utf8Str const &a_rStrUsername)
+{
+ /*
+ * Try do a simple lookup first.
+ */
+ VBoxSDSPerUserData *pUserData = i_lookupPerUserData(a_rStrUserSid);
+ if (!pUserData)
+ {
+ /*
+ * SID is not in map, create a new one.
+ */
+ try
+ {
+ pUserData = new VBoxSDSPerUserData(a_rStrUserSid, a_rStrUsername);
+ }
+ catch (std::bad_alloc &)
+ {
+ pUserData = NULL;
+ }
+ if (pUserData)
+ {
+ /*
+ * Insert it. We must check if someone raced us here.
+ */
+ VBoxSDSPerUserData *pUserDataFree = pUserData;
+ pUserData->i_lock();
+
+ int vrc = RTCritSectRwEnterExcl(&m_MapCritSect);
+ if (RT_SUCCESS(vrc))
+ {
+
+ UserDataMap_T::iterator it = m_UserDataMap.find(a_rStrUserSid);
+ if (it == m_UserDataMap.end())
+ {
+ try
+ {
+ m_UserDataMap[a_rStrUserSid] = pUserData;
+ pUserData->i_retain();
+ }
+ catch (std::bad_alloc &)
+ {
+ pUserData = NULL;
+ }
+ }
+ else
+ pUserData = NULL;
+
+ RTCritSectRwLeaveExcl(&m_MapCritSect);
+
+ if (pUserData)
+ LogRel(("i_lookupOrCreatePerUserData: Created new entry for %s (%s)\n",
+ pUserData->m_strUserSid.c_str(), pUserData->m_strUsername.c_str() ));
+ else
+ {
+ pUserDataFree->i_unlock();
+ delete pUserDataFree;
+ }
+ }
+ }
+ }
+
+ return pUserData;
+}
+
+
+#ifdef WITH_WATCHER
+/**
+ * Data about what's being watched.
+ */
+typedef struct VBoxSDSWatcherData
+{
+ /** The per-user data (referenced). */
+ VBoxSDSPerUserData *pUserData;
+ /** The chosen one revision number (for handling an almost impossible race
+ * where a client terminates while making a deregistration call). */
+ uint32_t iRevision;
+ /** The PID we're watching. */
+ RTPROCESS pid;
+
+ /** Sets the members to NULL values. */
+ void setNull()
+ {
+ pUserData = NULL;
+ iRevision = UINT32_MAX;
+ pid = NIL_RTPROCESS;
+ }
+} VBoxSDSWatcherData;
+
+/**
+ * Per watcher data.
+ */
+typedef struct VBoxSDSWatcher
+{
+ /** Pointer to the VBoxSDS instance. */
+ VirtualBoxSDS *pVBoxSDS;
+ /** The thread handle. */
+ RTTHREAD hThread;
+ /** Number of references to this structure. */
+ uint32_t volatile cRefs;
+ /** Set if the thread should shut down. */
+ bool volatile fShutdown;
+ /** Number of pending items in the todo array. */
+ uint32_t cTodos;
+ /** The watcher number. */
+ uint32_t iWatcher;
+ /** The number of handles once TODOs have been taken into account. */
+ uint32_t cHandlesEffective;
+ /** Number of handles / user data items being monitored. */
+ uint32_t cHandles;
+ /** Array of handles.
+ * The zero'th entry is the event semaphore use to signal the thread. */
+ HANDLE aHandles[MAXIMUM_WAIT_OBJECTS];
+ /** Array the runs parallel to aHandles with the VBoxSVC data. */
+ VBoxSDSWatcherData aData[MAXIMUM_WAIT_OBJECTS];
+ /** Pending changes. */
+ struct
+ {
+ /** If NULL the data is being removed, otherwise it's being added and
+ * this is the process handle to watch for termination. */
+ HANDLE hProcess;
+ /** The data about what's being watched. */
+ VBoxSDSWatcherData Data;
+ } aTodos[MAXIMUM_WAIT_OBJECTS * 4];
+
+
+ /** Helper for removing a handle & data table entry. */
+ uint32_t removeHandle(uint32_t a_iEntry, uint32_t a_cHandles)
+ {
+ uint32_t cToShift = a_cHandles - a_iEntry - 1;
+ if (cToShift > 0)
+ {
+ memmove(&aData[a_iEntry], &aData[a_iEntry + 1], sizeof(aData[0]) * cToShift);
+ memmove(&aHandles[a_iEntry], &aHandles[a_iEntry + 1], sizeof(aHandles[0]) * cToShift);
+ }
+ a_cHandles--;
+ aHandles[a_cHandles] = NULL;
+ aData[a_cHandles].setNull();
+
+ return a_cHandles;
+ }
+} VBoxSDSWatcher;
+
+
+
+/**
+ * Watcher thread.
+ */
+/*static*/ DECLCALLBACK(int) VirtualBoxSDS::i_watcherThreadProc(RTTHREAD hSelf, void *pvUser)
+{
+ VBoxSDSWatcher *pThis = (VBoxSDSWatcher *)pvUser;
+ VirtualBoxSDS *pVBoxSDS = pThis->pVBoxSDS;
+ RT_NOREF(hSelf);
+
+ /*
+ * This thread may release references to IVBoxSVCRegistration objects.
+ */
+ CoInitializeEx(NULL, COINIT_MULTITHREADED);
+
+ /*
+ * The loop.
+ */
+ RTCritSectEnter(&pVBoxSDS->m_WatcherCritSect);
+ while (!pThis->fShutdown)
+ {
+ /*
+ * Deal with the todo list.
+ */
+ uint32_t cHandles = pThis->cHandles;
+ uint32_t cTodos = pThis->cTodos;
+
+ for (uint32_t i = 0; i < cTodos; i++)
+ {
+ VBoxSDSPerUserData *pUserData = pThis->aTodos[i].Data.pUserData;
+ AssertContinue(pUserData);
+ if (pThis->aTodos[i].hProcess != NULL)
+ {
+ /* Add: */
+ AssertLogRelMsgBreakStmt(cHandles < RT_ELEMENTS(pThis->aHandles),
+ ("cHandles=%u cTodos=%u i=%u iWatcher=%u\n", cHandles, cTodos, i, pThis->iWatcher),
+ pThis->fShutdown = true);
+ pThis->aHandles[cHandles] = pThis->aTodos[i].hProcess;
+ pThis->aData[cHandles] = pThis->aTodos[i].Data;
+ cHandles++;
+ }
+ else
+ {
+ /* Remove: */
+ uint32_t cRemoved = 0;
+ uint32_t j = cHandles;
+ while (j-- > 1)
+ if (pThis->aData[j].pUserData == pUserData)
+ {
+ cHandles = pThis->removeHandle(j, cHandles);
+ pUserData->i_release();
+ cRemoved++;
+ }
+ if (cRemoved != 1)
+ LogRel(("i_watcherThreadProc/#%u: Warning! cRemoved=%u\n", pThis->iWatcher, cRemoved));
+ }
+ /* Zap the entry in case we assert and leave further up. */
+ pThis->aTodos[i].Data.setNull();
+ pThis->aTodos[i].hProcess = NULL;
+ }
+
+ Assert(cHandles > 0 && cHandles <= RT_ELEMENTS(pThis->aHandles));
+ pThis->cHandles = cHandles;
+ pThis->cHandlesEffective = cHandles;
+ pThis->cTodos = 0;
+
+ if (pThis->fShutdown)
+ break;
+
+ /*
+ * Wait.
+ */
+ RTCritSectLeave(&pVBoxSDS->m_WatcherCritSect);
+
+ LogRel(("i_watcherThreadProc/#%u: Waiting on %u handles...\n", pThis->iWatcher, cHandles));
+ DWORD const dwWait = WaitForMultipleObjects(cHandles, pThis->aHandles, FALSE /*fWaitAll*/, INFINITE);
+ LogRel(("i_watcherThreadProc/#%u: ... wait returned: %#x (%d)\n", pThis->iWatcher, dwWait, dwWait));
+
+ uint32_t const iHandle = dwWait - WAIT_OBJECT_0;
+ if (iHandle < cHandles && iHandle > 0)
+ {
+ /*
+ * A VBoxSVC process has terminated.
+ *
+ * Note! We need to take the user data lock before the watcher one here.
+ */
+ VBoxSDSPerUserData * const pUserData = pThis->aData[iHandle].pUserData;
+ uint32_t const iRevision = pThis->aData[iHandle].iRevision;
+ RTPROCESS const pid = pThis->aData[iHandle].pid;
+
+ pUserData->i_lock();
+ RTCritSectEnter(&pVBoxSDS->m_WatcherCritSect);
+
+ DWORD dwExit = 0;
+ GetExitCodeProcess(pThis->aHandles[iHandle], &dwExit);
+ LogRel(("i_watcherThreadProc/#%u: %s: PID %u/%#x termination detected: %d (%#x) [iRev=%u, cur %u]\n",
+ pThis->iWatcher, pUserData->m_strUsername.c_str(), pid, pid, dwExit, dwExit,
+ iRevision, pUserData->m_iTheChosenOneRevision));
+
+ /* Remove it from the handle array. */
+ CloseHandle(pThis->aHandles[iHandle]);
+ pThis->cHandles = cHandles = pThis->removeHandle(iHandle, cHandles);
+ pThis->cHandlesEffective -= 1;
+
+ /* If the process we were watching is still the current chosen one,
+ unchoose it and decrement the client count. Otherwise we were subject
+ to a deregistration/termination race (unlikely). */
+ if (pUserData->m_iTheChosenOneRevision == iRevision)
+ {
+ pUserData->i_unchooseTheOne(true /*fIrregular*/);
+ pUserData->i_unlock();
+ pVBoxSDS->i_decrementClientCount();
+ }
+ else
+ pUserData->i_unlock();
+ pUserData->i_release();
+ }
+ else
+ {
+ RTCritSectEnter(&pThis->pVBoxSDS->m_WatcherCritSect);
+ AssertLogRelMsgBreak(iHandle == 0 || dwWait == WAIT_TIMEOUT,
+ ("dwWait=%u (%#x) cHandles=%u\n", dwWait, dwWait, cHandles));
+ }
+ }
+
+ RTCritSectLeave(&pThis->pVBoxSDS->m_WatcherCritSect);
+
+ /*
+ * In case we quit w/o being told, signal i_watchIt that we're out of action.
+ */
+ pThis->fShutdown = true;
+
+ /*
+ * Release all our data on the way out.
+ */
+ uint32_t i = pThis->cHandles;
+ while (i-- > 1)
+ {
+ if (pThis->aData[i].pUserData)
+ {
+ pThis->aData[i].pUserData->i_release();
+ pThis->aData[i].pUserData = NULL;
+ }
+ if (pThis->aHandles[i])
+ {
+ CloseHandle(pThis->aHandles[i]);
+ pThis->aHandles[i] = NULL;
+ }
+ }
+ if (pThis->aHandles[0])
+ {
+ CloseHandle(pThis->aHandles[0]);
+ pThis->aHandles[0] = NULL;
+ }
+
+ i = pThis->cTodos;
+ pThis->cTodos = 0;
+ while (i-- > 0)
+ {
+ if (pThis->aTodos[i].Data.pUserData)
+ {
+ pThis->aTodos[i].Data.pUserData->i_release();
+ pThis->aTodos[i].Data.pUserData = NULL;
+ }
+ if (pThis->aTodos[i].hProcess)
+ {
+ CloseHandle(pThis->aTodos[i].hProcess);
+ pThis->aTodos[i].hProcess = NULL;
+ }
+ }
+
+ if (ASMAtomicDecU32(&pThis->cRefs) == 0)
+ RTMemFree(pThis);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Starts monitoring a VBoxSVC process.
+ *
+ * @param pUserData The user which chosen VBoxSVC should be watched.
+ * @param hProcess Handle to the VBoxSVC process. Consumed.
+ * @param pid The VBoxSVC PID.
+ * @returns Success indicator.
+ */
+bool VirtualBoxSDS::i_watchIt(VBoxSDSPerUserData *pUserData, HANDLE hProcess, RTPROCESS pid)
+{
+ RTCritSectEnter(&m_WatcherCritSect);
+
+ /*
+ * Find a watcher with capacity left over (we save 8 entries for removals).
+ */
+ for (uint32_t i = 0; i < m_cWatchers; i++)
+ {
+ VBoxSDSWatcher *pWatcher = m_papWatchers[i];
+ if ( pWatcher->cHandlesEffective < RT_ELEMENTS(pWatcher->aHandles)
+ && !pWatcher->fShutdown)
+ {
+ uint32_t iTodo = pWatcher->cTodos;
+ if (iTodo + 8 < RT_ELEMENTS(pWatcher->aTodos))
+ {
+ pWatcher->aTodos[iTodo].hProcess = hProcess;
+ pWatcher->aTodos[iTodo].Data.pUserData = pUserData;
+ pWatcher->aTodos[iTodo].Data.iRevision = ++pUserData->m_iTheChosenOneRevision;
+ pWatcher->aTodos[iTodo].Data.pid = pid;
+ pWatcher->cTodos = iTodo + 1;
+
+ pUserData->m_iWatcher = pWatcher->iWatcher;
+ pUserData->i_retain();
+
+ BOOL fRc = SetEvent(pWatcher->aHandles[0]);
+ AssertLogRelMsg(fRc, ("SetEvent() failed: %u\n", GetLastError()));
+ LogRel(("i_watchIt: Added process to watcher #%u: %RTbool\n", pWatcher->iWatcher, fRc));
+
+ i_incrementClientCount();
+ RTCritSectLeave(&m_WatcherCritSect);
+ RTThreadYield();
+ return true;
+ }
+ }
+ }
+
+ /*
+ * No watcher with capacity was found, so create a new one with
+ * the user/handle prequeued.
+ */
+ void *pvNew = RTMemRealloc(m_papWatchers, sizeof(m_papWatchers[0]) * (m_cWatchers + 1));
+ if (pvNew)
+ {
+ m_papWatchers = (VBoxSDSWatcher **)pvNew;
+ VBoxSDSWatcher *pWatcher = (VBoxSDSWatcher *)RTMemAllocZ(sizeof(*pWatcher));
+ if (pWatcher)
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(pWatcher->aData); i++)
+ pWatcher->aData[i].setNull();
+ for (uint32_t i = 0; i < RT_ELEMENTS(pWatcher->aTodos); i++)
+ pWatcher->aTodos[i].Data.setNull();
+
+ pWatcher->pVBoxSDS = this;
+ pWatcher->iWatcher = m_cWatchers;
+ pWatcher->cRefs = 2;
+ pWatcher->cHandlesEffective = 2;
+ pWatcher->cHandles = 2;
+ pWatcher->aHandles[0] = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL);
+ if (pWatcher->aHandles[0])
+ {
+ /* Add incoming VBoxSVC process in slot #1: */
+ pWatcher->aHandles[1] = hProcess;
+ pWatcher->aData[1].pid = pid;
+ pWatcher->aData[1].pUserData = pUserData;
+ pWatcher->aData[1].iRevision = ++pUserData->m_iTheChosenOneRevision;
+ pUserData->i_retain();
+ pUserData->m_iWatcher = pWatcher->iWatcher;
+
+ /* Start the thread and we're good. */
+ m_papWatchers[m_cWatchers++] = pWatcher;
+ int vrc = RTThreadCreateF(&pWatcher->hThread, i_watcherThreadProc, pWatcher, 0, RTTHREADTYPE_MAIN_WORKER,
+ RTTHREADFLAGS_WAITABLE, "watcher%u", pWatcher->iWatcher);
+ if (RT_SUCCESS(vrc))
+ {
+ LogRel(("i_watchIt: Created new watcher #%u\n", m_cWatchers));
+
+ i_incrementClientCount();
+ RTCritSectLeave(&m_WatcherCritSect);
+ return true;
+ }
+
+ LogRel(("i_watchIt: Error starting watcher thread: %Rrc\n", vrc));
+ m_papWatchers[--m_cWatchers] = NULL;
+
+ pUserData->m_iWatcher = UINT32_MAX;
+ pUserData->i_release();
+ CloseHandle(pWatcher->aHandles[0]);
+ }
+ else
+ LogRel(("i_watchIt: CreateEventW failed: %u\n", GetLastError()));
+ RTMemFree(pWatcher);
+ }
+ else
+ LogRel(("i_watchIt: failed to allocate watcher structure!\n"));
+ }
+ else
+ LogRel(("i_watchIt: Failed to grow watcher array to %u entries!\n", m_cWatchers + 1));
+
+ RTCritSectLeave(&m_WatcherCritSect);
+ CloseHandle(hProcess);
+ return false;
+}
+
+
+/**
+ * Stops monitoring a VBoxSVC process.
+ *
+ * @param pUserData The user which chosen VBoxSVC should be watched.
+ * @param pid The VBoxSVC PID.
+ */
+void VirtualBoxSDS::i_stopWatching(VBoxSDSPerUserData *pUserData, RTPROCESS pid)
+{
+ /*
+ * Add a remove order in the watcher's todo queue.
+ */
+ RTCritSectEnter(&m_WatcherCritSect);
+ for (uint32_t iRound = 0; ; iRound++)
+ {
+ uint32_t const iWatcher = pUserData->m_iWatcher;
+ if (iWatcher < m_cWatchers)
+ {
+ VBoxSDSWatcher *pWatcher = m_papWatchers[pUserData->m_iWatcher];
+ if (!pWatcher->fShutdown)
+ {
+ /*
+ * Remove duplicate todo entries.
+ */
+ bool fAddIt = true;
+ uint32_t iTodo = pWatcher->cTodos;
+ while (iTodo-- > 0)
+ if (pWatcher->aTodos[iTodo].Data.pUserData == pUserData)
+ {
+ if (pWatcher->aTodos[iTodo].hProcess == NULL)
+ fAddIt = true;
+ else
+ {
+ fAddIt = false;
+ CloseHandle(pWatcher->aTodos[iTodo].hProcess);
+ }
+ uint32_t const cTodos = --pWatcher->cTodos;
+ uint32_t const cToShift = cTodos - iTodo;
+ if (cToShift > 0)
+ memmove(&pWatcher->aTodos[iTodo], &pWatcher->aTodos[iTodo + 1], sizeof(pWatcher->aTodos[0]) * cToShift);
+ pWatcher->aTodos[cTodos].hProcess = NULL;
+ pWatcher->aTodos[cTodos].Data.setNull();
+ }
+
+ /*
+ * Did we just eliminated the add and cancel out this operation?
+ */
+ if (!fAddIt)
+ {
+ pUserData->m_iWatcher = UINT32_MAX;
+ pUserData->m_iTheChosenOneRevision++;
+ i_decrementClientCount();
+
+ RTCritSectLeave(&m_WatcherCritSect);
+ RTThreadYield();
+ return;
+ }
+
+ /*
+ * No we didn't. So, try append a removal item.
+ */
+ iTodo = pWatcher->cTodos;
+ if (iTodo < RT_ELEMENTS(pWatcher->aTodos))
+ {
+ pWatcher->aTodos[iTodo].hProcess = NULL;
+ pWatcher->aTodos[iTodo].Data.pUserData = pUserData;
+ pWatcher->aTodos[iTodo].Data.pid = pid;
+ pWatcher->aTodos[iTodo].Data.iRevision = pUserData->m_iTheChosenOneRevision++;
+ pWatcher->cTodos = iTodo + 1;
+ SetEvent(pWatcher->aHandles[0]);
+
+ pUserData->m_iWatcher = UINT32_MAX;
+ i_decrementClientCount();
+
+ RTCritSectLeave(&m_WatcherCritSect);
+ RTThreadYield();
+ return;
+ }
+ }
+ else
+ {
+ LogRel(("i_stopWatching: Watcher #%u has shut down.\n", iWatcher));
+ break;
+ }
+
+ /*
+ * Todo queue is full. Sleep a little and let the watcher process it.
+ */
+ LogRel(("i_stopWatching: Watcher #%u todo queue is full! (round #%u)\n", iWatcher, iRound));
+
+ uint32_t const iTheChosenOneRevision = pUserData->m_iTheChosenOneRevision;
+ SetEvent(pWatcher->aHandles[0]);
+
+ RTCritSectLeave(&m_WatcherCritSect);
+ RTThreadSleep(1 + (iRound & 127));
+ RTCritSectEnter(&m_WatcherCritSect);
+
+ AssertLogRelMsgBreak(pUserData->m_iTheChosenOneRevision == iTheChosenOneRevision,
+ ("Impossible! m_iTheChosenOneRevision changed %#x -> %#x!\n",
+ iTheChosenOneRevision, pUserData->m_iTheChosenOneRevision));
+ }
+ else
+ {
+ AssertLogRelMsg(pUserData->m_iWatcher == UINT32_MAX,
+ ("Impossible! iWatcher=%d m_cWatcher=%u\n", iWatcher, m_cWatchers));
+ break;
+ }
+ }
+ RTCritSectLeave(&m_WatcherCritSect);
+}
+
+
+/**
+ * Shutdowns all the watchers.
+ */
+void VirtualBoxSDS::i_shutdownAllWatchers(void)
+{
+ LogRel(("i_shutdownAllWatchers: %u watchers\n", m_cWatchers));
+
+ /* Notify them all. */
+ uint32_t i = m_cWatchers;
+ while (i-- > 0)
+ {
+ ASMAtomicWriteBool(&m_papWatchers[i]->fShutdown, true);
+ SetEvent(m_papWatchers[i]->aHandles[0]);
+ }
+
+ /* Wait for them to complete and destroy their data. */
+ i = m_cWatchers;
+ m_cWatchers = 0;
+ while (i-- > 0)
+ {
+ VBoxSDSWatcher *pWatcher = m_papWatchers[i];
+ if (pWatcher)
+ {
+ m_papWatchers[i] = NULL;
+
+ int vrc = RTThreadWait(pWatcher->hThread, RT_MS_1MIN / 2, NULL);
+ if (RT_SUCCESS(vrc))
+ pWatcher->hThread = NIL_RTTHREAD;
+ else
+ LogRel(("i_shutdownAllWatchers: RTThreadWait failed on #%u: %Rrc\n", i, vrc));
+
+ if (ASMAtomicDecU32(&pWatcher->cRefs) == 0)
+ RTMemFree(pWatcher);
+ }
+ }
+}
+
+
+/**
+ * Increments the VBoxSVC client count.
+ */
+void VirtualBoxSDS::i_incrementClientCount()
+{
+ Assert(RTCritSectIsOwner(&m_WatcherCritSect));
+ uint32_t cClients = ++m_cVBoxSvcProcesses;
+ Assert(cClients < 4096);
+ VBoxSDSNotifyClientCount(cClients);
+}
+
+
+/**
+ * Decrements the VBoxSVC client count.
+ */
+void VirtualBoxSDS::i_decrementClientCount()
+{
+ Assert(RTCritSectIsOwner(&m_WatcherCritSect));
+ uint32_t cClients = --m_cVBoxSvcProcesses;
+ Assert(cClients < 4096);
+ VBoxSDSNotifyClientCount(cClients);
+}
+
+
+#endif /* WITH_WATCHER */
+
+
+/* vi: set tabstop=4 shiftwidth=4 expandtab: */