]>
Commit | Line | Data |
---|---|---|
83c7162d XL |
1 | //! Temporary files and directories. |
2 | //! | |
3 | //! - Use the [`tempfile()`] function for temporary files | |
4 | //! - Use the [`tempdir()`] function for temporary directories. | |
5 | //! | |
6 | //! # Design | |
7 | //! | |
8 | //! This crate provides several approaches to creating temporary files and directories. | |
9 | //! [`tempfile()`] relies on the OS to remove the temporary file once the last handle is closed. | |
10 | //! [`TempDir`] and [`NamedTempFile`] both rely on Rust destructors for cleanup. | |
11 | //! | |
12 | //! When choosing between the temporary file variants, prefer `tempfile` | |
13 | //! unless you either need to know the file's path or to be able to persist it. | |
14 | //! | |
15 | //! ## Resource Leaking | |
16 | //! | |
b7449926 | 17 | //! `tempfile` will (almost) never fail to cleanup temporary resources, but `TempDir` and `NamedTempFile` will if |
83c7162d | 18 | //! their destructors don't run. This is because `tempfile` relies on the OS to cleanup the |
b7449926 | 19 | //! underlying file, while `TempDir` and `NamedTempFile` rely on their destructors to do so. |
83c7162d XL |
20 | //! |
21 | //! ## Security | |
22 | //! | |
23 | //! In the presence of pathological temporary file cleaner, relying on file paths is unsafe because | |
24 | //! a temporary file cleaner could delete the temporary file which an attacker could then replace. | |
25 | //! | |
26 | //! `tempfile` doesn't rely on file paths so this isn't an issue. However, `NamedTempFile` does | |
e1599b0c XL |
27 | //! rely on file paths for _some_ operations. See the security documentation on |
28 | //! the `NamedTempFile` type for more information. | |
83c7162d | 29 | //! |
487cf647 FG |
30 | //! ## Early drop pitfall |
31 | //! | |
32 | //! Because `TempDir` and `NamedTempFile` rely on their destructors for cleanup, this can lead | |
33 | //! to an unexpected early removal of the directory/file, usually when working with APIs which are | |
34 | //! generic over `AsRef<Path>`. Consider the following example: | |
35 | //! | |
36 | //! ```no_run | |
37 | //! # use tempfile::tempdir; | |
38 | //! # use std::io; | |
39 | //! # use std::process::Command; | |
40 | //! # fn main() { | |
41 | //! # if let Err(_) = run() { | |
42 | //! # ::std::process::exit(1); | |
43 | //! # } | |
44 | //! # } | |
45 | //! # fn run() -> Result<(), io::Error> { | |
46 | //! // Create a directory inside of `std::env::temp_dir()`. | |
47 | //! let temp_dir = tempdir()?; | |
48 | //! | |
49 | //! // Spawn the `touch` command inside the temporary directory and collect the exit status | |
50 | //! // Note that `temp_dir` is **not** moved into `current_dir`, but passed as a reference | |
51 | //! let exit_status = Command::new("touch").arg("tmp").current_dir(&temp_dir).status()?; | |
52 | //! assert!(exit_status.success()); | |
53 | //! | |
54 | //! # Ok(()) | |
55 | //! # } | |
56 | //! ``` | |
57 | //! | |
58 | //! This works because a reference to `temp_dir` is passed to `current_dir`, resulting in the | |
59 | //! destructor of `temp_dir` being run after the `Command` has finished execution. Moving the | |
60 | //! `TempDir` into the `current_dir` call would result in the `TempDir` being converted into | |
61 | //! an internal representation, with the original value being dropped and the directory thus | |
62 | //! being deleted, before the command can be executed. | |
63 | //! | |
64 | //! The `touch` command would fail with an `No such file or directory` error. | |
65 | //! | |
83c7162d XL |
66 | //! ## Examples |
67 | //! | |
68 | //! Create a temporary file and write some data into it: | |
69 | //! | |
70 | //! ``` | |
83c7162d XL |
71 | //! use tempfile::tempfile; |
72 | //! use std::io::{self, Write}; | |
73 | //! | |
74 | //! # fn main() { | |
75 | //! # if let Err(_) = run() { | |
76 | //! # ::std::process::exit(1); | |
77 | //! # } | |
78 | //! # } | |
79 | //! # fn run() -> Result<(), io::Error> { | |
80 | //! // Create a file inside of `std::env::temp_dir()`. | |
81 | //! let mut file = tempfile()?; | |
82 | //! | |
83 | //! writeln!(file, "Brian was here. Briefly.")?; | |
84 | //! # Ok(()) | |
85 | //! # } | |
86 | //! ``` | |
87 | //! | |
e1599b0c XL |
88 | //! Create a named temporary file and open an independent file handle: |
89 | //! | |
90 | //! ``` | |
91 | //! use tempfile::NamedTempFile; | |
92 | //! use std::io::{self, Write, Read}; | |
93 | //! | |
94 | //! # fn main() { | |
95 | //! # if let Err(_) = run() { | |
96 | //! # ::std::process::exit(1); | |
97 | //! # } | |
98 | //! # } | |
99 | //! # fn run() -> Result<(), io::Error> { | |
100 | //! let text = "Brian was here. Briefly."; | |
101 | //! | |
102 | //! // Create a file inside of `std::env::temp_dir()`. | |
103 | //! let mut file1 = NamedTempFile::new()?; | |
104 | //! | |
105 | //! // Re-open it. | |
106 | //! let mut file2 = file1.reopen()?; | |
107 | //! | |
108 | //! // Write some test data to the first handle. | |
109 | //! file1.write_all(text.as_bytes())?; | |
110 | //! | |
111 | //! // Read the test data using the second handle. | |
112 | //! let mut buf = String::new(); | |
113 | //! file2.read_to_string(&mut buf)?; | |
114 | //! assert_eq!(buf, text); | |
115 | //! # Ok(()) | |
116 | //! # } | |
117 | //! ``` | |
118 | //! | |
83c7162d XL |
119 | //! Create a temporary directory and add a file to it: |
120 | //! | |
121 | //! ``` | |
83c7162d XL |
122 | //! use tempfile::tempdir; |
123 | //! use std::fs::File; | |
124 | //! use std::io::{self, Write}; | |
125 | //! | |
126 | //! # fn main() { | |
127 | //! # if let Err(_) = run() { | |
128 | //! # ::std::process::exit(1); | |
129 | //! # } | |
130 | //! # } | |
131 | //! # fn run() -> Result<(), io::Error> { | |
132 | //! // Create a directory inside of `std::env::temp_dir()`. | |
133 | //! let dir = tempdir()?; | |
134 | //! | |
135 | //! let file_path = dir.path().join("my-temporary-note.txt"); | |
136 | //! let mut file = File::create(file_path)?; | |
137 | //! writeln!(file, "Brian was here. Briefly.")?; | |
138 | //! | |
139 | //! // By closing the `TempDir` explicitly, we can check that it has | |
140 | //! // been deleted successfully. If we don't close it explicitly, | |
141 | //! // the directory will still be deleted when `dir` goes out | |
142 | //! // of scope, but we won't know whether deleting the directory | |
143 | //! // succeeded. | |
144 | //! drop(file); | |
145 | //! dir.close()?; | |
146 | //! # Ok(()) | |
147 | //! # } | |
148 | //! ``` | |
149 | //! | |
150 | //! [`tempfile()`]: fn.tempfile.html | |
151 | //! [`tempdir()`]: fn.tempdir.html | |
152 | //! [`TempDir`]: struct.TempDir.html | |
153 | //! [`NamedTempFile`]: struct.NamedTempFile.html | |
154 | //! [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html | |
155 | ||
e1599b0c XL |
156 | #![doc( |
157 | html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", | |
158 | html_favicon_url = "https://www.rust-lang.org/favicon.ico", | |
159 | html_root_url = "https://docs.rs/tempfile/3.1.0" | |
160 | )] | |
83c7162d | 161 | #![cfg_attr(test, deny(warnings))] |
e1599b0c | 162 | #![deny(rust_2018_idioms)] |
cdc7bbd5 | 163 | #![allow(clippy::redundant_field_names)] |
487cf647 | 164 | #![cfg_attr(feature = "nightly", feature(wasi_ext))] |
83c7162d | 165 | |
487cf647 FG |
166 | #[cfg(doctest)] |
167 | doc_comment::doctest!("../README.md"); | |
83c7162d XL |
168 | |
169 | const NUM_RETRIES: u32 = 1 << 31; | |
170 | const NUM_RAND_CHARS: usize = 6; | |
171 | ||
e1599b0c XL |
172 | use std::ffi::OsStr; |
173 | use std::fs::OpenOptions; | |
83c7162d XL |
174 | use std::path::Path; |
175 | use std::{env, io}; | |
176 | ||
177 | mod dir; | |
e1599b0c | 178 | mod error; |
83c7162d | 179 | mod file; |
0731742a | 180 | mod spooled; |
e1599b0c | 181 | mod util; |
83c7162d | 182 | |
e1599b0c | 183 | pub use crate::dir::{tempdir, tempdir_in, TempDir}; |
cdc7bbd5 XL |
184 | pub use crate::file::{ |
185 | tempfile, tempfile_in, NamedTempFile, PathPersistError, PersistError, TempPath, | |
186 | }; | |
e1599b0c | 187 | pub use crate::spooled::{spooled_tempfile, SpooledTempFile}; |
83c7162d XL |
188 | |
189 | /// Create a new temporary file or directory with custom parameters. | |
190 | #[derive(Debug, Clone, Eq, PartialEq)] | |
191 | pub struct Builder<'a, 'b> { | |
192 | random_len: usize, | |
e1599b0c XL |
193 | prefix: &'a OsStr, |
194 | suffix: &'b OsStr, | |
195 | append: bool, | |
83c7162d XL |
196 | } |
197 | ||
198 | impl<'a, 'b> Default for Builder<'a, 'b> { | |
199 | fn default() -> Self { | |
200 | Builder { | |
e1599b0c XL |
201 | random_len: crate::NUM_RAND_CHARS, |
202 | prefix: OsStr::new(".tmp"), | |
203 | suffix: OsStr::new(""), | |
204 | append: false, | |
83c7162d XL |
205 | } |
206 | } | |
207 | } | |
208 | ||
209 | impl<'a, 'b> Builder<'a, 'b> { | |
210 | /// Create a new `Builder`. | |
211 | /// | |
212 | /// # Examples | |
213 | /// | |
214 | /// Create a named temporary file and write some data into it: | |
215 | /// | |
216 | /// ``` | |
83c7162d XL |
217 | /// # use std::io; |
218 | /// # use std::ffi::OsStr; | |
219 | /// # fn main() { | |
220 | /// # if let Err(_) = run() { | |
221 | /// # ::std::process::exit(1); | |
222 | /// # } | |
223 | /// # } | |
224 | /// # fn run() -> Result<(), io::Error> { | |
225 | /// use tempfile::Builder; | |
226 | /// | |
227 | /// let named_tempfile = Builder::new() | |
228 | /// .prefix("my-temporary-note") | |
229 | /// .suffix(".txt") | |
230 | /// .rand_bytes(5) | |
231 | /// .tempfile()?; | |
232 | /// | |
233 | /// let name = named_tempfile | |
234 | /// .path() | |
235 | /// .file_name().and_then(OsStr::to_str); | |
236 | /// | |
237 | /// if let Some(name) = name { | |
238 | /// assert!(name.starts_with("my-temporary-note")); | |
239 | /// assert!(name.ends_with(".txt")); | |
240 | /// assert_eq!(name.len(), "my-temporary-note.txt".len() + 5); | |
241 | /// } | |
242 | /// # Ok(()) | |
243 | /// # } | |
244 | /// ``` | |
245 | /// | |
246 | /// Create a temporary directory and add a file to it: | |
247 | /// | |
248 | /// ``` | |
83c7162d XL |
249 | /// # use std::io::{self, Write}; |
250 | /// # use std::fs::File; | |
251 | /// # use std::ffi::OsStr; | |
252 | /// # fn main() { | |
253 | /// # if let Err(_) = run() { | |
254 | /// # ::std::process::exit(1); | |
255 | /// # } | |
256 | /// # } | |
257 | /// # fn run() -> Result<(), io::Error> { | |
258 | /// use tempfile::Builder; | |
259 | /// | |
260 | /// let dir = Builder::new() | |
261 | /// .prefix("my-temporary-dir") | |
262 | /// .rand_bytes(5) | |
263 | /// .tempdir()?; | |
264 | /// | |
265 | /// let file_path = dir.path().join("my-temporary-note.txt"); | |
266 | /// let mut file = File::create(file_path)?; | |
267 | /// writeln!(file, "Brian was here. Briefly.")?; | |
268 | /// | |
269 | /// // By closing the `TempDir` explicitly, we can check that it has | |
270 | /// // been deleted successfully. If we don't close it explicitly, | |
271 | /// // the directory will still be deleted when `dir` goes out | |
272 | /// // of scope, but we won't know whether deleting the directory | |
273 | /// // succeeded. | |
274 | /// drop(file); | |
275 | /// dir.close()?; | |
276 | /// # Ok(()) | |
277 | /// # } | |
278 | /// ``` | |
279 | pub fn new() -> Self { | |
280 | Self::default() | |
281 | } | |
282 | ||
283 | /// Set a custom filename prefix. | |
284 | /// | |
285 | /// Path separators are legal but not advisable. | |
286 | /// Default: `.tmp`. | |
287 | /// | |
288 | /// # Examples | |
289 | /// | |
290 | /// ``` | |
83c7162d XL |
291 | /// # use std::io; |
292 | /// # fn main() { | |
293 | /// # if let Err(_) = run() { | |
294 | /// # ::std::process::exit(1); | |
295 | /// # } | |
296 | /// # } | |
297 | /// # fn run() -> Result<(), io::Error> { | |
298 | /// # use tempfile::Builder; | |
299 | /// let named_tempfile = Builder::new() | |
300 | /// .prefix("my-temporary-note") | |
301 | /// .tempfile()?; | |
302 | /// # Ok(()) | |
303 | /// # } | |
304 | /// ``` | |
e1599b0c XL |
305 | pub fn prefix<S: AsRef<OsStr> + ?Sized>(&mut self, prefix: &'a S) -> &mut Self { |
306 | self.prefix = prefix.as_ref(); | |
83c7162d XL |
307 | self |
308 | } | |
309 | ||
310 | /// Set a custom filename suffix. | |
311 | /// | |
312 | /// Path separators are legal but not advisable. | |
313 | /// Default: empty. | |
314 | /// | |
315 | /// # Examples | |
316 | /// | |
317 | /// ``` | |
83c7162d XL |
318 | /// # use std::io; |
319 | /// # fn main() { | |
320 | /// # if let Err(_) = run() { | |
321 | /// # ::std::process::exit(1); | |
322 | /// # } | |
323 | /// # } | |
324 | /// # fn run() -> Result<(), io::Error> { | |
325 | /// # use tempfile::Builder; | |
326 | /// let named_tempfile = Builder::new() | |
327 | /// .suffix(".txt") | |
328 | /// .tempfile()?; | |
329 | /// # Ok(()) | |
330 | /// # } | |
331 | /// ``` | |
e1599b0c XL |
332 | pub fn suffix<S: AsRef<OsStr> + ?Sized>(&mut self, suffix: &'b S) -> &mut Self { |
333 | self.suffix = suffix.as_ref(); | |
83c7162d XL |
334 | self |
335 | } | |
336 | ||
337 | /// Set the number of random bytes. | |
338 | /// | |
339 | /// Default: `6`. | |
340 | /// | |
341 | /// # Examples | |
342 | /// | |
343 | /// ``` | |
83c7162d XL |
344 | /// # use std::io; |
345 | /// # fn main() { | |
346 | /// # if let Err(_) = run() { | |
347 | /// # ::std::process::exit(1); | |
348 | /// # } | |
349 | /// # } | |
350 | /// # fn run() -> Result<(), io::Error> { | |
351 | /// # use tempfile::Builder; | |
352 | /// let named_tempfile = Builder::new() | |
353 | /// .rand_bytes(5) | |
354 | /// .tempfile()?; | |
355 | /// # Ok(()) | |
356 | /// # } | |
357 | /// ``` | |
358 | pub fn rand_bytes(&mut self, rand: usize) -> &mut Self { | |
359 | self.random_len = rand; | |
360 | self | |
361 | } | |
362 | ||
e1599b0c XL |
363 | /// Set the file to be opened in append mode. |
364 | /// | |
365 | /// Default: `false`. | |
366 | /// | |
367 | /// # Examples | |
368 | /// | |
369 | /// ``` | |
370 | /// # use std::io; | |
371 | /// # fn main() { | |
372 | /// # if let Err(_) = run() { | |
373 | /// # ::std::process::exit(1); | |
374 | /// # } | |
375 | /// # } | |
376 | /// # fn run() -> Result<(), io::Error> { | |
377 | /// # use tempfile::Builder; | |
378 | /// let named_tempfile = Builder::new() | |
379 | /// .append(true) | |
380 | /// .tempfile()?; | |
381 | /// # Ok(()) | |
382 | /// # } | |
383 | /// ``` | |
384 | pub fn append(&mut self, append: bool) -> &mut Self { | |
385 | self.append = append; | |
386 | self | |
387 | } | |
388 | ||
83c7162d XL |
389 | /// Create the named temporary file. |
390 | /// | |
391 | /// # Security | |
392 | /// | |
393 | /// See [the security][security] docs on `NamedTempFile`. | |
394 | /// | |
395 | /// # Resource leaking | |
396 | /// | |
397 | /// See [the resource leaking][resource-leaking] docs on `NamedTempFile`. | |
398 | /// | |
399 | /// # Errors | |
400 | /// | |
401 | /// If the file cannot be created, `Err` is returned. | |
402 | /// | |
403 | /// # Examples | |
404 | /// | |
405 | /// ``` | |
83c7162d XL |
406 | /// # use std::io; |
407 | /// # fn main() { | |
408 | /// # if let Err(_) = run() { | |
409 | /// # ::std::process::exit(1); | |
410 | /// # } | |
411 | /// # } | |
412 | /// # fn run() -> Result<(), io::Error> { | |
413 | /// # use tempfile::Builder; | |
414 | /// let tempfile = Builder::new().tempfile()?; | |
415 | /// # Ok(()) | |
416 | /// # } | |
417 | /// ``` | |
418 | /// | |
419 | /// [security]: struct.NamedTempFile.html#security | |
420 | /// [resource-leaking]: struct.NamedTempFile.html#resource-leaking | |
421 | pub fn tempfile(&self) -> io::Result<NamedTempFile> { | |
422 | self.tempfile_in(&env::temp_dir()) | |
423 | } | |
424 | ||
425 | /// Create the named temporary file in the specified directory. | |
426 | /// | |
427 | /// # Security | |
428 | /// | |
429 | /// See [the security][security] docs on `NamedTempFile`. | |
430 | /// | |
431 | /// # Resource leaking | |
432 | /// | |
433 | /// See [the resource leaking][resource-leaking] docs on `NamedTempFile`. | |
434 | /// | |
435 | /// # Errors | |
436 | /// | |
437 | /// If the file cannot be created, `Err` is returned. | |
438 | /// | |
439 | /// # Examples | |
440 | /// | |
441 | /// ``` | |
83c7162d XL |
442 | /// # use std::io; |
443 | /// # fn main() { | |
444 | /// # if let Err(_) = run() { | |
445 | /// # ::std::process::exit(1); | |
446 | /// # } | |
447 | /// # } | |
448 | /// # fn run() -> Result<(), io::Error> { | |
449 | /// # use tempfile::Builder; | |
450 | /// let tempfile = Builder::new().tempfile_in("./")?; | |
451 | /// # Ok(()) | |
452 | /// # } | |
453 | /// ``` | |
454 | /// | |
455 | /// [security]: struct.NamedTempFile.html#security | |
456 | /// [resource-leaking]: struct.NamedTempFile.html#resource-leaking | |
457 | pub fn tempfile_in<P: AsRef<Path>>(&self, dir: P) -> io::Result<NamedTempFile> { | |
458 | util::create_helper( | |
459 | dir.as_ref(), | |
460 | self.prefix, | |
461 | self.suffix, | |
462 | self.random_len, | |
e1599b0c | 463 | |path| file::create_named(path, OpenOptions::new().append(self.append)), |
83c7162d XL |
464 | ) |
465 | } | |
466 | ||
467 | /// Attempts to make a temporary directory inside of `env::temp_dir()` whose | |
468 | /// name will have the prefix, `prefix`. The directory and | |
469 | /// everything inside it will be automatically deleted once the | |
470 | /// returned `TempDir` is destroyed. | |
471 | /// | |
472 | /// # Resource leaking | |
473 | /// | |
474 | /// See [the resource leaking][resource-leaking] docs on `TempDir`. | |
475 | /// | |
476 | /// # Errors | |
477 | /// | |
478 | /// If the directory can not be created, `Err` is returned. | |
479 | /// | |
480 | /// # Examples | |
481 | /// | |
482 | /// ``` | |
483 | /// use std::fs::File; | |
484 | /// use std::io::Write; | |
485 | /// use tempfile::Builder; | |
486 | /// | |
487 | /// # use std::io; | |
488 | /// # fn run() -> Result<(), io::Error> { | |
489 | /// let tmp_dir = Builder::new().tempdir()?; | |
490 | /// # Ok(()) | |
491 | /// # } | |
492 | /// ``` | |
493 | /// | |
494 | /// [resource-leaking]: struct.TempDir.html#resource-leaking | |
495 | pub fn tempdir(&self) -> io::Result<TempDir> { | |
496 | self.tempdir_in(&env::temp_dir()) | |
497 | } | |
498 | ||
499 | /// Attempts to make a temporary directory inside of `dir`. | |
500 | /// The directory and everything inside it will be automatically | |
501 | /// deleted once the returned `TempDir` is destroyed. | |
502 | /// | |
503 | /// # Resource leaking | |
504 | /// | |
505 | /// See [the resource leaking][resource-leaking] docs on `TempDir`. | |
506 | /// | |
507 | /// # Errors | |
508 | /// | |
509 | /// If the directory can not be created, `Err` is returned. | |
510 | /// | |
511 | /// # Examples | |
512 | /// | |
513 | /// ``` | |
514 | /// use std::fs::{self, File}; | |
515 | /// use std::io::Write; | |
516 | /// use tempfile::Builder; | |
517 | /// | |
518 | /// # use std::io; | |
519 | /// # fn run() -> Result<(), io::Error> { | |
520 | /// let tmp_dir = Builder::new().tempdir_in("./")?; | |
521 | /// # Ok(()) | |
522 | /// # } | |
523 | /// ``` | |
524 | /// | |
525 | /// [resource-leaking]: struct.TempDir.html#resource-leaking | |
526 | pub fn tempdir_in<P: AsRef<Path>>(&self, dir: P) -> io::Result<TempDir> { | |
527 | let storage; | |
528 | let mut dir = dir.as_ref(); | |
529 | if !dir.is_absolute() { | |
530 | let cur_dir = env::current_dir()?; | |
531 | storage = cur_dir.join(dir); | |
532 | dir = &storage; | |
533 | } | |
534 | ||
535 | util::create_helper(dir, self.prefix, self.suffix, self.random_len, dir::create) | |
536 | } | |
537 | } |