Commit 0ef8e451 authored by Jan Schmidt's avatar Jan Schmidt

btrfs scrub: add fixup code for errors on nodatasum files

This removes a FIXME comment and introduces the first part of nodatasum
fixup: It gets the corresponding inode for a logical address and triggers a
regular readpage for the corrupted sector.

Once we have on-the-fly error correction our error will be automatically
corrected. The correction code is expected to clear the newly introduced
EXTENT_DAMAGED flag, making scrub report that error as "corrected" instead
of "uncorrectable" eventually.
Signed-off-by: default avatarJan Schmidt <list.btrfs@jan-o-sch.net>
parent e12fa9cd
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#define EXTENT_NODATASUM (1 << 10) #define EXTENT_NODATASUM (1 << 10)
#define EXTENT_DO_ACCOUNTING (1 << 11) #define EXTENT_DO_ACCOUNTING (1 << 11)
#define EXTENT_FIRST_DELALLOC (1 << 12) #define EXTENT_FIRST_DELALLOC (1 << 12)
#define EXTENT_DAMAGED (1 << 13)
#define EXTENT_IOBITS (EXTENT_LOCKED | EXTENT_WRITEBACK) #define EXTENT_IOBITS (EXTENT_LOCKED | EXTENT_WRITEBACK)
#define EXTENT_CTLBITS (EXTENT_DO_ACCOUNTING | EXTENT_FIRST_DELALLOC) #define EXTENT_CTLBITS (EXTENT_DO_ACCOUNTING | EXTENT_FIRST_DELALLOC)
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "volumes.h" #include "volumes.h"
#include "disk-io.h" #include "disk-io.h"
#include "ordered-data.h" #include "ordered-data.h"
#include "transaction.h"
#include "backref.h" #include "backref.h"
/* /*
...@@ -89,6 +90,7 @@ struct scrub_dev { ...@@ -89,6 +90,7 @@ struct scrub_dev {
int first_free; int first_free;
int curr; int curr;
atomic_t in_flight; atomic_t in_flight;
atomic_t fixup_cnt;
spinlock_t list_lock; spinlock_t list_lock;
wait_queue_head_t list_wait; wait_queue_head_t list_wait;
u16 csum_size; u16 csum_size;
...@@ -102,6 +104,14 @@ struct scrub_dev { ...@@ -102,6 +104,14 @@ struct scrub_dev {
spinlock_t stat_lock; spinlock_t stat_lock;
}; };
struct scrub_fixup_nodatasum {
struct scrub_dev *sdev;
u64 logical;
struct btrfs_root *root;
struct btrfs_work work;
int mirror_num;
};
struct scrub_warning { struct scrub_warning {
struct btrfs_path *path; struct btrfs_path *path;
u64 extent_item_size; u64 extent_item_size;
...@@ -190,12 +200,13 @@ struct scrub_dev *scrub_setup_dev(struct btrfs_device *dev) ...@@ -190,12 +200,13 @@ struct scrub_dev *scrub_setup_dev(struct btrfs_device *dev)
if (i != SCRUB_BIOS_PER_DEV-1) if (i != SCRUB_BIOS_PER_DEV-1)
sdev->bios[i]->next_free = i + 1; sdev->bios[i]->next_free = i + 1;
else else
sdev->bios[i]->next_free = -1; sdev->bios[i]->next_free = -1;
} }
sdev->first_free = 0; sdev->first_free = 0;
sdev->curr = -1; sdev->curr = -1;
atomic_set(&sdev->in_flight, 0); atomic_set(&sdev->in_flight, 0);
atomic_set(&sdev->fixup_cnt, 0);
atomic_set(&sdev->cancel_req, 0); atomic_set(&sdev->cancel_req, 0);
sdev->csum_size = btrfs_super_csum_size(&fs_info->super_copy); sdev->csum_size = btrfs_super_csum_size(&fs_info->super_copy);
INIT_LIST_HEAD(&sdev->csum_list); INIT_LIST_HEAD(&sdev->csum_list);
...@@ -347,6 +358,151 @@ static void scrub_print_warning(const char *errstr, struct scrub_bio *sbio, ...@@ -347,6 +358,151 @@ static void scrub_print_warning(const char *errstr, struct scrub_bio *sbio,
kfree(swarn.msg_buf); kfree(swarn.msg_buf);
} }
static int scrub_fixup_readpage(u64 inum, u64 offset, u64 root, void *ctx)
{
struct page *page;
unsigned long index;
struct scrub_fixup_nodatasum *fixup = ctx;
int ret;
int corrected;
struct btrfs_key key;
struct inode *inode;
u64 end = offset + PAGE_SIZE - 1;
struct btrfs_root *local_root;
key.objectid = root;
key.type = BTRFS_ROOT_ITEM_KEY;
key.offset = (u64)-1;
local_root = btrfs_read_fs_root_no_name(fixup->root->fs_info, &key);
if (IS_ERR(local_root))
return PTR_ERR(local_root);
key.type = BTRFS_INODE_ITEM_KEY;
key.objectid = inum;
key.offset = 0;
inode = btrfs_iget(fixup->root->fs_info->sb, &key, local_root, NULL);
if (IS_ERR(inode))
return PTR_ERR(inode);
ret = set_extent_bit(&BTRFS_I(inode)->io_tree, offset, end,
EXTENT_DAMAGED, 0, NULL, NULL, GFP_NOFS);
/* set_extent_bit should either succeed or give proper error */
WARN_ON(ret > 0);
if (ret)
return ret < 0 ? ret : -EFAULT;
index = offset >> PAGE_CACHE_SHIFT;
page = find_or_create_page(inode->i_mapping, index, GFP_NOFS);
if (!page)
return -ENOMEM;
ret = extent_read_full_page(&BTRFS_I(inode)->io_tree, page,
btrfs_get_extent, fixup->mirror_num);
wait_on_page_locked(page);
corrected = !test_range_bit(&BTRFS_I(inode)->io_tree, offset, end,
EXTENT_DAMAGED, 0, NULL);
if (corrected)
WARN_ON(!PageUptodate(page));
else
clear_extent_bit(&BTRFS_I(inode)->io_tree, offset, end,
EXTENT_DAMAGED, 0, 0, NULL, GFP_NOFS);
put_page(page);
iput(inode);
if (ret < 0)
return ret;
if (ret == 0 && corrected) {
/*
* we only need to call readpage for one of the inodes belonging
* to this extent. so make iterate_extent_inodes stop
*/
return 1;
}
return -EIO;
}
static void scrub_fixup_nodatasum(struct btrfs_work *work)
{
int ret;
struct scrub_fixup_nodatasum *fixup;
struct scrub_dev *sdev;
struct btrfs_trans_handle *trans = NULL;
struct btrfs_fs_info *fs_info;
struct btrfs_path *path;
int uncorrectable = 0;
fixup = container_of(work, struct scrub_fixup_nodatasum, work);
sdev = fixup->sdev;
fs_info = fixup->root->fs_info;
path = btrfs_alloc_path();
if (!path) {
spin_lock(&sdev->stat_lock);
++sdev->stat.malloc_errors;
spin_unlock(&sdev->stat_lock);
uncorrectable = 1;
goto out;
}
trans = btrfs_join_transaction(fixup->root);
if (IS_ERR(trans)) {
uncorrectable = 1;
goto out;
}
/*
* the idea is to trigger a regular read through the standard path. we
* read a page from the (failed) logical address by specifying the
* corresponding copynum of the failed sector. thus, that readpage is
* expected to fail.
* that is the point where on-the-fly error correction will kick in
* (once it's finished) and rewrite the failed sector if a good copy
* can be found.
*/
ret = iterate_inodes_from_logical(fixup->logical, fixup->root->fs_info,
path, scrub_fixup_readpage,
fixup);
if (ret < 0) {
uncorrectable = 1;
goto out;
}
WARN_ON(ret != 1);
spin_lock(&sdev->stat_lock);
++sdev->stat.corrected_errors;
spin_unlock(&sdev->stat_lock);
out:
if (trans && !IS_ERR(trans))
btrfs_end_transaction(trans, fixup->root);
if (uncorrectable) {
spin_lock(&sdev->stat_lock);
++sdev->stat.uncorrectable_errors;
spin_unlock(&sdev->stat_lock);
printk_ratelimited(KERN_ERR "btrfs: unable to fixup "
"(nodatasum) error at logical %llu\n",
fixup->logical);
}
btrfs_free_path(path);
kfree(fixup);
/* see caller why we're pretending to be paused in the scrub counters */
mutex_lock(&fs_info->scrub_lock);
atomic_dec(&fs_info->scrubs_running);
atomic_dec(&fs_info->scrubs_paused);
mutex_unlock(&fs_info->scrub_lock);
atomic_dec(&sdev->fixup_cnt);
wake_up(&fs_info->scrub_pause_wait);
wake_up(&sdev->list_wait);
}
/* /*
* scrub_recheck_error gets called when either verification of the page * scrub_recheck_error gets called when either verification of the page
* failed or the bio failed to read, e.g. with EIO. In the latter case, * failed or the bio failed to read, e.g. with EIO. In the latter case,
...@@ -417,6 +573,7 @@ static void scrub_fixup(struct scrub_bio *sbio, int ix) ...@@ -417,6 +573,7 @@ static void scrub_fixup(struct scrub_bio *sbio, int ix)
struct btrfs_fs_info *fs_info = sdev->dev->dev_root->fs_info; struct btrfs_fs_info *fs_info = sdev->dev->dev_root->fs_info;
struct btrfs_mapping_tree *map_tree = &fs_info->mapping_tree; struct btrfs_mapping_tree *map_tree = &fs_info->mapping_tree;
struct btrfs_multi_bio *multi = NULL; struct btrfs_multi_bio *multi = NULL;
struct scrub_fixup_nodatasum *fixup;
u64 logical = sbio->logical + ix * PAGE_SIZE; u64 logical = sbio->logical + ix * PAGE_SIZE;
u64 length; u64 length;
int i; int i;
...@@ -425,12 +582,30 @@ static void scrub_fixup(struct scrub_bio *sbio, int ix) ...@@ -425,12 +582,30 @@ static void scrub_fixup(struct scrub_bio *sbio, int ix)
if ((sbio->spag[ix].flags & BTRFS_EXTENT_FLAG_DATA) && if ((sbio->spag[ix].flags & BTRFS_EXTENT_FLAG_DATA) &&
(sbio->spag[ix].have_csum == 0)) { (sbio->spag[ix].have_csum == 0)) {
fixup = kzalloc(sizeof(*fixup), GFP_NOFS);
if (!fixup)
goto uncorrectable;
fixup->sdev = sdev;
fixup->logical = logical;
fixup->root = fs_info->extent_root;
fixup->mirror_num = sbio->spag[ix].mirror_num;
/* /*
* nodatasum, don't try to fix anything * increment scrubs_running to prevent cancel requests from
* FIXME: we can do better, open the inode and trigger a * completing as long as a fixup worker is running. we must also
* writeback * increment scrubs_paused to prevent deadlocking on pause
* requests used for transactions commits (as the worker uses a
* transaction context). it is safe to regard the fixup worker
* as paused for all matters practical. effectively, we only
* avoid cancellation requests from completing.
*/ */
goto uncorrectable; mutex_lock(&fs_info->scrub_lock);
atomic_inc(&fs_info->scrubs_running);
atomic_inc(&fs_info->scrubs_paused);
mutex_unlock(&fs_info->scrub_lock);
atomic_inc(&sdev->fixup_cnt);
fixup->work.func = scrub_fixup_nodatasum;
btrfs_queue_worker(&fs_info->scrub_workers, &fixup->work);
return;
} }
length = PAGE_SIZE; length = PAGE_SIZE;
...@@ -1425,10 +1600,11 @@ int btrfs_scrub_dev(struct btrfs_root *root, u64 devid, u64 start, u64 end, ...@@ -1425,10 +1600,11 @@ int btrfs_scrub_dev(struct btrfs_root *root, u64 devid, u64 start, u64 end,
ret = scrub_enumerate_chunks(sdev, start, end); ret = scrub_enumerate_chunks(sdev, start, end);
wait_event(sdev->list_wait, atomic_read(&sdev->in_flight) == 0); wait_event(sdev->list_wait, atomic_read(&sdev->in_flight) == 0);
atomic_dec(&fs_info->scrubs_running); atomic_dec(&fs_info->scrubs_running);
wake_up(&fs_info->scrub_pause_wait); wake_up(&fs_info->scrub_pause_wait);
wait_event(sdev->list_wait, atomic_read(&sdev->fixup_cnt) == 0);
if (progress) if (progress)
memcpy(progress, &sdev->stat, sizeof(*progress)); memcpy(progress, &sdev->stat, sizeof(*progress));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment