//! Error types for `tor-persist. use std::sync::Arc; use fs_mistrust::anon_home::PathExt as _; use tor_error::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(fmt = "persistent storage manager")] Manager, /// A checked directory. #[display(fmt = "directory {}", "dir.anonymize_home()")] Directory { /// The path to the directory. dir: std::path::PathBuf, }, /// A file on disk within our checked directory. #[display(fmt = "{} in {}", "file.display()", "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(fmt = "{} in memory-backed store", key)] Temporary { /// The key for the scratch item key: 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(fmt = "loading persistent data")] Loading, /// We were trying to save an element into the store. #[display(fmt = "storing persistent data")] Storing, /// We were trying to acquire the lock for the store. #[display(fmt = "acquiring lock")] Locking, /// We were trying to release the lock for the store. #[display(fmt = "releasing lock")] Unlocking, /// We were trying to validate the storage and initialize the manager. #[display(fmt = "constructing storage manager")] Initializing, } /// 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), /// Permissions on a file or path were incorrect #[error("Invalid permissions")] 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), } /// An error that occurred while manipulating persistent state. #[derive(Clone, Debug, derive_more::Display)] #[display(fmt = "{} 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; match &self.source { E::IoError(..) => K::PersistentStateAccessFailed, E::Permissions(e) => if e.is_bad_permission() { K::FsPermissions } else { K::PersistentStateAccessFailed } E::NoLock => K::BadApiUsage, 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::Permissions(other), } } }