From 0ab80ad1b34c157feb8ce9c07065010126019c18 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sat, 15 Nov 2025 07:25:04 +1030 Subject: [PATCH 1/4] btrfs-progs: check: add repair ability for missing orphan root item There is a known bug in older kernels that orphan items can be missing for dropped subvolumes. This makes those subvolumes unable to be removed on the next mount, and recent kernel commit 4289b494ac55 ("btrfs: do not allow relocation of partially dropped subvolumes") introduced one extra safe net to catch such problem. But unfortunately there is no way to repair it. Add the repair ability to both the original and lowmem modes. Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo --- check/main.c | 5 +++++ check/mode-common.c | 33 +++++++++++++++++++++++++++++++++ check/mode-common.h | 1 + check/mode-lowmem.c | 13 ++++++++++--- 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/check/main.c b/check/main.c index 77458a7690..db055ae194 100644 --- a/check/main.c +++ b/check/main.c @@ -3550,6 +3550,11 @@ static int check_root_refs(struct btrfs_root *root, */ if (!rec->found_root_item) continue; + if (opt_check_repair) { + ret = repair_subvol_orphan_item(gfs_info, rec->objectid); + if (!ret) + continue; + } errors++; fprintf(stderr, "fs tree %llu missing orphan item\n", rec->objectid); } diff --git a/check/mode-common.c b/check/mode-common.c index 0467ba2839..2d11a96dfb 100644 --- a/check/mode-common.c +++ b/check/mode-common.c @@ -1672,3 +1672,36 @@ int check_and_repair_super_num_devs(struct btrfs_fs_info *fs_info) printf("Successfully reset super num devices to %u\n", found_devs); return 0; } + +int repair_subvol_orphan_item(struct btrfs_fs_info *fs_info, u64 rootid) +{ + struct btrfs_root *tree_root = fs_info->tree_root; + struct btrfs_trans_handle *trans; + struct btrfs_path path = { 0 }; + int ret; + + trans = btrfs_start_transaction(tree_root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + errno = -ret; + error_msg(ERROR_MSG_START_TRANS, "%m"); + return ret; + } + ret = btrfs_add_orphan_item(trans, tree_root, &path, rootid); + btrfs_release_path(&path); + if (ret < 0) { + errno = -ret; + error("failed to insert orphan item for subvolume %llu: %m", rootid); + btrfs_abort_transaction(trans, ret); + btrfs_commit_transaction(trans, tree_root); + return ret; + } + ret = btrfs_commit_transaction(trans, tree_root); + if (ret < 0) { + errno = -ret; + error_msg(ERROR_MSG_COMMIT_TRANS, "%m"); + return ret; + } + printf("Added back missing orphan item for subvolume %llu\n", rootid); + return 0; +} diff --git a/check/mode-common.h b/check/mode-common.h index c37b4dc00e..e97835a5b6 100644 --- a/check/mode-common.h +++ b/check/mode-common.h @@ -197,5 +197,6 @@ int repair_dev_item_bytes_used(struct btrfs_fs_info *fs_info, int fill_csum_tree(struct btrfs_trans_handle *trans, bool search_fs_tree); int check_and_repair_super_num_devs(struct btrfs_fs_info *fs_info); +int repair_subvol_orphan_item(struct btrfs_fs_info *fs_info, u64 rootid); #endif diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c index 363dc4ae19..ea4d401782 100644 --- a/check/mode-lowmem.c +++ b/check/mode-lowmem.c @@ -5569,9 +5569,16 @@ static int check_btrfs_root(struct btrfs_root *root, int check_all) * If this tree is a subvolume (not a reloc tree) and has no refs, there * should be an orphan item for it, or this subvolume will never be deleted. */ - if (btrfs_root_refs(root_item) == 0 && is_fstree(btrfs_root_id(root))) { - if (!has_orphan_item(root->fs_info->tree_root, - btrfs_root_id(root))) { + if (btrfs_root_refs(root_item) == 0 && is_fstree(btrfs_root_id(root)) && + !has_orphan_item(root->fs_info->tree_root, btrfs_root_id(root))) { + bool repaired = false; + + if (opt_check_repair) { + ret = repair_subvol_orphan_item(root->fs_info, btrfs_root_id(root)); + if (!ret) + repaired = true; + } + if (!repaired) { error("missing orphan item for root %lld", btrfs_root_id(root)); err |= REFERENCER_MISSING; } From b13e49ab95f754dd3ea06045f547f6f6e864c576 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sat, 15 Nov 2025 07:52:10 +1030 Subject: [PATCH 2/4] btrfs-progs: fsck-tests: make test case 066 to be repairable The test case fsck/066 is only to verify we can detect the missing root orphan item, no repair for it yet. Now the repair ability is added, change the test case to verify the repair is also properly done. Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo --- .../.lowmem_repairable | 0 .../066-missing-root-orphan-item/test.sh | 14 -------------- 2 files changed, 14 deletions(-) create mode 100644 tests/fsck-tests/066-missing-root-orphan-item/.lowmem_repairable delete mode 100755 tests/fsck-tests/066-missing-root-orphan-item/test.sh diff --git a/tests/fsck-tests/066-missing-root-orphan-item/.lowmem_repairable b/tests/fsck-tests/066-missing-root-orphan-item/.lowmem_repairable new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fsck-tests/066-missing-root-orphan-item/test.sh b/tests/fsck-tests/066-missing-root-orphan-item/test.sh deleted file mode 100755 index 9db625714c..0000000000 --- a/tests/fsck-tests/066-missing-root-orphan-item/test.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -# -# Verify that check can report missing orphan root itemm as an error - -source "$TEST_TOP/common" || exit - -check_prereq btrfs - -check_image() { - run_mustfail "missing root orphan item not reported as an error" \ - "$TOP/btrfs" check "$1" -} - -check_all_images From aa3c9a6a99bb547c5e50701d92f6361c9189078b Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 24 Nov 2025 14:31:51 +1030 Subject: [PATCH 3/4] btrfs-progs: do not dump leaf if the path is released inside __free_extent() [BUG] There is a bug report that btrfs-convert crashed during converting an ext4 image which is almost full. [CAUSE] Just before the crash, btrfs-convert is committing the current transaction but failed to locate the backref inside __free_extent(). Then it went through the error handling path, which prints an error message and try to dump the leaf. But in this particular case, the error code is not -ENOENT, thus the path is already released, resuling path->nodes[0] to be NULL, and btrfs_print_leaf() triggers a NULL pointer dereference. [FIX] The kernel version of btrfs_free_extent() is only dumping the tree for -ENOENT error code. And patch "btrfs: refactor the error handling of __btrfs_free_extent()" is submitted to make abort_and_dump() to only dump the leaf if the path is not released. So follow the same kernel patch, by only dumping the leaf if the path is not released. Issue: #1064 Signed-off-by: Qu Wenruo --- kernel-shared/extent-tree.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel-shared/extent-tree.c b/kernel-shared/extent-tree.c index 5800320f7b..9d0155502d 100644 --- a/kernel-shared/extent-tree.c +++ b/kernel-shared/extent-tree.c @@ -2058,8 +2058,10 @@ static int __free_extent(struct btrfs_trans_handle *trans, (unsigned long long)root_objectid, (unsigned long long)owner_objectid, (unsigned long long)owner_offset); - printf("path->slots[0]: %d path->nodes[0]:\n", path->slots[0]); - btrfs_print_leaf(path->nodes[0]); + if (path->nodes[0]) { + printf("path->slots[0]: %d path->nodes[0]:\n", path->slots[0]); + btrfs_print_leaf(path->nodes[0]); + } ret = -EIO; goto fail; } From 6ad6b24a0118984ff10a0b51d0ef9e767876e479 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 24 Nov 2025 14:40:01 +1030 Subject: [PATCH 4/4] btrfs-progs: btrfs: refactor the error handling of __free_extent() Just follow the kernel patch "btrfs: refactor the error handling of __btrfs_free_extent()", to handle the error first for lookup_extent_backref(), so we can reduce one indent level. Furthermore remove the unnessary forced type casting of the error message, and replace the old printk() with proper the error() helper. Signed-off-by: Qu Wenruo --- kernel-shared/extent-tree.c | 146 +++++++++++++++++------------------- 1 file changed, 70 insertions(+), 76 deletions(-) diff --git a/kernel-shared/extent-tree.c b/kernel-shared/extent-tree.c index 9d0155502d..c32999055e 100644 --- a/kernel-shared/extent-tree.c +++ b/kernel-shared/extent-tree.c @@ -1980,92 +1980,86 @@ static int __free_extent(struct btrfs_trans_handle *trans, bytenr, num_bytes, parent, root_objectid, owner_objectid, owner_offset); - if (ret == 0) { - extent_slot = path->slots[0]; - while (extent_slot >= 0) { - btrfs_item_key_to_cpu(path->nodes[0], &key, - extent_slot); - if (key.objectid != bytenr) - break; - if (key.type == BTRFS_EXTENT_ITEM_KEY && - key.offset == num_bytes) { - found_extent = 1; - break; - } - if (key.type == BTRFS_METADATA_ITEM_KEY && - key.offset == owner_objectid) { - found_extent = 1; - break; - } - if (path->slots[0] - extent_slot > 5) - break; - extent_slot--; + if (ret) { + error("unable to find ref byte nr %llu parent %llu root %llu owner %llu offset %llu ret %d\n", + bytenr, parent, root_objectid, owner_objectid, + owner_offset, ret); + if (path->nodes[0]) { + printf("path->slots[0]: %d path->nodes[0]:\n", path->slots[0]); + btrfs_print_leaf(path->nodes[0]); } - if (!found_extent) { - BUG_ON(iref); - ret = remove_extent_backref(trans, extent_root, path, - NULL, refs_to_drop, - is_data); - BUG_ON(ret); - btrfs_release_path(path); + ret = -EIO; + goto fail; + } + extent_slot = path->slots[0]; + while (extent_slot >= 0) { + btrfs_item_key_to_cpu(path->nodes[0], &key, + extent_slot); + if (key.objectid != bytenr) + break; + if (key.type == BTRFS_EXTENT_ITEM_KEY && + key.offset == num_bytes) { + found_extent = 1; + break; + } + if (key.type == BTRFS_METADATA_ITEM_KEY && + key.offset == owner_objectid) { + found_extent = 1; + break; + } + if (path->slots[0] - extent_slot > 5) + break; + extent_slot--; + } + if (!found_extent) { + BUG_ON(iref); + ret = remove_extent_backref(trans, extent_root, path, + NULL, refs_to_drop, + is_data); + BUG_ON(ret); + btrfs_release_path(path); - key.objectid = bytenr; + key.objectid = bytenr; - if (skinny_metadata) { - key.type = BTRFS_METADATA_ITEM_KEY; - key.offset = owner_objectid; - } else { - key.type = BTRFS_EXTENT_ITEM_KEY; - key.offset = num_bytes; - } + if (skinny_metadata) { + key.type = BTRFS_METADATA_ITEM_KEY; + key.offset = owner_objectid; + } else { + key.type = BTRFS_EXTENT_ITEM_KEY; + key.offset = num_bytes; + } + + ret = btrfs_search_slot(trans, extent_root, + &key, path, -1, 1); + if (ret > 0 && skinny_metadata && path->slots[0]) { + path->slots[0]--; + btrfs_item_key_to_cpu(path->nodes[0], + &key, + path->slots[0]); + if (key.objectid == bytenr && + key.type == BTRFS_EXTENT_ITEM_KEY && + key.offset == num_bytes) + ret = 0; + } + if (ret > 0 && skinny_metadata) { + skinny_metadata = 0; + btrfs_release_path(path); + key.type = BTRFS_EXTENT_ITEM_KEY; + key.offset = num_bytes; ret = btrfs_search_slot(trans, extent_root, &key, path, -1, 1); - if (ret > 0 && skinny_metadata && path->slots[0]) { - path->slots[0]--; - btrfs_item_key_to_cpu(path->nodes[0], - &key, - path->slots[0]); - if (key.objectid == bytenr && - key.type == BTRFS_EXTENT_ITEM_KEY && - key.offset == num_bytes) - ret = 0; - } - - if (ret > 0 && skinny_metadata) { - skinny_metadata = 0; - btrfs_release_path(path); - key.type = BTRFS_EXTENT_ITEM_KEY; - key.offset = num_bytes; - ret = btrfs_search_slot(trans, extent_root, - &key, path, -1, 1); - } - - if (ret) { - printk(KERN_ERR "umm, got %d back from search" - ", was looking for %llu\n", ret, - (unsigned long long)bytenr); - btrfs_print_leaf(path->nodes[0]); - } - BUG_ON(ret); - extent_slot = path->slots[0]; } - } else { - printk(KERN_ERR "btrfs unable to find ref byte nr %llu " - "parent %llu root %llu owner %llu offset %llu\n", - (unsigned long long)bytenr, - (unsigned long long)parent, - (unsigned long long)root_objectid, - (unsigned long long)owner_objectid, - (unsigned long long)owner_offset); - if (path->nodes[0]) { - printf("path->slots[0]: %d path->nodes[0]:\n", path->slots[0]); + + if (ret) { + printk(KERN_ERR "umm, got %d back from search" + ", was looking for %llu\n", ret, + (unsigned long long)bytenr); btrfs_print_leaf(path->nodes[0]); } - ret = -EIO; - goto fail; + BUG_ON(ret); + extent_slot = path->slots[0]; } - leaf = path->nodes[0]; item_size = btrfs_item_size(leaf, extent_slot); if (item_size < sizeof(*ei)) {