Bug 1246972 - Always require the update working directory to be within the installation path. r=rstrong, a=ritu
authorMatt Howell <mhowell@mozilla.com>
Thu, 25 Aug 2016 15:31:13 -0700
changeset 350187 89ba44134660cdcf954e00ce8b387b0fbaeb7db3
parent 350186 404c54eecb4dcd1e9ee465ba054063471e310a5d
child 350188 df0e14b3f743afbc85a4c75a9e95789aedcf5c9a
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrstrong, ritu
bugs1246972
milestone50.0a2
Bug 1246972 - Always require the update working directory to be within the installation path. r=rstrong, a=ritu This required fixing a few chrome tests which broke on Mac because they were assuming updater-settings.ini would be at the same location as the executables. Also, this patch removes many dependencies on the current working directory from updater.cpp by changing it to use absolute paths instead. Otherwise this patch would have required adding yet more chdir() calls to avoid invalidating existing assumptions about what the current directory is. MozReview-Commit-ID: ASxfV4uVpmD
toolkit/mozapps/update/common/errors.h
toolkit/mozapps/update/tests/chrome/utils.js
toolkit/mozapps/update/tests/data/sharedUpdateXML.js
toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
toolkit/mozapps/update/tests/unit_base_updater/marWrongApplyToDirFailure_win.js
toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
toolkit/mozapps/update/updater/updater.cpp
--- a/toolkit/mozapps/update/common/errors.h
+++ b/toolkit/mozapps/update/common/errors.h
@@ -78,18 +78,19 @@
 #define WRITE_ERROR_PATCH_FILE 64
 #define WRITE_ERROR_APPLY_DIR_PATH 65
 #define WRITE_ERROR_CALLBACK_PATH 66
 #define WRITE_ERROR_FILE_ACCESS_DENIED 67
 #define WRITE_ERROR_DIR_ACCESS_DENIED 68
 #define WRITE_ERROR_DELETE_BACKUP 69
 #define WRITE_ERROR_EXTRACT 70
 #define REMOVE_FILE_SPEC_ERROR 71
-#define INVALID_STAGED_PARENT_ERROR 72
+#define INVALID_APPLYTO_DIR_STAGED_ERROR 72
 #define LOCK_ERROR_PATCH_FILE 73
+#define INVALID_APPLYTO_DIR_ERROR 74
 
 // Error codes 80 through 99 are reserved for nsUpdateService.js
 
 // The following error codes are only used by updater.exe
 // when a fallback key exists for tests.
 #define FALLBACKKEY_UNKNOWN_ERROR 100
 #define FALLBACKKEY_REGPATH_ERROR 101
 #define FALLBACKKEY_NOKEY_ERROR 102
--- a/toolkit/mozapps/update/tests/chrome/utils.js
+++ b/toolkit/mozapps/update/tests/chrome/utils.js
@@ -748,17 +748,17 @@ function verifyTestsRan() {
 }
 
 /**
  * Creates a backup of files the tests need to modify so they can be restored to
  * the original file when the test has finished and then modifies the files.
  */
 function setupFiles() {
   // Backup the updater-settings.ini file if it exists by moving it.
-  let baseAppDir = getAppBaseDir();
+  let baseAppDir = getGREDir();
   let updateSettingsIni = baseAppDir.clone();
   updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
   if (updateSettingsIni.exists()) {
     updateSettingsIni.moveTo(baseAppDir, FILE_UPDATE_SETTINGS_INI_BAK);
   }
   updateSettingsIni = baseAppDir.clone();
   updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
   writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS);
@@ -903,17 +903,17 @@ function setupPrefs() {
   Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false);
 }
 
 /**
  * Restores files that were backed up for the tests and general file cleanup.
  */
 function resetFiles() {
   // Restore the backed up updater-settings.ini if it exists.
-  let baseAppDir = getAppBaseDir();
+  let baseAppDir = getGREDir();
   let updateSettingsIni = baseAppDir.clone();
   updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI_BAK);
   if (updateSettingsIni.exists()) {
     updateSettingsIni.moveTo(baseAppDir, FILE_UPDATE_SETTINGS_INI);
   }
 
   // Not being able to remove the "updated" directory will not adversely affect
   // subsequent tests so wrap it in a try block and don't test whether its
--- a/toolkit/mozapps/update/tests/data/sharedUpdateXML.js
+++ b/toolkit/mozapps/update/tests/data/sharedUpdateXML.js
@@ -30,37 +30,43 @@ const STATE_PENDING         = "pending";
 const STATE_PENDING_SVC     = "pending-service";
 const STATE_APPLYING        = "applying";
 const STATE_APPLIED         = "applied";
 const STATE_APPLIED_SVC     = "applied-service";
 const STATE_SUCCEEDED       = "succeeded";
 const STATE_DOWNLOAD_FAILED = "download-failed";
 const STATE_FAILED          = "failed";
 
-const LOADSOURCE_ERROR_WRONG_SIZE = 2;
-const CRC_ERROR                   = 4;
-const READ_ERROR                  = 6;
-const WRITE_ERROR                 = 7;
-const MAR_CHANNEL_MISMATCH_ERROR  = 22;
-const VERSION_DOWNGRADE_ERROR     = 23;
+const LOADSOURCE_ERROR_WRONG_SIZE      = 2;
+const CRC_ERROR                        = 4;
+const READ_ERROR                       = 6;
+const WRITE_ERROR                      = 7;
+const MAR_CHANNEL_MISMATCH_ERROR       = 22;
+const VERSION_DOWNGRADE_ERROR          = 23;
+const INVALID_APPLYTO_DIR_STAGED_ERROR = 72;
+const INVALID_APPLYTO_DIR_ERROR        = 74;
 
 const STATE_FAILED_DELIMETER = ": ";
 
 const STATE_FAILED_LOADSOURCE_ERROR_WRONG_SIZE =
   STATE_FAILED + STATE_FAILED_DELIMETER + LOADSOURCE_ERROR_WRONG_SIZE;
 const STATE_FAILED_CRC_ERROR =
   STATE_FAILED + STATE_FAILED_DELIMETER + CRC_ERROR;
 const STATE_FAILED_READ_ERROR =
   STATE_FAILED + STATE_FAILED_DELIMETER + READ_ERROR;
 const STATE_FAILED_WRITE_ERROR =
   STATE_FAILED + STATE_FAILED_DELIMETER + WRITE_ERROR;
 const STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR =
   STATE_FAILED + STATE_FAILED_DELIMETER + MAR_CHANNEL_MISMATCH_ERROR;
 const STATE_FAILED_VERSION_DOWNGRADE_ERROR =
   STATE_FAILED + STATE_FAILED_DELIMETER + VERSION_DOWNGRADE_ERROR;
+const STATE_FAILED_INVALID_APPLYTO_DIR_STAGED_ERROR =
+  STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_APPLYTO_DIR_STAGED_ERROR;
+const STATE_FAILED_INVALID_APPLYTO_DIR_ERROR =
+  STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_APPLYTO_DIR_ERROR;
 
 /**
  * Constructs a string representing a remote update xml file.
  *
  * @param  aUpdates
  *         The string representing the update elements.
  * @return The string representing a remote update xml file.
  */
--- a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
+++ b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
@@ -148,16 +148,18 @@ var gStatusResult;
 var gProcess;
 var gAppTimer;
 var gHandle;
 
 var gGREDirOrig;
 var gGREBinDirOrig;
 var gAppDirOrig;
 
+var gApplyToDirOverride;
+
 var gServiceLaunchedCallbackLog = null;
 var gServiceLaunchedCallbackArgs = null;
 
 // Variables are used instead of contants so tests can override these values if
 // necessary.
 var gCallbackBinFile = "callback_app" + BIN_SUFFIX;
 var gCallbackArgs = ["./", "callback.log", "Test Arg 2", "Test Arg 3"];
 var gPostUpdateBinFile = "postup_app" + BIN_SUFFIX;
