1#![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
24pub 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
31pub struct TempDir {
42 dir: Option<Dir>,
43}
44
45impl TempDir {
46 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 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 pub fn into_dir(mut self) -> io::Result<Dir> {
111 Ok(self.dir.take().unwrap())
112 }
113
114 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 #[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
173pub fn tempdir(ambient_authority: AmbientAuthority) -> io::Result<TempDir> {
184 TempDir::new(ambient_authority)
185}
186
187pub fn tempdir_in(dir: &Dir) -> io::Result<TempDir> {
193 TempDir::new_in(dir)
194}
195
196pub(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}