1//! Error types for `tor-persist.
23#![forbid(unsafe_code)] // if you remove this, enable (or write) miri tests (git grep miri)
45use std::sync::Arc;
67use 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};
1213/// 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")]
18Manager,
19/// A checked directory.
20#[display("directory {}", dir.anonymize_home())]
21Directory {
22/// The path to the directory.
23dir: std::path::PathBuf,
24 },
25/// A file on disk within our checked directory.
26#[display("{} in {}", file.display_lossy(), container.anonymize_home())]
27File {
28/// The path to the checked directory
29container: std::path::PathBuf,
30/// The path within the checked directory to the file.
31file: 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)]
36Temporary {
37/// The key for the scratch item
38key: String,
39 },
40/// An instance state directory
41#[display(
42"instance {:?}/{:?} in {}",
43 kind,
44 identity,
45 state_dir.anonymize_home()
46 )]
47InstanceState {
48/// The path to the top-level state directory.
49state_dir: std::path::PathBuf,
50/// The instance's kind
51kind: String,
52/// The instance's identity
53identity: String,
54 },
55}
5657/// 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")]
62Loading,
63/// We were trying to save an element into the store.
64#[display("storing persistent data")]
65Storing,
66/// We were trying to remove an element from the store.
67#[display("deleting persistent data")]
68Deleting,
69/// We were trying to acquire the lock for the store.
70#[display("acquiring lock")]
71Locking,
72/// We were trying to release the lock for the store.
73#[display("releasing lock")]
74Unlocking,
75/// We were trying to validate the storage and initialize the manager.
76#[display("constructing storage manager")]
77Initializing,
78/// We were trying to enumerate state objects
79#[display("enumerating instances")]
80Enumerating,
81}
8283/// 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")]
92IoError(#[source] Arc<std::io::Error>),
9394/// Inaccessible path, or permissions were incorrect
95#[error("Problem accessing persistent state")]
96Inaccessible(#[source] fs_mistrust::Error),
9798/// 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")]
104Permissions(#[source] fs_mistrust::Error),
105106/// 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")]
112NoLock,
113114/// Problem when serializing or deserializing JSON data.
115#[error("JSON error")]
116Serde(#[from] Arc<serde_json::Error>),
117118/// Another task or process holds this persistent state lock, but we need exclusive access
119#[error("State already lockedr")]
120AlreadyLocked,
121122/// Programming error
123#[error("Programming error")]
124Bug(#[from] Bug),
125}
126127impl From<BadSlug> for ErrorSource {
128fn from(bs: BadSlug) -> ErrorSource {
129into_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 {
137fn from(bs: BadSlug) -> Error {
138// This metadata is approximate, but better information isn't readily available
139 // and this shouldn't really happen.
140Error::new(bs, Action::Initializing, Resource::Manager)
141 }
142}
143144/// 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.
149source: ErrorSource,
150/// The action we were trying to perform
151action: Action,
152/// The resource we were trying to perform it on.
153resource: Resource,
154}
155156impl std::error::Error for Error {
157fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
158self.source.source()
159 }
160}
161162impl Error {
163/// Return the underlying error source.
164pub fn source(&self) -> &ErrorSource {
165&self.source
166 }
167168/// Construct a new Error from its components.
169pub(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}
177178impl tor_error::HasKind for Error {
179#[rustfmt::skip] // the tabular layout of the `match` makes this a lot clearer
180fn kind(&self) -> ErrorKind {
181use ErrorSource as E;
182use tor_error::ErrorKind as K;
183#[allow(deprecated)]
184match &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}
196197impl From<std::io::Error> for ErrorSource {
198fn from(e: std::io::Error) -> ErrorSource {
199 ErrorSource::IoError(Arc::new(e))
200 }
201}
202203impl From<serde_json::Error> for ErrorSource {
204fn from(e: serde_json::Error) -> ErrorSource {
205 ErrorSource::Serde(Arc::new(e))
206 }
207}
208209impl From<fs_mistrust::Error> for ErrorSource {
210fn from(e: fs_mistrust::Error) -> ErrorSource {
211match e {
212 fs_mistrust::Error::Io { err, .. } => ErrorSource::IoError(err),
213 other => ErrorSource::Inaccessible(other),
214 }
215 }
216}
217218#[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 @@ -->
233234use super::*;
235use std::io;
236use tor_error::ErrorReport as _;
237238#[test]
239fn error_display() {
240assert_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(),
252r#"error: IO error while constructing storage manager on instance "KIND"/"IDENTY" in /STATE_DIR: permission denied"#
253);
254 }
255}