fix(filemeta): classify xl.meta CRC mismatch as FileCorrupt so heal repairs it (#5838)

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<rustfs_filemeta::Error> 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.
This commit is contained in:
terem42
2026-08-08 12:48:18 +02:00
committed by GitHub
parent e342457830
commit 7e8b500420
2 changed files with 42 additions and 3 deletions
+18
View File
@@ -1463,6 +1463,24 @@ mod test {
/// Regression test for rustfs/rustfs#2715: a corrupted version count in
/// xl.meta must yield a decode error instead of sizing a huge allocation
/// from the bogus count (which aborts the whole process).
/// A CRC mismatch means the bytes on disk are not the bytes that were
/// written — bitrot. It must surface as `Error::FileCorrupt` specifically:
/// that variant converts to `DiskError::FileCorrupt`, which is the only
/// corruption signal `should_heal_object_on_disk` recognises. As a generic
/// error the drive is skipped, the heal reports success, and the damaged
/// `xl.meta` is never rewritten.
#[test]
fn test_unmarshal_reports_file_corrupt_on_crc_mismatch() {
let mut fm = FileMeta::default();
let mut buf = fm.marshal_msg().expect("serialize default FileMeta");
// Flip one byte inside the meta blob: past the 8-byte XL2 header and
// the 5-byte bin32 length prefix, before the CRC trailer.
let idx = 8 + 5;
buf[idx] ^= 0xff;
let err = fm.unmarshal_msg(&buf).expect_err("corrupted meta must fail to decode");
assert_eq!(err, Error::FileCorrupt, "CRC mismatch must classify as FileCorrupt, got: {err}");
}
#[test]
fn test_unmarshal_rejects_absurd_version_count() {
let mut meta = Vec::new();
+24 -3
View File
@@ -97,7 +97,20 @@ impl FileMeta {
let meta_crc = xxh64::xxh64(meta, XXHASH_SEED) as u32;
if crc != meta_crc {
return Err(Error::other("xl file crc check failed"));
error!(
event = "filemeta_xl_crc_mismatch",
component = "filemeta",
expected_crc = meta_crc,
actual_crc = crc,
"xl.meta payload failed its CRC check"
);
// Error::FileCorrupt, not a generic error, for the same reason
// check_xl2_v1 classifies a bad magic as FileCorrupt: heal
// classification (should_heal_object_on_disk) recognises
// corruption only by the DiskError::FileCorrupt variant this
// converts to. As a generic error the drive is skipped,
// heal_object reports ok, and on-disk bitrot is never repaired.
return Err(Error::FileCorrupt);
}
Ok((meta, inline_data))
@@ -163,8 +176,16 @@ impl FileMeta {
let meta_crc = xxh64::xxh64(meta, XXHASH_SEED) as u32;
if crc != meta_crc {
error!("xl file crc check failed: expected CRC {:#x}, got {:#x}", meta_crc, crc);
return Err(Error::other("xl file crc check failed"));
error!(
event = "filemeta_xl_crc_mismatch",
component = "filemeta",
expected_crc = meta_crc,
actual_crc = crc,
"xl.meta payload failed its CRC check"
);
// See is_indexed_meta: the FileCorrupt variant is what makes heal
// classify this drive as needing metadata repair.
return Err(Error::FileCorrupt);
}
if !buf.is_empty() {