diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Main/src-global/win | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Main/src-global/win')
-rw-r--r-- | src/VBox/Main/src-global/win/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Main/src-global/win/VBoxSDS.cpp | 1048 | ||||
-rw-r--r-- | src/VBox/Main/src-global/win/VBoxSDS.rc | 88 | ||||
-rw-r--r-- | src/VBox/Main/src-global/win/VirtualBoxSDSImpl.cpp | 1371 |
4 files changed, 2507 insertions, 0 deletions
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: */ |