summaryrefslogtreecommitdiff
path: root/src/storage.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/storage.rs')
-rw-r--r--src/storage.rs65
1 files changed, 55 insertions, 10 deletions
diff --git a/src/storage.rs b/src/storage.rs
index cb7fcd9..93cb98c 100644
--- a/src/storage.rs
+++ b/src/storage.rs
@@ -1,4 +1,4 @@
-use crate::model::{Definitions, Entry};
+use crate::model::{Definitions, Entry, EntryStatus};
use anyhow::{Context, Result, ensure};
use rusqlite::{Connection, OptionalExtension, params};
use std::{
@@ -70,7 +70,30 @@ impl Storage {
);
crate::subscription::subscription_url(&subscription.url)?;
}
+ #[derive(serde::Deserialize)]
+ #[serde(deny_unknown_fields)]
+ struct GuardInput {
+ version: u32,
+ enabled: bool,
+ }
+ let guard_path = config_dir.join("guard.toml");
+ let guard_enabled = if guard_path.exists() {
+ let guard: GuardInput = toml::from_str(&fs::read_to_string(&guard_path)?)
+ .context("Corrupt guard state; use explicit recover --disable")?;
+ ensure!(
+ guard.version == 1,
+ "Unsupported guard state; use explicit recover --disable"
+ );
+ guard.enabled
+ } else {
+ false
+ };
let path = data_dir.join("state.sqlite3");
+ let database_existed = path.exists();
+ ensure!(
+ database_existed || !guard_enabled,
+ "Durable state is missing while enabled protection is requested; use explicit recover --disable"
+ );
if path.exists() {
ensure!(
fs::symlink_metadata(&path)?.is_file(),
@@ -84,6 +107,10 @@ impl Storage {
let version: u32 = db.query_row("PRAGMA user_version", [], |r| r.get(0))?;
ensure!(version <= 1, "Unsupported database version");
if version == 0 {
+ ensure!(
+ !database_existed,
+ "Existing database has no state schema; use explicit recover --disable"
+ );
db.execute_batch("BEGIN IMMEDIATE;
CREATE TABLE entries (subscription_id TEXT NOT NULL, ordinal INTEGER NOT NULL, id TEXT PRIMARY KEY, data TEXT NOT NULL);
CREATE TABLE state (singleton INTEGER PRIMARY KEY CHECK(singleton=1), enabled INTEGER NOT NULL CHECK(enabled IN (0,1)), selected TEXT, generation INTEGER NOT NULL);
@@ -156,13 +183,7 @@ impl Storage {
let values = query
.query_map([subscription_id], |r| r.get::<_, String>(0))?
.collect::<rusqlite::Result<Vec<_>>>()?;
- values
- .iter()
- .map(|v| {
- serde_json::from_str(v)
- .context("Corrupt saved configuration; explicit recovery is required")
- })
- .collect()
+ values.iter().map(|v| decode_entry(v)).collect()
}
pub fn find_entry(&self, id: &str) -> Result<Option<Entry>> {
@@ -171,7 +192,7 @@ impl Storage {
r.get::<_, String>(0)
})
.optional()?
- .map(|v| serde_json::from_str(&v).context("Corrupt saved configuration"))
+ .map(|v| decode_entry(&v))
.transpose()
}
@@ -254,7 +275,17 @@ pub fn persist_recovery_disabled(data_dir: &Path) -> Result<()> {
let db = Connection::open(&path)?;
fs::set_permissions(&path, fs::Permissions::from_mode(0o600))?;
db.busy_timeout(std::time::Duration::from_secs(5))?;
- db.execute_batch("PRAGMA synchronous=FULL; BEGIN EXCLUSIVE;")?;
+ db.execute_batch("PRAGMA synchronous=FULL;")?;
+ let version: u32 = db.query_row("PRAGMA user_version", [], |row| row.get(0))?;
+ ensure!(
+ version <= 1,
+ "Unknown database schema; repair it before explicit recovery"
+ );
+ db.execute_batch("BEGIN EXCLUSIVE;
+ CREATE TABLE IF NOT EXISTS entries (subscription_id TEXT NOT NULL, ordinal INTEGER NOT NULL, id TEXT PRIMARY KEY, data TEXT NOT NULL);
+ CREATE TABLE IF NOT EXISTS state (singleton INTEGER PRIMARY KEY CHECK(singleton=1), enabled INTEGER NOT NULL CHECK(enabled IN (0,1)), selected TEXT, generation INTEGER NOT NULL);
+ INSERT OR IGNORE INTO state VALUES (1,0,NULL,0);
+ PRAGMA user_version=1;")?;
db.execute(
"UPDATE state SET enabled=0,generation=generation+1 WHERE singleton=1",
[],
@@ -262,3 +293,17 @@ pub fn persist_recovery_disabled(data_dir: &Path) -> Result<()> {
db.execute_batch("COMMIT")?;
Ok(())
}
+
+fn decode_entry(value: &str) -> Result<Entry> {
+ let entry: Entry = serde_json::from_str(value).context("Corrupt saved configuration")?;
+ ensure!(
+ entry.status == EntryStatus::Supported
+ && entry.connection.is_some()
+ && entry.explanation.is_none()
+ || entry.status != EntryStatus::Supported
+ && entry.connection.is_none()
+ && entry.explanation.is_some(),
+ "Saved entry has contradictory validation state; explicit recovery is required"
+ );
+ Ok(entry)
+}