Lines
60.47 %
Functions
42.86 %
Branches
100 %
//! 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<std::io::Error>),
/// Inaccessible path, or permissions were incorrect
#[error("Problem accessing persistent state")]
Inaccessible(#[source] fs_mistrust::Error),
/// This variant name is misleading - see the docs for [`fs_mistrust::Error`].
/// Please use [`ErrorSource::Inaccessible`] instead.
#[deprecated = "use ErrorSource::Inaccessible instead"]
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<serde_json::Error>),
/// 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<BadSlug> 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<BadSlug> 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<ErrorSource>, 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<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 {
// @@ 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)]
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
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"#
);