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

            
127
impl 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`.)
136
impl 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)]
147
pub 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

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

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

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

            
178
impl tor_error::HasKind for Error {
179
    #[rustfmt::skip] // the tabular layout of the `match` makes this a lot clearer
180
2
    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
2
            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
2
    }
195
}
196

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

            
203
impl From<serde_json::Error> for ErrorSource {
204
94
    fn from(e: serde_json::Error) -> ErrorSource {
205
94
        ErrorSource::Serde(Arc::new(e))
206
94
    }
207
}
208

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

            
218
#[cfg(all(test, not(miri) /* fs-mistrust home directory lookup */))]
219
mod 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
}