From c2e23411e8620d2b59bdf545432f3770c098a9a4 Mon Sep 17 00:00:00 2001 From: houseme Date: Sat, 8 Aug 2026 19:36:44 +0800 Subject: [PATCH] test(filemeta): cover crc heal classification (#5841) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(filemeta): classify xl.meta CRC mismatch as FileCorrupt so heal repairs it A failed CRC means the metadata bytes on disk are not the bytes that were written — bitrot. Raising it as Error::other() surfaces a generic Io error, which should_heal_object_on_disk does not recognise as heal-worthy: the drive is skipped, disks_to_heal_count stays 0, heal_object returns ok, and the corrupted xl.meta is never rewritten — while the scanner re-submits the same no-op heal every deep-scan cycle. An explicit admin deep heal fails the same way, so no heal path repairs metadata bitrot, and every one of them reports success. check_xl2_v1 already classifies a short or wrong-magic header as FileCorrupt for exactly this reason (#5716); this completes the pattern for the two CRC sites. The existing From for DiskError conversion maps the variant to DiskError::FileCorrupt, which the heal path already handles. The previously silent is_indexed_meta site now logs the mismatch (structured event shape) like unmarshal_msg does. Regression test: corrupt one byte of a marshalled FileMeta and assert unmarshal_msg reports FileCorrupt; fails on the previous code, which returned Io(Other). Verified end-to-end on a 3-node / 12-drive EC:4 cluster: xl.meta corrupted on 2 of 12 drives via dd, admin deep heal — before this change the heal returns ok with the corruption intact and the scanner loops forever; with it, both copies are rewritten (decode-identical to the healthy quorum), the object reads back byte-correct, and a follow-up heal reports all twelve drives clean. * test(filemeta): cover crc heal classification Add regression coverage for the indexed xl.meta CRC path and the metadata-heal decision that consumes FileCorrupt. Co-Authored-By: heihutu --------- Co-authored-by: terem42 <9478806+terem42@users.noreply.github.com> Co-authored-by: heihutu Co-authored-by: zhi22915 --- crates/ecstore/src/set_disk/mod.rs | 6 ++++++ crates/filemeta/src/filemeta.rs | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/crates/ecstore/src/set_disk/mod.rs b/crates/ecstore/src/set_disk/mod.rs index af8d33442..5f2922c5a 100644 --- a/crates/ecstore/src/set_disk/mod.rs +++ b/crates/ecstore/src/set_disk/mod.rs @@ -7391,6 +7391,12 @@ mod tests { let (should_heal, _, _) = should_heal_object_on_disk(&err, &[], &meta, &latest_meta); assert!(should_heal); + let err = Some(DiskError::FileCorrupt); + let (should_heal, is_meta, reason) = should_heal_object_on_disk(&err, &[], &meta, &latest_meta); + assert!(should_heal); + assert!(is_meta); + assert_eq!(reason, Some(DiskError::FileCorrupt)); + // Test with no error and no part errors let (should_heal, _, _) = should_heal_object_on_disk(&None, &[CHECK_PART_SUCCESS], &meta, &latest_meta); assert!(!should_heal); diff --git a/crates/filemeta/src/filemeta.rs b/crates/filemeta/src/filemeta.rs index d654d2c8a..0fb46887c 100644 --- a/crates/filemeta/src/filemeta.rs +++ b/crates/filemeta/src/filemeta.rs @@ -1481,6 +1481,17 @@ mod test { assert_eq!(err, Error::FileCorrupt, "CRC mismatch must classify as FileCorrupt, got: {err}"); } + #[test] + fn test_is_indexed_meta_reports_file_corrupt_on_crc_mismatch() { + let fm = FileMeta::default(); + let mut buf = fm.marshal_msg().expect("serialize default FileMeta"); + let idx = 8 + 5; + buf[idx] ^= 0xff; + + let err = FileMeta::is_indexed_meta(&buf).expect_err("corrupted indexed metadata must fail"); + assert_eq!(err, Error::FileCorrupt, "indexed CRC mismatch must classify as FileCorrupt, got: {err}"); + } + #[test] fn test_unmarshal_rejects_absurd_version_count() { let mut meta = Vec::new();