1use std::fs::OpenOptions;
7use std::io::{ErrorKind, Write as _};
8use std::os::unix::fs::OpenOptionsExt as _;
9use std::path::PathBuf;
10use std::sync::atomic::{AtomicUsize, Ordering};
11
12use anyhow::{Context, Result, bail};
13
14pub fn write(what: &str, suffix: &str, bytes: &[u8]) -> Result<PathBuf> {
17 static COUNTER: AtomicUsize = AtomicUsize::new(0);
18 for _ in 0..100 {
19 let nanos = std::time::SystemTime::now()
22 .duration_since(std::time::UNIX_EPOCH)
23 .map_or(0, |d| d.subsec_nanos());
24 let path = std::env::temp_dir().join(format!(
25 "rmut-{what}-{}-{}-{nanos:08x}{suffix}",
26 std::process::id(),
27 COUNTER.fetch_add(1, Ordering::Relaxed),
28 ));
29 let mut file = match OpenOptions::new()
30 .write(true)
31 .create_new(true)
32 .mode(0o600)
33 .open(&path)
34 {
35 Ok(file) => file,
36 Err(e) if e.kind() == ErrorKind::AlreadyExists => continue,
37 Err(e) => return Err(e).with_context(|| format!("creating {}", path.display())),
38 };
39 if let Err(e) = file.write_all(bytes) {
40 let _ = std::fs::remove_file(&path);
41 return Err(e).with_context(|| format!("writing {}", path.display()));
42 }
43 return Ok(path);
44 }
45 bail!(
46 "no free temporary file name in {}",
47 std::env::temp_dir().display()
48 )
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use std::os::unix::fs::PermissionsExt as _;
55
56 #[test]
57 fn private_and_fresh_each_time() {
58 let a = write("scratch-test", ".txt", b"one").unwrap();
59 let b = write("scratch-test", ".txt", b"two").unwrap();
60 assert_ne!(a, b);
61 assert!(a.to_str().unwrap().ends_with(".txt"));
62 assert_eq!(std::fs::read(&a).unwrap(), b"one");
63 let mode = std::fs::metadata(&a).unwrap().permissions().mode();
64 assert_eq!(mode & 0o777, 0o600);
65 let _ = std::fs::remove_file(a);
66 let _ = std::fs::remove_file(b);
67 }
68
69 #[test]
70 fn never_writes_through_a_planted_link() {
71 let dir = tempfile::tempdir().unwrap();
74 let target = dir.path().join("victim");
75 let link = dir.path().join("link");
76 std::os::unix::fs::symlink(&target, &link).unwrap();
77 let err = OpenOptions::new()
78 .write(true)
79 .create_new(true)
80 .mode(0o600)
81 .open(&link)
82 .unwrap_err();
83 assert_eq!(err.kind(), ErrorKind::AlreadyExists);
84 assert!(!target.exists());
85 }
86}