cap_tempfile/
lib.rs

1//! Capability-based temporary directories and files.
2
3#![deny(missing_docs)]
4#![forbid(unsafe_code)]
5#![doc(
6    html_logo_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.svg"
7)]
8#![doc(
9    html_favicon_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.ico"
10)]
11
12use cap_std::fs::Dir;
13use std::ops::Deref;
14use std::{env, fmt, fs, io, mem};
15#[cfg(not(target_os = "emscripten"))]
16use uuid::Uuid;
17
18#[cfg(feature = "fs_utf8")]
19pub mod utf8;
20
21mod tempfile;
22pub use tempfile::*;
23
24/// Re-export because we use this in our public API.
25pub use cap_std;
26
27#[doc(hidden)]
28pub use cap_std::ambient_authority_known_at_compile_time;
29pub use cap_std::{ambient_authority, AmbientAuthority};
30
31/// A directory in a filesystem that is automatically deleted when it goes out
32/// of scope.
33///
34/// This corresponds to [`tempfile::TempDir`].
35///
36/// Unlike `tempfile::TempDir`, this API has no `TempDir::path`,
37/// `TempDir::into_path`, or `impl AsRef<Path>`, because absolute paths don't
38/// interoperate well with the capability model.
39///
40/// [`tempfile::TempDir`]: https://docs.rs/tempfile/latest/tempfile/struct.TempDir.html
41pub struct TempDir {
42    dir: Option<Dir>,
43}
44
45impl TempDir {
46    /// Attempts to make a temporary directory inside of `env::temp_dir()`.
47    ///
48    /// This corresponds to [`tempfile::TempDir::new`].
49    ///
50    /// [`tempfile::TempDir::new`]: https://docs.rs/tempfile/latest/tempfile/struct.TempDir.html#method.new
51    ///
52    /// # Ambient Authority
53    ///
54    /// This function makes use of ambient authority to access temporary
55    /// directories.
56    pub fn new(ambient_authority: AmbientAuthority) -> io::Result<Self> {
57        let system_tmp = env::temp_dir();
58        for _ in 0..Self::num_iterations() {
59            let name = system_tmp.join(&Self::new_name());
60            match fs::create_dir(&name) {
61                Ok(()) => {
62                    let dir = match Dir::open_ambient_dir(&name, ambient_authority) {
63                        Ok(dir) => dir,
64                        Err(e) => {
65                            fs::remove_dir(name).ok();
66                            return Err(e);
67                        }
68                    };
69                    return Ok(Self { dir: Some(dir) });
70                }
71                Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
72                Err(e) => return Err(e),
73            }
74        }
75        Err(Self::already_exists())
76    }
77
78    /// Create a new temporary directory.
79    ///
80    /// This corresponds to [`tempfile::TempDir::new_in`].
81    ///
82    /// [`tempfile::TempDir::new_in`]: https://docs.rs/tempfile/latest/tempfile/fn.tempdir_in.html
83    pub fn new_in(dir: &Dir) -> io::Result<Self> {
84        for _ in 0..Self::num_iterations() {
85            let name = &Self::new_name();
86            match dir.create_dir(name) {
87                Ok(()) => {
88                    let dir = match dir.open_dir(name) {
89                        Ok(dir) => dir,
90                        Err(e) => {
91                            dir.remove_dir(name).ok();
92                            return Err(e);
93                        }
94                    };
95                    return Ok(Self { dir: Some(dir) });
96                }
97                Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
98                Err(e) => return Err(e),
99            }
100        }
101        Err(Self::already_exists())
102    }
103
104    /// Make this directory persistent.
105    ///
106    /// This corresponds to [`tempfile::TempDir::into_path`], but returns a [`Dir`].
107    ///
108    /// [`tempfile::TempDir::into_path`]: https://docs.rs/tempfile/latest/tempfile/struct.TempDir.html#method.into_path
109    /// [`Dir`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html
110    pub fn into_dir(mut self) -> io::Result<Dir> {
111        Ok(self.dir.take().unwrap())
112    }
113
114    /// Closes and removes the temporary directory, returning a `Result`.
115    ///
116    /// This corresponds to [`tempfile::TempDir::close`].
117    ///
118    /// [`tempfile::TempDir::close`]: https://docs.rs/tempfile/latest/tempfile/struct.TempDir.html#method.close
119    pub fn close(mut self) -> io::Result<()> {
120        mem::take(&mut self.dir).unwrap().remove_open_dir_all()
121    }
122
123    pub(crate) fn new_name() -> String {
124        #[cfg(not(target_os = "emscripten"))]
125        {
126            Uuid::new_v4().to_string()
127        }
128
129        // Uuid doesn't support Emscripten yet, but Emscripten isn't multi-user
130        // or multi-process yet, so we can do something simple.
131        #[cfg(target_os = "emscripten")]
132        {
133            use rand::RngCore;
134            let mut r = rand::thread_rng();
135            format!("cap-primitives.{}", r.next_u32())
136        }
137    }
138
139    pub(crate) const fn num_iterations() -> i32 {
140        i32::MAX
141    }
142
143    fn already_exists() -> io::Error {
144        io::Error::new(
145            io::ErrorKind::AlreadyExists,
146            "too many temporary files exist",
147        )
148    }
149}
150
151impl Deref for TempDir {
152    type Target = Dir;
153
154    fn deref(&self) -> &Self::Target {
155        self.dir.as_ref().unwrap()
156    }
157}
158
159impl Drop for TempDir {
160    fn drop(&mut self) {
161        if let Some(dir) = mem::take(&mut self.dir) {
162            dir.remove_open_dir_all().ok();
163        }
164    }
165}
166
167impl fmt::Debug for TempDir {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        self.dir.fmt(f)
170    }
171}
172
173/// Create a new temporary directory.
174///
175/// This corresponds to [`tempfile::tempdir`].
176///
177/// [`tempfile::tempdir`]: https://docs.rs/tempfile/latest/tempfile/fn.tempdir.html
178///
179/// # Ambient Authority
180///
181/// This function makes use of ambient authority to access temporary
182/// directories.
183pub fn tempdir(ambient_authority: AmbientAuthority) -> io::Result<TempDir> {
184    TempDir::new(ambient_authority)
185}
186
187/// Create a new temporary directory.
188///
189/// This corresponds to [`tempfile::tempdir_in`].
190///
191/// [`tempfile::tempdir_in`]: https://docs.rs/tempfile/latest/tempfile/fn.tempdir_in.html
192pub fn tempdir_in(dir: &Dir) -> io::Result<TempDir> {
193    TempDir::new_in(dir)
194}
195
196/// Call f repeatedly, passing a randomly generated temporary name.
197/// An error matching the `err` will be ignored.
198/// This will repeat until a maximum number of attempts is reached.
199/// On success, the result of the function call along with the provided name is
200/// returned.
201pub(crate) fn retry_with_name_ignoring<F, T>(
202    err: std::io::ErrorKind,
203    mut f: F,
204) -> io::Result<(T, String)>
205where
206    F: FnMut(&str) -> io::Result<T>,
207{
208    for _ in 0..TempDir::num_iterations() {
209        let name = TempDir::new_name();
210        match f(name.as_str()) {
211            Ok(r) => return Ok((r, name)),
212            Err(e) if e.kind() == err => continue,
213            Err(e) => return Err(e),
214        }
215    }
216    Err(std::io::Error::new(err, "too many temporary files exist"))
217}
218
219#[test]
220fn drop_tempdir() {
221    use crate::ambient_authority;
222
223    let t = tempdir(ambient_authority()).unwrap();
224    drop(t)
225}
226
227#[test]
228fn close_tempdir() {
229    use crate::ambient_authority;
230
231    let t = tempdir(ambient_authority()).unwrap();
232    t.close().unwrap();
233}
234
235#[test]
236fn persist_tempdir() {
237    use crate::ambient_authority;
238
239    let t = tempdir(ambient_authority()).unwrap();
240    let d = t.into_dir().unwrap();
241    assert!(d.exists("."));
242}
243
244#[test]
245fn drop_tempdir_in() {
246    use crate::ambient_authority;
247
248    let dir = Dir::open_ambient_dir(env::temp_dir(), ambient_authority()).unwrap();
249    let t = tempdir_in(&dir).unwrap();
250    drop(t);
251}
252
253#[test]
254fn close_tempdir_in() {
255    use crate::ambient_authority;
256
257    let dir = Dir::open_ambient_dir(env::temp_dir(), ambient_authority()).unwrap();
258    let t = tempdir_in(&dir).unwrap();
259    t.close().unwrap();
260}
261
262#[test]
263fn close_outer() {
264    use crate::ambient_authority;
265
266    let t = tempdir(ambient_authority()).unwrap();
267    let _s = tempdir_in(&t).unwrap();
268    #[cfg(windows)]
269    assert!(matches!(
270        t.close().unwrap_err().raw_os_error().map(|err| err as _),
271        Some(windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION)
272            | Some(windows_sys::Win32::Foundation::ERROR_DIR_NOT_EMPTY)
273    ));
274    #[cfg(not(windows))]
275    t.close().unwrap();
276}
277
278#[test]
279fn close_inner() {
280    use crate::ambient_authority;
281
282    let t = tempdir(ambient_authority()).unwrap();
283    let s = tempdir_in(&t).unwrap();
284    s.close().unwrap();
285}