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/. */
11 #include "serviceinstall.hxx"
12 #include "maintenanceservice.hxx"
13 #include "servicebase.hxx"
14 #include "workmonitor.hxx"
15 #include "uachelper.h"
16 #include "updatehelper.h"
18 // Link w/ subsystem window so we don't get a console when executing
19 // this binary through the installer.
20 #pragma comment(linker, "/SUBSYSTEM:windows")
22 SERVICE_STATUS gSvcStatus
= { 0 };
23 SERVICE_STATUS_HANDLE gSvcStatusHandle
= nullptr;
24 HANDLE gWorkDoneEvent
= nullptr;
25 HANDLE gThread
= nullptr;
26 bool gServiceControlStopping
= false;
28 // logs are pretty small, about 20 lines, so 10 seems reasonable.
29 #define LOGS_TO_KEEP 10
31 BOOL
GetLogDirectoryPath(WCHAR
*path
);
34 wmain(int argc
, WCHAR
**argv
)
38 LOG_WARN(("missing mandatory command line argument"));
41 // If command-line parameter is "install", install the service
42 // or upgrade if already installed
43 // If command line parameter is "forceinstall", install the service
44 // even if it is older than what is already installed.
45 // If command-line parameter is "upgrade", upgrade the service
46 // but do not install it if it is not already installed.
47 // If command line parameter is "uninstall", uninstall the service.
48 // Otherwise, the service is probably being started by the SCM.
49 bool forceInstall
= !lstrcmpi(argv
[1], L
"forceinstall");
50 if (!lstrcmpi(argv
[1], L
"install") || forceInstall
)
52 WCHAR updatePath
[MAX_PATH
+ 1];
53 if (GetLogDirectoryPath(updatePath
))
55 LogInit(updatePath
, L
"maintenanceservice-install.log");
58 SvcInstallAction action
= InstallSvc
;
61 action
= ForceInstallSvc
;
62 LOG(("Installing service with force specified..."));
66 LOG(("Installing service..."));
69 bool ret
= SvcInstall(action
);
72 LOG_WARN(("Could not install service. (%d)", GetLastError()));
77 LOG(("The service was installed successfully"));
82 if (!lstrcmpi(argv
[1], L
"upgrade"))
84 WCHAR updatePath
[MAX_PATH
+ 1];
85 if (GetLogDirectoryPath(updatePath
))
87 LogInit(updatePath
, L
"maintenanceservice-install.log");
90 LOG(("Upgrading service if installed..."));
91 if (!SvcInstall(UpgradeSvc
))
93 LOG_WARN(("Could not upgrade service. (%d)", GetLastError()));
98 LOG(("The service was upgraded successfully"));
103 if (!lstrcmpi(argv
[1], L
"uninstall"))
105 WCHAR updatePath
[MAX_PATH
+ 1];
106 if (GetLogDirectoryPath(updatePath
))
108 LogInit(updatePath
, L
"maintenanceservice-uninstall.log");
110 LOG(("Uninstalling service..."));
113 LOG_WARN(("Could not uninstall service. (%d)", GetLastError()));
117 LOG(("The service was uninstalled successfully"));
122 SERVICE_TABLE_ENTRYW DispatchTable
[] =
124 { SVC_NAME
, (LPSERVICE_MAIN_FUNCTIONW
) SvcMain
},
128 // This call returns when the service has stopped.
129 // The process should simply terminate when the call returns.
130 if (!StartServiceCtrlDispatcherW(DispatchTable
))
132 LOG_WARN(("StartServiceCtrlDispatcher failed. (%d)", GetLastError()));
139 * Obtains the base path where logs should be stored
141 * @param path The out buffer for the backup log path of size MAX_PATH + 1
142 * @return TRUE if successful.
145 GetLogDirectoryPath(WCHAR
*path
)
147 HRESULT hr
= SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA
, nullptr,
148 SHGFP_TYPE_CURRENT
, path
);
154 if (!PathAppendSafe(path
, L
"Mozilla"))
158 // The directory should already be created from the installer, but
159 // just to be safe in case someone deletes.
160 CreateDirectoryW(path
, nullptr);
162 if (!PathAppendSafe(path
, L
"logs"))
166 CreateDirectoryW(path
, nullptr);
171 * Calculated a backup path based on the log number.
173 * @param path The out buffer to store the log path of size MAX_PATH + 1
174 * @param basePath The base directory where the calculated path should go
175 * @param logNumber The log number, 0 == updater.log
176 * @return TRUE if successful.
179 GetBackupLogPath(LPWSTR path
, LPCWSTR basePath
, int logNumber
)
181 WCHAR logName
[64] = { L
'\0' };
182 wcsncpy(path
, basePath
, sizeof(logName
) / sizeof(logName
[0]) - 1);
185 swprintf(logName
, sizeof(logName
) / sizeof(logName
[0]),
186 L
"maintenanceservice.log");
190 swprintf(logName
, sizeof(logName
) / sizeof(logName
[0]),
191 L
"maintenanceservice-%d.log", logNumber
);
193 return PathAppendSafe(path
, logName
);
197 * Moves the old log files out of the way before a new one is written.
198 * If you for example keep 3 logs, then this function will do:
199 * updater2.log -> updater3.log
200 * updater1.log -> updater2.log
201 * updater.log -> updater1.log
202 * Which clears room for a new updater.log in the basePath directory
204 * @param basePath The base directory path where log files are stored
205 * @param numLogsToKeep The number of logs to keep
208 BackupOldLogs(LPCWSTR basePath
, int numLogsToKeep
)
210 WCHAR oldPath
[MAX_PATH
+ 1];
211 WCHAR newPath
[MAX_PATH
+ 1];
212 for (int i
= numLogsToKeep
; i
>= 1; i
--)
214 if (!GetBackupLogPath(oldPath
, basePath
, i
-1))
219 if (!GetBackupLogPath(newPath
, basePath
, i
))
224 if (!MoveFileExW(oldPath
, newPath
, MOVEFILE_REPLACE_EXISTING
))
232 * Ensures the service is shutdown once all work is complete.
233 * There is an issue on XP SP2 and below where the service can hang
234 * in a stop pending state even though the SCM is notified of a stopped
235 * state. Control *should* be returned to StartServiceCtrlDispatcher from the
236 * call to SetServiceStatus on a stopped state in the wmain thread.
237 * Sometimes this is not the case though. This thread will terminate the process
238 * if it has been 5 seconds after all work is done and the process is still not
239 * terminated. This thread is only started once a stopped state was sent to the
240 * SCM. The stop pending hang can be reproduced intermittently even if you set
241 * a stopped state directly and never set a stop pending state. It is safe to
242 * forcefully terminate the process ourselves since all work is done once we
246 EnsureProcessTerminatedThread(LPVOID
)
253 StartTerminationThread()
255 // If the process does not self terminate like it should, this thread
256 // will terminate the process after 5 seconds.
257 HANDLE thread
= CreateThread(nullptr, 0, EnsureProcessTerminatedThread
,
258 nullptr, 0, nullptr);
266 * Main entry point when running as a service.
269 SvcMain(DWORD argc
, LPWSTR
*argv
)
271 // Setup logging, and backup the old logs
272 WCHAR updatePath
[MAX_PATH
+ 1];
273 if (GetLogDirectoryPath(updatePath
))
275 BackupOldLogs(updatePath
, LOGS_TO_KEEP
);
276 LogInit(updatePath
, L
"maintenanceservice.log");
279 // Disable every privilege we don't need. Processes started using
280 // CreateProcess will use the same token as this process.
281 UACHelper::DisablePrivileges(nullptr);
283 // Register the handler function for the service
284 gSvcStatusHandle
= RegisterServiceCtrlHandlerW(SVC_NAME
, SvcCtrlHandler
);
285 if (!gSvcStatusHandle
)
287 LOG_WARN(("RegisterServiceCtrlHandler failed. (%d)", GetLastError()));
288 ExecuteServiceCommand(argc
, argv
);
293 // These values will be re-used later in calls involving gSvcStatus
294 gSvcStatus
.dwServiceType
= SERVICE_WIN32_OWN_PROCESS
;
295 gSvcStatus
.dwServiceSpecificExitCode
= 0;
297 // Report initial status to the SCM
298 ReportSvcStatus(SERVICE_START_PENDING
, NO_ERROR
, 3000);
300 // This event will be used to tell the SvcCtrlHandler when the work is
301 // done for when a stop command is manually issued.
302 gWorkDoneEvent
= CreateEvent(nullptr, TRUE
, FALSE
, nullptr);
305 ReportSvcStatus(SERVICE_STOPPED
, 1, 0);
306 StartTerminationThread();
310 // Initialization complete and we're about to start working on
311 // the actual command. Report the service state as running to the SCM.
312 ReportSvcStatus(SERVICE_RUNNING
, NO_ERROR
, 0);
314 // The service command was executed, stop logging and set an event
315 // to indicate the work is done in case someone is waiting on a
316 // service stop operation.
317 ExecuteServiceCommand(argc
, argv
);
320 SetEvent(gWorkDoneEvent
);
322 // If we aren't already in a stopping state then tell the SCM we're stopped
323 // now. If we are already in a stopping state then the SERVICE_STOPPED state
324 // will be set by the SvcCtrlHandler.
325 if (!gServiceControlStopping
)
327 ReportSvcStatus(SERVICE_STOPPED
, NO_ERROR
, 0);
328 StartTerminationThread();
333 * Sets the current service status and reports it to the SCM.
335 * @param currentState The current state (see SERVICE_STATUS)
336 * @param exitCode The system error code
337 * @param waitHint Estimated time for pending operation in milliseconds
340 ReportSvcStatus(DWORD currentState
,
344 static DWORD dwCheckPoint
= 1;
346 gSvcStatus
.dwCurrentState
= currentState
;
347 gSvcStatus
.dwWin32ExitCode
= exitCode
;
348 gSvcStatus
.dwWaitHint
= waitHint
;
350 if (SERVICE_START_PENDING
== currentState
||
351 SERVICE_STOP_PENDING
== currentState
)
353 gSvcStatus
.dwControlsAccepted
= 0;
357 gSvcStatus
.dwControlsAccepted
= SERVICE_ACCEPT_STOP
|
358 SERVICE_ACCEPT_SHUTDOWN
;
361 if ((SERVICE_RUNNING
== currentState
) ||
362 (SERVICE_STOPPED
== currentState
))
364 gSvcStatus
.dwCheckPoint
= 0;
368 gSvcStatus
.dwCheckPoint
= dwCheckPoint
++;
371 // Report the status of the service to the SCM.
372 SetServiceStatus(gSvcStatusHandle
, &gSvcStatus
);
376 * Since the SvcCtrlHandler should only spend at most 30 seconds before
377 * returning, this function does the service stop work for the SvcCtrlHandler.
380 StopServiceAndWaitForCommandThread(LPVOID
)
384 ReportSvcStatus(SERVICE_STOP_PENDING
, NO_ERROR
, 1000);
386 while (WaitForSingleObject(gWorkDoneEvent
, 100) == WAIT_TIMEOUT
);
387 CloseHandle(gWorkDoneEvent
);
388 gWorkDoneEvent
= nullptr;
389 ReportSvcStatus(SERVICE_STOPPED
, NO_ERROR
, 0);
390 StartTerminationThread();
395 * Called by SCM whenever a control code is sent to the service
396 * using the ControlService function.
399 SvcCtrlHandler(DWORD dwCtrl
)
401 // After a SERVICE_CONTROL_STOP there should be no more commands sent to
402 // the SvcCtrlHandler.
403 if (gServiceControlStopping
)
408 // Handle the requested control code.
411 case SERVICE_CONTROL_SHUTDOWN
:
412 case SERVICE_CONTROL_STOP
:
414 gServiceControlStopping
= true;
415 ReportSvcStatus(SERVICE_STOP_PENDING
, NO_ERROR
, 1000);
417 // The SvcCtrlHandler thread should not spend more than 30 seconds in
418 // shutdown so we spawn a new thread for stopping the service
419 HANDLE thread
= CreateThread(nullptr, 0,
420 StopServiceAndWaitForCommandThread
,
421 nullptr, 0, nullptr);
428 // Couldn't start the thread so just call the stop ourselves.
429 // If it happens to take longer than 30 seconds the caller will
431 StopServiceAndWaitForCommandThread(nullptr);