Bug 313057: Automatic updater downgrades/overwrites browser version when an older/identical version is in the queue. r=bsmedberg, a1.9.1b2=beltzner
authorRobert Strong <robert.bugzilla@gmail.com>
Fri, 14 Nov 2008 12:11:08 +0000
changeset 21647 4db7b751339e5440305f60ba340ff2880d355f7b
parent 21646 0ec6c4bc82c12cb2dc4e78648278595df674c967
child 21648 ec9a1864d1fbb4b2b4aec31f702004b7f588f8b8
push id3635
push userdtownsend@mozilla.com
push dateFri, 14 Nov 2008 12:11:40 +0000
treeherdermozilla-central@ec9a1864d1fb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg
bugs313057
milestone1.9.1b2pre
Bug 313057: Automatic updater downgrades/overwrites browser version when an older/identical version is in the queue. r=bsmedberg, a1.9.1b2=beltzner
toolkit/mozapps/update/src/nsUpdateService.js.in
toolkit/xre/nsAppRunner.cpp
toolkit/xre/nsUpdateDriver.cpp
toolkit/xre/nsUpdateDriver.h
--- a/toolkit/mozapps/update/src/nsUpdateService.js.in
+++ b/toolkit/mozapps/update/src/nsUpdateService.js.in
@@ -79,16 +79,17 @@ const URI_UPDATE_NS             = "http:
 const KEY_APPDIR          = "XCurProcD";
 #ifdef XP_WIN
 const KEY_UPDROOT         = "UpdRootD";
 const KEY_UAPPDATA        = "UAppData";
 #endif
 
 const DIR_UPDATES         = "updates";
 const FILE_UPDATE_STATUS  = "update.status";
+const FILE_UPDATE_VERSION = "update.version";
 const FILE_UPDATE_ARCHIVE = "update.mar";
 const FILE_UPDATE_LOG     = "update.log"
 const FILE_UPDATES_DB     = "updates.xml";
 const FILE_UPDATE_ACTIVE  = "active-update.xml";
 const FILE_PERMS_TEST     = "update.test";
 const FILE_LAST_LOG       = "last-update.log";
 const FILE_UPDATER_INI    = "updater.ini";
 
