1
//! Error types for `tor-persist.
2

            
3
#![forbid(unsafe_code)] // if you remove this, enable (or write) miri tests (git grep miri)
4

            
5
use std::sync::Arc;
6

            
7
use crate::FsMistrustErrorExt as _;
8
use crate::slug::BadSlug;
9
use fs_mistrust::anon_home::PathExt as _;
10
use tor_basic_utils::PathExt as _;
11
use 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)]
15
pub(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)]
59
pub(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]
86
pub 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

            
124
impl 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`.)
133
impl 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)]
144
pub 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

            
153
impl std::error::Error for Error {
154
2
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
155
2
        self.source.source()
156
2
    }
157
}
158

            
159
impl Error {
160
    /// Return the underlying error source.
161
6
    pub fn source(&self) -> &ErrorSource {
162
6
        &self.source
163
6
    }
164

            
165
    /// Construct a new Error from its components.
166
204
    pub(crate) fn new(err: impl Into<ErrorSource>, action: Action, resource: Resource) -> Self {
167
204
        Error {
168
204
            source: err.into(),
169
204
            action,
170
204
            resource,
171
204
        }
172
204
    }
173
}
174

            
175
impl tor_error::HasKind for Error {
176
    #[rustfmt::skip] // the tabular layout of the `match` makes this a lot clearer
177
2
    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
2
            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
2
    }
192
}
193

            
194
impl From<std::io::Error> for ErrorSource {
195
2
    fn from(e: std::io::Error) -> ErrorSource {
196
2
        ErrorSource::IoError(Arc::new(e))
197
2
    }
198
}
199

            
200
impl From<serde_json::Error> for ErrorSource {
201
98
    fn from(e: serde_json::Error) -> ErrorSource {
202
98
        ErrorSource::Serde(Arc::new(e))
203
98
    }
204
}
205

            
206
impl From<fs_mistrust::Error> for ErrorSource {
207
2
    fn from(e: fs_mistrust::Error) -> ErrorSource {
208
2
        match e {
209
2
            fs_mistrust::Error::Io { err, .. } => ErrorSource::IoError(err),
210
            other => ErrorSource::Inaccessible(other),
211
        }
212
2
    }
213
}
214

            
215
#[cfg(all(test, not(miri) /* fs-mistrust home directory lookup */))]
216
mod 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
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
230

            
231
    use super::*;
232
    use std::io;
233
    use tor_error::ErrorReport as _;
234

            
235
    #[test]
236
    fn error_display() {
237
        assert_eq!(
238
            Error::new(
239
                io::Error::from(io::ErrorKind::PermissionDenied),
240
                Action::Initializing,
241
                Resource::InstanceState {
242
                    state_dir: "/STATE_DIR".into(),
243
                    kind: "KIND".into(),
244
                    identity: "IDENTY".into(),
245
                }
246
            )
247
            .report()
248
            .to_string(),
249
            r#"error: IO error while constructing storage manager on instance "KIND"/"IDENTY" in /STATE_DIR: permission denied"#
250
        );
251
    }
252
}