From 69059ceaf386035a10aefd83c7e8ca6817494318 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 14 May 2025 08:36:06 +0930 Subject: [PATCH 1/9] btrfs-progs: replace: fix an unexpected new line when replace failed [BUG] When a device replace failed, e.g. try to replace a device on a RO mounted btrfs, the error message is incorrectly broken into two lines: [adam@btrfs-vm ~]$ sudo btrfs replace start -fB 1 /dev/test/scratch3 /mnt/btrfs/ Performing full device TRIM /dev/mapper/test-scratch3 (10.00GiB) ... ERROR: ioctl(DEV_REPLACE_START) failed on "/mnt/btrfs/": Read-only file system [adam@btrfs-vm ~]$ Note the newline after the "Read-only file system" error message. [CAUSE] Inside cmd_replace_start(), if the ioctl failed we need to handle the error messages different depeneding on start_args.result. If the result is not BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT we will append extra info to the error message. But the initial error message is using error(), which implies a newline. This results the above incorrectly splitted error message. [FIX] Instead of manually appending an extra reason to the existing error message, just do different output depending on the start_args.result in the first place. Signed-off-by: Qu Wenruo Reviewed-by: Anand Jain --- cmds/replace.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cmds/replace.c b/cmds/replace.c index 5f1222b241..887c3251a7 100644 --- a/cmds/replace.c +++ b/cmds/replace.c @@ -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"); From 0e999c9bd3e7147e8ffbef176996f8959cf08db4 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sun, 11 May 2025 14:24:02 +0930 Subject: [PATCH 2/9] btrfs-progs: introduce "btrfs rescue fix-data-checksum" It's a long known problem that direct IO can lead to data checksum mismatches if the user space is also modifying its buffer during writeback. Although it's fixed in the recent v6.15 release (and backported to older kernels), we still need a user friendly way to fix those problems. This patch introduce the dryrun version of "btrfs rescue fix-data-checksum", which reports the logical bytenr and corrupted mirrors. Signed-off-by: Qu Wenruo --- Documentation/btrfs-rescue.rst | 19 +++ Makefile | 2 +- cmds/rescue-fix-data-checksum.c | 288 ++++++++++++++++++++++++++++++++ cmds/rescue.c | 48 ++++++ cmds/rescue.h | 7 + 5 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 cmds/rescue-fix-data-checksum.c diff --git a/Documentation/btrfs-rescue.rst b/Documentation/btrfs-rescue.rst index f52e6c2635..7a237ba8eb 100644 --- a/Documentation/btrfs-rescue.rst +++ b/Documentation/btrfs-rescue.rst @@ -50,6 +50,25 @@ fix-device-size WARNING: CPU: 3 PID: 439 at fs/btrfs/ctree.h:1559 btrfs_update_device+0x1c5/0x1d0 [btrfs] +fix-data-checksum + 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 + .. _man-rescue-clear-ino-cache: clear-ino-cache diff --git a/Makefile b/Makefile index 7e36aa4257..523b834955 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/cmds/rescue-fix-data-checksum.c b/cmds/rescue-fix-data-checksum.c new file mode 100644 index 0000000000..7e613ba57f --- /dev/null +++ b/cmds/rescue-fix-data-checksum.c @@ -0,0 +1,288 @@ +/* + * 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. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +#include "kerncompat.h" +#include "kernel-shared/disk-io.h" +#include "kernel-shared/ctree.h" +#include "kernel-shared/volumes.h" +#include "common/messages.h" +#include "common/open-utils.h" +#include "cmds/rescue.h" + +/* + * Record one corrupted data blocks. + * + * We do not report immediately, this is for future file deleting support. + */ +struct corrupted_block { + struct list_head list; + /* The logical bytenr of the exact corrupted block. */ + u64 logical; + + /* The amount of mirrors above logical have. */ + unsigned int num_mirrors; + + /* + * Which mirror failed. + * + * Note, bit 0 means mirror 1, since mirror 0 means choosing a + * live mirror, and we never utilized that mirror 0. + */ + unsigned long *error_mirror_bitmap; +}; + +static int global_repair_mode; +LIST_HEAD(corrupted_blocks); + +static int add_corrupted_block(struct btrfs_fs_info *fs_info, u64 logical, + unsigned int mirror, unsigned int num_mirrors) +{ + struct corrupted_block *last; + if (list_empty(&corrupted_blocks)) + goto add; + + last = list_entry(corrupted_blocks.prev, struct corrupted_block, list); + /* The last entry is the same, just set update the error mirror bitmap. */ + if (last->logical == logical) { + UASSERT(last->error_mirror_bitmap); + set_bit(mirror, last->error_mirror_bitmap); + return 0; + } +add: + last = calloc(1, sizeof(*last)); + if (!last) + return -ENOMEM; + last->error_mirror_bitmap = calloc(1, BITS_TO_LONGS(num_mirrors)); + if (!last->error_mirror_bitmap) { + free(last); + return -ENOMEM; + } + set_bit(mirror - 1, last->error_mirror_bitmap); + last->logical = logical; + last->num_mirrors = num_mirrors; + + list_add_tail(&last->list, &corrupted_blocks); + return 0; +} + +/* + * Verify all mirrors for @logical. + * + * If something critical happened, return <0 and should end the run immediately. + * Otherwise return 0, including data checksum mismatch or read failure. + */ +static int verify_one_data_block(struct btrfs_fs_info *fs_info, + struct extent_buffer *leaf, + unsigned long leaf_offset, u64 logical, + unsigned int num_mirrors) +{ + const u32 blocksize = fs_info->sectorsize; + const u32 csum_size = fs_info->csum_size; + u8 *buf; + u8 csum[BTRFS_CSUM_SIZE]; + u8 csum_expected[BTRFS_CSUM_SIZE]; + int ret = 0; + + buf = malloc(blocksize); + if (!buf) + return -ENOMEM; + + for (int mirror = 1; mirror <= num_mirrors; mirror++) { + u64 read_len = blocksize; + + ret = read_data_from_disk(fs_info, buf, logical, &read_len, mirror); + if (ret < 0) { + /* IO error, add one record. */ + ret = add_corrupted_block(fs_info, logical, mirror, num_mirrors); + if (ret < 0) + break; + } + /* Verify the data checksum. */ + btrfs_csum_data(fs_info, fs_info->csum_type, buf, csum, blocksize); + read_extent_buffer(leaf, csum_expected, leaf_offset, csum_size); + if (memcmp(csum_expected, csum, csum_size) != 0) { + ret = add_corrupted_block(fs_info, logical, mirror, num_mirrors); + if (ret < 0) + break; + } + } + + free(buf); + return ret; +} + +static int iterate_one_csum_item(struct btrfs_fs_info *fs_info, struct btrfs_path *path) +{ + struct btrfs_key key; + const unsigned long item_ptr_off = btrfs_item_ptr_offset(path->nodes[0], + path->slots[0]); + const u32 blocksize = fs_info->sectorsize; + int num_mirrors; + u64 data_size; + u64 cur; + char *buf; + int ret = 0; + + buf = malloc(blocksize); + if (!buf) + return -ENOMEM; + + btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]); + data_size = btrfs_item_size(path->nodes[0], path->slots[0]) / + fs_info->csum_size * blocksize; + num_mirrors = btrfs_num_copies(fs_info, key.offset, data_size); + + for (cur = 0; cur < data_size; cur += blocksize) { + const unsigned long leaf_offset = item_ptr_off + + cur / blocksize * fs_info->csum_size; + + ret = verify_one_data_block(fs_info, path->nodes[0], leaf_offset, + key.offset + cur, num_mirrors); + if (ret < 0) + break; + } + free(buf); + return ret; +} + +static int iterate_csum_root(struct btrfs_fs_info *fs_info, struct btrfs_root *csum_root) +{ + struct btrfs_path path = { 0 }; + struct btrfs_key key; + int ret; + + key.objectid = 0; + key.type = 0; + key.offset = 0; + + ret = btrfs_search_slot(NULL, csum_root, &key, &path, 0, 0); + if (ret < 0) { + errno = -ret; + error("failed to get the first tree block of csum tree: %m"); + return ret; + } + UASSERT(ret > 0); + while (true) { + btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); + if (key.type != BTRFS_EXTENT_CSUM_KEY) + goto next; + ret = iterate_one_csum_item(fs_info, &path); + if (ret < 0) + break; +next: + ret = btrfs_next_item(csum_root, &path); + if (ret > 0) { + ret = 0; + break; + } + if (ret < 0) { + errno = -ret; + error("failed to get next csum item: %m"); + } + } + btrfs_release_path(&path); + return ret; +} + +static void report_corrupted_blocks(void) +{ + struct corrupted_block *entry; + + if (list_empty(&corrupted_blocks)) { + printf("No data checksum mismatch found\n"); + return; + } + + list_for_each_entry(entry, &corrupted_blocks, list) { + bool has_printed = false; + + printf("logical=%llu corrtuped mirrors=", entry->logical); + /* Poor man's bitmap print. */ + for (int i = 0; i < entry->num_mirrors; i++) { + if (test_bit(i, entry->error_mirror_bitmap)) { + if (has_printed) + printf(","); + /* + * Bit 0 means mirror 1, thus we need to increase + * the value by 1. + */ + printf("%d", i + 1); + has_printed=true; + } + } + printf("\n"); + } +} + +static void free_corrupted_blocks(void) +{ + while (!list_empty(&corrupted_blocks)) { + struct corrupted_block *entry; + + entry = list_entry(corrupted_blocks.next, struct corrupted_block, list); + list_del_init(&entry->list); + free(entry->error_mirror_bitmap); + free(entry); + } +} + +int btrfs_recover_fix_data_checksum(const char *path, + enum btrfs_fix_data_checksum_mode mode) +{ + struct btrfs_fs_info *fs_info; + struct btrfs_root *csum_root; + struct open_ctree_args oca = { 0 }; + int ret; + + if (mode >= BTRFS_FIX_DATA_CSUMS_LAST) + return -EINVAL; + + ret = check_mounted(path); + if (ret < 0) { + errno = -ret; + error("could not check mount status: %m"); + return ret; + } + if (ret > 0) { + error("%s is currently mounted", path); + return -EBUSY; + } + + global_repair_mode = mode; + oca.filename = path; + oca.flags = OPEN_CTREE_WRITES; + fs_info = open_ctree_fs_info(&oca); + if (!fs_info) { + error("failed to open btrfs at %s", path); + return -EIO; + } + csum_root = btrfs_csum_root(fs_info, 0); + if (!csum_root) { + error("failed to get csum root"); + ret = -EIO; + goto out_close; + } + ret = iterate_csum_root(fs_info, csum_root); + if (ret) { + errno = -ret; + error("failed to iterate csum tree: %m"); + } + report_corrupted_blocks(); +out_close: + free_corrupted_blocks(); + close_ctree_fs_info(fs_info); + return ret; +} diff --git a/cmds/rescue.c b/cmds/rescue.c index c60bf11675..0d3f86f7e8 100644 --- a/cmds/rescue.c +++ b/cmds/rescue.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "kernel-lib/list.h" #include "kernel-shared/ctree.h" #include "kernel-shared/volumes.h" @@ -275,6 +276,52 @@ static int cmd_rescue_fix_device_size(const struct cmd_struct *cmd, } static DEFINE_SIMPLE_COMMAND(rescue_fix_device_size, "fix-device-size"); +static const char * const cmd_rescue_fix_data_checksum_usage[] = { + "btrfs rescue fix-data-checksum ", + "Fix data checksum mismatches.", + "", + OPTLINE("-r", "readonly mode, only report errors without repair"), + HELPINFO_INSERT_GLOBALS, + HELPINFO_INSERT_VERBOSE, + NULL +}; + +static int cmd_rescue_fix_data_checksum(const struct cmd_struct *cmd, + int argc, char **argv) +{ + enum btrfs_fix_data_checksum_mode mode = BTRFS_FIX_DATA_CSUMS_READONLY; + int ret; + optind = 0; + + while (1) { + int c; + enum { GETOPT_VAL_DRYRUN = GETOPT_VAL_FIRST, }; + static const struct option long_options [] = { + {"readonly", no_argument, NULL, 'r'}, + {"NULL", 0, NULL, 0}, + }; + c = getopt_long(argc, argv, "r", long_options, NULL); + if (c < 0) + break; + switch (c) { + case 'r': + mode = BTRFS_FIX_DATA_CSUMS_READONLY; + break; + default: + usage_unknown_option(cmd, argv); + } + } + if (check_argc_min(argc - optind, 1)) + return 1; + ret = btrfs_recover_fix_data_checksum(argv[optind], mode); + if (ret < 0) { + errno = -ret; + error("failed to fix data checksums: %m"); + } + return !!ret; +} +static DEFINE_SIMPLE_COMMAND(rescue_fix_data_checksum, "fix-data-checksum"); + static const char * const cmd_rescue_create_control_device_usage[] = { "btrfs rescue create-control-device", "Create /dev/btrfs-control (see 'CONTROL DEVICE' in btrfs(5))", @@ -527,6 +574,7 @@ static const struct cmd_group rescue_cmd_group = { &cmd_struct_rescue_super_recover, &cmd_struct_rescue_zero_log, &cmd_struct_rescue_fix_device_size, + &cmd_struct_rescue_fix_data_checksum, &cmd_struct_rescue_create_control_device, &cmd_struct_rescue_clear_ino_cache, &cmd_struct_rescue_clear_space_cache, diff --git a/cmds/rescue.h b/cmds/rescue.h index 5a9e46b7aa..331b595f1c 100644 --- a/cmds/rescue.h +++ b/cmds/rescue.h @@ -20,7 +20,14 @@ #ifndef __BTRFS_RESCUE_H__ #define __BTRFS_RESCUE_H__ +enum btrfs_fix_data_checksum_mode { + BTRFS_FIX_DATA_CSUMS_READONLY, + BTRFS_FIX_DATA_CSUMS_LAST, +}; + int btrfs_recover_superblocks(const char *path, int yes); int btrfs_recover_chunk_tree(const char *path, int yes); +int btrfs_recover_fix_data_checksum(const char *path, + enum btrfs_fix_data_checksum_mode mode); #endif From b52aee0182bd91f226409aa7c407990cee18d9a3 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sun, 11 May 2025 15:55:00 +0930 Subject: [PATCH 3/9] btrfs-progs: fix a bug in btrfs_find_item() [BUG] There is a seldomly utilized function, btrfs_find_item(), which has no document and is not behaving correctly. Inside backref.c, iterate_inode_refs() and btrfs_ref_to_path() both utilize this function, to find the parent inode. However btrfs_find_item() will never return 0 if @ioff is passed as 0 for such usage, result early failure for all kinds of inode iteration functions. [CAUSE] Both functions pass 0 as the @ioff parameter initially, e.g: We have the following fs tree root: item 0 key (256 INODE_ITEM 0) itemoff 16123 itemsize 160 generation 3 transid 9 size 6 nbytes 16384 block group 0 mode 40755 links 1 uid 0 gid 0 rdev 0 sequence 1 flags 0x0(none) item 1 key (256 INODE_REF 256) itemoff 16111 itemsize 12 index 0 namelen 2 name: .. item 2 key (256 DIR_ITEM 2507850652) itemoff 16078 itemsize 33 location key (257 INODE_ITEM 0) type FILE transid 9 data_len 0 name_len 3 name: foo item 3 key (256 DIR_INDEX 2) itemoff 16045 itemsize 33 location key (257 INODE_ITEM 0) type FILE transid 9 data_len 0 name_len 3 name: foo item 4 key (257 INODE_ITEM 0) itemoff 15885 itemsize 160 generation 9 transid 9 size 16384 nbytes 16384 block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0 sequence 4 flags 0x0(none) item 5 key (257 INODE_REF 256) itemoff 15872 itemsize 13 index 2 namelen 3 name: foo item 6 key (257 EXTENT_DATA 0) itemoff 15819 itemsize 53 generation 9 type 1 (regular) extent data disk byte 13631488 nr 16384 extent data offset 0 nr 16384 ram 16384 extent compression 0 (none) Then we call paths_from_inode() with: - @inum = 257 - ipath = {.fs_root = 5} Then we got the following sequence: iterate_irefs(257, fs_root, inode_to_path) |- iterate_inode_refs() |- inode_ref_info() |- btrfs_find_item(257, 0, fs_root) | Returned 1, with @found_key updated to | (257, INODE_REF, 256). This makes iterate_irefs() exit immediately, but obviously that btrfs_find_item() call is to find any INODE_REF, not to find the exact match. [FIX] If btrfs_find_item() found an item matching the objectid and type, then it should return 0 other than 1. Fix it and keep the behavior the same across btrfs-progs and the kernel. Since we're here, also add some comments explaining the function. Signed-off-by: Qu Wenruo --- kernel-shared/ctree.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/kernel-shared/ctree.c b/kernel-shared/ctree.c index 3184c91617..f90de606e7 100644 --- a/kernel-shared/ctree.c +++ b/kernel-shared/ctree.c @@ -1246,6 +1246,17 @@ static void reada_for_search(struct btrfs_fs_info *fs_info, } } +/* + * Find the first key in @fs_root that matches all the following conditions: + * + * - key.obojectid == @iobjectid + * - key.type == @key_type + * - key.offset >= ioff + * + * Return 0 if such key can be found, and @found_key is updated. + * Return >0 if no such key can be found. + * Return <0 for critical errors. + */ int btrfs_find_item(struct btrfs_root *fs_root, struct btrfs_path *found_path, u64 iobjectid, u64 ioff, u8 key_type, struct btrfs_key *found_key) @@ -1280,10 +1291,10 @@ int btrfs_find_item(struct btrfs_root *fs_root, struct btrfs_path *found_path, btrfs_item_key_to_cpu(eb, found_key, path->slots[0]); if (found_key->type != key.type || - found_key->objectid != key.objectid) { + found_key->objectid != key.objectid) ret = 1; - goto out; - } + else + ret = 0; out: if (path != found_path) From 108149bf4cd6806d4d5457c97372cbcafb57237d Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sun, 11 May 2025 16:09:02 +0930 Subject: [PATCH 4/9] btrfs-progs: fix-data-checksum: show affected files Previously "btrfs rescue fix-data-checksum" only show affected logical bytenr, which is not helpful to determine which files are affected. Enhance the output by also outputting the affected subvolumes (in numeric form), and the file paths inside that subvolume. The example looks like this: logical=13631488 corrtuped mirrors=1 affected files: (subvolume 5)/foo (subvolume 5)/dir/bar logical=13635584 corrtuped mirrors=1 affected files: (subvolume 5)/foo (subvolume 5)/dir/bar Although the end result is still not perfect, it's still much easier to find out which files are affected. Signed-off-by: Qu Wenruo --- cmds/rescue-fix-data-checksum.c | 59 +++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/cmds/rescue-fix-data-checksum.c b/cmds/rescue-fix-data-checksum.c index 7e613ba57f..bf3a65b31c 100644 --- a/cmds/rescue-fix-data-checksum.c +++ b/cmds/rescue-fix-data-checksum.c @@ -18,6 +18,7 @@ #include "kernel-shared/disk-io.h" #include "kernel-shared/ctree.h" #include "kernel-shared/volumes.h" +#include "kernel-shared/backref.h" #include "common/messages.h" #include "common/open-utils.h" #include "cmds/rescue.h" @@ -158,6 +159,49 @@ static int iterate_one_csum_item(struct btrfs_fs_info *fs_info, struct btrfs_pat return ret; } +static int print_filenames(u64 ino, u64 offset, u64 rootid, void *ctx) +{ + struct btrfs_fs_info *fs_info = ctx; + struct btrfs_root *root; + struct btrfs_key key; + struct inode_fs_paths *ipath; + struct btrfs_path path = { 0 }; + int ret; + + key.objectid = rootid; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + + root = btrfs_read_fs_root(fs_info, &key); + if (IS_ERR(root)) { + ret = PTR_ERR(root); + errno = -ret; + error("failed to get subvolume %llu: %m", rootid); + return ret; + } + ipath = init_ipath(128 * BTRFS_PATH_NAME_MAX, root, &path); + if (IS_ERR(ipath)) { + ret = PTR_ERR(ipath); + errno = -ret; + error("failed to initialize ipath: %m"); + return ret; + } + ret = paths_from_inode(ino, ipath); + if (ret < 0) { + errno = -ret; + error("failed to resolve root %llu ino %llu to paths: %m", rootid, ino); + goto out; + } + for (int i = 0; i < ipath->fspath->elem_cnt; i++) + printf(" (subvolume %llu)/%s\n", rootid, (char *)ipath->fspath->val[i]); + if (ipath->fspath->elem_missed) + printf(" (subvolume %llu) %d files not printed\n", rootid, + ipath->fspath->elem_missed); +out: + free_ipath(ipath); + return ret; +} + static int iterate_csum_root(struct btrfs_fs_info *fs_info, struct btrfs_root *csum_root) { struct btrfs_path path = { 0 }; @@ -197,9 +241,10 @@ static int iterate_csum_root(struct btrfs_fs_info *fs_info, struct btrfs_root *c return ret; } -static void report_corrupted_blocks(void) +static void report_corrupted_blocks(struct btrfs_fs_info *fs_info) { struct corrupted_block *entry; + struct btrfs_path path = { 0 }; if (list_empty(&corrupted_blocks)) { printf("No data checksum mismatch found\n"); @@ -208,6 +253,7 @@ static void report_corrupted_blocks(void) list_for_each_entry(entry, &corrupted_blocks, list) { bool has_printed = false; + int ret; printf("logical=%llu corrtuped mirrors=", entry->logical); /* Poor man's bitmap print. */ @@ -223,7 +269,14 @@ static void report_corrupted_blocks(void) has_printed=true; } } - printf("\n"); + printf(" affected files:\n"); + ret = iterate_inodes_from_logical(entry->logical, fs_info, &path, + print_filenames, fs_info); + if (ret < 0) { + errno = -ret; + error("failed to iterate involved files: %m"); + break; + } } } @@ -280,7 +333,7 @@ int btrfs_recover_fix_data_checksum(const char *path, errno = -ret; error("failed to iterate csum tree: %m"); } - report_corrupted_blocks(); + report_corrupted_blocks(fs_info); out_close: free_corrupted_blocks(); close_ctree_fs_info(fs_info); From c58de57afcc7b0501f92c0ff400ccfb6772c0706 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 15 May 2025 16:11:56 +0930 Subject: [PATCH 5/9] btrfs-progs: fix-data-checksum: introduce interactive mode This mode will ask user for how to fix each block. User input can match the first letter or the whole action name to specify given action, the input is verified case insensitive. If no user input is provided, the default action is to ignore the corrupted block. If the input matches no action, a warning is outputted and user must retry until a valid input is provided. Signed-off-by: Qu Wenruo --- Documentation/btrfs-rescue.rst | 3 ++ cmds/rescue-fix-data-checksum.c | 69 ++++++++++++++++++++++++++++++++- cmds/rescue.c | 9 ++++- cmds/rescue.h | 1 + 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/Documentation/btrfs-rescue.rst b/Documentation/btrfs-rescue.rst index 7a237ba8eb..0e6da4397f 100644 --- a/Documentation/btrfs-rescue.rst +++ b/Documentation/btrfs-rescue.rst @@ -69,6 +69,9 @@ fix-data-checksum 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 + .. _man-rescue-clear-ino-cache: clear-ino-cache diff --git a/cmds/rescue-fix-data-checksum.c b/cmds/rescue-fix-data-checksum.c index bf3a65b31c..9a4f3fd73a 100644 --- a/cmds/rescue-fix-data-checksum.c +++ b/cmds/rescue-fix-data-checksum.c @@ -14,6 +14,7 @@ * Boston, MA 021110-1307, USA. */ +#include #include "kerncompat.h" #include "kernel-shared/disk-io.h" #include "kernel-shared/ctree.h" @@ -45,6 +46,21 @@ struct corrupted_block { unsigned long *error_mirror_bitmap; }; +enum fix_data_checksum_action_value { + ACTION_IGNORE, + ACTION_LAST, +}; + +static const struct fix_data_checksum_action { + enum fix_data_checksum_action_value value; + const char *string; +} actions[] = { + [ACTION_IGNORE] = { + .value = ACTION_IGNORE, + .string = "ignore", + }, +}; + static int global_repair_mode; LIST_HEAD(corrupted_blocks); @@ -241,10 +257,49 @@ static int iterate_csum_root(struct btrfs_fs_info *fs_info, struct btrfs_root *c return ret; } -static void report_corrupted_blocks(struct btrfs_fs_info *fs_info) +#define ASK_ACTION_BUFSIZE (32) +static enum fix_data_checksum_action_value ask_action() +{ + char buf[ASK_ACTION_BUFSIZE] = { 0 }; + bool printed; + +again: + printed = false; + for (int i = 0; i < ACTION_LAST; i++) { + if (printed) + printf("/"); + /* Mark Ignore as default */ + if (i == ACTION_IGNORE) + printf("<<%c>>%s", toupper(actions[i].string[0]), + actions[i].string + 1); + else + printf("<%c>%s", toupper(actions[i].string[0]), + actions[i].string + 1); + } + printf(":"); + fflush(stdout); + /* Default to Ignore if no action provided. */ + if (!fgets(buf, sizeof(buf) - 1, stdin)) + return ACTION_IGNORE; + if (buf[0] == '\n') + return ACTION_IGNORE; + /* Check exact match or matching the initial letter. */ + for (int i = 0; i < ACTION_LAST; i++) { + if (strncasecmp(buf, actions[i].string, 1) == 0 || + strncasecmp(buf, actions[i].string, ASK_ACTION_BUFSIZE) == 0) + return actions[i].value; + } + /* No valid action found, retry. */ + warning("invalid action, please retry"); + goto again; +} + +static void report_corrupted_blocks(struct btrfs_fs_info *fs_info, + enum btrfs_fix_data_checksum_mode mode) { struct corrupted_block *entry; struct btrfs_path path = { 0 }; + enum fix_data_checksum_action_value action; if (list_empty(&corrupted_blocks)) { printf("No data checksum mismatch found\n"); @@ -277,6 +332,16 @@ static void report_corrupted_blocks(struct btrfs_fs_info *fs_info) error("failed to iterate involved files: %m"); break; } + switch (mode) { + case BTRFS_FIX_DATA_CSUMS_INTERACTIVE: + action = ask_action(); + UASSERT(action == ACTION_IGNORE); + fallthrough; + case BTRFS_FIX_DATA_CSUMS_READONLY: + break; + default: + UASSERT(0); + } } } @@ -333,7 +398,7 @@ int btrfs_recover_fix_data_checksum(const char *path, errno = -ret; error("failed to iterate csum tree: %m"); } - report_corrupted_blocks(fs_info); + report_corrupted_blocks(fs_info, mode); out_close: free_corrupted_blocks(); close_ctree_fs_info(fs_info); diff --git a/cmds/rescue.c b/cmds/rescue.c index 0d3f86f7e8..0c19de4789 100644 --- a/cmds/rescue.c +++ b/cmds/rescue.c @@ -280,7 +280,8 @@ static const char * const cmd_rescue_fix_data_checksum_usage[] = { "btrfs rescue fix-data-checksum ", "Fix data checksum mismatches.", "", - OPTLINE("-r", "readonly mode, only report errors without repair"), + OPTLINE("-r|--readonly", "readonly mode, only report errors without repair"), + OPTLINE("-i|--interactive", "interactive mode, ignore the error by default."), HELPINFO_INSERT_GLOBALS, HELPINFO_INSERT_VERBOSE, NULL @@ -298,15 +299,19 @@ static int cmd_rescue_fix_data_checksum(const struct cmd_struct *cmd, enum { GETOPT_VAL_DRYRUN = GETOPT_VAL_FIRST, }; static const struct option long_options [] = { {"readonly", no_argument, NULL, 'r'}, + {"interactive", no_argument, NULL, 'i'}, {"NULL", 0, NULL, 0}, }; - c = getopt_long(argc, argv, "r", long_options, NULL); + c = getopt_long(argc, argv, "ri", long_options, NULL); if (c < 0) break; switch (c) { case 'r': mode = BTRFS_FIX_DATA_CSUMS_READONLY; break; + case 'i': + mode = BTRFS_FIX_DATA_CSUMS_INTERACTIVE; + break; default: usage_unknown_option(cmd, argv); } diff --git a/cmds/rescue.h b/cmds/rescue.h index 331b595f1c..4ae43cbb4c 100644 --- a/cmds/rescue.h +++ b/cmds/rescue.h @@ -22,6 +22,7 @@ enum btrfs_fix_data_checksum_mode { BTRFS_FIX_DATA_CSUMS_READONLY, + BTRFS_FIX_DATA_CSUMS_INTERACTIVE, BTRFS_FIX_DATA_CSUMS_LAST, }; From 535b3217010cb31c021707477cbcd0e2231c61b4 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 15 May 2025 16:54:03 +0930 Subject: [PATCH 6/9] btrfs-progs: fix-data-checksum: update csum items to fix csum mismatch This adds a new group of action in the interactive mode to fix a csum mismatch. The output looks like this: logical=13631488 corrtuped mirrors=1 affected files: (subvolume 5)/foo (subvolume 5)/dir/bar <>gnore/<1>:1 Csum item for logical 13631488 updated using data from mirror 1 In the interactive mode, the update-csum-item behavior is outputted as all available mirror numbers. Considering all the existing (and future) action should starts with an alphabet, it's pretty easy to distinguish mirror number from other actions. The update-csum-item action itself is pretty straight-forward, just read out the data from specified mirror, then calculate a new checksum, and update the corresponding csum item in csum tree. Signed-off-by: Qu Wenruo --- cmds/rescue-fix-data-checksum.c | 119 +++++++++++++++++++++++++++++--- kernel-shared/file-item.c | 2 +- kernel-shared/file-item.h | 5 ++ 3 files changed, 114 insertions(+), 12 deletions(-) diff --git a/cmds/rescue-fix-data-checksum.c b/cmds/rescue-fix-data-checksum.c index 9a4f3fd73a..86924d6182 100644 --- a/cmds/rescue-fix-data-checksum.c +++ b/cmds/rescue-fix-data-checksum.c @@ -20,6 +20,8 @@ #include "kernel-shared/ctree.h" #include "kernel-shared/volumes.h" #include "kernel-shared/backref.h" +#include "kernel-shared/transaction.h" +#include "kernel-shared/file-item.h" #include "common/messages.h" #include "common/open-utils.h" #include "cmds/rescue.h" @@ -48,6 +50,7 @@ struct corrupted_block { enum fix_data_checksum_action_value { ACTION_IGNORE, + ACTION_UPDATE_CSUM, ACTION_LAST, }; @@ -59,6 +62,10 @@ static const struct fix_data_checksum_action { .value = ACTION_IGNORE, .string = "ignore", }, + [ACTION_UPDATE_CSUM] = { + .value = ACTION_UPDATE_CSUM, + .string = "update-csum", + }, }; static int global_repair_mode; @@ -258,10 +265,13 @@ static int iterate_csum_root(struct btrfs_fs_info *fs_info, struct btrfs_root *c } #define ASK_ACTION_BUFSIZE (32) -static enum fix_data_checksum_action_value ask_action() +static enum fix_data_checksum_action_value ask_action(unsigned int num_mirrors, + unsigned int *mirror_ret) { + unsigned long ret; char buf[ASK_ACTION_BUFSIZE] = { 0 }; bool printed; + char *endptr; again: printed = false; @@ -269,12 +279,22 @@ static enum fix_data_checksum_action_value ask_action() if (printed) printf("/"); /* Mark Ignore as default */ - if (i == ACTION_IGNORE) + if (i == ACTION_IGNORE) { printf("<<%c>>%s", toupper(actions[i].string[0]), actions[i].string + 1); - else + } else if (i == ACTION_UPDATE_CSUM) { + /* + * For update-csum action, we need a mirror number, + * so output all valid mirrors numbers instead. + */ + for (int cur_mirror = 1; cur_mirror <= num_mirrors; + cur_mirror++) + printf("<%u>", cur_mirror); + } else { printf("<%c>%s", toupper(actions[i].string[0]), actions[i].string + 1); + } + printed = true; } printf(":"); fflush(stdout); @@ -285,13 +305,79 @@ static enum fix_data_checksum_action_value ask_action() return ACTION_IGNORE; /* Check exact match or matching the initial letter. */ for (int i = 0; i < ACTION_LAST; i++) { - if (strncasecmp(buf, actions[i].string, 1) == 0 || - strncasecmp(buf, actions[i].string, ASK_ACTION_BUFSIZE) == 0) + if ((strncasecmp(buf, actions[i].string, 1) == 0 || + strncasecmp(buf, actions[i].string, ASK_ACTION_BUFSIZE) == 0) && + actions[i].value != ACTION_UPDATE_CSUM) return actions[i].value; } - /* No valid action found, retry. */ - warning("invalid action, please retry"); - goto again; + /* No match, check if it's some numeric string. */ + ret = strtoul(buf, &endptr, 10); + if (endptr == buf || ret == ULONG_MAX) { + /* No valid action found, retry. */ + warning("invalid action, please retry"); + goto again; + } + if (ret > num_mirrors || ret == 0) { + warning("invalid mirror number %lu, must be in range [1, %d], please retry", + ret, num_mirrors); + goto again; + } + *mirror_ret = ret; + return ACTION_UPDATE_CSUM; +} + +static int update_csum_item(struct btrfs_fs_info *fs_info, u64 logical, + unsigned int mirror) +{ + struct btrfs_trans_handle *trans; + struct btrfs_root *csum_root = btrfs_csum_root(fs_info, logical); + struct btrfs_path path = { 0 }; + struct btrfs_csum_item *citem; + u64 read_len = fs_info->sectorsize; + u8 csum[BTRFS_CSUM_SIZE] = { 0 }; + u8 *buf; + int ret; + + buf = malloc(fs_info->sectorsize); + if (!buf) + return -ENOMEM; + ret = read_data_from_disk(fs_info, buf, logical, &read_len, mirror); + if (ret < 0) { + errno = -ret; + error("failed to read block at logical %llu mirror %u: %m", + logical, mirror); + goto out; + } + trans = btrfs_start_transaction(csum_root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + errno = -ret; + error_msg(ERROR_MSG_START_TRANS, "%m"); + goto out; + } + citem = btrfs_lookup_csum(trans, csum_root, &path, logical, + BTRFS_EXTENT_CSUM_OBJECTID, fs_info->csum_type, 1); + if (IS_ERR(citem)) { + ret = PTR_ERR(citem); + errno = -ret; + error("failed to find csum item for logical %llu: $m", logical); + btrfs_abort_transaction(trans, ret); + goto out; + } + btrfs_csum_data(fs_info, fs_info->csum_type, buf, csum, fs_info->sectorsize); + write_extent_buffer(path.nodes[0], csum, (unsigned long)citem, fs_info->csum_size); + btrfs_release_path(&path); + ret = btrfs_commit_transaction(trans, csum_root); + if (ret < 0) { + errno = -ret; + error_msg(ERROR_MSG_COMMIT_TRANS, "%m"); + } + printf("Csum item for logical %llu updated using data from mirror %u\n", + logical, mirror); +out: + free(buf); + btrfs_release_path(&path); + return ret; } static void report_corrupted_blocks(struct btrfs_fs_info *fs_info, @@ -307,6 +393,7 @@ static void report_corrupted_blocks(struct btrfs_fs_info *fs_info, } list_for_each_entry(entry, &corrupted_blocks, list) { + unsigned int mirror; bool has_printed = false; int ret; @@ -334,10 +421,20 @@ static void report_corrupted_blocks(struct btrfs_fs_info *fs_info, } switch (mode) { case BTRFS_FIX_DATA_CSUMS_INTERACTIVE: - action = ask_action(); - UASSERT(action == ACTION_IGNORE); - fallthrough; + action = ask_action(entry->num_mirrors, &mirror); + break; case BTRFS_FIX_DATA_CSUMS_READONLY: + action = ACTION_IGNORE; + break; + default: + UASSERT(0); + } + + switch (action) { + case ACTION_IGNORE: + break; + case ACTION_UPDATE_CSUM: + ret = update_csum_item(fs_info, entry->logical, mirror); break; default: UASSERT(0); diff --git a/kernel-shared/file-item.c b/kernel-shared/file-item.c index 18791c0647..503ad657c6 100644 --- a/kernel-shared/file-item.c +++ b/kernel-shared/file-item.c @@ -112,7 +112,7 @@ int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans, return err; } -static struct btrfs_csum_item * +struct btrfs_csum_item * btrfs_lookup_csum(struct btrfs_trans_handle *trans, struct btrfs_root *root, struct btrfs_path *path, diff --git a/kernel-shared/file-item.h b/kernel-shared/file-item.h index cab0bc4e9c..5a5d8da102 100644 --- a/kernel-shared/file-item.h +++ b/kernel-shared/file-item.h @@ -89,6 +89,11 @@ int btrfs_insert_file_extent(struct btrfs_trans_handle *trans, struct btrfs_file_extent_item *stack_fi); int btrfs_csum_file_block(struct btrfs_trans_handle *trans, u64 logical, u64 csum_objectid, u32 csum_type, const char *data); +struct btrfs_csum_item * +btrfs_lookup_csum(struct btrfs_trans_handle *trans, + struct btrfs_root *root, + struct btrfs_path *path, + u64 bytenr, u64 csum_objectid, u16 csum_type, int cow); int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans, struct btrfs_root *root, u64 objectid, u64 offset, const char *buffer, size_t size, From 2c8f5c78cd9804e09bd30ad631aad72c817ef9a1 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 15 May 2025 17:15:55 +0930 Subject: [PATCH 7/9] btrfs-progs: fix-data-checksum: introduce -m|--mirror option This option allows "btrfs rescue fix-data-checksum" to use the specified mirror number to update checksum item for all corrupted mirrors. If the specified number is larger than the max number of mirrors, the real mirror number will be `num % (num_mirrors + 1)`. Signed-off-by: Qu Wenruo --- Documentation/btrfs-rescue.rst | 6 ++++++ cmds/rescue-fix-data-checksum.c | 16 ++++++++++++---- cmds/rescue.c | 16 ++++++++++++++-- cmds/rescue.h | 4 +++- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Documentation/btrfs-rescue.rst b/Documentation/btrfs-rescue.rst index 0e6da4397f..7fc2bde590 100644 --- a/Documentation/btrfs-rescue.rst +++ b/Documentation/btrfs-rescue.rst @@ -72,6 +72,12 @@ fix-data-checksum -i|--interactive interactive mode, ask for how to repair, ignore the error by default + -m|--mirror + 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 diff --git a/cmds/rescue-fix-data-checksum.c b/cmds/rescue-fix-data-checksum.c index 86924d6182..23b59fffe2 100644 --- a/cmds/rescue-fix-data-checksum.c +++ b/cmds/rescue-fix-data-checksum.c @@ -381,7 +381,8 @@ static int update_csum_item(struct btrfs_fs_info *fs_info, u64 logical, } static void report_corrupted_blocks(struct btrfs_fs_info *fs_info, - enum btrfs_fix_data_checksum_mode mode) + enum btrfs_fix_data_checksum_mode mode, + unsigned int mirror) { struct corrupted_block *entry; struct btrfs_path path = { 0 }; @@ -393,7 +394,6 @@ static void report_corrupted_blocks(struct btrfs_fs_info *fs_info, } list_for_each_entry(entry, &corrupted_blocks, list) { - unsigned int mirror; bool has_printed = false; int ret; @@ -426,6 +426,10 @@ static void report_corrupted_blocks(struct btrfs_fs_info *fs_info, case BTRFS_FIX_DATA_CSUMS_READONLY: action = ACTION_IGNORE; break; + case BTRFS_FIX_DATA_CSUMS_UPDATE_CSUM_ITEM: + action = ACTION_UPDATE_CSUM; + mirror = mirror % (entry->num_mirrors + 1); + break; default: UASSERT(0); } @@ -434,6 +438,7 @@ static void report_corrupted_blocks(struct btrfs_fs_info *fs_info, case ACTION_IGNORE: break; case ACTION_UPDATE_CSUM: + UASSERT(mirror > 0 && mirror <= entry->num_mirrors); ret = update_csum_item(fs_info, entry->logical, mirror); break; default: @@ -455,7 +460,8 @@ static void free_corrupted_blocks(void) } int btrfs_recover_fix_data_checksum(const char *path, - enum btrfs_fix_data_checksum_mode mode) + enum btrfs_fix_data_checksum_mode mode, + unsigned int mirror) { struct btrfs_fs_info *fs_info; struct btrfs_root *csum_root; @@ -465,6 +471,8 @@ int btrfs_recover_fix_data_checksum(const char *path, if (mode >= BTRFS_FIX_DATA_CSUMS_LAST) return -EINVAL; + if (mode == BTRFS_FIX_DATA_CSUMS_UPDATE_CSUM_ITEM) + UASSERT(mirror > 0); ret = check_mounted(path); if (ret < 0) { errno = -ret; @@ -495,7 +503,7 @@ int btrfs_recover_fix_data_checksum(const char *path, errno = -ret; error("failed to iterate csum tree: %m"); } - report_corrupted_blocks(fs_info, mode); + report_corrupted_blocks(fs_info, mode, mirror); out_close: free_corrupted_blocks(); close_ctree_fs_info(fs_info); diff --git a/cmds/rescue.c b/cmds/rescue.c index 0c19de4789..f575646c73 100644 --- a/cmds/rescue.c +++ b/cmds/rescue.c @@ -31,6 +31,7 @@ #include "kernel-shared/extent_io.h" #include "kernel-shared/accessors.h" #include "kernel-shared/uapi/btrfs_tree.h" +#include "common/string-utils.h" #include "common/messages.h" #include "common/utils.h" #include "common/help.h" @@ -282,6 +283,7 @@ static const char * const cmd_rescue_fix_data_checksum_usage[] = { "", OPTLINE("-r|--readonly", "readonly mode, only report errors without repair"), OPTLINE("-i|--interactive", "interactive mode, ignore the error by default."), + OPTLINE("-m|--mirror ", "update csum item using specified mirror"), HELPINFO_INSERT_GLOBALS, HELPINFO_INSERT_VERBOSE, NULL @@ -291,6 +293,7 @@ static int cmd_rescue_fix_data_checksum(const struct cmd_struct *cmd, int argc, char **argv) { enum btrfs_fix_data_checksum_mode mode = BTRFS_FIX_DATA_CSUMS_READONLY; + unsigned int mirror = 0; int ret; optind = 0; @@ -300,9 +303,10 @@ static int cmd_rescue_fix_data_checksum(const struct cmd_struct *cmd, static const struct option long_options [] = { {"readonly", no_argument, NULL, 'r'}, {"interactive", no_argument, NULL, 'i'}, + {"mirror", required_argument, NULL, 'm'}, {"NULL", 0, NULL, 0}, }; - c = getopt_long(argc, argv, "ri", long_options, NULL); + c = getopt_long(argc, argv, "rim:", long_options, NULL); if (c < 0) break; switch (c) { @@ -312,13 +316,21 @@ static int cmd_rescue_fix_data_checksum(const struct cmd_struct *cmd, case 'i': mode = BTRFS_FIX_DATA_CSUMS_INTERACTIVE; break; + case 'm': + mode = BTRFS_FIX_DATA_CSUMS_UPDATE_CSUM_ITEM; + mirror = arg_strtou64(optarg); + if (mirror == 0) { + error("invalid mirror number %u, must be >= 1", mirror); + return 1; + } + break; default: usage_unknown_option(cmd, argv); } } if (check_argc_min(argc - optind, 1)) return 1; - ret = btrfs_recover_fix_data_checksum(argv[optind], mode); + ret = btrfs_recover_fix_data_checksum(argv[optind], mode, mirror); if (ret < 0) { errno = -ret; error("failed to fix data checksums: %m"); diff --git a/cmds/rescue.h b/cmds/rescue.h index 4ae43cbb4c..f78ec436a9 100644 --- a/cmds/rescue.h +++ b/cmds/rescue.h @@ -23,12 +23,14 @@ enum btrfs_fix_data_checksum_mode { BTRFS_FIX_DATA_CSUMS_READONLY, BTRFS_FIX_DATA_CSUMS_INTERACTIVE, + BTRFS_FIX_DATA_CSUMS_UPDATE_CSUM_ITEM, BTRFS_FIX_DATA_CSUMS_LAST, }; int btrfs_recover_superblocks(const char *path, int yes); int btrfs_recover_chunk_tree(const char *path, int yes); int btrfs_recover_fix_data_checksum(const char *path, - enum btrfs_fix_data_checksum_mode mode); + enum btrfs_fix_data_checksum_mode mode, + unsigned int mirror); #endif From 4d2733925061856f95ceb90030b0024a41378856 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 29 May 2025 16:39:09 +0930 Subject: [PATCH 8/9] btrfs-progs: check/lowmem: fix a false alert when counting the refs [BUG] For a btrfs which is under metadata balance and interrupted (powerloss etc), we can have the following extent backref item for a data extent: item 22 key (13631488 EXTENT_ITEM 524288) itemoff 3161 itemsize 108 refs 180 gen 9 flags DATA (178 0xdfb591fbbf5f519) extent data backref root FS_TREE objectid 258 offset 0 count 77 (178 0xdfb591fa80d95ea) extent data backref root FS_TREE objectid 257 offset 0 count 1 (184 0x151e000) shared data backref parent 22142976 count 51 (184 0x512000) shared data backref parent 5316608 count 51 Then lowmem mode will cause the following false alert: [3/8] checking extents ERROR: extent[13631488, 524288] referencer count mismatch (root: 5, owner: 258, offset: 0) wanted: 77, have: 128 ERROR: errors found in extent allocation tree or chunk allocation [CAUSE] When shared and keyed backref items are found, we must follow the following rules to avoid incorrect refs count: - If the leaf belongs to the shared backref parent Then any found EXTENT_DATA inside the leaf will be contributed to the shared backref values. - Otherwise any found EXTENT_DATA can contributed to the keyed backref In above case, if our leaf is 5316608 or 22142976, then we should not contribute the number of found EXTENT_DATA to the keyed backref. Unfortunately the original fix d53d42fa2183 ("btrfs-progs: lowmem: fix false alerts of referencer count mismatch for blocks relocated") is not following the above strict rule, but relying on the flag of the leaf. However that RELOC flag is not a correct indicator, e.g in above case the leaf at 5316608 is not yet being relocated, thus no RELOC flag. [FIX] Instead of check the unreliable RELOC flag, follow the correct rule when checking the leaf. Before we start checking the content of a leaf for EXTENT_DATA items, make sure the leaf's bytenr is not in any shared backref item. If so skip to the next leaf. Fixes: d53d42fa2183 ("btrfs-progs: lowmem: fix false alerts of referencer count mismatch for blocks relocated") Signed-off-by: Qu Wenruo --- check/mode-lowmem.c | 145 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 6 deletions(-) diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c index 34af77f884..713ddc3d88 100644 --- a/check/mode-lowmem.c +++ b/check/mode-lowmem.c @@ -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 @@ -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; } From dbcb2d8f2ecbd949c1661b47e3dedbce1ba321ae Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 29 May 2025 11:31:40 +0930 Subject: [PATCH 9/9] btrfs-progs: fsck-tests: fix an image which has incorrect super dev item [CI FAILURE] With Mark's incoming fsck enhancement on super block device item, it exposed the following fs corruption on an existing image dump: restoring image keyed_data_ref_with_reloc_leaf.img ====== RUN CHECK /home/runner/work/btrfs-progs/btrfs-progs/btrfs check ./keyed_data_ref_with_reloc_leaf.img.restored [1/8] checking log skipped (none written) [2/8] checking root items [3/8] checking extents device 1's bytes_used was 55181312 in tree but 59375616 in superblock ERROR: errors found in extent allocation tree or chunk allocation [4/8] checking free space cache [5/8] checking fs roots [6/8] checking only csums items (without verifying data) [7/8] checking root refs [8/8] checking quota groups skipped (not enabled on this FS) Opening filesystem to check... Checking filesystem on ./keyed_data_ref_with_reloc_leaf.img.restored UUID: ca3568a3-d9d8-4901-b4dd-b180a437a07f found 1175552 bytes used, error(s) found total csum bytes: 512 total tree bytes: 651264 total fs tree bytes: 606208 total extent tree bytes: 16384 btree space waste bytes: 291631 file data blocks allocated: 67633152 referenced 1048576 [CAUSE] The image has the following device item in the super block: dev_item.uuid 5a1c9f96-b581-4fd3-a73a-5cfd789c3c84 dev_item.fsid ca3568a3-d9d8-4901-b4dd-b180a437a07f [match] dev_item.type 0 dev_item.total_bytes 67108864 dev_item.bytes_used 59375616 Meanwhile the tree dump shows the following device item in the chunk tree: item 0 key (DEV_ITEMS DEV_ITEM 1) itemoff 3897 itemsize 98 devid 1 total_bytes 67108864 bytes_used 55181312 io_align 4096 io_width 4096 sector_size 4096 type 0 The bytes_used value does not match and triggered the fsck failure. The root cause is that kernel has a bug that removed device extents will not be accounted during transaction commit, this bug will be fixed by Mark's patch: https://lore.kernel.org/linux-btrfs/20250529093821.2818081-1-maharmstone@fb.com/ [FIX] Re-generate the image with the latest kernel so that the mismatch won't happen. Also for any future updates on this image, here is the needed steps to re-create the image. - Modify the kernel btrfs module to commit transaction more frequently The idea is to replace the btrfs_end_transaction_throttle() call with btrfs_commit_transaction() in the while() loop of relocate_block_group(). So that every time a tree block is relocated, the fs will be committed. - Use the following script to populate the fs with log-writes This requries CONFIG_DM_LOG_WRITES to be enabled. ``` dev_src="/dev/test/scratch1" dev_log="/dev/test/log" dev="/dev/mapper/log" mnt="/mnt/btrfs/" table="0 $(blockdev --getsz $dev_src) log-writes $dev_src $dev_log" fail() { echo "!!! FAILED !!!" exit 1 } umount $dev &> /dev/null umount $mnt &> /dev/null dmsetup create log --table "$table" || fail mkfs.btrfs -b 1G -n 4k -f -m single $dev mount $dev $mnt xfs_io -f -c "pwrite 0 512k" $mnt/src > /dev/null sync for ((i = 0; i < 128; i++)); do xfs_io -f -c "reflink $mnt/src $(($i * 4096)) $(($i * 4096)) 4K" $mnt/file1 > /dev/null || fail done for ((i = 0; i < 128; i++)); do xfs_io -f -c "pwrite 0 2k" $mnt/inline_$i > /dev/null || fail done sync dmsetup message log 0 mark init btrfs balance start -m $mnt umount $mnt dmsetup remove log ``` - Replay the log and run "btrfs dump-tree -t extent" for each fua Use the log-writes tool (https://github.com/josefbacik/log-writes.git) to replay the log. And there will be one with the following contents for the data extent item, with mixed keyed and shared backref. item 22 key (13631488 EXTENT_ITEM 524288) itemoff 3161 itemsize 108 refs 180 gen 9 flags DATA (178 0xdfb591fbbf5f519) extent data backref root FS_TREE objectid 258 offset 0 count 77 (178 0xdfb591fa80d95ea) extent data backref root FS_TREE objectid 257 offset 0 count 1 (184 0x151e000) shared data backref parent 22142976 count 51 (184 0x512000) shared data backref parent 5316608 count 51 - Take the image of that fs This image has one extra benefit, that it exposed a bug in the older lowmem code where shared backref number is not correctly skipped, thus this image will also act as a regression test. Signed-off-by: Qu Wenruo --- .../keyed_data_ref_with_reloc_leaf.img | Bin 16384 -> 19456 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/fsck-tests/020-extent-ref-cases/keyed_data_ref_with_reloc_leaf.img b/tests/fsck-tests/020-extent-ref-cases/keyed_data_ref_with_reloc_leaf.img index 80345cf9498f296f001b8b63e230bcfb9293300d..02c63ef7c4e665fc10b5681ce96d09a5c5755883 100644 GIT binary patch literal 19456 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 f^iN?gag_*<{!M