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/. */
9 #include "nsUpdateDriver.h"
12 #include "nsXULAppAPI.h"
13 #include "nsAppRunner.h"
15 #include "nsVariant.h"
19 #include "mozilla/Logging.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"
33 #include "mozilla/ScopeExit.h"
34 #include "mozilla/Services.h"
35 #include "mozilla/dom/Promise.h"
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"
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)
60 # include <sys/wait.h>
63 using namespace mozilla
;
65 static LazyLogModule
sUpdateLog("updatedriver");
66 // Some other file in our unified batch might have defined LOG already.
70 #define LOG(args) MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug, args)
73 static void UpdateDriverSetupMacCommandLine(int& argc
, char**& argv
,
75 if (NS_IsMainThread()) {
76 CommandLineServiceMac::SetupMacCommandLine(argc
, argv
, restart
);
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();
96 ("Update driver error dispatching SetupMacCommandLine to main thread: "
102 // The length of this wait is arbitrary, but should be long enough that having
103 // it expire means something is seriously wrong.
105 MonitorAutoLock(monitor
).Wait(TimeDuration::FromSeconds(60));
106 if (status
== CVStatus::Timeout
) {
107 LOG(("Update driver timed out waiting for SetupMacCommandLine\n"));
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:
119 wchar_t wpath
[MAX_PATH
];
120 if (!_wgetcwd(wpath
, ArrayLength(wpath
))) {
121 return NS_ERROR_FAILURE
;
123 CopyUTF16toUTF8(nsDependentString(wpath
), aOutPath
);
125 char path
[MAXPATHLEN
];
126 if (!getcwd(path
, ArrayLength(path
))) {
127 return NS_ERROR_FAILURE
;
136 * Get the path to the installation directory. For Mac OS X this will be the
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
) {
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
);
153 nsAutoString installDirPathW
;
154 rv
= appDir
->GetPath(installDirPathW
);
155 NS_ENSURE_SUCCESS(rv
, rv
);
156 CopyUTF16toUTF8(installDirPathW
, installDirPath
);
158 rv
= appDir
->GetNativePath(installDirPath
);
159 NS_ENSURE_SUCCESS(rv
, rv
);
164 static bool GetFile(nsIFile
* dir
, const nsACString
& name
,
165 nsCOMPtr
<nsIFile
>& result
) {
168 nsCOMPtr
<nsIFile
> file
;
169 rv
= dir
->Clone(getter_AddRefs(file
));
174 rv
= file
->AppendNative(name
);
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
]) {
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
);
210 const int32_t n
= PR_Read(fd
, buf
, Size
);
226 * Returns a value indicating what needs to be done in order to handle an
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
)) {
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
270 static bool IsOlderVersion(nsIFile
* versionFile
, const char* appVersion
) {
271 PRFileDesc
* fd
= nullptr;
272 nsresult rv
= versionFile
->OpenNSPRFileDesc(PR_RDONLY
, 0660, &fd
);
278 const int32_t n
= PR_Read(fd
, buf
, sizeof(buf
));
285 // Trim off the trailing newline
286 if (buf
[n
- 1] == '\n') {
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) {
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.
326 nsCOMPtr
<nsIFile
> updater
;
327 nsAutoCString updaterPath
;
328 nsAutoCString updateDirPath
;
330 // Get an nsIFile reference for the updater in the installation dir.
331 if (!GetFile(greDir
, nsLiteralCString(UPDATER_BIN
), updater
)) {
335 // Get the path to the updater.
336 nsAutoString updaterPathW
;
337 rv
= updater
->GetPath(updaterPathW
);
341 CopyUTF16toUTF8(updaterPathW
, updaterPath
);
343 // Get the path to the update dir.
344 nsAutoString updateDirPathW
;
345 rv
= updateDir
->GetPath(updateDirPathW
);
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
)) {
355 rv
= updater
->AppendNative("Contents"_ns
);
359 rv
= updater
->AppendNative("MacOS"_ns
);
363 rv
= updater
->AppendNative(nsLiteralCString(UPDATER_BIN
));
368 // Get the path to the updater.
369 rv
= updater
->GetNativePath(updaterPath
);
374 // Get the path to the update dir.
375 rv
= updateDir
->GetNativePath(updateDirPath
);
380 // Get an nsIFile reference for the updater in the installation dir.
381 if (!GetFile(greDir
, nsLiteralCString(UPDATER_BIN
), updater
)) {
385 // Get the path to the updater.
386 rv
= updater
->GetNativePath(updaterPath
);
391 // Get the path to the update dir.
392 rv
= updateDir
->GetNativePath(updateDirPath
);
398 // appFilePath and workingDirPath are only used when the application will be
400 nsAutoCString appFilePath
;
401 nsAutoCString workingDirPath
;
403 // Get the path to the current working directory.
404 rv
= GetCurrentWorkingDir(workingDirPath
);
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
));
418 nsAutoString appFilePathW
;
419 rv
= appFile
->GetPath(appFilePathW
);
423 CopyUTF16toUTF8(appFilePathW
, appFilePath
);
425 rv
= appFile
->GetNativePath(appFilePath
);
432 // Get the installation directory path.
433 nsAutoCString installDirPath
;
434 rv
= GetInstallDirPath(appDir
, installDirPath
);
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).
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;
454 needElevation
= !IsRecursivelyWritable(installDirPath
.get());
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
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.
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
);
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
)) {
487 if (!GetFile(appDir
, "updated"_ns
, updatedDir
)) {
492 nsAutoString applyToDirPathW
;
493 rv
= updatedDir
->GetPath(applyToDirPathW
);
497 CopyUTF16toUTF8(applyToDirPathW
, applyToDirPath
);
499 rv
= updatedDir
->GetNativePath(applyToDirPath
);
506 if (restart
&& isStaged
) {
507 // When the update should already be staged make sure that the updated
509 bool updatedDirExists
= false;
510 if (NS_FAILED(updatedDir
->Exists(&updatedDirExists
)) || !updatedDirExists
) {
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
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");
533 pid
.AppendInt((int32_t)getpid());
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");
541 // Signal the updater application that it should stage the update.
542 pid
.AssignLiteral("-1");
548 if (gRestartedByOS
) {
552 char** argv
= static_cast<char**>(malloc((argc
+ 1) * sizeof(char*)));
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
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
588 int execResult
= execv(updaterPath
.get(), argv
);
598 int execResult
= execv(updaterPath
.get(), argv
);
602 #elif defined(XP_WIN)
604 // Launch the updater to replace the installation with the staged updated.
605 if (!WinLaunchChild(updaterPathW
.get(), argc
, argv
)) {
610 // Launch the updater to either stage or apply an update.
611 if (!WinLaunchChild(updaterPathW
.get(), argc
, argv
, nullptr, outpid
)) {
616 #elif defined(XP_MACOSX)
617 UpdateDriverSetupMacCommandLine(argc
, argv
, restart
);
618 if (restart
&& needElevation
) {
619 bool hasLaunched
= LaunchElevatedUpdate(argc
, argv
, outpid
);
622 LOG(("Failed to launch elevated update!"));
629 // Launch the updater to replace the installation with the staged updated.
630 LaunchChildMac(argc
, argv
);
632 // Launch the updater to either stage or apply an update.
633 LaunchChildMac(argc
, argv
, outpid
);
637 // Launch the updater to replace the installation with the staged updated.
638 PR_CreateProcessDetached(updaterPath
.get(), argv
, nullptr, nullptr);
640 // Launch the updater to either stage or apply an update.
641 *outpid
= PR_CreateProcess(updaterPath
.get(), argv
, nullptr, nullptr);
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.
660 # elif defined(XP_UNIX)
662 pid_t exited
= waitpid(pt
, &exitStatus
, WNOHANG
);
664 // Process is still running.
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.
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"));
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.
683 PR_WaitProcess(pt
, &exitCode
);
685 LOG(("Error while running the updater process, check update.log"));
692 nsresult
ProcessUpdates(nsIFile
* greDir
, nsIFile
* appDir
, nsIFile
* updRootDir
,
693 int argc
, char** argv
, const char* appVersion
,
694 bool restart
, ProcessType
* pid
) {
698 // If we're in a package, we know any updates that we find are not for us.
699 if (mozilla::widget::WinUtils::HasPackageIdentity()) {
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
)) {
722 nsCOMPtr
<nsIFile
> statusFile
;
723 UpdateStatus status
= GetUpdateStatus(updatesDir
, statusFile
);
726 case ePendingService
: {
727 ApplyUpdate(greDir
, updatesDir
, appDir
, argc
, argv
, restart
, false, pid
);
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
);
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.
748 NS_IMPL_ISUPPORTS(nsUpdateProcessor
, nsIUpdateProcessor
)
750 nsUpdateProcessor::nsUpdateProcessor() : mUpdaterPID(0) {}
753 nsUpdateProcessor::~nsUpdateProcessor() { mProcessWatcher
.Stop(); }
755 nsUpdateProcessor::~nsUpdateProcessor() = default;
759 nsUpdateProcessor::ProcessUpdate() {
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.
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
801 mInfo
.mGREDir
= greDir
;
802 mInfo
.mAppDir
= appDir
;
803 mInfo
.mUpdateRoot
= updRoot
;
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
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
);
834 MOZ_LOG(sUpdateLog
, mozilla::LogLevel::Error
,
835 ("could not start updater process: %s", GetStaticErrorName(rv
)));
841 MOZ_LOG(sUpdateLog
, mozilla::LogLevel::Verbose
,
842 ("ProcessUpdates() indicated nothing to do"));
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.
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
));
862 MOZ_LOG(sUpdateLog
, mozilla::LogLevel::Error
,
863 ("could not start updater process poll: error %s",
864 GetStaticErrorName(rv
)));
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();
874 void nsUpdateProcessor::ShutdownWorkerThread() {
875 MOZ_ASSERT(NS_IsMainThread(), "not main thread");
876 mWorkerThread
->Shutdown();
877 mWorkerThread
= nullptr;
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
));
887 NS_DispatchToCurrentThread(
888 NewRunnableMethod("nsUpdateProcessor::WaitForProcess", this,
889 &nsUpdateProcessor::WaitForProcess
));
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");
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.)
909 ShutdownWorkerThread();
914 nsUpdateProcessor::GetServiceRegKeyExists(bool* aResult
) {
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
);
941 LSTATUS ls
= RegOpenKeyExW(HKEY_LOCAL_MACHINE
, maintenanceServiceKey
, 0,
942 KEY_QUERY_VALUE
| KEY_WOW64_64KEY
, ®Handle
);
943 if (ls
== ERROR_SUCCESS
) {
944 RegCloseKey(regHandle
);
948 if (ls
== ERROR_FILE_NOT_FOUND
) {
952 // We got an error we weren't expecting reading the registry.
953 return NS_ERROR_NOT_AVAILABLE
;
954 #endif // #ifdef XP_WIN