From 950ee9566a5b6cc45d15f5fe044bab4f1e8b62cb Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Tue, 10 Sep 2013 11:41:12 -0400 Subject: exportfs: fix 32-bit nfsd handling of 64-bit inode numbers Symptoms were spurious -ENOENTs on stat of an NFS filesystem from a 32-bit NFS server exporting a very large XFS filesystem, when the server's cache is cold (so the inodes in question are not in cache). Reviewed-by: Christoph Hellwig Reported-by: Trevor Cordes Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- fs/exportfs/expfs.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'fs/exportfs/expfs.c') diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index a235f0016889..c43fe9b39ff2 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c @@ -215,7 +215,7 @@ struct getdents_callback { struct dir_context ctx; char *name; /* name that was found. It already points to a buffer NAME_MAX+1 is size */ - unsigned long ino; /* the inum we are looking for */ + u64 ino; /* the inum we are looking for */ int found; /* inode matched? */ int sequence; /* sequence counter */ }; @@ -255,10 +255,14 @@ static int get_name(const struct path *path, char *name, struct dentry *child) struct inode *dir = path->dentry->d_inode; int error; struct file *file; + struct kstat stat; + struct path child_path = { + .mnt = path->mnt, + .dentry = child, + }; struct getdents_callback buffer = { .ctx.actor = filldir_one, .name = name, - .ino = child->d_inode->i_ino }; error = -ENOTDIR; @@ -267,6 +271,16 @@ static int get_name(const struct path *path, char *name, struct dentry *child) error = -EINVAL; if (!dir->i_fop) goto out; + /* + * inode->i_ino is unsigned long, kstat->ino is u64, so the + * former would be insufficient on 32-bit hosts when the + * filesystem supports 64-bit inode numbers. So we need to + * actually call ->getattr, not just read i_ino: + */ + error = vfs_getattr_nosec(&child_path, &stat); + if (error) + return error; + buffer.ino = stat.ino; /* * Open the directory ... */ -- cgit v1.2.3 From 854ff5caabb5974b7464b438aba0bc47f1b6cf34 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 16 Oct 2013 15:48:53 -0400 Subject: exportfs: BUG_ON in crazy corner case This would indicate a nasty bug in the dcache and has never triggered in the past 10 years as far as I know. Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- fs/exportfs/expfs.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'fs/exportfs/expfs.c') diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index c43fe9b39ff2..6d0a7fa9abb3 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c @@ -112,18 +112,14 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) while (target_dir->d_flags & DCACHE_DISCONNECTED && noprogress++ < 10) { struct dentry *pd = find_disconnected_root(target_dir); + BUG_ON(pd == mnt->mnt_sb->s_root); + if (!IS_ROOT(pd)) { /* must have found a connected parent - great */ spin_lock(&pd->d_lock); pd->d_flags &= ~DCACHE_DISCONNECTED; spin_unlock(&pd->d_lock); noprogress = 0; - } else if (pd == mnt->mnt_sb->s_root) { - printk(KERN_ERR "export: Eeek filesystem root is not connected, impossible\n"); - spin_lock(&pd->d_lock); - pd->d_flags &= ~DCACHE_DISCONNECTED; - spin_unlock(&pd->d_lock); - noprogress = 0; } else { /* * We have hit the top of a disconnected path, try to -- cgit v1.2.3 From 78cee9a8e4b42b3f585ea3bd1c076f5a76fee722 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Tue, 22 Oct 2013 20:59:19 -0400 Subject: exportfs: more detailed comment for path_reconnect Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- fs/exportfs/expfs.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'fs/exportfs/expfs.c') diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index 6d0a7fa9abb3..87e6dca69e43 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c @@ -93,7 +93,19 @@ find_disconnected_root(struct dentry *dentry) /* * Make sure target_dir is fully connected to the dentry tree. * - * It may already be, as the flag isn't always updated when connection happens. + * On successful return, DCACHE_DISCONNECTED will be cleared on + * target_dir, and target_dir->d_parent->...->d_parent will reach the + * root of the filesystem. + * + * Whenever DCACHE_DISCONNECTED is unset, target_dir is fully connected. + * But the converse is not true: target_dir may have DCACHE_DISCONNECTED + * set but already be connected. In that case we'll verify the + * connection to root and then clear the flag. + * + * Note that target_dir could be removed by a concurrent operation. In + * that case reconnect_path may still succeed with target_dir fully + * connected, but further operations using the filehandle will fail when + * necessary (due to S_DEAD being set on the directory). */ static int reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) -- cgit v1.2.3 From 0dbc018a490ed482a1236aad77ac12e20742b322 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Mon, 9 Sep 2013 16:15:13 -0400 Subject: exportfs: clear DISCONNECTED on all parents sooner Once we've found any connected parent, we know all our parents are connected--that's true even if there's a concurrent rename. May as well clear them all at once and be done with it. Reviewed-by: Cristoph Hellwig Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- fs/exportfs/expfs.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'fs/exportfs/expfs.c') diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index 87e6dca69e43..c65b748688ff 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c @@ -90,6 +90,24 @@ find_disconnected_root(struct dentry *dentry) return dentry; } +static void clear_disconnected(struct dentry *dentry) +{ + dget(dentry); + while (dentry->d_flags & DCACHE_DISCONNECTED) { + struct dentry *parent = dget_parent(dentry); + + WARN_ON_ONCE(IS_ROOT(dentry)); + + spin_lock(&dentry->d_lock); + dentry->d_flags &= ~DCACHE_DISCONNECTED; + spin_unlock(&dentry->d_lock); + + dput(dentry); + dentry = parent; + } + dput(dentry); +} + /* * Make sure target_dir is fully connected to the dentry tree. * @@ -128,10 +146,9 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) if (!IS_ROOT(pd)) { /* must have found a connected parent - great */ - spin_lock(&pd->d_lock); - pd->d_flags &= ~DCACHE_DISCONNECTED; - spin_unlock(&pd->d_lock); - noprogress = 0; + clear_disconnected(target_dir); + dput(pd); + break; } else { /* * We have hit the top of a disconnected path, try to -- cgit v1.2.3 From a056cc8934c7bd046dc44af559bba163115fde40 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Wed, 16 Oct 2013 21:09:30 -0400 Subject: exportfs: stop retrying once we race with rename/remove There are two places here where we could race with a rename or remove: - We could find the parent, but then be removed or renamed away from that parent directory before finding our name in that directory. - We could find the parent, and find our name in that parent, but then be renamed or removed before we look ourselves up by that name in that parent. In both cases the concurrent rename or remove will take care of reconnecting the directory that we're currently examining. Our target directory should then also be connected. Check this and clear DISCONNECTED in these cases instead of looping around again. Note: we *do* need to check that this actually happened if we want to be robust in the face of corrupted filesystems: a corrupted filesystem could just return a completely wrong parent, and we want to fail with an error in that case before starting to clear DISCONNECTED on non-DISCONNECTED filesystems. Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- fs/exportfs/expfs.c | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) (limited to 'fs/exportfs/expfs.c') 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); @@ -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; } -- cgit v1.2.3 From e4b70ebeeba954cb9cbcf0f19016bb9c2b8711c1 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Wed, 16 Oct 2013 21:20:19 -0400 Subject: exportfs: eliminate unused "noprogress" counter Note this counter is now being set to 0 on every pass through the loop, so it no longer serves any useful purpose. Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- fs/exportfs/expfs.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) (limited to 'fs/exportfs/expfs.c') diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index 6b5ddd5492bc..d8ba88ac10e5 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c @@ -145,18 +145,9 @@ static void clear_disconnected(struct dentry *dentry) static int reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) { - int noprogress = 0; int err = -ESTALE; - /* - * It is possible that a confused file system might not let us complete - * the path to the root. For example, if get_parent returns a directory - * in which we cannot find a name for the child. While this implies a - * very sick filesystem we don't want it to cause knfsd to spin. Hence - * the noprogress counter. If we go through the loop 10 times (2 is - * probably enough) without getting anywhere, we just give up - */ - while (target_dir->d_flags & DCACHE_DISCONNECTED && noprogress++ < 10) { + while (target_dir->d_flags & DCACHE_DISCONNECTED) { struct dentry *pd = find_disconnected_root(target_dir); BUG_ON(pd == mnt->mnt_sb->s_root); @@ -230,9 +221,7 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) */ dput(npd); dput(ppd); - if (npd == pd) - noprogress = 0; - else + if (npd != pd) goto out_reconnected; if (IS_ROOT(pd)) { /* something went wrong, we have to give up */ -- cgit v1.2.3 From bbf7a8a3562f2de49ce24db3be0f514459dd7f8b Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Thu, 17 Oct 2013 11:13:00 -0400 Subject: exportfs: move most of reconnect_path to helper function Also replace 3 easily-confused three-letter acronyms by more helpful variable names. Just cleanup, no change in functionality, with one exception: the dentry_connected() check in the "out_reconnected" case will now only check the ancestors of the current dentry instead of checking all the way from target_dir. Since we've already verified connectivity up to this dentry, that should be sufficient. Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- fs/exportfs/expfs.c | 164 +++++++++++++++++++++++++++------------------------- 1 file changed, 86 insertions(+), 78 deletions(-) (limited to 'fs/exportfs/expfs.c') diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index d8ba88ac10e5..d32ead9026f0 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c @@ -125,6 +125,86 @@ static void clear_disconnected(struct dentry *dentry) dput(dentry); } +/* + * Reconnect a directory dentry with its parent. + * + * This can return a dentry, or NULL, or an error. + * + * In the first case the returned dentry is the parent of the given + * dentry, and may itself need to be reconnected to its parent. + * + * In the NULL case, a concurrent VFS operation has either renamed or + * removed this directory. The concurrent operation has reconnected our + * dentry, so we no longer need to. + */ +static struct dentry *reconnect_one(struct vfsmount *mnt, + struct dentry *dentry, char *nbuf) +{ + struct dentry *parent; + struct dentry *tmp; + int err; + + parent = ERR_PTR(-EACCES); + mutex_lock(&dentry->d_inode->i_mutex); + if (mnt->mnt_sb->s_export_op->get_parent) + parent = mnt->mnt_sb->s_export_op->get_parent(dentry); + mutex_unlock(&dentry->d_inode->i_mutex); + + if (IS_ERR(parent)) { + dprintk("%s: get_parent of %ld failed, err %d\n", + __func__, dentry->d_inode->i_ino, PTR_ERR(parent)); + return parent; + } + + dprintk("%s: find name of %lu in %lu\n", __func__, + dentry->d_inode->i_ino, parent->d_inode->i_ino); + err = exportfs_get_name(mnt, parent, nbuf, dentry); + if (err == -ENOENT) + goto out_reconnected; + if (err) + goto out_err; + dprintk("%s: found name: %s\n", __func__, nbuf); + mutex_lock(&parent->d_inode->i_mutex); + tmp = lookup_one_len(nbuf, parent, strlen(nbuf)); + mutex_unlock(&parent->d_inode->i_mutex); + if (IS_ERR(tmp)) { + dprintk("%s: lookup failed: %d\n", __func__, PTR_ERR(tmp)); + goto out_err; + } + if (tmp != dentry) { + dput(tmp); + goto out_reconnected; + } + dput(tmp); + if (IS_ROOT(dentry)) { + err = -ESTALE; + goto out_err; + } + return parent; + +out_err: + dput(parent); + return ERR_PTR(err); +out_reconnected: + dput(parent); + /* + * 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(dentry)) + return ERR_PTR(-ESTALE); + return NULL; +} + /* * Make sure target_dir is fully connected to the dentry tree. * @@ -158,76 +238,19 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) dput(pd); break; } else { + struct dentry *parent; /* * We have hit the top of a disconnected path, try to * find parent and connect. - * - * Racing with some other process renaming a directory - * isn't much of a problem here. If someone renames - * the directory, it will end up properly connected, - * which is what we want - * - * Getting the parent can't be supported generically, - * the locking is too icky. - * - * Instead we just return EACCES. If server reboots - * or inodes get flushed, you lose - */ - struct dentry *ppd = ERR_PTR(-EACCES); - struct dentry *npd; - - mutex_lock(&pd->d_inode->i_mutex); - if (mnt->mnt_sb->s_export_op->get_parent) - ppd = mnt->mnt_sb->s_export_op->get_parent(pd); - mutex_unlock(&pd->d_inode->i_mutex); - - if (IS_ERR(ppd)) { - err = PTR_ERR(ppd); - dprintk("%s: get_parent of %ld failed, err %d\n", - __func__, pd->d_inode->i_ino, err); - dput(pd); - break; - } - - dprintk("%s: find name of %lu in %lu\n", __func__, - pd->d_inode->i_ino, ppd->d_inode->i_ino); - err = exportfs_get_name(mnt, ppd, nbuf, pd); - if (err) { - dput(ppd); - dput(pd); - if (err == -ENOENT) - /* some race between get_parent and - * get_name? - */ - goto out_reconnected; - break; - } - dprintk("%s: found name: %s\n", __func__, nbuf); - mutex_lock(&ppd->d_inode->i_mutex); - npd = lookup_one_len(nbuf, ppd, strlen(nbuf)); - mutex_unlock(&ppd->d_inode->i_mutex); - if (IS_ERR(npd)) { - err = PTR_ERR(npd); - dprintk("%s: lookup failed: %d\n", - __func__, err); - dput(ppd); - dput(pd); - break; - } - /* we didn't really want npd, we really wanted - * a side-effect of the lookup. - * hopefully, npd == pd, though it isn't really - * a problem if it isn't */ - dput(npd); - dput(ppd); - if (npd != pd) + parent = reconnect_one(mnt, pd, nbuf); + if (!parent) goto out_reconnected; - if (IS_ROOT(pd)) { - /* something went wrong, we have to give up */ - dput(pd); + if (IS_ERR(parent)) { + err = PTR_ERR(parent); break; } + dput(parent); } dput(pd); } @@ -241,21 +264,6 @@ 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; } -- cgit v1.2.3 From efbf201f7a0be7ffc6532e672fbccb0eed4f5de0 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Thu, 17 Oct 2013 21:42:35 -0400 Subject: exportfs: better variable name Replace another unhelpful acronym. Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- fs/exportfs/expfs.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'fs/exportfs/expfs.c') diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index d32ead9026f0..b33b9c4deea0 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c @@ -228,14 +228,14 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) int err = -ESTALE; while (target_dir->d_flags & DCACHE_DISCONNECTED) { - struct dentry *pd = find_disconnected_root(target_dir); + struct dentry *dentry = find_disconnected_root(target_dir); - BUG_ON(pd == mnt->mnt_sb->s_root); + BUG_ON(dentry == mnt->mnt_sb->s_root); - if (!IS_ROOT(pd)) { + if (!IS_ROOT(dentry)) { /* must have found a connected parent - great */ clear_disconnected(target_dir); - dput(pd); + dput(dentry); break; } else { struct dentry *parent; @@ -243,7 +243,7 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) * We have hit the top of a disconnected path, try to * find parent and connect. */ - parent = reconnect_one(mnt, pd, nbuf); + parent = reconnect_one(mnt, dentry, nbuf); if (!parent) goto out_reconnected; if (IS_ERR(parent)) { @@ -252,7 +252,7 @@ reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) } dput(parent); } - dput(pd); + dput(dentry); } if (target_dir->d_flags & DCACHE_DISCONNECTED) { -- cgit v1.2.3 From f27c9298fd717e1f7e63e314a7a85a3a7e77139d Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Thu, 17 Oct 2013 21:34:21 -0400 Subject: exportfs: fix quadratic behavior in filehandle lookup Suppose we're given the filehandle for a directory whose closest ancestor in the dcache is its Nth ancestor. The main loop in reconnect_path searches for an IS_ROOT ancestor of target_dir, reconnects that ancestor to its parent, then recommences the search for an IS_ROOT ancestor from target_dir. This behavior is quadratic in N. And there's really no need to restart the search from target_dir each time: once a directory has been looked up, it won't become IS_ROOT again. So instead of starting from target_dir each time, we can continue where we left off. This simplifies the code and improves performance on very deep directory heirachies. (I can't think of any reason anyone should need heirarchies a hundred or more deep, but the performance improvement may be valuable if only to limit damage in case of abuse.) Reviewed-by: Christoph Hellwig Signed-off-by: J. Bruce Fields Signed-off-by: Al Viro --- fs/exportfs/expfs.c | 66 +++++++++++------------------------------------------ 1 file changed, 13 insertions(+), 53 deletions(-) (limited to 'fs/exportfs/expfs.c') diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c index b33b9c4deea0..48a359dd286e 100644 --- a/fs/exportfs/expfs.c +++ b/fs/exportfs/expfs.c @@ -69,27 +69,6 @@ find_acceptable_alias(struct dentry *result, return NULL; } -/* - * Find root of a disconnected subtree and return a reference to it. - */ -static struct dentry * -find_disconnected_root(struct dentry *dentry) -{ - dget(dentry); - while (!IS_ROOT(dentry)) { - struct dentry *parent = dget_parent(dentry); - - if (!(parent->d_flags & DCACHE_DISCONNECTED)) { - dput(parent); - break; - } - - dput(dentry); - dentry = parent; - } - return dentry; -} - static bool dentry_connected(struct dentry *dentry) { dget(dentry); @@ -225,45 +204,26 @@ out_reconnected: static int reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf) { - int err = -ESTALE; + struct dentry *dentry, *parent; - while (target_dir->d_flags & DCACHE_DISCONNECTED) { - struct dentry *dentry = find_disconnected_root(target_dir); + dentry = dget(target_dir); + while (dentry->d_flags & DCACHE_DISCONNECTED) { BUG_ON(dentry == mnt->mnt_sb->s_root); - if (!IS_ROOT(dentry)) { - /* must have found a connected parent - great */ - clear_disconnected(target_dir); - dput(dentry); + if (IS_ROOT(dentry)) + parent = reconnect_one(mnt, dentry, nbuf); + else + parent = dget_parent(dentry); + + if (!parent) break; - } else { - struct dentry *parent; - /* - * We have hit the top of a disconnected path, try to - * find parent and connect. - */ - parent = reconnect_one(mnt, dentry, nbuf); - if (!parent) - goto out_reconnected; - if (IS_ERR(parent)) { - err = PTR_ERR(parent); - break; - } - dput(parent); - } dput(dentry); + if (IS_ERR(parent)) + return PTR_ERR(parent); + dentry = parent; } - - if (target_dir->d_flags & DCACHE_DISCONNECTED) { - /* something went wrong - oh-well */ - if (!err) - err = -ESTALE; - return err; - } - - return 0; -out_reconnected: + dput(dentry); clear_disconnected(target_dir); return 0; } -- cgit v1.2.3