btrfs: fix deadlock between chunk allocation and chunk btree modifications
[platform/kernel/linux-rpi.git] / fs / btrfs / volumes.c
index 13fdc06..fa68efd 100644 (file)
@@ -530,15 +530,48 @@ error:
        return ret;
 }
 
-static bool device_path_matched(const char *path, struct btrfs_device *device)
+/*
+ * Check if the device in the path matches the device in the given struct device.
+ *
+ * Returns:
+ *   true  If it is the same device.
+ *   false If it is not the same device or on error.
+ */
+static bool device_matched(const struct btrfs_device *device, const char *path)
 {
-       int found;
+       char *device_name;
+       dev_t dev_old;
+       dev_t dev_new;
+       int ret;
+
+       /*
+        * If we are looking for a device with the matching dev_t, then skip
+        * device without a name (a missing device).
+        */
+       if (!device->name)
+               return false;
+
+       device_name = kzalloc(BTRFS_PATH_NAME_MAX, GFP_KERNEL);
+       if (!device_name)
+               return false;
 
        rcu_read_lock();
-       found = strcmp(rcu_str_deref(device->name), path);
+       scnprintf(device_name, BTRFS_PATH_NAME_MAX, "%s", rcu_str_deref(device->name));
        rcu_read_unlock();
 
-       return found == 0;
+       ret = lookup_bdev(device_name, &dev_old);
+       kfree(device_name);
+       if (ret)
+               return false;
+
+       ret = lookup_bdev(path, &dev_new);
+       if (ret)
+               return false;
+
+       if (dev_old == dev_new)
+               return true;
+
+       return false;
 }
 
 /*
@@ -571,9 +604,7 @@ static int btrfs_free_stale_devices(const char *path,
                                         &fs_devices->devices, dev_list) {
                        if (skip_device && skip_device == device)
                                continue;
-                       if (path && !device->name)
-                               continue;
-                       if (path && !device_path_matched(path, device))
+                       if (path && !device_matched(device, path))
                                continue;
                        if (fs_devices->opened) {
                                /* for an already deleted device return 0 */
