Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 67016f6c authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull vfs fixes from Al Viro:
 "A couple more of d_walk()/d_subdirs reordering fixes (stable fodder;
  ought to solve that crap for good) and a fix for a brown paperbag bug
  in d_alloc_parallel() (this cycle)"

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  fix idiotic braino in d_alloc_parallel()
  autofs races
  much milder d_walk() race
parents f780f00d e7d6ef97
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -70,9 +70,13 @@ struct autofs_info {
};

#define AUTOFS_INF_EXPIRING	(1<<0) /* dentry in the process of expiring */
#define AUTOFS_INF_NO_RCU	(1<<1) /* the dentry is being considered
#define AUTOFS_INF_WANT_EXPIRE	(1<<1) /* the dentry is being considered
					* for expiry, so RCU_walk is
					* not permitted
					* not permitted.  If it progresses to
					* actual expiry attempt, the flag is
					* not cleared when EXPIRING is set -
					* in that case it gets cleared only
					* when it comes to clearing EXPIRING.
					*/
#define AUTOFS_INF_PENDING	(1<<2) /* dentry pending mount */

+8 −19
Original line number Diff line number Diff line
@@ -316,19 +316,17 @@ struct dentry *autofs4_expire_direct(struct super_block *sb,
	if (ino->flags & AUTOFS_INF_PENDING)
		goto out;
	if (!autofs4_direct_busy(mnt, root, timeout, do_now)) {
		ino->flags |= AUTOFS_INF_NO_RCU;
		ino->flags |= AUTOFS_INF_WANT_EXPIRE;
		spin_unlock(&sbi->fs_lock);
		synchronize_rcu();
		spin_lock(&sbi->fs_lock);
		if (!autofs4_direct_busy(mnt, root, timeout, do_now)) {
			ino->flags |= AUTOFS_INF_EXPIRING;
			smp_mb();
			ino->flags &= ~AUTOFS_INF_NO_RCU;
			init_completion(&ino->expire_complete);
			spin_unlock(&sbi->fs_lock);
			return root;
		}
		ino->flags &= ~AUTOFS_INF_NO_RCU;
		ino->flags &= ~AUTOFS_INF_WANT_EXPIRE;
	}
out:
	spin_unlock(&sbi->fs_lock);
@@ -446,7 +444,7 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
	while ((dentry = get_next_positive_subdir(dentry, root))) {
		spin_lock(&sbi->fs_lock);
		ino = autofs4_dentry_ino(dentry);
		if (ino->flags & AUTOFS_INF_NO_RCU)
		if (ino->flags & AUTOFS_INF_WANT_EXPIRE)
			expired = NULL;
		else
			expired = should_expire(dentry, mnt, timeout, how);
@@ -455,7 +453,7 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
			continue;
		}
		ino = autofs4_dentry_ino(expired);
		ino->flags |= AUTOFS_INF_NO_RCU;
		ino->flags |= AUTOFS_INF_WANT_EXPIRE;
		spin_unlock(&sbi->fs_lock);
		synchronize_rcu();
		spin_lock(&sbi->fs_lock);
@@ -465,7 +463,7 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
			goto found;
		}

		ino->flags &= ~AUTOFS_INF_NO_RCU;
		ino->flags &= ~AUTOFS_INF_WANT_EXPIRE;
		if (expired != dentry)
			dput(expired);
		spin_unlock(&sbi->fs_lock);
@@ -475,17 +473,8 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
found:
	pr_debug("returning %p %pd\n", expired, expired);
	ino->flags |= AUTOFS_INF_EXPIRING;
	smp_mb();
	ino->flags &= ~AUTOFS_INF_NO_RCU;
	init_completion(&ino->expire_complete);
	spin_unlock(&sbi->fs_lock);
	spin_lock(&sbi->lookup_lock);
	spin_lock(&expired->d_parent->d_lock);
	spin_lock_nested(&expired->d_lock, DENTRY_D_LOCK_NESTED);
	list_move(&expired->d_parent->d_subdirs, &expired->d_child);
	spin_unlock(&expired->d_lock);
	spin_unlock(&expired->d_parent->d_lock);
	spin_unlock(&sbi->lookup_lock);
	return expired;
}

@@ -496,7 +485,7 @@ int autofs4_expire_wait(struct dentry *dentry, int rcu_walk)
	int status;

	/* Block on any pending expire */
	if (!(ino->flags & (AUTOFS_INF_EXPIRING | AUTOFS_INF_NO_RCU)))
	if (!(ino->flags & AUTOFS_INF_WANT_EXPIRE))
		return 0;
	if (rcu_walk)
		return -ECHILD;
