}
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;
- char *saved_names[MAX_NESTED_LINKS + 1];
+ 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;
}
-void nd_set_link(struct nameidata *nd, char *path)
+static inline void put_link(struct nameidata *nd)
{
- nd->saved_names[nd->depth] = path;
-}
-EXPORT_SYMBOL(nd_set_link);
-
-char *nd_get_link(struct nameidata *nd)
-{
- return nd->saved_names[nd->depth];
-}
-EXPORT_SYMBOL(nd_get_link);
-
-static inline void put_link(struct nameidata *nd, struct path *link, void *cookie)
-{
- struct inode *inode = link->dentry->d_inode;
- if (inode->i_op->put_link)
- inode->i_op->put_link(link->dentry, nd, 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 int
-follow_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;
- char *s;
+ 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);
+
+ if (unlikely(current->total_link_count >= MAXSYMLINKS)) {
+ path_put(&nd->path);
+ path_put(&nd->link);
+ return ERR_PTR(-ELOOP);
+ }
- error = -ELOOP;
- if (unlikely(current->total_link_count >= 40))
- goto out_put_nd_path;
+ last->link = nd->link;
+ last->cookie = NULL;
cond_resched();
current->total_link_count++;
- touch_atime(link);
- nd_set_link(nd, NULL);
+ touch_atime(&last->link);
- error = security_inode_follow_link(link->dentry, nd);
+ error = security_inode_follow_link(dentry);
+ res = ERR_PTR(error);
if (error)
- goto out_put_nd_path;
+ goto out;
nd->last_type = LAST_BIND;
- *p = dentry->d_inode->i_op->follow_link(dentry, nd);
- error = PTR_ERR(*p);
- if (IS_ERR(*p))
- goto out_put_nd_path;
-
- error = 0;
- s = nd_get_link(nd);
- if (s) {
- if (unlikely(IS_ERR(s))) {
- path_put(&nd->path);
- put_link(nd, link, *p);
- return PTR_ERR(s);
- }
- if (*s == '/') {
- if (!nd->root.mnt)
- set_root(nd);
+ 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);
- nd->path = nd->root;
- path_get(&nd->root);
- nd->flags |= LOOKUP_JUMPED;
+ path_put(&last->link);
+ return res;
}
- nd->inode = nd->path.dentry->d_inode;
- error = link_path_walk(s, nd);
- if (unlikely(error))
- put_link(nd, link, *p);
}
-
- return error;
-
-out_put_nd_path:
- *p = NULL;
- path_put(&nd->path);
- path_put(link);
- return error;
+ nd->depth++;
+ return res;
}
static int follow_up_rcu(struct path *path)
*/
if (nd->flags & LOOKUP_RCU) {
unsigned seq;
+ bool negative;
dentry = __d_lookup_rcu(parent, &nd->last, &seq);
if (!dentry)
goto unlazy;
* the dentry name information from lookup.
*/
*inode = dentry->d_inode;
+ negative = d_is_negative(dentry);
if (read_seqcount_retry(&dentry->d_seq, seq))
return -ECHILD;
+ if (negative)
+ return -ENOENT;
/*
* This sequence count validates that the parent had no
goto need_lookup;
}
+ if (unlikely(d_is_negative(dentry))) {
+ dput(dentry);
+ return -ENOENT;
+ }
path->mnt = mnt;
path->dentry = dentry;
err = follow_managed(path, nd->flags);
return unlikely(d_is_symlink(dentry)) ? follow : 0;
}
-static inline 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))
+ goto out_path_put;
}
- err = -ENOENT;
- 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;
}
}
EXPORT_SYMBOL(vfs_path_lookup);
-/*
- * Restricted form of lookup. Doesn't follow links, single-component only,
- * needs parent already locked. Doesn't follow mounts.
- * SMP-safe.
- */
-static struct dentry *lookup_hash(struct nameidata *nd)
-{
- return __lookup_hash(&nd->last, nd->path.dentry, nd->flags);
-}
-
/**
* lookup_one_len - filesystem helper to lookup single pathname component
* @name: pathname component to lookup
* path-walking is complete.
*/
static struct filename *
-user_path_parent(int dfd, const char __user *path, struct nameidata *nd,
+user_path_parent(int dfd, const char __user *path,
+ struct path *parent,
+ struct qstr *last,
+ int *type,
unsigned int flags)
{
+ struct nameidata nd;
struct filename *s = getname(path);
int error;
if (IS_ERR(s))
return s;
- error = filename_lookup(dfd, s, flags | LOOKUP_PARENT, nd);
+ error = filename_lookup(dfd, s, flags | LOOKUP_PARENT, &nd);
if (error) {
putname(s);
return ERR_PTR(error);
}
+ *parent = nd.path;
+ *last = nd.last;
+ *type = nd.last_type;
return s;
}
}
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;
*/
static int
path_mountpoint(int dfd, const struct filename *name, struct path *path,
- unsigned int flags)
+ struct nameidata *nd, unsigned int flags)
{
- struct nameidata nd;
- int err;
-
- err = path_init(dfd, name, flags, &nd);
+ int err = path_init(dfd, name, flags, nd);
if (unlikely(err))
goto out;
- err = mountpoint_last(&nd, path);
+ 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);
+ err = mountpoint_last(nd, path);
+ put_link(nd);
}
out:
- path_cleanup(&nd);
+ path_cleanup(nd);
return err;
}
filename_mountpoint(int dfd, struct filename *name, struct path *path,
unsigned int flags)
{
+ struct nameidata nd;
int error;
if (IS_ERR(name))
return PTR_ERR(name);
- error = path_mountpoint(dfd, name, path, flags | LOOKUP_RCU);
+ set_nameidata(&nd);
+ error = path_mountpoint(dfd, name, path, &nd, flags | LOOKUP_RCU);
if (unlikely(error == -ECHILD))
- error = path_mountpoint(dfd, name, path, flags);
+ error = path_mountpoint(dfd, name, path, &nd, flags);
if (unlikely(error == -ESTALE))
- error = path_mountpoint(dfd, name, path, flags | LOOKUP_REVAL);
+ 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)
{
bool got_write = false;
int acc_mode = op->acc_mode;
struct inode *inode;
- bool symlink_ok = false;
struct path save_parent = { .dentry = NULL, .mnt = NULL };
+ struct path path;
bool retried = false;
int error;
if (!(open_flag & O_CREAT)) {
if (nd->last.name[nd->last.len])
nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
- if (open_flag & O_PATH && !(nd->flags & LOOKUP_FOLLOW))
- symlink_ok = true;
/* 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;
-finish_lookup:
- /* we _can_ be in RCU mode here */
+ 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;
}
-
- if (should_follow_link(path->dentry, !symlink_ok)) {
+finish_lookup:
+ 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 ((nd->flags & LOOKUP_RCU) || nd->path.mnt != path->mnt) {
- 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);
} 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(file->f_flags & __O_TMPFILE)) {
error = do_tmpfile(dfd, pathname, nd, flags, op, file, &opened);
- goto out;
+ goto out2;
}
error = path_init(dfd, pathname, flags, nd);
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;
- if (!(nd->flags & LOOKUP_FOLLOW)) {
- path_put_conditional(&path, nd);
- path_put(&nd->path);
- error = -ELOOP;
- break;
- }
- 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);
+out2:
if (!(opened & FILE_OPENED)) {
BUG_ON(!error);
put_filp(file);
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;
}
* Do the final lookup.
*/
mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
- dentry = lookup_hash(&nd);
+ dentry = __lookup_hash(&nd.last, nd.path.dentry, nd.flags);
if (IS_ERR(dentry))
goto unlock;
int error = 0;
struct filename *name;
struct dentry *dentry;
- struct nameidata nd;
+ struct path path;
+ struct qstr last;
+ int type;
unsigned int lookup_flags = 0;
retry:
- name = user_path_parent(dfd, pathname, &nd, lookup_flags);
+ name = user_path_parent(dfd, pathname,
+ &path, &last, &type, lookup_flags);
if (IS_ERR(name))
return PTR_ERR(name);
- switch(nd.last_type) {
+ switch (type) {
case LAST_DOTDOT:
error = -ENOTEMPTY;
goto exit1;
goto exit1;
}
- nd.flags &= ~LOOKUP_PARENT;
- error = mnt_want_write(nd.path.mnt);
+ error = mnt_want_write(path.mnt);
if (error)
goto exit1;
- mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
- dentry = lookup_hash(&nd);
+ mutex_lock_nested(&path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
+ dentry = __lookup_hash(&last, path.dentry, lookup_flags);
error = PTR_ERR(dentry);
if (IS_ERR(dentry))
goto exit2;
error = -ENOENT;
goto exit3;
}
- error = security_path_rmdir(&nd.path, dentry);
+ error = security_path_rmdir(&path, dentry);
if (error)
goto exit3;
- error = vfs_rmdir(nd.path.dentry->d_inode, dentry);
+ error = vfs_rmdir(path.dentry->d_inode, dentry);
exit3:
dput(dentry);
exit2:
- mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
- mnt_drop_write(nd.path.mnt);
+ mutex_unlock(&path.dentry->d_inode->i_mutex);
+ mnt_drop_write(path.mnt);
exit1:
- path_put(&nd.path);
+ path_put(&path);
putname(name);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
int error;
struct filename *name;
struct dentry *dentry;
- struct nameidata nd;
+ struct path path;
+ struct qstr last;
+ int type;
struct inode *inode = NULL;
struct inode *delegated_inode = NULL;
unsigned int lookup_flags = 0;
retry:
- name = user_path_parent(dfd, pathname, &nd, lookup_flags);
+ name = user_path_parent(dfd, pathname,
+ &path, &last, &type, lookup_flags);
if (IS_ERR(name))
return PTR_ERR(name);
error = -EISDIR;
- if (nd.last_type != LAST_NORM)
+ if (type != LAST_NORM)
goto exit1;
- nd.flags &= ~LOOKUP_PARENT;
- error = mnt_want_write(nd.path.mnt);
+ error = mnt_want_write(path.mnt);
if (error)
goto exit1;
retry_deleg:
- mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
- dentry = lookup_hash(&nd);
+ mutex_lock_nested(&path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
+ dentry = __lookup_hash(&last, path.dentry, lookup_flags);
error = PTR_ERR(dentry);
if (!IS_ERR(dentry)) {
/* Why not before? Because we want correct error value */
- if (nd.last.name[nd.last.len])
+ if (last.name[last.len])
goto slashes;
inode = dentry->d_inode;
if (d_is_negative(dentry))
goto slashes;
ihold(inode);
- error = security_path_unlink(&nd.path, dentry);
+ error = security_path_unlink(&path, dentry);
if (error)
goto exit2;
- error = vfs_unlink(nd.path.dentry->d_inode, dentry, &delegated_inode);
+ error = vfs_unlink(path.dentry->d_inode, dentry, &delegated_inode);
exit2:
dput(dentry);
}
- mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
+ mutex_unlock(&path.dentry->d_inode->i_mutex);
if (inode)
iput(inode); /* truncate the inode here */
inode = NULL;
if (!error)
goto retry_deleg;
}
- mnt_drop_write(nd.path.mnt);
+ mnt_drop_write(path.mnt);
exit1:
- path_put(&nd.path);
+ path_put(&path);
putname(name);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
int, newdfd, const char __user *, newname, unsigned int, flags)
{
- struct dentry *old_dir, *new_dir;
struct dentry *old_dentry, *new_dentry;
struct dentry *trap;
- struct nameidata oldnd, newnd;
+ struct path old_path, new_path;
+ struct qstr old_last, new_last;
+ int old_type, new_type;
struct inode *delegated_inode = NULL;
struct filename *from;
struct filename *to;
- unsigned int lookup_flags = 0;
+ unsigned int lookup_flags = 0, target_flags = LOOKUP_RENAME_TARGET;
bool should_retry = false;
int error;
if ((flags & RENAME_WHITEOUT) && !capable(CAP_MKNOD))
return -EPERM;
+ if (flags & RENAME_EXCHANGE)
+ target_flags = 0;
+
retry:
- from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags);
+ from = user_path_parent(olddfd, oldname,
+ &old_path, &old_last, &old_type, lookup_flags);
if (IS_ERR(from)) {
error = PTR_ERR(from);
goto exit;
}
- to = user_path_parent(newdfd, newname, &newnd, lookup_flags);
+ to = user_path_parent(newdfd, newname,
+ &new_path, &new_last, &new_type, lookup_flags);
if (IS_ERR(to)) {
error = PTR_ERR(to);
goto exit1;
}
error = -EXDEV;
- if (oldnd.path.mnt != newnd.path.mnt)
+ if (old_path.mnt != new_path.mnt)
goto exit2;
- old_dir = oldnd.path.dentry;
error = -EBUSY;
- if (oldnd.last_type != LAST_NORM)
+ if (old_type != LAST_NORM)
goto exit2;
- new_dir = newnd.path.dentry;
if (flags & RENAME_NOREPLACE)
error = -EEXIST;
- if (newnd.last_type != LAST_NORM)
+ if (new_type != LAST_NORM)
goto exit2;
- error = mnt_want_write(oldnd.path.mnt);
+ error = mnt_want_write(old_path.mnt);
if (error)
goto exit2;
- oldnd.flags &= ~LOOKUP_PARENT;
- newnd.flags &= ~LOOKUP_PARENT;
- if (!(flags & RENAME_EXCHANGE))
- newnd.flags |= LOOKUP_RENAME_TARGET;
-
retry_deleg:
- trap = lock_rename(new_dir, old_dir);
+ trap = lock_rename(new_path.dentry, old_path.dentry);
- old_dentry = lookup_hash(&oldnd);
+ old_dentry = __lookup_hash(&old_last, old_path.dentry, lookup_flags);
error = PTR_ERR(old_dentry);
if (IS_ERR(old_dentry))
goto exit3;
error = -ENOENT;
if (d_is_negative(old_dentry))
goto exit4;
- new_dentry = lookup_hash(&newnd);
+ new_dentry = __lookup_hash(&new_last, new_path.dentry, lookup_flags | target_flags);
error = PTR_ERR(new_dentry);
if (IS_ERR(new_dentry))
goto exit4;
if (!d_is_dir(new_dentry)) {
error = -ENOTDIR;
- if (newnd.last.name[newnd.last.len])
+ if (new_last.name[new_last.len])
goto exit5;
}
}
/* unless the source is a directory trailing slashes give -ENOTDIR */
if (!d_is_dir(old_dentry)) {
error = -ENOTDIR;
- if (oldnd.last.name[oldnd.last.len])
+ if (old_last.name[old_last.len])
goto exit5;
- if (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len])
+ if (!(flags & RENAME_EXCHANGE) && new_last.name[new_last.len])
goto exit5;
}
/* source should not be ancestor of target */
if (new_dentry == trap)
goto exit5;
- error = security_path_rename(&oldnd.path, old_dentry,
- &newnd.path, new_dentry, flags);
+ error = security_path_rename(&old_path, old_dentry,
+ &new_path, new_dentry, flags);
if (error)
goto exit5;
- error = vfs_rename(old_dir->d_inode, old_dentry,
- new_dir->d_inode, new_dentry,
+ error = vfs_rename(old_path.dentry->d_inode, old_dentry,
+ new_path.dentry->d_inode, new_dentry,
&delegated_inode, flags);
exit5:
dput(new_dentry);
exit4:
dput(old_dentry);
exit3:
- unlock_rename(new_dir, old_dir);
+ unlock_rename(new_path.dentry, old_path.dentry);
if (delegated_inode) {
error = break_deleg_wait(&delegated_inode);
if (!error)
goto retry_deleg;
}
- mnt_drop_write(oldnd.path.mnt);
+ mnt_drop_write(old_path.mnt);
exit2:
if (retry_estale(error, lookup_flags))
should_retry = true;
- path_put(&newnd.path);
+ path_put(&new_path);
putname(to);
exit1:
- path_put(&oldnd.path);
+ path_put(&old_path);
putname(from);
if (should_retry) {
should_retry = false;
*/
int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
{
- struct nameidata nd;
void *cookie;
+ const char *link = dentry->d_inode->i_link;
int res;
- nd.depth = 0;
- cookie = dentry->d_inode->i_op->follow_link(dentry, &nd);
- if (IS_ERR(cookie))
- return PTR_ERR(cookie);
-
- res = readlink_copy(buffer, buflen, nd_get_link(&nd));
- if (dentry->d_inode->i_op->put_link)
- dentry->d_inode->i_op->put_link(dentry, &nd, cookie);
+ 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);
return res;
}
EXPORT_SYMBOL(generic_readlink);
}
EXPORT_SYMBOL(page_readlink);
-void *page_follow_link_light(struct dentry *dentry, struct nameidata *nd)
+const char *page_follow_link_light(struct dentry *dentry, void **cookie, struct nameidata *nd)
{
struct page *page = NULL;
- nd_set_link(nd, page_getlink(dentry, &page));
- return page;
+ char *res = page_getlink(dentry, &page);
+ if (!IS_ERR(res))
+ *cookie = page;
+ return res;
}
EXPORT_SYMBOL(page_follow_link_light);
-void page_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie)
+void page_put_link(struct dentry *dentry, void *cookie)
{
struct page *page = cookie;
-
- if (page) {
- kunmap(page);
- page_cache_release(page);
- }
+ kunmap(page);
+ page_cache_release(page);
}
EXPORT_SYMBOL(page_put_link);