Skip to main content

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::FsMistrustErrorExt as _;
8use crate::slug::BadSlug;
9use fs_mistrust::anon_home::PathExt as _;
10use tor_basic_utils::PathExt as _;
11use tor_error::{Bug, ErrorKind, into_bad_api_usage};
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 validate the storage and initialize the manager.
73    #[display("constructing storage manager")]
74    Initializing,
75    /// We were trying to enumerate state objects
76    #[display("enumerating instances")]
77    Enumerating,
78}
79
80/// An underlying error manipulating persistent state.
81///
82/// Since these are more or less orthogonal to what we were doing and where the
83/// problem was, this is a separate type.
84#[derive(thiserror::Error, Debug, Clone)]
85#[non_exhaustive]
86pub enum ErrorSource {
87    /// An IO error occurred.
88    #[error("IO error")]
89    IoError(#[source] Arc<std::io::Error>),
90
91    /// Inaccessible path, or permissions were incorrect
92    #[error("Problem accessing persistent state")]
93    Inaccessible(#[source] fs_mistrust::Error),
94
95    /// Inaccessible path, or permissions were incorrect
96    ///
97    /// This variant name is misleading - see the docs for [`fs_mistrust::Error`].
98    /// Please use [`ErrorSource::Inaccessible`] instead.
99    #[deprecated = "use ErrorSource::Inaccessible instead"]
100    #[error("Problem accessing persistent state")]
101    Permissions(#[source] fs_mistrust::Error),
102
103    /// Tried to save without holding an exclusive lock.
104    //
105    // TODO This error seems to actually be sometimes used to make store a no-op.
106    //      We should consider whether this is best handled as an error, but for now
107    //      this seems adequate.
108    #[error("Storage not locked")]
109    NoLock,
110
111    /// Problem when serializing or deserializing JSON data.
112    #[error("JSON error")]
113    Serde(#[from] Arc<serde_json::Error>),
114
115    /// Another task or process holds this persistent state lock, but we need exclusive access
116    #[error("State already lockedr")]
117    AlreadyLocked,
118
119    /// Programming error
120    #[error("Programming error")]
121    Bug(#[from] Bug),
122}
123
124impl From<BadSlug> for ErrorSource {
125    fn from(bs: BadSlug) -> ErrorSource {
126        into_bad_api_usage!("bad slug")(bs).into()
127    }
128}
129/// [`BadSlug`] errors auto-convert to a [`BadApiUsage`](tor_error::ErrorKind::BadApiUsage)
130///
131/// (Users of `tor-persist` ought to have newtypes for user-supplied slugs,
132/// and thereby avoid passing syntactically invalid slugs to `tor-persist`.)
133impl From<BadSlug> for Error {
134    fn from(bs: BadSlug) -> Error {
135        // This metadata is approximate, but better information isn't readily available
136        // and this shouldn't really happen.
137        Error::new(bs, Action::Initializing, Resource::Manager)
138    }
139}
140
141/// An error that occurred while manipulating persistent state.
142#[derive(Clone, Debug, derive_more::Display)]
143#[display("{} while {} on {}", source, action, resource)]
144pub struct Error {
145    /// The underlying error failure.
146    source: ErrorSource,
147    /// The action we were trying to perform
148    action: Action,
149    /// The resource we were trying to perform it on.
150    resource: Resource,
151}
152
153impl std::error::Error for Error {
154    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
155        self.source.source()
156    }
157}
158
159impl Error {
160    /// Return the underlying error source.
161    pub fn source(&self) -> &ErrorSource {
162        &self.source
163    }
164
165    /// Construct a new Error from its components.
166    pub(crate) fn new(err: impl Into<ErrorSource>, action: Action, resource: Resource) -> Self {
167        Error {
168            source: err.into(),
169            action,
170            resource,
171        }
172    }
173}
174
175impl tor_error::HasKind for Error {
176    #[rustfmt::skip] // the tabular layout of the `match` makes this a lot clearer
177    fn kind(&self) -> ErrorKind {
178        use ErrorSource as E;
179        use tor_error::ErrorKind as K;
180        #[allow(deprecated)]
181        match &self.source {
182            E::IoError(..)     => K::PersistentStateAccessFailed,
183            E::Permissions(e)  => e.state_error_kind(),
184            E::Inaccessible(e) => e.state_error_kind(),
185            E::NoLock          => K::BadApiUsage,
186            E::AlreadyLocked   => K::LocalResourceAlreadyInUse,
187            E::Bug(e)          => e.kind(),
188            E::Serde(..) if self.action == Action::Storing  => K::Internal,
189            E::Serde(..) => K::PersistentStateCorrupted,
190        }
191    }
192}
193
194impl From<std::io::Error> for ErrorSource {
195    fn from(e: std::io::Error) -> ErrorSource {
196        ErrorSource::IoError(Arc::new(e))
197    }
198}
199
200impl From<serde_json::Error> for ErrorSource {
201    fn from(e: serde_json::Error) -> ErrorSource {
202        ErrorSource::Serde(Arc::new(e))
203    }
204}
205
206impl From<fs_mistrust::Error> for ErrorSource {
207    fn from(e: fs_mistrust::Error) -> ErrorSource {
208        match e {
209            fs_mistrust::Error::Io { err, .. } => ErrorSource::IoError(err),
210            other => ErrorSource::Inaccessible(other),
211        }
212    }
213}
214
215#[cfg(all(test, not(miri) /* fs-mistrust home directory lookup */))]
216mod test {
217    // @@ begin test lint list maintained by maint/add_warning @@
218    #![allow(clippy::bool_assert_comparison)]
219    #![allow(clippy::clone_on_copy)]
220    #![allow(clippy::dbg_macro)]
221    #![allow(clippy::mixed_attributes_style)]
222    #![allow(clippy::print_stderr)]
223    #![allow(clippy::print_stdout)]
224    #![allow(clippy::single_char_pattern)]
225    #![allow(clippy::unwrap_used)]
226    #![allow(clippy::unchecked_time_subtraction)]
227    #![allow(clippy::useless_vec)]
228    #![allow(clippy::needless_pass_by_value)]
229    #![allow(clippy::string_slice)] // See arti#2571
230    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
231
232    use super::*;
233    use std::io;
234    use tor_error::ErrorReport as _;
235
236    #[test]
237    fn error_display() {
238        assert_eq!(
239            Error::new(
240                io::Error::from(io::ErrorKind::PermissionDenied),
241                Action::Initializing,
242                Resource::InstanceState {
243                    state_dir: "/STATE_DIR".into(),
244                    kind: "KIND".into(),
245                    identity: "IDENTY".into(),
246                }
247            )
248            .report()
249            .to_string(),
250            r#"error: IO error while constructing storage manager on instance "KIND"/"IDENTY" in /STATE_DIR: permission denied"#
251        );
252    }
253}