//! Error types for `tor-persist. #![forbid(unsafe_code)] // if you remove this, enable (or write) miri tests (git grep miri) use std::sync::Arc; use crate::slug::BadSlug; use crate::FsMistrustErrorExt as _; use fs_mistrust::anon_home::PathExt as _; use tor_basic_utils::PathExt as _; use tor_error::{into_bad_api_usage, Bug, ErrorKind}; /// A resource that we failed to access or where we found a problem. #[derive(Debug, Clone, derive_more::Display)] pub(crate) enum Resource { /// The manager as a whole. #[display("persistent storage manager")] Manager, /// A checked directory. #[display("directory {}", dir.anonymize_home())] Directory { /// The path to the directory. dir: std::path::PathBuf, }, /// A file on disk within our checked directory. #[display("{} in {}", file.display_lossy(), container.anonymize_home())] File { /// The path to the checked directory container: std::path::PathBuf, /// The path within the checked directory to the file. file: std::path::PathBuf, }, /// Testing-only: a scratch-item in a memory-backed store. #[cfg(feature = "testing")] #[display("{} in memory-backed store", key)] Temporary { /// The key for the scratch item key: String, }, /// An instance state directory #[display( "instance {:?}/{:?} in {}", kind, identity, state_dir.anonymize_home() )] InstanceState { /// The path to the top-level state directory. state_dir: std::path::PathBuf, /// The instance's kind kind: String, /// The instance's identity identity: String, }, } /// An action that we were trying to perform when an error occurred. #[derive(Debug, Clone, derive_more::Display, Eq, PartialEq)] pub(crate) enum Action { /// We were trying to load an element from the store. #[display("loading persistent data")] Loading, /// We were trying to save an element into the store. #[display("storing persistent data")] Storing, /// We were trying to remove an element from the store. #[display("deleting persistent data")] Deleting, /// We were trying to acquire the lock for the store. #[display("acquiring lock")] Locking, /// We were trying to release the lock for the store. #[display("releasing lock")] Unlocking, /// We were trying to validate the storage and initialize the manager. #[display("constructing storage manager")] Initializing, /// We were trying to enumerate state objects #[display("enumerating instances")] Enumerating, } /// An underlying error manipulating persistent state. /// /// Since these are more or less orthogonal to what we were doing and where the /// problem was, this is a separate type. #[derive(thiserror::Error, Debug, Clone)] #[non_exhaustive] pub enum ErrorSource { /// An IO error occurred. #[error("IO error")] IoError(#[source] Arc), /// Inaccessible path, or permissions were incorrect #[error("Problem accessing persistent state")] Inaccessible(#[source] fs_mistrust::Error), /// Inaccessible path, or permissions were incorrect /// /// This variant name is misleading - see the docs for [`fs_mistrust::Error`]. /// Please use [`ErrorSource::Inaccessible`] instead. #[deprecated = "use ErrorSource::Inaccessible instead"] #[error("Problem accessing persistent state")] Permissions(#[source] fs_mistrust::Error), /// Tried to save without holding an exclusive lock. // // TODO This error seems to actually be sometimes used to make store a no-op. // We should consider whether this is best handled as an error, but for now // this seems adequate. #[error("Storage not locked")] NoLock, /// Problem when serializing or deserializing JSON data. #[error("JSON error")] Serde(#[from] Arc), /// Another task or process holds this persistent state lock, but we need exclusive access #[error("State already lockedr")] AlreadyLocked, /// Programming error #[error("Programming error")] Bug(#[from] Bug), } impl From for ErrorSource { fn from(bs: BadSlug) -> ErrorSource { into_bad_api_usage!("bad slug")(bs).into() } } /// [`BadSlug`] errors auto-convert to a [`BadApiUsage`](tor_error::ErrorKind::BadApiUsage) /// /// (Users of `tor-persist` ought to have newtypes for user-supplied slugs, /// and thereby avoid passing syntactically invalid slugs to `tor-persist`.) impl From for Error { fn from(bs: BadSlug) -> Error { // This metadata is approximate, but better information isn't readily available // and this shouldn't really happen. Error::new(bs, Action::Initializing, Resource::Manager) } } /// An error that occurred while manipulating persistent state. #[derive(Clone, Debug, derive_more::Display)] #[display("{} while {} on {}", source, action, resource)] pub struct Error { /// The underlying error failure. source: ErrorSource, /// The action we were trying to perform action: Action, /// The resource we were trying to perform it on. resource: Resource, } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.source.source() } } impl Error { /// Return the underlying error source. pub fn source(&self) -> &ErrorSource { &self.source } /// Construct a new Error from its components. pub(crate) fn new(err: impl Into, action: Action, resource: Resource) -> Self { Error { source: err.into(), action, resource, } } } impl tor_error::HasKind for Error { #[rustfmt::skip] // the tabular layout of the `match` makes this a lot clearer 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 for ErrorSource { fn from(e: std::io::Error) -> ErrorSource { ErrorSource::IoError(Arc::new(e)) } } impl From for ErrorSource { fn from(e: serde_json::Error) -> ErrorSource { ErrorSource::Serde(Arc::new(e)) } } impl From 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 { // @@ begin test lint list maintained by maint/add_warning @@ #![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::*; 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"# ); } }