@@ -394,16 +395,38 @@ function readStatusFile(dir) {
  */
 function writeStatusFile(dir, state) {
   var statusFile = dir.clone();
   statusFile.append(FILE_UPDATE_STATUS);
   writeStringToFile(statusFile, state);
 }
 
 /**
+#  Writes the update's application version to a file in the patch directory. If
+#  the update doesn't provide application version information via the
+#  extensionVersion attribute the string "null" will be written to the file.
+#  This value is compared during startup (in nsUpdateDriver.cpp) to determine if
+#  the update should be applied. Note that this won't provide protection from
+#  downgrade of the application for the nightly user case where the application
+#  version doesn't change.
+#  @param   dir
+#           The patch directory where the update.version file should be
+#           written.
+#  @param   version
+#           The version value to write. Will be the string "null" when the
+#           update doesn't provide the extensionVersion attribute in the update
+#           xml.
+ */
+function writeVersionFile(dir, version) {
+  var versionFile = dir.clone();
+  versionFile.append(FILE_UPDATE_VERSION);
+  writeStringToFile(versionFile, version);
+}
+
+/**
  * Removes the Updates Directory
  * @param   key
  *          The Directory Service Key under which update directory resides
  *          (optional).
  */
 function cleanUpUpdatesDir(key) {
   // Bail out if we don't have appropriate permissions
   var updateDir;
@@ -1223,16 +1246,17 @@ UpdateService.prototype = {
         // that it can be attempted again the next time we restart.
         var ary = status.split(": ");
         update.state = ary[0];
         if (update.state == STATE_FAILED && ary[1]) {
           update.errorCode = ary[1];
           if (update.errorCode == WRITE_ERROR) {
             prompter.showUpdateError(update);
             writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
+            writeVersionFile(getUpdatesDir(), update.extensionVersion);
             return;
           }
         }
 
         // Something went wrong with the patch application process.
         cleanupActiveUpdate();
 
         update.statusText = bundle.GetStringFromName("patchApplyFailure");
@@ -2405,16 +2429,17 @@ Downloader.prototype = {
         // Something went wrong when we tried to apply the previous patch.
         // Try the complete patch next time.
         if (update && selectedPatch.type == "partial") {
           useComplete = true;
         } else {
           // This is a pretty fatal error.  Just bail.
           LOG("Downloader", "_selectPatch - failed to apply complete patch!");
           writeStatusFile(updateDir, STATE_NONE);
+          writeVersionFile(getUpdatesDir(), null);
           return null;
         }
       }
 
       selectedPatch = null;
     }
 
     // If we were not able to discover an update from a previous download, we
@@ -2630,16 +2655,17 @@ Downloader.prototype = {
         // We only need to explicitly show the prompt if this is a backround
         // download, since otherwise some kind of UI is already visible and
         // that UI will notify.
         if (this.background)
           shouldShowPrompt = true;
 
         // Tell the updater.exe we're ready to apply.
         writeStatusFile(getUpdatesDir(), state);
+        writeVersionFile(getUpdatesDir(), this._update.extensionVersion);
         this._update.installDate = (new Date()).getTime();
         this._update.statusText = updateStrings.GetStringFromName("installPending");
       }
       else {
         LOG("Downloader", "onStopRequest - download verification failed");
         state = STATE_DOWNLOAD_FAILED;
 
         var brandStrings = sbs.createBundle(URI_BRAND_PROPERTIES);
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -2971,17 +2971,18 @@ XRE_main(int argc, char* argv[], const n
   // XRE_UPDATE_ROOT_DIR may fail. Fallback to appDir if failed
   if (NS_FAILED(rv))
     updRoot = dirProvider.GetAppDir();
 
   ProcessUpdates(dirProvider.GetGREDir(),
                  dirProvider.GetAppDir(),
                  updRoot,
                  gRestartArgc,
-                 gRestartArgv);
+                 gRestartArgv,
+                 appData.version);
 #endif
 
     nsCOMPtr<nsIProfileLock> profileLock;
     PRBool startOffline = PR_FALSE;
     nsCAutoString profileName;
 
     rv = SelectProfile(getter_AddRefs(profileLock), nativeApp, &startOffline,
                        &profileName);
--- a/toolkit/xre/nsUpdateDriver.cpp
+++ b/toolkit/xre/nsUpdateDriver.cpp
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is Google Inc.
  * Portions created by the Initial Developer are Copyright (C) 2005
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  Darin Fisher <darin@meer.net>
  *  Ben Turner <mozilla@songbirdnest.com>
+ *  Robert Strong <robert.bugzilla@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -46,16 +47,17 @@
 #include "nsISimpleEnumerator.h"
 #include "nsIDirectoryEnumerator.h"
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsString.h"
 #include "nsPrintfCString.h"
 #include "prproces.h"
 #include "prlog.h"
+#include "nsVersionComparator.h"
 
 #ifdef XP_MACOSX
 #include "nsILocalFileMac.h"
 #include "nsCommandLineServiceMac.h"
 #endif
 
 #if defined(XP_WIN)
 # include <direct.h>
@@ -217,26 +219,26 @@ ScanDir(nsIFile *dir, nsCOMArray<nsIFile
   return NS_OK;
 }
 
 static PRBool
 GetFile(nsIFile *dir, const nsCSubstring &name, nsCOMPtr<nsILocalFile> &result)
 {
   nsresult rv;
   
-  nsCOMPtr<nsIFile> statusFile;
-  rv = dir->Clone(getter_AddRefs(statusFile));
+  nsCOMPtr<nsIFile> file;
+  rv = dir->Clone(getter_AddRefs(file));
   if (NS_FAILED(rv))
     return PR_FALSE;
 
-  rv = statusFile->AppendNative(name);
+  rv = file->AppendNative(name);
   if (NS_FAILED(rv))
     return PR_FALSE;
 
-  result = do_QueryInterface(statusFile, &rv);
+  result = do_QueryInterface(file, &rv);
   return NS_SUCCEEDED(rv);
 }
 
 static PRBool
 GetStatusFile(nsIFile *dir, nsCOMPtr<nsILocalFile> &result)
 {
   return GetFile(dir, NS_LITERAL_CSTRING("update.status"), result);
 }
@@ -270,16 +272,52 @@ SetStatus(nsILocalFile *statusFile, cons
     return PR_FALSE;
 
   fprintf(fp, "%s\n", status);
   fclose(fp);
   return PR_TRUE;
 }
 
 static PRBool
+GetVersionFile(nsIFile *dir, nsCOMPtr<nsILocalFile> &result)
+{
+  return GetFile(dir, NS_LITERAL_CSTRING("update.version"), result);
+}
+
+// Compares the current application version with the update's application
+// version.
+static PRBool
+IsOlderVersion(nsILocalFile *versionFile, const char *&appVersion)
+{
+  nsresult rv;
+
+  FILE *fp;
+  rv = versionFile->OpenANSIFileDesc("r", &fp);
+  if (NS_FAILED(rv))
+    return PR_TRUE;
+
+  char buf[32];
+  char *result = fgets(buf, sizeof(buf), fp);
+  fclose(fp);
+  if (!result)
+    return PR_TRUE;
+
+  // If the update xml doesn't provide the application version the file will
+  // contain the string "null" and it is assumed that the update is not older.
+  const char kNull[] = "null";
+  if (strncmp(buf, kNull, sizeof(kNull) - 1) == 0)
+    return PR_FALSE;
+
+  if (NS_CompareVersions(appVersion, result) > 0)
+    return PR_TRUE;
+
+  return PR_FALSE;
+}
+
+static PRBool
 CopyFileIntoUpdateDir(nsIFile *parentDir, const char *leafName, nsIFile *updateDir)
 {
   nsDependentCString leaf(leafName);
   nsCOMPtr<nsIFile> file;
 
   // Make sure there is not an existing file in the target location.
   nsresult rv = updateDir->Clone(getter_AddRefs(file));
   if (NS_FAILED(rv))
@@ -500,17 +538,17 @@ ApplyUpdate(nsIFile *greDir, nsIFile *up
 end:
   PR_DestroyProcessAttr(attr); 
   delete[] argv;
 #endif
 }
 
 nsresult
 ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir,
-               int argc, char **argv)
+               int argc, char **argv, const char *&appVersion)
 {
   nsresult rv;
 
   nsCOMPtr<nsIFile> updatesDir;
   rv = updRootDir->Clone(getter_AddRefs(updatesDir));
   if (NS_FAILED(rv))
     return rv;
   rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("updates"));
@@ -528,15 +566,25 @@ ProcessUpdates(nsIFile *greDir, nsIFile 
     return rv;
   if (dirEntries.Count() == 0)
     return NS_OK;
 
   // look for the first update subdirectory with a status of pending
   for (int i = 0; i < dirEntries.Count(); ++i) {
     nsCOMPtr<nsILocalFile> statusFile;
     if (GetStatusFile(dirEntries[i], statusFile) && IsPending(statusFile)) {
+      nsCOMPtr<nsILocalFile> versionFile;
+      // Remove the update if the update application version file doesn't exist
+      // or if the update's application version is less than the current
+      // application version.
+      if (!GetVersionFile(dirEntries[i], versionFile) ||
+          IsOlderVersion(versionFile, appVersion)) {
+        dirEntries[i]->Remove(PR_TRUE);
+        continue;
+      }
+
       ApplyUpdate(greDir, dirEntries[i], statusFile, appDir, argc, argv);
       break;
     }
   }
 
   return NS_OK;
 }
--- a/toolkit/xre/nsUpdateDriver.h
+++ b/toolkit/xre/nsUpdateDriver.h
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is Google Inc.
  * Portions created by the Initial Developer are Copyright (C) 2005
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  Darin Fisher <darin@meer.net>
  *  Ben Turner <mozilla@songbirdnest.com>
+ *  Robert Strong <robert.bugzilla@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -50,15 +51,20 @@ class nsIFile;
  *
  * Two directories are passed to this function: greDir (where the actual
  * binary resides) and appDir (which contains application.ini for XULRunner
  * apps). If this is not a XULRunner app then appDir is identical to greDir.
  * 
  * The argc and argv passed to this function should be what is needed to
  * relaunch the current process.
  *
+ * The appVersion param passed to this function is the current application's
+ * version and is used to determine if an update's version is older than the
+ * current application version.
+ *
  * This function does not modify appDir.
  */
 NS_HIDDEN_(nsresult) ProcessUpdates(nsIFile *greDir, nsIFile *appDir,
                                     nsIFile *updRootDir,
-                                    int argc, char **argv);
+                                    int argc, char **argv,
+                                    const char *&appVersion);
 
 #endif  // nsUpdateDriver_h__