use std::path::Path;
use std::{path::PathBuf, sync::Arc};
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
#[cfg(feature = "anon_home")]
use crate::anon_home::PathExt as _;
#[cfg(not(feature = "anon_home"))]
trait PathExt {
fn anonymize_home(&self) -> impl std::fmt::Display + '_;
}
#[cfg(not(feature = "anon_home"))]
impl PathExt for Path {
#[allow(clippy::disallowed_methods)] fn anonymize_home(&self) -> impl std::fmt::Display + '_ {
self.display()
}
}
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("File or directory {} not found", _0.anonymize_home())]
NotFound(PathBuf),
#[error("Incorrect permissions: {} is {}; must be {}",
_0.anonymize_home(),
format_access_bits(* .1, '='),
format_access_bits(* .2, '-'))]
BadPermission(PathBuf, u32, u32),
#[error("Bad owner (UID {1}) on file or directory {anon}", anon = _0.anonymize_home())]
BadOwner(PathBuf, u32),
#[error("Wrong type of file at {}", _0.anonymize_home())]
BadType(PathBuf),
#[error("Unable to access {}", _0.anonymize_home())]
CouldNotInspect(PathBuf, #[source] Arc<IoError>),
#[error("Multiple errors found")]
Multiple(Vec<Box<Error>>),
#[error("Too many steps taken or planned: Possible symlink loop?")]
StepsExceeded,
#[error("Problem finding current directory")]
CurrentDirectory(#[source] Arc<IoError>),
#[error("Problem creating directory")]
CreatingDir(#[source] Arc<IoError>),
#[error("Problem in directory contents")]
Content(#[source] Box<Error>),
#[cfg(feature = "walkdir")]
#[error("Unable to list directory contents")]
Listing(#[source] Arc<walkdir::Error>),
#[error("Provided path was not valid for use with CheckedDir")]
InvalidSubdirectory,
#[error("IO error on {} while attempting to {action}", filename.anonymize_home())]
Io {
filename: PathBuf,
action: &'static str,
#[source]
err: Arc<IoError>,
},
#[error("Missing field when constructing Mistrust")]
MissingField(#[from] derive_builder::UninitializedFieldError),
#[error("Configured with nonexistent group: {0}")]
NoSuchGroup(String),
#[error("Configured with nonexistent user: {0}")]
NoSuchUser(String),
#[error("Error accessing passwd/group databases or obtaining our uids/gids")]
PasswdGroupIoError(#[source] Arc<IoError>),
}
impl Error {
pub(crate) fn inspecting(err: IoError, fname: impl Into<PathBuf>) -> Self {
match err.kind() {
IoErrorKind::NotFound => Error::NotFound(fname.into()),
_ => Error::CouldNotInspect(fname.into(), Arc::new(err)),
}
}
pub(crate) fn io(err: IoError, fname: impl Into<PathBuf>, action: &'static str) -> Self {
match err.kind() {
IoErrorKind::NotFound => Error::NotFound(fname.into()),
_ => Error::Io {
filename: fname.into(),
action,
err: Arc::new(err),
},
}
}
pub fn path(&self) -> Option<&Path> {
Some(
match self {
Error::NotFound(pb) => pb,
Error::BadPermission(pb, ..) => pb,
Error::BadOwner(pb, _) => pb,
Error::BadType(pb) => pb,
Error::CouldNotInspect(pb, _) => pb,
Error::Io { filename: pb, .. } => pb,
Error::Multiple(_) => return None,
Error::StepsExceeded => return None,
Error::CurrentDirectory(_) => return None,
Error::CreatingDir(_) => return None,
Error::InvalidSubdirectory => return None,
Error::Content(e) => return e.path(),
#[cfg(feature = "walkdir")]
Error::Listing(e) => return e.path(),
Error::MissingField(_) => return None,
Error::NoSuchGroup(_) => return None,
Error::NoSuchUser(_) => return None,
Error::PasswdGroupIoError(_) => return None,
}
.as_path(),
)
}
pub fn is_bad_permission(&self) -> bool {
match self {
Error::BadPermission(..) | Error::BadOwner(_, _) | Error::BadType(_) => true,
Error::NotFound(_)
| Error::CouldNotInspect(_, _)
| Error::StepsExceeded
| Error::CurrentDirectory(_)
| Error::CreatingDir(_)
| Error::InvalidSubdirectory
| Error::Io { .. }
| Error::MissingField(_)
| Error::NoSuchGroup(_)
| Error::NoSuchUser(_)
| Error::PasswdGroupIoError(_) => false,
#[cfg(feature = "walkdir")]
Error::Listing(_) => false,
Error::Multiple(errs) => errs.iter().any(|e| e.is_bad_permission()),
Error::Content(err) => err.is_bad_permission(),
}
}
pub fn errors<'a>(&'a self) -> impl Iterator<Item = &'a Error> + 'a {
let result: Box<dyn Iterator<Item = &Error> + 'a> = match self {
Error::Multiple(v) => Box::new(v.iter().map(|e| e.as_ref())),
_ => Box::new(vec![self].into_iter()),
};
result
}
}
impl std::iter::FromIterator<Error> for Option<Error> {
fn from_iter<T: IntoIterator<Item = Error>>(iter: T) -> Self {
let mut iter = iter.into_iter();
let first_err = iter.next()?;
if let Some(second_err) = iter.next() {
let mut errors = Vec::with_capacity(iter.size_hint().0 + 2);
errors.push(Box::new(first_err));
errors.push(Box::new(second_err));
errors.extend(iter.map(Box::new));
Some(Error::Multiple(errors))
} else {
Some(first_err)
}
}
}
pub fn format_access_bits(bits: u32, c: char) -> String {
let mut s = String::new();
for (shift, prefix) in [(6, 'u'), (3, 'g'), (0, 'o')] {
let b = (bits >> shift) & 7;
if b != 0 {
if !s.is_empty() {
s.push(',');
}
s.push(prefix);
s.push(c);
for (bit, ch) in [(4, 'r'), (2, 'w'), (1, 'x')] {
if b & bit != 0 {
s.push(ch);
}
}
}
}
s
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
#[test]
fn bits() {
assert_eq!(format_access_bits(0o777, '='), "u=rwx,g=rwx,o=rwx");
assert_eq!(format_access_bits(0o022, '='), "g=w,o=w");
assert_eq!(format_access_bits(0o022, '-'), "g-w,o-w");
assert_eq!(format_access_bits(0o020, '-'), "g-w");
assert_eq!(format_access_bits(0, ' '), "");
}
#[test]
fn bad_perms() {
assert_eq!(
Error::BadPermission(PathBuf::from("/path"), 0o777, 0o022).to_string(),
"Incorrect permissions: /path is u=rwx,g=rwx,o=rwx; must be g-w,o-w"
);
}
}