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));