Merge branch 'linus' into perf/urgent
[deliverable/linux.git] / fs / exportfs / expfs.c
index d8ba88ac10e56dc0d748c0fe4bea57e750c7c92f..48a359dd286e79b08062db77ffdc593be92a2cb6 100644 (file)
@@ -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);
@@ -126,121 +105,67 @@ static void clear_disconnected(struct dentry *dentry)
 }
 
 /*
- * Make sure target_dir is fully connected to the dentry tree.
+ * Reconnect a directory dentry with its parent.
  *
- * 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.
+ * This can return a dentry, or NULL, or an error.
  *
- * 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.
+ * In the first case the returned dentry is the parent of the given
+ * dentry, and may itself need to be reconnected to its parent.
  *
- * 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).
+ * 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 int
-reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf)
+static struct dentry *reconnect_one(struct vfsmount *mnt,
+               struct dentry *dentry, char *nbuf)
 {
-       int err = -ESTALE;
-
-       while (target_dir->d_flags & DCACHE_DISCONNECTED) {
-               struct dentry *pd = find_disconnected_root(target_dir);
-
-               BUG_ON(pd == mnt->mnt_sb->s_root);
+       struct dentry *parent;
+       struct dentry *tmp;
+       int err;
 
-               if (!IS_ROOT(pd)) {
-                       /* must have found a connected parent - great */
-                       clear_disconnected(target_dir);
-                       dput(pd);
-                       break;
-               } else {
-                       /*
-                        * 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;
-                       }
+       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);
 
-                       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)
-                               goto out_reconnected;
-                       if (IS_ROOT(pd)) {
-                               /* something went wrong, we have to give up */
-                               dput(pd);
-                               break;
-                       }
-               }
-               dput(pd);
+       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;
        }
 
-       if (target_dir->d_flags & DCACHE_DISCONNECTED) {
-               /* something went wrong - oh-well */
-               if (!err)
-                       err = -ESTALE;
-               return err;
+       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;
 
-       return 0;
+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.
@@ -254,8 +179,51 @@ out_reconnected:
         * 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;
+       if (!dentry_connected(dentry))
+               return ERR_PTR(-ESTALE);
+       return NULL;
+}
+
+/*
+ * Make sure target_dir is fully connected to the dentry tree.
+ *
+ * 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)
+{
+       struct dentry *dentry, *parent;
+
+       dentry = dget(target_dir);
+
+       while (dentry->d_flags & DCACHE_DISCONNECTED) {
+               BUG_ON(dentry == mnt->mnt_sb->s_root);
+
+               if (IS_ROOT(dentry))
+                       parent = reconnect_one(mnt, dentry, nbuf);
+               else
+                       parent = dget_parent(dentry);
+
+               if (!parent)
+                       break;
+               dput(dentry);
+               if (IS_ERR(parent))
+                       return PTR_ERR(parent);
+               dentry = parent;
+       }
+       dput(dentry);
        clear_disconnected(target_dir);
        return 0;
 }
This page took 0.044969 seconds and 5 git commands to generate.