forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			370 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			370 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include <windows.h>
 | |
| #include <shlwapi.h>
 | |
| #include <stdio.h>
 | |
| #include <wchar.h>
 | |
| #include <shlobj.h>
 | |
| 
 | |
| #include "serviceinstall.h"
 | |
| #include "maintenanceservice.h"
 | |
| #include "servicebase.h"
 | |
| #include "workmonitor.h"
 | |
| #include "uachelper.h"
 | |
| #include "updatehelper.h"
 | |
| #include "registrycertificates.h"
 | |
| 
 | |
| // Link w/ subsystem window so we don't get a console when executing
 | |
| // this binary through the installer.
 | |
| #pragma comment(linker, "/SUBSYSTEM:windows")
 | |
| 
 | |
| SERVICE_STATUS gSvcStatus = {0};
 | |
| SERVICE_STATUS_HANDLE gSvcStatusHandle = nullptr;
 | |
| HANDLE gWorkDoneEvent = nullptr;
 | |
| HANDLE gThread = nullptr;
 | |
| bool gServiceControlStopping = false;
 | |
| 
 | |
| // logs are pretty small, about 20 lines, so 10 seems reasonable.
 | |
| #define LOGS_TO_KEEP 10
 | |
| 
 | |
| BOOL GetLogDirectoryPath(WCHAR* path);
 | |
| 
 | |
