VFS: allow ->d_manage() to declare -EISDIR in rcu_walk mode.
authorNeilBrown <neilb@suse.de>
Mon, 4 Aug 2014 07:06:29 +0000 (17:06 +1000)
committerAl Viro <viro@zeniv.linux.org.uk>
Thu, 7 Aug 2014 18:40:10 +0000 (14:40 -0400)
In REF-walk mode, ->d_manage can return -EISDIR to indicate
that the dentry is not really a mount trap (or even a mount point)
and that any mounts or any DCACHE_NEED_AUTOMOUNT flag should be
ignored.

RCU-walk mode doesn't currently support this, so if there is a dentry
with DCACHE_NEED_AUTOMOUNT set but which shouldn't be a mount-trap,
lookup_fast() will always drop in REF-walk mode.

With this patch, an -EISDIR from ->d_manage will always cause mounts
and automounts to be ignored, both in REF-walk and RCU-walk.

Bug-fixed-by: Dan Carpenter <dan.carpenter@oracle.com>
Cc: Ian Kent <raven@themaw.net>
Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Documentation/filesystems/vfs.txt
fs/namei.c

index a1d0d7a..61d65cc 100644 (file)
@@ -1053,7 +1053,8 @@ struct dentry_operations {
        If the 'rcu_walk' parameter is true, then the caller is doing a
        pathwalk in RCU-walk mode.  Sleeping is not permitted in this mode,
        and the caller can be asked to leave it and call again by returning
-       -ECHILD.
+       -ECHILD.  -EISDIR may also be returned to tell pathwalk to
+       ignore d_automount or any mounts.
 
        This function is only used if DCACHE_MANAGE_TRANSIT is set on the
        dentry being transited from.
index 0ff23ce..8a217c4 100644 (file)
@@ -1091,10 +1091,10 @@ int follow_down_one(struct path *path)
 }
 EXPORT_SYMBOL(follow_down_one);
 
-static inline bool managed_dentry_might_block(struct dentry *dentry)
+static inline int managed_dentry_rcu(struct dentry *dentry)
 {
-       return (dentry->d_flags & DCACHE_MANAGE_TRANSIT &&
-               dentry->d_op->d_manage(dentry, true) < 0);
+       return (dentry->d_flags & DCACHE_MANAGE_TRANSIT) ?
+               dentry->d_op->d_manage(dentry, true) : 0;
 }
 
 /*
@@ -1110,11 +1110,18 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,
                 * Don't forget we might have a non-mountpoint managed dentry
                 * that wants to block transit.
                 */
-               if (unlikely(managed_dentry_might_block(path->dentry)))
+               switch (managed_dentry_rcu(path->dentry)) {
+               case -ECHILD:
+               default:
                        return false;
+               case -EISDIR:
+                       return true;
+               case 0:
+                       break;
+               }
 
                if (!d_mountpoint(path->dentry))
-                       return true;
+                       return !(path->dentry->d_flags & DCACHE_NEED_AUTOMOUNT);
 
                mounted = __lookup_mnt(path->mnt, path->dentry);
                if (!mounted)
@@ -1130,7 +1137,8 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,
                 */
                *inode = path->dentry->d_inode;
        }
-       return read_seqretry(&mount_lock, nd->m_seq);
+       return read_seqretry(&mount_lock, nd->m_seq) &&
+               !(path->dentry->d_flags & DCACHE_NEED_AUTOMOUNT);
 }
 
 static int follow_dotdot_rcu(struct nameidata *nd)
@@ -1402,11 +1410,8 @@ static int lookup_fast(struct nameidata *nd,
                }
                path->mnt = mnt;
                path->dentry = dentry;
-               if (unlikely(!__follow_mount_rcu(nd, path, inode)))
-                       goto unlazy;
-               if (unlikely(path->dentry->d_flags & DCACHE_NEED_AUTOMOUNT))
-                       goto unlazy;
-               return 0;
+               if (likely(__follow_mount_rcu(nd, path, inode)))
+                       return 0;
 unlazy:
                if (unlazy_walk(nd, dentry))
                        return -ECHILD;