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