mktemp/
lib.rs

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//!
22extern crate uuid;
23
24use std::env;
25use std::fs;
26use std::io;
27use std::ops;
28#[cfg(unix)]
29use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt};
30use std::path::{Path, PathBuf};
31use uuid::Uuid;
32
33#[derive(Debug)]
34pub struct Temp {
35    path: PathBuf,
36}
37
38fn create_path() -> PathBuf {
39    create_path_in(env::temp_dir())
40}
41
42fn create_path_in(path: PathBuf) -> PathBuf {
43    let mut path = path;
44    let dir_uuid = Uuid::new_v4();
45
46    path.push(dir_uuid.simple().to_string());
47    path
48}
49
50impl Temp {
51    /// Create a temporary directory.
52    pub fn new_dir() -> io::Result<Self> {
53        let path = create_path();
54        Self::create_dir(&path)?;
55
56        let temp = Temp { path };
57
58        Ok(temp)
59    }
60
61    /// Create a new temporary directory in an existing directory
62    pub fn new_dir_in<P: AsRef<Path>>(directory: P) -> io::Result<Self> {
63        let path = create_path_in(directory.as_ref().to_path_buf());
64        Self::create_dir(&path)?;
65
66        let temp = Temp { path };
67
68        Ok(temp)
69    }
70
71    /// Create a new temporary file in an existing directory
72    pub fn new_file_in<P: AsRef<Path>>(directory: P) -> io::Result<Self> {
73        let path = create_path_in(directory.as_ref().to_path_buf());
74        Self::create_file(&path)?;
75
76        let temp = Temp { path };
77
78        Ok(temp)
79    }
80
81    /// Create a temporary file.
82    pub fn new_file() -> io::Result<Self> {
83        let path = create_path();
84        Self::create_file(&path)?;
85
86        let temp = Temp { path };
87
88        Ok(temp)
89    }
90
91    /// Create new uninitialized temporary path, i.e. a file or directory isn't created automatically
92    pub fn new_path() -> Self {
93        let path = create_path();
94
95        Temp { path }
96    }
97
98    /// Create a new uninitialized temporary path in an existing directory i.e. a file or directory
99    /// isn't created automatically
100    pub fn new_path_in<P: AsRef<Path>>(directory: P) -> Self {
101        let path = create_path_in(directory.as_ref().to_path_buf());
102
103        Temp { path }
104    }
105
106    /// Return this temporary file or directory as a PathBuf.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use mktemp::Temp;
112    ///
113    /// let temp_dir = Temp::new_dir().unwrap();
114    /// let mut path_buf = temp_dir.to_path_buf();
115    /// ```
116    pub fn to_path_buf(&self) -> PathBuf {
117        PathBuf::from(&self.path)
118    }
119
120    /// Release ownership of the temporary file or directory.
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// use mktemp::Temp;
126    /// let path_buf;
127    /// {
128    ///   let mut temp_dir = Temp::new_dir().unwrap();
129    ///   path_buf = temp_dir.to_path_buf();
130    ///   temp_dir.release();
131    /// }
132    /// assert!(path_buf.exists());
133    /// ```
134    pub fn release(self) -> PathBuf {
135        use std::mem::{forget, transmute_copy};
136
137        let path = unsafe { transmute_copy(&self.path) };
138        forget(self);
139        path
140    }
141
142    fn create_file(path: &Path) -> io::Result<()> {
143        let mut builder = fs::OpenOptions::new();
144        builder.write(true).create_new(true);
145
146        #[cfg(unix)]
147        builder.mode(0o600);
148
149        builder.open(path)?;
150        Ok(())
151    }
152
153    fn create_dir(path: &Path) -> io::Result<()> {
154        let mut builder = fs::DirBuilder::new();
155
156        #[cfg(unix)]
157        builder.mode(0o700);
158
159        builder.create(path)
160    }
161}
162
163impl AsRef<Path> for Temp {
164    fn as_ref(&self) -> &Path {
165        self.path.as_path()
166    }
167}
168
169impl ops::Deref for Temp {
170    type Target = PathBuf;
171    fn deref(&self) -> &Self::Target {
172        &self.path
173    }
174}
175
176impl ops::DerefMut for Temp {
177    fn deref_mut(&mut self) -> &mut Self::Target {
178        &mut self.path
179    }
180}
181
182impl Drop for Temp {
183    fn drop(&mut self) {
184        // Drop is blocking (make non-blocking?)
185        if !self.path.exists() {
186            return;
187        }
188
189        let _result = if self.path.is_dir() {
190            fs::remove_dir_all(&self)
191        } else {
192            fs::remove_file(&self)
193        };
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use std::fs::File;
201    #[cfg(unix)]
202    use std::os::unix::fs::MetadataExt;
203
204    #[test]
205    fn it_should_create_file_in_dir() {
206        let in_dir;
207        {
208            let temp_dir = Temp::new_dir().unwrap();
209
210            in_dir = temp_dir.path.clone();
211
212            {
213                let temp_file = Temp::new_file_in(in_dir).unwrap();
214                assert!(fs::metadata(temp_file).unwrap().is_file());
215            }
216        }
217    }
218
219    #[test]
220    fn it_should_drop_file_out_of_scope() {
221        let path;
222        {
223            let temp_file = Temp::new_file().unwrap();
224
225            path = temp_file.path.clone();
226            assert!(fs::metadata(temp_file).unwrap().is_file());
227        }
228
229        if let Err(e) = fs::metadata(path) {
230            assert_eq!(e.kind(), io::ErrorKind::NotFound);
231        } else {
232            panic!("File was not removed");
233        }
234    }
235
236    #[test]
237    fn it_should_drop_dir_out_of_scope() {
238        let path;
239        {
240            let temp_file = Temp::new_dir().unwrap();
241
242            path = temp_file.path.clone();
243            assert!(fs::metadata(temp_file).unwrap().is_dir());
244        }
245
246        if let Err(e) = fs::metadata(path) {
247            assert_eq!(e.kind(), io::ErrorKind::NotFound);
248        } else {
249            panic!("File was not removed");
250        }
251    }
252
253    #[test]
254    fn it_should_not_drop_released_file() {
255        let path_buf;
256        {
257            let temp_file = Temp::new_file().unwrap();
258            path_buf = temp_file.release();
259        }
260        assert!(path_buf.exists());
261        fs::remove_file(path_buf).unwrap();
262    }
263
264    #[test]
265    fn it_should_not_drop_released_dir() {
266        let path_buf;
267        {
268            let temp_dir = Temp::new_dir().unwrap();
269            path_buf = temp_dir.release();
270        }
271        assert!(path_buf.exists());
272        fs::remove_dir_all(path_buf).unwrap();
273    }
274
275    #[test]
276    #[cfg(unix)]
277    fn temp_file_only_readable_by_owner() {
278        let temp_file = Temp::new_file().unwrap();
279        let mode = fs::metadata(temp_file.as_ref()).unwrap().mode();
280        assert_eq!(0o600, mode & 0o777);
281    }
282
283    #[test]
284    #[cfg(unix)]
285    fn temp_dir_only_readable_by_owner() {
286        let dir = Temp::new_dir().unwrap();
287        let mode = fs::metadata(dir).unwrap().mode();
288        assert_eq!(0o700, mode & 0o777)
289    }
290
291    #[test]
292    fn target_dir_must_exist() {
293        let temp_dir = Temp::new_dir().unwrap();
294        let mut no_such_dir = temp_dir.as_ref().to_owned();
295        no_such_dir.push("no_such_dir");
296
297        match Temp::new_file_in(&no_such_dir) {
298            Err(ref e) if e.kind() == io::ErrorKind::NotFound => (),
299            _ => panic!(),
300        }
301
302        match Temp::new_dir_in(&no_such_dir) {
303            Err(ref e) if e.kind() == io::ErrorKind::NotFound => (),
304            _ => panic!(),
305        }
306    }
307
308    #[test]
309    fn uninitialized_file() {
310        let temp = Temp::new_path();
311        assert!(!temp.exists());
312        let _file = File::create(&temp);
313        assert!(temp.exists());
314    }
315
316    #[test]
317    fn uninitialized_no_panic_on_drop_with_release() {
318        let t = Temp::new_path();
319        t.release();
320    }
321
322    #[test]
323    #[cfg(unix)]
324    fn unix_socket() {
325        let t = Temp::new_path();
326        println!("Path is {:?}", t.to_str());
327        let socket = std::os::unix::net::UnixListener::bind(t.to_str().unwrap());
328        drop(socket);
329        drop(t);
330    }
331}