diff --git a/crates/filemeta/src/filemeta.rs b/crates/filemeta/src/filemeta.rs index da3c336c8..d654d2c8a 100644 --- a/crates/filemeta/src/filemeta.rs +++ b/crates/filemeta/src/filemeta.rs @@ -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(); diff --git a/crates/filemeta/src/filemeta/codec.rs b/crates/filemeta/src/filemeta/codec.rs index fb6dc20d9..7f2851581 100644 --- a/crates/filemeta/src/filemeta/codec.rs +++ b/crates/filemeta/src/filemeta/codec.rs @@ -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() {