Bug 733436: OSX resource forks on FAT disks cause problems installing and uninstalling add-ons. r=Unfocused
authorDave Townsend <dtownsend@oxymoronical.com>
Wed, 16 May 2012 11:35:53 -0700
changeset 95592 95def0230d0cdff26a9c4eb8d9be20127269e3b6
parent 95591 e428a3d95bcdafae76193a5eaa3033bcf4907ab9
child 95593 98ca9bd48170aac6a183a165a57b28f05a41b4f5
push idunknown
push userunknown
push dateunknown
reviewersUnfocused
bugs733436
milestone15.0a1
Bug 733436: OSX resource forks on FAT disks cause problems installing and uninstalling add-ons. r=Unfocused
toolkit/mozapps/extensions/XPIProvider.jsm
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -270,16 +270,20 @@ SafeInstallOperation.prototype = {
       let entry;
       while (entry = entries.nextFile)
         cacheEntries.push(entry);
     }
     finally {
       entries.close();
     }
 
+    cacheEntries.sort(function(a, b) {
+      return a.path > b.path ? -1 : 1;
+    });
+
     cacheEntries.forEach(function(aEntry) {
       try {
         this._installDirEntry(aEntry, newDir, aCopy);
       }
       catch (e) {
         ERROR("Failed to " + (aCopy ? "copy" : "move") + " entry " +
               aEntry.path, e);
         throw e;
@@ -302,18 +306,35 @@ SafeInstallOperation.prototype = {
     }
 
     // Note we put the directory move in after all the file moves so the
     // directory is recreated before all the files are moved back
     this._installedFiles.push({ oldFile: aDirectory, newFile: newDir });
   },
 
   _installDirEntry: function(aDirEntry, aTargetDirectory, aCopy) {
+    let isDir = null;
+
     try {
-      if (aDirEntry.isDirectory())
+      isDir = aDirEntry.isDirectory();
+    }
+    catch (e) {
+      // If the file has already gone away then don't worry about it, this can
+      // happen on OSX where the resource fork is automatically moved with the
+      // data fork for the file. See bug 733436.
+      if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+        return;
+
+      ERROR("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
+            " to " + aTargetDirectory.path);
+      throw e;
+    }
+
+    try {
+      if (isDir)
         this._installDirectory(aDirEntry, aTargetDirectory, aCopy);
       else
         this._installFile(aDirEntry, aTargetDirectory, aCopy);
     }
     catch (e) {
       ERROR("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
             " to " + aTargetDirectory.path);
       throw e;
@@ -1287,45 +1308,65 @@ function cleanStagingDir(aDir, aLeafName
 
 /**
  * Recursively removes a directory or file fixing permissions when necessary.
  *
  * @param  aFile
  *         The nsIFile to remove
  */
 function recursiveRemove(aFile) {
-  setFilePermissions(aFile, aFile.isDirectory() ? FileUtils.PERMS_DIRECTORY
-                                                : FileUtils.PERMS_FILE);
+  let isDir = null;
+
+  try {
+    isDir = aFile.isDirectory();
+  }
+  catch (e) {
+    // If the file has already gone away then don't worry about it, this can
+    // happen on OSX where the resource fork is automatically moved with the
+    // data fork for the file. See bug 733436.
+    if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+      return;
+
+    throw e;
+  }
+
+  setFilePermissions(aFile, isDir ? FileUtils.PERMS_DIRECTORY
+                                  : FileUtils.PERMS_FILE);
 
   try {
     aFile.remove(true);
     return;
   }
   catch (e) {
     if (!aFile.isDirectory()) {
       ERROR("Failed to remove file " + aFile.path, e);
       throw e;
     }
   }
 
+  let entries = aFile.directoryEntries
+                     .QueryInterface(Ci.nsIDirectoryEnumerator);
+  let cacheEntries = [];
   let entry;
-  let dirEntries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+  while (entry = entries.nextFile)
+    cacheEntries.push(entry);
+  entries.close();
+
+  cacheEntries.sort(function(a, b) {
+    return a.path > b.path ? -1 : 1;
+  });
+
+  cacheEntries.forEach(recursiveRemove);
+
   try {
-    while (entry = dirEntries.nextFile)
-      recursiveRemove(entry);
-    try {
-      aFile.remove(true);
-    }
-    catch (e) {
-      ERROR("Failed to remove empty directory " + aFile.path, e);
-      throw e;
-    }
+    aFile.remove(true);
   }
-  finally {
-    dirEntries.close();
+  catch (e) {
+    ERROR("Failed to remove empty directory " + aFile.path, e);
+    throw e;
   }
 }
 
 /**
  * Returns the timestamp of the most recently modified file in a directory,
  * or simply the file's own timestamp if it is not a directory.
  *
  * @param  aFile
@@ -2305,16 +2346,22 @@ var XPIProvider = {
       let newAddon = aManifests[aInstallLocation.name][aOldAddon.id];
 
       try {
         // If not load it
         if (!newAddon) {
           let file = aInstallLocation.getLocationForID(aOldAddon.id);
           newAddon = loadManifestFromFile(file);
           applyBlocklistChanges(aOldAddon, newAddon);
+
+          // Carry over any pendingUninstall state to add-ons modified directly
+          // in the profile. This is impoprtant when the attempt to remove the
+          // add-on in processPendingFileChanges failed and caused an mtime
+          // change to the add-ons files.
+          newAddon.pendingUninstall = aOldAddon.pendingUninstall;
         }
 
         // The ID in the manifest that was loaded must match the ID of the old
         // add-on.
         if (newAddon.id != aOldAddon.id)
           throw new Error("Incorrect id in install manifest");
       }
       catch (e) {
@@ -5418,17 +5465,17 @@ var XPIDatabase = {
     // Any errors in here should rollback the transaction
     try {
       this.removeAddonMetadata(aOldAddon);
       aNewAddon.syncGUID = aOldAddon.syncGUID;
       aNewAddon.installDate = aOldAddon.installDate;
       aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
       aNewAddon.foreignInstall = aOldAddon.foreignInstall;
       aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled &&
-                          !aNewAddon.appDisabled)
+                          !aNewAddon.appDisabled && !aNewAddon.pendingUninstall)
 
       this.addAddonMetadata(aNewAddon, aDescriptor);
       this.commitTransaction();
     }
     catch (e) {
       this.rollbackTransaction();
       throw e;
     }