| int wmain(int argc, WCHAR** argv) {
 | |
|   // If command-line parameter is "install", install the service
 | |
|   // or upgrade if already installed
 | |
|   // If command line parameter is "forceinstall", install the service
 | |
|   // even if it is older than what is already installed.
 | |
|   // If command-line parameter is "upgrade", upgrade the service
 | |
|   // but do not install it if it is not already installed.
 | |
|   // If command line parameter is "uninstall", uninstall the service.
 | |
|   // Otherwise, the service is probably being started by the SCM.
 | |
|   bool forceInstall = !lstrcmpi(argv[1], L"forceinstall");
 | |
|   if (!lstrcmpi(argv[1], L"install") || forceInstall) {
 | |
|     WCHAR logFilePath[MAX_PATH + 1];
 | |
|     if (GetLogDirectoryPath(logFilePath) &&
 | |
|         PathAppendSafe(logFilePath, L"maintenanceservice-install.log")) {
 | |
|       LogInit(logFilePath);
 | |
|     }
 | |
| 
 | |
|     SvcInstallAction action = InstallSvc;
 | |
|     if (forceInstall) {
 | |
|       action = ForceInstallSvc;
 | |
|       LOG(("Installing service with force specified..."));
 | |
|     } else {
 | |
|       LOG(("Installing service..."));
 | |
|     }
 | |
| 
 | |
|     bool ret = SvcInstall(action);
 | |
|     if (!ret) {
 | |
|       LOG_WARN(("Could not install service.  (%lu)", GetLastError()));
 | |
|       LogFinish();
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|     LOG(("The service was installed successfully"));
 | |
|     LogFinish();
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (!lstrcmpi(argv[1], L"upgrade")) {
 | |
|     WCHAR logFilePath[MAX_PATH + 1];
 | |
|     if (GetLogDirectoryPath(logFilePath) &&
 | |
|         PathAppendSafe(logFilePath, L"maintenanceservice-install.log")) {
 | |
|       LogInit(logFilePath);
 | |
|     }
 | |
| 
 | |
|     LOG(("Upgrading service if installed..."));
 | |
|     if (!SvcInstall(UpgradeSvc)) {
 | |
|       LOG_WARN(("Could not upgrade service.  (%lu)", GetLastError()));
 | |
|       LogFinish();
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|     LOG(("The service was upgraded successfully"));
 | |
|     LogFinish();
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (!lstrcmpi(argv[1], L"uninstall")) {
 | |
|     WCHAR logFilePath[MAX_PATH + 1];
 | |
|     if (GetLogDirectoryPath(logFilePath) &&
 | |
|         PathAppendSafe(logFilePath, L"maintenanceservice-uninstall.log")) {
 | |
|       LogInit(logFilePath);
 | |
|     }
 | |
|     LOG(("Uninstalling service..."));
 | |
|     if (!SvcUninstall()) {
 | |
|       LOG_WARN(("Could not uninstall service.  (%lu)", GetLastError()));
 | |
|       LogFinish();
 | |
|       return 1;
 | |
|     }
 | |
|     LOG(("The service was uninstalled successfully"));
 | |
|     LogFinish();
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (!lstrcmpi(argv[1], L"check-cert") && argc > 2) {
 | |
|     return DoesBinaryMatchAllowedCertificates(argv[2], argv[3], FALSE) ? 0 : 1;
 | |
|   }
 | |
| 
 | |
|   SERVICE_TABLE_ENTRYW DispatchTable[] = {
 | |
|       {const_cast<LPWSTR>(SVC_NAME),
 | |
|        (LPSERVICE_MAIN_FUNCTIONW)SvcMain},  // -Wwritable-strings
 | |
|       {nullptr, nullptr}};
 | |
| 
 | |
|   // This call returns when the service has stopped.
 | |
|   // The process should simply terminate when the call returns.
 | |
|   if (!StartServiceCtrlDispatcherW(DispatchTable)) {
 | |
|     LOG_WARN(("StartServiceCtrlDispatcher failed.  (%lu)", GetLastError()));
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Obtains the base path where logs should be stored
 | |
|  *
 | |
|  * @param  path The out buffer for the backup log path of size MAX_PATH + 1
 | |
|  * @return TRUE if successful.
 | |
|  */
 | |
| BOOL GetLogDirectoryPath(WCHAR* path) {
 | |
|   if (!GetModuleFileNameW(nullptr, path, MAX_PATH)) {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   if (!PathRemoveFileSpecW(path)) {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   if (!PathAppendSafe(path, L"logs")) {
 | |
|     return FALSE;
 | |
|   }
 | |
|   CreateDirectoryW(path, nullptr);
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Calculated a backup path based on the log number.
 | |
|  *
 | |
|  * @param  path      The out buffer to store the log path of size MAX_PATH + 1
 | |
|  * @param  basePath  The base directory where the calculated path should go
 | |
|  * @param  logNumber The log number, 0 == updater.log
 | |
|  * @return TRUE if successful.
 | |
|  */
 | |
| BOOL GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber) {
 | |
|   WCHAR logName[64] = {L'\0'};
 | |
|   wcsncpy(path, basePath, sizeof(logName) / sizeof(logName[0]) - 1);
 | |
|   if (logNumber <= 0) {
 | |
|     swprintf(logName, sizeof(logName) / sizeof(logName[0]),
 | |
|              L"maintenanceservice.log");
 | |
|   } else {
 | |
|     swprintf(logName, sizeof(logName) / sizeof(logName[0]),
 | |
|              L"maintenanceservice-%d.log", logNumber);
 | |
|   }
 | |
|   return PathAppendSafe(path, logName);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Moves the old log files out of the way before a new one is written.
 | |
|  * If you for example keep 3 logs, then this function will do:
 | |
|  *   updater2.log -> updater3.log
 | |
|  *   updater1.log -> updater2.log
 | |
|  *   updater.log -> updater1.log
 | |
|  * Which clears room for a new updater.log in the basePath directory
 | |
|  *
 | |
|  * @param basePath      The base directory path where log files are stored
 | |
|  * @param numLogsToKeep The number of logs to keep
 | |
|  */
 | |
| void BackupOldLogs(LPCWSTR basePath, int numLogsToKeep) {
 | |
|   WCHAR oldPath[MAX_PATH + 1];
 | |
|   WCHAR newPath[MAX_PATH + 1];
 | |
|   for (int i = numLogsToKeep; i >= 1; i--) {
 | |
|     if (!GetBackupLogPath(oldPath, basePath, i - 1)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (!GetBackupLogPath(newPath, basePath, i)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (!MoveFileExW(oldPath, newPath, MOVEFILE_REPLACE_EXISTING)) {
 | |
|       continue;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Ensures the service is shutdown once all work is complete.
 | |
|  * There is an issue on XP SP2 and below where the service can hang
 | |
|  * in a stop pending state even though the SCM is notified of a stopped
 | |
|  * state.  Control *should* be returned to StartServiceCtrlDispatcher from the
 | |
|  * call to SetServiceStatus on a stopped state in the wmain thread.
 | |
|  * Sometimes this is not the case though. This thread will terminate the process
 | |
|  * if it has been 5 seconds after all work is done and the process is still not
 | |
|  * terminated.  This thread is only started once a stopped state was sent to the
 | |
|  * SCM. The stop pending hang can be reproduced intermittently even if you set
 | |
|  * a stopped state dirctly and never set a stop pending state.  It is safe to
 | |
|  * forcefully terminate the process ourselves since all work is done once we
 | |
|  * start this thread.
 | |
|  */
 | |
| DWORD WINAPI EnsureProcessTerminatedThread(LPVOID) {
 | |
|   Sleep(5000);
 | |
|   exit(0);
 | |
| }
 | |
| 
 | |
| void StartTerminationThread() {
 | |
|   // If the process does not self terminate like it should, this thread
 | |
|   // will terminate the process after 5 seconds.
 | |
|   HANDLE thread = CreateThread(nullptr, 0, EnsureProcessTerminatedThread,
 | |
|                                nullptr, 0, nullptr);
 | |
|   if (thread) {
 | |
|     CloseHandle(thread);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Main entry point when running as a service.
 | |
|  */
 | |
| void WINAPI SvcMain(DWORD argc, LPWSTR* argv) {
 | |
|   // Setup logging, and backup the old logs
 | |
|   WCHAR logFilePath[MAX_PATH + 1];
 | |
|   if (GetLogDirectoryPath(logFilePath)) {
 | |
|     BackupOldLogs(logFilePath, LOGS_TO_KEEP);
 | |
|     if (PathAppendSafe(logFilePath, L"maintenanceservice.log")) {
 | |
|       LogInit(logFilePath);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Disable every privilege we don't need. Processes started using
 | |
|   // CreateProcess will use the same token as this process.
 | |
|   UACHelper::DisablePrivileges(nullptr);
 | |
| 
 | |
|   // Register the handler function for the service
 | |
|   gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler);
 | |
|   if (!gSvcStatusHandle) {
 | |
|     LOG_WARN(("RegisterServiceCtrlHandler failed.  (%lu)", GetLastError()));
 | |
|     ExecuteServiceCommand(argc, argv);
 | |
|     LogFinish();
 | |
|     exit(1);
 | |
|   }
 | |
| 
 | |
|   // These values will be re-used later in calls involving gSvcStatus
 | |
|   gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
 | |
|   gSvcStatus.dwServiceSpecificExitCode = 0;
 | |
| 
 | |
|   // Report initial status to the SCM
 | |
|   ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
 | |
| 
 | |
|   // This event will be used to tell the SvcCtrlHandler when the work is
 | |
|   // done for when a stop comamnd is manually issued.
 | |
|   gWorkDoneEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
 | |
|   if (!gWorkDoneEvent) {
 | |
|     ReportSvcStatus(SERVICE_STOPPED, 1, 0);
 | |
|     StartTerminationThread();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Initialization complete and we're about to start working on
 | |
|   // the actual command.  Report the service state as running to the SCM.
 | |
|   ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
 | |
| 
 | |
|   // The service command was executed, stop logging and set an event
 | |
|   // to indicate the work is done in case someone is waiting on a
 | |
|   // service stop operation.
 | |
|   BOOL success = ExecuteServiceCommand(argc, argv);
 | |
|   LogFinish();
 | |
| 
 | |
|   SetEvent(gWorkDoneEvent);
 | |
| 
 | |
|   // If we aren't already in a stopping state then tell the SCM we're stopped
 | |
|   // now.  If we are already in a stopping state then the SERVICE_STOPPED state
 | |
|   // will be set by the SvcCtrlHandler.
 | |
|   if (!gServiceControlStopping) {
 | |
|     ReportSvcStatus(SERVICE_STOPPED, success ? NO_ERROR : 1, 0);
 | |
|     StartTerminationThread();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Sets the current service status and reports it to the SCM.
 | |
|  *
 | |
|  * @param currentState  The current state (see SERVICE_STATUS)
 | |
|  * @param exitCode      The system error code
 | |
|  * @param waitHint      Estimated time for pending operation in milliseconds
 | |
|  */
 | |
| void ReportSvcStatus(DWORD currentState, DWORD exitCode, DWORD waitHint) {
 | |
|   static DWORD dwCheckPoint = 1;
 | |
| 
 | |
|   gSvcStatus.dwCurrentState = currentState;
 | |
|   gSvcStatus.dwWin32ExitCode = exitCode;
 | |
|   gSvcStatus.dwWaitHint = waitHint;
 | |
| 
 | |
|   if (SERVICE_START_PENDING == currentState ||
 | |
|       SERVICE_STOP_PENDING == currentState) {
 | |
|     gSvcStatus.dwControlsAccepted = 0;
 | |
|   } else {
 | |
|     gSvcStatus.dwControlsAccepted =
 | |
|         SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
 | |
|   }
 | |
| 
 | |
|   if ((SERVICE_RUNNING == currentState) || (SERVICE_STOPPED == currentState)) {
 | |
|     gSvcStatus.dwCheckPoint = 0;
 | |
|   } else {
 | |
|     gSvcStatus.dwCheckPoint = dwCheckPoint++;
 | |
|   }
 | |
| 
 | |
|   // Report the status of the service to the SCM.
 | |
|   SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Since the SvcCtrlHandler should only spend at most 30 seconds before
 | |
|  * returning, this function does the service stop work for the SvcCtrlHandler.
 | |
|  */
 | |
| DWORD WINAPI StopServiceAndWaitForCommandThread(LPVOID) {
 | |
|   do {
 | |
|     ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000);
 | |
|   } while (WaitForSingleObject(gWorkDoneEvent, 100) == WAIT_TIMEOUT);
 | |
|   CloseHandle(gWorkDoneEvent);
 | |
|   gWorkDoneEvent = nullptr;
 | |
|   ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
 | |
|   StartTerminationThread();
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Called by SCM whenever a control code is sent to the service
 | |
|  * using the ControlService function.
 | |
|  */
 | |
| void WINAPI SvcCtrlHandler(DWORD dwCtrl) {
 | |
|   // After a SERVICE_CONTROL_STOP there should be no more commands sent to
 | |
|   // the SvcCtrlHandler.
 | |
|   if (gServiceControlStopping) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Handle the requested control code.
 | |
|   switch (dwCtrl) {
 | |
|     case SERVICE_CONTROL_SHUTDOWN:
 | |
|     case SERVICE_CONTROL_STOP: {
 | |
|       gServiceControlStopping = true;
 | |
|       ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000);
 | |
| 
 | |
|       // The SvcCtrlHandler thread should not spend more than 30 seconds in
 | |
|       // shutdown so we spawn a new thread for stopping the service
 | |
|       HANDLE thread = CreateThread(
 | |
|           nullptr, 0, StopServiceAndWaitForCommandThread, nullptr, 0, nullptr);
 | |
|       if (thread) {
 | |
|         CloseHandle(thread);
 | |
|       } else {
 | |
|         // Couldn't start the thread so just call the stop ourselves.
 | |
|         // If it happens to take longer than 30 seconds the caller will
 | |
|         // get an error.
 | |
|         StopServiceAndWaitForCommandThread(nullptr);
 | |
|       }
 | |
|     } break;
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| }
 | 
