Skip to content

btrfs-progs: fix an old bug in lowmem mode #992

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Documentation/btrfs-rescue.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,34 @@ fix-device-size <device>

WARNING: CPU: 3 PID: 439 at fs/btrfs/ctree.h:1559 btrfs_update_device+0x1c5/0x1d0 [btrfs]

fix-data-checksum <device>
fix data checksum mismatch

There is a long existing problem that if a user space program is doing
direct IO and modifies the buffer before the write back finished, it
can lead to data checksum mismatches.

This problem is known but not fixed until upstream release v6.15
(backported to older kernels). So it's possible to hit false data
checksum mismatch for any long running btrfs.

In that case this program can be utilized to repair such problem.

``Options``

-r|--readonly
readonly mode, only scan and report for data checksum mismatch,
do no repair

-i|--interactive
interactive mode, ask for how to repair, ignore the error by default

-m|--mirror <num>
use specified mirror to update the checksum item for all corrupted blocks.

The value must be >= 1, and if the corrupted block has less mirrors than
the value, the mirror number will be `num % (num_mirrors + 1)`.

.. _man-rescue-clear-ino-cache:

clear-ino-cache <device>
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ cmds_objects = cmds/subvolume.o cmds/subvolume-list.o \
cmds/inspect.o cmds/balance.o cmds/send.o cmds/receive.o \
cmds/quota.o cmds/qgroup.o cmds/replace.o check/main.o \
cmds/restore.o cmds/rescue.o cmds/rescue-chunk-recover.o \
cmds/rescue-super-recover.o \
cmds/rescue-super-recover.o cmds/rescue-fix-data-checksum.o \
cmds/property.o cmds/filesystem-usage.o cmds/inspect-dump-tree.o \
cmds/inspect-dump-super.o cmds/inspect-tree-stats.o cmds/filesystem-du.o \
cmds/reflink.o \
Expand Down
145 changes: 139 additions & 6 deletions check/mode-lowmem.c
Original file line number Diff line number Diff line change
Expand Up @@ -3977,6 +3977,139 @@ static int check_shared_block_backref(u64 parent, u64 bytenr, int level)
return 0;
}

/*
* A read-only version of lookup_inline_extent_backref().
* We can not reuse that function as it always assume COW.
*/
static int has_inline_shared_backref(u64 data_bytenr, u64 data_len, u64 parent)
{
struct btrfs_root *extent_root = btrfs_extent_root(gfs_info, data_bytenr);
struct btrfs_extent_inline_ref *iref;
struct btrfs_extent_item *ei;
struct btrfs_path path = { 0 };
struct extent_buffer *leaf;
struct btrfs_key key;
unsigned long ptr;
unsigned long end;
bool found = false;
u32 item_size;
u64 flags;
int ret;

key.objectid = data_bytenr;
key.type = BTRFS_EXTENT_ITEM_KEY;
key.offset = data_len;
ret = btrfs_search_slot(NULL, extent_root, &key, &path, 0, 0);
if (ret > 0)
ret = -ENOENT;
if (ret < 0)
goto out;

leaf = path.nodes[0];
item_size = btrfs_item_size(leaf, path.slots[0]);
if (item_size < sizeof(*ei)) {
error("extent item size %u < %zu, leaf %llu slot %u",
item_size, sizeof(*ei), leaf->start, path.slots[0]);
ret = -EUCLEAN;
goto out;
}
ei = btrfs_item_ptr(leaf, path.slots[0], struct btrfs_extent_item);
flags = btrfs_extent_flags(leaf, ei);

if (!(flags & BTRFS_EXTENT_FLAG_DATA)) {
error("backref item flag for bytenr %llu is not data",
data_bytenr);
ret = -EUCLEAN;
goto out;
}

ptr = (unsigned long)(ei + 1);
end = (unsigned long)ei + item_size;

while (true) {
u64 ref_parent;
u8 type;

if (ptr >= end) {
if (ptr > end) {
error("inline extent item for %llu is not properly ended",
data_bytenr);
ret = -EUCLEAN;
goto out;
}
break;
}
iref = (struct btrfs_extent_inline_ref *)ptr;
type = btrfs_extent_inline_ref_type(leaf, iref);
if (type != BTRFS_SHARED_DATA_REF_KEY)
goto next;

ref_parent = btrfs_extent_inline_ref_offset(leaf, iref);
if (ref_parent == parent) {
found = true;
goto out;
}
next:
ptr += btrfs_extent_inline_ref_size(type);
}

out:
btrfs_release_path(&path);
if (ret < 0)
return ret;
return found;
}

