use std::io::Write;
use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering};
static TMP_COUNTER: AtomicU64 = AtomicU64::new(0);
pub(crate) fn atomic_write(final_path: &Path, data: &[u8]) -> std::io::Result<()> {
let seq = TMP_COUNTER.fetch_add(1, Ordering::Relaxed);
let pid = std::process::id();
let tid = std::thread::current().id();
let file_name = final_path.file_name().unwrap_or_default().to_string_lossy();
let tmp_path = final_path.with_file_name(format!("{file_name}.tmp.{pid}.{tid:?}.{seq}"));
let result = atomic_write_inner(&tmp_path, final_path, data);
if result.is_err() {
let _ = std::fs::remove_file(&tmp_path);
}
result
}
fn atomic_write_inner(tmp_path: &Path, final_path: &Path, data: &[u8]) -> std::io::Result<()> {
let file = std::fs::File::create(tmp_path)?;
let mut writer = std::io::BufWriter::new(file);
writer.write_all(data)?;
writer.flush()?;
writer.get_ref().sync_all()?; std::fs::rename(tmp_path, final_path)
}
#[cfg(test)]
mod tests {
use super::atomic_write;
#[test]
fn test_atomic_write_round_trips_and_leaves_no_temp() {
let dir = tempfile::TempDir::new().expect("test: temp dir");
let path = dir.path().join("snap.bin");
atomic_write(&path, b"hello").expect("test: write");
assert_eq!(std::fs::read(&path).expect("test: read"), b"hello");
let leftovers: Vec<_> = std::fs::read_dir(dir.path())
.expect("test: read dir")
.filter_map(std::result::Result::ok)
.filter(|e| e.file_name().to_string_lossy().contains(".tmp."))
.collect();
assert!(leftovers.is_empty(), "no .tmp files should remain");
}
#[test]
fn test_atomic_write_overwrites_existing() {
let dir = tempfile::TempDir::new().expect("test: temp dir");
let path = dir.path().join("snap.bin");
atomic_write(&path, b"first").expect("test: first");
atomic_write(&path, b"second").expect("test: overwrite");
assert_eq!(std::fs::read(&path).expect("test: read"), b"second");
}
}