@@ -1213,16 +1215,28 @@ function getAppVersion() {
             MSG_SHOULD_EXIST + getMsgPath(iniFile.path));
   let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
                   getService(Ci.nsIINIParserFactory).
                   createINIParser(iniFile);
   return iniParser.getString("App", "Version");
 }
 
 /**
+ * Override the apply-to directory parameter to be passed to the updater.
+ * This ought to cause the updater to fail when using any value that isn't the
+ * default, automatically computed one.
+ *
+ * @param dir
+ *        Complete string to use as the apply-to directory parameter.
+ */
+function overrideApplyToDir(dir) {
+  gApplyToDirOverride = dir;
+}
+
+/**
  * Helper function for getting the relative path to the directory where the
  * application binary is located (e.g. <test_file_leafname>/dir.app/).
  *
  * Note: The dir.app subdirectory under <test_file_leafname> is needed for
  *       platforms other than Mac OS X so the tests can run in parallel due to
  *       update staging creating a lock file named moz_update_in_progress.lock in
  *       the parent directory of the installation directory.
  *
@@ -1781,20 +1795,20 @@ function runUpdateUsingUpdater(aExpected
 
   let callbackApp = getApplyDirFile(DIR_RESOURCES + gCallbackBinFile);
   callbackApp.permissions = PERMS_DIRECTORY;
 
   setAppBundleModTime();
 
   let args = [updatesDirPath, applyToDirPath];
   if (aSwitchApp) {
-    args[2] = stageDirPath;
+    args[2] = gApplyToDirOverride || stageDirPath;
     args[3] = "0/replace";
   } else {
-    args[2] = applyToDirPath;
+    args[2] = gApplyToDirOverride || applyToDirPath;
     args[3] = "0";
   }
   args = args.concat([callbackApp.parent.path, callbackApp.path]);
   args = args.concat(gCallbackArgs);
   debugDump("running the updater: " + updateBin.path + " " + args.join(" "));
 
   // See bug 1279108.
   // nsIProcess doesn't have an API to pass a separate environment to the
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marWrongApplyToDirFailure_win.js
@@ -0,0 +1,39 @@
+/* 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/.
+ */
+
+/* Test trying to use an apply-to directory different from the install
+ * directory, which should fail.
+ */
+
+function run_test() {
+  if (!setupTestCommon()) {
+    return;
+  }
+  gTestFiles = gTestFilesCompleteSuccess;
+  gTestDirs = gTestDirsCompleteSuccess;
+  setTestFilesAndDirsForFailure();
+  setupUpdaterTest(FILE_COMPLETE_MAR, false);
+}
+
+/**
+ * Called after the call to setupUpdaterTest finishes.
+ */
+function setupUpdaterTestFinished() {
+  overrideApplyToDir(getApplyDirPath() + "/../NoSuchDir");
+  // If execv is used the updater process will turn into the callback process
+  // and the updater's return code will be that of the callback process.
+  runUpdateUsingUpdater(STATE_FAILED_INVALID_APPLYTO_DIR_ERROR, false,
+                        (USE_EXECV ? 0 : 1));
+}
+
+/**
+ * Called after the call to runUpdateUsingUpdater finishes.
+ */
+function runUpdateFinished() {
+  standardInit();
+  checkPostUpdateRunningFile(false);
+  checkFilesAfterUpdateFailure(getApplyDirFile);
+  waitForFilesInUse();
+}
--- a/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
+++ b/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
@@ -99,8 +99,11 @@ reason = Windows only test and bug 12919
 skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
 reason = Windows only test and bug 1291985
 [marAppApplyUpdateSuccess.js]
 skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') || toolkit == 'gonk'
 reason = bug 1291985 and bug 1164150
 [marAppApplyUpdateStageSuccess.js]
 skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2') || toolkit == 'gonk'
 reason = bug 1291985 and bug 1164150
+[marWrongApplyToDirFailure_win.js]
+skip-if = os != 'win' || debug && (os_version == '5.1' || os_version == '5.2')
+reason = Windows only test and bug 1291985
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -54,16 +54,17 @@
 
 #include "updatelogging.h"
 #ifdef XP_MACOSX
 #include "updaterfileutils_osx.h"
 #endif // XP_MACOSX
 
 #include "mozilla/Compiler.h"
 #include "mozilla/Types.h"
+#include "mozilla/UniquePtr.h"
 
 // Amount of the progress bar to use in each of the 3 update stages,
 // should total 100.0.
 #define PROGRESS_PREPARE_SIZE 20.0f
 #define PROGRESS_EXECUTE_SIZE 75.0f
 #define PROGRESS_FINISH_SIZE   5.0f
 
 // Amount of time in ms to wait for the parent process to close
@@ -315,16 +316,17 @@ static bool sReplaceRequest = false;
 static bool sUsingService = false;
 static bool sIsOSUpdate = false;
 
 #ifdef XP_WIN
 // The current working directory specified in the command line.
 static NS_tchar* gDestPath;
 static NS_tchar gCallbackRelPath[MAXPATHLEN];
 static NS_tchar gCallbackBackupPath[MAXPATHLEN];
+static NS_tchar gDeleteDirPath[MAXPATHLEN];
 #endif
 
 static const NS_tchar kWhitespace[] = NS_T(" \t");
 static const NS_tchar kNL[] = NS_T("\r\n");
 static const NS_tchar kQuote[] = NS_T("\"");
 
 static inline size_t
 mmin(size_t a, size_t b)
