diff options
Diffstat (limited to 'src/VBox/Main/src-server/win/svcmain.cpp')
-rw-r--r-- | src/VBox/Main/src-server/win/svcmain.cpp | 1212 |
1 files changed, 1212 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/win/svcmain.cpp b/src/VBox/Main/src-server/win/svcmain.cpp new file mode 100644 index 00000000..311a3bd7 --- /dev/null +++ b/src/VBox/Main/src-server/win/svcmain.cpp @@ -0,0 +1,1212 @@ +/* $Id: svcmain.cpp $ */ +/** @file + * SVCMAIN - COM out-of-proc server main entry + */ + +/* + * Copyright (C) 2004-2022 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_VBOXSVC +#include <iprt/win/windows.h> +#ifdef DEBUG_bird +# include <RpcAsync.h> +#endif + +#include "VBox/com/defs.h" +#include "VBox/com/com.h" +#include "VBox/com/VirtualBox.h" + +#include "VirtualBoxImpl.h" +#include "LoggingNew.h" + +#include "svchlp.h" + +#include <iprt/errcore.h> +#include <iprt/buildconfig.h> +#include <iprt/initterm.h> +#include <iprt/string.h> +#include <iprt/path.h> +#include <iprt/getopt.h> +#include <iprt/message.h> +#include <iprt/asm.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define MAIN_WND_CLASS L"VirtualBox Interface" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +class CExeModule : public ATL::CComModule +{ +public: + LONG Unlock() throw(); + DWORD dwThreadID; + HANDLE hEventShutdown; + void MonitorShutdown(); + bool StartMonitor(); + bool HasActiveConnection(); + bool bActivity; + static bool isIdleLockCount(LONG cLocks); +}; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +BEGIN_OBJECT_MAP(ObjectMap) + OBJECT_ENTRY(CLSID_VirtualBox, VirtualBox) +END_OBJECT_MAP() + +CExeModule *g_pModule = NULL; +HWND g_hMainWindow = NULL; +HINSTANCE g_hInstance = NULL; +#ifdef VBOX_WITH_SDS +/** This is set if we're connected to SDS. + * + * It means that we should discount a server lock that it is holding when + * deciding whether we're idle or not. + * + * Also, when set we deregister with SDS during class factory destruction. We + * exploit this to prevent attempts to deregister during or after COM shutdown. + */ +bool g_fRegisteredWithVBoxSDS = false; +#endif + +/* Normal timeout usually used in Shutdown Monitor */ +const DWORD dwNormalTimeout = 5000; +volatile uint32_t dwTimeOut = dwNormalTimeout; /* time for EXE to be idle before shutting down. Can be decreased at system shutdown phase. */ + + + +/** Passed to CreateThread to monitor the shutdown event. */ +static DWORD WINAPI MonitorProc(void *pv) RT_NOTHROW_DEF +{ + CExeModule *p = (CExeModule *)pv; + p->MonitorShutdown(); + return 0; +} + +LONG CExeModule::Unlock() throw() +{ + LONG cLocks = ATL::CComModule::Unlock(); + if (isIdleLockCount(cLocks)) + { + bActivity = true; + SetEvent(hEventShutdown); /* tell monitor that we transitioned to zero */ + } + return cLocks; +} + +bool CExeModule::HasActiveConnection() +{ + return bActivity || !isIdleLockCount(GetLockCount()); +} + +/** + * Checks if @a cLocks signifies an IDLE server lock load. + * + * This takes VBoxSDS into account (i.e. ignores it). + */ +/*static*/ bool CExeModule::isIdleLockCount(LONG cLocks) +{ +#ifdef VBOX_WITH_SDS + if (g_fRegisteredWithVBoxSDS) + return cLocks <= 1; +#endif + return cLocks <= 0; +} + +/* Monitors the shutdown event */ +void CExeModule::MonitorShutdown() +{ + while (1) + { + WaitForSingleObject(hEventShutdown, INFINITE); + DWORD dwWait; + do + { + bActivity = false; + dwWait = WaitForSingleObject(hEventShutdown, dwTimeOut); + } while (dwWait == WAIT_OBJECT_0); + /* timed out */ + if (!HasActiveConnection()) /* if no activity let's really bail */ + { + /* 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 rc = RTLogQueryDestinations(pReleaseLogger, szDest, sizeof(szDest)); + if (RT_SUCCESS(rc)) + { + rc = RTStrCat(szDest, sizeof(szDest), " nohistory"); + if (RT_SUCCESS(rc)) + { + rc = RTLogDestinations(pReleaseLogger, szDest); + AssertRC(rc); + } + } + } +#if _WIN32_WINNT >= 0x0400 + CoSuspendClassObjects(); + if (!HasActiveConnection()) +#endif + break; + } + } + CloseHandle(hEventShutdown); + PostThreadMessage(dwThreadID, WM_QUIT, 0, 0); +} + +bool CExeModule::StartMonitor() +{ + hEventShutdown = CreateEvent(NULL, false, false, NULL); + if (hEventShutdown == NULL) + return false; + DWORD idThreadIgnored; + HANDLE h = CreateThread(NULL, 0, MonitorProc, this, 0, &idThreadIgnored); + return (h != NULL); +} + + +#ifdef VBOX_WITH_SDS + +class VBoxSVCRegistration; + +/** + * Custom class factory for the VirtualBox singleton. + * + * The implementation of CreateInstance is found in win/svcmain.cpp. + */ +class VirtualBoxClassFactory : public ATL::CComClassFactory +{ +private: + /** Tri state: 0=uninitialized or initializing; 1=success; -1=failure. + * This will be updated after both m_hrcCreate and m_pObj have been set. */ + volatile int32_t m_iState; + /** The result of the instantiation attempt. */ + HRESULT m_hrcCreate; + /** The IUnknown of the VirtualBox object/interface we're working with. */ + IUnknown *m_pObj; + /** Pointer to the IVBoxSVCRegistration implementation that VBoxSDS works with. */ + VBoxSVCRegistration *m_pVBoxSVC; + /** The VBoxSDS interface. */ + ComPtr<IVirtualBoxSDS> m_ptrVirtualBoxSDS; + +public: + VirtualBoxClassFactory() : m_iState(0), m_hrcCreate(S_OK), m_pObj(NULL), m_pVBoxSVC(NULL) + { } + + virtual ~VirtualBoxClassFactory() + { + if (m_pObj) + { + m_pObj->Release(); + m_pObj = NULL; + } + + /* We usually get here during g_pModule->Term() via CoRevokeClassObjec, so COM + probably working well enough to talk to SDS when we get here. */ + if (g_fRegisteredWithVBoxSDS) + i_deregisterWithSds(); + } + + // IClassFactory + STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid, void **ppvObj); + + /** Worker for VBoxSVCRegistration::getVirtualBox. */ + HRESULT i_getVirtualBox(IUnknown **ppResult); + +private: + HRESULT i_registerWithSds(IUnknown **ppOtherVirtualBox); + void i_deregisterWithSds(void); + + friend VBoxSVCRegistration; +}; + + +/** + * The VBoxSVC class is handed to VBoxSDS so it can call us back and ask for the + * VirtualBox object when the next VBoxSVC for this user registers itself. + */ +class VBoxSVCRegistration : public IVBoxSVCRegistration +{ +private: + /** Number of references. */ + uint32_t volatile m_cRefs; + +public: + /** Pointer to the factory. */ + VirtualBoxClassFactory *m_pFactory; + +public: + VBoxSVCRegistration(VirtualBoxClassFactory *pFactory) + : m_cRefs(1), m_pFactory(pFactory) + { } + virtual ~VBoxSVCRegistration() + { + if (m_pFactory) + { + if (m_pFactory->m_pVBoxSVC) + m_pFactory->m_pVBoxSVC = NULL; + m_pFactory = NULL; + } + } + RTMEMEF_NEW_AND_DELETE_OPERATORS(); + + // IUnknown + STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject) + { + if (riid == __uuidof(IUnknown)) + *ppvObject = (void *)(IUnknown *)this; + else if (riid == __uuidof(IVBoxSVCRegistration)) + *ppvObject = (void *)(IVBoxSVCRegistration *)this; + else + { + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + + } + + STDMETHOD_(ULONG,AddRef)(void) + { + uint32_t cRefs = ASMAtomicIncU32(&m_cRefs); + return cRefs; + } + + STDMETHOD_(ULONG,Release)(void) + { + uint32_t cRefs = ASMAtomicDecU32(&m_cRefs); + if (cRefs == 0) + delete this; + return cRefs; + } + + // IVBoxSVCRegistration + STDMETHOD(GetVirtualBox)(IUnknown **ppResult) + { + if (m_pFactory) + return m_pFactory->i_getVirtualBox(ppResult); + return E_FAIL; + } +}; + + +HRESULT VirtualBoxClassFactory::i_registerWithSds(IUnknown **ppOtherVirtualBox) +{ +# ifdef DEBUG_bird + RPC_CALL_ATTRIBUTES_V2_W CallAttribs = { RPC_CALL_ATTRIBUTES_VERSION, RPC_QUERY_CLIENT_PID | RPC_QUERY_IS_CLIENT_LOCAL }; + RPC_STATUS rcRpc = RpcServerInqCallAttributesW(NULL, &CallAttribs); + LogRel(("i_registerWithSds: RpcServerInqCallAttributesW -> %#x ClientPID=%#x IsClientLocal=%d ProtocolSequence=%#x CallStatus=%#x CallType=%#x OpNum=%#x InterfaceUuid=%RTuuid\n", + rcRpc, CallAttribs.ClientPID, CallAttribs.IsClientLocal, CallAttribs.ProtocolSequence, CallAttribs.CallStatus, + CallAttribs.CallType, CallAttribs.OpNum, &CallAttribs.InterfaceUuid)); +# endif + + /* + * Connect to VBoxSDS. + */ + HRESULT hrc = CoCreateInstance(CLSID_VirtualBoxSDS, NULL, CLSCTX_LOCAL_SERVER, IID_IVirtualBoxSDS, + (void **)m_ptrVirtualBoxSDS.asOutParam()); + if (SUCCEEDED(hrc)) + { + /* By default the RPC_C_IMP_LEVEL_IDENTIFY is used for impersonation the client. It allows + ACL checking but restricts an access to system objects e.g. files. Call to CoSetProxyBlanket + elevates the impersonation level up to RPC_C_IMP_LEVEL_IMPERSONATE allowing the VBoxSDS + service to access the files. */ + hrc = CoSetProxyBlanket(m_ptrVirtualBoxSDS, + RPC_C_AUTHN_DEFAULT, + RPC_C_AUTHZ_DEFAULT, + COLE_DEFAULT_PRINCIPAL, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_DEFAULT); + if (SUCCEEDED(hrc)) + { + /* + * Create VBoxSVCRegistration object and hand that to VBoxSDS. + */ + m_pVBoxSVC = new VBoxSVCRegistration(this); + hrc = E_PENDING; + /* we try to register IVirtualBox 10 times */ + for (int regTimes = 0; hrc == E_PENDING && regTimes < 10; --regTimes) + { + hrc = m_ptrVirtualBoxSDS->RegisterVBoxSVC(m_pVBoxSVC, GetCurrentProcessId(), ppOtherVirtualBox); + if (SUCCEEDED(hrc)) + { + g_fRegisteredWithVBoxSDS = !*ppOtherVirtualBox; + return hrc; + } + /* sleep to give a time for windows session 0 registration */ + if (hrc == E_PENDING) + RTThreadSleep(1000); + } + m_pVBoxSVC->Release(); + } + } + m_ptrVirtualBoxSDS.setNull(); + m_pVBoxSVC = NULL; + *ppOtherVirtualBox = NULL; + return hrc; +} + + +void VirtualBoxClassFactory::i_deregisterWithSds(void) +{ + Log(("VirtualBoxClassFactory::i_deregisterWithSds\n")); + + if (m_ptrVirtualBoxSDS.isNotNull()) + { + if (m_pVBoxSVC) + { + HRESULT hrc = m_ptrVirtualBoxSDS->DeregisterVBoxSVC(m_pVBoxSVC, GetCurrentProcessId()); + NOREF(hrc); + } + m_ptrVirtualBoxSDS.setNull(); + g_fRegisteredWithVBoxSDS = false; + } + if (m_pVBoxSVC) + { + m_pVBoxSVC->m_pFactory = NULL; + m_pVBoxSVC->Release(); + m_pVBoxSVC = NULL; + } +} + + +HRESULT VirtualBoxClassFactory::i_getVirtualBox(IUnknown **ppResult) +{ +# ifdef DEBUG_bird + RPC_CALL_ATTRIBUTES_V2_W CallAttribs = { RPC_CALL_ATTRIBUTES_VERSION, RPC_QUERY_CLIENT_PID | RPC_QUERY_IS_CLIENT_LOCAL }; + RPC_STATUS rcRpc = RpcServerInqCallAttributesW(NULL, &CallAttribs); + LogRel(("i_getVirtualBox: RpcServerInqCallAttributesW -> %#x ClientPID=%#x IsClientLocal=%d ProtocolSequence=%#x CallStatus=%#x CallType=%#x OpNum=%#x InterfaceUuid=%RTuuid\n", + rcRpc, CallAttribs.ClientPID, CallAttribs.IsClientLocal, CallAttribs.ProtocolSequence, CallAttribs.CallStatus, + CallAttribs.CallType, CallAttribs.OpNum, &CallAttribs.InterfaceUuid)); +# endif + IUnknown *pObj = m_pObj; + if (pObj) + { + /** @todo Do we need to do something regarding server locking? Hopefully COM + * deals with that........... */ + pObj->AddRef(); + *ppResult = pObj; + Log(("VirtualBoxClassFactory::GetVirtualBox: S_OK - %p\n", pObj)); + return S_OK; + } + *ppResult = NULL; + Log(("VirtualBoxClassFactory::GetVirtualBox: E_FAIL\n")); + return E_FAIL; +} + + +/** + * Custom instantiation of CComObjectCached. + * + * This catches certain QueryInterface callers for the purpose of watching for + * abnormal client process termination (@bugref{3300}). + * + * @todo just merge this into class VirtualBox VirtualBoxImpl.h + */ +class VirtualBoxObjectCached : public VirtualBox +{ +public: + VirtualBoxObjectCached(void * = NULL) + : VirtualBox() + { + } + + virtual ~VirtualBoxObjectCached() + { + m_iRef = LONG_MIN / 2; /* Catch refcount screwups by setting refcount something insane. */ + FinalRelease(); + } + + /** @name IUnknown implementation for VirtualBox + * @{ */ + + STDMETHOD_(ULONG, AddRef)() throw() + { + ULONG cRefs = InternalAddRef(); + if (cRefs == 2) + { + AssertMsg(ATL::_pAtlModule, ("ATL: referring to ATL module without having one declared in this linking namespace\n")); + ATL::_pAtlModule->Lock(); + } + return cRefs; + } + + STDMETHOD_(ULONG, Release)() throw() + { + ULONG cRefs = InternalRelease(); + if (cRefs == 0) + delete this; + else if (cRefs == 1) + { + AssertMsg(ATL::_pAtlModule, ("ATL: referring to ATL module without having one declared in this linking namespace\n")); + ATL::_pAtlModule->Unlock(); + } + return cRefs; + } + + STDMETHOD(QueryInterface)(REFIID iid, void **ppvObj) throw() + { + HRESULT hrc = _InternalQueryInterface(iid, ppvObj); +#ifdef VBOXSVC_WITH_CLIENT_WATCHER + i_logCaller("QueryInterface %RTuuid -> %Rhrc %p", &iid, hrc, *ppvObj); +#endif + return hrc; + } + + /** @} */ + + static HRESULT WINAPI CreateInstance(VirtualBoxObjectCached **ppObj) throw() + { + AssertReturn(ppObj, E_POINTER); + *ppObj = NULL; + + HRESULT hrc = E_OUTOFMEMORY; + VirtualBoxObjectCached *p = new (std::nothrow) VirtualBoxObjectCached(); + if (p) + { + p->SetVoid(NULL); + p->InternalFinalConstructAddRef(); + hrc = p->_AtlInitialConstruct(); + if (SUCCEEDED(hrc)) + hrc = p->FinalConstruct(); + p->InternalFinalConstructRelease(); + if (FAILED(hrc)) + delete p; + else + *ppObj = p; + } + return hrc; + } +}; + + +/** + * Custom class factory impl for the VirtualBox singleton. + * + * This will consult with VBoxSDS on whether this VBoxSVC instance should + * provide the actual VirtualBox instance or just forward the instance from + * some other SVC instance. + * + * @param pUnkOuter This must be NULL. + * @param riid Reference to the interface ID to provide. + * @param ppvObj Where to return the pointer to the riid instance. + * + * @return COM status code. + */ +STDMETHODIMP VirtualBoxClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void **ppvObj) +{ +# ifdef VBOXSVC_WITH_CLIENT_WATCHER + VirtualBox::i_logCaller("VirtualBoxClassFactory::CreateInstance: %RTuuid", riid); +# endif + HRESULT hrc = E_POINTER; + if (ppvObj != NULL) + { + *ppvObj = NULL; + // no aggregation for singletons + AssertReturn(pUnkOuter == NULL, CLASS_E_NOAGGREGATION); + + /* + * We must make sure there is only one instance around. + * So, we check without locking and then again after locking. + */ + if (ASMAtomicReadS32(&m_iState) == 0) + { + Lock(); + __try + { + if (ASMAtomicReadS32(&m_iState) == 0) + { + /* + * lock the module to indicate activity + * (necessary for the monitor shutdown thread to correctly + * terminate the module in case when CreateInstance() fails) + */ + ATL::_pAtlModule->Lock(); + __try + { + /* + * Now we need to connect to VBoxSDS to register ourselves. + */ + IUnknown *pOtherVirtualBox = NULL; + m_hrcCreate = hrc = i_registerWithSds(&pOtherVirtualBox); + if (SUCCEEDED(hrc) && pOtherVirtualBox) + m_pObj = pOtherVirtualBox; + else if (SUCCEEDED(hrc)) + { + ATL::_pAtlModule->Lock(); + VirtualBoxObjectCached *p; + m_hrcCreate = hrc = VirtualBoxObjectCached::CreateInstance(&p); + if (SUCCEEDED(hrc)) + { + m_hrcCreate = hrc = p->QueryInterface(IID_IUnknown, (void **)&m_pObj); + if (SUCCEEDED(hrc)) + RTLogClearFileDelayFlag(RTLogRelGetDefaultInstance(), NULL); + else + { + delete p; + i_deregisterWithSds(); + m_pObj = NULL; + } + } + } + ASMAtomicWriteS32(&m_iState, SUCCEEDED(hrc) ? 1 : -1); + } + __finally + { + ATL::_pAtlModule->Unlock(); + } + } + } + __finally + { + if (ASMAtomicReadS32(&m_iState) == 0) + { + ASMAtomicWriteS32(&m_iState, -1); + if (SUCCEEDED(m_hrcCreate)) + m_hrcCreate = E_FAIL; + } + Unlock(); + } + } + + /* + * Query the requested interface from the IUnknown one we're keeping around. + */ + if (m_hrcCreate == S_OK) + hrc = m_pObj->QueryInterface(riid, ppvObj); + else + hrc = m_hrcCreate; + } + return hrc; +} + +#endif // VBOX_WITH_SDS + + +/* +* Wrapper for Win API function ShutdownBlockReasonCreate +* This function defined starting from Vista only. +*/ +static BOOL ShutdownBlockReasonCreateAPI(HWND hWnd, LPCWSTR pwszReason) +{ + typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNSHUTDOWNBLOCKREASONCREATE,(HWND hWnd, LPCWSTR pwszReason)); + + PFNSHUTDOWNBLOCKREASONCREATE pfn + = (PFNSHUTDOWNBLOCKREASONCREATE)GetProcAddress(GetModuleHandle(L"User32.dll"), "ShutdownBlockReasonCreate"); + AssertPtr(pfn); + + BOOL fResult = FALSE; + if (pfn) + fResult = pfn(hWnd, pwszReason); + return fResult; +} + +/* +* Wrapper for Win API function ShutdownBlockReasonDestroy +* This function defined starting from Vista only. +*/ +static BOOL ShutdownBlockReasonDestroyAPI(HWND hWnd) +{ + typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNSHUTDOWNBLOCKREASONDESTROY,(HWND hWnd)); + PFNSHUTDOWNBLOCKREASONDESTROY pfn + = (PFNSHUTDOWNBLOCKREASONDESTROY)GetProcAddress(GetModuleHandle(L"User32.dll"), "ShutdownBlockReasonDestroy"); + AssertPtr(pfn); + + BOOL fResult = FALSE; + if (pfn) + fResult = pfn(hWnd); + return fResult; +} + +static LRESULT CALLBACK WinMainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + LRESULT lResult = 0; + + switch (msg) + { + case WM_QUERYENDSESSION: + { + LogRel(("WM_QUERYENDSESSION:%s%s%s%s (0x%08lx)\n", + lParam == 0 ? " shutdown" : "", + lParam & ENDSESSION_CRITICAL ? " critical" : "", + lParam & ENDSESSION_LOGOFF ? " logoff" : "", + lParam & ENDSESSION_CLOSEAPP ? " close" : "", + (unsigned long)lParam)); + if (g_pModule) + { + bool fActiveConnection = g_pModule->HasActiveConnection(); + if (fActiveConnection) + { + lResult = FALSE; + LogRel(("VBoxSvc has active connections:" + " bActivity = %RTbool, lock count = %d\n", + g_pModule->bActivity, g_pModule->GetLockCount())); + + /* place the VBoxSVC into system shutdown list */ + ShutdownBlockReasonCreateAPI(hwnd, L"Has active connections."); + /* decrease a latency of MonitorShutdown loop */ + ASMAtomicXchgU32(&dwTimeOut, 100); + Log(("VBoxSVCWinMain: WM_QUERYENDSESSION: VBoxSvc has active connections." + " bActivity = %d. Lock count = %d\n", + g_pModule->bActivity, g_pModule->GetLockCount())); + } + else + { + LogRel(("No active connections:" + " bActivity = %RTbool, lock count = %d\n", + g_pModule->bActivity, g_pModule->GetLockCount())); + lResult = TRUE; + } + } + else + AssertMsgFailed(("VBoxSVCWinMain: WM_QUERYENDSESSION: Error: g_pModule is NULL")); + break; + } + case WM_ENDSESSION: + { + LogRel(("WM_ENDSESSION:%s%s%s%s%s (%s/0x%08lx)\n", + lParam == 0 ? " shutdown" : "", + lParam & ENDSESSION_CRITICAL ? " critical" : "", + lParam & ENDSESSION_LOGOFF ? " logoff" : "", + lParam & ENDSESSION_CLOSEAPP ? " close" : "", + wParam == FALSE ? " cancelled" : "", + wParam ? "TRUE" : "FALSE", + (unsigned long)lParam)); + + /* Restore timeout of Monitor Shutdown if user canceled system shutdown */ + if (wParam == FALSE) + { + Log(("VBoxSVCWinMain: user canceled system shutdown.\n")); + ASMAtomicXchgU32(&dwTimeOut, dwNormalTimeout); + ShutdownBlockReasonDestroyAPI(hwnd); + } + break; + } + case WM_DESTROY: + { + ShutdownBlockReasonDestroyAPI(hwnd); + PostQuitMessage(0); + break; + } + + default: + { + lResult = DefWindowProc(hwnd, msg, wParam, lParam); + break; + } + } + return lResult; +} + +static int CreateMainWindow() +{ + int rc = VINF_SUCCESS; + Assert(g_hMainWindow == NULL); + + LogFlow(("CreateMainWindow\n")); + + g_hInstance = (HINSTANCE)GetModuleHandle(NULL); + + /* Register the Window Class. */ + WNDCLASS wc; + RT_ZERO(wc); + + wc.style = CS_NOCLOSE; + wc.lpfnWndProc = WinMainWndProc; + wc.hInstance = g_hInstance; + wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1); + wc.lpszClassName = MAIN_WND_CLASS; + + ATOM atomWindowClass = RegisterClass(&wc); + if (atomWindowClass == 0) + { + LogRel(("Failed to register window class for session monitoring\n")); + rc = VERR_NOT_SUPPORTED; + } + else + { + /* Create the window. */ + g_hMainWindow = CreateWindowEx(0, MAIN_WND_CLASS, MAIN_WND_CLASS, 0, + 0, 0, 1, 1, NULL, NULL, g_hInstance, NULL); + if (g_hMainWindow == NULL) + { + LogRel(("Failed to create window for session monitoring\n")); + rc = VERR_NOT_SUPPORTED; + } + } + return rc; +} + + +static void DestroyMainWindow() +{ + Assert(g_hMainWindow != NULL); + Log(("SVCMain: DestroyMainWindow \n")); + if (g_hMainWindow != NULL) + { + DestroyWindow(g_hMainWindow); + g_hMainWindow = NULL; + if (g_hInstance != NULL) + { + UnregisterClass(MAIN_WND_CLASS, g_hInstance); + g_hInstance = NULL; + } + } +} + + +static const char * const ctrl_event_names[] = { + "CTRL_C_EVENT", + "CTRL_BREAK_EVENT", + "CTRL_CLOSE_EVENT", + /* reserved, not used */ + "<console control event 3>", + "<console control event 4>", + /* not sent to processes that load gdi32.dll or user32.dll */ + "CTRL_LOGOFF_EVENT", + "CTRL_SHUTDOWN_EVENT", +}; + +/** @todo r=uwe placeholder */ +BOOL WINAPI +ConsoleCtrlHandler(DWORD dwCtrlType) RT_NOTHROW_DEF +{ + const char *signame; + char namebuf[48]; + // int rc; + + if (dwCtrlType < RT_ELEMENTS(ctrl_event_names)) + signame = ctrl_event_names[dwCtrlType]; + else + { + /* should not happen, but be prepared */ + RTStrPrintf(namebuf, sizeof(namebuf), + "<console control event %lu>", (unsigned long)dwCtrlType); + signame = namebuf; + } + LogRel(("Got %s\n", signame)); + + if (RT_UNLIKELY(g_pModule == NULL)) + { + LogRel(("%s: g_pModule == NULL\n", __FUNCTION__)); + return TRUE; + } + + /* decrease latency of the MonitorShutdown loop */ + ASMAtomicXchgU32(&dwTimeOut, 100); + + bool fHasClients = g_pModule->HasActiveConnection(); + if (!fHasClients) + { + LogRel(("No clients, closing the shop.\n")); + return TRUE; + } + + LogRel(("VBoxSvc has clients: bActivity = %RTbool, lock count = %d\n", + g_pModule->bActivity, g_pModule->GetLockCount())); + + /** @todo r=uwe wait for clients to disconnect */ + return TRUE; +} + + + +/** Special export that make VBoxProxyStub not register this process as one that + * VBoxSDS should be watching. + */ +extern "C" DECLEXPORT(void) VBOXCALL Is_VirtualBox_service_process_like_VBoxSDS_And_VBoxSDS(void) +{ + /* never called, just need to be here */ +} + + +/* thread for registering the VBoxSVC started in session 0 */ +static DWORD WINAPI threadRegisterVirtualBox(LPVOID lpParam) throw() +{ + HANDLE hEvent = (HANDLE)lpParam; + HRESULT hrc = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (SUCCEEDED(hrc)) + { + /* create IVirtualBox instance */ + ComPtr<IVirtualBox> pVirtualBox; + hrc = CoCreateInstance(CLSID_VirtualBox, NULL, CLSCTX_INPROC_SERVER /*CLSCTX_LOCAL_SERVER */, IID_IVirtualBox, + (void **)pVirtualBox.asOutParam()); + if (SUCCEEDED(hrc)) + { + /* wait a minute allowing clients to connect to the instance */ + WaitForSingleObject(hEvent, 60 * 1000); + /* remove reference. If anybody connected to IVirtualBox it will stay alive. */ + pVirtualBox.setNull(); + } + CoUninitialize(); + } + return 0L; +} + + +///////////////////////////////////////////////////////////////////////////// +// +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/) +{ + int argc = __argc; + char **argv = __argv; + + /* + * Need to parse the command line before initializing the VBox runtime so we can + * change to the user home directory before logs are being created. + */ + for (int i = 1; i < argc; i++) + if ( (argv[i][0] == '/' || argv[i][0] == '-') + && stricmp(&argv[i][1], "embedding") == 0) /* ANSI */ + { + /* %HOMEDRIVE%%HOMEPATH% */ + wchar_t wszHome[RTPATH_MAX]; + DWORD cEnv = GetEnvironmentVariable(L"HOMEDRIVE", &wszHome[0], RTPATH_MAX); + if (cEnv && cEnv < RTPATH_MAX) + { + DWORD cwc = cEnv; /* doesn't include NUL */ + cEnv = GetEnvironmentVariable(L"HOMEPATH", &wszHome[cEnv], RTPATH_MAX - cwc); + if (cEnv && cEnv < RTPATH_MAX - cwc) + { + /* If this fails there is nothing we can do. Ignore. */ + SetCurrentDirectory(wszHome); + } + } + } + + /* + * 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 }, + { "--unregserver", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + { "-unregserver", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + { "/unregserver", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + { "--regserver", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + { "-regserver", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + { "/regserver", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + { "--reregserver", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + { "-reregserver", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + { "/reregserver", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + { "--helper", 'H', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE }, + { "-helper", 'H', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE }, + { "/helper", 'H', RTGETOPT_REQ_STRING | 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 }, + { "--registervbox", 'b', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + { "-registervbox", 'b', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + { "/registervbox", 'b', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE }, + }; + + bool fRun = true; + bool fRegister = false; + bool fUnregister = false; + const char *pszPipeName = NULL; + 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 + bool fRegisterVBox = false; + + 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': + /* already handled above */ + 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 'H': + pszPipeName = ValueUnion.psz; + if (!pszPipeName) + pszPipeName = ""; + 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 const WCHAR s_wszText[] = L"Options:\n\n" + L"/RegServer:\tregister COM out-of-proc server\n" + L"/UnregServer:\tunregister COM out-of-proc server\n" + L"/ReregServer:\tunregister and register COM server\n" + L"no options:\trun the server"; + static const WCHAR s_wszTitle[] = L"Usage"; + fRun = false; + MessageBoxW(NULL, s_wszText, s_wszTitle, MB_OK); + return 0; + } + + case 'V': + { + static const WCHAR s_wszTitle[] = L"Version"; + char *pszText = NULL; + RTStrAPrintf(&pszText, "%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr()); + PRTUTF16 pwszText = NULL; + RTStrToUtf16(pszText, &pwszText); + RTStrFree(pszText); + MessageBoxW(NULL, pwszText, s_wszTitle, MB_OK); + RTUtf16Free(pwszText); + fRun = false; + return 0; + } + + case 'b': + fRegisterVBox = true; + break; + + default: + /** @todo this assumes that stderr is visible, which is not + * true for standard Windows applications. */ + /* continue on command line errors... */ + RTGetOptPrintError(vrc, &ValueUnion); + } + } + + /* Only create the log file when running VBoxSVC normally, but not when + * registering/unregistering or calling the helper functionality. */ + if (fRun) + { + /** @todo Merge this code with server.cpp (use Logging.cpp?). */ + char szLogFile[RTPATH_MAX]; + if (!pszLogFile || !*pszLogFile) + { + vrc = com::GetVBoxUserHomeDirectory(szLogFile, sizeof(szLogFile)); + if (RT_SUCCESS(vrc)) + vrc = RTPathAppend(szLogFile, sizeof(szLogFile), "VBoxSVC.log"); + if (RT_FAILURE(vrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to construct release log filename, rc=%Rrc", vrc); + pszLogFile = szLogFile; + } + + RTERRINFOSTATIC ErrInfo; + vrc = com::VBoxLogRelCreate("COM Server", pszLogFile, + RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG, + VBOXSVC_LOG_DEFAULT, "VBOXSVC_RELEASE_LOG", +#ifdef VBOX_WITH_SDS + RTLOGDEST_FILE | RTLOGDEST_F_DELAY_FILE, +#else + RTLOGDEST_FILE, +#endif + 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); + } + + /* Set up a build identifier so that it can be seen from core dumps what + * exact build was used to produce the core. Same as in Console::i_powerUpThread(). */ + static char saBuildID[48]; + RTStrPrintf(saBuildID, sizeof(saBuildID), "%s%s%s%s VirtualBox %s r%u %s%s%s%s", + "BU", "IL", "DI", "D", RTBldCfgVersion(), RTBldCfgRevision(), "BU", "IL", "DI", "D"); + + AssertCompile(VBOX_COM_INIT_F_DEFAULT == VBOX_COM_INIT_F_AUTO_REG_UPDATE); + HRESULT hRes = com::Initialize(fRun ? VBOX_COM_INIT_F_AUTO_REG_UPDATE : 0); + AssertLogRelMsg(SUCCEEDED(hRes), ("SVCMAIN: init failed: %Rhrc\n", hRes)); + + g_pModule = new CExeModule(); + if(g_pModule == NULL) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "not enough memory to create ExeModule."); + g_pModule->Init(ObjectMap, hInstance, &LIBID_VirtualBox); + g_pModule->dwThreadID = GetCurrentThreadId(); + + int nRet = 0; + if (!fRun) + { +#ifndef VBOX_WITH_MIDL_PROXY_STUB /* VBoxProxyStub.dll does all the registration work. */ + if (fUnregister) + { + g_pModule->UpdateRegistryFromResource(IDR_VIRTUALBOX, FALSE); + nRet = g_pModule->UnregisterServer(TRUE); + } + if (fRegister) + { + g_pModule->UpdateRegistryFromResource(IDR_VIRTUALBOX, TRUE); + nRet = g_pModule->RegisterServer(TRUE); + } +#endif + if (pszPipeName) + { + Log(("SVCMAIN: Processing Helper request (cmdline=\"%s\")...\n", pszPipeName)); + + if (!*pszPipeName) + vrc = VERR_INVALID_PARAMETER; + + if (RT_SUCCESS(vrc)) + { + /* do the helper job */ + SVCHlpServer server; + vrc = server.open(pszPipeName); + if (RT_SUCCESS(vrc)) + vrc = server.run(); + } + if (RT_FAILURE(vrc)) + { + Log(("SVCMAIN: Failed to process Helper request (%Rrc).\n", vrc)); + nRet = 1; + } + } + } + else + { + + g_pModule->StartMonitor(); +#if _WIN32_WINNT >= 0x0400 + hRes = g_pModule->RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED); + _ASSERTE(SUCCEEDED(hRes)); + hRes = CoResumeClassObjects(); +#else + hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE); +#endif + _ASSERTE(SUCCEEDED(hRes)); + + /* + * Register windows console signal handler to react to Ctrl-C, + * Ctrl-Break, Close; but more importantly - to get notified + * about shutdown when we are running in the context of the + * autostart service - we won't get WM_ENDSESSION in that + * case. + */ + ::SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); + + + if (RT_SUCCESS(CreateMainWindow())) + Log(("SVCMain: Main window succesfully created\n")); + else + Log(("SVCMain: Failed to create main window\n")); + + /* create thread to register IVirtualBox in VBoxSDS + * It is used for starting the VBoxSVC in the windows + * session 0. */ + HANDLE hWaitEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + HANDLE hRegisterVBoxThread = NULL; + if (fRegisterVBox) + { + DWORD dwThreadId = 0; + hRegisterVBoxThread = CreateThread(NULL, 0, threadRegisterVirtualBox, (LPVOID)hWaitEvent, + 0, &dwThreadId); + } + + MSG msg; + while (GetMessage(&msg, 0, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + DestroyMainWindow(); + + if (fRegisterVBox) + { + SetEvent(hWaitEvent); + WaitForSingleObject(hRegisterVBoxThread, INFINITE); + CloseHandle(hRegisterVBoxThread); + CloseHandle(hWaitEvent); + } + + g_pModule->RevokeClassObjects(); + } + + g_pModule->Term(); + +#ifdef VBOX_WITH_SDS + g_fRegisteredWithVBoxSDS = false; /* Don't trust COM LPC to work right from now on. */ +#endif + com::Shutdown(); + + if(g_pModule) + delete g_pModule; + g_pModule = NULL; + + Log(("SVCMAIN: Returning, COM server process ends.\n")); + return nRet; +} |