]>
Commit | Line | Data |
---|---|---|
c34b1796 AL |
1 | // Copyright 2015 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | use std::env; | |
12 | use std::io::{self, Error, ErrorKind}; | |
13 | use std::fs; | |
14 | use std::path::{self, PathBuf, Path}; | |
9346a6ac | 15 | use std::__rand::{thread_rng, Rng}; |
c34b1796 AL |
16 | |
17 | /// A wrapper for a path to temporary directory implementing automatic | |
18 | /// scope-based deletion. | |
19 | pub struct TempDir { | |
20 | path: Option<PathBuf>, | |
21 | } | |
22 | ||
23 | // How many times should we (re)try finding an unused random name? It should be | |
24 | // enough that an attacker will run out of luck before we run out of patience. | |
25 | const NUM_RETRIES: u32 = 1 << 31; | |
26 | // How many characters should we include in a random file name? It needs to | |
27 | // be enough to dissuade an attacker from trying to preemptively create names | |
28 | // of that length, but not so huge that we unnecessarily drain the random number | |
29 | // generator of entropy. | |
30 | const NUM_RAND_CHARS: usize = 12; | |
31 | ||
32 | impl TempDir { | |
33 | /// Attempts to make a temporary directory inside of `tmpdir` whose name | |
34 | /// will have the prefix `prefix`. The directory will be automatically | |
35 | /// deleted once the returned wrapper is destroyed. | |
36 | /// | |
37 | /// If no directory can be created, `Err` is returned. | |
38 | #[allow(deprecated)] // rand usage | |
39 | pub fn new_in<P: AsRef<Path>>(tmpdir: P, prefix: &str) | |
40 | -> io::Result<TempDir> { | |
e9174d1e SL |
41 | Self::_new_in(tmpdir.as_ref(), prefix) |
42 | } | |
43 | ||
44 | fn _new_in(tmpdir: &Path, prefix: &str) -> io::Result<TempDir> { | |
c34b1796 | 45 | let storage; |
e9174d1e | 46 | let mut tmpdir = tmpdir; |
c34b1796 | 47 | if !tmpdir.is_absolute() { |
54a0048b | 48 | let cur_dir = env::current_dir()?; |
c34b1796 AL |
49 | storage = cur_dir.join(tmpdir); |
50 | tmpdir = &storage; | |
51 | // return TempDir::new_in(&cur_dir.join(tmpdir), prefix); | |
52 | } | |
53 | ||
54 | let mut rng = thread_rng(); | |
55 | for _ in 0..NUM_RETRIES { | |
56 | let suffix: String = rng.gen_ascii_chars().take(NUM_RAND_CHARS).collect(); | |
9346a6ac | 57 | let leaf = if !prefix.is_empty() { |
c34b1796 AL |
58 | format!("{}.{}", prefix, suffix) |
59 | } else { | |
60 | // If we're given an empty string for a prefix, then creating a | |
61 | // directory starting with "." would lead to it being | |
62 | // semi-invisible on some systems. | |
63 | suffix | |
64 | }; | |
65 | let path = tmpdir.join(&leaf); | |
66 | match fs::create_dir(&path) { | |
67 | Ok(_) => return Ok(TempDir { path: Some(path) }), | |
68 | Err(ref e) if e.kind() == ErrorKind::AlreadyExists => {} | |
69 | Err(e) => return Err(e) | |
70 | } | |
71 | } | |
72 | ||
73 | Err(Error::new(ErrorKind::AlreadyExists, | |
74 | "too many temporary directories already exist")) | |
75 | } | |
76 | ||
77 | /// Attempts to make a temporary directory inside of `env::temp_dir()` whose | |
78 | /// name will have the prefix `prefix`. The directory will be automatically | |
79 | /// deleted once the returned wrapper is destroyed. | |
80 | /// | |
81 | /// If no directory can be created, `Err` is returned. | |
c34b1796 AL |
82 | pub fn new(prefix: &str) -> io::Result<TempDir> { |
83 | TempDir::new_in(&env::temp_dir(), prefix) | |
84 | } | |
85 | ||
86 | /// Unwrap the wrapped `std::path::Path` from the `TempDir` wrapper. | |
87 | /// This discards the wrapper so that the automatic deletion of the | |
88 | /// temporary directory is prevented. | |
89 | pub fn into_path(mut self) -> PathBuf { | |
90 | self.path.take().unwrap() | |
91 | } | |
92 | ||
93 | /// Access the wrapped `std::path::Path` to the temporary directory. | |
94 | pub fn path(&self) -> &path::Path { | |
95 | self.path.as_ref().unwrap() | |
96 | } | |
97 | ||
98 | /// Close and remove the temporary directory | |
99 | /// | |
100 | /// Although `TempDir` removes the directory on drop, in the destructor | |
101 | /// any errors are ignored. To detect errors cleaning up the temporary | |
102 | /// directory, call `close` instead. | |
103 | pub fn close(mut self) -> io::Result<()> { | |
104 | self.cleanup_dir() | |
105 | } | |
106 | ||
107 | fn cleanup_dir(&mut self) -> io::Result<()> { | |
108 | match self.path { | |
109 | Some(ref p) => fs::remove_dir_all(p), | |
110 | None => Ok(()) | |
111 | } | |
112 | } | |
113 | } | |
114 | ||
115 | impl Drop for TempDir { | |
116 | fn drop(&mut self) { | |
117 | let _ = self.cleanup_dir(); | |
118 | } | |
119 | } | |
120 | ||
121 | // the tests for this module need to change the path using change_dir, | |
122 | // and this doesn't play nicely with other tests so these unit tests are located | |
123 | // in src/test/run-pass/tempfile.rs |