|
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 #ifdef MOZ_METRO |
|
6 // Needed for COM calls to launch Metro applications |
|
7 #undef WINVER |
|
8 #undef _WIN32_WINNT |
|
9 #define WINVER 0x602 |
|
10 #define _WIN32_WINNT 0x602 |
|
11 #include <objbase.h> |
|
12 #include <shobjidl.h> |
|
13 #pragma comment(lib, "ole32.lib") |
|
14 #endif |
|
15 |
|
16 #include <windows.h> |
|
17 |
|
18 // Needed for CreateToolhelp32Snapshot |
|
19 #include <tlhelp32.h> |
|
20 #ifndef ONLY_SERVICE_LAUNCHING |
|
21 |
|
22 #include <stdio.h> |
|
23 #include "shlobj.h" |
|
24 #include "updatehelper.h" |
|
25 #include "uachelper.h" |
|
26 #include "pathhash.h" |
|
27 #include "mozilla/Scoped.h" |
|
28 |
|
29 // Needed for PathAppendW |
|
30 #include <shlwapi.h> |
|
31 |
|
32 WCHAR* MakeCommandLine(int argc, WCHAR **argv); |
|
33 BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra); |
|
34 |
|
35 /** |
|
36 * Obtains the path of a file in the same directory as the specified file. |
|
37 * |
|
38 * @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result. |
|
39 * @param siblingFIlePath The path of another file in the same directory |
|
40 * @param newFileName The filename of another file in the same directory |
|
41 * @return TRUE if successful |
|
42 */ |
|
43 BOOL |
|
44 PathGetSiblingFilePath(LPWSTR destinationBuffer, |
|
45 LPCWSTR siblingFilePath, |
|
46 LPCWSTR newFileName) |
|
47 { |
|
48 if (wcslen(siblingFilePath) >= MAX_PATH) { |
|
49 return FALSE; |
|
50 } |
|
51 |
|
52 wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH); |
|
53 if (!PathRemoveFileSpecW(destinationBuffer)) { |
|
54 return FALSE; |
|
55 } |
|
56 |
|
57 if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) { |
|
58 return FALSE; |
|
59 } |
|
60 |
|
61 return PathAppendSafe(destinationBuffer, newFileName); |
|
62 } |
|
63 |
|
64 /** |
|
65 * Launch the post update application as the specified user (helper.exe). |
|
66 * It takes in the path of the callback application to calculate the path |
|
67 * of helper.exe. For service updates this is called from both the system |
|
68 * account and the current user account. |
|
69 * |
|
70 * @param installationDir The path to the callback application binary. |
|
71 * @param updateInfoDir The directory where update info is stored. |
|
72 * @param forceSync If true even if the ini file specifies async, the |
|
73 * process will wait for termination of PostUpdate. |
|
74 * @param userToken The user token to run as, if nullptr the current |
|
75 * user will be used. |
|
76 * @return TRUE if there was no error starting the process. |
|
77 */ |
|
78 BOOL |
|
79 LaunchWinPostProcess(const WCHAR *installationDir, |
|
80 const WCHAR *updateInfoDir, |
|
81 bool forceSync, |
|
82 HANDLE userToken) |
|
83 { |
|
84 WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' }; |
|
85 wcsncpy(workingDirectory, installationDir, MAX_PATH); |
|
86 |
|
87 // Launch helper.exe to perform post processing (e.g. registry and log file |
|
88 // modifications) for the update. |
|
89 WCHAR inifile[MAX_PATH + 1] = { L'\0' }; |
|
90 wcsncpy(inifile, installationDir, MAX_PATH); |
|
91 if (!PathAppendSafe(inifile, L"updater.ini")) { |
|
92 return FALSE; |
|
93 } |
|
94 |
|
95 WCHAR exefile[MAX_PATH + 1]; |
|
96 WCHAR exearg[MAX_PATH + 1]; |
|
97 WCHAR exeasync[10]; |
|
98 bool async = true; |
|
99 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr, |
|
100 exefile, MAX_PATH + 1, inifile)) { |
|
101 return FALSE; |
|
102 } |
|
103 |
|
104 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg, |
|
105 MAX_PATH + 1, inifile)) { |
|
106 return FALSE; |
|
107 } |
|
108 |
|
109 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE", |
|
110 exeasync, |
|
111 sizeof(exeasync)/sizeof(exeasync[0]), |
|
112 inifile)) { |
|
113 return FALSE; |
|
114 } |
|
115 |
|
116 WCHAR exefullpath[MAX_PATH + 1] = { L'\0' }; |
|
117 wcsncpy(exefullpath, installationDir, MAX_PATH); |
|
118 if (!PathAppendSafe(exefullpath, exefile)) { |
|
119 return false; |
|
120 } |
|
121 |
|
122 WCHAR dlogFile[MAX_PATH + 1]; |
|
123 if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) { |
|
124 return FALSE; |
|
125 } |
|
126 |
|
127 WCHAR slogFile[MAX_PATH + 1] = { L'\0' }; |
|
128 wcsncpy(slogFile, updateInfoDir, MAX_PATH); |
|
129 if (!PathAppendSafe(slogFile, L"update.log")) { |
|
130 return FALSE; |
|
131 } |
|
132 |
|
133 WCHAR dummyArg[14] = { L'\0' }; |
|
134 wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1); |
|
135 |
|
136 size_t len = wcslen(exearg) + wcslen(dummyArg); |
|
137 WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR)); |
|
138 if (!cmdline) { |
|
139 return FALSE; |
|
140 } |
|
141 |
|
142 wcsncpy(cmdline, dummyArg, len); |
|
143 wcscat(cmdline, exearg); |
|
144 |
|
145 if (forceSync || |
|
146 !_wcsnicmp(exeasync, L"false", 6) || |
|
147 !_wcsnicmp(exeasync, L"0", 2)) { |
|
148 async = false; |
|
149 } |
|
150 |
|
151 // We want to launch the post update helper app to update the Windows |
|
152 // registry even if there is a failure with removing the uninstall.update |
|
153 // file or copying the update.log file. |
|
154 CopyFileW(slogFile, dlogFile, false); |
|
155 |
|
156 STARTUPINFOW si = {sizeof(si), 0}; |
|
157 si.lpDesktop = L""; |
|
158 PROCESS_INFORMATION pi = {0}; |
|
159 |
|
160 bool ok; |
|
161 if (userToken) { |
|
162 ok = CreateProcessAsUserW(userToken, |
|
163 exefullpath, |
|
164 cmdline, |
|
165 nullptr, // no special security attributes |
|
166 nullptr, // no special thread attributes |
|
167 false, // don't inherit filehandles |
|
168 0, // No special process creation flags |
|
169 nullptr, // inherit my environment |
|
170 workingDirectory, |
|
171 &si, |
|
172 &pi); |
|
173 } else { |
|
174 ok = CreateProcessW(exefullpath, |
|
175 cmdline, |
|
176 nullptr, // no special security attributes |
|
177 nullptr, // no special thread attributes |
|
178 false, // don't inherit filehandles |
|
179 0, // No special process creation flags |
|
180 nullptr, // inherit my environment |
|
181 workingDirectory, |
|
182 &si, |
|
183 &pi); |
|
184 } |
|
185 free(cmdline); |
|
186 if (ok) { |
|
187 if (!async) |
|
188 WaitForSingleObject(pi.hProcess, INFINITE); |
|
189 CloseHandle(pi.hProcess); |
|
190 CloseHandle(pi.hThread); |
|
191 } |
|
192 return ok; |
|
193 } |
|
194 |
|
195 /** |
|
196 * Starts the upgrade process for update of the service if it is |
|
197 * already installed. |
|
198 * |
|
199 * @param installDir the installation directory where |
|
200 * maintenanceservice_installer.exe is located. |
|
201 * @return TRUE if successful |
|
202 */ |
|
203 BOOL |
|
204 StartServiceUpdate(LPCWSTR installDir) |
|
205 { |
|
206 // Get a handle to the local computer SCM database |
|
207 SC_HANDLE manager = OpenSCManager(nullptr, nullptr, |
|
208 SC_MANAGER_ALL_ACCESS); |
|
209 if (!manager) { |
|
210 return FALSE; |
|
211 } |
|
212 |
|
213 // Open the service |
|
214 SC_HANDLE svc = OpenServiceW(manager, SVC_NAME, |
|
215 SERVICE_ALL_ACCESS); |
|
216 if (!svc) { |
|
217 CloseServiceHandle(manager); |
|
218 return FALSE; |
|
219 } |
|
220 |
|
221 // If we reach here, then the service is installed, so |
|
222 // proceed with upgrading it. |
|
223 |
|
224 CloseServiceHandle(manager); |
|
225 |
|
226 // The service exists and we opened it, get the config bytes needed |
|
227 DWORD bytesNeeded; |
|
228 if (!QueryServiceConfigW(svc, nullptr, 0, &bytesNeeded) && |
|
229 GetLastError() != ERROR_INSUFFICIENT_BUFFER) { |
|
230 CloseServiceHandle(svc); |
|
231 return FALSE; |
|
232 } |
|
233 |
|
234 // Get the service config information, in particular we want the binary |
|
235 // path of the service. |
|
236 mozilla::ScopedDeleteArray<char> serviceConfigBuffer(new char[bytesNeeded]); |
|
237 if (!QueryServiceConfigW(svc, |
|
238 reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), |
|
239 bytesNeeded, &bytesNeeded)) { |
|
240 CloseServiceHandle(svc); |
|
241 return FALSE; |
|
242 } |
|
243 |
|
244 CloseServiceHandle(svc); |
|
245 |
|
246 QUERY_SERVICE_CONFIGW &serviceConfig = |
|
247 *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()); |
|
248 |
|
249 PathUnquoteSpacesW(serviceConfig.lpBinaryPathName); |
|
250 |
|
251 // Obtain the temp path of the maintenance service binary |
|
252 WCHAR tmpService[MAX_PATH + 1] = { L'\0' }; |
|
253 if (!PathGetSiblingFilePath(tmpService, serviceConfig.lpBinaryPathName, |
|
254 L"maintenanceservice_tmp.exe")) { |
|
255 return FALSE; |
|
256 } |
|
257 |
|
258 // Get the new maintenance service path from the install dir |
|
259 WCHAR newMaintServicePath[MAX_PATH + 1] = { L'\0' }; |
|
260 wcsncpy(newMaintServicePath, installDir, MAX_PATH); |
|
261 PathAppendSafe(newMaintServicePath, |
|
262 L"maintenanceservice.exe"); |
|
263 |
|
264 // Copy the temp file in alongside the maintenace service. |
|
265 // This is a requirement for maintenance service upgrades. |
|
266 if (!CopyFileW(newMaintServicePath, tmpService, FALSE)) { |
|
267 return FALSE; |
|
268 } |
|
269 |
|
270 // Start the upgrade comparison process |
|
271 STARTUPINFOW si = {0}; |
|
272 si.cb = sizeof(STARTUPINFOW); |
|
273 // No particular desktop because no UI |
|
274 si.lpDesktop = L""; |
|
275 PROCESS_INFORMATION pi = {0}; |
|
276 WCHAR cmdLine[64] = { '\0' }; |
|
277 wcsncpy(cmdLine, L"dummyparam.exe upgrade", |
|
278 sizeof(cmdLine) / sizeof(cmdLine[0]) - 1); |
|
279 BOOL svcUpdateProcessStarted = CreateProcessW(tmpService, |
|
280 cmdLine, |
|
281 nullptr, nullptr, FALSE, |
|
282 0, |
|
283 nullptr, installDir, &si, &pi); |
|
284 if (svcUpdateProcessStarted) { |
|
285 CloseHandle(pi.hProcess); |
|
286 CloseHandle(pi.hThread); |
|
287 } |
|
288 return svcUpdateProcessStarted; |
|
289 } |
|
290 |
|
291 #endif |
|
292 |
|
293 /** |
|
294 * Executes a maintenance service command |
|
295 * |
|
296 * @param argc The total number of arguments in argv |
|
297 * @param argv An array of null terminated strings to pass to the service, |
|
298 * @return ERROR_SUCCESS if the service command was started. |
|
299 * Less than 16000, a windows system error code from StartServiceW |
|
300 * More than 20000, 20000 + the last state of the service constant if |
|
301 * the last state is something other than stopped. |
|
302 * 17001 if the SCM could not be opened |
|
303 * 17002 if the service could not be opened |
|
304 */ |
|
305 DWORD |
|
306 StartServiceCommand(int argc, LPCWSTR* argv) |
|
307 { |
|
308 DWORD lastState = WaitForServiceStop(SVC_NAME, 5); |
|
309 if (lastState != SERVICE_STOPPED) { |
|
310 return 20000 + lastState; |
|
311 } |
|
312 |
|
313 // Get a handle to the SCM database. |
|
314 SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr, |
|
315 SC_MANAGER_CONNECT | |
|
316 SC_MANAGER_ENUMERATE_SERVICE); |
|
317 if (!serviceManager) { |
|
318 return 17001; |
|
319 } |
|
320 |
|
321 // Get a handle to the service. |
|
322 SC_HANDLE service = OpenServiceW(serviceManager, |
|
323 SVC_NAME, |
|
324 SERVICE_START); |
|
325 if (!service) { |
|
326 CloseServiceHandle(serviceManager); |
|
327 return 17002; |
|
328 } |
|
329 |
|
330 // Wait at most 5 seconds trying to start the service in case of errors |
|
331 // like ERROR_SERVICE_DATABASE_LOCKED or ERROR_SERVICE_REQUEST_TIMEOUT. |
|
332 const DWORD maxWaitMS = 5000; |
|
333 DWORD currentWaitMS = 0; |
|
334 DWORD lastError = ERROR_SUCCESS; |
|
335 while (currentWaitMS < maxWaitMS) { |
|
336 BOOL result = StartServiceW(service, argc, argv); |
|
337 if (result) { |
|
338 lastError = ERROR_SUCCESS; |
|
339 break; |
|
340 } else { |
|
341 lastError = GetLastError(); |
|
342 } |
|
343 Sleep(100); |
|
344 currentWaitMS += 100; |
|
345 } |
|
346 CloseServiceHandle(service); |
|
347 CloseServiceHandle(serviceManager); |
|
348 return lastError; |
|
349 } |
|
350 |
|
351 #ifndef ONLY_SERVICE_LAUNCHING |
|
352 |
|
353 /** |
|
354 * Launch a service initiated action for a software update with the |
|
355 * specified arguments. |
|
356 * |
|
357 * @param exePath The path of the executable to run |
|
358 * @param argc The total number of arguments in argv |
|
359 * @param argv An array of null terminated strings to pass to the exePath, |
|
360 * argv[0] must be the path to the updater.exe |
|
361 * @return ERROR_SUCCESS if successful |
|
362 */ |
|
363 DWORD |
|
364 LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR* argv) |
|
365 { |
|
366 // The service command is the same as the updater.exe command line except |
|
367 // it has 2 extra args: 1) The Path to udpater.exe, and 2) the command |
|
368 // being executed which is "software-update" |
|
369 LPCWSTR *updaterServiceArgv = new LPCWSTR[argc + 2]; |
|
370 updaterServiceArgv[0] = L"MozillaMaintenance"; |
|
371 updaterServiceArgv[1] = L"software-update"; |
|
372 |
|
373 for (int i = 0; i < argc; ++i) { |
|
374 updaterServiceArgv[i + 2] = argv[i]; |
|
375 } |
|
376 |
|
377 // Execute the service command by starting the service with |
|
378 // the passed in arguments. |
|
379 DWORD ret = StartServiceCommand(argc + 2, updaterServiceArgv); |
|
380 delete[] updaterServiceArgv; |
|
381 return ret; |
|
382 } |
|
383 |
|
384 /** |
|
385 * Joins a base directory path with a filename. |
|
386 * |
|
387 * @param base The base directory path of size MAX_PATH + 1 |
|
388 * @param extra The filename to append |
|
389 * @return TRUE if the file name was successful appended to base |
|
390 */ |
|
391 BOOL |
|
392 PathAppendSafe(LPWSTR base, LPCWSTR extra) |
|
393 { |
|
394 if (wcslen(base) + wcslen(extra) >= MAX_PATH) { |
|
395 return FALSE; |
|
396 } |
|
397 |
|
398 return PathAppendW(base, extra); |
|
399 } |
|
400 |
|
401 /** |
|
402 * Sets update.status to pending so that the next startup will not use |
|
403 * the service and instead will attempt an update the with a UAC prompt. |
|
404 * |
|
405 * @param updateDirPath The path of the update directory |
|
406 * @return TRUE if successful |
|
407 */ |
|
408 BOOL |
|
409 WriteStatusPending(LPCWSTR updateDirPath) |
|
410 { |
|
411 WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' }; |
|
412 wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); |
|
413 if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { |
|
414 return FALSE; |
|
415 } |
|
416 |
|
417 const char pending[] = "pending"; |
|
418 HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, |
|
419 nullptr, CREATE_ALWAYS, 0, nullptr); |
|
420 if (statusFile == INVALID_HANDLE_VALUE) { |
|
421 return FALSE; |
|
422 } |
|
423 |
|
424 DWORD wrote; |
|
425 BOOL ok = WriteFile(statusFile, pending, |
|
426 sizeof(pending) - 1, &wrote, nullptr); |
|
427 CloseHandle(statusFile); |
|
428 return ok && (wrote == sizeof(pending) - 1); |
|
429 } |
|
430 |
|
431 /** |
|
432 * Sets update.status to a specific failure code |
|
433 * |
|
434 * @param updateDirPath The path of the update directory |
|
435 * @return TRUE if successful |
|
436 */ |
|
437 BOOL |
|
438 WriteStatusFailure(LPCWSTR updateDirPath, int errorCode) |
|
439 { |
|
440 WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' }; |
|
441 wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); |
|
442 if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { |
|
443 return FALSE; |
|
444 } |
|
445 |
|
446 HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, |
|
447 nullptr, CREATE_ALWAYS, 0, nullptr); |
|
448 if (statusFile == INVALID_HANDLE_VALUE) { |
|
449 return FALSE; |
|
450 } |
|
451 char failure[32]; |
|
452 sprintf(failure, "failed: %d", errorCode); |
|
453 |
|
454 DWORD toWrite = strlen(failure); |
|
455 DWORD wrote; |
|
456 BOOL ok = WriteFile(statusFile, failure, |
|
457 toWrite, &wrote, nullptr); |
|
458 CloseHandle(statusFile); |
|
459 return ok && wrote == toWrite; |
|
460 } |
|
461 |
|
462 #endif |
|
463 |
|
464 /** |
|
465 * Waits for a service to enter a stopped state. |
|
466 * This function does not stop the service, it just blocks until the service |
|
467 * is stopped. |
|
468 * |
|
469 * @param serviceName The service to wait for. |
|
470 * @param maxWaitSeconds The maximum number of seconds to wait |
|
471 * @return state of the service after a timeout or when stopped. |
|
472 * A value of 255 is returned for an error. Typical values are: |
|
473 * SERVICE_STOPPED 0x00000001 |
|
474 * SERVICE_START_PENDING 0x00000002 |
|
475 * SERVICE_STOP_PENDING 0x00000003 |
|
476 * SERVICE_RUNNING 0x00000004 |
|
477 * SERVICE_CONTINUE_PENDING 0x00000005 |
|
478 * SERVICE_PAUSE_PENDING 0x00000006 |
|
479 * SERVICE_PAUSED 0x00000007 |
|
480 * last status not set 0x000000CF |
|
481 * Could no query status 0x000000DF |
|
482 * Could not open service, access denied 0x000000EB |
|
483 * Could not open service, invalid handle 0x000000EC |
|
484 * Could not open service, invalid name 0x000000ED |
|
485 * Could not open service, does not exist 0x000000EE |
|
486 * Could not open service, other error 0x000000EF |
|
487 * Could not open SCM, access denied 0x000000FD |
|
488 * Could not open SCM, database does not exist 0x000000FE; |
|
489 * Could not open SCM, other error 0x000000FF; |
|
490 * Note: The strange choice of error codes above SERVICE_PAUSED are chosen |
|
491 * in case Windows comes out with other service stats higher than 7, they |
|
492 * would likely call it 8 and above. JS code that uses this in TestAUSHelper |
|
493 * only handles values up to 255 so that's why we don't use GetLastError |
|
494 * directly. |
|
495 */ |
|
496 DWORD |
|
497 WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds) |
|
498 { |
|
499 // 0x000000CF is defined above to be not set |
|
500 DWORD lastServiceState = 0x000000CF; |
|
501 |
|
502 // Get a handle to the SCM database. |
|
503 SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr, |
|
504 SC_MANAGER_CONNECT | |
|
505 SC_MANAGER_ENUMERATE_SERVICE); |
|
506 if (!serviceManager) { |
|
507 DWORD lastError = GetLastError(); |
|
508 switch(lastError) { |
|
509 case ERROR_ACCESS_DENIED: |
|
510 return 0x000000FD; |
|
511 case ERROR_DATABASE_DOES_NOT_EXIST: |
|
512 return 0x000000FE; |
|
513 default: |
|
514 return 0x000000FF; |
|
515 } |
|
516 } |
|
517 |
|
518 // Get a handle to the service. |
|
519 SC_HANDLE service = OpenServiceW(serviceManager, |
|
520 serviceName, |
|
521 SERVICE_QUERY_STATUS); |
|
522 if (!service) { |
|
523 DWORD lastError = GetLastError(); |
|
524 CloseServiceHandle(serviceManager); |
|
525 switch(lastError) { |
|
526 case ERROR_ACCESS_DENIED: |
|
527 return 0x000000EB; |
|
528 case ERROR_INVALID_HANDLE: |
|
529 return 0x000000EC; |
|
530 case ERROR_INVALID_NAME: |
|
531 return 0x000000ED; |
|
532 case ERROR_SERVICE_DOES_NOT_EXIST: |
|
533 return 0x000000EE; |
|
534 default: |
|
535 return 0x000000EF; |
|
536 } |
|
537 } |
|
538 |
|
539 DWORD currentWaitMS = 0; |
|
540 SERVICE_STATUS_PROCESS ssp; |
|
541 ssp.dwCurrentState = lastServiceState; |
|
542 while (currentWaitMS < maxWaitSeconds * 1000) { |
|
543 DWORD bytesNeeded; |
|
544 if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, |
|
545 sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) { |
|
546 DWORD lastError = GetLastError(); |
|
547 switch (lastError) { |
|
548 case ERROR_INVALID_HANDLE: |
|
549 ssp.dwCurrentState = 0x000000D9; |
|
550 break; |
|
551 case ERROR_ACCESS_DENIED: |
|
552 ssp.dwCurrentState = 0x000000DA; |
|
553 break; |
|
554 case ERROR_INSUFFICIENT_BUFFER: |
|
555 ssp.dwCurrentState = 0x000000DB; |
|
556 break; |
|
557 case ERROR_INVALID_PARAMETER: |
|
558 ssp.dwCurrentState = 0x000000DC; |
|
559 break; |
|
560 case ERROR_INVALID_LEVEL: |
|
561 ssp.dwCurrentState = 0x000000DD; |
|
562 break; |
|
563 case ERROR_SHUTDOWN_IN_PROGRESS: |
|
564 ssp.dwCurrentState = 0x000000DE; |
|
565 break; |
|
566 // These 3 errors can occur when the service is not yet stopped but |
|
567 // it is stopping. |
|
568 case ERROR_INVALID_SERVICE_CONTROL: |
|
569 case ERROR_SERVICE_CANNOT_ACCEPT_CTRL: |
|
570 case ERROR_SERVICE_NOT_ACTIVE: |
|
571 currentWaitMS += 50; |
|
572 Sleep(50); |
|
573 continue; |
|
574 default: |
|
575 ssp.dwCurrentState = 0x000000DF; |
|
576 } |
|
577 |
|
578 // We couldn't query the status so just break out |
|
579 break; |
|
580 } |
|
581 |
|
582 // The service is already in use. |
|
583 if (ssp.dwCurrentState == SERVICE_STOPPED) { |
|
584 break; |
|
585 } |
|
586 currentWaitMS += 50; |
|
587 Sleep(50); |
|
588 } |
|
589 |
|
590 lastServiceState = ssp.dwCurrentState; |
|
591 CloseServiceHandle(service); |
|
592 CloseServiceHandle(serviceManager); |
|
593 return lastServiceState; |
|
594 } |
|
595 |
|
596 #ifndef ONLY_SERVICE_LAUNCHING |
|
597 |
|
598 /** |
|
599 * Determines if there is at least one process running for the specified |
|
600 * application. A match will be found across any session for any user. |
|
601 * |
|
602 * @param process The process to check for existance |
|
603 * @return ERROR_NOT_FOUND if the process was not found |
|
604 * ERROR_SUCCESS if the process was found and there were no errors |
|
605 * Other Win32 system error code for other errors |
|
606 **/ |
|
607 DWORD |
|
608 IsProcessRunning(LPCWSTR filename) |
|
609 { |
|
610 // Take a snapshot of all processes in the system. |
|
611 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); |
|
612 if (INVALID_HANDLE_VALUE == snapshot) { |
|
613 return GetLastError(); |
|
614 } |
|
615 |
|
616 PROCESSENTRY32W processEntry; |
|
617 processEntry.dwSize = sizeof(PROCESSENTRY32W); |
|
618 if (!Process32FirstW(snapshot, &processEntry)) { |
|
619 DWORD lastError = GetLastError(); |
|
620 CloseHandle(snapshot); |
|
621 return lastError; |
|
622 } |
|
623 |
|
624 do { |
|
625 if (wcsicmp(filename, processEntry.szExeFile) == 0) { |
|
626 CloseHandle(snapshot); |
|
627 return ERROR_SUCCESS; |
|
628 } |
|
629 } while (Process32NextW(snapshot, &processEntry)); |
|
630 CloseHandle(snapshot); |
|
631 return ERROR_NOT_FOUND; |
|
632 } |
|
633 |
|
634 /** |
|
635 * Waits for the specified applicaiton to exit. |
|
636 * |
|
637 * @param filename The application to wait for. |
|
638 * @param maxSeconds The maximum amount of seconds to wait for all |
|
639 * instances of the application to exit. |
|
640 * @return ERROR_SUCCESS if no instances of the application exist |
|
641 * WAIT_TIMEOUT if the process is still running after maxSeconds. |
|
642 * Any other Win32 system error code. |
|
643 */ |
|
644 DWORD |
|
645 WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds) |
|
646 { |
|
647 DWORD applicationRunningError = WAIT_TIMEOUT; |
|
648 for(DWORD i = 0; i < maxSeconds; i++) { |
|
649 DWORD applicationRunningError = IsProcessRunning(filename); |
|
650 if (ERROR_NOT_FOUND == applicationRunningError) { |
|
651 return ERROR_SUCCESS; |
|
652 } |
|
653 Sleep(1000); |
|
654 } |
|
655 |
|
656 if (ERROR_SUCCESS == applicationRunningError) { |
|
657 return WAIT_TIMEOUT; |
|
658 } |
|
659 |
|
660 return applicationRunningError; |
|
661 } |
|
662 |
|
663 /** |
|
664 * Determines if the fallback key exists or not |
|
665 * |
|
666 * @return TRUE if the fallback key exists and there was no error checking |
|
667 */ |
|
668 BOOL |
|
669 DoesFallbackKeyExist() |
|
670 { |
|
671 HKEY testOnlyFallbackKey; |
|
672 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, |
|
673 TEST_ONLY_FALLBACK_KEY_PATH, 0, |
|
674 KEY_READ | KEY_WOW64_64KEY, |
|
675 &testOnlyFallbackKey) != ERROR_SUCCESS) { |
|
676 return FALSE; |
|
677 } |
|
678 |
|
679 RegCloseKey(testOnlyFallbackKey); |
|
680 return TRUE; |
|
681 } |
|
682 |
|
683 #endif |
|
684 |
|
685 /** |
|
686 * Determines if the file system for the specified file handle is local |
|
687 * @param file path to check the filesystem type for, must be at most MAX_PATH |
|
688 * @param isLocal out parameter which will hold TRUE if the drive is local |
|
689 * @return TRUE if the call succeeded |
|
690 */ |
|
691 BOOL |
|
692 IsLocalFile(LPCWSTR file, BOOL &isLocal) |
|
693 { |
|
694 WCHAR rootPath[MAX_PATH + 1] = { L'\0' }; |
|
695 if (wcslen(file) > MAX_PATH) { |
|
696 return FALSE; |
|
697 } |
|
698 |
|
699 wcsncpy(rootPath, file, MAX_PATH); |
|
700 PathStripToRootW(rootPath); |
|
701 isLocal = GetDriveTypeW(rootPath) == DRIVE_FIXED; |
|
702 return TRUE; |
|
703 } |
|
704 |
|
705 |
|
706 /** |
|
707 * Determines the DWORD value of a registry key value |
|
708 * |
|
709 * @param key The base key to where the value name exists |
|
710 * @param valueName The name of the value |
|
711 * @param retValue Out parameter which will hold the value |
|
712 * @return TRUE on success |
|
713 */ |
|
714 static BOOL |
|
715 GetDWORDValue(HKEY key, LPCWSTR valueName, DWORD &retValue) |
|
716 { |
|
717 DWORD regDWORDValueSize = sizeof(DWORD); |
|
718 LONG retCode = RegQueryValueExW(key, valueName, 0, nullptr, |
|
719 reinterpret_cast<LPBYTE>(&retValue), |
|
720 ®DWORDValueSize); |
|
721 return ERROR_SUCCESS == retCode; |
|
722 } |
|
723 |
|
724 /** |
|
725 * Determines if the the system's elevation type allows |
|
726 * unprmopted elevation. |
|
727 * |
|
728 * @param isUnpromptedElevation Out parameter which specifies if unprompted |
|
729 * elevation is allowed. |
|
730 * @return TRUE if the user can actually elevate and the value was obtained |
|
731 * successfully. |
|
732 */ |
|
733 BOOL |
|
734 IsUnpromptedElevation(BOOL &isUnpromptedElevation) |
|
735 { |
|
736 if (!UACHelper::CanUserElevate()) { |
|
737 return FALSE; |
|
738 } |
|
739 |
|
740 LPCWSTR UACBaseRegKey = |
|
741 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"; |
|
742 HKEY baseKey; |
|
743 LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, |
|
744 UACBaseRegKey, 0, |
|
745 KEY_READ, &baseKey); |
|
746 if (retCode != ERROR_SUCCESS) { |
|
747 return FALSE; |
|
748 } |
|
749 |
|
750 DWORD consent, secureDesktop; |
|
751 BOOL success = GetDWORDValue(baseKey, L"ConsentPromptBehaviorAdmin", |
|
752 consent); |
|
753 success = success && |
|
754 GetDWORDValue(baseKey, L"PromptOnSecureDesktop", secureDesktop); |
|
755 isUnpromptedElevation = !consent && !secureDesktop; |
|
756 |
|
757 RegCloseKey(baseKey); |
|
758 return success; |
|
759 } |
|
760 |
|
761 #ifdef MOZ_METRO |
|
762 /* |
|
763 * Retrieve the app model id of the firefox metro browser. |
|
764 * |
|
765 * @aPathBuffer Buffer to fill |
|
766 * @aCharLength Length of buffer to fill in characters |
|
767 */ |
|
768 bool GetDefaultBrowserAppModelID(WCHAR* aIDBuffer, long aCharLength) |
|
769 { |
|
770 if (!aIDBuffer || aCharLength <= 0) |
|
771 return false; |
|
772 |
|
773 memset(aIDBuffer, 0, (sizeof(WCHAR)*aCharLength)); |
|
774 static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL"; |
|
775 |
|
776 HKEY key; |
|
777 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey, |
|
778 0, KEY_READ, &key) != ERROR_SUCCESS) { |
|
779 return false; |
|
780 } |
|
781 DWORD len = aCharLength * sizeof(WCHAR); |
|
782 memset(aIDBuffer, 0, len); |
|
783 if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr, |
|
784 (LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) { |
|
785 RegCloseKey(key); |
|
786 return false; |
|
787 } |
|
788 RegCloseKey(key); |
|
789 return true; |
|
790 } |
|
791 |
|
792 HRESULT |
|
793 LaunchDefaultMetroBrowser() |
|
794 { |
|
795 CoInitialize(nullptr); |
|
796 HRESULT hr = E_FAIL; |
|
797 // The interface that allows us to activate the browser |
|
798 IApplicationActivationManager *activateMgr; |
|
799 if (FAILED(hr = CoCreateInstance(CLSID_ApplicationActivationManager, |
|
800 nullptr, CLSCTX_LOCAL_SERVER, |
|
801 IID_IApplicationActivationManager, |
|
802 (void**)&activateMgr))) { |
|
803 CoUninitialize(); |
|
804 return hr; |
|
805 } |
|
806 |
|
807 // Activation is based on the browser's registered app model id |
|
808 WCHAR appModelID[256]; |
|
809 if (!GetDefaultBrowserAppModelID(appModelID, (sizeof(appModelID)/sizeof(WCHAR)))) { |
|
810 activateMgr->Release(); |
|
811 CoUninitialize(); |
|
812 return hr; |
|
813 } |
|
814 |
|
815 // Hand off focus rights to the out-of-process activation server. Without |
|
816 // this the metro interface won't launch. |
|
817 CoAllowSetForegroundWindow(activateMgr, nullptr); |
|
818 |
|
819 // Launch default browser in Metro |
|
820 DWORD processID; |
|
821 hr = activateMgr->ActivateApplication(appModelID, L"", AO_NOERRORUI, |
|
822 &processID); |
|
823 activateMgr->Release(); |
|
824 CoUninitialize(); |
|
825 return hr; |
|
826 } |
|
827 #endif |