
Benutzerdefinierte BTRFS-Reparaturwerkzeuge für schwere Extent-Baum-Korrumpierung, wenn btrfs check --repair fehlschlägt (Segfault, Endlosschleife oder Deadlock)
Eigene Werkzeuge, die während der Wiederherstellung eines 12-TB-Multi-Device-BTRFS-Pools mit schwerwiegender Korruption des Extent-Baums geschrieben wurden, den die nativen Befehle (btrfs check --repair, --init-extent-tree, usw.) nicht reparieren konnten.
Siehe INCIDENT-ANALYSIS.md für eine strukturierte Fallstudie der Wiederherstellung, eine Klassifizierung der Ursachen und eine Reihe konstruktiver Vorschläge für Verbesserungen des Upstream-btrfs-progs, die die Notwendigkeit der meisten dieser Werkzeuge verhindert hätten.
Verwenden Sie diese Werkzeuge NUR, wenn btrfs check --repair einen Segfault verursacht, in eine Endlosschleife gerät oder das Dateisystem in einem schlechteren Zustand hinterlässt als zuvor.
Dokumentierte Fälle, in denen sie helfen:
btrfs check --repair stürzt bei [3/8] checking extents ab (Issue #525)btrfs check --init-extent-tree verursacht einen Deadlockbtrfs check --repair gerät in eine Endlosschleife, die dieselben Reparaturen wiederholtrescue=all,ro, kann nicht RW gemountet werdenDiese Werkzeuge sind NICHT für leichte Korruption. Versuchen Sie bei normalen Schäden zuerst 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 ist optional)Die Werkzeuge verwenden die interne btrfs-progs-API und müssen innerhalb des btrfs-progs-Quellbaums erstellt werden:
# 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
Empfohlene Ausführungsreihenfolge:
scan_and_fix_all_backrefs.c (am wichtigsten)Das wichtigste Werkzeug. Durchläuft rekursiv jeden Baum im Dateisystem (ROOT, CHUNK, EXTENT, FS, DEV, CSUM, UUID, FREE_SPACE) und erkennt Metadatenblöcke, denen ein METADATA_ITEM-Backref im Extent-Baum fehlt. Fügt alle fehlenden Backrefs in einer einzigen Transaktion ein, um das Problem zu vermeiden, dass „der Root-Baum zwischen Commits verschoben wird".
Verwendung:
sudo ./scan_and_fix_all_backrefs /dev/sdX # scan only
sudo ./scan_and_fix_all_backrefs /dev/sdX --write # scan + inject
fix_owner_refs.cKorrigiert den owner im Inline-TREE_BLOCK_REF, wenn er nicht mit dem tatsächlichen btrfs_header_owner() des Blocks übereinstimmt. Fehlpaarungen treten auf, wenn Blöcke bei fehlgeschlagenen Reparaturen zwischen Bäumen neu zugewiesen werden.
sudo ./fix_owner_refs /dev/sdX # scan
sudo ./fix_owner_refs /dev/sdX --write # fix
fix_bad_levels.cKorrigiert METADATA_ITEM- und EXTENT_ITEM-Einträge mit einem falschen Level. Korrupte Level (z. B. 50, 55, 237) sind Datenmüll, der von einer Endlosschleife von btrfs check --repair hinterlassen wurde. Überprüft gegen das tatsächliche btrfs_header_level() des Blocks.
sudo ./fix_bad_levels /dev/sdX # scan
sudo ./fix_bad_levels /dev/sdX --write # fix
fix_duplicate_extents.cLöscht doppelte METADATA_ITEMs (gleiche Bytenr, verschiedene Level im Schlüssel). Behält denjenigen, dessen Level mit btrfs_header_level übereinstimmt, und löscht den anderen.
sudo ./fix_duplicate_extents /dev/sdX # scan
sudo ./fix_duplicate_extents /dev/sdX --write # delete duplicates
remove_stale_ptrs.cDurchläuft jeden Level-1-Knoten des FS_TREE. Erkennt veraltete Child-Zeiger anhand von drei Prüfungen: Owner-Konflikt, first_key-Konflikt oder ein first_key dessen Typ für den FS_TREE ungültig ist (z.B. BLOCK_GROUP_ITEM). Entfernt sie mit 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.cErstellt ein leeres Blatt für den UUID-Baum bzw. CSUM-Baum. Nützlich, wenn der ROOT_ITEM auf einen Block zeigt, der einem anderen Baum neu zugewiesen wurde. Der Kernel regeneriert den UUID-Baum automatisch beim RW-Mount. Mit einem leeren CSUM-Baum schlagen Dateien mit dem Flag NODATASUM bei der Überprüfung nicht fehl.
sudo ./fix_uuid_tree /dev/sdX
sudo ./fix_csum_tree /dev/sdX
set_nodatasum.cSetzt das Flag BTRFS_INODE_NODATASUM auf regulären Datei-Inodes. Verwenden Sie dies, wenn der CSUM-Baum leer ist, Dateien aber weiterhin erwartete Prüfsummen haben, was zu Lesefehlern führt. Mit NODATASUM überspringt der Kernel die CSUM-Suche.
sudo ./set_nodatasum /dev/sdX # scan
sudo ./set_nodatasum /dev/sdX --write # apply
fix_fstree_node.cVersion mit einer fest codierten Liste veralteter Blöcke. Bevorzugen Sie remove_stale_ptrs, das sie automatisch erkennt. Verwenden Sie dies nur, wenn Sie die manuelle Kontrolle darüber benötigen, welche bestimmten Blöcke entfernt werden sollen.
add_backrefs.cErste Version mit einer fest codierten Liste fehlender Backrefs. Bevorzugen Sie scan_and_fix_all_backrefs, das sie automatisch erkennt.
Wenn die Basiswerkzeuge oben nicht ausreichten (Pool mit 200K+ Fehlern, verteilt auf mehrere Bäume), wurden diese zusätzlichen Werkzeuge erstellt:
scan_fstree_extents.c + scan_extent_tree.cScanner für Durchlauf 1 und 2, die den FS_TREE bzw. den Extent-Baum durchlaufen und TSV-Dateien mit jeder Referenz-/Extent-Zuordnung erstellen. Dienen zur Erstellung der Eingabe für rebuild_extent_tree_apply, wenn der Extent-Baum von Grund auf neu aufgebaut werden muss.
rebuild_extent_tree_apply.c (Schreib-Schwergewicht)Der Hauptschreiber von Phase 3. Nimmt eine vorgefaltete Liste von Referenzen (aus der Differenz von scan_fstree_extents + scan_extent_tree) und fügt 3M+ EXTENT_DATA_REFs in Blöcken von 5000 pro Transaktion in den Extent-Baum ein. Drosselt alle 50K Einträge, um DM-SMR-Neuüberlagerungsstaus zu vermeiden. Erfolgreich verifiziert mit 3.248.617 Einfügungen in ~34 Min auf 3× WD40EFAX SMR-Festplatten.
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.cChirurgischer Einzelfeld-Patcher für BLOCK_GROUP_ITEM.used, wenn der Phase-3-Schreiber eine bestimmte Blockgruppe aufgrund bereits vorhandener überlappender file_extent_items überschreitet. Verwendet den direkten Setter btrfs_set_block_group_used, um die btrfs_update_block_group space_info-Buchhaltung zu umgehen (die wir hier NICHT wollen). Vorvalidiert flags & BTRFS_BLOCK_GROUP_DATA.
sudo ./patch_block_group_used /dev/sdX1 <bg_bytenr> <bg_length> <new_used> --write
remove_extent_items_by_key.cLöscht eine fest codierte Liste von EXTENT_ITEMs (bytenr, num_bytes, expected_inode) aus dem Extent-Baum. Wird verwendet, um überlappende veraltete Extents in einem einzelnen Blatt zu bereinigen, die den RO-Mount verhindern. Pro-Element-Sanity-Prüfungen vor dem Löschen (7 Invarianten, einschließlich Inode-Allowlist). Läuft mit rebuilding_extent_tree=1 + reinit_extent_tree=true, um die space-Buchhaltung zu überspringen (der Aufrufer patcht used zuerst manuell über patch_block_group_used).
clean_orphan_dir_entries.cBereinigt verwaiste DIR_ITEM + DIR_INDEX-Einträge aus dem FS_TREE. Blöcke von 100 Einträgen pro Transaktion. Aktualisiert die i_size des übergeordneten INODE_ITEM (Dekrement um namelen × 2: kritischer Bug behoben: v1 dekrementierte nur um namelen, was Verzeichnisse in einem ungültigen Zustand hinterließ). Feste Ausschlussliste für kritische Top-Level-Verzeichnisnamen (z.B. pelis, series, music, backups, homestorage). NIEMALS i_size um rohes namelen dekrementieren: BTRFS speichert eine namelen × 2-Buchhaltung.
clean_orphan_inode_refs.cDurchläuft den FS_TREE nach INODE_REF-Einträgen, deren key.offset (Parent-Inode) in einer Liste verwaister Eltern liegt. Überspringt INODE_EXTREF, um Fehlalarme zu vermeiden (EXTREFs key.offset ist ein Hash, keine Eltern-ID). Blöcke von 32 pro Transaktion.
fix_dir_inode_counts.cBerechnet i_size = sum(name_len × 2) und nlink = 1 für DIR-Inodes neu, deren Zählwerte durch vorherige Fehler bei der Bereinigung verwaister Einträge beschädigt wurden. KRITISCH für die Sicherheit: Wenn ein DIR nlink = 2 hat, löscht ein einzelnes rm -rf auf seinem Pfad still Tausende von Unterverzeichnissen (rmdir-Bombe). Durchläuft DIR_INDEX-Einträge, kreuzprüft DIR_ITEM auf Hash-Kollisionen (0 Kollisionen empirisch verifiziert).
remove_orphan_inode_subtrees.cEntfernt verwaiste Inode-Teilbäume (DIR-Familien + eigenständige REGs) aus dem FS_TREE. Für jedes Ziel: durchläuft und löscht EXTENT_DATA, INODE_REF, INODE_EXTREF, XATTR und schließlich INODE_ITEM. Transaktion pro DIR-Familie (atomar pro Teilbaum), Blöcke von 50 für eigenständige REGs. Feste paranoide Ausschlussliste.
⚠️ WICHTIGER SICHERHEITSHINWEIS: Siehe „Kugelsicheres Teilmengenkriterium" unten.
remove_stale_ptrs_v2.cVerbesserte Version von remove_stale_ptrs: erkennt leere Blätter mit parent expected_key (v1 übersprang diesen Fall), rekursiver 2-Level-Scan (root→level1 + level1→leaves), dynamischer Puffer (kein 512-Limit), toleriert Fehler von read_tree_block.
insert_one_extent_poc.cProof-of-Concept für das Einfügen eines einzelnen Extents mit Validierung. Wird verwendet, um den API-Pfad zu validieren, bevor rebuild_extent_tree_apply ausgeführt wird.
Während der Sitzung vom 2026-04-05 stürzte remove_orphan_inode_subtrees zweimal mit derselben BUG_ON-Assertion aus zwei unterschiedlichen Gründen ab:
Absturzvektor 1: Direktes btrfs_cow_block(leaf) über ein gemischtes Blatt (gen 3601, enthält sowohl verwaiste als auch lebende Inodes) → update_ref_for_cow durchläuft Kinder → __btrfs_mod_ref(inc=1) über veraltete Geschwisterkinder → btrfs_free_extent(phantom) gibt -ENOENT zurück → BUG_ON → SIGABRT.
Absturzvektor 2 (später entdeckt, durch Filterung umgangen):
btrfs_del_items entleert nach der Bereinigung ein Blatt unter LEAF_DATA_SIZE/4 = 4096 Bytes → ruft push_leaf_left(sibling) oder push_leaf_right(sibling) auf → wenn das Geschwister gen ≤ last_snapshot = 3701 hat, gibt btrfs_block_can_be_shared 1 zurück → update_ref_for_cow geht in den Pfad refs > 1 → btrfs_inc_ref(cow_sibling, 0) → __btrfs_mod_ref(cow, level=0, inc=1) → iteriert über alle EXTENT_DATAs des veralteten Geschwisters → btrfs_inc_extent_ref(phantom_bytenr) → BUG_ON(err) in extent-tree.c:1302 → SIGABRT.
Die Flags fs_info->rebuilding_extent_tree = 1 und trans->reinit_extent_tree = true
RETTEN den INC-Pfad NICHT: Sie befreien nur BTRFS_DROP_DELAYED_REF (verifiziert in extent-tree.c:3885). BTRFS_ADD_DELAYED_REF (von btrfs_inc_ref) ist tödlich.
Kugelsicheres Kriterium für jede Ziel-Inode, die gelöscht wird:
INODE_ITEM der Inode hostet, hat gen > 3701 (nach dem Absturz)gen > 3701used-Bytes nach der Bereinigung > 4096 (kein Rebalance-Trigger)gen > 3701 (auch wenn Bedingung 3 fehlschlägt, ist ein Rebalance zu Geschwistern nach dem Absturz sicher)disk_bytenr) sind im aktuellen Extent-Baum auflösbar (kein -ENOENT bei Backref-Suche)Verletzung einer der Bedingungen 3+4 löst Absturzvektor 2 aus. Bedingung 5 wird durch reinit_extent_tree für DROP entlastet, aber NICHT für INC (was push_leaf_left aufruft).
Durchlaufen Sie für jede Kandidaten-Inode-Menge den FS_TREE-Dump und klassifizieren Sie jedes Zielblatt anhand der 5 kugelsicheren Bedingungen. Beispielmuster (anonymisiert):
Blätter, bei denen ≥90% der Elemente verwaist sind, sind die Gefahrenzone: Sie werden mit Sicherheit unter die Rebalance-Schwelle (LEAF_DATA_SIZE/4 = 4096 Bytes) fallen und push_leaf_left/right erzwingen. Wenn ein unmittelbares Geschwister im übergeordneten Knoten gen ≤ last_snapshot hat, löst das Push CoW auf diesem Geschwister aus, das in den Pfad btrfs_block_can_be_shared → refs > 1 → btrfs_inc_ref → __btrfs_mod_ref(inc=1) gerät und mit BUG_ON(err) in btrfs_inc_extent_ref abstürzt.
Minderung: Schließen Sie die problematischen Inodes aus der Eingabedatei aus. Das Werkzeug verarbeitet alles, was die Vorflugvalidierung besteht; Blätter mit gemischten sicheren/unsicheren Zielen können teilweise verarbeitet werden, indem nur die sichere Teilmenge aufgelistet wird. Die Transaktionssemantik pro Familie bedeutet, dass jede sichere Familie atomar festgeschrieben wird, selbst wenn andere Familien ausgeschlossen sind.
Empirisches Ergebnis aus einer Sitzung: Ausgehend von N Kandidaten-Verwaisten ergab sich nach Anwendung aller 5 Bedingungen eine endgültige sichere Teilmenge von ~14% der Eingabe, aber diese Teilmenge wurde ohne eine einzige BUG_ON festgeschrieben, mit einer 0-Byte-Differenz auf einer Basis-sha256 von Live-Dateien, die vor dem Schreiben erfasst wurden.
patches/alloc_reserved_tree_block_eexist.patch modifiziert btrfs-progs so, dass wenn alloc_reserved_tree_block feststellt, dass das METADATA_ITEM bereits existiert, es 0 zurückgibt, anstatt EEXIST zu propagieren. Dies ist erforderlich, damit die Batch-Backref-Injektion funktioniert: Wenn viele Backrefs injiziert werden, versucht das System der verzögerten Referenzen auch, METADATA_ITEMs für Blöcke zu erstellen, die über COW neu zugewiesen wurden, und kollidiert mit den bereits eingefügten.
# 1. Backup
mkdir -p backup
for DEV in /dev/sdX1 /dev/sdY1; do
sudo dd if=$DEV of=backup/$(basename $DEV).sb bs=4096 count=1 skip=16
done
# 2. Make sure the filesystem is unmounted
sudo umount /mnt/pool 2>/dev/null
# 3. Zero the log tree (if applicable)
sudo btrfs rescue zero-log /dev/sdX1
# 4. Scan + fix everything (in order)
sudo ./scan_and_fix_all_backrefs /dev/sdX1 --write
sudo ./fix_bad_levels /dev/sdX1 --write
sudo ./fix_owner_refs /dev/sdX1 --write
sudo ./fix_duplicate_extents /dev/sdX1 --write
sudo ./remove_stale_ptrs /dev/sdX1 --write
# 5. Re-scan to verify convergence
sudo ./scan_and_fix_all_backrefs /dev/sdX1
sudo ./remove_stale_ptrs /dev/sdX1
# 6. If the csum tree is broken:
sudo ./fix_csum_tree /dev/sdX1
sudo ./set_nodatasum /dev/sdX1 --write
# 7. Try mounting RW
sudo mount -o rw /dev/sdX1 /mnt/pool
# 8. If it mounts, verify with btrfs check readonly
sudo btrfs check --force /dev/sdX1
Jede Reparatur kann durch COW neue Probleme schaffen: Wenn ein Werkzeug den Extent-Baum ändert, führt btrfs COW auf den betroffenen Knoten durch. Die neuen Knoten kopieren Zeiger von den alten, was veraltete Zeiger verbreiten kann. Möglicherweise sind mehrere Durchläufe erforderlich.
Fehlpaarungen von Daten-Extent-Referenzen werden nicht korrigiert: Diese Werkzeuge berühren nur Metadaten-Backrefs. Falsche Referenzzählungen auf Daten-Extents (häufig nach fehlgeschlagenen btrfs check --repair-Läufen) werden nicht bereinigt.
Verwaiste Inodes werden nicht bereinigt: Verwaiste Verzeichniseinträge im FS_TREE (Referenzen auf Inodes, die nicht mehr existieren) werden nicht entfernt.
Ersetzt nicht btrfs check --repair: Diese Werkzeuge zielen auf bestimmte Szenarien ab. Bei leichten oder mittelschweren Schäden ist btrfs check --repair besser.
NIEMALS einen Multi-Device-BTRFS-Dateisystem hart aus- und einschalten: Eine kombinierte Korruption des freien Speicherplatzes und des Extent-Baums ist extrem schwer zu reparieren.
NIEMALS btrfs check --repair mehrmals hintereinander ausführen, wenn der erste Lauf nicht alles gelöst hat: Es kann in eine Endlosschleife geraten und das Dateisystem drastisch verschlechtern.
Immer die Superblöcke vor jedem Schreibvorgang sichern.
trans->reinit_extent_tree = true ist der Schlüssel zum Ignorieren von DROP-Fehlern in verzögerten Referenzen für Blöcke ohne Backrefs.
fs_info->rebuilding_extent_tree = 1 deaktiviert Platzprüfungen während Reparaturen.
Ein großer Commit mit vielen Einfügungen ist besser als viele kleine Commits, weil zwischenzeitliche Commits den Root-Baum verschieben.
backup_slots im SB sind KEINE historischen Sicherungen: Sie sind ein gleitendes Fenster der letzten 4 Commits. Eine btrfs check --repair-Schleife mit 46.000+ Commits rotiert jeden Slot in Minuten etwa 11.000 Mal und zerstört jeden vor dem Absturz wiederherstellbaren Zustand aus dem Kernel. Für echte Aufbewahrung benötigen Sie explizite btrfs subvolume snapshot- oder btrfs send-Streams auf ein anderes Gerät.
: Befreit nur , NICHT . Jeder Codepfad, der auf einem veralteten Blatt aufruft (einschließlich während des Rebalances), wird dennoch über → abstürzen.
Diese Werkzeuge wurden für einen spezifischen Wiederherstellungsfall geschrieben, bei dem die nativen Werkzeuge versagten. Sie sind nicht für allgemeine Anwendungsfälle getestet. Verwenden Sie sie nur, wenn Sie den Code verstehen und das Risiko von Datenverlust akzeptieren.
Kopieren Sie immer Ihre Daten, bevor Sie eine Reparatur versuchen, wenn irgend möglich.
GPL-2.0 (kompatibel mit btrfs-progs, dessen interne API diese Werkzeuge verwenden).
| Blatt | Gen | Verwaiste Elemente / Gesamt | Genutzte Bytes nach Bereinigung (geschätzt) | Rebalance? | Unmittelbare Geschwister | Urteil |
|---|
$LEAF_A | nach Absturz | hauptsächlich verwaist, starke Bereinigung | unter Schwelle | JA | alle nach Absturz | ✓ sicher |
$LEAF_B | nach Absturz | hauptsächlich live, leichte Bereinigung | über Schwelle | NEIN | sauberes Eltern | ✓ sicher |
$LEAF_C | nach Absturz | nahezu 100% verwaist | weit unter 4096 | JA erzwungen | vor Absturz veraltet | ❌ ABSTURZ |
reinit_extent_tree ist ASYMMETRISCHBTRFS_DROP_DELAYED_REFBTRFS_ADD_DELAYED_REFbtrfs_inc_refpush_leaf_left/rightbtrfs_inc_extent_refBUG_ON(err)Das Sicherheitskriterium für die Verarbeitung von Inodes in einem beschädigten FS_TREE muss Geschwister einschließen, nicht nur das Zielblatt selbst. Siehe Abschnitt „Kugelsicheres Teilmengenkriterium".
Basis-sha256 von LIVE-Dateien ist der einzige empirische Beweis für Invarianten. Erfassen Sie ihn vor jedem Schreibvorgang, vergleichen Sie danach. Jede Abweichung = Rollback.
DIR i_size wird als sum(name_len × 2) gespeichert, NICHT sum(name_len). Jedes Werkzeug zur Bereinigung verwaister Einträge, das die i_size beim Entfernen eines Eintrags dekrementiert, muss um namelen × 2 dekrementieren. Wenn dies falsch gemacht wird, bleiben DIRs in einem ungültigen Zustand, der sich später als nlink = 2 manifestieren kann: Dies löst eine rmdir-Bombe aus, wenn der Pool RW gemountet ist (ein einzelnes rm -rf auf einem Elternverzeichnis kann Tausende von Unterverzeichnissen still löschen).
Experten-Reviewer-Agenten mit empirischen Beweisen sind entscheidend. Die Sitzung vom 2026-04-05 verwendete zwei parallele Opus-Reviewer (btrfs-Interna + Betrieb), die den vorgeschlagenen Plan anhand der dump-tree-Ausgabe analysierten. Sie erkannten einen deterministischen Absturzvektor (push_leaf_left → veraltetes Geschwister), der die vorherigen Fehler wiederholt hätte. Eine textuelle Planüberprüfung ohne empirische dump-tree-Analyse hätte dies übersehen.