summaryrefslogtreecommitdiff
path: root/crates/tor-dirmgr/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/tor-dirmgr/src')
-rw-r--r--crates/tor-dirmgr/src/err.rs24
-rw-r--r--crates/tor-dirmgr/src/storage/sqlite.rs47
2 files changed, 62 insertions, 9 deletions
diff --git a/crates/tor-dirmgr/src/err.rs b/crates/tor-dirmgr/src/err.rs
index 2a2f55079..20a533559 100644
--- a/crates/tor-dirmgr/src/err.rs
+++ b/crates/tor-dirmgr/src/err.rs
@@ -30,6 +30,9 @@ pub enum Error {
/// rusqlite gave us an error.
#[error("Error from sqlite database")]
SqliteError(#[source] Arc<rusqlite::Error>),
+ /// Error while creating a read-only store.
+ #[error("Failed to create read-only store")]
+ ReadOnlyStorage(#[from] ReadOnlyStorageError),
/// A schema version that says we can't read it.
#[error("Unrecognized data storage schema v{schema}. (We support v{supported})")]
UnrecognizedSchema {
@@ -232,6 +235,7 @@ impl Error {
| Error::CachePermissions(_)
| Error::CacheAccess(_)
| Error::SqliteError(_)
+ | Error::ReadOnlyStorage(_)
| Error::UnrecognizedSchema { .. }
| Error::DirectoryNotPresent
| Error::ManagerDropped
@@ -302,6 +306,7 @@ impl Error {
| Error::OfflineMode
| Error::CacheCorruption(_)
| Error::SqliteError(_)
+ | Error::ReadOnlyStorage(_)
| Error::UnrecognizedSchema { .. }
| Error::ManagerDropped
| Error::LockFile { .. }
@@ -345,6 +350,7 @@ impl HasKind for Error {
E::CachePermissions(e) => e.cache_error_kind(),
E::CacheAccess(e) => e.cache_error_kind(),
E::SqliteError(e) => sqlite_error_kind(e),
+ E::ReadOnlyStorage(_) => EK::LocalResourceAlreadyInUse,
E::UnrecognizedSchema { .. } => EK::CacheCorrupted,
E::DirectoryNotPresent => EK::DirectoryExpired,
E::NetDirOlder => EK::TorDirectoryError,
@@ -416,3 +422,21 @@ fn sqlite_error_kind(e: &rusqlite::Error) -> ErrorKind {
_ => EK::Internal,
}
}
+
+/// An error coming from a read-only store.
+#[derive(Error, Debug, Clone)]
+#[non_exhaustive]
+pub enum ReadOnlyStorageError {
+ /// We couldn't find the database.
+ #[error("The database could not be found.")]
+ NoDatabase,
+
+ /// A schema version that is not current.
+ #[error("Incomaptible data storage schema v{schema}. (We expected v{supported})")]
+ IncompatibleSchema {
+ /// The schema version in the database
+ schema: u32,
+ /// The schema that we actually support.
+ supported: u32,
+ },
+}
diff --git a/crates/tor-dirmgr/src/storage/sqlite.rs b/crates/tor-dirmgr/src/storage/sqlite.rs
index c58ac9953..8655d7a32 100644
--- a/crates/tor-dirmgr/src/storage/sqlite.rs
+++ b/crates/tor-dirmgr/src/storage/sqlite.rs
@@ -5,6 +5,7 @@
use super::ExpirationConfig;
use crate::docmeta::{AuthCertMeta, ConsensusMeta};
+use crate::err::ReadOnlyStorageError;
use crate::storage::{InputString, Store};
use crate::{Error, Result};
@@ -108,7 +109,7 @@ impl SqliteStore {
OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE
};
let conn = rusqlite::Connection::open_with_flags(&sqlpath, flags)?;
- let mut store = SqliteStore::from_conn(conn, blob_dir)?;
+ let mut store = SqliteStore::from_conn_internal(conn, blob_dir, readonly)?;
store.sql_path = Some(sqlpath);
store.lockfile = Some(lockfile);
Ok(store)
@@ -121,7 +122,20 @@ impl SqliteStore {
///
/// Note: `blob_dir` must not be used for anything other than storing the blobs associated with
/// this database, since we will freely remove unreferenced files from this directory.
- pub(crate) fn from_conn(conn: rusqlite::Connection, blob_dir: CheckedDir) -> Result<Self> {
+ #[cfg(test)]
+ fn from_conn(conn: rusqlite::Connection, blob_dir: CheckedDir) -> Result<Self> {
+ Self::from_conn_internal(conn, blob_dir, false)
+ }
+
+ /// Construct a new SqliteStore from a database connection and a location
+ /// for blob files.
+ ///
+ /// The `readonly` argument specifies whether the database connection should be read-only.
+ fn from_conn_internal(
+ conn: rusqlite::Connection,
+ blob_dir: CheckedDir,
+ readonly: bool,
+ ) -> Result<Self> {
// sqlite (as of Jun 2024) does not enforce foreign keys automatically unless you set this
// pragma on the connection.
conn.pragma_update(None, "foreign_keys", "ON")?;
@@ -133,14 +147,14 @@ impl SqliteStore {
sql_path: None,
};
- result.check_schema()?;
+ result.check_schema(readonly)?;
Ok(result)
}
/// Check whether this database has a schema format we can read, and
/// install or upgrade the schema if necessary.
- fn check_schema(&mut self) -> Result<()> {
+ fn check_schema(&mut self, readonly: bool) -> Result<()> {
let tx = self.conn.transaction()?;
let db_n_tables: u32 = tx.query_row(
"SELECT COUNT(name) FROM sqlite_master
@@ -165,9 +179,14 @@ impl SqliteStore {
};
if !db_exists {
- tx.execute_batch(INSTALL_V0_SCHEMA)?;
- update_schema(&tx, 0)?;
- tx.commit()?;
+ if !readonly {
+ tx.execute_batch(INSTALL_V0_SCHEMA)?;
+ update_schema(&tx, 0)?;
+ tx.commit()?;
+ } else {
+ // The other process should have created the database!
+ return Err(Error::ReadOnlyStorage(ReadOnlyStorageError::NoDatabase));
+ }
return Ok(());
}
@@ -179,8 +198,18 @@ impl SqliteStore {
)?;
if version < SCHEMA_VERSION {
- update_schema(&tx, version)?;
- tx.commit()?;
+ if !readonly {
+ update_schema(&tx, version)?;
+ tx.commit()?;
+ } else {
+ return Err(Error::ReadOnlyStorage(
+ ReadOnlyStorageError::IncompatibleSchema {
+ schema: version,
+ supported: SCHEMA_VERSION,
+ },
+ ));
+ }
+
return Ok(());
} else if readable_by > SCHEMA_VERSION {
return Err(Error::UnrecognizedSchema {