872 lines
21 KiB
C
872 lines
21 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License v2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*/
|
|
|
|
/*
|
|
* Main part to implement offline(unmounted) btrfs scrub
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
#include "ctree.h"
|
|
#include "volumes.h"
|
|
#include "disk-io.h"
|
|
#include "utils.h"
|
|
#include "kernel-lib/bitops.h"
|
|
#include "kernel-lib/raid56.h"
|
|
|
|
/*
|
|
* For parity based profile (RAID56)
|
|
* Mirror/stripe based on won't need this. They are iterated by bytenr and
|
|
* mirror number.
|
|
*/
|
|
struct scrub_stripe {
|
|
/* For P/Q logical start will be BTRFS_RAID5/6_P/Q_STRIPE */
|
|
u64 logical;
|
|
|
|
u64 physical;
|
|
|
|
/* Device is missing */
|
|
unsigned int dev_missing:1;
|
|
|
|
/* Any tree/data csum mismatches */
|
|
unsigned int csum_mismatch:1;
|
|
|
|
/* Some data doesn't have csum (nodatasum) */
|
|
unsigned int csum_missing:1;
|
|
|
|
/* Device fd, to write correct data back to disc */
|
|
int fd;
|
|
|
|
char *data;
|
|
};
|
|
|
|
/*
|
|
* RAID56 full stripe (data stripes + P/Q)
|
|
*/
|
|
struct scrub_full_stripe {
|
|
u64 logical_start;
|
|
u64 logical_len;
|
|
u64 bg_type;
|
|
u32 nr_stripes;
|
|
u32 stripe_len;
|
|
|
|
/* Read error stripes */
|
|
u32 err_read_stripes;
|
|
|
|
/* Missing devices */
|
|
u32 err_missing_devs;
|
|
|
|
/* Csum error data stripes */
|
|
u32 err_csum_dstripes;
|
|
|
|
/* Missing csum data stripes */
|
|
u32 missing_csum_dstripes;
|
|
|
|
/* currupted stripe index */
|
|
int corrupted_index[2];
|
|
|
|
int nr_corrupted_stripes;
|
|
|
|
/* Already recovered once? */
|
|
unsigned int recovered:1;
|
|
|
|
struct scrub_stripe stripes[];
|
|
};
|
|
|
|
static void free_full_stripe(struct scrub_full_stripe *fstripe)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < fstripe->nr_stripes; i++)
|
|
free(fstripe->stripes[i].data);
|
|
free(fstripe);
|
|
}
|
|
|
|
static struct scrub_full_stripe *alloc_full_stripe(int nr_stripes,
|
|
u32 stripe_len)
|
|
{
|
|
struct scrub_full_stripe *ret;
|
|
int size = sizeof(*ret) + sizeof(unsigned long *) +
|
|
nr_stripes * sizeof(struct scrub_stripe);
|
|
int i;
|
|
|
|
ret = malloc(size);
|
|
if (!ret)
|
|
return NULL;
|
|
|
|
memset(ret, 0, size);
|
|
ret->nr_stripes = nr_stripes;
|
|
ret->stripe_len = stripe_len;
|
|
ret->corrupted_index[0] = -1;
|
|
ret->corrupted_index[1] = -1;
|
|
|
|
/* Alloc data memory for each stripe */
|
|
for (i = 0; i < nr_stripes; i++) {
|
|
struct scrub_stripe *stripe = &ret->stripes[i];
|
|
|
|
stripe->data = malloc(stripe_len);
|
|
if (!stripe->data) {
|
|
free_full_stripe(ret);
|
|
return NULL;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static inline int is_data_stripe(struct scrub_stripe *stripe)
|
|
{
|
|
u64 bytenr = stripe->logical;
|
|
|
|
if (bytenr == BTRFS_RAID5_P_STRIPE || bytenr == BTRFS_RAID6_Q_STRIPE)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Check one tree mirror given by @bytenr and @mirror, or @data.
|
|
* If @data is not given (NULL), the function will try to read out tree block
|
|
* using @bytenr and @mirror.
|
|
* If @data is given, use data directly, won't try to read from disk.
|
|
*
|
|
* The extra @data prameter is handy for RAID5/6 recovery code to verify
|
|
* the recovered data.
|
|
*
|
|
* Return 0 if everything is OK.
|
|
* Return <0 something goes wrong, and @scrub_ctx accounting will be updated
|
|
* if it's a data corruption.
|
|
*/
|
|
static int check_tree_mirror(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_scrub_progress *scrub_ctx,
|
|
char *data, u64 bytenr, int mirror)
|
|
{
|
|
struct extent_buffer *eb;
|
|
u32 nodesize = fs_info->nodesize;
|
|
int ret;
|
|
|
|
if (!IS_ALIGNED(bytenr, fs_info->sectorsize)) {
|
|
/* Such error will be reported by check_tree_block() */
|
|
scrub_ctx->verify_errors++;
|
|
return -EIO;
|
|
}
|
|
|
|
eb = btrfs_find_create_tree_block(fs_info, bytenr, nodesize);
|
|
if (!eb)
|
|
return -ENOMEM;
|
|
if (data) {
|
|
memcpy(eb->data, data, nodesize);
|
|
} else {
|
|
ret = read_whole_eb(fs_info, eb, mirror);
|
|
if (ret) {
|
|
scrub_ctx->read_errors++;
|
|
error("failed to read tree block %llu mirror %d",
|
|
bytenr, mirror);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
scrub_ctx->tree_bytes_scrubbed += nodesize;
|
|
if (csum_tree_block(fs_info, eb, 1)) {
|
|
error("tree block %llu mirror %d checksum mismatch", bytenr,
|
|
mirror);
|
|
scrub_ctx->csum_errors++;
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
ret = check_tree_block(fs_info, eb);
|
|
if (ret < 0) {
|
|
error("tree block %llu mirror %d is invalid", bytenr, mirror);
|
|
scrub_ctx->verify_errors++;
|
|
goto out;
|
|
}
|
|
|
|
scrub_ctx->tree_extents_scrubbed++;
|
|
out:
|
|
free_extent_buffer(eb);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* read_extent_data() helper
|
|
*
|
|
* This function will handle short read and update @scrub_ctx when read
|
|
* error happens.
|
|
*/
|
|
static int read_extent_data_loop(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_scrub_progress *scrub_ctx,
|
|
char *buf, u64 start, u64 len, int mirror)
|
|
{
|
|
int ret = 0;
|
|
u64 cur = 0;
|
|
|
|
while (cur < len) {
|
|
u64 read_len = len - cur;
|
|
|
|
ret = read_extent_data(fs_info, buf + cur,
|
|
start + cur, &read_len, mirror);
|
|
if (ret < 0) {
|
|
error("failed to read out data at bytenr %llu mirror %d",
|
|
start + cur, mirror);
|
|
scrub_ctx->read_errors++;
|
|
break;
|
|
}
|
|
cur += read_len;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Recover all other (corrupted) mirrors for tree block.
|
|
*
|
|
* The method is quite simple, just read out the correct mirror specified by
|
|
* @good_mirror and write back correct data to all other blocks
|
|
*/
|
|
static int recover_tree_mirror(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_scrub_progress *scrub_ctx,
|
|
u64 start, int good_mirror)
|
|
{
|
|
char *buf;
|
|
u32 nodesize = fs_info->nodesize;
|
|
int i;
|
|
int num_copies;
|
|
int ret;
|
|
|
|
buf = malloc(nodesize);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
ret = read_extent_data_loop(fs_info, scrub_ctx, buf, start, nodesize,
|
|
good_mirror);
|
|
if (ret < 0) {
|
|
error("failed to read tree block at bytenr %llu mirror %d",
|
|
start, good_mirror);
|
|
goto out;
|
|
}
|
|
|
|
num_copies = btrfs_num_copies(fs_info, start, nodesize);
|
|
for (i = 0; i <= num_copies; i++) {
|
|
if (i == good_mirror)
|
|
continue;
|
|
ret = write_data_to_disk(fs_info, buf, start, nodesize, i);
|
|
if (ret < 0) {
|
|
error("failed to write tree block at bytenr %llu mirror %d",
|
|
start, i);
|
|
goto out;
|
|
}
|
|
}
|
|
ret = 0;
|
|
out:
|
|
free(buf);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Check one data mirror given by @start @len and @mirror, or @data
|
|
* If @data is not given, try to read it from disk.
|
|
* This function will try to read out all the data then check sum.
|
|
*
|
|
* If @data is given, just use the data.
|
|
* This behavior is useful for RAID5/6 recovery code to verify recovered data.
|
|
*
|
|
* If @corrupt_bitmap is given, restore corrupted sector to that bitmap.
|
|
* This is useful for mirror based profiles to recover its data.
|
|
*
|
|
* Return 0 if everything is OK.
|
|
* Return <0 if something goes wrong, and @scrub_ctx accounting will be updated
|
|
* if it's a data corruption.
|
|
*/
|
|
static int check_data_mirror(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_scrub_progress *scrub_ctx,
|
|
char *data, u64 start, u64 len, int mirror,
|
|
unsigned long *corrupt_bitmap)
|
|
{
|
|
u32 sectorsize = fs_info->sectorsize;
|
|
u32 data_csum;
|
|
u32 *csums = NULL;
|
|
char *buf = NULL;
|
|
int ret = 0;
|
|
int err = 0;
|
|
int i;
|
|
unsigned long *csum_bitmap = NULL;
|
|
|
|
if (!data) {
|
|
buf = malloc(len);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
ret = read_extent_data_loop(fs_info, scrub_ctx, buf, start,
|
|
len, mirror);
|
|
if (ret < 0)
|
|
goto out;
|
|
scrub_ctx->data_bytes_scrubbed += len;
|
|
} else {
|
|
buf = data;
|
|
}
|
|
|
|
/* Alloc and Check csums */
|
|
csums = malloc(len / sectorsize * sizeof(data_csum));
|
|
if (!csums) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
csum_bitmap = malloc(calculate_bitmap_len(len / sectorsize));
|
|
if (!csum_bitmap) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
if (corrupt_bitmap)
|
|
memset(corrupt_bitmap, 0,
|
|
calculate_bitmap_len(len / sectorsize));
|
|
ret = btrfs_read_data_csums(fs_info, start, len, csums, csum_bitmap);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
for (i = 0; i < len / sectorsize; i++) {
|
|
if (!test_bit(i, csum_bitmap)) {
|
|
scrub_ctx->csum_discards++;
|
|
continue;
|
|
}
|
|
|
|
data_csum = ~(u32)0;
|
|
data_csum = btrfs_csum_data(buf + i * sectorsize, data_csum,
|
|
sectorsize);
|
|
btrfs_csum_final(data_csum, (u8 *)&data_csum);
|
|
|
|
if (memcmp(&data_csum, (char *)csums + i * sizeof(data_csum),
|
|
sizeof(data_csum))) {
|
|
error("data at bytenr %llu mirror %d csum mismatch, have 0x%08x expect 0x%08x",
|
|
start + i * sectorsize, mirror, data_csum,
|
|
*(u32 *)((char *)csums + i * sizeof(data_csum)));
|
|
err = 1;
|
|
scrub_ctx->csum_errors++;
|
|
if (corrupt_bitmap)
|
|
set_bit(i, corrupt_bitmap);
|
|
continue;
|
|
}
|
|
scrub_ctx->data_bytes_scrubbed += sectorsize;
|
|
}
|
|
out:
|
|
if (!data)
|
|
free(buf);
|
|
free(csums);
|
|
free(csum_bitmap);
|
|
|
|
if (!ret && err)
|
|
return -EIO;
|
|
return ret;
|
|
}
|
|
|
|
/* Helper to check all mirrors for a good copy */
|
|
static int has_good_mirror(unsigned long *corrupt_bitmaps[], int num_copies,
|
|
int bit, int *good_mirror)
|
|
{
|
|
int found_good = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < num_copies; i++) {
|
|
if (!test_bit(bit, corrupt_bitmaps[i])) {
|
|
found_good = 1;
|
|
if (good_mirror)
|
|
*good_mirror = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
return found_good;
|
|
}
|
|
|
|
/*
|
|
* Helper function to check @corrupt_bitmaps, to verify if it's recoverable
|
|
* for mirror based data extent.
|
|
*
|
|
* Return 1 for recoverable, and 0 for not recoverable
|
|
*/
|
|
static int check_data_mirror_recoverable(struct btrfs_fs_info *fs_info,
|
|
u64 start, u64 len, u32 sectorsize,
|
|
unsigned long *corrupt_bitmaps[])
|
|
{
|
|
int i;
|
|
int corrupted = 0;
|
|
int bit;
|
|
int num_copies = btrfs_num_copies(fs_info, start, len);
|
|
|
|
for (i = 0; i < num_copies; i++) {
|
|
for_each_set_bit(bit, corrupt_bitmaps[i], len / sectorsize) {
|
|
if (!has_good_mirror(corrupt_bitmaps, num_copies,
|
|
bit, NULL)) {
|
|
corrupted = 1;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
return !corrupted;
|
|
}
|
|
|
|
/*
|
|
* Try to recover all corrupted sectors specified by @corrupt_bitmaps,
|
|
* by reading out good sector in other mirror.
|
|
*/
|
|
static int recover_data_mirror(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_scrub_progress *scrub_ctx,
|
|
u64 start, u64 len,
|
|
unsigned long *corrupt_bitmaps[])
|
|
{
|
|
char *buf;
|
|
u32 sectorsize = fs_info->sectorsize;
|
|
int ret = 0;
|
|
int bit;
|
|
int i;
|
|
int bad_mirror;
|
|
int num_copies;
|
|
|
|
/* Don't bother to recover unrecoverable extents */
|
|
if (!check_data_mirror_recoverable(fs_info, start, len,
|
|
sectorsize, corrupt_bitmaps))
|
|
return -EIO;
|
|
|
|
buf = malloc(sectorsize);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
num_copies = btrfs_num_copies(fs_info, start, len);
|
|
for (i = 0; i < num_copies; i++) {
|
|
for_each_set_bit(bit, corrupt_bitmaps[i], len / sectorsize) {
|
|
u64 cur = start + bit * sectorsize;
|
|
int good;
|
|
|
|
/* Find good mirror */
|
|
ret = has_good_mirror(corrupt_bitmaps, num_copies, bit,
|
|
&good);
|
|
if (!ret) {
|
|
error("failed to find good mirror for bytenr %llu",
|
|
cur);
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
/* Read out good mirror */
|
|
ret = read_data_from_disk(fs_info, buf, cur,
|
|
sectorsize, good);
|
|
if (ret < 0) {
|
|
error("failed to read good mirror from bytenr %llu mirror %d",
|
|
cur, good);
|
|
goto out;
|
|
}
|
|
/* Write back to all other mirrors */
|
|
for (bad_mirror = 1; bad_mirror <= num_copies;
|
|
bad_mirror++) {
|
|
if (bad_mirror == good)
|
|
continue;
|
|
ret = write_data_to_disk(fs_info, buf, cur,
|
|
sectorsize, bad_mirror);
|
|
if (ret < 0) {
|
|
error("failed to recover mirror for bytenr %llu mirror %d",
|
|
cur, bad_mirror);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
free(buf);
|
|
return ret;
|
|
}
|
|
|
|
/* Btrfs only supports up to 2 copies of data, yet */
|
|
#define BTRFS_MAX_COPIES 2
|
|
|
|
/*
|
|
* Check all copies of range @start, @len.
|
|
* Caller must ensure the range is covered by EXTENT_ITEM/METADATA_ITEM
|
|
* specified by leaf of @path.
|
|
* And @start, @len must be a subset of the EXTENT_ITEM/METADATA_ITEM.
|
|
*
|
|
* Return 0 if the range is all OK or recovered or recoverable.
|
|
* Return <0 if the range can't be recoverable.
|
|
*/
|
|
static int scrub_one_extent(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_scrub_progress *scrub_ctx,
|
|
struct btrfs_path *path, u64 start, u64 len,
|
|
int write)
|
|
{
|
|
struct btrfs_key key;
|
|
struct btrfs_extent_item *ei;
|
|
struct extent_buffer *leaf = path->nodes[0];
|
|
u32 sectorsize = fs_info->sectorsize;
|
|
unsigned long *corrupt_bitmaps[BTRFS_MAX_COPIES] = { NULL };
|
|
int slot = path->slots[0];
|
|
int num_copies;
|
|
int meta_corrupted = 0;
|
|
int meta_good_mirror = 0;
|
|
int data_bad_mirror = 0;
|
|
u64 extent_start;
|
|
u64 extent_len;
|
|
int metadata = 0;
|
|
int i;
|
|
int ret = 0;
|
|
|
|
btrfs_item_key_to_cpu(leaf, &key, slot);
|
|
if (key.type != BTRFS_METADATA_ITEM_KEY &&
|
|
key.type != BTRFS_EXTENT_ITEM_KEY)
|
|
goto invalid_arg;
|
|
|
|
extent_start = key.objectid;
|
|
if (key.type == BTRFS_METADATA_ITEM_KEY) {
|
|
extent_len = fs_info->nodesize;
|
|
metadata = 1;
|
|
} else {
|
|
extent_len = key.offset;
|
|
ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item);
|
|
if (btrfs_extent_flags(leaf, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK)
|
|
metadata = 1;
|
|
}
|
|
if (start >= extent_start + extent_len ||
|
|
start + len <= extent_start)
|
|
goto invalid_arg;
|
|
|
|
for (i = 0; i < BTRFS_MAX_COPIES; i++) {
|
|
corrupt_bitmaps[i] = malloc(
|
|
calculate_bitmap_len(len / sectorsize));
|
|
if (!corrupt_bitmaps[i])
|
|
goto out;
|
|
}
|
|
num_copies = btrfs_num_copies(fs_info, start, len);
|
|
for (i = 1; i <= num_copies; i++) {
|
|
if (metadata) {
|
|
ret = check_tree_mirror(fs_info, scrub_ctx,
|
|
NULL, extent_start, i);
|
|
scrub_ctx->tree_extents_scrubbed++;
|
|
if (ret < 0)
|
|
meta_corrupted++;
|
|
else
|
|
meta_good_mirror = i;
|
|
} else {
|
|
ret = check_data_mirror(fs_info, scrub_ctx, NULL, start,
|
|
len, i, corrupt_bitmaps[i - 1]);
|
|
scrub_ctx->data_extents_scrubbed++;
|
|
}
|
|
}
|
|
|
|
/* Metadata recover and report */
|
|
if (metadata) {
|
|
if (!meta_corrupted) {
|
|
goto out;
|
|
} else if (meta_corrupted && meta_corrupted < num_copies) {
|
|
if (write) {
|
|
ret = recover_tree_mirror(fs_info, scrub_ctx,
|
|
start, meta_good_mirror);
|
|
if (ret < 0) {
|
|
error("failed to recover tree block at bytenr %llu",
|
|
start);
|
|
goto out;
|
|
}
|
|
printf("extent %llu len %llu REPAIRED: has corrupted mirror, repaired\n",
|
|
start, len);
|
|
goto out;
|
|
}
|
|
printf("extent %llu len %llu RECOVERABLE: has corrupted mirror, but is recoverable\n",
|
|
start, len);
|
|
goto out;
|
|
} else {
|
|
error("extent %llu len %llu CORRUPTED: all mirror(s) corrupted, can't be recovered",
|
|
start, len);
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
}
|
|
/* Data recover and report */
|
|
for (i = 0; i < num_copies; i++) {
|
|
if (find_first_bit(corrupt_bitmaps[i], len / sectorsize) >=
|
|
len / sectorsize)
|
|
continue;
|
|
data_bad_mirror = i + 1;
|
|
}
|
|
/* All data sectors are good */
|
|
if (!data_bad_mirror) {
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (check_data_mirror_recoverable(fs_info, start, len,
|
|
sectorsize, corrupt_bitmaps)) {
|
|
if (write) {
|
|
ret = recover_data_mirror(fs_info, scrub_ctx, start,
|
|
len, corrupt_bitmaps);
|
|
if (ret < 0) {
|
|
error("failed to recover data extent at bytenr %llu len %llu",
|
|
start, len);
|
|
goto out;
|
|
}
|
|
printf("extent %llu len %llu REPARIED: has corrupted mirror, repaired\n",
|
|
start, len);
|
|
goto out;
|
|
}
|
|
printf("extent %llu len %llu RECOVERABLE: has corrupted mirror, recoverable\n",
|
|
start, len);
|
|
goto out;
|
|
}
|
|
error("extent %llu len %llu CORRUPTED, all mirror(s) corrupted, can't be repaired",
|
|
start, len);
|
|
ret = -EIO;
|
|
out:
|
|
for (i = 0; i < BTRFS_MAX_COPIES; i++)
|
|
kfree(corrupt_bitmaps[i]);
|
|
return ret;
|
|
|
|
invalid_arg:
|
|
error("invalid parameter for %s", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Scrub one full data stripe of RAID5/6.
|
|
* This means it will check any data/metadata extent in the data stripe
|
|
* spcified by @stripe and @stripe_len
|
|
*
|
|
* This function will only *CHECK* if the data stripe has any corruption.
|
|
* Won't repair at this function.
|
|
*
|
|
* Return 0 if the full stripe is OK.
|
|
* Return <0 if any error is found.
|
|
* Note: Missing csum is not counted as error (NODATACSUM is valid)
|
|
*/
|
|
static int scrub_one_data_stripe(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_scrub_progress *scrub_ctx,
|
|
struct scrub_stripe *stripe, u32 stripe_len)
|
|
{
|
|
struct btrfs_path *path;
|
|
struct btrfs_root *extent_root = fs_info->extent_root;
|
|
struct btrfs_key key;
|
|
u64 extent_start;
|
|
u64 extent_len;
|
|
u64 orig_csum_discards;
|
|
int ret;
|
|
|
|
if (!is_data_stripe(stripe))
|
|
return -EINVAL;
|
|
|
|
path = btrfs_alloc_path();
|
|
if (!path)
|
|
return -ENOMEM;
|
|
|
|
key.objectid = stripe->logical + stripe_len;
|
|
key.offset = 0;
|
|
key.type = 0;
|
|
|
|
ret = btrfs_search_slot(NULL, extent_root, &key, path, 0, 0);
|
|
if (ret < 0)
|
|
goto out;
|
|
while (1) {
|
|
struct btrfs_extent_item *ei;
|
|
struct extent_buffer *eb;
|
|
char *data;
|
|
int slot;
|
|
int metadata = 0;
|
|
u64 check_start;
|
|
u64 check_len;
|
|
|
|
ret = btrfs_previous_extent_item(extent_root, path, 0);
|
|
if (ret > 0) {
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
if (ret < 0)
|
|
goto out;
|
|
eb = path->nodes[0];
|
|
slot = path->slots[0];
|
|
btrfs_item_key_to_cpu(eb, &key, slot);
|
|
extent_start = key.objectid;
|
|
ei = btrfs_item_ptr(eb, slot, struct btrfs_extent_item);
|
|
|
|
/* tree block scrub */
|
|
if (key.type == BTRFS_METADATA_ITEM_KEY ||
|
|
btrfs_extent_flags(eb, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK) {
|
|
extent_len = extent_root->fs_info->nodesize;
|
|
metadata = 1;
|
|
} else {
|
|
extent_len = key.offset;
|
|
metadata = 0;
|
|
}
|
|
|
|
/* Current extent is out of our range, loop comes to end */
|
|
if (extent_start + extent_len <= stripe->logical)
|
|
break;
|
|
|
|
if (metadata) {
|
|
/*
|
|
* Check crossing stripe first, which can't be scrubbed
|
|
*/
|
|
if (check_crossing_stripes(fs_info, extent_start,
|
|
extent_root->fs_info->nodesize)) {
|
|
error("tree block at %llu is crossing stripe boundary, unable to scrub",
|
|
extent_start);
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
data = stripe->data + extent_start - stripe->logical;
|
|
ret = check_tree_mirror(fs_info, scrub_ctx,
|
|
data, extent_start, 0);
|
|
/* Any csum/verify error means the stripe is screwed */
|
|
if (ret < 0) {
|
|
stripe->csum_mismatch = 1;
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
ret = 0;
|
|
continue;
|
|
}
|
|
/* Restrict the extent range to fit stripe range */
|
|
check_start = max(extent_start, stripe->logical);
|
|
check_len = min(extent_start + extent_len, stripe->logical +
|
|
stripe_len) - check_start;
|
|
|
|
/* Record original csum_discards to detect missing csum case */
|
|
orig_csum_discards = scrub_ctx->csum_discards;
|
|
|
|
data = stripe->data + check_start - stripe->logical;
|
|
ret = check_data_mirror(fs_info, scrub_ctx, data, check_start,
|
|
check_len, 0, NULL);
|
|
/* Csum mismatch, no need to continue anyway*/
|
|
if (ret < 0) {
|
|
stripe->csum_mismatch = 1;
|
|
goto out;
|
|
}
|
|
/* Check if there is any missing csum for data */
|
|
if (scrub_ctx->csum_discards != orig_csum_discards)
|
|
stripe->csum_missing = 1;
|
|
/*
|
|
* Only increase data_extents_scrubbed if we are scrubbing the
|
|
* tailing part of the data extent
|
|
*/
|
|
if (extent_start + extent_len <= stripe->logical + stripe_len)
|
|
scrub_ctx->data_extents_scrubbed++;
|
|
ret = 0;
|
|
}
|
|
out:
|
|
btrfs_free_path(path);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Verify parities for RAID56
|
|
* Caller must fill @fstripe before calling this function
|
|
*
|
|
* Return 0 for parities matches.
|
|
* Return >0 for P or Q mismatch
|
|
* Return <0 for fatal error
|
|
*/
|
|
static int verify_parities(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_scrub_progress *scrub_ctx,
|
|
struct scrub_full_stripe *fstripe)
|
|
{
|
|
void **ptrs;
|
|
void *ondisk_p = NULL;
|
|
void *ondisk_q = NULL;
|
|
void *buf_p;
|
|
void *buf_q;
|
|
int nr_stripes = fstripe->nr_stripes;
|
|
int stripe_len = BTRFS_STRIPE_LEN;
|
|
int i;
|
|
int ret = 0;
|
|
|
|
ptrs = malloc(sizeof(void *) * fstripe->nr_stripes);
|
|
buf_p = malloc(fstripe->stripe_len);
|
|
buf_q = malloc(fstripe->stripe_len);
|
|
if (!ptrs || !buf_p || !buf_q) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < fstripe->nr_stripes; i++) {
|
|
struct scrub_stripe *stripe = &fstripe->stripes[i];
|
|
|
|
if (stripe->logical == BTRFS_RAID5_P_STRIPE) {
|
|
ondisk_p = stripe->data;
|
|
ptrs[i] = buf_p;
|
|
continue;
|
|
} else if (stripe->logical == BTRFS_RAID6_Q_STRIPE) {
|
|
ondisk_q = stripe->data;
|
|
ptrs[i] = buf_q;
|
|
continue;
|
|
} else {
|
|
ptrs[i] = stripe->data;
|
|
continue;
|
|
}
|
|
}
|
|
/* RAID6 */
|
|
if (ondisk_q) {
|
|
raid6_gen_syndrome(nr_stripes, stripe_len, ptrs);
|
|
|
|
if (memcmp(ondisk_q, ptrs[nr_stripes - 1], stripe_len) != 0 ||
|
|
memcmp(ondisk_p, ptrs[nr_stripes - 2], stripe_len))
|
|
ret = 1;
|
|
} else {
|
|
ret = raid5_gen_result(nr_stripes, stripe_len, nr_stripes - 1,
|
|
ptrs);
|
|
if (ret < 0)
|
|
goto out;
|
|
if (memcmp(ondisk_p, ptrs[nr_stripes - 1], stripe_len) != 0)
|
|
ret = 1;
|
|
}
|
|
out:
|
|
free(buf_p);
|
|
free(buf_q);
|
|
free(ptrs);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Try to recover data stripe from P or Q stripe
|
|
*
|
|
* Return >0 if it can't be require any more.
|
|
* Return 0 for successful repair or no need to repair at all
|
|
* Return <0 for fatal error
|
|
*/
|
|
static int recover_from_parities(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_scrub_progress *scrub_ctx,
|
|
struct scrub_full_stripe *fstripe)
|
|
{
|
|
void **ptrs;
|
|
int nr_stripes = fstripe->nr_stripes;
|
|
int stripe_len = BTRFS_STRIPE_LEN;
|
|
int max_tolerance;
|
|
int i;
|
|
int ret;
|
|
|
|
/* No need to recover */
|
|
if (!fstripe->nr_corrupted_stripes)
|
|
return 0;
|
|
|
|
/* Already recovered once, no more chance */
|
|
if (fstripe->recovered)
|
|
return 1;
|
|
|
|
if (fstripe->bg_type & BTRFS_BLOCK_GROUP_RAID5)
|
|
max_tolerance = 1;
|
|
else
|
|
max_tolerance = 2;
|
|
|
|
/* Out of repair */
|
|
if (fstripe->nr_corrupted_stripes > max_tolerance)
|
|
return 1;
|
|
|
|
ptrs = malloc(sizeof(void *) * fstripe->nr_stripes);
|
|
if (!ptrs)
|
|
return -ENOMEM;
|
|
|
|
/* Construct ptrs */
|
|
for (i = 0; i < nr_stripes; i++)
|
|
ptrs[i] = fstripe->stripes[i].data;
|
|
|
|
ret = raid56_recov(nr_stripes, stripe_len, fstripe->bg_type,
|
|
fstripe->corrupted_index[0],
|
|
fstripe->corrupted_index[1], ptrs);
|
|
fstripe->recovered = 1;
|
|
free(ptrs);
|
|
return ret;
|
|
}
|