diff --git a/seaweed-volume/src/storage/disk_location.rs b/seaweed-volume/src/storage/disk_location.rs
index 13c71cbed..d8959f9f1 100644
--- a/seaweed-volume/src/storage/disk_location.rs
+++ b/seaweed-volume/src/storage/disk_location.rs
@@ -395,6 +395,12 @@ impl DiskLocation {
for i in 0..MAX_SHARD_COUNT {
rm_if_present(format!("{}.ec{:02}", base, i))?;
}
+
+ // Remove the bitrot checksum sidecars from both the data and idx dirs.
+ remove_bitrot_sidecars(&base)?;
+ if self.idx_directory != self.directory {
+ remove_bitrot_sidecars(&idx_base)?;
+ }
Ok(())
}
@@ -1081,6 +1087,49 @@ fn rm_if_present(path: String) -> io::Result<()> {
}
}
+/// Remove the bitrot checksum sidecars for a base file name: the legacy
+/// `.ecsum` (generation 0) and any versioned `.ecsum.v`.
+/// Already-gone is success; returns the first real removal failure (and surfaces
+/// a directory-scan error) so a stale sidecar left behind is not reported as
+/// cleaned. Mirrors Go's removeBitrotSidecars.
+fn remove_bitrot_sidecars(base: &str) -> io::Result<()> {
+ use crate::storage::erasure_coding::ec_bitrot::BITROT_SIDECAR_EXT;
+ let rm = |path: std::path::PathBuf| -> io::Result<()> {
+ match fs::remove_file(&path) {
+ Err(e) if e.kind() != io::ErrorKind::NotFound => Err(e),
+ _ => Ok(()),
+ }
+ };
+ let mut first_err: Option = None;
+ let mut record = |res: io::Result<()>| {
+ if let Err(e) = res {
+ if first_err.is_none() {
+ first_err = Some(e);
+ }
+ }
+ };
+ record(rm(format!("{}{}", base, BITROT_SIDECAR_EXT).into()));
+ let path = std::path::Path::new(base);
+ if let (Some(parent), Some(fname)) = (path.parent(), path.file_name()) {
+ let prefix = format!("{}{}.v", fname.to_string_lossy(), BITROT_SIDECAR_EXT);
+ match fs::read_dir(parent) {
+ Ok(entries) => {
+ for entry in entries.flatten() {
+ if entry.file_name().to_string_lossy().starts_with(&prefix) {
+ record(rm(entry.path()));
+ }
+ }
+ }
+ Err(e) if e.kind() != io::ErrorKind::NotFound => record(Err(e)),
+ Err(_) => {}
+ }
+ }
+ match first_err {
+ Some(e) => Err(e),
+ None => Ok(()),
+ }
+}
+
fn ec_data_shards_from_vif(directory: &str, idx_directory: &str, collection: &str, vid: VolumeId) -> usize {
for dir in [directory, idx_directory] {
let vif = format!("{}.vif", volume_file_name(dir, collection, vid));