]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - fs/namei.c
namei: uninline set_root{,_rcu}()
[karo-tx-linux.git] / fs / namei.c
index 0fa7af23cff6dbd8d6687a43d06bc13ae0766ad0..177934c79a0456b0790e934e0db044c3ac5f4a6c 100644 (file)
@@ -499,7 +499,7 @@ struct nameidata {
        struct path     root;
        struct inode    *inode; /* path.dentry.d_inode */
        unsigned int    flags;
-       unsigned        seq, m_seq;
+       unsigned        seq, m_seq, root_seq;
        int             last_type;
        unsigned        depth;
        int             total_link_count;
@@ -508,6 +508,7 @@ struct nameidata {
                void *cookie;
                const char *name;
                struct inode *inode;
+               unsigned seq;
        } *stack, internal[EMBEDDED_LEVELS];
 };
 
@@ -535,10 +536,19 @@ static void restore_nameidata(struct nameidata *old)
 
 static int __nd_alloc_stack(struct nameidata *nd)
 {
-       struct saved *p = kmalloc(MAXSYMLINKS * sizeof(struct saved),
+       struct saved *p;
+
+       if (nd->flags & LOOKUP_RCU) {
+               p= kmalloc(MAXSYMLINKS * sizeof(struct saved),
+                                 GFP_ATOMIC);
+               if (unlikely(!p))
+                       return -ECHILD;
+       } else {
+               p= kmalloc(MAXSYMLINKS * sizeof(struct saved),
                                  GFP_KERNEL);
-       if (unlikely(!p))
-               return -ENOMEM;
+               if (unlikely(!p))
+                       return -ENOMEM;
+       }
        memcpy(p, nd->internal, sizeof(nd->internal));
        nd->stack = p;
        return 0;
@@ -553,6 +563,68 @@ static inline int nd_alloc_stack(struct nameidata *nd)
        return __nd_alloc_stack(nd);
 }
 
+static void drop_links(struct nameidata *nd)
+{
+       int i = nd->depth;
+       while (i--) {
+               struct saved *last = nd->stack + i;
+               struct inode *inode = last->inode;
+               if (last->cookie && inode->i_op->put_link) {
+                       inode->i_op->put_link(inode, last->cookie);
+                       last->cookie = NULL;
+               }
+       }
+}
+
+static void terminate_walk(struct nameidata *nd)
+{
+       drop_links(nd);
+       if (!(nd->flags & LOOKUP_RCU)) {
+               int i;
+               path_put(&nd->path);
+               for (i = 0; i < nd->depth; i++)
+                       path_put(&nd->stack[i].link);
+       } else {
+               nd->flags &= ~LOOKUP_RCU;
+               if (!(nd->flags & LOOKUP_ROOT))
+                       nd->root.mnt = NULL;
+               rcu_read_unlock();
+       }
+       nd->depth = 0;
+}
+
+/* path_put is needed afterwards regardless of success or failure */
+static bool legitimize_path(struct nameidata *nd,
+                           struct path *path, unsigned seq)
+{
+       int res = __legitimize_mnt(path->mnt, nd->m_seq);
+       if (unlikely(res)) {
+               if (res > 0)
+                       path->mnt = NULL;
+               path->dentry = NULL;
+               return false;
+       }
+       if (unlikely(!lockref_get_not_dead(&path->dentry->d_lockref))) {
+               path->dentry = NULL;
+               return false;
+       }
+       return !read_seqcount_retry(&path->dentry->d_seq, seq);
+}
+
+static bool legitimize_links(struct nameidata *nd)
+{
+       int i;
+       for (i = 0; i < nd->depth; i++) {
+               struct saved *last = nd->stack + i;
+               if (unlikely(!legitimize_path(nd, &last->link, last->seq))) {
+                       drop_links(nd);
+                       nd->depth = i + 1;
+                       return false;
+               }
+       }
+       return true;
+}
+
 /*
  * Path walking has 2 modes, rcu-walk and ref-walk (see
  * Documentation/filesystems/path-lookup.txt).  In situations when we can't
@@ -574,30 +646,22 @@ static inline int nd_alloc_stack(struct nameidata *nd)
  * unlazy_walk attempts to legitimize the current nd->path, nd->root and dentry
  * for ref-walk mode.  @dentry must be a path found by a do_lookup call on
  * @nd or NULL.  Must be called from rcu-walk context.
+ * Nothing should touch nameidata between unlazy_walk() failure and
+ * terminate_walk().
  */
 static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq)
 {
-       struct fs_struct *fs = current->fs;
        struct dentry *parent = nd->path.dentry;
 
        BUG_ON(!(nd->flags & LOOKUP_RCU));
 
-       /*
-        * After legitimizing the bastards, terminate_walk()
-        * will do the right thing for non-RCU mode, and all our
-        * subsequent exit cases should rcu_read_unlock()
-        * before returning.  Do vfsmount first; if dentry
-        * can't be legitimized, just set nd->path.dentry to NULL
-        * and rely on dput(NULL) being a no-op.
-        */
-       if (!legitimize_mnt(nd->path.mnt, nd->m_seq))
-               return -ECHILD;
        nd->flags &= ~LOOKUP_RCU;
-
-       if (!lockref_get_not_dead(&parent->d_lockref)) {
-               nd->path.dentry = NULL; 
-               goto out;
-       }
+       if (unlikely(!legitimize_links(nd)))
+               goto out2;
+       if (unlikely(!legitimize_mnt(nd->path.mnt, nd->m_seq)))
+               goto out2;
+       if (unlikely(!lockref_get_not_dead(&parent->d_lockref)))
+               goto out1;
 
        /*
         * For a negative lookup, the lookup sequence point is the parents
@@ -626,22 +690,24 @@ static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq
         * still valid and get it if required.
         */
        if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) {
-               spin_lock(&fs->lock);
-               if (nd->root.mnt != fs->root.mnt || nd->root.dentry != fs->root.dentry)
-                       goto unlock_and_drop_dentry;
-               path_get(&nd->root);
-               spin_unlock(&fs->lock);
+               if (unlikely(!legitimize_path(nd, &nd->root, nd->root_seq))) {
+                       rcu_read_unlock();
+                       dput(dentry);
+                       return -ECHILD;
+               }
        }
 
        rcu_read_unlock();
        return 0;
 
