Commit c44e1f78 authored by yangerkun's avatar yangerkun Committed by Zheng Zengkai
Browse files

fs/dcache.c: avoid softlock since too many negative dentry

hulk inclusion
category: bugfix
bugzilla: 185805, https://gitee.com/openeuler/kernel/issues/I4JX0L


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>

Conflicts:
	fs/dcache.c
	include/linux/dcache.h
Reviewed-by: default avatarZhang Yi <yi.zhang@huawei.com>
Signed-off-by: default avatarZheng Zengkai <zhengzengkai@huawei.com>
parent 6a8c6793
Loading
Loading
Loading
Loading
+41 −2
Original line number Original line Diff line number Diff line
@@ -314,10 +314,18 @@ static inline void __d_set_inode_and_type(struct dentry *dentry,
					  unsigned type_flags)
					  unsigned type_flags)
{
{
	unsigned 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;
	dentry->d_inode = inode;
	flags = READ_ONCE(dentry->d_flags);
	flags = READ_ONCE(dentry->d_flags);
	flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU);
	flags &= ~(DCACHE_ENTRY_TYPE | DCACHE_FALLTHRU |
			DCACHE_NEGATIVE_ACCOUNT);
	flags |= type_flags;
	flags |= type_flags;
	smp_store_release(&dentry->d_flags, flags);
	smp_store_release(&dentry->d_flags, flags);
}
}
@@ -336,6 +344,7 @@ static inline void __d_clear_type_and_inode(struct dentry *dentry)
static void dentry_free(struct dentry *dentry)
static void dentry_free(struct dentry *dentry)
{
{
	WARN_ON(!hlist_unhashed(&dentry->d_u.d_alias));
	WARN_ON(!hlist_unhashed(&dentry->d_u.d_alias));
	WARN_ON(dentry->d_flags & DCACHE_NEGATIVE_ACCOUNT);
	if (unlikely(dname_external(dentry))) {
	if (unlikely(dname_external(dentry))) {
		struct external_name *p = external_name(dentry);
		struct external_name *p = external_name(dentry);
		if (likely(atomic_dec_and_test(&p->u.count))) {
		if (likely(atomic_dec_and_test(&p->u.count))) {
@@ -573,8 +582,14 @@ static void __dentry_kill(struct dentry *dentry)
	/* if it was on the hash then remove it */
	/* if it was on the hash then remove it */
	__d_drop(dentry);
	__d_drop(dentry);
	dentry_unlist(dentry, parent);
	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);
		spin_unlock(&parent->d_lock);
	}
	if (dentry->d_inode)
	if (dentry->d_inode)
		dentry_unlink_inode(dentry);
		dentry_unlink_inode(dentry);
	else
	else
@@ -634,6 +649,8 @@ static inline struct dentry *lock_parent(struct dentry *dentry)


static inline bool retain_dentry(struct dentry *dentry)
static inline bool retain_dentry(struct dentry *dentry)
{
{
	struct dentry *parent;

	WARN_ON(d_in_lookup(dentry));
	WARN_ON(d_in_lookup(dentry));


	/* Unreachable? Get rid of it */
	/* Unreachable? Get rid of it */
@@ -651,6 +668,27 @@ static inline bool retain_dentry(struct dentry *dentry)
	if (unlikely(dentry->d_flags & DCACHE_DONTCACHE))
	if (unlikely(dentry->d_flags & DCACHE_DONTCACHE))
		return false;
		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 int 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 */
	/* retain; LRU fodder */
	dentry->d_lockref.count--;
	dentry->d_lockref.count--;
	if (unlikely(!(dentry->d_flags & DCACHE_LRU_LIST)))
	if (unlikely(!(dentry->d_flags & DCACHE_LRU_LIST)))
@@ -1749,6 +1787,7 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
	seqcount_spinlock_init(&dentry->d_seq, &dentry->d_lock);
	seqcount_spinlock_init(&dentry->d_seq, &dentry->d_lock);
	dentry->d_inode = NULL;
	dentry->d_inode = NULL;
	dentry->d_parent = dentry;
	dentry->d_parent = dentry;
	atomic_set(&dentry->d_neg_dnum, 0);
	dentry->d_sb = sb;
	dentry->d_sb = sb;
	dentry->d_op = NULL;
	dentry->d_op = NULL;
	dentry->d_fsdata = NULL;
	dentry->d_fsdata = NULL;
+4 −0
Original line number Original line Diff line number Diff line
@@ -84,6 +84,7 @@ extern struct dentry_stat_t dentry_stat;
# endif
# endif
#endif
#endif


#define NEG_DENTRY_LIMIT 16384
#define d_lock	d_lockref.lock
#define d_lock	d_lockref.lock


struct dentry {
struct dentry {
@@ -118,6 +119,8 @@ struct dentry {
		struct hlist_bl_node d_in_lookup_hash;	/* only for in-lookup ones */
		struct hlist_bl_node d_in_lookup_hash;	/* only for in-lookup ones */
	 	struct rcu_head d_rcu;
	 	struct rcu_head d_rcu;
	} d_u;
	} d_u;
	/* negative dentry under this dentry, if it's dir */
	atomic_t d_neg_dnum;
} __randomize_layout;
} __randomize_layout;


/*
/*
@@ -219,6 +222,7 @@ struct dentry_operations {
#define DCACHE_PAR_LOOKUP		0x10000000 /* being looked up (with parent locked shared) */
#define DCACHE_PAR_LOOKUP		0x10000000 /* being looked up (with parent locked shared) */
#define DCACHE_DENTRY_CURSOR		0x20000000
#define DCACHE_DENTRY_CURSOR		0x20000000
#define DCACHE_NORCU			0x40000000 /* No RCU delay for freeing */
#define DCACHE_NORCU			0x40000000 /* No RCU delay for freeing */
#define DCACHE_NEGATIVE_ACCOUNT		0x80000000


extern seqlock_t rename_lock;
extern seqlock_t rename_lock;