From b241f98920162b67b436ad0ab9f4bf7dc7958822 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Wed, 16 Dec 2015 23:33:56 -0500 Subject: [PATCH] Add test and check for SQLite being in single-threaded mode --- Cargo.toml | 3 +++ Changelog.md | 3 +++ src/error.rs | 7 ++++++ src/lib.rs | 24 +++++++++++++++++++++ tests/deny_single_threaded_sqlite_config.rs | 20 +++++++++++++++++ 5 files changed, 57 insertions(+) create mode 100644 tests/deny_single_threaded_sqlite_config.rs diff --git a/Cargo.toml b/Cargo.toml index 38200cb..d112f86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,6 @@ version = "0.3.0" [[test]] name = "config_log" harness = false + +[[test]] +name = "deny_single_threaded_sqlite_config" diff --git a/Changelog.md b/Changelog.md index f041681..490a62c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,9 @@ `SqliteFailure` cases (which still include the error code but also include a Rust-friendlier enum as well), and rusqlite-level errors are captured in other cases. Because of this change, `SqliteError` no longer implements `PartialEq`. +* BREAKING CHANGE: When opening a new detection, rusqlite now detects if SQLite was compiled or + configured for single-threaded use only; if it was, connection attempts will fail. If this + affects you, please open an issue. * BREAKING CHANGE: `SqliteTransactionDeferred`, `SqliteTransactionImmediate`, and `SqliteTransactionExclusive` are no longer exported. Instead, use `TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and diff --git a/src/error.rs b/src/error.rs index 810fb65..9670800 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,6 +14,10 @@ pub enum Error { /// An error from an underlying SQLite call. SqliteFailure(ffi::Error, Option), + /// Error reported when attempting to open a connection when SQLite was configured to + /// allow single-threaded use only. + SqliteSingleThreadedMode, + /// An error case available for implementors of the `FromSql` trait. FromSqlConversionFailure(Box), @@ -77,6 +81,7 @@ impl fmt::Display for Error { match self { &Error::SqliteFailure(ref err, None) => err.fmt(f), &Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s), + &Error::SqliteSingleThreadedMode => write!(f, "SQLite was compiled or configured for single-threaded use only"), &Error::FromSqlConversionFailure(ref err) => err.fmt(f), &Error::Utf8Error(ref err) => err.fmt(f), &Error::NulError(ref err) => err.fmt(f), @@ -101,6 +106,7 @@ impl error::Error for Error { match self { &Error::SqliteFailure(ref err, None) => err.description(), &Error::SqliteFailure(_, Some(ref s)) => s, + &Error::SqliteSingleThreadedMode => "SQLite was compiled or configured for single-threaded use only", &Error::FromSqlConversionFailure(ref err) => err.description(), &Error::Utf8Error(ref err) => err.description(), &Error::InvalidParameterName(_) => "invalid parameter name", @@ -122,6 +128,7 @@ impl error::Error for Error { fn cause(&self) -> Option<&error::Error> { match self { &Error::SqliteFailure(ref err, _) => Some(err), + &Error::SqliteSingleThreadedMode => None, &Error::FromSqlConversionFailure(ref err) => Some(&**err), &Error::Utf8Error(ref err) => Some(err), &Error::NulError(ref err) => Some(err), diff --git a/src/lib.rs b/src/lib.rs index 7ca67e1..0b77690 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -546,6 +546,30 @@ impl InnerConnection { flags: OpenFlags) -> Result { unsafe { + // Before opening the database, we need to check that SQLite hasn't been + // compiled or configured to be in single-threaded mode. If it has, we're + // exposing a very unsafe API to Rust, so refuse to open connections at all. + // Unfortunately, the check for this is quite gross. sqlite3_threadsafe() only + // returns how SQLite was _compiled_; there is no public API to check whether + // someone called sqlite3_config() to set single-threaded mode. We can cheat + // by trying to allocate a mutex, though; in single-threaded mode due to + // compilation settings, the magic value 8 is returned (see the definition of + // sqlite3_mutex_alloc at https://github.com/mackyle/sqlite/blob/master/src/mutex.h); + // in single-threaded mode due to sqlite3_config(), the magic value 8 is also + // returned (see the definition of noopMutexAlloc at + // https://github.com/mackyle/sqlite/blob/master/src/mutex_noop.c). + const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8; + let mutex_ptr = ffi::sqlite3_mutex_alloc(0); + let is_singlethreaded = if mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC { + true + } else { + false + }; + ffi::sqlite3_mutex_free(mutex_ptr); + if is_singlethreaded { + return Err(Error::SqliteSingleThreadedMode); + } + let mut db: *mut ffi::sqlite3 = mem::uninitialized(); let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null()); if r != ffi::SQLITE_OK { diff --git a/tests/deny_single_threaded_sqlite_config.rs b/tests/deny_single_threaded_sqlite_config.rs new file mode 100644 index 0000000..8178739 --- /dev/null +++ b/tests/deny_single_threaded_sqlite_config.rs @@ -0,0 +1,20 @@ +//! Ensure we reject connections when SQLite is in single-threaded mode, as it +//! would violate safety if multiple Rust threads tried to use connections. + +extern crate rusqlite; +extern crate libsqlite3_sys as ffi; + +use rusqlite::Connection; + +#[test] +fn test_error_when_singlethread_mode() { + // put SQLite into single-threaded mode + unsafe { + // 1 == SQLITE_CONFIG_SINGLETHREAD + assert_eq!(ffi::sqlite3_config(1), ffi::SQLITE_OK); + println!("{}", ffi::sqlite3_mutex_alloc(0) as u64); + } + + let result = Connection::open_in_memory(); + assert!(result.is_err()); +}