ic_host_fs/scratch/
mod.rs1#[cfg(unix)]
4use std::{
5 fs::{self, File, OpenOptions},
6 io::Write as _,
7 os::unix::fs::OpenOptionsExt as _,
8 sync::atomic::{AtomicU64, Ordering},
9};
10use std::{
11 io,
12 path::{Path, PathBuf},
13};
14
15#[cfg(all(test, unix))]
16mod tests;
17
18#[cfg(unix)]
19static SEQUENCE: AtomicU64 = AtomicU64::new(0);
20
21#[derive(Debug, thiserror::Error)]
23pub enum PrivateBytesError {
24 #[error("private scratch creation failed")]
26 BeforeCreation(#[source] io::Error),
27 #[error("private scratch write{} failed", if .cleanup.is_some() { " and cleanup" } else { "" })]
29 Write {
30 path: PathBuf,
32 source: io::Error,
34 cleanup: Option<io::Error>,
36 },
37}
38
39pub fn create_private_bytes(
61 parent: &Path,
62 prefix: &str,
63 bytes: &[u8],
64 attempts: usize,
65) -> Result<PathBuf, PrivateBytesError> {
66 #[cfg(unix)]
67 {
68 create_with_sequence(parent, prefix, bytes, attempts, || {
69 SEQUENCE.fetch_add(1, Ordering::Relaxed)
70 })
71 }
72 #[cfg(not(unix))]
73 {
74 let _ = (parent, prefix, bytes, attempts);
75 Err(PrivateBytesError::BeforeCreation(io::Error::new(
76 io::ErrorKind::Unsupported,
77 "private scratch files require Unix permissions",
78 )))
79 }
80}
81
82#[cfg(unix)]
83fn create_with_sequence(
84 parent: &Path,
85 prefix: &str,
86 bytes: &[u8],
87 attempts: usize,
88 mut sequence: impl FnMut() -> u64,
89) -> Result<PathBuf, PrivateBytesError> {
90 use std::path::Component;
91 let mut components = Path::new(prefix).components();
92 if attempts == 0
93 || prefix.contains(['\0', '\r', '\n'])
94 || !matches!(
95 (components.next(), components.next()),
96 (Some(Component::Normal(name)), None) if name == std::ffi::OsStr::new(prefix)
97 )
98 {
99 return Err(PrivateBytesError::BeforeCreation(io::Error::new(
100 io::ErrorKind::InvalidInput,
101 "invalid private scratch request",
102 )));
103 }
104 let parent = parent
105 .canonicalize()
106 .map_err(PrivateBytesError::BeforeCreation)?;
107 for _ in 0..attempts {
108 let path = parent.join(format!("{prefix}-{}-{}", std::process::id(), sequence()));
109 match OpenOptions::new()
110 .write(true)
111 .create_new(true)
112 .mode(0o600)
113 .open(&path)
114 {
115 Ok(file) => {
116 return finish_candidate(
117 file,
118 path,
119 |file| file.write_all(bytes),
120 |path| fs::remove_file(path),
121 );
122 }
123 Err(source) if source.kind() == io::ErrorKind::AlreadyExists => {}
124 Err(source) => return Err(PrivateBytesError::BeforeCreation(source)),
125 }
126 }
127 Err(PrivateBytesError::BeforeCreation(io::Error::new(
128 io::ErrorKind::AlreadyExists,
129 "private scratch creation attempts exhausted",
130 )))
131}
132
133#[cfg(unix)]
134fn finish_candidate(
135 mut file: File,
136 path: PathBuf,
137 write: impl FnOnce(&mut File) -> io::Result<()>,
138 remove: impl FnOnce(&Path) -> io::Result<()>,
139) -> Result<PathBuf, PrivateBytesError> {
140 let result = write(&mut file);
141 drop(file);
142 match result {
143 Ok(()) => Ok(path),
144 Err(source) => {
145 let cleanup = remove(&path).err();
146 Err(PrivateBytesError::Write {
147 path,
148 source,
149 cleanup,
150 })
151 }
152 }
153}