Bug 1458314 - Move the update directory to an installation specific location draft
authorKirk Steuber <ksteuber@mozilla.com>
Fri, 25 May 2018 14:13:11 -0700
changeset 828129 8def30df2e40f81246eaaa8277a0225f35538256
parent 827540 17116905bc072c37d74226ccc46c93f0bd45d516
child 828247 ea39bfee3a08b7a0a18726827fbd22747291e47a
child 829191 bbd4561e14d30b21f81fc286e78f951dd1e06755
push id118633
push userbmo:ksteuber@mozilla.com
push dateThu, 09 Aug 2018 23:45:41 +0000
bugs1458314
milestone63.0a1
Bug 1458314 - Move the update directory to an installation specific location 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` 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. MozReview-Commit-ID: AFVMlAcT2LG
toolkit/mozapps/installer/windows/nsis/common.nsh
toolkit/mozapps/update/nsUpdateServiceStub.js
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/xre/nsXREDirProvider.cpp
toolkit/xre/nsXREDirProvider.h
xpcom/build/nsXULAppAPI.h
xpcom/io/SpecialSystemDirectory.cpp
xpcom/io/SpecialSystemDirectory.h
--- a/toolkit/mozapps/installer/windows/nsis/common.nsh
+++ b/toolkit/mozapps/installer/windows/nsis/common.nsh
@@ -3280,51 +3280,75 @@
           ; 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-old-last-update.log"
+          ${EndIf}
+
+          ${If} ${FileExists} "$R0\updates\backup-update.log"
+            Rename "$R0\updates\backup-update.log" "$TEMP\moz-update-old-backup-update.log"
           ${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}
+          ; 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 Common Documents directory
+          ; (currently C:\Users\Public\Documents).
+          ; This system call gets that directory path. The arguments are:
+          ;   A null ptr for hwnd
+          ;   $R0 for the output string
+          ;   CSIDL_COMMON_DOCUMENTS == 0x002e == 46 for the csidl indicating which dir to get
+          ;   false for fCreate (i.e. Do not create the folder if it doesn't exist)
+          System::Call "Shell32::SHGetSpecialFolderPathW(p 0, t.R0, i 46, i 0)"
+          StrCpy $R0 "$R0\$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 new updates directory
+          ${If} ${FileExists} "$R0\updates"
+            RmDir /r "$R0"
           ${EndIf}
         ${EndIf}
       ${EndIf}
 
       ClearErrors
 
       Pop $R0
       Pop $R1
--- a/toolkit/mozapps/update/nsUpdateServiceStub.js
+++ b/toolkit/mozapps/update/nsUpdateServiceStub.js
@@ -1,43 +1,199 @@
 /* -*- 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";
+
+// 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);
+});
 
 /**
  * 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);
 }
 
 function UpdateServiceStub() {
-  let statusFile = getUpdateDirNoCreate([DIR_UPDATES, "0"]);
+  let updateDir = getUpdateDirNoCreate([DIR_UPDATES, "0"]);
+  let prefUpdateDirMigrated = PREF_PREFIX_UPDATE_DIR_MIGRATED
+                            + updateDir.leafName;
+
+  let statusFile = updateDir;
   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, [], true);
+
+  if (!sourceRootDir.exists()) {
+    LOG("UpdateServiceStub:_migrateUpdateDirectory - Abort: No migration " +
+        "necessary. Nothing to migrate.");
+    return;
+  }
+
+  sourceRootDir.normalize();
+  destRootDir.normalize();
+  if (sourceRootDir.equals(destRootDir)) {
+    LOG("UpdateServiceStub:_migrateUpdateDirectory - Abort: No migration " +
+        "necessary. Update directory has not moved.");
+    return;
+  }
+
+  let destRootEmpty;
+  try {
+    destRootEmpty = !destRootDir.directoryEntries.hasMoreElements();
+  } catch (e) {
+    // destRootDir.directoryEntries will throw if the root directory does not
+    // exist.
+    destRootEmpty = true;
+  }
+
+  if (!destRootEmpty) {
+    // 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/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
@@ -1591,55 +1591,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, "gCommonDocumentsDir", function test_gCDD() {
+  if (!IS_WIN) {
+    do_throw("Windows only function called by a different platform!");
+  }
+
+  const CSIDL_COMMON_DOCUMENTS = 0x002e;
+  return getSpecialFolderDir(CSIDL_COMMON_DOCUMENTS);
+});
+
 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 = gCommonDocumentsDir.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,21 +1682,21 @@ 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;
 }
 
-XPCOMUtils.defineLazyGetter(this, "gUpdatesRootDir", function test_gURD() {
+XPCOMUtils.defineLazyGetter(this, "gUpdatesRootDir", function test_gOURD() {
   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) {
     dir.append(MOZ_APP_VENDOR ? MOZ_APP_VENDOR : MOZ_APP_BASENAME);
@@ -3976,16 +3994,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,124 @@
 /* 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");
+
+  // Since we are testing migration, make sure the current update directory does
+  // not exist, since otherwise Firefox will assume migration has already
+  // occurred.
+  try {
+    getUpdatesRootDir().remove(true);
+  } catch (ex) {}
 
   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 +129,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,10 @@ 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' # Update directory migration happens on Windows only
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -396,16 +396,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));
   }
@@ -1172,18 +1175,25 @@ GetCachedHash(HKEY rootKey, const nsAStr
     cachedHash.Assign(cachedHashRaw);
   }
   return ERROR_SUCCESS == result;
 }
 
 #endif
 
 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);
 
@@ -1256,32 +1266,41 @@ nsXREDirProvider::GetUpdateRootDir(nsIFi
     pathHash.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(pathHash);
   }
 
-  // As a last ditch effort, get the local app data directory and if a vendor
+  // 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;
-  if ((hasVendor || gAppData->name) &&
-      NS_SUCCEEDED(GetUserDataDirectoryHome(getter_AddRefs(localDir), true)) &&
-      NS_SUCCEEDED(localDir->AppendNative(nsDependentCString(hasVendor ?
-                                          gAppData->vendor : gAppData->name))) &&
-      NS_SUCCEEDED(localDir->Append(NS_LITERAL_STRING("updates"))) &&
-      NS_SUCCEEDED(localDir->Append(pathHash))) {
-    localDir.forget(aResult);
-    return NS_OK;
+  if (hasVendor || gAppData->name) {
+    if (aGetOldLocation) {
+      rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), true);
+    } else {
+      rv = GetSpecialSystemDirectory(Win_Common_Documents,
+                                     getter_AddRefs(localDir));
+    }
+    if (NS_SUCCEEDED(rv) &&
+        NS_SUCCEEDED(localDir->AppendNative(nsDependentCString(hasVendor ?
+                                            gAppData->vendor : gAppData->name))) &&
+        NS_SUCCEEDED(localDir->Append(NS_LITERAL_STRING("updates"))) &&
+        NS_SUCCEEDED(localDir->Append(pathHash))) {
+      localDir.forget(aResult);
+      return NS_OK;
+    }
   }
 
+  // Falling back to old handling
+
   nsAutoString appPath;
   rv = updRoot->GetPath(appPath);
   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;
@@ -1313,17 +1332,21 @@ nsXREDirProvider::GetUpdateRootDir(nsIFi
   } 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);
   }
 
-  rv = GetUserLocalDataDirectory(getter_AddRefs(updRoot));
+  if (aGetOldLocation) {
+    rv = GetUserLocalDataDirectory(getter_AddRefs(updRoot));
+  } else {
+    rv = GetSpecialSystemDirectory(Win_Common_Documents, getter_AddRefs(updRoot));
+  }
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = updRoot->AppendRelativePath(programName);
   NS_ENSURE_SUCCESS(rv, rv);
 
 #endif // XP_WIN
   updRoot.forget(aResult);
   return NS_OK;
--- a/toolkit/xre/nsXREDirProvider.h
+++ b/toolkit/xre/nsXREDirProvider.h
@@ -68,18 +68,25 @@ public:
       return mXULAppDir;
     return mGREDir;
   }
 
   /**
    * 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/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -150,39 +150,61 @@
  * 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:
+ *             ProgramData\<vendor 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:
+ *               ProgramData\<relative path to app dir from Program Files>
+ *
+ *               If app dir isn't under Program Files:
+ *               ProgramData\<MOZ_APP_NAME>
+ *
+ * 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.
+ * 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>
+ *             <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>
+ *             <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:
+ *               If app dir isn't under Program Files:
  *               Documents and Settings\<User>\Local Settings\Application Data\
  *               <MOZ_APP_NAME>
  *
  * Mac:        ~/Library/Caches/Mozilla/updates/<absolute path to app dir>
  *
  * All others: Parent directory of XRE_EXECUTABLE_FILE.
  */
-#define XRE_UPDATE_ROOT_DIR "UpdRootD"
+#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.
--- a/xpcom/io/SpecialSystemDirectory.cpp
+++ b/xpcom/io/SpecialSystemDirectory.cpp
@@ -635,16 +635,19 @@ GetSpecialSystemDirectory(SystemDirector
 #endif
 #if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
     case Win_Documents: {
       return GetLibrarySaveToPath(CSIDL_MYDOCUMENTS,
                                   FOLDERID_DocumentsLibrary,
                                   aFile);
     }
 #endif
+    case Win_Common_Documents: {
+      return GetWindowsFolder(CSIDL_COMMON_DOCUMENTS, aFile);
+    }
 #endif  // XP_WIN
 
 #if defined(XP_UNIX)
     case Unix_HomeDirectory:
       return GetUnixHomeDir(aFile);
 
     case Unix_XDG_Desktop:
     case Unix_XDG_Download:
--- a/xpcom/io/SpecialSystemDirectory.h
+++ b/xpcom/io/SpecialSystemDirectory.h
@@ -32,16 +32,17 @@ enum SystemDirectories {
   Win_ProgramFiles          =   225,
   Win_Downloads             =   226,
 #if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
   Win_Documents             =   228,
 #endif
 #if defined(MOZ_CONTENT_SANDBOX)
   Win_LocalAppdataLow       =   232,
 #endif
+  Win_Common_Documents      =   233,
 
   Unix_HomeDirectory        =   303,
   Unix_XDG_Desktop          =   304,
   Unix_XDG_Download         =   306
 };
 
 nsresult
 GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory,