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

Commit 6e60a9ab authored by Ian Kent's avatar Ian Kent Committed by Linus Torvalds
Browse files

autofs4: fix direct mount pending expire race



For direct and offset type mounts that are covered by another mount we
cannot check the AUTOFS_INF_EXPIRING flag during a path walk which leads
to lookups walking into an expiring mount while it is being expired.

For example, for the direct multi-mount map entry with a couple of
offsets:

/race/mm1  /      <server1>:/<path1>
           /om1   <server2>:/<path2>
           /om2   <server1>:/<path3>

an autofs trigger mount is mounted on /race/mm1 and when accessed it is
over mounted and trigger mounts made for /race/mm1/om1 and /race/mm1/om2.
So it isn't possible for path walks to see the expiring flag at all and
they happily walk into the file system while it is expiring.

When expiring these mounts follow_down() must stop at the autofs mount and
all processes must block in the ->follow_link() method (except the daemon)
until the expire is complete.  This is done by decrementing the d_mounted
field of the autofs trigger mount root dentry until the expire is
completed.  In ->follow_link() all processes wait on the expire and the
mount following is completed for the daemon until the expire is complete.

Signed-off-by: default avatarIan Kent <raven@themaw.net>
Cc: Jeff Moyer <jmoyer@redhat.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 97e7449a
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -52,6 +52,8 @@ struct autofs_info {

	int		flags;

	struct completion expire_complete;

	struct list_head active;
	struct list_head expiring;

@@ -69,6 +71,7 @@ struct autofs_info {
};

#define AUTOFS_INF_EXPIRING	(1<<0) /* dentry is in the process of expiring */
#define AUTOFS_INF_MOUNTPOINT	(1<<1) /* mountpoint status for direct expire */

struct autofs_wait_queue {
	wait_queue_head_t queue;
+13 −3
Original line number Diff line number Diff line
@@ -259,13 +259,15 @@ static struct dentry *autofs4_expire_direct(struct super_block *sb,
	now = jiffies;
	timeout = sbi->exp_timeout;

	/* Lock the tree as we must expire as a whole */
	spin_lock(&sbi->fs_lock);
	if (!autofs4_direct_busy(mnt, root, timeout, do_now)) {
		struct autofs_info *ino = autofs4_dentry_ino(root);

		/* Set this flag early to catch sys_chdir and the like */
		if (d_mountpoint(root)) {
			ino->flags |= AUTOFS_INF_MOUNTPOINT;
			root->d_mounted--;
		}
		ino->flags |= AUTOFS_INF_EXPIRING;
		init_completion(&ino->expire_complete);
		spin_unlock(&sbi->fs_lock);
		return root;
	}
@@ -392,6 +394,7 @@ static struct dentry *autofs4_expire_indirect(struct super_block *sb,
		expired, (int)expired->d_name.len, expired->d_name.name);
	ino = autofs4_dentry_ino(expired);
	ino->flags |= AUTOFS_INF_EXPIRING;
	init_completion(&ino->expire_complete);
	spin_unlock(&sbi->fs_lock);
	spin_lock(&dcache_lock);
	list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child);
@@ -429,6 +432,7 @@ int autofs4_expire_run(struct super_block *sb,
	spin_lock(&sbi->fs_lock);
	ino = autofs4_dentry_ino(dentry);
	ino->flags &= ~AUTOFS_INF_EXPIRING;
	complete_all(&ino->expire_complete);
	spin_unlock(&sbi->fs_lock);

	return ret;
@@ -457,8 +461,14 @@ int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt,
		/* This is synchronous because it makes the daemon a
                   little easier */
		ret = autofs4_wait(sbi, dentry, NFY_EXPIRE);

		spin_lock(&sbi->fs_lock);
		if (ino->flags & AUTOFS_INF_MOUNTPOINT) {
			sb->s_root->d_mounted++;
			ino->flags &= ~AUTOFS_INF_MOUNTPOINT;
		}
		ino->flags &= ~AUTOFS_INF_EXPIRING;
		complete_all(&ino->expire_complete);
		spin_unlock(&sbi->fs_lock);
		dput(dentry);
	}
+49 −23
Original line number Diff line number Diff line
@@ -141,6 +141,7 @@ static int try_to_fill_dentry(struct dentry *dentry, int flags)
			 dentry, dentry->d_name.len, dentry->d_name.name);

		status = autofs4_wait(sbi, dentry, NFY_NONE);
		wait_for_completion(&ino->expire_complete);

		DPRINTK("expire done status=%d", status);

@@ -227,14 +228,32 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
	DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d",
		dentry, dentry->d_name.len, dentry->d_name.name, oz_mode,
		nd->flags);

	/* If it's our master or we shouldn't trigger a mount we're done */
	lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS);
	if (oz_mode ||
	    !(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING))
	/*
	 * For an expire of a covered direct or offset mount we need
	 * to beeak out of follow_down() at the autofs mount trigger
	 * (d_mounted--), so we can see the expiring flag, and manage
	 * the blocking and following here until the expire is completed.
	 */
	if (oz_mode) {
		spin_lock(&sbi->fs_lock);
		if (ino->flags & AUTOFS_INF_EXPIRING) {
			spin_unlock(&sbi->fs_lock);
			/* Follow down to our covering mount. */
			if (!follow_down(&nd->path.mnt, &nd->path.dentry))
				goto done;
			/*
			 * We shouldn't need to do this but we have no way
			 * of knowing what may have been done so try a follow
			 * just in case.
			 */
			autofs4_follow_mount(&nd->path.mnt, &nd->path.dentry);
			goto done;
		}
		spin_unlock(&sbi->fs_lock);
		goto done;
	}

	/* If an expire request is pending wait for it. */
	/* If an expire request is pending everyone must wait. */
	spin_lock(&sbi->fs_lock);
	if (ino->flags & AUTOFS_INF_EXPIRING) {
		spin_unlock(&sbi->fs_lock);
@@ -243,6 +262,7 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
			dentry, dentry->d_name.len, dentry->d_name.name);

		status = autofs4_wait(sbi, dentry, NFY_NONE);
		wait_for_completion(&ino->expire_complete);

		DPRINTK("request done status=%d", status);

