Skip to content

Commit

Permalink
autofs4: fix direct mount pending expire race
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
raven-au authored and torvalds committed Jul 24, 2008
1 parent 97e7449 commit 6e60a9a
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 26 deletions.
3 changes: 3 additions & 0 deletions fs/autofs4/autofs_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ struct autofs_info {

int flags;

struct completion expire_complete;

struct list_head active;
struct list_head expiring;

Expand All @@ -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;
Expand Down
16 changes: 13 additions & 3 deletions fs/autofs4/expire.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
72 changes: 49 additions & 23 deletions fs/autofs4/root.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand All @@ -243,17 +262,23 @@ 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);

goto cont;
}
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 ||
Expand All @@ -264,22 +289,22 @@ static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
if (status)
goto out_error;

/*
* 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;
goto follow;
}
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:
return NULL;
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit 6e60a9a

Please sign in to comment.