1use anyhow::{Context, Result};
9use std::path::{Path, PathBuf};
10use std::sync::atomic::{AtomicU64, Ordering};
11
12static TMP_COUNTER: AtomicU64 = AtomicU64::new(0);
14
15fn unique_tmp_path(path: &Path) -> PathBuf {
18 let counter = TMP_COUNTER.fetch_add(1, Ordering::Relaxed);
19 let pid = std::process::id();
20 let file_name = path
21 .file_name()
22 .and_then(|n| n.to_str())
23 .unwrap_or("zag-atomic");
24 let tmp_name = format!(".{}.{}.{}.tmp", file_name, pid, counter);
25 match path.parent() {
26 Some(parent) => parent.join(tmp_name),
27 None => PathBuf::from(tmp_name),
28 }
29}
30
31pub fn atomic_write(path: &Path, content: &[u8]) -> Result<()> {
39 if let Some(parent) = path.parent() {
40 std::fs::create_dir_all(parent)
41 .with_context(|| format!("Failed to create directory: {}", parent.display()))?;
42 }
43 let tmp_path = unique_tmp_path(path);
44 std::fs::write(&tmp_path, content)
45 .with_context(|| format!("Failed to write temp file: {}", tmp_path.display()))?;
46 std::fs::rename(&tmp_path, path).with_context(|| {
47 let _ = std::fs::remove_file(&tmp_path);
49 format!(
50 "Failed to rename {} -> {}",
51 tmp_path.display(),
52 path.display()
53 )
54 })?;
55 Ok(())
56}
57
58pub fn atomic_write_str(path: &Path, content: &str) -> Result<()> {
60 atomic_write(path, content.as_bytes())
61}
62
63#[cfg(test)]
64#[path = "file_util_tests.rs"]
65mod tests;