Btrfs: fix parity scrub of RAID 5/6 with missing device
authorOmar Sandoval <osandov@fb.com>
Fri, 19 Jun 2015 18:52:52 +0000 (11:52 -0700)
committerChris Mason <clm@fb.com>
Sun, 9 Aug 2015 14:34:26 +0000 (07:34 -0700)
When testing the previous patch, Zhao Lei reported a similar bug when
attempting to scrub a degraded RAID 5/6 filesystem with a missing
device, leading to NULL pointer dereferences from the RAID 5/6 parity
scrubbing code.

The first cause was the same as in the previous patch: attempting to
call bio_add_page() on a missing block device. To fix this,
scrub_extent_for_parity() can just mark the sectors on the missing
device as errors instead of attempting to read from it.

Additionally, the code uses scrub_remap_extent() to map the extent of
the corresponding data stripe, but the extent wasn't already mapped. If
scrub_remap_extent() finds a missing block device, it doesn't initialize
extent_dev, so we're left with a NULL struct btrfs_device. The solution
is to use btrfs_map_block() directly.

Reported-by: Zhao Lei <zhaolei@cn.fujitsu.com>
Signed-off-by: Omar Sandoval <osandov@fb.com>
Signed-off-by: Chris Mason <clm@fb.com>
fs/btrfs/scrub.c

index 6bce7f2ff80520d5c6a380553574b360887ae93d..c69c75e7b841e167b83c3992b42ef207d0f10bc5 100644 (file)
@@ -2696,6 +2696,11 @@ static int scrub_extent_for_parity(struct scrub_parity *sparity,
        u8 csum[BTRFS_CSUM_SIZE];
        u32 blocksize;
 
+       if (dev->missing) {
+               scrub_parity_mark_sectors_error(sparity, logical, len);
+               return 0;
+       }
+
        if (flags & BTRFS_EXTENT_FLAG_DATA) {
                blocksize = sctx->sectorsize;
        } else if (flags & BTRFS_EXTENT_FLAG_TREE_BLOCK) {
@@ -2905,6 +2910,7 @@ static noinline_for_stack int scrub_raid56_parity(struct scrub_ctx *sctx,
        struct btrfs_root *root = fs_info->extent_root;
        struct btrfs_root *csum_root = fs_info->csum_root;
        struct btrfs_extent_item *extent;
+       struct btrfs_bio *bbio = NULL;
        u64 flags;
        int ret;
        int slot;
@@ -2914,6 +2920,7 @@ static noinline_for_stack int scrub_raid56_parity(struct scrub_ctx *sctx,
        u64 extent_logical;
        u64 extent_physical;
        u64 extent_len;
+       u64 mapped_length;
        struct btrfs_device *extent_dev;
        struct scrub_parity *sparity;
        int nsectors;
@@ -3037,10 +3044,21 @@ again:
                        scrub_parity_mark_sectors_data(sparity, extent_logical,
                                                       extent_len);
 
-                       scrub_remap_extent(fs_info, extent_logical,
-                                          extent_len, &extent_physical,
-                                          &extent_dev,
-                                          &extent_mirror_num);
+                       mapped_length = extent_len;
+                       ret = btrfs_map_block(fs_info, READ, extent_logical,
+                                             &mapped_length, &bbio, 0);
+                       if (!ret) {
+                               if (!bbio || mapped_length < extent_len)
+                                       ret = -EIO;
+                       }
+                       if (ret) {
+                               btrfs_put_bbio(bbio);
+                               goto out;
+                       }
+                       extent_physical = bbio->stripes[0].physical;
+                       extent_mirror_num = bbio->mirror_num;
+                       extent_dev = bbio->stripes[0].dev;
+                       btrfs_put_bbio(bbio);
 
                        ret = btrfs_lookup_csums_range(csum_root,
                                                extent_logical,