@@ -375,44 +377,76 @@ mstrtok(const NS_tchar *delims, NS_tchar
 
 static bool
 EnvHasValue(const char *name)
 {
   const char *val = getenv(name);
   return (val && *val);
 }
 
-#ifdef XP_WIN
 /**
- * Coverts a relative update path to a full path for Windows.
+ * Coverts a relative update path to a full path.
  *
  * @param  relpath
  *         The relative path to convert to a full path.
  * @return valid filesystem full path or nullptr if memory allocation fails.
  */
 static NS_tchar*
 get_full_path(const NS_tchar *relpath)
 {
-  size_t lendestpath = NS_tstrlen(gDestPath);
+  NS_tchar *destpath = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
+  size_t lendestpath = NS_tstrlen(destpath);
   size_t lenrelpath = NS_tstrlen(relpath);
-  NS_tchar *s = (NS_tchar *) malloc((lendestpath + lenrelpath + 1) * sizeof(NS_tchar));
-  if (!s)
+  NS_tchar *s = new NS_tchar[lendestpath + lenrelpath + 2];
+  if (!s) {
     return nullptr;
+  }
 
   NS_tchar *c = s;
 
-  NS_tstrcpy(c, gDestPath);
+  NS_tstrcpy(c, destpath);
   c += lendestpath;
+  NS_tstrcat(c, NS_T("/"));
+  c++;
+
   NS_tstrcat(c, relpath);
   c += lenrelpath;
   *c = NS_T('\0');
-  c++;
   return s;
 }
+
+/**
+ * Converts a full update path into a relative path; reverses get_full_path.
+ *
+ * @param  fullpath
+ *         The absolute path to convert into a relative path.
+ * return pointer to the location within fullpath where the relative path starts
+ *        or fullpath itself if it already looks relative.
+ */
+static const NS_tchar*
+get_relative_path(const NS_tchar *fullpath)
+{
+  // If the path isn't absolute, just return it as-is.
+#ifdef XP_WIN
+  if (fullpath[1] != ':' && fullpath[2] != '\\') {
+#else
+  if (fullpath[0] != '/') {
 #endif
+    return fullpath;
+  }
+
+  NS_tchar *prefix = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
+
+  // If the path isn't long enough to be absolute, return it as-is.
+  if (NS_tstrlen(fullpath) <= NS_tstrlen(prefix)) {
+    return fullpath;
+  }
+
+  return fullpath + NS_tstrlen(prefix) + 1;
+}
 
 /**
  * Gets the platform specific path and performs simple checks to the path. If
  * the path checks don't pass nullptr will be returned.
  *
  * @param  line
  *         The line from the manifest that contains the path.
  * @param  isdir
@@ -963,81 +997,90 @@ static int backup_create(const NS_tchar 
   NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
                NS_T("%s") BACKUP_EXT, path);
 
   return rename_file(path, backup);
 }
 
 // Rename the backup of the specified file that was created by renaming it back
 // to the original file.
-static int backup_restore(const NS_tchar *path)
+static int backup_restore(const NS_tchar *path, const NS_tchar *relPath)
 {
   NS_tchar backup[MAXPATHLEN];
   NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
                NS_T("%s") BACKUP_EXT, path);
 
+  NS_tchar relBackup[MAXPATHLEN];
+  NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
+    NS_T("%s") BACKUP_EXT, relPath);
+
   if (NS_taccess(backup, F_OK)) {
-    LOG(("backup_restore: backup file doesn't exist: " LOG_S, backup));
+    LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup));
     return OK;
   }
 
   return rename_file(backup, path);
 }
 
 // Discard the backup of the specified file that was created by renaming it.
-static int backup_discard(const NS_tchar *path)
+static int backup_discard(const NS_tchar *path, const NS_tchar *relPath)
 {
   NS_tchar backup[MAXPATHLEN];
   NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
                NS_T("%s") BACKUP_EXT, path);
 
+  NS_tchar relBackup[MAXPATHLEN];
+  NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
+    NS_T("%s") BACKUP_EXT, relPath);
+
   // Nothing to discard
   if (NS_taccess(backup, F_OK)) {
     return OK;
   }
 
   int rv = ensure_remove(backup);
 #if defined(XP_WIN)
   if (rv && !sStagedUpdate && !sReplaceRequest) {
-    LOG(("backup_discard: unable to remove: " LOG_S, backup));
+    LOG(("backup_discard: unable to remove: " LOG_S, relBackup));
     NS_tchar path[MAXPATHLEN];
-    GetTempFileNameW(DELETE_DIR, L"moz", 0, path);
+    GetTempFileNameW(gDeleteDirPath, L"moz", 0, path);
     if (rename_file(backup, path)) {
       LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S,
-           backup, path));
+           relBackup, relPath));
       return WRITE_ERROR_DELETE_BACKUP;
     }
     // The MoveFileEx call to remove the file on OS reboot will fail if the
     // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
     // but this is ok since the installer / uninstaller will delete the
     // directory containing the file along with its contents after an update is
     // applied, on reinstall, and on uninstall.
     if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
       LOG(("backup_discard: file renamed and will be removed on OS " \
-           "reboot: " LOG_S, path));
+           "reboot: " LOG_S, relPath));
     } else {
       LOG(("backup_discard: failed to schedule OS reboot removal of " \
-           "file: " LOG_S, path));
+           "file: " LOG_S, relPath));
     }
   }
 #else
   if (rv)
     return WRITE_ERROR_DELETE_BACKUP;
 #endif
 
   return OK;
 }
 
 // Helper function for post-processing a temporary backup.
-static void backup_finish(const NS_tchar *path, int status)
+static void backup_finish(const NS_tchar *path, const NS_tchar *relPath,
+                          int status)
 {
   if (status == OK)
-    backup_discard(path);
+    backup_discard(path, relPath);
   else
-    backup_restore(path);
+    backup_restore(path, relPath);
 }
 
 //-----------------------------------------------------------------------------
 
 static int DoUpdate();
 
 class Action
 {
@@ -1065,71 +1108,81 @@ private:
   Action* mNext;
 
   friend class ActionList;
 };
 
 class RemoveFile : public Action
 {
 public:
-  RemoveFile() : mFile(nullptr), mSkip(0) { }
+  RemoveFile() : mSkip(0) { }
 
   int Parse(NS_tchar *line);
   int Prepare();
   int Execute();
   void Finish(int status);
 
 private:
-  const NS_tchar *mFile;
+  mozilla::UniquePtr<NS_tchar[]> mFile;
+  mozilla::UniquePtr<NS_tchar[]> mRelPath;
   int mSkip;
 };
 
 int
 RemoveFile::Parse(NS_tchar *line)
 {
   // format "<deadfile>"
 
-  mFile = get_valid_path(&line);
-  if (!mFile)
+  NS_tchar * validPath = get_valid_path(&line);
+  if (!validPath) {
     return PARSE_ERROR;
+  }
+
+  mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+  NS_tstrcpy(mRelPath.get(), validPath);
+
+  mFile.reset(get_full_path(validPath));
+  if (!mFile) {
+    return PARSE_ERROR;
+  }
 
   return OK;
 }
 
 int
 RemoveFile::Prepare()
 {
   // Skip the file if it already doesn't exist.
-  int rv = NS_taccess(mFile, F_OK);
+  int rv = NS_taccess(mFile.get(), F_OK);
   if (rv) {
     mSkip = 1;
     mProgressCost = 0;
     return OK;
   }
 
-  LOG(("PREPARE REMOVEFILE " LOG_S, mFile));
+  LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get()));
 
   // Make sure that we're actually a file...
   struct NS_tstat_t fileInfo;
-  rv = NS_tstat(mFile, &fileInfo);
+  rv = NS_tstat(mFile.get(), &fileInfo);
   if (rv) {
-    LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
+    LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(),
          errno));
     return READ_ERROR;
   }
 
   if (!S_ISREG(fileInfo.st_mode)) {
-    LOG(("path present, but not a file: " LOG_S, mFile));
+    LOG(("path present, but not a file: " LOG_S, mFile.get()));
     return DELETE_ERROR_EXPECTED_FILE;
   }
 
-  NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile, NS_T('/'));
+  NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile.get(), NS_T('/'));
   if (slash) {
     *slash = NS_T('\0');
-    rv = NS_taccess(mFile, W_OK);
+    rv = NS_taccess(mFile.get(), W_OK);
     *slash = NS_T('/');
   } else {
     rv = NS_taccess(NS_T("."), W_OK);
   }
 
   if (rv) {
     LOG(("access failed: %d", errno));
     return WRITE_ERROR_FILE_ACCESS_DENIED;
@@ -1139,259 +1192,278 @@ RemoveFile::Prepare()
 }
 
 int
 RemoveFile::Execute()
 {
   if (mSkip)
     return OK;
 
-  LOG(("EXECUTE REMOVEFILE " LOG_S, mFile));
+  LOG(("EXECUTE REMOVEFILE " LOG_S, mRelPath.get()));
 
   // The file is checked for existence here and in Prepare since it might have
   // been removed by a separate instruction: bug 311099.
-  int rv = NS_taccess(mFile, F_OK);
+  int rv = NS_taccess(mFile.get(), F_OK);
   if (rv) {
     LOG(("file cannot be removed because it does not exist; skipping"));
     mSkip = 1;
     return OK;
   }
 
   // Rename the old file. It will be removed in Finish.
-  rv = backup_create(mFile);
+  rv = backup_create(mFile.get());
   if (rv) {
     LOG(("backup_create failed: %d", rv));
     return rv;
   }
 
   return OK;
 }
 
 void
 RemoveFile::Finish(int status)
 {
   if (mSkip)
     return;
 
-  LOG(("FINISH REMOVEFILE " LOG_S, mFile));
-
-  backup_finish(mFile, status);
+  LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get()));
+
+  backup_finish(mFile.get(), mRelPath.get(), status);
 }
 
 class RemoveDir : public Action
 {
 public:
-  RemoveDir() : mDir(nullptr), mSkip(0) { }
+  RemoveDir() : mSkip(0) { }
 
   virtual int Parse(NS_tchar *line);
   virtual int Prepare(); // check that the source dir exists
   virtual int Execute();
   virtual void Finish(int status);
 
 private:
-  const NS_tchar *mDir;
+  mozilla::UniquePtr<NS_tchar[]> mDir;
+  mozilla::UniquePtr<NS_tchar[]> mRelPath;
   int mSkip;
 };
 
 int
 RemoveDir::Parse(NS_tchar *line)
 {
   // format "<deaddir>/"
 
-  mDir = get_valid_path(&line, true);
-  if (!mDir)
+  NS_tchar * validPath = get_valid_path(&line, true);
+  if (!validPath) {
     return PARSE_ERROR;
+  }
+
+  mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+  NS_tstrcpy(mRelPath.get(), validPath);
+
+  mDir.reset(get_full_path(validPath));
+  if (!mDir) {
+    return PARSE_ERROR;
+  }
 
   return OK;
 }
 
 int
 RemoveDir::Prepare()
 {
   // We expect the directory to exist if we are to remove it.
-  int rv = NS_taccess(mDir, F_OK);
+  int rv = NS_taccess(mDir.get(), F_OK);
   if (rv) {
     mSkip = 1;
     mProgressCost = 0;
     return OK;
   }
 
-  LOG(("PREPARE REMOVEDIR " LOG_S "/", mDir));
+  LOG(("PREPARE REMOVEDIR " LOG_S "/", mRelPath.get()));
 
   // Make sure that we're actually a dir.
   struct NS_tstat_t dirInfo;
-  rv = NS_tstat(mDir, &dirInfo);
+  rv = NS_tstat(mDir.get(), &dirInfo);
   if (rv) {
-    LOG(("failed to read directory status info: " LOG_S ", err: %d", mDir,
+    LOG(("failed to read directory status info: " LOG_S ", err: %d", mRelPath.get(),
          errno));
     return READ_ERROR;
   }
 
   if (!S_ISDIR(dirInfo.st_mode)) {
-    LOG(("path present, but not a directory: " LOG_S, mDir));
+    LOG(("path present, but not a directory: " LOG_S, mRelPath.get()));
     return DELETE_ERROR_EXPECTED_DIR;
   }
 
-  rv = NS_taccess(mDir, W_OK);
+  rv = NS_taccess(mDir.get(), W_OK);
   if (rv) {
     LOG(("access failed: %d, %d", rv, errno));
     return WRITE_ERROR_DIR_ACCESS_DENIED;
   }
 
   return OK;
 }
 
 int
 RemoveDir::Execute()
 {
   if (mSkip)
     return OK;
 
-  LOG(("EXECUTE REMOVEDIR " LOG_S "/", mDir));
+  LOG(("EXECUTE REMOVEDIR " LOG_S "/", mRelPath.get()));
 
   // The directory is checked for existence at every step since it might have
   // been removed by a separate instruction: bug 311099.
-  int rv = NS_taccess(mDir, F_OK);
+  int rv = NS_taccess(mDir.get(), F_OK);
   if (rv) {
     LOG(("directory no longer exists; skipping"));
     mSkip = 1;
   }
 
   return OK;
 }
 
 void
 RemoveDir::Finish(int status)
 {
   if (mSkip || status != OK)
     return;
 
-  LOG(("FINISH REMOVEDIR " LOG_S "/", mDir));
+  LOG(("FINISH REMOVEDIR " LOG_S "/", mRelPath.get()));
 
   // The directory is checked for existence at every step since it might have
   // been removed by a separate instruction: bug 311099.
-  int rv = NS_taccess(mDir, F_OK);
+  int rv = NS_taccess(mDir.get(), F_OK);
   if (rv) {
     LOG(("directory no longer exists; skipping"));
     return;
   }
 
 
   if (status == OK) {
-    if (NS_trmdir(mDir)) {
+    if (NS_trmdir(mDir.get())) {
       LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d",
-           mDir, rv, errno));
+           mRelPath.get(), rv, errno));
     }
   }
 }
 
 class AddFile : public Action
 {
 public:
-  AddFile() : mFile(nullptr)
-            , mAdded(false)
-            { }
+  AddFile() : mAdded(false) { }
 
   virtual int Parse(NS_tchar *line);
   virtual int Prepare();
   virtual int Execute();
   virtual void Finish(int status);
 
 private:
-  const NS_tchar *mFile;
+  mozilla::UniquePtr<NS_tchar[]> mFile;
+  mozilla::UniquePtr<NS_tchar[]> mRelPath;
   bool mAdded;
 };
 
 int
 AddFile::Parse(NS_tchar *line)
 {
   // format "<newfile>"
 
-  mFile = get_valid_path(&line);
-  if (!mFile)
+  NS_tchar * validPath = get_valid_path(&line);
+  if (!validPath) {
     return PARSE_ERROR;
+  }
+
+  mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+  NS_tstrcpy(mRelPath.get(), validPath);
+
+  mFile.reset(get_full_path(validPath));
+  if (!mFile) {
+    return PARSE_ERROR;
+  }
 
   return OK;
 }
 
 int
 AddFile::Prepare()
 {
-  LOG(("PREPARE ADD " LOG_S, mFile));
+  LOG(("PREPARE ADD " LOG_S, mRelPath.get()));
 
   return OK;
 }
 
 int
 AddFile::Execute()
 {
-  LOG(("EXECUTE ADD " LOG_S, mFile));
+  LOG(("EXECUTE ADD " LOG_S, mRelPath.get()));
 
   int rv;
 
   // First make sure that we can actually get rid of any existing file.
-  rv = NS_taccess(mFile, F_OK);
+  rv = NS_taccess(mFile.get(), F_OK);
   if (rv == 0) {
-    rv = backup_create(mFile);
+    rv = backup_create(mFile.get());
     if (rv)
       return rv;
   } else {
-    rv = ensure_parent_dir(mFile);
+    rv = ensure_parent_dir(mFile.get());
     if (rv)
       return rv;
   }
 
 #ifdef XP_WIN
   char sourcefile[MAXPATHLEN];
-  if (!WideCharToMultiByte(CP_UTF8, 0, mFile, -1, sourcefile, MAXPATHLEN,
-                           nullptr, nullptr)) {
+  if (!WideCharToMultiByte(CP_UTF8, 0, mRelPath.get(), -1, sourcefile,
+                           MAXPATHLEN, nullptr, nullptr)) {
     LOG(("error converting wchar to utf8: %d", GetLastError()));
     return STRING_CONVERSION_ERROR;
   }
 
-  rv = gArchiveReader.ExtractFile(sourcefile, mFile);
+  rv = gArchiveReader.ExtractFile(sourcefile, mFile.get());
 #else
-  rv = gArchiveReader.ExtractFile(mFile, mFile);
+  rv = gArchiveReader.ExtractFile(mRelPath.get(), mFile.get());
 #endif
   if (!rv) {
     mAdded = true;
   }
   return rv;
 }
 
 void
 AddFile::Finish(int status)
 {
-  LOG(("FINISH ADD " LOG_S, mFile));
+  LOG(("FINISH ADD " LOG_S, mRelPath.get()));
   // When there is an update failure and a file has been added it is removed
   // here since there might not be a backup to replace it.
   if (status && mAdded)
-    NS_tremove(mFile);
-  backup_finish(mFile, status);
+    NS_tremove(mFile.get());
+  backup_finish(mFile.get(), mRelPath.get(), status);
 }
 
 class PatchFile : public Action
 {
 public:
-  PatchFile() : mPatchIndex(-1), buf(nullptr) { }
+  PatchFile() : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr) { }
 
   virtual ~PatchFile();
 
   virtual int Parse(NS_tchar *line);
   virtual int Prepare(); // should check for patch file and for checksum here
   virtual int Execute();
   virtual void Finish(int status);
 
 private:
   int LoadSourceFile(FILE* ofile);
 
   static int sPatchIndex;
 
   const NS_tchar *mPatchFile;
-  const NS_tchar *mFile;
+  mozilla::UniquePtr<NS_tchar[]> mFile;
+  mozilla::UniquePtr<NS_tchar[]> mFileRelPath;
   int mPatchIndex;
   MBSPatchHeader header;
   unsigned char *buf;
   NS_tchar spath[MAXPATHLEN];
   AutoFile mPatchStream;
 };
 
 int PatchFile::sPatchIndex = 0;
@@ -1420,17 +1492,17 @@ PatchFile::~PatchFile()
 
 int
 PatchFile::LoadSourceFile(FILE* ofile)
 {
   struct stat os;
   int rv = fstat(fileno((FILE *)ofile), &os);
   if (rv) {
     LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \
-         "err: %d", mFile, errno));
+         "err: %d", mFileRelPath.get(), errno));
     return READ_ERROR;
   }
 
   if (uint32_t(os.st_size) != header.slen) {
     LOG(("LoadSourceFile: destination file size %d does not match expected size %d",
          uint32_t(os.st_size), header.slen));
     return LOADSOURCE_ERROR_WRONG_SIZE;
   }
@@ -1442,17 +1514,17 @@ PatchFile::LoadSourceFile(FILE* ofile)
 
   size_t r = header.slen;
   unsigned char *rb = buf;
   while (r) {
     const size_t count = mmin(SSIZE_MAX, r);
     size_t c = fread(rb, 1, count, ofile);
     if (c != count) {
       LOG(("LoadSourceFile: error reading destination file: " LOG_S,
-           mFile));
+           mFileRelPath.get()));
       return READ_ERROR;
     }
 
     r -= c;
     rb += c;
   }
 
   // Verify that the contents of the source file correspond to what we expect.
@@ -1480,28 +1552,36 @@ PatchFile::Parse(NS_tchar *line)
   }
 
   // consume whitespace between args
   NS_tchar *q = mstrtok(kQuote, &line);
   if (!q) {
     return PARSE_ERROR;
   }
 
-  mFile = get_valid_path(&line);
+  NS_tchar * validPath = get_valid_path(&line);
+  if (!validPath) {
+    return PARSE_ERROR;
+  }
+
+  mFileRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+  NS_tstrcpy(mFileRelPath.get(), validPath);
+
+  mFile.reset(get_full_path(validPath));
   if (!mFile) {
     return PARSE_ERROR;
   }
 
   return OK;
 }
 
 int
 PatchFile::Prepare()
 {
-  LOG(("PREPARE PATCH " LOG_S, mFile));
+  LOG(("PREPARE PATCH " LOG_S, mFileRelPath.get()));
 
   // extract the patch to a temporary file
   mPatchIndex = sPatchIndex++;
 
   NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]),
                NS_T("%s/updating/%d.patch"), gWorkingDirPath, mPatchIndex);
 
   NS_tremove(spath);
@@ -1532,118 +1612,119 @@ PatchFile::Prepare()
 #endif
 
   return rv;
 }
 
 int
 PatchFile::Execute()
 {
-  LOG(("EXECUTE PATCH " LOG_S, mFile));
+  LOG(("EXECUTE PATCH " LOG_S, mFileRelPath.get()));
 
   fseek(mPatchStream, 0, SEEK_SET);
 
   int rv = MBS_ReadHeader(mPatchStream, &header);
   if (rv) {
     return rv;
   }
 
   FILE *origfile = nullptr;
 #ifdef XP_WIN
-  if (NS_tstrcmp(mFile, gCallbackRelPath) == 0) {
+  if (NS_tstrcmp(mFileRelPath.get(), gCallbackRelPath) == 0) {
     // Read from the copy of the callback when patching since the callback can't
     // be opened for reading to prevent the application from being launched.
     origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb"));
   } else {
-    origfile = NS_tfopen(mFile, NS_T("rb"));
+    origfile = NS_tfopen(mFile.get(), NS_T("rb"));
   }
 #else
-  origfile = NS_tfopen(mFile, NS_T("rb"));
+  origfile = NS_tfopen(mFile.get(), NS_T("rb"));
 #endif
 
   if (!origfile) {
-    LOG(("unable to open destination file: " LOG_S ", err: %d", mFile,
-         errno));
+    LOG(("unable to open destination file: " LOG_S ", err: %d",
+         mFileRelPath.get(), errno));
     return READ_ERROR;
   }
 
   rv = LoadSourceFile(origfile);
   fclose(origfile);
   if (rv) {
     LOG(("LoadSourceFile failed"));
     return rv;
   }
 
   // Rename the destination file if it exists before proceeding so it can be
   // used to restore the file to its original state if there is an error.
   struct NS_tstat_t ss;
-  rv = NS_tstat(mFile, &ss);
+  rv = NS_tstat(mFile.get(), &ss);
   if (rv) {
-    LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
-         errno));
+    LOG(("failed to read file status info: " LOG_S ", err: %d",
+         mFileRelPath.get(), errno));
     return READ_ERROR;
   }
 
-  rv = backup_create(mFile);
+  rv = backup_create(mFile.get());
   if (rv) {
     return rv;
   }
 
 #if defined(HAVE_POSIX_FALLOCATE)
-  AutoFile ofile(ensure_open(mFile, NS_T("wb+"), ss.st_mode));
+  AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
   posix_fallocate(fileno((FILE *)ofile), 0, header.dlen);
 #elif defined(XP_WIN)
   bool shouldTruncate = true;
   // Creating the file, setting the size, and then closing the file handle
   // lessens fragmentation more than any other method tested. Other methods that
   // have been tested are:
   // 1. _chsize / _chsize_s reduced fragmentation though not completely.
   // 2. _get_osfhandle and then setting the size reduced fragmentation though
   //    not completely. There are also reports of _get_osfhandle failing on
   //    mingw.
-  HANDLE hfile = CreateFileW(mFile,
+  HANDLE hfile = CreateFileW(mFile.get(),
                              GENERIC_WRITE,
                              0,
                              nullptr,
                              CREATE_ALWAYS,
                              FILE_ATTRIBUTE_NORMAL,
                              nullptr);
 
   if (hfile != INVALID_HANDLE_VALUE) {
     if (SetFilePointer(hfile, header.dlen,
                        nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER &&
         SetEndOfFile(hfile) != 0) {
       shouldTruncate = false;
     }
     CloseHandle(hfile);
   }
 
-  AutoFile ofile(ensure_open(mFile, shouldTruncate ? NS_T("wb+") : NS_T("rb+"),
+  AutoFile ofile(ensure_open(mFile.get(), shouldTruncate ? NS_T("wb+") : NS_T("rb+"),
                              ss.st_mode));
 #elif defined(XP_MACOSX)
-  AutoFile ofile(ensure_open(mFile, NS_T("wb+"), ss.st_mode));
+  AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
   // Modified code from FileUtils.cpp
   fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen};
   // Try to get a continous chunk of disk space
   rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
   if (rv == -1) {
     // OK, perhaps we are too fragmented, allocate non-continuous
     store.fst_flags = F_ALLOCATEALL;
     rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
   }
 
   if (rv != -1) {
     ftruncate(fileno((FILE *)ofile), header.dlen);
   }
 #else
-  AutoFile ofile(ensure_open(mFile, NS_T("wb+"), ss.st_mode));
+  AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
 #endif
 
   if (ofile == nullptr) {
-    LOG(("unable to create new file: " LOG_S ", err: %d", mFile, errno));
+    LOG(("unable to create new file: " LOG_S ", err: %d", mFileRelPath.get(),
+         errno));
     return WRITE_ERROR_OPEN_PATCH_FILE;
   }
 
 #ifdef XP_WIN
   if (!shouldTruncate) {
     fseek(ofile, 0, SEEK_SET);
   }
 #endif
@@ -1665,57 +1746,57 @@ PatchFile::Execute()
   buf = nullptr;
 
   return rv;
 }
 
 void
 PatchFile::Finish(int status)
 {
-  LOG(("FINISH PATCH " LOG_S, mFile));
-
-  backup_finish(mFile, status);
+  LOG(("FINISH PATCH " LOG_S, mFileRelPath.get()));
+
+  backup_finish(mFile.get(), mFileRelPath.get(), status);
 }
 
 class AddIfFile : public AddFile
 {
 public:
-  AddIfFile() : mTestFile(nullptr) { }
-
   virtual int Parse(NS_tchar *line);
   virtual int Prepare();
   virtual int Execute();
   virtual void Finish(int status);
 
 protected:
-  const NS_tchar *mTestFile;
+  mozilla::UniquePtr<NS_tchar[]> mTestFile;
 };
 
 int
 AddIfFile::Parse(NS_tchar *line)
 {
   // format "<testfile>" "<newfile>"
 
-  mTestFile = get_valid_path(&line);
-  if (!mTestFile)
+  mTestFile.reset(get_full_path(get_valid_path(&line)));
+  if (!mTestFile) {
     return PARSE_ERROR;
+  }
 
   // consume whitespace between args
   NS_tchar *q = mstrtok(kQuote, &line);
-  if (!q)
+  if (!q) {
     return PARSE_ERROR;
+  }
 
   return AddFile::Parse(line);
 }
 
 int
 AddIfFile::Prepare()
 {
   // If the test file does not exist, then skip this action.
-  if (NS_taccess(mTestFile, F_OK)) {
+  if (NS_taccess(mTestFile.get(), F_OK)) {
     mTestFile = nullptr;
     return OK;
   }
 
   return AddFile::Prepare();
 }
 
 int
@@ -1734,49 +1815,49 @@ AddIfFile::Finish(int status)
     return;
 
   AddFile::Finish(status);
 }
 
 class AddIfNotFile : public AddFile
 {
 public:
-  AddIfNotFile() : mTestFile(NULL) { }
-
   virtual int Parse(NS_tchar *line);
   virtual int Prepare();
   virtual int Execute();
   virtual void Finish(int status);
 
 protected:
-  const NS_tchar *mTestFile;
+  mozilla::UniquePtr<NS_tchar[]> mTestFile;
 };
 
 int
 AddIfNotFile::Parse(NS_tchar *line)
 {
   // format "<testfile>" "<newfile>"
 
-  mTestFile = get_valid_path(&line);
-  if (!mTestFile)
+  mTestFile.reset(get_full_path(get_valid_path(&line)));
+  if (!mTestFile) {
     return PARSE_ERROR;
+  }
 
   // consume whitespace between args
   NS_tchar *q = mstrtok(kQuote, &line);
-  if (!q)
+  if (!q) {
     return PARSE_ERROR;
+  }
 
   return AddFile::Parse(line);
 }
 
 int
 AddIfNotFile::Prepare()
 {
   // If the test file exists, then skip this action.
-  if (!NS_taccess(mTestFile, F_OK)) {
+  if (!NS_taccess(mTestFile.get(), F_OK)) {
     mTestFile = NULL;
     return OK;
   }
 
   return AddFile::Prepare();
 }
 
 int
@@ -1795,49 +1876,49 @@ AddIfNotFile::Finish(int status)
     return;
 
   AddFile::Finish(status);
 }
 
 class PatchIfFile : public PatchFile
 {
 public:
-  PatchIfFile() : mTestFile(nullptr) { }
-
   virtual int Parse(NS_tchar *line);
   virtual int Prepare(); // should check for patch file and for checksum here
   virtual int Execute();
   virtual void Finish(int status);
 
 private:
-  const NS_tchar *mTestFile;
+  mozilla::UniquePtr<NS_tchar[]> mTestFile;
 };
 
 int
 PatchIfFile::Parse(NS_tchar *line)
 {
   // format "<testfile>" "<patchfile>" "<filetopatch>"
 
-  mTestFile = get_valid_path(&line);
-  if (!mTestFile)
+  mTestFile.reset(get_full_path(get_valid_path(&line)));
+  if (!mTestFile) {
     return PARSE_ERROR;
+  }
 
   // consume whitespace between args
   NS_tchar *q = mstrtok(kQuote, &line);
-  if (!q)
+  if (!q) {
     return PARSE_ERROR;
+  }
 
   return PatchFile::Parse(line);
 }
 
 int
 PatchIfFile::Prepare()
 {
   // If the test file does not exist, then skip this action.
-  if (NS_taccess(mTestFile, F_OK)) {
+  if (NS_taccess(mTestFile.get(), F_OK)) {
     mTestFile = nullptr;
     return OK;
   }
 
   return PatchFile::Prepare();
 }
 
 int
@@ -2510,18 +2591,17 @@ UpdateThreadFunc(void *param)
                    NS_T("%s/updating"), gWorkingDirPath);
       ensure_remove_recursive(updatingDir);
     }
   }
 
   if (rv && (sReplaceRequest || sStagedUpdate)) {
 #ifdef XP_WIN
     // On Windows, the current working directory of the process should be changed
-    // so that it's not locked. The working directory for staging an update was
-    // already changed earlier.
+    // so that it's not locked.
     if (sStagedUpdate) {
       NS_tchar sysDir[MAX_PATH + 1] = { L'\0' };
       if (GetSystemDirectoryW(sysDir, MAX_PATH + 1)) {
         NS_tchdir(sysDir);
       }
     }
 #endif
     ensure_remove_recursive(gWorkingDirPath);
@@ -2846,31 +2926,40 @@ int NS_main(int argc, NS_tchar **argv)
     LOG(("Performing a replace request"));
   }
 
   LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath));
   LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath));
   LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath));
 
 #if defined(XP_WIN)
-  if (sReplaceRequest || sStagedUpdate) {
-    NS_tchar stagedParent[MAX_PATH];
-    NS_tsnprintf(stagedParent, sizeof(stagedParent)/sizeof(stagedParent[0]),
+  if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0) {
+    if (!sStagedUpdate && !sReplaceRequest) {
+      WriteStatusFile(INVALID_APPLYTO_DIR_ERROR);
+      LOG(("Installation directory and working directory must be the same "
+           "for non-staged updates. Exiting."));
+      LogFinish();
+      return 1;
+    }
+
+    NS_tchar workingDirParent[MAX_PATH];
+    NS_tsnprintf(workingDirParent,
+                 sizeof(workingDirParent) / sizeof(workingDirParent[0]),
                  NS_T("%s"), gWorkingDirPath);
-    if (!PathRemoveFileSpecW(stagedParent)) {
+    if (!PathRemoveFileSpecW(workingDirParent)) {
       WriteStatusFile(REMOVE_FILE_SPEC_ERROR);
       LOG(("Error calling PathRemoveFileSpecW: %d", GetLastError()));
       LogFinish();
       return 1;
     }
 
-    if (_wcsnicmp(stagedParent, gInstallDirPath, MAX_PATH) != 0) {
-      WriteStatusFile(INVALID_STAGED_PARENT_ERROR);
-      LOG(("Stage and Replace requests require that the working directory " \
-           "is a sub-directory of the installation directory! Exiting"));
+    if (_wcsnicmp(workingDirParent, gInstallDirPath, MAX_PATH) != 0) {
+      WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR);
+      LOG(("The apply-to directory must be the same as or "
+           "a child of the installation directory! Exiting."));
       LogFinish();
       return 1;
     }
   }
 #endif
 
 #ifdef MOZ_WIDGET_GONK
   const char *prioEnv = getenv("MOZ_UPDATER_PRIO");
@@ -2918,25 +3007,16 @@ int NS_main(int argc, NS_tchar **argv)
     }
   }
 #else
   if (pid > 0)
     waitpid(pid, nullptr, 0);
 #endif
 
 #ifdef XP_WIN
-  if (sReplaceRequest) {
-    // On Windows, the current working directory of the process should be changed
-    // so that it's not locked.
-    NS_tchar sysDir[MAX_PATH + 1] = { L'\0' };
-    if (GetSystemDirectoryW(sysDir, MAX_PATH + 1)) {
-      NS_tchdir(sysDir);
-    }
-  }
-
 #ifdef MOZ_MAINTENANCE_SERVICE
   sUsingService = EnvHasValue("MOZ_USING_SERVICE");
   putenv(const_cast<char*>("MOZ_USING_SERVICE="));
 #endif
   // lastFallbackError keeps track of the last error for the service not being
   // used, in case of an error when fallback is not enabled we write the
   // error to the update.status file.
   // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then
@@ -3257,42 +3337,26 @@ int NS_main(int argc, NS_tchar **argv)
 #endif
 
   if (sStagedUpdate) {
     // When staging updates, blow away the old installation directory and create
     // it from scratch.
     ensure_remove_recursive(gWorkingDirPath);
   }
   if (!sReplaceRequest) {
-    // Change current directory to the directory where we need to apply the update.
-    if (NS_tchdir(gWorkingDirPath) != 0) {
-      // Try to create the destination directory if it doesn't exist
-      int rv = NS_tmkdir(gWorkingDirPath, 0755);
-      if (rv == OK && errno != EEXIST) {
-        // Try changing the current directory again
-        if (NS_tchdir(gWorkingDirPath) != 0) {
-          // OK, time to give up!
+    // Try to create the destination directory if it doesn't exist
+    int rv = NS_tmkdir(gWorkingDirPath, 0755);
+    if (rv != OK && errno != EEXIST) {
 #ifdef XP_MACOSX
-          if (isElevated) {
-            freeArguments(argc, argv);
-            CleanupElevatedMacUpdate(true);
-          }
+      if (isElevated) {
+        freeArguments(argc, argv);
+        CleanupElevatedMacUpdate(true);
+      }
 #endif
-          return 1;
-        }
-      } else {
-        // Failed to create the directory, bail out
-#ifdef XP_MACOSX
-        if (isElevated) {
-          freeArguments(argc, argv);
-          CleanupElevatedMacUpdate(true);
-        }
-#endif
-        return 1;
-      }
+      return 1;
     }
   }
 
 #ifdef XP_WIN
   // For replace requests, we don't need to do any real updates, so this is not
   // necessary.
   if (!sReplaceRequest) {
     // Allocate enough space for the length of the path an optional additional
@@ -3313,17 +3377,17 @@ int NS_main(int argc, NS_tchar **argv)
     *c = NS_T('\0');
     c++;
 
     gDestPath = destpath;
   }
 
   NS_tchar applyDirLongPath[MAXPATHLEN];
   if (!GetLongPathNameW(gWorkingDirPath, applyDirLongPath,
-                        sizeof(applyDirLongPath)/sizeof(applyDirLongPath[0]))) {
+                        sizeof(applyDirLongPath) / sizeof(applyDirLongPath[0]))) {
     LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath));
     LogFinish();
     WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH);
     EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
     if (argc > callbackIndex) {
       LaunchCallbackApp(argv[5], argc - callbackIndex,
                         argv + callbackIndex, sUsingService);
     }
@@ -3483,23 +3547,33 @@ int NS_main(int argc, NS_tchar **argv)
         }
         LOG(("NS_main: callback app file in use, continuing without " \
              "exclusive access for executable file: " LOG_S,
              argv[callbackIndex]));
       }
     }
   }
 
-  // DELETE_DIR is not required when staging an update.
+  // DELETE_DIR is not required when performing a staged update or replace
+  // request; it can be used during a replace request but then it doesn't
+  // use gDeleteDirPath.
   if (!sStagedUpdate && !sReplaceRequest) {
     // The directory to move files that are in use to on Windows. This directory
-    // will be deleted after the update is finished or on OS reboot using
-    // MoveFileEx if it contains files that are in use.
-    if (NS_taccess(DELETE_DIR, F_OK)) {
-      NS_tmkdir(DELETE_DIR, 0755);
+    // will be deleted after the update is finished, on OS reboot using
+    // MoveFileEx if it contains files that are in use, or by the post update
+    // process after the update finishes. On Windows when performing a normal
+    // update (e.g. the update is not a staged update and is not a replace
+    // request) gWorkingDirPath is the same as gInstallDirPath and
+    // gWorkingDirPath is used because it is the destination directory.
+    NS_tsnprintf(gDeleteDirPath,
+      sizeof(gDeleteDirPath) / sizeof(gDeleteDirPath[0]),
+      NS_T("%s/%s"), gWorkingDirPath, DELETE_DIR);
+
+    if (NS_taccess(gDeleteDirPath, F_OK)) {
+      NS_tmkdir(gDeleteDirPath, 0755);
     }
   }
 #endif /* XP_WIN */
 
   // Run update process on a background thread. ShowProgressUI may return
   // before QuitProgressUI has been called, so wait for UpdateThreadFunc to
   // terminate. Avoid showing the progress UI when staging an update, or if this
   // is an elevated process on OSX.
@@ -3519,29 +3593,29 @@ int NS_main(int argc, NS_tchar **argv)
   if (argc > callbackIndex && !sReplaceRequest) {
     if (callbackFile != INVALID_HANDLE_VALUE) {
       CloseHandle(callbackFile);
     }
     // Remove the copy of the callback executable.
     NS_tremove(gCallbackBackupPath);
   }
 
-  if (!sStagedUpdate && !sReplaceRequest && _wrmdir(DELETE_DIR)) {
+  if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath)) {
     LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d",
          DELETE_DIR, errno));
     // The directory probably couldn't be removed due to it containing files
     // that are in use and will be removed on OS reboot. The call to remove the
     // directory on OS reboot is done after the calls to remove the files so the
     // files are removed first on OS reboot since the directory must be empty
     // for the directory removal to be successful. The MoveFileEx call to remove
     // the directory on OS reboot will fail if the process doesn't have write
     // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the
     // installer / uninstaller will delete the directory along with its contents
     // after an update is applied, on reinstall, and on uninstall.
