}
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 + 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_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);
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.
name++;
} while (unlikely(*name == '/'));
if (!*name)
- return 0;
+ goto OK;
err = walk_component(nd, LOOKUP_FOLLOW);
+Walked:
if (err < 0)
- return err;
+ goto Err;
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);
+ 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 == '/'))
+ ;
}
- 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;
}
}
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 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])
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);
+ err = trailing_symlink(nd);
if (err)
break;
err = lookup_last(nd);
- put_link(nd, &link, cookie);
+ 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;
}
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;
}
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;
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);
+ 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;
}