summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/nsUpdateDriver.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/xre/nsUpdateDriver.cpp1081
1 files changed, 1081 insertions, 0 deletions
diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp
new file mode 100644
index 0000000000..bd2d4d3e59
--- /dev/null
+++ b/toolkit/xre/nsUpdateDriver.cpp
@@ -0,0 +1,1081 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include "nsUpdateDriver.h"
+#include "nsXULAppAPI.h"
+#include "nsAppRunner.h"
+#include "nsIFile.h"
+#include "nsVariant.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "prproces.h"
+#include "mozilla/Logging.h"
+#include "prenv.h"
+#include "nsVersionComparator.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsThreadUtils.h"
+#include "nsIXULAppInfo.h"
+#include "mozilla/Preferences.h"
+#include "nsPrintfCString.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Printf.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIObserverService.h"
+#include "nsNetCID.h"
+#include "mozilla/Services.h"
+
+#ifdef XP_MACOSX
+# include "nsILocalFileMac.h"
+# include "nsCommandLineServiceMac.h"
+# include "MacLaunchHelper.h"
+# include "updaterfileutils_osx.h"
+# include "mozilla/Monitor.h"
+#endif
+
+#if defined(XP_WIN)
+# include <direct.h>
+# include <process.h>
+# include <windows.h>
+# include <shlwapi.h>
+# include <strsafe.h>
+# include "commonupdatedir.h"
+# include "nsWindowsHelpers.h"
+# define getcwd(path, size) _getcwd(path, size)
+# define getpid() GetCurrentProcessId()
+#elif defined(XP_UNIX)
+# include <unistd.h>
+# include <sys/wait.h>
+#endif
+
+using namespace mozilla;
+
+static LazyLogModule sUpdateLog("updatedriver");
+// Some other file in our unified batch might have defined LOG already.
+#ifdef LOG
+# undef LOG
+#endif
+#define LOG(args) MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug, args)
+
+#ifdef XP_WIN
+# define UPDATER_BIN "updater.exe"
+# define MAINTENANCE_SVC_NAME L"MozillaMaintenance"
+#elif XP_MACOSX
+# define UPDATER_APP "updater.app"
+# define UPDATER_BIN "org.mozilla.updater"
+#else
+# define UPDATER_BIN "updater"
+#endif
+
+#ifdef XP_MACOSX
+static void UpdateDriverSetupMacCommandLine(int& argc, char**& argv,
+ bool restart) {
+ if (NS_IsMainThread()) {
+ CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart);
+ return;
+ }
+ // Bug 1335916: SetupMacCommandLine calls a CoreFoundation function that
+ // asserts that it was called from the main thread, so if we are not the main
+ // thread, we have to dispatch that call to there. But we also have to get the
+ // result from it, so we can't just dispatch and return, we have to wait
+ // until the dispatched operation actually completes. So we also set up a
+ // monitor to signal us when that happens, and block until then.
+ Monitor monitor("nsUpdateDriver SetupMacCommandLine");
+
+ nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "UpdateDriverSetupMacCommandLine",
+ [&argc, &argv, restart, &monitor]() -> void {
+ CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart);
+ MonitorAutoLock(monitor).Notify();
+ }));
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("Update driver error dispatching SetupMacCommandLine to main thread: "
+ "%d\n",
+ rv));
+ return;
+ }
+
+ // The length of this wait is arbitrary, but should be long enough that having
+ // it expire means something is seriously wrong.
+ CVStatus status =
+ MonitorAutoLock(monitor).Wait(TimeDuration::FromSeconds(60));
+ if (status == CVStatus::Timeout) {
+ LOG(("Update driver timed out waiting for SetupMacCommandLine\n"));
+ }
+}
+#endif
+
+static nsresult GetCurrentWorkingDir(nsACString& aOutPath) {
+ // Cannot use NS_GetSpecialDirectory because XPCOM is not yet initialized.
+ // This code is duplicated from xpcom/io/SpecialSystemDirectory.cpp:
+
+ aOutPath.Truncate();
+
+#if defined(XP_WIN)
+ wchar_t wpath[MAX_PATH];
+ if (!_wgetcwd(wpath, ArrayLength(wpath))) {
+ return NS_ERROR_FAILURE;
+ }
+ CopyUTF16toUTF8(nsDependentString(wpath), aOutPath);
+#else
+ char path[MAXPATHLEN];
+ if (!getcwd(path, ArrayLength(path))) {
+ return NS_ERROR_FAILURE;
+ }
+ aOutPath = path;
+#endif
+
+ return NS_OK;
+}
+
+/**
+ * Get the path to the installation directory. For Mac OS X this will be the
+ * bundle directory.
+ *
+ * @param appDir the application directory file object
+ * @param installDirPath the path to the installation directory
+ */
+static nsresult GetInstallDirPath(nsIFile* appDir, nsACString& installDirPath) {
+ nsresult rv;
+#ifdef XP_MACOSX
+ nsCOMPtr<nsIFile> parentDir1, parentDir2;
+ rv = appDir->GetParent(getter_AddRefs(parentDir1));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = parentDir1->GetParent(getter_AddRefs(parentDir2));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = parentDir2->GetNativePath(installDirPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+#elif XP_WIN
+ nsAutoString installDirPathW;
+ rv = appDir->GetPath(installDirPathW);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF16toUTF8(installDirPathW, installDirPath);
+#else
+ rv = appDir->GetNativePath(installDirPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+ return NS_OK;
+}
+
+static bool GetFile(nsIFile* dir, const nsACString& name,
+ nsCOMPtr<nsIFile>& result) {
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> file;
+ rv = dir->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ rv = file->AppendNative(name);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ result = file;
+ return true;
+}
+
+static bool GetStatusFile(nsIFile* dir, nsCOMPtr<nsIFile>& result) {
+ return GetFile(dir, "update.status"_ns, result);
+}
+
+/**
+ * Get the contents of the update.status file when the update.status file can
+ * be opened with read and write access. The reason it is opened for both read
+ * and write is to prevent trying to update when the user doesn't have write
+ * access to the update directory.
+ *
+ * @param statusFile the status file object.
+ * @param buf the buffer holding the file contents
+ *
+ * @return true if successful, false otherwise.
+ */
+template <size_t Size>
+static bool GetStatusFileContents(nsIFile* statusFile, char (&buf)[Size]) {
+ static_assert(
+ Size > 16,
+ "Buffer needs to be large enough to hold the known status codes");
+
+ PRFileDesc* fd = nullptr;
+ nsresult rv = statusFile->OpenNSPRFileDesc(PR_RDWR, 0660, &fd);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ const int32_t n = PR_Read(fd, buf, Size);
+ PR_Close(fd);
+
+ return (n >= 0);
+}
+
+typedef enum {
+ eNoUpdateAction,
+ ePendingUpdate,
+ ePendingService,
+ ePendingElevate,
+ eAppliedUpdate,
+ eAppliedService,
+} UpdateStatus;
+
+/**
+ * Returns a value indicating what needs to be done in order to handle an
+ * update.
+ *
+ * @param dir the directory in which we should look for an update.status file.
+ * @param statusFile the update.status file found in the directory.
+ *
+ * @return the update action to be performed.
+ */
+static UpdateStatus GetUpdateStatus(nsIFile* dir,
+ nsCOMPtr<nsIFile>& statusFile) {
+ if (GetStatusFile(dir, statusFile)) {
+ char buf[32];
+ if (GetStatusFileContents(statusFile, buf)) {
+ const char kPending[] = "pending";
+ const char kPendingService[] = "pending-service";
+ const char kPendingElevate[] = "pending-elevate";
+ const char kApplied[] = "applied";
+ const char kAppliedService[] = "applied-service";
+ if (!strncmp(buf, kPendingElevate, sizeof(kPendingElevate) - 1)) {
+ return ePendingElevate;
+ }
+ if (!strncmp(buf, kPendingService, sizeof(kPendingService) - 1)) {
+ return ePendingService;
+ }
+ if (!strncmp(buf, kPending, sizeof(kPending) - 1)) {
+ return ePendingUpdate;
+ }
+ if (!strncmp(buf, kAppliedService, sizeof(kAppliedService) - 1)) {
+ return eAppliedService;
+ }
+ if (!strncmp(buf, kApplied, sizeof(kApplied) - 1)) {
+ return eAppliedUpdate;
+ }
+ }
+ }
+ return eNoUpdateAction;
+}
+
+static bool GetVersionFile(nsIFile* dir, nsCOMPtr<nsIFile>& result) {
+ return GetFile(dir, "update.version"_ns, result);
+}
+
+// Compares the current application version with the update's application
+// version.
+static bool IsOlderVersion(nsIFile* versionFile, const char* appVersion) {
+ PRFileDesc* fd = nullptr;
+ nsresult rv = versionFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ char buf[32];
+ const int32_t n = PR_Read(fd, buf, sizeof(buf));
+ PR_Close(fd);
+
+ if (n < 0) {
+ return false;
+ }
+
+ // Trim off the trailing newline
+ if (buf[n - 1] == '\n') {
+ buf[n - 1] = '\0';
+ }
+
+ // If the update xml doesn't provide the application version the file will
+ // contain the string "null" and it is assumed that the update is not older.
+ const char kNull[] = "null";
+ if (strncmp(buf, kNull, sizeof(kNull) - 1) == 0) {
+ return false;
+ }
+
+ return mozilla::Version(appVersion) > buf;
+}
+
+/**
+ * Applies, switches, or stages an update.
+ *
+ * @param greDir the GRE directory
+ * @param updateDir the update root directory
+ * @param appDir the application directory
+ * @param appArgc the number of args passed to the application
+ * @param appArgv the args passed to the application
+ * (used for restarting the application when necessary)
+ * @param restart true when a restart is necessary.
+ * @param isStaged true when the update has already been staged
+ * @param outpid (out) parameter holding the handle to the updater application
+ * when staging updates
+ */
+static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir,
+ int appArgc, char** appArgv, bool restart,
+ bool isStaged, ProcessType* outpid) {
+ // The following determines the update operation to perform.
+ // 1. When restart is false the update will be staged.
+ // 2. When restart is true and isStaged is false the update will apply the mar
+ // file to the installation directory.
+ // 3. When restart is true and isStaged is true the update will switch the
+ // staged update with the installation directory.
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> updater;
+ nsAutoCString updaterPath;
+ nsAutoCString updateDirPath;
+#if defined(XP_WIN)
+ // Get an nsIFile reference for the updater in the installation dir.
+ if (!GetFile(greDir, nsLiteralCString(UPDATER_BIN), updater)) {
+ return;
+ }
+
+ // Get the path to the updater.
+ nsAutoString updaterPathW;
+ rv = updater->GetPath(updaterPathW);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ CopyUTF16toUTF8(updaterPathW, updaterPath);
+
+ // Get the path to the update dir.
+ nsAutoString updateDirPathW;
+ rv = updateDir->GetPath(updateDirPathW);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ CopyUTF16toUTF8(updateDirPathW, updateDirPath);
+#elif defined(XP_MACOSX)
+ // Get an nsIFile reference for the updater in the installation dir.
+ if (!GetFile(appDir, nsLiteralCString(UPDATER_APP), updater)) {
+ return;
+ }
+ rv = updater->AppendNative("Contents"_ns);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ rv = updater->AppendNative("MacOS"_ns);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ rv = updater->AppendNative(nsLiteralCString(UPDATER_BIN));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Get the path to the updater.
+ rv = updater->GetNativePath(updaterPath);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Get the path to the update dir.
+ rv = updateDir->GetNativePath(updateDirPath);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+#else
+ // Get an nsIFile reference for the updater in the installation dir.
+ if (!GetFile(greDir, nsLiteralCString(UPDATER_BIN), updater)) {
+ return;
+ }
+
+ // Get the path to the updater.
+ rv = updater->GetNativePath(updaterPath);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Get the path to the update dir.
+ rv = updateDir->GetNativePath(updateDirPath);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+#endif
+
+ // appFilePath and workingDirPath are only used when the application will be
+ // restarted.
+ nsAutoCString appFilePath;
+ nsAutoCString workingDirPath;
+ if (restart) {
+ // Get the path to the current working directory.
+ rv = GetCurrentWorkingDir(workingDirPath);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Get the application file path used by the updater to restart the
+ // application after the update has finished.
+ nsCOMPtr<nsIFile> appFile;
+ XRE_GetBinaryPath(getter_AddRefs(appFile));
+ if (!appFile) {
+ return;
+ }
+
+#if defined(XP_WIN)
+ nsAutoString appFilePathW;
+ rv = appFile->GetPath(appFilePathW);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ CopyUTF16toUTF8(appFilePathW, appFilePath);
+#else
+ rv = appFile->GetNativePath(appFilePath);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+#endif
+ }
+
+ // Get the installation directory path.
+ nsAutoCString installDirPath;
+ rv = GetInstallDirPath(appDir, installDirPath);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsAutoCString applyToDirPath;
+ nsCOMPtr<nsIFile> updatedDir;
+ if (restart && !isStaged) {
+ // The install directory is the same as the apply to directory.
+ applyToDirPath.Assign(installDirPath);
+ } else {
+ // Get the directory where the update is staged or will be staged.
+#if defined(XP_MACOSX)
+ if (!GetFile(updateDir, "Updated.app"_ns, updatedDir)) {
+#else
+ if (!GetFile(appDir, "updated"_ns, updatedDir)) {
+#endif
+ return;
+ }
+#if defined(XP_WIN)
+ nsAutoString applyToDirPathW;
+ rv = updatedDir->GetPath(applyToDirPathW);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ CopyUTF16toUTF8(applyToDirPathW, applyToDirPath);
+#else
+ rv = updatedDir->GetNativePath(applyToDirPath);
+#endif
+ }
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (restart && isStaged) {
+ // When the update should already be staged make sure that the updated
+ // directory exists.
+ bool updatedDirExists = false;
+ if (NS_FAILED(updatedDir->Exists(&updatedDirExists)) || !updatedDirExists) {
+ return;
+ }
+ }
+
+ // On platforms where we are not calling execv, we may need to make the
+ // updater executable wait for the calling process to exit. Otherwise, the
+ // updater may have trouble modifying our executable image (because it might
+ // still be in use). This is accomplished by passing our PID to the updater
+ // so that it can wait for us to exit. This is not perfect as there is a race
+ // condition that could bite us. It's possible that the calling process could
+ // exit before the updater waits on the specified PID, and in the meantime a
+ // new process with the same PID could be created. This situation is
+ // unlikely, however, given the way most operating systems recycle PIDs. We'll
+ // take our chances ;-) Construct the PID argument for this process to pass to
+ // the updater.
+ nsAutoCString pid;
+ if (restart) {
+#if defined(XP_UNIX) & !defined(XP_MACOSX)
+ // When execv is used for an update that requires a restart 0 is passed
+ // which is ignored by the updater.
+ pid.AssignLiteral("0");
+#else
+ pid.AppendInt((int32_t)getpid());
+#endif
+ if (isStaged) {
+ // Append a special token to the PID in order to inform the updater that
+ // it should replace install with the updated directory.
+ pid.AppendLiteral("/replace");
+ }
+ } else {
+ // Signal the updater application that it should stage the update.
+ pid.AssignLiteral("-1");
+ }
+
+ int argc = 5;
+ if (restart) {
+ argc = appArgc + 6;
+ if (gRestartedByOS) {
+ argc += 1;
+ }
+ }
+ char** argv = static_cast<char**>(malloc((argc + 1) * sizeof(char*)));
+ if (!argv) {
+ return;
+ }
+ argv[0] = (char*)updaterPath.get();
+ argv[1] = (char*)updateDirPath.get();
+ argv[2] = (char*)installDirPath.get();
+ argv[3] = (char*)applyToDirPath.get();
+ argv[4] = (char*)pid.get();
+ if (restart && appArgc) {
+ argv[5] = (char*)workingDirPath.get();
+ argv[6] = (char*)appFilePath.get();
+ for (int i = 1; i < appArgc; ++i) {
+ argv[6 + i] = appArgv[i];
+ }
+ if (gRestartedByOS) {
+ // We haven't truly started up, restore this argument so that we will have
+ // it upon restart.
+ argv[6 + appArgc] = const_cast<char*>("-os-restarted");
+ }
+ }
+ argv[argc] = nullptr;
+
+ if (restart && gSafeMode) {
+ PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
+ }
+
+ LOG(("spawning updater process [%s]\n", updaterPath.get()));
+
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
+ // We use execv to spawn the updater process on all UNIX systems except Mac
+ // OSX since it is known to cause problems on the Mac. Windows has execv, but
+ // it is a faked implementation that doesn't really replace the current
+ // process. Instead it spawns a new process, so we gain nothing from using
+ // execv on Windows.
+ if (restart) {
+ int execResult = execv(updaterPath.get(), argv);
+ free(argv);
+ exit(execResult);
+ }
+ *outpid = fork();
+ if (*outpid == -1) {
+ free(argv);
+ return;
+ }
+ if (*outpid == 0) {
+ int execResult = execv(updaterPath.get(), argv);
+ free(argv);
+ exit(execResult);
+ }
+#elif defined(XP_WIN)
+ if (isStaged) {
+ // Launch the updater to replace the installation with the staged updated.
+ if (!WinLaunchChild(updaterPathW.get(), argc, argv)) {
+ free(argv);
+ return;
+ }
+ } else {
+ // Launch the updater to either stage or apply an update.
+ if (!WinLaunchChild(updaterPathW.get(), argc, argv, nullptr, outpid)) {
+ free(argv);
+ return;
+ }
+ }
+#elif defined(XP_MACOSX)
+UpdateDriverSetupMacCommandLine(argc, argv, restart);
+// We need to detect whether elevation is required for this update. This can
+// occur when an admin user installs the application, but another admin
+// user attempts to update (see bug 394984).
+if (restart && !IsRecursivelyWritable(installDirPath.get())) {
+ bool hasLaunched = LaunchElevatedUpdate(argc, argv, outpid);
+ free(argv);
+ if (!hasLaunched) {
+ LOG(("Failed to launch elevated update!"));
+ exit(1);
+ }
+ exit(0);
+}
+
+if (isStaged) {
+ // Launch the updater to replace the installation with the staged updated.
+ LaunchChildMac(argc, argv);
+} else {
+ // Launch the updater to either stage or apply an update.
+ LaunchChildMac(argc, argv, outpid);
+}
+#else
+if (isStaged) {
+ // Launch the updater to replace the installation with the staged updated.
+ PR_CreateProcessDetached(updaterPath.get(), argv, nullptr, nullptr);
+} else {
+ // Launch the updater to either stage or apply an update.
+ *outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr);
+}
+#endif
+ free(argv);
+ if (restart) {
+ exit(0);
+ }
+}
+
+/**
+ * Wait briefly to see if a process terminates, then return true if it has.
+ */
+static bool ProcessHasTerminated(ProcessType pt) {
+#if defined(XP_WIN)
+ if (WaitForSingleObject(pt, 1000)) {
+ return false;
+ }
+ CloseHandle(pt);
+ return true;
+#elif defined(XP_MACOSX)
+ // We're waiting for the process to terminate in LaunchChildMac.
+ return true;
+#elif defined(XP_UNIX)
+ int exitStatus;
+ pid_t exited = waitpid(pt, &exitStatus, WNOHANG);
+ if (exited == 0) {
+ // Process is still running.
+ sleep(1);
+ return false;
+ }
+ if (exited == -1) {
+ LOG(("Error while checking if the updater process is finished"));
+ // This shouldn't happen, but if it does, the updater process is lost to us,
+ // so the best we can do is pretend that it's exited.
+ return true;
+ }
+ // If we get here, the process has exited; make sure it exited normally.
+ if (WIFEXITED(exitStatus) && (WEXITSTATUS(exitStatus) != 0)) {
+ LOG(("Error while running the updater process, check update.log"));
+ }
+ return true;
+#else
+ // No way to have a non-blocking implementation on these platforms,
+ // because we're using NSPR and it only provides a blocking wait.
+ int32_t exitCode;
+ PR_WaitProcess(pt, &exitCode);
+ if (exitCode != 0) {
+ LOG(("Error while running the updater process, check update.log"));
+ }
+ return true;
+#endif
+}
+
+nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir,
+ int argc, char** argv, const char* appVersion,
+ bool restart, ProcessType* pid) {
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> updatesDir;
+ rv = updRootDir->Clone(getter_AddRefs(updatesDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = updatesDir->AppendNative("updates"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = updatesDir->AppendNative("0"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Return early since there isn't a valid update when the update application
+ // version file doesn't exist or if the update's application version is less
+ // than the current application version. The cleanup of the update will happen
+ // during post update processing in nsUpdateService.js.
+ nsCOMPtr<nsIFile> versionFile;
+ if (!GetVersionFile(updatesDir, versionFile) ||
+ IsOlderVersion(versionFile, appVersion)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> statusFile;
+ UpdateStatus status = GetUpdateStatus(updatesDir, statusFile);
+ switch (status) {
+ case ePendingUpdate:
+ case ePendingService: {
+ ApplyUpdate(greDir, updatesDir, appDir, argc, argv, restart, false, pid);
+ break;
+ }
+ case eAppliedUpdate:
+ case eAppliedService:
+ // An update was staged and needs to be switched so the updated
+ // application is used.
+ ApplyUpdate(greDir, updatesDir, appDir, argc, argv, restart, true, pid);
+ break;
+ case ePendingElevate:
+ // No action should be performed since the user hasn't opted into
+ // elevating for the update so continue application startup.
+ case eNoUpdateAction:
+ // We don't need to do any special processing here, we'll just continue to
+ // startup the application.
+ break;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsUpdateProcessor, nsIUpdateProcessor)
+
+nsUpdateProcessor::nsUpdateProcessor() : mUpdaterPID(0) {}
+
+nsUpdateProcessor::~nsUpdateProcessor() = default;
+
+NS_IMETHODIMP
+nsUpdateProcessor::ProcessUpdate() {
+ nsresult rv;
+
+ nsCOMPtr<nsIProperties> ds =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> exeFile;
+ rv = ds->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
+ getter_AddRefs(exeFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> appDir;
+ rv = exeFile->GetParent(getter_AddRefs(appDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> greDir;
+ rv = ds->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), getter_AddRefs(greDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> updRoot;
+ rv = ds->Get(XRE_UPDATE_ROOT_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(updRoot));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the UpdRootD dir");
+
+ // XRE_UPDATE_ROOT_DIR should not fail but if it does fallback to the
+ // application directory just to be safe.
+ if (NS_FAILED(rv)) {
+ rv = appDir->Clone(getter_AddRefs(updRoot));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString appVersion;
+ rv = appInfo->GetVersion(appVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copy the parameters to the StagedUpdateInfo structure shared with the
+ // watcher thread.
+ mInfo.mGREDir = greDir;
+ mInfo.mAppDir = appDir;
+ mInfo.mUpdateRoot = updRoot;
+ mInfo.mArgc = 0;
+ mInfo.mArgv = nullptr;
+ mInfo.mAppVersion = appVersion;
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ nsCOMPtr<nsIRunnable> r =
+ NewRunnableMethod("nsUpdateProcessor::StartStagedUpdate", this,
+ &nsUpdateProcessor::StartStagedUpdate);
+ return NS_NewNamedThread("Update Watcher", getter_AddRefs(mProcessWatcher),
+ r);
+}
+
+NS_IMETHODIMP
+nsUpdateProcessor::FixUpdateDirectoryPerms(bool aUseServiceOnFailure) {
+#ifndef XP_WIN
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ enum class State {
+ Initializing,
+ WaitingToStart,
+ Starting,
+ WaitingForFinish,
+ };
+
+ class FixUpdateDirectoryPermsRunnable final : public mozilla::Runnable {
+ public:
+ FixUpdateDirectoryPermsRunnable(const char* aName,
+ bool aUseServiceOnFailure,
+ const nsAutoString& aInstallPath)
+ : Runnable(aName),
+ mState(State::Initializing)
+# ifdef MOZ_MAINTENANCE_SERVICE
+ ,
+ mUseServiceOnFailure(aUseServiceOnFailure)
+# endif
+ {
+ size_t installPathSize = aInstallPath.Length() + 1;
+ mInstallPath = mozilla::MakeUnique<wchar_t[]>(installPathSize);
+ if (mInstallPath) {
+ HRESULT hrv = StringCchCopyW(mInstallPath.get(), installPathSize,
+ PromiseFlatString(aInstallPath).get());
+ if (FAILED(hrv)) {
+ mInstallPath.reset();
+ }
+ }
+ }
+
+ NS_IMETHOD Run() override {
+# ifdef MOZ_MAINTENANCE_SERVICE
+ // These constants control how often and how many times we poll the
+ // maintenance service to see if it has stopped. If there is no delay in
+ // the event queue, this works out to 8 minutes of polling.
+ const unsigned int kMaxQueries = 2400;
+ const unsigned int kQueryIntervalMS = 200;
+ // These constants control how often and how many times we attempt to
+ // start the service. If there is no delay in the event queue, this works
+ // out to 5 seconds of polling.
+ const unsigned int kMaxStartAttempts = 50;
+ const unsigned int kStartAttemptIntervalMS = 100;
+# endif
+
+ if (mState == State::Initializing) {
+ if (!mInstallPath) {
+ LOG(
+ ("Warning: No install path available in "
+ "FixUpdateDirectoryPermsRunnable\n"));
+ }
+ // In the event that the directory is owned by this user, we may be able
+ // to fix things without the maintenance service
+ mozilla::UniquePtr<wchar_t[]> updateDir;
+ HRESULT permResult = GetCommonUpdateDirectory(
+ mInstallPath.get(), SetPermissionsOf::AllFilesAndDirs, updateDir);
+ if (SUCCEEDED(permResult)) {
+ LOG(("Successfully fixed permissions from within Firefox\n"));
+ return NS_OK;
+ }
+# ifdef MOZ_MAINTENANCE_SERVICE
+ else if (!mUseServiceOnFailure) {
+ LOG(
+ ("Error: Unable to fix permissions within Firefox and "
+ "maintenance service is disabled\n"));
+ return ReportUpdateError();
+ }
+# else
+ LOG(("Error: Unable to fix permissions\n"));
+ return ReportUpdateError();
+# endif
+
+# ifdef MOZ_MAINTENANCE_SERVICE
+ SC_HANDLE serviceManager =
+ OpenSCManager(nullptr, nullptr,
+ SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
+ mServiceManager.own(serviceManager);
+ if (!serviceManager) {
+ LOG(
+ ("Error: Unable to get the service manager. Cannot fix "
+ "permissions.\n"));
+ return NS_ERROR_FAILURE;
+ }
+ SC_HANDLE service = OpenServiceW(serviceManager, MAINTENANCE_SVC_NAME,
+ SERVICE_QUERY_STATUS | SERVICE_START);
+ mService.own(service);
+ if (!service) {
+ LOG(
+ ("Error: Unable to get the maintenance service. Unable fix "
+ "permissions without it.\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ mStartServiceArgCount = mInstallPath ? 3 : 2;
+ mStartServiceArgs =
+ mozilla::MakeUnique<LPCWSTR[]>(mStartServiceArgCount);
+ if (!mStartServiceArgs) {
+ LOG(
+ ("Error: Unable to allocate memory for argument pointers. Cannot "
+ "fix permissions.\n"));
+ return NS_ERROR_FAILURE;
+ }
+ mStartServiceArgs[0] = L"MozillaMaintenance";
+ mStartServiceArgs[1] = L"fix-update-directory-perms";
+ if (mInstallPath) {
+ mStartServiceArgs[2] = mInstallPath.get();
+ }
+
+ mState = State::WaitingToStart;
+ mCurrentTry = 1;
+# endif
+ }
+# ifdef MOZ_MAINTENANCE_SERVICE
+ if (mState == State::WaitingToStart ||
+ mState == State::WaitingForFinish) {
+ SERVICE_STATUS_PROCESS ssp;
+ DWORD bytesNeeded;
+ BOOL success =
+ QueryServiceStatusEx(mService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
+ sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded);
+ if (!success) {
+ DWORD lastError = GetLastError();
+ // These 3 errors can occur when the service is not yet stopped but it
+ // is stopping. If we got another error, waiting will probably not
+ // help.
+ if (lastError != ERROR_INVALID_SERVICE_CONTROL &&
+ lastError != ERROR_SERVICE_CANNOT_ACCEPT_CTRL &&
+ lastError != ERROR_SERVICE_NOT_ACTIVE) {
+ LOG(
+ ("Error: Unable to query service when fixing permissions. Got "
+ "an error that cannot be fixed by waiting: 0x%lx\n",
+ lastError));
+ return NS_ERROR_FAILURE;
+ }
+ if (mCurrentTry >= kMaxQueries) {
+ LOG(
+ ("Error: Unable to query service when fixing permissions: "
+ "Timed out after %u attempts.\n",
+ mCurrentTry));
+ return NS_ERROR_FAILURE;
+ }
+ return RetryInMS(kQueryIntervalMS);
+ } else { // We successfully queried the service
+ if (ssp.dwCurrentState != SERVICE_STOPPED) {
+ return RetryInMS(kQueryIntervalMS);
+ }
+ if (mState == State::WaitingForFinish) {
+ if (ssp.dwWin32ExitCode != NO_ERROR) {
+ LOG(
+ ("Error: Maintenance Service was unable to fix update "
+ "directory permissions\n"));
+ return ReportUpdateError();
+ }
+ LOG(
+ ("Maintenance service successully fixed update directory "
+ "permissions\n"));
+ return NS_OK;
+ }
+ mState = State::Starting;
+ mCurrentTry = 1;
+ }
+ }
+ if (mState == State::Starting) {
+ BOOL success = StartServiceW(mService, mStartServiceArgCount,
+ mStartServiceArgs.get());
+ if (success) {
+ mState = State::WaitingForFinish;
+ mCurrentTry = 1;
+ return RetryInMS(kQueryIntervalMS);
+ } else if (mCurrentTry >= kMaxStartAttempts) {
+ LOG(
+ ("Error: Unable to fix permissions: Timed out after %u attempts "
+ "to start the maintenance service\n",
+ mCurrentTry));
+ return NS_ERROR_FAILURE;
+ }
+ return RetryInMS(kStartAttemptIntervalMS);
+ }
+# endif
+ // We should not have fallen through all three state checks above
+ LOG(
+ ("Error: Reached logically unreachable code when correcting update "
+ "directory permissions\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ private:
+ State mState;
+ mozilla::UniquePtr<wchar_t[]> mInstallPath;
+# ifdef MOZ_MAINTENANCE_SERVICE
+ bool mUseServiceOnFailure;
+ unsigned int mCurrentTry;
+ nsAutoServiceHandle mServiceManager;
+ nsAutoServiceHandle mService;
+ DWORD mStartServiceArgCount;
+ mozilla::UniquePtr<LPCWSTR[]> mStartServiceArgs;
+
+ nsresult RetryInMS(unsigned int aDelayMS) {
+ ++mCurrentTry;
+ nsCOMPtr<nsIRunnable> runnable(this);
+ return NS_DelayedDispatchToCurrentThread(runnable.forget(), aDelayMS);
+ }
+# endif
+ nsresult ReportUpdateError() {
+ return NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsUpdateProcessor::FixUpdateDirectoryPerms::"
+ "FixUpdateDirectoryPermsRunnable::ReportUpdateError",
+ []() -> void {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (NS_WARN_IF(!observerService)) {
+ return;
+ }
+ observerService->NotifyObservers(nullptr, "update-error",
+ u"bad-perms");
+ }));
+ }
+ };
+
+ nsCOMPtr<nsIProperties> dirSvc(
+ do_GetService("@mozilla.org/file/directory_service;1"));
+ NS_ENSURE_TRUE(dirSvc, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIFile> appPath;
+ nsresult rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
+ getter_AddRefs(appPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> installDir;
+ rv = appPath->GetParent(getter_AddRefs(installDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString installPath;
+ rv = installDir->GetPath(installPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Stream transport service has a thread pool we can use so that this happens
+ // off the main thread.
+ nsCOMPtr<nsIEventTarget> eventTarget =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(eventTarget, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIRunnable> runnable = new FixUpdateDirectoryPermsRunnable(
+ "FixUpdateDirectoryPermsRunnable", aUseServiceOnFailure, installPath);
+ rv = eventTarget->Dispatch(runnable.forget());
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+ return NS_OK;
+}
+
+void nsUpdateProcessor::StartStagedUpdate() {
+ MOZ_ASSERT(!NS_IsMainThread(), "main thread");
+
+ nsresult rv = ProcessUpdates(mInfo.mGREDir, mInfo.mAppDir, mInfo.mUpdateRoot,
+ mInfo.mArgc, mInfo.mArgv,
+ mInfo.mAppVersion.get(), false, &mUpdaterPID);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (mUpdaterPID) {
+ // Track the state of the updater process while it is staging an update.
+ rv = NS_DispatchToCurrentThread(
+ NewRunnableMethod("nsUpdateProcessor::WaitForProcess", this,
+ &nsUpdateProcessor::WaitForProcess));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ } else {
+ // Failed to launch the updater process for some reason.
+ // We need to shutdown the current thread as there isn't anything more for
+ // us to do...
+ rv = NS_DispatchToMainThread(
+ NewRunnableMethod("nsUpdateProcessor::ShutdownWatcherThread", this,
+ &nsUpdateProcessor::ShutdownWatcherThread));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+}
+
+void nsUpdateProcessor::ShutdownWatcherThread() {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ mProcessWatcher->Shutdown();
+ mProcessWatcher = nullptr;
+}
+
+void nsUpdateProcessor::WaitForProcess() {
+ MOZ_ASSERT(!NS_IsMainThread(), "main thread");
+ if (ProcessHasTerminated(mUpdaterPID)) {
+ NS_DispatchToMainThread(NewRunnableMethod(
+ "nsUpdateProcessor::UpdateDone", this, &nsUpdateProcessor::UpdateDone));
+ } else {
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("nsUpdateProcessor::WaitForProcess", this,
+ &nsUpdateProcessor::WaitForProcess));
+ }
+}
+
+void nsUpdateProcessor::UpdateDone() {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ nsCOMPtr<nsIUpdateManager> um =
+ do_GetService("@mozilla.org/updates/update-manager;1");
+ if (um) {
+ um->RefreshUpdateStatus();
+ }
+
+ ShutdownWatcherThread();
+}