Commit c57b1f0a authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'work.namei' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs

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