diff --git a/src/bin/ayb_isolated_runner.rs b/src/bin/ayb_isolated_runner.rs index edb96e3..7b65840 100644 --- a/src/bin/ayb_isolated_runner.rs +++ b/src/bin/ayb_isolated_runner.rs @@ -1,10 +1,11 @@ use ayb::hosted_db::sqlite::query_sqlite; +use ayb::hosted_db::QueryMode; use std::env; use std::path::PathBuf; /// This binary runs a query against a database and returns the /// result in QueryResults format. To run it, you would type: -/// $ ayb_isolated_runner database.sqlite SELECT xyz FROM ... +/// $ ayb_isolated_runner database.sqlite [0=read-only|1=read-write] SELECT xyz FROM ... /// /// This command is meant to be run inside a sandbox that isolates /// parallel invocations of the command from accessing each @@ -13,8 +14,14 @@ use std::path::PathBuf; fn main() -> Result<(), serde_json::Error> { let args: Vec = env::args().collect(); let db_file = &args[1]; + let query_mode = QueryMode::try_from( + args[2] + .parse::() + .expect("query mode should be an integer"), + ) + .expect("query mode should be 0 or 1"); let query = (args[2..]).to_vec(); - let result = query_sqlite(&PathBuf::from(db_file), &query.join(" "), false); + let result = query_sqlite(&PathBuf::from(db_file), &query.join(" "), false, query_mode); match result { Ok(result) => println!("{}", serde_json::to_string(&result)?), Err(error) => eprintln!("{}", serde_json::to_string(&error)?), diff --git a/src/hosted_db.rs b/src/hosted_db.rs index eb8ca4c..4105804 100644 --- a/src/hosted_db.rs +++ b/src/hosted_db.rs @@ -7,11 +7,23 @@ use crate::error::AybError; use crate::formatting::TabularFormatter; use crate::hosted_db::sqlite::potentially_isolated_sqlite_query; use crate::server::config::AybConfigIsolation; +use crate::try_from_i16; use prettytable::{Cell, Row, Table}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::vec::Vec; +#[repr(i16)] +pub enum QueryMode { + ReadOnly = 0, + ReadWrite = 1, +} + +try_from_i16!(QueryMode, { + 0 => QueryMode::ReadOnly, + 1 => QueryMode::ReadWrite +}); + #[derive(Serialize, Debug, Deserialize)] pub struct QueryResult { pub fields: Vec, @@ -48,9 +60,12 @@ pub async fn run_query( query: &str, db_type: &DBType, isolation: &Option, + query_mode: QueryMode, ) -> Result { match db_type { - DBType::Sqlite => Ok(potentially_isolated_sqlite_query(path, query, isolation).await?), + DBType::Sqlite => { + Ok(potentially_isolated_sqlite_query(path, query, isolation, query_mode).await?) + } _ => Err(AybError::Other { message: "Unsupported DB type".to_string(), }), diff --git a/src/hosted_db/sqlite.rs b/src/hosted_db/sqlite.rs index dc0f6a5..c7f22fa 100644 --- a/src/hosted_db/sqlite.rs +++ b/src/hosted_db/sqlite.rs @@ -1,5 +1,5 @@ use crate::error::AybError; -use crate::hosted_db::{sandbox::run_in_sandbox, QueryResult}; +use crate::hosted_db::{sandbox::run_in_sandbox, QueryMode, QueryResult}; use crate::server::config::AybConfigIsolation; use rusqlite::config::DbConfig; use rusqlite::limits::Limit; @@ -14,8 +14,19 @@ pub fn query_sqlite( path: &PathBuf, query: &str, allow_unsafe: bool, + query_mode: QueryMode, ) -> Result { - let conn = rusqlite::Connection::open(path)?; + // The flags below are the default `open` flags in `rusqlite` + // except for `..READ_ONLY` and `..READ_WRITE`. + let mut open_flags = rusqlite::OpenFlags::SQLITE_OPEN_CREATE + | rusqlite::OpenFlags::SQLITE_OPEN_URI + | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX; + open_flags = open_flags + | match query_mode { + QueryMode::ReadOnly => rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY, + QueryMode::ReadWrite => rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE, + }; + let conn = rusqlite::Connection::open_with_flags(path, open_flags)?; if !allow_unsafe { // Disable the usage of ATTACH @@ -63,6 +74,7 @@ pub async fn potentially_isolated_sqlite_query( path: &PathBuf, query: &str, isolation: &Option, + query_mode: QueryMode, ) -> Result { if let Some(isolation) = isolation { let result = run_in_sandbox(Path::new(&isolation.nsjail_path), path, query).await?; @@ -88,5 +100,5 @@ pub async fn potentially_isolated_sqlite_query( } // No isolation configuration, so run the query without a sandbox. - query_sqlite(path, query, false) + query_sqlite(path, query, false, query_mode) } diff --git a/src/server/endpoints/query.rs b/src/server/endpoints/query.rs index 5bccab5..651a694 100644 --- a/src/server/endpoints/query.rs +++ b/src/server/endpoints/query.rs @@ -3,7 +3,7 @@ use crate::ayb_db::models::{DBType, InstantiatedEntity}; use crate::error::AybError; use crate::hosted_db::paths::current_database_path; -use crate::hosted_db::{run_query, QueryResult}; +use crate::hosted_db::{run_query, QueryMode, QueryResult}; use crate::http::structs::EntityDatabasePath; use crate::server::config::AybConfig; use crate::server::permissions::can_query; @@ -26,7 +26,15 @@ async fn query( if can_query(&authenticated_entity, &database) { let db_type = DBType::try_from(database.db_type)?; let db_path = current_database_path(entity_slug, database_slug, &ayb_config.data_path)?; - let result = run_query(&db_path, &query, &db_type, &ayb_config.isolation).await?; + // TODO(marcua): Determine read-only or read-write + let result = run_query( + &db_path, + &query, + &db_type, + &ayb_config.isolation, + QueryMode::ReadWrite, + ) + .await?; Ok(web::Json(result)) } else { Err(AybError::Other { diff --git a/src/server/snapshots/execution.rs b/src/server/snapshots/execution.rs index 56e3893..8090be2 100644 --- a/src/server/snapshots/execution.rs +++ b/src/server/snapshots/execution.rs @@ -5,6 +5,7 @@ use crate::hosted_db::paths::{ pathbuf_to_parent, }; use crate::hosted_db::sqlite::query_sqlite; +use crate::hosted_db::QueryMode; use crate::server::config::{AybConfig, SqliteSnapshotMethod}; use crate::server::snapshots::hashes::hash_db_directory; use crate::server::snapshots::models::{Snapshot, SnapshotType}; @@ -164,13 +165,19 @@ pub async fn snapshot_database( // Run in unsafe mode to allow backup process to // attach to destination database. true, + QueryMode::ReadWrite, )?; if !result.rows.is_empty() { return Err(AybError::SnapshotError { message: format!("Unexpected snapshot result: {:?}", result), }); } - let result = query_sqlite(&snapshot_path, "PRAGMA integrity_check;", false)?; + let result = query_sqlite( + &snapshot_path, + "PRAGMA integrity_check;", + false, + QueryMode::ReadWrite, + )?; if result.fields.len() != 1 || result.rows.len() != 1 || result.rows[0][0] != Some("ok".to_string())