namei: move link/cookie pairs into nameidata
[cascardo/linux.git] / fs / namei.c
index ffab2e0..d4b238a 100644 (file)
@@ -494,7 +494,10 @@ EXPORT_SYMBOL(path_put);
 
 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;
@@ -502,7 +505,11 @@ struct nameidata {
        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[MAX_NESTED_LINKS + 1];
 };
 
 /*
@@ -666,8 +673,6 @@ static __always_inline void set_root(struct nameidata *nd)
        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;
@@ -713,23 +718,11 @@ void nd_jump_link(struct nameidata *nd, struct path *path)
        nd->flags |= LOOKUP_JUMPED;
 }
 
-void nd_set_link(struct nameidata *nd, char *path)
-{
-       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);
+       if (cookie && inode->i_op->put_link)
+               inode->i_op->put_link(link->dentry, cookie);
        path_put(link);
 }
 
@@ -849,67 +842,45 @@ static int may_linkat(struct path *link)
        return -EPERM;
 }
 
-static __always_inline int
-follow_link(struct path *link, struct nameidata *nd, void **p)
+static __always_inline const char *
+get_link(struct path *link, struct nameidata *nd, void **p)
 {
        struct dentry *dentry = 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);
 
-       error = -ELOOP;
+       res = ERR_PTR(-ELOOP);
        if (unlikely(current->total_link_count >= 40))
-               goto out_put_nd_path;
+               goto out;
 
        cond_resched();
        current->total_link_count++;
 
        touch_atime(link);
-       nd_set_link(nd, NULL);
 
-       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);
+       *p = NULL;
+       res = inode->i_link;
+       if (!res) {
+               res = inode->i_op->follow_link(dentry, p, nd);
+               if (IS_ERR(res)) {
+out:
                        path_put(&nd->path);
-                       nd->path = nd->root;
-                       path_get(&nd->root);
-                       nd->flags |= LOOKUP_JUMPED;
+                       path_put(link);
                }
-               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;
+       return res;
 }
 
 static int follow_up_rcu(struct path *path)
@@ -1415,6 +1386,7 @@ static int lookup_fast(struct nameidata *nd,
         */
        if (nd->flags & LOOKUP_RCU) {
                unsigned seq;
+               bool negative;
                dentry = __d_lookup_rcu(parent, &nd->last, &seq);
                if (!dentry)
                        goto unlazy;
@@ -1424,8 +1396,11 @@ static int lookup_fast(struct nameidata *nd,
                 * 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
@@ -1472,6 +1447,10 @@ unlazy:
                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);
@@ -1561,9 +1540,9 @@ static inline int should_follow_link(struct dentry *dentry, int follow)
        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;
        /*
@@ -1573,79 +1552,44 @@ static inline int walk_component(struct nameidata *nd, struct path *path,
         */
        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(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:
@@ -1772,9 +1716,9 @@ static inline u64 hash_name(const char *name)
  */
 static int link_path_walk(const char *name, struct nameidata *nd)
 {
-       struct path next;
+       struct saved *last = nd->stack;
        int err;
-       
+
        while (*name=='/')
                name++;
        if (!*name)
@@ -1821,7 +1765,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 
                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.
@@ -1830,24 +1774,89 @@ static int link_path_walk(const char *name, struct nameidata *nd)
                        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;
+
+                       if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
+                               path_put_conditional(&nd->link, nd);
+                               path_put(&nd->path);
+                               err = -ELOOP;
+                               goto Err;
+                       }
+                       BUG_ON(nd->depth >= MAX_NESTED_LINKS);
+
+                       nd->depth++;
+                       current->link_count++;
+                       last++;
+
+                       last->link = nd->link;
+                       s = get_link(&last->link, nd, &last->cookie);
+
+                       if (unlikely(IS_ERR(s))) {
+                               err = PTR_ERR(s);
+                               current->link_count--;
+                               nd->depth--;
+                               last--;
+                               goto Err;
+                       }
+                       err = 0;
+                       if (unlikely(!s)) {
+                               /* jumped */
+                               put_link(nd, &last->link, last->cookie);
+                               current->link_count--;
+                               nd->depth--;
+                               last--;
+                       } 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;
+                               last->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)) {
+               put_link(nd, &last->link, last->cookie);
+               current->link_count--;
+               nd->depth--;
+               last--;
+       }
        return err;
