1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,387 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include <windows.h> 1.9 +#include <shlwapi.h> 1.10 +#include <stdio.h> 1.11 +#include <wchar.h> 1.12 +#include <shlobj.h> 1.13 + 1.14 +#include "serviceinstall.h" 1.15 +#include "maintenanceservice.h" 1.16 +#include "servicebase.h" 1.17 +#include "workmonitor.h" 1.18 +#include "uachelper.h" 1.19 +#include "updatehelper.h" 1.20 + 1.21 +SERVICE_STATUS gSvcStatus = { 0 }; 1.22 +SERVICE_STATUS_HANDLE gSvcStatusHandle = nullptr; 1.23 +HANDLE gWorkDoneEvent = nullptr; 1.24 +HANDLE gThread = nullptr; 1.25 +bool gServiceControlStopping = false; 1.26 + 1.27 +// logs are pretty small, about 20 lines, so 10 seems reasonable. 1.28 +#define LOGS_TO_KEEP 10 1.29 + 1.30 +BOOL GetLogDirectoryPath(WCHAR *path); 1.31 + 1.32 +int 1.33 +wmain(int argc, WCHAR **argv) 1.34 +{ 1.35 + // If command-line parameter is "install", install the service 1.36 + // or upgrade if already installed 1.37 + // If command line parameter is "forceinstall", install the service 1.38 + // even if it is older than what is already installed. 1.39 + // If command-line parameter is "upgrade", upgrade the service 1.40 + // but do not install it if it is not already installed. 1.41 + // If command line parameter is "uninstall", uninstall the service. 1.42 + // Otherwise, the service is probably being started by the SCM. 1.43 + bool forceInstall = !lstrcmpi(argv[1], L"forceinstall"); 1.44 + if (!lstrcmpi(argv[1], L"install") || forceInstall) { 1.45 + WCHAR updatePath[MAX_PATH + 1]; 1.46 + if (GetLogDirectoryPath(updatePath)) { 1.47 + LogInit(updatePath, L"maintenanceservice-install.log"); 1.48 + } 1.49 + 1.50 + SvcInstallAction action = InstallSvc; 1.51 + if (forceInstall) { 1.52 + action = ForceInstallSvc; 1.53 + LOG(("Installing service with force specified...")); 1.54 + } else { 1.55 + LOG(("Installing service...")); 1.56 + } 1.57 + 1.58 + bool ret = SvcInstall(action); 1.59 + if (!ret) { 1.60 + LOG_WARN(("Could not install service. (%d)", GetLastError())); 1.61 + LogFinish(); 1.62 + return 1; 1.63 + } 1.64 + 1.65 + LOG(("The service was installed successfully")); 1.66 + LogFinish(); 1.67 + return 0; 1.68 + } 1.69 + 1.70 + if (!lstrcmpi(argv[1], L"upgrade")) { 1.71 + WCHAR updatePath[MAX_PATH + 1]; 1.72 + if (GetLogDirectoryPath(updatePath)) { 1.73 + LogInit(updatePath, L"maintenanceservice-install.log"); 1.74 + } 1.75 + 1.76 + LOG(("Upgrading service if installed...")); 1.77 + if (!SvcInstall(UpgradeSvc)) { 1.78 + LOG_WARN(("Could not upgrade service. (%d)", GetLastError())); 1.79 + LogFinish(); 1.80 + return 1; 1.81 + } 1.82 + 1.83 + LOG(("The service was upgraded successfully")); 1.84 + LogFinish(); 1.85 + return 0; 1.86 + } 1.87 + 1.88 + if (!lstrcmpi(argv[1], L"uninstall")) { 1.89 + WCHAR updatePath[MAX_PATH + 1]; 1.90 + if (GetLogDirectoryPath(updatePath)) { 1.91 + LogInit(updatePath, L"maintenanceservice-uninstall.log"); 1.92 + } 1.93 + LOG(("Uninstalling service...")); 1.94 + if (!SvcUninstall()) { 1.95 + LOG_WARN(("Could not uninstall service. (%d)", GetLastError())); 1.96 + LogFinish(); 1.97 + return 1; 1.98 + } 1.99 + LOG(("The service was uninstalled successfully")); 1.100 + LogFinish(); 1.101 + return 0; 1.102 + } 1.103 + 1.104 + SERVICE_TABLE_ENTRYW DispatchTable[] = { 1.105 + { SVC_NAME, (LPSERVICE_MAIN_FUNCTIONW) SvcMain }, 1.106 + { nullptr, nullptr } 1.107 + }; 1.108 + 1.109 + // This call returns when the service has stopped. 1.110 + // The process should simply terminate when the call returns. 1.111 + if (!StartServiceCtrlDispatcherW(DispatchTable)) { 1.112 + LOG_WARN(("StartServiceCtrlDispatcher failed. (%d)", GetLastError())); 1.113 + } 1.114 + 1.115 + return 0; 1.116 +} 1.117 + 1.118 +/** 1.119 + * Obtains the base path where logs should be stored 1.120 + * 1.121 + * @param path The out buffer for the backup log path of size MAX_PATH + 1 1.122 + * @return TRUE if successful. 1.123 + */ 1.124 +BOOL 1.125 +GetLogDirectoryPath(WCHAR *path) 1.126 +{ 1.127 + HRESULT hr = SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA, nullptr, 1.128 + SHGFP_TYPE_CURRENT, path); 1.129 + if (FAILED(hr)) { 1.130 + return FALSE; 1.131 + } 1.132 + 1.133 + if (!PathAppendSafe(path, L"Mozilla")) { 1.134 + return FALSE; 1.135 + } 1.136 + // The directory should already be created from the installer, but 1.137 + // just to be safe in case someone deletes. 1.138 + CreateDirectoryW(path, nullptr); 1.139 + 1.140 + if (!PathAppendSafe(path, L"logs")) { 1.141 + return FALSE; 1.142 + } 1.143 + CreateDirectoryW(path, nullptr); 1.144 + return TRUE; 1.145 +} 1.146 + 1.147 +/** 1.148 + * Calculated a backup path based on the log number. 1.149 + * 1.150 + * @param path The out buffer to store the log path of size MAX_PATH + 1 1.151 + * @param basePath The base directory where the calculated path should go 1.152 + * @param logNumber The log number, 0 == updater.log 1.153 + * @return TRUE if successful. 1.154 + */ 1.155 +BOOL 1.156 +GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber) 1.157 +{ 1.158 + WCHAR logName[64] = { L'\0' }; 1.159 + wcsncpy(path, basePath, sizeof(logName) / sizeof(logName[0]) - 1); 1.160 + if (logNumber <= 0) { 1.161 + swprintf(logName, sizeof(logName) / sizeof(logName[0]), 1.162 + L"maintenanceservice.log"); 1.163 + } else { 1.164 + swprintf(logName, sizeof(logName) / sizeof(logName[0]), 1.165 + L"maintenanceservice-%d.log", logNumber); 1.166 + } 1.167 + return PathAppendSafe(path, logName); 1.168 +} 1.169 + 1.170 +/** 1.171 + * Moves the old log files out of the way before a new one is written. 1.172 + * If you for example keep 3 logs, then this function will do: 1.173 + * updater2.log -> updater3.log 1.174 + * updater1.log -> updater2.log 1.175 + * updater.log -> updater1.log 1.176 + * Which clears room for a new updater.log in the basePath directory 1.177 + * 1.178 + * @param basePath The base directory path where log files are stored 1.179 + * @param numLogsToKeep The number of logs to keep 1.180 + */ 1.181 +void 1.182 +BackupOldLogs(LPCWSTR basePath, int numLogsToKeep) 1.183 +{ 1.184 + WCHAR oldPath[MAX_PATH + 1]; 1.185 + WCHAR newPath[MAX_PATH + 1]; 1.186 + for (int i = numLogsToKeep; i >= 1; i--) { 1.187 + if (!GetBackupLogPath(oldPath, basePath, i -1)) { 1.188 + continue; 1.189 + } 1.190 + 1.191 + if (!GetBackupLogPath(newPath, basePath, i)) { 1.192 + continue; 1.193 + } 1.194 + 1.195 + if (!MoveFileExW(oldPath, newPath, MOVEFILE_REPLACE_EXISTING)) { 1.196 + continue; 1.197 + } 1.198 + } 1.199 +} 1.200 + 1.201 +/** 1.202 + * Ensures the service is shutdown once all work is complete. 1.203 + * There is an issue on XP SP2 and below where the service can hang 1.204 + * in a stop pending state even though the SCM is notified of a stopped 1.205 + * state. Control *should* be returned to StartServiceCtrlDispatcher from the 1.206 + * call to SetServiceStatus on a stopped state in the wmain thread. 1.207 + * Sometimes this is not the case though. This thread will terminate the process 1.208 + * if it has been 5 seconds after all work is done and the process is still not 1.209 + * terminated. This thread is only started once a stopped state was sent to the 1.210 + * SCM. The stop pending hang can be reproduced intermittently even if you set 1.211 + * a stopped state dirctly and never set a stop pending state. It is safe to 1.212 + * forcefully terminate the process ourselves since all work is done once we 1.213 + * start this thread. 1.214 +*/ 1.215 +DWORD WINAPI 1.216 +EnsureProcessTerminatedThread(LPVOID) 1.217 +{ 1.218 + Sleep(5000); 1.219 + exit(0); 1.220 + return 0; 1.221 +} 1.222 + 1.223 +void 1.224 +StartTerminationThread() 1.225 +{ 1.226 + // If the process does not self terminate like it should, this thread 1.227 + // will terminate the process after 5 seconds. 1.228 + HANDLE thread = CreateThread(nullptr, 0, EnsureProcessTerminatedThread, 1.229 + nullptr, 0, nullptr); 1.230 + if (thread) { 1.231 + CloseHandle(thread); 1.232 + } 1.233 +} 1.234 + 1.235 +/** 1.236 + * Main entry point when running as a service. 1.237 + */ 1.238 +void WINAPI 1.239 +SvcMain(DWORD argc, LPWSTR *argv) 1.240 +{ 1.241 + // Setup logging, and backup the old logs 1.242 + WCHAR updatePath[MAX_PATH + 1]; 1.243 + if (GetLogDirectoryPath(updatePath)) { 1.244 + BackupOldLogs(updatePath, LOGS_TO_KEEP); 1.245 + LogInit(updatePath, L"maintenanceservice.log"); 1.246 + } 1.247 + 1.248 + // Disable every privilege we don't need. Processes started using 1.249 + // CreateProcess will use the same token as this process. 1.250 + UACHelper::DisablePrivileges(nullptr); 1.251 + 1.252 + // Register the handler function for the service 1.253 + gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler); 1.254 + if (!gSvcStatusHandle) { 1.255 + LOG_WARN(("RegisterServiceCtrlHandler failed. (%d)", GetLastError())); 1.256 + ExecuteServiceCommand(argc, argv); 1.257 + LogFinish(); 1.258 + exit(1); 1.259 + } 1.260 + 1.261 + // These values will be re-used later in calls involving gSvcStatus 1.262 + gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; 1.263 + gSvcStatus.dwServiceSpecificExitCode = 0; 1.264 + 1.265 + // Report initial status to the SCM 1.266 + ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000); 1.267 + 1.268 + // This event will be used to tell the SvcCtrlHandler when the work is 1.269 + // done for when a stop comamnd is manually issued. 1.270 + gWorkDoneEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); 1.271 + if (!gWorkDoneEvent) { 1.272 + ReportSvcStatus(SERVICE_STOPPED, 1, 0); 1.273 + StartTerminationThread(); 1.274 + return; 1.275 + } 1.276 + 1.277 + // Initialization complete and we're about to start working on 1.278 + // the actual command. Report the service state as running to the SCM. 1.279 + ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); 1.280 + 1.281 + // The service command was executed, stop logging and set an event 1.282 + // to indicate the work is done in case someone is waiting on a 1.283 + // service stop operation. 1.284 + ExecuteServiceCommand(argc, argv); 1.285 + LogFinish(); 1.286 + 1.287 + SetEvent(gWorkDoneEvent); 1.288 + 1.289 + // If we aren't already in a stopping state then tell the SCM we're stopped 1.290 + // now. If we are already in a stopping state then the SERVICE_STOPPED state 1.291 + // will be set by the SvcCtrlHandler. 1.292 + if (!gServiceControlStopping) { 1.293 + ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); 1.294 + StartTerminationThread(); 1.295 + } 1.296 +} 1.297 + 1.298 +/** 1.299 + * Sets the current service status and reports it to the SCM. 1.300 + * 1.301 + * @param currentState The current state (see SERVICE_STATUS) 1.302 + * @param exitCode The system error code 1.303 + * @param waitHint Estimated time for pending operation in milliseconds 1.304 + */ 1.305 +void 1.306 +ReportSvcStatus(DWORD currentState, 1.307 + DWORD exitCode, 1.308 + DWORD waitHint) 1.309 +{ 1.310 + static DWORD dwCheckPoint = 1; 1.311 + 1.312 + gSvcStatus.dwCurrentState = currentState; 1.313 + gSvcStatus.dwWin32ExitCode = exitCode; 1.314 + gSvcStatus.dwWaitHint = waitHint; 1.315 + 1.316 + if (SERVICE_START_PENDING == currentState || 1.317 + SERVICE_STOP_PENDING == currentState) { 1.318 + gSvcStatus.dwControlsAccepted = 0; 1.319 + } else { 1.320 + gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | 1.321 + SERVICE_ACCEPT_SHUTDOWN; 1.322 + } 1.323 + 1.324 + if ((SERVICE_RUNNING == currentState) || 1.325 + (SERVICE_STOPPED == currentState)) { 1.326 + gSvcStatus.dwCheckPoint = 0; 1.327 + } else { 1.328 + gSvcStatus.dwCheckPoint = dwCheckPoint++; 1.329 + } 1.330 + 1.331 + // Report the status of the service to the SCM. 1.332 + SetServiceStatus(gSvcStatusHandle, &gSvcStatus); 1.333 +} 1.334 + 1.335 +/** 1.336 + * Since the SvcCtrlHandler should only spend at most 30 seconds before 1.337 + * returning, this function does the service stop work for the SvcCtrlHandler. 1.338 +*/ 1.339 +DWORD WINAPI 1.340 +StopServiceAndWaitForCommandThread(LPVOID) 1.341 +{ 1.342 + do { 1.343 + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000); 1.344 + } while(WaitForSingleObject(gWorkDoneEvent, 100) == WAIT_TIMEOUT); 1.345 + CloseHandle(gWorkDoneEvent); 1.346 + gWorkDoneEvent = nullptr; 1.347 + ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); 1.348 + StartTerminationThread(); 1.349 + return 0; 1.350 +} 1.351 + 1.352 +/** 1.353 + * Called by SCM whenever a control code is sent to the service 1.354 + * using the ControlService function. 1.355 + */ 1.356 +void WINAPI 1.357 +SvcCtrlHandler(DWORD dwCtrl) 1.358 +{ 1.359 + // After a SERVICE_CONTROL_STOP there should be no more commands sent to 1.360 + // the SvcCtrlHandler. 1.361 + if (gServiceControlStopping) { 1.362 + return; 1.363 + } 1.364 + 1.365 + // Handle the requested control code. 1.366 + switch(dwCtrl) { 1.367 + case SERVICE_CONTROL_SHUTDOWN: 1.368 + case SERVICE_CONTROL_STOP: { 1.369 + gServiceControlStopping = true; 1.370 + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000); 1.371 + 1.372 + // The SvcCtrlHandler thread should not spend more than 30 seconds in 1.373 + // shutdown so we spawn a new thread for stopping the service 1.374 + HANDLE thread = CreateThread(nullptr, 0, 1.375 + StopServiceAndWaitForCommandThread, 1.376 + nullptr, 0, nullptr); 1.377 + if (thread) { 1.378 + CloseHandle(thread); 1.379 + } else { 1.380 + // Couldn't start the thread so just call the stop ourselves. 1.381 + // If it happens to take longer than 30 seconds the caller will 1.382 + // get an error. 1.383 + StopServiceAndWaitForCommandThread(nullptr); 1.384 + } 1.385 + } 1.386 + break; 1.387 + default: 1.388 + break; 1.389 + } 1.390 +}