toolkit/components/maintenanceservice/maintenanceservice.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 #include <windows.h>
michael@0 6 #include <shlwapi.h>
michael@0 7 #include <stdio.h>
michael@0 8 #include <wchar.h>
michael@0 9 #include <shlobj.h>
michael@0 10
michael@0 11 #include "serviceinstall.h"
michael@0 12 #include "maintenanceservice.h"
michael@0 13 #include "servicebase.h"
michael@0 14 #include "workmonitor.h"
michael@0 15 #include "uachelper.h"
michael@0 16 #include "updatehelper.h"
michael@0 17
michael@0 18 SERVICE_STATUS gSvcStatus = { 0 };
michael@0 19 SERVICE_STATUS_HANDLE gSvcStatusHandle = nullptr;
michael@0 20 HANDLE gWorkDoneEvent = nullptr;
michael@0 21 HANDLE gThread = nullptr;
michael@0 22 bool gServiceControlStopping = false;
michael@0 23
michael@0 24 // logs are pretty small, about 20 lines, so 10 seems reasonable.
michael@0 25 #define LOGS_TO_KEEP 10
michael@0 26
michael@0 27 BOOL GetLogDirectoryPath(WCHAR *path);
michael@0 28
michael@0 29 int
michael@0 30 wmain(int argc, WCHAR **argv)
michael@0 31 {
michael@0 32 // If command-line parameter is "install", install the service
michael@0 33 // or upgrade if already installed
michael@0 34 // If command line parameter is "forceinstall", install the service
michael@0 35 // even if it is older than what is already installed.
michael@0 36 // If command-line parameter is "upgrade", upgrade the service
michael@0 37 // but do not install it if it is not already installed.
michael@0 38 // If command line parameter is "uninstall", uninstall the service.
michael@0 39 // Otherwise, the service is probably being started by the SCM.
michael@0 40 bool forceInstall = !lstrcmpi(argv[1], L"forceinstall");
michael@0 41 if (!lstrcmpi(argv[1], L"install") || forceInstall) {
michael@0 42 WCHAR updatePath[MAX_PATH + 1];
michael@0 43 if (GetLogDirectoryPath(updatePath)) {
michael@0 44 LogInit(updatePath, L"maintenanceservice-install.log");
michael@0 45 }
michael@0 46
michael@0 47 SvcInstallAction action = InstallSvc;
michael@0 48 if (forceInstall) {
michael@0 49 action = ForceInstallSvc;
michael@0 50 LOG(("Installing service with force specified..."));
michael@0 51 } else {
michael@0 52 LOG(("Installing service..."));
michael@0 53 }
michael@0 54
michael@0 55 bool ret = SvcInstall(action);
michael@0 56 if (!ret) {
michael@0 57 LOG_WARN(("Could not install service. (%d)", GetLastError()));
michael@0 58 LogFinish();
michael@0 59 return 1;
michael@0 60 }
michael@0 61
michael@0 62 LOG(("The service was installed successfully"));
michael@0 63 LogFinish();
michael@0 64 return 0;
michael@0 65 }
michael@0 66
michael@0 67 if (!lstrcmpi(argv[1], L"upgrade")) {
michael@0 68 WCHAR updatePath[MAX_PATH + 1];
michael@0 69 if (GetLogDirectoryPath(updatePath)) {
michael@0 70 LogInit(updatePath, L"maintenanceservice-install.log");
michael@0 71 }
michael@0 72
michael@0 73 LOG(("Upgrading service if installed..."));
michael@0 74 if (!SvcInstall(UpgradeSvc)) {
michael@0 75 LOG_WARN(("Could not upgrade service. (%d)", GetLastError()));
michael@0 76 LogFinish();
michael@0 77 return 1;
michael@0 78 }
michael@0 79
michael@0 80 LOG(("The service was upgraded successfully"));
michael@0 81 LogFinish();
michael@0 82 return 0;
michael@0 83 }
michael@0 84
michael@0 85 if (!lstrcmpi(argv[1], L"uninstall")) {
michael@0 86 WCHAR updatePath[MAX_PATH + 1];
michael@0 87 if (GetLogDirectoryPath(updatePath)) {
michael@0 88 LogInit(updatePath, L"maintenanceservice-uninstall.log");
michael@0 89 }
michael@0 90 LOG(("Uninstalling service..."));
michael@0 91 if (!SvcUninstall()) {
michael@0 92 LOG_WARN(("Could not uninstall service. (%d)", GetLastError()));
michael@0 93 LogFinish();
michael@0 94 return 1;
michael@0 95 }
michael@0 96 LOG(("The service was uninstalled successfully"));
michael@0 97 LogFinish();
michael@0 98 return 0;
michael@0 99 }
michael@0 100
michael@0 101 SERVICE_TABLE_ENTRYW DispatchTable[] = {
michael@0 102 { SVC_NAME, (LPSERVICE_MAIN_FUNCTIONW) SvcMain },
michael@0 103 { nullptr, nullptr }
michael@0 104 };
michael@0 105
michael@0 106 // This call returns when the service has stopped.
michael@0 107 // The process should simply terminate when the call returns.
michael@0 108 if (!StartServiceCtrlDispatcherW(DispatchTable)) {
michael@0 109 LOG_WARN(("StartServiceCtrlDispatcher failed. (%d)", GetLastError()));
michael@0 110 }
michael@0 111
michael@0 112 return 0;
michael@0 113 }
michael@0 114
michael@0 115 /**
michael@0 116 * Obtains the base path where logs should be stored
michael@0 117 *
michael@0 118 * @param path The out buffer for the backup log path of size MAX_PATH + 1
michael@0 119 * @return TRUE if successful.
michael@0 120 */
michael@0 121 BOOL
michael@0 122 GetLogDirectoryPath(WCHAR *path)
michael@0 123 {
michael@0 124 HRESULT hr = SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA, nullptr,
michael@0 125 SHGFP_TYPE_CURRENT, path);
michael@0 126 if (FAILED(hr)) {
michael@0 127 return FALSE;
michael@0 128 }
michael@0 129
michael@0 130 if (!PathAppendSafe(path, L"Mozilla")) {
michael@0 131 return FALSE;
michael@0 132 }
michael@0 133 // The directory should already be created from the installer, but
michael@0 134 // just to be safe in case someone deletes.
michael@0 135 CreateDirectoryW(path, nullptr);
michael@0 136
michael@0 137 if (!PathAppendSafe(path, L"logs")) {
michael@0 138 return FALSE;
michael@0 139 }
michael@0 140 CreateDirectoryW(path, nullptr);
michael@0 141 return TRUE;
michael@0 142 }
michael@0 143
michael@0 144 /**
michael@0 145 * Calculated a backup path based on the log number.
michael@0 146 *
michael@0 147 * @param path The out buffer to store the log path of size MAX_PATH + 1
michael@0 148 * @param basePath The base directory where the calculated path should go
michael@0 149 * @param logNumber The log number, 0 == updater.log
michael@0 150 * @return TRUE if successful.
michael@0 151 */
michael@0 152 BOOL
michael@0 153 GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber)
michael@0 154 {
michael@0 155 WCHAR logName[64] = { L'\0' };
michael@0 156 wcsncpy(path, basePath, sizeof(logName) / sizeof(logName[0]) - 1);
michael@0 157 if (logNumber <= 0) {
michael@0 158 swprintf(logName, sizeof(logName) / sizeof(logName[0]),
michael@0 159 L"maintenanceservice.log");
michael@0 160 } else {
michael@0 161 swprintf(logName, sizeof(logName) / sizeof(logName[0]),
michael@0 162 L"maintenanceservice-%d.log", logNumber);
michael@0 163 }
michael@0 164 return PathAppendSafe(path, logName);
michael@0 165 }
michael@0 166
michael@0 167 /**
michael@0 168 * Moves the old log files out of the way before a new one is written.
michael@0 169 * If you for example keep 3 logs, then this function will do:
michael@0 170 * updater2.log -> updater3.log
michael@0 171 * updater1.log -> updater2.log
michael@0 172 * updater.log -> updater1.log
michael@0 173 * Which clears room for a new updater.log in the basePath directory
michael@0 174 *
michael@0 175 * @param basePath The base directory path where log files are stored
michael@0 176 * @param numLogsToKeep The number of logs to keep
michael@0 177 */
michael@0 178 void
michael@0 179 BackupOldLogs(LPCWSTR basePath, int numLogsToKeep)
michael@0 180 {
michael@0 181 WCHAR oldPath[MAX_PATH + 1];
michael@0 182 WCHAR newPath[MAX_PATH + 1];
michael@0 183 for (int i = numLogsToKeep; i >= 1; i--) {
michael@0 184 if (!GetBackupLogPath(oldPath, basePath, i -1)) {
michael@0 185 continue;
michael@0 186 }
michael@0 187
michael@0 188 if (!GetBackupLogPath(newPath, basePath, i)) {
michael@0 189 continue;
michael@0 190 }
michael@0 191
michael@0 192 if (!MoveFileExW(oldPath, newPath, MOVEFILE_REPLACE_EXISTING)) {
michael@0 193 continue;
michael@0 194 }
michael@0 195 }
michael@0 196 }
michael@0 197
michael@0 198 /**
michael@0 199 * Ensures the service is shutdown once all work is complete.
michael@0 200 * There is an issue on XP SP2 and below where the service can hang
michael@0 201 * in a stop pending state even though the SCM is notified of a stopped
michael@0 202 * state. Control *should* be returned to StartServiceCtrlDispatcher from the
michael@0 203 * call to SetServiceStatus on a stopped state in the wmain thread.
michael@0 204 * Sometimes this is not the case though. This thread will terminate the process
michael@0 205 * if it has been 5 seconds after all work is done and the process is still not
michael@0 206 * terminated. This thread is only started once a stopped state was sent to the
michael@0 207 * SCM. The stop pending hang can be reproduced intermittently even if you set
michael@0 208 * a stopped state dirctly and never set a stop pending state. It is safe to
michael@0 209 * forcefully terminate the process ourselves since all work is done once we
michael@0 210 * start this thread.
michael@0 211 */
michael@0 212 DWORD WINAPI
michael@0 213 EnsureProcessTerminatedThread(LPVOID)
michael@0 214 {
michael@0 215 Sleep(5000);
michael@0 216 exit(0);
michael@0 217 return 0;
michael@0 218 }
michael@0 219
michael@0 220 void
michael@0 221 StartTerminationThread()
michael@0 222 {
michael@0 223 // If the process does not self terminate like it should, this thread
michael@0 224 // will terminate the process after 5 seconds.
michael@0 225 HANDLE thread = CreateThread(nullptr, 0, EnsureProcessTerminatedThread,
michael@0 226 nullptr, 0, nullptr);
michael@0 227 if (thread) {
michael@0 228 CloseHandle(thread);
michael@0 229 }
michael@0 230 }
michael@0 231
michael@0 232 /**
michael@0 233 * Main entry point when running as a service.
michael@0 234 */
michael@0 235 void WINAPI
michael@0 236 SvcMain(DWORD argc, LPWSTR *argv)
michael@0 237 {
michael@0 238 // Setup logging, and backup the old logs
michael@0 239 WCHAR updatePath[MAX_PATH + 1];
michael@0 240 if (GetLogDirectoryPath(updatePath)) {
michael@0 241 BackupOldLogs(updatePath, LOGS_TO_KEEP);
michael@0 242 LogInit(updatePath, L"maintenanceservice.log");
michael@0 243 }
michael@0 244
michael@0 245 // Disable every privilege we don't need. Processes started using
michael@0 246 // CreateProcess will use the same token as this process.
michael@0 247 UACHelper::DisablePrivileges(nullptr);
michael@0 248
michael@0 249 // Register the handler function for the service
michael@0 250 gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler);
michael@0 251 if (!gSvcStatusHandle) {
michael@0 252 LOG_WARN(("RegisterServiceCtrlHandler failed. (%d)", GetLastError()));
michael@0 253 ExecuteServiceCommand(argc, argv);
michael@0 254 LogFinish();
michael@0 255 exit(1);
michael@0 256 }
michael@0 257
michael@0 258 // These values will be re-used later in calls involving gSvcStatus
michael@0 259 gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
michael@0 260 gSvcStatus.dwServiceSpecificExitCode = 0;
michael@0 261
michael@0 262 // Report initial status to the SCM
michael@0 263 ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
michael@0 264
michael@0 265 // This event will be used to tell the SvcCtrlHandler when the work is
michael@0 266 // done for when a stop comamnd is manually issued.
michael@0 267 gWorkDoneEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
michael@0 268 if (!gWorkDoneEvent) {
michael@0 269 ReportSvcStatus(SERVICE_STOPPED, 1, 0);
michael@0 270 StartTerminationThread();
michael@0 271 return;
michael@0 272 }
michael@0 273
michael@0 274 // Initialization complete and we're about to start working on
michael@0 275 // the actual command. Report the service state as running to the SCM.
michael@0 276 ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
michael@0 277
michael@0 278 // The service command was executed, stop logging and set an event
michael@0 279 // to indicate the work is done in case someone is waiting on a
michael@0 280 // service stop operation.
michael@0 281 ExecuteServiceCommand(argc, argv);
michael@0 282 LogFinish();
michael@0 283
michael@0 284 SetEvent(gWorkDoneEvent);
michael@0 285
michael@0 286 // If we aren't already in a stopping state then tell the SCM we're stopped
michael@0 287 // now. If we are already in a stopping state then the SERVICE_STOPPED state
michael@0 288 // will be set by the SvcCtrlHandler.
michael@0 289 if (!gServiceControlStopping) {
michael@0 290 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
michael@0 291 StartTerminationThread();
michael@0 292 }
michael@0 293 }
michael@0 294
michael@0 295 /**
michael@0 296 * Sets the current service status and reports it to the SCM.
michael@0 297 *
michael@0 298 * @param currentState The current state (see SERVICE_STATUS)
michael@0 299 * @param exitCode The system error code
michael@0 300 * @param waitHint Estimated time for pending operation in milliseconds
michael@0 301 */
michael@0 302 void
michael@0 303 ReportSvcStatus(DWORD currentState,
michael@0 304 DWORD exitCode,
michael@0 305 DWORD waitHint)
michael@0 306 {
michael@0 307 static DWORD dwCheckPoint = 1;
michael@0 308
michael@0 309 gSvcStatus.dwCurrentState = currentState;
michael@0 310 gSvcStatus.dwWin32ExitCode = exitCode;
michael@0 311 gSvcStatus.dwWaitHint = waitHint;
michael@0 312
michael@0 313 if (SERVICE_START_PENDING == currentState ||
michael@0 314 SERVICE_STOP_PENDING == currentState) {
michael@0 315 gSvcStatus.dwControlsAccepted = 0;
michael@0 316 } else {
michael@0 317 gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
michael@0 318 SERVICE_ACCEPT_SHUTDOWN;
michael@0 319 }
michael@0 320
michael@0 321 if ((SERVICE_RUNNING == currentState) ||
michael@0 322 (SERVICE_STOPPED == currentState)) {
michael@0 323 gSvcStatus.dwCheckPoint = 0;
michael@0 324 } else {
michael@0 325 gSvcStatus.dwCheckPoint = dwCheckPoint++;
michael@0 326 }
michael@0 327
michael@0 328 // Report the status of the service to the SCM.
michael@0 329 SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
michael@0 330 }
michael@0 331
michael@0 332 /**
michael@0 333 * Since the SvcCtrlHandler should only spend at most 30 seconds before
michael@0 334 * returning, this function does the service stop work for the SvcCtrlHandler.
michael@0 335 */
michael@0 336 DWORD WINAPI
michael@0 337 StopServiceAndWaitForCommandThread(LPVOID)
michael@0 338 {
michael@0 339 do {
michael@0 340 ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000);
michael@0 341 } while(WaitForSingleObject(gWorkDoneEvent, 100) == WAIT_TIMEOUT);
michael@0 342 CloseHandle(gWorkDoneEvent);
michael@0 343 gWorkDoneEvent = nullptr;
michael@0 344 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
michael@0 345 StartTerminationThread();
michael@0 346 return 0;
michael@0 347 }
michael@0 348
michael@0 349 /**
michael@0 350 * Called by SCM whenever a control code is sent to the service
michael@0 351 * using the ControlService function.
michael@0 352 */
michael@0 353 void WINAPI
michael@0 354 SvcCtrlHandler(DWORD dwCtrl)
michael@0 355 {
michael@0 356 // After a SERVICE_CONTROL_STOP there should be no more commands sent to
michael@0 357 // the SvcCtrlHandler.
michael@0 358 if (gServiceControlStopping) {
michael@0 359 return;
michael@0 360 }
michael@0 361
michael@0 362 // Handle the requested control code.
michael@0 363 switch(dwCtrl) {
michael@0 364 case SERVICE_CONTROL_SHUTDOWN:
michael@0 365 case SERVICE_CONTROL_STOP: {
michael@0 366 gServiceControlStopping = true;
michael@0 367 ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000);
michael@0 368
michael@0 369 // The SvcCtrlHandler thread should not spend more than 30 seconds in
michael@0 370 // shutdown so we spawn a new thread for stopping the service
michael@0 371 HANDLE thread = CreateThread(nullptr, 0,
michael@0 372 StopServiceAndWaitForCommandThread,
michael@0 373 nullptr, 0, nullptr);
michael@0 374 if (thread) {
michael@0 375 CloseHandle(thread);
michael@0 376 } else {
michael@0 377 // Couldn't start the thread so just call the stop ourselves.
michael@0 378 // If it happens to take longer than 30 seconds the caller will
michael@0 379 // get an error.
michael@0 380 StopServiceAndWaitForCommandThread(nullptr);
michael@0 381 }
michael@0 382 }
michael@0 383 break;
michael@0 384 default:
michael@0 385 break;
michael@0 386 }
michael@0 387 }

mercurial