use std::ffi::CStr; use std::os::raw::{c_char, c_int}; #[cfg(feature = "load_extension")] use std::path::Path; use std::ptr; use std::str; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; use super::ffi; use super::str_for_sqlite; use super::{Connection, InterruptHandle, OpenFlags, PrepFlags, Result}; use crate::error::{error_from_handle, error_from_sqlite_code, error_with_offset, Error}; use crate::raw_statement::RawStatement; use crate::statement::Statement; use crate::version::version_number; pub struct InnerConnection { pub db: *mut ffi::sqlite3, // It's unsafe to call `sqlite3_close` while another thread is performing // a `sqlite3_interrupt`, and vice versa, so we take this mutex during // those functions. This protects a copy of the `db` pointer (which is // cleared on closing), however the main copy, `db`, is unprotected. // Otherwise, a long running query would prevent calling interrupt, as // interrupt would only acquire the lock after the query's completion. interrupt_lock: Arc>, #[cfg(feature = "hooks")] pub free_commit_hook: Option, #[cfg(feature = "hooks")] pub free_rollback_hook: Option, #[cfg(feature = "hooks")] pub free_update_hook: Option, #[cfg(feature = "hooks")] pub progress_handler: Option bool + Send>>, #[cfg(feature = "hooks")] pub authorizer: Option, owned: bool, } unsafe impl Send for InnerConnection {} impl InnerConnection { #[allow(clippy::mutex_atomic, clippy::arc_with_non_send_sync)] // See unsafe impl Send / Sync for InterruptHandle #[inline] pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> InnerConnection { InnerConnection { db, interrupt_lock: Arc::new(Mutex::new(db)), #[cfg(feature = "hooks")] free_commit_hook: None, #[cfg(feature = "hooks")] free_rollback_hook: None, #[cfg(feature = "hooks")] free_update_hook: None, #[cfg(feature = "hooks")] progress_handler: None, #[cfg(feature = "hooks")] authorizer: None, owned, } } pub fn open_with_flags( c_path: &CStr, flags: OpenFlags, vfs: Option<&CStr>, ) -> Result { ensure_safe_sqlite_threading_mode()?; // Replicate the check for sane open flags from SQLite, because the check in // SQLite itself wasn't added until version 3.7.3. debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_ONLY.bits(), 0x02); debug_assert_eq!(1 << OpenFlags::SQLITE_OPEN_READ_WRITE.bits(), 0x04); debug_assert_eq!( 1 << (OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE).bits(), 0x40 ); if (1 << (flags.bits() & 0x7)) & 0x46 == 0 { return Err(Error::SqliteFailure( ffi::Error::new(ffi::SQLITE_MISUSE), None, )); } let z_vfs = match vfs { Some(c_vfs) => c_vfs.as_ptr(), None => ptr::null(), }; unsafe { let mut db: *mut ffi::sqlite3 = ptr::null_mut(); let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), z_vfs); if r != ffi::SQLITE_OK { let e = if db.is_null() { error_from_sqlite_code(r, Some(c_path.to_string_lossy().to_string())) } else { let mut e = error_from_handle(db, r); if let Error::SqliteFailure( ffi::Error { code: ffi::ErrorCode::CannotOpen, .. }, Some(msg), ) = e { e = Error::SqliteFailure( ffi::Error::new(r), Some(format!("{msg}: {}", c_path.to_string_lossy())), ); } ffi::sqlite3_close(db); e }; return Err(e); } // attempt to turn on extended results code; don't fail if we can't. ffi::sqlite3_extended_result_codes(db, 1); let r = ffi::sqlite3_busy_timeout(db, 5000); if r != ffi::SQLITE_OK { let e = error_from_handle(db, r); ffi::sqlite3_close(db); return Err(e); } Ok(InnerConnection::new(db, true)) } } #[inline] pub fn db(&self) -> *mut ffi::sqlite3 { self.db } #[inline] pub fn decode_result(&self, code: c_int) -> Result<()> { unsafe { InnerConnection::decode_result_raw(self.db(), code) } } #[inline] unsafe fn decode_result_raw(db: *mut ffi::sqlite3, code: c_int) -> Result<()> { if code == ffi::SQLITE_OK { Ok(()) } else { Err(error_from_handle(db, code)) } } #[allow(clippy::mutex_atomic)] pub fn close(&mut self) -> Result<()> { if self.db.is_null() { return Ok(()); } self.remove_hooks(); let mut shared_handle = self.interrupt_lock.lock().unwrap(); assert!( !shared_handle.is_null(), "Bug: Somehow interrupt_lock was cleared before the DB was closed" ); if !self.owned { self.db = ptr::null_mut(); return Ok(()); } unsafe { let r = ffi::sqlite3_close(self.db); // Need to use _raw because _guard has a reference out, and // decode_result takes &mut self. let r = InnerConnection::decode_result_raw(self.db, r); if r.is_ok() { *shared_handle = ptr::null_mut(); self.db = ptr::null_mut(); } r } } #[inline] pub fn get_interrupt_handle(&self) -> InterruptHandle { InterruptHandle { db_lock: Arc::clone(&self.interrupt_lock), } } #[inline] #[cfg(feature = "load_extension")] pub unsafe fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> { let r = ffi::sqlite3_enable_load_extension(self.db, onoff); self.decode_result(r) } #[cfg(feature = "load_extension")] pub unsafe fn load_extension( &self, dylib_path: &Path, entry_point: Option<&str>, ) -> Result<()> { let dylib_str = super::path_to_cstring(dylib_path)?; let mut errmsg: *mut c_char = ptr::null_mut(); let r = if let Some(entry_point) = entry_point { let c_entry = crate::str_to_cstring(entry_point)?; ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), c_entry.as_ptr(), &mut errmsg) } else { ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), ptr::null(), &mut errmsg) }; if r == ffi::SQLITE_OK { Ok(()) } else { let message = super::errmsg_to_string(errmsg); ffi::sqlite3_free(errmsg.cast::()); Err(error_from_sqlite_code(r, Some(message))) } } #[inline] pub fn last_insert_rowid(&self) -> i64 { unsafe { ffi::sqlite3_last_insert_rowid(self.db()) } } pub fn prepare<'a>( &mut self, conn: &'a Connection, sql: &str, flags: PrepFlags, ) -> Result> { let mut c_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut(); let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?; let mut c_tail: *const c_char = ptr::null(); // TODO sqlite3_prepare_v3 (https://sqlite.org/c3ref/c_prepare_normalize.html) // 3.20.0, #728 #[cfg(not(feature = "unlock_notify"))] let r = unsafe { self.prepare_(c_sql, len, flags, &mut c_stmt, &mut c_tail) }; #[cfg(feature = "unlock_notify")] let r = unsafe { use crate::unlock_notify; let mut rc; loop { rc = self.prepare_(c_sql, len, flags, &mut c_stmt, &mut c_tail); if !unlock_notify::is_locked(self.db, rc) { break; } rc = unlock_notify::wait_for_unlock_notify(self.db); if rc != ffi::SQLITE_OK { break; } } rc }; // If there is an error, *ppStmt is set to NULL. if r != ffi::SQLITE_OK { return Err(unsafe { error_with_offset(self.db, r, sql) }); } // If the input text contains no SQL (if the input is an empty string or a // comment) then *ppStmt is set to NULL. let tail = if c_tail.is_null() { 0 } else { let n = (c_tail as isize) - (c_sql as isize); if n <= 0 || n >= len as isize { 0 } else { n as usize } }; Ok(Statement::new(conn, unsafe { RawStatement::new(c_stmt, tail) })) } #[inline] #[cfg(not(feature = "modern_sqlite"))] unsafe fn prepare_( &self, z_sql: *const c_char, n_byte: c_int, _: PrepFlags, pp_stmt: *mut *mut ffi::sqlite3_stmt, pz_tail: *mut *const c_char, ) -> c_int { ffi::sqlite3_prepare_v2(self.db(), z_sql, n_byte, pp_stmt, pz_tail) } #[inline] #[cfg(feature = "modern_sqlite")] unsafe fn prepare_( &self, z_sql: *const c_char, n_byte: c_int, flags: PrepFlags, pp_stmt: *mut *mut ffi::sqlite3_stmt, pz_tail: *mut *const c_char, ) -> c_int { ffi::sqlite3_prepare_v3(self.db(), z_sql, n_byte, flags.bits(), pp_stmt, pz_tail) } #[inline] pub fn changes(&self) -> u64 { #[cfg(not(feature = "modern_sqlite"))] unsafe { ffi::sqlite3_changes(self.db()) as u64 } #[cfg(feature = "modern_sqlite")] // 3.37.0 unsafe { ffi::sqlite3_changes64(self.db()) as u64 } } #[inline] pub fn is_autocommit(&self) -> bool { unsafe { ffi::sqlite3_get_autocommit(self.db()) != 0 } } pub fn is_busy(&self) -> bool { let db = self.db(); unsafe { let mut stmt = ffi::sqlite3_next_stmt(db, ptr::null_mut()); while !stmt.is_null() { if ffi::sqlite3_stmt_busy(stmt) != 0 { return true; } stmt = ffi::sqlite3_next_stmt(db, stmt); } } false } pub fn cache_flush(&mut self) -> Result<()> { crate::error::check(unsafe { ffi::sqlite3_db_cacheflush(self.db()) }) } #[cfg(not(feature = "hooks"))] #[inline] fn remove_hooks(&mut self) {} pub fn db_readonly(&self, db_name: super::DatabaseName<'_>) -> Result { let name = db_name.as_cstring()?; let r = unsafe { ffi::sqlite3_db_readonly(self.db, name.as_ptr()) }; match r { 0 => Ok(false), 1 => Ok(true), -1 => Err(Error::SqliteFailure( ffi::Error::new(ffi::SQLITE_MISUSE), Some(format!("{db_name:?} is not the name of a database")), )), _ => Err(error_from_sqlite_code( r, Some("Unexpected result".to_owned()), )), } } #[cfg(feature = "modern_sqlite")] // 3.37.0 pub fn txn_state( &self, db_name: Option>, ) -> Result { let r = if let Some(ref name) = db_name { let name = name.as_cstring()?; unsafe { ffi::sqlite3_txn_state(self.db, name.as_ptr()) } } else { unsafe { ffi::sqlite3_txn_state(self.db, ptr::null()) } }; match r { 0 => Ok(super::transaction::TransactionState::None), 1 => Ok(super::transaction::TransactionState::Read), 2 => Ok(super::transaction::TransactionState::Write), -1 => Err(Error::SqliteFailure( ffi::Error::new(ffi::SQLITE_MISUSE), Some(format!("{db_name:?} is not the name of a valid schema")), )), _ => Err(error_from_sqlite_code( r, Some("Unexpected result".to_owned()), )), } } #[inline] #[cfg(feature = "release_memory")] pub fn release_memory(&self) -> Result<()> { self.decode_result(unsafe { ffi::sqlite3_db_release_memory(self.db) }) } } impl Drop for InnerConnection { #[allow(unused_must_use)] #[inline] fn drop(&mut self) { self.close(); } } #[cfg(not(any(target_arch = "wasm32", feature = "loadable_extension")))] static SQLITE_INIT: std::sync::Once = std::sync::Once::new(); pub static BYPASS_SQLITE_INIT: AtomicBool = AtomicBool::new(false); // threading mode checks are not necessary (and do not work) on target // platforms that do not have threading (such as webassembly) #[cfg(target_arch = "wasm32")] fn ensure_safe_sqlite_threading_mode() -> Result<()> { Ok(()) } #[cfg(not(any(target_arch = "wasm32")))] fn ensure_safe_sqlite_threading_mode() -> Result<()> { // Ensure SQLite was compiled in threadsafe mode. if unsafe { ffi::sqlite3_threadsafe() == 0 } { return Err(Error::SqliteSingleThreadedMode); } // Now we know SQLite is _capable_ of being in Multi-thread of Serialized mode, // but it's possible someone configured it to be in Single-thread mode // before calling into us. That would mean we're exposing an unsafe API via // a safe one (in Rust terminology), which is no good. We have two options // to protect against this, depending on the version of SQLite we're linked // with: // // 1. If we're on 3.7.0 or later, we can ask SQLite for a mutex and check for // the magic value 8. This isn't documented, but it's what SQLite // returns for its mutex allocation function in Single-thread mode. // 2. If we're prior to SQLite 3.7.0, AFAIK there's no way to check the // threading mode. The check we perform for >= 3.7.0 will segfault. // Instead, we insist on being able to call sqlite3_config and // sqlite3_initialize ourself, ensuring we know the threading // mode. This will fail if someone else has already initialized SQLite // even if they initialized it safely. That's not ideal either, which is // why we expose bypass_sqlite_initialization above. if version_number() >= 3_007_000 { const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8; let is_singlethreaded = unsafe { let mutex_ptr = ffi::sqlite3_mutex_alloc(0); let is_singlethreaded = mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC; ffi::sqlite3_mutex_free(mutex_ptr); is_singlethreaded }; if is_singlethreaded { Err(Error::SqliteSingleThreadedMode) } else { Ok(()) } } else { #[cfg(not(feature = "loadable_extension"))] SQLITE_INIT.call_once(|| { use std::sync::atomic::Ordering; if BYPASS_SQLITE_INIT.load(Ordering::Relaxed) { return; } unsafe { assert!(ffi::sqlite3_config(ffi::SQLITE_CONFIG_MULTITHREAD) == ffi::SQLITE_OK && ffi::sqlite3_initialize() == ffi::SQLITE_OK, "Could not ensure safe initialization of SQLite.\n\ To fix this, either:\n\ * Upgrade SQLite to at least version 3.7.0\n\ * Ensure that SQLite has been initialized in Multi-thread or Serialized mode and call\n\ rusqlite::bypass_sqlite_initialization() prior to your first connection attempt." ); } }); Ok(()) } }