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

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
...@@ -52,6 +52,8 @@ struct autofs_info { ...@@ -52,6 +52,8 @@ struct autofs_info {
int flags; int flags;
struct completion expire_complete;
struct list_head active; struct list_head active;
struct list_head expiring; struct list_head expiring;
...@@ -69,6 +71,7 @@ struct autofs_info { ...@@ -69,6 +71,7 @@ struct autofs_info {
}; };
#define AUTOFS_INF_EXPIRING (1<<0) /* dentry is in the process of expiring */ #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 { struct autofs_wait_queue {
wait_queue_head_t queue; wait_queue_head_t queue;
......
...@@ -259,13 +259,15 @@ static struct dentry *autofs4_expire_direct(struct super_block *sb, ...@@ -259,13 +259,15 @@ static struct dentry *autofs4_expire_direct(struct super_block *sb,
now = jiffies; now = jiffies;
timeout = sbi->exp_timeout; timeout = sbi->exp_timeout;
/* Lock the tree as we must expire as a whole */
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
if (!autofs4_direct_busy(mnt, root, timeout, do_now)) { if (!autofs4_direct_busy(mnt, root, timeout, do_now)) {
struct autofs_info *ino = autofs4_dentry_ino(root); struct autofs_info *ino = autofs4_dentry_ino(root);
if (d_mountpoint(root)) {
/* Set this flag early to catch sys_chdir and the like */ ino->flags |= AUTOFS_INF_MOUNTPOINT;
root->d_mounted--;
}
ino->flags |= AUTOFS_INF_EXPIRING; ino->flags |= AUTOFS_INF_EXPIRING;
init_completion(&ino->expire_complete);
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
return root; return root;
} }
...@@ -392,6 +394,7 @@ found: ...@@ -392,6 +394,7 @@ found:
expired, (int)expired->d_name.len, expired->d_name.name); expired, (int)expired->d_name.len, expired->d_name.name);
ino = autofs4_dentry_ino(expired); ino = autofs4_dentry_ino(expired);
ino->flags |= AUTOFS_INF_EXPIRING; ino->flags |= AUTOFS_INF_EXPIRING;
init_completion(&ino->expire_complete);
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
spin_lock(&dcache_lock); spin_lock(&dcache_lock);
list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child); list_move(&expired->d_parent->d_subdirs, &expired->d_u.d_child);
...@@ -429,6 +432,7 @@ int autofs4_expire_run(struct super_block *sb, ...@@ -429,6 +432,7 @@ int autofs4_expire_run(struct super_block *sb,
spin_lock(&sbi->fs_lock); spin_lock(&sbi->fs_lock);
ino = autofs4_dentry_ino(dentry); ino = autofs4_dentry_ino(dentry);
ino->flags &= ~AUTOFS_INF_EXPIRING; ino->flags &= ~AUTOFS_INF_EXPIRING;
complete_all(&ino->expire_complete);
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
return ret; return ret;
...@@ -457,8 +461,14 @@ int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt, ...@@ -457,8 +461,14 @@ int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt,
/* This is synchronous because it makes the daemon a /* This is synchronous because it makes the daemon a
little easier */ little easier */
ret = autofs4_wait(sbi, dentry, NFY_EXPIRE); ret = autofs4_wait(sbi, dentry, NFY_EXPIRE);
spin_lock(&sbi->fs_lock); 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; ino->flags &= ~AUTOFS_INF_EXPIRING;
complete_all(&ino->expire_complete);
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
dput(dentry); dput(dentry);
} }
......
...@@ -141,6 +141,7 @@ static int try_to_fill_dentry(struct dentry *dentry, int flags) ...@@ -141,6 +141,7 @@ static int try_to_fill_dentry(struct dentry *dentry, int flags)
dentry, dentry->d_name.len, dentry->d_name.name); dentry, dentry->d_name.len, dentry->d_name.name);
status = autofs4_wait(sbi, dentry, NFY_NONE); status = autofs4_wait(sbi, dentry, NFY_NONE);
wait_for_completion(&ino->expire_complete);
DPRINTK("expire done status=%d", status); DPRINTK("expire done status=%d", status);
...@@ -227,14 +228,32 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd) ...@@ -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", DPRINTK("dentry=%p %.*s oz_mode=%d nd->flags=%d",
dentry, dentry->d_name.len, dentry->d_name.name, oz_mode, dentry, dentry->d_name.len, dentry->d_name.name, oz_mode,
nd->flags); nd->flags);
/*
/* If it's our master or we shouldn't trigger a mount we're done */ * For an expire of a covered direct or offset mount we need
lookup_type = nd->flags & (TRIGGER_FLAGS | TRIGGER_INTENTS); * to beeak out of follow_down() at the autofs mount trigger
if (oz_mode || * (d_mounted--), so we can see the expiring flag, and manage
!(lookup_type || dentry->d_flags & DCACHE_AUTOFS_PENDING)) * 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; 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); spin_lock(&sbi->fs_lock);
if (ino->flags & AUTOFS_INF_EXPIRING) { if (ino->flags & AUTOFS_INF_EXPIRING) {
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
...@@ -243,6 +262,7 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd) ...@@ -243,6 +262,7 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
dentry, dentry->d_name.len, dentry->d_name.name); dentry, dentry->d_name.len, dentry->d_name.name);
status = autofs4_wait(sbi, dentry, NFY_NONE); status = autofs4_wait(sbi, dentry, NFY_NONE);
wait_for_completion(&ino->expire_complete);
DPRINTK("request done status=%d", status); DPRINTK("request done status=%d", status);
...@@ -250,10 +270,15 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd) ...@@ -250,10 +270,15 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
} }
spin_unlock(&sbi->fs_lock); spin_unlock(&sbi->fs_lock);
cont: 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 * If the dentry contains directories then it is an autofs
* autofs multi-mount with no root mount offset. So * multi-mount with no root mount offset. So don't try to
* don't try to mount it again. * mount it again.
*/ */
spin_lock(&dcache_lock); spin_lock(&dcache_lock);
if (dentry->d_flags & DCACHE_AUTOFS_PENDING || if (dentry->d_flags & DCACHE_AUTOFS_PENDING ||
...@@ -264,10 +289,14 @@ cont: ...@@ -264,10 +289,14 @@ cont:
if (status) if (status)
goto out_error; goto out_error;
goto follow;
}
spin_unlock(&dcache_lock);
follow:
/* /*
* The mount succeeded but if there is no root mount * If there is no root mount it must be an autofs
* it must be an autofs multi-mount with no root offset * multi-mount with no root offset so we don't need
* so we don't need to follow the mount. * to follow it.
*/ */
if (d_mountpoint(dentry)) { if (d_mountpoint(dentry)) {
if (!autofs4_follow_mount(&nd->path.mnt, if (!autofs4_follow_mount(&nd->path.mnt,
...@@ -277,10 +306,6 @@ cont: ...@@ -277,10 +306,6 @@ cont:
} }
} }
goto done;
}
spin_unlock(&dcache_lock);
done: done:
return NULL; return NULL;
...@@ -545,6 +570,7 @@ static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, s ...@@ -545,6 +570,7 @@ static struct dentry *autofs4_lookup(struct inode *dir, struct dentry *dentry, s
expiring, expiring->d_name.len, expiring, expiring->d_name.len,
expiring->d_name.name); expiring->d_name.name);
autofs4_wait(sbi, expiring, NFY_NONE); autofs4_wait(sbi, expiring, NFY_NONE);
wait_for_completion(&ino->expire_complete);
DPRINTK("request completed"); DPRINTK("request completed");
goto cont; goto cont;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment