]> Pileus Git - ~andy/linux/blobdiff - fs/namei.c
clear RCU on all failure exits from link_path_walk()
[~andy/linux] / fs / namei.c
index 0087cf9c2c6bccaf99000fbd0bfe95257549d81b..f09887a45831498525d7520e4bc7f4c59e1a8919 100644 (file)
@@ -539,14 +539,6 @@ err_unlock:
        return -ECHILD;
 }
 
-/* Try to drop out of rcu-walk mode if we were in it, otherwise do nothing.  */
-static inline int nameidata_drop_rcu_last_maybe(struct nameidata *nd)
-{
-       if (likely(nd->flags & LOOKUP_RCU))
-               return nameidata_drop_rcu_last(nd);
-       return 0;
-}
-
 /**
  * release_open_intent - free up open intent resources
  * @nd: pointer to nameidata
@@ -613,19 +605,8 @@ do_revalidate_rcu(struct dentry *dentry, struct nameidata *nd)
        return dentry;
 }
 
-static inline int need_reval_dot(struct dentry *dentry)
-{
-       if (likely(!(dentry->d_flags & DCACHE_OP_REVALIDATE)))
-               return 0;
-
-       if (likely(!(dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)))
-               return 0;
-
-       return 1;
-}
-
 /*
- * force_reval_path - force revalidation of a dentry
+ * handle_reval_path - force revalidation of a dentry
  *
  * In some situations the path walking code will trust dentries without
  * revalidating them. This causes problems for filesystems that depend on
@@ -639,27 +620,28 @@ static inline int need_reval_dot(struct dentry *dentry)
  * invalidate the dentry. It's up to the caller to handle putting references
  * to the path if necessary.
  */
-static int
-force_reval_path(struct path *path, struct nameidata *nd)
+static inline int handle_reval_path(struct nameidata *nd)
 {
+       struct dentry *dentry = nd->path.dentry;
        int status;
-       struct dentry *dentry = path->dentry;
 
-       /*
-        * only check on filesystems where it's possible for the dentry to
-        * become stale.
-        */
-       if (!need_reval_dot(dentry))
+       if (likely(!(nd->flags & LOOKUP_JUMPED)))
                return 0;
 
+       if (likely(!(dentry->d_flags & DCACHE_OP_REVALIDATE)))
+               return 0;
+
+       if (likely(!(dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)))
+               return 0;
+
+       /* Note: we do not d_invalidate() */
        status = d_revalidate(dentry, nd);
        if (status > 0)
                return 0;
 
-       if (!status) {
-               d_invalidate(dentry);
+       if (!status)
                status = -ESTALE;
-       }
+
        return status;
 }
 
@@ -728,6 +710,7 @@ static __always_inline int __vfs_follow_link(struct nameidata *nd, const char *l
                path_put(&nd->path);
                nd->path = nd->root;
                path_get(&nd->root);
+               nd->flags |= LOOKUP_JUMPED;
        }
        nd->inode = nd->path.dentry->d_inode;
 
@@ -771,6 +754,13 @@ __do_follow_link(const struct path *link, struct nameidata *nd, void **p)
        if (link->mnt == nd->path.mnt)
                mntget(link->mnt);
 
+       error = security_inode_follow_link(link->dentry, nd);
+       if (error) {
+               *p = ERR_PTR(error); /* no ->put_link(), please */
+               path_put(&nd->path);
+               return error;
+       }
+
        nd->last_type = LAST_BIND;
        *p = dentry->d_inode->i_op->follow_link(dentry, nd);
        error = PTR_ERR(*p);
@@ -779,11 +769,8 @@ __do_follow_link(const struct path *link, struct nameidata *nd, void **p)
                error = 0;
                if (s)
                        error = __vfs_follow_link(nd, s);
-               else if (nd->last_type == LAST_BIND) {
-                       error = force_reval_path(&nd->path, nd);
-                       if (error)
-                               path_put(&nd->path);
-               }
+               else if (nd->last_type == LAST_BIND)
+                       nd->flags |= LOOKUP_JUMPED;
        }
        return error;
 }
