use std::io::Write;
use std::path::Path;
pub fn write(target: &Path, body: &[u8]) -> std::io::Result<()> {
let parent = target.parent().unwrap_or(Path::new("."));
let tmp_name = match target.file_name() {
Some(name) => {
let mut s = name.to_os_string();
s.push(".tmp");
s
}
None => return Err(std::io::Error::other("io_atomic: target has no file_name")),
};
let tmp = parent.join(tmp_name);
let mut f = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&tmp)?;
f.write_all(body)?;
f.sync_all()?;
drop(f);
std::fs::rename(&tmp, target)?;
#[cfg(unix)]
{
if let Ok(d) = std::fs::File::open(parent) {
let _ = d.sync_all();
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn write_replaces_existing_atomically() {
let dir = std::env::temp_dir().join(format!(
"io-atomic-test-{}",
std::process::id()
));
std::fs::create_dir_all(&dir).unwrap();
let target = dir.join("doc.txt");
write(&target, b"first").unwrap();
assert_eq!(std::fs::read_to_string(&target).unwrap(), "first");
write(&target, b"second").unwrap();
assert_eq!(std::fs::read_to_string(&target).unwrap(), "second");
let tmp = dir.join("doc.txt.tmp");
assert!(!tmp.exists(), "tmp file should have been renamed away");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn write_to_missing_parent_returns_error_no_panic() {
let target = std::env::temp_dir()
.join(format!("io-atomic-missing-{}", std::process::id()))
.join("subdir-that-does-not-exist")
.join("doc.txt");
let err = write(&target, b"hi").unwrap_err();
assert!(matches!(
err.kind(),
std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied
), "got {err:?}");
}
}