use std::cell::RefCell;
use std::ffi::c_int;
use rusqlite::ffi::{
sqlite3_file, sqlite3_filename, sqlite3_io_methods, sqlite3_vfs, sqlite3_vfs_find,
SQLITE_IOERR, SQLITE_OK,
};
type XOpen = unsafe extern "C" fn(
*mut sqlite3_vfs,
sqlite3_filename,
*mut sqlite3_file,
c_int,
*mut c_int,
) -> c_int;
thread_local! {
static ORIGINALS: RefCell<Vec<(*mut sqlite3_vfs, XOpen)>> = const { RefCell::new(Vec::new()) };
static METHODS: RefCell<Vec<(*const sqlite3_io_methods, *mut sqlite3_io_methods)>> =
const { RefCell::new(Vec::new()) };
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub(crate) fn disable_foreign_lock_check() -> Result<(), String> {
let vfs = unsafe { sqlite3_vfs_find(std::ptr::null()) };
if vfs.is_null() {
return Err("client.storage_unavailable: no SQLite VFS is registered".to_owned());
}
patch_vfs(vfs)
}
fn patch_vfs(vfs: *mut sqlite3_vfs) -> Result<(), String> {
if ORIGINALS.with(|originals| {
originals
.borrow()
.iter()
.any(|(patched, _)| *patched == vfs)
}) {
return Ok(());
}
let original = unsafe { (*vfs).xOpen }
.ok_or_else(|| "client.storage_unavailable: the SQLite VFS has no xOpen".to_owned())?;
ORIGINALS.with(|originals| originals.borrow_mut().push((vfs, original)));
unsafe { (*vfs).xOpen = Some(crash_recovery_open) };
Ok(())
}
unsafe extern "C" fn crash_recovery_open(
p_vfs: *mut sqlite3_vfs,
name: sqlite3_filename,
file: *mut sqlite3_file,
flags: c_int,
out_flags: *mut c_int,
) -> c_int {
let Some(original) = ORIGINALS.with(|originals| {
originals
.borrow()
.iter()
.find(|(patched, _)| *patched == p_vfs)
.map(|(_, original)| *original)
}) else {
return SQLITE_IOERR;
};
let code = original(p_vfs, name, file, flags, out_flags);
if code == SQLITE_OK && !file.is_null() {
let methods = (*file).pMethods;
if !methods.is_null() {
unsafe { (*file).pMethods = patched_methods(methods) };
}
}
code
}
unsafe fn patched_methods(original: *const sqlite3_io_methods) -> *const sqlite3_io_methods {
METHODS.with(|methods| {
let mut methods = methods.borrow_mut();
if let Some((_, patched)) = methods.iter().find(|(source, _)| *source == original) {
return *patched;
}
let mut patched = unsafe { *original };
patched.xCheckReservedLock = Some(no_foreign_lock);
let patched = Box::into_raw(Box::new(patched));
methods.push((original, patched));
patched
})
}
unsafe extern "C" fn no_foreign_lock(_file: *mut sqlite3_file, reserved: *mut c_int) -> c_int {
if !reserved.is_null() {
unsafe { *reserved = 0 };
}
SQLITE_OK
}
#[cfg(test)]
mod tests {
use super::*;
unsafe extern "C" fn reserved_one(_file: *mut sqlite3_file, reserved: *mut c_int) -> c_int {
unsafe { *reserved = 1 };
SQLITE_OK
}
static FAKE_METHODS: sqlite3_io_methods = sqlite3_io_methods {
iVersion: 1,
xClose: None,
xRead: None,
xWrite: None,
xTruncate: None,
xSync: None,
xFileSize: None,
xLock: None,
xUnlock: None,
xCheckReservedLock: Some(reserved_one),
xFileControl: None,
xSectorSize: None,
xDeviceCharacteristics: None,
xShmMap: None,
xShmLock: None,
xShmBarrier: None,
xShmUnmap: None,
xFetch: None,
xUnfetch: None,
};
unsafe extern "C" fn fake_open(
_vfs: *mut sqlite3_vfs,
_name: sqlite3_filename,
file: *mut sqlite3_file,
_flags: c_int,
_out: *mut c_int,
) -> c_int {
unsafe { (*file).pMethods = &FAKE_METHODS };
SQLITE_OK
}
#[test]
fn the_wrapper_replaces_the_reserved_lock_callback() {
let mut vfs = Box::new(unsafe { *sqlite3_vfs_find(std::ptr::null()) });
vfs.xOpen = Some(fake_open);
patch_vfs(&mut *vfs).expect("patch the fake VFS");
let mut file = sqlite3_file {
pMethods: std::ptr::null(),
};
let mut out = 0;
let open = vfs.xOpen.expect("patched opener");
let code = unsafe { open(&mut *vfs, std::ptr::null(), &mut file, 0, &mut out) };
assert_eq!(code, SQLITE_OK);
let check = unsafe { (*file.pMethods).xCheckReservedLock }.expect("patched callback");
let mut reserved = 1;
assert_eq!(unsafe { check(&mut file, &mut reserved) }, SQLITE_OK);
assert_eq!(reserved, 0);
}
}