-unlock_and_drop_dentry:
-       spin_unlock(&fs->lock);
 drop_dentry:
        rcu_read_unlock();
        dput(dentry);
        goto drop_root_mnt;
+out2:
+       nd->path.mnt = NULL;
+out1:
+       nd->path.dentry = NULL;
 out:
        rcu_read_unlock();
 drop_root_mnt:
@@ -650,6 +716,24 @@ drop_root_mnt:
        return -ECHILD;
 }
 
+static int unlazy_link(struct nameidata *nd, struct path *link, unsigned seq)
+{
+       if (unlikely(!legitimize_path(nd, link, seq))) {
+               drop_links(nd);
+               nd->depth = 0;
+               nd->flags &= ~LOOKUP_RCU;
+               nd->path.mnt = NULL;
+               nd->path.dentry = NULL;
+               if (!(nd->flags & LOOKUP_ROOT))
+                       nd->root.mnt = NULL;
+               rcu_read_unlock();
+       } else if (likely(unlazy_walk(nd, NULL, 0)) == 0) {
+               return 0;
+       }
+       path_put(link);
+       return -ECHILD;
+}
+
 static inline int d_revalidate(struct dentry *dentry, unsigned int flags)
 {
        return dentry->d_op->d_revalidate(dentry, flags);
@@ -693,22 +777,22 @@ static int complete_walk(struct nameidata *nd)
        return status;
 }
 
-static __always_inline void set_root(struct nameidata *nd)
+static void set_root(struct nameidata *nd)
 {
        get_fs_root(current->fs, &nd->root);
 }
 
-static __always_inline unsigned set_root_rcu(struct nameidata *nd)
+static unsigned set_root_rcu(struct nameidata *nd)
 {
        struct fs_struct *fs = current->fs;
-       unsigned seq, res;
+       unsigned seq;
 
        do {
                seq = read_seqcount_begin(&fs->seq);
                nd->root = fs->root;
-               res = __read_seqcount_begin(&nd->root.dentry->d_seq);
+               nd->root_seq = __read_seqcount_begin(&nd->root.dentry->d_seq);
        } while (read_seqcount_retry(&fs->seq, seq));
-       return res;
+       return nd->root_seq;
 }
 
 static void path_put_conditional(struct path *path, struct nameidata *nd)
@@ -749,8 +833,9 @@ static inline void put_link(struct nameidata *nd)
        struct saved *last = nd->stack + --nd->depth;
        struct inode *inode = last->inode;
        if (last->cookie && inode->i_op->put_link)
-               inode->i_op->put_link(last->link.dentry, last->cookie);
-       path_put(&last->link);
+               inode->i_op->put_link(inode, last->cookie);
+       if (!(nd->flags & LOOKUP_RCU))
+               path_put(&last->link);
 }
 
 int sysctl_protected_symlinks __read_mostly = 0;
@@ -793,6 +878,9 @@ static inline int may_follow_link(struct nameidata *nd)
        if (uid_eq(parent->i_uid, inode->i_uid))
                return 0;
 
