| // 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. |
| 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<P: AsRef<OsStr>>(prefix: P) -> Result<TempDir> { |
| 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<TempDir> { |
| 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<TempDir> { |
| 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())); |
| } |
| } |