mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-10-31 16:28:05 +02:00 
			
		
		
		
	 0721e5dda6
			
		
	
	
		0721e5dda6
		
	
	
	
	
		
			
			Note that this patch does not implement the proper usage of the argument. It reads the argument but then does nothing with it. Original Revision: https://phabricator.services.mozilla.com/D225421 Differential Revision: https://phabricator.services.mozilla.com/D242449
		
			
				
	
	
		
			1028 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1028 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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 "nsDebug.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/ErrorNames.h"
 | |
| #include "mozilla/Printf.h"
 | |
| #include "mozilla/UniquePtr.h"
 | |
| #include "nsIObserverService.h"
 | |
| #include "nsNetCID.h"
 | |
| #include "mozilla/ScopeExit.h"
 | |
| #include "mozilla/Services.h"
 | |
| #include "mozilla/dom/Promise.h"
 | |
| #include "mozilla/CmdLineAndEnvUtils.h"
 | |
| 
 | |
| #ifdef XP_MACOSX
 | |
| #  include "nsILocalFileMac.h"
 | |
| #  include "nsCommandLineServiceMac.h"
 | |
| #  include "MacLaunchHelper.h"
 | |
| #  include "updaterfileutils_osx.h"
 | |
| #  include "mozilla/Monitor.h"
 | |
| #  include "gfxPlatformMac.h"
 | |
| #endif
 | |
| 
 | |
| #if defined(XP_WIN)
 | |
| #  include <direct.h>
 | |
| #  include <process.h>
 | |
| #  include <windows.h>
 | |
| #  include <shlwapi.h>
 | |
| #  include <strsafe.h>
 | |
| #  include <shellapi.h>
 | |
| #  include "commonupdatedir.h"
 | |
| #  include "nsWindowsHelpers.h"
 | |
| #  include "pathhash.h"
 | |
| #  include "WinUtils.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)
 | |
| 
 | |
| 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);
 | |
| }
 | |
| 
 | |
| enum UpdateStatus {
 | |
|   eNoUpdateAction,
 | |
|   ePendingUpdate,
 | |
|   ePendingService,
 | |
|   ePendingElevate,
 | |
|   eAppliedUpdate,
 | |
|   eAppliedService,
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * 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) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(
 | |
|       !restart || NS_IsMainThread(),
 | |
|       "restart may only be set when called on the main thread");
 | |
|   // 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;
 | |
|   }
 | |
| 
 | |
| #if defined(XP_MACOSX)
 | |
|   // If we're going to do a restart, we need to make sure the font registration
 | |
|   // thread has finished before this process exits (bug 1777332).
 | |
|   if (restart) {
 | |
|     gfxPlatformMac::WaitForFontRegistration();
 | |
|   }
 | |
| 
 | |
|   // 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).
 | |
|   // We only check if we need elevation if we are restarting. We don't attempt
 | |
|   // to stage if elevation is required. Staging happens without the user knowing
 | |
|   // about it, and we don't want to ask for elevation for seemingly no reason.
 | |
|   bool needElevation = false;
 | |
