All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/2] autofs races
@ 2016-09-27 10:07 Ian Kent
  2016-09-27 10:07 ` [PATCH 2/2] autofs: use dentry flags to block walks during expire Ian Kent
  0 siblings, 1 reply; 3+ messages in thread
From: Ian Kent @ 2016-09-27 10:07 UTC (permalink / raw)
  To: stable
  Cc: tiwai, gregkh, NeilBrown, Al Viro, Andrew Morton, Linus Torvalds,
	Ian Kent

From: Al Viro <viro@zeniv.linux.org.uk>

Original Linus tree commit id: ea01a18494

* make autofs4_expire_indirect() skip the dentries being in process of
expiry
* do *not* mess with list_move(); making sure that dentry with
AUTOFS_INF_EXPIRING are not picked for expiry is enough.
* do not remove NO_RCU when we set EXPIRING, don't bother with smp_mb()
there.  Clear it at the same time we clear EXPIRING.  Makes a bunch of
tests simpler.
* rename NO_RCU to WANT_EXPIRE, which is what it really is.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
---
 fs/autofs4/autofs_i.h |    8 ++++++--
 fs/autofs4/expire.c   |   27 ++++++++-------------------
 fs/autofs4/root.c     |    2 +-
 3 files changed, 15 insertions(+), 22 deletions(-)

diff --git a/fs/autofs4/autofs_i.h b/fs/autofs4/autofs_i.h
index c37149b..502d389 100644
--- a/fs/autofs4/autofs_i.h
+++ b/fs/autofs4/autofs_i.h
@@ -79,9 +79,13 @@ struct autofs_info {
 };
 
 #define AUTOFS_INF_EXPIRING	(1<<0) /* dentry is 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 */
 
diff --git a/fs/autofs4/expire.c b/fs/autofs4/expire.c
index 1cebc3c..4182d3e 100644
--- a/fs/autofs4/expire.c
+++ b/fs/autofs4/expire.c
@@ -315,19 +315,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);
@@ -444,7 +442,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);
@@ -453,7 +451,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);
@@ -463,7 +461,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);
@@ -473,17 +471,8 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
 found:
 	DPRINTK("returning %p %pd", 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;
 }
 
@@ -494,7 +483,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;
@@ -551,7 +540,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);
 
@@ -579,7 +568,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);
diff --git a/fs/autofs4/root.c b/fs/autofs4/root.c
index c6d7d3d..7a54c6a 100644
--- a/fs/autofs4/root.c
+++ b/fs/autofs4/root.c
@@ -455,7 +455,7 @@ static int autofs4_d_manage(struct dentry *dentry, bool rcu_walk)
 		 * a mount-trap.
 		 */
 		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;


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 2/2] autofs: use dentry flags to block walks during expire
  2016-09-27 10:07 [PATCH 1/2] autofs races Ian Kent
@ 2016-09-27 10:07 ` Ian Kent
  2016-09-27 14:22   ` Greg KH
  0 siblings, 1 reply; 3+ messages in thread
From: Ian Kent @ 2016-09-27 10:07 UTC (permalink / raw)
  To: stable
  Cc: tiwai, gregkh, NeilBrown, Al Viro, Andrew Morton, Linus Torvalds,
	Ian Kent

Original Linus tree commit id: 7cbdb4a286

Somewhere along the way the autofs expire operation has changed to hold
a spin lock over expired dentry selection.  The autofs indirect mount
expired dentry selection is complicated and quite lengthy so it isn't
appropriate to hold a spin lock over the operation.

Commit 47be61845c77 ("fs/dcache.c: avoid soft-lockup in dput()") added a
might_sleep() to dput() causing a WARN_ONCE() about this usage to be
issued.

But the spin lock doesn't need to be held over this check, the autofs
dentry info.  flags are enough to block walks into dentrys during the
expire.

I've left the direct mount expire as it is (for now) because it is much
simpler and quicker than the indirect mount expire and adding spin lock
release and re-aquires would do nothing more than add overhead.

Fixes: 47be61845c77 ("fs/dcache.c: avoid soft-lockup in dput()")
Link: http://lkml.kernel.org/r/20160912014017.1773.73060.stgit@pluto.themaw.net
Signed-off-by: Ian Kent <raven@themaw.net>
Reported-by: Takashi Iwai <tiwai@suse.de>
Tested-by: Takashi Iwai <tiwai@suse.de>
Cc: Takashi Iwai <tiwai@suse.de>
Cc: NeilBrown <neilb@suse.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
---
 fs/autofs4/expire.c |   55 +++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 42 insertions(+), 13 deletions(-)

diff --git a/fs/autofs4/expire.c b/fs/autofs4/expire.c
index 4182d3e..5b4c4d4 100644
--- a/fs/autofs4/expire.c
+++ b/fs/autofs4/expire.c
@@ -415,6 +415,7 @@ static struct dentry *should_expire(struct dentry *dentry,
 	}
 	return NULL;
 }
+
 /*
  * Find an eligible tree to time-out
  * A tree is eligible if :-
@@ -430,6 +431,7 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
 	struct dentry *root = sb->s_root;
 	struct dentry *dentry;
 	struct dentry *expired;
+	struct dentry *found;
 	struct autofs_info *ino;
 
 	if (!root)
@@ -440,31 +442,46 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
 
 	dentry = NULL;
 	while ((dentry = get_next_positive_subdir(dentry, root))) {
+		int flags = how;
+
 		spin_lock(&sbi->fs_lock);
 		ino = autofs4_dentry_ino(dentry);
-		if (ino->flags & AUTOFS_INF_WANT_EXPIRE)
-			expired = NULL;
-		else
-			expired = should_expire(dentry, mnt, timeout, how);
-		if (!expired) {
+		if (ino->flags & AUTOFS_INF_WANT_EXPIRE) {
 			spin_unlock(&sbi->fs_lock);
 			continue;
 		}
+		spin_unlock(&sbi->fs_lock);
+
+		expired = should_expire(dentry, mnt, timeout, flags);
+		if (!expired)
+			continue;
+
+		spin_lock(&sbi->fs_lock);
 		ino = autofs4_dentry_ino(expired);
 		ino->flags |= AUTOFS_INF_WANT_EXPIRE;
 		spin_unlock(&sbi->fs_lock);
 		synchronize_rcu();
-		spin_lock(&sbi->fs_lock);
-		if (should_expire(expired, mnt, timeout, how)) {
-			if (expired != dentry)
-				dput(dentry);
-			goto found;
-		}
 
+		/* Make sure a reference is not taken on found if
+		 * things have changed.
+		 */
+		flags &= ~AUTOFS_EXP_LEAVES;
+		found = should_expire(expired, mnt, timeout, how);
+		if (!found || found != expired)
+			/* Something has changed, continue */
+			goto next;
+
+		if (expired != dentry)
+			dput(dentry);
+
+		spin_lock(&sbi->fs_lock);
+		goto found;
+next:
+		spin_lock(&sbi->fs_lock);
 		ino->flags &= ~AUTOFS_INF_WANT_EXPIRE;
+		spin_unlock(&sbi->fs_lock);
 		if (expired != dentry)
 			dput(expired);
-		spin_unlock(&sbi->fs_lock);
 	}
 	return NULL;
 
@@ -481,6 +498,7 @@ int autofs4_expire_wait(struct dentry *dentry, int rcu_walk)
 	struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
 	struct autofs_info *ino = autofs4_dentry_ino(dentry);
 	int status;
+	int state;
 
 	/* Block on any pending expire */
 	if (!(ino->flags & AUTOFS_INF_WANT_EXPIRE))
@@ -488,8 +506,19 @@ int autofs4_expire_wait(struct dentry *dentry, int rcu_walk)
 	if (rcu_walk)
 		return -ECHILD;
 
+retry:
 	spin_lock(&sbi->fs_lock);
-	if (ino->flags & AUTOFS_INF_EXPIRING) {
+	state = ino->flags & (AUTOFS_INF_WANT_EXPIRE | AUTOFS_INF_EXPIRING);
+	if (state & AUTOFS_INF_EXPIRING) {
+		spin_unlock(&sbi->fs_lock);
+		/*
+		 * Possibly being selected for expire, wait until
+		 * it's selected or not.
+		 */
+		schedule_timeout_uninterruptible(HZ/10);
+		goto retry;
+	}
+	if (state & AUTOFS_INF_EXPIRING) {
 		spin_unlock(&sbi->fs_lock);
 
 		DPRINTK("waiting for expire %p name=%pd", dentry, dentry);


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH 2/2] autofs: use dentry flags to block walks during expire
  2016-09-27 10:07 ` [PATCH 2/2] autofs: use dentry flags to block walks during expire Ian Kent
