1//! temp-dir
2//! ========
3//! [![crates.io version](https://img.shields.io/crates/v/temp-dir.svg)](https://crates.io/crates/temp-dir)
4//! [![license: Apache 2.0](https://gitlab.com/leonhard-llc/ops/-/raw/main/license-apache-2.0.svg)](https://gitlab.com/leonhard-llc/ops/-/raw/main/temp-dir/LICENSE)
5//! [![unsafe forbidden](https://gitlab.com/leonhard-llc/ops/-/raw/main/unsafe-forbidden.svg)](https://github.com/rust-secure-code/safety-dance/)
6//! [![pipeline status](https://gitlab.com/leonhard-llc/ops/badges/main/pipeline.svg)](https://gitlab.com/leonhard-llc/ops/-/pipelines)
7//!
8//! Provides a `TempDir` struct.
9//!
10//! # Features
11//! - Makes a directory in a system temporary directory
12//! - Recursively deletes the directory and its contents on drop
13//! - Deletes symbolic links and does not follow them.
14//! - Optional name prefix
15//! - Depends only on `std`
16//! - `forbid(unsafe_code)`
17//! - 100% test coverage
18//!
19//! # Limitations
20//! - Not security-hardened.
21//! For example, directory and file names are predictable.
22//! - This crate uses
23//! [`std::fs::remove_dir_all`](https://doc.rust-lang.org/stable/std/fs/fn.remove_dir_all.html)
24//! which may be unreliable on Windows.
25//! See [rust#29497](https://github.com/rust-lang/rust/issues/29497) and
26//! [`remove_dir_all`](https://crates.io/crates/remove_dir_all) crate.
27//!
28//! # Alternatives
29//! - [`tempfile`](https://crates.io/crates/tempfile)
30//! - Popular and mature
31//! - Contains `unsafe`, dependencies full of `unsafe`
32//! - Heavy dependencies (libc, winapi, rand, etc.)
33//! - [`test_dir`](https://crates.io/crates/test_dir)
34//! - Has a handy `TestDir` struct
35//! - Incomplete documentation
36//! - [`temp_testdir`](https://crates.io/crates/temp_testdir)
37//! - Incomplete documentation
38//! - [`mktemp`](https://crates.io/crates/mktemp)
39//! - Sets directory mode 0700 on unix
40//! - Contains `unsafe`
41//! - No readme or online docs
42//!
43//! # Related Crates
44//! - [`temp-file`](https://crates.io/crates/temp-file)
45//!
46//! # Example
47//! ```rust
48//! use temp_dir::TempDir;
49//! let d = TempDir::new().unwrap();
50//! // Prints "/tmp/t1a9b-0".
51//! println!("{:?}", d.path());
52//! let f = d.child("file1");
53//! // Prints "/tmp/t1a9b-0/file1".
54//! println!("{:?}", f);
55//! std::fs::write(&f, b"abc").unwrap();
56//! assert_eq!(
57//! "abc",
58//! std::fs::read_to_string(&f).unwrap(),
59//! );
60//! // Prints "/tmp/t1a9b-1".
61//! println!(
62//! "{:?}", TempDir::new().unwrap().path());
63//! ```
64//!
65//! # Cargo Geiger Safety Report
66//! # Changelog
67//! - v0.1.12 - Work when the directory already exists.
68//! - v0.1.11
69//! - Return `std::io::Error` instead of `String`.
70//! - Add
71//! [`cleanup`](https://docs.rs/temp-file/latest/temp_file/struct.TempFile.html#method.cleanup).
72//! - v0.1.10 - Implement `Eq`, `Ord`, `Hash`
73//! - v0.1.9 - Increase test coverage
74//! - v0.1.8 - Add [`leak`](https://docs.rs/temp-dir/latest/temp_dir/struct.TempDir.html#method.leak).
75//! - v0.1.7 - Update docs:
76//! Warn about `std::fs::remove_dir_all` being unreliable on Windows.
77//! Warn about predictable directory and file names.
78//! Thanks to Reddit user
79//! [burntsushi](https://www.reddit.com/r/rust/comments/ma6y0x/tempdir_simple_temporary_directory_with_cleanup/gruo5iu/).
80//! - v0.1.6 - Add
81//! [`TempDir::panic_on_cleanup_error`](https://docs.rs/temp-dir/latest/temp_dir/struct.TempDir.html#method.panic_on_cleanup_error).
82//! Thanks to Reddit users
83//! [`KhorneLordOfChaos`](https://www.reddit.com/r/rust/comments/ma6y0x/tempdir_simple_temporary_directory_with_cleanup/grsb5s3/)
84//! and
85//! [`dpc_pw`](https://www.reddit.com/r/rust/comments/ma6y0x/tempdir_simple_temporary_directory_with_cleanup/gru26df/)
86//! for their comments.
87//! - v0.1.5 - Explain how it handles symbolic links.
88//! Thanks to Reddit user Mai4eeze for this
89//! [idea](https://www.reddit.com/r/rust/comments/ma6y0x/tempdir_simple_temporary_directory_with_cleanup/grsoz2g/).
90//! - v0.1.4 - Update docs
91//! - v0.1.3 - Minor code cleanup, update docs
92//! - v0.1.2 - Update docs
93//! - v0.1.1 - Fix license
94//! - v0.1.0 - Initial version
95#![forbid(unsafe_code)]
96use core::sync::atomic::{AtomicU32, Ordering};
97use std::io::ErrorKind;
98use std::path::{Path, PathBuf};
99use std::sync::atomic::AtomicBool;
100
101static COUNTER: AtomicU32 = AtomicU32::new(0);
102static INTERNAL_RETRY: AtomicBool = AtomicBool::new(true);
103
104/// The path of an existing writable directory in a system temporary directory.
105///
106/// Drop the struct to delete the directory and everything under it.
107/// Deletes symbolic links and does not follow them.
108///
109/// Ignores any error while deleting.
110/// See [`TempDir::panic_on_cleanup_error`](struct.TempDir.html#method.panic_on_cleanup_error).
111///
112/// # Example
113/// ```rust
114/// use temp_dir::TempDir;
115/// let d = TempDir::new().unwrap();
116/// // Prints "/tmp/t1a9b-0".
117/// println!("{:?}", d.path());
118/// let f = d.child("file1");
119/// // Prints "/tmp/t1a9b-0/file1".
120/// println!("{:?}", f);
121/// std::fs::write(&f, b"abc").unwrap();
122/// assert_eq!(
123/// "abc",
124/// std::fs::read_to_string(&f).unwrap(),
125/// );
126/// // Prints "/tmp/t1a9b-1".
127/// println!("{:?}", TempDir::new().unwrap().path());
128/// ```
129#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)]
130pub struct TempDir {
131 path_buf: Option<PathBuf>,
132 panic_on_delete_err: bool,
133}
134impl TempDir {
135 fn remove_dir(path: &Path) -> Result<(), std::io::Error> {
136 match std::fs::remove_dir_all(path) {
137 Ok(()) => Ok(()),
138 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
139 Err(e) => Err(std::io::Error::new(
140 e.kind(),
141 format!("error removing directory and contents {path:?}: {e}"),
142 )),
143 }
144 }
145
146 /// Create a new empty directory in a system temporary directory.
147 ///
148 /// Drop the struct to delete the directory and everything under it.
149 /// Deletes symbolic links and does not follow them.
150 ///
151 /// Ignores any error while deleting.
152 /// See [`TempDir::panic_on_cleanup_error`](struct.TempDir.html#method.panic_on_cleanup_error).
153 ///
154 /// # Errors
155 /// Returns `Err` when it fails to create the directory.
156 ///
157 /// # Example
158 /// ```rust
159 /// // Prints "/tmp/t1a9b-0".
160 /// println!("{:?}", temp_dir::TempDir::new().unwrap().path());
161 /// ```
162 pub fn new() -> Result<Self, std::io::Error> {
163 // Prefix with 't' to avoid name collisions with `temp-file` crate.
164 Self::with_prefix("t")
165 }
166
167 /// Create a new empty directory in a system temporary directory.
168 /// Use `prefix` as the first part of the directory's name.
169 ///
170 /// Drop the struct to delete the directory and everything under it.
171 /// Deletes symbolic links and does not follow them.
172 ///
173 /// Ignores any error while deleting.
174 /// See [`TempDir::panic_on_cleanup_error`](struct.TempDir.html#method.panic_on_cleanup_error).
175 ///
176 /// # Errors
177 /// Returns `Err` when it fails to create the directory.
178 ///
179 /// # Example
180 /// ```rust
181 /// // Prints "/tmp/ok1a9b-0".
182 /// println!("{:?}", temp_dir::TempDir::with_prefix("ok").unwrap().path());
183 /// ```
184 pub fn with_prefix(prefix: impl AsRef<str>) -> Result<Self, std::io::Error> {
185 loop {
186 let path_buf = std::env::temp_dir().join(format!(
187 "{}{:x}-{:x}",
188 prefix.as_ref(),
189 std::process::id(),
190 COUNTER.fetch_add(1, Ordering::AcqRel),
191 ));
192 match std::fs::create_dir(&path_buf) {
193 Err(e)
194 if e.kind() == ErrorKind::AlreadyExists
195 && INTERNAL_RETRY.load(Ordering::Acquire) => {}
196 Err(e) => {
197 return Err(std::io::Error::new(
198 e.kind(),
199 format!("error creating directory {path_buf:?}: {e}"),
200 ))
201 }
202 Ok(()) => {
203 return Ok(Self {
204 path_buf: Some(path_buf),
205 panic_on_delete_err: false,
206 })
207 }
208 }
209 }
210 }
211
212 /// Remove the directory and its contents now.
213 ///
214 /// # Errors
215 /// Returns an error if the directory exists and we fail to remove it and its contents.
216 #[allow(clippy::missing_panics_doc)]
217 pub fn cleanup(mut self) -> Result<(), std::io::Error> {
218 Self::remove_dir(&self.path_buf.take().unwrap())
219 }
220
221 /// Make the struct panic on drop if it hits an error while
222 /// removing the directory or its contents.
223 #[must_use]
224 pub fn panic_on_cleanup_error(mut self) -> Self {
225 self.panic_on_delete_err = true;
226 self
227 }
228
229 /// Do not delete the directory or its contents.
230 ///
231 /// This is useful when debugging a test.
232 pub fn leak(mut self) {
233 self.path_buf.take();
234 }
235
236 /// The path to the directory.
237 #[must_use]
238 #[allow(clippy::missing_panics_doc)]
239 pub fn path(&self) -> &Path {
240 self.path_buf.as_ref().unwrap()
241 }
242
243 /// The path to `name` under the directory.
244 #[must_use]
245 #[allow(clippy::missing_panics_doc)]
246 pub fn child(&self, name: impl AsRef<str>) -> PathBuf {
247 let mut result = self.path_buf.as_ref().unwrap().clone();
248 result.push(name.as_ref());
249 result
250 }
251}
252impl Drop for TempDir {
253 fn drop(&mut self) {
254 if let Some(path: PathBuf) = self.path_buf.take() {
255 let result: Result<(), Error> = Self::remove_dir(&path);
256 if self.panic_on_delete_err {
257 if let Err(e: Error) = result {
258 panic!("{}", e);
259 }
260 }
261 }
262 }
263}
264
265#[cfg(test)]
266mod test;
267