// Copyright 2019 Intel Corporation. All Rights Reserved. // // Copyright 2017 The Chromium OS Authors. All rights reserved. // // SPDX-License-Identifier: BSD-3-Clause //! Structure for handling temporary directories. use std::env::temp_dir; use std::ffi::{CString, OsStr, OsString}; use std::fs; use std::os::unix::ffi::OsStringExt; use std::path::{Path, PathBuf}; use crate::errno::{errno_result, Error, Result}; /// Wrapper over a temporary directory. /// /// The directory will be maintained for the lifetime of the `TempDir` object. #[derive(Debug)] pub struct TempDir { path: PathBuf, } impl TempDir { /// Creates a new temporary directory with `prefix`. /// /// The directory will be removed when the object goes out of scope. /// /// # Examples /// /// ``` /// # use vmm_sys_util::tempdir::TempDir; /// let t = TempDir::new_with_prefix("/tmp/testdir").unwrap(); /// ``` pub fn new_with_prefix>(prefix: P) -> Result { let mut dir_string = prefix.as_ref().to_os_string(); dir_string.push("XXXXXX"); // unwrap this result as the internal bytes can't have a null with a valid path. let dir_name = CString::new(dir_string.into_vec()).unwrap(); let mut dir_bytes = dir_name.into_bytes_with_nul(); // SAFETY: Creating the directory isn't unsafe. The fact that it modifies the guts of the // path is also OK because it only overwrites the last 6 Xs added above. let ret = unsafe { libc::mkdtemp(dir_bytes.as_mut_ptr() as *mut libc::c_char) }; if ret.is_null() { return errno_result(); } dir_bytes.pop(); // Remove the null becasue from_vec can't handle it. Ok(TempDir { path: PathBuf::from(OsString::from_vec(dir_bytes)), }) } /// Creates a new temporary directory with inside `path`. /// /// The directory will be removed when the object goes out of scope. /// /// # Examples /// /// ``` /// # use std::path::Path; /// # use vmm_sys_util::tempdir::TempDir; /// let t = TempDir::new_in(Path::new("/tmp/")).unwrap(); /// ``` pub fn new_in(path: &Path) -> Result { let mut path_buf = path.canonicalize().unwrap(); // This `push` adds a trailing slash ("/whatever/path" -> "/whatever/path/"). // This is safe for paths with already trailing slash. path_buf.push(""); let temp_dir = TempDir::new_with_prefix(path_buf)?; Ok(temp_dir) } /// Creates a new temporary directory with inside `$TMPDIR` if set, otherwise in `/tmp`. /// /// The directory will be removed when the object goes out of scope. /// /// # Examples /// /// ``` /// # use vmm_sys_util::tempdir::TempDir; /// let t = TempDir::new().unwrap(); /// ``` pub fn new() -> Result { let mut in_tmp_dir = temp_dir(); // This `push` adds a trailing slash ("/tmp" -> "/tmp/"). // This is safe for paths with already trailing slash. in_tmp_dir.push(""); let temp_dir = TempDir::new_in(in_tmp_dir.as_path())?; Ok(temp_dir) } /// Removes the temporary directory. /// /// Calling this is optional as when a `TempDir` object goes out of scope, /// the directory will be removed. /// Calling remove explicitly allows for better error handling. /// /// # Errors /// /// This function can only be called once per object. An error is returned /// otherwise. /// /// # Examples /// /// ``` /// # use std::path::Path; /// # use std::path::PathBuf; /// # use vmm_sys_util::tempdir::TempDir; /// let temp_dir = TempDir::new_with_prefix("/tmp/testdir").unwrap(); /// temp_dir.remove().unwrap(); pub fn remove(&self) -> Result<()> { fs::remove_dir_all(&self.path).map_err(Error::from) } /// Returns the path to the tempdir. /// /// # Examples /// /// ``` /// # use std::path::Path; /// # use std::path::PathBuf; /// # use vmm_sys_util::tempdir::TempDir; /// let temp_dir = TempDir::new_with_prefix("/tmp/testdir").unwrap(); /// assert!(temp_dir.as_path().exists()); pub fn as_path(&self) -> &Path { self.path.as_ref() } } impl Drop for TempDir { fn drop(&mut self) { let _ = self.remove(); } } #[cfg(test)] mod tests { use super::*; #[test] fn test_create_dir() { let t = TempDir::new().unwrap(); let path = t.as_path(); assert!(path.exists()); assert!(path.is_dir()); assert!(path.starts_with(temp_dir())); } #[test] fn test_create_dir_with_prefix() { let t = TempDir::new_with_prefix("/tmp/testdir").unwrap(); let path = t.as_path(); assert!(path.exists()); assert!(path.is_dir()); assert!(path.to_str().unwrap().contains("/tmp/testdir")); } #[test] fn test_remove_dir() { use crate::tempfile::TempFile; let t = TempDir::new().unwrap(); let path = t.as_path().to_owned(); assert!(t.remove().is_ok()); // Calling remove twice returns error. assert!(t.remove().is_err()); assert!(!path.exists()); let t = TempDir::new().unwrap(); let mut file = TempFile::new_in(t.as_path()).unwrap(); let t2 = TempDir::new_in(t.as_path()).unwrap(); let mut file2 = TempFile::new_in(t2.as_path()).unwrap(); let path2 = t2.as_path().to_owned(); assert!(t.remove().is_ok()); // Calling t2.remove returns error because parent dir has removed assert!(t2.remove().is_err()); assert!(!path2.exists()); assert!(file.remove().is_err()); assert!(file2.remove().is_err()); } #[test] fn test_create_dir_in() { let t = TempDir::new_in(Path::new("/tmp")).unwrap(); let path = t.as_path(); assert!(path.exists()); assert!(path.is_dir()); assert!(path.starts_with("/tmp/")); let t = TempDir::new_in(Path::new("/tmp")).unwrap(); let path = t.as_path(); assert!(path.exists()); assert!(path.is_dir()); assert!(path.starts_with("/tmp")); } #[test] fn test_drop() { use std::mem::drop; let t = TempDir::new_with_prefix("/tmp/asdf").unwrap(); let path = t.as_path().to_owned(); // Force tempdir object to go out of scope. drop(t); assert!(!(path.exists())); } }