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: Ian Kent <raven@themaw.net> Cc: Jeff Moyer <jmoyer@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
@@ -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 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#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,
|
|||||||
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:
|
|||||||
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,
|
|||||||
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,
|
|||||||
/* 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)
|
|||||||
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)
|
|||||||
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;
|
||||||
|
}
|
||||||
|
spin_unlock(&sbi->fs_lock);
|
||||||
goto done;
|
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
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,22 +289,22 @@ cont:
|
|||||||
if (status)
|
if (status)
|
||||||
goto out_error;
|
goto out_error;
|
||||||
|
|
||||||
/*
|
goto 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 (d_mountpoint(dentry)) {
|
|
||||||
if (!autofs4_follow_mount(&nd->path.mnt,
|
|
||||||
&nd->path.dentry)) {
|
|
||||||
status = -ENOENT;
|
|
||||||
goto out_error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
spin_unlock(&dcache_lock);
|
spin_unlock(&dcache_lock);
|
||||||
|
follow:
|
||||||
|
/*
|
||||||
|
* 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,
|
||||||
|
&nd->path.dentry)) {
|
||||||
|
status = -ENOENT;
|
||||||
|
goto out_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user