Bug 1852754: part 9) Add tests for dynamically loading <link rel="prefetch"> elements...
[gecko.git] / toolkit / xre / nsUpdateDriver.cpp
blobd64138b8331458ddb3cdc4e5048d0c5883c2391b
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"
36 #include "mozilla/CmdLineAndEnvUtils.h"
38 #ifdef XP_MACOSX
39 # include "nsILocalFileMac.h"
40 # include "nsCommandLineServiceMac.h"
41 # include "MacLaunchHelper.h"
42 # include "updaterfileutils_osx.h"
43 # include "mozilla/Monitor.h"
44 # include "gfxPlatformMac.h"
45 #endif
47 #if defined(XP_WIN)
48 # include <direct.h>
49 # include <process.h>
50 # include <windows.h>
51 # include <shlwapi.h>
52 # include <strsafe.h>
53 # include <shellapi.h>
54 # include "commonupdatedir.h"
55 # include "nsWindowsHelpers.h"
56 # include "pathhash.h"
57 # include "WinUtils.h"
58 # define getcwd(path, size) _getcwd(path, size)
59 # define getpid() GetCurrentProcessId()
60 #elif defined(XP_UNIX)
61 # include <unistd.h>
62 # include <sys/wait.h>
63 #endif
65 using namespace mozilla;
67 static LazyLogModule sUpdateLog("updatedriver");
68 // Some other file in our unified batch might have defined LOG already.
69 #ifdef LOG
70 # undef LOG
71 #endif
72 #define LOG(args) MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug, args)
74 #ifdef XP_MACOSX
75 static void UpdateDriverSetupMacCommandLine(int& argc, char**& argv,
76 bool restart) {
77 if (NS_IsMainThread()) {
78 CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart);
79 return;
81 // Bug 1335916: SetupMacCommandLine calls a CoreFoundation function that
82 // asserts that it was called from the main thread, so if we are not the main
83 // thread, we have to dispatch that call to there. But we also have to get the
84 // result from it, so we can't just dispatch and return, we have to wait
85 // until the dispatched operation actually completes. So we also set up a
86 // monitor to signal us when that happens, and block until then.
87 Monitor monitor MOZ_UNANNOTATED("nsUpdateDriver SetupMacCommandLine");
89 nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
90 "UpdateDriverSetupMacCommandLine",
91 [&argc, &argv, restart, &monitor]() -> void {
92 CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart);
93 MonitorAutoLock(monitor).Notify();
94 }));
96 if (NS_FAILED(rv)) {
97 LOG(
98 ("Update driver error dispatching SetupMacCommandLine to main thread: "
99 "%d\n",
100 uint32_t(rv)));
101 return;
104 // The length of this wait is arbitrary, but should be long enough that having
105 // it expire means something is seriously wrong.
106 CVStatus status =
107 MonitorAutoLock(monitor).Wait(TimeDuration::FromSeconds(60));
108 if (status == CVStatus::Timeout) {
109 LOG(("Update driver timed out waiting for SetupMacCommandLine\n"));
112 #endif
114 static nsresult GetCurrentWorkingDir(nsACString& aOutPath) {
115 // Cannot use NS_GetSpecialDirectory because XPCOM is not yet initialized.
116 // This code is duplicated from xpcom/io/SpecialSystemDirectory.cpp:
118 aOutPath.Truncate();
120 #if defined(XP_WIN)
121 wchar_t wpath[MAX_PATH];
122 if (!_wgetcwd(wpath, ArrayLength(wpath))) {
123 return NS_ERROR_FAILURE;
125 CopyUTF16toUTF8(nsDependentString(wpath), aOutPath);
126 #else
127 char path[MAXPATHLEN];
128 if (!getcwd(path, ArrayLength(path))) {
129 return NS_ERROR_FAILURE;
131 aOutPath = path;
132 #endif
134 return NS_OK;
138 * Get the path to the installation directory. For Mac OS X this will be the
139 * bundle directory.
141 * @param appDir the application directory file object
142 * @param installDirPath the path to the installation directory
144 static nsresult GetInstallDirPath(nsIFile* appDir, nsACString& installDirPath) {
145 nsresult rv;
146 #ifdef XP_MACOSX
147 nsCOMPtr<nsIFile> parentDir1, parentDir2;
148 rv = appDir->GetParent(getter_AddRefs(parentDir1));
149 NS_ENSURE_SUCCESS(rv, rv);
150 rv = parentDir1->GetParent(getter_AddRefs(parentDir2));
151 NS_ENSURE_SUCCESS(rv, rv);
152 rv = parentDir2->GetNativePath(installDirPath);
153 NS_ENSURE_SUCCESS(rv, rv);
154 #elif XP_WIN
155 nsAutoString installDirPathW;
156 rv = appDir->GetPath(installDirPathW);
157 NS_ENSURE_SUCCESS(rv, rv);
158 CopyUTF16toUTF8(installDirPathW, installDirPath);
159 #else
160 rv = appDir->GetNativePath(installDirPath);
161 NS_ENSURE_SUCCESS(rv, rv);
162 #endif
163 return NS_OK;
166 static bool GetFile(nsIFile* dir, const nsACString& name,
167 nsCOMPtr<nsIFile>& result) {
168 nsresult rv;
170 nsCOMPtr<nsIFile> file;
171 rv = dir->Clone(getter_AddRefs(file));
172 if (NS_FAILED(rv)) {
173 return false;
176 rv = file->AppendNative(name);
177 if (NS_FAILED(rv)) {
178 return false;
181 result = file;
182 return true;
185 static bool GetStatusFile(nsIFile* dir, nsCOMPtr<nsIFile>& result) {
186 return GetFile(dir, "update.status"_ns, result);
190 * Get the contents of the update.status file when the update.status file can
191 * be opened with read and write access. The reason it is opened for both read
192 * and write is to prevent trying to update when the user doesn't have write
193 * access to the update directory.
195 * @param statusFile the status file object.
196 * @param buf the buffer holding the file contents
198 * @return true if successful, false otherwise.
200 template <size_t Size>
201 static bool GetStatusFileContents(nsIFile* statusFile, char (&buf)[Size]) {
202 static_assert(
203 Size > 16,
204 "Buffer needs to be large enough to hold the known status codes");
206 PRFileDesc* fd = nullptr;
207 nsresult rv = statusFile->OpenNSPRFileDesc(PR_RDWR, 0660, &fd);
208 if (NS_FAILED(rv)) {
209 return false;
212 const int32_t n = PR_Read(fd, buf, Size);
213 PR_Close(fd);
215 return (n >= 0);
218 enum UpdateStatus {
219 eNoUpdateAction,
220 ePendingUpdate,
221 ePendingService,
222 ePendingElevate,
223 eAppliedUpdate,
224 eAppliedService,
228 * Returns a value indicating what needs to be done in order to handle an
229 * update.
231 * @param dir the directory in which we should look for an update.status file.
232 * @param statusFile the update.status file found in the directory.
234 * @return the update action to be performed.
236 static UpdateStatus GetUpdateStatus(nsIFile* dir,
237 nsCOMPtr<nsIFile>& statusFile) {
238 if (GetStatusFile(dir, statusFile)) {
239 char buf[32];
240 if (GetStatusFileContents(statusFile, buf)) {
241 const char kPending[] = "pending";
242 const char kPendingService[] = "pending-service";
243 const char kPendingElevate[] = "pending-elevate";
244 const char kApplied[] = "applied";
245 const char kAppliedService[] = "applied-service";
246 if (!strncmp(buf, kPendingElevate, sizeof(kPendingElevate) - 1)) {
247 return ePendingElevate;
249 if (!strncmp(buf, kPendingService, sizeof(kPendingService) - 1)) {
250 return ePendingService;
252 if (!strncmp(buf, kPending, sizeof(kPending) - 1)) {
253 return ePendingUpdate;
255 if (!strncmp(buf, kAppliedService, sizeof(kAppliedService) - 1)) {
256 return eAppliedService;
258 if (!strncmp(buf, kApplied, sizeof(kApplied) - 1)) {
259 return eAppliedUpdate;
263 return eNoUpdateAction;
266 static bool GetVersionFile(nsIFile* dir, nsCOMPtr<nsIFile>& result) {
267 return GetFile(dir, "update.version"_ns, result);
270 // Compares the current application version with the update's application
271 // version.
272 static bool IsOlderVersion(nsIFile* versionFile, const char* appVersion) {
273 PRFileDesc* fd = nullptr;
274 nsresult rv = versionFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd);
275 if (NS_FAILED(rv)) {
276 return true;
279 char buf[32];
280 const int32_t n = PR_Read(fd, buf, sizeof(buf));
281 PR_Close(fd);
283 if (n < 0) {
284 return false;
287 // Trim off the trailing newline
288 if (buf[n - 1] == '\n') {
289 buf[n - 1] = '\0';
292 // If the update xml doesn't provide the application version the file will
293 // contain the string "null" and it is assumed that the update is not older.
294 const char kNull[] = "null";
295 if (strncmp(buf, kNull, sizeof(kNull) - 1) == 0) {
296 return false;
299 return mozilla::Version(appVersion) > buf;
303 * Applies, switches, or stages an update.
305 * @param greDir the GRE directory
306 * @param updateDir the update root directory
307 * @param appDir the application directory
308 * @param appArgc the number of args passed to the application
309 * @param appArgv the args passed to the application
310 * (used for restarting the application when necessary)
311 * @param restart true when a restart is necessary.
312 * @param isStaged true when the update has already been staged
313 * @param outpid (out) parameter holding the handle to the updater application
314 * when staging updates
316 static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir,
317 int appArgc, char** appArgv, bool restart,
318 bool isStaged, ProcessType* outpid) {
319 // The following determines the update operation to perform.
320 // 1. When restart is false the update will be staged.
321 // 2. When restart is true and isStaged is false the update will apply the mar
322 // file to the installation directory.
323 // 3. When restart is true and isStaged is true the update will switch the
324 // staged update with the installation directory.
326 nsresult rv;
328 nsCOMPtr<nsIFile> updater;
329 nsAutoCString updaterPath;
330 nsAutoCString updateDirPath;
331 #if defined(XP_WIN)
332 // Get an nsIFile reference for the updater in the installation dir.
333 if (!GetFile(greDir, nsLiteralCString(UPDATER_BIN), updater)) {
334 return;
337 // Get the path to the updater.
338 nsAutoString updaterPathW;
339 rv = updater->GetPath(updaterPathW);
340 if (NS_FAILED(rv)) {
341 return;
343 CopyUTF16toUTF8(updaterPathW, updaterPath);
345 // Get the path to the update dir.
346 nsAutoString updateDirPathW;
347 rv = updateDir->GetPath(updateDirPathW);
348 if (NS_FAILED(rv)) {
349 return;
351 CopyUTF16toUTF8(updateDirPathW, updateDirPath);
352 #elif defined(XP_MACOSX)
353 // Get an nsIFile reference for the updater in the installation dir.
354 if (!GetFile(appDir, nsLiteralCString(UPDATER_APP), updater)) {
355 return;
357 rv = updater->AppendNative("Contents"_ns);
358 if (NS_FAILED(rv)) {
359 return;
361 rv = updater->AppendNative("MacOS"_ns);
362 if (NS_FAILED(rv)) {
363 return;
365 rv = updater->AppendNative(nsLiteralCString(UPDATER_BIN));
366 if (NS_FAILED(rv)) {
367 return;
370 // Get the path to the updater.
371 rv = updater->GetNativePath(updaterPath);
372 if (NS_FAILED(rv)) {
373 return;
376 // Get the path to the update dir.
377 rv = updateDir->GetNativePath(updateDirPath);
378 if (NS_FAILED(rv)) {
379 return;
381 #else
382 // Get an nsIFile reference for the updater in the installation dir.
383 if (!GetFile(greDir, nsLiteralCString(UPDATER_BIN), updater)) {
384 return;
387 // Get the path to the updater.
388 rv = updater->GetNativePath(updaterPath);
389 if (NS_FAILED(rv)) {
390 return;
393 // Get the path to the update dir.
394 rv = updateDir->GetNativePath(updateDirPath);
395 if (NS_FAILED(rv)) {
396 return;
398 #endif
400 // appFilePath and workingDirPath are only used when the application will be
401 // restarted.
402 nsAutoCString appFilePath;
403 nsAutoCString workingDirPath;
404 if (restart) {
405 // Get the path to the current working directory.
406 rv = GetCurrentWorkingDir(workingDirPath);
407 if (NS_FAILED(rv)) {
408 return;
411 // Get the application file path used by the updater to restart the
412 // application after the update has finished.
413 nsCOMPtr<nsIFile> appFile;
414 XRE_GetBinaryPath(getter_AddRefs(appFile));
415 if (!appFile) {
416 return;
419 #if defined(XP_WIN)
420 nsAutoString appFilePathW;
421 rv = appFile->GetPath(appFilePathW);
422 if (NS_FAILED(rv)) {
423 return;
425 CopyUTF16toUTF8(appFilePathW, appFilePath);
426 #else
427 rv = appFile->GetNativePath(appFilePath);
428 if (NS_FAILED(rv)) {
429 return;
431 #endif
434 // Get the installation directory path.
435 nsAutoCString installDirPath;
436 rv = GetInstallDirPath(appDir, installDirPath);
437 if (NS_FAILED(rv)) {
438 return;
441 #if defined(XP_MACOSX)
442 // If we're going to do a restart, we need to make sure the font registration
443 // thread has finished before this process exits (bug 1777332).
444 if (restart) {
445 gfxPlatformMac::WaitForFontRegistration();
448 // We need to detect whether elevation is required for this update. This can
449 // occur when an admin user installs the application, but another admin
450 // user attempts to update (see bug 394984).
451 // We only check if we need elevation if we are restarting. We don't attempt
452 // to stage if elevation is required. Staging happens without the user knowing
453 // about it, and we don't want to ask for elevation for seemingly no reason.
454 bool needElevation = false;
455 if (restart) {
456 needElevation = !IsRecursivelyWritable(installDirPath.get());
457 if (needElevation) {
458 // Normally we would check this via nsIAppStartup::wasSilentlyStarted,
459 // but nsIAppStartup isn't available yet.
460 char* mozAppSilentStart = PR_GetEnv("MOZ_APP_SILENT_START");
461 bool wasSilentlyStarted =
462 mozAppSilentStart && (strcmp(mozAppSilentStart, "") != 0);
463 if (wasSilentlyStarted) {
464 // Elevation always requires prompting for credentials on macOS. If we
465 // are trying to restart silently, we must not display UI such as this
466 // prompt.
467 // We make this check here rather than in the updater, because it is
468 // actually Firefox that shows the elevation prompt (via
469 // InstallPrivilegedHelper), not the updater.
470 return;
474 #endif
476 nsAutoCString applyToDirPath;
477 nsCOMPtr<nsIFile> updatedDir;
478 if (restart && !isStaged) {
479 // The install directory is the same as the apply to directory.
480 applyToDirPath.Assign(installDirPath);
481 } else {
482 // Get the directory where the update is staged or will be staged. This is
483 // `updateDir` for macOS and `appDir` for all other platforms. macOS cannot
484 // stage updates inside the .app bundle (`appDir`) without breaking the code
485 // signature on the bundle, so we use `updateDir` instead.
486 #if defined(XP_MACOSX)
487 if (!GetFile(updateDir, "Updated.app"_ns, updatedDir)) {
488 #else
489 if (!GetFile(appDir, "updated"_ns, updatedDir)) {
490 #endif
491 return;
493 #if defined(XP_WIN)
494 nsAutoString applyToDirPathW;
495 rv = updatedDir->GetPath(applyToDirPathW);
496 if (NS_FAILED(rv)) {
497 return;
499 CopyUTF16toUTF8(applyToDirPathW, applyToDirPath);
500 #else
501 rv = updatedDir->GetNativePath(applyToDirPath);
502 #endif
504 if (NS_FAILED(rv)) {
505 return;
508 if (restart && isStaged) {
509 // When the update should already be staged make sure that the updated
510 // directory exists.
511 bool updatedDirExists = false;
512 if (NS_FAILED(updatedDir->Exists(&updatedDirExists)) || !updatedDirExists) {
513 return;
517 // On platforms where we are not calling execv, we may need to make the
518 // updater executable wait for the calling process to exit. Otherwise, the
519 // updater may have trouble modifying our executable image (because it might
520 // still be in use). This is accomplished by passing our PID to the updater
521 // so that it can wait for us to exit. This is not perfect as there is a race
522 // condition that could bite us. It's possible that the calling process could
523 // exit before the updater waits on the specified PID, and in the meantime a
524 // new process with the same PID could be created. This situation is
525 // unlikely, however, given the way most operating systems recycle PIDs. We'll
526 // take our chances ;-) Construct the PID argument for this process to pass to
527 // the updater.
528 nsAutoCString pid;
529 if (restart) {
530 #if defined(XP_UNIX) & !defined(XP_MACOSX)
531 // When execv is used for an update that requires a restart 0 is passed
532 // which is ignored by the updater.
533 pid.AssignLiteral("0");
534 #else
535 pid.AppendInt((int32_t)getpid());
536 #endif
537 if (isStaged) {
538 // Append a special token to the PID in order to inform the updater that
539 // it should replace install with the updated directory.
540 pid.AppendLiteral("/replace");
542 } else {
543 // Signal the updater application that it should stage the update.
544 pid.AssignLiteral("-1");
547 int argc = 5;
548 if (restart) {
549 argc = appArgc + 6;
550 if (gRestartedByOS) {
551 argc += 1;
554 char** argv = static_cast<char**>(malloc((argc + 1) * sizeof(char*)));
555 if (!argv) {
556 return;
558 argv[0] = (char*)updaterPath.get();
559 argv[1] = (char*)updateDirPath.get();
560 argv[2] = (char*)installDirPath.get();
561 argv[3] = (char*)applyToDirPath.get();
562 argv[4] = (char*)pid.get();
563 if (restart && appArgc) {
564 argv[5] = (char*)workingDirPath.get();
565 argv[6] = (char*)appFilePath.get();
566 for (int i = 1; i < appArgc; ++i) {
567 argv[6 + i] = appArgv[i];
569 if (gRestartedByOS) {
570 // We haven't truly started up, restore this argument so that we will have
571 // it upon restart.
572 argv[6 + appArgc] = const_cast<char*>("-os-restarted");
575 argv[argc] = nullptr;
577 if (restart && gSafeMode) {
578 PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
581 LOG(("spawning updater process [%s]\n", updaterPath.get()));
583 #if defined(XP_UNIX) && !defined(XP_MACOSX)
584 // We use execv to spawn the updater process on all UNIX systems except Mac
585 // OSX since it is known to cause problems on the Mac. Windows has execv, but
586 // it is a faked implementation that doesn't really replace the current
587 // process. Instead it spawns a new process, so we gain nothing from using
588 // execv on Windows.
589 if (restart) {
590 int execResult = execv(updaterPath.get(), argv);
591 free(argv);
592 exit(execResult);
594 *outpid = fork();
595 if (*outpid == -1) {
596 free(argv);
597 return;
599 if (*outpid == 0) {
600 int execResult = execv(updaterPath.get(), argv);
601 free(argv);
602 exit(execResult);
604 #elif defined(XP_WIN)
605 if (isStaged) {
606 // Launch the updater to replace the installation with the staged updated.
607 if (!WinLaunchChild(updaterPathW.get(), argc, argv)) {
608 free(argv);
609 return;
611 } else {
612 // Launch the updater to either stage or apply an update.
613 if (!WinLaunchChild(updaterPathW.get(), argc, argv, nullptr, outpid)) {
614 free(argv);
615 return;
618 #elif defined(XP_MACOSX)
619 UpdateDriverSetupMacCommandLine(argc, argv, restart);
620 if (restart && needElevation) {
621 bool hasLaunched = LaunchElevatedUpdate(argc, argv, outpid);
622 free(argv);
623 if (!hasLaunched) {
624 LOG(("Failed to launch elevated update!"));
625 exit(1);
627 exit(0);
630 if (isStaged) {
631 // Launch the updater to replace the installation with the staged updated.
632 LaunchChildMac(argc, argv);
633 } else {
634 // Launch the updater to either stage or apply an update.
635 LaunchChildMac(argc, argv, outpid);
637 #else
638 if (isStaged) {
639 // Launch the updater to replace the installation with the staged updated.
640 PR_CreateProcessDetached(updaterPath.get(), argv, nullptr, nullptr);
641 } else {
642 // Launch the updater to either stage or apply an update.
643 *outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr);
645 #endif
646 free(argv);
647 if (restart) {
648 exit(0);
652 #if !defined(XP_WIN)
654 * Wait briefly to see if a process terminates, then return true if it has.
656 * (Not implemented on Windows, where HandleWatcher is used instead.)
658 static bool ProcessHasTerminated(ProcessType pt) {
659 # if defined(XP_MACOSX)
660 // We're waiting for the process to terminate in LaunchChildMac.
661 return true;
662 # elif defined(XP_UNIX)
663 int exitStatus;
664 pid_t exited = waitpid(pt, &exitStatus, WNOHANG);
665 if (exited == 0) {
666 // Process is still running.
667 sleep(1);
668 return false;
670 if (exited == -1) {
671 LOG(("Error while checking if the updater process is finished"));
672 // This shouldn't happen, but if it does, the updater process is lost to us,
673 // so the best we can do is pretend that it's exited.
674 return true;
676 // If we get here, the process has exited; make sure it exited normally.
677 if (WIFEXITED(exitStatus) && (WEXITSTATUS(exitStatus) != 0)) {
678 LOG(("Error while running the updater process, check update.log"));
680 return true;
681 # else
682 // No way to have a non-blocking implementation on these platforms,
683 // because we're using NSPR and it only provides a blocking wait.
684 int32_t exitCode;
685 PR_WaitProcess(pt, &exitCode);
686 if (exitCode != 0) {
687 LOG(("Error while running the updater process, check update.log"));
689 return true;
690 # endif
692 #endif
694 nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir,
695 int argc, char** argv, const char* appVersion,
696 bool restart, ProcessType* pid) {
697 nsresult rv;
699 #ifdef XP_WIN
700 // If we're in a package, we know any updates that we find are not for us.
701 if (mozilla::widget::WinUtils::HasPackageIdentity()) {
702 return NS_OK;
704 #endif
706 nsCOMPtr<nsIFile> updatesDir;
707 rv = updRootDir->Clone(getter_AddRefs(updatesDir));
708 NS_ENSURE_SUCCESS(rv, rv);
709 rv = updatesDir->AppendNative("updates"_ns);
710 NS_ENSURE_SUCCESS(rv, rv);
711 rv = updatesDir->AppendNative("0"_ns);
712 NS_ENSURE_SUCCESS(rv, rv);
714 // Return early since there isn't a valid update when the update application
715 // version file doesn't exist or if the update's application version is less
716 // than the current application version. The cleanup of the update will happen
717 // during post update processing in nsUpdateService.js.
718 nsCOMPtr<nsIFile> versionFile;
719 if (!GetVersionFile(updatesDir, versionFile) ||
720 IsOlderVersion(versionFile, appVersion)) {
721 return NS_OK;
724 nsCOMPtr<nsIFile> statusFile;
725 UpdateStatus status = GetUpdateStatus(updatesDir, statusFile);
726 switch (status) {
727 case ePendingUpdate:
728 case ePendingService: {
729 ApplyUpdate(greDir, updatesDir, appDir, argc, argv, restart, false, pid);
730 break;
732 case eAppliedUpdate:
733 case eAppliedService:
734 // An update was staged and needs to be switched so the updated
735 // application is used.
736 ApplyUpdate(greDir, updatesDir, appDir, argc, argv, restart, true, pid);
737 break;
738 case ePendingElevate:
739 // No action should be performed since the user hasn't opted into
740 // elevating for the update so continue application startup.
741 case eNoUpdateAction:
742 // We don't need to do any special processing here, we'll just continue to
743 // startup the application.
744 break;
747 return NS_OK;
750 NS_IMPL_ISUPPORTS(nsUpdateProcessor, nsIUpdateProcessor)
752 nsUpdateProcessor::nsUpdateProcessor() : mUpdaterPID(0) {}
754 #ifdef XP_WIN
755 nsUpdateProcessor::~nsUpdateProcessor() { mProcessWatcher.Stop(); }
756 #else
757 nsUpdateProcessor::~nsUpdateProcessor() = default;
758 #endif
760 NS_IMETHODIMP
761 nsUpdateProcessor::ProcessUpdate() {
762 nsresult rv;
764 nsCOMPtr<nsIProperties> ds =
765 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
766 NS_ENSURE_SUCCESS(rv, rv);
768 nsCOMPtr<nsIFile> exeFile;
769 rv = ds->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
770 getter_AddRefs(exeFile));
771 NS_ENSURE_SUCCESS(rv, rv);
773 nsCOMPtr<nsIFile> appDir;
774 rv = exeFile->GetParent(getter_AddRefs(appDir));
775 NS_ENSURE_SUCCESS(rv, rv);
777 nsCOMPtr<nsIFile> greDir;
778 rv = ds->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), getter_AddRefs(greDir));
779 NS_ENSURE_SUCCESS(rv, rv);
781 nsCOMPtr<nsIFile> updRoot;
782 rv = ds->Get(XRE_UPDATE_ROOT_DIR, NS_GET_IID(nsIFile),
783 getter_AddRefs(updRoot));
784 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the UpdRootD dir");
786 // XRE_UPDATE_ROOT_DIR should not fail but if it does fallback to the
787 // application directory just to be safe.
788 if (NS_FAILED(rv)) {
789 rv = appDir->Clone(getter_AddRefs(updRoot));
790 NS_ENSURE_SUCCESS(rv, rv);
793 nsCOMPtr<nsIXULAppInfo> appInfo =
794 do_GetService("@mozilla.org/xre/app-info;1", &rv);
795 NS_ENSURE_SUCCESS(rv, rv);
797 nsAutoCString appVersion;
798 rv = appInfo->GetVersion(appVersion);
799 NS_ENSURE_SUCCESS(rv, rv);
801 // Copy the parameters to the StagedUpdateInfo structure shared with the
802 // worker thread.
803 mInfo.mGREDir = greDir;
804 mInfo.mAppDir = appDir;
805 mInfo.mUpdateRoot = updRoot;
806 mInfo.mArgc = 0;
807 mInfo.mArgv = nullptr;
808 mInfo.mAppVersion = appVersion;
810 MOZ_ASSERT(NS_IsMainThread(), "not main thread");
811 nsCOMPtr<nsIRunnable> r =
812 NewRunnableMethod("nsUpdateProcessor::StartStagedUpdate", this,
813 &nsUpdateProcessor::StartStagedUpdate);
814 return NS_NewNamedThread("UpdateProcessor", getter_AddRefs(mWorkerThread), r);
817 void nsUpdateProcessor::StartStagedUpdate() {
818 MOZ_ASSERT(!NS_IsMainThread(), "main thread");
820 // If we fail to launch the updater process or its monitor for some reason, we
821 // need to shut down the worker thread, as there isn't anything more for us to
822 // do.
823 auto onExitStopThread = mozilla::MakeScopeExit([&] {
824 nsresult rv = NS_DispatchToMainThread(
825 NewRunnableMethod("nsUpdateProcessor::ShutdownWorkerThread", this,
826 &nsUpdateProcessor::ShutdownWorkerThread));
827 NS_ENSURE_SUCCESS_VOID(rv);
830 // Launch updater. (We do this on a worker thread to avoid blocking the main
831 // thread with file I/O.)
832 nsresult rv = ProcessUpdates(mInfo.mGREDir, mInfo.mAppDir, mInfo.mUpdateRoot,
833 mInfo.mArgc, mInfo.mArgv,
834 mInfo.mAppVersion.get(), false, &mUpdaterPID);
835 if (NS_FAILED(rv)) {
836 MOZ_LOG(sUpdateLog, mozilla::LogLevel::Error,
837 ("could not start updater process: %s", GetStaticErrorName(rv)));
838 return;
841 if (!mUpdaterPID) {
842 // not an error
843 MOZ_LOG(sUpdateLog, mozilla::LogLevel::Verbose,
844 ("ProcessUpdates() indicated nothing to do"));
845 return;
848 #ifdef WIN32
849 // Set up a HandleWatcher to report to the main thread when we're done.
850 RefPtr<nsIThread> mainThread;
851 NS_GetMainThread(getter_AddRefs(mainThread));
852 mProcessWatcher.Watch(mUpdaterPID, mainThread,
853 NewRunnableMethod("nsUpdateProcessor::UpdateDone", this,
854 &nsUpdateProcessor::UpdateDone));
856 // On Windows, that's all we need the worker thread for. Let
857 // `onExitStopThread` shut us down.
858 #else
859 // Monitor the state of the updater process while it is staging an update.
860 rv = NS_DispatchToCurrentThread(
861 NewRunnableMethod("nsUpdateProcessor::WaitForProcess", this,
862 &nsUpdateProcessor::WaitForProcess));
863 if (NS_FAILED(rv)) {
864 MOZ_LOG(sUpdateLog, mozilla::LogLevel::Error,
865 ("could not start updater process poll: error %s",
866 GetStaticErrorName(rv)));
867 return;
870 // Leave the worker thread alive to run WaitForProcess. Either it or its
871 // successors will be responsible for shutting down the worker thread.
872 onExitStopThread.release();
873 #endif
876 void nsUpdateProcessor::ShutdownWorkerThread() {
877 MOZ_ASSERT(NS_IsMainThread(), "not main thread");
878 mWorkerThread->Shutdown();
879 mWorkerThread = nullptr;
882 #ifndef WIN32
883 void nsUpdateProcessor::WaitForProcess() {
884 MOZ_ASSERT(!NS_IsMainThread(), "main thread");
885 if (ProcessHasTerminated(mUpdaterPID)) {
886 NS_DispatchToMainThread(NewRunnableMethod(
887 "nsUpdateProcessor::UpdateDone", this, &nsUpdateProcessor::UpdateDone));
888 } else {
889 NS_DispatchToCurrentThread(
890 NewRunnableMethod("nsUpdateProcessor::WaitForProcess", this,
891 &nsUpdateProcessor::WaitForProcess));
894 #endif
896 void nsUpdateProcessor::UpdateDone() {
897 MOZ_ASSERT(NS_IsMainThread(), "not main thread");
899 nsCOMPtr<nsIUpdateManager> um =
900 do_GetService("@mozilla.org/updates/update-manager;1");
901 if (um) {
902 // This completes asynchronously, but nothing else that we are doing in this
903 // function requires waiting for this to complete.
904 RefPtr<mozilla::dom::Promise> outPromise;
905 um->RefreshUpdateStatus(getter_AddRefs(outPromise));
908 // On Windows, shutting down the worker thread is taken care of by another task.
909 // (Which may not have run yet, so we can't assert.)
910 #ifndef XP_WIN
911 ShutdownWorkerThread();
912 #endif
915 NS_IMETHODIMP
916 nsUpdateProcessor::GetServiceRegKeyExists(bool* aResult) {
917 #ifndef XP_WIN
918 return NS_ERROR_NOT_IMPLEMENTED;
919 #else // #ifdef XP_WIN
920 nsCOMPtr<nsIProperties> dirSvc(
921 do_GetService("@mozilla.org/file/directory_service;1"));
922 NS_ENSURE_TRUE(dirSvc, NS_ERROR_SERVICE_NOT_AVAILABLE);
924 nsCOMPtr<nsIFile> installBin;
925 nsresult rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
926 getter_AddRefs(installBin));
927 NS_ENSURE_SUCCESS(rv, rv);
929 nsCOMPtr<nsIFile> installDir;
930 rv = installBin->GetParent(getter_AddRefs(installDir));
931 NS_ENSURE_SUCCESS(rv, rv);
933 nsAutoString installPath;
934 rv = installDir->GetPath(installPath);
935 NS_ENSURE_SUCCESS(rv, rv);
937 wchar_t maintenanceServiceKey[MAX_PATH + 1];
938 BOOL success = CalculateRegistryPathFromFilePath(
939 PromiseFlatString(installPath).get(), maintenanceServiceKey);
940 NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
942 HKEY regHandle;
943 LSTATUS ls = RegOpenKeyExW(HKEY_LOCAL_MACHINE, maintenanceServiceKey, 0,
944 KEY_QUERY_VALUE | KEY_WOW64_64KEY, &regHandle);
945 if (ls == ERROR_SUCCESS) {
946 RegCloseKey(regHandle);
947 *aResult = true;
948 return NS_OK;
950 if (ls == ERROR_FILE_NOT_FOUND) {
951 *aResult = false;
952 return NS_OK;
954 // We got an error we weren't expecting reading the registry.
955 return NS_ERROR_NOT_AVAILABLE;
956 #endif // #ifdef XP_WIN
959 NS_IMETHODIMP
960 nsUpdateProcessor::RegisterApplicationRestartWithLaunchArgs(
961 const nsTArray<nsString>& argvExtra) {
962 #ifndef XP_WIN
963 return NS_ERROR_NOT_IMPLEMENTED;
964 #else
965 // Retrieve current command line arguments for restart
966 // GetCommandLineW() returns a read only pointer to
967 // the arguments the process was launched with.
968 LPWSTR currentCommandLine = GetCommandLineW();
970 // Register a restart flag for the application based on the current
971 // command line. The program will then automatically restart
972 // upon termination.
973 // The application must have been running for a minimum of 60
974 // seconds for a restart to be correctly registered.
975 if (currentCommandLine) {
976 // Append additional command line arguments to current command line for
977 // restart
978 nsTArray<const wchar_t*> additionalArgv(argvExtra.Length());
979 for (const nsString& arg : argvExtra) {
980 additionalArgv.AppendElement(static_cast<const wchar_t*>(arg.get()));
983 int currentArgc = 0;
984 LPWSTR* currentCommandLineArgv =
985 CommandLineToArgvW(currentCommandLine, &currentArgc);
986 UniquePtr<LPWSTR, LocalFreeDeleter> uniqueCurrentArgv(
987 currentCommandLineArgv);
988 mozilla::UniquePtr<wchar_t[]> restartCommandLine = mozilla::MakeCommandLine(
989 currentArgc, uniqueCurrentArgv.get(), additionalArgv.Length(),
990 additionalArgv.Elements());
991 ::RegisterApplicationRestart(restartCommandLine.get(),
992 RESTART_NO_CRASH | RESTART_NO_HANG);
994 MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug,
995 ("register application restart succeeded"));
996 } else {
997 MOZ_LOG(sUpdateLog, mozilla::LogLevel::Error,
998 ("could not register application restart"));
999 return NS_ERROR_NOT_AVAILABLE;
1001 return NS_OK;
1002 #endif // #ifndef XP_WIN