-    if (MoveFileEx(DELETE_DIR, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
+    if (MoveFileEx(gDeleteDirPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
       LOG(("NS_main: directory will be removed on OS reboot: " LOG_S,
            DELETE_DIR));
     } else {
       LOG(("NS_main: failed to schedule OS reboot removal of " \
            "directory: " LOG_S, DELETE_DIR));
     }
   }
 #endif /* XP_WIN */
@@ -3738,19 +3812,19 @@ int add_dir_entries(const NS_tchar *dirp
   int rv = OK;
   WIN32_FIND_DATAW finddata;
   HANDLE hFindFile;
   NS_tchar searchspec[MAXPATHLEN];
   NS_tchar foundpath[MAXPATHLEN];
 
   NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]),
                NS_T("%s*"), dirpath);
-  const NS_tchar *pszSpec = get_full_path(searchspec);
-
-  hFindFile = FindFirstFileW(pszSpec, &finddata);
+  mozilla::UniquePtr<const NS_tchar> pszSpec(get_full_path(searchspec));
+
+  hFindFile = FindFirstFileW(pszSpec.get(), &finddata);
   if (hFindFile != INVALID_HANDLE_VALUE) {
     do {
       // Don't process the current or parent directory.
       if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 ||
           NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0)
         continue;
 
       NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
@@ -3772,74 +3846,70 @@ int add_dir_entries(const NS_tchar *dirp
 
         Action *action = new RemoveFile();
         rv = action->Parse(quotedpath);
         if (rv) {
           LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
                quotedpath, rv));
           return rv;
         }