|   if (restart) {
 | |
|     needElevation = !IsRecursivelyWritable(installDirPath.get());
 | |
|     if (needElevation) {
 | |
|       // Normally we would check this via nsIAppStartup::wasSilentlyStarted,
 | |
|       // but nsIAppStartup isn't available yet.
 | |
|       char* mozAppSilentStart = PR_GetEnv("MOZ_APP_SILENT_START");
 | |
|       bool wasSilentlyStarted =
 | |
|           mozAppSilentStart && (strcmp(mozAppSilentStart, "") != 0);
 | |
|       if (wasSilentlyStarted) {
 | |
|         // Elevation always requires prompting for credentials on macOS. If we
 | |
|         // are trying to restart silently, we must not display UI such as this
 | |
|         // prompt.
 | |
|         // We make this check here rather than in the updater, because it is
 | |
|         // actually Firefox that shows the elevation prompt (via
 | |
|         // InstallPrivilegedHelper), not the updater.
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   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. This is
 | |
|     // `updateDir` for macOS and `appDir` for all other platforms. macOS cannot
 | |
|     // stage updates inside the .app bundle (`appDir`) without breaking the code
 | |
|     // signature on the bundle, so we use `updateDir` instead.
 | |
| #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 = 7;
 | |
|   if (restart) {
 | |
|     argc += 1;  // callback working directory
 | |
|     argc += appArgc;
 | |
|     if (gRestartedByOS) {
 | |
|       argc += 1;
 | |
|     }
 | |
|   }
 | |
|   char** argv = static_cast<char**>(malloc((argc + 1) * sizeof(char*)));
 | |
|   if (!argv) {
 | |
|     return;
 | |
|   }
 | |
|   argv[0] = (char*)updaterPath.get();
 | |
|   argv[1] = const_cast<char*>("3");
 | |
|   argv[2] = (char*)updateDirPath.get();
 | |
|   argv[3] = (char*)installDirPath.get();
 | |
|   argv[4] = (char*)applyToDirPath.get();
 | |
|   argv[5] = const_cast<char*>("first");
 | |
|   argv[6] = (char*)pid.get();
 | |
|   if (restart && appArgc) {
 | |
|     argv[7] = (char*)workingDirPath.get();
 | |
|     argv[8] = (char*)appFilePath.get();
 | |
|     for (int i = 1; i < appArgc; ++i) {
 | |
|       argv[8 + i] = appArgv[i];
 | |
|     }
 | |
|     if (gRestartedByOS) {
 | |
|       // We haven't truly started up, restore this argument so that we will have
 | |
|       // it upon restart.
 | |
|       argv[8 + 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)
 | |
| if (restart) {
 | |
|   // Ensure we've added URLs to load into the app command line if we're
 | |
|   // restarting.
 | |
|   CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart);
 | |
| 
 | |
|   if (needElevation) {
 | |
|     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);
 | |
|   }
 | |
| }
 | |
| 
 | |
| #if !defined(XP_WIN)
 | |
| /**
 | |
|  * Wait briefly to see if a process terminates, then return true if it has.
 | |
|  *
 | |
|  * (Not implemented on Windows, where HandleWatcher is used instead.)
 | |
|  */
 | |
| static bool ProcessHasTerminated(ProcessType pt) {
 | |
| #  if 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
 | |
| }
 | |
| #endif
 | |
| 
 | |
| nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir,
 | |
|                         int argc, char** argv, const char* appVersion,
 | |
|                         bool restart, ProcessType* pid) {
 | |
|   nsresult rv;
 | |
| 
 | |
| #ifdef XP_WIN
 | |
|   // If we're in a package, we know any updates that we find are not for us.
 | |
|   if (mozilla::widget::WinUtils::HasPackageIdentity()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   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) {}
 | |
| 
 | |
| #ifdef XP_WIN
 | |
| nsUpdateProcessor::~nsUpdateProcessor() { mProcessWatcher.Stop(); }
 | |
| #else
 | |
| nsUpdateProcessor::~nsUpdateProcessor() = default;
 | |
| #endif
 | |
| 
 | |
| 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
 | |
|   // worker 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("UpdateProcessor", getter_AddRefs(mWorkerThread), r);
 | |
| }
 | |
| 
 | |
| void nsUpdateProcessor::StartStagedUpdate() {
 | |
|   MOZ_ASSERT(!NS_IsMainThread(), "main thread");
 | |
| 
 | |
|   // If we fail to launch the updater process or its monitor for some reason, we
 | |
|   // need to shut down the worker thread, as there isn't anything more for us to
 | |
|   // do.
 | |
|   auto onExitStopThread = mozilla::MakeScopeExit([&] {
 | |
|     nsresult rv = NS_DispatchToMainThread(
 | |
|         NewRunnableMethod("nsUpdateProcessor::ShutdownWorkerThread", this,
 | |
|                           &nsUpdateProcessor::ShutdownWorkerThread));
 | |
|     NS_ENSURE_SUCCESS_VOID(rv);
 | |
|   });
 | |
| 
 | |
|   // Launch updater. (We do this on a worker thread to avoid blocking the main
 | |
|   // thread with file I/O.)
 | |
|   nsresult rv = ProcessUpdates(mInfo.mGREDir, mInfo.mAppDir, mInfo.mUpdateRoot,
 | |
|                                mInfo.mArgc, mInfo.mArgv,
 | |
|                                mInfo.mAppVersion.get(), false, &mUpdaterPID);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     MOZ_LOG(sUpdateLog, mozilla::LogLevel::Error,
 | |
|             ("could not start updater process: %s", GetStaticErrorName(rv)));
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mUpdaterPID) {
 | |
|     // not an error
 | |
|     MOZ_LOG(sUpdateLog, mozilla::LogLevel::Verbose,
 | |
|             ("ProcessUpdates() indicated nothing to do"));
 | |
|     return;
 | |
|   }
 | |
| 
 | |
| #ifdef WIN32
 | |
|   // Set up a HandleWatcher to report to the main thread when we're done.
 | |
|   RefPtr<nsIThread> mainThread;
 | |
|   NS_GetMainThread(getter_AddRefs(mainThread));
 | |
|   mProcessWatcher.Watch(mUpdaterPID, mainThread,
 | |
|                         NewRunnableMethod("nsUpdateProcessor::UpdateDone", this,
 | |
|                                           &nsUpdateProcessor::UpdateDone));
 | |
| 
 | |
| // On Windows, that's all we need the worker thread for. Let
 | |
| // `onExitStopThread` shut us down.
 | |
| #else
 | |
|   // Monitor the state of the updater process while it is staging an update.
 | |
|   rv = NS_DispatchToCurrentThread(
 | |
|       NewRunnableMethod("nsUpdateProcessor::WaitForProcess", this,
 | |
|                         &nsUpdateProcessor::WaitForProcess));
 | |
|   if (NS_FAILED(rv)) {
 | |
|     MOZ_LOG(sUpdateLog, mozilla::LogLevel::Error,
 | |
|             ("could not start updater process poll: error %s",
 | |
|              GetStaticErrorName(rv)));
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Leave the worker thread alive to run WaitForProcess. Either it or its
 | |
|   // successors will be responsible for shutting down the worker thread.
 | |
|   onExitStopThread.release();
 | |
| #endif
 | |
| }
 | |
| 
 | |
| void nsUpdateProcessor::ShutdownWorkerThread() {
 | |
|   MOZ_ASSERT(NS_IsMainThread(), "not main thread");
 | |
|   mWorkerThread->Shutdown();
 | |
|   mWorkerThread = nullptr;
 | |
| }
 | |
| 
 | |
| #ifndef WIN32
 | |
| 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));
 | |