@@ -811,9 +798,6 @@ static inline int do_follow_link(struct inode *inode, struct path *path, struct
                goto loop;
        BUG_ON(nd->depth >= MAX_NESTED_LINKS);
        cond_resched();
-       err = security_inode_follow_link(path->dentry, nd);
-       if (err)
-               goto loop;
        current->link_count++;
        current->total_link_count++;
        nd->depth++;
@@ -1322,6 +1306,30 @@ fail:
        return PTR_ERR(dentry);
 }
 
+static inline int may_lookup(struct nameidata *nd)
+{
+       if (nd->flags & LOOKUP_RCU) {
+               int err = exec_permission(nd->inode, IPERM_FLAG_RCU);
+               if (err != -ECHILD)
+                       return err;
+               if (nameidata_drop_rcu(nd))
+                       return -ECHILD;
+       }
+       return exec_permission(nd->inode, 0);
+}
+
+static inline int handle_dots(struct nameidata *nd, int type)
+{
+       if (type == LAST_DOTDOT) {
+               if (nd->flags & LOOKUP_RCU) {
+                       if (follow_dotdot_rcu(nd))
+                               return -ECHILD;
+               } else
+                       follow_dotdot(nd);
+       }
+       return 0;
+}
+
 /*
  * Name resolution.
  * This is the basic name resolution function, turning a pathname into
@@ -1339,7 +1347,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
        while (*name=='/')
                name++;
        if (!*name)
-               goto return_reval;
+               return 0;
 
        if (nd->depth)
                lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);
@@ -1350,19 +1358,11 @@ static int link_path_walk(const char *name, struct nameidata *nd)
                unsigned long hash;
                struct qstr this;
                unsigned int c;
+               int type;
 
                nd->flags |= LOOKUP_CONTINUE;
-               if (nd->flags & LOOKUP_RCU) {
-                       err = exec_permission(nd->inode, IPERM_FLAG_RCU);
-                       if (err == -ECHILD) {
-                               if (nameidata_drop_rcu(nd))
-                                       return -ECHILD;
-                               goto exec_again;
-                       }
-               } else {
-exec_again:
-                       err = exec_permission(nd->inode, 0);
-               }
+
+               err = may_lookup(nd);
                if (err)
                        break;
 
@@ -1378,6 +1378,20 @@ exec_again:
                this.len = name - (const char *) this.name;
                this.hash = end_name_hash(hash);
 
+               type = LAST_NORM;
+               if (this.name[0] == '.') switch (this.len) {
+                       case 2:
+                               if (this.name[1] == '.') {
+                                       type = LAST_DOTDOT;
+                                       nd->flags |= LOOKUP_JUMPED;
+                               }
+                               break;
+                       case 1:
+                               type = LAST_DOT;
+               }
+               if (likely(type == LAST_NORM))
+                       nd->flags &= ~LOOKUP_JUMPED;
+
                /* remove trailing slashes? */
                if (!c)
                        goto last_component;
@@ -1390,41 +1404,30 @@ exec_again:
                 * to be able to know about the current root directory and
                 * parent relationships.
                 */
