btrfs: scrub: introduce a helper to verify one metadata block
[sfrench/cifs-2.6.git] / fs / btrfs / scrub.c
index 56f8c54102b0e475fe984a3030a6d12f96fbd134..034d2ad05a929c27c2581e121071a26a0a4833b9 100644 (file)
@@ -2159,6 +2159,112 @@ static int scrub_checksum_data(struct scrub_block *sblock)
        return sblock->checksum_error;
 }
 
+static struct page *scrub_stripe_get_page(struct scrub_stripe *stripe, int sector_nr)
+{
+       struct btrfs_fs_info *fs_info = stripe->bg->fs_info;
+       int page_index = (sector_nr << fs_info->sectorsize_bits) >> PAGE_SHIFT;
+
+       return stripe->pages[page_index];
+}
+
+static unsigned int scrub_stripe_get_page_offset(struct scrub_stripe *stripe,
+                                                int sector_nr)
+{
+       struct btrfs_fs_info *fs_info = stripe->bg->fs_info;
+
+       return offset_in_page(sector_nr << fs_info->sectorsize_bits);
+}
+
+void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr)
+{
+       struct btrfs_fs_info *fs_info = stripe->bg->fs_info;
+       const u32 sectors_per_tree = fs_info->nodesize >> fs_info->sectorsize_bits;
+       const u64 logical = stripe->logical + (sector_nr << fs_info->sectorsize_bits);
+       const struct page *first_page = scrub_stripe_get_page(stripe, sector_nr);
+       const unsigned int first_off = scrub_stripe_get_page_offset(stripe, sector_nr);
+       SHASH_DESC_ON_STACK(shash, fs_info->csum_shash);
+       u8 on_disk_csum[BTRFS_CSUM_SIZE];
+       u8 calculated_csum[BTRFS_CSUM_SIZE];
+       struct btrfs_header *header;
+
+       /*
+        * Here we don't have a good way to attach the pages (and subpages)
+        * to a dummy extent buffer, thus we have to directly grab the members
+        * from pages.
+        */
+       header = (struct btrfs_header *)(page_address(first_page) + first_off);
+       memcpy(on_disk_csum, header->csum, fs_info->csum_size);
+
+       if (logical != btrfs_stack_header_bytenr(header)) {
+               bitmap_set(&stripe->csum_error_bitmap, sector_nr, sectors_per_tree);
+               bitmap_set(&stripe->error_bitmap, sector_nr, sectors_per_tree);
+               btrfs_warn_rl(fs_info,
+               "tree block %llu mirror %u has bad bytenr, has %llu want %llu",
+                             logical, stripe->mirror_num,
+                             btrfs_stack_header_bytenr(header), logical);
+               return;
+       }
+       if (memcmp(header->fsid, fs_info->fs_devices->fsid, BTRFS_FSID_SIZE) != 0) {
+               bitmap_set(&stripe->meta_error_bitmap, sector_nr, sectors_per_tree);
+               bitmap_set(&stripe->error_bitmap, sector_nr, sectors_per_tree);
+               btrfs_warn_rl(fs_info,
+               "tree block %llu mirror %u has bad fsid, has %pU want %pU",
+                             logical, stripe->mirror_num,
+                             header->fsid, fs_info->fs_devices->fsid);
+               return;
+       }
+       if (memcmp(header->chunk_tree_uuid, fs_info->chunk_tree_uuid,
+                  BTRFS_UUID_SIZE) != 0) {
+               bitmap_set(&stripe->meta_error_bitmap, sector_nr, sectors_per_tree);
+               bitmap_set(&stripe->error_bitmap, sector_nr, sectors_per_tree);
+               btrfs_warn_rl(fs_info,
+               "tree block %llu mirror %u has bad chunk tree uuid, has %pU want %pU",
+                             logical, stripe->mirror_num,
+                             header->chunk_tree_uuid, fs_info->chunk_tree_uuid);
+               return;
+       }
+
+       /* Now check tree block csum. */
+       shash->tfm = fs_info->csum_shash;
+       crypto_shash_init(shash);
+       crypto_shash_update(shash, page_address(first_page) + first_off +
+                           BTRFS_CSUM_SIZE, fs_info->sectorsize - BTRFS_CSUM_SIZE);
+
+       for (int i = sector_nr + 1; i < sector_nr + sectors_per_tree; i++) {
+               struct page *page = scrub_stripe_get_page(stripe, i);
+               unsigned int page_off = scrub_stripe_get_page_offset(stripe, i);
+
+               crypto_shash_update(shash, page_address(page) + page_off,
+                                   fs_info->sectorsize);
+       }
+
+       crypto_shash_final(shash, calculated_csum);
+       if (memcmp(calculated_csum, on_disk_csum, fs_info->csum_size) != 0) {
+               bitmap_set(&stripe->meta_error_bitmap, sector_nr, sectors_per_tree);
+               bitmap_set(&stripe->error_bitmap, sector_nr, sectors_per_tree);
+               btrfs_warn_rl(fs_info,
+               "tree block %llu mirror %u has bad csum, has " CSUM_FMT " want " CSUM_FMT,
+                             logical, stripe->mirror_num,
+                             CSUM_FMT_VALUE(fs_info->csum_size, on_disk_csum),
+                             CSUM_FMT_VALUE(fs_info->csum_size, calculated_csum));
+               return;
+       }
+       if (stripe->sectors[sector_nr].generation !=
+           btrfs_stack_header_generation(header)) {
+               bitmap_set(&stripe->meta_error_bitmap, sector_nr, sectors_per_tree);
+               bitmap_set(&stripe->error_bitmap, sector_nr, sectors_per_tree);
+               btrfs_warn_rl(fs_info,
+               "tree block %llu mirror %u has bad generation, has %llu want %llu",
+                             logical, stripe->mirror_num,
+                             btrfs_stack_header_generation(header),
+                             stripe->sectors[sector_nr].generation);
+               return;
+       }
+       bitmap_clear(&stripe->error_bitmap, sector_nr, sectors_per_tree);
+       bitmap_clear(&stripe->csum_error_bitmap, sector_nr, sectors_per_tree);
+       bitmap_clear(&stripe->meta_error_bitmap, sector_nr, sectors_per_tree);
+}
+
 static int scrub_checksum_tree_block(struct scrub_block *sblock)
 {
        struct scrub_ctx *sctx = sblock->sctx;