sqlite_wasm_rs/vfs/
utils.rs

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