fs: dcache rationalise dget variants
[deliverable/linux.git] / fs / dcache.c
index a09f0771fd27a3271245e29d073ecf1d4aaaa3a0..b4d2e28eef5b1e0ee9a78e8d9b58566312300f5d 100644 (file)
  *   - d_alias, d_inode
  *
  * Ordering:
- * dcache_lock
- *   dcache_inode_lock
- *     dentry->d_lock
- *       dcache_lru_lock
- *       dcache_hash_lock
+ * dcache_inode_lock
+ *   dentry->d_lock
+ *     dcache_lru_lock
+ *     dcache_hash_lock
  *
  * If there is an ancestor relationship:
  * dentry->d_parent->...->d_parent->d_lock
@@ -77,11 +76,10 @@ EXPORT_SYMBOL_GPL(sysctl_vfs_cache_pressure);
 __cacheline_aligned_in_smp DEFINE_SPINLOCK(dcache_inode_lock);
 static __cacheline_aligned_in_smp DEFINE_SPINLOCK(dcache_hash_lock);
 static __cacheline_aligned_in_smp DEFINE_SPINLOCK(dcache_lru_lock);
-__cacheline_aligned_in_smp DEFINE_SPINLOCK(dcache_lock);
 __cacheline_aligned_in_smp DEFINE_SEQLOCK(rename_lock);
 
+EXPORT_SYMBOL(rename_lock);
 EXPORT_SYMBOL(dcache_inode_lock);
-EXPORT_SYMBOL(dcache_lock);
 
 static struct kmem_cache *dentry_cache __read_mostly;
 
