12TB マルチデバイス BTRFS プールにおいて、ネイティブコマンド(btrfs check --repair、--init-extent-tree など)では修復できなかった深刻なエクステントツリーの破損を回復するために作成されたカスタムツールです。
構造化されたリカバリのケーススタディ、根本原因の分類、およびこれらのツールのほとんどを不要にするはずだった上流 btrfs-progs の改善提案については、INCIDENT-ANALYSIS.md を参照してください。
これらのツールは、btrfs check --repair がセグフォルトする、無限ループに入る、またはファイルシステムを以前よりも悪化させる場合にのみ使用してください。
これらのツールが役立つことが確認されているケース:
btrfs check --repair が [3/8] checking extents でセグフォルトする (Issue #525)btrfs check --init-extent-tree がデッドロックするbtrfs check --repair が同じ修復を繰り返す無限ループに入るrescue=all,ro でのみマウントでき、RW マウントに失敗するプールこれらのツールは軽微な破損には使用しないでください。 通常の損傷の場合は、まず btrfs check --repair を試してください。
--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 はオプトイン)ツールは内部の btrfs-progs API を使用しており、btrfs-progs ソースツリー内でビルドする必要があります:
# 1. btrfs-progs のクローン
git clone --depth 1 --branch v6.19.1 https://github.com/kdave/btrfs-progs.git
cd btrfs-progs
# 2. EEXIST パッチの適用(バッチバック参照注入に必要)
patch -p1 < path/to/btrfs_fixes/patches/alloc_reserved_tree_block_eexist.patch
# 3. ベースとなる btrfs-progs の構成とビルド
./autogen.sh
./configure
make -j$(nproc)
# 4. このリポジトリから .c ファイルを btrfs-progs ディレクトリにコピー
cp path/to/btrfs_fixes/programs/*.c .
# 5. 各プログラムを Makefile に追加:
echo '
PROGNAME: PROGNAME.o $(objects) $(libs_shared)
@echo " [LD] $@"
$(Q)$(CC) -o $@ PROGNAME.o $(objects) $(libs_shared) $(LDFLAGS) $(LIBS)
' >> Makefile
# 6. ビルド
make PROGNAME
推奨される実行順序:
scan_and_fix_all_backrefs.c (最も重要)最も重要なツールです。 ファイルシステム内のすべてのツリー (ROOT, CHUNK, EXTENT, FS, DEV, CSUM, UUID, FREE_SPACE) を再帰的に巡回し、エクステントツリーに METADATA_ITEM バック参照がないメタデータブロックを検出します。欠落しているすべてのバック参照を1つのトランザクションで注入し、「ルートツリーがコミット間で移動する」問題を回避します。
使用方法:
sudo ./scan_and_fix_all_backrefs /dev/sdX # スキャンのみ
sudo ./scan_and_fix_all_backrefs /dev/sdX --write # スキャン + 注入
fix_owner_refs.cインライン TREE_BLOCK_REF 内の owner が、ブロックの実際の btrfs_header_owner() と一致しない場合に修正します。修復の失敗時にブロックがツリー間で再割り当てされると、不一致が発生します。
sudo ./fix_owner_refs /dev/sdX # スキャン
sudo ./fix_owner_refs /dev/sdX --write # 修正
fix_bad_levels.c不正なレベルを持つ METADATA_ITEM および EXTENT_ITEM エントリを修正します。破損したレベル (例: 50, 55, 237) は、btrfs check --repair がループに入ったときに残されたゴミです。ブロックの実際の btrfs_header_level() と照合して検証します。
sudo ./fix_bad_levels /dev/sdX # スキャン
sudo ./fix_bad_levels /dev/sdX --write # 修正
fix_duplicate_extents.c重複する METADATA_ITEM(同じ bytenr で異なるレベルのキー)を削除します。レベルが btrfs_header_level と一致する方を保持し、もう一方を削除します。
sudo ./fix_duplicate_extents /dev/sdX # スキャン
sudo ./fix_duplicate_extents /dev/sdX --write # 重複削除
remove_stale_ptrs.cFS_TREE のすべてのレベル1ノードをスキャンします。3つのチェック(owner 不一致、first_key 不一致、または FS_TREE では無効なタイプ(例: BLOCK_GROUP_ITEM)の first_key)を使用して古い子ポインタを検出します。btrfs_del_ptr で削除します。
sudo ./remove_stale_ptrs /dev/sdX # スキャン
sudo ./remove_stale_ptrs /dev/sdX --write # 削除
fix_uuid_tree.c / fix_csum_tree.cそれぞれ UUID ツリー / CSUM ツリー用の空のリーフを作成します。ROOT_ITEM が別のツリーに再割り当てされたブロックを指している場合に便利です。カーネルは RW マウント時に自動的に UUID ツリーを再生成します。空の CSUM ツリーの場合、NODATASUM とフラグが立てられたファイルは検証に失敗しません。
sudo ./fix_uuid_tree /dev/sdX
sudo ./fix_csum_tree /dev/sdX
set_nodatasum.c通常のファイル inode に BTRFS_INODE_NODATASUM フラグを設定します。csum ツリーが空でもファイルに期待されるチェックサムがある場合に使用します。これにより読み取りエラーが発生します。NODATASUM により、カーネルは checksum のルックアップをスキップします。
sudo ./set_nodatasum /dev/sdX # スキャン
sudo ./set_nodatasum /dev/sdX --write # 適用
fix_fstree_node.c古いブロックのハードコードされたリストを持つバージョンです。これらを自動的に検出する remove_stale_ptrs を優先してください。削除する特定のブロックを手動で制御する必要がある場合にのみ使用してください。
add_backrefs.c欠落しているバック参照のハードコードされたリストを持つ初期バージョンです。これらを自動的に検出する scan_and_fix_all_backrefs を優先してください。
上記のベースラインツールでは不十分だった場合 (プールに複数のツリーにまたがる 200K+ のエラーがある場合)、以下の追加ツールが作成されました:
scan_fstree_extents.c + scan_extent_tree.cそれぞれ FS_TREE と extent tree をウォークし、すべての参照/エクステントマッピングを含む TSV ファイルを生成する Pass 1 および Pass 2 スキャナーです。エクステントツリーをスクラッチから再構築する必要がある場合に、rebuild_extent_tree_apply への入力を作成するために使用されます。
rebuild_extent_tree_apply.c (ヘビーライター)メインの Phase 3 ライターです。事前に折り畳まれた参照リスト(scan_fstree_extents + scan_extent_tree の差分から取得)を受け取り、トランザクションあたり 5000 チャンクで 300万+ の EXTENT_DATA_REF をエクステントツリーに注入します。DM-SMR の再シングルストールを避けるため、50K アイテムごとにスロットリングします。3× WD40EFAX SMR ディスク上で、約 34 分で 3,248,617 の挿入を成功させた実績があります。
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.cPhase 3 ライターが既存の重複する file_extent_item のために特定の bg にオーバーシュートを残した場合に、BLOCK_GROUP_ITEM.used に対する外科的な単一フィールドパッチャーです。btrfs_update_block_group の space_info アカウンティング(ここでは不要)を避けるために、btrfs_set_block_group_used の直接セッターを使用します。flags & BTRFS_BLOCK_GROUP_DATA を事前検証します。
sudo ./patch_block_group_used /dev/sdX1 <bg_bytenr> <bg_length> <new_used> --write
remove_extent_items_by_key.cエクステントツリーから (bytenr, num_bytes, expected_inode) EXTENT_ITEM のハードコードされたリストを削除します。RO マウントを妨げる単一のリーフ内の重複する古いエクステントをクリーンアップするために使用されます。削除前にアイテムごとのサニティチェック(inode 許可リストを含む 7 つの不変条件)を実行します。rebuilding_extent_tree=1 + reinit_extent_tree=true で実行し、space アカウンティングをスキップします(呼び出し側は patch_block_group_used で先に used をパッチします)。
clean_orphan_dir_entries.cFS_TREE から孤立した DIR_ITEM + DIR_INDEX エントリをクリーンアップします。トランザクションあたり 100 エントリのチャンクで動作します。親 INODE_ITEM の i_size を更新します(namelen × 2 でデクリメント: 重大なバグ修正: v1 は namelen のみでデクリメントし、ディレクトリを無効な状態にしていました)。重要なトップレベルディレクトリ名(例: pelis, series, music, backups, homestorage)のハードコードされた除外リストがあります。i_size を raw namelen で決してデクリメントしないでください: BTRFS は namelen × 2 のアカウンティングを保存します。
clean_orphan_inode_refs.ckey.offset(親 inode)が孤立した親リストにある INODE_REF アイテムについて FS_TREE をウォークします。誤検出を避けるため、INODE_EXTREF はスキップします(EXTREF の key.offset はハッシュであり、親 ID ではありません)。トランザクションあたり 32 チャンクで動作します。
fix_dir_inode_counts.ci_size = sum(name_len × 2) と nlink = 1 を、以前の孤立クリーンアップバグによってカウントが破損した DIR inode に対して再計算します。安全性のために極めて重要: いずれかの DIR が nlink = 2 の場合、そのパスに対する単一の rm -rf が数千のサブディレクトリを静かに削除します(rmdir 爆弾)。DIR_INDEX エントリをウォークし、ハッシュ衝突検出のために DIR_ITEM と相互参照します(経験的に 0 衝突が確認されています)。
remove_orphan_inode_subtrees.cFS_TREE から孤立した inode サブツリー(DIR ファミリー + スタンドアロン REG)を削除します。ターゲットごとに: EXTENT_DATA, INODE_REF, INODE_EXTREF, XATTR、そして最後に INODE_ITEM をウォークして削除します。DIR ファミリーごとにトランザクション(サブツリーごとにアトミック)、スタンドアロン REG は 50 チャンクで動作します。ハードコードされたパラノイド除外リストがあります。
⚠️ 主要な安全性の注意: 以下の「Bulletproof subset criterion」を参照してください。
remove_stale_ptrs_v2.cremove_stale_ptrs の改良版: parent expected_key を持つ空のリーフを検出し(v1 はこのケースをスキップ)、再帰的な 2 レベルスキャン(root→level1 + level1→leaves)、動的バッファ(512 制限なし)、read_tree_block の失敗を許容します。
insert_one_extent_poc.c検証付きの単一エクステント挿入の PoC です。rebuild_extent_tree_apply を実行する前に API パスを検証するために使用されました。
2026-04-05 セッション中、remove_orphan_inode_subtrees は異なる理由で同じ BUG_ON アサーションで 2 回クラッシュしました:
クラッシュベクトル 1: MIXED リーフ (gen 3601、孤立した inode とライブ inode の両方を含む) 上の直接の btrfs_cow_block(leaf) → update_ref_for_cow が子をウォーク → 古い兄弟子に対する __btrfs_mod_ref(inc=1) → btrfs_free_extent(phantom) が -ENOENT を返す → BUG_ON → SIGABRT。