follow_dotdot{,_rcu}(): massage loops
authorAl Viro <viro@zeniv.linux.org.uk>
Wed, 26 Feb 2020 19:59:56 +0000 (14:59 -0500)
committerAl Viro <viro@zeniv.linux.org.uk>
Thu, 2 Apr 2020 05:09:19 +0000 (01:09 -0400)
The logics in both of them is the same:
while true
if in process' root // uncommon
break
if *not* in mount root // normal case
find the parent
return
if at absolute root // very uncommon
break
move to underlying mountpoint
report that we are in root

Pull the common path out of the loop:
if in process' root // uncommon
goto in_root
if unlikely(in mount root)
while true
if at absolute root
goto in_root
move to underlying mountpoint
if in process' root
goto in_root
if in mount root
break;
find the parent // we are not in mount root
return
in_root:
report that we are in root

The reason for that transformation is that we get to keep the
common path straight *and* get a separate block for "move
through underlying mountpoints", which will allow to sanitize
NO_XDEV handling there.  What's more, the pared-down loops
will be easier to deal with - in particular, non-RCU case
has no need to grab mount_lock and rewriting it to the
form that wouldn't do that is a non-trivial change.  Better
do that with less stuff getting in the way...

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/namei.c

index fda239b..8cfbd5f 100644 (file)
@@ -1691,21 +1691,12 @@ static struct dentry *follow_dotdot_rcu(struct nameidata *nd,
                                        struct inode **inodep,
                                        unsigned *seqp)
 {
-       while (1) {
-               if (path_equal(&nd->path, &nd->root))
-                       break;
-               if (nd->path.dentry != nd->path.mnt->mnt_root) {
-                       struct dentry *old = nd->path.dentry;
-                       struct dentry *parent = old->d_parent;
+       struct dentry *parent, *old;
 
-                       *inodep = parent->d_inode;
-                       *seqp = read_seqcount_begin(&parent->d_seq);
-                       if (unlikely(read_seqcount_retry(&old->d_seq, nd->seq)))
-                               return ERR_PTR(-ECHILD);
-                       if (unlikely(!path_connected(nd->path.mnt, parent)))
-                               return ERR_PTR(-ECHILD);
-                       return parent;
-               } else {
+       if (path_equal(&nd->path, &nd->root))
+               goto in_root;
+       if (unlikely(nd->path.dentry == nd->path.mnt->mnt_root)) {
+               while (1) {
                        struct mount *mnt = real_mount(nd->path.mnt);
                        struct mount *mparent = mnt->mnt_parent;
                        struct dentry *mountpoint = mnt->mnt_mountpoint;
@@ -1714,7 +1705,7 @@ static struct dentry *follow_dotdot_rcu(struct nameidata *nd,
                        if (unlikely(read_seqretry(&mount_lock, nd->m_seq)))
                                return ERR_PTR(-ECHILD);
                        if (&mparent->mnt == nd->path.mnt)
-                               break;
+                               goto in_root;
                        if (unlikely(nd->flags & LOOKUP_NO_XDEV))
                                return ERR_PTR(-ECHILD);
                        /* we know that mountpoint was pinned */
@@ -1722,8 +1713,22 @@ static struct dentry *follow_dotdot_rcu(struct nameidata *nd,
                        nd->path.mnt = &mparent->mnt;
                        nd->inode = inode;
                        nd->seq = seq;
+                       if (path_equal(&nd->path, &nd->root))
+                               goto in_root;
+                       if (nd->path.dentry != nd->path.mnt->mnt_root)
+                               break;
                }
        }
+       old = nd->path.dentry;
+       parent = old->d_parent;
+       *inodep = parent->d_inode;
+       *seqp = read_seqcount_begin(&parent->d_seq);
+       if (unlikely(read_seqcount_retry(&old->d_seq, nd->seq)))
+               return ERR_PTR(-ECHILD);
+       if (unlikely(!path_connected(nd->path.mnt, parent)))
+               return ERR_PTR(-ECHILD);
+       return parent;
+in_root:
        if (unlikely(nd->flags & LOOKUP_BENEATH))
                return ERR_PTR(-ECHILD);
        return NULL;
@@ -1733,25 +1738,33 @@ static struct dentry *follow_dotdot(struct nameidata *nd,
                                 struct inode **inodep,
                                 unsigned *seqp)
 {
-       while (1) {
-               if (path_equal(&nd->path, &nd->root))
-                       break;
-               if (nd->path.dentry != nd->path.mnt->mnt_root) {
-                       /* rare case of legitimate dget_parent()... */
-                       struct dentry *parent = dget_parent(nd->path.dentry);
-                       if (unlikely(!path_connected(nd->path.mnt, parent))) {
-                               dput(parent);
-                               return ERR_PTR(-ENOENT);
-                       }
-                       *seqp = 0;
-                       *inodep = parent->d_inode;
-                       return parent;
+       struct dentry *parent;
+
+       if (path_equal(&nd->path, &nd->root))
+               goto in_root;
+       if (unlikely(nd->path.dentry == nd->path.mnt->mnt_root)) {
+               while (1) {
+                       if (!follow_up(&nd->path))
+                               goto in_root;
+                       if (unlikely(nd->flags & LOOKUP_NO_XDEV))
+                               return ERR_PTR(-EXDEV);
+                       if (path_equal(&nd->path, &nd->root))
+                               goto in_root;
+                       if (nd->path.dentry != nd->path.mnt->mnt_root)
+                               break;
                }
-               if (!follow_up(&nd->path))
-                       break;
-               if (unlikely(nd->flags & LOOKUP_NO_XDEV))
-                       return ERR_PTR(-EXDEV);
        }
+       /* rare case of legitimate dget_parent()... */
+       parent = dget_parent(nd->path.dentry);
+       if (unlikely(!path_connected(nd->path.mnt, parent))) {
+               dput(parent);
+               return ERR_PTR(-ENOENT);
+       }
+       *seqp = 0;
+       *inodep = parent->d_inode;
+       return parent;
+
+in_root:
        if (unlikely(nd->flags & LOOKUP_BENEATH))
                return ERR_PTR(-EXDEV);
        dget(nd->path.dentry);