VirtualBox

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

Last change on this file was 104275, checked in by vboxsync, 6 weeks ago

VBoxSDS: Better check for log directory locations. bugref:10632

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

© 2023 Oracle
ContactPrivacy policyTerms of Use