Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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 }