use std::{fs::File, io, path::Path};
mod publish;
pub use publish::{
DirectoryCommitError, DirectoryCommitOutcome, DirectoryCommitStep,
commit_existing_directory_with,
};
#[derive(Debug, thiserror::Error)]
pub enum DirectoryCreationError {
#[error("private directory creation refused")]
BeforeCreation(#[source] io::Error),
#[error("created private directory admission failed")]
AfterCreation(#[source] io::Error),
}
pub fn create_private_directory(path: &Path) -> Result<File, DirectoryCreationError> {
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
{
use std::os::unix::fs::DirBuilderExt as _;
let parent = path
.parent()
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or_else(|| Path::new("."));
for directory in [
path.to_path_buf(),
parent
.canonicalize()
.map_err(DirectoryCreationError::BeforeCreation)?,
] {
if directory
.as_os_str()
.as_encoded_bytes()
.iter()
.any(|b| matches!(b, b'\r' | b'\n'))
{
return Err(DirectoryCreationError::BeforeCreation(
io::ErrorKind::InvalidInput.into(),
));
}
}
std::fs::DirBuilder::new()
.mode(0o700)
.create(path)
.map_err(DirectoryCreationError::BeforeCreation)?;
let finish = || -> io::Result<File> {
use rustix::fs::{Mode, OFlags, open};
File::open(parent)?.sync_all()?;
let file = File::from(open(
path,
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC,
Mode::empty(),
)?);
admit_directory(path, &file)?;
Ok(file)
};
finish().map_err(DirectoryCreationError::AfterCreation)
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))]
{
let _ = path;
Err(DirectoryCreationError::BeforeCreation(
io::ErrorKind::Unsupported.into(),
))
}
}
pub fn admit_directory(path: &Path, directory: &File) -> io::Result<()> {
#[cfg(unix)]
{
use crate::identity::FileIdentity;
let current = std::fs::symlink_metadata(path)?;
let held = directory.metadata()?;
if !current.is_dir()
|| !held.is_dir()
|| FileIdentity::from_metadata(¤t) != FileIdentity::from_metadata(&held)
{
return Err(io::ErrorKind::InvalidInput.into());
}
Ok(())
}
#[cfg(not(unix))]
{
let _ = (path, directory);
Err(io::ErrorKind::Unsupported.into())
}
}
#[cfg(test)]
mod tests;