@ 2016-09-27 14:22   ` Greg KH
  0 siblings, 0 replies; 3+ messages in thread
From: Greg KH @ 2016-09-27 14:22 UTC (permalink / raw)
  To: Ian Kent; +Cc: stable, tiwai, NeilBrown, Al Viro, Andrew Morton, Linus Torvalds

On Tue, Sep 27, 2016 at 06:07:32PM +0800, Ian Kent wrote:
> Original Linus tree commit id: 7cbdb4a286
> 
> Somewhere along the way the autofs expire operation has changed to hold
> a spin lock over expired dentry selection.  The autofs indirect mount
> expired dentry selection is complicated and quite lengthy so it isn't
> appropriate to hold a spin lock over the operation.
> 
> Commit 47be61845c77 ("fs/dcache.c: avoid soft-lockup in dput()") added a
> might_sleep() to dput() causing a WARN_ONCE() about this usage to be
> issued.
> 
> But the spin lock doesn't need to be held over this check, the autofs
> dentry info.  flags are enough to block walks into dentrys during the
> expire.
> 
> I've left the direct mount expire as it is (for now) because it is much
> simpler and quicker than the indirect mount expire and adding spin lock
> release and re-aquires would do nothing more than add overhead.
> 
> Fixes: 47be61845c77 ("fs/dcache.c: avoid soft-lockup in dput()")
> Link: http://lkml.kernel.org/r/20160912014017.1773.73060.stgit@pluto.themaw.net
> Signed-off-by: Ian Kent <raven@themaw.net>
> Reported-by: Takashi Iwai <tiwai@suse.de>
> Tested-by: Takashi Iwai <tiwai@suse.de>
> Cc: Takashi Iwai <tiwai@suse.de>
> Cc: NeilBrown <neilb@suse.com>
> Cc: Al Viro <viro@zeniv.linux.org.uk>
> Cc: <stable@vger.kernel.org>
> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
> ---
>  fs/autofs4/expire.c |   55 +++++++++++++++++++++++++++++++++++++++------------
>  1 file changed, 42 insertions(+), 13 deletions(-)
> 
> diff --git a/fs/autofs4/expire.c b/fs/autofs4/expire.c
> index 4182d3e..5b4c4d4 100644
> --- a/fs/autofs4/expire.c
> +++ b/fs/autofs4/expire.c
> @@ -415,6 +415,7 @@ static struct dentry *should_expire(struct dentry *dentry,
>  	}
>  	return NULL;
>  }
> +
>  /*
>   * Find an eligible tree to time-out
>   * A tree is eligible if :-
> @@ -430,6 +431,7 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
>  	struct dentry *root = sb->s_root;
>  	struct dentry *dentry;
>  	struct dentry *expired;
> +	struct dentry *found;
>  	struct autofs_info *ino;
>  
>  	if (!root)
> @@ -440,31 +442,46 @@ struct dentry *autofs4_expire_indirect(struct super_block *sb,
>  
>  	dentry = NULL;
>  	while ((dentry = get_next_positive_subdir(dentry, root))) {
> +		int flags = how;
> +
>  		spin_lock(&sbi->fs_lock);
>  		ino = autofs4_dentry_ino(dentry);
> -		if (ino->flags & AUTOFS_INF_WANT_EXPIRE)
> -			expired = NULL;
> -		else
> -			expired = should_expire(dentry, mnt, timeout, how);
> -		if (!expired) {
> +		if (ino->flags & AUTOFS_INF_WANT_EXPIRE) {
>  			spin_unlock(&sbi->fs_lock);
>  			continue;
>  		}
> +		spin_unlock(&sbi->fs_lock);
> +
> +		expired = should_expire(dentry, mnt, timeout, flags);
> +		if (!expired)
> +			continue;
> +
> +		spin_lock(&sbi->fs_lock);
>  		ino = autofs4_dentry_ino(expired);
>  		ino->flags |= AUTOFS_INF_WANT_EXPIRE;
>  		spin_unlock(&sbi->fs_lock);
>  		synchronize_rcu();
> -		spin_lock(&sbi->fs_lock);
> -		if (should_expire(expired, mnt, timeout, how)) {
> -			if (expired != dentry)
> -				dput(dentry);
> -			goto found;
> -		}
>  
> +		/* Make sure a reference is not taken on found if
> +		 * things have changed.
> +		 */
> +		flags &= ~AUTOFS_EXP_LEAVES;
> +		found = should_expire(expired, mnt, timeout, how);
> +		if (!found || found != expired)
> +			/* Something has changed, continue */
> +			goto next;
> +
> +		if (expired != dentry)
> +			dput(dentry);
> +
> +		spin_lock(&sbi->fs_lock);
> +		goto found;
> +next:
> +		spin_lock(&sbi->fs_lock);
>  		ino->flags &= ~AUTOFS_INF_WANT_EXPIRE;
> +		spin_unlock(&sbi->fs_lock);
>  		if (expired != dentry)
>  			dput(expired);
> -		spin_unlock(&sbi->fs_lock);
>  	}
>  	return NULL;
>  
> @@ -481,6 +498,7 @@ int autofs4_expire_wait(struct dentry *dentry, int rcu_walk)
>  	struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
>  	struct autofs_info *ino = autofs4_dentry_ino(dentry);
>  	int status;
> +	int state;
>  
>  	/* Block on any pending expire */
>  	if (!(ino->flags & AUTOFS_INF_WANT_EXPIRE))
> @@ -488,8 +506,19 @@ int autofs4_expire_wait(struct dentry *dentry, int rcu_walk)
>  	if (rcu_walk)
>  		return -ECHILD;
>  
> +retry:
>  	spin_lock(&sbi->fs_lock);
> -	if (ino->flags & AUTOFS_INF_EXPIRING) {
> +	state = ino->flags & (AUTOFS_INF_WANT_EXPIRE | AUTOFS_INF_EXPIRING);
> +	if (state & AUTOFS_INF_EXPIRING) {

The upstream patch says:
	if (state == AUTOFS_INF_WANT_EXPIRE) {
here, why change it?

> +		spin_unlock(&sbi->fs_lock);
> +		/*
> +		 * Possibly being selected for expire, wait until
> +		 * it's selected or not.
> +		 */
> +		schedule_timeout_uninterruptible(HZ/10);
> +		goto retry;
> +	}
> +	if (state & AUTOFS_INF_EXPIRING) {

Now you are checking the same thing again?

Are you sure this backport is correct?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2016-09-27 14:22 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-09-27 10:07 [PATCH 1/2] autofs races Ian Kent
2016-09-27 10:07 ` [PATCH 2/2] autofs: use dentry flags to block walks during expire Ian Kent
2016-09-27 14:22   ` Greg KH

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.