From 7ba5d5d0e8b5e24a171016e517f3ee38ec638c8e Mon Sep 17 00:00:00 2001 From: yangerkun Date: Sun, 12 May 2019 23:44:23 +0800 Subject: [PATCH] fs/dcache.c: avoid softlock since too many negative dentry euler inclusion category: bugfix bugzilla: 15743 CVE: NA --------------------------- Parallel thread to add negative dentry under root dir. Sometimes later, 'systemctl daemon-reload' will report softlockup since __fsnotify_update_child_dentry_flags need update all child under root dentry without distinguish does it active or not. It will waste so long time with catching d_lock of root dentry. And other thread try to spin_lock d_lock will run overtime. Limit negative dentry under dir can avoid this. Signed-off-by: yangerkun Reviewed-by: Miao Xie Signed-off-by: Yang Yingliang --- fs/dcache.c | 44 ++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 4 ++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/fs/dcache.c b/fs/dcache.c index 6c7ff1bbf195..b7bba97d5fb5 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -316,12 +316,20 @@ static inline void __d_set_inode_and_type(struct dentry *dentry, unsigned type_flags) { unsigned flags; + struct dentry *parent; + + parent = dentry->d_parent; + if ((dentry->d_flags & DCACHE_NEGATIVE_ACCOUNT) && parent) { + WARN_ON(!inode); + atomic_dec(&parent->d_neg_dnum); + } dentry->d_inode = inode; /* paired with smp_rmb() in lookup_fast() */ smp_wmb(); flags = READ_ONCE(dentry->d_flags); - flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU); + flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU | + DCACHE_NEGATIVE_ACCOUNT); flags |= type_flags; WRITE_ONCE(dentry->d_flags, flags); } @@ -338,6 +346,7 @@ static inline void __d_clear_type_and_inode(struct dentry *dentry) static void dentry_free(struct dentry *dentry) { WARN_ON(!hlist_unhashed(&dentry->d_u.d_alias)); + WARN_ON(dentry->d_flags & DCACHE_NEGATIVE_ACCOUNT); if (unlikely(dname_external(dentry))) { struct external_name *p = external_name(dentry); if (likely(atomic_dec_and_test(&p->u.count))) { @@ -562,8 +571,14 @@ static void __dentry_kill(struct dentry *dentry) /* if it was on the hash then remove it */ __d_drop(dentry); dentry_unlist(dentry, parent); - if (parent) + if (parent) { + if (dentry->d_flags & DCACHE_NEGATIVE_ACCOUNT) { + atomic_dec(&parent->d_neg_dnum); + dentry->d_flags &= ~DCACHE_NEGATIVE_ACCOUNT; + } + spin_unlock(&parent->d_lock); + } if (dentry->d_inode) dentry_unlink_inode(dentry); else @@ -623,6 +638,8 @@ static inline struct dentry *lock_parent(struct dentry *dentry) static inline bool retain_dentry(struct dentry *dentry) { + struct dentry *parent; + WARN_ON(d_in_lookup(dentry)); /* Unreachable? Get rid of it */ @@ -636,6 +653,28 @@ static inline bool retain_dentry(struct dentry *dentry) if (dentry->d_op->d_delete(dentry)) return false; } + + if (unlikely(!dentry->d_parent)) + goto noparent; + + parent = dentry->d_parent; + /* Return false if it's negative */ + WARN_ON((atomic_read(&parent->d_neg_dnum) < 0)); + if (!dentry->d_inode) { + if (!(dentry->d_flags & DCACHE_NEGATIVE_ACCOUNT)) { + unsigned flags = READ_ONCE(dentry->d_flags); + + flags |= DCACHE_NEGATIVE_ACCOUNT; + WRITE_ONCE(dentry->d_flags, flags); + atomic_inc(&parent->d_neg_dnum); + } + } + + if (!dentry->d_inode && + atomic_read(&parent->d_neg_dnum) >= NEG_DENTRY_LIMIT) + return false; + +noparent: /* retain; LRU fodder */ dentry->d_lockref.count--; if (unlikely(!(dentry->d_flags & DCACHE_LRU_LIST))) @@ -1651,6 +1690,7 @@ struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name) seqcount_init(&dentry->d_seq); dentry->d_inode = NULL; dentry->d_parent = dentry; + atomic_set(&dentry->d_neg_dnum, 0); dentry->d_sb = sb; dentry->d_op = NULL; dentry->d_fsdata = NULL; diff --git a/include/linux/dcache.h b/include/linux/dcache.h index da7741bc959e..8aa6a03dcec2 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -84,6 +84,7 @@ extern struct dentry_stat_t dentry_stat; # endif #endif +#define NEG_DENTRY_LIMIT 16384 #define d_lock d_lockref.lock struct dentry { @@ -119,6 +120,8 @@ struct dentry { struct rcu_head d_rcu; } d_u; + /* negative dentry under this dentry, if it's dir */ + atomic_t d_neg_dnum; KABI_RESERVE(1) KABI_RESERVE(2) } __randomize_layout; @@ -225,6 +228,7 @@ struct dentry_operations { #define DCACHE_PAR_LOOKUP 0x10000000 /* being looked up (with parent locked shared) */ #define DCACHE_DENTRY_CURSOR 0x20000000 +#define DCACHE_NEGATIVE_ACCOUNT 0x40000000 extern seqlock_t rename_lock; -- GitLab