Merge pull request #105 from jgallagher/check-sqlite-threading-mode

Add test and check for SQLite being in single-threaded mode
This commit is contained in:
John Gallagher 2015-12-17 00:02:10 -05:00
commit 94492ebef3
5 changed files with 57 additions and 0 deletions

View File

@ -36,3 +36,6 @@ version = "0.3.0"
[[test]] [[test]]
name = "config_log" name = "config_log"
harness = false harness = false
[[test]]
name = "deny_single_threaded_sqlite_config"

View File

@ -6,6 +6,9 @@
`SqliteFailure` cases (which still include the error code but also include a Rust-friendlier `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, enum as well), and rusqlite-level errors are captured in other cases. Because of this change,
`SqliteError` no longer implements `PartialEq`. `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 * BREAKING CHANGE: `SqliteTransactionDeferred`, `SqliteTransactionImmediate`, and
`SqliteTransactionExclusive` are no longer exported. Instead, use `SqliteTransactionExclusive` are no longer exported. Instead, use
`TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and `TransactionBehavior::Deferred`, `TransactionBehavior::Immediate`, and

View File

@ -14,6 +14,10 @@ pub enum Error {
/// An error from an underlying SQLite call. /// An error from an underlying SQLite call.
SqliteFailure(ffi::Error, Option<String>), SqliteFailure(ffi::Error, Option<String>),
/// 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. /// An error case available for implementors of the `FromSql` trait.
FromSqlConversionFailure(Box<error::Error + Send + Sync>), FromSqlConversionFailure(Box<error::Error + Send + Sync>),
@ -77,6 +81,7 @@ impl fmt::Display for Error {
match self { match self {
&Error::SqliteFailure(ref err, None) => err.fmt(f), &Error::SqliteFailure(ref err, None) => err.fmt(f),
&Error::SqliteFailure(_, Some(ref s)) => write!(f, "{}", s), &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::FromSqlConversionFailure(ref err) => err.fmt(f),
&Error::Utf8Error(ref err) => err.fmt(f), &Error::Utf8Error(ref err) => err.fmt(f),
&Error::NulError(ref err) => err.fmt(f), &Error::NulError(ref err) => err.fmt(f),
@ -101,6 +106,7 @@ impl error::Error for Error {
match self { match self {
&Error::SqliteFailure(ref err, None) => err.description(), &Error::SqliteFailure(ref err, None) => err.description(),
&Error::SqliteFailure(_, Some(ref s)) => s, &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::FromSqlConversionFailure(ref err) => err.description(),
&Error::Utf8Error(ref err) => err.description(), &Error::Utf8Error(ref err) => err.description(),
&Error::InvalidParameterName(_) => "invalid parameter name", &Error::InvalidParameterName(_) => "invalid parameter name",
@ -122,6 +128,7 @@ impl error::Error for Error {
fn cause(&self) -> Option<&error::Error> { fn cause(&self) -> Option<&error::Error> {
match self { match self {
&Error::SqliteFailure(ref err, _) => Some(err), &Error::SqliteFailure(ref err, _) => Some(err),
&Error::SqliteSingleThreadedMode => None,
&Error::FromSqlConversionFailure(ref err) => Some(&**err), &Error::FromSqlConversionFailure(ref err) => Some(&**err),
&Error::Utf8Error(ref err) => Some(err), &Error::Utf8Error(ref err) => Some(err),
&Error::NulError(ref err) => Some(err), &Error::NulError(ref err) => Some(err),

View File

@ -546,6 +546,30 @@ impl InnerConnection {
flags: OpenFlags) flags: OpenFlags)
-> Result<InnerConnection> { -> Result<InnerConnection> {
unsafe { 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 mut db: *mut ffi::sqlite3 = mem::uninitialized();
let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null()); let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), ptr::null());
if r != ffi::SQLITE_OK { if r != ffi::SQLITE_OK {

View File

@ -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());
}