+        free(quotedpath);
 
         list->Append(action);
       }
     } while (FindNextFileW(hFindFile, &finddata) != 0);
 
     FindClose(hFindFile);
     {
       // Add the directory to be removed to the ActionList.
       NS_tchar *quotedpath = get_quoted_path(dirpath);
       if (!quotedpath)
         return PARSE_ERROR;
 
       Action *action = new RemoveDir();
       rv = action->Parse(quotedpath);
-      if (rv)
+      if (rv) {
         LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
              quotedpath, rv));
-      else
+      } else {
         list->Append(action);
+      }
+      free(quotedpath);
     }
   }
 
   return rv;
 }
 
 #elif defined(SOLARIS)
 int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
 {
   int rv = OK;
-  NS_tchar searchpath[MAXPATHLEN];
   NS_tchar foundpath[MAXPATHLEN];
   struct {
     dirent dent_buffer;
     char chars[MAXNAMLEN];
   } ent_buf;
   struct dirent* ent;
-
-
-  NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"),
-               dirpath);
-  // Remove the trailing slash so the paths don't contain double slashes. The
-  // existence of the slash has already been checked in DoUpdate.
-  searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0');
-
-  DIR* dir = opendir(searchpath);
+  mozilla::UniquePtr<NS_tchar[]> searchpath(get_full_path(dirpath));
+
+  DIR* dir = opendir(searchpath.get());
   if (!dir) {
-    LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath,
+    LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath.get(),
          errno));
     return UNEXPECTED_FILE_OPERATION_ERROR;
   }
 
   while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent) {
     if ((strcmp(ent->d_name, ".") == 0) ||
         (strcmp(ent->d_name, "..") == 0))
       continue;
 
     NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
-                 NS_T("%s%s"), dirpath, ent->d_name);
+                 NS_T("%s%s"), searchpath.get(), ent->d_name);
     struct stat64 st_buf;
     int test = stat64(foundpath, &st_buf);
     if (test) {
       closedir(dir);
       return UNEXPECTED_FILE_OPERATION_ERROR;
     }
     if (S_ISDIR(st_buf.st_mode)) {
       NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
@@ -3848,17 +3918,17 @@ int add_dir_entries(const NS_tchar *dirp
       rv = add_dir_entries(foundpath, list);
       if (rv) {
         LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
         closedir(dir);
         return rv;
       }
     } else {
       // Add the file to be removed to the ActionList.
-      NS_tchar *quotedpath = get_quoted_path(foundpath);
+      NS_tchar *quotedpath = get_quoted_path(get_relative_path(foundpath));
       if (!quotedpath) {
         closedir(dir);
         return PARSE_ERROR;
       }
 
       Action *action = new RemoveFile();
       rv = action->Parse(quotedpath);
       if (rv) {
@@ -3869,17 +3939,17 @@ int add_dir_entries(const NS_tchar *dirp
       }
 
       list->Append(action);
     }
   }
   closedir(dir);
 
   // Add the directory to be removed to the ActionList.
-  NS_tchar *quotedpath = get_quoted_path(dirpath);
+  NS_tchar *quotedpath = get_quoted_path(get_relative_path(dirpath));
   if (!quotedpath)
     return PARSE_ERROR;
 
   Action *action = new RemoveDir();
   rv = action->Parse(quotedpath);
   if (rv) {
     LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
          quotedpath, rv));
