summaryrefslogtreecommitdiff
path: root/fs/exportfs
diff options
context:
space:
mode:
Diffstat (limited to 'fs/exportfs')
-rw-r--r--fs/exportfs/expfs.c45
1 files changed, 40 insertions, 5 deletions
diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c
index c65b748688ff..6b5ddd5492bc 100644
--- a/fs/exportfs/expfs.c
+++ b/fs/exportfs/expfs.c
@@ -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);
@@ -234,6 +251,24 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf)
}
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;
}
struct getdents_callback {