Skip to content

Commit 951a3f5

Browse files
adam900710kdave
authored andcommitted
btrfs: fix mount failure due to remount races
[BUG] The following reproducer can cause btrfs mount to fail: dev="/dev/test/scratch1" mnt1="/mnt/test" mnt2="/mnt/scratch" mkfs.btrfs -f $dev mount $dev $mnt1 btrfs subvolume create $mnt1/subvol1 btrfs subvolume create $mnt1/subvol2 umount $mnt1 mount $dev $mnt1 -o subvol=subvol1 while mount -o remount,ro $mnt1; do mount -o remount,rw $mnt1; done & bg=$! while mount $dev $mnt2 -o subvol=subvol2; do umount $mnt2; done kill $bg wait umount -R $mnt1 umount -R $mnt2 The script will fail with the following error: mount: /mnt/scratch: /dev/mapper/test-scratch1 already mounted on /mnt/test. dmesg(1) may have more information after failed mount system call. umount: /mnt/test: target is busy. umount: /mnt/scratch/: not mounted And there is no kernel error message. [CAUSE] During the btrfs mount, to support mounting different subvolumes with different RO/RW flags, we need to detect that and retry if needed: Retry with matching RO flags if the initial mount fail with -EBUSY. The problem is, during that retry we do not hold any super block lock (s_umount), this means there can be a remount process changing the RO flags of the original fs super block. If so, we can have an EBUSY error during retry. And this time we treat any failure as an error, without any retry and cause the above EBUSY mount failure. [FIX] The current retry behavior is racy because we do not have a super block thus no way to hold s_umount to prevent the race with remount. Solve the root problem by allowing fc->sb_flags to mismatch from the sb->s_flags at btrfs_get_tree_super(). Then at the re-entry point btrfs_get_tree_subvol(), manually check the fc->s_flags against sb->s_flags, if it's a RO->RW mismatch, then reconfigure with s_umount lock hold. Reported-by: Enno Gotthold <[email protected]> Reported-by: Fabian Vogt <[email protected]> [ Special thanks for the reproducer and early analysis pointing to btrfs. ] Fixes: f044b31 ("btrfs: handle the ro->rw transition for mounting different subvolumes") Link: https://bugzilla.suse.com/show_bug.cgi?id=1231836 Signed-off-by: Qu Wenruo <[email protected]> Reviewed-by: David Sterba <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 22d2e48 commit 951a3f5

File tree

1 file changed

+27
-39
lines changed

1 file changed

+27
-39
lines changed

fs/btrfs/super.c

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,18 +1885,21 @@ static int btrfs_get_tree_super(struct fs_context *fc)
18851885

18861886
if (sb->s_root) {
18871887
btrfs_close_devices(fs_devices);
1888-
if ((fc->sb_flags ^ sb->s_flags) & SB_RDONLY)
1889-
ret = -EBUSY;
1888+
/*
1889+
* At this stage we may have RO flag mismatch between
1890+
* fc->sb_flags and sb->s_flags. Caller should detect such
1891+
* mismatch and reconfigure with sb->s_umount rwsem held if
1892+
* needed.
1893+
*/
18901894
} else {
18911895
snprintf(sb->s_id, sizeof(sb->s_id), "%pg", bdev);
18921896
shrinker_debugfs_rename(sb->s_shrink, "sb-btrfs:%s", sb->s_id);
18931897
btrfs_sb(sb)->bdev_holder = &btrfs_fs_type;
18941898
ret = btrfs_fill_super(sb, fs_devices);
1895-
}
1896-
1897-
if (ret) {
1898-
deactivate_locked_super(sb);
1899-
return ret;
1899+
if (ret) {
1900+
deactivate_locked_super(sb);
1901+
return ret;
1902+
}
19001903
}
19011904

19021905
btrfs_clear_oneshot_options(fs_info);
@@ -1982,39 +1985,18 @@ static int btrfs_get_tree_super(struct fs_context *fc)
19821985
* btrfs or not, setting the whole super block RO. To make per-subvolume mounting
19831986
* work with different options work we need to keep backward compatibility.
19841987
*/
1985-
static struct vfsmount *btrfs_reconfigure_for_mount(struct fs_context *fc)
1988+
static int btrfs_reconfigure_for_mount(struct fs_context *fc, struct vfsmount *mnt)
19861989
{
1987-
struct vfsmount *mnt;
1988-
int ret;
1989-
const bool ro2rw = !(fc->sb_flags & SB_RDONLY);
1990-
1991-
/*
1992-
* We got an EBUSY because our SB_RDONLY flag didn't match the existing
1993-
* super block, so invert our setting here and retry the mount so we
1994-
* can get our vfsmount.
1995-
*/
1996-
if (ro2rw)
1997-
fc->sb_flags |= SB_RDONLY;
1998-
else
1999-
fc->sb_flags &= ~SB_RDONLY;
2000-
2001-
mnt = fc_mount(fc);
2002-
if (IS_ERR(mnt))
2003-
return mnt;
1990+
int ret = 0;
20041991

2005-
if (!ro2rw)
2006-
return mnt;
1992+
if (fc->sb_flags & SB_RDONLY)
1993+
return ret;
20071994

2008-
/* We need to convert to rw, call reconfigure. */
2009-
fc->sb_flags &= ~SB_RDONLY;
20101995
down_write(&mnt->mnt_sb->s_umount);
2011-
ret = btrfs_reconfigure(fc);
1996+
if (!(fc->sb_flags & SB_RDONLY) && (mnt->mnt_sb->s_flags & SB_RDONLY))
1997+
ret = btrfs_reconfigure(fc);
20121998
up_write(&mnt->mnt_sb->s_umount);
2013-
if (ret) {
2014-
mntput(mnt);
2015-
return ERR_PTR(ret);
2016-
}
2017-
return mnt;
1999+
return ret;
20182000
}
20192001

20202002
static int btrfs_get_tree_subvol(struct fs_context *fc)
@@ -2024,6 +2006,7 @@ static int btrfs_get_tree_subvol(struct fs_context *fc)
20242006
struct fs_context *dup_fc;
20252007
struct dentry *dentry;
20262008
struct vfsmount *mnt;
2009+
int ret = 0;
20272010

20282011
/*
20292012
* Setup a dummy root and fs_info for test/set super. This is because
@@ -2066,11 +2049,16 @@ static int btrfs_get_tree_subvol(struct fs_context *fc)
20662049
fc->security = NULL;
20672050

20682051
mnt = fc_mount(dup_fc);
2069-
if (PTR_ERR_OR_ZERO(mnt) == -EBUSY)
2070-
mnt = btrfs_reconfigure_for_mount(dup_fc);
2071-
put_fs_context(dup_fc);
2072-
if (IS_ERR(mnt))
2052+
if (IS_ERR(mnt)) {
2053+
put_fs_context(dup_fc);
20732054
return PTR_ERR(mnt);
2055+
}
2056+
ret = btrfs_reconfigure_for_mount(dup_fc, mnt);
2057+
put_fs_context(dup_fc);
2058+
if (ret) {
2059+
mntput(mnt);
2060+
return ret;
2061+
}
20742062

20752063
/*
20762064
* This free's ->subvol_name, because if it isn't set we have to

0 commit comments

Comments
 (0)