+       if (nd->flags & LOOKUP_RCU)
+               return -ECHILD;
+
        audit_log_link_denied("follow_link", &nd->stack[0].link);
        return -EACCES;
 }
@@ -875,11 +963,14 @@ const char *get_link(struct nameidata *nd)
        int error;
        const char *res;
 
-       BUG_ON(nd->flags & LOOKUP_RCU);
-
-       cond_resched();
-
-       touch_atime(&last->link);
+       if (!(nd->flags & LOOKUP_RCU)) {
+               touch_atime(&last->link);
+               cond_resched();
+       } else if (atime_needs_update(&last->link, inode)) {
+               if (unlikely(unlazy_walk(nd, NULL, 0)))
+                       return ERR_PTR(-ECHILD);
+               touch_atime(&last->link);
+       }
 
        error = security_inode_follow_link(dentry, inode,
                                           nd->flags & LOOKUP_RCU);
@@ -889,6 +980,10 @@ const char *get_link(struct nameidata *nd)
        nd->last_type = LAST_BIND;
        res = inode->i_link;
        if (!res) {
+               if (nd->flags & LOOKUP_RCU) {
+                       if (unlikely(unlazy_walk(nd, NULL, 0)))
+                               return ERR_PTR(-ECHILD);
+               }
                res = inode->i_op->follow_link(dentry, &last->cookie);
                if (IS_ERR_OR_NULL(res)) {
                        last->cookie = NULL;
@@ -896,12 +991,24 @@ const char *get_link(struct nameidata *nd)
                }
        }
        if (*res == '/') {
-               if (!nd->root.mnt)
-                       set_root(nd);
-               path_put(&nd->path);
-               nd->path = nd->root;
-               path_get(&nd->root);
-               nd->inode = nd->path.dentry->d_inode;
+               if (nd->flags & LOOKUP_RCU) {
+                       struct dentry *d;
+                       if (!nd->root.mnt)
+                               set_root_rcu(nd);
+                       nd->path = nd->root;
+                       d = nd->path.dentry;
+                       nd->inode = d->d_inode;
+                       nd->seq = nd->root_seq;
+                       if (unlikely(read_seqcount_retry(&d->d_seq, nd->seq)))
+                               return ERR_PTR(-ECHILD);
+               } else {
+                       if (!nd->root.mnt)
+                               set_root(nd);
+                       path_put(&nd->path);
+                       nd->path = nd->root;
+                       path_get(&nd->root);
+                       nd->inode = nd->path.dentry->d_inode;
+               }
                nd->flags |= LOOKUP_JUMPED;
                while (unlikely(*++res == '/'))
                        ;
@@ -911,21 +1018,6 @@ const char *get_link(struct nameidata *nd)
        return res;
 }
 
-static int follow_up_rcu(struct path *path)
-{
-       struct mount *mnt = real_mount(path->mnt);
-       struct mount *parent;
-       struct dentry *mountpoint;
-
-       parent = mnt->mnt_parent;
-       if (&parent->mnt == path->mnt)
-               return 0;
-       mountpoint = mnt->mnt_mountpoint;
-       path->dentry = mountpoint;
-       path->mnt = &parent->mnt;
-       return 1;
-}
-
 /*
  * follow_up - Find the mountpoint of path's vfsmount
  *
@@ -1182,10 +1274,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
                set_root_rcu(nd);
 
        while (1) {
-               if (nd->path.dentry == nd->root.dentry &&
-                   nd->path.mnt == nd->root.mnt) {
+               if (path_equal(&nd->path, &nd->root))
                        break;
-               }
                if (nd->path.dentry != nd->path.mnt->mnt_root) {
                        struct dentry *old = nd->path.dentry;
                        struct dentry *parent = old->d_parent;
@@ -1193,34 +1283,42 @@ static int follow_dotdot_rcu(struct nameidata *nd)
 
                        inode = parent->d_inode;
                        seq = read_seqcount_begin(&parent->d_seq);
-                       if (read_seqcount_retry(&old->d_seq, nd->seq))
-                               goto failed;
+                       if (unlikely(read_seqcount_retry(&old->d_seq, nd->seq)))
+                               return -ECHILD;
                        nd->path.dentry = parent;
                        nd->seq = seq;
                        break;
+               } else {
+                       struct mount *mnt = real_mount(nd->path.mnt);
+                       struct mount *mparent = mnt->mnt_parent;
+                       struct dentry *mountpoint = mnt->mnt_mountpoint;
+                       struct inode *inode2 = mountpoint->d_inode;
+                       unsigned seq = read_seqcount_begin(&mountpoint->d_seq);
+                       if (unlikely(read_seqretry(&mount_lock, nd->m_seq)))
+                               return -ECHILD;
+                       if (&mparent->mnt == nd->path.mnt)
+                               break;
+                       /* we know that mountpoint was pinned */
+                       nd->path.dentry = mountpoint;
+                       nd->path.mnt = &mparent->mnt;
+                       inode = inode2;
+                       nd->seq = seq;
                }