@@ -554,7 +543,7 @@ int autofs4_expire_run(struct super_block *sb,
	ino = autofs4_dentry_ino(dentry);
	/* avoid rapid-fire expire attempts if expiry fails */
	ino->last_used = now;
	ino->flags &= ~AUTOFS_INF_EXPIRING;
	ino->flags &= ~(AUTOFS_INF_EXPIRING|AUTOFS_INF_WANT_EXPIRE);
	complete_all(&ino->expire_complete);
	spin_unlock(&sbi->fs_lock);

@@ -583,7 +572,7 @@ int autofs4_do_expire_multi(struct super_block *sb, struct vfsmount *mnt,
		spin_lock(&sbi->fs_lock);
		/* avoid rapid-fire expire attempts if expiry fails */
		ino->last_used = now;
		ino->flags &= ~AUTOFS_INF_EXPIRING;
		ino->flags &= ~(AUTOFS_INF_EXPIRING|AUTOFS_INF_WANT_EXPIRE);
		complete_all(&ino->expire_complete);
		spin_unlock(&sbi->fs_lock);
		dput(dentry);
+1 −1
Original line number Diff line number Diff line
@@ -458,7 +458,7 @@ static int autofs4_d_manage(struct dentry *dentry, bool rcu_walk)
		 */
		struct inode *inode;

		if (ino->flags & (AUTOFS_INF_EXPIRING | AUTOFS_INF_NO_RCU))
		if (ino->flags & AUTOFS_INF_WANT_EXPIRE)
			return 0;
		if (d_mountpoint(dentry))
			return 0;
+64 −11
Original line number Diff line number Diff line
@@ -507,6 +507,44 @@ void d_drop(struct dentry *dentry)
}
EXPORT_SYMBOL(d_drop);

static inline void dentry_unlist(struct dentry *dentry, struct dentry *parent)
{
	struct dentry *next;
	/*
	 * Inform d_walk() and shrink_dentry_list() that we are no longer
	 * attached to the dentry tree
	 */
	dentry->d_flags |= DCACHE_DENTRY_KILLED;
	if (unlikely(list_empty(&dentry->d_child)))
		return;
	__list_del_entry(&dentry->d_child);
	/*
	 * Cursors can move around the list of children.  While we'd been
	 * a normal list member, it didn't matter - ->d_child.next would've
	 * been updated.  However, from now on it won't be and for the
	 * things like d_walk() it might end up with a nasty surprise.
	 * Normally d_walk() doesn't care about cursors moving around -
	 * ->d_lock on parent prevents that and since a cursor has no children
	 * of its own, we get through it without ever unlocking the parent.
	 * There is one exception, though - if we ascend from a child that
	 * gets killed as soon as we unlock it, the next sibling is found
	 * using the value left in its ->d_child.next.  And if _that_
	 * pointed to a cursor, and cursor got moved (e.g. by lseek())
	 * before d_walk() regains parent->d_lock, we'll end up skipping
	 * everything the cursor had been moved past.
	 *
	 * Solution: make sure that the pointer left behind in ->d_child.next
	 * points to something that won't be moving around.  I.e. skip the
	 * cursors.
	 */
	while (dentry->d_child.next != &parent->d_subdirs) {
		next = list_entry(dentry->d_child.next, struct dentry, d_child);
		if (likely(!(next->d_flags & DCACHE_DENTRY_CURSOR)))
			break;
		dentry->d_child.next = next->d_child.next;
	}
}

static void __dentry_kill(struct dentry *dentry)
{
	struct dentry *parent = NULL;
@@ -532,12 +570,7 @@ static void __dentry_kill(struct dentry *dentry)
	}
	/* if it was on the hash then remove it */
	__d_drop(dentry);
	__list_del_entry(&dentry->d_child);
	/*
	 * Inform d_walk() that we are no longer attached to the
	 * dentry tree
	 */
	dentry->d_flags |= DCACHE_DENTRY_KILLED;
	dentry_unlist(dentry, parent);
	if (parent)
		spin_unlock(&parent->d_lock);
	dentry_iput(dentry);
@@ -1203,6 +1236,9 @@ static void d_walk(struct dentry *parent, void *data,
		struct dentry *dentry = list_entry(tmp, struct dentry, d_child);
		next = tmp->next;

		if (unlikely(dentry->d_flags & DCACHE_DENTRY_CURSOR))
			continue;

		spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);

		ret = enter(data, dentry);
@@ -1651,6 +1687,16 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
}
EXPORT_SYMBOL(d_alloc);

struct dentry *d_alloc_cursor(struct dentry * parent)
{
	struct dentry *dentry = __d_alloc(parent->d_sb, NULL);
	if (dentry) {
		dentry->d_flags |= DCACHE_RCUACCESS | DCACHE_DENTRY_CURSOR;
		dentry->d_parent = dget(parent);
	}
	return dentry;
}

/**
 * d_alloc_pseudo - allocate a dentry (for lookup-less filesystems)
 * @sb: the superblock
@@ -2457,7 +2503,6 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
		rcu_read_unlock();
		goto retry;
	}
	rcu_read_unlock();
	/*
	 * No changes for the parent since the beginning of d_lookup().
	 * Since all removals from the chain happen with hlist_bl_lock(),
@@ -2470,8 +2515,6 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
			continue;
		if (dentry->d_parent != parent)
			continue;
		if (d_unhashed(dentry))
			continue;
		if (parent->d_flags & DCACHE_OP_COMPARE) {
			int tlen = dentry->d_name.len;
			const char *tname = dentry->d_name.name;
@@ -2483,9 +2526,18 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
			if (dentry_cmp(dentry, str, len))
				continue;
		}
		dget(dentry);
		hlist_bl_unlock(b);
		/* somebody is doing lookup for it right now; wait for it */
		/* now we can try to grab a reference */
		if (!lockref_get_not_dead(&dentry->d_lockref)) {
			rcu_read_unlock();
			goto retry;
		}

		rcu_read_unlock();
		/*
		 * somebody is likely to be still doing lookup for it;
		 * wait for them to finish
		 */
		spin_lock(&dentry->d_lock);
		d_wait_lookup(dentry);
		/*
@@ -2516,6 +2568,7 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
		dput(new);
		return dentry;
	}
	rcu_read_unlock();
	/* we can't take ->d_lock here; it's OK, though. */
	new->d_flags |= DCACHE_PAR_LOOKUP;
	new->d_wait = wq;
+1 −0
Original line number Diff line number Diff line
@@ -130,6 +130,7 @@ extern int invalidate_inodes(struct super_block *, bool);
extern struct dentry *__d_alloc(struct super_block *, const struct qstr *);
extern int d_set_mounted(struct dentry *dentry);
extern long prune_dcache_sb(struct super_block *sb, struct shrink_control *sc);
extern struct dentry *d_alloc_cursor(struct dentry *);

/*
 * read_write.c
Loading