Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / onlineupdate / source / service / maintenanceservice.cxx
blobda324de54df37dc059bfa434c6b79370cdc37785
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.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);
33 int
34 wmain(int argc, WCHAR **argv)
36 if (argc < 2)
38 LOG_WARN(("missing mandatory command line argument"));
39 return 1;
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;
59 if (forceInstall)
61 action = ForceInstallSvc;
62 LOG(("Installing service with force specified..."));
64 else
66 LOG(("Installing service..."));
69 bool ret = SvcInstall(action);
70 if (!ret)
72 LOG_WARN(("Could not install service. (%d)", GetLastError()));
73 LogFinish();
74 return 1;
77 LOG(("The service was installed successfully"));
78 LogFinish();
79 return 0;
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()));
94 LogFinish();
95 return 1;
98 LOG(("The service was upgraded successfully"));
99 LogFinish();
100 return 0;
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..."));
111 if (!SvcUninstall())
113 LOG_WARN(("Could not uninstall service. (%d)", GetLastError()));
114 LogFinish();
115 return 1;
117 LOG(("The service was uninstalled successfully"));
118 LogFinish();
119 return 0;
122 SERVICE_TABLE_ENTRYW DispatchTable[] =
124 { SVC_NAME, (LPSERVICE_MAIN_FUNCTIONW) SvcMain },
125 { nullptr, nullptr }
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()));
135 return 0;
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.
144 BOOL
145 GetLogDirectoryPath(WCHAR *path)
147 HRESULT hr = SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA, nullptr,
148 SHGFP_TYPE_CURRENT, path);
149 if (FAILED(hr))
151 return FALSE;
154 if (!PathAppendSafe(path, L"Mozilla"))
156 return FALSE;
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"))
164 return FALSE;
166 CreateDirectoryW(path, nullptr);
167 return TRUE;
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.
178 BOOL
179 GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber)
181 WCHAR logName[64] = { L'\0' };
182 wcsncpy(path, basePath, sizeof(logName) / sizeof(logName[0]) - 1);
183 if (logNumber <= 0)
185 swprintf(logName, sizeof(logName) / sizeof(logName[0]),
186 L"maintenanceservice.log");
188 else
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
207 void
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))
216 continue;
219 if (!GetBackupLogPath(newPath, basePath, i))
221 continue;
224 if (!MoveFileExW(oldPath, newPath, MOVEFILE_REPLACE_EXISTING))
226 continue;
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
243 * start this thread.
245 DWORD WINAPI
246 EnsureProcessTerminatedThread(LPVOID)
248 Sleep(5000);
249 exit(0);
252 void
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);
259 if (thread)
261 CloseHandle(thread);
266 * Main entry point when running as a service.
268 void WINAPI
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);
289 LogFinish();
290 exit(1);
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);
303 if (!gWorkDoneEvent)
305 ReportSvcStatus(SERVICE_STOPPED, 1, 0);
306 StartTerminationThread();
307 return;
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);
318 LogFinish();
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
339 void
340 ReportSvcStatus(DWORD currentState,
341 DWORD exitCode,
342 DWORD waitHint)
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;
355 else
357 gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
358 SERVICE_ACCEPT_SHUTDOWN;
361 if ((SERVICE_RUNNING == currentState) ||
362 (SERVICE_STOPPED == currentState))
364 gSvcStatus.dwCheckPoint = 0;
366 else
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.
379 DWORD WINAPI
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();
391 return 0;
395 * Called by SCM whenever a control code is sent to the service
396 * using the ControlService function.
398 void WINAPI
399 SvcCtrlHandler(DWORD dwCtrl)
401 // After a SERVICE_CONTROL_STOP there should be no more commands sent to
402 // the SvcCtrlHandler.
403 if (gServiceControlStopping)
405 return;
408 // Handle the requested control code.
409 switch (dwCtrl)
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);
422 if (thread)
424 CloseHandle(thread);
426 else
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.
431 StopServiceAndWaitForCommandThread(nullptr);
434 break;
435 default:
436 break;