@@ -138,7 +136,7 @@ static void __d_free(struct rcu_head *head)
 }
 
 /*
- * no dcache_lock, please.
+ * no locks, please.
  */
 static void d_free(struct dentry *dentry)
 {
@@ -161,7 +159,6 @@ static void d_free(struct dentry *dentry)
 static void dentry_iput(struct dentry * dentry)
        __releases(dentry->d_lock)
        __releases(dcache_inode_lock)
-       __releases(dcache_lock)
 {
        struct inode *inode = dentry->d_inode;
        if (inode) {
@@ -169,7 +166,6 @@ static void dentry_iput(struct dentry * dentry)
                list_del_init(&dentry->d_alias);
                spin_unlock(&dentry->d_lock);
                spin_unlock(&dcache_inode_lock);
-               spin_unlock(&dcache_lock);
                if (!inode->i_nlink)
                        fsnotify_inoderemove(inode);
                if (dentry->d_op && dentry->d_op->d_iput)
@@ -179,7 +175,6 @@ static void dentry_iput(struct dentry * dentry)
        } else {
                spin_unlock(&dentry->d_lock);
                spin_unlock(&dcache_inode_lock);
-               spin_unlock(&dcache_lock);
        }
 }
 
@@ -234,15 +229,15 @@ static void dentry_lru_move_tail(struct dentry *dentry)
  *
  * If this is the root of the dentry tree, return NULL.
  *
- * dcache_lock and d_lock and d_parent->d_lock must be held by caller, and
- * are dropped by d_kill.
+ * dentry->d_lock and parent->d_lock must be held by caller, and are dropped by
+ * d_kill.
  */
 static struct dentry *d_kill(struct dentry *dentry, struct dentry *parent)
        __releases(dentry->d_lock)
        __releases(parent->d_lock)
        __releases(dcache_inode_lock)
-       __releases(dcache_lock)
 {
+       dentry->d_parent = NULL;
        list_del(&dentry->d_u.d_child);
        if (parent)
                spin_unlock(&parent->d_lock);
@@ -283,11 +278,9 @@ EXPORT_SYMBOL(__d_drop);
 
 void d_drop(struct dentry *dentry)
 {
-       spin_lock(&dcache_lock);
        spin_lock(&dentry->d_lock);
        __d_drop(dentry);
        spin_unlock(&dentry->d_lock);
-       spin_unlock(&dcache_lock);
 }
 EXPORT_SYMBOL(d_drop);
 
@@ -330,47 +323,16 @@ repeat:
        if (dentry->d_count == 1)
                might_sleep();
        spin_lock(&dentry->d_lock);
-       if (IS_ROOT(dentry))
-               parent = NULL;
-       else
-               parent = dentry->d_parent;
-       if (dentry->d_count == 1) {
-               if (!spin_trylock(&dcache_lock)) {
-                       /*
-                        * Something of a livelock possibility we could avoid
-                        * by taking dcache_lock and trying again, but we
-                        * want to reduce dcache_lock anyway so this will
-                        * get improved.
-                        */
-drop1:
-                       spin_unlock(&dentry->d_lock);
-                       goto repeat;
-               }
-               if (!spin_trylock(&dcache_inode_lock)) {
-drop2:
-                       spin_unlock(&dcache_lock);
-                       goto drop1;
-               }
-               if (parent && !spin_trylock(&parent->d_lock)) {
-                       spin_unlock(&dcache_inode_lock);
-                       goto drop2;
-               }
-       }
-       dentry->d_count--;
-       if (dentry->d_count) {
+       BUG_ON(!dentry->d_count);
+       if (dentry->d_count > 1) {
+               dentry->d_count--;
                spin_unlock(&dentry->d_lock);
-               if (parent)
-                       spin_unlock(&parent->d_lock);
-               spin_unlock(&dcache_lock);
                return;
        }
 
-       /*
-        * AV: ->d_delete() is _NOT_ allowed to block now.
-        */
        if (dentry->d_op && dentry->d_op->d_delete) {
                if (dentry->d_op->d_delete(dentry))
-                       goto unhash_it;
+                       goto kill_it;
        }
 
        /* Unreachable? Get rid of it */
@@ -381,18 +343,30 @@ drop2:
        dentry->d_flags |= DCACHE_REFERENCED;
        dentry_lru_add(dentry);
 
-       spin_unlock(&dentry->d_lock);
-       if (parent)
-               spin_unlock(&parent->d_lock);
-       spin_unlock(&dcache_inode_lock);
-       spin_unlock(&dcache_lock);
+       dentry->d_count--;
+       spin_unlock(&dentry->d_lock);
        return;
 
-unhash_it:
-       __d_drop(dentry);
 kill_it:
+       if (!spin_trylock(&dcache_inode_lock)) {
+relock:
+               spin_unlock(&dentry->d_lock);
+               cpu_relax();
+               goto repeat;
+       }
+       if (IS_ROOT(dentry))
+               parent = NULL;
+       else
+               parent = dentry->d_parent;
+       if (parent && !spin_trylock(&parent->d_lock)) {
+               spin_unlock(&dcache_inode_lock);
+               goto relock;
+       }
+       dentry->d_count--;
        /* if dentry was on the d_lru list delete it from there */
        dentry_lru_del(dentry);
+       /* if it was on the hash (d_delete case), then remove it */
+       __d_drop(dentry);
        dentry = d_kill(dentry, parent);
        if (dentry)
                goto repeat;
@@ -416,11 +390,9 @@ int d_invalidate(struct dentry * dentry)
        /*
         * If it's already been dropped, return OK.
         */
-       spin_lock(&dcache_lock);
        spin_lock(&dentry->d_lock);
        if (d_unhashed(dentry)) {
                spin_unlock(&dentry->d_lock);
-               spin_unlock(&dcache_lock);
                return 0;
        }
        /*
@@ -429,9 +401,7 @@ int d_invalidate(struct dentry * dentry)
         */
        if (!list_empty(&dentry->d_subdirs)) {
                spin_unlock(&dentry->d_lock);
-               spin_unlock(&dcache_lock);
                shrink_dcache_parent(dentry);
-               spin_lock(&dcache_lock);
                spin_lock(&dentry->d_lock);
        }
 
@@ -448,46 +418,29 @@ int d_invalidate(struct dentry * dentry)
        if (dentry->d_count > 1) {
                if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) {
                        spin_unlock(&dentry->d_lock);
-                       spin_unlock(&dcache_lock);
                        return -EBUSY;
                }
        }
 
        __d_drop(dentry);
        spin_unlock(&dentry->d_lock);
-       spin_unlock(&dcache_lock);
        return 0;
 }
 EXPORT_SYMBOL(d_invalidate);
 
-/* This must be called with dcache_lock and d_lock held */
-static inline struct dentry * __dget_locked_dlock(struct dentry *dentry)
+/* This must be called with d_lock held */
+static inline void __dget_dlock(struct dentry *dentry)
 {
        dentry->d_count++;
-       dentry_lru_del(dentry);
-       return dentry;
 }
 
-/* This should be called _only_ with dcache_lock held */
-static inline struct dentry * __dget_locked(struct dentry *dentry)
+static inline void __dget(struct dentry *dentry)
 {
        spin_lock(&dentry->d_lock);
-       __dget_locked_dlock(dentry);
+       __dget_dlock(dentry);
        spin_unlock(&dentry->d_lock);
-       return dentry;
-}
-
-struct dentry * dget_locked_dlock(struct dentry *dentry)
-{
-       return __dget_locked_dlock(dentry);
 }
 
-struct dentry * dget_locked(struct dentry *dentry)
-{
-       return __dget_locked(dentry);
-}
-EXPORT_SYMBOL(dget_locked);
-
 struct dentry *dget_parent(struct dentry *dentry)
 {
        struct dentry *ret;
@@ -544,7 +497,7 @@ again:
                            (alias->d_flags & DCACHE_DISCONNECTED)) {
                                discon_alias = alias;
                        } else if (!want_discon) {
-                               __dget_locked_dlock(alias);
+                               __dget_dlock(alias);
                                spin_unlock(&alias->d_lock);
                                return alias;
                        }
@@ -557,7 +510,7 @@ again:
                if (S_ISDIR(inode->i_mode) || !d_unhashed(alias)) {
                        if (IS_ROOT(alias) &&
                            (alias->d_flags & DCACHE_DISCONNECTED)) {
-                               __dget_locked_dlock(alias);
+                               __dget_dlock(alias);
                                spin_unlock(&alias->d_lock);
                                return alias;
                        }
@@ -573,11 +526,9 @@ struct dentry *d_find_alias(struct inode *inode)
        struct dentry *de = NULL;
 
        if (!list_empty(&inode->i_dentry)) {
-               spin_lock(&dcache_lock);
                spin_lock(&dcache_inode_lock);
                de = __d_find_alias(inode, 0);
                spin_unlock(&dcache_inode_lock);
-               spin_unlock(&dcache_lock);
        }
        return de;
 }
@@ -591,23 +542,20 @@ void d_prune_aliases(struct inode *inode)
 {
        struct dentry *dentry;
 restart:
-       spin_lock(&dcache_lock);
        spin_lock(&dcache_inode_lock);
        list_for_each_entry(dentry, &inode->i_dentry, d_alias) {
                spin_lock(&dentry->d_lock);
                if (!dentry->d_count) {
-                       __dget_locked_dlock(dentry);
+                       __dget_dlock(dentry);
                        __d_drop(dentry);
                        spin_unlock(&dentry->d_lock);
                        spin_unlock(&dcache_inode_lock);
-                       spin_unlock(&dcache_lock);
                        dput(dentry);
                        goto restart;
                }
                spin_unlock(&dentry->d_lock);
        }
        spin_unlock(&dcache_inode_lock);
-       spin_unlock(&dcache_lock);
 }
 EXPORT_SYMBOL(d_prune_aliases);
 
@@ -623,17 +571,14 @@ static void prune_one_dentry(struct dentry *dentry, struct dentry *parent)
        __releases(dentry->d_lock)
        __releases(parent->d_lock)
        __releases(dcache_inode_lock)
-       __releases(dcache_lock)
 {
        __d_drop(dentry);
        dentry = d_kill(dentry, parent);
 
        /*
-        * Prune ancestors.  Locking is simpler than in dput(),
-        * because dcache_lock needs to be taken anyway.
+        * Prune ancestors.
         */
        while (dentry) {
-               spin_lock(&dcache_lock);
                spin_lock(&dcache_inode_lock);
 again:
                spin_lock(&dentry->d_lock);
@@ -651,7 +596,6 @@ again:
                                spin_unlock(&parent->d_lock);
                        spin_unlock(&dentry->d_lock);
                        spin_unlock(&dcache_inode_lock);
-                       spin_unlock(&dcache_lock);
                        return;
                }
 
@@ -700,8 +644,7 @@ relock:
                spin_unlock(&dcache_lru_lock);
 
                prune_one_dentry(dentry, parent);
-               /* dcache_lock, dcache_inode_lock and dentry->d_lock dropped */
-               spin_lock(&dcache_lock);
+               /* dcache_inode_lock and dentry->d_lock dropped */
                spin_lock(&dcache_inode_lock);
                spin_lock(&dcache_lru_lock);
        }
@@ -723,7 +666,6 @@ static void __shrink_dcache_sb(struct super_block *sb, int *count, int flags)
        LIST_HEAD(tmp);
        int cnt = *count;
 
-       spin_lock(&dcache_lock);
        spin_lock(&dcache_inode_lock);
 relock:
        spin_lock(&dcache_lru_lock);
@@ -764,7 +706,6 @@ relock:
                list_splice(&referenced, &sb->s_dentry_lru);
        spin_unlock(&dcache_lru_lock);
        spin_unlock(&dcache_inode_lock);
-       spin_unlock(&dcache_lock);
 }
 
 /**
@@ -786,7 +727,6 @@ static void prune_dcache(int count)
 
        if (unused == 0 || count == 0)
                return;
-       spin_lock(&dcache_lock);
        if (count >= unused)
                prune_ratio = 1;
        else
@@ -823,11 +763,9 @@ static void prune_dcache(int count)
                if (down_read_trylock(&sb->s_umount)) {
                        if ((sb->s_root != NULL) &&
                            (!list_empty(&sb->s_dentry_lru))) {
-                               spin_unlock(&dcache_lock);
                                __shrink_dcache_sb(sb, &w_count,
                                                DCACHE_REFERENCED);
                                pruned -= w_count;
-                               spin_lock(&dcache_lock);
                        }
                        up_read(&sb->s_umount);
                }
@@ -843,7 +781,6 @@ static void prune_dcache(int count)
        if (p)
                __put_super(p);
        spin_unlock(&sb_lock);
-       spin_unlock(&dcache_lock);
 }
 
 /**
@@ -857,7 +794,6 @@ void shrink_dcache_sb(struct super_block *sb)
 {
        LIST_HEAD(tmp);
 
-       spin_lock(&dcache_lock);
        spin_lock(&dcache_inode_lock);
        spin_lock(&dcache_lru_lock);
        while (!list_empty(&sb->s_dentry_lru)) {
@@ -866,7 +802,6 @@ void shrink_dcache_sb(struct super_block *sb)
        }
        spin_unlock(&dcache_lru_lock);
        spin_unlock(&dcache_inode_lock);
-       spin_unlock(&dcache_lock);
 }
 EXPORT_SYMBOL(shrink_dcache_sb);
 
@@ -883,12 +818,10 @@ static void shrink_dcache_for_umount_subtree(struct dentry *dentry)
        BUG_ON(!IS_ROOT(dentry));
 
        /* detach this root from the system */
