tor_persist/
err.rs

1//! Error types for `tor-persist.
2
3#![forbid(unsafe_code)] // if you remove this, enable (or write) miri tests (git grep miri)
4
5use std::sync::Arc;
6
7use crate::slug::BadSlug;
8use crate::FsMistrustErrorExt as _;
9use fs_mistrust::anon_home::PathExt as _;
10use tor_basic_utils::PathExt as _;
11use tor_error::{into_bad_api_usage, Bug, ErrorKind};
12
13/// A resource that we failed to access or where we found a problem.
14#[derive(Debug, Clone, derive_more::Display)]
15pub(crate) enum Resource {
16    /// The manager as a whole.
17    #[display("persistent storage manager")]
18    Manager,
19    /// A checked directory.
20    #[display("directory {}", dir.anonymize_home())]
21    Directory {
22        /// The path to the directory.
23        dir: std::path::PathBuf,
24    },
25    /// A file on disk within our checked directory.
26    #[display("{} in {}", file.display_lossy(), container.anonymize_home())]
27    File {
28        /// The path to the checked directory
29        container: std::path::PathBuf,
30        /// The path within the checked directory to the file.
31        file: std::path::PathBuf,
32    },
33    /// Testing-only: a scratch-item in a memory-backed store.
34    #[cfg(feature = "testing")]
35    #[display("{} in memory-backed store", key)]
36    Temporary {
37        /// The key for the scratch item
38        key: String,
39    },
40    /// An instance state directory
41    #[display(
42        "instance {:?}/{:?} in {}",
43        kind,
44        identity,
45        state_dir.anonymize_home()
46    )]
47    InstanceState {
48        /// The path to the top-level state directory.
49        state_dir: std::path::PathBuf,
50        /// The instance's kind
51        kind: String,
52        /// The instance's identity
53        identity: String,
54    },
55}
56
57/// An action that we were trying to perform when an error occurred.
58#[derive(Debug, Clone, derive_more::Display, Eq, PartialEq)]
59pub(crate) enum Action {
60    /// We were trying to load an element from the store.
61    #[display("loading persistent data")]
62    Loading,
63    /// We were trying to save an element into the store.
64    #[display("storing persistent data")]
65    Storing,
66    /// We were trying to remove an element from the store.
67    #[display("deleting persistent data")]
68    Deleting,
69    /// We were trying to acquire the lock for the store.
70    #[display("acquiring lock")]
71    Locking,
72    /// We were trying to release the lock for the store.
73    #[display("releasing lock")]
74    Unlocking,
75    /// We were trying to validate the storage and initialize the manager.
76    #[display("constructing storage manager")]
77    Initializing,
78    /// We were trying to enumerate state objects
79    #[display("enumerating instances")]
80    Enumerating,
81}
82
83/// An underlying error manipulating persistent state.
84///
85/// Since these are more or less orthogonal to what we were doing and where the
86/// problem was, this is a separate type.
87#[derive(thiserror::Error, Debug, Clone)]
88#[non_exhaustive]
89pub enum ErrorSource {
90    /// An IO error occurred.
91    #[error("IO error")]
92    IoError(#[source] Arc<std::io::Error>),
93
94    /// Inaccessible path, or permissions were incorrect
95    #[error("Problem accessing persistent state")]
96    Inaccessible(#[source] fs_mistrust::Error),
97
98    /// Inaccessible path, or permissions were incorrect
99    ///
100    /// This variant name is misleading - see the docs for [`fs_mistrust::Error`].
101    /// Please use [`ErrorSource::Inaccessible`] instead.
102    #[deprecated = "use ErrorSource::Inaccessible instead"]
103    #[error("Problem accessing persistent state")]
104    Permissions(#[source] fs_mistrust::Error),
105
106    /// Tried to save without holding an exclusive lock.
107    //
108    // TODO This error seems to actually be sometimes used to make store a no-op.
109    //      We should consider whether this is best handled as an error, but for now
110    //      this seems adequate.
111    #[error("Storage not locked")]
112    NoLock,
113
114    /// Problem when serializing or deserializing JSON data.
115    #[error("JSON error")]
116    Serde(#[from] Arc<serde_json::Error>),
117
118    /// Another task or process holds this persistent state lock, but we need exclusive access
119    #[error("State already lockedr")]
120    AlreadyLocked,
121
122    /// Programming error
123    #[error("Programming error")]
124    Bug(#[from] Bug),
125}
126
127impl From<BadSlug> for ErrorSource {
128    fn from(bs: BadSlug) -> ErrorSource {
129        into_bad_api_usage!("bad slug")(bs).into()
130    }
131}
132/// [`BadSlug`] errors auto-convert to a [`BadApiUsage`](tor_error::ErrorKind::BadApiUsage)
133///
134/// (Users of `tor-persist` ought to have newtypes for user-supplied slugs,
135/// and thereby avoid passing syntactically invalid slugs to `tor-persist`.)
136impl From<BadSlug> for Error {
137    fn from(bs: BadSlug) -> Error {
138        // This metadata is approximate, but better information isn't readily available
139        // and this shouldn't really happen.
140        Error::new(bs, Action::Initializing, Resource::Manager)
141    }
142}
143
144/// An error that occurred while manipulating persistent state.
145#[derive(Clone, Debug, derive_more::Display)]
146#[display("{} while {} on {}", source, action, resource)]
147pub struct Error {
148    /// The underlying error failure.
149    source: ErrorSource,
150    /// The action we were trying to perform
151    action: Action,
152    /// The resource we were trying to perform it on.
153    resource: Resource,
154}
155
156impl std::error::Error for Error {
157    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
158        self.source.source()
159    }
160}
161
162impl Error {
163    /// Return the underlying error source.
164    pub fn source(&self) -> &ErrorSource {
165        &self.source
166    }
167
168    /// Construct a new Error from its components.
169    pub(crate) fn new(err: impl Into<ErrorSource>, action: Action, resource: Resource) -> Self {
170        Error {
171            source: err.into(),
172            action,
173            resource,
174        }
175    }
176}
177
178impl tor_error::HasKind for Error {
179    #[rustfmt::skip] // the tabular layout of the `match` makes this a lot clearer
180    fn kind(&self) -> ErrorKind {
181        use ErrorSource as E;
182        use tor_error::ErrorKind as K;
183        #[allow(deprecated)]
184        match &self.source {
185            E::IoError(..)     => K::PersistentStateAccessFailed,
186            E::Permissions(e)  => e.state_error_kind(),
187            E::Inaccessible(e) => e.state_error_kind(),
188            E::NoLock          => K::BadApiUsage,
189            E::AlreadyLocked   => K::LocalResourceAlreadyInUse,
190            E::Bug(e)          => e.kind(),
191            E::Serde(..) if self.action == Action::Storing  => K::Internal,
192            E::Serde(..) => K::PersistentStateCorrupted,
193        }
194    }
195}
196
197impl From<std::io::Error> for ErrorSource {
198    fn from(e: std::io::Error) -> ErrorSource {
199        ErrorSource::IoError(Arc::new(e))
200    }
201}
202
203impl From<serde_json::Error> for ErrorSource {
204    fn from(e: serde_json::Error) -> ErrorSource {
205        ErrorSource::Serde(Arc::new(e))
206    }
207}
208
209impl From<fs_mistrust::Error> for ErrorSource {
210    fn from(e: fs_mistrust::Error) -> ErrorSource {
211        match e {
212            fs_mistrust::Error::Io { err, .. } => ErrorSource::IoError(err),
213            other => ErrorSource::Inaccessible(other),
214        }
215    }
216}
217
218#[cfg(all(test, not(miri) /* fs-mistrust home directory lookup */))]
219mod test {
220    // @@ begin test lint list maintained by maint/add_warning @@
221    #![allow(clippy::bool_assert_comparison)]
222    #![allow(clippy::clone_on_copy)]
223    #![allow(clippy::dbg_macro)]
224    #![allow(clippy::mixed_attributes_style)]
225    #![allow(clippy::print_stderr)]
226    #![allow(clippy::print_stdout)]
227    #![allow(clippy::single_char_pattern)]
228    #![allow(clippy::unwrap_used)]
229    #![allow(clippy::unchecked_duration_subtraction)]
230    #![allow(clippy::useless_vec)]
231    #![allow(clippy::needless_pass_by_value)]
232    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
233
234    use super::*;
235    use std::io;
236    use tor_error::ErrorReport as _;
237
238    #[test]
239    fn error_display() {
240        assert_eq!(
241            Error::new(
242                io::Error::from(io::ErrorKind::PermissionDenied),
243                Action::Initializing,
244                Resource::InstanceState {
245                    state_dir: "/STATE_DIR".into(),
246                    kind: "KIND".into(),
247                    identity: "IDENTY".into(),
248                }
249            )
250            .report()
251            .to_string(),
252            r#"error: IO error while constructing storage manager on instance "KIND"/"IDENTY" in /STATE_DIR: permission denied"#
253        );
254    }
255}