toolkit/components/maintenanceservice/maintenanceservice.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial