diff options
Diffstat (limited to 'toolkit/components/maintenanceservice/maintenanceservice.cpp')
-rw-r--r-- | toolkit/components/maintenanceservice/maintenanceservice.cpp | 391 |
1 files changed, 0 insertions, 391 deletions
diff --git a/toolkit/components/maintenanceservice/maintenanceservice.cpp b/toolkit/components/maintenanceservice/maintenanceservice.cpp deleted file mode 100644 index f1275b095..000000000 --- a/toolkit/components/maintenanceservice/maintenanceservice.cpp +++ /dev/null @@ -1,391 +0,0 @@ -/* 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 updatePath[MAX_PATH + 1]; - if (GetLogDirectoryPath(updatePath)) { - LogInit(updatePath, L"maintenanceservice-install.log"); - } - - 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. (%d)", GetLastError())); - LogFinish(); - return 1; - } - - LOG(("The service was installed successfully")); - LogFinish(); - return 0; - } - - if (!lstrcmpi(argv[1], L"upgrade")) { - WCHAR updatePath[MAX_PATH + 1]; - if (GetLogDirectoryPath(updatePath)) { - LogInit(updatePath, L"maintenanceservice-install.log"); - } - - LOG(("Upgrading service if installed...")); - if (!SvcInstall(UpgradeSvc)) { - LOG_WARN(("Could not upgrade service. (%d)", GetLastError())); - LogFinish(); - return 1; - } - - LOG(("The service was upgraded successfully")); - LogFinish(); - return 0; - } - - if (!lstrcmpi(argv[1], L"uninstall")) { - WCHAR updatePath[MAX_PATH + 1]; - if (GetLogDirectoryPath(updatePath)) { - LogInit(updatePath, L"maintenanceservice-uninstall.log"); - } - LOG(("Uninstalling service...")); - if (!SvcUninstall()) { - LOG_WARN(("Could not uninstall service. (%d)", 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[] = { - { SVC_NAME, (LPSERVICE_MAIN_FUNCTIONW) SvcMain }, - { 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. (%d)", 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); - return 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 updatePath[MAX_PATH + 1]; - if (GetLogDirectoryPath(updatePath)) { - BackupOldLogs(updatePath, LOGS_TO_KEEP); - LogInit(updatePath, L"maintenanceservice.log"); - } - - // 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. (%d)", 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. - 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, NO_ERROR, 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; - } -} |