use std::{
fs, io,
num::NonZeroUsize,
path::{Component, Path, PathBuf},
sync::atomic::{AtomicU64, Ordering},
};
static SEQUENCE: AtomicU64 = AtomicU64::new(0);
pub fn create_unique_directory(
parent: &Path,
prefix: &str,
permissions: u32,
attempts: Option<NonZeroUsize>,
) -> io::Result<PathBuf> {
create_unique_directory_with_sequence(parent, prefix, permissions, attempts, &SEQUENCE)
}
fn create_unique_directory_with_sequence(
parent: &Path,
prefix: &str,
permissions: u32,
attempts: Option<NonZeroUsize>,
sequence: &AtomicU64,
) -> io::Result<PathBuf> {
validate_options(prefix, permissions)?;
let builder = fs::DirBuilder::new();
#[cfg(unix)]
let builder = {
use std::os::unix::fs::DirBuilderExt as _;
let mut builder = builder;
builder.mode(permissions);
builder
};
let mut remaining = attempts.map(NonZeroUsize::get);
while remaining.is_none_or(|count| count > 0) {
if let Some(count) = &mut remaining {
*count -= 1;
}
let candidate = parent.join(format!(
"{prefix}-{}-{}",
std::process::id(),
sequence.fetch_add(1, Ordering::Relaxed),
));
match builder.create(&candidate) {
Ok(()) => return Ok(candidate),
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {}
Err(error) => return Err(error),
}
}
Err(io::Error::new(
io::ErrorKind::AlreadyExists,
format!("failed to allocate directory beneath {}", parent.display()),
))
}
pub(crate) fn validate_options(prefix: &str, permissions: u32) -> io::Result<()> {
let mut components = Path::new(prefix).components();
if prefix.contains(['\r', '\n'])
|| !matches!(
(components.next(), components.next()),
(Some(Component::Normal(name)), None) if name == std::ffi::OsStr::new(prefix)
)
|| permissions & !0o777 != 0
{
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid directory prefix or permissions",
));
}
Ok(())
}
#[cfg(test)]
mod tests;