
Custom BTRFS repair tools for severe extent tree corruption where btrfs check --repair fails (segfault, loop, or deadlock)
Custom tools written during recovery of a 12TB multi-device BTRFS pool with
severe extent tree corruption that the native commands (btrfs check --repair,
--init-extent-tree, etc.) could not repair.
See INCIDENT-ANALYSIS.md for a structured case
study of the recovery, a root cause classification, and a set of constructive
proposals for upstream btrfs-progs improvements that would have prevented
the need for most of these tools.
Use these tools ONLY if btrfs check --repair segfaults, enters an infinite
loop, or leaves the filesystem in worse shape than before.
Documented cases where they help:
btrfs check --repair segfaults at [3/8] checking extents (Issue #525)btrfs check --init-extent-tree deadlocksbtrfs check --repair enters an infinite loop repeating the same repairsrescue=all,ro, fails to mount RWThese tools are NOT for light corruption. For normal damage, try
btrfs check --repair first.
--write:
for DEV in sda1 sdb1 sdc1; do
sudo dd if=/dev/$DEV of=sb_${DEV}.bin bs=4096 count=1 skip=16
done
--write is opt-in)The tools use the internal btrfs-progs API and must be built inside the btrfs-progs source tree:
# 1. Clone btrfs-progs
git clone --depth 1 --branch v6.19.1 https://github.com/kdave/btrfs-progs.git
cd btrfs-progs
# 2. Apply the EEXIST patch (required for batch backref injection)
patch -p1 < path/to/btrfs_fixes/patches/alloc_reserved_tree_block_eexist.patch
# 3. Configure and build base btrfs-progs
./autogen.sh
./configure
make -j$(nproc)
# 4. Copy the .c files from this repo into the btrfs-progs directory
cp path/to/btrfs_fixes/programs/*.c .
# 5. For each program, add to the Makefile:
echo '
PROGNAME: PROGNAME.o $(objects) $(libs_shared)
@echo " [LD] $@"
$(Q)$(CC) -o $@ PROGNAME.o $(objects) $(libs_shared) $(LDFLAGS) $(LIBS)
' >> Makefile
# 6. Build
make PROGNAME
Recommended execution order:
scan_and_fix_all_backrefs.c (most important)The most important tool. Recursively walks every tree in the filesystem (ROOT, CHUNK, EXTENT, FS, DEV, CSUM, UUID, FREE_SPACE) and detects metadata blocks that are missing a METADATA_ITEM backref in the extent tree. Injects all missing backrefs in a single transaction to avoid the "root tree moves between commits" problem.
Usage:
sudo ./scan_and_fix_all_backrefs /dev/sdX # scan only
sudo ./scan_and_fix_all_backrefs /dev/sdX --write # scan + inject
fix_owner_refs.cFixes the owner in the inline TREE_BLOCK_REF when it doesn't match the
block's actual btrfs_header_owner(). Mismatches occur when blocks are
reassigned between trees during failed repairs.
sudo ./fix_owner_refs /dev/sdX # scan
sudo ./fix_owner_refs /dev/sdX --write # fix
fix_bad_levels.cFixes METADATA_ITEM and EXTENT_ITEM entries with an incorrect level. Corrupt
levels (e.g. 50, 55, 237) are garbage left behind by btrfs check --repair
entering a loop. Verified against the block's real btrfs_header_level().
sudo ./fix_bad_levels /dev/sdX # scan
sudo ./fix_bad_levels /dev/sdX --write # fix
fix_duplicate_extents.cDeletes duplicate METADATA_ITEMs (same bytenr, different levels in the key).
Keeps the one whose level matches btrfs_header_level and deletes the other.
sudo ./fix_duplicate_extents /dev/sdX # scan
sudo ./fix_duplicate_extents /dev/sdX --write # delete duplicates
remove_stale_ptrs.cScans every level-1 node of the FS_TREE. Detects stale child pointers using
three checks: owner mismatch, first_key mismatch, or a first key whose type
is invalid for the FS_TREE (e.g. BLOCK_GROUP_ITEM). Removes them with
btrfs_del_ptr.
sudo ./remove_stale_ptrs /dev/sdX # scan
sudo ./remove_stale_ptrs /dev/sdX --write # remove
fix_uuid_tree.c / fix_csum_tree.cCreates an empty leaf for the UUID tree / CSUM tree respectively. Useful when the ROOT_ITEM points to a block that was reassigned to another tree. The kernel regenerates the UUID tree automatically on RW mount. With an empty CSUM tree, files flagged NODATASUM do not fail verification.
sudo ./fix_uuid_tree /dev/sdX
sudo ./fix_csum_tree /dev/sdX
set_nodatasum.cSets the BTRFS_INODE_NODATASUM flag on regular file inodes. Use this if
the csum tree is empty but files still have expected checksums, which causes
read errors. With NODATASUM, the kernel skips csum lookups.
sudo ./set_nodatasum /dev/sdX # scan
sudo ./set_nodatasum /dev/sdX --write # apply
fix_fstree_node.cVersion with a hardcoded list of stale blocks. Prefer remove_stale_ptrs,
which detects them automatically. Only use this if you need manual control
over which specific blocks to remove.
add_backrefs.cInitial version with a hardcoded list of missing backrefs. Prefer
scan_and_fix_all_backrefs, which detects them automatically.
When the baseline tools above were insufficient (pool with 200K+ errors spread across multiple trees), these additional tools were built:
scan_fstree_extents.c + scan_extent_tree.cPass 1 and Pass 2 scanners that walk the FS_TREE and extent tree respectively,
producing TSV files with every ref/extent mapping. Used to build input for
rebuild_extent_tree_apply when the extent tree needs to be rebuilt from scratch.
rebuild_extent_tree_apply.c (heavy writer)The main Phase 3 writer. Takes a pre-folded list of refs (from scan_fstree_extents
scan_extent_tree diff) and injects 3M+ EXTENT_DATA_REFs into the extent tree
in chunks of 5000 per transaction. Throttles every 50K items to avoid DM-SMR
re-shingle stalls. Verified successful at 3,248,617 inserts in ~34 min on
3× WD40EFAX SMR disks.sudo ./rebuild_extent_tree_apply /dev/sdX1 refs_folded.txt to_insert.txt watermark.txt --dryrun
sudo ./rebuild_extent_tree_apply /dev/sdX1 refs_folded.txt to_insert.txt watermark.txt --write
patch_block_group_used.cSurgical single-field patcher for BLOCK_GROUP_ITEM.used when Fase 3 writer
leaves a specific bg with overshoot due to pre-existing overlapping file_extent_items.
Uses btrfs_set_block_group_used direct setter to avoid btrfs_update_block_group
space_info accounting (which we DON'T want here). Pre-validates flags & BTRFS_BLOCK_GROUP_DATA.
sudo ./patch_block_group_used /dev/sdX1 <bg_bytenr> <bg_length> <new_used> --write
remove_extent_items_by_key.cDeletes a hardcoded list of (bytenr, num_bytes, expected_inode) EXTENT_ITEMs from
the extent tree. Used to clean up overlapping stale extents in a single leaf that
prevent RO mount. Per-item sanity checks before delete (7 invariants including
inode allowlist). Runs with rebuilding_extent_tree=1 + reinit_extent_tree=true
to skip space accounting (caller patches used manually first via
patch_block_group_used).