Mac v2 signing - Bug 1068439 - Move the distribution directory from Content/MacOS to Contents/Resources on app update due to v2 signing requirements. r=spohl
authorRobert Strong <robert.bugzilla@gmail.com>
Fri, 19 Sep 2014 15:54:35 -0700
changeset 491256 d478d59064cd607d15cada69e764308aa300663e
parent 491255 53657ba8aec13a6f2a05ea2be9da2e325a3198de
child 491257 8c8d3dfb011d917d263c0390135bcfc184ec930b
push id47343
push userbmo:dothayer@mozilla.com
push dateWed, 01 Mar 2017 22:58:58 +0000
reviewersspohl
bugs1068439
milestone35.0a1
Mac v2 signing - Bug 1068439 - Move the distribution directory from Content/MacOS to Contents/Resources on app update due to v2 signing requirements. r=spohl
toolkit/mozapps/update/tests/unit_aus_update/head_update.js
toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js
toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js
toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js
toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js
toolkit/mozapps/update/updater/updater.cpp
--- a/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
@@ -651,71 +651,16 @@ var gTestFilesPartialSuccess = [
   compareFile      : null,
   originalPerms    : null,
   comparePerms     : null
 }];
 
 // Concatenate the common files to the end of the array.
 gTestFilesPartialSuccess = gTestFilesPartialSuccess.concat(gTestFilesCommon);
 
