1use std::borrow::Cow;
8use std::fs::{self, File, OpenOptions};
9use std::io::{self, Write};
10use std::path::{Path, PathBuf};
11use std::sync::atomic::{AtomicU64, Ordering};
12
13const ATTEMPTS: usize = 100;
15static COUNTER: AtomicU64 = AtomicU64::new(0);
16
17pub fn write_atomic<F>(path: &Path, write: F) -> io::Result<()>
26where
27 F: FnOnce(&mut File) -> io::Result<()>,
28{
29 let parent = path.parent().unwrap_or_else(|| Path::new("."));
30 let name = path
31 .file_name()
32 .map_or(Cow::Borrowed("file"), |name| name.to_string_lossy());
33 let (temporary, mut file) = create_sibling(parent, &name)?;
34 let result = (|| {
35 write(&mut file)?;
36 file.sync_all()?;
37 drop(file);
38 fs::rename(&temporary, path)
39 })();
40 if result.is_err() {
41 let _ = fs::remove_file(&temporary);
42 }
43 result
44}
45
46pub fn write_atomic_bytes(path: &Path, bytes: &[u8]) -> io::Result<()> {
48 write_atomic(path, |file| file.write_all(bytes))
49}
50
51fn create_sibling(parent: &Path, name: &str) -> io::Result<(PathBuf, File)> {
52 let pid = std::process::id();
53 for _ in 0..ATTEMPTS {
54 let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
55 let candidate = parent.join(format!(".{name}.tmp-{pid}-{counter}"));
56 match OpenOptions::new()
57 .write(true)
58 .create_new(true)
59 .open(&candidate)
60 {
61 Ok(file) => return Ok((candidate, file)),
62 Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {}
63 Err(error) => return Err(error),
64 }
65 }
66 Err(io::Error::new(
67 io::ErrorKind::AlreadyExists,
68 format!("no free temporary name for {name} in {}", parent.display()),
69 ))
70}