1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//! Classified failures of the encrypted-store lifecycle.
use super::keystore::{KeySource, KeyStoreError};
use crate::{DbError, UnreadableCause};
use std::path::PathBuf;
/// Why an encrypted-store operation failed. Every variant says what was and was not touched;
/// none of them means "an empty database was created instead".
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum LifecycleError {
/// A cross-process or in-process conflict stopped the operation before any file or key was
/// changed (the guard could not be taken, a reader or writer would not drain, the host would
/// not stop, or another transition is running).
#[error("store is busy, nothing was changed: {reason}")]
Busy { reason: String },
/// The database file is not there. No empty database was created (see
/// `EncryptedStoreConfig::create_if_missing`).
#[error("encrypted store {} does not exist; no empty database was created", .path.display())]
StoreMissing { path: PathBuf },
/// The file is a plaintext SQLite database. It was not touched and no key was applied.
#[error("{} is a plaintext SQLite database, not an encrypted one; it was not touched", .path.display())]
NotEncrypted { path: PathBuf },
/// The file exists but no key is stored anywhere (primary, pending, legacy staging, recovery).
#[error(
"no database key is stored (primary, pending, legacy staging and recovery are all empty)"
)]
KeyMissing,
/// Protected key storage refused or could not be reached (denied, unavailable, corrupt). There
/// is never a plaintext fallback, and nothing was changed.
#[error("protected key storage ({slot}) failed: {source}")]
KeyStore {
slot: String,
#[source]
source: KeyStoreError,
},
/// Keys are stored and none opens the file. SQLCipher cannot tell a wrong key from a damaged
/// file, so the two are not distinguished here. The file was not touched, quarantined or
/// replaced.
#[error("no stored key opens {} (tried {tried:?}); the key is wrong or the file is damaged, and the file was not touched", .path.display())]
NoKeyOpens {
path: PathBuf,
tried: Vec<KeySource>,
},
/// The file could not be opened for a reason that says nothing about the key (permission,
/// I/O, lock, device not ready). Nothing was changed.
#[error("database {} could not be opened: {source}", .path.display())]
DatabaseUnavailable {
path: PathBuf,
cause: Option<UnreadableCause>,
#[source]
source: Box<DbError>,
},
/// An interrupted or unresolved transition (journal, indeterminate rotation) blocks ordinary
/// use until `recover_rotation` resolves it. Recovery material is retained.
#[error("an interrupted or unresolved transition must be recovered before use: {detail}")]
RecoveryRequired { detail: String },
/// A replacement or destination holds, or would receive, a key other than the store's key.
#[error("wrong destination key for {}: {detail}", .path.display())]
WrongDestinationKey { path: PathBuf, detail: String },
/// A destination file already exists; nothing was overwritten.
#[error("{} already exists; refusing to overwrite it", .path.display())]
DestinationExists { path: PathBuf },
/// Protected storage holds a different primary key than the one that opens the database.
#[error("protected storage does not match the key that opens the database: {detail}")]
KeyStoreMismatch { detail: String },
#[error("invalid encrypted-store configuration: {0}")]
InvalidConfig(String),
/// The requested operation is not allowed in the store's current state.
#[error("{0}")]
Precondition(String),
/// The caller's validation callback rejected the result.
#[error("validation failed: {0}")]
ValidationFailed(String),
#[error(transparent)]
Db(#[from] DbError),
#[error(transparent)]
Fs(#[from] rightkit_fs::FsError),
}
impl LifecycleError {
pub(crate) fn busy(reason: impl Into<String>) -> Self {
Self::Busy {
reason: reason.into(),
}
}
pub(crate) fn key_store(slot: impl Into<String>, source: KeyStoreError) -> Self {
Self::KeyStore {
slot: slot.into(),
source,
}
}
/// True for the conflicts that mean "nothing was changed, try again later".
pub fn is_busy(&self) -> bool {
matches!(self, Self::Busy { .. })
}
}