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.
  *
                        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);
        }
 
        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;
 }