@@ -250,10 +270,15 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
	}
	spin_unlock(&sbi->fs_lock);
cont:
	/* We trigger a mount for almost all flags */
	lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS);
	if (!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING))
		goto done;

	/*
	 * If the dentry contains directories then it is an
	 * autofs multi-mount with no root mount offset. So
	 * don't try to mount it again.
	 * If the dentry contains directories then it is an autofs
	 * multi-mount with no root mount offset. So don't try to
	 * mount it again.
	 */
	spin_lock(&dcache_lock);
	if (dentry->d_flags & DCACHE_AUTOFS_PENDING ||
@@ -264,10 +289,14 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
		if (status)
			goto out_error;

		goto follow;
	}
	spin_unlock(&dcache_lock);
follow:
	/*
		 * The mount succeeded but if there is no root mount
		 * it must be an autofs multi-mount with no root offset
		 * so we don't need to follow the mount.
	 * If there is no root mount it must be an autofs
	 * multi-mount with no root offset so we don't need
	 * to follow it.
	 */
	if (d_mountpoint(dentry)) {
		if (!autofs4_follow_mount(&nd->path.mnt,
@@ -277,10 +306,6 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
		}
	}

		goto done;
	}
	spin_unlock(&dcache_lock);

done:
	return NULL;

@@ -545,6 +570,7 @@ static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, s
				expiring, expiring->d_name.len,
				expiring->d_name.name);
			autofs4_wait(sbi, expiring, NFY_NONE);
			wait_for_completion(&ino->expire_complete);
			DPRINTK("request completed");
			goto cont;
		}