1 // Copyright 2019 Intel Corporation. All Rights Reserved.
2 //
3 // Copyright 2017 The Chromium OS Authors. All rights reserved.
4 //
5 // SPDX-License-Identifier: BSD-3-Clause
6 
7 //! Structure for handling temporary directories.
8 use std::env::temp_dir;
9 use std::ffi::{CString, OsStr, OsString};
10 use std::fs;
11 use std::os::unix::ffi::OsStringExt;
12 use std::path::{Path, PathBuf};
13 
14 use crate::errno::{errno_result, Error, Result};
15 
16 /// Wrapper over a temporary directory.
17 ///
18 /// The directory will be maintained for the lifetime of the `TempDir` object.
19 #[derive(Debug)]
20 pub struct TempDir {
21     path: PathBuf,
22 }
23 
24 impl TempDir {
25     /// Creates a new temporary directory with `prefix`.
26     ///
27     /// The directory will be removed when the object goes out of scope.
28     ///
29     /// # Examples
30     ///
31     /// ```
32     /// # use vmm_sys_util::tempdir::TempDir;
33     /// let t = TempDir::new_with_prefix("/tmp/testdir").unwrap();
34     /// ```
new_with_prefix<P: AsRef<OsStr>>(prefix: P) -> Result<TempDir>35     pub fn new_with_prefix<P: AsRef<OsStr>>(prefix: P) -> Result<TempDir> {
36         let mut dir_string = prefix.as_ref().to_os_string();
37         dir_string.push("XXXXXX");
38         // unwrap this result as the internal bytes can't have a null with a valid path.
39         let dir_name = CString::new(dir_string.into_vec()).unwrap();
40         let mut dir_bytes = dir_name.into_bytes_with_nul();
41         // SAFETY: Creating the directory isn't unsafe.  The fact that it modifies the guts of the
42         // path is also OK because it only overwrites the last 6 Xs added above.
43         let ret = unsafe { libc::mkdtemp(dir_bytes.as_mut_ptr() as *mut libc::c_char) };
44         if ret.is_null() {
45             return errno_result();
46         }
47         dir_bytes.pop(); // Remove the null becasue from_vec can't handle it.
48         Ok(TempDir {
49             path: PathBuf::from(OsString::from_vec(dir_bytes)),
50         })
51     }
52 
53     /// Creates a new temporary directory with inside `path`.
54     ///
55     /// The directory will be removed when the object goes out of scope.
56     ///
57     /// # Examples
58     ///
59     /// ```
60     /// # use std::path::Path;
61     /// # use vmm_sys_util::tempdir::TempDir;
62     /// let t = TempDir::new_in(Path::new("/tmp/")).unwrap();
63     /// ```
new_in(path: &Path) -> Result<TempDir>64     pub fn new_in(path: &Path) -> Result<TempDir> {
65         let mut path_buf = path.canonicalize().unwrap();
66         // This `push` adds a trailing slash ("/whatever/path" -> "/whatever/path/").
67         // This is safe for paths with already trailing slash.
68         path_buf.push("");
69         let temp_dir = TempDir::new_with_prefix(path_buf)?;
70         Ok(temp_dir)
71     }
72 
73     /// Creates a new temporary directory with inside `$TMPDIR` if set, otherwise in `/tmp`.
74     ///
75     /// The directory will be removed when the object goes out of scope.
76     ///
77     /// # Examples
78     ///
79     /// ```
80     /// # use vmm_sys_util::tempdir::TempDir;
81     /// let t = TempDir::new().unwrap();
82     /// ```
new() -> Result<TempDir>83     pub fn new() -> Result<TempDir> {
84         let mut in_tmp_dir = temp_dir();
85         // This `push` adds a trailing slash ("/tmp" -> "/tmp/").
86         // This is safe for paths with already trailing slash.
87         in_tmp_dir.push("");
88         let temp_dir = TempDir::new_in(in_tmp_dir.as_path())?;
89         Ok(temp_dir)
90     }
91 
92     /// Removes the temporary directory.
93     ///
94     /// Calling this is optional as when a `TempDir` object goes out of scope,
95     /// the directory will be removed.
96     /// Calling remove explicitly allows for better error handling.
97     ///
98     /// # Errors
99     ///
100     /// This function can only be called once per object. An error is returned
101     /// otherwise.
102     ///
103     /// # Examples
104     ///
105     /// ```
106     /// # use std::path::Path;
107     /// # use std::path::PathBuf;
108     /// # use vmm_sys_util::tempdir::TempDir;
109     /// let temp_dir = TempDir::new_with_prefix("/tmp/testdir").unwrap();
110     /// temp_dir.remove().unwrap();
remove(&self) -> Result<()>111     pub fn remove(&self) -> Result<()> {
112         fs::remove_dir_all(&self.path).map_err(Error::from)
113     }
114 
115     /// Returns the path to the tempdir.
116     ///
117     /// # Examples
118     ///
119     /// ```
120     /// # use std::path::Path;
121     /// # use std::path::PathBuf;
122     /// # use vmm_sys_util::tempdir::TempDir;
123     /// let temp_dir = TempDir::new_with_prefix("/tmp/testdir").unwrap();
124     /// assert!(temp_dir.as_path().exists());
as_path(&self) -> &Path125     pub fn as_path(&self) -> &Path {
126         self.path.as_ref()
127     }
128 }
129 
130 impl Drop for TempDir {
drop(&mut self)131     fn drop(&mut self) {
132         let _ = self.remove();
133     }
134 }
135 
136 #[cfg(test)]
137 mod tests {
138     use super::*;
139 
140     #[test]
test_create_dir()141     fn test_create_dir() {
142         let t = TempDir::new().unwrap();
143         let path = t.as_path();
144         assert!(path.exists());
145         assert!(path.is_dir());
146         assert!(path.starts_with(temp_dir()));
147     }
148 
149     #[test]
test_create_dir_with_prefix()150     fn test_create_dir_with_prefix() {
151         let t = TempDir::new_with_prefix("/tmp/testdir").unwrap();
152         let path = t.as_path();
153         assert!(path.exists());
154         assert!(path.is_dir());
155         assert!(path.to_str().unwrap().contains("/tmp/testdir"));
156     }
157 
158     #[test]
test_remove_dir()159     fn test_remove_dir() {
160         use crate::tempfile::TempFile;
161         let t = TempDir::new().unwrap();
162         let path = t.as_path().to_owned();
163         assert!(t.remove().is_ok());
164         // Calling remove twice returns error.
165         assert!(t.remove().is_err());
166         assert!(!path.exists());
167 
168         let t = TempDir::new().unwrap();
169         let mut file = TempFile::new_in(t.as_path()).unwrap();
170         let t2 = TempDir::new_in(t.as_path()).unwrap();
171         let mut file2 = TempFile::new_in(t2.as_path()).unwrap();
172         let path2 = t2.as_path().to_owned();
173         assert!(t.remove().is_ok());
174         // Calling t2.remove returns error because parent dir has removed
175         assert!(t2.remove().is_err());
176         assert!(!path2.exists());
177         assert!(file.remove().is_err());
178         assert!(file2.remove().is_err());
179     }
180 
181     #[test]
test_create_dir_in()182     fn test_create_dir_in() {
183         let t = TempDir::new_in(Path::new("/tmp")).unwrap();
184         let path = t.as_path();
185         assert!(path.exists());
186         assert!(path.is_dir());
187         assert!(path.starts_with("/tmp/"));
188 
189         let t = TempDir::new_in(Path::new("/tmp")).unwrap();
190         let path = t.as_path();
191         assert!(path.exists());
192         assert!(path.is_dir());
193         assert!(path.starts_with("/tmp"));
194     }
195 
196     #[test]
test_drop()197     fn test_drop() {
198         use std::mem::drop;
199         let t = TempDir::new_with_prefix("/tmp/asdf").unwrap();
200         let path = t.as_path().to_owned();
201         // Force tempdir object to go out of scope.
202         drop(t);
203 
204         assert!(!(path.exists()));
205     }
206 }
207