@@ -1366,8 +1397,10 @@ struct btrfs_device *btrfs_scan_one_device(const char *path, fmode_t flags,
 
        bytenr_orig = btrfs_sb_offset(0);
        ret = btrfs_sb_log_location_bdev(bdev, 0, READ, &bytenr);
-       if (ret)
-               return ERR_PTR(ret);
+       if (ret) {
+               device = ERR_PTR(ret);
+               goto error_bdev_put;
+       }
 
        disk_super = btrfs_read_disk_super(bdev, bytenr, bytenr_orig);
        if (IS_ERR(disk_super)) {
@@ -1846,8 +1879,10 @@ static int btrfs_add_dev_item(struct btrfs_trans_handle *trans,
        key.type = BTRFS_DEV_ITEM_KEY;
        key.offset = device->devid;
 
+       btrfs_reserve_chunk_metadata(trans, true);
        ret = btrfs_insert_empty_item(trans, trans->fs_info->chunk_root, path,
                                      &key, sizeof(*dev_item));
+       btrfs_trans_release_chunk_metadata(trans);
        if (ret)
                goto out;
 
@@ -1924,7 +1959,9 @@ static int btrfs_rm_dev_item(struct btrfs_device *device)
        key.type = BTRFS_DEV_ITEM_KEY;
        key.offset = device->devid;
 
+       btrfs_reserve_chunk_metadata(trans, false);
        ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
+       btrfs_trans_release_chunk_metadata(trans);
        if (ret) {
                if (ret > 0)
                        ret = -ENOENT;
@@ -2480,7 +2517,9 @@ static int btrfs_finish_sprout(struct btrfs_trans_handle *trans)
        key.type = BTRFS_DEV_ITEM_KEY;
 
        while (1) {
+               btrfs_reserve_chunk_metadata(trans, false);
                ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
+               btrfs_trans_release_chunk_metadata(trans);
                if (ret < 0)
                        goto error;
 
@@ -2594,7 +2633,7 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path
        device->fs_info = fs_info;
        device->bdev = bdev;
 
-       ret = btrfs_get_dev_zone_info(device);
+       ret = btrfs_get_dev_zone_info(device, false);
        if (ret)
                goto error_free_device;
 
@@ -2828,6 +2867,7 @@ int btrfs_grow_device(struct btrfs_trans_handle *trans,
        struct btrfs_super_block *super_copy = fs_info->super_copy;
        u64 old_total;
        u64 diff;
+       int ret;
 
        if (!test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state))
                return -EACCES;
@@ -2856,7 +2896,11 @@ int btrfs_grow_device(struct btrfs_trans_handle *trans,
                              &trans->transaction->dev_update_list);
        mutex_unlock(&fs_info->chunk_mutex);
 
-       return btrfs_update_device(trans, device);
+       btrfs_reserve_chunk_metadata(trans, false);
+       ret = btrfs_update_device(trans, device);
+       btrfs_trans_release_chunk_metadata(trans);
+
+       return ret;
 }
 
 static int btrfs_free_chunk(struct btrfs_trans_handle *trans, u64 chunk_offset)
@@ -3098,7 +3142,7 @@ int btrfs_remove_chunk(struct btrfs_trans_handle *trans, u64 chunk_offset)
                const u64 sys_flags = btrfs_system_alloc_profile(fs_info);
                struct btrfs_block_group *sys_bg;
 
-               sys_bg = btrfs_alloc_chunk(trans, sys_flags);
+               sys_bg = btrfs_create_chunk(trans, sys_flags);
                if (IS_ERR(sys_bg)) {
                        ret = PTR_ERR(sys_bg);
                        btrfs_abort_transaction(trans, ret);
@@ -4356,10 +4400,12 @@ static int balance_kthread(void *data)
        struct btrfs_fs_info *fs_info = data;
        int ret = 0;
 
+       sb_start_write(fs_info->sb);
        mutex_lock(&fs_info->balance_mutex);
        if (fs_info->balance_ctl)
                ret = btrfs_balance(fs_info, fs_info->balance_ctl, NULL);
        mutex_unlock(&fs_info->balance_mutex);
+       sb_end_write(fs_info->sb);
 
        return ret;
 }
@@ -4891,8 +4937,10 @@ again:
                        round_down(old_total - diff, fs_info->sectorsize));
        mutex_unlock(&fs_info->chunk_mutex);
 
+       btrfs_reserve_chunk_metadata(trans, false);
        /* Now btrfs_update_device() will change the on-disk size. */
        ret = btrfs_update_device(trans, device);
+       btrfs_trans_release_chunk_metadata(trans);
        if (ret < 0) {
                btrfs_abort_transaction(trans, ret);
                btrfs_end_transaction(trans);
@@ -4975,7 +5023,7 @@ static void check_raid1c34_incompat_flag(struct btrfs_fs_info *info, u64 type)
 }
 
 /*
- * Structure used internally for __btrfs_alloc_chunk() function.
+ * Structure used internally for btrfs_create_chunk() function.
  * Wraps needed parameters.
  */
 struct alloc_chunk_ctl {
@@ -5379,7 +5427,7 @@ error_del_extent:
        return block_group;
 }
 
-struct btrfs_block_group *btrfs_alloc_chunk(struct btrfs_trans_handle *trans,
+struct btrfs_block_group *btrfs_create_chunk(struct btrfs_trans_handle *trans,
                                            u64 type)
 {
        struct btrfs_fs_info *info = trans->fs_info;
@@ -5580,12 +5628,12 @@ static noinline int init_first_rw_device(struct btrfs_trans_handle *trans)
         */
 
        alloc_profile = btrfs_metadata_alloc_profile(fs_info);
-       meta_bg = btrfs_alloc_chunk(trans, alloc_profile);
+       meta_bg = btrfs_create_chunk(trans, alloc_profile);
        if (IS_ERR(meta_bg))
                return PTR_ERR(meta_bg);
 
        alloc_profile = btrfs_system_alloc_profile(fs_info);
-       sys_bg = btrfs_alloc_chunk(trans, alloc_profile);
+       sys_bg = btrfs_create_chunk(trans, alloc_profile);
        if (IS_ERR(sys_bg))
                return PTR_ERR(sys_bg);
 
@@ -7561,12 +7609,12 @@ int btrfs_read_chunk_tree(struct btrfs_fs_info *fs_info)
         * do another round of validation checks.
         */
        if (total_dev != fs_info->fs_devices->total_devices) {
-               btrfs_err(fs_info,
-          "super_num_devices %llu mismatch with num_devices %llu found here",
+               btrfs_warn(fs_info,
+"super block num_devices %llu mismatch with DEV_ITEM count %llu, will be repaired on next transaction commit",
                          btrfs_super_num_devices(fs_info->super_copy),
                          total_dev);
-               ret = -EINVAL;
-               goto error;
+               fs_info->fs_devices->total_devices = total_dev;
+               btrfs_set_super_num_devices(fs_info->super_copy, total_dev);
        }
        if (btrfs_super_total_bytes(fs_info->super_copy) <
            fs_info->fs_devices->total_rw_bytes) {
@@ -8183,10 +8231,12 @@ static int relocating_repair_kthread(void *data)
        target = cache->start;
        btrfs_put_block_group(cache);
 
+       sb_start_write(fs_info->sb);
        if (!btrfs_exclop_start(fs_info, BTRFS_EXCLOP_BALANCE)) {
                btrfs_info(fs_info,
                           "zoned: skip relocating block group %llu to repair: EBUSY",
                           target);
+               sb_end_write(fs_info->sb);
                return -EBUSY;
        }
 
@@ -8214,6 +8264,7 @@ out:
                btrfs_put_block_group(cache);
        mutex_unlock(&fs_info->reclaim_bgs_lock);
        btrfs_exclop_finish(fs_info);
+       sb_end_write(fs_info->sb);
 
        return ret;
 }