rsqlite_vfs/
lib.rs

1//! Low-level utilities, traits, and macros for implementing custom SQLite Virtual File Systems (VFS).
2//!
3//! This crate also contains a simple operating system independent memvfs implementation
4#![no_std]
5#![allow(non_snake_case)]
6#![allow(non_camel_case_types)]
7
8extern crate alloc;
9
10/// sqlite3 types to implement VFS.
11#[rustfmt::skip]
12pub mod ffi;
13pub mod memvfs;
14
15use alloc::string::String;
16use alloc::vec::Vec;
17use alloc::{boxed::Box, ffi::CString};
18use alloc::{format, vec};
19use core::time::Duration;
20use core::{cell::RefCell, ffi::CStr, ops::Deref};
21use ffi::*;
22
23/// A macro to return a specific SQLite error code if a condition is true.
24///
25/// The default error code is SQLITE_ERROR.
26#[macro_export]
27macro_rules! bail {
28    ($ex:expr) => {
29        bail!($ex, SQLITE_ERROR);
30    };
31    ($ex:expr, $code: expr) => {
32        if $ex {
33            return $code;
34        }
35    };
36}
37
38/// A macro to safely unwrap an `Option<T>`, returning a SQLite error code on `None`.
39///
40/// The default error code is SQLITE_ERROR.
41#[macro_export]
42macro_rules! check_option {
43    ($ex:expr) => {
44        check_option!($ex, SQLITE_ERROR)
45    };
46    ($ex:expr, $code: expr) => {
47        if let Some(v) = $ex {
48            v
49        } else {
50            return $code;
51        }
52    };
53}
54
55/// A macro to safely unwrap a `Result<T, E>`, returning a SQLite error code on `Err`.
56///
57/// The default err code is SQLITE_ERROR.
58#[macro_export]
59macro_rules! check_result {
60    ($ex:expr) => {
61        check_result!($ex, SQLITE_ERROR)
62    };
63    ($ex:expr, $code: expr) => {
64        if let Ok(v) = $ex {
65            v
66        } else {
67            return $code;
68        }
69    };
70}
71
72/// A macro to explicitly mark a parameter as unused, suppressing compiler warnings.
73#[macro_export]
74macro_rules! unused {
75    ($ex:expr) => {
76        let _ = $ex;
77    };
78}
79
80/// The header of the SQLite file is used to determine whether the imported file is legal.
81pub const SQLITE3_HEADER: &str = "SQLite format 3";
82
83/// Generates a random, temporary filename, typically used when SQLite requests a file with a NULL name.
84pub fn random_name(randomness: fn(&mut [u8])) -> String {
85    const GEN_ASCII_STR_CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
86                abcdefghijklmnopqrstuvwxyz\
87                0123456789";
88    const GEN_LEN: u8 = GEN_ASCII_STR_CHARSET.len() as u8;
89    let mut random_buffer = [0; 32];
90    randomness(&mut random_buffer);
91    random_buffer
92        .into_iter()
93        .map(|e| {
94            let idx = e.saturating_sub(GEN_LEN * (e / GEN_LEN));
95            GEN_ASCII_STR_CHARSET[idx as usize] as char
96        })
97        .collect()
98}
99
100/// An in-memory file implementation that stores data in fixed-size chunks. Suitable for temporary files.
101pub struct MemChunksFile {
102    chunks: Vec<Vec<u8>>,
103    chunk_size: Option<usize>,
104    file_size: usize,
105}
106
107impl Default for MemChunksFile {
108    fn default() -> Self {
109        Self::new(512)
110    }
111}
112
113impl MemChunksFile {
114    /// Creates a new `MemChunksFile` with a specified chunk size.
115    pub fn new(chunk_size: usize) -> Self {
116        assert!(chunk_size != 0, "chunk size can't be zero");
117        MemChunksFile {
118            chunks: Vec::new(),
119            chunk_size: Some(chunk_size),
120            file_size: 0,
121        }
122    }
123
124    /// Creates a `MemChunksFile` where the chunk size is determined by the size of the first write operation.
125    ///
126    /// This is often used for the main DB file implementation.
127    pub fn waiting_for_write() -> Self {
128        MemChunksFile {
129            chunks: Vec::new(),
130            chunk_size: None,
131            file_size: 0,
132        }
133    }
134}
135
136impl VfsFile for MemChunksFile {
137    fn read(&self, buf: &mut [u8], offset: usize) -> VfsResult<bool> {
138        let Some(chunk_size) = self.chunk_size else {
139            buf.fill(0);
140            return Ok(false);
141        };
142
143        if self.file_size <= offset {
144            buf.fill(0);
145            return Ok(false);
146        }
147
148        if chunk_size == buf.len() && offset % chunk_size == 0 {
149            buf.copy_from_slice(&self.chunks[offset / chunk_size]);
150            Ok(true)
151        } else {
152            let mut size = buf.len();
153            let chunk_idx = offset / chunk_size;
154            let mut remaining_idx = offset % chunk_size;
155            let mut offset = 0;
156
157            for chunk in &self.chunks[chunk_idx..] {
158                let n = core::cmp::min(chunk_size.min(self.file_size) - remaining_idx, size);
159                buf[offset..offset + n].copy_from_slice(&chunk[remaining_idx..remaining_idx + n]);
160                offset += n;
161                size -= n;
162                remaining_idx = 0;
163                if size == 0 {
164                    break;
165                }
166            }
167
168            if offset < buf.len() {
169                buf[offset..].fill(0);
170                Ok(false)
171            } else {
172                Ok(true)
173            }
174        }
175    }
176
177    fn write(&mut self, buf: &[u8], offset: usize) -> VfsResult<()> {
178        if buf.is_empty() {
179            return Ok(());
180        }
181
182        let chunk_size = if let Some(chunk_size) = self.chunk_size {
183            chunk_size
184        } else {
185            let size = buf.len();
186            self.chunk_size = Some(size);
187            size
188        };
189
190        let new_length = self.file_size.max(offset + buf.len());
191
192        if chunk_size == buf.len() && offset % chunk_size == 0 {
193            for _ in self.chunks.len()..offset / chunk_size {
194                self.chunks.push(vec![0; chunk_size]);
195            }
196            if let Some(buffer) = self.chunks.get_mut(offset / chunk_size) {
197                buffer.copy_from_slice(buf);
198            } else {
199                self.chunks.push(buf.to_vec());
200            }
201        } else {
202            let mut size = buf.len();
203            let chunk_start_idx = offset / chunk_size;
204            let chunk_end_idx = (offset + size - 1) / chunk_size;
205            let chunks_length = self.chunks.len();
206
207            for _ in chunks_length..=chunk_end_idx {
208                self.chunks.push(vec![0; chunk_size]);
209            }
210
211            let mut remaining_idx = offset % chunk_size;
212            let mut offset = 0;
213
214            for idx in chunk_start_idx..=chunk_end_idx {
215                let n = core::cmp::min(chunk_size - remaining_idx, size);
216                self.chunks[idx][remaining_idx..remaining_idx + n]
217                    .copy_from_slice(&buf[offset..offset + n]);
218                offset += n;
219                size -= n;
220                remaining_idx = 0;
221                if size == 0 {
222                    break;
223                }
224            }
225        }
226
227        self.file_size = new_length;
228
229        Ok(())
230    }
231
232    fn truncate(&mut self, size: usize) -> VfsResult<()> {
233        if let Some(chunk_size) = self.chunk_size {
234            if size == 0 {
235                core::mem::take(&mut self.chunks);
236            } else {
237                let idx = ((size - 1) / chunk_size) + 1;
238                self.chunks.drain(idx..);
239            }
240        } else if size != 0 {
241            assert_eq!(self.file_size, 0);
242            return Err(VfsError::new(SQLITE_IOERR, "Failed to truncate".into()));
243        }
244        self.file_size = size;
245        Ok(())
246    }
247
248    fn flush(&mut self) -> VfsResult<()> {
249        Ok(())
250    }
251
252    fn size(&self) -> VfsResult<usize> {
253        Ok(self.file_size)
254    }
255}
256
257/// The core file-handle structure for a custom VFS, designed to be compatible with SQLite's C interface.
258///
259/// `szOsFile` must be set to the size of `SQLiteVfsFile`.
260#[repr(C)]
261pub struct SQLiteVfsFile {
262    /// The first field must be of type sqlite_file.
263    /// In C layout, the pointer to SQLiteVfsFile is the pointer to io_methods.
264    pub io_methods: sqlite3_file,
265    /// The vfs where the file is located, usually used to manage files.
266    pub vfs: *mut sqlite3_vfs,
267    /// Flags used to open the database.
268    pub flags: i32,
269    /// The pointer to the file name (owned by the VFS, freed in xClose).
270    pub name_ptr: *const u8,
271    /// Length of the file name, on wasm32 platform, usize is u32.
272    pub name_length: usize,
273}
274
275impl SQLiteVfsFile {
276    /// Convert a `sqlite3_file` pointer to a `SQLiteVfsFile` pointer.
277    ///
278    /// # Safety
279    ///
280    /// You must ensure that the pointer passed in is `SQLiteVfsFile`
281    pub unsafe fn from_file(file: *mut sqlite3_file) -> &'static SQLiteVfsFile {
282        &*file.cast::<Self>()
283    }
284
285    /// Get the file name.
286    ///
287    /// # Safety
288    ///
289    /// The name is created from a UTF-8 Rust `String` and leaked for SQLite.
290    ///
291    /// Do not use again after free.
292    pub unsafe fn name(&self) -> &'static mut str {
293        // emm, `from_raw_parts_mut` is unstable
294        core::str::from_utf8_unchecked_mut(core::slice::from_raw_parts_mut(
295            self.name_ptr.cast_mut(),
296            self.name_length,
297        ))
298    }
299
300    /// Converts a reference to this VFS file structure into a raw `*mut sqlite3_file` pointer that can be passed to SQLite.
301    pub fn sqlite3_file(&'static self) -> *mut sqlite3_file {
302        self as *const SQLiteVfsFile as *mut sqlite3_file
303    }
304}
305
306/// Represents errors that can occur during the VFS registration process.
307#[derive(thiserror::Error, Debug)]
308pub enum RegisterVfsError {
309    #[error("An error occurred converting the given vfs name to a CStr")]
310    ToCStr,
311    #[error("An error occurred while registering vfs with sqlite")]
312    RegisterVfs,
313}
314
315/// Checks if a VFS with the given name is already registered with SQLite and returns a pointer to it if found.
316pub fn registered_vfs(vfs_name: &str) -> Result<Option<*mut sqlite3_vfs>, RegisterVfsError> {
317    let name = CString::new(vfs_name).map_err(|_| RegisterVfsError::ToCStr)?;
318    let vfs = unsafe { sqlite3_vfs_find(name.as_ptr()) };
319    Ok((!vfs.is_null()).then_some(vfs))
320}
321
322/// A generic function to register a custom VFS implementation with SQLite.
323pub fn register_vfs<IO: SQLiteIoMethods, V: SQLiteVfs<IO>>(
324    vfs_name: &str,
325    app_data: IO::AppData,
326    default_vfs: bool,
327) -> Result<*mut sqlite3_vfs, RegisterVfsError> {
328    let name = CString::new(vfs_name).map_err(|_| RegisterVfsError::ToCStr)?;
329    let name_ptr = name.into_raw();
330
331    // `name_ptr` and `app_data` are owned by SQLite until the VFS is unregistered.
332    let app_data = VfsAppData::new(app_data).leak();
333    let vfs = Box::leak(Box::new(V::vfs(name_ptr, app_data.cast())));
334    let ret = unsafe { sqlite3_vfs_register(vfs, i32::from(default_vfs)) };
335
336    if ret != SQLITE_OK {
337        unsafe {
338            drop(Box::from_raw(vfs));
339            drop(CString::from_raw(name_ptr));
340            drop(VfsAppData::from_raw(app_data));
341        }
342        return Err(RegisterVfsError::RegisterVfs);
343    }
344
345    Ok(vfs as *mut sqlite3_vfs)
346}
347
348/// A container for VFS-specific errors, holding both an error code and a descriptive message.
349#[derive(Debug)]
350pub struct VfsError {
351    code: i32,
352    message: String,
353}
354
355impl VfsError {
356    pub fn new(code: i32, message: String) -> Self {
357        VfsError { code, message }
358    }
359}
360
361/// A specialized `Result` type for VFS operations.
362pub type VfsResult<T> = Result<T, VfsError>;
363
364/// A wrapper for the `pAppData` pointer in `sqlite3_vfs`, providing a safe way
365/// to manage VFS-specific application data and error states.
366pub struct VfsAppData<T> {
367    data: T,
368    last_err: RefCell<Option<(i32, String)>>,
369}
370
371impl<T> VfsAppData<T> {
372    pub fn new(t: T) -> Self {
373        VfsAppData {
374            data: t,
375            last_err: RefCell::new(None),
376        }
377    }
378
379    /// Leak, then pAppData can be set to VFS
380    pub fn leak(self) -> *mut Self {
381        Box::into_raw(Box::new(self))
382    }
383
384    /// # Safety
385    ///
386    /// Takes ownership of a pointer returned by `leak`.
387    pub unsafe fn from_raw(t: *mut Self) -> VfsAppData<T> {
388        *Box::from_raw(t)
389    }
390
391    /// Retrieves and clears the last error recorded for the VFS.
392    pub fn pop_err(&self) -> Option<(i32, String)> {
393        self.last_err.borrow_mut().take()
394    }
395
396    /// Stores an error code and message for the VFS, to be retrieved later by `xGetLastError`.
397    pub fn store_err(&self, err: VfsError) -> i32 {
398        let VfsError { code, message } = err;
399        self.last_err.borrow_mut().replace((code, message));
400        code
401    }
402}
403
404/// Deref only, returns immutable reference, avoids race conditions
405impl<T> Deref for VfsAppData<T> {
406    type Target = T;
407
408    fn deref(&self) -> &Self::Target {
409        &self.data
410    }
411}
412
413/// A trait defining the basic I/O capabilities required for a VFS file implementation.
414pub trait VfsFile {
415    /// Abstraction of `xRead`, returns true for `SQLITE_OK` and false for `SQLITE_IOERR_SHORT_READ`
416    fn read(&self, buf: &mut [u8], offset: usize) -> VfsResult<bool>;
417    /// Abstraction of `xWrite`
418    fn write(&mut self, buf: &[u8], offset: usize) -> VfsResult<()>;
419    /// Abstraction of `xTruncate`
420    fn truncate(&mut self, size: usize) -> VfsResult<()>;
421    /// Abstraction of `xSync`
422    fn flush(&mut self) -> VfsResult<()>;
423    /// Abstraction of `xFileSize`
424    fn size(&self) -> VfsResult<usize>;
425}
426
427/// Make changes to files
428pub trait VfsStore<File, AppData> {
429    /// Convert pAppData to the type we need
430    ///
431    /// # Safety
432    ///
433    /// As long as it is set through the abstract VFS interface, it is safe
434    unsafe fn app_data(vfs: *mut sqlite3_vfs) -> &'static VfsAppData<AppData> {
435        &*(*vfs).pAppData.cast()
436    }
437    /// Adding files to the Store, use for `xOpen` and `xAccess`
438    fn add_file(vfs: *mut sqlite3_vfs, file: &str, flags: i32) -> VfsResult<()>;
439    /// Checks if the specified file exists in the Store, use for `xOpen` and `xAccess`
440    fn contains_file(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<bool>;
441    /// Delete the specified file in the Store, use for `xClose` and `xDelete`
442    fn delete_file(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<()>;
443    /// Read the file contents, use for `xRead`, `xFileSize`
444    fn with_file<F: Fn(&File) -> VfsResult<i32>>(vfs_file: &SQLiteVfsFile, f: F) -> VfsResult<i32>;
445    /// Write the file contents, use for `xWrite`, `xTruncate` and `xSync`
446    fn with_file_mut<F: Fn(&mut File) -> VfsResult<i32>>(
447        vfs_file: &SQLiteVfsFile,
448        f: F,
449    ) -> VfsResult<i32>;
450}
451
452/// Platform implementation
453pub trait OsCallback {
454    fn sleep(dur: Duration);
455    fn random(buf: &mut [u8]);
456    fn epoch_timestamp_in_ms() -> i64;
457}
458
459/// A trait that abstracts the `sqlite3_vfs` struct, allowing for a more idiomatic Rust implementation.
460#[allow(clippy::missing_safety_doc)]
461pub trait SQLiteVfs<IO: SQLiteIoMethods> {
462    const VERSION: ::core::ffi::c_int;
463    const MAX_PATH_SIZE: ::core::ffi::c_int = 1024;
464
465    fn sleep(dur: Duration);
466    fn random(buf: &mut [u8]);
467    fn epoch_timestamp_in_ms() -> i64;
468
469    fn vfs(
470        vfs_name: *const ::core::ffi::c_char,
471        app_data: *mut VfsAppData<IO::AppData>,
472    ) -> sqlite3_vfs {
473        sqlite3_vfs {
474            iVersion: Self::VERSION,
475            szOsFile: core::mem::size_of::<SQLiteVfsFile>() as i32,
476            mxPathname: Self::MAX_PATH_SIZE,
477            pNext: core::ptr::null_mut(),
478            zName: vfs_name,
479            pAppData: app_data.cast(),
480            xOpen: Some(Self::xOpen),
481            xDelete: Some(Self::xDelete),
482            xAccess: Some(Self::xAccess),
483            xFullPathname: Some(Self::xFullPathname),
484            xDlOpen: None,
485            xDlError: None,
486            xDlSym: None,
487            xDlClose: None,
488            xRandomness: Some(Self::xRandomness),
489            xSleep: Some(Self::xSleep),
490            xCurrentTime: Some(Self::xCurrentTime),
491            xGetLastError: Some(Self::xGetLastError),
492            xCurrentTimeInt64: Some(Self::xCurrentTimeInt64),
493            xSetSystemCall: None,
494            xGetSystemCall: None,
495            xNextSystemCall: None,
496        }
497    }
498
499    unsafe extern "C" fn xOpen(
500        pVfs: *mut sqlite3_vfs,
501        zName: sqlite3_filename,
502        pFile: *mut sqlite3_file,
503        flags: ::core::ffi::c_int,
504        pOutFlags: *mut ::core::ffi::c_int,
505    ) -> ::core::ffi::c_int {
506        Self::xOpenImpl(pVfs, zName, pFile, flags, pOutFlags)
507    }
508
509    unsafe extern "C" fn xOpenImpl(
510        pVfs: *mut sqlite3_vfs,
511        zName: sqlite3_filename,
512        pFile: *mut sqlite3_file,
513        flags: ::core::ffi::c_int,
514        pOutFlags: *mut ::core::ffi::c_int,
515    ) -> ::core::ffi::c_int {
516        let app_data = IO::Store::app_data(pVfs);
517
518        let name = if zName.is_null() {
519            random_name(Self::random)
520        } else {
521            check_result!(CStr::from_ptr(zName).to_str()).into()
522        };
523
524        let exist = match IO::Store::contains_file(pVfs, &name) {
525            Ok(exist) => exist,
526            Err(err) => return app_data.store_err(err),
527        };
528
529        if !exist {
530            if flags & SQLITE_OPEN_CREATE == 0 {
531                return app_data.store_err(VfsError::new(
532                    SQLITE_CANTOPEN,
533                    format!("file not found: {name}"),
534                ));
535            }
536            if let Err(err) = IO::Store::add_file(pVfs, &name, flags) {
537                return app_data.store_err(err);
538            }
539        }
540
541        let leak = name.leak();
542        let vfs_file = pFile.cast::<SQLiteVfsFile>();
543        (*vfs_file).vfs = pVfs;
544        (*vfs_file).flags = flags;
545        (*vfs_file).name_ptr = leak.as_ptr();
546        (*vfs_file).name_length = leak.len();
547
548        (*pFile).pMethods = &IO::METHODS;
549
550        if !pOutFlags.is_null() {
551            *pOutFlags = flags;
552        }
553
554        SQLITE_OK
555    }
556
557    unsafe extern "C" fn xDelete(
558        pVfs: *mut sqlite3_vfs,
559        zName: *const ::core::ffi::c_char,
560        syncDir: ::core::ffi::c_int,
561    ) -> ::core::ffi::c_int {
562        unused!(syncDir);
563
564        let app_data = IO::Store::app_data(pVfs);
565        bail!(zName.is_null(), SQLITE_IOERR_DELETE);
566        let s = check_result!(CStr::from_ptr(zName).to_str());
567        if let Err(err) = IO::Store::delete_file(pVfs, s) {
568            app_data.store_err(err)
569        } else {
570            SQLITE_OK
571        }
572    }
573
574    unsafe extern "C" fn xAccess(
575        pVfs: *mut sqlite3_vfs,
576        zName: *const ::core::ffi::c_char,
577        flags: ::core::ffi::c_int,
578        pResOut: *mut ::core::ffi::c_int,
579    ) -> ::core::ffi::c_int {
580        unused!(flags);
581
582        *pResOut = if zName.is_null() {
583            0
584        } else {
585            let app_data = IO::Store::app_data(pVfs);
586            let file = check_result!(CStr::from_ptr(zName).to_str());
587            let exist = match IO::Store::contains_file(pVfs, file) {
588                Ok(exist) => exist,
589                Err(err) => return app_data.store_err(err),
590            };
591            i32::from(exist)
592        };
593
594        SQLITE_OK
595    }
596
597    unsafe extern "C" fn xFullPathname(
598        pVfs: *mut sqlite3_vfs,
599        zName: *const ::core::ffi::c_char,
600        nOut: ::core::ffi::c_int,
601        zOut: *mut ::core::ffi::c_char,
602    ) -> ::core::ffi::c_int {
603        unused!(pVfs);
604        bail!(zName.is_null() || zOut.is_null(), SQLITE_CANTOPEN);
605        let len = CStr::from_ptr(zName).to_bytes_with_nul().len();
606        bail!(len > nOut as usize, SQLITE_CANTOPEN);
607        zName.copy_to(zOut, len);
608        SQLITE_OK
609    }
610
611    unsafe extern "C" fn xGetLastError(
612        pVfs: *mut sqlite3_vfs,
613        nOut: ::core::ffi::c_int,
614        zOut: *mut ::core::ffi::c_char,
615    ) -> ::core::ffi::c_int {
616        let app_data = IO::Store::app_data(pVfs);
617        let Some((code, msg)) = app_data.pop_err() else {
618            return SQLITE_OK;
619        };
620        if !zOut.is_null() {
621            let nOut = nOut as usize;
622            let count = msg.len().min(nOut);
623            msg.as_ptr().copy_to(zOut.cast(), count);
624            let zero = match nOut.cmp(&msg.len()) {
625                core::cmp::Ordering::Less | core::cmp::Ordering::Equal => nOut,
626                core::cmp::Ordering::Greater => msg.len() + 1,
627            };
628            if zero > 0 {
629                core::ptr::write(zOut.add(zero - 1), 0);
630            }
631        }
632        code
633    }
634
635    /// <https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L951>
636    unsafe extern "C" fn xRandomness(
637        pVfs: *mut sqlite3_vfs,
638        nByte: ::core::ffi::c_int,
639        zOut: *mut ::core::ffi::c_char,
640    ) -> ::core::ffi::c_int {
641        unused!(pVfs);
642        let slice = core::slice::from_raw_parts_mut(zOut.cast(), nByte as usize);
643        Self::random(slice);
644        nByte
645    }
646
647    /// <https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L870>
648    unsafe extern "C" fn xCurrentTime(
649        pVfs: *mut sqlite3_vfs,
650        pTimeOut: *mut f64,
651    ) -> ::core::ffi::c_int {
652        unused!(pVfs);
653        *pTimeOut = 2440587.5 + (Self::epoch_timestamp_in_ms() as f64 / 86400000.0);
654        SQLITE_OK
655    }
656
657    /// <https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L877>
658    unsafe extern "C" fn xCurrentTimeInt64(
659        pVfs: *mut sqlite3_vfs,
660        pOut: *mut sqlite3_int64,
661    ) -> ::core::ffi::c_int {
662        unused!(pVfs);
663        *pOut = ((2440587.5 * 86400000.0) + Self::epoch_timestamp_in_ms() as f64) as sqlite3_int64;
664        SQLITE_OK
665    }
666
667    unsafe extern "C" fn xSleep(
668        pVfs: *mut sqlite3_vfs,
669        microseconds: ::core::ffi::c_int,
670    ) -> ::core::ffi::c_int {
671        unused!(pVfs);
672        let dur = Duration::from_micros(microseconds as u64);
673        Self::sleep(dur);
674        SQLITE_OK
675    }
676}
677
678/// A trait that abstracts the `sqlite3_io_methods` struct, allowing for a more idiomatic Rust implementation.
679#[allow(clippy::missing_safety_doc)]
680pub trait SQLiteIoMethods {
681    type File: VfsFile;
682    type AppData: 'static;
683    type Store: VfsStore<Self::File, Self::AppData>;
684
685    const VERSION: ::core::ffi::c_int;
686
687    const METHODS: sqlite3_io_methods = sqlite3_io_methods {
688        iVersion: Self::VERSION,
689        xClose: Some(Self::xClose),
690        xRead: Some(Self::xRead),
691        xWrite: Some(Self::xWrite),
692        xTruncate: Some(Self::xTruncate),
693        xSync: Some(Self::xSync),
694        xFileSize: Some(Self::xFileSize),
695        xLock: Some(Self::xLock),
696        xUnlock: Some(Self::xUnlock),
697        xCheckReservedLock: Some(Self::xCheckReservedLock),
698        xFileControl: Some(Self::xFileControl),
699        xSectorSize: Some(Self::xSectorSize),
700        xDeviceCharacteristics: Some(Self::xDeviceCharacteristics),
701        xShmMap: None,
702        xShmLock: None,
703        xShmBarrier: None,
704        xShmUnmap: None,
705        xFetch: None,
706        xUnfetch: None,
707    };
708
709    unsafe extern "C" fn xClose(pFile: *mut sqlite3_file) -> ::core::ffi::c_int {
710        Self::xCloseImpl(pFile)
711    }
712
713    unsafe extern "C" fn xCloseImpl(pFile: *mut sqlite3_file) -> ::core::ffi::c_int {
714        let vfs_file = SQLiteVfsFile::from_file(pFile);
715        let app_data = Self::Store::app_data(vfs_file.vfs);
716
717        if vfs_file.flags & SQLITE_OPEN_DELETEONCLOSE != 0 {
718            if let Err(err) = Self::Store::delete_file(vfs_file.vfs, vfs_file.name()) {
719                return app_data.store_err(err);
720            }
721        }
722
723        drop(Box::from_raw(vfs_file.name()));
724
725        SQLITE_OK
726    }
727
728    unsafe extern "C" fn xRead(
729        pFile: *mut sqlite3_file,
730        zBuf: *mut ::core::ffi::c_void,
731        iAmt: ::core::ffi::c_int,
732        iOfst: sqlite3_int64,
733    ) -> ::core::ffi::c_int {
734        let vfs_file = SQLiteVfsFile::from_file(pFile);
735        let app_data = Self::Store::app_data(vfs_file.vfs);
736
737        let f = |file: &Self::File| {
738            let size = iAmt as usize;
739            let offset = iOfst as usize;
740            let slice = core::slice::from_raw_parts_mut(zBuf.cast::<u8>(), size);
741            let code = if file.read(slice, offset)? {
742                SQLITE_OK
743            } else {
744                SQLITE_IOERR_SHORT_READ
745            };
746            Ok(code)
747        };
748
749        match Self::Store::with_file(vfs_file, f) {
750            Ok(code) => code,
751            Err(err) => app_data.store_err(err),
752        }
753    }
754
755    unsafe extern "C" fn xWrite(
756        pFile: *mut sqlite3_file,
757        zBuf: *const ::core::ffi::c_void,
758        iAmt: ::core::ffi::c_int,
759        iOfst: sqlite3_int64,
760    ) -> ::core::ffi::c_int {
761        let vfs_file = SQLiteVfsFile::from_file(pFile);
762        let app_data = Self::Store::app_data(vfs_file.vfs);
763
764        let f = |file: &mut Self::File| {
765            let (offset, size) = (iOfst as usize, iAmt as usize);
766            let slice = core::slice::from_raw_parts(zBuf.cast::<u8>(), size);
767            file.write(slice, offset)?;
768            Ok(SQLITE_OK)
769        };
770
771        match Self::Store::with_file_mut(vfs_file, f) {
772            Ok(code) => code,
773            Err(err) => app_data.store_err(err),
774        }
775    }
776
777    unsafe extern "C" fn xTruncate(
778        pFile: *mut sqlite3_file,
779        size: sqlite3_int64,
780    ) -> ::core::ffi::c_int {
781        let vfs_file = SQLiteVfsFile::from_file(pFile);
782        let app_data = Self::Store::app_data(vfs_file.vfs);
783
784        let f = |file: &mut Self::File| {
785            file.truncate(size as usize)?;
786            Ok(SQLITE_OK)
787        };
788
789        match Self::Store::with_file_mut(vfs_file, f) {
790            Ok(code) => code,
791            Err(err) => app_data.store_err(err),
792        }
793    }
794
795    unsafe extern "C" fn xSync(
796        pFile: *mut sqlite3_file,
797        flags: ::core::ffi::c_int,
798    ) -> ::core::ffi::c_int {
799        unused!(flags);
800
801        let vfs_file = SQLiteVfsFile::from_file(pFile);
802        let app_data = Self::Store::app_data(vfs_file.vfs);
803
804        let f = |file: &mut Self::File| {
805            file.flush()?;
806            Ok(SQLITE_OK)
807        };
808
809        match Self::Store::with_file_mut(vfs_file, f) {
810            Ok(code) => code,
811            Err(err) => app_data.store_err(err),
812        }
813    }
814
815    unsafe extern "C" fn xFileSize(
816        pFile: *mut sqlite3_file,
817        pSize: *mut sqlite3_int64,
818    ) -> ::core::ffi::c_int {
819        let vfs_file = SQLiteVfsFile::from_file(pFile);
820        let app_data = Self::Store::app_data(vfs_file.vfs);
821
822        let f = |file: &Self::File| {
823            file.size().map(|size| {
824                *pSize = size as sqlite3_int64;
825            })?;
826            Ok(SQLITE_OK)
827        };
828
829        match Self::Store::with_file(vfs_file, f) {
830            Ok(code) => code,
831            Err(err) => app_data.store_err(err),
832        }
833    }
834
835    unsafe extern "C" fn xLock(
836        pFile: *mut sqlite3_file,
837        eLock: ::core::ffi::c_int,
838    ) -> ::core::ffi::c_int {
839        // No-op: in-memory/WASM VFS does not support file locking.
840        unused!((pFile, eLock));
841        SQLITE_OK
842    }
843
844    unsafe extern "C" fn xUnlock(
845        pFile: *mut sqlite3_file,
846        eLock: ::core::ffi::c_int,
847    ) -> ::core::ffi::c_int {
848        // No-op: in-memory/WASM VFS does not support file locking.
849        unused!((pFile, eLock));
850        SQLITE_OK
851    }
852
853    unsafe extern "C" fn xCheckReservedLock(
854        pFile: *mut sqlite3_file,
855        pResOut: *mut ::core::ffi::c_int,
856    ) -> ::core::ffi::c_int {
857        unused!(pFile);
858        *pResOut = 0;
859        SQLITE_OK
860    }
861
862    unsafe extern "C" fn xFileControl(
863        pFile: *mut sqlite3_file,
864        op: ::core::ffi::c_int,
865        pArg: *mut ::core::ffi::c_void,
866    ) -> ::core::ffi::c_int {
867        unused!((pFile, op, pArg));
868        SQLITE_NOTFOUND
869    }
870
871    unsafe extern "C" fn xSectorSize(pFile: *mut sqlite3_file) -> ::core::ffi::c_int {
872        unused!(pFile);
873        512
874    }
875
876    unsafe extern "C" fn xDeviceCharacteristics(pFile: *mut sqlite3_file) -> ::core::ffi::c_int {
877        unused!(pFile);
878        0
879    }
880}
881
882#[derive(thiserror::Error, Debug)]
883pub enum ImportDbError {
884    #[error("Byte array size is invalid for an SQLite db.")]
885    InvalidDbSize,
886    #[error("Input does not contain an SQLite database header.")]
887    InvalidHeader,
888    #[error("Page size must be a power of two between 512 and 65536 inclusive")]
889    InvalidPageSize,
890}
891
892/// Simple verification when importing db, and return page size;
893/// This only checks size/header; validate page size via `check_db_and_page_size`.
894pub fn check_import_db(bytes: &[u8]) -> Result<usize, ImportDbError> {
895    let length = bytes.len();
896
897    if length < 512 || length % 512 != 0 {
898        return Err(ImportDbError::InvalidDbSize);
899    }
900
901    if !bytes.starts_with(SQLITE3_HEADER.as_bytes()) {
902        return Err(ImportDbError::InvalidHeader);
903    }
904
905    // The database page size in bytes.
906    // Must be a power of two between 512 and 32768 inclusive, or the value 1 representing a page size of 65536.
907    let page_size = u16::from_be_bytes([bytes[16], bytes[17]]);
908    let page_size = if page_size == 1 {
909        65536
910    } else {
911        usize::from(page_size)
912    };
913
914    Ok(page_size)
915}
916
917/// Check db and page size, page size must be a power of two between 512 and 65536 inclusive, db size must be a multiple of page size.
918pub fn check_db_and_page_size(db_size: usize, page_size: usize) -> Result<(), ImportDbError> {
919    if !(page_size.is_power_of_two() && (512..=65536).contains(&page_size)) {
920        return Err(ImportDbError::InvalidPageSize);
921    }
922    if db_size % page_size != 0 {
923        return Err(ImportDbError::InvalidDbSize);
924    }
925    Ok(())
926}
927
928/// This is a testing utility for VFS, don't use it in production code.
929#[doc(hidden)]
930pub mod test_suite {
931    use alloc::vec;
932
933    use super::{
934        sqlite3_file, sqlite3_vfs, SQLiteVfsFile, VfsAppData, VfsError, VfsFile, VfsResult,
935        VfsStore, SQLITE_IOERR, SQLITE_OK, SQLITE_OPEN_CREATE, SQLITE_OPEN_MAIN_DB,
936        SQLITE_OPEN_READWRITE,
937    };
938
939    fn test_vfs_file<File: VfsFile>(file: &mut File) -> VfsResult<i32> {
940        let base_offset = 1024 * 1024;
941
942        let mut write_buffer = vec![42; 64 * 1024];
943        let mut read_buffer = vec![42; base_offset + write_buffer.len()];
944        let hw = "hello world!";
945        write_buffer[0..hw.len()].copy_from_slice(hw.as_bytes());
946
947        file.write(&write_buffer, 0)?;
948        assert!(!file.read(&mut read_buffer, 0)?);
949        if &read_buffer[0..hw.len()] != hw.as_bytes()
950            || read_buffer[hw.len()..write_buffer.len()]
951                .iter()
952                .any(|&x| x != 42)
953            || read_buffer[write_buffer.len()..].iter().any(|&x| x != 0)
954        {
955            Err(VfsError::new(SQLITE_IOERR, "incorrect buffer data".into()))?;
956        }
957        if file.size()? != write_buffer.len() {
958            Err(VfsError::new(
959                SQLITE_IOERR,
960                "incorrect buffer length".into(),
961            ))?;
962        }
963
964        file.write(&write_buffer, base_offset)?;
965        if file.size()? != base_offset + write_buffer.len() {
966            Err(VfsError::new(
967                SQLITE_IOERR,
968                "incorrect buffer length".into(),
969            ))?;
970        }
971        assert!(file.read(&mut read_buffer, 0)?);
972        if &read_buffer[0..hw.len()] != hw.as_bytes()
973            || read_buffer[hw.len()..write_buffer.len()]
974                .iter()
975                .any(|&x| x != 42)
976            || read_buffer[write_buffer.len()..base_offset]
977                .iter()
978                .all(|&x| x != 0)
979            || &read_buffer[base_offset..base_offset + hw.len()] != hw.as_bytes()
980            || read_buffer[base_offset + hw.len()..]
981                .iter()
982                .any(|&x| x != 42)
983        {
984            Err(VfsError::new(SQLITE_IOERR, "incorrect buffer data".into()))?;
985        }
986
987        Ok(SQLITE_OK)
988    }
989
990    pub fn test_vfs_store<AppData, File: VfsFile, Store: VfsStore<File, AppData>>(
991        vfs_data: VfsAppData<AppData>,
992    ) -> VfsResult<()> {
993        let layout = core::alloc::Layout::new::<sqlite3_vfs>();
994        let vfs = unsafe {
995            let vfs = alloc::alloc::alloc(layout) as *mut sqlite3_vfs;
996            (*vfs).pAppData = vfs_data.leak().cast();
997            vfs
998        };
999
1000        let test_file = |filename: &str, flags: i32| {
1001            if Store::contains_file(vfs, filename)? {
1002                Err(VfsError::new(SQLITE_IOERR, "found file before test".into()))?;
1003            }
1004
1005            let vfs_file = SQLiteVfsFile {
1006                io_methods: sqlite3_file {
1007                    pMethods: core::ptr::null(),
1008                },
1009                vfs,
1010                flags,
1011                name_ptr: filename.as_ptr(),
1012                name_length: filename.len(),
1013            };
1014
1015            Store::add_file(vfs, filename, flags)?;
1016
1017            if !Store::contains_file(vfs, filename)? {
1018                Err(VfsError::new(
1019                    SQLITE_IOERR,
1020                    "not found file after create".into(),
1021                ))?;
1022            }
1023
1024            Store::with_file_mut(&vfs_file, |file| test_vfs_file(file))?;
1025
1026            Store::delete_file(vfs, filename)?;
1027
1028            if Store::contains_file(vfs, filename)? {
1029                Err(VfsError::new(
1030                    SQLITE_IOERR,
1031                    "found file after delete".into(),
1032                ))?;
1033            }
1034
1035            Ok(())
1036        };
1037
1038        test_file(
1039            "___test_vfs_store#1___",
1040            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB,
1041        )?;
1042
1043        test_file(
1044            "___test_vfs_store#2___",
1045            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
1046        )?;
1047
1048        unsafe {
1049            drop(VfsAppData::<AppData>::from_raw((*vfs).pAppData as *mut _));
1050            alloc::alloc::dealloc(vfs.cast(), layout);
1051        }
1052
1053        Ok(())
1054    }
1055}
1056
1057#[cfg(test)]
1058mod tests {
1059    use crate::random_name;
1060    use crate::{MemChunksFile, VfsFile};
1061
1062    #[test]
1063    fn test_chunks_file() {
1064        let mut file = MemChunksFile::new(512);
1065        file.write(&[], 0).unwrap();
1066        assert!(file.size().unwrap() == 0);
1067
1068        let mut buffer = [1; 2];
1069        let ret = file.read(&mut buffer, 0).unwrap();
1070        assert_eq!(ret, false);
1071        assert_eq!([0; 2], buffer);
1072
1073        file.write(&[1], 0).unwrap();
1074        assert!(file.size().unwrap() == 1);
1075        let mut buffer = [2; 2];
1076        let ret = file.read(&mut buffer, 0).unwrap();
1077        assert_eq!(ret, false);
1078        assert_eq!([1, 0], buffer);
1079
1080        let mut file = MemChunksFile::new(512);
1081        file.write(&[1; 512], 0).unwrap();
1082        assert!(file.size().unwrap() == 512);
1083        assert!(file.chunks.len() == 1);
1084
1085        file.truncate(512).unwrap();
1086        assert!(file.size().unwrap() == 512);
1087        assert!(file.chunks.len() == 1);
1088
1089        file.write(&[41, 42, 43], 511).unwrap();
1090        assert!(file.size().unwrap() == 514);
1091        assert!(file.chunks.len() == 2);
1092
1093        let mut buffer = [0; 3];
1094        let ret = file.read(&mut buffer, 511).unwrap();
1095        assert_eq!(ret, true);
1096        assert_eq!(buffer, [41, 42, 43]);
1097
1098        file.truncate(513).unwrap();
1099        assert!(file.size().unwrap() == 513);
1100        assert!(file.chunks.len() == 2);
1101
1102        file.write(&[1], 2048).unwrap();
1103        assert!(file.size().unwrap() == 2049);
1104        assert!(file.chunks.len() == 5);
1105
1106        file.truncate(0).unwrap();
1107        assert!(file.size().unwrap() == 0);
1108        assert!(file.chunks.len() == 0);
1109    }
1110
1111    #[test]
1112    fn random_name_is_valid() {
1113        fn random(buf: &mut [u8]) {
1114            rand::fill(buf);
1115        }
1116        let name_1 = random_name(random);
1117        let name_2 = random_name(random);
1118        assert!(name_1.is_ascii(), "Expected an ascii-name: `{name_1}`");
1119        assert!(name_2.is_ascii(), "Expected an ascii-name: `{name_2}`");
1120        assert_ne!(name_1, name_2);
1121    }
1122}