Btrfs: Force stripesize to the value of sectorsize
[cascardo/linux.git] / fs / btrfs / volumes.c
index 41bc384..64eec2c 100644 (file)
@@ -1487,7 +1487,7 @@ again:
                extent = btrfs_item_ptr(leaf, path->slots[0],
                                        struct btrfs_dev_extent);
        } else {
-               btrfs_std_error(root->fs_info, ret, "Slot search failed");
+               btrfs_handle_fs_error(root->fs_info, ret, "Slot search failed");
                goto out;
        }
 
@@ -1495,7 +1495,7 @@ again:
 
        ret = btrfs_del_item(trans, root, path);
        if (ret) {
-               btrfs_std_error(root->fs_info, ret,
+               btrfs_handle_fs_error(root->fs_info, ret,
                            "Failed to remove dev extent item");
        } else {
                set_bit(BTRFS_TRANS_HAVE_FREE_BGS, &trans->transaction->flags);
@@ -1756,10 +1756,49 @@ static int btrfs_check_raid_min_devices(struct btrfs_fs_info *fs_info,
        return 0;
 }
 
+struct btrfs_device *btrfs_find_next_active_device(struct btrfs_fs_devices *fs_devs,
+                                       struct btrfs_device *device)
+{
+       struct btrfs_device *next_device;
+
+       list_for_each_entry(next_device, &fs_devs->devices, dev_list) {
+               if (next_device != device &&
+                       !next_device->missing && next_device->bdev)
+                       return next_device;
+       }
+
+       return NULL;
+}
+
+/*
+ * Helper function to check if the given device is part of s_bdev / latest_bdev
+ * and replace it with the provided or the next active device, in the context
+ * where this function called, there should be always be another device (or
+ * this_dev) which is active.
+ */
+void btrfs_assign_next_active_device(struct btrfs_fs_info *fs_info,
+               struct btrfs_device *device, struct btrfs_device *this_dev)
+{
+       struct btrfs_device *next_device;
+
+       if (this_dev)
+               next_device = this_dev;
+       else
+               next_device = btrfs_find_next_active_device(fs_info->fs_devices,
+                                                               device);
+       ASSERT(next_device);
+
+       if (fs_info->sb->s_bdev &&
+                       (fs_info->sb->s_bdev == device->bdev))
+               fs_info->sb->s_bdev = next_device->bdev;
+
+       if (fs_info->fs_devices->latest_bdev == device->bdev)
+               fs_info->fs_devices->latest_bdev = next_device->bdev;
+}
+
 int btrfs_rm_device(struct btrfs_root *root, char *device_path, u64 devid)
 {
        struct btrfs_device *device;
-       struct btrfs_device *next_device;
        struct btrfs_fs_devices *cur_devices;
        u64 num_devices;
        int ret = 0;
@@ -1846,13 +1885,7 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path, u64 devid)
        if (device->missing)
                device->fs_devices->missing_devices--;
 
-       next_device = list_entry(root->fs_info->fs_devices->devices.next,
-                                struct btrfs_device, dev_list);
-       if (root->fs_info->sb->s_bdev &&
-               (root->fs_info->sb->s_bdev == device->bdev))
-               root->fs_info->sb->s_bdev = next_device->bdev;
-       if (device->bdev == root->fs_info->fs_devices->latest_bdev)
-               root->fs_info->fs_devices->latest_bdev = next_device->bdev;
+       btrfs_assign_next_active_device(root->fs_info, device, NULL);
 
        if (device->bdev) {
                device->fs_devices->open_devices--;
@@ -1937,11 +1970,8 @@ void btrfs_rm_dev_replace_remove_srcdev(struct btrfs_fs_info *fs_info,
        if (srcdev->missing)
                fs_devices->missing_devices--;
 
-       if (srcdev->writeable) {
+       if (srcdev->writeable)
                fs_devices->rw_devices--;
-               /* zero out the old super if it is writable */
-               btrfs_scratch_superblocks(srcdev->bdev, srcdev->name->str);
-       }
 
        if (srcdev->bdev)
                fs_devices->open_devices--;
@@ -1952,6 +1982,10 @@ void btrfs_rm_dev_replace_free_srcdev(struct btrfs_fs_info *fs_info,
 {
        struct btrfs_fs_devices *fs_devices = srcdev->fs_devices;
 
+       if (srcdev->writeable) {
+               /* zero out the old super if it is writable */
+               btrfs_scratch_superblocks(srcdev->bdev, srcdev->name->str);
+       }
        call_rcu(&srcdev->rcu, free_device);
 
        /*
@@ -1981,33 +2015,33 @@ void btrfs_rm_dev_replace_free_srcdev(struct btrfs_fs_info *fs_info,
 void btrfs_destroy_dev_replace_tgtdev(struct btrfs_fs_info *fs_info,
                                      struct btrfs_device *tgtdev)
 {
-       struct btrfs_device *next_device;
-
        mutex_lock(&uuid_mutex);
        WARN_ON(!tgtdev);
        mutex_lock(&fs_info->fs_devices->device_list_mutex);
 
        btrfs_sysfs_rm_device_link(fs_info->fs_devices, tgtdev);
 
-       if (tgtdev->bdev) {
-               btrfs_scratch_superblocks(tgtdev->bdev, tgtdev->name->str);
+       if (tgtdev->bdev)
                fs_info->fs_devices->open_devices--;
-       }
+
        fs_info->fs_devices->num_devices--;
 
-       next_device = list_entry(fs_info->fs_devices->devices.next,
-                                struct btrfs_device, dev_list);
-       if (fs_info->sb->s_bdev &&
-               (tgtdev->bdev == fs_info->sb->s_bdev))
-               fs_info->sb->s_bdev = next_device->bdev;
-       if (tgtdev->bdev == fs_info->fs_devices->latest_bdev)
-               fs_info->fs_devices->latest_bdev = next_device->bdev;
-       list_del_rcu(&tgtdev->dev_list);
+       btrfs_assign_next_active_device(fs_info, tgtdev, NULL);
 
-       call_rcu(&tgtdev->rcu, free_device);
+       list_del_rcu(&tgtdev->dev_list);
 
        mutex_unlock(&fs_info->fs_devices->device_list_mutex);
        mutex_unlock(&uuid_mutex);
+
+       /*
+        * The update_dev_time() with in btrfs_scratch_superblocks()
+        * may lead to a call to btrfs_show_devname() which will try
+        * to hold device_list_mutex. And here this device
+        * is already out of device list, so we don't have to hold
+        * the device_list_mutex lock.
+        */
+       btrfs_scratch_superblocks(tgtdev->bdev, tgtdev->name->str);
+       call_rcu(&tgtdev->rcu, free_device);
 }
 
 static int btrfs_find_device_by_path(struct btrfs_root *root, char *device_path,
@@ -2156,7 +2190,7 @@ static int btrfs_prepare_sprout(struct btrfs_root *root)
 }
 
 /*
- * strore the expected generation for seed devices in device items.
+ * Store the expected generation for seed devices in device items.
  */
 static int btrfs_finish_sprout(struct btrfs_trans_handle *trans,
                               struct btrfs_root *root)
@@ -2409,7 +2443,7 @@ int btrfs_init_new_device(struct btrfs_root *root, char *device_path)
 
                ret = btrfs_relocate_sys_chunks(root);
                if (ret < 0)
-                       btrfs_std_error(root->fs_info, ret,
+                       btrfs_handle_fs_error(root->fs_info, ret,
                                    "Failed to relocate sys chunks after "
                                    "device initialization. This can be fixed "
                                    "using the \"btrfs balance\" command.");
@@ -2654,7 +2688,7 @@ static int btrfs_free_chunk(struct btrfs_trans_handle *trans,
        if (ret < 0)
                goto out;
        else if (ret > 0) { /* Logic error or corruption */
-               btrfs_std_error(root->fs_info, -ENOENT,
+               btrfs_handle_fs_error(root->fs_info, -ENOENT,
                            "Failed lookup while freeing chunk.");
                ret = -ENOENT;
                goto out;
@@ -2662,7 +2696,7 @@ static int btrfs_free_chunk(struct btrfs_trans_handle *trans,
 
        ret = btrfs_del_item(trans, root, path);
        if (ret < 0)
-               btrfs_std_error(root->fs_info, ret,
+               btrfs_handle_fs_error(root->fs_info, ret,
                            "Failed to delete chunk item.");
 out:
        btrfs_free_path(path);
@@ -2727,6 +2761,7 @@ int btrfs_remove_chunk(struct btrfs_trans_handle *trans,
        u64 dev_extent_len = 0;
        u64 chunk_objectid = BTRFS_FIRST_CHUNK_TREE_OBJECTID;
        int i, ret = 0;
+       struct btrfs_fs_devices *fs_devices = root->fs_info->fs_devices;
 
        /* Just in case */
        root = root->fs_info->chunk_root;
@@ -2753,12 +2788,19 @@ int btrfs_remove_chunk(struct btrfs_trans_handle *trans,
        check_system_chunk(trans, extent_root, map->type);
        unlock_chunks(root->fs_info->chunk_root);
 
+       /*
+        * Take the device list mutex to prevent races with the final phase of
+        * a device replace operation that replaces the device object associated
+        * with map stripes (dev-replace.c:btrfs_dev_replace_finishing()).
+        */
+       mutex_lock(&fs_devices->device_list_mutex);
        for (i = 0; i < map->num_stripes; i++) {
                struct btrfs_device *device = map->stripes[i].dev;
                ret = btrfs_free_dev_extent(trans, device,
                                            map->stripes[i].physical,
                                            &dev_extent_len);
                if (ret) {
+                       mutex_unlock(&fs_devices->device_list_mutex);
                        btrfs_abort_transaction(trans, root, ret);
                        goto out;
                }
@@ -2777,11 +2819,14 @@ int btrfs_remove_chunk(struct btrfs_trans_handle *trans,
                if (map->stripes[i].dev) {
                        ret = btrfs_update_device(trans, map->stripes[i].dev);
                        if (ret) {
+                               mutex_unlock(&fs_devices->device_list_mutex);
                                btrfs_abort_transaction(trans, root, ret);
                                goto out;
                        }
                }
        }
+       mutex_unlock(&fs_devices->device_list_mutex);
+
        ret = btrfs_free_chunk(trans, root, chunk_objectid, chunk_offset);
        if (ret) {
                btrfs_abort_transaction(trans, root, ret);
@@ -2848,7 +2893,7 @@ static int btrfs_relocate_chunk(struct btrfs_root *root, u64 chunk_offset)
                                                     chunk_offset);
        if (IS_ERR(trans)) {
                ret = PTR_ERR(trans);
-               btrfs_std_error(root->fs_info, ret, NULL);
+               btrfs_handle_fs_error(root->fs_info, ret, NULL);
                return ret;
        }
 
@@ -3353,7 +3398,7 @@ static int should_balance_chunk(struct btrfs_root *root,
        } else if ((bargs->flags & BTRFS_BALANCE_ARGS_LIMIT_RANGE)) {
                /*
                 * Same logic as the 'limit' filter; the minimum cannot be
-                * determined here because we do not have the global informatoin
+                * determined here because we do not have the global information
                 * about the count of all chunks that satisfy the filters.
                 */
                if (bargs->limit_max == 0)
@@ -3393,6 +3438,7 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info)
        u32 count_meta = 0;
        u32 count_sys = 0;
        int chunk_reserved = 0;
+       u64 bytes_used = 0;
 
        /* step one make some room on all the devices */
        devices = &fs_info->fs_devices->devices;
@@ -3531,7 +3577,13 @@ again:
                        goto loop;
                }
 
-               if ((chunk_type & BTRFS_BLOCK_GROUP_DATA) && !chunk_reserved) {
+               ASSERT(fs_info->data_sinfo);
+               spin_lock(&fs_info->data_sinfo->lock);
+               bytes_used = fs_info->data_sinfo->bytes_used;
+               spin_unlock(&fs_info->data_sinfo->lock);
+
+               if ((chunk_type & BTRFS_BLOCK_GROUP_DATA) &&
+                   !chunk_reserved && !bytes_used) {
                        trans = btrfs_start_transaction(chunk_root, 0);
                        if (IS_ERR(trans)) {
                                mutex_unlock(&fs_info->delete_unused_bgs_mutex);
@@ -3623,7 +3675,7 @@ static void __cancel_balance(struct btrfs_fs_info *fs_info)
        unset_balance_control(fs_info);
        ret = del_balance_item(fs_info->tree_root);
        if (ret)
-               btrfs_std_error(fs_info, ret, NULL);
+               btrfs_handle_fs_error(fs_info, ret, NULL);
 
        atomic_set(&fs_info->mutually_exclusive_operation_running, 0);
 }
@@ -3684,10 +3736,8 @@ int btrfs_balance(struct btrfs_balance_control *bctl,
                num_devices--;
        }
        btrfs_dev_replace_unlock(&fs_info->dev_replace, 0);
-       allowed = BTRFS_AVAIL_ALLOC_BIT_SINGLE;
-       if (num_devices == 1)
-               allowed |= BTRFS_BLOCK_GROUP_DUP;
-       else if (num_devices > 1)
+       allowed = BTRFS_AVAIL_ALLOC_BIT_SINGLE | BTRFS_BLOCK_GROUP_DUP;
+       if (num_devices > 1)
                allowed |= (BTRFS_BLOCK_GROUP_RAID0 | BTRFS_BLOCK_GROUP_RAID1);
        if (num_devices > 2)
                allowed |= BTRFS_BLOCK_GROUP_RAID5;
@@ -4191,6 +4241,7 @@ int btrfs_create_uuid_tree(struct btrfs_fs_info *fs_info)
        if (IS_ERR(uuid_root)) {
                ret = PTR_ERR(uuid_root);
                btrfs_abort_transaction(trans, tree_root, ret);
+               btrfs_end_transaction(trans, tree_root);
                return ret;
        }
 
@@ -4643,12 +4694,12 @@ static int __btrfs_alloc_chunk(struct btrfs_trans_handle *trans,
 
        if (type & BTRFS_BLOCK_GROUP_RAID5) {
                raid_stripe_len = find_raid56_stripe_len(ndevs - 1,
-                                btrfs_super_stripesize(info->super_copy));
+                                               extent_root->stripesize);
                data_stripes = num_stripes - 1;
        }
        if (type & BTRFS_BLOCK_GROUP_RAID6) {
                raid_stripe_len = find_raid56_stripe_len(ndevs - 2,
-                                btrfs_super_stripesize(info->super_copy));
+                                               extent_root->stripesize);
                data_stripes = num_stripes - 2;
        }
 
@@ -5269,7 +5320,15 @@ static int __btrfs_map_block(struct btrfs_fs_info *fs_info, int rw,
        stripe_nr = div64_u64(stripe_nr, stripe_len);
 
        stripe_offset = stripe_nr * stripe_len;
-       BUG_ON(offset < stripe_offset);
+       if (offset < stripe_offset) {
+               btrfs_crit(fs_info, "stripe math has gone wrong, "
+                          "stripe_offset=%llu, offset=%llu, start=%llu, "
+                          "logical=%llu, stripe_len=%llu",
+                          stripe_offset, offset, em->start, logical,
+                          stripe_len);
+               free_extent_map(em);
+               return -EINVAL;
+       }
 
        /* stripe_offset is the offset of this block in its stripe*/
        stripe_offset = offset - stripe_offset;
@@ -5510,7 +5569,13 @@ static int __btrfs_map_block(struct btrfs_fs_info *fs_info, int rw,
                                &stripe_index);
                mirror_num = stripe_index + 1;
        }
-       BUG_ON(stripe_index >= map->num_stripes);
+       if (stripe_index >= map->num_stripes) {
+               btrfs_crit(fs_info, "stripe index math went horribly wrong, "
+                          "got stripe_index=%u, num_stripes=%u",
+                          stripe_index, map->num_stripes);
+               ret = -EINVAL;
+               goto out;
+       }
 
        num_alloc_stripes = num_stripes;
        if (dev_replace_is_ongoing) {
@@ -5709,20 +5774,17 @@ static int __btrfs_map_block(struct btrfs_fs_info *fs_info, int rw,
                        }
                }
                if (found) {
-                       if (physical_of_found + map->stripe_len <=
-                           dev_replace->cursor_left) {
-                               struct btrfs_bio_stripe *tgtdev_stripe =
-                                       bbio->stripes + num_stripes;
+                       struct btrfs_bio_stripe *tgtdev_stripe =
+                               bbio->stripes + num_stripes;
 
-                               tgtdev_stripe->physical = physical_of_found;
-                               tgtdev_stripe->length =
-                                       bbio->stripes[index_srcdev].length;
-                               tgtdev_stripe->dev = dev_replace->tgtdev;
-                               bbio->tgtdev_map[index_srcdev] = num_stripes;
+                       tgtdev_stripe->physical = physical_of_found;
+                       tgtdev_stripe->length =
+                               bbio->stripes[index_srcdev].length;
+                       tgtdev_stripe->dev = dev_replace->tgtdev;
+                       bbio->tgtdev_map[index_srcdev] = num_stripes;
 
-                               tgtdev_indexes++;
-                               num_stripes++;
-                       }
+                       tgtdev_indexes++;
+                       num_stripes++;
                }
        }
 
@@ -6023,7 +6085,7 @@ static void bbio_error(struct btrfs_bio *bbio, struct bio *bio, u64 logical)
 {
        atomic_inc(&bbio->error);
        if (atomic_dec_and_test(&bbio->stripes_pending)) {
-               /* Shoud be the original bio. */
+               /* Should be the original bio. */
                WARN_ON(bio != bbio->orig_bio);
 
                btrfs_io_bio(bio)->mirror_num = bbio->mirror_num;
@@ -6197,27 +6259,23 @@ struct btrfs_device *btrfs_alloc_device(struct btrfs_fs_info *fs_info,
        return dev;
 }
 
-static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key,
-                         struct extent_buffer *leaf,
-                         struct btrfs_chunk *chunk)
+/* Return -EIO if any error, otherwise return 0. */
+static int btrfs_check_chunk_valid(struct btrfs_root *root,
+                                  struct extent_buffer *leaf,
+                                  struct btrfs_chunk *chunk, u64 logical)
 {
-       struct btrfs_mapping_tree *map_tree = &root->fs_info->mapping_tree;
-       struct map_lookup *map;
-       struct extent_map *em;
-       u64 logical;
        u64 length;
        u64 stripe_len;
-       u64 devid;
-       u8 uuid[BTRFS_UUID_SIZE];
-       int num_stripes;
-       int ret;
-       int i;
+       u16 num_stripes;
+       u16 sub_stripes;
+       u64 type;
 
-       logical = key->offset;
        length = btrfs_chunk_length(leaf, chunk);
        stripe_len = btrfs_chunk_stripe_len(leaf, chunk);
        num_stripes = btrfs_chunk_num_stripes(leaf, chunk);
-       /* Validation check */
+       sub_stripes = btrfs_chunk_sub_stripes(leaf, chunk);
+       type = btrfs_chunk_type(leaf, chunk);
+
        if (!num_stripes) {
                btrfs_err(root->fs_info, "invalid chunk num_stripes: %u",
                          num_stripes);
@@ -6228,24 +6286,70 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key,
                          "invalid chunk logical %llu", logical);
                return -EIO;
        }
+       if (btrfs_chunk_sector_size(leaf, chunk) != root->sectorsize) {
+               btrfs_err(root->fs_info, "invalid chunk sectorsize %u",
+                         btrfs_chunk_sector_size(leaf, chunk));
+               return -EIO;
+       }
        if (!length || !IS_ALIGNED(length, root->sectorsize)) {
                btrfs_err(root->fs_info,
                        "invalid chunk length %llu", length);
                return -EIO;
        }
-       if (!is_power_of_2(stripe_len)) {
+       if (!is_power_of_2(stripe_len) || stripe_len != BTRFS_STRIPE_LEN) {
                btrfs_err(root->fs_info, "invalid chunk stripe length: %llu",
                          stripe_len);
                return -EIO;
        }
        if (~(BTRFS_BLOCK_GROUP_TYPE_MASK | BTRFS_BLOCK_GROUP_PROFILE_MASK) &
-           btrfs_chunk_type(leaf, chunk)) {
+           type) {
                btrfs_err(root->fs_info, "unrecognized chunk type: %llu",
                          ~(BTRFS_BLOCK_GROUP_TYPE_MASK |
                            BTRFS_BLOCK_GROUP_PROFILE_MASK) &
                          btrfs_chunk_type(leaf, chunk));
                return -EIO;
        }
+       if ((type & BTRFS_BLOCK_GROUP_RAID10 && sub_stripes != 2) ||
+           (type & BTRFS_BLOCK_GROUP_RAID1 && num_stripes < 1) ||
+           (type & BTRFS_BLOCK_GROUP_RAID5 && num_stripes < 2) ||
+           (type & BTRFS_BLOCK_GROUP_RAID6 && num_stripes < 3) ||
+           (type & BTRFS_BLOCK_GROUP_DUP && num_stripes > 2) ||
+           ((type & BTRFS_BLOCK_GROUP_PROFILE_MASK) == 0 &&
+            num_stripes != 1)) {
+               btrfs_err(root->fs_info,
+                       "invalid num_stripes:sub_stripes %u:%u for profile %llu",
+                       num_stripes, sub_stripes,
+                       type & BTRFS_BLOCK_GROUP_PROFILE_MASK);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key,
+                         struct extent_buffer *leaf,
+                         struct btrfs_chunk *chunk)
+{
+       struct btrfs_mapping_tree *map_tree = &root->fs_info->mapping_tree;
+       struct map_lookup *map;
+       struct extent_map *em;
+       u64 logical;
+       u64 length;
+       u64 stripe_len;
+       u64 devid;
+       u8 uuid[BTRFS_UUID_SIZE];
+       int num_stripes;
+       int ret;
+       int i;
+
+       logical = key->offset;
+       length = btrfs_chunk_length(leaf, chunk);
+       stripe_len = btrfs_chunk_stripe_len(leaf, chunk);
+       num_stripes = btrfs_chunk_num_stripes(leaf, chunk);
+
+       ret = btrfs_check_chunk_valid(root, leaf, chunk, logical);
+       if (ret)
+               return ret;
 
        read_lock(&map_tree->map_tree.lock);
        em = lookup_extent_mapping(&map_tree->map_tree, logical, 1);
@@ -6493,6 +6597,7 @@ int btrfs_read_sys_array(struct btrfs_root *root)
        u32 array_size;
        u32 len = 0;
        u32 cur_offset;
+       u64 type;
        struct btrfs_key key;
 
        ASSERT(BTRFS_SUPER_INFO_SIZE <= root->nodesize);
@@ -6502,12 +6607,12 @@ int btrfs_read_sys_array(struct btrfs_root *root)
         * overallocate but we can keep it as-is, only the first page is used.
         */
        sb = btrfs_find_create_tree_block(root, BTRFS_SUPER_INFO_OFFSET);
-       if (!sb)
-               return -ENOMEM;
+       if (IS_ERR(sb))
+               return PTR_ERR(sb);
        set_extent_buffer_uptodate(sb);
        btrfs_set_buffer_lockdep_class(root->root_key.objectid, sb, 0);
        /*
-        * The sb extent buffer is artifical and just used to read the system array.
+        * The sb extent buffer is artificial and just used to read the system array.
         * set_extent_buffer_uptodate() call does not properly mark all it's
         * pages up-to-date when the page is larger: extent does not cover the
         * whole page and consequently check_page_uptodate does not find all
@@ -6559,6 +6664,15 @@ int btrfs_read_sys_array(struct btrfs_root *root)
                                break;
                        }
 
+                       type = btrfs_chunk_type(sb, chunk);
+                       if ((type & BTRFS_BLOCK_GROUP_SYSTEM) == 0) {
+                               btrfs_err(root->fs_info,
+                           "invalid chunk type %llu in sys_array at offset %u",
+                                       type, cur_offset);
+                               ret = -EIO;
+                               break;
+                       }
+
                        len = btrfs_chunk_item_size(num_stripes);
                        if (cur_offset + len > array_size)
                                goto out_short_read;
@@ -6577,13 +6691,15 @@ int btrfs_read_sys_array(struct btrfs_root *root)
                sb_array_offset += len;
                cur_offset += len;
        }
-       free_extent_buffer(sb);
+       clear_extent_buffer_uptodate(sb);
+       free_extent_buffer_stale(sb);
        return ret;
 
 out_short_read:
        printk(KERN_ERR "BTRFS: sys_array too short to read %u bytes at offset %u\n",
                        len, cur_offset);
-       free_extent_buffer(sb);
+       clear_extent_buffer_uptodate(sb);
+       free_extent_buffer_stale(sb);
        return -EIO;
 }
 
@@ -6595,6 +6711,7 @@ int btrfs_read_chunk_tree(struct btrfs_root *root)
        struct btrfs_key found_key;
        int ret;
        int slot;
+       u64 total_dev = 0;
 
        root = root->fs_info->chunk_root;
 
@@ -6636,6 +6753,7 @@ int btrfs_read_chunk_tree(struct btrfs_root *root)
                        ret = read_one_dev(root, leaf, dev_item);
                        if (ret)
                                goto error;
+                       total_dev++;
                } else if (found_key.type == BTRFS_CHUNK_ITEM_KEY) {
                        struct btrfs_chunk *chunk;
                        chunk = btrfs_item_ptr(leaf, slot, struct btrfs_chunk);
@@ -6645,6 +6763,28 @@ int btrfs_read_chunk_tree(struct btrfs_root *root)
                }
                path->slots[0]++;
        }
+
+       /*
+        * After loading chunk tree, we've got all device information,
+        * do another round of validation checks.
+        */
+       if (total_dev != root->fs_info->fs_devices->total_devices) {
+               btrfs_err(root->fs_info,
+          "super_num_devices %llu mismatch with num_devices %llu found here",
+                         btrfs_super_num_devices(root->fs_info->super_copy),
+                         total_dev);
+               ret = -EINVAL;
+               goto error;
+       }
+       if (btrfs_super_total_bytes(root->fs_info->super_copy) <
+           root->fs_info->fs_devices->total_rw_bytes) {
+               btrfs_err(root->fs_info,
+       "super_total_bytes %llu mismatch with fs_devices total_rw_bytes %llu",
+                         btrfs_super_total_bytes(root->fs_info->super_copy),
+                         root->fs_info->fs_devices->total_rw_bytes);
+               ret = -EINVAL;
+               goto error;
+       }
        ret = 0;
 error:
        unlock_chunks(root);