#![forbid(unsafe_code)]
use std::sync::Arc;
use crate::FsMistrustErrorExt as _;
use crate::slug::BadSlug;
use fs_mistrust::anon_home::PathExt as _;
use tor_basic_utils::PathExt as _;
use tor_error::{Bug, ErrorKind, into_bad_api_usage};
#[derive(Debug, Clone, derive_more::Display)]
pub(crate) enum Resource {
#[display("persistent storage manager")]
Manager,
#[display("directory {}", dir.anonymize_home())]
Directory {
dir: std::path::PathBuf,
},
#[display("{} in {}", file.display_lossy(), container.anonymize_home())]
File {
container: std::path::PathBuf,
file: std::path::PathBuf,
},
#[cfg(feature = "testing")]
#[display("{} in memory-backed store", key)]
Temporary {
key: String,
},
#[display(
"instance {:?}/{:?} in {}",
kind,
identity,
state_dir.anonymize_home()
)]
InstanceState {
state_dir: std::path::PathBuf,
kind: String,
identity: String,
},
}
#[derive(Debug, Clone, derive_more::Display, Eq, PartialEq)]
pub(crate) enum Action {
#[display("loading persistent data")]
Loading,
#[display("storing persistent data")]
Storing,
#[display("deleting persistent data")]
Deleting,
#[display("acquiring lock")]
Locking,
#[display("releasing lock")]
Unlocking,
#[display("constructing storage manager")]
Initializing,
#[display("enumerating instances")]
Enumerating,
}
#[derive(thiserror::Error, Debug, Clone)]
#[non_exhaustive]
pub enum ErrorSource {
#[error("IO error")]
IoError(#[source] Arc<std::io::Error>),
#[error("Problem accessing persistent state")]
Inaccessible(#[source] fs_mistrust::Error),
#[deprecated = "use ErrorSource::Inaccessible instead"]
#[error("Problem accessing persistent state")]
Permissions(#[source] fs_mistrust::Error),
#[error("Storage not locked")]
NoLock,
#[error("JSON error")]
Serde(#[from] Arc<serde_json::Error>),
#[error("State already lockedr")]
AlreadyLocked,
#[error("Programming error")]
Bug(#[from] Bug),
}
impl From<BadSlug> for ErrorSource {
fn from(bs: BadSlug) -> ErrorSource {
into_bad_api_usage!("bad slug")(bs).into()
}
}
impl From<BadSlug> for Error {
fn from(bs: BadSlug) -> Error {
Error::new(bs, Action::Initializing, Resource::Manager)
}
}
#[derive(Clone, Debug, derive_more::Display)]
#[display("{} while {} on {}", source, action, resource)]
pub struct Error {
source: ErrorSource,
action: Action,
resource: Resource,
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.source()
}
}
impl Error {
pub fn source(&self) -> &ErrorSource {
&self.source
}
pub(crate) fn new(err: impl Into<ErrorSource>, action: Action, resource: Resource) -> Self {
Error {
source: err.into(),
action,
resource,
}
}
}
impl tor_error::HasKind for Error {
#[rustfmt::skip] fn kind(&self) -> ErrorKind {
use ErrorSource as E;
use tor_error::ErrorKind as K;
#[allow(deprecated)]
match &self.source {
E::IoError(..) => K::PersistentStateAccessFailed,
E::Permissions(e) => e.state_error_kind(),
E::Inaccessible(e) => e.state_error_kind(),
E::NoLock => K::BadApiUsage,
E::AlreadyLocked => K::LocalResourceAlreadyInUse,
E::Bug(e) => e.kind(),
E::Serde(..) if self.action == Action::Storing => K::Internal,
E::Serde(..) => K::PersistentStateCorrupted,
}
}
}
impl From<std::io::Error> for ErrorSource {
fn from(e: std::io::Error) -> ErrorSource {
ErrorSource::IoError(Arc::new(e))
}
}
impl From<serde_json::Error> for ErrorSource {
fn from(e: serde_json::Error) -> ErrorSource {
ErrorSource::Serde(Arc::new(e))
}
}
impl From<fs_mistrust::Error> for ErrorSource {
fn from(e: fs_mistrust::Error) -> ErrorSource {
match e {
fs_mistrust::Error::Io { err, .. } => ErrorSource::IoError(err),
other => ErrorSource::Inaccessible(other),
}
}
}
#[cfg(all(test, not(miri) /* fs-mistrust home directory lookup */))]
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_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use std::io;
use tor_error::ErrorReport as _;
#[test]
fn error_display() {
assert_eq!(
Error::new(
io::Error::from(io::ErrorKind::PermissionDenied),
Action::Initializing,
Resource::InstanceState {
state_dir: "/STATE_DIR".into(),
kind: "KIND".into(),
identity: "IDENTY".into(),
}
)
.report()
.to_string(),
r#"error: IO error while constructing storage manager on instance "KIND"/"IDENTY" in /STATE_DIR: permission denied"#
);
}
}