static int has_keyed_shared_backref(u64 data_bytenr, u64 parent)
{
struct btrfs_root *extent_root = btrfs_extent_root(gfs_info, data_bytenr);
struct btrfs_path path = { 0 };
struct btrfs_key key;
int ret;

key.objectid = data_bytenr;
key.type = BTRFS_SHARED_DATA_REF_KEY;
key.offset = parent;
ret = btrfs_search_slot(NULL, extent_root, &key, &path, 0, 0);
btrfs_release_path(&path);
if (ret < 0)
return ret;
/* No keyed ref found, return 0. */
if (ret > 0)
return 0;
return 1;
}

/*
* A helper to determine if the @leaf already belongs to a shared data backref item.
* (with parent bytenr)
*
* Return >0 if the @leaf belongs to a shared data backref.
* Return 0 if not.
* Return <0 for critical error.
*/
static int is_leaf_shared(struct extent_buffer *leaf, u64 data_bytenr, u64 data_len)
{
int ret;

ret = has_inline_shared_backref(data_bytenr, data_len, leaf->start);
if (ret < 0) {
errno = -ret;
error("failed to search inlined shared backref for logical %llu len %llu, %m",
data_bytenr, data_len);
return ret;
}
if (ret > 0)
return ret;
ret = has_keyed_shared_backref(data_bytenr, leaf->start);
if (ret < 0) {
errno = -ret;
error("failed to search keyed shared backref for logical %llu len %llu, %m",
data_bytenr, data_len);
}
return ret;
}

/*
* Check referencer for normal (inlined) data ref
* If len == 0, it will be resolved by searching in extent tree
Expand Down Expand Up @@ -4049,13 +4182,13 @@ static int check_extent_data_backref(u64 root_id, u64 objectid, u64 offset,
btrfs_header_owner(leaf) != root_id)
goto next;
/*
* For tree blocks have been relocated, data backref are
* shared instead of keyed. Do not account it.
* If the node belongs to a shared backref item, we should not
* account the number.
*/
if (btrfs_header_flag(leaf, BTRFS_HEADER_FLAG_RELOC)) {
/*
* skip the leaf to speed up.
*/
ret = is_leaf_shared(leaf, bytenr, len);
if (ret < 0)
break;
if (ret > 0) {
slot = btrfs_header_nritems(leaf);
goto next;
}
Expand Down
9 changes: 4 additions & 5 deletions cmds/replace.c
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,11 @@ static int cmd_replace_start(const struct cmd_struct *cmd,
ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
if (do_not_background) {
if (ret < 0) {
error("ioctl(DEV_REPLACE_START) failed on \"%s\": %m", path);
if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
pr_stderr(LOG_DEFAULT, ", %s\n",
replace_dev_result2string(start_args.result));
if (start_args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
error("ioctl(DEV_REPLACE_START) failed on \"%s\": %m", path);
else
pr_stderr(LOG_DEFAULT, "\n");
error("ioctl(DEV_REPLACE_START) failed on \"%s\": %m, %s",
path, replace_dev_result2string(start_args.result));

if (errno == EOPNOTSUPP)
warning("device replace of RAID5/6 not supported with this kernel");
Expand Down
Loading