From 4642adf046e6a08575e47f67123e42f71706bf83 Mon Sep 17 00:00:00 2001 From: Brian Koropoff Date: Mon, 23 Feb 2026 21:56:12 -0800 Subject: [PATCH 1/2] Use `ptr::write`/`ptr::read` to initialize and destroy file handle --- src/vfs.rs | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/vfs.rs b/src/vfs.rs index 186e432..646e28c 100644 --- a/src/vfs.rs +++ b/src/vfs.rs @@ -7,7 +7,7 @@ use alloc::boxed::Box; use alloc::ffi::CString; use alloc::format; use alloc::string::String; -use core::mem::{self, ManuallyDrop, MaybeUninit, size_of}; +use core::mem::{ManuallyDrop, size_of}; use core::slice; use core::{ ffi::{CStr, c_char, c_int, c_void}, @@ -45,7 +45,7 @@ pub type VfsResult = Result; struct FileWrapper { file: ffi::sqlite3_file, vfs: *mut ffi::sqlite3_vfs, - handle: MaybeUninit, + handle: Handle, } struct AppData { @@ -371,7 +371,6 @@ unsafe extern "C" fn x_open( let vfs = unwrap_vfs!(p_vfs, T)?; let handle = vfs.open(name.as_ref().map(|s| s.as_ref()), opts)?; - let out_file = unwrap_file!(p_file, T)?; let appdata = unwrap_appdata!(p_vfs, T)?; if let Some(p_out_flags) = unsafe { p_out_flags.as_mut() } { @@ -385,9 +384,17 @@ unsafe extern "C" fn x_open( *p_out_flags = out_flags; } - out_file.file.pMethods = &appdata.io_methods; - out_file.vfs = p_vfs; - out_file.handle.write(handle); + let out_file = p_file.cast::>(); + unsafe { + core::ptr::write( + out_file, + FileWrapper { + file: ffi::sqlite3_file { pMethods: &appdata.io_methods }, + vfs: p_vfs, + handle, + }, + ); + } Ok(vars::SQLITE_OK) }) @@ -447,11 +454,9 @@ unsafe extern "C" fn x_full_pathname( unsafe extern "C" fn x_close(p_file: *mut ffi::sqlite3_file) -> c_int { fallible(|| { - let file = unwrap_file!(p_file, T)?; + let file = unsafe { core::ptr::read(p_file.cast::>()) }; let vfs = unwrap_vfs!(file.vfs, T)?; - let handle = mem::replace(&mut file.handle, MaybeUninit::uninit()); - let handle = unsafe { handle.assume_init() }; - vfs.close(handle)?; + vfs.close(file.handle)?; Ok(vars::SQLITE_OK) }) } @@ -468,7 +473,7 @@ unsafe extern "C" fn x_read( let buf_len: usize = i_amt.try_into().map_err(|_| vars::SQLITE_IOERR_READ)?; let offset: usize = i_ofst.try_into().map_err(|_| vars::SQLITE_IOERR_READ)?; let buf = unsafe { slice::from_raw_parts_mut(buf.cast::(), buf_len) }; - vfs.read(unsafe { file.handle.assume_init_mut() }, offset, buf)?; + vfs.read(&mut file.handle, offset, buf)?; Ok(vars::SQLITE_OK) }) } @@ -485,7 +490,7 @@ unsafe extern "C" fn x_write( let buf_len: usize = i_amt.try_into().map_err(|_| vars::SQLITE_IOERR_WRITE)?; let offset: usize = i_ofst.try_into().map_err(|_| vars::SQLITE_IOERR_WRITE)?; let buf = unsafe { slice::from_raw_parts(buf.cast::(), buf_len) }; - let n = vfs.write(unsafe { file.handle.assume_init_mut() }, offset, buf)?; + let n = vfs.write(&mut file.handle, offset, buf)?; if n != buf_len { return Err(vars::SQLITE_IOERR_WRITE); } @@ -501,7 +506,7 @@ unsafe extern "C" fn x_truncate( let file = unwrap_file!(p_file, T)?; let vfs = unwrap_vfs!(file.vfs, T)?; let size: usize = size.try_into().map_err(|_| vars::SQLITE_IOERR_TRUNCATE)?; - vfs.truncate(unsafe { file.handle.assume_init_mut() }, size)?; + vfs.truncate(&mut file.handle, size)?; Ok(vars::SQLITE_OK) }) } @@ -510,7 +515,7 @@ unsafe extern "C" fn x_sync(p_file: *mut ffi::sqlite3_file, _flags: c_in fallible(|| { let file = unwrap_file!(p_file, T)?; let vfs = unwrap_vfs!(file.vfs, T)?; - vfs.sync(unsafe { file.handle.assume_init_mut() })?; + vfs.sync(&mut file.handle)?; Ok(vars::SQLITE_OK) }) } @@ -522,7 +527,7 @@ unsafe extern "C" fn x_file_size( fallible(|| { let file = unwrap_file!(p_file, T)?; let vfs = unwrap_vfs!(file.vfs, T)?; - let size = vfs.file_size(unsafe { file.handle.assume_init_mut() })?; + let size = vfs.file_size(&mut file.handle)?; let p_size = unsafe { p_size.as_mut() }.ok_or(vars::SQLITE_INTERNAL)?; *p_size = size.try_into().map_err(|_| vars::SQLITE_IOERR_FSTAT)?; Ok(vars::SQLITE_OK) @@ -534,7 +539,7 @@ unsafe extern "C" fn x_lock(p_file: *mut ffi::sqlite3_file, raw_lock: c_ let level: LockLevel = raw_lock.into(); let file = unwrap_file!(p_file, T)?; let vfs = unwrap_vfs!(file.vfs, T)?; - vfs.lock(unsafe { file.handle.assume_init_mut() }, level)?; + vfs.lock(&mut file.handle, level)?; Ok(vars::SQLITE_OK) }) } @@ -544,7 +549,7 @@ unsafe extern "C" fn x_unlock(p_file: *mut ffi::sqlite3_file, raw_lock: let level: LockLevel = raw_lock.into(); let file = unwrap_file!(p_file, T)?; let vfs = unwrap_vfs!(file.vfs, T)?; - vfs.unlock(unsafe { file.handle.assume_init_mut() }, level)?; + vfs.unlock(&mut file.handle, level)?; Ok(vars::SQLITE_OK) }) } @@ -557,7 +562,7 @@ unsafe extern "C" fn x_check_reserved_lock( let file = unwrap_file!(p_file, T)?; let vfs = unwrap_vfs!(file.vfs, T)?; unsafe { - *p_out = vfs.check_reserved_lock(file.handle.assume_init_mut())? as c_int; + *p_out = vfs.check_reserved_lock(&mut file.handle)? as c_int; } Ok(vars::SQLITE_OK) }) @@ -598,7 +603,7 @@ unsafe extern "C" fn x_file_control( }; let pragma = Pragma { name: &name, arg: arg.as_deref() }; - let (result, msg) = match vfs.pragma(unsafe { file.handle.assume_init_mut() }, pragma) { + let (result, msg) = match vfs.pragma(&mut file.handle, pragma) { Ok(msg) => (Ok(vars::SQLITE_OK), msg), Err(PragmaErr::NotFound) => (Err(vars::SQLITE_NOTFOUND), None), Err(PragmaErr::Fail(err, msg)) => (Err(err), msg), @@ -623,7 +628,7 @@ unsafe extern "C" fn x_sector_size(p_file: *mut ffi::sqlite3_file) -> c_ fallible(|| { let file = unwrap_file!(p_file, T)?; let vfs = unwrap_vfs!(file.vfs, T)?; - vfs.sector_size(unsafe { file.handle.assume_init_mut() }) + vfs.sector_size(&mut file.handle) }) } @@ -631,7 +636,7 @@ unsafe extern "C" fn x_device_characteristics(p_file: *mut ffi::sqlite3_ fallible(|| { let file = unwrap_file!(p_file, T)?; let vfs = unwrap_vfs!(file.vfs, T)?; - vfs.device_characteristics(unsafe { file.handle.assume_init_mut() }) + vfs.device_characteristics(&mut file.handle) }) } From cdc4bd039e5cd813f8e59585ac4809ee8c40e74a Mon Sep 17 00:00:00 2001 From: Brian Koropoff Date: Mon, 23 Feb 2026 22:13:11 -0800 Subject: [PATCH 2/2] Support shm interface The lifetime semantics of returned mapped regions are odd: the regions are cached until all being released at once with `xShmUnmap`, but region 0 (the start of the file) might be fetched more than one. This makes a safe Rust interface nearly impossible, as sqlite wants to mutably alias whatever you return to it. --- src/flags.rs | 26 +++++++++++++ src/vfs.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 121 insertions(+), 6 deletions(-) diff --git a/src/flags.rs b/src/flags.rs index b5254ae..3699b74 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -178,3 +178,29 @@ impl From for LockLevel { } } } + +#[derive(Copy, Clone, Debug)] +pub enum ShmLockMode { + LockShared, + LockExclusive, + UnlockShared, + UnlockExclusive, +} + +impl TryFrom for ShmLockMode { + type Error = i32; + + fn try_from(flags: i32) -> Result { + const LOCK_SHARED: i32 = vars::SQLITE_SHM_LOCK | vars::SQLITE_SHM_SHARED; + const LOCK_EXCLUSIVE: i32 = vars::SQLITE_SHM_LOCK | vars::SQLITE_SHM_EXCLUSIVE; + const UNLOCK_SHARED: i32 = vars::SQLITE_SHM_UNLOCK | vars::SQLITE_SHM_SHARED; + const UNLOCK_EXCLUSIVE: i32 = vars::SQLITE_SHM_UNLOCK | vars::SQLITE_SHM_EXCLUSIVE; + Ok(match flags { + LOCK_SHARED => Self::LockShared, + LOCK_EXCLUSIVE => Self::LockExclusive, + UNLOCK_SHARED => Self::UnlockShared, + UNLOCK_EXCLUSIVE => Self::UnlockExclusive, + _ => return Err(vars::SQLITE_IOERR), + }) + } +} diff --git a/src/vfs.rs b/src/vfs.rs index 646e28c..f4732d9 100644 --- a/src/vfs.rs +++ b/src/vfs.rs @@ -1,4 +1,4 @@ -use crate::flags::{AccessFlags, LockLevel, OpenOpts}; +use crate::flags::{AccessFlags, LockLevel, OpenOpts, ShmLockMode}; use crate::logger::SqliteLogger; use crate::vars::SQLITE_ERROR; use crate::{ffi, vars}; @@ -11,7 +11,7 @@ use core::mem::{ManuallyDrop, size_of}; use core::slice; use core::{ ffi::{CStr, c_char, c_int, c_void}, - ptr::null_mut, + ptr::{NonNull, null_mut}, }; /// The minimim supported `SQLite` version. @@ -186,6 +186,32 @@ pub trait Vfs: Send + Sync { fn device_characteristics(&self, handle: &mut Self::Handle) -> VfsResult { Ok(DEFAULT_DEVICE_CHARACTERISTICS) } + + fn shm_map( + &self, + handle: &mut Self::Handle, + region_idx: usize, + region_size: usize, + extend: bool, + ) -> VfsResult>> { + Err(vars::SQLITE_READONLY_CANTINIT) + } + + fn shm_lock( + &self, + handle: &mut Self::Handle, + offset: u32, + count: u32, + mode: ShmLockMode, + ) -> VfsResult<()> { + Err(vars::SQLITE_IOERR) + } + + fn shm_barrier(&self, handle: &mut Self::Handle) {} + + fn shm_unmap(&self, handle: &mut Self::Handle, delete: bool) -> VfsResult<()> { + Err(vars::SQLITE_IOERR) + } } #[derive(Clone)] @@ -300,10 +326,10 @@ fn register_inner( xFileControl: Some(x_file_control::), xSectorSize: Some(x_sector_size::), xDeviceCharacteristics: Some(x_device_characteristics::), - xShmMap: None, - xShmLock: None, - xShmBarrier: None, - xShmUnmap: None, + xShmMap: Some(x_shm_map::), + xShmLock: Some(x_shm_lock::), + xShmBarrier: Some(x_shm_barrier::), + xShmUnmap: Some(x_shm_unmap::), xFetch: None, xUnfetch: None, }; @@ -640,6 +666,69 @@ unsafe extern "C" fn x_device_characteristics(p_file: *mut ffi::sqlite3_ }) } +unsafe extern "C" fn x_shm_map( + p_file: *mut ffi::sqlite3_file, + pg: c_int, + pgsz: c_int, + extend: c_int, + p_page: *mut *mut c_void, +) -> c_int { + fallible(|| { + let file = unwrap_file!(p_file, T)?; + let vfs = unwrap_vfs!(file.vfs, T)?; + if let Some(region) = vfs.shm_map( + &mut file.handle, + pg.try_into().map_err(|_| vars::SQLITE_IOERR)?, + pgsz.try_into().map_err(|_| vars::SQLITE_IOERR)?, + extend != 0, + )? { + unsafe { *p_page = region.as_ptr() as *mut c_void } + } else { + unsafe { *p_page = null_mut() } + } + Ok(vars::SQLITE_OK) + }) +} + +unsafe extern "C" fn x_shm_lock( + p_file: *mut ffi::sqlite3_file, + offset: c_int, + n: c_int, + flags: c_int, +) -> c_int { + fallible(|| { + let file = unwrap_file!(p_file, T)?; + let vfs = unwrap_vfs!(file.vfs, T)?; + vfs.shm_lock( + &mut file.handle, + offset.try_into().map_err(|_| vars::SQLITE_IOERR)?, + n.try_into().map_err(|_| vars::SQLITE_IOERR)?, + ShmLockMode::try_from(flags)?, + )?; + Ok(vars::SQLITE_OK) + }) +} + +unsafe extern "C" fn x_shm_barrier(p_file: *mut ffi::sqlite3_file) { + if let Ok(file) = unwrap_file!(p_file, T) { + if let Ok(vfs) = unwrap_vfs!(file.vfs, T) { + vfs.shm_barrier(&mut file.handle) + } + } +} + +unsafe extern "C" fn x_shm_unmap( + p_file: *mut ffi::sqlite3_file, + delete_flag: c_int, +) -> c_int { + fallible(|| { + let file = unwrap_file!(p_file, T)?; + let vfs = unwrap_vfs!(file.vfs, T)?; + vfs.shm_unmap(&mut file.handle, delete_flag != 0)?; + Ok(vars::SQLITE_OK) + }) +} + // the following functions are wrappers around the base vfs functions unsafe extern "C" fn x_dlopen(