-               if (this.name[0] == '.') switch (this.len) {
-                       default:
-                               break;
-                       case 2:
-                               if (this.name[1] != '.')
-                                       break;
-                               if (nd->flags & LOOKUP_RCU) {
-                                       if (follow_dotdot_rcu(nd))
-                                               return -ECHILD;
-                               } else
-                                       follow_dotdot(nd);
-                               /* fallthrough */
-                       case 1:
-                               continue;
+               if (unlikely(type != LAST_NORM)) {
+                       err = handle_dots(nd, type);
+                       if (err)
+                               goto return_err;
+                       continue;
                }
+
                /* This does the actual lookups.. */
                err = do_lookup(nd, &this, &next, &inode);
                if (err)
                        break;
-               err = -ENOENT;
-               if (!inode)
-                       goto out_dput;
 
-               if (inode->i_op->follow_link) {
+               if (inode && inode->i_op->follow_link) {
                        err = do_follow_link(inode, &next, nd);
                        if (err)
                                goto return_err;
                        nd->inode = nd->path.dentry->d_inode;
-                       err = -ENOENT;
-                       if (!nd->inode)
-                               break;
                } else {
                        path_to_nameidata(&next, nd);
                        nd->inode = inode;
                }
+               err = -ENOENT;
+               if (!nd->inode)
+                       break;
                err = -ENOTDIR; 
                if (!nd->inode->i_op->lookup)
                        break;
@@ -1438,20 +1441,11 @@ last_component:
                nd->flags &= lookup_flags | ~LOOKUP_CONTINUE;
                if (lookup_flags & LOOKUP_PARENT)
                        goto lookup_parent;
-               if (this.name[0] == '.') switch (this.len) {
-                       default:
-                               break;
-                       case 2:
-                               if (this.name[1] != '.')
-                                       break;
-                               if (nd->flags & LOOKUP_RCU) {
-                                       if (follow_dotdot_rcu(nd))
-                                               return -ECHILD;
-                               } else
-                                       follow_dotdot(nd);
-                               /* fallthrough */
-                       case 1:
-                               goto return_reval;
+               if (unlikely(type != LAST_NORM)) {
+                       err = handle_dots(nd, type);
+                       if (err)
+                               goto return_err;
+                       return 0;
                }
                err = do_lookup(nd, &this, &next, &inode);
                if (err)
@@ -1474,140 +1468,62 @@ last_component:
                        if (!nd->inode->i_op->lookup)
                                break;
                }
-               goto return_base;
+               return 0;
 lookup_parent:
                nd->last = this;
-               nd->last_type = LAST_NORM;
-               if (this.name[0] != '.')
-                       goto return_base;
-               if (this.len == 1)
-                       nd->last_type = LAST_DOT;
-               else if (this.len == 2 && this.name[1] == '.')
-                       nd->last_type = LAST_DOTDOT;
-               else
-                       goto return_base;
-return_reval:
-               /*
-                * We bypassed the ordinary revalidation routines.
-                * We may need to check the cached dentry for staleness.
-                */
-               if (need_reval_dot(nd->path.dentry)) {
-                       if (nameidata_drop_rcu_last_maybe(nd))
-                               return -ECHILD;
-                       /* Note: we do not d_invalidate() */
-                       err = d_revalidate(nd->path.dentry, nd);
-                       if (!err)
-                               err = -ESTALE;
-                       if (err < 0)
-                               break;
-                       return 0;
-               }
-return_base:
-               if (nameidata_drop_rcu_last_maybe(nd))
-                       return -ECHILD;
+               nd->last_type = type;
                return 0;
-out_dput:
-               if (!(nd->flags & LOOKUP_RCU))
-                       path_put_conditional(&next, nd);
-               break;
        }
        if (!(nd->flags & LOOKUP_RCU))
                path_put(&nd->path);
 return_err:
-       return err;
-}
-
-static inline int path_walk_rcu(const char *name, struct nameidata *nd)
-{
-       current->total_link_count = 0;
-
-       return link_path_walk(name, nd);
-}
-
-static inline int path_walk_simple(const char *name, struct nameidata *nd)
-{
-       current->total_link_count = 0;
-
-       return link_path_walk(name, nd);
-}
-
-static int path_walk(const char *name, struct nameidata *nd)
-{
-       struct path save = nd->path;
-       int result;
-
-       current->total_link_count = 0;
-
-       /* make sure the stuff we saved doesn't go away */
-       path_get(&save);
-
-       result = link_path_walk(name, nd);
-       if (result == -ESTALE) {
-               /* nd->path had been dropped */
-               current->total_link_count = 0;
-               nd->path = save;
-               path_get(&nd->path);
-               nd->flags |= LOOKUP_REVAL;
-               result = link_path_walk(name, nd);
-       }
-
-       path_put(&save);
-
-       return result;
-}
-
-static void path_finish_rcu(struct nameidata *nd)
-{
        if (nd->flags & LOOKUP_RCU) {
-               /* RCU dangling. Cancel it. */
                nd->flags &= ~LOOKUP_RCU;
                nd->root.mnt = NULL;
                rcu_read_unlock();
                br_read_unlock(vfsmount_lock);
        }
-       if (nd->file)
-               fput(nd->file);
+       return err;
 }
 