|   }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void nsUpdateProcessor::UpdateDone() {
 | |
|   MOZ_ASSERT(NS_IsMainThread(), "not main thread");
 | |
| 
 | |
|   nsCOMPtr<nsIUpdateManager> um =
 | |
|       do_GetService("@mozilla.org/updates/update-manager;1");
 | |
|   if (um) {
 | |
|     // This completes asynchronously, but nothing else that we are doing in this
 | |
|     // function requires waiting for this to complete.
 | |
|     RefPtr<mozilla::dom::Promise> outPromise;
 | |
|     um->RefreshUpdateStatus(getter_AddRefs(outPromise));
 | |
|   }
 | |
| 
 | |
| // On Windows, shutting down the worker thread is taken care of by another task.
 | |
| // (Which may not have run yet, so we can't assert.)
 | |
| #ifndef XP_WIN
 | |
|   ShutdownWorkerThread();
 | |
| #endif
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsUpdateProcessor::GetServiceRegKeyExists(bool* aResult) {
 | |
| #ifndef XP_WIN
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| #else   // #ifdef XP_WIN
 | |
|   nsCOMPtr<nsIProperties> dirSvc(
 | |
|       do_GetService("@mozilla.org/file/directory_service;1"));
 | |
|   NS_ENSURE_TRUE(dirSvc, NS_ERROR_SERVICE_NOT_AVAILABLE);
 | |
| 
 | |
|   nsCOMPtr<nsIFile> installBin;
 | |
|   nsresult rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
 | |
|                             getter_AddRefs(installBin));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsCOMPtr<nsIFile> installDir;
 | |
|   rv = installBin->GetParent(getter_AddRefs(installDir));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsAutoString installPath;
 | |
|   rv = installDir->GetPath(installPath);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   wchar_t maintenanceServiceKey[MAX_PATH + 1];
 | |
|   BOOL success = CalculateRegistryPathFromFilePath(
 | |
|       PromiseFlatString(installPath).get(), maintenanceServiceKey);
 | |
|   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
 | |
| 
 | |
|   HKEY regHandle;
 | |
|   LSTATUS ls = RegOpenKeyExW(HKEY_LOCAL_MACHINE, maintenanceServiceKey, 0,
 | |
|                              KEY_QUERY_VALUE | KEY_WOW64_64KEY, ®Handle);
 | |
|   if (ls == ERROR_SUCCESS) {
 | |
|     RegCloseKey(regHandle);
 | |
|     *aResult = true;
 | |
|     return NS_OK;
 | |
|   }
 | |
|   if (ls == ERROR_FILE_NOT_FOUND) {
 | |
|     *aResult = false;
 | |
|     return NS_OK;
 | |
|   }
 | |
|   // We got an error we weren't expecting reading the registry.
 | |
|   return NS_ERROR_NOT_AVAILABLE;
 | |
| #endif  // #ifdef XP_WIN
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsUpdateProcessor::AttemptAutomaticApplicationRestartWithLaunchArgs(
 | |
|     const nsTArray<nsString>& argvExtra, int32_t* pidRet) {
 | |
| #ifndef XP_WIN
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| #else
 | |
|   // Retrieve current command line arguments for restart
 | |
|   // GetCommandLineW() returns a read only pointer to
 | |
|   // the arguments the process was launched with.
 | |
|   LPWSTR currentCommandLine = GetCommandLineW();
 | |
| 
 | |
|   // Spawn a new process for the application based on the current
 | |
|   // command line with the -restart-pid <pid> flag. This flag
 | |
|   // can be used with MaybeWaitForProcessExit() to have
 | |
|   // the process wait until the parent process has exited.
 | |
|   if (currentCommandLine) {
 | |
|     // Append additional command line arguments to current command line for
 | |
|     // restart.
 | |
|     int currentArgc = 0;
 | |
|     UniquePtr<LPWSTR, LocalFreeDeleter> currentArgv(
 | |
|         CommandLineToArgvW(currentCommandLine, ¤tArgc));
 | |
|     nsTArray<wchar_t*> restartCommandLineArgv(currentArgc + argvExtra.Length() +
 | |
|                                               2);
 | |
|     for (int i = 0; i < currentArgc; i++) {
 | |
|       restartCommandLineArgv.AppendElement(currentArgv.get()[i]);
 | |
|     }
 | |
|     for (const nsString& arg : argvExtra) {
 | |
|       restartCommandLineArgv.AppendElement(static_cast<wchar_t*>(arg.get()));
 | |
|     }
 | |
|     // Append -restart-pid flag and pid to restart command line.
 | |
|     DWORD pidCurrent = GetCurrentProcessId();
 | |
|     nsString pid;
 | |
|     pid.AppendInt(static_cast<uint32_t>(pidCurrent));
 | |
|     nsString pidFlag = u"-restart-pid"_ns;
 | |
|     restartCommandLineArgv.AppendElement(pidFlag.get());
 | |
|     restartCommandLineArgv.AppendElement(pid.get());
 | |
| 
 | |
|     // Create new process that interacts with MaybeWaitForProcessExit()
 | |
|     // and sleeps until the original process is killed.
 | |
|     wchar_t exeName[MAX_PATH];
 | |
|     GetModuleFileNameW(NULL, exeName, MAX_PATH);
 | |
|     HANDLE childHandle;
 | |
|     WinLaunchChild(exeName, restartCommandLineArgv.Length(),
 | |
|                    restartCommandLineArgv.Elements(), nullptr, &childHandle);
 | |
|     *pidRet = GetProcessId(childHandle);
 | |
|     CloseHandle(childHandle);
 | |
|     if (!*pidRet) {
 | |
|       printf_stderr("*** ApplyUpdate: !pidRet ***\n");
 | |
|       return NS_ERROR_ABORT;
 | |
|     }
 | |
|     printf_stderr("*** ApplyUpdate: launched pidRet = %d ***\n", *pidRet);
 | |
| 
 | |
|     MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug,
 | |
|             ("register application restart succeeded"));
 | |
|   } else {
 | |
|     MOZ_LOG(sUpdateLog, mozilla::LogLevel::Error,
 | |
|             ("could not register application restart"));
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
|   return NS_OK;
 | |
| #endif  // #ifndef XP_WIN
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsUpdateProcessor::WaitForProcessExit(uint32_t pid, uint32_t timeoutMS) {
 | |
| #ifndef XP_WIN
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| #else
 | |
| 
 | |
|   nsAutoHandle hProcess(OpenProcess(SYNCHRONIZE, FALSE, pid));
 | |
|   if (!hProcess) {
 | |
|     // It's possible the pid is incorrect, or the process has exited.
 | |
|     // This isn't necessarily a failure state as if the process has
 | |
|     // already exited then that is the desired behavior.
 | |
|     MOZ_LOG(sUpdateLog, mozilla::LogLevel::Warning,
 | |
|             ("WaitForProcessExit(%d): failed to OpenProcess", pid));
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Wait up to timeoutMS milliseconds for termination.
 | |
|   DWORD waitRv = WaitForSingleObjectEx(hProcess, timeoutMS, FALSE);
 | |
|   if (waitRv != WAIT_OBJECT_0) {
 | |
|     if (waitRv == WAIT_TIMEOUT) {
 | |
|       MOZ_LOG(
 | |
|           sUpdateLog, mozilla::LogLevel::Debug,
 | |
|           ("WaitForProcessExit(%d): timed out after %d MS", pid, timeoutMS));
 | |
|       return NS_ERROR_ABORT;
 | |
|     }
 | |
| 
 | |
|     MOZ_LOG(sUpdateLog, mozilla::LogLevel::Warning,
 | |
|             ("WaitForProcessExit(%d): unexpected error %lx", pid, waitRv));
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug,
 | |
|           ("WaitForProcessExit(%d): success", pid));
 | |
|   return NS_OK;
 | |
| #endif  // XP_WIN
 | |
| }
 |