}
EXPORT_SYMBOL(path_put);
+#define EMBEDDED_LEVELS 2
struct nameidata {
struct path path;
union {
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 * 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->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_link;
if (!res) {
- res = inode->i_op->follow_link(dentry, p, nd);
+ res = inode->i_op->follow_link(dentry, &last->cookie, nd);
if (IS_ERR(res)) {
out:
- path_put(&nd->path);
- path_put(link);
+ path_put(&last->link);
+ return res;
}
}
+ nd->depth++;
return res;
}
-static 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 0;
failed:
- nd->flags &= ~LOOKUP_RCU;
- if (!(nd->flags & LOOKUP_ROOT))
- nd->root.mnt = NULL;
- rcu_read_unlock();
return -ECHILD;
}
{
if (type == LAST_DOTDOT) {
if (nd->flags & LOOKUP_RCU) {
- if (follow_dotdot_rcu(nd))
- return -ECHILD;
+ return follow_dotdot_rcu(nd);
} else
follow_dotdot(nd);
}
nd->root.mnt = NULL;
rcu_read_unlock();
}
+ while (unlikely(nd->depth))
+ put_link(nd);
}
/*
err = lookup_fast(nd, &path, &inode);
if (unlikely(err)) {
if (err < 0)
- goto out_err;
+ return err;
err = lookup_slow(nd, &path);
if (err < 0)
- goto out_err;
+ return err;
inode = path.dentry->d_inode;
err = -ENOENT;
if (nd->flags & LOOKUP_RCU) {
if (unlikely(nd->path.mnt != path.mnt ||
unlazy_walk(nd, path.dentry))) {
- err = -ECHILD;
- goto out_err;
+ return -ECHILD;
}
}
BUG_ON(inode != path.dentry->d_inode);
out_path_put:
path_to_nameidata(&path, nd);
-out_err:
- terminate_walk(nd);
return err;
}
static int link_path_walk(const char *name, struct nameidata *nd)
{
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.
do {
name++;
} while (unlikely(*name == '/'));
- if (!*name)
- return 0;
-
- err = walk_component(nd, LOOKUP_FOLLOW);
+ if (unlikely(!*name)) {
+OK:
+ /* called from path_init(), done */
+ if (!nd->depth)
+ return 0;
+ name = nd->stack[nd->depth - 1].name;
+ /* called from trailing_symlink(), done */
+ if (!name)
+ return 0;
+ /* last component of nested symlink */
+ err = walk_component(nd, LOOKUP_FOLLOW);
+ put_link(nd);
+ } else {
+ err = walk_component(nd, LOOKUP_FOLLOW);
+ }
if (err < 0)
- return err;
+ break;
if (err) {
- if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
- path_put_conditional(&nd->link, nd);
- path_put(&nd->path);
- return -ELOOP;
- }
- BUG_ON(nd->depth >= MAX_NESTED_LINKS);
+ const char *s;
- nd->depth++;
- current->link_count++;
+ err = nd_alloc_stack(nd);
+ if (unlikely(err)) {
+ path_to_nameidata(&nd->link, nd);
+ break;
+ }
- do {
- struct path link = nd->link;
- void *cookie;
- const char *s = get_link(&link, nd, &cookie);
+ s = get_link(nd);
- if (unlikely(IS_ERR(s))) {
- err = PTR_ERR(s);
- break;
- }
- err = 0;
- if (likely(s)) {
- 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;
- err = link_path_walk(s, nd);
- if (unlikely(err)) {
- put_link(nd, &link, cookie);
- break;
- }
+ if (unlikely(IS_ERR(s))) {
+ err = PTR_ERR(s);
+ break;
+ }
+ err = 0;
+ if (unlikely(!s)) {
+ /* jumped */
+ 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 == '/'))
+ ;
}
- err = walk_component(nd, LOOKUP_FOLLOW);
- put_link(nd, &link, cookie);
- } while (err > 0);
-
- current->link_count--;
- nd->depth--;
- if (err)
- return err;
+ 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;
fput(nd->base);
}
+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))) {
+ terminate_walk(nd);
+ 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;
+ nd->stack[0].name = NULL;
+ return link_path_walk(s, nd);
+}
+
static inline int lookup_last(struct nameidata *nd)
{
+ int err;
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, nd->flags & LOOKUP_FOLLOW);
+ err = walk_component(nd, nd->flags & LOOKUP_FOLLOW);
+ if (nd->depth)
+ put_link(nd);
+ if (err < 0)
+ terminate_walk(nd);
+ return err;
}
/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
*/
err = path_init(dfd, name, flags, nd);
if (!err && !(flags & LOOKUP_PARENT)) {
- err = lookup_last(nd);
- while (err > 0) {
- void *cookie;
- struct path link = nd->link;
- err = may_follow_link(&link, nd);
- if (unlikely(err))
- break;
- nd->flags |= LOOKUP_PARENT;
- err = follow_link(&link, nd, &cookie);
+ while ((err = lookup_last(nd)) > 0) {
+ err = trailing_symlink(nd);
if (err)
break;
- err = lookup_last(nd);
- put_link(nd, &link, cookie);
}
}
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;
}
dput(dentry);
goto out;
}
+ if (nd->depth)
+ put_link(nd);
path->dentry = dentry;
path->mnt = nd->path.mnt;
if (should_follow_link(dentry, nd->flags & LOOKUP_FOLLOW)) {
if (unlikely(err))
goto out;
- 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);
+ while ((err = mountpoint_last(nd, path)) > 0) {
+ err = trailing_symlink(nd);
if (err)
break;
- err = mountpoint_last(nd, path);
- put_link(nd, &link, cookie);
}
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;
}
if (nd->last_type != LAST_NORM) {
error = handle_dots(nd, nd->last_type);
- if (error)
+ if (unlikely(error)) {
+ terminate_walk(nd);
return error;
+ }
goto finish_open;
}
* about to look up
*/
error = complete_walk(nd);
- if (error)
+ if (error) {
+ if (nd->depth)
+ put_link(nd);
return error;
+ }
audit_inode(name, dir, LOOKUP_PARENT);
error = -EISDIR;
}
}
BUG_ON(inode != path.dentry->d_inode);
+ if (nd->depth)
+ put_link(nd);
nd->link = path;
return 1;
}
finish_open:
error = complete_walk(nd);
if (error) {
+ if (nd->depth)
+ put_link(nd);
path_put(&save_parent);
return error;
}
if (unlikely(error))
goto out;
- error = do_last(nd, file, op, &opened, pathname);
- while (unlikely(error > 0)) { /* trailing symlink */
- struct path link = nd->link;
- void *cookie;
- error = may_follow_link(&link, nd);
- if (unlikely(error))
- break;
- nd->flags |= LOOKUP_PARENT;
+ while ((error = do_last(nd, file, op, &opened, pathname)) > 0) {
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, file, op, &opened, pathname);
- put_link(nd, &link, cookie);
}
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;
}