]> Pileus Git - ~andy/linux/blobdiff - fs/exportfs/expfs.c
exportfs: stop retrying once we race with rename/remove
[~andy/linux] / fs / exportfs / expfs.c
index c65b748688ff6917c56f726a6a2d77e623aa1c76..6b5ddd5492bc8320d4389bc27e5f72f9d6d19a87 100644 (file)
@@ -90,6 +90,23 @@ find_disconnected_root(struct dentry *dentry)
        return dentry;
 }
 
+static bool dentry_connected(struct dentry *dentry)
+{
+       dget(dentry);
+       while (dentry->d_flags & DCACHE_DISCONNECTED) {
+               struct dentry *parent = dget_parent(dentry);
+
+               dput(dentry);
+               if (IS_ROOT(dentry)) {
+                       dput(parent);
+                       return false;
+               }
+               dentry = parent;
+       }
+       dput(dentry);
+       return true;
+}
+
 static void clear_disconnected(struct dentry *dentry)
 {
        dget(dentry);
@@ -189,9 +206,9 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf)
                                dput(pd);
                                if (err == -ENOENT)
                                        /* some race between get_parent and
-                                        * get_name?  just try again
+                                        * get_name?
                                         */
-                                       continue;
+                                       goto out_reconnected;
                                break;
                        }
                        dprintk("%s: found name: %s\n", __func__, nbuf);
@@ -211,12 +228,12 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf)
                         * hopefully, npd == pd, though it isn't really
                         * a problem if it isn't
                         */
+                       dput(npd);
+                       dput(ppd);
                        if (npd == pd)
                                noprogress = 0;
                        else
-                               printk("%s: npd != pd\n", __func__);
-                       dput(npd);
-                       dput(ppd);
+                               goto out_reconnected;
                        if (IS_ROOT(pd)) {
                                /* something went wrong, we have to give up */
                                dput(pd);
@@ -233,6 +250,24 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf)
                return err;
        }
 
+       return 0;
+out_reconnected:
+       /*
+        * Someone must have renamed our entry into another parent, in
+        * which case it has been reconnected by the rename.
+        *
+        * Or someone removed it entirely, in which case filehandle
+        * lookup will succeed but the directory is now IS_DEAD and
+        * subsequent operations on it will fail.
+        *
+        * Alternatively, maybe there was no race at all, and the
+        * filesystem is just corrupt and gave us a parent that doesn't
+        * actually contain any entry pointing to this inode.  So,
+        * double check that this worked and return -ESTALE if not:
+        */
+       if (!dentry_connected(target_dir))
+               return -ESTALE;
+       clear_disconnected(target_dir);
        return 0;
 }