LibreOffice Module onlineupdate (master) 1
maintenanceservice.cxx
Go to the documentation of this file.
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/. */
4
5#include <windows.h>
6#include <shlwapi.h>
7#include <stdio.h>
8#include <wchar.h>
9#include <shlobj.h>
10
11#include "serviceinstall.hxx"
13#include "servicebase.hxx"
14#include "workmonitor.hxx"
15#include "uachelper.h"
16#include "updatehelper.h"
17
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")
21
22SERVICE_STATUS gSvcStatus = { 0 };
23SERVICE_STATUS_HANDLE gSvcStatusHandle = nullptr;
24HANDLE gWorkDoneEvent = nullptr;
25HANDLE gThread = nullptr;
27
28// logs are pretty small, about 20 lines, so 10 seems reasonable.
29#define LOGS_TO_KEEP 10
30
31BOOL GetLogDirectoryPath(WCHAR *path);
32
33int
34wmain(int argc, WCHAR **argv)
35{
36 if (argc < 2)
37 {
38 LOG_WARN(("missing mandatory command line argument"));
39 return 1;
40 }
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)
51 {
52 WCHAR updatePath[MAX_PATH + 1];
53 if (GetLogDirectoryPath(updatePath))
54 {
55 LogInit(updatePath, L"maintenanceservice-install.log");
56 }
57
59 if (forceInstall)
60 {
62 LOG(("Installing service with force specified..."));
63 }
64 else
65 {
66 LOG(("Installing service..."));
67 }
68
69 bool ret = SvcInstall(action);
70 if (!ret)
71 {
72 LOG_WARN(("Could not install service. (%d)", GetLastError()));
73 LogFinish();
74 return 1;
75 }
76
77 LOG(("The service was installed successfully"));
78 LogFinish();
79 return 0;
80 }
81
82 if (!lstrcmpi(argv[1], L"upgrade"))
83 {
84 WCHAR updatePath[MAX_PATH + 1];
85 if (GetLogDirectoryPath(updatePath))
86 {
87 LogInit(updatePath, L"maintenanceservice-install.log");
88 }
89
90 LOG(("Upgrading service if installed..."));
92 {
93 LOG_WARN(("Could not upgrade service. (%d)", GetLastError()));
94 LogFinish();
95 return 1;
96 }
97
98 LOG(("The service was upgraded successfully"));
99 LogFinish();
100 return 0;
101 }
102
103 if (!lstrcmpi(argv[1], L"uninstall"))
104 {
105 WCHAR updatePath[MAX_PATH + 1];
106 if (GetLogDirectoryPath(updatePath))
107 {
108 LogInit(updatePath, L"maintenanceservice-uninstall.log");
109 }
110 LOG(("Uninstalling service..."));
111 if (!SvcUninstall())
112 {
113 LOG_WARN(("Could not uninstall service. (%d)", GetLastError()));
114 LogFinish();
115 return 1;
116 }
117 LOG(("The service was uninstalled successfully"));
118 LogFinish();
119 return 0;
120 }
121
122 SERVICE_TABLE_ENTRYW DispatchTable[] =
123 {
124 { SVC_NAME, (LPSERVICE_MAIN_FUNCTIONW) SvcMain },
125 { nullptr, nullptr }
126 };
127
128 // This call returns when the service has stopped.
129 // The process should simply terminate when the call returns.
130 if (!StartServiceCtrlDispatcherW(DispatchTable))
131 {
132 LOG_WARN(("StartServiceCtrlDispatcher failed. (%d)", GetLastError()));
133 }
134
135 return 0;
136}
137
144BOOL
146{
147 HRESULT hr = SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA, nullptr,
148 SHGFP_TYPE_CURRENT, path);
149 if (FAILED(hr))
150 {
151 return FALSE;
152 }
153
154 if (!PathAppendSafe(path, L"Mozilla"))
155 {
156 return FALSE;
157 }
158 // The directory should already be created from the installer, but
159 // just to be safe in case someone deletes.
160 CreateDirectoryW(path, nullptr);
161
162 if (!PathAppendSafe(path, L"logs"))
163 {
164 return FALSE;
165 }
166 CreateDirectoryW(path, nullptr);
167 return TRUE;
168}
169
178BOOL
179GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber)
180{
181 WCHAR logName[64] = { L'\0' };
182 wcsncpy(path, basePath, sizeof(logName) / sizeof(logName[0]) - 1);
183 if (logNumber <= 0)
184 {
185 swprintf(logName, sizeof(logName) / sizeof(logName[0]),
186 L"maintenanceservice.log");
187 }
188 else
189 {
190 swprintf(logName, sizeof(logName) / sizeof(logName[0]),
191 L"maintenanceservice-%d.log", logNumber);
192 }
193 return PathAppendSafe(path, logName);
194}
195
207void
208BackupOldLogs(LPCWSTR basePath, int numLogsToKeep)
209{
210 WCHAR oldPath[MAX_PATH + 1];
211 WCHAR newPath[MAX_PATH + 1];
212 for (int i = numLogsToKeep; i >= 1; i--)
213 {
214 if (!GetBackupLogPath(oldPath, basePath, i -1))
215 {
216 continue;
217 }
218
219 if (!GetBackupLogPath(newPath, basePath, i))
220 {
221 continue;
222 }
223
224 if (!MoveFileExW(oldPath, newPath, MOVEFILE_REPLACE_EXISTING))
225 {
226 continue;
227 }
228 }
229}
230
245DWORD WINAPI
247{
248 Sleep(5000);
249 exit(0);
250}
251
252void
254{
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);
259 if (thread)
260 {
261 CloseHandle(thread);
262 }
263}
264
268void WINAPI
269SvcMain(DWORD argc, LPWSTR *argv)
270{
271 // Setup logging, and backup the old logs
272 WCHAR updatePath[MAX_PATH + 1];
273 if (GetLogDirectoryPath(updatePath))
274 {
275 BackupOldLogs(updatePath, LOGS_TO_KEEP);
276 LogInit(updatePath, L"maintenanceservice.log");
277 }
278
279 // Disable every privilege we don't need. Processes started using
280 // CreateProcess will use the same token as this process.
282
283 // Register the handler function for the service
284 gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler);
285 if (!gSvcStatusHandle)
286 {
287 LOG_WARN(("RegisterServiceCtrlHandler failed. (%d)", GetLastError()));
288 ExecuteServiceCommand(argc, argv);
289 LogFinish();
290 exit(1);
291 }
292
293 // These values will be re-used later in calls involving gSvcStatus
294 gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
295 gSvcStatus.dwServiceSpecificExitCode = 0;
296
297 // Report initial status to the SCM
298 ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
299
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);
303 if (!gWorkDoneEvent)
304 {
305 ReportSvcStatus(SERVICE_STOPPED, 1, 0);
307 return;
308 }
309
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);
313
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);
318 LogFinish();
319
320 SetEvent(gWorkDoneEvent);
321
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.
326 {
327 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
329 }
330}
331
339void
340ReportSvcStatus(DWORD currentState,
341 DWORD exitCode,
342 DWORD waitHint)
343{
344 static DWORD dwCheckPoint = 1;
345
346 gSvcStatus.dwCurrentState = currentState;
347 gSvcStatus.dwWin32ExitCode = exitCode;
348 gSvcStatus.dwWaitHint = waitHint;
349
350 if (SERVICE_START_PENDING == currentState ||
351 SERVICE_STOP_PENDING == currentState)
352 {
353 gSvcStatus.dwControlsAccepted = 0;
354 }
355 else
356 {
357 gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
358 SERVICE_ACCEPT_SHUTDOWN;
359 }
360
361 if ((SERVICE_RUNNING == currentState) ||
362 (SERVICE_STOPPED == currentState))
363 {
364 gSvcStatus.dwCheckPoint = 0;
365 }
366 else
367 {
368 gSvcStatus.dwCheckPoint = dwCheckPoint++;
369 }
370
371 // Report the status of the service to the SCM.
372 SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
373}
374
379DWORD WINAPI
381{
382 do
383 {
384 ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000);
385 }
386 while (WaitForSingleObject(gWorkDoneEvent, 100) == WAIT_TIMEOUT);
387 CloseHandle(gWorkDoneEvent);
388 gWorkDoneEvent = nullptr;
389 ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
391 return 0;
392}
393
398void WINAPI
399SvcCtrlHandler(DWORD dwCtrl)
400{
401 // After a SERVICE_CONTROL_STOP there should be no more commands sent to
402 // the SvcCtrlHandler.
404 {
405 return;
406 }
407
408 // Handle the requested control code.
409 switch (dwCtrl)
410 {
411 case SERVICE_CONTROL_SHUTDOWN:
412 case SERVICE_CONTROL_STOP:
413 {
415 ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000);
416
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,
421 nullptr, 0, nullptr);
422 if (thread)
423 {
424 CloseHandle(thread);
425 }
426 else
427 {
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
430 // get an error.
432 }
433 }
434 break;
435 default:
436 break;
437 }
438}
static BOOL DisablePrivileges(HANDLE token)
#define TRUE
#define FALSE
#define MAX_PATH
void WINAPI SvcMain(DWORD argc, LPWSTR *argv)
Main entry point when running as a service.
void BackupOldLogs(LPCWSTR basePath, int numLogsToKeep)
Moves the old log files out of the way before a new one is written.
DWORD WINAPI EnsureProcessTerminatedThread(LPVOID)
Ensures the service is shutdown once all work is complete.
bool gServiceControlStopping
void ReportSvcStatus(DWORD currentState, DWORD exitCode, DWORD waitHint)
Sets the current service status and reports it to the SCM.
BOOL GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber)
Calculated a backup path based on the log number.
HANDLE gWorkDoneEvent
BOOL GetLogDirectoryPath(WCHAR *path)
Obtains the base path where logs should be stored.
void WINAPI SvcCtrlHandler(DWORD dwCtrl)
Called by SCM whenever a control code is sent to the service using the ControlService function.
int wmain(int argc, WCHAR **argv)
DWORD WINAPI StopServiceAndWaitForCommandThread(LPVOID)
Since the SvcCtrlHandler should only spend at most 30 seconds before returning, this function does th...
SERVICE_STATUS gSvcStatus
HANDLE gThread
void StartTerminationThread()
SERVICE_STATUS_HANDLE gSvcStatusHandle
#define LOGS_TO_KEEP
int i
action
const wchar_t *typedef BOOL
NO_ERROR
BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra)
BOOL SvcInstall(SvcInstallAction action)
Installs or upgrades the SVC_NAME service.
BOOL SvcUninstall()
Uninstalls the Maintenance service.
SvcInstallAction
@ UpgradeSvc
@ InstallSvc
@ ForceInstallSvc
return hr
#define SVC_NAME
Definition: updatehelper.h:21
#define LogInit(PATHNAME_, FILENAME_)
Definition: updatelogging.h:40
#define LogFinish()
Definition: updatelogging.h:44
#define LOG_WARN(args)
Definition: updatelogging.h:38
#define LOG(args)
Definition: updatelogging.h:39
BOOL ExecuteServiceCommand(int argc, LPWSTR *argv)
Executes a service command.