-               if (!follow_up_rcu(&nd->path))
-                       break;
-               inode = nd->path.dentry->d_inode;
-               nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
        }
-       while (d_mountpoint(nd->path.dentry)) {
+       while (unlikely(d_mountpoint(nd->path.dentry))) {
                struct mount *mounted;
                mounted = __lookup_mnt(nd->path.mnt, nd->path.dentry);
+               if (unlikely(read_seqretry(&mount_lock, nd->m_seq)))
+                       return -ECHILD;
                if (!mounted)
                        break;
                nd->path.mnt = &mounted->mnt;
                nd->path.dentry = mounted->mnt.mnt_root;
                inode = nd->path.dentry->d_inode;
                nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
-               if (read_seqretry(&mount_lock, nd->m_seq))
-                       goto failed;
        }
        nd->inode = inode;
        return 0;
-
-failed:
-       return -ECHILD;
 }
 
 /*
@@ -1532,20 +1630,6 @@ static inline int handle_dots(struct nameidata *nd, int type)
        return 0;
 }
 
-static void terminate_walk(struct nameidata *nd)
-{
-       if (!(nd->flags & LOOKUP_RCU)) {
-               path_put(&nd->path);
-       } else {
-               nd->flags &= ~LOOKUP_RCU;
-               if (!(nd->flags & LOOKUP_ROOT))
-                       nd->root.mnt = NULL;
-               rcu_read_unlock();
-       }
-       while (unlikely(nd->depth))
-               put_link(nd);
-}
-
 static int pick_link(struct nameidata *nd, struct path *link,
                     struct inode *inode, unsigned seq)
 {
@@ -1555,24 +1639,28 @@ static int pick_link(struct nameidata *nd, struct path *link,
                path_to_nameidata(link, nd);
                return -ELOOP;
        }
-       if (nd->flags & LOOKUP_RCU) {
-               if (unlikely(nd->path.mnt != link->mnt ||
-                            unlazy_walk(nd, link->dentry, seq))) {
-                       return -ECHILD;
-               }
+       if (!(nd->flags & LOOKUP_RCU)) {
+               if (link->mnt == nd->path.mnt)
+                       mntget(link->mnt);
        }
-       if (link->mnt == nd->path.mnt)
-               mntget(link->mnt);
        error = nd_alloc_stack(nd);
        if (unlikely(error)) {
-               path_put(link);
-               return error;
+               if (error == -ECHILD) {
+                       if (unlikely(unlazy_link(nd, link, seq)))
+                               return -ECHILD;
+                       error = nd_alloc_stack(nd);
+               }
+               if (error) {
+                       path_put(link);
+                       return error;
+               }
        }
 
        last = nd->stack + nd->depth++;
        last->link = *link;
        last->cookie = NULL;
        last->inode = inode;
+       last->seq = seq;
        return 1;
 }
 
@@ -1887,6 +1975,7 @@ static const char *path_init(int dfd, const struct filename *name,
                if (flags & LOOKUP_RCU) {
                        rcu_read_lock();
                        nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
+                       nd->root_seq = nd->seq;
                        nd->m_seq = read_seqbegin(&mount_lock);
                } else {
                        path_get(&nd->path);
@@ -4444,17 +4533,18 @@ EXPORT_SYMBOL(readlink_copy);
 int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 {
        void *cookie;
-       const char *link = dentry->d_inode->i_link;
+       struct inode *inode = d_inode(dentry);
+       const char *link = inode->i_link;
        int res;
 
        if (!link) {
-               link = dentry->d_inode->i_op->follow_link(dentry, &cookie);
+               link = inode->i_op->follow_link(dentry, &cookie);
                if (IS_ERR(link))
                        return PTR_ERR(link);
        }
        res = readlink_copy(buffer, buflen, link);
-       if (dentry->d_inode->i_op->put_link)
-               dentry->d_inode->i_op->put_link(dentry, cookie);
+       if (inode->i_op->put_link)
+               inode->i_op->put_link(inode, cookie);
        return res;
 }
 EXPORT_SYMBOL(generic_readlink);
@@ -4496,7 +4586,7 @@ const char *page_follow_link_light(struct dentry *dentry, void **cookie)
 }
 EXPORT_SYMBOL(page_follow_link_light);
 
-void page_put_link(struct dentry *dentry, void *cookie)
+void page_put_link(struct inode *unused, void *cookie)
 {
        struct page *page = cookie;
        kunmap(page);