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