tor_dirmgr/
err.rs

1//! Declare an error type for the tor-dirmgr crate.
2
3use std::error::Error as StdError;
4use std::sync::Arc;
5
6use crate::DocSource;
7use fs_mistrust::anon_home::PathExt as _;
8use futures::task::SpawnError;
9use thiserror::Error;
10use tor_error::{ErrorKind, HasKind};
11use tor_persist::FsMistrustErrorExt as _;
12
13/// An error originated by the directory manager code
14#[derive(Error, Debug, Clone)]
15#[non_exhaustive]
16pub enum Error {
17    /// We received a document we didn't want at all.
18    #[error("Received an object we didn't ask for: {0}")]
19    Unwanted(&'static str),
20    /// The NetDir we downloaded is older than the one we already have.
21    #[error("Downloaded netdir is older than the one we have")]
22    NetDirOlder,
23    /// This DirMgr doesn't support downloads.
24    #[error("Tried to download information on a DirMgr with no download support")]
25    NoDownloadSupport,
26    /// We couldn't read something from disk that we should have been
27    /// able to read.
28    #[error("Corrupt cache: {0}")]
29    CacheCorruption(&'static str),
30    /// rusqlite gave us an error.
31    #[error("Error from sqlite database")]
32    SqliteError(#[source] Arc<rusqlite::Error>),
33    /// Error while creating a read-only store.
34    #[error("Failed to create read-only store")]
35    ReadOnlyStorage(#[from] ReadOnlyStorageError),
36    /// A schema version that says we can't read it.
37    #[error("Unrecognized data storage schema v{schema}. (We support v{supported})")]
38    UnrecognizedSchema {
39        /// The schema version in the database
40        schema: u32,
41        /// The schema that we actually support.
42        supported: u32,
43    },
44    /// User requested an operation that required a usable
45    /// bootstrapped directory, but we didn't have one.
46    #[error("Directory not present or not up-to-date")]
47    DirectoryNotPresent,
48    /// A consensus document is signed by an unrecognized authority set.
49    #[error("Authorities on consensus are not the ones we expect")]
50    UnrecognizedAuthorities,
51    /// A directory manager has been dropped; background tasks can exit too.
52    #[error("Dirmgr has been dropped; background tasks exiting")]
53    ManagerDropped,
54    /// We made a bunch of attempts, but weren't unable to advance the
55    /// state of a download.
56    #[error("Unable to finish bootstrapping a directory")]
57    CantAdvanceState,
58    /// Error while accessing a lockfile.
59    #[error("Unable to access lock file")]
60    LockFile(Arc<std::io::Error>),
61    /// Error while accessing a file in the store.
62    #[error("Error while {action} cache file {}", fname.anonymize_home())]
63    CacheFile {
64        /// What we were doing when we encountered the error.
65        action: &'static str,
66        /// The file that we were trying to access.
67        fname: std::path::PathBuf,
68        /// The underlying IO error.
69        #[source]
70        error: Arc<std::io::Error>,
71    },
72    /// An error given by the consensus diff crate.
73    #[error("Problem applying consensus diff")]
74    ConsensusDiffError(#[from] tor_consdiff::Error),
75    /// Invalid UTF8 in directory response.
76    #[error("Invalid utf-8 from directory server")]
77    BadUtf8FromDirectory(#[source] std::string::FromUtf8Error),
78    /// Invalid UTF8 from our cache.
79    #[error("Invalid utf-8 in directory cache")]
80    BadUtf8InCache(#[source] std::str::Utf8Error),
81    /// Invalid hexadecimal value in the cache.
82    #[error("Invalid hexadecimal id in directory cache")]
83    BadHexInCache(#[source] hex::FromHexError),
84    /// Invalid json value in the cache
85    #[error("Invalid JSON in directory cache")]
86    BadJsonInCache(#[source] Arc<serde_json::Error>),
87    /// An error given by the network document crate.
88    #[error("Invalid document from {source}")]
89    NetDocError {
90        /// Where the document came from.
91        source: DocSource,
92        /// What error we got.
93        #[source]
94        cause: tor_netdoc::Error,
95    },
96    /// An error indicating that the consensus could not be validated.
97    ///
98    /// This kind of error is only returned during the certificate fetching
99    /// state; it indicates that a consensus which previously seemed to be
100    /// plausible has turned out to be wrong after we got the certificates.
101    #[error("Could not validate consensus from {source}")]
102    ConsensusInvalid {
103        /// Where the document came from.
104        source: DocSource,
105        /// What error we got.
106        #[source]
107        cause: tor_netdoc::Error,
108    },
109    /// An error caused by an expired or not-yet-valid object.
110    #[error("Directory object expired or not yet valid")]
111    UntimelyObject(#[from] tor_checkable::TimeValidityError),
112    /// An error given by dirclient
113    #[error("Problem downloading directory object")]
114    DirClientError(#[from] tor_dirclient::Error),
115    /// An error given by the checkable crate.
116    #[error("Invalid signatures")]
117    SignatureError(#[source] Arc<signature::Error>),
118    /// An attempt was made to bootstrap a `DirMgr` created in offline mode.
119    #[error("Tried to bootstrap a DirMgr that was configured as offline-only")]
120    OfflineMode,
121    /// A problem accessing our cache directory (for example, no such directory)
122    #[error("Problem accessing cache directory")]
123    CacheAccess(#[from] fs_mistrust::Error),
124    /// A problem accessing our cache directory (for example, no such directory)
125    ///
126    /// This variant name is misleading - see the docs for [`fs_mistrust::Error`].
127    /// Please use [`Error::CacheAccess`] instead.
128    #[error("Problem accessing cache directory")]
129    #[deprecated = "use Error::CacheAccess instead"]
130    CachePermissions(#[source] fs_mistrust::Error),
131    /// Unable to spawn task
132    #[error("Unable to spawn {spawning}")]
133    Spawn {
134        /// What we were trying to spawn
135        spawning: &'static str,
136        /// What happened when we tried to spawn it
137        #[source]
138        cause: Arc<SpawnError>,
139    },
140
141    /// Other error from an external directory provider
142    #[error("Error from external directory provider")]
143    ExternalDirProvider {
144        /// What happened
145        #[source]
146        cause: Arc<dyn std::error::Error + Send + Sync + 'static>,
147
148        /// The kind
149        kind: ErrorKind,
150    },
151
152    /// A programming problem, either in our code or the code calling it.
153    #[error("Internal programming issue")]
154    Bug(#[from] tor_error::Bug),
155}
156
157impl From<signature::Error> for Error {
158    fn from(err: signature::Error) -> Self {
159        Self::SignatureError(Arc::new(err))
160    }
161}
162
163impl From<tor_rtcompat::scheduler::SleepError> for Error {
164    fn from(err: tor_rtcompat::scheduler::SleepError) -> Self {
165        use tor_rtcompat::scheduler::SleepError::*;
166        match err {
167            ScheduleDropped => Error::ManagerDropped,
168            e => tor_error::into_internal!("Unexpected sleep error")(e).into(),
169        }
170    }
171}
172
173impl AsRef<dyn StdError + 'static> for Error {
174    fn as_ref(&self) -> &(dyn StdError + 'static) {
175        self
176    }
177}
178
179/// The effect that a given error has on our bootstrapping process
180#[derive(Copy, Clone, Debug)]
181pub(crate) enum BootstrapAction {
182    /// The error isn't fatal.  We should blame it on its source (if any), and
183    /// continue bootstrapping.
184    Nonfatal,
185    /// The error requires that we restart bootstrapping from scratch.  
186    ///
187    /// This kind of error typically means that we've downloaded a consensus
188    /// that turned out to be useless at a later stage, and so we need to
189    /// restart the downloading process from the beginning, by downloading a
190    /// fresh one.
191    Reset,
192    /// The error indicates that we cannot bootstrap, and should stop trying.
193    ///
194    /// These are typically internal programming errors, filesystem access
195    /// problems, directory manager shutdown, and the like.
196    Fatal,
197}
198
199impl Error {
200    /// Construct a new `Error` from a `SpawnError`.
201    pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> Error {
202        Error::Spawn {
203            spawning,
204            cause: Arc::new(err),
205        }
206    }
207
208    /// Construct a new `Error` from `tor_netdoc::Error`.
209    ///
210    /// Also takes a source so that we can keep track of where the document came from.
211    pub(crate) fn from_netdoc(source: DocSource, cause: tor_netdoc::Error) -> Error {
212        Error::NetDocError { source, cause }
213    }
214
215    /// Construct a new `Error` from `std::io::Error` for an error that occurred
216    /// while locking a file.
217    pub(crate) fn from_lockfile(err: std::io::Error) -> Error {
218        Error::LockFile(Arc::new(err))
219    }
220
221    /// Return true if this error is serious enough that we should mark this
222    /// cache as having failed.
223    pub(crate) fn indicates_cache_failure(&self) -> bool {
224        #[allow(deprecated)]
225        match self {
226            // These indicate a problem from the cache.
227            Error::Unwanted(_)
228            | Error::UnrecognizedAuthorities
229            | Error::BadUtf8FromDirectory(_)
230            | Error::ConsensusDiffError(_)
231            | Error::SignatureError(_)
232            | Error::ConsensusInvalid { .. }
233            | Error::UntimelyObject(_) => true,
234
235            // These errors cannot come from a directory cache.
236            Error::NoDownloadSupport
237            | Error::CacheCorruption(_)
238            | Error::CachePermissions(_)
239            | Error::CacheAccess(_)
240            | Error::SqliteError(_)
241            | Error::ReadOnlyStorage(_)
242            | Error::UnrecognizedSchema { .. }
243            | Error::DirectoryNotPresent
244            | Error::ManagerDropped
245            | Error::CantAdvanceState
246            | Error::LockFile { .. }
247            | Error::CacheFile { .. }
248            | Error::BadUtf8InCache(_)
249            | Error::BadHexInCache(_)
250            | Error::OfflineMode
251            | Error::Spawn { .. }
252            | Error::NetDirOlder
253            | Error::BadJsonInCache(_)
254            | Error::Bug(_) => false,
255
256            // For this one, we delegate.
257            Error::DirClientError(e) => e.should_retire_circ(),
258
259            // TODO: This one is special.  It could mean that the directory
260            // cache is serving us bad unparsable stuff, or it could mean that
261            // for some reason we're unable to parse a real legit document.
262            //
263            // If the cache is serving us something unparsable, it might be
264            // because the cache doesn't know all the same parsing rules for the
265            // object that we know.  That case might need special handling to
266            // avoid erroneously avoiding a good cache... especially if the document
267            // is one that the cache could be tricked into serving us.
268            Error::NetDocError { .. } => true,
269
270            // We can never see this kind of error from within the crate.
271            Error::ExternalDirProvider { .. } => false,
272        }
273    }
274
275    /// Return information about which directory cache caused this error, if
276    /// this error contains one.
277    pub(crate) fn responsible_cache(&self) -> Option<&tor_dirclient::SourceInfo> {
278        match self {
279            Error::NetDocError {
280                source: DocSource::DirServer { source },
281                ..
282            } => source.as_ref(),
283            Error::ConsensusInvalid {
284                source: DocSource::DirServer { source },
285                ..
286            } => source.as_ref(),
287            _ => None,
288        }
289    }
290
291    /// Return information about what to do if this error occurs during the
292    /// bootstrapping process.
293    #[allow(dead_code)]
294    pub(crate) fn bootstrap_action(&self) -> BootstrapAction {
295        #[allow(deprecated)]
296        match self {
297            Error::Unwanted(_)
298            | Error::NetDirOlder
299            | Error::UnrecognizedAuthorities
300            | Error::ConsensusDiffError(_)
301            | Error::BadUtf8FromDirectory(_)
302            | Error::UntimelyObject(_)
303            | Error::DirClientError(_)
304            | Error::SignatureError(_)
305            | Error::NetDocError { .. } => BootstrapAction::Nonfatal,
306
307            Error::ConsensusInvalid { .. } | Error::CantAdvanceState => BootstrapAction::Reset,
308
309            Error::NoDownloadSupport
310            | Error::OfflineMode
311            | Error::CacheCorruption(_)
312            | Error::SqliteError(_)
313            | Error::ReadOnlyStorage(_)
314            | Error::UnrecognizedSchema { .. }
315            | Error::ManagerDropped
316            | Error::LockFile { .. }
317            | Error::CacheFile { .. }
318            | Error::BadUtf8InCache(_)
319            | Error::BadHexInCache(_)
320            | Error::CachePermissions(_)
321            | Error::CacheAccess(_)
322            | Error::Spawn { .. }
323            | Error::BadJsonInCache(_)
324            | Error::ExternalDirProvider { .. } => BootstrapAction::Fatal,
325
326            // These should actually be impossible during the bootstrap process.
327            Error::DirectoryNotPresent | Error::Bug(_) => BootstrapAction::Fatal,
328        }
329    }
330}
331
332impl From<rusqlite::Error> for Error {
333    fn from(err: rusqlite::Error) -> Self {
334        use ErrorKind as EK;
335        let kind = sqlite_error_kind(&err);
336        match kind {
337            EK::Internal | EK::BadApiUsage => {
338                // TODO: should this be a .is_bug() on EK ?
339                tor_error::Bug::from_error(kind, err, "sqlite detected bug").into()
340            }
341            _ => Self::SqliteError(Arc::new(err)),
342        }
343    }
344}
345
346impl HasKind for Error {
347    fn kind(&self) -> ErrorKind {
348        use Error as E;
349        use ErrorKind as EK;
350        #[allow(deprecated)]
351        match self {
352            E::Unwanted(_) => EK::TorProtocolViolation,
353            E::NoDownloadSupport => EK::NotImplemented,
354            E::CacheCorruption(_) => EK::CacheCorrupted,
355            E::CachePermissions(e) => e.cache_error_kind(),
356            E::CacheAccess(e) => e.cache_error_kind(),
357            E::SqliteError(e) => sqlite_error_kind(e),
358            E::ReadOnlyStorage(_) => EK::LocalResourceAlreadyInUse,
359            E::UnrecognizedSchema { .. } => EK::CacheCorrupted,
360            E::BadJsonInCache(_) => EK::CacheCorrupted,
361            E::DirectoryNotPresent => EK::DirectoryExpired,
362            E::NetDirOlder => EK::TorDirectoryError,
363            E::BadUtf8FromDirectory(_) => EK::TorProtocolViolation,
364            E::BadUtf8InCache(_) => EK::CacheCorrupted,
365            E::BadHexInCache(_) => EK::CacheCorrupted,
366            E::UnrecognizedAuthorities => EK::TorProtocolViolation,
367            E::ManagerDropped => EK::ArtiShuttingDown,
368            E::CantAdvanceState => EK::TorAccessFailed,
369            E::LockFile { .. } => EK::CacheAccessFailed,
370            E::CacheFile { .. } => EK::CacheAccessFailed,
371            E::ConsensusDiffError(_) => EK::TorProtocolViolation,
372            E::NetDocError { source, .. } => match source {
373                DocSource::LocalCache => EK::CacheCorrupted,
374                DocSource::DirServer { .. } => EK::TorProtocolViolation,
375            },
376            E::ConsensusInvalid { source, .. } => match source {
377                DocSource::LocalCache => EK::CacheCorrupted,
378                DocSource::DirServer { .. } => EK::TorProtocolViolation,
379            },
380            E::UntimelyObject(_) => EK::TorProtocolViolation,
381            E::DirClientError(e) => e.kind(),
382            E::SignatureError(_) => EK::TorProtocolViolation,
383            E::OfflineMode => EK::BadApiUsage,
384            E::Spawn { cause, .. } => cause.kind(),
385            E::ExternalDirProvider { kind, .. } => *kind,
386            E::Bug(e) => e.kind(),
387        }
388    }
389}
390
391/// Convert a sqlite error code into a real ErrorKind.
392fn sqlite_error_kind(e: &rusqlite::Error) -> ErrorKind {
393    use rusqlite::ErrorCode as RE;
394    use ErrorKind as EK;
395
396    match e {
397        rusqlite::Error::SqliteFailure(code, _) => match code.code {
398            RE::DatabaseCorrupt => EK::CacheCorrupted,
399            RE::SchemaChanged
400            | RE::TooBig
401            | RE::ConstraintViolation
402            | RE::TypeMismatch
403            | RE::ApiMisuse
404            | RE::NoLargeFileSupport
405            | RE::ParameterOutOfRange
406            | RE::OperationInterrupted
407            | RE::ReadOnly
408            | RE::OperationAborted
409            | RE::DatabaseBusy
410            | RE::DatabaseLocked
411            | RE::OutOfMemory
412            | RE::InternalMalfunction => EK::Internal,
413
414            RE::FileLockingProtocolFailed
415            | RE::AuthorizationForStatementDenied
416            | RE::NotFound
417            | RE::DiskFull
418            | RE::CannotOpen
419            | RE::SystemIoFailure
420            | RE::PermissionDenied => EK::CacheAccessFailed,
421            RE::NotADatabase => EK::InvalidConfig,
422            _ => EK::Internal,
423        },
424
425        // TODO: Some of the other sqlite error types can sometimes represent
426        // possible database corruption (like UTF8Error.)  But I haven't
427        // found a way to distinguish when.
428        _ => EK::Internal,
429    }
430}
431
432/// An error coming from a read-only store.
433#[derive(Error, Debug, Clone)]
434#[non_exhaustive]
435pub enum ReadOnlyStorageError {
436    /// We couldn't find the database.
437    #[error("The database could not be found.")]
438    NoDatabase,
439
440    /// A schema version that is not current.
441    #[error("Incompatible data storage schema v{schema}. (We expected v{supported})")]
442    IncompatibleSchema {
443        /// The schema version in the database
444        schema: u32,
445        /// The schema that we actually support.
446        supported: u32,
447    },
448}