|
|
|
|
@@ -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:
|
|
|
|
|
|