VirtualBox

source: vbox/trunk/src/VBox/Main/src-global/win/VBoxSDS.cpp@ 108838

Last change on this file since 108838 was 107538, checked in by vboxsync, 5 months ago

src/VBox/Main/src-global/win/VBoxSDS.cpp: Fixed warnings found by Parfait (uninitialized attributes). Initialized thread ID and only post WM_QUIT if a thread ID actually has been assigned [build fix]. jiraref:VBP-1424

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.7 KB
Line 
1/* $Id: VBoxSDS.cpp 107538 2025-01-08 16:24:13Z vboxsync $ */
2/** @file
3 * VBoxSDS - COM global service main entry (System Directory Service)
4 */
5
6/*
7 * Copyright (C) 2017-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/** @page pg_VBoxSDS VBoxSDS - Per user CLSID_VirtualBox coordinater
30 *
31 * VBoxSDS is short for VirtualBox System Directory Service (SDS). Its purpose
32 * is to make sure there is only one CLSID_VirtualBox object running for each
33 * user using VirtualBox on a Windows host system.
34 *
35 *
36 * @section sec_vboxsds_backgroud Background
37 *
38 * COM is desktop oriented when it comes to activate-as-activator (AAA) COM
39 * servers. This means that if the users has two logins to the same box (e.g.
40 * physical console, RDP, SSHD) and tries to use an AAA COM server, a new server
41 * will be instantiated for each login. With the introduction of User Account
42 * Control (UAC) in Windows Vista, this was taken a step further and a user
43 * would talk different AAA COM server instances depending on the elevation
44 * level too.
45 *
46 * VBoxSVC is a service affected by this issue. Using VirtualBox across logins
47 * or between user elevation levels was impossible to do simultaneously. This
48 * was confusing and illogical to the user.
49 *
50 *
51 * @section sec_vboxsds_how How it works
52 *
53 * VBoxSDS assists in working around this problem by tracking which VBoxSVC
54 * server is currently providing CLSID_VirtualBox for a user. Each VBoxSVC
55 * instance will register itself with VBoxSDS when the CLSID_VirtualBox object
56 * is requested via their class factory. The first VBoxSVC registering for a
57 * given user will be allowed to instantate CLSID_VirtualBox. We will call this
58 * the chosen one. Subsequent VBoxSVC instance for the given user, regardless
59 * of elevation, session, windows station, or whatever else, will be told to use
60 * the instance from the first VBoxSVC.
61 *
62 * The registration call passes along an IVBoxSVCRegistration interface from
63 * VBoxSVC. VBoxSDS keeps this around for the chosen one only. When other
64 * VBoxSVC instances for the same user tries to register, VBoxSDS will ask the
65 * choosen one for its CLSID_VirtualBox object and return it to the new
66 * registrant.
67 *
68 * The chosen one will deregister with VBoxSDS before it terminates. Should it
69 * terminate abnormally, VBoxSDS will (probably) notice the next time it tries
70 * to request CLSID_VirtualBox from it and replace it as the chosen one with the
71 * new registrant.
72 *
73 *
74 * @section sec_vboxsds_locking Locking
75 *
76 * VBoxSDS stores data in a map indexed by the stringified secure identifier
77 * (SID) for each user. The map is protected by a shared critical section, so
78 * only inserting new users requires exclusive access.
79 *
80 * Each user data entry has it own lock (regular, not shared), so that it won't
81 * be necessary to hold down the map lock while accessing per user data. Thus
82 * preventing a user from blocking all others from using VirtualBox by
83 * suspending or debugging their chosen VBoxSVC process.
84 *
85 */
86
87
88/*********************************************************************************************************************************
89* Header Files *
90*********************************************************************************************************************************/
91#define LOG_GROUP LOG_GROUP_MAIN_VIRTUALBOXSDS
92#include <iprt/win/windows.h>
93#include <iprt/win/shlobj.h>
94
95#include "VBox/com/defs.h"
96#include "VBox/com/com.h"
97#include "VBox/com/VirtualBox.h"
98
99#include "VirtualBoxSDSImpl.h"
100#include "LoggingNew.h"
101
102#include <iprt/errcore.h>
103#include <iprt/asm.h>
104#include <iprt/buildconfig.h>
105#include <iprt/dir.h>
106#include <iprt/env.h>
107#include <iprt/err.h>
108#include <iprt/getopt.h>
109#include <iprt/initterm.h>
110#include <iprt/path.h>
111#include <iprt/message.h>
112#include <iprt/stream.h>
113#include <iprt/string.h>
114
115#include <VBox/com/microatl.h>
116
117#include <package-generated.h>
118#include "product-generated.h"
119
120#include <VBox/version.h>
121
122#define _ATL_FREE_THREADED /** @todo r=bird: WTF? */
123
124/**
125 * Implements Windows Service
126 */
127class ATL_NO_VTABLE CWindowsServiceModule
128{
129protected:
130 // data members
131 WCHAR m_wszServiceName[256];
132 WCHAR m_wszServiceDisplayName[256];
133 WCHAR m_wszServiceDescription[256];
134 SERVICE_STATUS_HANDLE m_hServiceStatus;
135 SERVICE_STATUS m_Status;
136 DWORD m_dwThreadID;
137
138 /** Pointer to the instance, for use by staticServiceMain and staticHandler. */
139 static CWindowsServiceModule *s_pInstance;
140
141public:
142 CWindowsServiceModule() throw()
143 {
144 // set up the initial service status
145 m_hServiceStatus = NULL;
146 m_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
147 m_Status.dwCurrentState = SERVICE_STOPPED;
148 m_Status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
149 m_Status.dwWin32ExitCode = 0;
150 m_Status.dwServiceSpecificExitCode = 0;
151 m_Status.dwCheckPoint = 0;
152 m_Status.dwWaitHint = 3000;
153
154 m_dwThreadID = 0;
155
156 s_pInstance = this;
157 }
158
159 virtual ~CWindowsServiceModule()
160 {
161 s_pInstance = NULL;
162 }
163
164 HRESULT startService(int /*nShowCmd*/) throw()
165 {
166 SERVICE_TABLE_ENTRY aServiceTable[] =
167 {
168 { m_wszServiceName, staticServiceMain },
169 { NULL, NULL }
170 };
171
172 if (::StartServiceCtrlDispatcher(aServiceTable) == 0)
173 {
174 m_Status.dwWin32ExitCode = ::GetLastError();
175 LogRelFunc(("Error: Cannot start service in console mode. Code: %u\n", m_Status.dwWin32ExitCode));
176 }
177
178 return m_Status.dwWin32ExitCode;
179 }
180
181 virtual HRESULT registerService() throw()
182 {
183 HRESULT hrc;
184 if (uninstallService())
185 {
186 hrc = onRegisterService();
187 if (SUCCEEDED(hrc))
188 {
189 if (installService())
190 hrc = S_OK;
191 else
192 hrc = E_FAIL;
193 }
194 }
195 else
196 hrc = E_FAIL;
197 return hrc;
198 }
199
200 virtual HRESULT unregisterService() throw()
201 {
202 HRESULT hrc = E_FAIL;
203 if (uninstallService())
204 hrc = onUnregisterService();
205 return hrc;
206 }
207
208private:
209 void serviceMain(DWORD, LPTSTR *) throw()
210 {
211 LogFunc(("Enter into serviceMain\n"));
212 // Register the control request handler
213 m_Status.dwCurrentState = SERVICE_START_PENDING;
214 m_dwThreadID = ::GetCurrentThreadId();
215 m_hServiceStatus = ::RegisterServiceCtrlHandler(m_wszServiceName, staticHandler);
216 if (m_hServiceStatus == NULL)
217 {
218 LogWarnFunc(("Handler not installed\n"));
219 return;
220 }
221 setServiceStatus(SERVICE_START_PENDING);
222
223 m_Status.dwWin32ExitCode = S_OK;
224 m_Status.dwCheckPoint = 0;
225 m_Status.dwWaitHint = 0;
226
227 // When the Run function returns, the service has stopped.
228 m_Status.dwWin32ExitCode = runService(SW_HIDE);
229
230 setServiceStatus(SERVICE_STOPPED);
231 LogFunc(("Windows Service stopped\n"));
232 }
233
234 /** Service table callback. */
235 static void WINAPI staticServiceMain(DWORD cArgs, LPTSTR *papwszArgs) throw()
236 {
237 AssertPtrReturnVoid(s_pInstance);
238 s_pInstance->serviceMain(cArgs, papwszArgs);
239 }
240
241 HRESULT runService(int nShowCmd = SW_HIDE) throw()
242 {
243 HRESULT hr = preMessageLoop(nShowCmd);
244
245 if (hr == S_OK)
246 runMessageLoop();
247
248 if (SUCCEEDED(hr))
249 hr = postMessageLoop();
250
251 return hr;
252 }
253
254protected:
255 /** Hook that's called before the message loop starts.
256 * Must return S_OK for it to start. */
257 virtual HRESULT preMessageLoop(int /*nShowCmd*/) throw()
258 {
259 LogFunc(("Enter\n"));
260 if (::InterlockedCompareExchange(&m_Status.dwCurrentState, SERVICE_RUNNING, SERVICE_START_PENDING) == SERVICE_START_PENDING)
261 {
262 LogFunc(("VBoxSDS Service started/resumed without delay\n"));
263 ::SetServiceStatus(m_hServiceStatus, &m_Status);
264 }
265 return S_OK;
266 }
267
268 /** Your typical windows message loop. */
269 virtual void runMessageLoop()
270 {
271 MSG msg;
272 while (::GetMessage(&msg, 0, 0, 0) > 0)
273 {
274 ::TranslateMessage(&msg);
275 ::DispatchMessage(&msg);
276 }
277 }
278
279 /** Hook that's called after the message loop ends. */
280 virtual HRESULT postMessageLoop()
281 {
282 return S_OK;
283 }
284
285 /** @name Overridable status change handlers
286 * @{ */
287 virtual void onStop() throw()
288 {
289 setServiceStatus(SERVICE_STOP_PENDING);
290 if (m_dwThreadID)
291 ::PostThreadMessage(m_dwThreadID, WM_QUIT, 0, 0);
292 LogFunc(("Windows Service stopped\n"));
293 }
294
295 virtual void onPause() throw()
296 {
297 }
298
299 virtual void onContinue() throw()
300 {
301 }
302
303 virtual void onInterrogate() throw()
304 {
305 }
306
307 virtual void onShutdown() throw()
308 {
309 }
310
311 virtual void onUnknownRequest(DWORD dwOpcode) throw()
312 {
313 LogRelFunc(("Bad service request: %u (%#x)\n", dwOpcode, dwOpcode));
314 }
315
316 virtual HRESULT onRegisterService()
317 {
318 return S_OK;
319 }
320
321 virtual HRESULT onUnregisterService()
322 {
323 return S_OK;
324 }
325 /** @} */
326
327private:
328 void handler(DWORD dwOpcode) throw()
329 {
330
331 switch (dwOpcode)
332 {
333 case SERVICE_CONTROL_STOP:
334 onStop();
335 break;
336 case SERVICE_CONTROL_PAUSE:
337 onPause();
338 break;
339 case SERVICE_CONTROL_CONTINUE:
340 onContinue();
341 break;
342 case SERVICE_CONTROL_INTERROGATE:
343 onInterrogate();
344 break;
345 case SERVICE_CONTROL_SHUTDOWN:
346 onShutdown();
347 break;
348 default:
349 onUnknownRequest(dwOpcode);
350 }
351 }
352
353 static void WINAPI staticHandler(DWORD dwOpcode) throw()
354 {
355 AssertPtrReturnVoid(s_pInstance);
356 s_pInstance->handler(dwOpcode);
357 }
358
359protected:
360 void setServiceStatus(DWORD dwState) throw()
361 {
362 uint32_t const uPrevState = ASMAtomicXchgU32((uint32_t volatile *)&m_Status.dwCurrentState, dwState);
363 if (!::SetServiceStatus(m_hServiceStatus, &m_Status))
364 LogRel(("Error: SetServiceStatus(,%u) failed: %u (uPrevState=%u)\n",
365 dwState, GetLastError(), uPrevState));
366 }
367
368
369public:
370 /** @note unused */
371 BOOL IsInstalled() throw()
372 {
373 BOOL fResult = FALSE;
374
375 SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
376 if (hSCM != NULL)
377 {
378 SC_HANDLE hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_QUERY_CONFIG);
379 if (hService != NULL)
380 {
381 fResult = TRUE;
382 ::CloseServiceHandle(hService);
383 }
384 ::CloseServiceHandle(hSCM);
385 }
386
387 return fResult;
388 }
389
390 BOOL installService() throw()
391 {
392 BOOL fResult = FALSE;
393 SC_HANDLE hSCM = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
394 if (hSCM != NULL)
395 {
396 SC_HANDLE hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_QUERY_CONFIG);
397 if (hService != NULL)
398 {
399 fResult = TRUE; /* Already installed. */
400
401 ::CloseServiceHandle(hService);
402 }
403 else
404 {
405 // Get the executable file path and quote it.
406 const int QUOTES_SPACE = 2;
407 WCHAR wszFilePath[MAX_PATH + QUOTES_SPACE];
408 DWORD cwcFilePath = ::GetModuleFileNameW(NULL, wszFilePath + 1, MAX_PATH);
409 if (cwcFilePath != 0 && cwcFilePath < MAX_PATH)
410 {
411 wszFilePath[0] = L'\"';
412 wszFilePath[cwcFilePath + 1] = L'\"';
413 wszFilePath[cwcFilePath + 2] = L'\0';
414
415 hService = ::CreateServiceW(hSCM, m_wszServiceName, m_wszServiceDisplayName,
416 SERVICE_CHANGE_CONFIG,
417 SERVICE_WIN32_OWN_PROCESS,
418 SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
419 wszFilePath, NULL, NULL, L"RPCSS\0", NULL, NULL);
420 if (hService != NULL)
421 {
422 SERVICE_DESCRIPTIONW sd;
423 sd.lpDescription = m_wszServiceDescription;
424 if (!::ChangeServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, &sd))
425 AssertLogRelMsgFailed(("Error: could not set service description: %u\n", GetLastError()));
426
427 fResult = TRUE;
428
429 ::CloseServiceHandle(hService);
430 }
431 else
432 AssertLogRelMsgFailed(("Error: Could not create service '%ls': %u\n", m_wszServiceName, GetLastError()));
433 }
434 else
435 AssertLogRelMsgFailed(("Error: GetModuleFileNameW returned %u: %u\n", cwcFilePath, GetLastError()));
436 }
437 }
438 else
439 AssertLogRelMsgFailed(("Error: Could not open the service control manager: %u\n", GetLastError()));
440 return fResult;
441 }
442
443 BOOL uninstallService() throw()
444 {
445 BOOL fResult = FALSE;
446 SC_HANDLE hSCM = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
447 if (hSCM != NULL)
448 {
449 SC_HANDLE hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_STOP | DELETE);
450 if (hService == NULL)
451 {
452 DWORD dwErr = GetLastError();
453 hService = ::OpenService(hSCM, m_wszServiceName, SERVICE_QUERY_CONFIG);
454 if (hService == NULL)
455 fResult = TRUE; /* Probably not installed or some access problem. */
456 else
457 {
458 ::CloseServiceHandle(hService);
459 AssertLogRelMsgFailed(("Error: Failed to open '%ls' for stopping and deletion: %u\n", m_wszServiceName, dwErr));
460 }
461 }
462 else
463 {
464 /* Try stop it. */
465 SERVICE_STATUS status;
466 RT_ZERO(status);
467 if (!::ControlService(hService, SERVICE_CONTROL_STOP, &status))
468 {
469 DWORD dwErr = GetLastError();
470 AssertLogRelMsg( dwErr == ERROR_SERVICE_NOT_ACTIVE
471 || ( dwErr == ERROR_SERVICE_CANNOT_ACCEPT_CTRL
472 && status.dwCurrentState == SERVICE_STOP_PENDING)
473 , ("Error: Failed to stop serive '%ls': dwErr=%u dwCurrentState=%u\n",
474 m_wszServiceName, dwErr, status.dwCurrentState));
475 }
476
477 /* Try delete it. */
478 fResult = ::DeleteService(hService);
479 AssertLogRelMsg(fResult, ("Error: Failed to delete serivce '%ls': %u\n", m_wszServiceName, GetLastError()));
480
481 ::CloseServiceHandle(hService);
482 }
483 ::CloseServiceHandle(hSCM);
484 }
485 else
486 AssertLogRelMsgFailed(("Error: Could not open the service control manager: %u\n", GetLastError()));
487 return fResult;
488 }
489};
490
491/*static*/ CWindowsServiceModule *CWindowsServiceModule::s_pInstance = NULL;
492
493
494/**
495 * Implements COM Module that used within Windows Service.
496 *
497 * It is derived from ComModule to intercept Unlock() and derived from
498 * CWindowsServiceModule to implement Windows Service
499 */
500class CComServiceModule : public CWindowsServiceModule, public ATL::CComModule
501{
502private:
503 /** Tracks whether Init() has been called for debug purposes. */
504 bool m_fInitialized;
505 /** Tracks COM init status for no visible purpose other than debugging. */
506 bool m_fComInitialized;
507 /** Part of the shutdown monitoring logic. */
508 bool volatile m_fActivity;
509#ifdef WITH_WATCHER
510 /** Part of the shutdown monitoring logic. */
511 bool volatile m_fHasClients;
512#endif
513 /** Auto reset event for communicating with the shutdown thread.
514 * This is created by startMonitor(). */
515 HANDLE m_hEventShutdown;
516 /** The main thread ID.
517 * The monitorShutdown code needs this to post a WM_QUIT message. */
518 DWORD m_dwMainThreadID;
519
520public:
521 /** Time for EXE to be idle before shutting down.
522 * Can be decreased at system shutdown phase. */
523 volatile uint32_t m_cMsShutdownTimeOut;
524
525 /** The service module instance. */
526 static CComServiceModule * volatile s_pInstance;
527
528public:
529 /**
530 * Constructor.
531 *
532 * @param cMsShutdownTimeout Number of milliseconds to idle without clients
533 * before autoamtically shutting down the service.
534 *
535 * The default is 2 seconds, because VBoxSVC (our
536 * only client) already does 5 seconds making the
537 * effective idle time 7 seconds from clients like
538 * VBoxManage's point of view. We consider single
539 * user and development as the dominant usage
540 * patterns here, not configuration activity by
541 * multiple users via VBoxManage.
542 */
543 CComServiceModule(DWORD cMsShutdownTimeout = 2000)
544 : m_fInitialized(false)
545 , m_fComInitialized(false)
546 , m_fActivity(false)
547#ifdef WITH_WATCHER
548 , m_fHasClients(false)
549#endif
550 , m_hEventShutdown(INVALID_HANDLE_VALUE)
551 , m_dwMainThreadID(~(DWORD)42)
552 , m_cMsShutdownTimeOut(cMsShutdownTimeout)
553 {
554 }
555
556 /**
557 * Initialization function.
558 */
559 HRESULT init(ATL::_ATL_OBJMAP_ENTRY *p, HINSTANCE h, const GUID *pLibID,
560 wchar_t const *p_wszServiceName, wchar_t const *p_wszDisplayName, wchar_t const *p_wszDescription)
561 {
562 HRESULT hrc = ATL::CComModule::Init(p, h, pLibID);
563 if (SUCCEEDED(hrc))
564 {
565 // copy service name
566 int vrc = ::RTUtf16Copy(m_wszServiceName, sizeof(m_wszServiceName), p_wszServiceName);
567 AssertRCReturn(vrc, E_NOT_SUFFICIENT_BUFFER);
568 vrc = ::RTUtf16Copy(m_wszServiceDisplayName, sizeof(m_wszServiceDisplayName), p_wszDisplayName);
569 AssertRCReturn(vrc, E_NOT_SUFFICIENT_BUFFER);
570 vrc = ::RTUtf16Copy(m_wszServiceDescription, sizeof(m_wszServiceDescription), p_wszDescription);
571 AssertRCReturn(vrc, E_NOT_SUFFICIENT_BUFFER);
572
573 m_fInitialized = true;
574 }
575
576 return hrc;
577 }
578
579 /**
580 * Overload CAtlModule::Unlock to trigger delayed automatic shutdown action.
581 */
582 virtual LONG Unlock() throw()
583 {
584 LONG cLocks = ATL::CComModule::Unlock();
585 LogFunc(("Unlock() called. Ref=%d\n", cLocks));
586 if (cLocks == 0)
587 {
588 ::ASMAtomicWriteBool(&m_fActivity, true);
589 ::SetEvent(m_hEventShutdown); // tell monitor that we transitioned to zero
590 }
591 return cLocks;
592 }
593
594 /**
595 * Overload CAtlModule::Lock to untrigger automatic shutdown.
596 */
597 virtual LONG Lock() throw()
598 {
599 LONG cLocks = ATL::CComModule::Lock();
600 LogFunc(("Lock() called. Ref=%d\n", cLocks));
601#ifdef WITH_WATCHER
602 ::ASMAtomicWriteBool(&m_fActivity, true);
603 ::SetEvent(m_hEventShutdown); /* reset the timeout interval */
604#endif
605 return cLocks;
606 }
607
608#ifdef WITH_WATCHER
609
610 /** Called to start the automatic shutdown behaviour based on client count
611 * rather than lock count.. */
612 void notifyZeroClientConnections()
613 {
614 m_fHasClients = false;
615 ::ASMAtomicWriteBool(&m_fActivity, true);
616 ::SetEvent(m_hEventShutdown);
617 }
618
619 /** Called to make sure automatic shutdown is cancelled. */
620 void notifyHasClientConnections()
621 {
622 m_fHasClients = true;
623 ::ASMAtomicWriteBool(&m_fActivity, true);
624 }
625
626#endif /* WITH_WATCHER */
627
628protected:
629
630 bool hasActiveConnection()
631 {
632#ifdef WITH_WATCHER
633 return m_fActivity || (m_fHasClients && GetLockCount() > 0);
634#else
635 return m_fActivity || GetLockCount() > 0;
636#endif
637 }
638
639 void monitorShutdown() throw()
640 {
641 for (;;)
642 {
643 ::WaitForSingleObject(m_hEventShutdown, INFINITE);
644 DWORD dwWait;
645 do
646 {
647 m_fActivity = false;
648 dwWait = ::WaitForSingleObject(m_hEventShutdown, m_cMsShutdownTimeOut);
649 } while (dwWait == WAIT_OBJECT_0);
650
651 /* timed out */
652 if (!hasActiveConnection()) /* if no activity let's really bail */
653 {
654 ::CoSuspendClassObjects();
655
656 /* Disable log rotation at this point, worst case a log file becomes slightly
657 bigger than it should. Avoids quirks with log rotation: There might be
658 another API service process running at this point which would rotate the
659 logs concurrently, creating a mess. */
660 PRTLOGGER pReleaseLogger = ::RTLogRelGetDefaultInstance();
661 if (pReleaseLogger)
662 {
663 char szDest[1024];
664 int vrc = ::RTLogQueryDestinations(pReleaseLogger, szDest, sizeof(szDest));
665 if (RT_SUCCESS(vrc))
666 {
667 vrc = ::RTStrCat(szDest, sizeof(szDest), " nohistory");
668 if (RT_SUCCESS(vrc))
669 {
670 vrc = ::RTLogDestinations(pReleaseLogger, szDest);
671 AssertRC(vrc);
672 }
673 }
674 }
675
676 if (!hasActiveConnection())
677 break;
678 LogRel(("Still got active connection(s)...\n"));
679 }
680 }
681
682 LogRel(("Shutting down\n"));
683 if (m_hEventShutdown)
684 {
685 ::CloseHandle(m_hEventShutdown);
686 m_hEventShutdown = NULL;
687 }
688 ::PostThreadMessage(m_dwMainThreadID, WM_QUIT, 0, 0);
689 }
690
691 static DECLCALLBACK(int) monitorThreadProc(RTTHREAD hThreadSelf, void *pvUser) throw()
692 {
693 RT_NOREF(hThreadSelf);
694 CComServiceModule *p = static_cast<CComServiceModule *>(pvUser);
695 p->monitorShutdown();
696 return VINF_SUCCESS;
697 }
698
699 void startMonitor()
700 {
701 m_dwMainThreadID = ::GetCurrentThreadId();
702 m_hEventShutdown = ::CreateEvent(NULL, false, false, NULL);
703 AssertLogRelMsg(m_hEventShutdown != NULL, ("GetLastError => %u\n", GetLastError()));
704
705 int vrc = RTThreadCreate(NULL, monitorThreadProc, this, 0 /*cbStack*/, RTTHREADTYPE_DEFAULT, 0 /*fFlags*/, "MonShdwn");
706 if (RT_FAILURE(vrc))
707 {
708 ::CloseHandle(m_hEventShutdown);
709 m_hEventShutdown = NULL;
710 LogRel(("Error: RTThreadCreate failed to create shutdown monitor thread: %Rrc\n", vrc));
711 }
712 }
713
714 virtual HRESULT preMessageLoop(int nShowCmd) throw()
715 {
716 Assert(m_fInitialized);
717 LogFunc(("Enter\n"));
718
719 HRESULT hrc = com::Initialize();
720 if (SUCCEEDED(hrc))
721 {
722 m_fComInitialized = true;
723 hrc = ATL::CComModule::RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED);
724 if (SUCCEEDED(hrc))
725 {
726 // Start Shutdown monitor here
727 startMonitor();
728
729 hrc = CWindowsServiceModule::preMessageLoop(nShowCmd);
730 if (FAILED(hrc))
731 LogRelFunc(("Warning: preMessageLoop failed: %Rhrc\n", hrc));
732
733 hrc = CoResumeClassObjects();
734 if (FAILED(hrc))
735 {
736 ATL::CComModule::RevokeClassObjects();
737 LogRelFunc(("Error: CoResumeClassObjects failed: %Rhrc\n", hrc));
738 }
739 }
740 else
741 LogRel(("Error: ATL::CComModule::RegisterClassObjects: %Rhrc\n", hrc));
742 }
743 else
744 LogRel(("Error: com::Initialize failed\n", hrc));
745 return hrc;
746 }
747
748 virtual HRESULT postMessageLoop()
749 {
750 com::Shutdown();
751 m_fComInitialized = false;
752 return S_OK;
753 }
754};
755
756/*static*/ CComServiceModule * volatile CComServiceModule::s_pInstance = NULL;
757
758
759#ifdef WITH_WATCHER
760/**
761 * Go-between for CComServiceModule and VirtualBoxSDS.
762 */
763void VBoxSDSNotifyClientCount(uint32_t cClients)
764{
765 CComServiceModule *pInstance = CComServiceModule::s_pInstance;
766 if (pInstance)
767 {
768 if (cClients == 0)
769 pInstance->notifyZeroClientConnections();
770 else
771 pInstance->notifyHasClientConnections();
772 }
773}
774#endif
775
776/**
777 * Shows an information message box.
778 *
779 * @param pszFormat The message text.
780 * @param ... Format string arguments.
781 */
782static void vboxSdsShowInfoMsgBox(const char *pszFormat, ...)
783{
784 va_list va;
785 va_start(va, pszFormat);
786
787 char *psz = NULL;
788 int vrc = RTStrAPrintfV(&psz, pszFormat, va);
789 AssertRCReturnVoid(vrc);
790
791 va_end(va);
792
793 PRTUTF16 pwsz = NULL;
794 vrc = RTStrToUtf16(psz, &pwsz);
795 AssertRCReturnVoid(vrc);
796
797 MessageBoxW(NULL, pwsz, L"VBoxSDS", MB_OK | MB_ICONINFORMATION);
798
799 RTUtf16Free(pwsz);
800 RTStrFree(psz);
801}
802
803/**
804 * Shows tool usage text.
805 *
806 * @returns RTEXITCODE_SYNTAX
807 */
808static RTEXITCODE vboxSdsShowUsage(void)
809{
810 vboxSdsShowInfoMsgBox(
811 VBOX_PRODUCT " VBoxSDS (System Directory Service) Version " VBOX_VERSION_STRING " - r%s\n"
812 "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
813 "\n"
814 " Service handling:\n"
815 " --regservice, /RegService\n"
816 " Registers COM out-of-proc service\n"
817 " --unregservice, /UnregService\n"
818 " Unregisters COM out-of-proc service\n"
819 " --reregservice, /ReregService\n"
820 " Unregisters and registers COM service\n"
821 "\n"
822 " Common options:\n"
823 " -V, --version\n"
824 " Displays version\n"
825 " -h, -?, --help\n"
826 " Displays help\n"
827 "\n"
828 " Logging options:\n"
829 " --logfile, /logfile </path/to/log>\n"
830 " Specifies the log file destination\n"
831 " --logsize, /logsize <bytes>\n"
832 " Specifies the maximum log size (in bytes)\n"
833 " --loginterval, /loginterval <s>\n"
834 " Specifies the maximum log time (in seconds)\n",
835 RTBldCfgRevisionStr());
836
837 return RTEXITCODE_SYNTAX;
838}
839
840
841/**
842 * Main function for the VBoxSDS process.
843 *
844 * @param hInstance The process instance.
845 * @param hPrevInstance Previous instance (not used here).
846 * @param nShowCmd The show flags.
847 * @param lpCmdLine The command line (not used here, we get it from the
848 * C runtime library).
849 *
850 * @return Exit code
851 */
852int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
853{
854 RT_NOREF(hPrevInstance, lpCmdLine);
855 int argc = __argc;
856 char **argv = __argv;
857
858 /*
859 * Initialize the VBox runtime without loading the support driver.
860 */
861 RTR3InitExe(argc, &argv, 0);
862
863 static const RTGETOPTDEF s_aOptions[] =
864 {
865 { "--embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
866 { "-embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
867 { "/embedding", 'e', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
868 { "--unregservice", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
869 { "-unregservice", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
870 { "/unregservice", 'u', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
871 { "--regservice", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
872 { "-regservice", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
873 { "/regservice", 'r', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
874 { "--reregservice", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
875 { "-reregservice", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
876 { "/reregservice", 'f', RTGETOPT_REQ_NOTHING | RTGETOPT_FLAG_ICASE },
877 { "--logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
878 { "-logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
879 { "/logfile", 'F', RTGETOPT_REQ_STRING | RTGETOPT_FLAG_ICASE },
880 { "--logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
881 { "-logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
882 { "/logrotate", 'R', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
883 { "--logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
884 { "-logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
885 { "/logsize", 'S', RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_ICASE },
886 { "--loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
887 { "-loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
888 { "/loginterval", 'I', RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_ICASE },
889 { "/?", 'h', RTGETOPT_REQ_NOTHING }, /* Most Windows programs use '/?', so have this as an alias. */
890 { "/h", 'h', RTGETOPT_REQ_NOTHING }, /* Ditto for '/h'. */
891 { "/help", 'h', RTGETOPT_REQ_NOTHING }, /* Ditto for '/help'. */
892 { "--version", 'V', RTGETOPT_REQ_NOTHING },
893 { "/V", 'V', RTGETOPT_REQ_NOTHING },
894 { "/version", 'V', RTGETOPT_REQ_NOTHING }
895 };
896
897 bool fRun = true;
898 bool fRegister = false;
899 bool fUnregister = false;
900 const char *pszLogFile = NULL;
901 uint32_t cHistory = 10; // enable log rotation, 10 files
902 uint32_t uHistoryFileTime = RT_SEC_1DAY; // max 1 day per file
903 uint64_t uHistoryFileSize = 100 * _1M; // max 100MB per file
904
905 RTGETOPTSTATE GetOptState;
906 int vrc = RTGetOptInit(&GetOptState, argc, argv, &s_aOptions[0], RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
907 AssertRC(vrc);
908
909 RTGETOPTUNION ValueUnion;
910 while ((vrc = RTGetOpt(&GetOptState, &ValueUnion)))
911 {
912 switch (vrc)
913 {
914 case 'e':
915 break;
916
917 case 'u':
918 fUnregister = true;
919 fRun = false;
920 break;
921
922 case 'r':
923 fRegister = true;
924 fRun = false;
925 break;
926
927 case 'f':
928 fUnregister = true;
929 fRegister = true;
930 fRun = false;
931 break;
932
933 case 'F':
934 pszLogFile = ValueUnion.psz;
935 break;
936
937 case 'R':
938 cHistory = ValueUnion.u32;
939 break;
940
941 case 'S':
942 uHistoryFileSize = ValueUnion.u64; /** @todo r=andy Too fine grained (bytes), MB would be enough, I think. */
943 break;
944
945 case 'I':
946 uHistoryFileTime = ValueUnion.u32; /** @todo r=andy Too fine grained (seconds), minutes/hours would be enough, I think. */
947 break;
948
949 case 'h':
950 {
951 vboxSdsShowUsage();
952 return RTEXITCODE_SUCCESS;
953 }
954
955 case 'V':
956 {
957 vboxSdsShowInfoMsgBox("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
958 return RTEXITCODE_SUCCESS;
959 }
960
961 default:
962 return vboxSdsShowUsage();
963 }
964 }
965
966 /*
967 * Default log location (LOGDIR) is %APPDATA%\VirtualBox\VBoxSDS.log.
968 *
969 * When running VBoxSDS as a regular user, LOGDIR typically will be 'C:\Users\<User>\AppData\Roaming\VirtualBox\'.
970 * When running VBoxSDS as a service (via SCM), LOGDIR typically will be 'C:\Windows\System32\config\systemprofile\AppData\Roaming\VirtualBox\'.
971 *
972 * We change the current directory to LOGDIR if possible.
973 *
974 * See @bugref{10632}.
975 *
976 * We only create the log file when running VBoxSDS normally, but not
977 * when registering/unregistering, at least for now.
978 */
979 if (fRun)
980 {
981 char szLogFile[RTPATH_MAX];
982 if (!pszLogFile || !*pszLogFile)
983 {
984 WCHAR wszSpecialFolder[MAX_PATH + 16];
985 if (SHGetSpecialFolderPathW(NULL, wszSpecialFolder, CSIDL_APPDATA, TRUE /*fCreate*/))
986 {
987 char *pszConv = szLogFile;
988 vrc = RTUtf16ToUtf8Ex(wszSpecialFolder, RTSTR_MAX, &pszConv, sizeof(szLogFile) - 12, NULL);
989 }
990 else if (SHGetSpecialFolderPathW(NULL, wszSpecialFolder, CSIDL_SYSTEM, TRUE /*fCreate*/))
991 {
992 char *pszConv = szLogFile;
993 vrc = RTUtf16ToUtf8Ex(wszSpecialFolder, RTSTR_MAX, &pszConv, sizeof(szLogFile) - 12, NULL);
994 }
995 else /* Note! No fallback to environment variables or such. See @bugref{10632}. */
996 vrc = VERR_PATH_NOT_FOUND;
997 if (RT_SUCCESS(vrc))
998 {
999 vrc = RTPathAppend(szLogFile, sizeof(szLogFile), "VirtualBox\\");
1000 if (RT_SUCCESS(vrc))
1001 {
1002 /* Make sure it exists. */
1003 if (!RTDirExists(szLogFile))
1004 vrc = RTDirCreate(szLogFile, 0755, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET);
1005 if (RT_SUCCESS(vrc))
1006 {
1007 /* Change into it.
1008 * If this fails, better don't continue, as there might be something fishy. */
1009 vrc = RTPathSetCurrent(szLogFile);
1010 if (RT_SUCCESS(vrc))
1011 vrc = RTStrCat(szLogFile, sizeof(szLogFile), "VBoxSDS.log");
1012 }
1013 }
1014 }
1015 if (RT_FAILURE(vrc))
1016 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct release log filename: %Rrc", vrc);
1017 pszLogFile = szLogFile;
1018 }
1019
1020 RTERRINFOSTATIC ErrInfo;
1021 vrc = com::VBoxLogRelCreate("COM Service", pszLogFile,
1022 RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG,
1023 VBOXSDS_LOG_DEFAULT, "VBOXSDS_RELEASE_LOG",
1024 RTLOGDEST_FILE | RTLOGDEST_FIXED_FILE | RTLOGDEST_FIXED_DIR,
1025 UINT32_MAX /* cMaxEntriesPerGroup */,
1026 cHistory, uHistoryFileTime, uHistoryFileSize,
1027 RTErrInfoInitStatic(&ErrInfo));
1028 if (RT_FAILURE(vrc))
1029 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open release log (%s, %Rrc)", ErrInfo.Core.pszMsg, vrc);
1030 }
1031
1032
1033 /*
1034 * Initialize COM.
1035 */
1036 HRESULT hrcExit = com::Initialize();
1037 if (SUCCEEDED(hrcExit))
1038 {
1039 HRESULT hrcSec = CoInitializeSecurity(NULL,
1040 -1,
1041 NULL,
1042 NULL,
1043 RPC_C_AUTHN_LEVEL_DEFAULT,
1044 RPC_C_IMP_LEVEL_IMPERSONATE,//RPC_C_IMP_LEVEL_IMPERSONATE, RPC_C_IMP_LEVEL_DELEGATE
1045 NULL,
1046 EOAC_NONE, //EOAC_DYNAMIC_CLOAKING,//EOAC_STATIC_CLOAKING, //EOAC_NONE,
1047 NULL);
1048 LogRelFunc(("VBoxSDS: InitializeSecurity: %x\n", hrcSec));
1049
1050 /*
1051 * Instantiate our COM service class.
1052 */
1053 CComServiceModule *pServiceModule = new CComServiceModule();
1054 if (pServiceModule)
1055 {
1056 BEGIN_OBJECT_MAP(s_aObjectMap)
1057 OBJECT_ENTRY(CLSID_VirtualBoxSDS, VirtualBoxSDS)
1058 END_OBJECT_MAP()
1059 hrcExit = pServiceModule->init(s_aObjectMap, hInstance, &LIBID_VirtualBox,
1060 L"VBoxSDS",
1061 L"VirtualBox system service",
1062 L"Used as a COM server for VirtualBox API.");
1063
1064 if (SUCCEEDED(hrcExit))
1065 {
1066 if (!fRun)
1067 {
1068 /*
1069 * Do registration work and quit.
1070 */
1071 /// @todo The VBoxProxyStub should do all work for COM registration
1072 if (fUnregister)
1073 hrcExit = pServiceModule->unregisterService();
1074 if (fRegister)
1075 hrcExit = pServiceModule->registerService();
1076 }
1077 else
1078 {
1079 /*
1080 * Run service.
1081 */
1082 CComServiceModule::s_pInstance = pServiceModule;
1083 hrcExit = pServiceModule->startService(nShowCmd);
1084 LogRelFunc(("VBoxSDS: Calling _ServiceModule.RevokeClassObjects()...\n"));
1085 CComServiceModule::s_pInstance = NULL;
1086 pServiceModule->RevokeClassObjects();
1087 }
1088
1089 LogRelFunc(("VBoxSDS: Calling _ServiceModule.Term()...\n"));
1090 pServiceModule->Term();
1091 }
1092 else
1093 LogRelFunc(("VBoxSDS: new CComServiceModule::Init failed: %Rhrc\n", hrcExit));
1094
1095 LogRelFunc(("VBoxSDS: deleting pServiceModule\n"));
1096 delete pServiceModule;
1097 pServiceModule = NULL;
1098 }
1099 else
1100 LogRelFunc(("VBoxSDS: new CComServiceModule() failed\n"));
1101
1102 LogRelFunc(("VBoxSDS: Calling com::Shutdown\n"));
1103 com::Shutdown();
1104 }
1105 else
1106 LogRelFunc(("VBoxSDS: COM initialization failed: %Rrc\n", hrcExit));
1107
1108 LogRelFunc(("VBoxSDS: COM service process ends: hrcExit=%Rhrc (%#x)\n", hrcExit, hrcExit));
1109 return (int)hrcExit;
1110}
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette