Commit 7ba5d5d0 authored by yangerkun's avatar yangerkun Committed by Xie XiuQi
Browse files

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: default avataryangerkun <yangerkun@huawei.com>
Reviewed-by: default avatarMiao Xie <miaoxie@huawei.com>
Signed-off-by: default avatarYang Yingliang <yangyingliang@huawei.com>
parent 3cd16d87
Loading
Loading
Loading
Loading
+42 −2
Original line number Diff line number Diff line
@@ -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;
+4 −0
Original line number Diff line number Diff line
@@ -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;