diff options
Diffstat (limited to 'fs/btrfs/super.c')
| -rw-r--r-- | fs/btrfs/super.c | 58 |
1 files changed, 45 insertions, 13 deletions
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index 77249e0dd578..e0ee96cc749c 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -1841,7 +1841,6 @@ static int btrfs_get_tree_super(struct fs_context *fc) struct btrfs_fs_info *fs_info = fc->s_fs_info; struct btrfs_fs_context *ctx = fc->fs_private; struct btrfs_fs_devices *fs_devices = NULL; - struct block_device *bdev; struct btrfs_device *device; struct super_block *sb; blk_mode_t mode = btrfs_open_mode(fc); @@ -1860,23 +1859,37 @@ static int btrfs_get_tree_super(struct fs_context *fc) mutex_unlock(&uuid_mutex); return PTR_ERR(device); } - fs_devices = device->fs_devices; + /* + * We cannot hold uuid_mutex calling sget_fc(), it will lead to a + * locking order reversal with s_umount. + * + * So here we increase the holding number of fs_devices, this will ensure + * the fs_devices itself won't be freed. + */ + btrfs_fs_devices_inc_holding(fs_devices); fs_info->fs_devices = fs_devices; - - ret = btrfs_open_devices(fs_devices, mode, &btrfs_fs_type); mutex_unlock(&uuid_mutex); - if (ret) - return ret; - if (!(fc->sb_flags & SB_RDONLY) && fs_devices->rw_devices == 0) - return -EACCES; - - bdev = fs_devices->latest_dev->bdev; sb = sget_fc(fc, btrfs_fc_test_super, set_anon_super_fc); - if (IS_ERR(sb)) + if (IS_ERR(sb)) { + mutex_lock(&uuid_mutex); + btrfs_fs_devices_dec_holding(fs_devices); + /* + * Since the fs_devices is not opened, it can be freed at any + * time after unlocking uuid_mutex. We need to avoid double + * free through put_fs_context()->btrfs_free_fs_info(). + * So here we reset fs_info->fs_devices to NULL, and let the + * regular fs_devices reclaim path to handle it. + * + * This applies to all later branches where no fs_devices is + * opened. + */ + fs_info->fs_devices = NULL; + mutex_unlock(&uuid_mutex); return PTR_ERR(sb); + } set_device_specific_options(fs_info); @@ -1887,11 +1900,13 @@ static int btrfs_get_tree_super(struct fs_context *fc) * * fc->s_fs_info is not touched and will be later freed by * put_fs_context() through btrfs_free_fs_context(). - * - * The fs_info->fs_devices will also be closed by btrfs_free_fs_context(). */ ASSERT(fc->s_fs_info == fs_info); + mutex_lock(&uuid_mutex); + btrfs_fs_devices_dec_holding(fs_devices); + fs_info->fs_devices = NULL; + mutex_unlock(&uuid_mutex); /* * At this stage we may have RO flag mismatch between * fc->sb_flags and sb->s_flags. Caller should detect such @@ -1899,6 +1914,8 @@ static int btrfs_get_tree_super(struct fs_context *fc) * needed. */ } else { + struct block_device *bdev; + /* * The first mount of the fs thus a new superblock, fc->s_fs_info * must be NULL, and the ownership of our fs_info and fs_devices is @@ -1906,6 +1923,21 @@ static int btrfs_get_tree_super(struct fs_context *fc) */ ASSERT(fc->s_fs_info == NULL); + mutex_lock(&uuid_mutex); + btrfs_fs_devices_dec_holding(fs_devices); + ret = btrfs_open_devices(fs_devices, mode, &btrfs_fs_type); + if (ret < 0) + fs_info->fs_devices = NULL; + mutex_unlock(&uuid_mutex); + if (ret < 0) { + deactivate_locked_super(sb); + return ret; + } + if (!(fc->sb_flags & SB_RDONLY) && fs_devices->rw_devices == 0) { + deactivate_locked_super(sb); + return -EACCES; + } + bdev = fs_devices->latest_dev->bdev; snprintf(sb->s_id, sizeof(sb->s_id), "%pg", bdev); shrinker_debugfs_rename(sb->s_shrink, "sb-btrfs:%s", sb->s_id); btrfs_sb(sb)->bdev_holder = &btrfs_fs_type; |
