Skip to main content

sqlite_plugin/
vfs.rs

1use crate::flags::{AccessFlags, LockLevel, OpenOpts, ShmLockMode};
2use crate::logger::SqliteLogger;
3use crate::vars::SQLITE_ERROR;
4use crate::{ffi, vars};
5use alloc::borrow::Cow;
6use alloc::boxed::Box;
7use alloc::ffi::CString;
8use alloc::format;
9use alloc::string::String;
10use core::mem::{ManuallyDrop, size_of};
11use core::slice;
12use core::{
13    ffi::{CStr, c_char, c_int, c_void},
14    ptr::{NonNull, null_mut},
15};
16
17/// The minimim supported `SQLite` version.
18// If you need to make this earlier, make sure the tests are testing the earlier version
19pub const MIN_SQLITE_VERSION_NUMBER: i32 = 3043000;
20
21const DEFAULT_MAX_PATH_LEN: i32 = 512;
22pub const DEFAULT_SECTOR_SIZE: i32 = 4096;
23
24pub const DEFAULT_DEVICE_CHARACTERISTICS: i32 =
25    // writes of any size are atomic
26    vars::SQLITE_IOCAP_ATOMIC |
27    // after reboot following a crash or power loss, the only bytes in a file that were written
28    // at the application level might have changed and that adjacent bytes, even bytes within
29    // the same sector are guaranteed to be unchanged
30    vars::SQLITE_IOCAP_POWERSAFE_OVERWRITE |
31    // when data is appended to a file, the data is appended first then the size of the file is
32    // extended, never the other way around
33    vars::SQLITE_IOCAP_SAFE_APPEND |
34    // information is written to disk in the same order as calls to xWrite()
35    vars::SQLITE_IOCAP_SEQUENTIAL;
36
37/// A `SQLite3` extended error code
38pub type SqliteErr = i32;
39
40pub type VfsResult<T> = Result<T, SqliteErr>;
41
42// FileWrapper needs to be repr(C) and have sqlite3_file as it's first member
43// because it's a "subclass" of sqlite3_file
44#[repr(C)]
45struct FileWrapper<Handle> {
46    file: ffi::sqlite3_file,
47    vfs: *mut ffi::sqlite3_vfs,
48    handle: Handle,
49}
50
51struct AppData<Vfs> {
52    base_vfs: *mut ffi::sqlite3_vfs,
53    vfs: Vfs,
54    io_methods: ffi::sqlite3_io_methods,
55    sqlite_api: SqliteApi,
56}
57
58#[derive(Debug)]
59pub struct Pragma<'a> {
60    pub name: &'a str,
61    pub arg: Option<&'a str>,
62}
63
64#[derive(Debug)]
65pub enum PragmaErr {
66    NotFound,
67    Fail(SqliteErr, Option<String>),
68}
69
70impl PragmaErr {
71    pub fn required_arg(p: &Pragma<'_>) -> Self {
72        PragmaErr::Fail(
73            SQLITE_ERROR,
74            Some(format!(
75                "argument required (e.g. `pragma {} = ...`)",
76                p.name
77            )),
78        )
79    }
80}
81
82fn fallible(mut cb: impl FnMut() -> Result<i32, SqliteErr>) -> i32 {
83    cb().unwrap_or_else(|err| err)
84}
85
86unsafe fn lossy_cstr<'a>(p: *const c_char) -> VfsResult<Cow<'a, str>> {
87    unsafe {
88        p.as_ref()
89            .map(|p| CStr::from_ptr(p).to_string_lossy())
90            .ok_or(vars::SQLITE_INTERNAL)
91    }
92}
93
94macro_rules! unwrap_appdata {
95    ($p_vfs:expr, $t_vfs:ty) => {
96        unsafe {
97            let out: VfsResult<&AppData<$t_vfs>> = (*$p_vfs)
98                .pAppData
99                .cast::<AppData<$t_vfs>>()
100                .as_ref()
101                .ok_or(vars::SQLITE_INTERNAL);
102            out
103        }
104    };
105}
106
107macro_rules! unwrap_vfs {
108    ($p_vfs:expr, $t_vfs:ty) => {{
109        let out: VfsResult<&$t_vfs> = unwrap_appdata!($p_vfs, $t_vfs).map(|app_data| &app_data.vfs);
110        out
111    }};
112}
113
114macro_rules! unwrap_base_vfs {
115    ($p_vfs:expr, $t_vfs:ty) => {{
116        let out: VfsResult<&mut ffi::sqlite3_vfs> =
117            unwrap_appdata!($p_vfs, $t_vfs).and_then(|app_data| {
118                unsafe { app_data.base_vfs.as_mut() }.ok_or(vars::SQLITE_INTERNAL)
119            });
120        out
121    }};
122}
123
124macro_rules! unwrap_file {
125    ($p_file:expr, $t_vfs:ty) => {
126        unsafe {
127            let out: VfsResult<&mut FileWrapper<<$t_vfs>::Handle>> = $p_file
128                .cast::<FileWrapper<<$t_vfs>::Handle>>()
129                .as_mut()
130                .ok_or(vars::SQLITE_INTERNAL);
131            out
132        }
133    };
134}
135
136pub trait VfsHandle: Send {
137    fn readonly(&self) -> bool;
138    fn in_memory(&self) -> bool;
139}
140
141#[allow(unused_variables)]
142pub trait Vfs: Send + Sync {
143    type Handle: VfsHandle;
144
145    /// construct a canonical version of the given path
146    fn canonical_path<'a>(&self, path: Cow<'a, str>) -> VfsResult<Cow<'a, str>> {
147        Ok(path)
148    }
149
150    // file system operations
151    fn open(&self, path: Option<&str>, opts: OpenOpts) -> VfsResult<Self::Handle>;
152    fn delete(&self, path: &str) -> VfsResult<()>;
153    fn access(&self, path: &str, flags: AccessFlags) -> VfsResult<bool>;
154
155    // file operations
156    fn file_size(&self, handle: &mut Self::Handle) -> VfsResult<usize>;
157    fn truncate(&self, handle: &mut Self::Handle, size: usize) -> VfsResult<()>;
158    fn write(&self, handle: &mut Self::Handle, offset: usize, data: &[u8]) -> VfsResult<usize>;
159    fn read(&self, handle: &mut Self::Handle, offset: usize, data: &mut [u8]) -> VfsResult<usize>;
160
161    fn lock(&self, handle: &mut Self::Handle, level: LockLevel) -> VfsResult<()>;
162
163    fn unlock(&self, handle: &mut Self::Handle, level: LockLevel) -> VfsResult<()>;
164
165    fn check_reserved_lock(&self, handle: &mut Self::Handle) -> VfsResult<bool>;
166
167    fn sync(&self, handle: &mut Self::Handle) -> VfsResult<()> {
168        Ok(())
169    }
170
171    fn close(&self, handle: Self::Handle) -> VfsResult<()>;
172
173    fn pragma(
174        &self,
175        handle: &mut Self::Handle,
176        pragma: Pragma<'_>,
177    ) -> Result<Option<String>, PragmaErr> {
178        Err(PragmaErr::NotFound)
179    }
180
181    // system queries
182    fn sector_size(&self, handle: &mut Self::Handle) -> VfsResult<i32> {
183        Ok(DEFAULT_SECTOR_SIZE)
184    }
185
186    fn device_characteristics(&self, handle: &mut Self::Handle) -> VfsResult<i32> {
187        Ok(DEFAULT_DEVICE_CHARACTERISTICS)
188    }
189
190    fn shm_map(
191        &self,
192        handle: &mut Self::Handle,
193        region_idx: usize,
194        region_size: usize,
195        extend: bool,
196    ) -> VfsResult<Option<NonNull<u8>>> {
197        Err(vars::SQLITE_READONLY_CANTINIT)
198    }
199
200    fn shm_lock(
201        &self,
202        handle: &mut Self::Handle,
203        offset: u32,
204        count: u32,
205        mode: ShmLockMode,
206    ) -> VfsResult<()> {
207        Err(vars::SQLITE_IOERR)
208    }
209
210    fn shm_barrier(&self, handle: &mut Self::Handle) {}
211
212    fn shm_unmap(&self, handle: &mut Self::Handle, delete: bool) -> VfsResult<()> {
213        Err(vars::SQLITE_IOERR)
214    }
215
216    /// Memory-mapped page read (xFetch). Return a pointer to `amt` bytes of
217    /// the file starting at `offset`, or `Ok(None)` to decline and have `SQLite`
218    /// fall back to `xRead`.
219    ///
220    /// The default implementation declines all mmap requests. Override this to
221    /// enable memory-mapped I/O for your VFS (e.g. mmap the database file).
222    ///
223    /// # Safety contract
224    ///
225    /// The returned pointer must remain valid until `unfetch` is called with
226    /// the same offset. `SQLite` may read from the pointer concurrently from
227    /// multiple threads.
228    fn fetch(
229        &self,
230        handle: &mut Self::Handle,
231        offset: i64,
232        amt: usize,
233    ) -> VfsResult<Option<NonNull<u8>>> {
234        Ok(None)
235    }
236
237    /// Release a memory-mapped page previously returned by `fetch`.
238    ///
239    /// If `ptr` is null, this is a hint that the VFS should reduce its
240    /// memory-mapped footprint (`SQLite` calls this when shrinking mmap).
241    /// The default implementation is a no-op.
242    fn unfetch(&self, handle: &mut Self::Handle, offset: i64, ptr: *mut u8) -> VfsResult<()> {
243        Ok(())
244    }
245}
246
247#[derive(Clone)]
248pub struct SqliteApi {
249    register: unsafe extern "C" fn(arg1: *mut ffi::sqlite3_vfs, arg2: c_int) -> c_int,
250    find: unsafe extern "C" fn(arg1: *const c_char) -> *mut ffi::sqlite3_vfs,
251    mprintf: unsafe extern "C" fn(arg1: *const c_char, ...) -> *mut c_char,
252    log: unsafe extern "C" fn(arg1: c_int, arg2: *const c_char, ...),
253    libversion_number: unsafe extern "C" fn() -> c_int,
254}
255
256impl SqliteApi {
257    #[cfg(feature = "static")]
258    pub fn new_static() -> Self {
259        Self {
260            register: ffi::sqlite3_vfs_register,
261            find: ffi::sqlite3_vfs_find,
262            mprintf: ffi::sqlite3_mprintf,
263            log: ffi::sqlite3_log,
264            libversion_number: ffi::sqlite3_libversion_number,
265        }
266    }
267
268    /// Initializes SqliteApi from a filled `sqlite3_api_routines` object.
269    /// # Safety
270    /// `api` must be a valid, aligned pointer to a `sqlite3_api_routines` struct
271    #[cfg(feature = "dynamic")]
272    pub unsafe fn new_dynamic(api: &ffi::sqlite3_api_routines) -> VfsResult<Self> {
273        Ok(Self {
274            register: api.vfs_register.ok_or(vars::SQLITE_INTERNAL)?,
275            find: api.vfs_find.ok_or(vars::SQLITE_INTERNAL)?,
276            mprintf: api.mprintf.ok_or(vars::SQLITE_INTERNAL)?,
277            log: api.log.ok_or(vars::SQLITE_INTERNAL)?,
278            libversion_number: api.libversion_number.ok_or(vars::SQLITE_INTERNAL)?,
279        })
280    }
281
282    /// Copies the provided string into a memory buffer allocated by `sqlite3_mprintf`.
283    /// Writes the pointer to the memory buffer to `out` if `out` is not null.
284    /// # Safety
285    /// 1. the out pointer must not be null
286    /// 2. it is the callers responsibility to eventually free the allocated buffer
287    pub unsafe fn mprintf(&self, s: &str, out: *mut *const c_char) -> VfsResult<()> {
288        let s = CString::new(s).map_err(|_| vars::SQLITE_INTERNAL)?;
289        let p = unsafe { (self.mprintf)(s.as_ptr()) };
290        if p.is_null() {
291            Err(vars::SQLITE_NOMEM)
292        } else {
293            unsafe {
294                *out = p;
295            }
296            Ok(())
297        }
298    }
299}
300
301pub struct RegisterOpts {
302    /// If true, make this vfs the default vfs for `SQLite`.
303    pub make_default: bool,
304}
305
306#[cfg(feature = "static")]
307pub fn register_static<T: Vfs>(
308    name: CString,
309    vfs: T,
310    opts: RegisterOpts,
311) -> VfsResult<SqliteLogger> {
312    register_inner(SqliteApi::new_static(), name, vfs, opts)
313}
314
315/// Register a vfs with `SQLite` using the dynamic API. This API is available when
316/// `SQLite` is initializing extensions.
317/// # Safety
318/// `p_api` must be a valid, aligned pointer to a `sqlite3_api_routines` struct
319#[cfg(feature = "dynamic")]
320pub unsafe fn register_dynamic<T: Vfs>(
321    p_api: *mut ffi::sqlite3_api_routines,
322    name: CString,
323    vfs: T,
324    opts: RegisterOpts,
325) -> VfsResult<SqliteLogger> {
326    let api = unsafe { p_api.as_ref() }.ok_or(vars::SQLITE_INTERNAL)?;
327    let sqlite_api = unsafe { SqliteApi::new_dynamic(api)? };
328    register_inner(sqlite_api, name, vfs, opts)
329}
330
331fn register_inner<T: Vfs>(
332    sqlite_api: SqliteApi,
333    name: CString,
334    vfs: T,
335    opts: RegisterOpts,
336) -> VfsResult<SqliteLogger> {
337    let version = unsafe { (sqlite_api.libversion_number)() };
338    if version < MIN_SQLITE_VERSION_NUMBER {
339        panic!(
340            "sqlite3 must be at least version {}, found version {}",
341            MIN_SQLITE_VERSION_NUMBER, version
342        );
343    }
344
345    let io_methods = ffi::sqlite3_io_methods {
346        iVersion: 3,
347        xClose: Some(x_close::<T>),
348        xRead: Some(x_read::<T>),
349        xWrite: Some(x_write::<T>),
350        xTruncate: Some(x_truncate::<T>),
351        xSync: Some(x_sync::<T>),
352        xFileSize: Some(x_file_size::<T>),
353        xLock: Some(x_lock::<T>),
354        xUnlock: Some(x_unlock::<T>),
355        xCheckReservedLock: Some(x_check_reserved_lock::<T>),
356        xFileControl: Some(x_file_control::<T>),
357        xSectorSize: Some(x_sector_size::<T>),
358        xDeviceCharacteristics: Some(x_device_characteristics::<T>),
359        xShmMap: Some(x_shm_map::<T>),
360        xShmLock: Some(x_shm_lock::<T>),
361        xShmBarrier: Some(x_shm_barrier::<T>),
362        xShmUnmap: Some(x_shm_unmap::<T>),
363        xFetch: Some(x_fetch::<T>),
364        xUnfetch: Some(x_unfetch::<T>),
365    };
366
367    let logger = SqliteLogger::new(sqlite_api.log);
368
369    let p_name = ManuallyDrop::new(name).as_ptr();
370    let base_vfs = unsafe { (sqlite_api.find)(null_mut()) };
371    let vfs_register = sqlite_api.register;
372    let p_appdata = Box::into_raw(Box::new(AppData { base_vfs, vfs, io_methods, sqlite_api }));
373
374    let filewrapper_size: c_int = size_of::<FileWrapper<T::Handle>>()
375        .try_into()
376        .map_err(|_| vars::SQLITE_INTERNAL)?;
377
378    let p_vfs = Box::into_raw(Box::new(ffi::sqlite3_vfs {
379        iVersion: 3,
380        szOsFile: filewrapper_size,
381        mxPathname: DEFAULT_MAX_PATH_LEN,
382        pNext: null_mut(),
383        zName: p_name,
384        pAppData: p_appdata.cast(),
385        xOpen: Some(x_open::<T>),
386        xDelete: Some(x_delete::<T>),
387        xAccess: Some(x_access::<T>),
388        xFullPathname: Some(x_full_pathname::<T>),
389        xDlOpen: Some(x_dlopen::<T>),
390        xDlError: Some(x_dlerror::<T>),
391        xDlSym: Some(x_dlsym::<T>),
392        xDlClose: Some(x_dlclose::<T>),
393        xRandomness: Some(x_randomness::<T>),
394        xSleep: Some(x_sleep::<T>),
395        xCurrentTime: Some(x_current_time::<T>),
396        xGetLastError: None,
397        xCurrentTimeInt64: Some(x_current_time_int64::<T>),
398        xSetSystemCall: None,
399        xGetSystemCall: None,
400        xNextSystemCall: None,
401    }));
402
403    let result = unsafe { vfs_register(p_vfs, opts.make_default.into()) };
404    if result != vars::SQLITE_OK {
405        // cleanup memory
406        unsafe {
407            drop(Box::from_raw(p_vfs));
408            drop(Box::from_raw(p_appdata));
409            drop(CString::from_raw(p_name as *mut c_char));
410        };
411        Err(result)
412    } else {
413        Ok(logger)
414    }
415}
416
417unsafe extern "C" fn x_open<T: Vfs>(
418    p_vfs: *mut ffi::sqlite3_vfs,
419    z_name: ffi::sqlite3_filename,
420    p_file: *mut ffi::sqlite3_file,
421    flags: c_int,
422    p_out_flags: *mut c_int,
423) -> c_int {
424    // From https://sqlite.org/c3ref/vfs.html:
425    // "Note that the xOpen method must set the sqlite3_file.pMethods to either a valid
426    // sqlite3_io_methods object or to NULL. xOpen must do this even if the open fails."
427    if let Some(f) = unsafe { p_file.as_mut() } {
428        f.pMethods = core::ptr::null();
429    }
430
431    fallible(|| {
432        let opts = flags.into();
433        let name = unsafe { lossy_cstr(z_name) }.ok();
434        let vfs = unwrap_vfs!(p_vfs, T)?;
435        let handle = vfs.open(name.as_ref().map(|s| s.as_ref()), opts)?;
436
437        let appdata = unwrap_appdata!(p_vfs, T)?;
438
439        if let Some(p_out_flags) = unsafe { p_out_flags.as_mut() } {
440            let mut out_flags = flags;
441            if handle.readonly() {
442                out_flags |= vars::SQLITE_OPEN_READONLY;
443            }
444            if handle.in_memory() {
445                out_flags |= vars::SQLITE_OPEN_MEMORY;
446            }
447            *p_out_flags = out_flags;
448        }
449
450        let out_file = p_file.cast::<FileWrapper<T::Handle>>();
451        // Safety: SQLite owns the heap allocation backing p_file (it handles malloc/free).
452        // We use ptr::write to initialize that allocation directly.
453        unsafe {
454            core::ptr::write(
455                out_file,
456                FileWrapper {
457                    file: ffi::sqlite3_file { pMethods: &appdata.io_methods },
458                    vfs: p_vfs,
459                    handle,
460                },
461            );
462        }
463
464        Ok(vars::SQLITE_OK)
465    })
466}
467
468unsafe extern "C" fn x_delete<T: Vfs>(
469    p_vfs: *mut ffi::sqlite3_vfs,
470    z_name: ffi::sqlite3_filename,
471    _sync_dir: c_int,
472) -> c_int {
473    fallible(|| {
474        let name = unsafe { lossy_cstr(z_name)? };
475        let vfs = unwrap_vfs!(p_vfs, T)?;
476        vfs.delete(&name)?;
477        Ok(vars::SQLITE_OK)
478    })
479}
480
481unsafe extern "C" fn x_access<T: Vfs>(
482    p_vfs: *mut ffi::sqlite3_vfs,
483    z_name: ffi::sqlite3_filename,
484    flags: c_int,
485    p_res_out: *mut c_int,
486) -> c_int {
487    fallible(|| {
488        let name = unsafe { lossy_cstr(z_name)? };
489        let vfs = unwrap_vfs!(p_vfs, T)?;
490        let result = vfs.access(&name, flags.into())?;
491        let out = unsafe { p_res_out.as_mut() }.ok_or(vars::SQLITE_IOERR_ACCESS)?;
492        *out = result as i32;
493        Ok(vars::SQLITE_OK)
494    })
495}
496
497unsafe extern "C" fn x_full_pathname<T: Vfs>(
498    p_vfs: *mut ffi::sqlite3_vfs,
499    z_name: ffi::sqlite3_filename,
500    n_out: c_int,
501    z_out: *mut c_char,
502) -> c_int {
503    fallible(|| {
504        let name = unsafe { lossy_cstr(z_name)? };
505        let vfs = unwrap_vfs!(p_vfs, T)?;
506        let full_name = vfs.canonical_path(name)?;
507        let n_out = n_out.try_into().map_err(|_| vars::SQLITE_INTERNAL)?;
508        let out = unsafe { slice::from_raw_parts_mut(z_out as *mut u8, n_out) };
509        let from = &full_name.as_bytes()[..full_name.len().min(n_out - 1)];
510        // copy the name into the output buffer
511        out[..from.len()].copy_from_slice(from);
512        // add the trailing null byte
513        out[from.len()] = 0;
514        Ok(vars::SQLITE_OK)
515    })
516}
517
518// file operations
519
520unsafe extern "C" fn x_close<T: Vfs>(p_file: *mut ffi::sqlite3_file) -> c_int {
521    fallible(|| {
522        // Safety: SQLite owns the heap allocation backing p_file (it handles
523        // malloc/free). We use ptr::read to copy our Handle out of that
524        // allocation so it can be passed to vfs.close() and properly dropped.
525        // SQLite will not call any other file methods after x_close without
526        // first calling x_open to reinitialize the handle.
527        let (vfs, handle) = unsafe {
528            // verify p_file is not null and get a mutable reference
529            let p_file_ref = p_file.as_mut().ok_or(vars::SQLITE_INTERNAL)?;
530            // set pMethods to null, signaling to SQLite that the file is closed
531            p_file_ref.pMethods = core::ptr::null();
532
533            // extract a copy of the FileWrapper
534            let file = core::ptr::read(p_file.cast::<FileWrapper<T::Handle>>());
535            (file.vfs, file.handle)
536        };
537
538        let vfs = unwrap_vfs!(vfs, T)?;
539        vfs.close(handle)?;
540        Ok(vars::SQLITE_OK)
541    })
542}
543
544unsafe extern "C" fn x_read<T: Vfs>(
545    p_file: *mut ffi::sqlite3_file,
546    buf: *mut c_void,
547    i_amt: c_int,
548    i_ofst: ffi::sqlite_int64,
549) -> c_int {
550    fallible(|| {
551        let file = unwrap_file!(p_file, T)?;
552        let vfs = unwrap_vfs!(file.vfs, T)?;
553        let buf_len: usize = i_amt.try_into().map_err(|_| vars::SQLITE_IOERR_READ)?;
554        let offset: usize = i_ofst.try_into().map_err(|_| vars::SQLITE_IOERR_READ)?;
555        let buf = unsafe { slice::from_raw_parts_mut(buf.cast::<u8>(), buf_len) };
556        let bytes_read = vfs.read(&mut file.handle, offset, buf)?;
557        if bytes_read < buf_len {
558            // From https://sqlite.org/c3ref/io_methods.html:
559            // "If xRead() returns SQLITE_IOERR_SHORT_READ it must also fill in the unread portions
560            // of the buffer with zeros."
561            buf[bytes_read..].fill(0);
562            return Err(vars::SQLITE_IOERR_SHORT_READ);
563        }
564        Ok(vars::SQLITE_OK)
565    })
566}
567
568unsafe extern "C" fn x_write<T: Vfs>(
569    p_file: *mut ffi::sqlite3_file,
570    buf: *const c_void,
571    i_amt: c_int,
572    i_ofst: ffi::sqlite_int64,
573) -> c_int {
574    fallible(|| {
575        let file = unwrap_file!(p_file, T)?;
576        let vfs = unwrap_vfs!(file.vfs, T)?;
577        let buf_len: usize = i_amt.try_into().map_err(|_| vars::SQLITE_IOERR_WRITE)?;
578        let offset: usize = i_ofst.try_into().map_err(|_| vars::SQLITE_IOERR_WRITE)?;
579        let buf = unsafe { slice::from_raw_parts(buf.cast::<u8>(), buf_len) };
580        let n = vfs.write(&mut file.handle, offset, buf)?;
581        if n != buf_len {
582            return Err(vars::SQLITE_IOERR_WRITE);
583        }
584        Ok(vars::SQLITE_OK)
585    })
586}
587
588unsafe extern "C" fn x_truncate<T: Vfs>(
589    p_file: *mut ffi::sqlite3_file,
590    size: ffi::sqlite_int64,
591) -> c_int {
592    fallible(|| {
593        let file = unwrap_file!(p_file, T)?;
594        let vfs = unwrap_vfs!(file.vfs, T)?;
595        let size: usize = size.try_into().map_err(|_| vars::SQLITE_IOERR_TRUNCATE)?;
596        vfs.truncate(&mut file.handle, size)?;
597        Ok(vars::SQLITE_OK)
598    })
599}
600
601unsafe extern "C" fn x_sync<T: Vfs>(p_file: *mut ffi::sqlite3_file, _flags: c_int) -> c_int {
602    fallible(|| {
603        let file = unwrap_file!(p_file, T)?;
604        let vfs = unwrap_vfs!(file.vfs, T)?;
605        vfs.sync(&mut file.handle)?;
606        Ok(vars::SQLITE_OK)
607    })
608}
609
610unsafe extern "C" fn x_file_size<T: Vfs>(
611    p_file: *mut ffi::sqlite3_file,
612    p_size: *mut ffi::sqlite3_int64,
613) -> c_int {
614    fallible(|| {
615        let file = unwrap_file!(p_file, T)?;
616        let vfs = unwrap_vfs!(file.vfs, T)?;
617        let size = vfs.file_size(&mut file.handle)?;
618        let p_size = unsafe { p_size.as_mut() }.ok_or(vars::SQLITE_INTERNAL)?;
619        *p_size = size.try_into().map_err(|_| vars::SQLITE_IOERR_FSTAT)?;
620        Ok(vars::SQLITE_OK)
621    })
622}
623
624unsafe extern "C" fn x_lock<T: Vfs>(p_file: *mut ffi::sqlite3_file, raw_lock: c_int) -> c_int {
625    fallible(|| {
626        let level: LockLevel = raw_lock.into();
627        let file = unwrap_file!(p_file, T)?;
628        let vfs = unwrap_vfs!(file.vfs, T)?;
629        vfs.lock(&mut file.handle, level)?;
630        Ok(vars::SQLITE_OK)
631    })
632}
633
634unsafe extern "C" fn x_unlock<T: Vfs>(p_file: *mut ffi::sqlite3_file, raw_lock: c_int) -> c_int {
635    fallible(|| {
636        let level: LockLevel = raw_lock.into();
637        let file = unwrap_file!(p_file, T)?;
638        let vfs = unwrap_vfs!(file.vfs, T)?;
639        vfs.unlock(&mut file.handle, level)?;
640        Ok(vars::SQLITE_OK)
641    })
642}
643
644unsafe extern "C" fn x_check_reserved_lock<T: Vfs>(
645    p_file: *mut ffi::sqlite3_file,
646    p_out: *mut c_int,
647) -> c_int {
648    fallible(|| {
649        let file = unwrap_file!(p_file, T)?;
650        let vfs = unwrap_vfs!(file.vfs, T)?;
651        unsafe {
652            *p_out = vfs.check_reserved_lock(&mut file.handle)? as c_int;
653        }
654        Ok(vars::SQLITE_OK)
655    })
656}
657
658unsafe extern "C" fn x_file_control<T: Vfs>(
659    p_file: *mut ffi::sqlite3_file,
660    op: c_int,
661    p_arg: *mut c_void,
662) -> c_int {
663    /*
664    Other interesting ops:
665    SIZE_HINT: hint of how large the database will grow during the current transaction
666    COMMIT_PHASETWO: after transaction commits before file unlocks (only used in WAL mode)
667    VFS_NAME: should return this vfs's name + / + base vfs's name
668
669    Atomic write support: (requires SQLITE_IOCAP_BATCH_ATOMIC device characteristic)
670    Docs: https://www3.sqlite.org/cgi/src/technote/714f6cbbf78c8a1351cbd48af2b438f7f824b336
671    BEGIN_ATOMIC_WRITE: start an atomic write operation
672    COMMIT_ATOMIC_WRITE: commit an atomic write operation
673    ROLLBACK_ATOMIC_WRITE: rollback an atomic write operation
674    */
675
676    if op == vars::SQLITE_FCNTL_PRAGMA {
677        return fallible(|| {
678            let file = unwrap_file!(p_file, T)?;
679            let vfs = unwrap_vfs!(file.vfs, T)?;
680
681            // p_arg is a pointer to an array of strings
682            // the second value is the pragma name
683            // the third value is either null or the pragma arg
684            let args = p_arg.cast::<*const c_char>();
685            let name = unsafe { lossy_cstr(*args.add(1)) }?;
686            let arg = unsafe {
687                (*args.add(2))
688                    .as_ref()
689                    .map(|p| CStr::from_ptr(p).to_string_lossy())
690            };
691            let pragma = Pragma { name: &name, arg: arg.as_deref() };
692
693            let (result, msg) = match vfs.pragma(&mut file.handle, pragma) {
694                Ok(msg) => (Ok(vars::SQLITE_OK), msg),
695                Err(PragmaErr::NotFound) => (Err(vars::SQLITE_NOTFOUND), None),
696                Err(PragmaErr::Fail(err, msg)) => (Err(err), msg),
697            };
698
699            if let Some(msg) = msg {
700                // write the msg back to the first element of the args array.
701                // SQLite is responsible for eventually freeing the result
702                let appdata = unwrap_appdata!(file.vfs, T)?;
703                unsafe { appdata.sqlite_api.mprintf(&msg, args)? };
704            }
705
706            result
707        });
708    }
709    vars::SQLITE_NOTFOUND
710}
711
712// system queries
713
714unsafe extern "C" fn x_sector_size<T: Vfs>(p_file: *mut ffi::sqlite3_file) -> c_int {
715    fallible(|| {
716        let file = unwrap_file!(p_file, T)?;
717        let vfs = unwrap_vfs!(file.vfs, T)?;
718        vfs.sector_size(&mut file.handle)
719    })
720}
721
722unsafe extern "C" fn x_device_characteristics<T: Vfs>(p_file: *mut ffi::sqlite3_file) -> c_int {
723    fallible(|| {
724        let file = unwrap_file!(p_file, T)?;
725        let vfs = unwrap_vfs!(file.vfs, T)?;
726        vfs.device_characteristics(&mut file.handle)
727    })
728}
729
730unsafe extern "C" fn x_shm_map<T: Vfs>(
731    p_file: *mut ffi::sqlite3_file,
732    pg: c_int,
733    pgsz: c_int,
734    extend: c_int,
735    p_page: *mut *mut c_void,
736) -> c_int {
737    fallible(|| {
738        let file = unwrap_file!(p_file, T)?;
739        let vfs = unwrap_vfs!(file.vfs, T)?;
740        if let Some(region) = vfs.shm_map(
741            &mut file.handle,
742            pg.try_into().map_err(|_| vars::SQLITE_IOERR)?,
743            pgsz.try_into().map_err(|_| vars::SQLITE_IOERR)?,
744            extend != 0,
745        )? {
746            unsafe { *p_page = region.as_ptr() as *mut c_void }
747        } else {
748            unsafe { *p_page = null_mut() }
749        }
750        Ok(vars::SQLITE_OK)
751    })
752}
753
754unsafe extern "C" fn x_shm_lock<T: Vfs>(
755    p_file: *mut ffi::sqlite3_file,
756    offset: c_int,
757    n: c_int,
758    flags: c_int,
759) -> c_int {
760    fallible(|| {
761        let file = unwrap_file!(p_file, T)?;
762        let vfs = unwrap_vfs!(file.vfs, T)?;
763        vfs.shm_lock(
764            &mut file.handle,
765            offset.try_into().map_err(|_| vars::SQLITE_IOERR)?,
766            n.try_into().map_err(|_| vars::SQLITE_IOERR)?,
767            ShmLockMode::try_from(flags)?,
768        )?;
769        Ok(vars::SQLITE_OK)
770    })
771}
772
773unsafe extern "C" fn x_shm_barrier<T: Vfs>(p_file: *mut ffi::sqlite3_file) {
774    if let Ok(file) = unwrap_file!(p_file, T) {
775        if let Ok(vfs) = unwrap_vfs!(file.vfs, T) {
776            vfs.shm_barrier(&mut file.handle)
777        }
778    }
779}
780
781unsafe extern "C" fn x_shm_unmap<T: Vfs>(
782    p_file: *mut ffi::sqlite3_file,
783    delete_flag: c_int,
784) -> c_int {
785    fallible(|| {
786        let file = unwrap_file!(p_file, T)?;
787        let vfs = unwrap_vfs!(file.vfs, T)?;
788        vfs.shm_unmap(&mut file.handle, delete_flag != 0)?;
789        Ok(vars::SQLITE_OK)
790    })
791}
792
793unsafe extern "C" fn x_fetch<T: Vfs>(
794    p_file: *mut ffi::sqlite3_file,
795    i_ofst: ffi::sqlite3_int64,
796    i_amt: c_int,
797    pp: *mut *mut c_void,
798) -> c_int {
799    fallible(|| {
800        let file = unwrap_file!(p_file, T)?;
801        let vfs = unwrap_vfs!(file.vfs, T)?;
802        let amt: usize = i_amt.try_into().map_err(|_| vars::SQLITE_IOERR)?;
803        if let Some(ptr) = vfs.fetch(&mut file.handle, i_ofst, amt)? {
804            unsafe { *pp = ptr.as_ptr() as *mut c_void }
805        } else {
806            unsafe { *pp = null_mut() }
807        }
808        Ok(vars::SQLITE_OK)
809    })
810}
811
812unsafe extern "C" fn x_unfetch<T: Vfs>(
813    p_file: *mut ffi::sqlite3_file,
814    i_ofst: ffi::sqlite3_int64,
815    p: *mut c_void,
816) -> c_int {
817    fallible(|| {
818        let file = unwrap_file!(p_file, T)?;
819        let vfs = unwrap_vfs!(file.vfs, T)?;
820        vfs.unfetch(&mut file.handle, i_ofst, p as *mut u8)?;
821        Ok(vars::SQLITE_OK)
822    })
823}
824
825// the following functions are wrappers around the base vfs functions
826
827unsafe extern "C" fn x_dlopen<T: Vfs>(
828    p_vfs: *mut ffi::sqlite3_vfs,
829    z_path: *const c_char,
830) -> *mut c_void {
831    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
832        if let Some(x_dlopen) = vfs.xDlOpen {
833            return unsafe { x_dlopen(vfs, z_path) };
834        }
835    }
836    null_mut()
837}
838
839unsafe extern "C" fn x_dlerror<T: Vfs>(
840    p_vfs: *mut ffi::sqlite3_vfs,
841    n_byte: c_int,
842    z_err_msg: *mut c_char,
843) {
844    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
845        if let Some(x_dlerror) = vfs.xDlError {
846            unsafe { x_dlerror(vfs, n_byte, z_err_msg) };
847        }
848    }
849}
850
851unsafe extern "C" fn x_dlsym<T: Vfs>(
852    p_vfs: *mut ffi::sqlite3_vfs,
853    p_handle: *mut c_void,
854    z_symbol: *const c_char,
855) -> Option<unsafe extern "C" fn(arg1: *mut ffi::sqlite3_vfs, arg2: *mut c_void, arg3: *const c_char)>
856{
857    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
858        if let Some(x_dlsym) = vfs.xDlSym {
859            return unsafe { x_dlsym(vfs, p_handle, z_symbol) };
860        }
861    }
862    None
863}
864
865unsafe extern "C" fn x_dlclose<T: Vfs>(p_vfs: *mut ffi::sqlite3_vfs, p_handle: *mut c_void) {
866    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
867        if let Some(x_dlclose) = vfs.xDlClose {
868            unsafe { x_dlclose(vfs, p_handle) };
869        }
870    }
871}
872
873unsafe extern "C" fn x_randomness<T: Vfs>(
874    p_vfs: *mut ffi::sqlite3_vfs,
875    n_byte: c_int,
876    z_out: *mut c_char,
877) -> c_int {
878    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
879        if let Some(x_randomness) = vfs.xRandomness {
880            return unsafe { x_randomness(vfs, n_byte, z_out) };
881        }
882    }
883    vars::SQLITE_INTERNAL
884}
885
886unsafe extern "C" fn x_sleep<T: Vfs>(p_vfs: *mut ffi::sqlite3_vfs, microseconds: c_int) -> c_int {
887    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
888        if let Some(x_sleep) = vfs.xSleep {
889            return unsafe { x_sleep(vfs, microseconds) };
890        }
891    }
892    vars::SQLITE_INTERNAL
893}
894
895unsafe extern "C" fn x_current_time<T: Vfs>(
896    p_vfs: *mut ffi::sqlite3_vfs,
897    p_time: *mut f64,
898) -> c_int {
899    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
900        if let Some(x_current_time) = vfs.xCurrentTime {
901            return unsafe { x_current_time(vfs, p_time) };
902        }
903    }
904    vars::SQLITE_INTERNAL
905}
906
907unsafe extern "C" fn x_current_time_int64<T: Vfs>(
908    p_vfs: *mut ffi::sqlite3_vfs,
909    p_time: *mut i64,
910) -> c_int {
911    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
912        if let Some(x_current_time_int64) = vfs.xCurrentTimeInt64 {
913            return unsafe { x_current_time_int64(vfs, p_time) };
914        }
915    }
916    vars::SQLITE_INTERNAL
917}
918
919#[cfg(test)]
920mod tests {
921    // tests use std
922    extern crate std;
923
924    use super::*;
925    use crate::{
926        flags::{CreateMode, OpenKind, OpenMode},
927        mock::*,
928    };
929    use alloc::{sync::Arc, vec::Vec};
930    use parking_lot::Mutex;
931    use rusqlite::{Connection, OpenFlags};
932    use std::{boxed::Box, io::Write, println};
933
934    fn log_handler(_: i32, arg2: &str) {
935        println!("{arg2}");
936    }
937
938    #[test]
939    fn sanity() -> Result<(), Box<dyn std::error::Error>> {
940        unsafe {
941            rusqlite::trace::config_log(Some(log_handler)).unwrap();
942        }
943
944        struct H {}
945        impl Hooks for H {
946            fn open(&mut self, path: &Option<&str>, opts: &OpenOpts) {
947                let path = path.unwrap();
948                if path == "main.db" {
949                    assert!(!opts.delete_on_close());
950                    assert_eq!(opts.kind(), OpenKind::MainDb);
951                    assert_eq!(
952                        opts.mode(),
953                        OpenMode::ReadWrite { create: CreateMode::Create }
954                    );
955                } else if path == "main.db-journal" {
956                    assert!(!opts.delete_on_close());
957                    assert_eq!(opts.kind(), OpenKind::MainJournal);
958                    assert_eq!(
959                        opts.mode(),
960                        OpenMode::ReadWrite { create: CreateMode::Create }
961                    );
962                } else {
963                    panic!("unexpected path: {}", path);
964                }
965            }
966        }
967
968        let shared = Arc::new(Mutex::new(MockState::new(Box::new(H {}))));
969        let vfs = MockVfs::new(shared.clone());
970        let logger = register_static(
971            CString::new("mock").unwrap(),
972            vfs,
973            RegisterOpts { make_default: true },
974        )
975        .map_err(|_| "failed to register vfs")?;
976
977        // setup the logger
978        shared.lock().setup_logger(logger);
979
980        // create a sqlite connection using the mock vfs
981        let conn = Connection::open_with_flags_and_vfs(
982            "main.db",
983            OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE,
984            "mock",
985        )?;
986
987        conn.execute("create table t (val int)", [])?;
988        conn.execute("insert into t (val) values (1)", [])?;
989        conn.execute("insert into t (val) values (2)", [])?;
990
991        conn.execute("pragma mock_test", [])?;
992
993        let n: i64 = conn.query_row("select sum(val) from t", [], |row| row.get(0))?;
994        assert_eq!(n, 3);
995
996        // the blob api is interesting and stress tests reading/writing pages and journaling
997        conn.execute("create table b (data blob)", [])?;
998        println!("inserting zero blob");
999        conn.execute("insert into b values (zeroblob(8192))", [])?;
1000        let rowid = conn.last_insert_rowid();
1001        let mut blob = conn.blob_open(rusqlite::MAIN_DB, "b", "data", rowid, false)?;
1002
1003        // write some data to the blob
1004        println!("writing to blob");
1005        let n = blob.write(b"hello")?;
1006        assert_eq!(n, 5);
1007
1008        blob.close()?;
1009
1010        // query the table for the blob and print it
1011        let mut stmt = conn.prepare("select data from b")?;
1012        let mut rows = stmt.query([])?;
1013        while let Some(row) = rows.next()? {
1014            let data: Vec<u8> = row.get(0)?;
1015            assert_eq!(&data[0..5], b"hello");
1016        }
1017        drop(rows);
1018        drop(stmt);
1019
1020        conn.close().expect("failed to close connection");
1021
1022        Ok(())
1023    }
1024}