-/**
- * The mar files used for the updater tests contain the following remove
- * operations.
- *
- * partial and complete test mar remove operations
- * -----------------------------------------------
- * remove "text1"
- * remove "text0"
- * rmrfdir "9/99/"
- * rmdir "9/99/"
- * rmrfdir "9/98/"
- * rmrfdir "9/97/"
- * rmrfdir "9/96/"
- * rmrfdir "9/95/"
- * rmrfdir "9/95/"
- * rmrfdir "9/94/"
- * rmdir "9/94/"
- * rmdir "9/93/"
- * rmdir "9/92/"
- * rmdir "9/91/"
- * rmdir "9/90/"
- * rmdir "9/90/"
- * rmrfdir "8/89/"
- * rmdir "8/89/"
- * rmrfdir "8/88/"
- * rmrfdir "8/87/"
- * rmrfdir "8/86/"
- * rmrfdir "8/85/"
- * rmrfdir "8/85/"
- * rmrfdir "8/84/"
- * rmdir "8/84/"
- * rmdir "8/83/"
- * rmdir "8/82/"
- * rmdir "8/81/"
- * rmdir "8/80/"
- * rmdir "8/80/"
- * rmrfdir "7/"
- * rmdir "6/"
- * remove "5/text1"
- * remove "5/text0"
- * rmrfdir "5/"
- * remove "4/text1"
- * remove "4/text0"
- * remove "4/exe0.exe"
- * rmdir "4/"
- * remove "3/text1"
- * remove "3/text0"
- *
- * partial test mar additional remove operations
- * ---------------------------------------------
- * remove "0/00/00text1"
- * remove "1/10/10text0"
- * rmdir "1/10/"
- * rmdir "1/"
- */
 var gTestDirsCommon = [
 {
   relPathDir   : DIR_RESOURCES + "3/",
   dirRemoved   : false,
   files        : ["3text0", "3text1"],
   filesRemoved : true
 }, {
   relPathDir   : DIR_RESOURCES + "4/",
@@ -1110,16 +1055,31 @@ function setTestFilesAndDirsForFailure()
     aTestDir.dirRemoved = false;
     if (aTestDir.filesRemoved) {
       aTestDir.filesRemoved = false;
     }
   });
 }
 
 /**
+ * Helper function for updater binary tests that prevents the distribution
+ * directory files from being created.
+ */
+function preventDistributionFiles() {
+  gTestFiles = gTestFiles.filter(function(aTestFile) {
+    return aTestFile.relPathDir.indexOf("distribution/") == -1;
+  });
+
+  gTestDirs = gTestDirs.filter(function(aTestDir) {
+    return aTestDir.relPathDir.indexOf("distribution/") == -1;
+  });
+}
+
+
+/**
  * Initializes the most commonly used settings and creates an instance of the
  * update service stub.
  */
 function standardInit() {
   createAppInfo("xpcshell@tests.mozilla.org", APP_INFO_NAME, "1.0", "2.0");
   setDefaultPrefs();
   // Initialize the update service stub component
   initUpdateServiceStub();
@@ -2383,18 +2343,21 @@ function createUpdaterINI(aIsExeAsync) {
 }
 
 /**
  * Helper function for updater binary tests for verifying the contents of the
  * update log after a successful update.
  *
  * @param   aCompareLogFile
  *          The log file to compare the update log with.
+ * @param   aExcludeDistributionDir
+ *          Removes lines containing the distribution directory from the log
+ *          file to compare the update log with.
  */
-function checkUpdateLogContents(aCompareLogFile) {
+function checkUpdateLogContents(aCompareLogFile, aExcludeDistributionDir) {
   if (IS_UNIX && !IS_MACOSX) {
     // Sorting on Linux is different so skip checking the logs for now.
     return;
   }
   let updateLog = getUpdatesPatchDir();
   updateLog.append(FILE_UPDATE_LOG);
   let updateLogContents = readFileBytes(updateLog);
 
@@ -2419,26 +2382,24 @@ function checkUpdateLogContents(aCompare
     updateLogContents = updateLogContents.replace(/Performing a replace request/, "");
   }
   // Skip the source/destination lines since they contain absolute paths.
   updateLogContents = updateLogContents.replace(/PATCH DIRECTORY.*/g, "");
   updateLogContents = updateLogContents.replace(/INSTALLATION DIRECTORY.*/g, "");
   updateLogContents = updateLogContents.replace(/WORKING DIRECTORY.*/g, "");
   // Skip lines that log failed attempts to open the callback executable.
   updateLogContents = updateLogContents.replace(/NS_main: callback app file .*/g, "");
+  if (IS_MACOSX) {
+    // Skip lines that log moving the distribution directory for Mac v2 signing.
+    updateLogContents = updateLogContents.replace(/Moving old [^\n]*\nrename_file: .*/g, "");
+    updateLogContents = updateLogContents.replace(/New distribution directory .*/g, "");
+  }
   if (gSwitchApp) {
     // Remove the lines which contain absolute paths
     updateLogContents = updateLogContents.replace(/^Begin moving.*$/mg, "");
-    if (IS_MACOSX) {
-      // Remove the entire section about moving the precomplete file as it contains
-      // absolute paths.
-      updateLogContents = updateLogContents.replace(/\n/g, "%%%EOL%%%");
-      updateLogContents = updateLogContents.replace(/Moving the precomplete file.*Finished moving the precomplete file/, "");
-      updateLogContents = updateLogContents.replace(/%%%EOL%%%/g, "\n");
-    }
   }
   updateLogContents = updateLogContents.replace(/\r/g, "");
   // Replace error codes since they are different on each platform.
   updateLogContents = updateLogContents.replace(/, err:.*\n/g, "\n");
   // Replace to make the log parsing happy.
   updateLogContents = updateLogContents.replace(/non-fatal error /g, "");
   // The FindFile results when enumerating the filesystem on Windows is not
   // determistic so the results matching the following need to be ignored.
@@ -2465,16 +2426,19 @@ function checkUpdateLogContents(aCompare
       !gTestFiles[gTestFiles.length - 1].originalContents) {
     compareLogContents = compareLogContents.replace(/.*defaults\/.*/g, "");
   }
   if (gTestFiles.length > 2 &&
       gTestFiles[gTestFiles.length - 2].fileName == FILE_UPDATE_SETTINGS_INI &&
       !gTestFiles[gTestFiles.length - 2].originalContents) {
     compareLogContents = compareLogContents.replace(/.*update-settings.ini.*/g, "");
   }
+  if (aExcludeDistributionDir) {
+    compareLogContents = compareLogContents.replace(/.*distribution\/.*/g, "");
+  }
   // Remove leading and trailing newlines
   compareLogContents = compareLogContents.replace(/\n+/g, "\n");
   // Remove leading and trailing newlines
   compareLogContents = compareLogContents.replace(/^\n|\n$/g, "");
 
   // Don't write the contents of the file to the log to reduce log spam
   // unless there is a failure.
   if (compareLogContents == updateLogContents) {
--- a/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js
@@ -9,16 +9,25 @@ function run_test() {
   gStageUpdate = true;
   setupTestCommon();
   gTestFiles = gTestFilesCompleteSuccess;
   gTestFiles[gTestFiles.length - 1].originalContents = null;
   gTestFiles[gTestFiles.length - 1].compareContents = "FromComplete\n";
   gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
   gTestDirs = gTestDirsCompleteSuccess;
   setupUpdaterTest(FILE_COMPLETE_MAR);
+  if (IS_MACOSX) {
+    // Create files in the old distribution directory location to verify that
+    // the directory and its contents are removed when there is a distribution
+    // directory in the new location.
+    let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+    writeFile(testFile, "test\n");
+    testFile = getApplyDirFile(DIR_MACOS + "distribution/test1/testFile", true);
+    writeFile(testFile, "test\n");
+  }
 
   createUpdaterINI(false);
 
   // For Mac OS X set the last modified time for the root directory to a date in
   // the past to test that the last modified time is updated on a successful
   // update (bug 600098).
   if (IS_MACOSX) {
     let now = Date.now();
@@ -88,16 +97,27 @@ function finishCheckUpdateApplied() {
     logTestInfo("testing last modified time on the apply to directory has " +
                 "changed after a successful update (bug 600098)");
     let now = Date.now();
     let applyToDir = getApplyDirFile();
     let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
     do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
   }
 
+  if (IS_MACOSX) {
+    logTestInfo("testing that the distribution directory is removed from the " +
+                "old location when there is a distribution directory in the " +
+                "new location");
+    let distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+    logTestInfo("testing " + distributionDir.path + " shouldn't exist");
+    do_check_false(distributionDir.exists());
+
+    checkUpdateLogContains("removing old distribution directory");
+  }
+
   if (IS_UNIX && !IS_MACOSX) {
     checkSymlink();
   }
   checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
   checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
   checkCallbackAppLog();
 }
 
--- a/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js
@@ -8,34 +8,43 @@
 function run_test() {
   gStageUpdate = true;
   setupTestCommon();
   gTestFiles = gTestFilesPartialSuccess;
   gTestFiles[gTestFiles.length - 2].originalContents = null;
   gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n";
   gTestFiles[gTestFiles.length - 2].comparePerms = 0o644;
   gTestDirs = gTestDirsPartialSuccess;
+  preventDistributionFiles();
   setupUpdaterTest(FILE_PARTIAL_MAR);
+  if (IS_MACOSX) {
+    // Create files in the old distribution directory location to verify that
+    // the directory and its contents are moved to the new location on update.
+    let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+    writeFile(testFile, "test\n");
+    testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true);
+    writeFile(testFile, "test\n");
+  }
 
   createUpdaterINI(false);
 
   // For Mac OS X set the last modified time for the root directory to a date in
   // the past to test that the last modified time is updated on all updates since
   // the precomplete file in the root of the bundle is renamed, etc. (bug 600098).
   if (IS_MACOSX) {
     let now = Date.now();
     let yesterday = now - (1000 * 60 * 60 * 24);
     let applyToDir = getApplyDirFile();
     applyToDir.lastModifiedTime = yesterday;
   }
 
   runUpdate(0, STATE_APPLIED, null);
 
   checkFilesAfterUpdateSuccess(getStageDirFile, true, false);
-  checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+  checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
 
   if (IS_WIN || IS_MACOSX) {
     // Check that the post update process was not launched when staging an
     // update.
     do_check_false(getPostUpdateFile(".running").exists());
   }
 
   // Switch the application to the staged application that was updated.
@@ -68,12 +77,39 @@ function finishCheckUpdateApplied() {
     logTestInfo("testing last modified time on the apply to directory has " +
                 "changed after a successful update (bug 600098)");
     let now = Date.now();
     let applyToDir = getApplyDirFile();
     let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
     do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
   }
 
+  let distributionDir = getApplyDirFile(DIR_RESOURCES + "distribution", true);
+  if (IS_MACOSX) {
+    logTestInfo("testing that the distribution directory is moved from the " +
+                "old location to the new location");
+    logTestInfo("testing " + distributionDir.path + " should exist");
+    do_check_true(distributionDir.exists());
+
+    let testFile = getApplyDirFile(DIR_RESOURCES + "distribution/testFile", true);
+    logTestInfo("testing " + testFile.path + " should exist");
+    do_check_true(testFile.exists());
+
+    testFile = getApplyDirFile(DIR_RESOURCES + "distribution/test/testFile", true);
+    logTestInfo("testing " + testFile.path + " should exist");
+    do_check_true(testFile.exists());
+
+    distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+    logTestInfo("testing " + distributionDir.path + " shouldn't exist");
+    do_check_false(distributionDir.exists());
+
+    checkUpdateLogContains("Moving old distribution directory to new location");
+  } else {
+    logTestInfo("testing that files aren't added with an add-if instruction " +
+                "when the file's destination directory doesn't exist");
+    logTestInfo("testing " + distributionDir.path + " shouldn't exist");
+    do_check_false(distributionDir.exists());
+  }
+
   checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
-  checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+  checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
   checkCallbackAppLog();
 }
--- a/toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js
@@ -4,17 +4,26 @@
  */
 
 /* General Complete MAR File Patch Apply Test */
 
 function run_test() {
   setupTestCommon();
   gTestFiles = gTestFilesCompleteSuccess;
   gTestDirs = gTestDirsCompleteSuccess;
+  preventDistributionFiles();
   setupUpdaterTest(FILE_COMPLETE_MAR);
+  if (IS_MACOSX) {
+    // Create files in the old distribution directory location to verify that
+    // the directory and its contents are moved to the new location on update.
+    let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+    writeFile(testFile, "test\n");
+    testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true);
+    writeFile(testFile, "test\n");
+  }
 
   createUpdaterINI();
 
   // For Mac OS X set the last modified time for the root directory to a date in
   // the past to test that the last modified time is updated on a successful
   // update (bug 600098).
   if (IS_MACOSX) {
     let now = Date.now();
@@ -48,12 +57,39 @@ function finishCheckUpdateApplied() {
     logTestInfo("testing last modified time on the apply to directory has " +
                 "changed after a successful update (bug 600098)");
     let now = Date.now();
     let applyToDir = getApplyDirFile();
     let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
     do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
   }
 
+  let distributionDir = getApplyDirFile(DIR_RESOURCES + "distribution", true);
+  if (IS_MACOSX) {
+    logTestInfo("testing that the distribution directory is moved from the " +
+                "old location to the new location");
+    logTestInfo("testing " + distributionDir.path + " should exist");
+    do_check_true(distributionDir.exists());
+
+    let testFile = getApplyDirFile(DIR_RESOURCES + "distribution/testFile", true);
+    logTestInfo("testing " + testFile.path + " should exist");
+    do_check_true(testFile.exists());
+
+    testFile = getApplyDirFile(DIR_RESOURCES + "distribution/test/testFile", true);
+    logTestInfo("testing " + testFile.path + " should exist");
+    do_check_true(testFile.exists());
+
+    distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+    logTestInfo("testing " + distributionDir.path + " shouldn't exist");
+    do_check_false(distributionDir.exists());
+
+    checkUpdateLogContains("Moving old distribution directory to new location");
+  } else {
+    logTestInfo("testing that files aren't added with an add-if instruction " +
+                "when the file's destination directory doesn't exist");
+    logTestInfo("testing " + distributionDir.path + " shouldn't exist");
+    do_check_false(distributionDir.exists());
+  }
+
   checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
-  checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+  checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
   checkCallbackAppLog();
 }
--- a/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js
@@ -11,16 +11,25 @@ function run_test() {
   gTestFiles[gTestFiles.length - 1].originalContents = null;
   gTestFiles[gTestFiles.length - 1].compareContents = "FromPartial\n";
   gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
   gTestFiles[gTestFiles.length - 2].originalContents = null;
   gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n";
   gTestFiles[gTestFiles.length - 2].comparePerms = 0o644;
   gTestDirs = gTestDirsPartialSuccess;
   setupUpdaterTest(FILE_PARTIAL_MAR);
+  if (IS_MACOSX) {
+    // Create files in the old distribution directory location to verify that
+    // the directory and its contents are removed when there is a distribution
+    // directory in the new location.
+    let testFile = getApplyDirFile(DIR_MACOS + "distribution/testFile", true);
+    writeFile(testFile, "test\n");
+    testFile = getApplyDirFile(DIR_MACOS + "distribution/test/testFile", true);
+    writeFile(testFile, "test\n");
+  }
 
   createUpdaterINI(true);
 
   // For Mac OS X set the last modified time for the root directory to a date in
   // the past to test that the last modified time is updated on all updates since
   // the precomplete file in the root of the bundle is renamed, etc. (bug 600098).
   if (IS_MACOSX) {
     let now = Date.now();
@@ -54,12 +63,23 @@ function finishCheckUpdateApplied() {
     logTestInfo("testing last modified time on the apply to directory has " +
                 "changed after a successful update (bug 600098)");
     let now = Date.now();
     let applyToDir = getApplyDirFile();
     let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
     do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
   }
 
+  if (IS_MACOSX) {
+    logTestInfo("testing that the distribution directory is removed from the " +
+                "old location when there is a distribution directory in the " +
+                "new location");
+    let distributionDir = getApplyDirFile(DIR_MACOS + "distribution", true);
+    logTestInfo("testing " + distributionDir.path + " shouldn't exist");
+    do_check_false(distributionDir.exists());
+
+    checkUpdateLogContains("removing old distribution directory");
+  }
+
   checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
   checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
   checkCallbackAppLog();
 }
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -2673,22 +2673,22 @@ int NS_main(int argc, NS_tchar **argv)
             updateStatusSucceeded) {
           if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath, false, nullptr)) {
             fprintf(stderr, "The post update process which runs as the user"
                     " for service update could not be launched.");
           }
         }
       }
 
-      // If we didn't want to use the service at all, or if an update was 
-      // already happening, or launching the service command failed, then 
+      // If we didn't want to use the service at all, or if an update was
+      // already happening, or launching the service command failed, then
       // launch the elevated updater.exe as we do without the service.
       // We don't launch the elevated updater in the case that we did have
       // write access all along because in that case the only reason we're
-      // using the service is because we are testing. 
+      // using the service is because we are testing.
       if (!useService && !noServiceFallback &&
           updateLockFileHandle == INVALID_HANDLE_VALUE) {
         SHELLEXECUTEINFO sinfo;
         memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
         sinfo.cbSize       = sizeof(SHELLEXECUTEINFO);
         sinfo.fMask        = SEE_MASK_FLAG_NO_UI |
                              SEE_MASK_FLAG_DDEWAIT |
                              SEE_MASK_NOCLOSEPROCESS;
@@ -3015,16 +3015,50 @@ int NS_main(int argc, NS_tchar **argv)
     }
   }
 #endif /* XP_WIN */
 
 #if defined(MOZ_WIDGET_GONK)
   } // end the extra level of scope for the GonkAutoMounter
 #endif
 
