}
EXPORT_SYMBOL(path_put);
+#define EMBEDDED_LEVELS 2
struct nameidata {
struct path path;
- struct qstr last;
+ union {
+ struct qstr last;
+ struct path link;
+ };
struct path root;
struct inode *inode; /* path.dentry.d_inode */
unsigned int flags;
int last_type;
unsigned depth;
struct file *base;
+ struct saved {
+ struct path link;
+ void *cookie;
+ const char *name;
+ } *stack, internal[EMBEDDED_LEVELS];
};
+static void set_nameidata(struct nameidata *nd)
+{
+ nd->stack = nd->internal;
+}
+
+static void restore_nameidata(struct nameidata *nd)
+{
+ if (nd->stack != nd->internal) {
+ kfree(nd->stack);
+ nd->stack = nd->internal;
+ }
+}
+
+static int __nd_alloc_stack(struct nameidata *nd)
+{
+ struct saved *p = kmalloc((MAXSYMLINKS + 1) * sizeof(struct saved),
+ GFP_KERNEL);
+ if (unlikely(!p))
+ return -ENOMEM;
+ memcpy(p, nd->internal, sizeof(nd->internal));
+ nd->stack = p;
+ return 0;
+}
+
+static inline int nd_alloc_stack(struct nameidata *nd)
+{
+ if (likely(nd->depth != EMBEDDED_LEVELS))
+ return 0;
+ if (likely(nd->stack != nd->internal))
+ return 0;
+ return __nd_alloc_stack(nd);
+}
+
/*
* Path walking has 2 modes, rcu-walk and ref-walk (see
* Documentation/filesystems/path-lookup.txt). In situations when we can't
get_fs_root(current->fs, &nd->root);
}
-static int link_path_walk(const char *, struct nameidata *);
-
static __always_inline unsigned set_root_rcu(struct nameidata *nd)
{
struct fs_struct *fs = current->fs;
nd->flags |= LOOKUP_JUMPED;
}
-static inline void put_link(struct nameidata *nd, struct path *link, void *cookie)
+static inline void put_link(struct nameidata *nd)
{
- struct inode *inode = link->dentry->d_inode;
- if (cookie && inode->i_op->put_link)
- inode->i_op->put_link(link->dentry, cookie);
- path_put(link);
+ struct saved *last = nd->stack + nd->depth;
+ struct inode *inode = last->link.dentry->d_inode;
+ if (last->cookie && inode->i_op->put_link)
+ inode->i_op->put_link(last->link.dentry, last->cookie);
+ path_put(&last->link);
}
int sysctl_protected_symlinks __read_mostly = 0;
return -EPERM;
}
-static __always_inline const char *
-get_link(struct path *link, struct nameidata *nd, void **p)
+static __always_inline
+const char *get_link(struct nameidata *nd)
{
- struct dentry *dentry = link->dentry;
+ struct saved *last = nd->stack + nd->depth;
+ struct dentry *dentry = nd->link.dentry;
struct inode *inode = dentry->d_inode;
int error;
const char *res;
BUG_ON(nd->flags & LOOKUP_RCU);
- if (link->mnt == nd->path.mnt)
- mntget(link->mnt);
+ if (nd->link.mnt == nd->path.mnt)
+ mntget(nd->link.mnt);
- res = ERR_PTR(-ELOOP);
- if (unlikely(current->total_link_count >= 40))
- goto out;
+ if (unlikely(current->total_link_count >= MAXSYMLINKS)) {
+ path_put(&nd->path);
+ path_put(&nd->link);
+ return ERR_PTR(-ELOOP);
+ }
+
+ last->link = nd->link;
+ last->cookie = NULL;
cond_resched();
current->total_link_count++;
- touch_atime(link);
+ touch_atime(&last->link);
error = security_inode_follow_link(dentry);
res = ERR_PTR(error);
goto out;
nd->last_type = LAST_BIND;
- *p = NULL;
- res = inode->i_op->follow_link(dentry, p, nd);
- if (IS_ERR(res)) {
+ res = inode->i_link;
+ if (!res) {
+ res = inode->i_op->follow_link(dentry, &last->cookie, nd);
+ if (IS_ERR(res)) {
out:
- path_put(&nd->path);
- path_put(link);
+ path_put(&nd->path);
+ path_put(&last->link);
+ return res;
+ }
}
+ nd->depth++;
return res;
}
-static __always_inline int
-follow_link(struct path *link, struct nameidata *nd, void **p)
-{
- const char *s = get_link(link, nd, p);
- int error;
-
- if (unlikely(IS_ERR(s)))
- return PTR_ERR(s);
- if (unlikely(!s))
- return 0;
- if (*s == '/') {
- if (!nd->root.mnt)
- set_root(nd);
- path_put(&nd->path);
- nd->path = nd->root;
- path_get(&nd->root);
- nd->flags |= LOOKUP_JUMPED;
- }
- nd->inode = nd->path.dentry->d_inode;
- error = link_path_walk(s, nd);
- if (unlikely(error))
- put_link(nd, link, *p);
- return error;
-}
-
static int follow_up_rcu(struct path *path)
{
struct mount *mnt = real_mount(path->mnt);
return unlikely(d_is_symlink(dentry)) ? follow : 0;
}
-static int walk_component(struct nameidata *nd, struct path *path, int follow)
+static int walk_component(struct nameidata *nd, int follow)
{
+ struct path path;
struct inode *inode;
int err;
/*
*/
if (unlikely(nd->last_type != LAST_NORM))
return handle_dots(nd, nd->last_type);
- err = lookup_fast(nd, path, &inode);
+ err = lookup_fast(nd, &path, &inode);
if (unlikely(err)) {
if (err < 0)
goto out_err;
- err = lookup_slow(nd, path);
+ err = lookup_slow(nd, &path);
if (err < 0)
goto out_err;
- inode = path->dentry->d_inode;
+ inode = path.dentry->d_inode;
err = -ENOENT;
- if (d_is_negative(path->dentry))
+ if (d_is_negative(path.dentry))
goto out_path_put;
}
- if (should_follow_link(path->dentry, follow)) {
+ if (should_follow_link(path.dentry, follow)) {
if (nd->flags & LOOKUP_RCU) {
- if (unlikely(nd->path.mnt != path->mnt ||
- unlazy_walk(nd, path->dentry))) {
+ if (unlikely(nd->path.mnt != path.mnt ||
+ unlazy_walk(nd, path.dentry))) {
err = -ECHILD;
goto out_err;
}
}
- BUG_ON(inode != path->dentry->d_inode);
+ BUG_ON(inode != path.dentry->d_inode);
+ nd->link = path;
return 1;
}
- path_to_nameidata(path, nd);
+ path_to_nameidata(&path, nd);
nd->inode = inode;
return 0;
out_path_put:
- path_to_nameidata(path, nd);
+ path_to_nameidata(&path, nd);
out_err:
terminate_walk(nd);
return err;
}
-/*
- * This limits recursive symlink follows to 8, while
- * limiting consecutive symlinks to 40.
- *
- * Without that kind of total limit, nasty chains of consecutive
- * symlinks can cause almost arbitrarily long lookups.
- */
-static inline int nested_symlink(struct path *path, struct nameidata *nd)
-{
- int res;
-
- if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
- path_put_conditional(path, nd);
- path_put(&nd->path);
- return -ELOOP;
- }
- BUG_ON(nd->depth >= MAX_NESTED_LINKS);
-
- nd->depth++;
- current->link_count++;
-
- do {
- struct path link = *path;
- void *cookie;
-
- res = follow_link(&link, nd, &cookie);
- if (res)
- break;
- res = walk_component(nd, path, LOOKUP_FOLLOW);
- put_link(nd, &link, cookie);
- } while (res > 0);
-
- current->link_count--;
- nd->depth--;
- return res;
-}
-
/*
* We can do the critical dentry name comparison and hashing
* operations one word at a time, but we are limited to:
*/
static int link_path_walk(const char *name, struct nameidata *nd)
{
- struct path next;
int err;
-
+
while (*name=='/')
name++;
if (!*name)
name += hashlen_len(hash_len);
if (!*name)
- return 0;
+ goto OK;
/*
* If it wasn't NUL, we know it was '/'. Skip that
* slash, and continue until no more slashes.
name++;
} while (unlikely(*name == '/'));
if (!*name)
- return 0;
+ goto OK;
- err = walk_component(nd, &next, LOOKUP_FOLLOW);
+ err = walk_component(nd, LOOKUP_FOLLOW);
+Walked:
if (err < 0)
- return err;
+ goto Err;
if (err) {
- err = nested_symlink(&next, nd);
- if (err)
- return err;
+ const char *s;
+
+ err = nd_alloc_stack(nd);
+ if (unlikely(err)) {
+ path_to_nameidata(&nd->link, nd);
+ break;
+ }
+
+ s = get_link(nd);
+
+ if (unlikely(IS_ERR(s))) {
+ err = PTR_ERR(s);
+ goto Err;
+ }
+ err = 0;
+ if (unlikely(!s)) {
+ /* jumped */
+ nd->depth--;
+ put_link(nd);
+ } else {
+ if (*s == '/') {
+ if (!nd->root.mnt)
+ set_root(nd);
+ path_put(&nd->path);
+ nd->path = nd->root;
+ path_get(&nd->root);
+ nd->flags |= LOOKUP_JUMPED;
+ while (unlikely(*++s == '/'))
+ ;
+ }
+ nd->inode = nd->path.dentry->d_inode;
+ nd->stack[nd->depth - 1].name = name;
+ if (!*s)
+ goto OK;
+ name = s;
+ continue;
+ }
}
if (!d_can_lookup(nd->path.dentry)) {
- err = -ENOTDIR;
+ err = -ENOTDIR;
break;
}
}
terminate_walk(nd);
+Err:
+ while (unlikely(nd->depth > 1)) {
+ nd->depth--;
+ put_link(nd);
+ }
return err;
+OK:
+ if (unlikely(nd->depth > 1)) {
+ name = nd->stack[nd->depth - 1].name;
+ err = walk_component(nd, LOOKUP_FOLLOW);
+ nd->depth--;
+ put_link(nd);
+ goto Walked;
+ }
+ return 0;
}
static int path_init(int dfd, const struct filename *name, unsigned int flags,
return -ECHILD;
done:
current->total_link_count = 0;
- return link_path_walk(s, nd);
+ nd->depth++;
+ retval = link_path_walk(s, nd);
+ nd->depth--;
+ return retval;
}
static void path_cleanup(struct nameidata *nd)
fput(nd->base);
}
-static inline int lookup_last(struct nameidata *nd, struct path *path)
+static int trailing_symlink(struct nameidata *nd)
+{
+ const char *s;
+ int error = may_follow_link(&nd->link, nd);
+ if (unlikely(error))
+ return error;
+ nd->flags |= LOOKUP_PARENT;
+ s = get_link(nd);
+ if (unlikely(IS_ERR(s)))
+ return PTR_ERR(s);
+ if (unlikely(!s)) {
+ nd->depth--;
+ return 0;
+ }
+ if (*s == '/') {
+ if (!nd->root.mnt)
+ set_root(nd);
+ path_put(&nd->path);
+ nd->path = nd->root;
+ path_get(&nd->root);
+ nd->flags |= LOOKUP_JUMPED;
+ }
+ nd->inode = nd->path.dentry->d_inode;
+ error = link_path_walk(s, nd);
+ if (unlikely(error)) {
+ nd->depth--;
+ put_link(nd);
+ return error;
+ }
+ nd->depth--;
+ return 0;
+}
+
+static inline int lookup_last(struct nameidata *nd)
{
if (nd->last_type == LAST_NORM && nd->last.name[nd->last.len])
nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
nd->flags &= ~LOOKUP_PARENT;
- return walk_component(nd, path, nd->flags & LOOKUP_FOLLOW);
+ return walk_component(nd, nd->flags & LOOKUP_FOLLOW);
}
/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
static int path_lookupat(int dfd, const struct filename *name,
unsigned int flags, struct nameidata *nd)
{
- struct path path;
int err;
/*
*/
err = path_init(dfd, name, flags, nd);
if (!err && !(flags & LOOKUP_PARENT)) {
- err = lookup_last(nd, &path);
+ err = lookup_last(nd);
while (err > 0) {
- void *cookie;
- struct path link = path;
- err = may_follow_link(&link, nd);
- if (unlikely(err))
- break;
- nd->flags |= LOOKUP_PARENT;
- err = follow_link(&link, nd, &cookie);
+ err = trailing_symlink(nd);
if (err)
break;
- err = lookup_last(nd, &path);
- put_link(nd, &link, cookie);
+ err = lookup_last(nd);
+ put_link(nd);
}
}
static int filename_lookup(int dfd, struct filename *name,
unsigned int flags, struct nameidata *nd)
{
- int retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, nd);
+ int retval;
+
+ set_nameidata(nd);
+ retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, nd);
+
if (unlikely(retval == -ECHILD))
retval = path_lookupat(dfd, name, flags, nd);
if (unlikely(retval == -ESTALE))
if (likely(!retval))
audit_inode(name, nd->path.dentry, flags & LOOKUP_PARENT);
+ restore_nameidata(nd);
return retval;
}
}
path->dentry = dentry;
path->mnt = nd->path.mnt;
- if (should_follow_link(dentry, nd->flags & LOOKUP_FOLLOW))
+ if (should_follow_link(dentry, nd->flags & LOOKUP_FOLLOW)) {
+ nd->link = *path;
return 1;
+ }
mntget(path->mnt);
follow_mount(path);
error = 0;
err = mountpoint_last(nd, path);
while (err > 0) {
- void *cookie;
- struct path link = *path;
- err = may_follow_link(&link, nd);
- if (unlikely(err))
- break;
- nd->flags |= LOOKUP_PARENT;
- err = follow_link(&link, nd, &cookie);
+ err = trailing_symlink(nd);
if (err)
break;
err = mountpoint_last(nd, path);
- put_link(nd, &link, cookie);
+ put_link(nd);
}
out:
path_cleanup(nd);
int error;
if (IS_ERR(name))
return PTR_ERR(name);
+ set_nameidata(&nd);
error = path_mountpoint(dfd, name, path, &nd, flags | LOOKUP_RCU);
if (unlikely(error == -ECHILD))
error = path_mountpoint(dfd, name, path, &nd, flags);
error = path_mountpoint(dfd, name, path, &nd, flags | LOOKUP_REVAL);
if (likely(!error))
audit_inode(name, path->dentry, 0);
+ restore_nameidata(&nd);
putname(name);
return error;
}
/*
* Handle the last step of open()
*/
-static int do_last(struct nameidata *nd, struct path *path,
+static int do_last(struct nameidata *nd,
struct file *file, const struct open_flags *op,
int *opened, struct filename *name)
{
int acc_mode = op->acc_mode;
struct inode *inode;
struct path save_parent = { .dentry = NULL, .mnt = NULL };
+ struct path path;
bool retried = false;
int error;
if (nd->last.name[nd->last.len])
nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
/* we _can_ be in RCU mode here */
- error = lookup_fast(nd, path, &inode);
+ error = lookup_fast(nd, &path, &inode);
if (likely(!error))
goto finish_lookup;
*/
}
mutex_lock(&dir->d_inode->i_mutex);
- error = lookup_open(nd, path, file, op, got_write, opened);
+ error = lookup_open(nd, &path, file, op, got_write, opened);
mutex_unlock(&dir->d_inode->i_mutex);
if (error <= 0) {
open_flag &= ~O_TRUNC;
will_truncate = false;
acc_mode = MAY_OPEN;
- path_to_nameidata(path, nd);
+ path_to_nameidata(&path, nd);
goto finish_open_created;
}
/*
* create/update audit record if it already exists.
*/
- if (d_is_positive(path->dentry))
- audit_inode(name, path->dentry, 0);
+ if (d_is_positive(path.dentry))
+ audit_inode(name, path.dentry, 0);
/*
* If atomic_open() acquired write access it is dropped now due to
if ((open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT))
goto exit_dput;
- error = follow_managed(path, nd->flags);
+ error = follow_managed(&path, nd->flags);
if (error < 0)
goto exit_dput;
nd->flags |= LOOKUP_JUMPED;
BUG_ON(nd->flags & LOOKUP_RCU);
- inode = path->dentry->d_inode;
+ inode = path.dentry->d_inode;
error = -ENOENT;
- if (d_is_negative(path->dentry)) {
- path_to_nameidata(path, nd);
+ if (d_is_negative(path.dentry)) {
+ path_to_nameidata(&path, nd);
goto out;
}
finish_lookup:
- if (should_follow_link(path->dentry, nd->flags & LOOKUP_FOLLOW)) {
+ if (should_follow_link(path.dentry, nd->flags & LOOKUP_FOLLOW)) {
if (nd->flags & LOOKUP_RCU) {
- if (unlikely(nd->path.mnt != path->mnt ||
- unlazy_walk(nd, path->dentry))) {
+ if (unlikely(nd->path.mnt != path.mnt ||
+ unlazy_walk(nd, path.dentry))) {
error = -ECHILD;
goto out;
}
}
- BUG_ON(inode != path->dentry->d_inode);
+ BUG_ON(inode != path.dentry->d_inode);
+ nd->link = path;
return 1;
}
- if (unlikely(d_is_symlink(path->dentry)) && !(open_flag & O_PATH)) {
- path_to_nameidata(path, nd);
+ if (unlikely(d_is_symlink(path.dentry)) && !(open_flag & O_PATH)) {
+ path_to_nameidata(&path, nd);
error = -ELOOP;
goto out;
}
- if ((nd->flags & LOOKUP_RCU) || nd->path.mnt != path->mnt) {
- path_to_nameidata(path, nd);
+ if ((nd->flags & LOOKUP_RCU) || nd->path.mnt != path.mnt) {
+ path_to_nameidata(&path, nd);
} else {
save_parent.dentry = nd->path.dentry;
- save_parent.mnt = mntget(path->mnt);
- nd->path.dentry = path->dentry;
+ save_parent.mnt = mntget(path.mnt);
+ nd->path.dentry = path.dentry;
}
nd->inode = inode;
return error;
exit_dput:
- path_put_conditional(path, nd);
+ path_put_conditional(&path, nd);
goto out;
exit_fput:
fput(file);
struct nameidata *nd, const struct open_flags *op, int flags)
{
struct file *file;
- struct path path;
int opened = 0;
int error;
if (unlikely(error))
goto out;
- error = do_last(nd, &path, file, op, &opened, pathname);
+ error = do_last(nd, file, op, &opened, pathname);
while (unlikely(error > 0)) { /* trailing symlink */
- struct path link = path;
- void *cookie;
- error = may_follow_link(&link, nd);
- if (unlikely(error))
- break;
- nd->flags |= LOOKUP_PARENT;
nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
- error = follow_link(&link, nd, &cookie);
+ error = trailing_symlink(nd);
if (unlikely(error))
break;
- error = do_last(nd, &path, file, op, &opened, pathname);
- put_link(nd, &link, cookie);
+ error = do_last(nd, file, op, &opened, pathname);
+ put_link(nd);
}
out:
path_cleanup(nd);
int flags = op->lookup_flags;
struct file *filp;
+ set_nameidata(&nd);
filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
if (unlikely(filp == ERR_PTR(-ECHILD)))
filp = path_openat(dfd, pathname, &nd, op, flags);
if (unlikely(filp == ERR_PTR(-ESTALE)))
filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_REVAL);
+ restore_nameidata(&nd);
return filp;
}
nd.root.mnt = mnt;
nd.root.dentry = dentry;
+ set_nameidata(&nd);
if (d_is_symlink(dentry) && op->intent & LOOKUP_OPEN)
return ERR_PTR(-ELOOP);
file = path_openat(-1, filename, &nd, op, flags);
if (unlikely(file == ERR_PTR(-ESTALE)))
file = path_openat(-1, filename, &nd, op, flags | LOOKUP_REVAL);
+ restore_nameidata(&nd);
putname(filename);
return file;
}
int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
{
void *cookie;
- const char *link = dentry->d_inode->i_op->follow_link(dentry, &cookie, NULL);
+ const char *link = dentry->d_inode->i_link;
int res;
- if (IS_ERR(link))
- return PTR_ERR(link);
+ if (!link) {
+ link = dentry->d_inode->i_op->follow_link(dentry, &cookie, NULL);
+ if (IS_ERR(link))
+ return PTR_ERR(link);
+ }
res = readlink_copy(buffer, buflen, link);
if (cookie && dentry->d_inode->i_op->put_link)
dentry->d_inode->i_op->put_link(dentry, cookie);