Browse Source

VFS: allow ->d_manage() to declare -EISDIR in rcu_walk mode.

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>
NeilBrown 11 years ago
parent
commit
b8faf035ea
2 changed files with 18 additions and 12 deletions
  1. 2 1
      Documentation/filesystems/vfs.txt
  2. 16 11
      fs/namei.c

+ 2 - 1
Documentation/filesystems/vfs.txt

@@ -1053,7 +1053,8 @@ struct dentry_operations {
 	If the 'rcu_walk' parameter is true, then the caller is doing a
 	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,
 	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
 	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
 	This function is only used if DCACHE_MANAGE_TRANSIT is set on the
 	dentry being transited from.
 	dentry being transited from.

+ 16 - 11
fs/namei.c

@@ -1091,10 +1091,10 @@ int follow_down_one(struct path *path)
 }
 }
 EXPORT_SYMBOL(follow_down_one);
 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
 		 * Don't forget we might have a non-mountpoint managed dentry
 		 * that wants to block transit.
 		 * that wants to block transit.
 		 */
 		 */
-		if (unlikely(managed_dentry_might_block(path->dentry)))
+		switch (managed_dentry_rcu(path->dentry)) {
+		case -ECHILD:
+		default:
 			return false;
 			return false;
+		case -EISDIR:
+			return true;
+		case 0:
+			break;
+		}
 
 
 		if (!d_mountpoint(path->dentry))
 		if (!d_mountpoint(path->dentry))
-			return true;
+			return !(path->dentry->d_flags & DCACHE_NEED_AUTOMOUNT);
 
 
 		mounted = __lookup_mnt(path->mnt, path->dentry);
 		mounted = __lookup_mnt(path->mnt, path->dentry);
 		if (!mounted)
 		if (!mounted)
@@ -1130,7 +1137,8 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,
 		 */
 		 */
 		*inode = path->dentry->d_inode;
 		*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)
 static int follow_dotdot_rcu(struct nameidata *nd)
@@ -1402,11 +1410,8 @@ static int lookup_fast(struct nameidata *nd,
 		}
 		}
 		path->mnt = mnt;
 		path->mnt = mnt;
 		path->dentry = dentry;
 		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:
 unlazy:
 		if (unlazy_walk(nd, dentry))
 		if (unlazy_walk(nd, dentry))
 			return -ECHILD;
 			return -ECHILD;