-       spin_lock(&dcache_lock);
        spin_lock(&dentry->d_lock);
        dentry_lru_del(dentry);
        __d_drop(dentry);
        spin_unlock(&dentry->d_lock);
-       spin_unlock(&dcache_lock);
 
        for (;;) {
                /* descend to the first leaf in the current subtree */
@@ -897,7 +830,6 @@ static void shrink_dcache_for_umount_subtree(struct dentry *dentry)
 
                        /* this is a branch with children - detach all of them
                         * from the system in one go */
-                       spin_lock(&dcache_lock);
                        spin_lock(&dentry->d_lock);
                        list_for_each_entry(loop, &dentry->d_subdirs,
                                            d_u.d_child) {
@@ -908,7 +840,6 @@ static void shrink_dcache_for_umount_subtree(struct dentry *dentry)
                                spin_unlock(&loop->d_lock);
                        }
                        spin_unlock(&dentry->d_lock);
-                       spin_unlock(&dcache_lock);
 
                        /* move to the first child */
                        dentry = list_entry(dentry->d_subdirs.next,
@@ -975,8 +906,7 @@ static void shrink_dcache_for_umount_subtree(struct dentry *dentry)
 
 /*
  * destroy the dentries attached to a superblock on unmounting
- * - we don't need to use dentry->d_lock, and only need dcache_lock when
- *   removing the dentry from the system lists and hashes because:
+ * - we don't need to use dentry->d_lock because:
  *   - the superblock is detached from all mountings and open files, so the
  *     dentry trees will not be rearranged by the VFS
  *   - s_umount is write-locked, so the memory pressure shrinker will ignore
@@ -1017,13 +947,17 @@ void shrink_dcache_for_umount(struct super_block *sb)
  * Return true if the parent or its subdirectories contain
  * a mount point
  */
 int have_submounts(struct dentry *parent)
 {
-       struct dentry *this_parent = parent;
+       struct dentry *this_parent;
        struct list_head *next;
+       unsigned seq;
+       int locked = 0;
+
+       seq = read_seqbegin(&rename_lock);
+again:
+       this_parent = parent;
 
-       spin_lock(&dcache_lock);
        if (d_mountpoint(parent))
                goto positive;
        spin_lock(&this_parent->d_lock);
@@ -1055,18 +989,44 @@ resume:
         * All done at this level ... ascend and resume the search.
         */
        if (this_parent != parent) {
-               next = this_parent->d_u.d_child.next;
+               struct dentry *tmp;
+               struct dentry *child;
+
+               tmp = this_parent->d_parent;
+               rcu_read_lock();
                spin_unlock(&this_parent->d_lock);
-               this_parent = this_parent->d_parent;
+               child = this_parent;
+               this_parent = tmp;
                spin_lock(&this_parent->d_lock);
+               /* might go back up the wrong parent if we have had a rename
+                * or deletion */
+               if (this_parent != child->d_parent ||
+                        (!locked && read_seqretry(&rename_lock, seq))) {
+                       spin_unlock(&this_parent->d_lock);
+                       rcu_read_unlock();
+                       goto rename_retry;
+               }
+               rcu_read_unlock();
+               next = child->d_u.d_child.next;
                goto resume;
        }
        spin_unlock(&this_parent->d_lock);
-       spin_unlock(&dcache_lock);
+       if (!locked && read_seqretry(&rename_lock, seq))
+               goto rename_retry;
+       if (locked)
+               write_sequnlock(&rename_lock);
        return 0; /* No mount points found in tree */
 positive:
-       spin_unlock(&dcache_lock);
+       if (!locked && read_seqretry(&rename_lock, seq))
+               goto rename_retry;
+       if (locked)
+               write_sequnlock(&rename_lock);
        return 1;
+
+rename_retry:
+       locked = 1;
+       write_seqlock(&rename_lock);
+       goto again;
 }
 EXPORT_SYMBOL(have_submounts);
 
@@ -1086,11 +1046,15 @@ EXPORT_SYMBOL(have_submounts);
  */
 static int select_parent(struct dentry * parent)
 {
-       struct dentry *this_parent = parent;
+       struct dentry *this_parent;
        struct list_head *next;
+       unsigned seq;
        int found = 0;
+       int locked = 0;
 
-       spin_lock(&dcache_lock);
+       seq = read_seqbegin(&rename_lock);
+again:
+       this_parent = parent;
        spin_lock(&this_parent->d_lock);
 repeat:
        next = this_parent->d_subdirs.next;
@@ -1099,7 +1063,6 @@ resume:
                struct list_head *tmp = next;
                struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child);
                next = tmp->next;
-               BUG_ON(this_parent == dentry);
 
                spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
 
@@ -1142,18 +1105,40 @@ resume:
         */
        if (this_parent != parent) {
                struct dentry *tmp;
-               next = this_parent->d_u.d_child.next;
+               struct dentry *child;
+
                tmp = this_parent->d_parent;
+               rcu_read_lock();
                spin_unlock(&this_parent->d_lock);
-               BUG_ON(tmp == this_parent);
+               child = this_parent;
                this_parent = tmp;
                spin_lock(&this_parent->d_lock);
+               /* might go back up the wrong parent if we have had a rename
+                * or deletion */
+               if (this_parent != child->d_parent ||
+                       (!locked && read_seqretry(&rename_lock, seq))) {
+                       spin_unlock(&this_parent->d_lock);
+                       rcu_read_unlock();
+                       goto rename_retry;
+               }
+               rcu_read_unlock();
+               next = child->d_u.d_child.next;
                goto resume;
        }
 out:
        spin_unlock(&this_parent->d_lock);
-       spin_unlock(&dcache_lock);
+       if (!locked && read_seqretry(&rename_lock, seq))
+               goto rename_retry;
+       if (locked)
+               write_sequnlock(&rename_lock);
        return found;
+
+rename_retry:
+       if (found)
+               return found;
+       locked = 1;
+       write_seqlock(&rename_lock);
+       goto again;
 }
 
 /**
@@ -1252,15 +1237,16 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
        INIT_LIST_HEAD(&dentry->d_u.d_child);
 
        if (parent) {
-               spin_lock(&dcache_lock);
                spin_lock(&parent->d_lock);
-               spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
-               dentry->d_parent = dget_dlock(parent);
+               /*
+                * don't need child lock because it is not subject
+                * to concurrency here
+                */
+               __dget_dlock(parent);
+               dentry->d_parent = parent;
                dentry->d_sb = parent->d_sb;
                list_add(&dentry->d_u.d_child, &parent->d_subdirs);
-               spin_unlock(&dentry->d_lock);
                spin_unlock(&parent->d_lock);
-               spin_unlock(&dcache_lock);
        }
 
        this_cpu_inc(nr_dentry);
