toolkit/components/maintenanceservice/maintenanceservice.cpp

changeset 0
6474c204b198
     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 +}

mercurial