use std::fs;
use std::path::Path;
use super::disk::{check_private, first_foreign, io, writable_by_mode};
use super::{QUARANTINE, Spool, SpoolError};
impl Spool {
pub fn inspect(directory: &Path) -> Result<(), SpoolError> {
let metadata = match fs::metadata(directory) {
Ok(metadata) => metadata,
Err(error) if missing(&error) => return inspect_ancestor(directory),
Err(source) => return Err(io("read", directory)(source)),
};
if !metadata.is_dir() {
return Err(SpoolError::NotADirectory(directory.to_owned()));
}
check_private(directory)?;
if !writable_by_mode(directory)? {
return Err(SpoolError::Unwritable(directory.to_owned()));
}
let held = directory.join(QUARANTINE);
match fs::metadata(&held) {
Ok(metadata) if metadata.is_dir() => {
check_private(&held)?;
if let Some(path) = first_foreign(&held, None)? {
return Err(SpoolError::Foreign {
directory: held,
path,
});
}
}
Ok(_) => return Err(SpoolError::NotADirectory(held)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(source) => return Err(io("read", &held)(source)),
}
match first_foreign(directory, Some(&held))? {
Some(path) => Err(SpoolError::Foreign {
directory: directory.to_owned(),
path,
}),
None => Ok(()),
}
}
}
fn inspect_ancestor(directory: &Path) -> Result<(), SpoolError> {
for ancestor in directory.ancestors().skip(1) {
let ancestor = if ancestor.as_os_str().is_empty() {
Path::new(".")
} else {
ancestor
};
match fs::metadata(ancestor) {
Ok(metadata) if !metadata.is_dir() => {
return Err(SpoolError::NotADirectory(ancestor.to_owned()));
}
Ok(_) if !writable_by_mode(ancestor)? => {
return Err(SpoolError::Unwritable(ancestor.to_owned()));
}
Ok(_) => return Ok(()),
Err(error) if missing(&error) => {}
Err(source) => return Err(io("read", ancestor)(source)),
}
}
Err(SpoolError::NotADirectory(directory.to_owned()))
}
fn missing(error: &std::io::Error) -> bool {
matches!(
error.kind(),
std::io::ErrorKind::NotFound | std::io::ErrorKind::NotADirectory
)
}