From e56acf4f14b08bd31fafa2e44e52c7abae452b00 Mon Sep 17 00:00:00 2001 From: Marc Hartmayer Date: Thu, 1 Feb 2024 10:44:57 +0100 Subject: [PATCH] pv_core: add `TemporaryDirectory` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the type `TemporaryDirectory` that creates a temporary directory that is automatically removed when it goes out of scope. Reviewed-by: Steffen Eiden Reviewed-By: Harald Freudenberger Signed-off-by: Marc Hartmayer Signed-off-by: Jan Höppner --- rust/pv_core/src/lib.rs | 3 + rust/pv_core/src/tmpfile.rs | 152 ++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 rust/pv_core/src/tmpfile.rs diff --git a/rust/pv_core/src/lib.rs b/rust/pv_core/src/lib.rs index 092b5d9e..4648b5b9 100644 --- a/rust/pv_core/src/lib.rs +++ b/rust/pv_core/src/lib.rs @@ -15,6 +15,7 @@ mod error; mod log; mod macros; +mod tmpfile; mod utils; mod uvdevice; mod uvsecret; @@ -29,6 +30,8 @@ pub mod misc { pub use crate::utils::{parse_hex, to_u16, to_u32, try_parse_u128, try_parse_u64}; pub use crate::utils::{read, write}; pub use crate::utils::{Flags, Lsb0Flags64, Msb0Flags64}; + + pub use crate::tmpfile::TemporaryDirectory; } /// Definitions and functions for interacting with the Ultravisor diff --git a/rust/pv_core/src/tmpfile.rs b/rust/pv_core/src/tmpfile.rs new file mode 100644 index 00000000..24dafbf7 --- /dev/null +++ b/rust/pv_core/src/tmpfile.rs @@ -0,0 +1,152 @@ +use std::{ + ffi::{CString, OsStr}, + os::unix::prelude::OsStrExt, + path::{Path, PathBuf}, +}; + +/// Rust wrapper for `libc::mkdtemp` +fn mkdtemp>(template: P) -> Result { + let template_cstr = CString::new(template.as_ref().as_os_str().as_bytes())?; + let template_raw = template_cstr.into_raw(); + unsafe { + // SAFETY: template_raw is a valid CString because it was generated by + // the `CString::new`. + let ret = libc::mkdtemp(template_raw); + + if ret.is_null() { + Err(std::io::Error::last_os_error()) + } else { + // SAFETY: `template_raw` is still a valid CString because it was + // generated by `CString::new` and modified by `libc::mkdtemp`. + let path_cstr = std::ffi::CString::from_raw(template_raw); + let path = OsStr::from_bytes(path_cstr.as_bytes()); + let path = std::path::PathBuf::from(path); + + Ok(path) + } + } +} + +/// This type creates a temporary directory that is automatically removed when +/// it goes out of scope. It utilizes the `mkdtemp` function and its semantics, +/// with the addition of automatically including the template characters +/// `XXXXXX`. +#[derive(PartialEq, Eq, Debug)] +pub struct TemporaryDirectory { + path: Box, +} + +impl TemporaryDirectory { + /// Creates a temporary directory using `prefix` as directory prefix. + /// + /// # Errors + /// + /// An error is returned if the temporary directory could not be created. + pub fn new>(prefix: P) -> Result { + let mut template = prefix.as_ref().to_owned(); + let mut template_os_string = template.as_mut_os_string(); + template_os_string.push("XXXXXX"); + + let temp_dir = mkdtemp(template_os_string)?; + Ok(Self { + path: temp_dir.into_boxed_path(), + }) + } + + /// Returns the path of the created temporary directory. + pub fn path(&self) -> &Path { + self.path.as_ref() + } + + fn forget(mut self) { + self.path = PathBuf::new().into_boxed_path(); + std::mem::forget(self); + } + + /// Removes the created temporary directory and it's contents. + pub fn close(mut self) -> std::io::Result<()> { + let ret = std::fs::remove_dir_all(&self.path); + self.forget(); + ret + } +} + +impl AsRef for TemporaryDirectory { + fn as_ref(&self) -> &Path { + self.path() + } +} + +impl Drop for TemporaryDirectory { + fn drop(&mut self) { + let _ = std::fs::remove_dir_all(&self.path); + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::{mkdtemp, TemporaryDirectory}; + + #[test] + fn mkdtemp_test() { + let template_inv_not_last_characters = "XXXXXXyay"; + let template_inv_too_less_x = "yayXXXXX"; + let template_inv_path_does_not_exist = "../NA-yay/XXXXXX"; + + let template = "yayXXXXXX"; + + let err = mkdtemp(template_inv_not_last_characters).expect_err("invalid template"); + let err = mkdtemp(template_inv_too_less_x).expect_err("invalid template"); + let err = + mkdtemp(template_inv_path_does_not_exist).expect_err("path does not exist template"); + + let path = mkdtemp(template).expect("mkdtemp should work"); + assert!(path.exists()); + assert!(path.as_os_str().to_str().expect("works").starts_with("yay")); + std::fs::remove_dir(path); + } + + #[test] + fn temporary_directory_empty_name_test() { + let temp_dir = TemporaryDirectory::new("").expect("should work"); + let path = temp_dir.path().to_owned(); + assert!(path.exists()); + + // Test that close removes the directory + temp_dir.close(); + assert!(!path.exists()); + } + + #[test] + fn temporary_directory_drop_test() { + let temp_dir = TemporaryDirectory::new("").expect("should work"); + let path = temp_dir.path().to_owned(); + assert!(path.exists()); + + // Test that the destructor removes the directory + drop(temp_dir); + assert!(!path.exists()); + } + + #[test] + fn temporary_directory_close_test() { + let temp_dir = TemporaryDirectory::new("yay").expect("should work"); + + let path = temp_dir.path().to_owned(); + assert!(path.exists()); + assert!(path.as_os_str().to_str().expect("works").starts_with("yay")); + + // Test that close() removes the directory + temp_dir.close(); + assert!(!path.exists()); + } + + #[test] + fn temporary_directory_as_ref_test() { + let temp_dir = TemporaryDirectory::new("").expect("should work"); + + assert_eq!(temp_dir.path(), temp_dir.as_ref()); + } +}