/** * Try adjust the time using adjtime or similar. * * @returns true on success, false on failure. * * @param pDrift The time adjustment. */ static void vgsvcTimeSyncSet(PCRTTIMESPEC pDrift) { /* * Query the current time, adjust it by adding the drift and set it. */ RTTIMESPEC NewGuestTime; int rc = RTTimeSet(RTTimeSpecAdd(RTTimeNow(&NewGuestTime), pDrift)); if (RT_SUCCESS(rc)) { /* Succeeded - reset the error count and log the change. */ g_cTimeSyncErrors = 0; if (g_cVerbosity >= 1) { char sz[64]; RTTIME Time; VGSvcVerbose(1, "time set to %s\n", RTTimeToString(RTTimeExplode(&Time, &NewGuestTime), sz, sizeof(sz))); #ifdef DEBUG RTTIMESPEC Tmp; if (g_cVerbosity >= 3) VGSvcVerbose(3, " now %s\n", RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&Tmp)), sz, sizeof(sz))); #endif } } else if (g_cTimeSyncErrors++ < 10) VGSvcError("vgsvcTimeSyncSet: RTTimeSet(%RDtimespec) failed: %Rrc\n", &NewGuestTime, rc); }
static void WINAPI vgsvcWinMain(DWORD argc, LPTSTR *argv) { RT_NOREF2(argc, argv); VGSvcVerbose(2, "Registering service control handler ...\n"); if (g_pfnRegisterServiceCtrlHandlerExA) g_hWinServiceStatus = g_pfnRegisterServiceCtrlHandlerExA(VBOXSERVICE_NAME, vgsvcWinCtrlHandlerNt5Plus, NULL); else g_hWinServiceStatus = RegisterServiceCtrlHandlerA(VBOXSERVICE_NAME, vgsvcWinCtrlHandlerNt4); if (g_hWinServiceStatus != NULL) { VGSvcVerbose(2, "Service control handler registered.\n"); vgsvcWinStart(); } else { DWORD dwErr = GetLastError(); switch (dwErr) { case ERROR_INVALID_NAME: VGSvcError("Invalid service name!\n"); break; case ERROR_SERVICE_DOES_NOT_EXIST: VGSvcError("Service does not exist!\n"); break; default: VGSvcError("Could not register service control handle! Error: %ld\n", dwErr); break; } } }
/** * Callback registered by RegisterServiceCtrlHandler on NT5 and later. */ static DWORD WINAPI vgsvcWinCtrlHandlerNt5Plus(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) { VGSvcVerbose(2, "Control handler: dwControl=%#x, dwEventType=%#x\n", dwControl, dwEventType); RT_NOREF1(lpContext); switch (dwControl) { default: return vgsvcWinCtrlHandlerCommon(dwControl); case SERVICE_CONTROL_SESSIONCHANGE: /* Only Windows 2000 and up. */ { AssertPtr(lpEventData); PWTSSESSION_NOTIFICATION pNotify = (PWTSSESSION_NOTIFICATION)lpEventData; Assert(pNotify->cbSize == sizeof(WTSSESSION_NOTIFICATION)); VGSvcVerbose(1, "Control handler: %s (Session=%ld, Event=%#x)\n", vgsvcWTSStateToString(dwEventType), pNotify->dwSessionId, dwEventType); /* Handle all events, regardless of dwEventType. */ int rc2 = VGSvcVMInfoSignal(); AssertRC(rc2); return NO_ERROR; } } }
/** * Block the main thread until the service shuts down. * * @remarks Also called from VBoxService-win.cpp, thus not static. */ void VGSvcMainWait(void) { int rc; VGSvcReportStatus(VBoxGuestFacilityStatus_Active); #ifdef RT_OS_WINDOWS /* * Wait for the semaphore to be signalled. */ VGSvcVerbose(1, "Waiting in main thread\n"); rc = RTSemEventCreate(&g_hEvtWindowsService); AssertRC(rc); while (!ASMAtomicReadBool(&g_fWindowsServiceShutdown)) { rc = RTSemEventWait(g_hEvtWindowsService, RT_INDEFINITE_WAIT); AssertRC(rc); } RTSemEventDestroy(g_hEvtWindowsService); g_hEvtWindowsService = NIL_RTSEMEVENT; #else /* * Wait explicitly for a HUP, INT, QUIT, ABRT or TERM signal, blocking * all important signals. * * The annoying EINTR/ERESTART loop is for the benefit of Solaris where * sigwait returns when we receive a SIGCHLD. Kind of makes sense since */ sigset_t signalMask; sigemptyset(&signalMask); sigaddset(&signalMask, SIGHUP); sigaddset(&signalMask, SIGINT); sigaddset(&signalMask, SIGQUIT); sigaddset(&signalMask, SIGABRT); sigaddset(&signalMask, SIGTERM); pthread_sigmask(SIG_BLOCK, &signalMask, NULL); int iSignal; do { iSignal = -1; rc = sigwait(&signalMask, &iSignal); } while ( rc == EINTR # ifdef ERESTART || rc == ERESTART # endif ); VGSvcVerbose(3, "VGSvcMainWait: Received signal %d (rc=%d)\n", iSignal, rc); #endif /* !RT_OS_WINDOWS */ }
/** * Console control event callback. * * @returns TRUE if handled, FALSE if not. * @param dwCtrlType The control event type. * * @remarks This is generally called on a new thread, so we're racing every * other thread in the process. */ static BOOL WINAPI vgsvcWinConsoleControlHandler(DWORD dwCtrlType) { int rc = VINF_SUCCESS; bool fEventHandled = FALSE; switch (dwCtrlType) { /* User pressed CTRL+C or CTRL+BREAK or an external event was sent * via GenerateConsoleCtrlEvent(). */ case CTRL_BREAK_EVENT: case CTRL_CLOSE_EVENT: case CTRL_C_EVENT: VGSvcVerbose(2, "ControlHandler: Received break/close event\n"); rc = VGSvcStopServices(); fEventHandled = TRUE; break; default: break; /** @todo Add other events here. */ } if (RT_FAILURE(rc)) VGSvcError("ControlHandler: Event %ld handled with error rc=%Rrc\n", dwCtrlType, rc); return fEventHandled; }
/** * Common control handler. * * @returns Return code for NT5+. * @param dwControl The control code. */ static DWORD vgsvcWinCtrlHandlerCommon(DWORD dwControl) { DWORD rcRet = NO_ERROR; switch (dwControl) { case SERVICE_CONTROL_INTERROGATE: vgsvcWinSetStatus(g_dwWinServiceLastStatus, 0); break; case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: { vgsvcWinSetStatus(SERVICE_STOP_PENDING, 0); int rc2 = VGSvcStopServices(); if (RT_FAILURE(rc2)) rcRet = ERROR_GEN_FAILURE; else { rc2 = VGSvcReportStatus(VBoxGuestFacilityStatus_Terminated); AssertRC(rc2); } vgsvcWinSetStatus(SERVICE_STOPPED, 0); break; } default: VGSvcVerbose(1, "Control handler: Function not implemented: %#x\n", dwControl); rcRet = ERROR_CALL_NOT_IMPLEMENTED; break; } return rcRet; }
/** * Uninstalls the service. */ RTEXITCODE VGSvcWinUninstall(void) { VGSvcVerbose(1, "Uninstalling service ...\n"); SC_HANDLE hSCManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS); if (hSCManager == NULL) { VGSvcError("Could not open SCM! Error: %d\n", GetLastError()); return RTEXITCODE_FAILURE; } RTEXITCODE rcExit; SC_HANDLE hService = OpenService(hSCManager, VBOXSERVICE_NAME, SERVICE_ALL_ACCESS ); if (hService != NULL) { if (DeleteService(hService)) { /* * ??? */ HKEY hKey = NULL; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\System", 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) { RegDeleteKey(hKey, VBOXSERVICE_NAME); RegCloseKey(hKey); } VGSvcVerbose(0, "Service successfully uninstalled!\n"); rcExit = RTEXITCODE_SUCCESS; } else rcExit = VGSvcError("Could not remove service! Error: %d\n", GetLastError()); CloseServiceHandle(hService); } else rcExit = VGSvcError("Could not open service! Error: %d\n", GetLastError()); CloseServiceHandle(hSCManager); return rcExit; }
/** * @interface_method_impl{VBOXSERVICE,pfnInit} */ static DECLCALLBACK(int) vgsvcVMStatsInit(void) { VGSvcVerbose(3, "vgsvcVMStatsInit\n"); int rc = RTSemEventMultiCreate(&g_VMStatEvent); AssertRCReturn(rc, rc); g_VMStat.cMsStatInterval = 0; /* default; update disabled */ RT_ZERO(g_VMStat.au64LastCpuLoad_Idle); RT_ZERO(g_VMStat.au64LastCpuLoad_Kernel); RT_ZERO(g_VMStat.au64LastCpuLoad_User); RT_ZERO(g_VMStat.au64LastCpuLoad_Nice); rc = VbglR3StatQueryInterval(&g_VMStat.cMsStatInterval); if (RT_SUCCESS(rc)) VGSvcVerbose(3, "vgsvcVMStatsInit: New statistics interval %u seconds\n", g_VMStat.cMsStatInterval); else VGSvcVerbose(3, "vgsvcVMStatsInit: DeviceIoControl failed with %d\n", rc); #ifdef RT_OS_WINDOWS /* NtQuerySystemInformation might be dropped in future releases, so load it dynamically as per Microsoft's recommendation. */ *(void **)&g_VMStat.pfnNtQuerySystemInformation = RTLdrGetSystemSymbol("ntdll.dll", "NtQuerySystemInformation"); if (g_VMStat.pfnNtQuerySystemInformation) VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.pfnNtQuerySystemInformation = %x\n", g_VMStat.pfnNtQuerySystemInformation); else { VGSvcVerbose(3, "vgsvcVMStatsInit: ntdll.NtQuerySystemInformation not found!\n"); return VERR_SERVICE_DISABLED; } /* GlobalMemoryStatus is win2k and up, so load it dynamically */ *(void **)&g_VMStat.pfnGlobalMemoryStatusEx = RTLdrGetSystemSymbol("kernel32.dll", "GlobalMemoryStatusEx"); if (g_VMStat.pfnGlobalMemoryStatusEx) VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.GlobalMemoryStatusEx = %x\n", g_VMStat.pfnGlobalMemoryStatusEx); else { /** @todo Now fails in NT4; do we care? */ VGSvcVerbose(3, "vgsvcVMStatsInit: kernel32.GlobalMemoryStatusEx not found!\n"); return VERR_SERVICE_DISABLED; } /* GetPerformanceInfo is xp and up, so load it dynamically */ *(void **)&g_VMStat.pfnGetPerformanceInfo = RTLdrGetSystemSymbol("psapi.dll", "GetPerformanceInfo"); if (g_VMStat.pfnGetPerformanceInfo) VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.pfnGetPerformanceInfo= %x\n", g_VMStat.pfnGetPerformanceInfo); #endif /* RT_OS_WINDOWS */ return VINF_SUCCESS; }
/** * Cancels any pending time adjustment. * * Called when we've caught up and before calls to vgsvcTimeSyncSet. */ static void vgsvcTimeSyncCancelAdjust(void) { #ifdef RT_OS_WINDOWS /** @todo r=bird: g_hTokenProcess cannot be NULL here. See argumentation in * vgsvcTimeSyncAdjust. */ if (g_hTokenProcess == NULL) /* No process token (anymore)? */ return; if (SetSystemTimeAdjustment(0, TRUE /* Periodic adjustments disabled. */)) VGSvcVerbose(3, "vgsvcTimeSyncCancelAdjust: Windows Time Adjustment is now disabled.\n"); else if (g_cTimeSyncErrors++ < 10) VGSvcError("vgsvcTimeSyncCancelAdjust: SetSystemTimeAdjustment(,disable) failed, error=%u\n", GetLastError()); #endif /* !RT_OS_WINDOWS */ }
/** * Reports the current VBoxService status to the host. * * This makes sure that the Failed state is sticky. * * @return IPRT status code. * @param enmStatus Status to report to the host. */ int VGSvcReportStatus(VBoxGuestFacilityStatus enmStatus) { /* * VBoxGuestFacilityStatus_Failed is sticky. */ static VBoxGuestFacilityStatus s_enmLastStatus = VBoxGuestFacilityStatus_Inactive; VGSvcVerbose(4, "Setting VBoxService status to %u\n", enmStatus); if (s_enmLastStatus != VBoxGuestFacilityStatus_Failed) { int rc = VbglR3ReportAdditionsStatus(VBoxGuestFacilityType_VBoxService, enmStatus, 0 /* Flags */); if (RT_FAILURE(rc)) { VGSvcError("Could not report VBoxService status (%u), rc=%Rrc\n", enmStatus, rc); return rc; } s_enmLastStatus = enmStatus; } return VINF_SUCCESS; }
/** Reports our current status to the SCM. */ static BOOL vgsvcWinSetStatus(DWORD dwStatus, DWORD dwCheckPoint) { if (g_hWinServiceStatus == NULL) /* Program could be in testing mode, so no service environment available. */ return FALSE; VGSvcVerbose(2, "Setting service status to: %ld\n", dwStatus); g_dwWinServiceLastStatus = dwStatus; SERVICE_STATUS ss; RT_ZERO(ss); ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS; ss.dwCurrentState = dwStatus; /* Don't accept controls when in start pending state. */ if (ss.dwCurrentState != SERVICE_START_PENDING) { ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; /* Don't use SERVICE_ACCEPT_SESSIONCHANGE on Windows 2000 or earlier. This makes SCM angry. */ char szOSVersion[32]; int rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOSVersion, sizeof(szOSVersion)); if (RT_SUCCESS(rc)) { if (RTStrVersionCompare(szOSVersion, "5.1") >= 0) ss.dwControlsAccepted |= SERVICE_ACCEPT_SESSIONCHANGE; } else VGSvcError("Error determining OS version, rc=%Rrc\n", rc); } ss.dwWin32ExitCode = NO_ERROR; ss.dwServiceSpecificExitCode = 0; /* Not used */ ss.dwCheckPoint = dwCheckPoint; ss.dwWaitHint = 3000; BOOL fStatusSet = SetServiceStatus(g_hWinServiceStatus, &ss); if (!fStatusSet) VGSvcError("Error reporting service status=%ld (controls=%x, checkpoint=%ld) to SCM: %ld\n", dwStatus, ss.dwControlsAccepted, dwCheckPoint, GetLastError()); return fStatusSet; }
/** * @interface_method_impl{VBOXSERVICE,pfnWorker} */ DECLCALLBACK(int) vgsvcTimeSyncWorker(bool volatile *pfShutdown) { RTTIME Time; char sz[64]; int rc = VINF_SUCCESS; /* * Tell the control thread that it can continue spawning services. */ RTThreadUserSignal(RTThreadSelf()); /* * The Work Loop. */ for (;;) { /* * Try get a reliable time reading. */ int cTries = 3; do { /* query it. */ RTTIMESPEC GuestNow0, GuestNow, HostNow; RTTimeNow(&GuestNow0); int rc2 = VbglR3GetHostTime(&HostNow); if (RT_FAILURE(rc2)) { if (g_cTimeSyncErrors++ < 10) VGSvcError("vgsvcTimeSyncWorker: VbglR3GetHostTime failed; rc2=%Rrc\n", rc2); break; } RTTimeNow(&GuestNow); /* calc latency and check if it's ok. */ RTTIMESPEC GuestElapsed = GuestNow; RTTimeSpecSub(&GuestElapsed, &GuestNow0); if ((uint32_t)RTTimeSpecGetMilli(&GuestElapsed) < g_TimeSyncMaxLatency) { /* * Set the time once after we were restored. * (Of course only if the drift is bigger than MinAdjust) */ uint32_t TimeSyncSetThreshold = g_TimeSyncSetThreshold; if (g_fTimeSyncSetOnRestore) { uint64_t idNewSession = g_idTimeSyncSession; VbglR3GetSessionId(&idNewSession); if (idNewSession != g_idTimeSyncSession) { VGSvcVerbose(3, "vgsvcTimeSyncWorker: The VM session ID changed, forcing resync.\n"); TimeSyncSetThreshold = 0; g_idTimeSyncSession = idNewSession; } } /* * Calculate the adjustment threshold and the current drift. */ uint32_t MinAdjust = RTTimeSpecGetMilli(&GuestElapsed) * g_TimeSyncLatencyFactor; if (MinAdjust < g_TimeSyncMinAdjust) MinAdjust = g_TimeSyncMinAdjust; RTTIMESPEC Drift = HostNow; RTTimeSpecSub(&Drift, &GuestNow); if (RTTimeSpecGetMilli(&Drift) < 0) MinAdjust += g_TimeSyncMinAdjust; /* extra buffer against moving time backwards. */ RTTIMESPEC AbsDrift = Drift; RTTimeSpecAbsolute(&AbsDrift); if (g_cVerbosity >= 3) { VGSvcVerbose(3, "vgsvcTimeSyncWorker: Host: %s (MinAdjust: %RU32 ms)\n", RTTimeToString(RTTimeExplode(&Time, &HostNow), sz, sizeof(sz)), MinAdjust); VGSvcVerbose(3, "vgsvcTimeSyncWorker: Guest: - %s => %RDtimespec drift\n", RTTimeToString(RTTimeExplode(&Time, &GuestNow), sz, sizeof(sz)), &Drift); } uint32_t AbsDriftMilli = RTTimeSpecGetMilli(&AbsDrift); if (AbsDriftMilli > MinAdjust) { /* * Ok, the drift is above the threshold. * * Try a gradual adjustment first, if that fails or the drift is * too big, fall back on just setting the time. */ if ( AbsDriftMilli > TimeSyncSetThreshold || g_fTimeSyncSetNext || !vgsvcTimeSyncAdjust(&Drift)) { vgsvcTimeSyncCancelAdjust(); vgsvcTimeSyncSet(&Drift); } } else vgsvcTimeSyncCancelAdjust(); break; } VGSvcVerbose(3, "vgsvcTimeSyncWorker: %RDtimespec: latency too high (%RDtimespec) sleeping 1s\n", GuestElapsed); RTThreadSleep(1000); } while (--cTries > 0); /* Clear the set-next/set-start flag. */ g_fTimeSyncSetNext = false; /* * Block for a while. * * The event semaphore takes care of ignoring interruptions and it * allows us to implement service wakeup later. */ if (*pfShutdown) break; int rc2 = RTSemEventMultiWait(g_TimeSyncEvent, g_TimeSyncInterval); if (*pfShutdown) break; if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) { VGSvcError("vgsvcTimeSyncWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); rc = rc2; break; } } vgsvcTimeSyncCancelAdjust(); RTSemEventMultiDestroy(g_TimeSyncEvent); g_TimeSyncEvent = NIL_RTSEMEVENTMULTI; return rc; }
/** * Starts the service. * * @returns VBox status code, errors are fully bitched. * * @remarks Also called from VBoxService-win.cpp, thus not static. */ int VGSvcStartServices(void) { int rc; VGSvcReportStatus(VBoxGuestFacilityStatus_Init); /* * Initialize the services. */ VGSvcVerbose(2, "Initializing services ...\n"); for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) if (g_aServices[j].fEnabled) { rc = g_aServices[j].pDesc->pfnInit(); if (RT_FAILURE(rc)) { if (rc != VERR_SERVICE_DISABLED) { VGSvcError("Service '%s' failed to initialize: %Rrc\n", g_aServices[j].pDesc->pszName, rc); VGSvcReportStatus(VBoxGuestFacilityStatus_Failed); return rc; } g_aServices[j].fEnabled = false; VGSvcVerbose(0, "Service '%s' was disabled because of missing functionality\n", g_aServices[j].pDesc->pszName); } } /* * Start the service(s). */ VGSvcVerbose(2, "Starting services ...\n"); rc = VINF_SUCCESS; for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) { if (!g_aServices[j].fEnabled) continue; VGSvcVerbose(2, "Starting service '%s' ...\n", g_aServices[j].pDesc->pszName); rc = RTThreadCreate(&g_aServices[j].Thread, vgsvcThread, (void *)(uintptr_t)j, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, g_aServices[j].pDesc->pszName); if (RT_FAILURE(rc)) { VGSvcError("RTThreadCreate failed, rc=%Rrc\n", rc); break; } g_aServices[j].fStarted = true; /* Wait for the thread to initialize. */ /** @todo There is a race between waiting and checking * the fShutdown flag of a thread here and processing * the thread's actual worker loop. If the thread decides * to exit the loop before we skipped the fShutdown check * below the service will fail to start! */ /** @todo This presumably means either a one-shot service or that * something has gone wrong. In the second case treating it as failure * to start is probably right, so we need a way to signal the first * rather than leaving the idle thread hanging around. A flag in the * service description? */ RTThreadUserWait(g_aServices[j].Thread, 60 * 1000); if (g_aServices[j].fShutdown) { VGSvcError("Service '%s' failed to start!\n", g_aServices[j].pDesc->pszName); rc = VERR_GENERAL_FAILURE; } } if (RT_SUCCESS(rc)) VGSvcVerbose(1, "All services started.\n"); else { VGSvcError("An error occcurred while the services!\n"); VGSvcReportStatus(VBoxGuestFacilityStatus_Failed); } return rc; }
/** * Stops and terminates the services. * * This should be called even when VBoxServiceStartServices fails so it can * clean up anything that we succeeded in starting. * * @remarks Also called from VBoxService-win.cpp, thus not static. */ int VGSvcStopServices(void) { VGSvcReportStatus(VBoxGuestFacilityStatus_Terminating); /* * Signal all the services. */ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) ASMAtomicWriteBool(&g_aServices[j].fShutdown, true); /* * Do the pfnStop callback on all running services. */ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) if (g_aServices[j].fStarted) { VGSvcVerbose(3, "Calling stop function for service '%s' ...\n", g_aServices[j].pDesc->pszName); g_aServices[j].pDesc->pfnStop(); } VGSvcVerbose(3, "All stop functions for services called\n"); /* * Wait for all the service threads to complete. */ int rc = VINF_SUCCESS; for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) { if (!g_aServices[j].fEnabled) /* Only stop services which were started before. */ continue; if (g_aServices[j].Thread != NIL_RTTHREAD) { VGSvcVerbose(2, "Waiting for service '%s' to stop ...\n", g_aServices[j].pDesc->pszName); int rc2 = VINF_SUCCESS; for (int i = 0; i < 30; i++) /* Wait 30 seconds in total */ { rc2 = RTThreadWait(g_aServices[j].Thread, 1000 /* Wait 1 second */, NULL); if (RT_SUCCESS(rc2)) break; #ifdef RT_OS_WINDOWS /* Notify SCM that it takes a bit longer ... */ VGSvcWinSetStopPendingStatus(i + j*32); #endif } if (RT_FAILURE(rc2)) { VGSvcError("Service '%s' failed to stop. (%Rrc)\n", g_aServices[j].pDesc->pszName, rc2); rc = rc2; } } VGSvcVerbose(3, "Terminating service '%s' (%d) ...\n", g_aServices[j].pDesc->pszName, j); g_aServices[j].pDesc->pfnTerm(); } #ifdef RT_OS_WINDOWS /* * Wake up and tell the main() thread that we're shutting down (it's * sleeping in VBoxServiceMainWait). */ ASMAtomicWriteBool(&g_fWindowsServiceShutdown, true); if (g_hEvtWindowsService != NIL_RTSEMEVENT) { VGSvcVerbose(3, "Stopping the main thread...\n"); int rc2 = RTSemEventSignal(g_hEvtWindowsService); AssertRC(rc2); } #endif VGSvcVerbose(2, "Stopping services returning: %Rrc\n", rc); VGSvcReportStatus(RT_SUCCESS(rc) ? VBoxGuestFacilityStatus_Paused : VBoxGuestFacilityStatus_Failed); return rc; }
/** * Try adjust the time using adjtime or similar. * * @returns true on success, false on failure. * * @param pDrift The time adjustment. */ static bool vgsvcTimeSyncAdjust(PCRTTIMESPEC pDrift) { #ifdef RT_OS_WINDOWS /** @todo r=bird: g_hTokenProcess cannot be NULL here. * vgsvcTimeSyncInit will fail and the service will not be started with * it being NULL. vgsvcTimeSyncInit OTOH will *NOT* be called until the * service thread has terminated. If anything * else is the case, there is buggy code somewhere.*/ if (g_hTokenProcess == NULL) /* Is the token already closed when shutting down? */ return false; DWORD dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwWinTimeIncrement; BOOL fWinTimeAdjustmentDisabled; if (GetSystemTimeAdjustment(&dwWinTimeAdjustment, &dwWinTimeIncrement, &fWinTimeAdjustmentDisabled)) { DWORD dwDiffMax = g_dwWinTimeAdjustment * 0.50; DWORD dwDiffNew = dwWinTimeAdjustment * 0.10; if (RTTimeSpecGetMilli(pDrift) > 0) { dwWinNewTimeAdjustment = dwWinTimeAdjustment + dwDiffNew; if (dwWinNewTimeAdjustment > (g_dwWinTimeAdjustment + dwDiffMax)) { dwWinNewTimeAdjustment = g_dwWinTimeAdjustment + dwDiffMax; dwDiffNew = dwDiffMax; } } else { dwWinNewTimeAdjustment = dwWinTimeAdjustment - dwDiffNew; if (dwWinNewTimeAdjustment < (g_dwWinTimeAdjustment - dwDiffMax)) { dwWinNewTimeAdjustment = g_dwWinTimeAdjustment - dwDiffMax; dwDiffNew = dwDiffMax; } } VGSvcVerbose(3, "vgsvcTimeSyncAdjust: Drift=%lldms\n", RTTimeSpecGetMilli(pDrift)); VGSvcVerbose(3, "vgsvcTimeSyncAdjust: OrgTA=%ld, CurTA=%ld, NewTA=%ld, DiffNew=%ld, DiffMax=%ld\n", g_dwWinTimeAdjustment, dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwDiffNew, dwDiffMax); if (SetSystemTimeAdjustment(dwWinNewTimeAdjustment, FALSE /* Periodic adjustments enabled. */)) { g_cTimeSyncErrors = 0; return true; } if (g_cTimeSyncErrors++ < 10) VGSvcError("vgsvcTimeSyncAdjust: SetSystemTimeAdjustment failed, error=%u\n", GetLastError()); } else if (g_cTimeSyncErrors++ < 10) VGSvcError("vgsvcTimeSyncAdjust: GetSystemTimeAdjustment failed, error=%ld\n", GetLastError()); #elif defined(RT_OS_OS2) || defined(RT_OS_HAIKU) /* No API for doing gradual time adjustments. */ #else /* PORTME */ /* * Try use adjtime(), most unix-like systems have this. */ struct timeval tv; RTTimeSpecGetTimeval(pDrift, &tv); if (adjtime(&tv, NULL) == 0) { if (g_cVerbosity >= 1) VGSvcVerbose(1, "vgsvcTimeSyncAdjust: adjtime by %RDtimespec\n", pDrift); g_cTimeSyncErrors = 0; return true; } #endif /* failed */ return false; }
/** * @interface_method_impl{VBOXSERVICE,pfnInit} */ static DECLCALLBACK(int) vgsvcTimeSyncInit(void) { /* * If not specified, find the right interval default. * Then create the event sem to block on. */ if (!g_TimeSyncInterval) g_TimeSyncInterval = g_DefaultInterval * 1000; if (!g_TimeSyncInterval) g_TimeSyncInterval = 10 * 1000; VbglR3GetSessionId(&g_idTimeSyncSession); /* The status code is ignored as this information is not available with VBox < 3.2.10. */ int rc = RTSemEventMultiCreate(&g_TimeSyncEvent); AssertRC(rc); #ifdef RT_OS_WINDOWS if (RT_SUCCESS(rc)) { /* * Adjust privileges of this process so we can make system time adjustments. */ if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &g_hTokenProcess)) { TOKEN_PRIVILEGES tkPriv; RT_ZERO(tkPriv); tkPriv.PrivilegeCount = 1; tkPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &tkPriv.Privileges[0].Luid)) { DWORD cbRet = sizeof(g_TkOldPrivileges); if (AdjustTokenPrivileges(g_hTokenProcess, FALSE, &tkPriv, sizeof(TOKEN_PRIVILEGES), &g_TkOldPrivileges, &cbRet)) rc = VINF_SUCCESS; else { DWORD dwErr = GetLastError(); rc = RTErrConvertFromWin32(dwErr); VGSvcError("vgsvcTimeSyncInit: Adjusting token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", dwErr, rc); } } else { DWORD dwErr = GetLastError(); rc = RTErrConvertFromWin32(dwErr); VGSvcError("vgsvcTimeSyncInit: Looking up token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", dwErr, rc); } if (RT_FAILURE(rc)) { CloseHandle(g_hTokenProcess); g_hTokenProcess = NULL; } } else { DWORD dwErr = GetLastError(); rc = RTErrConvertFromWin32(dwErr); VGSvcError("vgsvcTimeSyncInit: Opening process token (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n", dwErr, rc); g_hTokenProcess = NULL; } } if (GetSystemTimeAdjustment(&g_dwWinTimeAdjustment, &g_dwWinTimeIncrement, &g_bWinTimeAdjustmentDisabled)) VGSvcVerbose(3, "vgsvcTimeSyncInit: Initially %ld (100ns) units per %ld (100 ns) units interval, disabled=%d\n", g_dwWinTimeAdjustment, g_dwWinTimeIncrement, g_bWinTimeAdjustmentDisabled ? 1 : 0); else { DWORD dwErr = GetLastError(); rc = RTErrConvertFromWin32(dwErr); VGSvcError("vgsvcTimeSyncInit: Could not get time adjustment values! Last error: %ld!\n", dwErr); } #endif /* RT_OS_WINDOWS */ return rc; }
/** * @interface_method_impl{VBOXSERVICE,pfnPreInit} */ static DECLCALLBACK(int) vgsvcTimeSyncPreInit(void) { #ifdef VBOX_WITH_GUEST_PROPS /** @todo Merge this function with vgsvcTimeSyncOption() to generalize * the "command line args override guest property values" behavior. */ /* * Read the service options from the VM's guest properties. * Note that these options can be overridden by the command line options later. */ uint32_t uGuestPropSvcClientID; int rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID); if (RT_FAILURE(rc)) { if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */ { VGSvcVerbose(0, "VMInfo: Guest property service is not available, skipping\n"); rc = VINF_SUCCESS; } else VGSvcError("Failed to connect to the guest property service! Error: %Rrc\n", rc); } else { rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-interval", &g_TimeSyncInterval, 50, UINT32_MAX - 1); if ( RT_SUCCESS(rc) || rc == VERR_NOT_FOUND) rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-min-adjust", &g_TimeSyncMinAdjust, 0, 3600000); if ( RT_SUCCESS(rc) || rc == VERR_NOT_FOUND) rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-latency-factor", &g_TimeSyncLatencyFactor, 1, 1024); if ( RT_SUCCESS(rc) || rc == VERR_NOT_FOUND) rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-max-latency", &g_TimeSyncMaxLatency, 1, 3600000); if ( RT_SUCCESS(rc) || rc == VERR_NOT_FOUND) rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold", &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000 /* a week */); if ( RT_SUCCESS(rc) || rc == VERR_NOT_FOUND) { char *pszValue; rc = VGSvcReadProp(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-start", &pszValue, NULL /* ppszFlags */, NULL /* puTimestamp */); if (RT_SUCCESS(rc)) { g_fTimeSyncSetNext = true; RTStrFree(pszValue); } } if ( RT_SUCCESS(rc) || rc == VERR_NOT_FOUND) { uint32_t value; rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-on-restore", &value, 1, 1); if (RT_SUCCESS(rc)) g_fTimeSyncSetOnRestore = !!value; } VbglR3GuestPropDisconnect(uGuestPropSvcClientID); } if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */ rc = VINF_SUCCESS; return rc; #else /* Nothing to do here yet. */ return VINF_SUCCESS; #endif }
/** * @interface_method_impl{VBOXSERVICE,pfnWorker} */ DECLCALLBACK(int) vgsvcVMStatsWorker(bool volatile *pfShutdown) { int rc = VINF_SUCCESS; /* Start monitoring of the stat event change event. */ rc = VbglR3CtlFilterMask(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0); if (RT_FAILURE(rc)) { VGSvcVerbose(3, "vgsvcVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc); return rc; } /* * Tell the control thread that it can continue * spawning services. */ RTThreadUserSignal(RTThreadSelf()); /* * Now enter the loop retrieving runtime data continuously. */ for (;;) { uint32_t fEvents = 0; RTMSINTERVAL cWaitMillies; /* Check if an update interval change is pending. */ rc = VbglR3WaitEvent(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0 /* no wait */, &fEvents); if ( RT_SUCCESS(rc) && (fEvents & VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST)) VbglR3StatQueryInterval(&g_VMStat.cMsStatInterval); if (g_VMStat.cMsStatInterval) { vgsvcVMStatsReport(); cWaitMillies = g_VMStat.cMsStatInterval; } else cWaitMillies = 3000; /* * Block for a while. * * The event semaphore takes care of ignoring interruptions and it * allows us to implement service wakeup later. */ if (*pfShutdown) break; int rc2 = RTSemEventMultiWait(g_VMStatEvent, cWaitMillies); if (*pfShutdown) break; if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2)) { VGSvcError("vgsvcVMStatsWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2); rc = rc2; break; } } /* Cancel monitoring of the stat event change event. */ rc = VbglR3CtlFilterMask(0, VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST); if (RT_FAILURE(rc)) VGSvcVerbose(3, "vgsvcVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc); VGSvcVerbose(3, "VBoxStatsThread: finished statistics change request thread\n"); return 0; }
/** * Gathers VM statistics and reports them to the host. */ static void vgsvcVMStatsReport(void) { #if defined(RT_OS_WINDOWS) Assert(g_VMStat.pfnGlobalMemoryStatusEx && g_VMStat.pfnNtQuerySystemInformation); if ( !g_VMStat.pfnGlobalMemoryStatusEx || !g_VMStat.pfnNtQuerySystemInformation) return; /* Clear the report so we don't report garbage should NtQuerySystemInformation behave in an unexpected manner. */ VMMDevReportGuestStats req; RT_ZERO(req); /* Query and report guest statistics */ SYSTEM_INFO systemInfo; GetSystemInfo(&systemInfo); MEMORYSTATUSEX memStatus; memStatus.dwLength = sizeof(memStatus); g_VMStat.pfnGlobalMemoryStatusEx(&memStatus); req.guestStats.u32PageSize = systemInfo.dwPageSize; req.guestStats.u32PhysMemTotal = (uint32_t)(memStatus.ullTotalPhys / _4K); req.guestStats.u32PhysMemAvail = (uint32_t)(memStatus.ullAvailPhys / _4K); /* The current size of the committed memory limit, in bytes. This is physical memory plus the size of the page file, minus a small overhead. */ req.guestStats.u32PageFileSize = (uint32_t)(memStatus.ullTotalPageFile / _4K) - req.guestStats.u32PhysMemTotal; req.guestStats.u32MemoryLoad = memStatus.dwMemoryLoad; req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL | VBOX_GUEST_STAT_PHYS_MEM_AVAIL | VBOX_GUEST_STAT_PAGE_FILE_SIZE | VBOX_GUEST_STAT_MEMORY_LOAD; # ifdef VBOX_WITH_MEMBALLOON req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K); req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON; # else req.guestStats.u32PhysMemBalloon = 0; # endif if (g_VMStat.pfnGetPerformanceInfo) { PERFORMANCE_INFORMATION perfInfo; if (g_VMStat.pfnGetPerformanceInfo(&perfInfo, sizeof(perfInfo))) { req.guestStats.u32Processes = perfInfo.ProcessCount; req.guestStats.u32Threads = perfInfo.ThreadCount; req.guestStats.u32Handles = perfInfo.HandleCount; req.guestStats.u32MemCommitTotal = perfInfo.CommitTotal; /* already in pages */ req.guestStats.u32MemKernelTotal = perfInfo.KernelTotal; /* already in pages */ req.guestStats.u32MemKernelPaged = perfInfo.KernelPaged; /* already in pages */ req.guestStats.u32MemKernelNonPaged = perfInfo.KernelNonpaged; /* already in pages */ req.guestStats.u32MemSystemCache = perfInfo.SystemCache; /* already in pages */ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PROCESSES | VBOX_GUEST_STAT_THREADS | VBOX_GUEST_STAT_HANDLES | VBOX_GUEST_STAT_MEM_COMMIT_TOTAL | VBOX_GUEST_STAT_MEM_KERNEL_TOTAL | VBOX_GUEST_STAT_MEM_KERNEL_PAGED | VBOX_GUEST_STAT_MEM_KERNEL_NONPAGED | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE; } else VGSvcVerbose(3, "vgsvcVMStatsReport: GetPerformanceInfo failed with %d\n", GetLastError()); } /* Query CPU load information */ uint32_t cbStruct = systemInfo.dwNumberOfProcessors * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION); PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION pProcInfo; pProcInfo = (PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)RTMemAlloc(cbStruct); if (!pProcInfo) return; /* Unfortunately GetSystemTimes is XP SP1 and up only, so we need to use the semi-undocumented NtQuerySystemInformation */ bool fCpuInfoAvail = false; DWORD cbReturned; NTSTATUS rcNt = g_VMStat.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned); if ( !rcNt && cbReturned == cbStruct) { for (uint32_t i = 0; i < systemInfo.dwNumberOfProcessors; i++) { if (i >= VMM_MAX_CPU_COUNT) { VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPUs %u..%u\n", i, systemInfo.dwNumberOfProcessors); break; } if (g_VMStat.au64LastCpuLoad_Kernel[i] == 0) { /* first time */ g_VMStat.au64LastCpuLoad_Idle[i] = pProcInfo[i].IdleTime.QuadPart; g_VMStat.au64LastCpuLoad_Kernel[i] = pProcInfo[i].KernelTime.QuadPart; g_VMStat.au64LastCpuLoad_User[i] = pProcInfo[i].UserTime.QuadPart; Sleep(250); rcNt = g_VMStat.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned); Assert(!rcNt); } uint64_t deltaIdle = (pProcInfo[i].IdleTime.QuadPart - g_VMStat.au64LastCpuLoad_Idle[i]); uint64_t deltaKernel = (pProcInfo[i].KernelTime.QuadPart - g_VMStat.au64LastCpuLoad_Kernel[i]); uint64_t deltaUser = (pProcInfo[i].UserTime.QuadPart - g_VMStat.au64LastCpuLoad_User[i]); deltaKernel -= deltaIdle; /* idle time is added to kernel time */ uint64_t ullTotalTime = deltaIdle + deltaKernel + deltaUser; if (ullTotalTime == 0) /* Prevent division through zero. */ ullTotalTime = 1; req.guestStats.u32CpuLoad_Idle = (uint32_t)(deltaIdle * 100 / ullTotalTime); req.guestStats.u32CpuLoad_Kernel = (uint32_t)(deltaKernel* 100 / ullTotalTime); req.guestStats.u32CpuLoad_User = (uint32_t)(deltaUser * 100 / ullTotalTime); req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE | VBOX_GUEST_STAT_CPU_LOAD_KERNEL | VBOX_GUEST_STAT_CPU_LOAD_USER; req.guestStats.u32CpuId = i; fCpuInfoAvail = true; int rc = VbglR3StatReport(&req); if (RT_SUCCESS(rc)) VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", i); else VGSvcVerbose(3, "vgsvcVMStatsReport: VbglR3StatReport failed with rc=%Rrc\n", rc); g_VMStat.au64LastCpuLoad_Idle[i] = pProcInfo[i].IdleTime.QuadPart; g_VMStat.au64LastCpuLoad_Kernel[i] = pProcInfo[i].KernelTime.QuadPart; g_VMStat.au64LastCpuLoad_User[i] = pProcInfo[i].UserTime.QuadPart; } } RTMemFree(pProcInfo); if (!fCpuInfoAvail) { VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n"); int rc = VbglR3StatReport(&req); if (RT_SUCCESS(rc)) VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n"); else VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); } #elif defined(RT_OS_LINUX) VMMDevReportGuestStats req; RT_ZERO(req); PRTSTREAM pStrm; char szLine[256]; char *psz; int rc = RTStrmOpen("/proc/meminfo", "r", &pStrm); if (RT_SUCCESS(rc)) { uint64_t u64Kb; uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0; for (;;) { rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine)); if (RT_FAILURE(rc)) break; if (strstr(szLine, "MemTotal:") == szLine) { rc = RTStrToUInt64Ex(RTStrStripL(&szLine[9]), &psz, 0, &u64Kb); if (RT_SUCCESS(rc)) u64Total = u64Kb * _1K; } else if (strstr(szLine, "MemFree:") == szLine) { rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb); if (RT_SUCCESS(rc)) u64Free = u64Kb * _1K; } else if (strstr(szLine, "Buffers:") == szLine) { rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb); if (RT_SUCCESS(rc)) u64Buffers = u64Kb * _1K; } else if (strstr(szLine, "Cached:") == szLine) { rc = RTStrToUInt64Ex(RTStrStripL(&szLine[7]), &psz, 0, &u64Kb); if (RT_SUCCESS(rc)) u64Cached = u64Kb * _1K; } else if (strstr(szLine, "SwapTotal:") == szLine) { rc = RTStrToUInt64Ex(RTStrStripL(&szLine[10]), &psz, 0, &u64Kb); if (RT_SUCCESS(rc)) u64PagedTotal = u64Kb * _1K; } } req.guestStats.u32PhysMemTotal = u64Total / _4K; req.guestStats.u32PhysMemAvail = (u64Free + u64Buffers + u64Cached) / _4K; req.guestStats.u32MemSystemCache = (u64Buffers + u64Cached) / _4K; req.guestStats.u32PageFileSize = u64PagedTotal / _4K; RTStrmClose(pStrm); } else VGSvcVerbose(3, "vgsvcVMStatsReport: memory info not available!\n"); req.guestStats.u32PageSize = getpagesize(); req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL | VBOX_GUEST_STAT_PHYS_MEM_AVAIL | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE | VBOX_GUEST_STAT_PAGE_FILE_SIZE; # ifdef VBOX_WITH_MEMBALLOON req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K); req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON; # else req.guestStats.u32PhysMemBalloon = 0; # endif /** @todo req.guestStats.u32Threads */ /** @todo req.guestStats.u32Processes */ /* req.guestStats.u32Handles doesn't make sense here. */ /** @todo req.guestStats.u32MemoryLoad */ /** @todo req.guestStats.u32MemCommitTotal */ /** @todo req.guestStats.u32MemKernelTotal */ /** @todo req.guestStats.u32MemKernelPaged, make any sense? = u32MemKernelTotal? */ /** @todo req.guestStats.u32MemKernelNonPaged, make any sense? = 0? */ bool fCpuInfoAvail = false; rc = RTStrmOpen("/proc/stat", "r", &pStrm); if (RT_SUCCESS(rc)) { for (;;) { rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine)); if (RT_FAILURE(rc)) break; if ( strstr(szLine, "cpu") == szLine && strlen(szLine) > 3 && RT_C_IS_DIGIT(szLine[3])) { uint32_t u32CpuId; rc = RTStrToUInt32Ex(&szLine[3], &psz, 0, &u32CpuId); if (u32CpuId < VMM_MAX_CPU_COUNT) { uint64_t u64User = 0; if (RT_SUCCESS(rc)) rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64User); uint64_t u64Nice = 0; if (RT_SUCCESS(rc)) rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Nice); uint64_t u64System = 0; if (RT_SUCCESS(rc)) rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64System); uint64_t u64Idle = 0; if (RT_SUCCESS(rc)) rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Idle); uint64_t u64DeltaIdle = u64Idle - g_VMStat.au64LastCpuLoad_Idle[u32CpuId]; uint64_t u64DeltaSystem = u64System - g_VMStat.au64LastCpuLoad_Kernel[u32CpuId]; uint64_t u64DeltaUser = u64User - g_VMStat.au64LastCpuLoad_User[u32CpuId]; uint64_t u64DeltaNice = u64Nice - g_VMStat.au64LastCpuLoad_Nice[u32CpuId]; uint64_t u64DeltaAll = u64DeltaIdle + u64DeltaSystem + u64DeltaUser + u64DeltaNice; if (u64DeltaAll == 0) /* Prevent division through zero. */ u64DeltaAll = 1; g_VMStat.au64LastCpuLoad_Idle[u32CpuId] = u64Idle; g_VMStat.au64LastCpuLoad_Kernel[u32CpuId] = u64System; g_VMStat.au64LastCpuLoad_User[u32CpuId] = u64User; g_VMStat.au64LastCpuLoad_Nice[u32CpuId] = u64Nice; req.guestStats.u32CpuLoad_Idle = (uint32_t)(u64DeltaIdle * 100 / u64DeltaAll); req.guestStats.u32CpuLoad_Kernel = (uint32_t)(u64DeltaSystem * 100 / u64DeltaAll); req.guestStats.u32CpuLoad_User = (uint32_t)((u64DeltaUser + u64DeltaNice) * 100 / u64DeltaAll); req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE | VBOX_GUEST_STAT_CPU_LOAD_KERNEL | VBOX_GUEST_STAT_CPU_LOAD_USER; req.guestStats.u32CpuId = u32CpuId; fCpuInfoAvail = true; rc = VbglR3StatReport(&req); if (RT_SUCCESS(rc)) VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", u32CpuId); else VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); } else VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPU%u\n", u32CpuId); } } RTStrmClose(pStrm); } if (!fCpuInfoAvail) { VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n"); rc = VbglR3StatReport(&req); if (RT_SUCCESS(rc)) VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n"); else VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); } #elif defined(RT_OS_SOLARIS) VMMDevReportGuestStats req; RT_ZERO(req); kstat_ctl_t *pStatKern = kstat_open(); if (pStatKern) { /* * Memory statistics. */ uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0; int rc = -1; kstat_t *pStatPages = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"system_pages"); if (pStatPages) { rc = kstat_read(pStatKern, pStatPages, NULL /* optional-copy-buf */); if (rc != -1) { kstat_named_t *pStat = NULL; pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"pagestotal"); if (pStat) u64Total = pStat->value.ul; pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"freemem"); if (pStat) u64Free = pStat->value.ul; } } kstat_t *pStatZFS = kstat_lookup(pStatKern, (char *)"zfs", 0 /* instance */, (char *)"arcstats"); if (pStatZFS) { rc = kstat_read(pStatKern, pStatZFS, NULL /* optional-copy-buf */); if (rc != -1) { kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(pStatZFS, (char *)"size"); if (pStat) u64Cached = pStat->value.ul; } } /* * The vminfo are accumulative counters updated every "N" ticks. Let's get the * number of stat updates so far and use that to divide the swap counter. */ kstat_t *pStatInfo = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"sysinfo"); if (pStatInfo) { sysinfo_t SysInfo; rc = kstat_read(pStatKern, pStatInfo, &SysInfo); if (rc != -1) { kstat_t *pStatVMInfo = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"vminfo"); if (pStatVMInfo) { vminfo_t VMInfo; rc = kstat_read(pStatKern, pStatVMInfo, &VMInfo); if (rc != -1) { Assert(SysInfo.updates != 0); u64PagedTotal = VMInfo.swap_avail / SysInfo.updates; } } } } req.guestStats.u32PhysMemTotal = u64Total; /* already in pages */ req.guestStats.u32PhysMemAvail = u64Free; /* already in pages */ req.guestStats.u32MemSystemCache = u64Cached / _4K; req.guestStats.u32PageFileSize = u64PagedTotal; /* already in pages */ /** @todo req.guestStats.u32Threads */ /** @todo req.guestStats.u32Processes */ /** @todo req.guestStats.u32Handles -- ??? */ /** @todo req.guestStats.u32MemoryLoad */ /** @todo req.guestStats.u32MemCommitTotal */ /** @todo req.guestStats.u32MemKernelTotal */ /** @todo req.guestStats.u32MemKernelPaged */ /** @todo req.guestStats.u32MemKernelNonPaged */ req.guestStats.u32PageSize = getpagesize(); req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL | VBOX_GUEST_STAT_PHYS_MEM_AVAIL | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE | VBOX_GUEST_STAT_PAGE_FILE_SIZE; #ifdef VBOX_WITH_MEMBALLOON req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K); req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON; #else req.guestStats.u32PhysMemBalloon = 0; #endif /* * CPU statistics. */ cpu_stat_t StatCPU; RT_ZERO(StatCPU); kstat_t *pStatNode = NULL; uint32_t cCPUs = 0; bool fCpuInfoAvail = false; for (pStatNode = pStatKern->kc_chain; pStatNode != NULL; pStatNode = pStatNode->ks_next) { if (!strcmp(pStatNode->ks_module, "cpu_stat")) { rc = kstat_read(pStatKern, pStatNode, &StatCPU); if (rc == -1) break; if (cCPUs < VMM_MAX_CPU_COUNT) { uint64_t u64Idle = StatCPU.cpu_sysinfo.cpu[CPU_IDLE]; uint64_t u64User = StatCPU.cpu_sysinfo.cpu[CPU_USER]; uint64_t u64System = StatCPU.cpu_sysinfo.cpu[CPU_KERNEL]; uint64_t u64DeltaIdle = u64Idle - g_VMStat.au64LastCpuLoad_Idle[cCPUs]; uint64_t u64DeltaSystem = u64System - g_VMStat.au64LastCpuLoad_Kernel[cCPUs]; uint64_t u64DeltaUser = u64User - g_VMStat.au64LastCpuLoad_User[cCPUs]; uint64_t u64DeltaAll = u64DeltaIdle + u64DeltaSystem + u64DeltaUser; if (u64DeltaAll == 0) /* Prevent division through zero. */ u64DeltaAll = 1; g_VMStat.au64LastCpuLoad_Idle[cCPUs] = u64Idle; g_VMStat.au64LastCpuLoad_Kernel[cCPUs] = u64System; g_VMStat.au64LastCpuLoad_User[cCPUs] = u64User; req.guestStats.u32CpuId = cCPUs; req.guestStats.u32CpuLoad_Idle = (uint32_t)(u64DeltaIdle * 100 / u64DeltaAll); req.guestStats.u32CpuLoad_Kernel = (uint32_t)(u64DeltaSystem * 100 / u64DeltaAll); req.guestStats.u32CpuLoad_User = (uint32_t)(u64DeltaUser * 100 / u64DeltaAll); req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE | VBOX_GUEST_STAT_CPU_LOAD_KERNEL | VBOX_GUEST_STAT_CPU_LOAD_USER; fCpuInfoAvail = true; rc = VbglR3StatReport(&req); if (RT_SUCCESS(rc)) VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", cCPUs); else VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); cCPUs++; } else VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPU%u\n", cCPUs); } } /* * Report whatever statistics were collected. */ if (!fCpuInfoAvail) { VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n"); rc = VbglR3StatReport(&req); if (RT_SUCCESS(rc)) VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n"); else VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc); } kstat_close(pStatKern); } #else /** @todo implement for other platforms. */ #endif }
int main(int argc, char **argv) { RTEXITCODE rcExit; /* * Init globals and such. */ int rc = RTR3InitExe(argc, &argv, 0); if (RT_FAILURE(rc)) return RTMsgInitFailure(rc); g_pszProgName = RTPathFilename(argv[0]); #ifdef RT_OS_WINDOWS VGSvcWinResolveApis(); #endif #ifdef DEBUG rc = RTCritSectInit(&g_csLog); AssertRC(rc); #endif #ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX /* * Run toolbox code before all other stuff since these things are simpler * shell/file/text utility like programs that just happens to be inside * VBoxService and shouldn't be subject to /dev/vboxguest, pid-files and * global mutex restrictions. */ if (VGSvcToolboxMain(argc, argv, &rcExit)) return rcExit; #endif bool fUserSession = false; #ifdef VBOX_WITH_VBOXSERVICE_CONTROL /* * Check if we're the specially spawned VBoxService.exe process that * handles a guest control session. */ if ( argc >= 2 && !RTStrICmp(argv[1], "guestsession")) fUserSession = true; #endif /* * Connect to the kernel part before daemonizing so we can fail and * complain if there is some kind of problem. We need to initialize the * guest lib *before* we do the pre-init just in case one of services needs * do to some initial stuff with it. */ if (fUserSession) rc = VbglR3InitUser(); else rc = VbglR3Init(); if (RT_FAILURE(rc)) { if (rc == VERR_ACCESS_DENIED) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Insufficient privileges to start %s! Please start with Administrator/root privileges!\n", g_pszProgName); return RTMsgErrorExit(RTEXITCODE_FAILURE, "VbglR3Init failed with rc=%Rrc\n", rc); } #ifdef RT_OS_WINDOWS /* * Check if we're the specially spawned VBoxService.exe process that * handles page fusion. This saves an extra statically linked executable. */ if ( argc == 2 && !RTStrICmp(argv[1], "pagefusion")) return VGSvcPageSharingWorkerChild(); #endif #ifdef VBOX_WITH_VBOXSERVICE_CONTROL /* * Check if we're the specially spawned VBoxService.exe process that * handles a guest control session. */ if (fUserSession) return VGSvcGstCtrlSessionSpawnInit(argc, argv); #endif /* * Parse the arguments. * * Note! This code predates RTGetOpt, thus the manual parsing. */ bool fDaemonize = true; bool fDaemonized = false; for (int i = 1; i < argc; i++) { const char *psz = argv[i]; if (*psz != '-') return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown argument '%s'\n", psz); psz++; /* translate long argument to short */ if (*psz == '-') { psz++; size_t cch = strlen(psz); #define MATCHES(strconst) ( cch == sizeof(strconst) - 1 \ && !memcmp(psz, strconst, sizeof(strconst) - 1) ) if (MATCHES("foreground")) psz = "f"; else if (MATCHES("verbose")) psz = "v"; else if (MATCHES("version")) psz = "V"; else if (MATCHES("help")) psz = "h"; else if (MATCHES("interval")) psz = "i"; #ifdef RT_OS_WINDOWS else if (MATCHES("register")) psz = "r"; else if (MATCHES("unregister")) psz = "u"; #endif else if (MATCHES("logfile")) psz = "l"; else if (MATCHES("pidfile")) psz = "p"; else if (MATCHES("daemonized")) { fDaemonized = true; continue; } else { bool fFound = false; if (cch > sizeof("enable-") && !memcmp(psz, RT_STR_TUPLE("enable-"))) for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++) if ((fFound = !RTStrICmp(psz + sizeof("enable-") - 1, g_aServices[j].pDesc->pszName))) g_aServices[j].fEnabled = true; if (cch > sizeof("disable-") && !memcmp(psz, RT_STR_TUPLE("disable-"))) for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++) if ((fFound = !RTStrICmp(psz + sizeof("disable-") - 1, g_aServices[j].pDesc->pszName))) g_aServices[j].fEnabled = false; if (cch > sizeof("only-") && !memcmp(psz, RT_STR_TUPLE("only-"))) for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) { g_aServices[j].fEnabled = !RTStrICmp(psz + sizeof("only-") - 1, g_aServices[j].pDesc->pszName); if (g_aServices[j].fEnabled) fFound = true; } if (!fFound) { rcExit = vgsvcLazyPreInit(); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++) { rc = g_aServices[j].pDesc->pfnOption(NULL, argc, argv, &i); fFound = rc == VINF_SUCCESS; if (fFound) break; if (rc != -1) return rc; } } if (!fFound) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown option '%s'\n", argv[i]); continue; } #undef MATCHES } /* handle the string of short options. */ do { switch (*psz) { case 'i': rc = VGSvcArgUInt32(argc, argv, psz + 1, &i, &g_DefaultInterval, 1, (UINT32_MAX / 1000) - 1); if (rc) return rc; psz = NULL; break; case 'f': fDaemonize = false; break; case 'v': g_cVerbosity++; break; case 'V': RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr()); return RTEXITCODE_SUCCESS; case 'h': case '?': return vgsvcUsage(); #ifdef RT_OS_WINDOWS case 'r': return VGSvcWinInstall(); case 'u': return VGSvcWinUninstall(); #endif case 'l': { rc = vgsvcArgString(argc, argv, psz + 1, &i, g_szLogFile, sizeof(g_szLogFile)); if (rc) return rc; psz = NULL; break; } case 'p': { rc = vgsvcArgString(argc, argv, psz + 1, &i, g_szPidFile, sizeof(g_szPidFile)); if (rc) return rc; psz = NULL; break; } default: { rcExit = vgsvcLazyPreInit(); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; bool fFound = false; for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++) { rc = g_aServices[j].pDesc->pfnOption(&psz, argc, argv, &i); fFound = rc == VINF_SUCCESS; if (fFound) break; if (rc != -1) return rc; } if (!fFound) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown option '%c' (%s)\n", *psz, argv[i]); break; } } } while (psz && *++psz); } /* Check that at least one service is enabled. */ if (vgsvcCountEnabledServices() == 0) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "At least one service must be enabled\n"); rc = VGSvcLogCreate(g_szLogFile[0] ? g_szLogFile : NULL); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create release log '%s', rc=%Rrc\n", g_szLogFile[0] ? g_szLogFile : "<None>", rc); /* Call pre-init if we didn't do it already. */ rcExit = vgsvcLazyPreInit(); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; #ifdef RT_OS_WINDOWS /* * Make sure only one instance of VBoxService runs at a time. Create a * global mutex for that. * * Note! The \\Global\ namespace was introduced with Win2K, thus the * version check. * Note! If the mutex exists CreateMutex will open it and set last error to * ERROR_ALREADY_EXISTS. */ OSVERSIONINFOEX OSInfoEx; RT_ZERO(OSInfoEx); OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); SetLastError(NO_ERROR); HANDLE hMutexAppRunning; if ( GetVersionEx((LPOSVERSIONINFO)&OSInfoEx) && OSInfoEx.dwPlatformId == VER_PLATFORM_WIN32_NT && OSInfoEx.dwMajorVersion >= 5 /* NT 5.0 a.k.a W2K */) hMutexAppRunning = CreateMutex(NULL, FALSE, "Global\\" VBOXSERVICE_NAME); else hMutexAppRunning = CreateMutex(NULL, FALSE, VBOXSERVICE_NAME); if (hMutexAppRunning == NULL) { DWORD dwErr = GetLastError(); if ( dwErr == ERROR_ALREADY_EXISTS || dwErr == ERROR_ACCESS_DENIED) { VGSvcError("%s is already running! Terminating.\n", g_pszProgName); return RTEXITCODE_FAILURE; } VGSvcError("CreateMutex failed with last error %u! Terminating.\n", GetLastError()); return RTEXITCODE_FAILURE; } #else /* !RT_OS_WINDOWS */ /** @todo Add PID file creation here? */ #endif /* !RT_OS_WINDOWS */ VGSvcVerbose(0, "%s r%s started. Verbose level = %d\n", RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity); /* * Daemonize if requested. */ if (fDaemonize && !fDaemonized) { #ifdef RT_OS_WINDOWS VGSvcVerbose(2, "Starting service dispatcher ...\n"); rcExit = VGSvcWinEnterCtrlDispatcher(); #else VGSvcVerbose(1, "Daemonizing...\n"); rc = VbglR3Daemonize(false /* fNoChDir */, false /* fNoClose */, false /* fRespawn */, NULL /* pcRespawn */); if (RT_FAILURE(rc)) return VGSvcError("Daemon failed: %Rrc\n", rc); /* in-child */ #endif } #ifdef RT_OS_WINDOWS else #endif { /* * Windows: We're running the service as a console application now. Start the * services, enter the main thread's run loop and stop them again * when it returns. * * POSIX: This is used for both daemons and console runs. Start all services * and return immediately. */ #ifdef RT_OS_WINDOWS # ifndef RT_OS_NT4 /** @todo r=bird: What's RT_OS_NT4??? */ /* Install console control handler. */ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)vgsvcWinConsoleControlHandler, TRUE /* Add handler */)) { VGSvcError("Unable to add console control handler, error=%ld\n", GetLastError()); /* Just skip this error, not critical. */ } # endif /* !RT_OS_NT4 */ #endif /* RT_OS_WINDOWS */ rc = VGSvcStartServices(); RTFILE hPidFile = NIL_RTFILE; if (RT_SUCCESS(rc)) if (g_szPidFile[0]) rc = VbglR3PidFile(g_szPidFile, &hPidFile); rcExit = RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; if (RT_SUCCESS(rc)) VGSvcMainWait(); if (g_szPidFile[0] && hPidFile != NIL_RTFILE) VbglR3ClosePidFile(g_szPidFile, hPidFile); #ifdef RT_OS_WINDOWS # ifndef RT_OS_NT4 /* Uninstall console control handler. */ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */)) { VGSvcError("Unable to remove console control handler, error=%ld\n", GetLastError()); /* Just skip this error, not critical. */ } # endif /* !RT_OS_NT4 */ #else /* !RT_OS_WINDOWS */ /* On Windows - since we're running as a console application - we already stopped all services * through the console control handler. So only do the stopping of services here on other platforms * where the break/shutdown/whatever signal was just received. */ VGSvcStopServices(); #endif /* RT_OS_WINDOWS */ } VGSvcReportStatus(VBoxGuestFacilityStatus_Terminated); #ifdef RT_OS_WINDOWS /* * Cleanup mutex. */ CloseHandle(hMutexAppRunning); #endif VGSvcVerbose(0, "Ended.\n"); #ifdef DEBUG RTCritSectDelete(&g_csLog); //RTMemTrackerDumpAllToStdOut(); #endif VGSvcLogDestroy(); return rcExit; }
/** * Installs the service. */ RTEXITCODE VGSvcWinInstall(void) { VGSvcVerbose(1, "Installing service ...\n"); TCHAR imagePath[MAX_PATH] = { 0 }; GetModuleFileName(NULL, imagePath, sizeof(imagePath)); SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (hSCManager == NULL) { VGSvcError("Could not open SCM! Error: %ld\n", GetLastError()); return RTEXITCODE_FAILURE; } RTEXITCODE rc = RTEXITCODE_SUCCESS; SC_HANDLE hService = CreateService(hSCManager, VBOXSERVICE_NAME, VBOXSERVICE_FRIENDLY_NAME, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, imagePath, NULL, NULL, NULL, NULL, NULL); if (hService != NULL) VGSvcVerbose(0, "Service successfully installed!\n"); else { DWORD dwErr = GetLastError(); switch (dwErr) { case ERROR_SERVICE_EXISTS: VGSvcVerbose(1, "Service already exists, just updating the service config.\n"); hService = OpenService(hSCManager, VBOXSERVICE_NAME, SERVICE_ALL_ACCESS); if (hService) { if (ChangeServiceConfig (hService, SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, imagePath, NULL, NULL, NULL, NULL, NULL, VBOXSERVICE_FRIENDLY_NAME)) VGSvcVerbose(1, "The service config has been successfully updated.\n"); else rc = VGSvcError("Could not change service config! Error: %ld\n", GetLastError()); } else rc = VGSvcError("Could not open service! Error: %ld\n", GetLastError()); break; default: rc = VGSvcError("Could not create service! Error: %ld\n", dwErr); break; } } if (rc == RTEXITCODE_SUCCESS) rc = vgsvcWinSetDesc(hService); CloseServiceHandle(hService); CloseServiceHandle(hSCManager); return rc; }
/** * Callback registered by RegisterServiceCtrlHandler on NT4 and earlier. */ static VOID WINAPI vgsvcWinCtrlHandlerNt4(DWORD dwControl) { VGSvcVerbose(2, "Control handler (NT4): dwControl=%#x\n", dwControl); vgsvcWinCtrlHandlerCommon(dwControl); }