Merge branch 'work.namei' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
authorLinus Torvalds <torvalds@linux-foundation.org>
Sun, 21 Feb 2021 17:42:18 +0000 (09:42 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sun, 21 Feb 2021 17:42:18 +0000 (09:42 -0800)
Pull namei updates from Al Viro:
 "Most of that pile is LOOKUP_CACHED series; the rest is a couple of
  misc cleanups in the general area...

  There's a minor bisect hazard in the end of series, and normally I
  would've just folded the fix into the previous commit, but this branch
  is shared with Jens' tree, with stuff on top of it in there, so that
  would've required rebases outside of vfs.git"

* 'work.namei' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  fix handling of nd->depth on LOOKUP_CACHED failures in try_to_unlazy*
  fs: expose LOOKUP_CACHED through openat2() RESOLVE_CACHED
  fs: add support for LOOKUP_CACHED
  saner calling conventions for unlazy_child()
  fs: make unlazy_walk() error handling consistent
  fs/namei.c: Remove unlikely of status being -ECHILD in lookup_fast()
  do_tmpfile(): don't mess with finish_open()

fs/namei.c
fs/open.c
include/linux/fcntl.h
include/linux/namei.h
include/uapi/linux/openat2.h

index 78443a8..de74ad2 100644 (file)
@@ -630,6 +630,11 @@ static inline bool legitimize_path(struct nameidata *nd,
 static bool legitimize_links(struct nameidata *nd)
 {
        int i;
+       if (unlikely(nd->flags & LOOKUP_CACHED)) {
+               drop_links(nd);
+               nd->depth = 0;
+               return false;
+       }
        for (i = 0; i < nd->depth; i++) {
                struct saved *last = nd->stack + i;
                if (unlikely(!legitimize_path(nd, &last->link, last->seq))) {
@@ -669,17 +674,17 @@ static bool legitimize_root(struct nameidata *nd)
  */
 
 /**
- * unlazy_walk - try to switch to ref-walk mode.
+ * try_to_unlazy - try to switch to ref-walk mode.
  * @nd: nameidata pathwalk data
- * Returns: 0 on success, -ECHILD on failure
+ * Returns: true on success, false on failure
  *
- * unlazy_walk attempts to legitimize the current nd->path and nd->root
+ * try_to_unlazy attempts to legitimize the current nd->path and nd->root
  * for ref-walk mode.
  * Must be called from rcu-walk context.
- * Nothing should touch nameidata between unlazy_walk() failure and
+ * Nothing should touch nameidata between try_to_unlazy() failure and
  * terminate_walk().
  */
-static int unlazy_walk(struct nameidata *nd)
+static bool try_to_unlazy(struct nameidata *nd)
 {
        struct dentry *parent = nd->path.dentry;
 
@@ -694,30 +699,30 @@ static int unlazy_walk(struct nameidata *nd)
                goto out;
        rcu_read_unlock();
        BUG_ON(nd->inode != parent->d_inode);
-       return 0;
+       return true;
 
 out1:
        nd->path.mnt = NULL;
        nd->path.dentry = NULL;
 out:
        rcu_read_unlock();
-       return -ECHILD;
+       return false;
 }
 
 /**
- * unlazy_child - try to switch to ref-walk mode.
+ * try_to_unlazy_next - try to switch to ref-walk mode.
  * @nd: nameidata pathwalk data
- * @dentry: child of nd->path.dentry
- * @seq: seq number to check dentry against
- * Returns: 0 on success, -ECHILD on failure
+ * @dentry: next dentry to step into
+ * @seq: seq number to check @dentry against
+ * Returns: true on success, false on failure
  *
- * unlazy_child attempts to legitimize the current nd->path, nd->root and dentry
- * for ref-walk mode.  @dentry must be a path found by a do_lookup call on
- * @nd.  Must be called from rcu-walk context.
- * Nothing should touch nameidata between unlazy_child() failure and
+ * Similar to to try_to_unlazy(), but here we have the next dentry already
+ * picked by rcu-walk and want to legitimize that in addition to the current
+ * nd->path and nd->root for ref-walk mode.  Must be called from rcu-walk context.
+ * Nothing should touch nameidata between try_to_unlazy_next() failure and
  * terminate_walk().
  */
-static int unlazy_child(struct nameidata *nd, struct dentry *dentry, unsigned seq)
+static bool try_to_unlazy_next(struct nameidata *nd, struct dentry *dentry, unsigned seq)
 {
        BUG_ON(!(nd->flags & LOOKUP_RCU));
 
@@ -747,7 +752,7 @@ static int unlazy_child(struct nameidata *nd, struct dentry *dentry, unsigned se
        if (unlikely(!legitimize_root(nd)))
                goto out_dput;
        rcu_read_unlock();
-       return 0;
+       return true;
 
 out2:
        nd->path.mnt = NULL;
@@ -755,11 +760,11 @@ out1:
        nd->path.dentry = NULL;
 out:
        rcu_read_unlock();
-       return -ECHILD;
+       return false;
 out_dput:
        rcu_read_unlock();
        dput(dentry);
-       return -ECHILD;
+       return false;
 }
 
 static inline int d_revalidate(struct dentry *dentry, unsigned int flags)
@@ -792,7 +797,8 @@ static int complete_walk(struct nameidata *nd)
                 */
                if (!(nd->flags & (LOOKUP_ROOT | LOOKUP_IS_SCOPED)))
                        nd->root.mnt = NULL;
-               if (unlikely(unlazy_walk(nd)))
+               nd->flags &= ~LOOKUP_CACHED;
+               if (!try_to_unlazy(nd))
                        return -ECHILD;
        }
 
@@ -1372,7 +1378,7 @@ static inline int handle_mounts(struct nameidata *nd, struct dentry *dentry,
                        return -ENOENT;
                if (likely(__follow_mount_rcu(nd, path, inode, seqp)))
                        return 0;
-               if (unlazy_child(nd, dentry, seq))
+               if (!try_to_unlazy_next(nd, dentry, seq))
                        return -ECHILD;
                // *path might've been clobbered by __follow_mount_rcu()
                path->mnt = nd->path.mnt;
@@ -1466,7 +1472,7 @@ static struct dentry *lookup_fast(struct nameidata *nd,
                unsigned seq;
                dentry = __d_lookup_rcu(parent, &nd->last, &seq);
                if (unlikely(!dentry)) {
-                       if (unlazy_walk(nd))
+                       if (!try_to_unlazy(nd))
                                return ERR_PTR(-ECHILD);
                        return NULL;
                }
@@ -1493,9 +1499,9 @@ static struct dentry *lookup_fast(struct nameidata *nd,
                status = d_revalidate(dentry, nd->flags);
                if (likely(status > 0))
                        return dentry;
-               if (unlazy_child(nd, dentry, seq))
+               if (!try_to_unlazy_next(nd, dentry, seq))
                        return ERR_PTR(-ECHILD);
-               if (unlikely(status == -ECHILD))
+               if (status == -ECHILD)
                        /* we'd been told to redo it in non-rcu mode */
                        status = d_revalidate(dentry, nd->flags);
        } else {
@@ -1567,10 +1573,8 @@ static inline int may_lookup(struct nameidata *nd)
 {
        if (nd->flags & LOOKUP_RCU) {
                int err = inode_permission(nd->inode, MAY_EXEC|MAY_NOT_BLOCK);
-               if (err != -ECHILD)
+               if (err != -ECHILD || !try_to_unlazy(nd))
                        return err;
-               if (unlazy_walk(nd))
-                       return -ECHILD;
        }
        return inode_permission(nd->inode, MAY_EXEC);
 }
@@ -1592,7 +1596,7 @@ static int reserve_stack(struct nameidata *nd, struct path *link, unsigned seq)
                // unlazy even if we fail to grab the link - cleanup needs it
                bool grabbed_link = legitimize_path(nd, link, seq);
 
-               if (unlazy_walk(nd) != 0 || !grabbed_link)
+               if (!try_to_unlazy(nd) != 0 || !grabbed_link)
                        return -ECHILD;
 
                if (nd_alloc_stack(nd))
@@ -1634,7 +1638,7 @@ static const char *pick_link(struct nameidata *nd, struct path *link,
                touch_atime(&last->link);
                cond_resched();
        } else if (atime_needs_update(&last->link, inode)) {
-               if (unlikely(unlazy_walk(nd)))
+               if (!try_to_unlazy(nd))
                        return ERR_PTR(-ECHILD);
                touch_atime(&last->link);
        }
@@ -1651,11 +1655,8 @@ static const char *pick_link(struct nameidata *nd, struct path *link,
                get = inode->i_op->get_link;
                if (nd->flags & LOOKUP_RCU) {
                        res = get(NULL, inode, &last->done);
-                       if (res == ERR_PTR(-ECHILD)) {
-                               if (unlikely(unlazy_walk(nd)))
-                                       return ERR_PTR(-ECHILD);
+                       if (res == ERR_PTR(-ECHILD) && try_to_unlazy(nd))
                                res = get(link->dentry, inode, &last->done);
-                       }
                } else {
                        res = get(link->dentry, inode, &last->done);
                }
@@ -2195,7 +2196,7 @@ OK:
                }
                if (unlikely(!d_can_lookup(nd->path.dentry))) {
                        if (nd->flags & LOOKUP_RCU) {
-                               if (unlazy_walk(nd))
+                               if (!try_to_unlazy(nd))
                                        return -ECHILD;
                        }
                        return -ENOTDIR;
@@ -2209,6 +2210,10 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
        int error;
        const char *s = nd->name->name;
 
+       /* LOOKUP_CACHED requires RCU, ask caller to retry */
+       if ((flags & (LOOKUP_RCU | LOOKUP_CACHED)) == LOOKUP_CACHED)
+               return ERR_PTR(-EAGAIN);
+
        if (!*s)
                flags &= ~LOOKUP_RCU;
        if (flags & LOOKUP_RCU)
@@ -3129,7 +3134,6 @@ static const char *open_last_lookups(struct nameidata *nd,
        struct inode *inode;
        struct dentry *dentry;
        const char *res;
-       int error;
 
        nd->flags |= op->intent;
 
@@ -3153,9 +3157,8 @@ static const char *open_last_lookups(struct nameidata *nd,
        } else {
                /* create side of things */
                if (nd->flags & LOOKUP_RCU) {
-                       error = unlazy_walk(nd);
-                       if (unlikely(error))
-                               return ERR_PTR(error);
+                       if (!try_to_unlazy(nd))
+                               return ERR_PTR(-ECHILD);
                }
                audit_inode(nd->name, dir, AUDIT_INODE_PARENT);
                /* trailing slashes? */
@@ -3164,9 +3167,7 @@ static const char *open_last_lookups(struct nameidata *nd,
        }
 
        if (open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) {
-               error = mnt_want_write(nd->path.mnt);
-               if (!error)
-                       got_write = true;
+               got_write = !mnt_want_write(nd->path.mnt);
                /*
                 * do _not_ fail yet - we might not need that or fail with
                 * a different error; let lookup_open() decide; we'll be
@@ -3325,10 +3326,8 @@ static int do_tmpfile(struct nameidata *nd, unsigned flags,
        audit_inode(nd->name, child, 0);
        /* Don't check for other permissions, the inode was just created */
        error = may_open(&path, 0, op->open_flag);
-       if (error)
-               goto out2;
-       file->f_path.mnt = path.mnt;
-       error = finish_open(file, child, NULL);
+       if (!error)
+               error = vfs_open(&path, file);
 out2:
        mnt_drop_write(path.mnt);
 out:
index 1e06e44..ca54447 100644 (file)
--- a/fs/open.c
+++ b/fs/open.c
@@ -1091,6 +1091,12 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
                lookup_flags |= LOOKUP_BENEATH;
        if (how->resolve & RESOLVE_IN_ROOT)
                lookup_flags |= LOOKUP_IN_ROOT;
+       if (how->resolve & RESOLVE_CACHED) {
+               /* Don't bother even trying for create/truncate/tmpfile open */
+               if (flags & (O_TRUNC | O_CREAT | O_TMPFILE))
+                       return -EAGAIN;
+               lookup_flags |= LOOKUP_CACHED;
+       }
 
        op->lookup_flags = lookup_flags;
        return 0;
index 921e750..766fcd9 100644 (file)
@@ -19,7 +19,7 @@
 /* List of all valid flags for the how->resolve argument: */
 #define VALID_RESOLVE_FLAGS \
        (RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS | \
-        RESOLVE_BENEATH | RESOLVE_IN_ROOT)
+        RESOLVE_BENEATH | RESOLVE_IN_ROOT | RESOLVE_CACHED)
 
 /* List of all open_how "versions". */
 #define OPEN_HOW_SIZE_VER0     24 /* sizeof first published struct */
index a4bb992..b9605b2 100644 (file)
@@ -46,6 +46,7 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT};
 #define LOOKUP_NO_XDEV         0x040000 /* No mountpoint crossing. */
 #define LOOKUP_BENEATH         0x080000 /* No escaping from starting point. */
 #define LOOKUP_IN_ROOT         0x100000 /* Treat dirfd as fs root. */
+#define LOOKUP_CACHED          0x200000 /* Only do cached lookup */
 /* LOOKUP_* flags which do scope-related checks based on the dirfd. */
 #define LOOKUP_IS_SCOPED (LOOKUP_BENEATH | LOOKUP_IN_ROOT)
 
index 58b1eb7..a5feb76 100644 (file)
@@ -35,5 +35,9 @@ struct open_how {
 #define RESOLVE_IN_ROOT                0x10 /* Make all jumps to "/" and ".."
                                        be scoped inside the dirfd
                                        (similar to chroot(2)). */
+#define RESOLVE_CACHED         0x20 /* Only complete if resolution can be
+                                       completed through cached lookup. May
+                                       return -EAGAIN if that's not
+                                       possible. */
 
 #endif /* _UAPI_LINUX_OPENAT2_H */