
أدوات إصلاح مخصصة لنظام BTRFS لمعالجة تلف شديد في شجرة الامتداد حيث يفشل الأمر btrfs check --repair (segfault, loop, or deadlock)
أدوات مخصصة تم كتابتها أثناء استرداد تجمع BTRFS متعدد الأجهزة بسعة 12 تيرابايت مع فساد شديد في شجرة الامتدادات لم تستطع الأوامر الأصلية (btrfs check --repair, --init-extent-tree, إلخ) إصلاحه.
راجع INCIDENT-ANALYSIS.md لدراسة حالة منظمة لعملية الاسترداد، وتصنيف للسبب الجذري، ومجموعة من المقترحات البناءة لتحسينات btrfs-progs في المنبع والتي كانت ستمنع الحاجة لمعظم هذه الأدوات.
استخدم هذه الأدوات فقط إذا كان btrfs check --repair يتعطل بخطأ تجزئة (segfault)، أو يدخل في حلقة لا نهائية، أو يترك نظام الملفات في حالة أسوأ مما كانت عليه.
حالات موثقة حيث تساعد هذه الأدوات:
btrfs check --repair بخطأ تجزئة عند [3/8] checking extents (المشكلة #525)deadlock) لـ btrfs check --init-extent-treebtrfs check --repair في حلقة لا نهائية تكرر نفس الإصلاحاتstale child pointers) تشير إلى كتل أعيد استخدامها بواسطة أشجار أخرى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 ويجب بناؤها داخل شفرة المصدر لـ 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 backref في شجرة الامتدادات. تقوم بحقن جميع المراجع الخلفية المفقودة في معاملة واحدة لتجنب مشكلة "تحرك الشجرة الجذرية بين الإرساليات".
الاستخدام:
sudo ./scan_and_fix_all_backrefs /dev/sdX # مسح فقط
sudo ./scan_and_fix_all_backrefs /dev/sdX --write # مسح + حقن
fix_owner_refs.cيصلح owner في TREE_BLOCK_REF المضمن عندما لا يتطابق مع 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_ITEMs المكررة (نفس bytenr، مستويات مختلفة في المفتاح). يحتفظ بالذي يتطابق مستواه مع btrfs_header_level ويحذف الآخر.
sudo ./fix_duplicate_extents /dev/sdX # مسح
sudo ./fix_duplicate_extents /dev/sdX --write # حذف المكررات
remove_stale_ptrs.cيمسح كل عقدة من المستوى 1 في شجرة FS_TREE. يكتشف المؤشرات الفرعية القديمة باستخدام ثلاثة فحوصات: عدم تطابق owner، عدم تطابق first_key، أو مفتاح أول نوعه غير صالح لـ FS_TREE (مثل BLOCK_GROUP_ITEM). يزيلها باستخدام 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 إلى كتلة أعيد تخصيصها لشجرة أخرى. يقوم النواة بإعادة إنشاء شجرة UUID تلقائيًا عند التحميل مع RW. مع شجرة CSUM فارغة، الملفات المُعلّمة بـ NODATASUM لا تفشل في التحقق.
sudo ./fix_uuid_tree /dev/sdX
sudo ./fix_csum_tree /dev/sdX
set_nodatasum.cيضبط العلم BTRFS_INODE_NODATASUM على inodes الملفات العادية. استخدم هذا إذا كانت شجرة 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ماسحات المرحلة 1 والمرحلة 2 التي تجول في شجرة FS_TREE وشجرة الامتدادات على التوالي، منتجة ملفات TSV تحتوي على كل تعيينات المرجع/الامتداد. تُستخدم لبناء المدخلات لـ rebuild_extent_tree_apply عندما تحتاج شجرة الامتدادات إلى إعادة البناء من الصفر.
rebuild_extent_tree_apply.c (كاتب ثقيل)كاتب المرحلة 3 الرئيسي. يأخذ قائمة مراجع مطوية مسبقًا (من فرق scan_fstree_extents + scan_extent_tree) ويحقن أكثر من 3 مليون EXTENT_DATA_REF في شجرة الامتدادات في أجزاء من 5000 لكل معاملة. يقوم بالتحديد كل 50 ألف عنصر لتجنب توقف إعادة ترتيب القطاعات DM-SMR. تم التحقق من نجاحه عند 3,248,617 إدراجًا في حوالي 34 دقيقة على 3 أقراص SMR من نوع WD40EFAX.
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أداة تصحيح جراحية لحقل واحد BLOCK_GROUP_ITEM.used عندما يترك كاتب المرحلة 3 مجموعة كتل محددة مع زيادة بسبب عناصر file_extent_item متداخلة موجودة مسبقًا. يستخدم الضابط المباشر btrfs_set_block_group_used لتجنب محاسبة btrfs_update_block_group الخاصة بـ space_info (والتي لا نريدها هنا). يتحقق مسبقًا من 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يحذف قائمة مبرمجة من EXTENT_ITEMS (bytenr, num_bytes, expected_inode) من شجرة الامتدادات. يُستخدم لتنظيف الامتدادات القديمة المتداخلة في ورقة واحدة تمنع التحميل للقراءة فقط. فحوصات سلامة لكل عنصر قبل الحذف (7 متغيرات ثابتة تشمل قائمة inode المسموحة). يعمل مع rebuilding_extent_tree=1 + reinit_extent_tree=true لتخطي محاسبة المساحة (يقوم المتصل بتصحيح used يدويًا أولاً عبر patch_block_group_used).
clean_orphan_dir_entries.cينظف إدخالات DIR_ITEM و DIR_INDEX اليتيمة من FS_TREE. أجزاء من 100 إدخال لكل معاملة. يحدث i_size لعناصر INODE_ITEM الأصلية (إنقاص بمقدار namelen × 2: خطأ بالغ تم إصلاحه: الإصدار v1 أنقص بمقدار namelen فقط، مما ترك الدلائل في حالة غير صالحة). قائمة استثناء مبرمجة لأسماء الدلائل الجذرية الهامة (مثل pelis, series, music, backups, homestorage). لا تقم أبدًا بإنقاص i_size بطول الاسم الخام: BTRFS يخزن محاسبة بمقدار namelen × 2.
clean_orphan_inode_refs.cيجول في FS_TREE للعثور على عناصر INODE_REF التي يكون key.offset الخاص بها (inode الأصل) موجودًا في قائمة الآباء اليتامى. يتخطى INODE_EXTREF لتجنب النتائج الإيجابية الخاطئة (حيث أن key.offset الخاص بـ EXTREF هو تجزئة وليس معرف أب). أجزاء من 32 لكل معاملة.
fix_dir_inode_counts.cيعيد حساب i_size = sum(name_len × 2) و nlink = 1 لدلائل INODE التي تم إفساد أعدادها بواسطة أخطاء تنظيف اليتامى السابقة. بالغ الأهمية للسلامة: إذا كان لأي دليل nlink = 2، فإن أمر rm -rf واحد على مساره سيحذف آلاف الدلائل الفرعية بصمت (قنبلة rmdir). يجول في إدخالات DIR_INDEX، ويتحقق من DIR_ITEM لكشف تضارب التجزئة (تم التحقق تجريبيًا من 0 تضارب).
remove_orphan_inode_subtrees.cيزيل الأشجار الفرعية للـ inode اليتيمة (عائلات الدلائل + الملفات العادية المستقلة) من FS_TREE. لكل هدف: يجول ويحذف EXTENT_DATA و INODE_REF و INODE_EXTREF و XATTR وأخيرًا INODE_ITEM. معاملة لكل عائلة دليل (ذري لكل شجرة فرعية)، أجزاء من 50 للملفات العادية المستقلة. قائمة استثناء مرتابة مبرمجة.
⚠️ تحذير سلامة رئيسي: راجع "معيار المجموعة الفرعية المقاومة للرصاص" أدناه.
remove_stale_ptrs_v2.cإصدار محسّن من remove_stale_ptrs: يكتشف الأوراق الفارغة باستخدام parent expected_key (الإصدار v1 تخطى هذه الحالة)، مسح متكرر بمستويين (جذر→مستوى1 + مستوى1→أوراق)، مخزن مؤقت ديناميكي (بدون حد 512)، يتحمل فشل read_tree_block.
insert_one_extent_poc.cإثبات مفهوم لإدراج امتداد واحد مع التحقق. استخدم للتحقق من مسار API قبل تشغيل rebuild_extent_tree_apply.
خلال جلسة 2026-04-05، تعطلت remove_orphan_inode_subtrees مرتين على نفس التأكيد BUG_ON لسببين مختلفين:
متجه التعطل 1: استدعاء مباشر لـ btrfs_cow_block(leaf) فوق ورقة مختلطة (gen 3601، تحتوي على inodes يتيمة وحية معًا) → update_ref_for_cow يجول في الأطفال → __btrfs_mod_ref(inc=1) فوق أطفال أشقاء قديمين → btrfs_free_extent(شبح) يعيد -ENOENT → BUG_ON → SIGABRT.
متجه التعطل 2 (اكتشف لاحقًا، تم تفاديه بالتصفية):
btrfs_del_items بعد التنظيف يستنزف ورقة إلى أقل من LEAF_DATA_SIZE/4 = 4096 بايت → يستدعي push_leaf_left(أخ) أو push_leaf_right(أخ) → إذا كان للأخ gen ≤ last_snapshot = 3701، فإن btrfs_block_can_be_shared يعيد 1 → update_ref_for_cow يدخل مسار refs > 1 → btrfs_inc_ref(cow_sibling, 0) → __btrfs_mod_ref(cow, level=0, inc=1) → يجول عبر كل EXTENT_DATAs للأخ القديم → btrfs_inc_extent_ref(bytenr_شبح) → BUG_ON(err) في extent-tree.c:1302 → SIGABRT.
الأعلام fs_info->rebuilding_extent_tree = 1 و trans->reinit_extent_tree = true لا تنقذ مسار INC: فهي تعفي فقط BTRFS_DROP_DELAYED_REF (تم التحقق في extent-tree.c:3885). BTRFS_ADD_DELAYED_REF (من btrfs_inc_ref) قاتل.
المعيار المقاوم للرصاص لأي inode هدف سيتم حذفه:
INODE_ITEM للـ inode لها gen > 3701 (بعد التعطل)gen > 3701gen > 3701 (حتى لو فشل الشرط 3، فإن إعادة التوازن إلى أشقاء بعد التعطل آمنة)disk_bytenr) يتم حلها في شجرة الامتدادات الحالية (لا -ENOENT عند بحث المرجع الخلفي)انتهاك أي من الشرطين 3+4 يؤدي إلى متجه التعطل 2. الشرط 5 معفي بواسطة reinit_extent_tree لـ DROP ولكن ليس لـ INC (وهو ما يستدعيه push_leaf_left).
لأي مجموعة مرشحة من inodes يتيمة، قم بتجوال تفريغ FS_TREE وقم بتصنيف كل ورقة هدف وفقًا للشروط الخمسة المقاومة للرصاص. مثال (مجهول):
| الورقة | Gen | العناصر اليتيمة / الإجمالي | المستخدم بعد التنظيف (تقديري) | إعادة توازن؟ | الأشقاء المباشرون | الحكم |
|---|---|---|---|---|---|---|
$LEAF_A | بعد التعطل | معظمها يتيم، تنظيف ثقيل | أقل من العتبة | نعم | جميعها بعد التعطل | ✓ آمن |
$LEAF_B | بعد التعطل | معظمها حي، تنظيف خفيف | أعلى من العتبة | لا | أب نظيف | ✓ آمن |
$LEAF_C | بعد التعطل | يتيم بنسبة 100% تقريبًا | أقل بكثير من 4096 | نعم قسري | أشقاء قبل التعطل قديمون | ❌ تعطل |
الأوراق التي 90% أو أكثر من عناصرها يتيمة هي منطقة الخطر: ستنخفض بالتأكيد تحت عتبة إعادة التوازن (LEAF_DATA_SIZE/4 = 4096 بايت)، مما يجبر push_leaf_left/right. إذا كان أي أخ مباشر في العقدة الأصلية له gen ≤ last_snapshot، فإن الدفع يؤدي إلى CoW على ذلك الأخ، الذي يدخل مسار btrfs_block_can_be_shared → refs > 1 → btrfs_inc_ref → __btrfs_mod_ref(inc=1) ويتعطل بـ BUG_ON(err) في btrfs_inc_extent_ref.
التخفيف: استبعاد الـ inodes المخالفة من ملف الإدخال. تقوم الأداة بمعالجة ما يجتاز التحقق المسبق؛ يمكن معالجة الأوراق ذات الأهداف الآمنة/غير الآمنة المختلطة جزئيًا عن طريق إدراج المجموعة الآمنة فقط. دلالات المعاملة لكل عائلة تعني أن كل عائلة آمنة تُرسل بشكل ذري حتى لو تم استبعاد عائلات أخرى.
النتيجة التجريبية من جلسة واحدة: بدءًا من N من اليتامى المرشحين، بعد تطبيق جميع الشروط الخمسة كانت المجموعة الآمنة النهائية حوالي 14% من الإدخال، لكن تلك المجموعة ألتزمت دون أي BUG_ON واحد، مع فرق 0 بايت على sha256 أساسي للملفات الحية الملتقطة قبل الكتابة.
patches/alloc_reserved_tree_block_eexist.patch يعدل btrfs-progs بحيث عندما يجد alloc_reserved_tree_block أن METADATA_ITEM موجود بالفعل، فإنه يعيد 0 بدلاً من نشر EEXIST. هذا مطلوب ليعمل حقن المراجع الخلفية الدفعي: عند حقن العديد من المراجع الخلفية، يحاول نظام المراجع المؤجلة أيضًا إنشاء METADATA_ITEMs للكتل المخصصة حديثًا عبر COW ويصطدم مع تلك التي أدخلناها بالفعل.
# 1. نسخ احتياطي
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. تأكد من أن نظام الملفات غير محمل
sudo umount /mnt/pool 2>/dev/null
# 3. مسح شجرة السجل (إن أمكن)
sudo btrfs rescue zero-log /dev/sdX1
# 4. مسح + إصلاح كل شيء (بالترتيب)
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. إعادة المسح للتحقق من التقارب
sudo ./scan_and_fix_all_backrefs /dev/sdX1
sudo ./remove_stale_ptrs /dev/sdX1
# 6. إذا كانت شجرة csum معطلة:
sudo ./fix_csum_tree /dev/sdX1
sudo ./set_nodatasum /dev/sdX1 --write
# 7. حاول التحميل للقراءة/الكتابة
sudo mount -o rw /dev/sdX1 /mnt/pool
# 8. إذا تم التحميل، تحقق باستخدام btrfs check للقراءة فقط
sudo btrfs check --force /dev/sdX1
كل إصلاح يمكن أن يخلق مشاكل جديدة عبر COW: عندما تعدل أداة شجرة الامتدادات، يقوم BTRFS بعملية CoW على العقد المتأثرة. العقد الجديدة تنسخ المؤشرات من القديمة، مما قد ينشر المؤشرات القديمة. قد تكون هناك حاجة لعدة جولات.
عدم إصلاح عدم تطابق مراجع امتداد البيانات: هذه الأدوات تتعامل فقط مع المراجع الخلفية الوصفية. الأعداد المرجعية غير الصحيحة لامتدادات البيانات (شائعة بعد تشغيلات btrfs check --repair الفاشلة) لا يتم تنظيفها.
عدم تنظيف inodes اليتيمة: إدخالات الدليل اليتيمة في FS_TREE (مراجع إلى inodes لم تعد موجودة) لا تتم إزالتها.
لا يحل محل btrfs check --repair: هذه الأدوات تستهدف سيناريوهات محددة. للأضرار البسيطة أو المتوسطة، btrfs check --repair أفضل.
لا تقم أبدًا بإيقاف التشغيل بقوة لنظام ملفات BTRFS متعدد الأجهزة: الفساد المشترك لشجرة المساحة الحرة + شجرة الامتداد صعب جدًا إصلاحه.
لا تقم أبدًا بتشغيل btrfs check --repair عدة مرات متتالية إذا لم تحل الجولة الأولى كل شيء: يمكن أن يدخل في حلقة لا نهائية ويجعل نظام الملفات أسوأ بشكل كبير.
قم دائمًا بنسخ الكتل الفائقة احتياطيًا قبل كل عملية كتابة.
trans->reinit_extent_tree = true هو المفتاح لتجاهل فشل DROP في المراجع المؤجلة للكتل التي لا تحتوي على مراجع خلفية.
fs_info->rebuilding_extent_tree = 1 يعطل فحوصات المساحة أثناء الإصلاحات.
التزام واحد كبير يحتوي على العديد من الإدراجات أفضل من العديد من الالتزامات الصغيرة، لأن الالتزامات الوسيطة تحرك الشجرة الجذرية.
backup_slots في الكتلة الفائقة ليست نسخًا احتياطية تاريخية: إنها نافذة منزلقة لأحدث 4 التزامات فقط. حلقة btrfs check --repair المكونة من 46,000+ التزام ستدير كل فتحة حوالي 11,000 مرة في دقائق، مما يدمر أي حالة قبل التعطل يمكن استعادتها من النواة. للاحتفاظ الفعلي تحتاج إلى تيارات btrfs subvolume snapshot أو btrfs send صريحة إلى جهاز آخر.
reinit_extent_tree غير متماثل: يعفي فقط BTRFS_DROP_DELAYED_REF، وليس BTRFS_ADD_DELAYED_REF. أي مسار كود يستدعي btrfs_inc_ref على ورقة قديمة (بما في ذلك push_leaf_left/right أثناء إعادة التوازن) سيظل يتعطل عبر btrfs_inc_extent_ref → BUG_ON(err).
معيار السلامة لمعالجة inodes في شجرة FS_TREE تالفة يجب أن يشمل الأشقاء، وليس فقط الورقة الهدف نفسها. راجع قسم "معيار المجموعة الفرعية المقاومة للرصاص".
sha256 الأساسي للملفات الحية هو الدليل التجريبي الوحيد على الثوابت. التقطه قبل أي عملية كتابة، قارن بعده. أي عدم تطابق = تراجع.
يتم تخزين i_size للدليل كـ sum(name_len × 2)، وليس sum(name_len). أي أداة تنظيف يتيم تقوم بإنقاص i_size عند إزالة إدخال يجب أن تنقص بمقدار namelen × 2. الخطأ في هذا يترك الدلائل في حالة غير صالحة يمكن أن تظهر لاحقًا كـ nlink = 2: مما يؤدي إلى قنبلة rmdir إذا تم تحميل التجمع للقراءة/الكتابة (أمر rm -rf واحد على أب يمكن أن يحذف آلاف الدلائل الفرعية بصمت).
وكلاء المراجعة الخبراء مع الأدلة التجريبية بالغو الأهمية. استخدمت جلسة 2026-04-05 مراجعين متوازيين من Opus (متخصص في داخلية BTRFS + عمليات) حللا الخطة المقترحة مقابل إخراج dump-tree. اكتشفوا متجه تعطل محدد (push_leaf_left → أخ قديم) كان سيكرر الإخفاقات السابقة. كانت مراجعة الخطة النصية دون تحليل dump-tree تجريبي قد تفوت هذا.
تم كتابة هذه الأدوات لحالة استرداد محددة حيث فشلت الأدوات الأصلية. لم يتم اختبارها لحالات الاستخدام العامة. استخدمها فقط إذا كنت تفهم الكود وتقبل مخاطر فقدان البيانات.
قم دائمًا بنقل بياناتك قبل محاولة أي إصلاح إذا كان ذلك ممكنًا على الإطلاق.
GPL-2.0 (متوافق مع btrfs-progs، الذي تستخدم أدواتنا واجهة برمجة تطبيقاته الداخلية).