use std::fs::{DirBuilder, File};
#[cfg(target_os = "linux")]
use std::os::unix::{fs::DirBuilderExt, prelude::OpenOptionsExt};
use std::path::{Path, PathBuf};
pub struct TempFile {
pub f: std::fs::File,
pub path: PathBuf,
removed: bool,
}
impl TempFile {
pub fn new(pattern: &str) -> std::io::Result<Self> {
let (f, path) = create_temp(None, pattern)?;
Ok(Self {
f,
path,
removed: false,
})
}
pub fn new_in_dir(dir: &Path, pattern: &str) -> std::io::Result<Self> {
let (f, path) = create_temp(Some(dir), pattern)?;
Ok(Self {
f,
path,
removed: false,
})
}
pub fn remove(&mut self) -> std::io::Result<()> {
if !self.removed {
std::fs::remove_file(&self.path)?;
self.removed = true;
}
Ok(())
}
}
impl Drop for TempFile {
fn drop(&mut self) {
_ = self.remove()
}
}
pub struct TempDir {
pub path: PathBuf,
removed: bool,
}
impl TempDir {
pub fn new(pattern: &str) -> std::io::Result<Self> {
let path = mkdir_temp(None, pattern)?;
Ok(Self {
path,
removed: false,
})
}
pub fn new_in_dir(dir: &Path, pattern: &str) -> std::io::Result<Self> {
let path = mkdir_temp(Some(dir), pattern)?;
Ok(Self {
path,
removed: false,
})
}
pub fn remove(&mut self) -> std::io::Result<()> {
if !self.removed {
std::fs::remove_dir_all(&self.path)?;
self.removed = true;
}
Ok(())
}
}
impl Drop for TempDir {
fn drop(&mut self) {
_ = self.remove()
}
}
pub fn create_temp(
dir: Option<&std::path::Path>,
pattern: &str,
) -> std::io::Result<(File, PathBuf)> {
let (prefix, suffix) = prefix_and_suffix(pattern)?;
let dir = if let Some(dir) = dir {
PathBuf::from(dir)
} else {
super::temp_dir()
};
let mut attempt = 0;
loop {
let random = crate::runtime::fastrand();
let file_name = if let Some(suffix) = suffix {
format!("{}{}{}", prefix, random, suffix)
} else {
format!("{}{}", prefix, random)
};
let path = dir.join(file_name);
let mut options = std::fs::OpenOptions::new();
options.read(true).write(true).create_new(true);
#[cfg(target_os = "linux")]
options.mode(0o600);
let file = options.open(&path);
match file {
Ok(file) => return Ok((file, path)),
Err(err) => {
if err.kind() == std::io::ErrorKind::AlreadyExists {
attempt += 1;
if attempt < 10000 {
continue;
}
}
return Err(err);
}
}
}
}
const ERR_PATTERN_HAS_SEPARATOR: &str = "pattern contains path separator";
pub fn is_err_pattern_has_separator(err: &std::io::Error) -> bool {
err.to_string().contains(ERR_PATTERN_HAS_SEPARATOR)
}
fn prefix_and_suffix(pattern: &str) -> std::io::Result<(&str, Option<&str>)> {
for c in pattern.chars() {
if super::is_path_separator(c) {
return Err(crate::errors::new_stdio_other_error(
ERR_PATTERN_HAS_SEPARATOR.to_string(),
));
}
}
let parts: Vec<&str> = pattern.rsplitn(2, '*').collect();
if parts.len() == 2 {
Ok((parts[1], Some(parts[0])))
} else {
Ok((pattern, None))
}
}
pub fn mkdir_temp(dir: Option<&std::path::Path>, pattern: &str) -> std::io::Result<PathBuf> {
let (prefix, suffix) = prefix_and_suffix(pattern)?;
let dir = if let Some(dir) = dir {
PathBuf::from(dir)
} else {
super::temp_dir()
};
let mut attempt = 0;
loop {
let random = crate::runtime::fastrand();
let file_name = if let Some(suffix) = suffix {
format!("{}{}{}", prefix, random, suffix)
} else {
format!("{}{}", prefix, random)
};
let path = dir.join(file_name);
#[cfg(target_os = "windows")]
let err = DirBuilder::new().create(&path);
#[cfg(target_os = "linux")]
let err = DirBuilder::new().mode(0o700).create(&path);
if let Err(err) = err {
if err.kind() == std::io::ErrorKind::AlreadyExists {
attempt += 1;
if attempt < 10000 {
continue;
}
}
return Err(err);
}
return Ok(path);
}
}