diff options
| author | Al Viro <viro@zeniv.linux.org.uk> | 2014-05-01 10:30:00 -0400 |
|---|---|---|
| committer | Jiri Slaby <jslaby@suse.cz> | 2014-10-30 22:17:35 +0100 |
| commit | 11662c7b4621457dd0fccd3bba23a4d9cee2f722 (patch) | |
| tree | a35bf98289718fb5873e11b168093afe11d325ee | |
| parent | 1be1a028ca20541434bf93ae5961480e34974f05 (diff) | |
dentry_kill(): don't try to remove from shrink list
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>
Signed-off-by: Jiri Slaby <jslaby@suse.cz>
| -rw-r--r-- | fs/dcache.c | 27 | ||||
| -rw-r--r-- | include/linux/dcache.h | 2 |
2 files changed, 21 insertions, 8 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index 2bb4e4f1ca6d..8647519e864a 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -490,7 +490,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)) { @@ -501,9 +508,7 @@ relock: } 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) @@ -526,8 +531,6 @@ relock: 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); @@ -549,7 +552,15 @@ relock: 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; } @@ -851,7 +862,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; } diff --git a/include/linux/dcache.h b/include/linux/dcache.h index 59066e0b4ff1..cbde0540d4dd 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -211,6 +211,8 @@ struct dentry_operations { #define DCACHE_LRU_LIST 0x80000 #define DCACHE_DENTRY_KILLED 0x100000 +#define DCACHE_MAY_FREE 0x00800000 + extern seqlock_t rename_lock; static inline int dname_external(const struct dentry *dentry) |
