1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 //! This module provides a simple way of creating temporary files and
5 //! directories where their lifetime is defined by the scope they exist in.
6 //!
7 //! Once the variable goes out of scope, the underlying file system resource is removed.
8 //!
9 //! # Examples
10 //!
11 //! ```
12 //! use mktemp::Temp;
13 //! use std::fs;
14 //!
15 //! {
16 //! let temp_file = Temp::new_file().unwrap();
17 //! assert!(fs::File::open(temp_file).is_ok());
18 //! }
19 //! // temp_file is cleaned from the fs here
20 //! ```
21 //!
22 extern crate uuid;
23
24 use std::env;
25 use std::fs;
26 use std::io;
27 #[cfg(unix)]
28 use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt};
29 use std::path::{Path, PathBuf};
30 use std::ops;
31 use uuid::Uuid;
32
33 #[derive(Clone)]
34 pub struct Temp {
35 path: PathBuf,
36 }
37
create_path() -> PathBuf38 fn create_path() -> PathBuf {
39 create_path_in(env::temp_dir())
40 }
41
create_path_in(path: PathBuf) -> PathBuf42 fn create_path_in(path: PathBuf) -> PathBuf {
43 let mut path = path;
44 let dir_uuid = Uuid::new_v4();
45
46 path.push(dir_uuid.to_simple().to_string());
47 path
48 }
49
50 impl Temp {
51 /// Create a temporary directory.
new_dir() -> io::Result<Self>52 pub fn new_dir() -> io::Result<Self> {
53 let path = create_path();
54 Self::create_dir(&path)?;
55
56 let temp = Temp {
57 path: path,
58 };
59
60 Ok(temp)
61 }
62
63 /// Create a new temporary directory in an existing directory
new_dir_in<P: AsRef<Path>>(directory: P) -> io::Result<Self>64 pub fn new_dir_in<P: AsRef<Path>>(directory: P) -> io::Result<Self> {
65 let path = create_path_in(directory.as_ref().to_path_buf());
66 Self::create_dir(&path)?;
67
68 let temp = Temp {
69 path: path,
70 };
71
72 Ok(temp)
73 }
74
75 /// Create a new temporary file in an existing directory
new_file_in<P: AsRef<Path>>(directory: P) -> io::Result<Self>76 pub fn new_file_in<P: AsRef<Path>>(directory: P) -> io::Result<Self> {
77 let path = create_path_in(directory.as_ref().to_path_buf());
78 Self::create_file(&path)?;
79
80 let temp = Temp {
81 path: path,
82 };
83
84 Ok(temp)
85 }
86
87 /// Create a temporary file.
new_file() -> io::Result<Self>88 pub fn new_file() -> io::Result<Self> {
89 let path = create_path();
90 Self::create_file(&path)?;
91
92 let temp = Temp {
93 path: path,
94 };
95
96 Ok(temp)
97 }
98
99 /// Create new uninitialized temporary path, i.e. a file or directory isn't created automatically
new_path() -> Self100 pub fn new_path() -> Self {
101 let path = create_path();
102
103 Temp {
104 path,
105 }
106 }
107
108 /// Create a new uninitialized temporary path in an existing directory i.e. a file or directory
109 /// isn't created automatically
new_path_in<P: AsRef<Path>>(directory: P) -> Self110 pub fn new_path_in<P: AsRef<Path>>(directory: P) -> Self {
111 let path = create_path_in(directory.as_ref().to_path_buf());
112
113 Temp {
114 path
115 }
116 }
117
118 /// Return this temporary file or directory as a PathBuf.
119 ///
120 /// # Examples
121 ///
122 /// ```
123 /// use mktemp::Temp;
124 ///
125 /// let temp_dir = Temp::new_dir().unwrap();
126 /// let mut path_buf = temp_dir.to_path_buf();
127 /// ```
to_path_buf(&self) -> PathBuf128 pub fn to_path_buf(&self) -> PathBuf {
129 PathBuf::from(&self.path)
130 }
131
132 /// Release ownership of the temporary file or directory.
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// use mktemp::Temp;
138 /// let path_buf;
139 /// {
140 /// let mut temp_dir = Temp::new_dir().unwrap();
141 /// path_buf = temp_dir.to_path_buf();
142 /// temp_dir.release();
143 /// }
144 /// assert!(path_buf.exists());
145 /// ```
release(self) -> PathBuf146 pub fn release(self) -> PathBuf {
147 use std::mem::{forget, transmute_copy};
148
149 let path = unsafe { transmute_copy(&self.path) };
150 forget(self);
151 path
152 }
153
create_file(path: &Path) -> io::Result<()>154 fn create_file(path: &Path) -> io::Result<()> {
155 let mut builder = fs::OpenOptions::new();
156 builder.write(true)
157 .create_new(true);
158
159 #[cfg(unix)]
160 builder.mode(0o600);
161
162 builder.open(path)?;
163 Ok(())
164 }
165
create_dir(path: &Path) -> io::Result<()>166 fn create_dir(path: &Path) -> io::Result<()> {
167 let mut builder = fs::DirBuilder::new();
168
169 #[cfg(unix)]
170 builder.mode(0o700);
171
172 builder.create(path)
173 }
174 }
175
176 impl AsRef<Path> for Temp {
as_ref(&self) -> &Path177 fn as_ref(&self) -> &Path {
178 &self.path.as_path()
179 }
180 }
181
182 impl ops::Deref for Temp {
183 type Target = PathBuf;
deref(&self) -> &Self::Target184 fn deref(&self) -> &Self::Target {
185 &self.path
186 }
187 }
188
189 impl ops::DerefMut for Temp {
deref_mut(&mut self) -> &mut Self::Target190 fn deref_mut(&mut self) -> &mut Self::Target {
191 &mut self.path
192 }
193 }
194
195 impl Drop for Temp {
drop(&mut self)196 fn drop(&mut self) {
197 // Drop is blocking (make non-blocking?)
198 if let Err(e) = if self.path.is_file() {
199 fs::remove_file(&self)
200 } else {
201 fs::remove_dir_all(&self)
202 } {
203 if ::std::thread::panicking() {
204 eprintln!("Could not remove path {:?}: {}", self.path, e);
205 } else {
206 panic!("Could not remove path {:?}: {}", self.path, e);
207 }
208 }
209 }
210 }
211
212 #[cfg(test)]
213 mod tests {
214 use super::*;
215 #[cfg(unix)]
216 use std::os::unix::fs::MetadataExt;
217 use std::fs::File;
218
219 #[test]
it_should_create_file_in_dir()220 fn it_should_create_file_in_dir() {
221 let in_dir;
222 {
223 let temp_dir = Temp::new_dir().unwrap();
224
225 in_dir = temp_dir.path.clone();
226
227 {
228 let temp_file = Temp::new_file_in(in_dir).unwrap();
229 assert!(fs::metadata(temp_file).unwrap().is_file());
230 }
231 }
232 }
233
234 #[test]
it_should_drop_file_out_of_scope()235 fn it_should_drop_file_out_of_scope() {
236 let path;
237 {
238 let temp_file = Temp::new_file().unwrap();
239
240 path = temp_file.path.clone();
241 assert!(fs::metadata(temp_file).unwrap().is_file());
242 }
243
244 if let Err(e) = fs::metadata(path) {
245 assert_eq!(e.kind(), io::ErrorKind::NotFound);
246 } else {
247 panic!("File was not removed");
248 }
249 }
250
251 #[test]
it_should_drop_dir_out_of_scope()252 fn it_should_drop_dir_out_of_scope() {
253 let path;
254 {
255 let temp_file = Temp::new_dir().unwrap();
256
257 path = temp_file.path.clone();
258 assert!(fs::metadata(temp_file).unwrap().is_dir());
259 }
260
261 if let Err(e) = fs::metadata(path) {
262 assert_eq!(e.kind(), io::ErrorKind::NotFound);
263 } else {
264 panic!("File was not removed");
265 }
266 }
267
268 #[test]
it_should_not_drop_released_file()269 fn it_should_not_drop_released_file() {
270 let path_buf;
271 {
272 let temp_file = Temp::new_file().unwrap();
273 path_buf = temp_file.release();
274 }
275 assert!(path_buf.exists());
276 fs::remove_file(path_buf).unwrap();
277 }
278
279 #[test]
it_should_not_drop_released_dir()280 fn it_should_not_drop_released_dir() {
281 let path_buf;
282 {
283 let temp_dir = Temp::new_dir().unwrap();
284 path_buf = temp_dir.release();
285 }
286 assert!(path_buf.exists());
287 fs::remove_dir_all(path_buf).unwrap();
288 }
289
290 #[test]
291 #[cfg(unix)]
temp_file_only_readable_by_owner()292 fn temp_file_only_readable_by_owner() {
293 let temp_file = Temp::new_file().unwrap();
294 let mode = fs::metadata(temp_file.as_ref()).unwrap().mode();
295 assert_eq!(0o600, mode & 0o777);
296 }
297
298 #[test]
299 #[cfg(unix)]
temp_dir_only_readable_by_owner()300 fn temp_dir_only_readable_by_owner() {
301 let dir = Temp::new_dir().unwrap();
302 let mode = fs::metadata(dir).unwrap().mode();
303 assert_eq!(0o700, mode & 0o777)
304 }
305
306 #[test]
target_dir_must_exist()307 fn target_dir_must_exist() {
308 let temp_dir = Temp::new_dir().unwrap();
309 let mut no_such_dir = temp_dir.as_ref().to_owned();
310 no_such_dir.push("no_such_dir");
311
312 match Temp::new_file_in(&no_such_dir) {
313 Err(ref e) if e.kind() == io::ErrorKind::NotFound => (),
314 _ => panic!(),
315 }
316
317 match Temp::new_dir_in(&no_such_dir) {
318 Err(ref e) if e.kind() == io::ErrorKind::NotFound => (),
319 _ => panic!(),
320 }
321 }
322
323 #[test]
uninitialized_panic_on_drop()324 fn uninitialized_panic_on_drop() {
325 use std::panic::catch_unwind;
326
327 assert!(catch_unwind(|| {
328 let _ = Temp::new_path();
329 }).is_err());
330 }
331
332 #[test]
uninitialized_file()333 fn uninitialized_file() {
334 let temp = Temp::new_path();
335 assert!(!temp.exists());
336 let _file = File::create(&temp);
337 assert!(temp.exists());
338 }
339
340 #[test]
uninitialized_no_panic_on_drop_with_release()341 fn uninitialized_no_panic_on_drop_with_release() {
342 let t = Temp::new_path();
343 t.release();
344 }
345 }
346