
심각한 익스텐트 트리 손상용 사용자 정의 BTRFS 복구 도구 (btrfs check --repair가 세그폴트, 루프, 또는 데드락으로 실패하는 경우)
네이티브 명령어(btrfs check --repair, --init-extent-tree 등)로 복구할 수 없었던 심각한 익스텐트 트리 손상이 있는 12TB 멀티 디바이스 BTRFS 풀 복구 중에 작성된 사용자 정의 도구입니다.
복구의 구조화된 사례 연구, 근본 원인 분류, 그리고 이러한 도구의 대부분이 필요하지 않게 했을 상류 btrfs-progs 개선을 위한 건설적인 제안 모음을 보려면 **INCIDENT-ANALYSIS.md**를 참조하십시오.
btrfs check --repair가 세그폴트를 일으키거나, 무한 루프에 빠지거나, 파일 시스템을 이전보다 더 나쁜 상태로 만드는 경우에만 이 도구를 사용하십시오.
도움이 되는 것으로 문서화된 사례:
btrfs check --repair가 [3/8] checking extents에서 세그폴트 (이슈 #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 백레퍼가 누락된 메타데이터 블록을 감지합니다. 단일 트랜잭션에서 누락된 모든 백레퍼를 주입하여 '루트 트리가 커밋 사이에 이동하는' 문제를 방지합니다.
사용법:
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 노드를 스캔합니다. 세 가지 검사(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.cUUID 트리 / 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이 설정되면 커널이 csum 조회를 건너뜁니다.
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와 익스텐트 트리를 탐색하여 모든 참조/익스텐트 매핑을 TSV 파일로 생성하는 패스 1 및 패스 2 스캐너입니다. 익스텐트 트리를 처음부터 다시 빌드해야 할 때 rebuild_extent_tree_apply의 입력을 구축하는 데 사용됩니다.
rebuild_extent_tree_apply.c (무거운 기록기)주요 페이즈 3 기록기입니다. 사전 병합된 참조 목록(scan_fstree_extents + scan_extent_tree 비교 결과)을 가져와 트랜잭션당 5000개씩 묶어 3M+ 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.c페이즈 3 기록기가 기존 중복 file_extent_items로 인해 특정 bg에 초과 사용량을 남겼을 때 BLOCK_GROUP_ITEM.used를 외과적으로 단일 필드 패치하는 도구입니다. btrfs_update_block_group 공간 정보 계정(여기서는 원하지 않음)을 피하기 위해 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로 실행하여 공간 계정을 건너뜁니다(호출자가 먼저 patch_block_group_used를 통해 used를 수동으로 패치함).
clean_orphan_dir_entries.cFS_TREE에서 고아 DIR_ITEM + DIR_INDEX 항목을 정리합니다. 트랜잭션당 100개 항목씩 청크로 처리합니다. 상위 INODE_ITEM i_size를 업데이트합니다(namelen × 2만큼 감소: 중요한 버그 수정: v1은 namelen만 감소시켜 디렉토리를 잘못된 상태로 남겼습니다). 중요한 최상위 디렉토리 이름(e.g. pelis, series, music, backups, homestorage)에 대한 하드코딩된 제외 목록이 있습니다. 절대 i_size를 원시 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.c이전 고아 정리 버그로 인해 개수가 손상된 DIR inode에 대해 i_size = sum(name_len × 2) 및 nlink = 1을 다시 계산합니다. 안전에 매우 중요: 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개씩 청크로 처리합니다. 하드코딩된 편집증적 제외 목록이 있습니다.
⚠️ 주요 안전 주의사항: 아래 "무적 부분 집합 기준"을 참조하십시오.
remove_stale_ptrs_v2.cremove_stale_ptrs의 개선된 버전: parent expected_key가 있는 빈 리프를 감지합니다(v1은 이 경우를 건너뜀), 재귀적 2레벨 스캔(루트→레벨1 + 레벨1→리프), 동적 버퍼(512 제한 없음), read_tree_block 실패를 허용합니다.
insert_one_extent_poc.c검증이 포함된 단일 익스텐트 삽입을 위한 개념 증명입니다. rebuild_extent_tree_apply를 실행하기 전에 API 경로를 검증하는 데 사용됩니다.
2026-04-05 세션에서 remove_orphan_inode_subtrees가 동일한 BUG_ON 어설션에서 서로 다른 두 가지 이유로 두 번 충돌했습니다:
충돌 벡터 1: 혼합 리프(세대 3601, 고아 및 활성 inode 모두 포함)에서 직접 btrfs_cow_block(leaf) → update_ref_for_cow가 자식 탐색 → 오래된 형제 자식에 대해 __btrfs_mod_ref(inc=1) → btrfs_free_extent(phantom)이 -ENOENT 반환 → BUG_ON → SIGABRT.