-static int path_init_rcu(int dfd, const char *name, unsigned int flags, struct nameidata *nd)
+static int path_init(int dfd, const char *name, unsigned int flags, struct nameidata *nd)
 {
        int retval = 0;
        int fput_needed;
        struct file *file;
 
        nd->last_type = LAST_ROOT; /* if there are only slashes... */
-       nd->flags = flags | LOOKUP_RCU;
+       nd->flags = flags | LOOKUP_JUMPED;
        nd->depth = 0;
        nd->root.mnt = NULL;
        nd->file = NULL;
 
        if (*name=='/') {
-               struct fs_struct *fs = current->fs;
-               unsigned seq;
-
-               br_read_lock(vfsmount_lock);
-               rcu_read_lock();
-
-               do {
-                       seq = read_seqcount_begin(&fs->seq);
-                       nd->root = fs->root;
-                       nd->path = nd->root;
-                       nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
-               } while (read_seqcount_retry(&fs->seq, seq));
-
+               if (flags & LOOKUP_RCU) {
+                       br_read_lock(vfsmount_lock);
+                       rcu_read_lock();
+                       set_root_rcu(nd);
+               } else {
+                       set_root(nd);
+                       path_get(&nd->root);
+               }
+               nd->path = nd->root;
        } else if (dfd == AT_FDCWD) {
-               struct fs_struct *fs = current->fs;
-               unsigned seq;
-
-               br_read_lock(vfsmount_lock);
-               rcu_read_lock();
+               if (flags & LOOKUP_RCU) {
+                       struct fs_struct *fs = current->fs;
+                       unsigned seq;
 
-               do {
-                       seq = read_seqcount_begin(&fs->seq);
-                       nd->path = fs->pwd;
-                       nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
-               } while (read_seqcount_retry(&fs->seq, seq));
+                       br_read_lock(vfsmount_lock);
+                       rcu_read_lock();
 
+                       do {
+                               seq = read_seqcount_begin(&fs->seq);
+                               nd->path = fs->pwd;
+                               nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
+                       } while (read_seqcount_retry(&fs->seq, seq));
+               } else {
+                       get_fs_pwd(current->fs, &nd->path);
+               }
        } else {
                struct dentry *dentry;
 
@@ -1627,62 +1543,18 @@ static int path_init_rcu(int dfd, const char *name, unsigned int flags, struct n
                        goto fput_fail;
 
                nd->path = file->f_path;
-               if (fput_needed)
-                       nd->file = file;
-
-               nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
-               br_read_lock(vfsmount_lock);
-               rcu_read_lock();
+               if (flags & LOOKUP_RCU) {
+                       if (fput_needed)
+                               nd->file = file;
+                       nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
+                       br_read_lock(vfsmount_lock);
+                       rcu_read_lock();
+               } else {
+                       path_get(&file->f_path);
+                       fput_light(file, fput_needed);
+               }
        }
-       nd->inode = nd->path.dentry->d_inode;
-       return 0;
-
-fput_fail:
-       fput_light(file, fput_needed);
-out_fail:
-       return retval;
-}
-
-static int path_init(int dfd, const char *name, unsigned int flags, struct nameidata *nd)
-{
-       int retval = 0;
-       int fput_needed;
-       struct file *file;
-
-       nd->last_type = LAST_ROOT; /* if there are only slashes... */
-       nd->flags = flags;
-       nd->depth = 0;
-       nd->root.mnt = NULL;
-
-       if (*name=='/') {
-               set_root(nd);
-               nd->path = nd->root;
-               path_get(&nd->root);
-       } else if (dfd == AT_FDCWD) {
-               get_fs_pwd(current->fs, &nd->path);
-       } else {
-               struct dentry *dentry;
-
-               file = fget_light(dfd, &fput_needed);
-               retval = -EBADF;
-               if (!file)
-                       goto out_fail;
-
-               dentry = file->f_path.dentry;
-
-               retval = -ENOTDIR;
-               if (!S_ISDIR(dentry->d_inode->i_mode))
-                       goto fput_fail;
 
-               retval = file_permission(file, MAY_EXEC);
-               if (retval)
-                       goto fput_fail;
-
-               nd->path = file->f_path;
-               path_get(&file->f_path);
-
-               fput_light(file, fput_needed);
-       }
        nd->inode = nd->path.dentry->d_inode;
        return 0;
 
@@ -1693,7 +1565,7 @@ out_fail:
 }
 
 /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
-static int do_path_lookup(int dfd, const char *name,
+static int path_lookupat(int dfd, const char *name,
                                unsigned int flags, struct nameidata *nd)
 {
        int retval;
@@ -1712,29 +1584,44 @@ static int do_path_lookup(int dfd, const char *name,
         * be handled by restarting a traditional ref-walk (which will always
         * be able to complete).
         */
-       retval = path_init_rcu(dfd, name, flags, nd);
+       retval = path_init(dfd, name, flags, nd);
+
        if (unlikely(retval))
                return retval;
-       retval = path_walk_rcu(name, nd);
-       path_finish_rcu(nd);
+
+       current->total_link_count = 0;
+       retval = link_path_walk(name, nd);
+
+       if (nd->flags & LOOKUP_RCU) {
+               /* went all way through without dropping RCU */
+               BUG_ON(retval);
+               if (nameidata_drop_rcu_last(nd))
+                       retval = -ECHILD;
+       }
+
+       if (!retval)
+               retval = handle_reval_path(nd);
+
+       if (nd->file) {
+               fput(nd->file);
+               nd->file = NULL;
+       }
+
        if (nd->root.mnt) {
                path_put(&nd->root);
                nd->root.mnt = NULL;
        }
+       return retval;
+}
 
-       if (unlikely(retval == -ECHILD || retval == -ESTALE)) {
-               /* slower, locked walk */
-               if (retval == -ESTALE)
-                       flags |= LOOKUP_REVAL;
-               retval = path_init(dfd, name, flags, nd);
-               if (unlikely(retval))
-                       return retval;
-               retval = path_walk(name, nd);
-               if (nd->root.mnt) {
-                       path_put(&nd->root);
-                       nd->root.mnt = NULL;
-               }
-       }
+static int do_path_lookup(int dfd, const char *name,
+                               unsigned int flags, struct nameidata *nd)
+{
+       int retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, nd);
+       if (unlikely(retval == -ECHILD))
+               retval = path_lookupat(dfd, name, flags, nd);
+       if (unlikely(retval == -ESTALE))
+               retval = path_lookupat(dfd, name, flags | LOOKUP_REVAL, nd);
 
        if (likely(!retval)) {
                if (unlikely(!audit_dummy_context())) {
@@ -1742,14 +1629,12 @@ static int do_path_lookup(int dfd, const char *name,
                                audit_inode(name, nd->path.dentry);
                }
        }
-
        return retval;
 }
 
-int path_lookup(const char *name, unsigned int flags,
-                       struct nameidata *nd)
+int kern_path_parent(const char *name, struct nameidata *nd)
 {
-       return do_path_lookup(AT_FDCWD, name, flags, nd);
+       return do_path_lookup(AT_FDCWD, name, LOOKUP_PARENT, nd);
 }
 
 int kern_path(const char *name, unsigned int flags, struct path *path)
@@ -1773,11 +1658,11 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
                    const char *name, unsigned int flags,
                    struct nameidata *nd)
 {
-       int retval;
+       int result;
 
        /* same as do_path_lookup */
        nd->last_type = LAST_ROOT;
-       nd->flags = flags;
+       nd->flags = flags | LOOKUP_JUMPED;
        nd->depth = 0;
 
        nd->path.dentry = dentry;
@@ -1787,15 +1672,32 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
        path_get(&nd->root);
        nd->inode = nd->path.dentry->d_inode;
 
-       retval = path_walk(name, nd);
-       if (unlikely(!retval && !audit_dummy_context() && nd->path.dentry &&
+       current->total_link_count = 0;
+
+       result = link_path_walk(name, nd);
+       if (!result)
+               result = handle_reval_path(nd);
+       if (result == -ESTALE) {
+               /* nd->path had been dropped */
+               current->total_link_count = 0;
+               nd->path.dentry = dentry;
+               nd->path.mnt = mnt;
+               nd->inode = dentry->d_inode;
+               path_get(&nd->path);
+               nd->flags = flags | LOOKUP_JUMPED | LOOKUP_REVAL;
+
+               result = link_path_walk(name, nd);
+               if (!result)
+                       result = handle_reval_path(nd);
+       }
+       if (unlikely(!result && !audit_dummy_context() && nd->path.dentry &&
                                nd->inode))
                audit_inode(name, nd->path.dentry);
 
        path_put(&nd->root);
        nd->root.mnt = NULL;
 
-       return retval;
+       return result;
 }
 
 static struct dentry *__lookup_hash(struct qstr *name,
@@ -2268,35 +2170,36 @@ exit:
  * Handle O_CREAT case for do_filp_open
  */
 static struct file *do_last(struct nameidata *nd, struct path *path,
-                           int open_flag, int acc_mode,
-                           int mode, const char *pathname)
+                           const struct open_flags *op, const char *pathname)
 {
        struct dentry *dir = nd->path.dentry;
        struct file *filp;
-       int error = -EISDIR;
+       int error;
+
+       nd->flags &= ~LOOKUP_PARENT;
+       nd->flags |= op->intent;
 
        switch (nd->last_type) {
        case LAST_DOTDOT:
                follow_dotdot(nd);
                dir = nd->path.dentry;
        case LAST_DOT:
-               if (need_reval_dot(dir)) {
-                       int status = d_revalidate(nd->path.dentry, nd);
-                       if (!status)
-                               status = -ESTALE;
-                       if (status < 0) {
-                               error = status;
-                               goto exit;
-                       }
-               }
                /* fallthrough */
        case LAST_ROOT:
+               error = handle_reval_path(nd);
+               if (error)
+                       goto exit;
+               error = -EISDIR;
                goto exit;
        case LAST_BIND:
+               error = handle_reval_path(nd);
+               if (error)
+                       goto exit;
                audit_inode(pathname, dir);
                goto ok;
        }
 
+       error = -EISDIR;
        /* trailing slashes? */
        if (nd->last.name[nd->last.len])
                goto exit;
@@ -2329,7 +2232,7 @@ static struct file *do_last(struct nameidata *nd, struct path *path,
                error = mnt_want_write(nd->path.mnt);
                if (error)
                        goto exit_mutex_unlock;
-               error = __open_namei_create(nd, path, open_flag, mode);
+               error = __open_namei_create(nd, path, op->open_flag, op->mode);
                if (error) {
                        mnt_drop_write(nd->path.mnt);
                        goto exit;
@@ -2338,7 +2241,7 @@ static struct file *do_last(struct nameidata *nd, struct path *path,
                mnt_drop_write(nd->path.mnt);
                path_put(&nd->path);
                if (!IS_ERR(filp)) {
-                       error = ima_file_check(filp, acc_mode);
+                       error = ima_file_check(filp, op->acc_mode);
                        if (error) {
                                fput(filp);
                                filp = ERR_PTR(error);
@@ -2354,7 +2257,7 @@ static struct file *do_last(struct nameidata *nd, struct path *path,
        audit_inode(pathname, path->dentry);
 
        error = -EEXIST;
-       if (open_flag & O_EXCL)
+       if (op->open_flag & O_EXCL)
                goto exit_dput;
 
        error = follow_managed(path, nd->flags);
@@ -2374,7 +2277,7 @@ static struct file *do_last(struct nameidata *nd, struct path *path,
        if (S_ISDIR(nd->inode->i_mode))
                goto exit;
 ok:
-       filp = finish_open(nd, open_flag, acc_mode);
+       filp = finish_open(nd, op->open_flag, op->acc_mode);
        return filp;
 
 exit_mutex_unlock:
@@ -2386,74 +2289,29 @@ exit:
        return ERR_PTR(error);
 }
 
-/*
- * Note that the low bits of the passed in "open_flag"
- * are not the same as in the local variable "flag". See
- * open_to_namei_flags() for more details.
- */
-struct file *do_filp_open(int dfd, const char *pathname,
-               int open_flag, int mode, int acc_mode)
+static struct file *path_openat(int dfd, const char *pathname,
+               const struct open_flags *op, int flags)
 {
        struct file *filp;
        struct nameidata nd;
-       int error;
        struct path path;
        int count = 0;
-       int flag = open_to_namei_flags(open_flag);
-       int flags;
-
-       if (!(open_flag & O_CREAT))
-               mode = 0;
-
-       /* Must never be set by userspace */
-       open_flag &= ~FMODE_NONOTIFY;
-
-       /*
-        * O_SYNC is implemented as __O_SYNC|O_DSYNC.  As many places only
-        * check for O_DSYNC if the need any syncing at all we enforce it's
-        * always set instead of having to deal with possibly weird behaviour
-        * for malicious applications setting only __O_SYNC.
-        */
-       if (open_flag & __O_SYNC)
-               open_flag |= O_DSYNC;
-
-       if (!acc_mode)
-               acc_mode = MAY_OPEN | ACC_MODE(open_flag);
-
-       /* O_TRUNC implies we need access checks for write permissions */
-       if (open_flag & O_TRUNC)
-               acc_mode |= MAY_WRITE;
-
-       /* Allow the LSM permission hook to distinguish append 
-          access from general write access. */
-       if (open_flag & O_APPEND)
-               acc_mode |= MAY_APPEND;
-
-       flags = LOOKUP_OPEN;
-       if (open_flag & O_CREAT) {
-               flags |= LOOKUP_CREATE;
-               if (open_flag & O_EXCL)
-                       flags |= LOOKUP_EXCL;
-       }
-       if (open_flag & O_DIRECTORY)
-               flags |= LOOKUP_DIRECTORY;
-       if (!(open_flag & O_NOFOLLOW))
-               flags |= LOOKUP_FOLLOW;
+       int error;
 
        filp = get_empty_filp();
        if (!filp)
                return ERR_PTR(-ENFILE);
 
-       filp->f_flags = open_flag;
+       filp->f_flags = op->open_flag;
        nd.intent.open.file = filp;
-       nd.intent.open.flags = flag;
-       nd.intent.open.create_mode = mode;
+       nd.intent.open.flags = open_to_namei_flags(op->open_flag);
+       nd.intent.open.create_mode = op->mode;
 
-       if (open_flag & O_CREAT)
+       if (op->open_flag & O_CREAT)
                goto creat;
 
        /* !O_CREAT, simple open */
-       error = do_path_lookup(dfd, pathname, flags, &nd);
+       error = path_lookupat(dfd, pathname, flags | op->intent, &nd);
        if (unlikely(error))
                goto out_filp;
        error = -ELOOP;
@@ -2467,31 +2325,13 @@ struct file *do_filp_open(int dfd, const char *pathname,
                        goto out_path;
        }
        audit_inode(pathname, nd.path.dentry);
-       filp = finish_open(&nd, open_flag, acc_mode);
+       filp = finish_open(&nd, op->open_flag, op->acc_mode);
        release_open_intent(&nd);
        return filp;
 
 creat:
        /* OK, have to create the file. Find the parent. */
-       error = path_init_rcu(dfd, pathname,
-                       LOOKUP_PARENT | (flags & LOOKUP_REVAL), &nd);
-       if (error)
-               goto out_filp;
-       error = path_walk_rcu(pathname, &nd);
-       path_finish_rcu(&nd);
-       if (unlikely(error == -ECHILD || error == -ESTALE)) {
-               /* slower, locked walk */
-               if (error == -ESTALE) {
-reval:
-                       flags |= LOOKUP_REVAL;
-               }
-               error = path_init(dfd, pathname,
-                               LOOKUP_PARENT | (flags & LOOKUP_REVAL), &nd);
-               if (error)
-                       goto out_filp;
-
-               error = path_walk_simple(pathname, &nd);
-       }
+       error = path_lookupat(dfd, pathname, LOOKUP_PARENT | flags, &nd);
        if (unlikely(error))
                goto out_filp;
        if (unlikely(!audit_dummy_context()))
@@ -2500,8 +2340,7 @@ reval:
        /*
         * We have the parent and last component.
         */
-       nd.flags = flags;
-       filp = do_last(&nd, &path, open_flag, acc_mode, mode, pathname);
+       filp = do_last(&nd, &path, op, pathname);
        while (unlikely(!filp)) { /* trailing symlink */
                struct path link = path;
                struct inode *linki = link.dentry->d_inode;
@@ -2523,28 +2362,19 @@ reval:
                 * just set LAST_BIND.
                 */
                nd.flags |= LOOKUP_PARENT;
-               error = security_inode_follow_link(link.dentry, &nd);
-               if (error)
-                       goto exit_dput;
+               nd.flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
                error = __do_follow_link(&link, &nd, &cookie);
-               if (unlikely(error)) {
-                       if (!IS_ERR(cookie) && linki->i_op->put_link)
-                               linki->i_op->put_link(link.dentry, &nd, cookie);
-                       /* nd.path had been dropped */
-                       nd.path = link;
-                       goto out_path;
-               }
-               nd.flags &= ~LOOKUP_PARENT;
-               filp = do_last(&nd, &path, open_flag, acc_mode, mode, pathname);
-               if (linki->i_op->put_link)
+               if (unlikely(error))
+                       filp = ERR_PTR(error);
+               else
+                       filp = do_last(&nd, &path, op, pathname);
+               if (!IS_ERR(cookie) && linki->i_op->put_link)
                        linki->i_op->put_link(link.dentry, &nd, cookie);
                path_put(&link);
        }
 out:
        if (nd.root.mnt)
                path_put(&nd.root);
-       if (filp == ERR_PTR(-ESTALE) && !(flags & LOOKUP_REVAL))
-               goto reval;
        release_open_intent(&nd);
        return filp;
 
@@ -2557,22 +2387,18 @@ out_filp:
        goto out;
 }
 
-/**
- * filp_open - open file and return file pointer
- *
- * @filename:  path to open
- * @flags:     open flags as per the open(2) second argument
- * @mode:      mode for the new file if O_CREAT is set, else ignored
- *
- * This is the helper to open a file from kernelspace if you really
- * have to.  But in generally you should not do this, so please move
- * along, nothing to see here..
- */
-struct file *filp_open(const char *filename, int flags, int mode)
+struct file *do_filp_open(int dfd, const char *pathname,
+               const struct open_flags *op, int flags)
 {
-       return do_filp_open(AT_FDCWD, filename, flags, mode, 0);
+       struct file *filp;
+
+       filp = path_openat(dfd, pathname, op, flags | LOOKUP_RCU);
+       if (unlikely(filp == ERR_PTR(-ECHILD)))
+               filp = path_openat(dfd, pathname, op, flags);
+       if (unlikely(filp == ERR_PTR(-ESTALE)))
+               filp = path_openat(dfd, pathname, op, flags | LOOKUP_REVAL);
+       return filp;
 }
-EXPORT_SYMBOL(filp_open);
 
 /**
  * lookup_create - lookup a dentry, creating it if it doesn't exist
@@ -3578,7 +3404,7 @@ EXPORT_SYMBOL(page_readlink);
 EXPORT_SYMBOL(__page_symlink);
 EXPORT_SYMBOL(page_symlink);
 EXPORT_SYMBOL(page_symlink_inode_operations);
-EXPORT_SYMBOL(path_lookup);
+EXPORT_SYMBOL(kern_path_parent);
 EXPORT_SYMBOL(kern_path);
 EXPORT_SYMBOL(vfs_path_lookup);
 EXPORT_SYMBOL(inode_permission);