use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
pub fn write_durable(tmp_path: &Path, final_path: &Path, bytes: &[u8]) -> std::io::Result<()> {
{
let mut f = std::fs::File::create(tmp_path)?;
f.write_all(bytes)?;
f.sync_all()?;
}
std::fs::rename(tmp_path, final_path)?;
if let Some(dir) = final_path.parent() {
if let Ok(d) = std::fs::File::open(dir) {
let _ = d.sync_all();
}
}
Ok(())
}
static QUARANTINE_SEQ: AtomicU64 = AtomicU64::new(0);
fn quarantine_candidate(path: &Path) -> Option<PathBuf> {
let elapsed = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
for _ in 0..128 {
let sequence = QUARANTINE_SEQ.fetch_add(1, Ordering::Relaxed);
let suffix = format!(
"json.corrupt-{}-{}-{}-{}",
elapsed.as_secs(),
elapsed.subsec_nanos(),
std::process::id(),
sequence
);
let candidate = path.with_extension(suffix);
match std::fs::symlink_metadata(&candidate) {
Ok(_) => continue,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Some(candidate),
Err(_) => return None,
}
}
None
}
pub fn quarantine_copy(path: &Path) -> Option<PathBuf> {
let metadata = std::fs::symlink_metadata(path).ok()?;
if !metadata.file_type().is_file() {
return None;
}
let backup = quarantine_candidate(path)?;
let mut source = std::fs::File::open(path).ok()?;
let mut destination = match std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&backup)
{
Ok(file) => file,
Err(_) => return None,
};
if std::io::copy(&mut source, &mut destination).is_err() || destination.sync_all().is_err() {
let _ = std::fs::remove_file(&backup);
return None;
}
Some(backup)
}
pub fn quarantine_corrupt(path: &Path) -> Option<PathBuf> {
let metadata = std::fs::symlink_metadata(path).ok()?;
if !metadata.file_type().is_file() {
return None;
}
let backup = quarantine_candidate(path)?;
if std::fs::hard_link(path, &backup).is_ok() {
let _ = std::fs::remove_file(path);
return Some(backup);
}
quarantine_copy(path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn write_durable_round_trips_and_replaces_atomically() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("state.json");
let tmp = dir.path().join("state.json.tmp");
write_durable(&tmp, &path, b"hello").unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"hello");
assert!(
!tmp.exists(),
"temp file must be renamed away, not left behind"
);
write_durable(&tmp, &path, b"world!!").unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"world!!");
assert!(!tmp.exists());
}
#[test]
fn quarantine_copy_keeps_multiple_same_tick_backups() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("state.json");
std::fs::write(&path, b"corrupt state").unwrap();
let first = quarantine_copy(&path).expect("first quarantine copy should succeed");
let second = quarantine_copy(&path).expect("second quarantine copy should succeed");
assert_ne!(first, second, "quarantine names must never collide");
assert_eq!(std::fs::read(first).unwrap(), b"corrupt state");
assert_eq!(std::fs::read(second).unwrap(), b"corrupt state");
assert!(path.exists(), "copy quarantine must preserve the source");
}
#[test]
fn quarantine_corrupt_moves_only_regular_files() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("state.json");
std::fs::write(&path, b"corrupt").unwrap();
let backup = quarantine_corrupt(&path).expect("regular file should be quarantined");
assert!(!path.exists());
assert_eq!(std::fs::read(backup).unwrap(), b"corrupt");
let directory = dir.path().join("directory.json");
std::fs::create_dir(&directory).unwrap();
assert!(quarantine_corrupt(&directory).is_none());
}
}