use std::path::{Path, PathBuf};
pub fn atomic_write(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
let tmp = tmp_path_for(path);
std::fs::write(&tmp, bytes)?;
if let Err(e) = std::fs::rename(&tmp, path) {
let _ = std::fs::remove_file(&tmp);
return Err(e);
}
Ok(())
}
#[must_use]
pub fn tmp_path_for(path: &Path) -> PathBuf {
let mut s = path.as_os_str().to_owned();
s.push(".tmp");
PathBuf::from(s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn writes_bytes_to_path() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("data.bin");
atomic_write(&path, b"hello").unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"hello");
}
#[test]
fn no_tmp_file_lingers_after_successful_write() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("data.bin");
atomic_write(&path, b"hello").unwrap();
let tmp = tmp_path_for(&path);
assert!(
!tmp.exists(),
"tmp file {} should not exist after successful rename",
tmp.display(),
);
}
#[test]
fn overwrites_existing_destination() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("data.bin");
atomic_write(&path, b"first").unwrap();
atomic_write(&path, b"second").unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"second");
}
#[test]
fn tmp_path_preserves_full_filename() {
let p = Path::new("/some/dir/agents.lock");
assert_eq!(tmp_path_for(p), Path::new("/some/dir/agents.lock.tmp"));
let p = Path::new("/some/dir/agents.yml");
assert_eq!(tmp_path_for(p), Path::new("/some/dir/agents.yml.tmp"));
}
#[test]
fn tmp_path_handles_no_extension() {
let p = Path::new("/some/dir/file");
assert_eq!(tmp_path_for(p), Path::new("/some/dir/file.tmp"));
}
#[cfg(unix)]
#[test]
fn cleans_up_tmp_on_failure() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let subdir = dir.path().join("ro");
std::fs::create_dir(&subdir).unwrap();
let path = subdir.join("data.bin");
let mut perms = std::fs::metadata(&subdir).unwrap().permissions();
perms.set_mode(0o555);
std::fs::set_permissions(&subdir, perms).unwrap();
let outcome = atomic_write(&path, b"new");
let mut perms = std::fs::metadata(&subdir).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&subdir, perms).unwrap();
if outcome.is_ok() {
return;
}
let tmp = tmp_path_for(&path);
assert!(
!tmp.exists(),
"tmp file {} should not linger after failed write",
tmp.display(),
);
}
}