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 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(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 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).unwrap(); } #[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().unwrap(); 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().unwrap(); 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()); } }