1 // Copyright 2017 The Chromium OS Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE-BSD-3-Clause file. 4 // 5 // SPDX-License-Identifier: BSD-3-Clause 6 7 //! Struct for handling temporary files as well as any cleanup required. 8 //! 9 //! The temporary files will be created with a name available as well as having 10 //! an exposed `fs::File` for reading/writing. 11 //! 12 //! The file will be removed when the object goes out of scope. 13 //! 14 //! # Examples 15 //! 16 //! ``` 17 //! use std::env::temp_dir; 18 //! use std::io::Write; 19 //! use std::path::{Path, PathBuf}; 20 //! use vmm_sys_util::tempfile::TempFile; 21 //! 22 //! let mut prefix = temp_dir(); 23 //! prefix.push("tempfile"); 24 //! let t = TempFile::new_with_prefix(prefix).unwrap(); 25 //! let mut f = t.as_file(); 26 //! f.write_all(b"hello world").unwrap(); 27 //! f.sync_all().unwrap(); 28 29 use std::env::temp_dir; 30 use std::ffi::OsStr; 31 use std::fs; 32 use std::fs::File; 33 use std::path::{Path, PathBuf}; 34 35 use libc; 36 37 use crate::errno::{errno_result, Error, Result}; 38 39 /// Wrapper for working with temporary files. 40 /// 41 /// The file will be maintained for the lifetime of the `TempFile` object. 42 #[derive(Debug)] 43 pub struct TempFile { 44 path: PathBuf, 45 file: Option<File>, 46 } 47 48 impl TempFile { 49 /// Creates the TempFile using a prefix. 50 /// 51 /// # Arguments 52 /// 53 /// `prefix`: The path and filename where to create the temporary file. Six 54 /// random alphanumeric characters will be added to the end of this to form 55 /// the filename. 56 #[cfg(unix)] new_with_prefix<P: AsRef<OsStr>>(prefix: P) -> Result<TempFile>57 pub fn new_with_prefix<P: AsRef<OsStr>>(prefix: P) -> Result<TempFile> { 58 use std::ffi::CString; 59 use std::os::unix::{ffi::OsStrExt, io::FromRawFd}; 60 61 let mut os_fname = prefix.as_ref().to_os_string(); 62 os_fname.push("XXXXXX"); 63 64 let c_tempname = CString::new(os_fname.as_bytes()).map_err(|_| Error::new(libc::EINVAL))?; 65 let raw_tempname = c_tempname.into_raw(); 66 67 // SAFETY: Safe because `c_tempname` is a null-terminated string, as it originates from 68 // `CString::into_raw`. 69 let ret = unsafe { libc::mkstemp(raw_tempname) }; 70 71 // SAFETY: `raw_tempname` originates from `CString::into_raw`. 72 let c_tempname = unsafe { CString::from_raw(raw_tempname) }; 73 74 let fd = match ret { 75 -1 => return errno_result(), 76 _ => ret, 77 }; 78 79 let os_tempname = OsStr::from_bytes(c_tempname.as_bytes()); 80 81 // SAFETY: Safe because we checked `fd != -1` above and we uniquely own the file 82 // descriptor. This `fd` will be freed etc when `File` and thus 83 // `TempFile` goes out of scope. 84 let file = unsafe { File::from_raw_fd(fd) }; 85 86 Ok(TempFile { 87 path: PathBuf::from(os_tempname), 88 file: Some(file), 89 }) 90 } 91 92 /// Creates the TempFile using a prefix. 93 /// 94 /// # Arguments 95 /// 96 /// `prefix`: The path and filename where to create the temporary file. Six 97 /// random alphanumeric characters will be added to the end of this to form 98 /// the filename. 99 #[cfg(windows)] new_with_prefix<P: AsRef<OsStr>>(prefix: P) -> Result<TempFile>100 pub fn new_with_prefix<P: AsRef<OsStr>>(prefix: P) -> Result<TempFile> { 101 use crate::rand::rand_alphanumerics; 102 use std::fs::OpenOptions; 103 104 let file_path_str = format!( 105 "{}{}", 106 prefix.as_ref().to_str().unwrap_or_default(), 107 rand_alphanumerics(6).to_str().unwrap_or_default() 108 ); 109 let file_path_buf = PathBuf::from(&file_path_str); 110 111 let file = OpenOptions::new() 112 .read(true) 113 .write(true) 114 .create(true) 115 .truncate(true) 116 .open(file_path_buf.as_path())?; 117 118 Ok(TempFile { 119 path: file_path_buf, 120 file: Some(file), 121 }) 122 } 123 124 /// Creates the TempFile inside a specific location. 125 /// 126 /// # Arguments 127 /// 128 /// `path`: The path where to create a temporary file with a filename formed from 129 /// six random alphanumeric characters. new_in(path: &Path) -> Result<Self>130 pub fn new_in(path: &Path) -> Result<Self> { 131 let mut path_buf = path.canonicalize().unwrap(); 132 // This `push` adds a trailing slash ("/whatever/path" -> "/whatever/path/"). 133 // This is safe for paths with an already existing trailing slash. 134 path_buf.push(""); 135 let temp_file = TempFile::new_with_prefix(path_buf.as_path())?; 136 Ok(temp_file) 137 } 138 139 /// Creates the TempFile. 140 /// 141 /// Creates a temporary file inside `$TMPDIR` if set, otherwise inside `/tmp`. 142 /// The filename will consist of six random alphanumeric characters. new() -> Result<Self>143 pub fn new() -> Result<Self> { 144 let in_tmp_dir = temp_dir(); 145 let temp_file = TempFile::new_in(in_tmp_dir.as_path())?; 146 Ok(temp_file) 147 } 148 149 /// Removes the temporary file. 150 /// 151 /// Calling this is optional as dropping a `TempFile` object will also 152 /// remove the file. Calling remove explicitly allows for better error 153 /// handling. remove(&mut self) -> Result<()>154 pub fn remove(&mut self) -> Result<()> { 155 fs::remove_file(&self.path).map_err(Error::from) 156 } 157 158 /// Returns the path to the file if the `TempFile` object that is wrapping the file 159 /// is still in scope. 160 /// 161 /// If we remove the file by explicitly calling [`remove`](#method.remove), 162 /// `as_path()` can still be used to return the path to that file (even though that 163 /// path does not point at an existing entity anymore). 164 /// Calling `as_path()` after `remove()` is useful, for example, when you need a 165 /// random path string, but don't want an actual resource at that path. as_path(&self) -> &Path166 pub fn as_path(&self) -> &Path { 167 &self.path 168 } 169 170 /// Returns a reference to the File. as_file(&self) -> &File171 pub fn as_file(&self) -> &File { 172 // It's safe to unwrap because `file` can be `None` only after calling `into_file` 173 // which consumes this object. 174 self.file.as_ref().unwrap() 175 } 176 177 /// Consumes the TempFile, returning the wrapped file. 178 /// 179 /// This also removes the file from the system. The file descriptor remains opened and 180 /// it can be used until the returned file is dropped. into_file(mut self) -> File181 pub fn into_file(mut self) -> File { 182 self.file.take().unwrap() 183 } 184 } 185 186 impl Drop for TempFile { drop(&mut self)187 fn drop(&mut self) { 188 let _ = self.remove(); 189 } 190 } 191 192 #[cfg(test)] 193 mod tests { 194 use super::*; 195 use std::io::{Read, Write}; 196 197 #[test] test_create_file_with_prefix()198 fn test_create_file_with_prefix() { 199 fn between(lower: u8, upper: u8, to_check: u8) -> bool { 200 (to_check >= lower) && (to_check <= upper) 201 } 202 203 let mut prefix = temp_dir(); 204 prefix.push("asdf"); 205 let t = TempFile::new_with_prefix(&prefix).unwrap(); 206 let path = t.as_path().to_owned(); 207 208 // Check filename exists 209 assert!(path.is_file()); 210 211 // Check filename is in the correct location 212 assert!(path.starts_with(temp_dir())); 213 214 // Check filename has random added 215 assert_eq!(path.file_name().unwrap().to_string_lossy().len(), 10); 216 217 // Check filename has only ascii letters / numbers 218 for n in path.file_name().unwrap().to_string_lossy().bytes() { 219 assert!(between(b'0', b'9', n) || between(b'a', b'z', n) || between(b'A', b'Z', n)); 220 } 221 222 // Check we can write to the file 223 let mut f = t.as_file(); 224 f.write_all(b"hello world").unwrap(); 225 f.sync_all().unwrap(); 226 assert_eq!(f.metadata().unwrap().len(), 11); 227 } 228 229 #[test] test_create_file_new()230 fn test_create_file_new() { 231 let t = TempFile::new().unwrap(); 232 let path = t.as_path().to_owned(); 233 234 // Check filename is in the correct location 235 assert!(path.starts_with(temp_dir().canonicalize().unwrap())); 236 } 237 238 #[test] test_create_file_new_in()239 fn test_create_file_new_in() { 240 let t = TempFile::new_in(temp_dir().as_path()).unwrap(); 241 let path = t.as_path().to_owned(); 242 243 // Check filename exists 244 assert!(path.is_file()); 245 246 // Check filename is in the correct location 247 assert!(path.starts_with(temp_dir().canonicalize().unwrap())); 248 249 let t = TempFile::new_in(temp_dir().as_path()).unwrap(); 250 let path = t.as_path().to_owned(); 251 252 // Check filename is in the correct location 253 assert!(path.starts_with(temp_dir().canonicalize().unwrap())); 254 } 255 256 #[test] test_remove_file()257 fn test_remove_file() { 258 let mut prefix = temp_dir(); 259 prefix.push("asdf"); 260 261 let mut t = TempFile::new_with_prefix(prefix).unwrap(); 262 let path = t.as_path().to_owned(); 263 264 // Check removal. 265 assert!(t.remove().is_ok()); 266 assert!(!path.exists()); 267 268 // Calling `as_path()` after the file was removed is allowed. 269 let path_2 = t.as_path().to_owned(); 270 assert_eq!(path, path_2); 271 272 // Check trying to remove a second time returns an error. 273 assert!(t.remove().is_err()); 274 } 275 276 #[test] test_drop_file()277 fn test_drop_file() { 278 let mut prefix = temp_dir(); 279 prefix.push("asdf"); 280 281 let t = TempFile::new_with_prefix(prefix).unwrap(); 282 let path = t.as_path().to_owned(); 283 284 assert!(path.starts_with(temp_dir())); 285 drop(t); 286 assert!(!path.exists()); 287 } 288 289 #[test] test_into_file()290 fn test_into_file() { 291 let mut prefix = temp_dir(); 292 prefix.push("asdf"); 293 294 let text = b"hello world"; 295 let temp_file = TempFile::new_with_prefix(prefix).unwrap(); 296 let path = temp_file.as_path().to_owned(); 297 fs::write(path, text).unwrap(); 298 299 let mut file = temp_file.into_file(); 300 let mut buf: Vec<u8> = Vec::new(); 301 file.read_to_end(&mut buf).unwrap(); 302 assert_eq!(buf, text); 303 } 304 } 305