dentry_kill(): don't try to remove from shrink list
authorAl Viro <viro@zeniv.linux.org.uk>
Thu, 1 May 2014 14:30:00 +0000 (10:30 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 17 Aug 2015 03:51:53 +0000 (20:51 -0700)
commit 41edf278fc2f042f4e22a12ed87d19c5201210e1 upstream.

If the victim in on the shrink list, don't remove it from there.
If shrink_dentry_list() manages to remove it from the list before
we are done - fine, we'll just free it as usual.  If not - mark
it with new flag (DCACHE_MAY_FREE) and leave it there.

Eventually, shrink_dentry_list() will get to it, remove the sucker
from shrink list and call dentry_kill(dentry, 0).  Which is where
we'll deal with freeing.

Since now dentry_kill(dentry, 0) may happen after or during
dentry_kill(dentry, 1), we need to recognize that (by seeing
DCACHE_DENTRY_KILLED already set), unlock everything
and either free the sucker (in case DCACHE_MAY_FREE has been
set) or leave it for ongoing dentry_kill(dentry, 1) to deal with.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Cc: "Nicholas A. Bellinger" <nab@linux-iscsi.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/dcache.c
include/linux/dcache.h

index 16f9066ca7b85dcd9eaa5a4bd660958f894a070a..099308a50d944a8ae1384750f369a097a01eee2e 100644 (file)
@@ -466,7 +466,14 @@ dentry_kill(struct dentry *dentry, int unlock_on_failure)
        __releases(dentry->d_lock)
 {
        struct inode *inode;
-       struct dentry *parent;
+       struct dentry *parent = NULL;
+       bool can_free = true;
+
+       if (unlikely(dentry->d_flags & DCACHE_DENTRY_KILLED)) {
+               can_free = dentry->d_flags & DCACHE_MAY_FREE;
+               spin_unlock(&dentry->d_lock);
+               goto out;
+       }
 
        inode = dentry->d_inode;
        if (inode && !spin_trylock(&inode->i_lock)) {
@@ -477,9 +484,7 @@ dentry_kill(struct dentry *dentry, int unlock_on_failure)
                }
                return dentry; /* try again with same dentry */
        }
-       if (IS_ROOT(dentry))
-               parent = NULL;
-       else
+       if (!IS_ROOT(dentry))
                parent = dentry->d_parent;
        if (parent && !spin_trylock(&parent->d_lock)) {
                if (inode)
@@ -502,8 +507,6 @@ dentry_kill(struct dentry *dentry, int unlock_on_failure)
        if (dentry->d_flags & DCACHE_LRU_LIST) {
                if (!(dentry->d_flags & DCACHE_SHRINK_LIST))
                        d_lru_del(dentry);
-               else
-                       d_shrink_del(dentry);
        }
        /* if it was on the hash then remove it */
        __d_drop(dentry);
@@ -525,7 +528,15 @@ dentry_kill(struct dentry *dentry, int unlock_on_failure)
        if (dentry->d_op && dentry->d_op->d_release)
                dentry->d_op->d_release(dentry);
 
-       dentry_free(dentry);
+       spin_lock(&dentry->d_lock);
+       if (dentry->d_flags & DCACHE_SHRINK_LIST) {
+               dentry->d_flags |= DCACHE_MAY_FREE;
+               can_free = false;
+       }
+       spin_unlock(&dentry->d_lock);
+out:
+       if (likely(can_free))
+               dentry_free(dentry);
        return parent;
 }
 
@@ -830,7 +841,7 @@ static void shrink_dentry_list(struct list_head *list)
                 * We found an inuse dentry which was not removed from
                 * the LRU because of laziness during lookup. Do not free it.
                 */
-               if (dentry->d_lockref.count) {
+               if ((int)dentry->d_lockref.count > 0) {
                        spin_unlock(&dentry->d_lock);
                        continue;
                }
index 0f0eb1c1e676c27a0d69415e2d0d4f1c088cf3cb..2a23ecb4f71cd50db8da2f4c6bfd8077d941b9f3 100644 (file)
@@ -221,6 +221,8 @@ struct dentry_operations {
 #define DCACHE_SYMLINK_TYPE            0x00300000 /* Symlink */
 #define DCACHE_FILE_TYPE               0x00400000 /* Other file type */
 
+#define DCACHE_MAY_FREE                        0x00800000
+
 extern seqlock_t rename_lock;
 
 static inline int dname_external(const struct dentry *dentry)