@@ -3893,35 +3963,33 @@ int add_dir_entries(const NS_tchar *dirp
 
 #else
 
 int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
 {
   int rv = OK;
   FTS *ftsdir;
   FTSENT *ftsdirEntry;
-  NS_tchar searchpath[MAXPATHLEN];
-
-  NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"),
-               dirpath);
+  mozilla::UniquePtr<NS_tchar[]> searchpath(get_full_path(dirpath));
+
   // Remove the trailing slash so the paths don't contain double slashes. The
   // existence of the slash has already been checked in DoUpdate.
-  searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0');
-  char* const pathargv[] = {searchpath, nullptr};
+  searchpath[NS_tstrlen(searchpath.get()) - 1] = NS_T('\0');
+  char* const pathargv[] = {searchpath.get(), nullptr};
 
   // FTS_NOCHDIR is used so relative paths from the destination directory are
   // returned.
   if (!(ftsdir = fts_open(pathargv,
                           FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR,
                           nullptr)))
     return UNEXPECTED_FILE_OPERATION_ERROR;
 
   while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) {
     NS_tchar foundpath[MAXPATHLEN];
-    NS_tchar *quotedpath;
+    NS_tchar *quotedpath = nullptr;
     Action *action = nullptr;
 
     switch (ftsdirEntry->fts_info) {
       // Filesystem objects that shouldn't be in the application's directories
       case FTS_SL:
       case FTS_SLNONE:
       case FTS_DEFAULT:
         LOG(("add_dir_entries: found a non-standard file: " LOG_S,
@@ -3930,41 +3998,43 @@ int add_dir_entries(const NS_tchar *dirp
         MOZ_FALLTHROUGH;
 
       // Files
       case FTS_F:
       case FTS_NSOK:
         // Add the file to be removed to the ActionList.
         NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
                      NS_T("%s"), ftsdirEntry->fts_accpath);
-        quotedpath = get_quoted_path(foundpath);
+        quotedpath = get_quoted_path(get_relative_path(foundpath));
         if (!quotedpath) {
           rv = UPDATER_QUOTED_PATH_MEM_ERROR;
           break;
         }
         action = new RemoveFile();
         rv = action->Parse(quotedpath);
+        free(quotedpath);
         if (!rv)
           list->Append(action);
         break;
 
       // Directories
       case FTS_DP:
         rv = OK;
         // Add the directory to be removed to the ActionList.
         NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
                      NS_T("%s/"), ftsdirEntry->fts_accpath);
-        quotedpath = get_quoted_path(foundpath);
+        quotedpath = get_quoted_path(get_relative_path(foundpath));
         if (!quotedpath) {
           rv = UPDATER_QUOTED_PATH_MEM_ERROR;
           break;
         }
 
         action = new RemoveDir();
         rv = action->Parse(quotedpath);
+        free(quotedpath);
         if (!rv)
           list->Append(action);
         break;
 
       // Errors
       case FTS_DNR:
       case FTS_NS:
         // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that
@@ -4065,20 +4135,24 @@ GetManifestContents(const NS_tchar *mani
 
 int AddPreCompleteActions(ActionList *list)
 {
   if (sIsOSUpdate) {
     return OK;
   }
 
 #ifdef XP_MACOSX
-  NS_tchar *rb = GetManifestContents(NS_T("Contents/Resources/precomplete"));
+  mozilla::UniquePtr<NS_tchar[]> manifestPath(get_full_path(
+    NS_T("Contents/Resources/precomplete")));
 #else
-  NS_tchar *rb = GetManifestContents(NS_T("precomplete"));
+  mozilla::UniquePtr<NS_tchar[]> manifestPath(get_full_path(
+    NS_T("precomplete")));
 #endif
+
+  NS_tchar *rb = GetManifestContents(manifestPath.get());
   if (rb == nullptr) {
     LOG(("AddPreCompleteActions: error getting contents of precomplete " \
          "manifest"));
     // Applications aren't required to have a precomplete manifest. The mar
     // generation scripts enforce the presence of a precomplete manifest.
     return OK;
   }