@@ -1280,7 +1266,6 @@ struct dentry *d_alloc_name(struct dentry *parent, const char *name)
 }
 EXPORT_SYMBOL(d_alloc_name);
 
-/* the caller must hold dcache_lock */
 static void __d_instantiate(struct dentry *dentry, struct inode *inode)
 {
        spin_lock(&dentry->d_lock);
@@ -1309,11 +1294,9 @@ static void __d_instantiate(struct dentry *dentry, struct inode *inode)
 void d_instantiate(struct dentry *entry, struct inode * inode)
 {
        BUG_ON(!list_empty(&entry->d_alias));
-       spin_lock(&dcache_lock);
        spin_lock(&dcache_inode_lock);
        __d_instantiate(entry, inode);
        spin_unlock(&dcache_inode_lock);
-       spin_unlock(&dcache_lock);
        security_d_instantiate(entry, inode);
 }
 EXPORT_SYMBOL(d_instantiate);
@@ -1363,7 +1346,7 @@ static struct dentry *__d_instantiate_unique(struct dentry *entry,
                        continue;
                if (memcmp(qstr->name, name, len))
                        continue;
-               dget_locked(alias);
+               __dget(alias);
                return alias;
        }
 
@@ -1377,11 +1360,9 @@ struct dentry *d_instantiate_unique(struct dentry *entry, struct inode *inode)
 
        BUG_ON(!list_empty(&entry->d_alias));
 
-       spin_lock(&dcache_lock);
        spin_lock(&dcache_inode_lock);
        result = __d_instantiate_unique(entry, inode);
        spin_unlock(&dcache_inode_lock);
-       spin_unlock(&dcache_lock);
 
        if (!result) {
                security_d_instantiate(entry, inode);
@@ -1470,12 +1451,11 @@ struct dentry *d_obtain_alias(struct inode *inode)
        }
        tmp->d_parent = tmp; /* make sure dput doesn't croak */
 
-       spin_lock(&dcache_lock);
+
        spin_lock(&dcache_inode_lock);
        res = __d_find_alias(inode, 0);
        if (res) {
                spin_unlock(&dcache_inode_lock);
-               spin_unlock(&dcache_lock);
                dput(tmp);
                goto out_iput;
        }
@@ -1493,7 +1473,6 @@ struct dentry *d_obtain_alias(struct inode *inode)
        spin_unlock(&tmp->d_lock);
        spin_unlock(&dcache_inode_lock);
 
-       spin_unlock(&dcache_lock);
        return tmp;
 
  out_iput:
@@ -1523,21 +1502,18 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
        struct dentry *new = NULL;
 
        if (inode && S_ISDIR(inode->i_mode)) {
-               spin_lock(&dcache_lock);
                spin_lock(&dcache_inode_lock);
                new = __d_find_alias(inode, 1);
                if (new) {
                        BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
                        spin_unlock(&dcache_inode_lock);
-                       spin_unlock(&dcache_lock);
                        security_d_instantiate(new, inode);
                        d_move(new, dentry);
                        iput(inode);
                } else {
-                       /* already taking dcache_lock, so d_add() by hand */
+                       /* already taking dcache_inode_lock, so d_add() by hand */
                        __d_instantiate(dentry, inode);
                        spin_unlock(&dcache_inode_lock);
-                       spin_unlock(&dcache_lock);
                        security_d_instantiate(dentry, inode);
                        d_rehash(dentry);
                }
@@ -1610,12 +1586,10 @@ struct dentry *d_add_ci(struct dentry *dentry, struct inode *inode,
         * Negative dentry: instantiate it unless the inode is a directory and
         * already has a dentry.
         */
-       spin_lock(&dcache_lock);
        spin_lock(&dcache_inode_lock);
        if (!S_ISDIR(inode->i_mode) || list_empty(&inode->i_dentry)) {
                __d_instantiate(found, inode);
                spin_unlock(&dcache_inode_lock);
-               spin_unlock(&dcache_lock);
                security_d_instantiate(found, inode);
                return found;
        }
@@ -1625,9 +1599,8 @@ struct dentry *d_add_ci(struct dentry *dentry, struct inode *inode,
         * reference to it, move it in place and use it.
         */
        new = list_entry(inode->i_dentry.next, struct dentry, d_alias);
-       dget_locked(new);
+       __dget(new);
        spin_unlock(&dcache_inode_lock);
-       spin_unlock(&dcache_lock);
        security_d_instantiate(found, inode);
        d_move(new, found);
        iput(inode);
@@ -1654,7 +1627,7 @@ EXPORT_SYMBOL(d_add_ci);
 struct dentry * d_lookup(struct dentry * parent, struct qstr * name)
 {
        struct dentry * dentry = NULL;
-       unsigned long seq;
+       unsigned seq;
 
         do {
                 seq = read_seqbegin(&rename_lock);
@@ -1798,20 +1771,17 @@ int d_validate(struct dentry *dentry, struct dentry *dparent)
 {
        struct dentry *child;
 
-       spin_lock(&dcache_lock);
        spin_lock(&dparent->d_lock);
        list_for_each_entry(child, &dparent->d_subdirs, d_u.d_child) {
                if (dentry == child) {
                        spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
-                       __dget_locked_dlock(dentry);
+                       __dget_dlock(dentry);
                        spin_unlock(&dentry->d_lock);
                        spin_unlock(&dparent->d_lock);
-                       spin_unlock(&dcache_lock);
                        return 1;
                }
        }
        spin_unlock(&dparent->d_lock);
-       spin_unlock(&dcache_lock);
 
        return 0;
 }
@@ -1844,11 +1814,15 @@ void d_delete(struct dentry * dentry)
        /*
         * Are we the only user?
         */
-       spin_lock(&dcache_lock);
-       spin_lock(&dcache_inode_lock);
+again:
        spin_lock(&dentry->d_lock);
        isdir = S_ISDIR(dentry->d_inode->i_mode);
        if (dentry->d_count == 1) {
+               if (!spin_trylock(&dcache_inode_lock)) {
+                       spin_unlock(&dentry->d_lock);
+                       cpu_relax();
+                       goto again;
+               }
                dentry->d_flags &= ~DCACHE_CANT_MOUNT;
                dentry_iput(dentry);
                fsnotify_nameremove(dentry, isdir);
@@ -1859,8 +1833,6 @@ void d_delete(struct dentry * dentry)
                __d_drop(dentry);
 
        spin_unlock(&dentry->d_lock);
-       spin_unlock(&dcache_inode_lock);
-       spin_unlock(&dcache_lock);
 
        fsnotify_nameremove(dentry, isdir);
 }
@@ -1887,13 +1859,11 @@ static void _d_rehash(struct dentry * entry)
  
 void d_rehash(struct dentry * entry)
 {
-       spin_lock(&dcache_lock);
        spin_lock(&entry->d_lock);
        spin_lock(&dcache_hash_lock);
        _d_rehash(entry);
        spin_unlock(&dcache_hash_lock);
        spin_unlock(&entry->d_lock);
-       spin_unlock(&dcache_lock);
 }
 EXPORT_SYMBOL(d_rehash);
 
@@ -1916,11 +1886,9 @@ void dentry_update_name_case(struct dentry *dentry, struct qstr *name)
        BUG_ON(!mutex_is_locked(&dentry->d_inode->i_mutex));
        BUG_ON(dentry->d_name.len != name->len); /* d_lookup gives this */
 
-       spin_lock(&dcache_lock);
        spin_lock(&dentry->d_lock);
        memcpy((unsigned char *)dentry->d_name.name, name->name, name->len);
        spin_unlock(&dentry->d_lock);
-       spin_unlock(&dcache_lock);
 }
 EXPORT_SYMBOL(dentry_update_name_case);
 
@@ -2013,14 +1981,14 @@ static void dentry_unlock_parents_for_move(struct dentry *dentry,
  * The hash value has to match the hash queue that the dentry is on..
  */
 /*
- * d_move_locked - move a dentry
+ * d_move - move a dentry
  * @dentry: entry to move
  * @target: new dentry
  *
  * Update the dcache to reflect the move of a file name. Negative
  * dcache entries should not be moved in this way.
  */
-static void d_move_locked(struct dentry * dentry, struct dentry * target)
+void d_move(struct dentry * dentry, struct dentry * target)
 {
        if (!dentry->d_inode)
                printk(KERN_WARNING "VFS: moving negative dcache entry\n");
@@ -2069,22 +2037,6 @@ static void d_move_locked(struct dentry * dentry, struct dentry * target)
        spin_unlock(&dentry->d_lock);
        write_sequnlock(&rename_lock);
 }
-
-/**
- * d_move - move a dentry
- * @dentry: entry to move
- * @target: new dentry
- *
- * Update the dcache to reflect the move of a file name. Negative
- * dcache entries should not be moved in this way.
- */
-
-void d_move(struct dentry * dentry, struct dentry * target)
-{
-       spin_lock(&dcache_lock);
-       d_move_locked(dentry, target);
-       spin_unlock(&dcache_lock);
-}
 EXPORT_SYMBOL(d_move);
 
 /**
@@ -2110,13 +2062,12 @@ struct dentry *d_ancestor(struct dentry *p1, struct dentry *p2)
  * This helper attempts to cope with remotely renamed directories
  *
  * It assumes that the caller is already holding
- * dentry->d_parent->d_inode->i_mutex and the dcache_lock
+ * dentry->d_parent->d_inode->i_mutex and the dcache_inode_lock
  *
  * Note: If ever the locking in lock_rename() changes, then please
  * remember to update this too...
  */
 static struct dentry *__d_unalias(struct dentry *dentry, struct dentry *alias)
-       __releases(dcache_lock)
        __releases(dcache_inode_lock)
 {
        struct mutex *m1 = NULL, *m2 = NULL;
@@ -2140,11 +2091,10 @@ static struct dentry *__d_unalias(struct dentry *dentry, struct dentry *alias)
                goto out_err;
        m2 = &alias->d_parent->d_inode->i_mutex;
 out_unalias:
-       d_move_locked(alias, dentry);
+       d_move(alias, dentry);
        ret = alias;
 out_err:
        spin_unlock(&dcache_inode_lock);
-       spin_unlock(&dcache_lock);
        if (m2)
                mutex_unlock(m2);
        if (m1)
@@ -2204,15 +2154,15 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
 
        BUG_ON(!d_unhashed(dentry));
 
-       spin_lock(&dcache_lock);
-       spin_lock(&dcache_inode_lock);
-
        if (!inode) {
                actual = dentry;
                __d_instantiate(dentry, NULL);
-               goto found_lock;
+               d_rehash(actual);
+               goto out_nolock;
        }
 
+       spin_lock(&dcache_inode_lock);
+
        if (S_ISDIR(inode->i_mode)) {
                struct dentry *alias;
 
@@ -2239,10 +2189,9 @@ struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
        actual = __d_instantiate_unique(dentry, inode);
        if (!actual)
                actual = dentry;
-       else if (unlikely(!d_unhashed(actual)))
-               goto shouldnt_be_hashed;
+       else
+               BUG_ON(!d_unhashed(actual));
 
-found_lock:
        spin_lock(&actual->d_lock);
 found:
        spin_lock(&dcache_hash_lock);
@@ -2250,7 +2199,6 @@ found:
        spin_unlock(&dcache_hash_lock);
        spin_unlock(&actual->d_lock);
        spin_unlock(&dcache_inode_lock);
-       spin_unlock(&dcache_lock);
 out_nolock:
        if (actual == dentry) {
                security_d_instantiate(dentry, inode);
@@ -2259,11 +2207,6 @@ out_nolock:
 
        iput(inode);
        return actual;
-
-shouldnt_be_hashed:
-       spin_unlock(&dcache_inode_lock);
-       spin_unlock(&dcache_lock);
-       BUG();
 }
 EXPORT_SYMBOL_GPL(d_materialise_unique);
 
@@ -2290,7 +2233,7 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
  * @buffer: pointer to the end of the buffer
  * @buflen: pointer to buffer length
  *
- * Caller holds the dcache_lock.
+ * Caller holds the rename_lock.
  *
  * If path is not reachable from the supplied root, then the value of
  * root is changed (without modifying refcounts).
@@ -2376,9 +2319,9 @@ char *__d_path(const struct path *path, struct path *root,
        int error;
 
        prepend(&res, &buflen, "\0", 1);
-       spin_lock(&dcache_lock);
+       write_seqlock(&rename_lock);
        error = prepend_path(path, root, &res, &buflen);
-       spin_unlock(&dcache_lock);
+       write_sequnlock(&rename_lock);
 
        if (error)
                return ERR_PTR(error);
@@ -2440,12 +2383,12 @@ char *d_path(const struct path *path, char *buf, int buflen)
                return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
 
        get_fs_root(current->fs, &root);
-       spin_lock(&dcache_lock);
+       write_seqlock(&rename_lock);
        tmp = root;
        error = path_with_deleted(path, &tmp, &res, &buflen);
        if (error)
                res = ERR_PTR(error);
-       spin_unlock(&dcache_lock);
+       write_sequnlock(&rename_lock);
        path_put(&root);
        return res;
 }
@@ -2471,12 +2414,12 @@ char *d_path_with_unreachable(const struct path *path, char *buf, int buflen)
                return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
 
        get_fs_root(current->fs, &root);
-       spin_lock(&dcache_lock);
+       write_seqlock(&rename_lock);
        tmp = root;
        error = path_with_deleted(path, &tmp, &res, &buflen);
        if (!error && !path_equal(&tmp, &root))
                error = prepend_unreachable(&res, &buflen);
-       spin_unlock(&dcache_lock);
+       write_sequnlock(&rename_lock);
        path_put(&root);
        if (error)
                res =  ERR_PTR(error);
@@ -2543,9 +2486,9 @@ char *dentry_path_raw(struct dentry *dentry, char *buf, int buflen)
 {
        char *retval;
 
-       spin_lock(&dcache_lock);
+       write_seqlock(&rename_lock);
        retval = __dentry_path(dentry, buf, buflen);
-       spin_unlock(&dcache_lock);
+       write_sequnlock(&rename_lock);
 
        return retval;
 }
@@ -2556,7 +2499,7 @@ char *dentry_path(struct dentry *dentry, char *buf, int buflen)
        char *p = NULL;
        char *retval;
 
-       spin_lock(&dcache_lock);
+       write_seqlock(&rename_lock);
        if (d_unlinked(dentry)) {
                p = buf + buflen;
                if (prepend(&p, &buflen, "//deleted", 10) != 0)
@@ -2564,12 +2507,11 @@ char *dentry_path(struct dentry *dentry, char *buf, int buflen)
                buflen++;
        }
        retval = __dentry_path(dentry, buf, buflen);
-       spin_unlock(&dcache_lock);
+       write_sequnlock(&rename_lock);
        if (!IS_ERR(retval) && p)
                *p = '/';       /* restore '/' overriden with '\0' */
        return retval;
 Elong:
-       spin_unlock(&dcache_lock);
        return ERR_PTR(-ENAMETOOLONG);
 }
 
@@ -2603,7 +2545,7 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
        get_fs_root_and_pwd(current->fs, &root, &pwd);
 
        error = -ENOENT;
-       spin_lock(&dcache_lock);
+       write_seqlock(&rename_lock);
        if (!d_unlinked(pwd.dentry)) {
                unsigned long len;
                struct path tmp = root;
@@ -2612,7 +2554,7 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
 
                prepend(&cwd, &buflen, "\0", 1);
                error = prepend_path(&pwd, &tmp, &cwd, &buflen);
-               spin_unlock(&dcache_lock);
+               write_sequnlock(&rename_lock);
 
                if (error)
                        goto out;
@@ -2631,8 +2573,9 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
                        if (copy_to_user(buf, cwd, len))
                                error = -EFAULT;
                }
-       } else
-               spin_unlock(&dcache_lock);
+       } else {
+               write_sequnlock(&rename_lock);
+       }
 
 out:
        path_put(&pwd);
@@ -2660,25 +2603,25 @@ out:
 int is_subdir(struct dentry *new_dentry, struct dentry *old_dentry)
 {
        int result;
-       unsigned long seq;
+       unsigned seq;
 
        if (new_dentry == old_dentry)
                return 1;
 
-       /*
-        * Need rcu_readlock to protect against the d_parent trashing
-        * due to d_move
-        */
-       rcu_read_lock();
        do {
                /* for restarting inner loop in case of seq retry */
                seq = read_seqbegin(&rename_lock);
+               /*
+                * Need rcu_readlock to protect against the d_parent trashing
+                * due to d_move
+                */
+               rcu_read_lock();
                if (d_ancestor(old_dentry, new_dentry))
                        result = 1;
                else
                        result = 0;
+               rcu_read_unlock();
        } while (read_seqretry(&rename_lock, seq));
-       rcu_read_unlock();
 
        return result;
 }
@@ -2710,10 +2653,14 @@ EXPORT_SYMBOL(path_is_under);
 
 void d_genocide(struct dentry *root)
 {
-       struct dentry *this_parent = root;
+       struct dentry *this_parent;
        struct list_head *next;
+       unsigned seq;
+       int locked = 0;
 
-       spin_lock(&dcache_lock);
+       seq = read_seqbegin(&rename_lock);
+again:
+       this_parent = root;
        spin_lock(&this_parent->d_lock);
 repeat:
        next = this_parent->d_subdirs.next;
@@ -2722,6 +2669,7 @@ resume:
                struct list_head *tmp = next;
                struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child);
                next = tmp->next;
+
                spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
                if (d_unhashed(dentry) || !dentry->d_inode) {
                        spin_unlock(&dentry->d_lock);
@@ -2734,19 +2682,49 @@ resume:
                        spin_acquire(&this_parent->d_lock.dep_map, 0, 1, _RET_IP_);
                        goto repeat;
                }
-               dentry->d_count--;
+               if (!(dentry->d_flags & DCACHE_GENOCIDE)) {
+                       dentry->d_flags |= DCACHE_GENOCIDE;
+                       dentry->d_count--;
+               }
                spin_unlock(&dentry->d_lock);
        }
        if (this_parent != root) {
-               next = this_parent->d_u.d_child.next;
-               this_parent->d_count--;
+               struct dentry *tmp;
+               struct dentry *child;
+
+               tmp = this_parent->d_parent;
+               if (!(this_parent->d_flags & DCACHE_GENOCIDE)) {
+                       this_parent->d_flags |= DCACHE_GENOCIDE;
+                       this_parent->d_count--;
+               }
+               rcu_read_lock();
                spin_unlock(&this_parent->d_lock);
-               this_parent = this_parent->d_parent;
+               child = this_parent;
+               this_parent = tmp;
                spin_lock(&this_parent->d_lock);
+               /* might go back up the wrong parent if we have had a rename
+                * or deletion */
+               if (this_parent != child->d_parent ||
+                        (!locked && read_seqretry(&rename_lock, seq))) {
+                       spin_unlock(&this_parent->d_lock);
+                       rcu_read_unlock();
+                       goto rename_retry;
+               }
+               rcu_read_unlock();
+               next = child->d_u.d_child.next;
                goto resume;
        }
        spin_unlock(&this_parent->d_lock);
-       spin_unlock(&dcache_lock);
+       if (!locked && read_seqretry(&rename_lock, seq))
+               goto rename_retry;
+       if (locked)
+               write_sequnlock(&rename_lock);
+       return;
+
+rename_retry:
+       locked = 1;
+       write_seqlock(&rename_lock);
+       goto again;
 }
 
 /**
This page took 0.057556 seconds and 5 git commands to generate.