+OK:
+       if (unlikely(nd->depth)) {
+               name = last->name;
+               err = walk_component(nd, LOOKUP_FOLLOW);
+               put_link(nd, &last->link, last->cookie);
+               current->link_count--;
+               nd->depth--;
+               last--;
+               goto Walked;
+       }
+       return 0;
 }
 
 static int path_init(int dfd, const struct filename *name, unsigned int flags,
@@ -1962,20 +1971,46 @@ 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 path *link, struct nameidata *nd, void **p)
+{
+       const char *s;
+       int error = may_follow_link(link, nd);
+       if (unlikely(error))
+               return error;
+       nd->flags |= LOOKUP_PARENT;
+       s = get_link(link, nd, p);
+       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 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;
 
        /*
@@ -1994,19 +2029,15 @@ static int path_lookupat(int dfd, const struct filename *name,
         */
        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);
+                       nd->stack[0].link = nd->link;
+                       err = trailing_symlink(&nd->stack[0].link,
+                                               nd, &nd->stack[0].cookie);
                        if (err)
                                break;
-                       err = lookup_last(nd, &path);
-                       put_link(nd, &link, cookie);
+                       err = lookup_last(nd);
+                       put_link(nd, &nd->stack[0].link, nd->stack[0].cookie);
                }
        }
 
@@ -2120,16 +2151,6 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
 }
 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
@@ -2213,9 +2234,13 @@ EXPORT_SYMBOL(user_path_at);
  *     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;
 
@@ -2225,11 +2250,14 @@ user_path_parent(int dfd, const char __user *path, struct nameidata *nd,
        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;
 }
@@ -2317,8 +2345,10 @@ done:
        }
        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;
@@ -2339,31 +2369,24 @@ out:
  */
 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);
+               nd->stack[0].link = nd->link;
+               err = trailing_symlink(&nd->stack[0].link,
+                                       nd, &nd->stack[0].cookie);
                if (err)
                        break;
-               err = mountpoint_last(&nd, path);
-               put_link(&nd, &link, cookie);
+               err = mountpoint_last(nd, path);
+               put_link(nd, &nd->stack[0].link, nd->stack[0].cookie);
        }
 out:
-       path_cleanup(&nd);
+       path_cleanup(nd);
        return err;
 }
 
@@ -2371,14 +2394,15 @@ static int
 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);
+       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);
        putname(name);
@@ -2913,7 +2937,7 @@ out_dput:
 /*
  * 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)
 {
@@ -2923,8 +2947,8 @@ static int do_last(struct nameidata *nd, struct path *path,
        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;
 
@@ -2941,10 +2965,8 @@ static int do_last(struct nameidata *nd, struct path *path,
        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;
 
@@ -2982,7 +3004,7 @@ retry_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) {
@@ -3002,15 +3024,15 @@ retry_lookup:
                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
@@ -3026,7 +3048,7 @@ retry_lookup:
        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;
 
@@ -3034,32 +3056,38 @@ retry_lookup:
                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(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;
@@ -3121,7 +3149,7 @@ out:
        return error;
 
 exit_dput:
-       path_put_conditional(path, nd);
+       path_put_conditional(&path, nd);
        goto out;
 exit_fput:
        fput(file);
@@ -3212,7 +3240,6 @@ static struct file *path_openat(int dfd, struct filename *pathname,
                struct nameidata *nd, const struct open_flags *op, int flags)
 {
        struct file *file;
-       struct path path;
        int opened = 0;
        int error;
 
@@ -3224,36 +3251,27 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 
        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);
+               nd->stack[0].link = nd->link;
+               error= trailing_symlink(&nd->stack[0].link,
+                                       nd, &nd->stack[0].cookie);
                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, &nd->stack[0].link, nd->stack[0].cookie);
        }
 out:
        path_cleanup(nd);
+out2:
        if (!(opened & FILE_OPENED)) {
                BUG_ON(!error);
                put_filp(file);
@@ -3346,7 +3364,7 @@ static struct dentry *filename_create(int dfd, struct filename *name,
         * 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;
 
@@ -3635,14 +3653,17 @@ static long do_rmdir(int dfd, const char __user *pathname)
        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;
@@ -3654,13 +3675,12 @@ retry:
                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;
@@ -3668,17 +3688,17 @@ retry:
                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;
@@ -3761,43 +3781,45 @@ static long do_unlinkat(int dfd, const char __user *pathname)
        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;
@@ -3806,9 +3828,9 @@ exit2:
                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;
@@ -4238,14 +4260,15 @@ EXPORT_SYMBOL(vfs_rename);
 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;
 
@@ -4259,47 +4282,45 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
        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;
@@ -4307,7 +4328,7 @@ retry_deleg:
        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;
@@ -4321,16 +4342,16 @@ retry_deleg:
 
                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 */
@@ -4343,32 +4364,32 @@ retry_deleg:
        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;
@@ -4427,18 +4448,18 @@ EXPORT_SYMBOL(readlink_copy);
  */
 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);
@@ -4470,22 +4491,21 @@ int page_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 }
 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);