+#ifdef XP_MACOSX
+  // When the update is successful move the distribution directory from
+  // Contents/MacOS to Contents/Resources and if both exist delete the
+  // directory under Contents/MacOS (see Bug 1068439).
+  if (gSucceeded) {
+    NS_tchar oldDistDir[MAXPATHLEN];
+    NS_tsnprintf(oldDistDir, sizeof(oldDistDir)/sizeof(oldDistDir[0]),
+                 NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath);
+    int rv = NS_taccess(oldDistDir, F_OK);
+    if (!rv) {
+      NS_tchar newDistDir[MAXPATHLEN];
+      NS_tsnprintf(newDistDir, sizeof(newDistDir)/sizeof(newDistDir[0]),
+                   NS_T("%s/Contents/Resources/distribution"), gInstallDirPath);
+      rv = NS_taccess(newDistDir, F_OK);
+      if (!rv) {
+        LOG(("New distribution directory already exists... removing old " \
+             "distribution directory: " LOG_S, oldDistDir));
+        rv = ensure_remove_recursive(oldDistDir);
+        if (rv) {
+          LOG(("Removing old distribution directory failed - err: %d", rv));
+        }
+      } else {
+        LOG(("Moving old distribution directory to new location. src: " LOG_S \
+             ", dst:" LOG_S, oldDistDir, newDistDir));
+        rv = rename_file(oldDistDir, newDistDir, true);
+        if (rv) {
+          LOG(("Moving old distribution directory to new location failed - " \
+               "err: %d", rv));
+        }
+      }
+    }
+  }
+#endif /* XP_MACOSX */
+
   LogFinish();
 
   if (argc > callbackIndex) {
 #if defined(XP_WIN)
     if (gSucceeded) {
       // The service update will only be executed if it is already installed.
       // For first time installs of the service, the install will happen from
       // the PostUpdate process. We do the service update process here