Bug 1838629 - Part 7: Inline nursery cell allocation method in Allocator.h r=sfink
[gecko.git] / toolkit / xre / nsUpdateDriver.cpp
blobc355741c76c973ccc2c81a33a864079558f66dc5
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include "nsUpdateDriver.h"
11 #include "nsDebug.h"
12 #include "nsXULAppAPI.h"
13 #include "nsAppRunner.h"
14 #include "nsIFile.h"
15 #include "nsVariant.h"
16 #include "nsCOMPtr.h"
17 #include "nsString.h"
18 #include "prproces.h"
19 #include "mozilla/Logging.h"
20 #include "prenv.h"
21 #include "nsVersionComparator.h"
22 #include "nsDirectoryServiceDefs.h"
23 #include "nsThreadUtils.h"
24 #include "nsIXULAppInfo.h"
25 #include "mozilla/Preferences.h"
26 #include "nsPrintfCString.h"
27 #include "mozilla/DebugOnly.h"
28 #include "mozilla/ErrorNames.h"
29 #include "mozilla/Printf.h"
30 #include "mozilla/UniquePtr.h"
31 #include "nsIObserverService.h"
32 #include "nsNetCID.h"
33 #include "mozilla/ScopeExit.h"
34 #include "mozilla/Services.h"
35 #include "mozilla/dom/Promise.h"
37 #ifdef XP_MACOSX
38 # include "nsILocalFileMac.h"
39 # include "nsCommandLineServiceMac.h"
40 # include "MacLaunchHelper.h"
41 # include "updaterfileutils_osx.h"
42 # include "mozilla/Monitor.h"
43 # include "gfxPlatformMac.h"
44 #endif
46 #if defined(XP_WIN)
47 # include <direct.h>
48 # include <process.h>
49 # include <windows.h>
50 # include <shlwapi.h>
51 # include <strsafe.h>
52 # include "commonupdatedir.h"
53 # include "nsWindowsHelpers.h"
54 # include "pathhash.h"
55 # include "WinUtils.h"
56 # define getcwd(path, size) _getcwd(path, size)
57 # define getpid() GetCurrentProcessId()
58 #elif defined(XP_UNIX)
59 # include <unistd.h>
60 # include <sys/wait.h>
61 #endif
63 using namespace mozilla;
65 static LazyLogModule sUpdateLog("updatedriver");
66 // Some other file in our unified batch might have defined LOG already.
67 #ifdef LOG
68 # undef LOG
69 #endif
70 #define LOG(args) MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug, args)
72 #ifdef XP_MACOSX
73 static void UpdateDriverSetupMacCommandLine(int& argc, char**& argv,
74 bool restart) {
75 if (NS_IsMainThread()) {
76 CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart);
77 return;
79 // Bug 1335916: SetupMacCommandLine calls a CoreFoundation function that
80 // asserts that it was called from the main thread, so if we are not the main
81 // thread, we have to dispatch that call to there. But we also have to get the
82 // result from it, so we can't just dispatch and return, we have to wait
83 // until the dispatched operation actually completes. So we also set up a
84 // monitor to signal us when that happens, and block until then.
85 Monitor monitor MOZ_UNANNOTATED("nsUpdateDriver SetupMacCommandLine");
87 nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
88 "UpdateDriverSetupMacCommandLine",
89 [&argc, &argv, restart, &monitor]() -> void {
90 CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart);
91 MonitorAutoLock(monitor).Notify();
92 }));
94 if (NS_FAILED(rv)) {
95 LOG(
96 ("Update driver error dispatching SetupMacCommandLine to main thread: "
97 "%d\n",
98 rv));
99 return;
102 // The length of this wait is arbitrary, but should be long enough that having
103 // it expire means something is seriously wrong.
104 CVStatus status =
105 MonitorAutoLock(monitor).Wait(TimeDuration::FromSeconds(60));
106 if (status == CVStatus::Timeout) {
107 LOG(("Update driver timed out waiting for SetupMacCommandLine\n"));
110 #endif
112 static nsresult GetCurrentWorkingDir(nsACString& aOutPath) {
113 // Cannot use NS_GetSpecialDirectory because XPCOM is not yet initialized.
114 // This code is duplicated from xpcom/io/SpecialSystemDirectory.cpp:
116 aOutPath.Truncate();
118 #if defined(XP_WIN)
119 wchar_t wpath[MAX_PATH];
120 if (!_wgetcwd(wpath, ArrayLength(wpath))) {
121 return NS_ERROR_FAILURE;
123 CopyUTF16toUTF8(nsDependentString(wpath), aOutPath);
124 #else
125 char path[MAXPATHLEN];
126 if (!getcwd(path, ArrayLength(path))) {
127 return NS_ERROR_FAILURE;
129 aOutPath = path;
130 #endif
132 return NS_OK;
136 * Get the path to the installation directory. For Mac OS X this will be the
137 * bundle directory.
139 * @param appDir the application directory file object
140 * @param installDirPath the path to the installation directory
142 static nsresult GetInstallDirPath(nsIFile* appDir, nsACString& installDirPath) {
143 nsresult rv;
144 #ifdef XP_MACOSX
145 nsCOMPtr<nsIFile> parentDir1, parentDir2;
146 rv = appDir->GetParent(getter_AddRefs(parentDir1));
147 NS_ENSURE_SUCCESS(rv, rv);
148 rv = parentDir1->GetParent(getter_AddRefs(parentDir2));
149 NS_ENSURE_SUCCESS(rv, rv);
150 rv = parentDir2->GetNativePath(installDirPath);
151 NS_ENSURE_SUCCESS(rv, rv);
152 #elif XP_WIN
153 nsAutoString installDirPathW;
154 rv = appDir->GetPath(installDirPathW);
155 NS_ENSURE_SUCCESS(rv, rv);
156 CopyUTF16toUTF8(installDirPathW, installDirPath);
157 #else
158 rv = appDir->GetNativePath(installDirPath);
159 NS_ENSURE_SUCCESS(rv, rv);
160 #endif
161 return NS_OK;
164 static bool GetFile(nsIFile* dir, const nsACString& name,
165 nsCOMPtr<nsIFile>& result) {
166 nsresult rv;
168 nsCOMPtr<nsIFile> file;
169 rv = dir->Clone(getter_AddRefs(file));
170 if (NS_FAILED(rv)) {
171 return false;
174 rv = file->AppendNative(name);
175 if (NS_FAILED(rv)) {
176 return false;
179 result = file;
180 return true;
183 static bool GetStatusFile(nsIFile* dir, nsCOMPtr<nsIFile>& result) {
184 return GetFile(dir, "update.status"_ns, result);
188 * Get the contents of the update.status file when the update.status file can
189 * be opened with read and write access. The reason it is opened for both read
190 * and write is to prevent trying to update when the user doesn't have write
191 * access to the update directory.
193 * @param statusFile the status file object.
194 * @param buf the buffer holding the file contents
196 * @return true if successful, false otherwise.
198 template <size_t Size>
199 static bool GetStatusFileContents(nsIFile* statusFile, char (&buf)[Size]) {
200 static_assert(
201 Size > 16,
202 "Buffer needs to be large enough to hold the known status codes");
204 PRFileDesc* fd = nullptr;
205 nsresult rv = statusFile->OpenNSPRFileDesc(PR_RDWR, 0660, &fd);
206 if (NS_FAILED(rv)) {
207 return false;
210 const int32_t n = PR_Read(fd, buf, Size);
211 PR_Close(fd);
213 return (n >= 0);
216 enum UpdateStatus {
217 eNoUpdateAction,
218 ePendingUpdate,
219 ePendingService,
220 ePendingElevate,
221 eAppliedUpdate,
222 eAppliedService,
226 * Returns a value indicating what needs to be done in order to handle an
227 * update.
229 * @param dir the directory in which we should look for an update.status file.
230 * @param statusFile the update.status file found in the directory.
232 * @return the update action to be performed.
234 static UpdateStatus GetUpdateStatus(nsIFile* dir,
235 nsCOMPtr<nsIFile>& statusFile) {
236 if (GetStatusFile(dir, statusFile)) {
237 char buf[32];
238 if (GetStatusFileContents(statusFile, buf)) {
239 const char kPending[] = "pending";
240 const char kPendingService[] = "pending-service";
241 const char kPendingElevate[] = "pending-elevate";
242 const char kApplied[] = "applied";
243 const char kAppliedService[] = "applied-service";
244 if (!strncmp(buf, kPendingElevate, sizeof(kPendingElevate) - 1)) {
245 return ePendingElevate;
247 if (!strncmp(buf, kPendingService, sizeof(kPendingService) - 1)) {
248 return ePendingService;
250 if (!strncmp(buf, kPending, sizeof(kPending) - 1)) {
251 return ePendingUpdate;
253 if (!strncmp(buf, kAppliedService, sizeof(kAppliedService) - 1)) {
254 return eAppliedService;
256 if (!strncmp(buf, kApplied, sizeof(kApplied) - 1)) {
257 return eAppliedUpdate;
261 return eNoUpdateAction;
264 static bool GetVersionFile(nsIFile* dir, nsCOMPtr<nsIFile>& result) {
265 return GetFile(dir, "update.version"_ns, result);
268 // Compares the current application version with the update's application
269 // version.
270 static bool IsOlderVersion(nsIFile* versionFile, const char* appVersion) {
271 PRFileDesc* fd = nullptr;
272 nsresult rv = versionFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd);
273 if (NS_FAILED(rv)) {
274 return true;
277 char buf[32];
278 const int32_t n = PR_Read(fd, buf, sizeof(buf));
279 PR_Close(fd);
281 if (n < 0) {
282 return false;
285 // Trim off the trailing newline
286 if (buf[n - 1] == '\n') {
287 buf[n - 1] = '\0';
290 // If the update xml doesn't provide the application version the file will
291 // contain the string "null" and it is assumed that the update is not older.
292 const char kNull[] = "null";
293 if (strncmp(buf, kNull, sizeof(kNull) - 1) == 0) {
294 return false;
297 return mozilla::Version(appVersion) > buf;
301 * Applies, switches, or stages an update.
303 * @param greDir the GRE directory
304 * @param updateDir the update root directory
305 * @param appDir the application directory
306 * @param appArgc the number of args passed to the application
307 * @param appArgv the args passed to the application
308 * (used for restarting the application when necessary)
309 * @param restart true when a restart is necessary.
310 * @param isStaged true when the update has already been staged
311 * @param outpid (out) parameter holding the handle to the updater application
312 * when staging updates
314 static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir,
315 int appArgc, char** appArgv, bool restart,
316 bool isStaged, ProcessType* outpid) {
317 // The following determines the update operation to perform.
318 // 1. When restart is false the update will be staged.
319 // 2. When restart is true and isStaged is false the update will apply the mar
320 // file to the installation directory.
321 // 3. When restart is true and isStaged is true the update will switch the
322 // staged update with the installation directory.
324 nsresult rv;
326 nsCOMPtr<nsIFile> updater;
327 nsAutoCString updaterPath;
328 nsAutoCString updateDirPath;
329 #if defined(XP_WIN)
330 // Get an nsIFile reference for the updater in the installation dir.
331 if (!GetFile(greDir, nsLiteralCString(UPDATER_BIN), updater)) {
332 return;
335 // Get the path to the updater.
336 nsAutoString updaterPathW;
337 rv = updater->GetPath(updaterPathW);
338 if (NS_FAILED(rv)) {
339 return;
341 CopyUTF16toUTF8(updaterPathW, updaterPath);
343 // Get the path to the update dir.
344 nsAutoString updateDirPathW;
345 rv = updateDir->GetPath(updateDirPathW);
346 if (NS_FAILED(rv)) {
347 return;
349 CopyUTF16toUTF8(updateDirPathW, updateDirPath);
350 #elif defined(XP_MACOSX)
351 // Get an nsIFile reference for the updater in the installation dir.
352 if (!GetFile(appDir, nsLiteralCString(UPDATER_APP), updater)) {
353 return;
355 rv = updater->AppendNative("Contents"_ns);
356 if (NS_FAILED(rv)) {
357 return;
359 rv = updater->AppendNative("MacOS"_ns);
360 if (NS_FAILED(rv)) {
361 return;
363 rv = updater->AppendNative(nsLiteralCString(UPDATER_BIN));
364 if (NS_FAILED(rv)) {
365 return;
368 // Get the path to the updater.
369 rv = updater->GetNativePath(updaterPath);
370 if (NS_FAILED(rv)) {
371 return;
374 // Get the path to the update dir.
375 rv = updateDir->GetNativePath(updateDirPath);
376 if (NS_FAILED(rv)) {
377 return;
379 #else
380 // Get an nsIFile reference for the updater in the installation dir.
381 if (!GetFile(greDir, nsLiteralCString(UPDATER_BIN), updater)) {
382 return;
385 // Get the path to the updater.
386 rv = updater->GetNativePath(updaterPath);
387 if (NS_FAILED(rv)) {
388 return;
391 // Get the path to the update dir.
392 rv = updateDir->GetNativePath(updateDirPath);
393 if (NS_FAILED(rv)) {
394 return;
396 #endif
398 // appFilePath and workingDirPath are only used when the application will be
399 // restarted.
400 nsAutoCString appFilePath;
401 nsAutoCString workingDirPath;
402 if (restart) {
403 // Get the path to the current working directory.
404 rv = GetCurrentWorkingDir(workingDirPath);
405 if (NS_FAILED(rv)) {
406 return;
409 // Get the application file path used by the updater to restart the
410 // application after the update has finished.
411 nsCOMPtr<nsIFile> appFile;
412 XRE_GetBinaryPath(getter_AddRefs(appFile));
413 if (!appFile) {
414 return;
417 #if defined(XP_WIN)
418 nsAutoString appFilePathW;
419 rv = appFile->GetPath(appFilePathW);
420 if (NS_FAILED(rv)) {
421 return;
423 CopyUTF16toUTF8(appFilePathW, appFilePath);
424 #else
425 rv = appFile->GetNativePath(appFilePath);
426 if (NS_FAILED(rv)) {
427 return;
429 #endif
432 // Get the installation directory path.
433 nsAutoCString installDirPath;
434 rv = GetInstallDirPath(appDir, installDirPath);
435 if (NS_FAILED(rv)) {
436 return;
439 #if defined(XP_MACOSX)
440 // If we're going to do a restart, we need to make sure the font registration
441 // thread has finished before this process exits (bug 1777332).
442 if (restart) {
443 gfxPlatformMac::WaitForFontRegistration();
446 // We need to detect whether elevation is required for this update. This can
447 // occur when an admin user installs the application, but another admin
448 // user attempts to update (see bug 394984).
449 // We only check if we need elevation if we are restarting. We don't attempt
450 // to stage if elevation is required. Staging happens without the user knowing
451 // about it, and we don't want to ask for elevation for seemingly no reason.
452 bool needElevation = false;
453 if (restart) {
454 needElevation = !IsRecursivelyWritable(installDirPath.get());
455 if (needElevation) {
456 // Normally we would check this via nsIAppStartup::wasSilentlyStarted,
457 // but nsIAppStartup isn't available yet.
458 char* mozAppSilentStart = PR_GetEnv("MOZ_APP_SILENT_START");
459 bool wasSilentlyStarted =
460 mozAppSilentStart && (strcmp(mozAppSilentStart, "") != 0);
461 if (wasSilentlyStarted) {
462 // Elevation always requires prompting for credentials on macOS. If we
463 // are trying to restart silently, we must not display UI such as this
464 // prompt.
465 // We make this check here rather than in the updater, because it is
466 // actually Firefox that shows the elevation prompt (via
467 // InstallPrivilegedHelper), not the updater.
468 return;
472 #endif
474 nsAutoCString applyToDirPath;
475 nsCOMPtr<nsIFile> updatedDir;
476 if (restart && !isStaged) {
477 // The install directory is the same as the apply to directory.
478 applyToDirPath.Assign(installDirPath);
479 } else {
480 // Get the directory where the update is staged or will be staged. This is
481 // `updateDir` for macOS and `appDir` for all other platforms. macOS cannot
482 // stage updates inside the .app bundle (`appDir`) without breaking the code
483 // signature on the bundle, so we use `updateDir` instead.
484 #if defined(XP_MACOSX)
485 if (!GetFile(updateDir, "Updated.app"_ns, updatedDir)) {
486 #else
487 if (!GetFile(appDir, "updated"_ns, updatedDir)) {
488 #endif
489 return;
491 #if defined(XP_WIN)
492 nsAutoString applyToDirPathW;
493 rv = updatedDir->GetPath(applyToDirPathW);
494 if (NS_FAILED(rv)) {
495 return;
497 CopyUTF16toUTF8(applyToDirPathW, applyToDirPath);
498 #else
499 rv = updatedDir->GetNativePath(applyToDirPath);
500 #endif
502 if (NS_FAILED(rv)) {
503 return;
506 if (restart && isStaged) {
507 // When the update should already be staged make sure that the updated
508 // directory exists.
509 bool updatedDirExists = false;
510 if (NS_FAILED(updatedDir->Exists(&updatedDirExists)) || !updatedDirExists) {
511 return;
515 // On platforms where we are not calling execv, we may need to make the
516 // updater executable wait for the calling process to exit. Otherwise, the
517 // updater may have trouble modifying our executable image (because it might
518 // still be in use). This is accomplished by passing our PID to the updater
519 // so that it can wait for us to exit. This is not perfect as there is a race
520 // condition that could bite us. It's possible that the calling process could
521 // exit before the updater waits on the specified PID, and in the meantime a
522 // new process with the same PID could be created. This situation is
523 // unlikely, however, given the way most operating systems recycle PIDs. We'll
524 // take our chances ;-) Construct the PID argument for this process to pass to
525 // the updater.
526 nsAutoCString pid;
527 if (restart) {
528 #if defined(XP_UNIX) & !defined(XP_MACOSX)
529 // When execv is used for an update that requires a restart 0 is passed
530 // which is ignored by the updater.
531 pid.AssignLiteral("0");
532 #else
533 pid.AppendInt((int32_t)getpid());
534 #endif
535 if (isStaged) {
536 // Append a special token to the PID in order to inform the updater that
537 // it should replace install with the updated directory.
538 pid.AppendLiteral("/replace");
540 } else {
541 // Signal the updater application that it should stage the update.
542 pid.AssignLiteral("-1");
545 int argc = 5;
546 if (restart) {
547 argc = appArgc + 6;
548 if (gRestartedByOS) {
549 argc += 1;
552 char** argv = static_cast<char**>(malloc((argc + 1) * sizeof(char*)));
553 if (!argv) {
554 return;
556 argv[0] = (char*)updaterPath.get();
557 argv[1] = (char*)updateDirPath.get();
558 argv[2] = (char*)installDirPath.get();
559 argv[3] = (char*)applyToDirPath.get();
560 argv[4] = (char*)pid.get();
561 if (restart && appArgc) {
562 argv[5] = (char*)workingDirPath.get();
563 argv[6] = (char*)appFilePath.get();
564 for (int i = 1; i < appArgc; ++i) {
565 argv[6 + i] = appArgv[i];
567 if (gRestartedByOS) {
568 // We haven't truly started up, restore this argument so that we will have
569 // it upon restart.
570 argv[6 + appArgc] = const_cast<char*>("-os-restarted");
573 argv[argc] = nullptr;
575 if (restart && gSafeMode) {
576 PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
579 LOG(("spawning updater process [%s]\n", updaterPath.get()));
581 #if defined(XP_UNIX) && !defined(XP_MACOSX)
582 // We use execv to spawn the updater process on all UNIX systems except Mac
583 // OSX since it is known to cause problems on the Mac. Windows has execv, but
584 // it is a faked implementation that doesn't really replace the current
585 // process. Instead it spawns a new process, so we gain nothing from using
586 // execv on Windows.
587 if (restart) {
588 int execResult = execv(updaterPath.get(), argv);
589 free(argv);
590 exit(execResult);
592 *outpid = fork();
593 if (*outpid == -1) {
594 free(argv);
595 return;
597 if (*outpid == 0) {
598 int execResult = execv(updaterPath.get(), argv);
599 free(argv);
600 exit(execResult);
602 #elif defined(XP_WIN)
603 if (isStaged) {
604 // Launch the updater to replace the installation with the staged updated.
605 if (!WinLaunchChild(updaterPathW.get(), argc, argv)) {
606 free(argv);
607 return;
609 } else {
610 // Launch the updater to either stage or apply an update.
611 if (!WinLaunchChild(updaterPathW.get(), argc, argv, nullptr, outpid)) {
612 free(argv);
613 return;
616 #elif defined(XP_MACOSX)
617 UpdateDriverSetupMacCommandLine(argc, argv, restart);
618 if (restart && needElevation) {
619 bool hasLaunched = LaunchElevatedUpdate(argc, argv, outpid);
620 free(argv);
621 if (!hasLaunched) {
622 LOG(("Failed to launch elevated update!"));
623 exit(1);
625 exit(0);
628 if (isStaged) {
629 // Launch the updater to replace the installation with the staged updated.
630 LaunchChildMac(argc, argv);
631 } else {
632 // Launch the updater to either stage or apply an update.
633 LaunchChildMac(argc, argv, outpid);
635 #else
636 if (isStaged) {
637 // Launch the updater to replace the installation with the staged updated.
638 PR_CreateProcessDetached(updaterPath.get(), argv, nullptr, nullptr);
639 } else {
640 // Launch the updater to either stage or apply an update.
641 *outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr);
643 #endif
644 free(argv);
645 if (restart) {
646 exit(0);
650 #if !defined(XP_WIN)
652 * Wait briefly to see if a process terminates, then return true if it has.
654 * (Not implemented on Windows, where HandleWatcher is used instead.)
656 static bool ProcessHasTerminated(ProcessType pt) {
657 # if defined(XP_MACOSX)
658 // We're waiting for the process to terminate in LaunchChildMac.
659 return true;
660 # elif defined(XP_UNIX)
661 int exitStatus;
662 pid_t exited = waitpid(pt, &exitStatus, WNOHANG);
663 if (exited == 0) {
664 // Process is still running.
665 sleep(1);
666 return false;
668 if (exited == -1) {
669 LOG(("Error while checking if the updater process is finished"));
670 // This shouldn't happen, but if it does, the updater process is lost to us,
671 // so the best we can do is pretend that it's exited.
672 return true;
674 // If we get here, the process has exited; make sure it exited normally.
675 if (WIFEXITED(exitStatus) && (WEXITSTATUS(exitStatus) != 0)) {
676 LOG(("Error while running the updater process, check update.log"));
678 return true;
679 # else
680 // No way to have a non-blocking implementation on these platforms,
681 // because we're using NSPR and it only provides a blocking wait.
682 int32_t exitCode;
683 PR_WaitProcess(pt, &exitCode);
684 if (exitCode != 0) {
685 LOG(("Error while running the updater process, check update.log"));
687 return true;
688 # endif
690 #endif
692 nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir,
693 int argc, char** argv, const char* appVersion,
694 bool restart, ProcessType* pid) {
695 nsresult rv;
697 #ifdef XP_WIN
698 // If we're in a package, we know any updates that we find are not for us.
699 if (mozilla::widget::WinUtils::HasPackageIdentity()) {
700 return NS_OK;
702 #endif
704 nsCOMPtr<nsIFile> updatesDir;
705 rv = updRootDir->Clone(getter_AddRefs(updatesDir));
706 NS_ENSURE_SUCCESS(rv, rv);
707 rv = updatesDir->AppendNative("updates"_ns);
708 NS_ENSURE_SUCCESS(rv, rv);
709 rv = updatesDir->AppendNative("0"_ns);
710 NS_ENSURE_SUCCESS(rv, rv);
712 // Return early since there isn't a valid update when the update application
713 // version file doesn't exist or if the update's application version is less
714 // than the current application version. The cleanup of the update will happen
715 // during post update processing in nsUpdateService.js.
716 nsCOMPtr<nsIFile> versionFile;
717 if (!GetVersionFile(updatesDir, versionFile) ||
718 IsOlderVersion(versionFile, appVersion)) {
719 return NS_OK;
722 nsCOMPtr<nsIFile> statusFile;
723 UpdateStatus status = GetUpdateStatus(updatesDir, statusFile);
724 switch (status) {
725 case ePendingUpdate:
726 case ePendingService: {
727 ApplyUpdate(greDir, updatesDir, appDir, argc, argv, restart, false, pid);
728 break;
730 case eAppliedUpdate:
731 case eAppliedService:
732 // An update was staged and needs to be switched so the updated
733 // application is used.
734 ApplyUpdate(greDir, updatesDir, appDir, argc, argv, restart, true, pid);
735 break;
736 case ePendingElevate:
737 // No action should be performed since the user hasn't opted into
738 // elevating for the update so continue application startup.
739 case eNoUpdateAction:
740 // We don't need to do any special processing here, we'll just continue to
741 // startup the application.
742 break;
745 return NS_OK;
748 NS_IMPL_ISUPPORTS(nsUpdateProcessor, nsIUpdateProcessor)
750 nsUpdateProcessor::nsUpdateProcessor() : mUpdaterPID(0) {}
752 #ifdef XP_WIN
753 nsUpdateProcessor::~nsUpdateProcessor() { mProcessWatcher.Stop(); }
754 #else
755 nsUpdateProcessor::~nsUpdateProcessor() = default;
756 #endif
758 NS_IMETHODIMP
759 nsUpdateProcessor::ProcessUpdate() {
760 nsresult rv;
762 nsCOMPtr<nsIProperties> ds =
763 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
764 NS_ENSURE_SUCCESS(rv, rv);
766 nsCOMPtr<nsIFile> exeFile;
767 rv = ds->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
768 getter_AddRefs(exeFile));
769 NS_ENSURE_SUCCESS(rv, rv);
771 nsCOMPtr<nsIFile> appDir;
772 rv = exeFile->GetParent(getter_AddRefs(appDir));
773 NS_ENSURE_SUCCESS(rv, rv);
775 nsCOMPtr<nsIFile> greDir;
776 rv = ds->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), getter_AddRefs(greDir));
777 NS_ENSURE_SUCCESS(rv, rv);
779 nsCOMPtr<nsIFile> updRoot;
780 rv = ds->Get(XRE_UPDATE_ROOT_DIR, NS_GET_IID(nsIFile),
781 getter_AddRefs(updRoot));
782 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the UpdRootD dir");
784 // XRE_UPDATE_ROOT_DIR should not fail but if it does fallback to the
785 // application directory just to be safe.
786 if (NS_FAILED(rv)) {
787 rv = appDir->Clone(getter_AddRefs(updRoot));
788 NS_ENSURE_SUCCESS(rv, rv);
791 nsCOMPtr<nsIXULAppInfo> appInfo =
792 do_GetService("@mozilla.org/xre/app-info;1", &rv);
793 NS_ENSURE_SUCCESS(rv, rv);
795 nsAutoCString appVersion;
796 rv = appInfo->GetVersion(appVersion);
797 NS_ENSURE_SUCCESS(rv, rv);
799 // Copy the parameters to the StagedUpdateInfo structure shared with the
800 // worker thread.
801 mInfo.mGREDir = greDir;
802 mInfo.mAppDir = appDir;
803 mInfo.mUpdateRoot = updRoot;
804 mInfo.mArgc = 0;
805 mInfo.mArgv = nullptr;
806 mInfo.mAppVersion = appVersion;
808 MOZ_ASSERT(NS_IsMainThread(), "not main thread");
809 nsCOMPtr<nsIRunnable> r =
810 NewRunnableMethod("nsUpdateProcessor::StartStagedUpdate", this,
811 &nsUpdateProcessor::StartStagedUpdate);
812 return NS_NewNamedThread("UpdateProcessor", getter_AddRefs(mWorkerThread), r);
815 void nsUpdateProcessor::StartStagedUpdate() {
816 MOZ_ASSERT(!NS_IsMainThread(), "main thread");
818 // If we fail to launch the updater process or its monitor for some reason, we
819 // need to shut down the worker thread, as there isn't anything more for us to
820 // do.
821 auto onExitStopThread = mozilla::MakeScopeExit([&] {
822 nsresult rv = NS_DispatchToMainThread(
823 NewRunnableMethod("nsUpdateProcessor::ShutdownWorkerThread", this,
824 &nsUpdateProcessor::ShutdownWorkerThread));
825 NS_ENSURE_SUCCESS_VOID(rv);
828 // Launch updater. (We do this on a worker thread to avoid blocking the main
829 // thread with file I/O.)
830 nsresult rv = ProcessUpdates(mInfo.mGREDir, mInfo.mAppDir, mInfo.mUpdateRoot,
831 mInfo.mArgc, mInfo.mArgv,
832 mInfo.mAppVersion.get(), false, &mUpdaterPID);
833 if (NS_FAILED(rv)) {
834 MOZ_LOG(sUpdateLog, mozilla::LogLevel::Error,
835 ("could not start updater process: %s", GetStaticErrorName(rv)));
836 return;
839 if (!mUpdaterPID) {
840 // not an error
841 MOZ_LOG(sUpdateLog, mozilla::LogLevel::Verbose,
842 ("ProcessUpdates() indicated nothing to do"));
843 return;
846 #ifdef WIN32
847 // Set up a HandleWatcher to report to the main thread when we're done.
848 RefPtr<nsIThread> mainThread;
849 NS_GetMainThread(getter_AddRefs(mainThread));
850 mProcessWatcher.Watch(mUpdaterPID, mainThread,
851 NewRunnableMethod("nsUpdateProcessor::UpdateDone", this,
852 &nsUpdateProcessor::UpdateDone));
854 // On Windows, that's all we need the worker thread for. Let
855 // `onExitStopThread` shut us down.
856 #else
857 // Monitor the state of the updater process while it is staging an update.
858 rv = NS_DispatchToCurrentThread(
859 NewRunnableMethod("nsUpdateProcessor::WaitForProcess", this,
860 &nsUpdateProcessor::WaitForProcess));
861 if (NS_FAILED(rv)) {
862 MOZ_LOG(sUpdateLog, mozilla::LogLevel::Error,
863 ("could not start updater process poll: error %s",
864 GetStaticErrorName(rv)));
865 return;
868 // Leave the worker thread alive to run WaitForProcess. Either it or its
869 // successors will be responsible for shutting down the worker thread.
870 onExitStopThread.release();
871 #endif
874 void nsUpdateProcessor::ShutdownWorkerThread() {
875 MOZ_ASSERT(NS_IsMainThread(), "not main thread");
876 mWorkerThread->Shutdown();
877 mWorkerThread = nullptr;
880 #ifndef WIN32
881 void nsUpdateProcessor::WaitForProcess() {
882 MOZ_ASSERT(!NS_IsMainThread(), "main thread");
883 if (ProcessHasTerminated(mUpdaterPID)) {
884 NS_DispatchToMainThread(NewRunnableMethod(
885 "nsUpdateProcessor::UpdateDone", this, &nsUpdateProcessor::UpdateDone));
886 } else {
887 NS_DispatchToCurrentThread(
888 NewRunnableMethod("nsUpdateProcessor::WaitForProcess", this,
889 &nsUpdateProcessor::WaitForProcess));
892 #endif
894 void nsUpdateProcessor::UpdateDone() {
895 MOZ_ASSERT(NS_IsMainThread(), "not main thread");
897 nsCOMPtr<nsIUpdateManager> um =
898 do_GetService("@mozilla.org/updates/update-manager;1");
899 if (um) {
900 // This completes asynchronously, but nothing else that we are doing in this
901 // function requires waiting for this to complete.
902 RefPtr<mozilla::dom::Promise> outPromise;
903 um->RefreshUpdateStatus(getter_AddRefs(outPromise));
906 // On Windows, shutting down the worker thread is taken care of by another task.
907 // (Which may not have run yet, so we can't assert.)
908 #ifndef XP_WIN
909 ShutdownWorkerThread();
910 #endif
913 NS_IMETHODIMP
914 nsUpdateProcessor::GetServiceRegKeyExists(bool* aResult) {
915 #ifndef XP_WIN
916 return NS_ERROR_NOT_IMPLEMENTED;
917 #else // #ifdef XP_WIN
918 nsCOMPtr<nsIProperties> dirSvc(
919 do_GetService("@mozilla.org/file/directory_service;1"));
920 NS_ENSURE_TRUE(dirSvc, NS_ERROR_SERVICE_NOT_AVAILABLE);
922 nsCOMPtr<nsIFile> installBin;
923 nsresult rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
924 getter_AddRefs(installBin));
925 NS_ENSURE_SUCCESS(rv, rv);
927 nsCOMPtr<nsIFile> installDir;
928 rv = installBin->GetParent(getter_AddRefs(installDir));
929 NS_ENSURE_SUCCESS(rv, rv);
931 nsAutoString installPath;
932 rv = installDir->GetPath(installPath);
933 NS_ENSURE_SUCCESS(rv, rv);
935 wchar_t maintenanceServiceKey[MAX_PATH + 1];
936 BOOL success = CalculateRegistryPathFromFilePath(
937 PromiseFlatString(installPath).get(), maintenanceServiceKey);
938 NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
940 HKEY regHandle;
941 LSTATUS ls = RegOpenKeyExW(HKEY_LOCAL_MACHINE, maintenanceServiceKey, 0,
942 KEY_QUERY_VALUE | KEY_WOW64_64KEY, &regHandle);
943 if (ls == ERROR_SUCCESS) {
944 RegCloseKey(regHandle);
945 *aResult = true;
946 return NS_OK;
948 if (ls == ERROR_FILE_NOT_FOUND) {
949 *aResult = false;
950 return NS_OK;
952 // We got an error we weren't expecting reading the registry.
953 return NS_ERROR_NOT_AVAILABLE;
954 #endif // #ifdef XP_WIN