Skip to content

Commit 2aaa7a9

Browse files
adam900710smb49
authored andcommitted
btrfs: fix mount failure due to remount races
BugLink: https://bugs.launchpad.net/bugs/2102118 [ Upstream commit 951a3f5 ] [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 <egotthold@suse.com> Reported-by: Fabian Vogt <fvogt@suse.com> [ 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 <wqu@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com> Signed-off-by: Sasha Levin <sashal@kernel.org> Signed-off-by: Koichiro Den <koichiro.den@canonical.com> Signed-off-by: Stefan Bader <stefan.bader@canonical.com>
1 parent 180cfca commit 2aaa7a9

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
@@ -1866,18 +1866,21 @@ static int btrfs_get_tree_super(struct fs_context *fc)
18661866

18671867
if (sb->s_root) {
18681868
btrfs_close_devices(fs_devices);
1869-
if ((fc->sb_flags ^ sb->s_flags) & SB_RDONLY)
1870-
ret = -EBUSY;
1869+
/*
1870+
* At this stage we may have RO flag mismatch between
1871+
* fc->sb_flags and sb->s_flags. Caller should detect such
1872+
* mismatch and reconfigure with sb->s_umount rwsem held if
1873+
* needed.
1874+
*/
18711875
} else {
18721876
snprintf(sb->s_id, sizeof(sb->s_id), "%pg", bdev);
18731877
shrinker_debugfs_rename(sb->s_shrink, "sb-btrfs:%s", sb->s_id);
18741878
btrfs_sb(sb)->bdev_holder = &btrfs_fs_type;
18751879
ret = btrfs_fill_super(sb, fs_devices);
1876-
}
1877-
1878-
if (ret) {
1879-
deactivate_locked_super(sb);
1880-
return ret;
1880+
if (ret) {
1881+
deactivate_locked_super(sb);
1882+
return ret;
1883+
}
18811884
}
18821885

18831886
btrfs_clear_oneshot_options(fs_info);
@@ -1963,39 +1966,18 @@ static int btrfs_get_tree_super(struct fs_context *fc)
19631966
* btrfs or not, setting the whole super block RO. To make per-subvolume mounting
19641967
* work with different options work we need to keep backward compatibility.
19651968
*/
1966-
static struct vfsmount *btrfs_reconfigure_for_mount(struct fs_context *fc)
1969+
static int btrfs_reconfigure_for_mount(struct fs_context *fc, struct vfsmount *mnt)
19671970
{
1968-
struct vfsmount *mnt;
1969-
int ret;
1970-
const bool ro2rw = !(fc->sb_flags & SB_RDONLY);
1971-
1972-
/*
1973-
* We got an EBUSY because our SB_RDONLY flag didn't match the existing
1974-
* super block, so invert our setting here and retry the mount so we
1975-
* can get our vfsmount.
1976-
*/
1977-
if (ro2rw)
1978-
fc->sb_flags |= SB_RDONLY;
1979-
else
1980-
fc->sb_flags &= ~SB_RDONLY;
1981-
1982-
mnt = fc_mount(fc);
1983-
if (IS_ERR(mnt))
1984-
return mnt;
1971+
int ret = 0;
19851972

1986-
if (!ro2rw)
1987-
return mnt;
1973+
if (fc->sb_flags & SB_RDONLY)
1974+
return ret;
19881975

1989-
/* We need to convert to rw, call reconfigure. */
1990-
fc->sb_flags &= ~SB_RDONLY;
19911976
down_write(&mnt->mnt_sb->s_umount);
1992-
ret = btrfs_reconfigure(fc);
1977+
if (!(fc->sb_flags & SB_RDONLY) && (mnt->mnt_sb->s_flags & SB_RDONLY))
1978+
ret = btrfs_reconfigure(fc);
19931979
up_write(&mnt->mnt_sb->s_umount);
1994-
if (ret) {
1995-
mntput(mnt);
1996-
return ERR_PTR(ret);
1997-
}
1998-
return mnt;
1980+
return ret;
19991981
}
20001982

20011983
static int btrfs_get_tree_subvol(struct fs_context *fc)
@@ -2005,6 +1987,7 @@ static int btrfs_get_tree_subvol(struct fs_context *fc)
20051987
struct fs_context *dup_fc;
20061988
struct dentry *dentry;
20071989
struct vfsmount *mnt;
1990+
int ret = 0;
20081991

20091992
/*
20101993
* Setup a dummy root and fs_info for test/set super. This is because
@@ -2047,11 +2030,16 @@ static int btrfs_get_tree_subvol(struct fs_context *fc)
20472030
fc->security = NULL;
20482031

20492032
mnt = fc_mount(dup_fc);
2050-
if (PTR_ERR_OR_ZERO(mnt) == -EBUSY)
2051-
mnt = btrfs_reconfigure_for_mount(dup_fc);
2052-
put_fs_context(dup_fc);
2053-
if (IS_ERR(mnt))
2033+
if (IS_ERR(mnt)) {
2034+
put_fs_context(dup_fc);
20542035
return PTR_ERR(mnt);
2036+
}
2037+
ret = btrfs_reconfigure_for_mount(dup_fc, mnt);
2038+
put_fs_context(dup_fc);
2039+
if (ret) {
2040+
mntput(mnt);
2041+
return ret;
2042+
}
20552043

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

0 commit comments

Comments
 (0)