1364 lines
33 KiB
C
1364 lines
33 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 "task-utils.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);
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Helper to write a full stripe to disk
|
|
* P/Q will be re-calculated.
|
|
*/
|
|
static int write_full_stripe(struct scrub_full_stripe *fstripe)
|
|
{
|
|
void **ptrs;
|
|
int nr_stripes = fstripe->nr_stripes;
|
|
int stripe_len = BTRFS_STRIPE_LEN;
|
|
int i;
|
|
int ret = 0;
|
|
|
|
ptrs = malloc(sizeof(void *) * fstripe->nr_stripes);
|
|
if (!ptrs)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < fstripe->nr_stripes; i++)
|
|
ptrs[i] = fstripe->stripes[i].data;
|
|
|
|
if (fstripe->bg_type & BTRFS_BLOCK_GROUP_RAID6) {
|
|
raid6_gen_syndrome(nr_stripes, stripe_len, ptrs);
|
|
} else {
|
|
ret = raid5_gen_result(nr_stripes, stripe_len, nr_stripes - 1,
|
|
ptrs);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < fstripe->nr_stripes; i++) {
|
|
struct scrub_stripe *stripe = &fstripe->stripes[i];
|
|
|
|
ret = pwrite(stripe->fd, stripe->data, fstripe->stripe_len,
|
|
stripe->physical);
|
|
if (ret != fstripe->stripe_len) {
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
free(ptrs);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Return 0 if we still have chance to recover
|
|
* Return <0 if we have no more chance
|
|
*/
|
|
static int report_recoverablity(struct scrub_full_stripe *fstripe)
|
|
{
|
|
int max_tolerance;
|
|
u64 start = fstripe->logical_start;
|
|
|
|
if (fstripe->bg_type & BTRFS_BLOCK_GROUP_RAID5)
|
|
max_tolerance = 1;
|
|
else
|
|
max_tolerance = 2;
|
|
|
|
if (fstripe->nr_corrupted_stripes > max_tolerance) {
|
|
error(
|
|
"full stripe %llu CORRUPTED: too many read error or corrupted devices",
|
|
start);
|
|
error(
|
|
"full stripe %llu: tolerance: %d, missing: %d, read error: %d, csum error: %d",
|
|
start, max_tolerance, fstripe->err_read_stripes,
|
|
fstripe->err_missing_devs, fstripe->err_csum_dstripes);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void clear_corrupted_stripe_record(struct scrub_full_stripe *fstripe)
|
|
{
|
|
fstripe->corrupted_index[0] = -1;
|
|
fstripe->corrupted_index[1] = -1;
|
|
fstripe->nr_corrupted_stripes = 0;
|
|
}
|
|
|
|
static void record_corrupted_stripe(struct scrub_full_stripe *fstripe,
|
|
int index)
|
|
{
|
|
int i = 0;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
if (fstripe->corrupted_index[i] == -1) {
|
|
fstripe->corrupted_index[i] = index;
|
|
break;
|
|
}
|
|
}
|
|
fstripe->nr_corrupted_stripes++;
|
|
}
|
|
|
|
/*
|
|
* Scrub one full stripe.
|
|
*
|
|
* If everything matches, that's good.
|
|
* If data stripe corrupted badly, no mean to recovery, it will report it.
|
|
* If data stripe corrupted, try recovery first and recheck csum, to
|
|
* determine if it's recoverable or screwed up.
|
|
*/
|
|
static int scrub_one_full_stripe(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_scrub_progress *scrub_ctx,
|
|
u64 start, u64 *next_ret, int write)
|
|
{
|
|
struct scrub_full_stripe *fstripe;
|
|
struct btrfs_map_block *map_block = NULL;
|
|
u32 stripe_len = BTRFS_STRIPE_LEN;
|
|
u64 bg_type;
|
|
u64 len;
|
|
int i;
|
|
int ret;
|
|
|
|
if (!next_ret) {
|
|
error("invalid argument for %s", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = __btrfs_map_block_v2(fs_info, WRITE, start, stripe_len,
|
|
&map_block);
|
|
if (ret < 0) {
|
|
/* Let caller to skip the whole block group */
|
|
*next_ret = (u64)-1;
|
|
return ret;
|
|
}
|
|
start = map_block->start;
|
|
len = map_block->length;
|
|
*next_ret = start + len;
|
|
|
|
/*
|
|
* Step 0: Check if we need to scrub the full stripe
|
|
*
|
|
* If no extent lies in the full stripe, not need to check
|
|
*/
|
|
ret = btrfs_check_extent_exists(fs_info, start, len);
|
|
if (ret < 0) {
|
|
free(map_block);
|
|
return ret;
|
|
}
|
|
/* No extents in range, no need to check */
|
|
if (ret == 0) {
|
|
free(map_block);
|
|
return 0;
|
|
}
|
|
|
|
bg_type = map_block->type & BTRFS_BLOCK_GROUP_PROFILE_MASK;
|
|
if (bg_type != BTRFS_BLOCK_GROUP_RAID5 &&
|
|
bg_type != BTRFS_BLOCK_GROUP_RAID6) {
|
|
free(map_block);
|
|
return -EINVAL;
|
|
}
|
|
|
|
fstripe = alloc_full_stripe(map_block->num_stripes,
|
|
map_block->stripe_len);
|
|
if (!fstripe)
|
|
return -ENOMEM;
|
|
|
|
fstripe->logical_start = map_block->start;
|
|
fstripe->nr_stripes = map_block->num_stripes;
|
|
fstripe->stripe_len = stripe_len;
|
|
fstripe->bg_type = bg_type;
|
|
|
|
/*
|
|
* Step 1: Read out the whole full stripe
|
|
*
|
|
* Then we have the chance to exit early if too many devices are
|
|
* missing.
|
|
*/
|
|
for (i = 0; i < map_block->num_stripes; i++) {
|
|
struct scrub_stripe *s_stripe = &fstripe->stripes[i];
|
|
struct btrfs_map_stripe *m_stripe = &map_block->stripes[i];
|
|
|
|
s_stripe->logical = m_stripe->logical;
|
|
s_stripe->fd = m_stripe->dev->fd;
|
|
s_stripe->physical = m_stripe->physical;
|
|
|
|
if (m_stripe->dev->fd == -1) {
|
|
s_stripe->dev_missing = 1;
|
|
record_corrupted_stripe(fstripe, i);
|
|
fstripe->err_missing_devs++;
|
|
continue;
|
|
}
|
|
|
|
ret = pread(m_stripe->dev->fd, s_stripe->data, stripe_len,
|
|
m_stripe->physical);
|
|
if (ret < stripe_len) {
|
|
record_corrupted_stripe(fstripe, i);
|
|
fstripe->err_read_stripes++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ret = report_recoverablity(fstripe);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ret = recover_from_parities(fs_info, scrub_ctx, fstripe);
|
|
if (ret < 0) {
|
|
error("full stripe %llu CORRUPTED: failed to recover: %s\n",
|
|
fstripe->logical_start, strerror(-ret));
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Clear corrupted stripes report, since they are recovered,
|
|
* and later checker need to record csum mismatch stripes reusing
|
|
* these members
|
|
*/
|
|
clear_corrupted_stripe_record(fstripe);
|
|
|
|
/*
|
|
* Step 2: Check each data stripes against csum
|
|
*/
|
|
for (i = 0; i < map_block->num_stripes; i++) {
|
|
struct scrub_stripe *stripe = &fstripe->stripes[i];
|
|
|
|
if (!is_data_stripe(stripe))
|
|
continue;
|
|
ret = scrub_one_data_stripe(fs_info, scrub_ctx, stripe,
|
|
stripe_len);
|
|
if (ret < 0) {
|
|
fstripe->err_csum_dstripes++;
|
|
record_corrupted_stripe(fstripe, i);
|
|
}
|
|
}
|
|
|
|
ret = report_recoverablity(fstripe);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
/*
|
|
* Recovered before, but no csum error
|
|
*/
|
|
if (fstripe->err_csum_dstripes == 0 && fstripe->recovered) {
|
|
error(
|
|
"full stripe %llu RECOVERABLE: P/Q is good for recovery",
|
|
start);
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
/*
|
|
* No csum error, not recovered before.
|
|
*
|
|
* Only need to check if P/Q matches.
|
|
*/
|
|
if (fstripe->err_csum_dstripes == 0 && !fstripe->recovered) {
|
|
ret = verify_parities(fs_info, scrub_ctx, fstripe);
|
|
if (ret < 0) {
|
|
error(
|
|
"full stripe %llu CORRUPTED: failed to check P/Q: %s",
|
|
start, strerror(-ret));
|
|
goto out;
|
|
}
|
|
if (ret > 0) {
|
|
if (write) {
|
|
ret = write_full_stripe(fstripe);
|
|
if (ret < 0)
|
|
error("failed to write full stripe %llu: %s",
|
|
start, strerror(-ret));
|
|
else
|
|
printf("full stripe %llu REPARIED: only P/Q mismatches, repaired\n",
|
|
start);
|
|
goto out;
|
|
} else {
|
|
printf("full stripe %llu RECOVERABLE: only P/Q is corrupted\n",
|
|
start);
|
|
ret = 0;
|
|
}
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Still csum error after recovery
|
|
*
|
|
* No mean to fix further, screwed up already.
|
|
*/
|
|
if (fstripe->err_csum_dstripes && fstripe->recovered) {
|
|
error(
|
|
"full stripe %llu CORRUPTED: csum still mismatch after recovery",
|
|
start);
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
/* Csum mismatch, but we still has chance to recover. */
|
|
ret = recover_from_parities(fs_info, scrub_ctx, fstripe);
|
|
if (ret < 0) {
|
|
error(
|
|
"full stripe %llu CORRUPTED: failed to recover: %s\n",
|
|
fstripe->logical_start, strerror(-ret));
|
|
goto out;
|
|
}
|
|
|
|
/* After recovery, recheck data stripe csum */
|
|
for (i = 0; i < 2; i++) {
|
|
int index = fstripe->corrupted_index[i];
|
|
struct scrub_stripe *stripe;
|
|
|
|
if (i == -1)
|
|
continue;
|
|
stripe = &fstripe->stripes[index];
|
|
ret = scrub_one_data_stripe(fs_info, scrub_ctx, stripe,
|
|
stripe_len);
|
|
if (ret < 0) {
|
|
error(
|
|
"full stripe %llu CORRUPTED: csum still mismatch after recovery",
|
|
start);
|
|
goto out;
|
|
}
|
|
}
|
|
if (write) {
|
|
ret = write_full_stripe(fstripe);
|
|
if (ret < 0)
|
|
error("failed to write full stripe %llu: %s",
|
|
start, strerror(-ret));
|
|
else
|
|
printf("full stripe %llu REPARIED: corrupted data with good P/Q, repaired\n",
|
|
start);
|
|
goto out;
|
|
}
|
|
printf(
|
|
"full stripe %llu RECOVERABLE: Data stripes corrupted, but P/Q is good\n",
|
|
start);
|
|
|
|
out:
|
|
free_full_stripe(fstripe);
|
|
free(map_block);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Scrub one block group.
|
|
*
|
|
* This function will handle all profiles current btrfs supports.
|
|
* Return 0 for scrubbing the block group. Found error will be recorded into
|
|
* scrub_ctx.
|
|
* Return <0 for fatal error preventing scrubing the block group.
|
|
*/
|
|
static int scrub_one_block_group(struct btrfs_fs_info *fs_info,
|
|
struct btrfs_scrub_progress *scrub_ctx,
|
|
struct btrfs_block_group_cache *bg_cache,
|
|
int write)
|
|
{
|
|
struct btrfs_root *extent_root = fs_info->extent_root;
|
|
struct btrfs_path *path;
|
|
struct btrfs_key key;
|
|
u64 bg_start = bg_cache->key.objectid;
|
|
u64 bg_len = bg_cache->key.offset;
|
|
int ret;
|
|
|
|
if (bg_cache->flags &
|
|
(BTRFS_BLOCK_GROUP_RAID5 | BTRFS_BLOCK_GROUP_RAID6)) {
|
|
u64 cur = bg_start;
|
|
u64 next;
|
|
|
|
while (cur < bg_start + bg_len) {
|
|
ret = scrub_one_full_stripe(fs_info, scrub_ctx, cur,
|
|
&next, write);
|
|
/* Ignore any non-fatal error */
|
|
if (ret < 0 && ret != -EIO) {
|
|
error("fatal error happens checking one full stripe at bytenr: %llu: %s",
|
|
cur, strerror(-ret));
|
|
return ret;
|
|
}
|
|
cur = next;
|
|
}
|
|
/* Ignore any -EIO error, such error will be reported at last */
|
|
return 0;
|
|
}
|
|
/* None parity based profile, check extent by extent */
|
|
key.objectid = bg_start;
|
|
key.type = 0;
|
|
key.offset = 0;
|
|
|
|
path = btrfs_alloc_path();
|
|
if (!path)
|
|
return -ENOMEM;
|
|
ret = btrfs_search_slot(NULL, extent_root, &key, path, 0, 0);
|
|
if (ret < 0)
|
|
goto out;
|
|
while (1) {
|
|
struct extent_buffer *eb = path->nodes[0];
|
|
int slot = path->slots[0];
|
|
u64 extent_start;
|
|
u64 extent_len;
|
|
|
|
btrfs_item_key_to_cpu(eb, &key, slot);
|
|
if (key.objectid >= bg_start + bg_len)
|
|
break;
|
|
if (key.type != BTRFS_EXTENT_ITEM_KEY &&
|
|
key.type != BTRFS_METADATA_ITEM_KEY)
|
|
goto next;
|
|
|
|
extent_start = key.objectid;
|
|
if (key.type == BTRFS_METADATA_ITEM_KEY)
|
|
extent_len = extent_root->fs_info->nodesize;
|
|
else
|
|
extent_len = key.offset;
|
|
|
|
ret = scrub_one_extent(fs_info, scrub_ctx, path, extent_start,
|
|
extent_len, write);
|
|
if (ret < 0 && ret != -EIO) {
|
|
error("fatal error checking extent bytenr %llu len %llu: %s",
|
|
extent_start, extent_len, strerror(-ret));
|
|
goto out;
|
|
}
|
|
ret = 0;
|
|
next:
|
|
ret = btrfs_next_extent_item(extent_root, path, bg_start +
|
|
bg_len);
|
|
if (ret < 0)
|
|
goto out;
|
|
if (ret > 0) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
out:
|
|
btrfs_free_path(path);
|
|
return ret;
|
|
}
|
|
|
|
int btrfs_scrub(struct btrfs_fs_info *fs_info, struct task_context *task,
|
|
int write)
|
|
{
|
|
u64 bg_nr = 0;
|
|
struct btrfs_block_group_cache *bg_cache;
|
|
struct btrfs_scrub_progress scrub_ctx = {0};
|
|
int ret = 0;
|
|
|
|
ASSERT(fs_info);
|
|
|
|
bg_cache = btrfs_lookup_first_block_group(fs_info, 0);
|
|
if (!bg_cache) {
|
|
error("no block group is found");
|
|
return -ENOENT;
|
|
}
|
|
++bg_nr;
|
|
|
|
if (task) {
|
|
/* get block group numbers for progress */
|
|
while (1) {
|
|
u64 bg_offset = bg_cache->key.objectid +
|
|
bg_cache->key.offset;
|
|
bg_cache = btrfs_lookup_first_block_group(fs_info,
|
|
bg_offset);
|
|
if (!bg_cache)
|
|
break;
|
|
++bg_nr;
|
|
}
|
|
task->all = bg_nr;
|
|
task->cur = 1;
|
|
task_start(task->info);
|
|
|
|
bg_cache = btrfs_lookup_first_block_group(fs_info, 0);
|
|
}
|
|
|
|
while (1) {
|
|
ret = scrub_one_block_group(fs_info, &scrub_ctx, bg_cache,
|
|
write);
|
|
if (ret < 0 && ret != -EIO)
|
|
break;
|
|
if (task)
|
|
task->cur++;
|
|
|
|
bg_cache = btrfs_lookup_first_block_group(fs_info,
|
|
bg_cache->key.objectid + bg_cache->key.offset);
|
|
if (!bg_cache)
|
|
break;
|
|
}
|
|
|
|
if (task)
|
|
task_stop(task->info);
|
|
|
|
printf("Scrub result:\n");
|
|
printf("Tree bytes scrubbed: %llu\n", scrub_ctx.tree_bytes_scrubbed);
|
|
printf("Tree extents scrubbed: %llu\n", scrub_ctx.tree_extents_scrubbed);
|
|
printf("Data bytes scrubbed: %llu\n", scrub_ctx.data_bytes_scrubbed);
|
|
printf("Data extents scrubbed: %llu\n", scrub_ctx.data_extents_scrubbed);
|
|
printf("Data bytes without csum: %llu\n", scrub_ctx.csum_discards *
|
|
fs_info->sectorsize);
|
|
printf("Read error: %llu\n", scrub_ctx.read_errors);
|
|
printf("Verify error: %llu\n", scrub_ctx.verify_errors);
|
|
printf("Csum error: %llu\n", scrub_ctx.csum_errors);
|
|
if (scrub_ctx.csum_errors || scrub_ctx.read_errors ||
|
|
scrub_ctx.uncorrectable_errors || scrub_ctx.verify_errors)
|
|
ret = 1;
|
|
else
|
|
ret = 0;
|
|
return ret;
|
|
}
|