Bug 1458314 - Move the update directory to an installation specific location r=rstrong
authorKirk Steuber <ksteuber@mozilla.com>
Tue, 23 Oct 2018 21:41:04 +0000
changeset 442649 3d22697d9c23a23087190225aa201a44bc1be130
parent 442648 e19cd8da255dd2fedd21c69a99545d4b5f0f1ef3
child 442650 bee1c344cb473d75347c012bbd0f8eac58428926
push id34916
push useraiakab@mozilla.com
push dateWed, 24 Oct 2018 04:14:42 +0000
treeherdermozilla-central@e61ca916da32 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrstrong
bugs1458314
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1458314 - Move the update directory to an installation specific location r=rstrong This change applies to Windows only. Firefox will need to migrate the directory from the old location to the new location. This will be done only once by setting the pref `app.update.migrated.updateDir2.<install path hash>` to `true` once migration has completed. Note: The pref name app.update.migrated.updateDir has already been used, thus the '2' suffix. It can be found in ESR24. This also removes the old handling fallback for generating the update directory path. Since xulrunner is no longer supported, this should no longer be needed. If neither the vendor nor app name are defined, it falls back to the literal string "Mozilla". The code to generate the update directory path and the installation hash have been moved to the updatecommon library. This will allow those functions to be used in Firefox, the Mozilla Maintenance Service, the Mozilla Maintenance Service Installer, and TestAUSHelper. Additionally, the function that generates the update directory path now has extra functionality. It creates the update directory, sets the permissions on it and, optionally, recursively sets the permissions on everything within. This patch adds functionality that allows Firefox to set permissions on the new update directory on write failure. It attempts to set the permissions itself and, if that fails and the maintenance service is enabled, it calls into the maintenance service to try from there. If a write fails and the permissions cannot be fixed, the user is prompted to reinstall. Differential Revision: https://phabricator.services.mozilla.com/D4249
browser/installer/windows/nsis/installer.nsi
ipc/mscom/MainThreadRuntime.cpp
toolkit/components/maintenanceservice/maintenanceservice.cpp
toolkit/components/maintenanceservice/serviceinstall.cpp
toolkit/components/maintenanceservice/workmonitor.cpp
toolkit/mozapps/installer/windows/nsis/common.nsh
toolkit/mozapps/update/UpdateListener.jsm
toolkit/mozapps/update/UpdateTelemetry.jsm
toolkit/mozapps/update/common/commonupdatedir.cpp
toolkit/mozapps/update/common/commonupdatedir.h
toolkit/mozapps/update/common/moz.build
toolkit/mozapps/update/common/updatedefines.h
toolkit/mozapps/update/common/win_dirent.cpp
toolkit/mozapps/update/common/win_dirent.h
toolkit/mozapps/update/nsIUpdateService.idl
toolkit/mozapps/update/nsUpdateService.js
toolkit/mozapps/update/nsUpdateServiceStub.js
toolkit/mozapps/update/tests/TestAUSHelper.cpp
toolkit/mozapps/update/tests/TestAUSReadStrings.cpp
toolkit/mozapps/update/tests/data/shared.js
toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js
toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
toolkit/mozapps/update/updater/updater-common.build
toolkit/mozapps/update/updater/win_dirent.cpp
toolkit/xre/moz.build
toolkit/xre/nsUpdateDriver.cpp
toolkit/xre/nsXREDirProvider.cpp
toolkit/xre/nsXREDirProvider.h
xpcom/base/nsWindowsHelpers.h
xpcom/build/nsXULAppAPI.h
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -347,16 +347,22 @@ Section "-Application" APP_IDX
     ${EndIf}
   ${EndIf}
 
   ; Default for creating Desktop shortcut (1 = create, 0 = don't create)
   ${If} $AddDesktopSC == ""
     StrCpy $AddDesktopSC "1"
   ${EndIf}
 
+  ${CreateUpdateDir} "Mozilla"
+  ${If} ${Errors}
+    Pop $0
+    ${LogMsg} "** ERROR Failed to create update directory: $0"
+  ${EndIf}
+
   ${LogHeader} "Adding Registry Entries"
   SetShellVarContext current  ; Set SHCTX to HKCU
   ${RegCleanMain} "Software\Mozilla"
   ${RegCleanUninstall}
   ${UpdateProtocolHandlers}
 
   ClearErrors
   WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" "Write Test"
--- a/ipc/mscom/MainThreadRuntime.cpp
+++ b/ipc/mscom/MainThreadRuntime.cpp
@@ -20,28 +20,16 @@
 #include "nsWindowsHelpers.h"
 #include "nsXULAppAPI.h"
 
 #include <accctrl.h>
 #include <aclapi.h>
 #include <objbase.h>
 #include <objidl.h>
 
-namespace {
-
-struct LocalFreeDeleter
-{
-  void operator()(void* aPtr)
-  {
-    ::LocalFree(aPtr);
-  }
-};
-
-} // anonymous namespace
-
 // This API from oleaut32.dll is not declared in Windows SDK headers
 extern "C" void __cdecl SetOaNoCache(void);
 
 namespace mozilla {
 namespace mscom {
 
 MainThreadRuntime* MainThreadRuntime::sInstance = nullptr;
 
--- a/toolkit/components/maintenanceservice/maintenanceservice.cpp
+++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp
@@ -277,26 +277,26 @@ SvcMain(DWORD argc, LPWSTR *argv)
 
   // Initialization complete and we're about to start working on
   // the actual command.  Report the service state as running to the SCM.
   ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
 
   // The service command was executed, stop logging and set an event
   // to indicate the work is done in case someone is waiting on a
   // service stop operation.
-  ExecuteServiceCommand(argc, argv);
+  BOOL success = ExecuteServiceCommand(argc, argv);
   LogFinish();
 
   SetEvent(gWorkDoneEvent);
 
   // If we aren't already in a stopping state then tell the SCM we're stopped
   // now.  If we are already in a stopping state then the SERVICE_STOPPED state
   // will be set by the SvcCtrlHandler.
   if (!gServiceControlStopping) {
-    ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
+    ReportSvcStatus(SERVICE_STOPPED, success ? NO_ERROR : 1, 0);
     StartTerminationThread();
   }
 }
 
 /**
  * Sets the current service status and reports it to the SCM.
  *
  * @param currentState  The current state (see SERVICE_STATUS)
--- a/toolkit/components/maintenanceservice/serviceinstall.cpp
+++ b/toolkit/components/maintenanceservice/serviceinstall.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/UniquePtr.h"
 
 #include "serviceinstall.h"
 #include "servicebase.h"
 #include "updatehelper.h"
 #include "shellapi.h"
 #include "readstrings.h"
 #include "errors.h"
+#include "commonupdatedir.h"
 
 #pragma comment(lib, "version.lib")
 
 // This uninstall key is defined originally in maintenanceservice_installer.nsi
 #define MAINT_UNINSTALL_KEY L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MozillaMaintenanceService"
 
 static BOOL
 UpdateUninstallerVersionString(LPWSTR versionString)
@@ -250,16 +251,25 @@ FixServicePath(SC_HANDLE service,
  * currently running process.
  *
  * @param  action The action to perform.
  * @return TRUE if the service was installed/upgraded
  */
 BOOL
 SvcInstall(SvcInstallAction action)
 {
+  mozilla::UniquePtr<wchar_t[]> updateDir;
+  HRESULT permResult = GetCommonUpdateDirectory(nullptr, nullptr, nullptr,
+                                                SetPermissionsOf::AllFilesAndDirs,
+                                                updateDir);
+  if (FAILED(permResult)) {
+    LOG_WARN(("Unable to set the permissions on the update directory ('%S'): %d",
+             updateDir.get(), permResult));
+  }
+
   // Get a handle to the local computer SCM database with full access rights.
   nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
                                                  SC_MANAGER_ALL_ACCESS));
   if (!schSCManager) {
     LOG_WARN(("Could not open service manager.  (%d)", GetLastError()));
     return FALSE;
   }
 
--- a/toolkit/components/maintenanceservice/workmonitor.cpp
+++ b/toolkit/components/maintenanceservice/workmonitor.cpp
@@ -22,16 +22,17 @@
 #include "workmonitor.h"
 #include "serviceinstall.h"
 #include "servicebase.h"
 #include "registrycertificates.h"
 #include "uachelper.h"
 #include "updatehelper.h"
 #include "pathhash.h"
 #include "errors.h"
+#include "commonupdatedir.h"
 
 #define PATCH_DIR_PATH L"\\updates\\0"
 
 // Wait 15 minutes for an update operation to run at most.
 // Updates usually take less than a minute so this seems like a
 // significantly large and safe amount of time to wait.
 static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000;
 BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
@@ -685,16 +686,25 @@ ExecuteServiceCommand(int argc, LPWSTR *
     if (!GetInstallationDir(argc - 3, argv + 3, installDir)) {
       LOG_WARN(("Could not get the installation directory"));
       if (!WriteStatusFailure(argv[4], SERVICE_INSTALLDIR_ERROR)) {
         LOG_WARN(("Could not write update.status for previous failure."));
       }
       return FALSE;
     }
 
+    mozilla::UniquePtr<wchar_t[]> updateDir;
+    HRESULT permResult = GetCommonUpdateDirectory(installDir, nullptr, nullptr,
+                                                  SetPermissionsOf::AllFilesAndDirs,
+                                                  updateDir);
+    if (FAILED(permResult)) {
+      LOG_WARN(("Unable to set the permissions on the update directory ('%S'): %d",
+               updateDir.get(), permResult));
+    }
+
     if (!DoesFallbackKeyExist()) {
       WCHAR maintenanceServiceKey[MAX_PATH + 1];
       if (CalculateRegistryPathFromFilePath(installDir, maintenanceServiceKey)) {
         LOG(("Checking for Maintenance Service registry. key: '%ls'",
              maintenanceServiceKey));
         HKEY baseKey = nullptr;
         if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                           maintenanceServiceKey, 0,
@@ -763,17 +773,35 @@ ExecuteServiceCommand(int argc, LPWSTR *
       result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3);
       DeleteSecureUpdater(secureUpdaterPath);
     }
 
     // We might not reach here if the service install succeeded
     // because the service self updates itself and the service
     // installer will stop the service.
     LOG(("Service command %ls complete.", argv[2]));
+  } else if (!lstrcmpi(argv[2], L"fix-update-directory-perms")) {
+    bool gotInstallDir = true;
+    mozilla::UniquePtr<wchar_t[]> updateDir;
+    if (argc <= 3) {
+      LOG_WARN(("Didn't get an install dir for fix-update-directory-perms"));
+      gotInstallDir = false;
+    }
+    HRESULT permResult = GetCommonUpdateDirectory(gotInstallDir ? argv[3]
+                                                                : nullptr,
+                           nullptr, nullptr,
+                           SetPermissionsOf::AllFilesAndDirs, updateDir);
+    if (FAILED(permResult)) {
+      LOG_WARN(("Unable to set the permissions on the update directory "
+                "('%S'): %d", updateDir.get(), permResult));
+      result = FALSE;
+    } else {
+      result = TRUE;
+    }
   } else {
     LOG_WARN(("Service command not recognized: %ls.", argv[2]));
     // result is already set to FALSE
   }
 
   LOG(("service command %ls complete with result: %ls.",
        argv[1], (result ? L"Success" : L"Failure")));
-  return TRUE;
+  return result;
 }
--- a/toolkit/mozapps/installer/windows/nsis/common.nsh
+++ b/toolkit/mozapps/installer/windows/nsis/common.nsh
@@ -3280,51 +3280,78 @@
           ; Concatenate the path $LOCALAPPDATA to the relative profile path and
           ; the relative path to $INSTDIR from $PROGRAMFILES
           StrCpy $R2 "$LOCALAPPDATA\$R7$R2"
           ${${_MOZFUNC_UN}GetLongPath} "$R2" $R2
 
           ${If} $R2 != ""
             ; Backup the old update directory logs and delete the directory
             ${If} ${FileExists} "$R2\updates\last-update.log"
-              Rename "$R2\updates\last-update.log" "$TEMP\moz-update-old-last-update.log"
+              Rename "$R2\updates\last-update.log" "$TEMP\moz-update-oldest-last-update.log"
             ${EndIf}
 
             ${If} ${FileExists} "$R2\updates\backup-update.log"
-              Rename "$R2\updates\backup-update.log" "$TEMP\moz-update-old-backup-update.log"
+              Rename "$R2\updates\backup-update.log" "$TEMP\moz-update-oldest-backup-update.log"
             ${EndIf}
 
             ${If} ${FileExists} "$R2\updates"
                 RmDir /r "$R2"
             ${EndIf}
           ${EndIf}
-
-          ; Get the taskbar ID hash for this installation path
-          ReadRegStr $R1 HKLM "SOFTWARE\$R7\TaskBarIDs" $R6
-          ${If} $R1 == ""
-            ReadRegStr $R1 HKCU "SOFTWARE\$R7\TaskBarIDs" $R6
+        ${EndIf}
+
+        ; Get the taskbar ID hash for this installation path
+        ReadRegStr $R1 HKLM "SOFTWARE\$R7\TaskBarIDs" $R6
+        ${If} $R1 == ""
+          ReadRegStr $R1 HKCU "SOFTWARE\$R7\TaskBarIDs" $R6
+        ${EndIf}
+
+        ; If the taskbar ID hash exists then delete the new update directory
+        ; Backup its logs before deleting it.
+        ${If} $R1 != ""
+          StrCpy $R0 "$LOCALAPPDATA\$R8\$R1"
+
+          ${If} ${FileExists} "$R0\updates\last-update.log"
+            Rename "$R0\updates\last-update.log" "$TEMP\moz-update-older-last-update.log"
+          ${EndIf}
+
+          ${If} ${FileExists} "$R0\updates\backup-update.log"
+            Rename "$R0\updates\backup-update.log" "$TEMP\moz-update-older-backup-update.log"
+          ${EndIf}
+
+          ; Remove the old updates directory, located in the user's Windows profile directory
+          ${If} ${FileExists} "$R0\updates"
+            RmDir /r "$R0"
           ${EndIf}
 
-          ; If the taskbar ID hash exists then delete the new update directory
-          ; Backup its logs before deleting it.
-          ${If} $R1 != ""
-            StrCpy $R0 "$LOCALAPPDATA\$R8\$R1"
-
-            ${If} ${FileExists} "$R0\updates\last-update.log"
-              Rename "$R0\updates\last-update.log" "$TEMP\moz-update-new-last-update.log"
-            ${EndIf}
-
-            ${If} ${FileExists} "$R0\updates\backup-update.log"
-              Rename "$R0\updates\backup-update.log" "$TEMP\moz-update-new-backup-update.log"
-            ${EndIf}
-
-            ; Remove the old updates directory
-            ${If} ${FileExists} "$R0\updates"
-              RmDir /r "$R0"
-            ${EndIf}
+          ; Get the new updates directory so we can remove that too
+          ; The new update directory is in the Program Data directory
+          ; (currently C:\ProgramData).
+          ; This system call gets that directory path. The arguments are:
+          ;   A null ptr for hwnd
+          ;   $R0 for the output string
+          ;   CSIDL_COMMON_APPDATA == 0x0023 == 35 for the csidl indicating which dir to get
+          ;   false for fCreate (i.e. Do not create the folder if it doesn't exist)
+          ; We could use %APPDATA% for this instead, but that requires state: the shell
+          ; var context would need to be saved, set, and reset. It is easier just to use
+          ; the system call.
+          System::Call "Shell32::SHGetSpecialFolderPathW(p 0, t.R0, i 35, i 0)"
+          StrCpy $R0 "$R0\$R8\$R1"
+
+          ${If} ${FileExists} "$R0\updates\last-update.log"
+            Rename "$R0\updates\last-update.log" "$TEMP\moz-update-newest-last-update.log"
+          ${EndIf}
+
+          ${If} ${FileExists} "$R0\updates\backup-update.log"
+            Rename "$R0\updates\backup-update.log" "$TEMP\moz-update-newest-backup-update.log"
+          ${EndIf}
+
+          ; Remove the new updates directory, which is shared by all users of the installation
+          ${If} ${FileExists} "$R0\updates"
+            RmDir /r "$R0"
           ${EndIf}
         ${EndIf}
       ${EndIf}
 
       ClearErrors
 
       Pop $R0
       Pop $R1
@@ -3371,16 +3398,102 @@
 
     !undef _MOZFUNC_UN
     !define _MOZFUNC_UN
     !verbose pop
   !endif
 !macroend
 
 /**
+ * Create the update directory and sets the permissions correctly
+ *
+ * @param   ROOT_DIR_NAME
+ *          The name of the update directory to be created in the common
+ *          application directory. For example, if ROOT_DIR_NAME is "Mozilla",
+ *          the created directory will be "C:\ProgramData\Mozilla".
+ *
+ * $R0 = Used for checking errors
+ * $R1 = The common application directory path
+ * $R9 = An error message to be returned on the stack
+ */
+!macro CreateUpdateDir ROOT_DIR_NAME
+  Push $R9
+  Push $R0
+  Push $R1
+
+  ; The update directory is in the Program Data directory
+  ; (currently C:\ProgramData).
+  ; This system call gets that directory path. The arguments are:
+  ;   A null ptr for hwnd
+  ;   $R1 for the output string
+  ;   CSIDL_COMMON_APPDATA == 0x0023 == 35 for the csidl indicating which dir to get
+  ;   true for fCreate (i.e. Do create the folder if it doesn't exist)
+  ; We could use %APPDATA% for this instead, but that requires state: the shell
+  ; var context would need to be saved, set, and reset. It is easier just to use
+  ; the system call.
+  System::Call "Shell32::SHGetSpecialFolderPathW(p 0, t.R1, i 35, i 1)"
+  StrCpy $R1 "$R1\${ROOT_DIR_NAME}"
+
+  ClearErrors
+  ${IfNot} ${FileExists} "$R1"
+    CreateDirectory "$R1"
+    ${If} ${Errors}
+      StrCpy $R9 "Unable to create directory: $R1"
+      GoTo end
+    ${EndIf}
+  ${EndIf}
+
+  ; Grant Full Access to the Builtin User group
+  AccessControl::SetOnFile "$R1" "(BU)" "FullAccess"
+  Pop $R0
+  ${If} $R0 == error
+    Pop $R9  ; Get AccessControl's Error Message
+    SetErrors
+    GoTo end
+  ${EndIf}
+
+  ; Grant Full Access to the Builtin Administrator group
+  AccessControl::SetOnFile "$R1" "(BA)" "FullAccess"
+  Pop $R0
+  ${If} $R0 == error
+    Pop $R9  ; Get AccessControl's Error Message
+    SetErrors
+    GoTo end
+  ${EndIf}
+
+  ; Grant Full Access to the SYSTEM user
+  AccessControl::SetOnFile "$R1" "(SY)" "FullAccess"
+  Pop $R0
+  ${If} $R0 == error
+    Pop $R9  ; Get AccessControl's Error Message
+    SetErrors
+    GoTo end
+  ${EndIf}
+
+  ; Remove inherited permissions
+  AccessControl::DisableFileInheritance "$R1"
+  Pop $R0
+  ${If} $R0 == error
+    Pop $R9  ; Get AccessControl's Error Message
+    SetErrors
+    GoTo end
+  ${EndIf}
+
+end:
+  Pop $R1
+  Pop $R0
+  ${If} ${Errors}
+    Exch $R9
+  ${Else}
+    Pop $R9
+  ${EndIf}
+!macroend
+!define CreateUpdateDir "!insertmacro CreateUpdateDir"
+
+/**
  * Deletes all relative profiles specified in an application's profiles.ini and
  * performs various other cleanup.
  *
  * @param   _REL_PROFILE_PATH
  *          The relative path to the profile directory.
  *
  * $R6 = value of IsRelative read from profiles.ini
  * $R7 = value of Path to profile read from profiles.ini
--- a/toolkit/mozapps/update/UpdateListener.jsm
+++ b/toolkit/mozapps/update/UpdateListener.jsm
@@ -141,16 +141,17 @@ var UpdateListener = {
         this.showRestartNotification(update, false);
         break;
       case "elevation-attempts-exceeded":
         this.clearCallbacks();
         this.showManualUpdateNotification(update, false);
         break;
       case "check-attempts-exceeded":
       case "unknown":
+      case "bad-perms":
         // Background update has failed, let's show the UI responsible for
         // prompting the user to update manually.
         this.clearCallbacks();
         this.showManualUpdateNotification(update, false);
         break;
     }
   },
 
--- a/toolkit/mozapps/update/UpdateTelemetry.jsm
+++ b/toolkit/mozapps/update/UpdateTelemetry.jsm
@@ -81,16 +81,18 @@ var AUSTLMY = {
   // Update elevation failures or cancelations threshold reached for this
   // version, OSX only (no notification)
   CHK_ELEVATION_DISABLED_FOR_VERSION: 35,
   // User opted out of elevated updates for the available update version, OSX
   // only (no notification)
   CHK_ELEVATION_OPTOUT_FOR_VERSION: 36,
   // Update checks disabled by enterprise policy
   CHK_DISABLED_BY_POLICY: 37,
+  // Update check failed due to write error
+  CHK_ERR_WRITE_FAILURE: 38,
 
   /**
    * Submit a telemetry ping for the update check result code or a telemetry
    * ping for a count type histogram count when no update was found. The no
    * update found ping is separate since it is the typical result, is less
    * interesting than the other result codes, and it is easier to analyze the
    * other codes without including it.
    *
@@ -168,16 +170,17 @@ var AUSTLMY = {
   DWNLD_ERR_NO_UPDATE_PATCH: 6,
   DWNLD_ERR_PATCH_SIZE_LARGER: 8,
   DWNLD_ERR_PATCH_SIZE_NOT_EQUAL: 9,
   DWNLD_ERR_BINDING_ABORTED: 10,
   DWNLD_ERR_ABORT: 11,
   DWNLD_ERR_DOCUMENT_NOT_CACHED: 12,
   DWNLD_ERR_VERIFY_NO_REQUEST: 13,
   DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL: 14,
+  DWNLD_ERR_WRITE_FAILURE: 15,
 
   /**
    * Submit a telemetry ping for the update download result code.
    *
    * @param  aIsComplete
    *         If true the histogram is for a patch type complete, if false the
    *         histogram is for a patch type partial, and when undefined the
    *         histogram is for an unknown patch type. This is used to determine
@@ -200,16 +203,20 @@ var AUSTLMY = {
       let id = "UPDATE_DOWNLOAD_CODE_" + patchType;
       // enumerated type histogram
       Services.telemetry.getHistogramById(id).add(aCode);
     } catch (e) {
       Cu.reportError(e);
     }
   },
 
+  // Previous state codes are defined in pingStateAndStatusCodes() in
+  // nsUpdateService.js
+  STATE_WRITE_FAILURE: 14,
+
   /**
    * Submit a telemetry ping for the update status state code.
    *
    * @param  aSuffix
    *         The histogram id suffix for histogram IDs:
    *         UPDATE_STATE_CODE_COMPLETE_STARTUP
    *         UPDATE_STATE_CODE_PARTIAL_STARTUP
    *         UPDATE_STATE_CODE_UNKNOWN_STARTUP
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/common/commonupdatedir.cpp
@@ -0,0 +1,1138 @@
+/* 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/. */
+
+/*
+ * This file does not use many of the features Firefox provides such as
+ * nsAString and nsIFile because code in this file will be included not only
+ * in Firefox, but also in the Mozilla Maintenance Service, the Mozilla
+ * Maintenance Service installer, and TestAUSHelper.
+ */
+
+#include <cinttypes>
+#include <cwchar>
+#include <string>
+#include "city.h"
+#include "commonupdatedir.h"
+#include "updatedefines.h"
+
+#ifdef XP_WIN
+#include <accctrl.h>
+#include <aclapi.h>
+#include <cstdarg>
+#include <errno.h>
+#include <objbase.h>
+#include <shellapi.h>
+#include <shlobj.h>
+#include <strsafe.h>
+#include <winerror.h>
+#include "nsWindowsHelpers.h"
+#include "win_dirent.h"
+#endif
+
+#ifdef XP_WIN
+// This is the name of the directory to be put in the application data directory
+// if no vendor or application name is specified.
+// (i.e. C:\ProgramData\<FALLBACK_VENDOR_NAME>)
+#define FALLBACK_VENDOR_NAME "Mozilla"
+// This describes the directory between the "Mozilla" directory and the install
+// path hash (i.e. C:\ProgramData\Mozilla\<UPDATE_PATH_MID_DIR_NAME>\<hash>)
+#define UPDATE_PATH_MID_DIR_NAME "updates"
+// This describes the directory between the update directory and the patch
+// directory.
+// (i.e. C:\ProgramData\Mozilla\updates\<hash>\<UPDATE_SUBDIRECTORY>\0)
+#define UPDATE_SUBDIRECTORY "updates"
+// This defines the leaf update directory, where the MAR file is downloaded to
+// (i.e. C:\ProgramData\Mozilla\updates\<hash>\updates\<PATCH_DIRECTORY>)
+#define PATCH_DIRECTORY "0"
+
+enum class WhichUpdateDir {
+  CommonAppData,
+  UserAppData,
+};
+
+/**
+ * This is a very simple string class.
+ *
+ * This class has some substantial limitations for the sake of simplicity. It
+ * has no support whatsoever for modifying a string that already has data. There
+ * is, therefore, no append function and no support for automatically resizing
+ * strings.
+ *
+ * Error handling is also done in a slightly unusual manner. If there is ever
+ * a failure allocating or assigning to a string, it will do the simplest
+ * possible recovery: truncate itself to 0-length.
+ * This coupled with the fact that the length is cached means that an effective
+ * method of error checking is to attempt assignment and then check the length
+ * of the result.
+ */
+class SimpleAutoString
+{
+private:
+  size_t mLength;
+  // Unique pointer frees the buffer when the class is deleted or goes out of
+  // scope.
+  mozilla::UniquePtr<wchar_t[]> mString;
+
+  /**
+   * Allocates enough space to store a string of the specified length.
+   */
+  bool AllocLen(size_t len) {
+    mString = mozilla::MakeUnique<wchar_t[]>(len + 1);
+    return mString.get() != nullptr;
+  }
+
+  /**
+   * Allocates a buffer of the size given.
+   */
+  bool AllocSize(size_t size) {
+    mString = mozilla::MakeUnique<wchar_t[]>(size);
+    return mString.get() != nullptr;
+  }
+
+public:
+  SimpleAutoString()
+  : mLength(0)
+  {
+  }
+
+  /*
+   * Allocates enough space for a string of the given length and formats it as
+   * an empty string.
+   */
+  bool AllocEmpty(size_t len) {
+    bool success = AllocLen(len);
+    Truncate();
+    return success;
+  }
+
+  /**
+   * These functions can potentially return null if no buffer has yet been
+   * allocated.
+   */
+  wchar_t* MutableString() {
+    return mString.get();
+  }
+  const wchar_t* String() const {
+    return mString.get();
+  }
+
+  size_t Length() const {
+    return mLength;
+  }
+
+  void SwapBufferWith(mozilla::UniquePtr<wchar_t[]>& other) {
+    mString.swap(other);
+    if (mString) {
+      mLength = wcslen(mString.get());
+    } else {
+      mLength = 0;
+    }
+  }
+
+  void Truncate() {
+    mLength = 0;
+    if (mString) {
+      mString.get()[0] = L'\0';
+    }
+  }
+
+  /**
+   * Assigns a string and ensures that the resulting string is valid and has its
+   * length set properly.
+   *
+   * Note that although other similar functions in this class take length, this
+   * function takes buffer size instead because it is intended to be follow the
+   * input convention of sprintf.
+   *
+   * Returns the new length, which will be 0 if there was any failure.
+   *
+   * This function does no allocation or reallocation. If the buffer is not
+   * large enough to hold the new string, the call will fail.
+   */
+  size_t AssignSprintf(size_t bufferSize, const wchar_t* format, ...) {
+    va_list ap;
+    va_start(ap, format);
+    size_t returnValue = AssignVsprintf(bufferSize, format, ap);
+    va_end(ap);
+    return returnValue;
+  }
+  /**
+   * Same as the above, but takes a va_list like vsprintf does.
+   */
+  size_t AssignVsprintf(size_t bufferSize, const wchar_t* format, va_list ap) {
+    if (!mString) {
+      Truncate();
+      return 0;
+    }
+
+    int charsWritten = vswprintf(mString.get(), bufferSize, format, ap);
+    if (charsWritten < 0 || static_cast<size_t>(charsWritten) >= bufferSize) {
+      // charsWritten does not include the null terminator. If charsWritten is
+      // equal to the buffer size, we do not have a null terminator nor do we
+      // have room for one.
+      Truncate();
+      return 0;
+    }
+    mString.get()[charsWritten] = L'\0';
+
+    mLength = charsWritten;
+    return mLength;
+  }
+
+  /**
+   * Allocates enough space for the string and assigns a value to it with
+   * sprintf. Takes, as an argument, the maximum length that the string is
+   * expected to use (which, after adding 1 for the null byte, is the amount of
+   * space that will be allocated).
+   *
+   * Returns the new length, which will be 0 on any failure.
+   */
+  size_t AllocAndAssignSprintf(size_t maxLength, const wchar_t* format, ...) {
+    if (!AllocLen(maxLength)) {
+      Truncate();
+      return 0;
+    }
+    va_list ap;
+    va_start(ap, format);
+    size_t charsWritten = AssignVsprintf(maxLength + 1, format, ap);
+    va_end(ap);
+    return charsWritten;
+  }
+
+  /*
+   * Allocates enough for the formatted text desired. Returns maximum storable
+   * length of a string in the allocated buffer on success, or 0 on failure.
+   */
+  size_t AllocFromScprintf(const wchar_t* format, ...) {
+    va_list ap;
+    va_start(ap, format);
+    size_t returnValue = AllocFromVscprintf(format, ap);
+    va_end(ap);
+    return returnValue;
+  }
+  /**
+   * Same as the above, but takes a va_list like vscprintf does.
+   */
+  size_t AllocFromVscprintf(const wchar_t* format, va_list ap) {
+    int len = _vscwprintf(format, ap);
+    if (len < 0) {
+      Truncate();
+      return 0;
+    }
+    if(!AllocEmpty(len)) {
+      // AllocEmpty will Truncate, no need to call it here.
+      return 0;
+    }
+    return len;
+  }
+
+  /**
+   * Automatically determines how much space is necessary, allocates that much
+   * for the string, and assigns the data using swprintf. Returns the resulting
+   * length of the string, which will be 0 if the function fails.
+   */
+  size_t AutoAllocAndAssignSprintf(const wchar_t* format, ...) {
+    va_list ap;
+    va_start(ap, format);
+    size_t len = AllocFromVscprintf(format, ap);
+    va_end(ap);
+    if (len == 0) {
+      // AllocFromVscprintf will Truncate, no need to call it here.
+      return 0;
+    }
+
+    va_start(ap, format);
+    size_t charsWritten = AssignVsprintf(len + 1, format, ap);
+    va_end(ap);
+
+    if (len != charsWritten) {
+      Truncate();
+      return 0;
+    }
+    return charsWritten;
+  }
+
+  /**
+   * The following CopyFrom functions take various types of strings, allocate
+   * enough space to hold them, and then copy them into that space.
+   *
+   * They return an HRESULT that should be interpreted with the SUCCEEDED or
+   * FAILED macro.
+   */
+  HRESULT CopyFrom(const wchar_t* src) {
+    mLength = wcslen(src);
+    if (!AllocLen(mLength)) {
+      Truncate();
+      return E_OUTOFMEMORY;
+    }
+    HRESULT hrv = StringCchCopyW(mString.get(), mLength + 1, src);
+    if (FAILED(hrv)) {
+      Truncate();
+    }
+    return hrv;
+  }
+  HRESULT CopyFrom(const SimpleAutoString& src) {
+    if (!src.mString) {
+      Truncate();
+      return S_OK;
+    }
+    mLength = src.mLength;
+    if (!AllocLen(mLength)) {
+      Truncate();
+      return E_OUTOFMEMORY;
+    }
+    HRESULT hrv = StringCchCopyW(mString.get(), mLength + 1, src.mString.get());
+    if (FAILED(hrv)) {
+      Truncate();
+    }
+    return hrv;
+  }
+  HRESULT CopyFrom(const char* src) {
+    int bufferSize = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1,
+                                         nullptr, 0);
+    if (bufferSize == 0) {
+      Truncate();
+      return HRESULT_FROM_WIN32(GetLastError());
+    }
+    if (!AllocSize(bufferSize)) {
+      Truncate();
+      return E_OUTOFMEMORY;
+    }
+    int charsWritten = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src,
+                                           -1, mString.get(), bufferSize);
+    if (charsWritten == 0) {
+      Truncate();
+      return HRESULT_FROM_WIN32(GetLastError());
+    } else if (charsWritten != bufferSize) {
+      Truncate();
+      return E_FAIL;
+    }
+    mLength = charsWritten - 1;
+    return S_OK;
+  }
+
+  bool StartsWith(const SimpleAutoString& prefix) const {
+    if (!mString) {
+      return (prefix.mLength == 0);
+    }
+    if (!prefix.mString) {
+      return true;
+    }
+    if (prefix.mLength > mLength) {
+      return false;
+    }
+    return (wcsncmp(mString.get(), prefix.mString.get(), prefix.mLength) == 0);
+  }
+};
+
+// Deleter for use with UniquePtr
+struct CoTaskMemFreeDeleter
+{
+  void operator()(void* aPtr)
+  {
+    ::CoTaskMemFree(aPtr);
+  }
+};
+
+/**
+ * A lot of data goes into constructing an ACL and security attributes, and the
+ * Windows documentation does not make it very clear what can be safely freed
+ * after these objects are constructed. This struct holds all of the
+ * construction data in one place so that it can be passed around and freed
+ * properly.
+ */
+struct AutoPerms
+{
+  SID_IDENTIFIER_AUTHORITY sidIdentifierAuthority;
+  nsAutoSid usersSID;
+  nsAutoSid adminsSID;
+  nsAutoSid systemSID;
+  EXPLICIT_ACCESS_W ea[3];
+  mozilla::UniquePtr<ACL, LocalFreeDeleter> acl;
+  mozilla::UniquePtr<uint8_t[]> securityDescriptorBuffer;
+  PSECURITY_DESCRIPTOR securityDescriptor;
+  SECURITY_ATTRIBUTES securityAttributes;
+};
+
+static bool GetCachedHash(const char16_t* installPath, HKEY rootKey,
+                          const SimpleAutoString& regPath,
+                          mozilla::UniquePtr<NS_tchar[]>& result);
+static HRESULT GetUpdateDirectory(const wchar_t* installPath,
+                                  const char* vendor,
+                                  const char* appName,
+                                  WhichUpdateDir whichDir,
+                                  SetPermissionsOf permsToSet,
+                                  mozilla::UniquePtr<wchar_t[]>& result);
+static HRESULT SetUpdateDirectoryPermissions(const SimpleAutoString& basePath,
+                                             const SimpleAutoString& updatePath,
+                                             bool fullUpdatePath,
+                                             SetPermissionsOf permsToSet);
+static HRESULT GeneratePermissions(AutoPerms& result);
+static HRESULT SetPathPerms(SimpleAutoString& path, const AutoPerms& perms);
+static HRESULT MoveConflicting(const SimpleAutoString& path);
+static HRESULT SetPermissionsOfContents(const SimpleAutoString& basePath,
+                                        const SimpleAutoString& leafUpdateDir,
+                                        const AutoPerms& perms);
+static bool PathConflictsWithLeaf(const SimpleAutoString& path,
+                                  DWORD pathAttributes,
+                                  bool permsSuccessfullySet,
+                                  const SimpleAutoString& leafPath);
+#endif // XP_WIN
+
+/**
+ * Returns a hash of the install path, suitable for uniquely identifying the
+ * particular Firefox installation that is running.
+ *
+ * This function includes a compatibility mode that should NOT be used except by
+ * GetUserUpdateDirectory. Previous implementations of this function could
+ * return a value inconsistent with what our installer would generate. When the
+ * update directory was migrated, this function was re-implemented to return
+ * values consistent with those generated by the installer. The compatibility
+ * mode is retained only so that we can properly get the old update directory
+ * when migrating it.
+ *
+ * @param installPath The null-terminated path to the installation directory
+ *                    (i.e. the directory that contains the binary). Must not be
+ *                    null. The path must not include a trailing slash.
+ * @param vendor A pointer to a null-terminated string containing the vendor
+ *               name, or null. This is only used to look up a registry key on
+ *               Windows. On other platforms, the value has no effect. If null
+ *               is passed on Windows, "Mozilla" will be used.
+ * @param result The out parameter that will be set to contain the resulting
+ *               hash. The value is wrapped in a UniquePtr to make cleanup
+ *               easier on the caller.
+ * @param useCompatibilityMode Enables compatibiliy mode. Defaults to false.
+ * @return NS_OK, if successful.
+ */
+nsresult
+GetInstallHash(const char16_t* installPath, const char* vendor,
+               mozilla::UniquePtr<NS_tchar[]>& result,
+               bool useCompatibilityMode /* = false */)
+{
+  MOZ_ASSERT(installPath != nullptr,
+             "Install path must not be null in GetInstallHash");
+
+#ifdef XP_WIN
+  // The Windows installer caches this hash value in the registry
+  SimpleAutoString regPath;
+  regPath.AutoAllocAndAssignSprintf(L"SOFTWARE\\%S\\%S\\TaskBarIDs",
+                                    vendor ? vendor : "Mozilla",
+                                    MOZ_APP_BASENAME);
+  if (regPath.Length() != 0) {
+    bool gotCachedHash = GetCachedHash(installPath, HKEY_LOCAL_MACHINE, regPath,
+                                       result);
+    if (gotCachedHash) {
+      return NS_OK;
+    }
+    gotCachedHash = GetCachedHash(installPath, HKEY_CURRENT_USER, regPath,
+                                  result);
+    if (gotCachedHash) {
+      return NS_OK;
+    }
+  }
+#endif
+
+  // Unable to get the cached hash, so compute it.
+  size_t pathSize = std::char_traits<char16_t>::length(installPath) *
+                    sizeof(*installPath);
+  uint64_t hash = CityHash64(reinterpret_cast<const char*>(installPath),
+                             pathSize);
+
+  size_t hashStrSize = sizeof(hash) * 2 + 1; // 2 hex digits per byte + null
+  result = mozilla::MakeUnique<NS_tchar[]>(hashStrSize);
+  int charsWritten;
+  if (useCompatibilityMode) {
+    // This behavior differs slightly from the default behavior.
+    // When the default output would be "1234567800000009", this instead
+    // produces "123456789".
+    charsWritten = NS_tsnprintf(result.get(), hashStrSize,
+                                NS_T("%") NS_T(PRIX32) NS_T("%") NS_T(PRIX32),
+                                static_cast<uint32_t>(hash >> 32),
+                                static_cast<uint32_t>(hash));
+  } else {
+    charsWritten = NS_tsnprintf(result.get(), hashStrSize,
+                                NS_T("%") NS_T(PRIX64), hash);
+  }
+  if (charsWritten < 1 || static_cast<size_t>(charsWritten) > hashStrSize - 1) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+#ifdef XP_WIN
+/**
+ * Returns true if the registry key was successfully found and read into result.
+ */
+static bool
+GetCachedHash(const char16_t* installPath, HKEY rootKey,
+              const SimpleAutoString& regPath,
+              mozilla::UniquePtr<NS_tchar[]>& result)
+{
+  // Find the size of the string we are reading before we read it so we can
+  // allocate space.
+  unsigned long bufferSize;
+  LSTATUS lrv = RegGetValueW(rootKey, regPath.String(),
+                             reinterpret_cast<const wchar_t*>(installPath),
+                             RRF_RT_REG_SZ, nullptr, nullptr, &bufferSize);
+  if (lrv != ERROR_SUCCESS) {
+    return false;
+  }
+  result = mozilla::MakeUnique<NS_tchar[]>(bufferSize);
+  if (!result) {
+    return false;
+  }
+  // Now read the actual value from the registry.
+  lrv = RegGetValueW(rootKey, regPath.String(),
+                     reinterpret_cast<const wchar_t*>(installPath),
+                     RRF_RT_REG_SZ, nullptr, result.get(), &bufferSize);
+  return (lrv == ERROR_SUCCESS);
+}
+
+/**
+ * Returns the update directory path. The update directory needs to have
+ * different permissions from the default, so we don't really want anyone using
+ * the path without the directory already being created with the correct
+ * permissions. Therefore, this function also ensures that the base directory
+ * that needs permissions set already exists. If it does not exist, it is
+ * created with the needed permissions.
+ * The desired permissions give Full Control to SYSTEM, Administrators, and
+ * Users.
+ *
+ * vendor and appName are passed as char*, not because we want that (we don't,
+ * we want wchar_t), but because of the expected origin of the data. If this
+ * data is available, it is probably available via XREAppData::vendor and
+ * XREAppData::name.
+ *
+ * @param installPath The null-terminated path to the installation directory
+ *                    (i.e. the directory that contains the binary). The path
+ *                    must not include a trailing slash. If null is passed for
+ *                    this value, the entire update directory path cannot be
+ *                    retrieved, so the function will return the update
+ *                    directory without the installation-specific leaf
+ *                    directory. This feature exists for when the caller wants
+ *                    to use this function to set directory permissions and does
+ *                    not need the full update directory path.
+ * @param vendor A pointer to a null-terminated string containing the vendor
+ *               name. Will default to "Mozilla" if null is passed.
+ * @param appName A pointer to a null-terminated string containing the
+ *                application name, or null.
+ * @param permsToSet Determines how aggressive to be when setting permissions.
+ *                   This is the behavior by value:
+ *                     BaseDir - Sets the permissions on the base directory
+ *                               (Most likely C:\ProgramData\Mozilla)
+ *                     BaseDirIfNotExists - Sets the permissions on the base
+ *                                          directory, but only if it does not
+ *                                          already exist.
+ *                     AllFilesAndDirs - Recurses through the base directory,
+ *                                       setting the permissions on all files
+ *                                       and directories contained. Symlinks
+ *                                       are removed. Files with names
+ *                                       conflicting with the creation of the
+ *                                       update directory are moved or removed.
+ * @param result The out parameter that will be set to contain the resulting
+ *               path. The value is wrapped in a UniquePtr to make cleanup
+ *               easier on the caller.
+ *
+ * @return An HRESULT that should be tested with SUCCEEDED or FAILED.
+ */
+HRESULT
+GetCommonUpdateDirectory(const wchar_t* installPath,
+                         const char* vendor,
+                         const char* appName,
+                         SetPermissionsOf permsToSet,
+                         mozilla::UniquePtr<wchar_t[]>& result)
+{
+  return GetUpdateDirectory(installPath,
+                            vendor,
+                            appName,
+                            WhichUpdateDir::CommonAppData,
+                            permsToSet,
+                            result);
+}
+
+/**
+ * This function is identical to the function above except that it gets the
+ * "old" (pre-migration) update directory that is located in the user's app data
+ * directory, rather than the new one in the common app data directory.
+ *
+ * The other difference is that this function does not create or change the
+ * permissions of the update directory since the default permissions on this
+ * directory are acceptable as they are.
+ */
+HRESULT
+GetUserUpdateDirectory(const wchar_t* installPath,
+                       const char* vendor,
+                       const char* appName,
+                       mozilla::UniquePtr<wchar_t[]>& result)
+{
+  return GetUpdateDirectory(installPath,
+                            vendor,
+                            appName,
+                            WhichUpdateDir::UserAppData,
+                            SetPermissionsOf::BaseDir, // Arbitrary value
+                            result);
+}
+
+/**
+ * This is a helper function that does all of the work for
+ * GetCommonUpdateDirectory and GetUserUpdateDirectory. It partially exists to
+ * prevent callers of GetUserUpdateDirectory from having to pass a useless
+ * SetPermissionsOf argument, which will be ignored if whichDir is UserAppData.
+ *
+ * For information on the parameters and return value, see
+ * GetCommonUpdateDirectory.
+ */
+static HRESULT
+GetUpdateDirectory(const wchar_t* installPath,
+                   const char* vendor,
+                   const char* appName,
+                   WhichUpdateDir whichDir,
+                   SetPermissionsOf permsToSet,
+                   mozilla::UniquePtr<wchar_t[]>& result)
+{
+  PWSTR programDataPath;
+  REFKNOWNFOLDERID folderID =
+    (whichDir == WhichUpdateDir::CommonAppData) ? FOLDERID_ProgramData
+                                                : FOLDERID_LocalAppData;
+  HRESULT hrv = SHGetKnownFolderPath(folderID, KF_FLAG_CREATE, nullptr,
+                                     &programDataPath);
+  // Free programDataPath when it goes out of scope.
+  mozilla::UniquePtr<wchar_t, CoTaskMemFreeDeleter>
+    programDataPathUnique(programDataPath);
+  if (FAILED(hrv)) {
+    return hrv;
+  }
+
+  SimpleAutoString baseDir;
+  if (vendor || appName) {
+    const char* rawBaseDir = vendor ? vendor : appName;
+    hrv = baseDir.CopyFrom(rawBaseDir);
+  } else {
+    const wchar_t baseDirLiteral[] = NS_T(FALLBACK_VENDOR_NAME);
+    hrv = baseDir.CopyFrom(baseDirLiteral);
+  }
+  if (FAILED(hrv)) {
+    return hrv;
+  }
+
+  // Generate the base path (C:\ProgramData\Mozilla)
+  SimpleAutoString basePath;
+  size_t basePathLen = wcslen(programDataPath) + 1 /* path separator */ +
+                       baseDir.Length();
+  basePath.AllocAndAssignSprintf(basePathLen, L"%s\\%s", programDataPath,
+                                 baseDir.String());
+  if (basePath.Length() != basePathLen) {
+    return E_FAIL;
+  }
+
+  // Generate the update directory path. This is the value to be returned by
+  // this function.
+  SimpleAutoString updatePath;
+  if (installPath) {
+    mozilla::UniquePtr<NS_tchar[]> hash;
+    bool useCompatibilityMode = (whichDir == WhichUpdateDir::UserAppData);
+    nsresult rv = GetInstallHash(reinterpret_cast<const char16_t*>(installPath),
+                                 vendor, hash, useCompatibilityMode);
+    if (NS_SUCCEEDED(rv)) {
+      const wchar_t midPathDirName[] = NS_T(UPDATE_PATH_MID_DIR_NAME);
+      size_t updatePathLen = basePath.Length() + 1 /* path separator */ +
+                             wcslen(midPathDirName) + 1 /* path separator */ +
+                             wcslen(hash.get());
+      updatePath.AllocAndAssignSprintf(updatePathLen, L"%s\\%s\\%s",
+                                       basePath.String(), midPathDirName,
+                                       hash.get());
+      // Permissions can still be set without this string, so wait until after
+      // setting permissions to return failure if the string assignment failed.
+    }
+  }
+
+  if (whichDir == WhichUpdateDir::CommonAppData) {
+    if (updatePath.Length() > 0) {
+      hrv = SetUpdateDirectoryPermissions(basePath, updatePath, true,
+                                          permsToSet);
+    } else {
+      hrv = SetUpdateDirectoryPermissions(basePath, basePath, false,
+                                          permsToSet);
+    }
+    if (FAILED(hrv)) {
+      return hrv;
+    }
+  }
+
+  if (!installPath) {
+    basePath.SwapBufferWith(result);
+    return S_OK;
+  }
+
+  if (updatePath.Length() == 0) {
+    return E_FAIL;
+  }
+  updatePath.SwapBufferWith(result);
+  return S_OK;
+}
+
+/**
+ * If the basePath does not exist, it is created. If it does exist, it and its
+ * contents may have their permissions reset based on the value of permsToSet.
+ *
+ * This function tries to set as many permissions as possible, even if an error
+ * is encountered along the way. Any encountered error eventually causes the
+ * function to return failure, but does not stop the execution of the function.
+ *
+ * @param basePath The top directory within the application data directory.
+ *                 Typically "C:\ProgramData\Mozilla".
+ * @param updatePath The update directory to be checked for conflicts. If files
+ *                   conflicting with this directory structure exist, they may
+ *                   be moved or deleted depending on the value of permsToSet.
+ * @param fullUpdatePath Set to true if updatePath is the full update path. If
+ *                       set to false, it means that we don't have the
+ *                       installation-specific path component.
+ * @param permsToSet See the documentation for GetCommonUpdateDirectory for the
+ *                   descriptions of the effects of each SetPermissionsOf value.
+ */
+static HRESULT
+SetUpdateDirectoryPermissions(const SimpleAutoString& basePath,
+                              const SimpleAutoString& updatePath,
+                              bool fullUpdatePath,
+                              SetPermissionsOf permsToSet)
+{
+  HRESULT returnValue = S_OK; // Stores the value that will eventually be
+                              // returned. If errors occur, this is set to the
+                              // first error encountered.
+  DWORD attributes = GetFileAttributesW(basePath.String());
+  // validBaseDir will be true if the basePath exists, and is a non-symlinked
+  // directory.
+  bool validBaseDir = attributes != INVALID_FILE_ATTRIBUTES &&
+                      attributes & FILE_ATTRIBUTE_DIRECTORY &&
+                      !(attributes & FILE_ATTRIBUTE_REPARSE_POINT);
+
+  // The most common case when calling this function is when the caller of
+  // GetCommonUpdateDirectory just wants the update directory path, and passes
+  // in the least aggressive option for setting permissions.
+  // The most common environment is that the update directory already exists.
+  // Optimize for this case.
+  if (permsToSet == SetPermissionsOf::BaseDirIfNotExists && validBaseDir) {
+    return S_OK;
+  }
+
+  AutoPerms perms;
+  HRESULT hrv = GeneratePermissions(perms);
+  if (FAILED(hrv)) {
+    // Fatal error. There is no real way to recover from this.
+    return hrv;
+  }
+
+  // If the base directory looks ok, we want to ensure that the permissions on
+  // it are correct (we already exited early in the case where we don't need to
+  // check permissions).
+  // If the base directory doesn't look right, we may as well attempt to fix the
+  // permissions before we attempt to get rid of the conflicting file.
+  // Either way, attempt to set the permissions here.
+  // Annoyingly, setting the permissions requires a mutable string.
+  SimpleAutoString mutableBasePath;
+  mutableBasePath.CopyFrom(basePath);
+  if (mutableBasePath.Length() == 0) {
+    // If we don't have a valid base directory, we are about to try to recreate
+    // it, in which case we only care about the success of creating the base
+    // directory. But if we aren't doing that, we are failing here by not
+    // successfully setting the directory's permissions.
+    if (validBaseDir) {
+      returnValue = FAILED(returnValue) ? returnValue : E_OUTOFMEMORY;
+    }
+  } else {
+    hrv = SetPathPerms(mutableBasePath, perms);
+    // Don't set returnValue for this error. The error means that that path is
+    // inaccessible, but if we can successfuly move/remove it, then the function
+    // will have done its job.
+    if (FAILED(hrv)) {
+      validBaseDir = false;
+    }
+  }
+
+  if (!validBaseDir) {
+    MoveConflicting(basePath);
+    // Don't bother checking the error here. Just check once for whether we
+    // created the directory or not.
+
+    // Now that there is nothing in the way of our base directory, create it.
+    BOOL success = CreateDirectoryW(basePath.String(),
+                                    &perms.securityAttributes);
+    if (success) {
+      // We successfully created a directory. It is safe to assume that it is
+      // empty, so there is no reason to check the permissions of its contents.
+      return returnValue;
+    } else {
+      returnValue = FAILED(returnValue) ? returnValue
+                                        : HRESULT_FROM_WIN32(GetLastError());
+    }
+  }
+
+  // We have now done our best to ensure that the base directory exists and has
+  // the right permissions set. We only need to continue if we are setting the
+  // permissions on all contained files.
+  if (permsToSet != SetPermissionsOf::AllFilesAndDirs) {
+    return returnValue;
+  }
+
+  if (fullUpdatePath) {
+    // When we are doing a full permissions reset, we are also ensuring that no
+    // files are in the way of our required directory structure. Generate the
+    // path of the furthest leaf in our directory structure so that we can check
+    // for conflicting files.
+    SimpleAutoString leafDirPath;
+    wchar_t updateSubdirectoryName[] = NS_T(UPDATE_SUBDIRECTORY);
+    wchar_t patchDirectoryName[] = NS_T(PATCH_DIRECTORY);
+    size_t leafDirLen = updatePath.Length() + wcslen(updateSubdirectoryName) +
+                        wcslen(patchDirectoryName) + 2; /* 2 path seperators */
+    leafDirPath.AllocAndAssignSprintf(leafDirLen, L"%s\\%s\\%s",
+                                      updatePath.String(),
+                                      updateSubdirectoryName,
+                                      patchDirectoryName);
+    if (leafDirPath.Length() == leafDirLen) {
+      hrv = SetPermissionsOfContents(basePath, leafDirPath, perms);
+    } else {
+      // If we cannot generate the leaf path, just do the best we can by using
+      // the updatePath.
+      returnValue = FAILED(returnValue) ? returnValue : E_FAIL;
+      hrv = SetPermissionsOfContents(basePath, updatePath, perms);
+    }
+  } else {
+    hrv = SetPermissionsOfContents(basePath, updatePath, perms);
+  }
+  return FAILED(returnValue) ? returnValue : hrv;
+}
+
+/**
+ * Generates the permission set that we want to be applied to the update
+ * directory and its contents. Returns the permissions data via the result
+ * outparam.
+ */
+static HRESULT
+GeneratePermissions(AutoPerms& result)
+{
+  result.sidIdentifierAuthority = SECURITY_NT_AUTHORITY;
+  ZeroMemory(&result.ea, sizeof(result.ea));
+
+  // Make Users group SID and add it to the Explicit Access List.
+  PSID usersSID = nullptr;
+  BOOL success = AllocateAndInitializeSid(&result.sidIdentifierAuthority, 2,
+                                          SECURITY_BUILTIN_DOMAIN_RID,
+                                          DOMAIN_ALIAS_RID_USERS,
+                                          0, 0, 0, 0, 0, 0, &usersSID);
+  result.usersSID.own(usersSID);
+  if (!success) {
+    return HRESULT_FROM_WIN32(GetLastError());
+  }
+  result.ea[0].grfAccessPermissions = FILE_ALL_ACCESS;
+  result.ea[0].grfAccessMode = SET_ACCESS;
+  result.ea[0].grfInheritance= SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+  result.ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+  result.ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
+  result.ea[0].Trustee.ptstrName  = static_cast<LPWSTR>(usersSID);
+
+  // Make Administrators group SID and add it to the Explicit Access List.
+  PSID adminsSID = nullptr;
+  success = AllocateAndInitializeSid(&result.sidIdentifierAuthority, 2,
+                                     SECURITY_BUILTIN_DOMAIN_RID,
+                                     DOMAIN_ALIAS_RID_ADMINS,
+                                     0, 0, 0, 0, 0, 0, &adminsSID);
+  result.adminsSID.own(adminsSID);
+  if (!success) {
+    return HRESULT_FROM_WIN32(GetLastError());
+  }
+  result.ea[1].grfAccessPermissions = FILE_ALL_ACCESS;
+  result.ea[1].grfAccessMode = SET_ACCESS;
+  result.ea[1].grfInheritance= SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+  result.ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+  result.ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
+  result.ea[1].Trustee.ptstrName  = static_cast<LPWSTR>(adminsSID);
+
+  // Make SYSTEM user SID and add it to the Explicit Access List.
+  PSID systemSID = nullptr;
+  success = AllocateAndInitializeSid(&result.sidIdentifierAuthority, 1,
+                                     SECURITY_LOCAL_SYSTEM_RID,
+                                     0, 0, 0, 0, 0, 0, 0, &systemSID);
+  result.systemSID.own(systemSID);
+  if (!success) {
+    return HRESULT_FROM_WIN32(GetLastError());
+  }
+  result.ea[2].grfAccessPermissions = FILE_ALL_ACCESS;
+  result.ea[2].grfAccessMode = SET_ACCESS;
+  result.ea[2].grfInheritance= SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+  result.ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+  result.ea[2].Trustee.TrusteeType = TRUSTEE_IS_USER;
+  result.ea[2].Trustee.ptstrName  = static_cast<LPWSTR>(systemSID);
+
+  PACL acl = nullptr;
+  DWORD drv = SetEntriesInAclW(3, result.ea, nullptr, &acl);
+  // Put the ACL in a unique pointer so that LocalFree is called when it goes
+  // out of scope
+  result.acl.reset(acl);
+  if(drv != ERROR_SUCCESS) {
+    return HRESULT_FROM_WIN32(drv);
+  }
+
+  result.securityDescriptorBuffer =
+    mozilla::MakeUnique<uint8_t[]>(SECURITY_DESCRIPTOR_MIN_LENGTH);
+  if (!result.securityDescriptorBuffer) {
+    return E_OUTOFMEMORY;
+  }
+  result.securityDescriptor =
+    reinterpret_cast<PSECURITY_DESCRIPTOR>(result.securityDescriptorBuffer.get());
+  success = InitializeSecurityDescriptor(result.securityDescriptor,
+                                         SECURITY_DESCRIPTOR_REVISION);
+  if (!success) {
+    return HRESULT_FROM_WIN32(GetLastError());
+  }
+
+  success = SetSecurityDescriptorDacl(result.securityDescriptor, TRUE, acl,
+                                      FALSE);
+  if (!success) {
+    return HRESULT_FROM_WIN32(GetLastError());
+  }
+
+  result.securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
+  result.securityAttributes.lpSecurityDescriptor = result.securityDescriptor;
+  result.securityAttributes.bInheritHandle = FALSE;
+  return S_OK;
+}
+
+/**
+ * Sets the permissions of the file indicated by path to the permissions passed.
+ * Unfortunately this does not take a const string because SetNamedSecurityInfoW
+ * doesn't take one.
+ */
+static HRESULT
+SetPathPerms(SimpleAutoString& path, const AutoPerms& perms)
+{
+  DWORD drv = SetNamedSecurityInfoW(path.MutableString(), SE_FILE_OBJECT,
+                                    DACL_SECURITY_INFORMATION, nullptr,
+                                    nullptr, perms.acl.get(), nullptr);
+  return HRESULT_FROM_WIN32(drv);
+}
+
+/**
+ * Attempts to move the file or directory to the Windows Recycle Bin.
+ * If removal fails with an ERROR_FILE_NOT_FOUND, the file must not exist, so
+ * this will return success in that case.
+ */
+static HRESULT
+RemoveRecursive(const SimpleAutoString& path)
+{
+  DWORD attributes = GetFileAttributesW(path.String());
+
+  // Ignore errors setting attributes. We only care if it was successfully
+  // deleted.
+  if (attributes == INVALID_FILE_ATTRIBUTES) {
+    SetFileAttributesW(path.String(), FILE_ATTRIBUTE_NORMAL);
+  } else if (attributes & FILE_ATTRIBUTE_READONLY) {
+    SetFileAttributesW(path.String(), attributes & ~FILE_ATTRIBUTE_READONLY);
+  }
+
+  // The SHFILEOPSTRUCTW expects a list of paths. The list is simply one long
+  // string separated by null characters. The end of the list is designated by
+  // two null characters.
+  SimpleAutoString pathList;
+  pathList.AllocAndAssignSprintf(path.Length() + 1, L"%s\0", path.String());
+
+  SHFILEOPSTRUCTW fileOperation;
+  fileOperation.hwnd = nullptr;
+  fileOperation.wFunc = FO_DELETE;
+  fileOperation.pFrom = pathList.String();
+  fileOperation.pTo = nullptr;
+  fileOperation.fFlags = FOF_ALLOWUNDO | FOF_NO_UI;
+  fileOperation.lpszProgressTitle = nullptr;
+
+  int rv = SHFileOperationW(&fileOperation);
+  if (rv != 0 && rv != ERROR_FILE_NOT_FOUND) {
+    return E_FAIL;
+  }
+
+  return S_OK;
+}
+
+/**
+ * Attempts to move the file or directory to a path that will not conflict with
+ * our directory structure.
+ *
+ * If an attempt results in the error ERROR_FILE_NOT_FOUND, this function
+ * considers the file to no longer be a conflict and returns success.
+ */
+static HRESULT
+MoveConflicting(const SimpleAutoString& path)
+{
+  // Try to move the file to a backup location
+  SimpleAutoString newPath;
+  unsigned int maxTries = 9;
+  const wchar_t newPathFormat[] = L"%s.bak%u";
+  size_t newPathMaxLength = newPath.AllocFromScprintf(newPathFormat,
+                                                      path.String(),
+                                                      maxTries);
+  if (newPathMaxLength > 0) {
+    for (unsigned int suffix = 0; suffix <= maxTries; ++suffix) {
+      newPath.AssignSprintf(newPathMaxLength + 1, newPathFormat,
+                            path.String(), suffix);
+      if (newPath.Length() == 0) {
+        // If we failed to make this string, we probably aren't going to
+        // succeed on the next one.
+        break;
+      }
+      BOOL success;
+      if (suffix < maxTries) {
+        success = MoveFileW(path.String(), newPath.String());
+      } else {
+        // Moving a file can sometimes work when deleting a file does not. If
+        // there are already the maximum number of backed up files, try
+        // overwriting the last backup before we fall back to deleting the
+        // original.
+        success = MoveFileExW(path.String(), newPath.String(),
+                              MOVEFILE_REPLACE_EXISTING);
+      }
+      if (success) {
+        return S_OK;
+      }
+      DWORD drv = GetLastError();
+      if (drv == ERROR_FILE_NOT_FOUND) {
+        return S_OK;
+      }
+      // If the move failed because newPath already exists, loop to try a new
+      // suffix. If the move failed for any other reason, a new suffix will
+      // probably not help.
+      if (drv != ERROR_ALREADY_EXISTS) {
+        break;
+      }
+    }
+  }
+
+  // Moving failed. Try to delete.
+  return RemoveRecursive(path);
+}
+
+/**
+ * This function recurses through the files and directories in the path passed.
+ * All files and directories have their permissions set to those described in
+ * perms.
+ *
+ * This function attempts to set as many permissions as possible. If an error is
+ * encountered, the function will return the error *after* it attempts to set
+ * permissions on all remaining files.
+ */
+static HRESULT
+SetPermissionsOfContents(const SimpleAutoString& basePath,
+                         const SimpleAutoString& leafUpdateDir,
+                         const AutoPerms& perms)
+{
+  HRESULT returnValue = S_OK; // Stores the value that will eventually be
+                              // returned. If errors occur, this is set to the
+                              // first error encountered.
+
+  SimpleAutoString pathBuffer;
+  if (!pathBuffer.AllocEmpty(MAX_PATH)) {
+    // Fatal error. We need a buffer to put the path in.
+    return FAILED(returnValue) ? returnValue : E_OUTOFMEMORY;
+  }
+
+  DIR directoryHandle(basePath.String());
+  errno = 0;
+
+  for (dirent* entry = readdir(&directoryHandle); entry;
+       entry = readdir(&directoryHandle)) {
+    if (wcscmp(entry->d_name, L".") == 0 || wcscmp(entry->d_name, L"..") == 0) {
+      continue;
+    }
+
+    pathBuffer.AssignSprintf(MAX_PATH + 1, L"%s\\%s", basePath.String(),
+                             entry->d_name);
+    if (pathBuffer.Length() == 0) {
+      returnValue = FAILED(returnValue) ? returnValue
+                  : HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
+      continue;
+    }
+
+    HRESULT hrv = SetPathPerms(pathBuffer, perms);
+    returnValue = FAILED(returnValue) ? returnValue : hrv;
+    bool permsSuccessfullySet = SUCCEEDED(hrv);
+
+    // Rewrite the path buffer, since we cannot guarantee that SetPathPerms did
+    // not change it.
+    pathBuffer.AssignSprintf(MAX_PATH + 1, L"%s\\%s", basePath.String(),
+                             entry->d_name);
+    if (pathBuffer.Length() == 0) {
+      returnValue = FAILED(returnValue) ? returnValue
+                  : HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
+      continue;
+    }
+
+    DWORD attributes = GetFileAttributesW(pathBuffer.String());
+
+    if (attributes == INVALID_FILE_ATTRIBUTES ||
+        attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+      // Remove symlinks.
+      hrv = RemoveRecursive(pathBuffer);
+      if (SUCCEEDED(hrv)) {
+        // If we successfully deleted the file, move on to the next file. There
+        // is nothing else to do for this one.
+        continue;
+      } else {
+        // Do not call continue in the error case. If this file conflicts with
+        // our directory structure, we want to attempt to move it (which may
+        // succeed even though deletion failed).
+        returnValue = FAILED(returnValue) ? returnValue : hrv;
+      }
+    }
+    if (PathConflictsWithLeaf(pathBuffer, attributes, permsSuccessfullySet,
+                              leafUpdateDir)) {
+      hrv = MoveConflicting(pathBuffer);
+      if (SUCCEEDED(hrv)) {
+        // File is out of the way. Move on to the next one.
+        continue;
+      }
+      returnValue = FAILED(returnValue) ? returnValue : hrv;
+    }
+
+    // Recursively set permissions for non-symlink directories
+    if (attributes != INVALID_FILE_ATTRIBUTES &&
+        attributes & FILE_ATTRIBUTE_DIRECTORY &&
+        !(attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
+      hrv = SetPermissionsOfContents(pathBuffer, leafUpdateDir, perms);
+      returnValue = FAILED(returnValue) ? returnValue : hrv;
+    }
+
+    // Before looping, clear any errors that might have been encountered so we
+    // can correctly get errors from readdir.
+    errno = 0;
+  }
+  if (errno != 0) {
+    returnValue = FAILED(returnValue) ? returnValue : E_FAIL;
+  }
+
+  return returnValue;
+}
+
+/**
+ * Returns true if the path conflicts with the leaf path.
+ */
+static bool
+PathConflictsWithLeaf(const SimpleAutoString& path,
+                      DWORD pathAttributes,
+                      bool permsSuccessfullySet,
+                      const SimpleAutoString& leafPath)
+{
+  // Directories only conflict with our directory structure if we don't have
+  // access to them
+  if (pathAttributes != INVALID_FILE_ATTRIBUTES &&
+      pathAttributes & FILE_ATTRIBUTE_DIRECTORY &&
+      permsSuccessfullySet) {
+    return false;
+  }
+  if (!leafPath.StartsWith(path)) {
+    return false;
+  }
+  // Make sure that the next character after the path ends is a path separator
+  // or the end of the string. We don't want to say that "C:\f" conflicts with
+  // "C:\foo\bar".
+  wchar_t charAfterPath = leafPath.String()[path.Length()];
+  return (charAfterPath == L'\\' || charAfterPath == L'\0');
+}
+#endif // XP_WIN
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/common/commonupdatedir.h
@@ -0,0 +1,40 @@
+/* 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/. */
+#ifndef COMMONUPDATEDIR_H
+#define COMMONUPDATEDIR_H
+
+#include "mozilla/UniquePtr.h"
+#include "nsError.h"
+
+#ifdef XP_WIN
+#include <windows.h>
+typedef WCHAR NS_tchar;
+#else
+typedef char NS_tchar;
+#endif
+
+nsresult GetInstallHash(const char16_t* installPath, const char* vendor,
+                        mozilla::UniquePtr<NS_tchar[]>& result,
+                        bool useCompatibilityMode = false);
+
+#ifdef XP_WIN
+enum class SetPermissionsOf {
+  BaseDir,
+  BaseDirIfNotExists,
+  AllFilesAndDirs,
+};
+// This function does two things. It retrieves the update directory and it sets
+// the permissions of the directory and, optionally, its contents.
+HRESULT GetCommonUpdateDirectory(const wchar_t* installPath,
+                                 const char* vendor,
+                                 const char* appName,
+                                 SetPermissionsOf dirPermsToSet,
+                                 mozilla::UniquePtr<wchar_t[]>& result);
+HRESULT GetUserUpdateDirectory(const wchar_t* installPath,
+                               const char* vendor,
+                               const char* appName,
+                               mozilla::UniquePtr<wchar_t[]>& result);
+#endif
+
+#endif
--- a/toolkit/mozapps/update/common/moz.build
+++ b/toolkit/mozapps/update/common/moz.build
@@ -1,26 +1,28 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXPORTS += [
+    'commonupdatedir.h',
     'readstrings.h',
     'updatecommon.h',
     'updatedefines.h',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXPORTS += [
         'pathhash.h',
         'uachelper.h',
         'updatehelper.cpp',
         'updatehelper.h',
+        'win_dirent.h',
     ]
     if CONFIG['MOZ_MAINTENANCE_SERVICE']:
         EXPORTS += [
             'certificatecheck.h',
             'registrycertificates.h',
         ]
 
 Library('updatecommon')
@@ -30,29 +32,41 @@ USE_STATIC_LIBS = True
 
 DisableStlWrapping()
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     SOURCES += [
         'pathhash.cpp',
         'uachelper.cpp',
         'updatehelper.cpp',
+        'win_dirent.cpp',
     ]
     OS_LIBS += [
+        'advapi32',
+        'ole32',
         'rpcrt4',
+        'shell32',
     ]
     if CONFIG['MOZ_MAINTENANCE_SERVICE']:
         SOURCES += [
             'certificatecheck.cpp',
             'registrycertificates.cpp',
         ]
         OS_LIBS += [
             'crypt32',
             'wintrust',
         ]
 
 SOURCES += [
+    '/other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp',
+    'commonupdatedir.cpp',
     'readstrings.cpp',
     'updatecommon.cpp',
 ]
 
 if CONFIG['CC_TYPE'] == 'clang-cl':
     AllowCompilerWarnings()  # workaround for bug 1090497
+
+LOCAL_INCLUDES += [
+    '/other-licenses/nsis/Contrib/CityHash/cityhash',
+]
+
+DEFINES['MOZ_APP_BASENAME'] = '"%s"' % CONFIG['MOZ_APP_BASENAME']
--- a/toolkit/mozapps/update/common/updatedefines.h
+++ b/toolkit/mozapps/update/common/updatedefines.h
@@ -46,17 +46,19 @@
 # define putenv _putenv
 # if defined(_MSC_VER) && _MSC_VER < 1900
 #  define stat _stat
 # endif
 # define DELETE_DIR L"tobedeleted"
 # define CALLBACK_BACKUP_EXT L".moz-callback"
 
 # define LOG_S "%S"
-# define NS_T(str) L ## str
+# define NS_CONCAT(x, y) x##y
+// The extra layer of indirection here allows this macro to be passed macros
+# define NS_T(str) NS_CONCAT(L, str)
 # define NS_SLASH NS_T('\\')
 
 static inline int mywcsprintf(WCHAR* dest, size_t count, const WCHAR* fmt, ...)
 {
   size_t _count = count - 1;
   va_list varargs;
   va_start(varargs, fmt);
   int result = _vsnwprintf(dest, count - 1, fmt, varargs);
rename from toolkit/mozapps/update/updater/win_dirent.cpp
rename to toolkit/mozapps/update/common/win_dirent.cpp
--- a/toolkit/mozapps/update/updater/win_dirent.cpp
+++ b/toolkit/mozapps/update/common/win_dirent.cpp
@@ -42,37 +42,40 @@ opendir(const WCHAR* path)
 
 int
 closedir(DIR* dir)
 {
   delete dir;
   return 0;
 }
 
-dirent* readdir(DIR* dir)
+dirent*
+readdir(DIR* dir)
 {
   WIN32_FIND_DATAW data;
   if (dir->findHandle != INVALID_HANDLE_VALUE) {
     BOOL result = FindNextFileW(dir->findHandle, &data);
     if (!result) {
-      if (GetLastError() != ERROR_FILE_NOT_FOUND) {
+      if (GetLastError() != ERROR_NO_MORE_FILES) {
         errno = ENOENT;
       }
       return 0;
     }
   } else {
     // Reading the first directory entry
     dir->findHandle = FindFirstFileW(dir->name, &data);
     if (dir->findHandle == INVALID_HANDLE_VALUE) {
       if (GetLastError() == ERROR_FILE_NOT_FOUND) {
         errno = ENOENT;
       } else {
         errno = EBADF;
       }
       return 0;
     }
   }
-  memset(gDirEnt.d_name, 0, sizeof(gDirEnt.d_name));
-  wcsncpy(gDirEnt.d_name, data.cFileName,
-           sizeof(gDirEnt.d_name)/sizeof(gDirEnt.d_name[0]));
+  size_t direntBufferLength = sizeof(gDirEnt.d_name)/sizeof(gDirEnt.d_name[0]);
+  wcsncpy(gDirEnt.d_name, data.cFileName, direntBufferLength);
+  // wcsncpy does not guarantee a null-terminated string if the source string is
+  // too long.
+  gDirEnt.d_name[direntBufferLength - 1] = '\0';
   return &gDirEnt;
 }
 
--- a/toolkit/mozapps/update/common/win_dirent.h
+++ b/toolkit/mozapps/update/common/win_dirent.h
@@ -12,21 +12,21 @@
 #endif
 
 #include <windows.h>
 
 struct DIR {
   explicit DIR(const WCHAR* path);
   ~DIR();
   HANDLE findHandle;
-  WCHAR name[MAX_PATH];
+  WCHAR name[MAX_PATH + 1];
 };
 
 struct dirent {
   dirent();
-  WCHAR d_name[MAX_PATH];
+  WCHAR d_name[MAX_PATH + 1];
 };
 
 DIR* opendir(const WCHAR* path);
 int closedir(DIR* dir);
 dirent* readdir(DIR* dir);
 
 #endif  // WINDIRENT_H__
--- a/toolkit/mozapps/update/nsIUpdateService.idl
+++ b/toolkit/mozapps/update/nsIUpdateService.idl
@@ -402,16 +402,40 @@ interface nsIUpdateProcessor : nsISuppor
    * Processes the update which has been downloaded.
    * This happens without restarting the application.
    * On Windows, this can also be used for switching to an applied
    * update request.
    * @param update The update being applied, or null if this is a switch
    *               to updated application request.
    */
   void processUpdate(in nsIUpdate update);
+
+  /**
+   * Attempts to fix the permissions of the update directory. This can be done
+   * in two ways. Firefox can attempt to fix the permissions itself, or it can
+   * call into the maintenance service to request that it attempt to fix the
+   * permissions.
+   *
+   * Fixing the permissions can take some time, so this work is all done off of
+   * the main thread.
+   *
+   * Currently, this function only has a Windows implementation. On other
+   * operating systems, it will throw NS_ERROR_NOT_IMPLEMENTED.
+   *
+   * Since this function does its work off of the main thread and does not
+   * block, it will only throw if it was unable to dispatch to another thread.
+   *
+   * @param shouldUseService If set to false, Firefox will attempt to fix the
+   *                         permissions itself, but the maintenance service
+   *                         will not be used. If set to true and Firefox is
+   *                         unable to fix the permissions itself, it will
+   *                         attempt to call into the maintenance service to
+   *                         request that it attempt to fix the permissions.
+   */
+  void fixUpdateDirectoryPerms(in boolean shouldUseService);
 };
 
 /**
  * An interface describing a global application service that maintains a list
  * of updates previously performed as well as the current active update.
  */
 [scriptable, uuid(0f1098e9-a447-4af9-b030-6f8f35c85f89)]
 interface nsIUpdateManager : nsISupports
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -184,16 +184,19 @@ const APPID_TO_TOPIC = {
   "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "sessionstore-windows-restored",
   // Thunderbird
   "{3550f703-e582-4d05-9a08-453d09bdfdc6}": "mail-startup-done",
 };
 
 // A var is used for the delay so tests can set a smaller value.
 var gSaveUpdateXMLDelay = 2000;
 var gUpdateMutexHandle = null;
+// The permissions of the update directory should be fixed no more than once per
+// session
+var gUpdateDirPermissionFixAttempted = false;
 
 ChromeUtils.defineModuleGetter(this, "UpdateUtils",
                                "resource://gre/modules/UpdateUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "WindowsRegistry",
                                "resource://gre/modules/WindowsRegistry.jsm");
 ChromeUtils.defineModuleGetter(this, "AsyncShutdown",
                                "resource://gre/modules/AsyncShutdown.jsm");
 ChromeUtils.defineModuleGetter(this, "OS",
@@ -655,17 +658,20 @@ function readStatusFile(dir) {
  *          The patch directory where the update.status file should be
  *          written.
  * @param   state
  *          The state value to write.
  */
 function writeStatusFile(dir, state) {
   let statusFile = dir.clone();
   statusFile.append(FILE_UPDATE_STATUS);
-  writeStringToFile(statusFile, state);
+  let success = writeStringToFile(statusFile, state);
+  if (!success) {
+    handleCriticalWriteFailure(statusFile.path);
+  }
 }
 
 /**
  * Writes the update's application version to a file in the patch directory. If
  * the update doesn't provide application version information via the
  * appVersion attribute the string "null" will be written to the file.
  * This value is compared during startup (in nsUpdateDriver.cpp) to determine if
  * the update should be applied. Note that this won't provide protection from
@@ -676,17 +682,20 @@ function writeStatusFile(dir, state) {
  *          written.
  * @param   version
  *          The version value to write. Will be the string "null" when the
  *          update doesn't provide the appVersion attribute in the update xml.
  */
 function writeVersionFile(dir, version) {
   let versionFile = dir.clone();
   versionFile.append(FILE_UPDATE_VERSION);
-  writeStringToFile(versionFile, version);
+  let success = writeStringToFile(versionFile, version);
+  if (!success) {
+    handleCriticalWriteFailure(versionFile.path);
+  }
 }
 
 /**
  * Determines if the service should be used to attempt an update
  * or not.
  *
  * @return  true if the service should be used for updates.
  */
@@ -808,22 +817,30 @@ function cleanupActiveUpdate() {
 
   // Now trash the updates directory, since we're done with it
   cleanUpUpdatesDir();
 }
 
 /**
  * Writes a string of text to a file.  A newline will be appended to the data
  * written to the file.  This function only works with ASCII text.
+ * @param file An nsIFile indicating what file to write to.
+ * @param text A string containing the text to write to the file.
+ * @return true on success, false on failure.
  */
 function writeStringToFile(file, text) {
-  let fos = FileUtils.openSafeFileOutputStream(file);
-  text += "\n";
-  fos.write(text, text.length);
-  FileUtils.closeSafeFileOutputStream(fos);
+  try {
+    let fos = FileUtils.openSafeFileOutputStream(file);
+    text += "\n";
+    fos.write(text, text.length);
+    FileUtils.closeSafeFileOutputStream(fos);
+  } catch (e) {
+    return false;
+  }
+  return true;
 }
 
 function readStringFromInputStream(inputStream) {
   var sis = Cc["@mozilla.org/scriptableinputstream;1"].
             createInstance(Ci.nsIScriptableInputStream);
   sis.init(inputStream);
   var text = sis.read(sis.available());
   sis.close();
@@ -1021,32 +1038,129 @@ function pingStateAndStatusCodes(aUpdate
         stateCode = 11;
         break;
       case STATE_FAILED:
         stateCode = 12;
         break;
       case STATE_PENDING_ELEVATE:
         stateCode = 13;
         break;
+      // Note: Do not use stateCode 14 here. It is defined in
+      // UpdateTelemetry.jsm
       default:
         stateCode = 1;
     }
 
     if (parts.length > 1) {
       let statusErrorCode = INVALID_UPDATER_STATE_CODE;
       if (parts[0] == STATE_FAILED) {
         statusErrorCode = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE;
       }
       AUSTLMY.pingStatusErrorCode(suffix, statusErrorCode);
     }
   }
   AUSTLMY.pingStateCode(suffix, stateCode);
 }
 
 /**
+ * This function should be called whenever we fail to write to a file required
+ * for update to function. This function will, if possible, attempt to fix the
+ * file permissions. If the file permissions cannot be fixed, the user will be
+ * prompted to reinstall.
+ *
+ * All functionality happens asynchronously.
+ *
+ * Returns false if the permission-fixing process cannot be started. Since this
+ * is asynchronous, a true return value does not mean that the permissions were
+ * actually fixed.
+ *
+ * @param path A string representing the path that could not be written. This
+ *             value will only be used for logging purposes.
+ */
+function handleCriticalWriteFailure(path) {
+  LOG("Unable to write to critical update file: " + path);
+
+  let updateManager = Cc["@mozilla.org/updates/update-manager;1"].
+                      getService(Ci.nsIUpdateManager);
+
+  let update = updateManager.activeUpdate;
+  if (update) {
+    let patch = update.selectedPatch;
+    let patchType = AUSTLMY.PATCH_UNKNOWN;
+    if (patch.type == "complete") {
+      patchType = AUSTLMY.PATCH_COMPLETE;
+    } else if (patch.type == "partial") {
+      patchType = AUSTLMY.PATCH_PARTIAL;
+    }
+    if (update.state == STATE_DOWNLOADING) {
+      if (!patch.downloadWriteFailureTelemetrySent) {
+        AUSTLMY.pingDownloadCode(patchType, AUSTLMY.DWNLD_ERR_WRITE_FAILURE);
+        patch.downloadWriteFailureTelemetrySent = true;
+      }
+    } else if (!patch.applyWriteFailureTelemetrySent) {
+      // It's not ideal to hardcode AUSTLMY.STARTUP below (it could be
+      // AUSTLMY.STAGE). But staging is not used anymore and neither value
+      // really makes sense for this code. For the other codes it indicates when
+      // that code was read from the update status file, but this code was never
+      // read from the update status file.
+      let suffix = patchType + "_" + AUSTLMY.STARTUP;
+      AUSTLMY.pingStateCode(suffix, AUSTLMY.STATE_WRITE_FAILURE);
+      patch.applyWriteFailureTelemetrySent = true;
+    }
+  } else {
+    let updateServiceInstance = UpdateServiceFactory.createInstance();
+    let request = updateServiceInstance.backgroundChecker._request;
+    if (!request.checkWriteFailureTelemetrySent) {
+      let pingSuffix = updateServiceInstance._pingSuffix;
+      AUSTLMY.pingCheckCode(pingSuffix, AUSTLMY.CHK_ERR_WRITE_FAILURE);
+      request.checkWriteFailureTelemetrySent = true;
+    }
+  }
+
+  if (!gUpdateDirPermissionFixAttempted) {
+    // Currently, we only have a mechanism for fixing update directory permissions
+    // on Windows.
+    if (AppConstants.platform != "win") {
+      LOG("There is currently no implementation for fixing update directory " +
+          "permissions on this platform");
+      return false;
+    }
+    LOG("Attempting to fix update directory permissions");
+    try {
+      Cc["@mozilla.org/updates/update-processor;1"].
+        createInstance(Ci.nsIUpdateProcessor).
+        fixUpdateDirectoryPerms(shouldUseService());
+    } catch (e) {
+      LOG("Attempt to fix update directory permissions failed. Exception: " + e);
+      return false;
+    }
+    gUpdateDirPermissionFixAttempted = true;
+    return true;
+  }
+  return false;
+}
+
+/**
+ * This is a convenience function for calling the above function depending on a
+ * boolean success value.
+ *
+ * @param wroteSuccessfully A boolean representing whether or not the write was
+ *                          successful. When this is true, this function does
+ *                          nothing.
+ * @param path A string representing the path to the file that the operation
+ *             attempted to write to. This value is only used for logging
+ *             purposes.
+ */
+function handleCriticalWriteResult(wroteSuccessfully, path) {
+  if (!wroteSuccessfully) {
+    handleCriticalWriteFailure(path);
+  }
+}
+
+/**
  * Update Patch
  * @param   patch
  *          A <patch> element to initialize this object with
  * @throws if patch has a size of 0
  * @constructor
  */
 function UpdatePatch(patch) {
   this._properties = {};
@@ -2546,19 +2660,25 @@ UpdateManager.prototype = {
     let updates = [];
     let file = getUpdateFile([fileName]);
     if (!file.exists()) {
       LOG("UpdateManager:_loadXMLFileIntoArray - XML file does not exist. " +
           "path: " + file.path);
       return updates;
     }
 
-    var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
+    let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
                      createInstance(Ci.nsIFileInputStream);
-    fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+    try {
+      fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+    } catch (e) {
+      LOG("UpdateManager:_loadXMLFileIntoArray - error initializing file " +
+          "stream. Exception: " + e);
+      return updates;
+    }
     try {
       var parser = new DOMParser();
       var doc = parser.parseFromStream(fileStream, "UTF-8",
                                        fileStream.available(), "text/xml");
 
       var updateCount = doc.documentElement.childNodes.length;
       for (var i = 0; i < updateCount; ++i) {
         var updateElement = doc.documentElement.childNodes.item(i);
@@ -2645,24 +2765,38 @@ UpdateManager.prototype = {
 
   /**
    * Serializes an array of updates to an XML file or removes the file if the
    * array length is 0.
    * @param   updates
    *          An array of nsIUpdate objects
    * @param   fileName
    *          The file name in the updates directory to write to.
+   * @return  true on success, false on error
    */
   _writeUpdatesToXMLFile: async function UM__writeUpdatesToXMLFile(updates, fileName) {
-    let file = getUpdateFile([fileName]);
+    let file;
+    try {
+      file = getUpdateFile([fileName]);
+    } catch (e) {
+      LOG("UpdateManager:_writeUpdatesToXMLFile - Unable to get XML file - " +
+          "Exception: " + e);
+      return false;
+    }
     if (updates.length == 0) {
       LOG("UpdateManager:_writeUpdatesToXMLFile - no updates to write. " +
           "removing file: " + file.path);
-      await OS.File.remove(file.path, {ignoreAbsent: true});
-      return;
+      try {
+        await OS.File.remove(file.path, {ignoreAbsent: true});
+      } catch (e) {
+        LOG("UpdateManager:_writeUpdatesToXMLFile - Delete file exception: " +
+            e);
+        return false;
+      }
+      return true;
     }
 
     const EMPTY_UPDATES_DOCUMENT_OPEN = "<?xml version=\"1.0\"?><updates xmlns=\"http://www.mozilla.org/2005/app-update\">";
     const EMPTY_UPDATES_DOCUMENT_CLOSE = "</updates>";
     try {
       var parser = new DOMParser();
       var doc = parser.parseFromString(EMPTY_UPDATES_DOCUMENT_OPEN + EMPTY_UPDATES_DOCUMENT_CLOSE, "text/xml");
 
@@ -2672,17 +2806,19 @@ UpdateManager.prototype = {
 
       var xml = EMPTY_UPDATES_DOCUMENT_OPEN + doc.documentElement.innerHTML +
                 EMPTY_UPDATES_DOCUMENT_CLOSE;
       await OS.File.writeAtomic(file.path, xml, {encoding: "utf-8",
                                                  tmpPath: file.path + ".tmp"});
       await OS.File.setPermissions(file.path, {unixMode: FileUtils.PERMS_FILE});
     } catch (e) {
       LOG("UpdateManager:_writeUpdatesToXMLFile - Exception: " + e);
+      return false;
     }
+    return true;
   },
 
   _updatesXMLSaver: null,
   _updatesXMLSaverCallback: null,
   /**
    * See nsIUpdateService.idl
    */
   saveUpdates: function UM_saveUpdates() {
@@ -2708,23 +2844,29 @@ UpdateManager.prototype = {
     AsyncShutdown.profileBeforeChange.removeBlocker(this._updatesXMLSaverCallback);
     this._updatesXMLSaver = null;
     this._updatesXMLSaverCallback = null;
 
     // The active update stored in the active-update.xml file will change during
     // the lifetime of an active update and the file should always be updated
     // when saveUpdates is called.
     this._writeUpdatesToXMLFile(this._activeUpdate ? [this._activeUpdate] : [],
-                                FILE_ACTIVE_UPDATE_XML);
+                                FILE_ACTIVE_UPDATE_XML).then(
+      wroteSuccessfully => handleCriticalWriteResult(wroteSuccessfully,
+                                                     FILE_ACTIVE_UPDATE_XML)
+    );
     // The update history stored in the updates.xml file should only need to be
     // updated when an active update has been added to it in which case
     // |_updatesDirty| will be true.
     if (this._updatesDirty) {
       this._updatesDirty = false;
-      this._writeUpdatesToXMLFile(this._updates, FILE_UPDATES_XML);
+      this._writeUpdatesToXMLFile(this._updates, FILE_UPDATES_XML).then(
+        wroteSuccessfully => handleCriticalWriteResult(wroteSuccessfully,
+                                                       FILE_UPDATES_XML)
+      );
     }
   },
 
   /**
    * See nsIUpdateService.idl
    */
   refreshUpdateStatus: function UM_refreshUpdateStatus() {
     var update = this._activeUpdate;
@@ -3520,16 +3662,17 @@ Downloader.prototype = {
    * When data transfer ceases
    * @param   request
    *          The nsIRequest object for the transfer
    * @param   context
    *          Additional data
    * @param   status
    *          Status code containing the reason for the cessation.
    */
+   /* eslint-disable-next-line complexity */
   onStopRequest: function Downloader_onStopRequest(request, context, status) {
     if (request instanceof Ci.nsIIncrementalDownload)
       LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec +
           ", final URI spec: " + request.finalURI.spec + ", status: " + status);
 
     // XXX ehsan shouldShowPrompt should always be false here.
     // But what happens when there is already a UI showing?
     var state = this._patch.state;
@@ -3540,16 +3683,17 @@ Downloader.prototype = {
     var retryTimeout = Services.prefs.getIntPref(PREF_APP_UPDATE_SOCKET_RETRYTIMEOUT,
                                                  DEFAULT_SOCKET_RETRYTIMEOUT);
     // Prevent the preference from setting a value greater than 10000.
     retryTimeout = Math.min(retryTimeout, 10000);
     var maxFail = Services.prefs.getIntPref(PREF_APP_UPDATE_SOCKET_MAXERRORS,
                                             DEFAULT_SOCKET_MAX_ERRORS);
     // Prevent the preference from setting a value greater than 20.
     maxFail = Math.min(maxFail, 20);
+    let permissionFixingInProgress = false;
     LOG("Downloader:onStopRequest - status: " + status + ", " +
         "current fail: " + this.updateService._consecutiveSocketErrors + ", " +
         "max fail: " + maxFail + ", " +
         "retryTimeout: " + retryTimeout);
     if (Components.isSuccessCode(status)) {
       if (this._verifyDownload()) {
         if (shouldUseService()) {
           state = STATE_PENDING_SERVICE;
@@ -3614,17 +3758,28 @@ Downloader.prototype = {
       } else if (status == Cr.NS_ERROR_DOCUMENT_NOT_CACHED) {
         dwnldCode = AUSTLMY.DWNLD_ERR_DOCUMENT_NOT_CACHED;
       }
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate, dwnldCode);
       shouldRetrySoon = true;
       deleteActiveUpdate = false;
     } else if (status != Cr.NS_BINDING_ABORTED &&
                status != Cr.NS_ERROR_ABORT) {
-      LOG("Downloader:onStopRequest - non-verification failure");
+      if (status == Cr.NS_ERROR_FILE_ACCESS_DENIED ||
+          status == Cr.NS_ERROR_FILE_READ_ONLY) {
+        LOG("Downloader:onStopRequest - permission error");
+        // This will either fix the permissions, or asynchronously show the
+        // reinstall prompt if it cannot fix them.
+        let patchFile = getUpdatesDir().clone();
+        patchFile.append(FILE_UPDATE_MAR);
+        permissionFixingInProgress = handleCriticalWriteFailure(patchFile.path);
+      } else {
+        LOG("Downloader:onStopRequest - non-verification failure");
+      }
+
       let dwnldCode = AUSTLMY.DWNLD_ERR_BINDING_ABORTED;
       if (status == Cr.NS_ERROR_ABORT) {
         dwnldCode = AUSTLMY.DWNLD_ERR_ABORT;
       }
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate, dwnldCode);
 
       // Some sort of other failure, log this in the |statusText| property
       state = STATE_DOWNLOAD_FAILED;
@@ -3677,17 +3832,17 @@ Downloader.prototype = {
 
         if (updateStatus == STATE_NONE) {
           cleanupActiveUpdate();
         } else {
           allFailed = false;
         }
       }
 
-      if (allFailed) {
+      if (allFailed && !permissionFixingInProgress) {
         if (Services.prefs.getBoolPref(PREF_APP_UPDATE_DOORHANGER, false)) {
           let downloadAttempts = Services.prefs.getIntPref(PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, 0);
           downloadAttempts++;
           Services.prefs.setIntPref(PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, downloadAttempts);
           let maxAttempts = Math.min(Services.prefs.getIntPref(PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS, 2), 10);
 
           if (downloadAttempts > maxAttempts) {
             LOG("Downloader:onStopRequest - notifying observers of error. " +
@@ -3708,17 +3863,18 @@ Downloader.prototype = {
         // notify the user about the error. If the update was a background
         // update there is no notification since the user won't be expecting it.
         if (!Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) &&
             this._update.getProperty("foregroundDownload") == "true") {
           let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                          createInstance(Ci.nsIUpdatePrompt);
           prompter.showUpdateError(this._update);
         }
-
+      }
+      if (allFailed) {
         // Prevent leaking the update object (bug 454964).
         this._update = null;
       }
       // A complete download has been initiated or the failure was handled.
       return;
     }
 
     if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
--- a/toolkit/mozapps/update/nsUpdateServiceStub.js
+++ b/toolkit/mozapps/update/nsUpdateServiceStub.js
@@ -1,43 +1,172 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* 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/. */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
 ChromeUtils.import("resource://gre/modules/FileUtils.jsm", this);
+ChromeUtils.import("resource://gre/modules/Services.jsm", this);
+ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this);
 
 const DIR_UPDATES         = "updates";
 const FILE_UPDATE_STATUS  = "update.status";
+const FILE_ACTIVE_UPDATE_XML = "active-update.xml";
+const FILE_LAST_UPDATE_LOG   = "last-update.log";
+const FILE_UPDATES_XML       = "updates.xml";
+const FILE_UPDATE_LOG        = "update.log";
 
 const KEY_UPDROOT         = "UpdRootD";
+const KEY_OLD_UPDROOT     = "OldUpdRootD";
 
-/**
- * Gets the specified directory at the specified hierarchy under the update root
- * directory without creating it if it doesn't exist.
- * @param   pathArray
- *          An array of path components to locate beneath the directory
- *          specified by |key|
- * @return  nsIFile object for the location specified.
- */
-function getUpdateDirNoCreate(pathArray) {
-  return FileUtils.getDir(KEY_UPDROOT, pathArray, false);
-}
+// The pref prefix below should have the hash of the install path appended to
+// ensure that this is a per-installation pref (i.e. to ensure that migration
+// happens for every install rather than once per profile)
+const PREF_PREFIX_UPDATE_DIR_MIGRATED  = "app.update.migrated.updateDir2.";
+const PREF_APP_UPDATE_LOG              = "app.update.log";
+
+XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
+  return Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false);
+});
 
 function UpdateServiceStub() {
-  let statusFile = getUpdateDirNoCreate([DIR_UPDATES, "0"]);
+  let updateDir = FileUtils.getDir(KEY_UPDROOT, [], false);
+  let prefUpdateDirMigrated = PREF_PREFIX_UPDATE_DIR_MIGRATED
+                            + updateDir.leafName;
+
+  let statusFile = updateDir;
+  statusFile.append(DIR_UPDATES);
+  statusFile.append("0");
   statusFile.append(FILE_UPDATE_STATUS);
+  updateDir = null; // We don't need updateDir anymore, plus now its nsIFile
+                    // contains the status file's path
+
+  // We may need to migrate update data
+  if (AppConstants.platform == "win" &&
+      !Services.prefs.getBoolPref(prefUpdateDirMigrated, false)) {
+    migrateUpdateDirectory();
+    Services.prefs.setBoolPref(prefUpdateDirMigrated, true);
+  }
+
   // If the update.status file exists then initiate post update processing.
   if (statusFile.exists()) {
     let aus = Cc["@mozilla.org/updates/update-service;1"].
               getService(Ci.nsIApplicationUpdateService).
               QueryInterface(Ci.nsIObserver);
     aus.observe(null, "post-update-processing", "");
   }
 }
 UpdateServiceStub.prototype = {
   observe() {},
   classID: Components.ID("{e43b0010-04ba-4da6-b523-1f92580bc150}"),
   QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdateServiceStub]);
+
+/**
+ * This function should be called when there are files in the old update
+ * directory that may need to be migrated to the new update directory.
+ */
+function migrateUpdateDirectory() {
+  let sourceRootDir = FileUtils.getDir(KEY_OLD_UPDROOT, [], false);
+  let destRootDir = FileUtils.getDir(KEY_UPDROOT, [], false);
+
+  if (!sourceRootDir.exists()) {
+    LOG("UpdateServiceStub:_migrateUpdateDirectory - Abort: No migration " +
+        "necessary. Nothing to migrate.");
+    return;
+  }
+
+  if (destRootDir.exists()) {
+    // Migration must have already been done by another user
+    LOG("UpdateServiceStub:_migrateUpdateDirectory - migrated and unmigrated " +
+        "update directories found. Deleting the unmigrated directory: " +
+        sourceRootDir.path);
+    try {
+      sourceRootDir.remove(true);
+    } catch (e) {
+      LOG("UpdateServiceStub:_migrateUpdateDirectory - Deletion of " +
+          "unmigrated directory failed. Exception: " + e);
+    }
+    return;
+  }
+
+  let sourceUpdateDir = sourceRootDir.clone();
+  sourceUpdateDir.append(DIR_UPDATES);
+  let destUpdateDir = destRootDir.clone();
+  destUpdateDir.append(DIR_UPDATES);
+
+  let sourcePatchDir = sourceUpdateDir.clone();
+  sourcePatchDir.append("0");
+  let destPatchDir = destUpdateDir.clone();
+  destPatchDir.append("0");
+
+  let sourceStatusFile = sourcePatchDir.clone();
+  sourceStatusFile.append(FILE_UPDATE_STATUS);
+  let destStatusFile = destPatchDir.clone();
+  destStatusFile.append(FILE_UPDATE_STATUS);
+
+  let sourceActiveXML = sourceRootDir.clone();
+  sourceActiveXML.append(FILE_ACTIVE_UPDATE_XML);
+  try {
+    sourceActiveXML.moveTo(destRootDir, sourceActiveXML.leafName);
+  } catch (e) {
+    LOG("UpdateServiceStub:_migrateUpdateDirectory - Unable to move active " +
+        "update XML file. Exception: " + e);
+  }
+
+  let sourceUpdateXML = sourceRootDir.clone();
+  sourceUpdateXML.append(FILE_UPDATES_XML);
+  try {
+    sourceUpdateXML.moveTo(destRootDir, sourceUpdateXML.leafName);
+  } catch (e) {
+    LOG("UpdateServiceStub:_migrateUpdateDirectory - Unable to move " +
+        "update XML file. Exception: " + e);
+  }
+
+  let sourceUpdateLog = sourcePatchDir.clone();
+  sourceUpdateLog.append(FILE_UPDATE_LOG);
+  try {
+    sourceUpdateLog.moveTo(destPatchDir, sourceUpdateLog.leafName);
+  } catch (e) {
+    LOG("UpdateServiceStub:_migrateUpdateDirectory - Unable to move " +
+        "update log file. Exception: " + e);
+  }
+
+  let sourceLastUpdateLog = sourceUpdateDir.clone();
+  sourceLastUpdateLog.append(FILE_LAST_UPDATE_LOG);
+  try {
+    sourceLastUpdateLog.moveTo(destUpdateDir, sourceLastUpdateLog.leafName);
+  } catch (e) {
+    LOG("UpdateServiceStub:_migrateUpdateDirectory - Unable to move " +
+        "last-update log file. Exception: " + e);
+  }
+
+  try {
+    sourceStatusFile.moveTo(destStatusFile.parent, destStatusFile.leafName);
+  } catch (e) {
+    LOG("UpdateServiceStub:_migrateUpdateDirectory - Unable to move update " +
+        "status file. Exception: " + e);
+  }
+
+  // Remove all remaining files in the old update directory. We don't need
+  // them anymore
+  try {
+    sourceRootDir.remove(true);
+  } catch (e) {
+    LOG("UpdateServiceStub:_migrateUpdateDirectory - Deletion of old update " +
+        "directory failed. Exception: " + e);
+  }
+}
+
+/**
+ * Logs a string to the error console.
+ * @param   string
+ *          The string to write to the error console.
+ */
+function LOG(string) {
+  if (gLogEnabled) {
+    dump("*** AUS:SVC " + string + "\n");
+    Services.console.logStringMessage("AUS:SVC " + string);
+  }
+}
--- a/toolkit/mozapps/update/tests/TestAUSHelper.cpp
+++ b/toolkit/mozapps/update/tests/TestAUSHelper.cpp
@@ -4,16 +4,17 @@
 
 #ifdef XP_WIN
 # include <windows.h>
 # include <wintrust.h>
 # include <tlhelp32.h>
 # include <softpub.h>
 # include <direct.h>
 # include <io.h>
+# include "commonupdatedir.h"
   typedef WCHAR NS_tchar;
 # define NS_main wmain
 # ifndef F_OK
 #   define F_OK 00
 # endif
 # ifndef W_OK
 #   define W_OK 02
 # endif
@@ -189,16 +190,17 @@ int NS_main(int argc, NS_tchar **argv)
             "\n" \
             "Usage: WORKINGDIR INFILE OUTFILE -s SECONDS [FILETOLOCK]\n"  \
             "   or: WORKINGDIR LOGFILE [ARG2 ARG3...]\n" \
             "   or: signature-check filepath\n" \
             "   or: setup-symlink dir1 dir2 file symlink\n" \
             "   or: remove-symlink dir1 dir2 file symlink\n" \
             "   or: check-symlink symlink\n" \
             "   or: post-update\n" \
+            "   or: create-update-dir\n" \
             "\n" \
             "  WORKINGDIR  \tThe relative path to the working directory to use.\n" \
             "  INFILE      \tThe relative path from the working directory for the file to\n" \
             "              \tread actions to perform such as finish.\n" \
             "  OUTFILE     \tThe relative path from the working directory for the file to\n" \
             "              \twrite status information.\n" \
             "  SECONDS     \tThe number of seconds to sleep.\n" \
             "  FILETOLOCK  \tThe relative path from the working directory to an existing\n" \
@@ -351,16 +353,29 @@ int NS_main(int argc, NS_tchar **argv)
     }
     return lastState;
 #else
     // Not implemented on non-Windows platforms
     return 1;
 #endif
   }
 
+  if (!NS_tstrcmp(argv[1], NS_T("create-update-dir"))) {
+#ifdef XP_WIN
+    mozilla::UniquePtr<wchar_t[]> updateDir;
+    HRESULT result = GetCommonUpdateDirectory(argv[2], nullptr, nullptr,
+                                              SetPermissionsOf::AllFilesAndDirs,
+                                              updateDir);
+    return SUCCEEDED(result) ? 0 : 1;
+#else
+    // Not implemented on non-Windows platforms
+    return 1;
+#endif
+  }
+
   if (NS_tchdir(argv[1]) != 0) {
     return 1;
   }
 
   // File in use test helper section
   if (!NS_tstrcmp(argv[4], NS_T("-s"))) {
     NS_tchar *cwd = NS_tgetcwd(nullptr, 0);
     NS_tchar inFilePath[MAXPATHLEN];
--- a/toolkit/mozapps/update/tests/TestAUSReadStrings.cpp
+++ b/toolkit/mozapps/update/tests/TestAUSReadStrings.cpp
@@ -5,37 +5,34 @@
 
 /**
  * This binary tests the updater's ReadStrings ini parser and should run in a
  * directory with a Unicode character to test bug 473417.
  */
 #ifdef XP_WIN
   #include <windows.h>
   #define NS_main wmain
-  #define NS_tstrrchr wcsrchr
-  #define NS_T(str) L ## str
   #define PATH_SEPARATOR_CHAR L'\\'
   // On Windows, argv[0] can also have forward slashes instead
   #define ALT_PATH_SEPARATOR_CHAR L'/'
 #else
   #include <unistd.h>
   #define NS_main main
-  #define NS_tstrrchr strrchr
-  #define NS_T(str) str
   #define PATH_SEPARATOR_CHAR '/'
 #endif
 
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
 
 #include "updater/resource.h"
 #include "updater/progressui.h"
 #include "common/readstrings.h"
 #include "common/errors.h"
+#include "common/updatedefines.h"
 #include "mozilla/ArrayUtils.h"
 
 #ifndef MAXPATHLEN
 # ifdef PATH_MAX
 #  define MAXPATHLEN PATH_MAX
 # elif defined(MAX_PATH)
 #  define MAXPATHLEN MAX_PATH
 # elif defined(_MAX_PATH)
--- a/toolkit/mozapps/update/tests/data/shared.js
+++ b/toolkit/mozapps/update/tests/data/shared.js
@@ -33,20 +33,21 @@ const PREF_APP_UPDATE_URL_MANUAL        
 
 const PREFBRANCH_APP_PARTNER         = "app.partner.";
 const PREF_DISTRIBUTION_ID           = "distribution.id";
 const PREF_DISTRIBUTION_VERSION      = "distribution.version";
 const PREF_DISABLE_SECURITY          = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
 
 const NS_APP_PROFILE_DIR_STARTUP   = "ProfDS";
 const NS_APP_USER_PROFILE_50_DIR   = "ProfD";
+const NS_GRE_BIN_DIR               = "GreBinD";
 const NS_GRE_DIR                   = "GreD";
-const NS_GRE_BIN_DIR               = "GreBinD";
 const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD";
 const XRE_EXECUTABLE_FILE          = "XREExeF";
+const XRE_OLD_UPDATE_ROOT_DIR      = "OldUpdRootD";
 const XRE_UPDATE_ROOT_DIR          = "UpdRootD";
 
 const DIR_PATCH        = "0";
 const DIR_TOBEDELETED  = "tobedeleted";
 const DIR_UPDATES      = "updates";
 const DIR_UPDATED      = IS_MACOSX ? "Updated.app" : "updated";
 
 const FILE_ACTIVE_UPDATE_XML         = "active-update.xml";
--- a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
+++ b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
@@ -835,16 +835,18 @@ function setupTestCommon() {
 
   if (IS_WIN) {
     Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, !!IS_SERVICE_TEST);
   }
 
   // adjustGeneralPaths registers a cleanup function that calls end_test when
   // it is defined as a function.
   adjustGeneralPaths();
+  createWorldWritableAppUpdateDir();
+
   // Logged once here instead of in the mock directory provider to lessen test
   // log spam.
   debugDump("Updates Directory (UpdRootD) Path: " + getMockUpdRootD().path);
 
   // This prevents a warning about not being able to find the greprefs.js file
   // from being logged.
   let grePrefsFile = getGREDir();
   if (!grePrefsFile.exists()) {
@@ -1591,55 +1593,73 @@ XPCOMUtils.defineLazyGetter(this, "gLoca
   if (!IS_WIN) {
     do_throw("Windows only function called by a different platform!");
   }
 
   const CSIDL_LOCAL_APPDATA = 0x1c;
   return getSpecialFolderDir(CSIDL_LOCAL_APPDATA);
 });
 
+XPCOMUtils.defineLazyGetter(this, "gCommonAppDataDir", function test_gCDD() {
+  if (!IS_WIN) {
+    do_throw("Windows only function called by a different platform!");
+  }
+
+  const CSIDL_COMMON_APPDATA = 0x0023;
+  return getSpecialFolderDir(CSIDL_COMMON_APPDATA);
+});
+
 XPCOMUtils.defineLazyGetter(this, "gProgFilesDir", function test_gPFD() {
   if (!IS_WIN) {
     do_throw("Windows only function called by a different platform!");
   }
 
   const CSIDL_PROGRAM_FILES = 0x26;
   return getSpecialFolderDir(CSIDL_PROGRAM_FILES);
 });
 
 /**
  * Helper function for getting the update root directory used by the tests. This
  * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
  * in nsXREDirProvider.cpp so an application will be able to find the update
  * when running a test that launches the application.
+ *
+ * The aGetOldLocation argument performs the same function that the argument
+ * with the same name in nsXREDirProvider::GetUpdateRootDir performs. If true,
+ * the old (pre-migration) update directory is returned.
  */
-function getMockUpdRootD() {
+function getMockUpdRootD(aGetOldLocation = false) {
   if (IS_WIN) {
-    return getMockUpdRootDWin();
+    return getMockUpdRootDWin(aGetOldLocation);
   }
 
   if (IS_MACOSX) {
     return getMockUpdRootDMac();
   }
 
   return getApplyDirFile(DIR_MACOS, true);
 }
 
 /**
  * Helper function for getting the update root directory used by the tests. This
  * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
  * in nsXREDirProvider.cpp so an application will be able to find the update
  * when running a test that launches the application.
  */
-function getMockUpdRootDWin() {
+function getMockUpdRootDWin(aGetOldLocation) {
   if (!IS_WIN) {
     do_throw("Windows only function called by a different platform!");
   }
 
-  let localAppDataDir = gLocalAppDataDir.clone();
+  let dataDirectory;
+  if (aGetOldLocation) {
+    dataDirectory = gLocalAppDataDir.clone();
+  } else {
+    dataDirectory = gCommonAppDataDir.clone();
+  }
   let progFilesDir = gProgFilesDir.clone();
   let appDir = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).parent;
 
   let appDirPath = appDir.path;
   let relPathUpdates = "";
   if (gInstallDirPathHash && (MOZ_APP_VENDOR || MOZ_APP_BASENAME)) {
     relPathUpdates += (MOZ_APP_VENDOR ? MOZ_APP_VENDOR : MOZ_APP_BASENAME) +
                       "\\" + DIR_UPDATES + "\\" + gInstallDirPathHash;
@@ -1664,20 +1684,29 @@ function getMockUpdRootDWin() {
     } else {
       relPathUpdates += MOZ_APP_BASENAME;
     }
     relPathUpdates += "\\" + MOZ_APP_NAME;
   }
 
   let updatesDir = Cc["@mozilla.org/file/local;1"].
                    createInstance(Ci.nsIFile);
-  updatesDir.initWithPath(localAppDataDir.path + "\\" + relPathUpdates);
+  updatesDir.initWithPath(dataDirectory.path + "\\" + relPathUpdates);
   return updatesDir;
 }
 
+function createWorldWritableAppUpdateDir() {
+  // This function is only necessary in Windows
+  if (IS_WIN) {
+    let installDir = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).parent;
+    let exitValue = runTestHelperSync(["create-update-dir", installDir.path]);
+    Assert.equal(exitValue, 0, "The helper process exit value should be 0");
+  }
+}
+
 XPCOMUtils.defineLazyGetter(this, "gUpdatesRootDir", function test_gURD() {
   if (!IS_MACOSX) {
     do_throw("Mac OS X only function called by a different platform!");
   }
 
   let dir = Services.dirsvc.get("ULibDir", Ci.nsIFile);
   dir.append("Caches");
   if (MOZ_APP_VENDOR || MOZ_APP_BASENAME) {
@@ -3921,18 +3950,18 @@ function getProcessArgs(aExtraArgs) {
     let scriptContents = "#! /bin/sh\n";
     scriptContents += appBinPath + " -no-remote -test-process-updates " +
                       aExtraArgs.join(" ") + " " + PIPE_TO_NULL;
     writeFile(launchScript, scriptContents);
     debugDump("created " + launchScript.path + " containing:\n" +
               scriptContents);
     args = [launchScript.path];
   } else {
-    args = ["/D", "/Q", "/C", appBinPath, "-no-remote", "-test-process-updates"].
-           concat(aExtraArgs).concat([PIPE_TO_NULL]);
+    args = ["/D", "/Q", "/C", appBinPath, "-no-remote", "-test-process-updates",
+            "-wait-for-browser"].concat(aExtraArgs).concat([PIPE_TO_NULL]);
   }
   return args;
 }
 
 /**
  * Gets a file path for the application to dump its arguments into.  This is used
  * to verify that a callback application is launched.
  *
@@ -3976,16 +4005,18 @@ function adjustGeneralPaths() {
         case NS_GRE_DIR:
           return getApplyDirFile(DIR_RESOURCES, true);
         case NS_GRE_BIN_DIR:
           return getApplyDirFile(DIR_MACOS, true);
         case XRE_EXECUTABLE_FILE:
           return getApplyDirFile(DIR_MACOS + FILE_APP_BIN, true);
         case XRE_UPDATE_ROOT_DIR:
           return getMockUpdRootD();
+        case XRE_OLD_UPDATE_ROOT_DIR:
+          return getMockUpdRootD(true);
       }
       return null;
     },
     QueryInterface: ChromeUtils.generateQI([Ci.nsIDirectoryServiceProvider]),
   };
   let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
   ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_DIR);
   ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_BIN_DIR);
copy from toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js
copy to toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js
--- a/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js
@@ -1,27 +1,117 @@
 /* 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/.
  */
 
+/**
+ * Gets the root directory for the old (unmigrated) updates directory.
+ *
+ * @return nsIFile for the updates root directory.
+ */
+function getOldUpdatesRootDir() {
+  return Services.dirsvc.get(XRE_OLD_UPDATE_ROOT_DIR, Ci.nsIFile);
+}
+
+/**
+ * Gets the old (unmigrated) updates directory.
+ *
+ * @return nsIFile for the updates directory.
+ */
+function getOldUpdatesDir() {
+  let dir = getOldUpdatesRootDir();
+  dir.append(DIR_UPDATES);
+  return dir;
+}
+
+/**
+ * Gets the directory for update patches in the old (unmigrated) updates
+ * directory.
+ *
+ * @return nsIFile for the updates directory.
+ */
+function getOldUpdatesPatchDir() {
+  let dir = getOldUpdatesDir();
+  dir.append(DIR_PATCH);
+  return dir;
+}
+
+/**
+ * Returns either the active or regular update database XML file in the old
+ * (unmigrated) updates directory
+ *
+ * @param  isActiveUpdate
+ *         If true this will return the active-update.xml otherwise it will
+ *         return the updates.xml file.
+ */
+function getOldUpdatesXMLFile(aIsActiveUpdate) {
+  let file = getOldUpdatesRootDir();
+  file.append(aIsActiveUpdate ? FILE_ACTIVE_UPDATE_XML : FILE_UPDATES_XML);
+  return file;
+}
+
+/**
+ * Writes the updates specified to either the active-update.xml or the
+ * updates.xml in the old (unmigrated) update directory
+ *
+ * @param  aContent
+ *         The updates represented as a string to write to the XML file.
+ * @param  isActiveUpdate
+ *         If true this will write to the active-update.xml otherwise it will
+ *         write to the updates.xml file.
+ */
+function writeUpdatesToOldXMLFile(aContent, aIsActiveUpdate) {
+  writeFile(getOldUpdatesXMLFile(aIsActiveUpdate), aContent);
+}
+
+/**
+ * Writes the given update operation/state to a file in the old (unmigrated)
+ * patch directory, indicating to the patching system what operations need
+ * to be performed.
+ *
+ * @param  aStatus
+ *         The status value to write.
+ */
+function writeOldStatusFile(aStatus) {
+  let file = getOldUpdatesPatchDir();
+  file.append(FILE_UPDATE_STATUS);
+  writeFile(file, aStatus + "\n");
+}
+
+/**
+ * Gets the specified update log from the old (unmigrated) update directory
+ *
+ * @param   aLogLeafName
+ *          The leaf name of the log to get.
+ * @return  nsIFile for the update log.
+ */
+function getOldUpdateLog(aLogLeafName) {
+  let updateLog = getOldUpdatesDir();
+  if (aLogLeafName == FILE_UPDATE_LOG) {
+    updateLog.append(DIR_PATCH);
+  }
+  updateLog.append(aLogLeafName);
+  return updateLog;
+}
+
 function run_test() {
   setupTestCommon();
 
-  debugDump("testing that the update.log is moved after a successful update");
+  debugDump("testing that the update directory is migrated after a successful update");
 
   Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, 5);
 
   let patchProps = {state: STATE_PENDING};
   let patches = getLocalPatchString(patchProps);
   let updates = getLocalUpdateString({}, patches);
-  writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
-  writeStatusFile(STATE_SUCCEEDED);
+  writeUpdatesToOldXMLFile(getLocalUpdatesXMLString(updates), true);
+  writeOldStatusFile(STATE_SUCCEEDED);
 
-  let log = getUpdateLog(FILE_UPDATE_LOG);
+  let log = getOldUpdateLog(FILE_UPDATE_LOG);
   writeFile(log, "Last Update Log");
 
   standardInit();
 
   Assert.ok(!gUpdateManager.activeUpdate,
             "there should not be an active update");
   Assert.equal(gUpdateManager.updateCount, 1,
                "the update manager update count" + MSG_SHOULD_EQUAL);
@@ -32,16 +122,23 @@ function run_test() {
  * Called after the call to waitForUpdateXMLFiles finishes.
  */
 function waitForUpdateXMLFilesFinished() {
   let cancelations = Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS, 0);
   Assert.equal(cancelations, 0,
                "the " + PREF_APP_UPDATE_CANCELATIONS + " preference " +
                MSG_SHOULD_EQUAL);
 
+  let oldDir = getOldUpdatesRootDir();
+  let newDir = getUpdatesRootDir();
+  if (oldDir.path != newDir.path) {
+    Assert.ok(!oldDir.exists(),
+              "Old update directory should have been deleted after migration");
+  }
+
   let log = getUpdateLog(FILE_UPDATE_LOG);
   Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
 
   log = getUpdateLog(FILE_LAST_UPDATE_LOG);
   Assert.ok(log.exists(), MSG_SHOULD_EXIST);
   Assert.equal(readFile(log), "Last Update Log",
                "the last update log contents" + MSG_SHOULD_EQUAL);
 
--- a/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
+++ b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
@@ -23,8 +23,11 @@ head = head_update.js
 [downloadInterruptedOffline.js]
 [downloadInterruptedNoRecovery.js]
 [downloadInterruptedRecovery.js]
 [downloadResumeForSameAppVersion.js]
 [downloadCompleteAfterPartialFailure.js]
 [uiSilentPref.js]
 [uiUnsupportedAlreadyNotified.js]
 [uiAutoPref.js]
+[updateDirectoryMigrate.js]
+skip-if = os != 'win'
+reason = Update directory migration is currently Windows only
--- a/toolkit/mozapps/update/updater/updater-common.build
+++ b/toolkit/mozapps/update/updater/updater-common.build
@@ -17,17 +17,16 @@ if CONFIG['MOZ_VERIFY_MAR_SIGNATURE']:
         'verifymar',
     ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     have_progressui = 1
     srcs += [
         'loaddlls.cpp',
         'progressui_win.cpp',
-        'win_dirent.cpp',
     ]
     RCINCLUDE = '%supdater.rc' % updater_rel_path
     DEFINES['UNICODE'] = True
     DEFINES['_UNICODE'] = True
     USE_STATIC_LIBS = True
 
     # Pick up nsWindowsRestart.cpp
     LOCAL_INCLUDES += [
--- a/toolkit/xre/moz.build
+++ b/toolkit/xre/moz.build
@@ -42,16 +42,17 @@ if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']:
     EXPORTS += ['EventTracer.h']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXPORTS.mozilla += [
         'PolicyChecks.h',
         'WinDllServices.h',
     ]
     UNIFIED_SOURCES += [
+        '/toolkit/mozapps/update/common/win_dirent.cpp',
         'nsNativeAppSupportWin.cpp',
         'WinDllServices.cpp',
     ]
     DEFINES['PROXY_PRINTING'] = 1
     LOCAL_INCLUDES += [
         '../components/printingui',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
@@ -93,16 +94,17 @@ if CONFIG['MOZ_X11']:
     ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     UNIFIED_SOURCES += [
         'nsAndroidStartup.cpp',
     ]
 
 UNIFIED_SOURCES += [
+    '/toolkit/mozapps/update/common/commonupdatedir.cpp',
     'AutoSQLiteLifetime.cpp',
     'Bootstrap.cpp',
     'CreateAppData.cpp',
     'nsAppStartupNotifier.cpp',
     'nsConsoleWriter.cpp',
     'nsEmbeddingModule.cpp',
     'nsNativeAppSupportBase.cpp',
     'nsSigHandlers.cpp',
--- a/toolkit/xre/nsUpdateDriver.cpp
+++ b/toolkit/xre/nsUpdateDriver.cpp
@@ -20,44 +20,48 @@
 #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"
 
 #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 "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");
 #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_BIN "org.mozilla.updater"
 #else
 #define UPDATER_BIN "updater"
 #endif
 #define UPDATER_INI "updater.ini"
 #ifdef XP_MACOSX
 #define UPDATER_APP "updater.app"
@@ -894,17 +898,244 @@ nsUpdateProcessor::ProcessUpdate(nsIUpda
   nsCOMPtr<nsIRunnable> r =
     NewRunnableMethod("nsUpdateProcessor::StartStagedUpdate",
                       this,
                       &nsUpdateProcessor::StartStagedUpdate);
   return NS_NewNamedThread("Update Watcher", getter_AddRefs(mProcessWatcher),
                            r);
 }
 
+NS_IMETHODIMP
+nsUpdateProcessor::FixUpdateDirectoryPerms(bool aShouldUseService)
+{
+#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 aShouldUseService,
+                                    const nsAutoString& aInstallPath)
+      : Runnable(aName)
+      , mShouldUseService(aShouldUseService)
+      , mState(State::Initializing)
+    {
+      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
+    {
+      // 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;
+
+      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(), nullptr,
+                               nullptr, SetPermissionsOf::AllFilesAndDirs,
+                               updateDir);
+        if (SUCCEEDED(permResult)) {
+          LOG(("Successfully fixed permissions from within Firefox\n"));
+          return NS_OK;
+        } else if (!mShouldUseService) {
+          LOG(("Error: Unable to fix permissions within Firefox and "
+               "maintenance service is disabled\n"));
+          return ReportUpdateError();
+        }
+
+        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;
+      }
+      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);
+      }
+      // 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:
+    bool mShouldUseService;
+    unsigned int mCurrentTry;
+    State mState;
+    mozilla::UniquePtr<wchar_t[]> mInstallPath;
+    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);
+    }
+    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",
+                                        aShouldUseService, 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,
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -38,21 +38,21 @@
 
 #include "mozilla/AutoRestore.h"
 #include "mozilla/Services.h"
 #include "mozilla/Omnijar.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 
 #include <stdlib.h>
-#include "city.h"
 
 #ifdef XP_WIN
 #include <windows.h>
 #include <shlobj.h>
+#include "commonupdatedir.h"
 #endif
 #ifdef XP_MACOSX
 #include "nsILocalFileMac.h"
 // for chflags()
 #include <sys/stat.h>
 #include <unistd.h>
 #endif
 #ifdef XP_UNIX
@@ -430,16 +430,19 @@ nsXREDirProvider::GetFile(const char* aP
     if (NS_SUCCEEDED(rv)) {
       localDir.swap(file);
     }
   }
 #endif
   else if (!strcmp(aProperty, XRE_UPDATE_ROOT_DIR)) {
     rv = GetUpdateRootDir(getter_AddRefs(file));
   }
+  else if (!strcmp(aProperty, XRE_OLD_UPDATE_ROOT_DIR)) {
+    rv = GetUpdateRootDir(getter_AddRefs(file), true);
+  }
   else if (!strcmp(aProperty, NS_APP_APPLICATION_REGISTRY_FILE)) {
     rv = GetUserAppDataDirectory(getter_AddRefs(file));
     if (NS_SUCCEEDED(rv))
       rv = file->AppendNative(NS_LITERAL_CSTRING(APP_REGISTRY_NAME));
   }
   else if (!strcmp(aProperty, NS_APP_USER_PROFILES_ROOT_DIR)) {
     rv = GetUserProfilesRootDir(getter_AddRefs(file));
   }
@@ -1251,110 +1254,62 @@ GetRegWindowsAppDataFolder(bool aLocal, 
 
   if (!_retval.CharAt(resultLen - 1)) {
     // It was already null terminated.
     _retval.Truncate(resultLen - 1);
   }
 
   return NS_OK;
 }
-
-static bool
-GetCachedHash(HKEY rootKey, const nsAString &regPath, const nsAString &path,
-              nsAString &cachedHash)
-{
-  HKEY baseKey;
-  if (RegOpenKeyExW(rootKey, reinterpret_cast<const wchar_t*>(regPath.BeginReading()), 0, KEY_READ, &baseKey) !=
-      ERROR_SUCCESS) {
-    return false;
-  }
-
-  wchar_t cachedHashRaw[512];
-  DWORD bufferSize = sizeof(cachedHashRaw);
-  LONG result = RegQueryValueExW(baseKey, reinterpret_cast<const wchar_t*>(path.BeginReading()), 0, nullptr,
-                                 (LPBYTE)cachedHashRaw, &bufferSize);
-  RegCloseKey(baseKey);
-  if (result == ERROR_SUCCESS) {
-    cachedHash.Assign(cachedHashRaw);
-  }
-  return ERROR_SUCCESS == result;
-}
-
 #endif
 
-// Temporary for nsIXREDirProvider until compatibility mode goes away.
 nsresult
 nsXREDirProvider::GetInstallHash(nsAString & aPathHash)
 {
-  return GetInstallHash(aPathHash, false);
-}
-
-// Compatibility Mode (aUseCompatibilityMode) outputs hashes that are what this
-// function has historically returned. The new default is to output hashes that
-// are consistent with those generated by the installer.
-nsresult
-nsXREDirProvider::GetInstallHash(nsAString & aPathHash, bool aUseCompatibilityMode)
-{
-  nsCOMPtr<nsIFile> updRoot;
+  nsCOMPtr<nsIFile> installDir;
   nsCOMPtr<nsIFile> appFile;
   bool per = false;
   nsresult rv = GetFile(XRE_EXECUTABLE_FILE, &per, getter_AddRefs(appFile));
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = appFile->GetParent(getter_AddRefs(updRoot));
+  rv = appFile->GetParent(getter_AddRefs(installDir));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsAutoString appDirPath;
-  rv = updRoot->GetPath(appDirPath);
+  nsAutoString installPath;
+  rv = installDir->GetPath(installPath);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  aPathHash.Truncate();
-
-#ifdef XP_WIN
-  // Figure out where we should check for a cached hash value. If the
-  // application doesn't have the nsXREAppData vendor value defined check
-  // under SOFTWARE\Mozilla.
-  bool hasVendor = GetAppVendor() && strlen(GetAppVendor()) != 0;
-  wchar_t regPath[1024] = { L'\0' };
-  swprintf_s(regPath, mozilla::ArrayLength(regPath), L"SOFTWARE\\%S\\%S\\TaskBarIDs",
-              (hasVendor ? GetAppVendor() : "Mozilla"), MOZ_APP_BASENAME);
-
-  // If we pre-computed the hash, grab it from the registry.
-  if (GetCachedHash(HKEY_LOCAL_MACHINE, nsDependentString(regPath), appDirPath,
-                    aPathHash)) {
-    return NS_OK;
+  const char* vendor = GetAppVendor();
+  if (vendor && vendor[0] == '\0') {
+    vendor = nullptr;
   }
 
-  if (GetCachedHash(HKEY_CURRENT_USER, nsDependentString(regPath), appDirPath,
-                    aPathHash)) {
-    return NS_OK;
-  }
-#endif
+  mozilla::UniquePtr<NS_tchar[]> hash;
+  rv = ::GetInstallHash(PromiseFlatString(installPath).get(), vendor, hash);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  // This should only happen when the installer isn't used (e.g. zip builds).
-  void* buffer = appDirPath.BeginWriting();
-  uint32_t length = appDirPath.Length() * sizeof(nsAutoString::char_type);
-  uint64_t hash = CityHash64(static_cast<const char*>(buffer), length);
-  if (aUseCompatibilityMode) {
-    aPathHash.AppendInt((int)(hash >> 32), 16);
-    aPathHash.AppendInt((int)hash, 16);
-    // The installer implementation writes the registry values that were checked
-    // in the previous block for this value in uppercase and since it is an
-    // option to have a case sensitive file system on Windows this value must
-    // also be in uppercase.
-    ToUpperCase(aPathHash);
-  } else {
-    aPathHash.AppendPrintf("%" PRIX64, hash);
-  }
-
+  // The hash string is a NS_tchar*, which is wchar* in Windows and char*
+  // elsewhere.
+#ifdef XP_WIN
+  aPathHash.Assign(hash.get());
+#else
+  aPathHash.AssignASCII(hash.get());
+#endif
   return NS_OK;
 }
 
 nsresult
-nsXREDirProvider::GetUpdateRootDir(nsIFile* *aResult)
+nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult, bool aGetOldLocation)
 {
+#ifndef XP_WIN
+  // There is no old update location on platforms other than Windows. Windows is
+  // the only platform for which we migrated the update directory.
+  if (aGetOldLocation) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+#endif
   nsCOMPtr<nsIFile> updRoot;
   nsCOMPtr<nsIFile> appFile;
   bool per = false;
   nsresult rv = GetFile(XRE_EXECUTABLE_FILE, &per, getter_AddRefs(appFile));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = appFile->GetParent(getter_AddRefs(updRoot));
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1389,84 +1344,46 @@ nsXREDirProvider::GetUpdateRootDir(nsIFi
       NS_FAILED(localDir->AppendRelativePath(appDirPath))) {
     return NS_ERROR_FAILURE;
   }
 
   localDir.forget(aResult);
   return NS_OK;
 
 #elif XP_WIN
-  nsAutoString pathHash;
-  rv = GetInstallHash(pathHash, true);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // As a last ditch effort, get the local app data directory and if a vendor
-  // name exists append it. If only a product name exists, append it. If neither
-  // exist fallback to old handling. We don't use the product name on purpose
-  // because we want a shared update directory for different apps run from the
-  // same path.
-  nsCOMPtr<nsIFile> localDir;
-  bool hasVendor = GetAppVendor() && strlen(GetAppVendor()) != 0;
-  if ((hasVendor || GetAppName()) &&
-      NS_SUCCEEDED(GetUserDataDirectoryHome(getter_AddRefs(localDir), true)) &&
-      NS_SUCCEEDED(localDir->AppendNative(nsDependentCString(hasVendor ?
-                                          GetAppVendor() : GetAppName()))) &&
-      NS_SUCCEEDED(localDir->Append(NS_LITERAL_STRING("updates"))) &&
-      NS_SUCCEEDED(localDir->Append(pathHash))) {
-    localDir.forget(aResult);
-    return NS_OK;
-  }
-
-  nsAutoString appPath;
-  rv = updRoot->GetPath(appPath);
+  nsAutoString installPath;
+  rv = updRoot->GetPath(installPath);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // AppDir may be a short path. Convert to long path to make sure
-  // the consistency of the update folder location
-  nsString longPath;
-  wchar_t* buf;
-
-  uint32_t bufLength = longPath.GetMutableData(&buf, MAXPATHLEN);
-  NS_ENSURE_TRUE(bufLength >= MAXPATHLEN, NS_ERROR_OUT_OF_MEMORY);
-
-  DWORD len = GetLongPathNameW(appPath.get(), buf, bufLength);
-
-  // Failing GetLongPathName() is not fatal.
-  if (len <= 0 || len >= bufLength)
-    longPath.Assign(appPath);
-  else
-    longPath.SetLength(len);
-
-  // Use <UserLocalDataDir>\updates\<relative path to app dir from
-  // Program Files> if app dir is under Program Files to avoid the
-  // folder virtualization mess on Windows Vista
-  nsAutoString programFiles;
-  rv = GetShellFolderPath(FOLDERID_ProgramFiles, programFiles);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  programFiles.Append('\\');
-  uint32_t programFilesLen = programFiles.Length();
-
-  nsAutoString programName;
-  if (_wcsnicmp(programFiles.get(), longPath.get(), programFilesLen) == 0) {
-    programName = Substring(longPath, programFilesLen);
-  } else {
-    // We need the update root directory to live outside of the installation
-    // directory, because otherwise the updater writing the log file can cause
-    // the directory to be locked, which prevents it from being replaced after
-    // background updates.
-    programName.AssignASCII(MOZ_APP_NAME);
+  const char* vendor = GetAppVendor();
+  if (vendor && vendor[0] == '\0') {
+    vendor = nullptr;
+  }
+  const char* appName = GetAppName();
+  if (appName && appName[0] == '\0') {
+    appName = nullptr;
   }
 
-  rv = GetUserLocalDataDirectory(getter_AddRefs(updRoot));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = updRoot->AppendRelativePath(programName);
-  NS_ENSURE_SUCCESS(rv, rv);
-
+  mozilla::UniquePtr<wchar_t[]> updatePath;
+  HRESULT hrv;
+  if (aGetOldLocation) {
+    hrv = GetUserUpdateDirectory(PromiseFlatString(installPath).get(), vendor,
+                                 appName, updatePath);
+  } else {
+    hrv = GetCommonUpdateDirectory(PromiseFlatString(installPath).get(), vendor,
+                                   appName,
+                                   SetPermissionsOf::BaseDirIfNotExists,
+                                   updatePath);
+  }
+  if (FAILED(hrv)) {
+    return NS_ERROR_FAILURE;
+  }
+  nsAutoString updatePathStr;
+  updatePathStr.Assign(updatePath.get());
+  updRoot->InitWithPath(updatePathStr);
 #endif // XP_WIN
   updRoot.forget(aResult);
   return NS_OK;
 }
 
 nsresult
 nsXREDirProvider::GetProfileStartupDir(nsIFile* *aResult)
 {
--- a/toolkit/xre/nsXREDirProvider.h
+++ b/toolkit/xre/nsXREDirProvider.h
@@ -74,26 +74,28 @@ public:
   nsIFile* GetGREBinDir() { return mGREBinDir; }
   nsIFile* GetAppDir() {
     if (mXULAppDir)
       return mXULAppDir;
     return mGREDir;
   }
 
   /**
-   * Get a hash for the install directory.
-   */
-  nsresult GetInstallHash(nsAString & aPathHash, bool aUseCompatibilityMode);
-
-  /**
    * Get the directory under which update directory is created.
    * This method may be called before XPCOM is started. aResult
    * is a clone, it may be modified.
+   *
+   * If aGetOldLocation is true, this function will return the location of
+   * the update directory before it was moved from the user profile directory
+   * to a per-installation directory. This functionality is only meant to be
+   * used for migration of the update directory to the new location. It is only
+   * valid to request the old update location on Windows, since that is the only
+   * platform on which the update directory was migrated.
    */
-  nsresult GetUpdateRootDir(nsIFile* *aResult);
+  nsresult GetUpdateRootDir(nsIFile** aResult, bool aGetOldLocation = false);
 
   /**
    * Get the profile startup directory as determined by this class or by
    * mAppProvider. This method may be called before XPCOM is started. aResult
    * is a clone, it may be modified.
    */
   nsresult GetProfileStartupDir(nsIFile* *aResult);
 
--- a/xpcom/base/nsWindowsHelpers.h
+++ b/xpcom/base/nsWindowsHelpers.h
@@ -292,27 +292,47 @@ public:
 
   static void Release(RawRef hPrinter)
   {
     ::ClosePrinter(hPrinter);
   }
 };
 
 
+template<>
+class nsAutoRefTraits<PSID>
+{
+public:
+  typedef PSID RawRef;
+  static RawRef Void()
+  {
+    return nullptr;
+  }
+
+  static void Release(RawRef aFD)
+  {
+    if (aFD != Void()) {
+      FreeSid(aFD);
+    }
+  }
+};
+
+
 typedef nsAutoRef<HKEY> nsAutoRegKey;
 typedef nsAutoRef<HDC> nsAutoHDC;
 typedef nsAutoRef<HBRUSH> nsAutoBrush;
 typedef nsAutoRef<HRGN> nsAutoRegion;
 typedef nsAutoRef<HBITMAP> nsAutoBitmap;
 typedef nsAutoRef<SC_HANDLE> nsAutoServiceHandle;
 typedef nsAutoRef<HANDLE> nsAutoHandle;
 typedef nsAutoRef<HMODULE> nsModuleHandle;
 typedef nsAutoRef<DEVMODEW*> nsAutoDevMode;
 typedef nsAutoRef<nsHGLOBAL> nsAutoGlobalMem;
 typedef nsAutoRef<nsHPRINTER> nsAutoPrinter;
+typedef nsAutoRef<PSID> nsAutoSid;
 
 namespace {
 
 // Construct a path "<system32>\<aModule>". return false if the output buffer
 // is too small.
 // Note: If the system path cannot be found, or doesn't fit in the output buffer
 // with the module name, we will just ignore the system path and output the
 // module name alone;
@@ -363,9 +383,17 @@ LoadLibrarySystem32(LPCWSTR aModule)
   if (!ConstructSystem32Path(aModule, systemPath, MAX_PATH + 1)) {
     return NULL;
   }
   return LoadLibraryW(systemPath);
 }
 
 }
 
+struct LocalFreeDeleter
+{
+  void operator()(void* aPtr)
+  {
+    ::LocalFree(aPtr);
+  }
+};
+
 #endif
--- a/xpcom/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -150,41 +150,55 @@
  * Should be a synonym for XCurProcD everywhere except in tests.
  */
 #define XRE_ADDON_APP_DIR "XREAddonAppDir"
 
 /**
  * A directory service key which provides the update directory. Callers should
  * fall back to appDir.
  * Windows:    If vendor name exists:
- *             Documents and Settings\<User>\Local Settings\Application Data\
- *             <vendor name>\updates\
- *             <hash of the path to XRE_EXECUTABLE_FILE’s parent directory>
+ *             ProgramData\<vendor name>\updates\
+ *             <hash of the path to XRE_EXECUTABLE_FILE's parent directory>
  *
  *             If vendor name doesn't exist, but product name exists:
- *             Documents and Settings\<User>\Local Settings\Application Data\
- *             <product name>\updates\
- *             <hash of the path to XRE_EXECUTABLE_FILE’s parent directory>
+ *             ProgramData\<product name>\updates\
+ *             <hash of the path to XRE_EXECUTABLE_FILE's parent directory>
  *
  *             If neither vendor nor product name exists:
- *               If app dir is under Program Files:
- *               Documents and Settings\<User>\Local Settings\Application Data\
- *               <relative path to app dir from Program Files>
- *
- *               If app dir isn’t under Program Files:
- *               Documents and Settings\<User>\Local Settings\Application Data\
- *               <MOZ_APP_NAME>
+ *             ProgramData\Mozilla\updates
  *
  * Mac:        ~/Library/Caches/Mozilla/updates/<absolute path to app dir>
  *
  * All others: Parent directory of XRE_EXECUTABLE_FILE.
  */
 #define XRE_UPDATE_ROOT_DIR "UpdRootD"
 
 /**
+ * A directory service key which provides the *old* update directory. This
+ * path should only be used when data needs to be migrated from the old update
+ * directory.
+ * Windows:    If vendor name exists:
+ *             Documents and Settings\<User>\Local Settings\Application Data\
+ *             <vendor name>\updates\
+ *             <hash of the path to XRE_EXECUTABLE_FILE's parent directory>
+ *
+ *             If vendor name doesn't exist, but product name exists:
+ *             Documents and Settings\<User>\Local Settings\Application Data\
+ *             <product name>\updates\
+ *             <hash of the path to XRE_EXECUTABLE_FILE's parent directory>
+ *
+ *             If neither vendor nor product name exists:
+ *             Documents and Settings\<User>\Local Settings\Application Data\
+ *             Mozilla\updates
+ *
+ * This path does not exist on other operating systems
+ */
+#define XRE_OLD_UPDATE_ROOT_DIR "OldUpdRootD"
+
+/**
  * Begin an XUL application. Does not return until the user exits the
  * application.
  *
  * @param argc/argv Command-line parameters to pass to the application. On
  *                  Windows, these should be in UTF8. On unix-like platforms
  *                  these are in the "native" character set.
  *
  * @param aConfig  Information about the application to be run.