sqlite_wasm_rs/vfs/
utils.rs

1//! Some tools for implementing VFS
2
3use crate::libsqlite3::*;
4
5use fragile::Fragile;
6use js_sys::{Date, Math, Number, Uint8Array, WebAssembly};
7use parking_lot::Mutex;
8use std::{
9    ffi::{CStr, CString},
10    ops::{Deref, DerefMut},
11};
12use wasm_bindgen::{prelude::wasm_bindgen, JsCast};
13
14/// The header of the SQLite file is used to determine whether the imported file is legal.
15pub const SQLITE3_HEADER: &str = "SQLite format 3";
16
17/// A [`FragileComfirmed<T>`] wraps a non sendable `T` to be safely send to other threads.
18///
19/// Once the value has been wrapped it can be sent to other threads but access
20/// to the value on those threads will fail.
21pub struct FragileComfirmed<T> {
22    fragile: Fragile<T>,
23}
24
25unsafe impl<T> Send for FragileComfirmed<T> {}
26unsafe impl<T> Sync for FragileComfirmed<T> {}
27
28impl<T> FragileComfirmed<T> {
29    pub fn new(t: T) -> Self {
30        FragileComfirmed {
31            fragile: Fragile::new(t),
32        }
33    }
34}
35
36impl<T> Deref for FragileComfirmed<T> {
37    type Target = T;
38    fn deref(&self) -> &Self::Target {
39        self.fragile.get()
40    }
41}
42
43impl<T> DerefMut for FragileComfirmed<T> {
44    fn deref_mut(&mut self) -> &mut Self::Target {
45        self.fragile.get_mut()
46    }
47}
48
49/// get random name if zFileName is null and other cases
50pub fn get_random_name() -> String {
51    let random = Number::from(Math::random()).to_string(36).unwrap();
52    random.slice(2, random.length()).as_string().unwrap()
53}
54
55/// Directly using copy_from and copy_to to convert Uint8Array and Vec<u8> is risky.
56/// There is a possibility that the memory will grow and the buffer will be detached during copy.
57/// So here we convert on the js side.
58///
59/// Related issues:
60///
61/// <https://github.com/rustwasm/wasm-bindgen/issues/4395>
62///
63/// <https://github.com/rustwasm/wasm-bindgen/issues/4392>
64#[wasm_bindgen(module = "/src/vfs/utils.js")]
65extern "C" {
66    type JSUtils;
67
68    #[wasm_bindgen(static_method_of = JSUtils, js_name = toSlice)]
69    fn to_slice(memory: &WebAssembly::Memory, buffer: &Uint8Array, dst: *mut u8, len: usize);
70
71    #[wasm_bindgen(static_method_of = JSUtils, js_name = toUint8Array)]
72    fn to_uint8_array(memory: &WebAssembly::Memory, src: *const u8, len: usize, dst: &Uint8Array);
73}
74
75/// Copy `Uint8Array` and return new `Vec<u8>`
76pub fn copy_to_vec(src: &Uint8Array) -> Vec<u8> {
77    let mut vec = vec![0u8; src.length() as usize];
78    copy_to_slice(src, vec.as_mut_slice());
79    vec
80}
81
82/// Copy `Uint8Array` to `slice`
83pub fn copy_to_slice(src: &Uint8Array, dst: &mut [u8]) {
84    assert!(
85        src.length() as usize == dst.len(),
86        "Unit8Array and slice have different sizes"
87    );
88
89    let buf = wasm_bindgen::memory();
90    let mem = buf.unchecked_ref::<WebAssembly::Memory>();
91    JSUtils::to_slice(mem, src, dst.as_mut_ptr(), dst.len());
92}
93
94/// Copy `slice` and return new `Uint8Array`
95pub fn copy_to_uint8_array(src: &[u8]) -> Uint8Array {
96    let uint8 = Uint8Array::new_with_length(src.len() as u32);
97    copy_to_uint8_array_subarray(src, &uint8);
98    uint8
99}
100
101/// Copy `slice` to `Unit8Array`
102pub fn copy_to_uint8_array_subarray(src: &[u8], dst: &Uint8Array) {
103    assert!(
104        src.len() == dst.length() as usize,
105        "Unit8Array and slice have different sizes"
106    );
107    let buf = wasm_bindgen::memory();
108    let mem = buf.unchecked_ref::<WebAssembly::Memory>();
109    JSUtils::to_uint8_array(mem, src.as_ptr(), src.len(), dst)
110}
111
112/// Return error code if expr is true.
113///
114/// The default error code is SQLITE_ERROR.
115#[macro_export]
116macro_rules! bail {
117    ($ex:expr) => {
118        bail!($ex, SQLITE_ERROR);
119    };
120    ($ex:expr, $code: expr) => {
121        if $ex {
122            return $code;
123        }
124    };
125}
126
127/// Unpack Option<T>.
128///
129/// If it is None, return an error code.
130///
131/// The default error code is SQLITE_ERROR.
132#[macro_export]
133macro_rules! check_option {
134    ($ex:expr) => {
135        check_option!($ex, SQLITE_ERROR)
136    };
137    ($ex:expr, $code: expr) => {
138        if let Some(v) = $ex {
139            v
140        } else {
141            return $code;
142        }
143    };
144}
145
146/// Unpack Ok<T>.
147///
148/// If it is Err, return an error code.
149///
150/// The default err code is SQLITE_ERROR.
151#[macro_export]
152macro_rules! check_result {
153    ($ex:expr) => {
154        check_result!($ex, SQLITE_ERROR)
155    };
156    ($ex:expr, $code: expr) => {
157        if let Ok(v) = $ex {
158            v
159        } else {
160            return $code;
161        }
162    };
163}
164
165/// Mark unused parameter
166#[macro_export]
167macro_rules! unused {
168    ($ex:expr) => {
169        let _ = $ex;
170    };
171}
172
173/// The actual pFile type in Vfs.
174///
175/// `szOsFile` must be set to the size of `SQLiteVfsFile`.
176#[repr(C)]
177pub struct SQLiteVfsFile {
178    /// The first field must be of type sqlite_file.
179    /// In C layout, the pointer to SQLiteVfsFile is the pointer to io_methods.
180    pub io_methods: sqlite3_file,
181    /// The vfs where the file is located, usually used to manage files.
182    pub vfs: *mut sqlite3_vfs,
183    /// Flags used to open the database.
184    pub flags: i32,
185    /// The pointer to the file name.
186    /// If it is a leaked static pointer, you need to drop it manually when xClose it.
187    pub name_ptr: *const u8,
188    /// Length of the file name, on wasm32 platform, usize is u32.
189    pub name_length: usize,
190}
191
192impl SQLiteVfsFile {
193    /// Convert a `sqlite3_file` pointer to a `SQLiteVfsFile` pointer.
194    ///
195    /// # Safety
196    ///
197    /// You must ensure that the pointer passed in is `SQLiteVfsFile`
198    pub unsafe fn from_file(file: *mut sqlite3_file) -> &'static SQLiteVfsFile {
199        &*file.cast::<Self>()
200    }
201
202    /// Get the file name.
203    ///
204    /// # Safety
205    ///
206    /// When xClose, you can free the memory by `drop(Box::from_raw(ptr));`.
207    ///
208    /// Do not use again after free.
209    pub unsafe fn name(&self) -> &'static mut str {
210        // emm, `from_raw_parts_mut` is unstable
211        std::str::from_utf8_unchecked_mut(std::slice::from_raw_parts_mut(
212            self.name_ptr.cast_mut(),
213            self.name_length,
214        ))
215    }
216
217    /// Convert a `&'static SQLiteVfsFile` pointer to `*mut sqlite3_file` pointer.
218    pub fn sqlite3_file(&'static self) -> *mut sqlite3_file {
219        self as *const SQLiteVfsFile as *mut sqlite3_file
220    }
221}
222
223/// Possible errors when registering Vfs
224#[derive(thiserror::Error, Debug)]
225pub enum RegisterVfsError {
226    #[error("An error occurred converting the given vfs name to a CStr")]
227    ToCStr,
228    #[error("An error occurred while registering vfs with sqlite")]
229    RegisterVfs,
230}
231
232/// Register vfs general method
233pub fn register_vfs<IO: SQLiteIoMethods, V: SQLiteVfs<IO>>(
234    vfs_name: &str,
235    app_data: IO::AppData,
236    default_vfs: bool,
237) -> Result<*mut sqlite3_vfs, RegisterVfsError> {
238    let name = CString::new(vfs_name).map_err(|_| RegisterVfsError::ToCStr)?;
239    let name_ptr = name.into_raw();
240    let app_data = VfsAppData::new(app_data).leak();
241
242    let vfs = Box::leak(Box::new(V::vfs(name_ptr, app_data.cast())));
243    let ret = unsafe { sqlite3_vfs_register(vfs, i32::from(default_vfs)) };
244
245    if ret != SQLITE_OK {
246        unsafe {
247            drop(Box::from_raw(vfs));
248            drop(CString::from_raw(name_ptr));
249            drop(VfsAppData::from_raw(app_data));
250        }
251        return Err(RegisterVfsError::RegisterVfs);
252    }
253
254    Ok(vfs as *mut sqlite3_vfs)
255}
256
257/// Generic function for reading by page (block)
258pub fn page_read<T, G: Fn(usize) -> Option<T>, R: Fn(T, &mut [u8], (usize, usize))>(
259    buf: &mut [u8],
260    page_size: usize,
261    file_size: usize,
262    offset: usize,
263    get_page: G,
264    read_fn: R,
265) -> i32 {
266    if page_size == 0 || file_size == 0 {
267        buf.fill(0);
268        return SQLITE_IOERR_SHORT_READ;
269    }
270
271    let mut bytes_read = 0;
272    let mut p_data_offset = 0;
273    let p_data_length = buf.len();
274    let i_offset = offset;
275
276    while p_data_offset < p_data_length {
277        let file_offset = i_offset + p_data_offset;
278        let page_idx = file_offset / page_size;
279        let page_offset = file_offset % page_size;
280        let page_addr = page_idx * page_size;
281
282        let Some(page) = get_page(page_addr) else {
283            break;
284        };
285
286        let page_length = (page_size - page_offset).min(p_data_length - p_data_offset);
287        read_fn(
288            page,
289            &mut buf[p_data_offset..p_data_offset + page_length],
290            (page_offset, page_offset + page_length),
291        );
292
293        p_data_offset += page_length;
294        bytes_read += page_length;
295    }
296
297    if bytes_read < p_data_length {
298        buf[bytes_read..].fill(0);
299        return SQLITE_IOERR_SHORT_READ;
300    }
301
302    SQLITE_OK
303}
304
305/// Linear storage in memory, used for temporary DB
306#[derive(Default)]
307pub struct MemLinearFile(Vec<u8>);
308
309impl VfsFile for MemLinearFile {
310    fn read(&self, buf: &mut [u8], offset: usize) -> VfsResult<i32> {
311        let size = buf.len();
312        let end = size + offset;
313        if self.0.len() <= offset {
314            buf.fill(0);
315            return Ok(SQLITE_IOERR_SHORT_READ);
316        }
317
318        let read_end = end.min(self.0.len());
319        let read_size = read_end - offset;
320        buf[..read_size].copy_from_slice(&self.0[offset..read_end]);
321
322        if read_size < size {
323            buf[read_size..].fill(0);
324            return Ok(SQLITE_IOERR_SHORT_READ);
325        }
326        Ok(SQLITE_OK)
327    }
328
329    fn write(&mut self, buf: &[u8], offset: usize) -> VfsResult<()> {
330        let end = buf.len() + offset;
331        if end > self.0.len() {
332            self.0.resize(end, 0);
333        }
334        self.0[offset..end].copy_from_slice(buf);
335        Ok(())
336    }
337
338    fn truncate(&mut self, size: usize) -> VfsResult<()> {
339        self.0.truncate(size);
340        Ok(())
341    }
342
343    fn flush(&mut self) -> VfsResult<()> {
344        Ok(())
345    }
346
347    fn size(&self) -> VfsResult<usize> {
348        Ok(self.0.len())
349    }
350}
351
352/// Used to log and retrieve Vfs errors
353pub struct VfsError {
354    code: i32,
355    message: String,
356}
357
358impl VfsError {
359    pub fn new(code: i32, message: String) -> Self {
360        VfsError { code, message }
361    }
362}
363
364/// Wrapper for `Result`
365pub type VfsResult<T> = Result<T, VfsError>;
366
367/// Wrapper for `pAppData`
368pub struct VfsAppData<T> {
369    data: T,
370    last_err: Mutex<Option<(i32, String)>>,
371}
372
373impl<T> VfsAppData<T> {
374    pub fn new(t: T) -> Self {
375        VfsAppData {
376            data: t,
377            last_err: Mutex::new(None),
378        }
379    }
380
381    /// Leak, then pAppData can be set to VFS
382    pub fn leak(self) -> *mut Self {
383        Box::into_raw(Box::new(self))
384    }
385
386    /// # Safety
387    ///
388    /// You have to make sure the pointer is correct
389    pub unsafe fn from_raw(t: *mut Self) -> VfsAppData<T> {
390        *Box::from_raw(t)
391    }
392
393    /// Pop vfs last errcode and errmsg
394    pub fn pop_err(&self) -> Option<(i32, String)> {
395        self.last_err.lock().take()
396    }
397
398    /// Store errcode and errmsg
399    pub fn store_err(&self, err: VfsError) -> i32 {
400        let VfsError { code, message } = err;
401        self.last_err.lock().replace((code, message));
402        code
403    }
404}
405
406/// Deref only, returns immutable reference, avoids race conditions
407impl<T> Deref for VfsAppData<T> {
408    type Target = T;
409
410    fn deref(&self) -> &Self::Target {
411        &self.data
412    }
413}
414
415/// Some basic capabilities of file
416pub trait VfsFile {
417    /// Abstraction of `xRead`, returns `SQLITE_OK` or `SQLITE_IOERR_SHORT_READ`
418    fn read(&self, buf: &mut [u8], offset: usize) -> VfsResult<i32>;
419    /// Abstraction of `xWrite`
420    fn write(&mut self, buf: &[u8], offset: usize) -> VfsResult<()>;
421    /// Abstraction of `xTruncate`
422    fn truncate(&mut self, size: usize) -> VfsResult<()>;
423    /// Abstraction of `xSync`
424    fn flush(&mut self) -> VfsResult<()>;
425    /// Abstraction of `xFileSize`
426    fn size(&self) -> VfsResult<usize>;
427}
428
429/// Make changes to files
430pub trait VfsStore<File, AppData> {
431    /// Convert pAppData to the type we need
432    ///
433    /// # Safety
434    ///
435    /// As long as it is set through the abstract VFS interface, it is safe
436    unsafe fn app_data(vfs: *mut sqlite3_vfs) -> &'static VfsAppData<AppData> {
437        &*(*vfs).pAppData.cast()
438    }
439    /// Get file path, use for `xOpen`
440    fn name2path(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<String> {
441        unused!(vfs);
442        Ok(file.into())
443    }
444    /// Adding files to the Store, use for `xOpen` and `xAccess`
445    fn add_file(vfs: *mut sqlite3_vfs, file: &str, flags: i32) -> VfsResult<()>;
446    /// Checks if the specified file exists in the Store, use for `xOpen` and `xAccess`
447    fn contains_file(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<bool>;
448    /// Delete the specified file in the Store, use for `xClose` and `xDelete`
449    fn delete_file(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<()>;
450    /// Read the file contents, use for `xRead`, `xFileSize`
451    fn with_file<F: Fn(&File) -> i32>(vfs_file: &SQLiteVfsFile, f: F) -> VfsResult<i32>;
452    /// Write the file contents, use for `xWrite`, `xTruncate` and `xSync`
453    fn with_file_mut<F: Fn(&mut File) -> i32>(vfs_file: &SQLiteVfsFile, f: F) -> VfsResult<i32>;
454}
455
456/// Abstraction of SQLite vfs
457#[allow(clippy::missing_safety_doc)]
458pub trait SQLiteVfs<IO: SQLiteIoMethods> {
459    const VERSION: ::std::os::raw::c_int;
460    const MAX_PATH_SIZE: ::std::os::raw::c_int = 1024;
461
462    fn vfs(
463        vfs_name: *const ::std::os::raw::c_char,
464        app_data: *mut VfsAppData<IO::AppData>,
465    ) -> sqlite3_vfs {
466        sqlite3_vfs {
467            iVersion: Self::VERSION,
468            szOsFile: std::mem::size_of::<SQLiteVfsFile>() as i32,
469            mxPathname: Self::MAX_PATH_SIZE,
470            pNext: std::ptr::null_mut(),
471            zName: vfs_name,
472            pAppData: app_data.cast(),
473            xOpen: Some(Self::xOpen),
474            xDelete: Some(Self::xDelete),
475            xAccess: Some(Self::xAccess),
476            xFullPathname: Some(Self::xFullPathname),
477            xDlOpen: None,
478            xDlError: None,
479            xDlSym: None,
480            xDlClose: None,
481            xRandomness: Some(x_methods_shim::xRandomness),
482            xSleep: Some(x_methods_shim::xSleep),
483            xCurrentTime: Some(x_methods_shim::xCurrentTime),
484            xGetLastError: Some(Self::xGetLastError),
485            xCurrentTimeInt64: Some(x_methods_shim::xCurrentTimeInt64),
486            xSetSystemCall: None,
487            xGetSystemCall: None,
488            xNextSystemCall: None,
489        }
490    }
491
492    unsafe extern "C" fn xOpen(
493        pVfs: *mut sqlite3_vfs,
494        zName: sqlite3_filename,
495        pFile: *mut sqlite3_file,
496        flags: ::std::os::raw::c_int,
497        pOutFlags: *mut ::std::os::raw::c_int,
498    ) -> ::std::os::raw::c_int {
499        let app_data = IO::Store::app_data(pVfs);
500
501        let name = if zName.is_null() {
502            get_random_name()
503        } else {
504            check_result!(CStr::from_ptr(zName).to_str()).into()
505        };
506
507        let name = match IO::Store::name2path(pVfs, &name) {
508            Ok(name) => name,
509            Err(err) => return app_data.store_err(err),
510        };
511
512        let exist = match IO::Store::contains_file(pVfs, &name) {
513            Ok(exist) => exist,
514            Err(err) => return app_data.store_err(err),
515        };
516
517        if !exist {
518            if flags & SQLITE_OPEN_CREATE == 0 {
519                return app_data.store_err(VfsError::new(
520                    SQLITE_CANTOPEN,
521                    format!("file not found: {name}"),
522                ));
523            }
524            if let Err(err) = IO::Store::add_file(pVfs, &name, flags) {
525                return app_data.store_err(err);
526            }
527        }
528
529        let leak = name.leak();
530        let vfs_file = pFile.cast::<SQLiteVfsFile>();
531        (*vfs_file).vfs = pVfs;
532        (*vfs_file).flags = flags;
533        (*vfs_file).name_ptr = leak.as_ptr();
534        (*vfs_file).name_length = leak.len();
535
536        (*pFile).pMethods = &IO::METHODS;
537
538        if !pOutFlags.is_null() {
539            *pOutFlags = flags;
540        }
541
542        SQLITE_OK
543    }
544
545    unsafe extern "C" fn xDelete(
546        pVfs: *mut sqlite3_vfs,
547        zName: *const ::std::os::raw::c_char,
548        syncDir: ::std::os::raw::c_int,
549    ) -> ::std::os::raw::c_int {
550        unused!(syncDir);
551
552        let app_data = IO::Store::app_data(pVfs);
553        bail!(zName.is_null(), SQLITE_IOERR_DELETE);
554        let s = check_result!(CStr::from_ptr(zName).to_str());
555        if let Err(err) = IO::Store::delete_file(pVfs, s) {
556            app_data.store_err(err)
557        } else {
558            SQLITE_OK
559        }
560    }
561
562    unsafe extern "C" fn xAccess(
563        pVfs: *mut sqlite3_vfs,
564        zName: *const ::std::os::raw::c_char,
565        flags: ::std::os::raw::c_int,
566        pResOut: *mut ::std::os::raw::c_int,
567    ) -> ::std::os::raw::c_int {
568        unused!(flags);
569
570        *pResOut = if zName.is_null() {
571            0
572        } else {
573            let app_data = IO::Store::app_data(pVfs);
574            let file = check_result!(CStr::from_ptr(zName).to_str());
575            let exist = match IO::Store::contains_file(pVfs, file) {
576                Ok(exist) => exist,
577                Err(err) => return app_data.store_err(err),
578            };
579            i32::from(exist)
580        };
581
582        SQLITE_OK
583    }
584
585    unsafe extern "C" fn xFullPathname(
586        pVfs: *mut sqlite3_vfs,
587        zName: *const ::std::os::raw::c_char,
588        nOut: ::std::os::raw::c_int,
589        zOut: *mut ::std::os::raw::c_char,
590    ) -> ::std::os::raw::c_int {
591        unused!(pVfs);
592        bail!(zName.is_null() || zOut.is_null(), SQLITE_CANTOPEN);
593        let len = CStr::from_ptr(zName).to_bytes_with_nul().len();
594        bail!(len > nOut as usize, SQLITE_CANTOPEN);
595        zName.copy_to(zOut, len);
596        SQLITE_OK
597    }
598
599    unsafe extern "C" fn xGetLastError(
600        pVfs: *mut sqlite3_vfs,
601        nOut: ::std::os::raw::c_int,
602        zOut: *mut ::std::os::raw::c_char,
603    ) -> ::std::os::raw::c_int {
604        let app_data = IO::Store::app_data(pVfs);
605        let Some((code, msg)) = app_data.pop_err() else {
606            return SQLITE_OK;
607        };
608        if !zOut.is_null() {
609            let nOut = nOut as usize;
610            let count = msg.len().min(nOut);
611            msg.as_ptr().copy_to(zOut.cast(), count);
612            let zero = match nOut.cmp(&msg.len()) {
613                std::cmp::Ordering::Less | std::cmp::Ordering::Equal => nOut,
614                std::cmp::Ordering::Greater => msg.len() + 1,
615            };
616            if zero > 0 {
617                std::ptr::write(zOut.add(zero - 1), 0);
618            }
619        }
620        code
621    }
622}
623
624/// Abstraction of SQLite vfs's io methods
625#[allow(clippy::missing_safety_doc)]
626pub trait SQLiteIoMethods {
627    type File: VfsFile;
628    type AppData: 'static;
629    type Store: VfsStore<Self::File, Self::AppData>;
630
631    const VERSION: ::std::os::raw::c_int;
632
633    const METHODS: sqlite3_io_methods = sqlite3_io_methods {
634        iVersion: Self::VERSION,
635        xClose: Some(Self::xClose),
636        xRead: Some(Self::xRead),
637        xWrite: Some(Self::xWrite),
638        xTruncate: Some(Self::xTruncate),
639        xSync: Some(Self::xSync),
640        xFileSize: Some(Self::xFileSize),
641        xLock: Some(Self::xLock),
642        xUnlock: Some(Self::xUnlock),
643        xCheckReservedLock: Some(Self::xCheckReservedLock),
644        xFileControl: Some(Self::xFileControl),
645        xSectorSize: Some(Self::xSectorSize),
646        xDeviceCharacteristics: Some(Self::xDeviceCharacteristics),
647        xShmMap: None,
648        xShmLock: None,
649        xShmBarrier: None,
650        xShmUnmap: None,
651        xFetch: None,
652        xUnfetch: None,
653    };
654
655    unsafe extern "C" fn xClose(pFile: *mut sqlite3_file) -> ::std::os::raw::c_int {
656        let vfs_file = SQLiteVfsFile::from_file(pFile);
657        let app_data = Self::Store::app_data(vfs_file.vfs);
658
659        if vfs_file.flags & SQLITE_OPEN_DELETEONCLOSE != 0 {
660            if let Err(err) = Self::Store::delete_file(vfs_file.vfs, vfs_file.name()) {
661                return app_data.store_err(err);
662            }
663        }
664
665        drop(Box::from_raw(vfs_file.name()));
666
667        SQLITE_OK
668    }
669
670    unsafe extern "C" fn xRead(
671        pFile: *mut sqlite3_file,
672        zBuf: *mut ::std::os::raw::c_void,
673        iAmt: ::std::os::raw::c_int,
674        iOfst: sqlite3_int64,
675    ) -> ::std::os::raw::c_int {
676        let vfs_file = SQLiteVfsFile::from_file(pFile);
677        let app_data = Self::Store::app_data(vfs_file.vfs);
678
679        let f = |file: &Self::File| {
680            let size = iAmt as usize;
681            let offset = iOfst as usize;
682            let slice = std::slice::from_raw_parts_mut(zBuf.cast::<u8>(), size);
683            match file.read(slice, offset) {
684                Ok(code) => code,
685                Err(err) => app_data.store_err(err),
686            }
687        };
688
689        match Self::Store::with_file(vfs_file, f) {
690            Ok(code) => code,
691            Err(err) => app_data.store_err(err),
692        }
693    }
694
695    unsafe extern "C" fn xWrite(
696        pFile: *mut sqlite3_file,
697        zBuf: *const ::std::os::raw::c_void,
698        iAmt: ::std::os::raw::c_int,
699        iOfst: sqlite3_int64,
700    ) -> ::std::os::raw::c_int {
701        let vfs_file = SQLiteVfsFile::from_file(pFile);
702        let app_data = Self::Store::app_data(vfs_file.vfs);
703
704        let f = |file: &mut Self::File| {
705            let (offset, size) = (iOfst as usize, iAmt as usize);
706            let slice = std::slice::from_raw_parts(zBuf.cast::<u8>(), size);
707            if let Err(err) = file.write(slice, offset) {
708                app_data.store_err(err)
709            } else {
710                SQLITE_OK
711            }
712        };
713
714        match Self::Store::with_file_mut(vfs_file, f) {
715            Ok(code) => code,
716            Err(err) => app_data.store_err(err),
717        }
718    }
719
720    unsafe extern "C" fn xTruncate(
721        pFile: *mut sqlite3_file,
722        size: sqlite3_int64,
723    ) -> ::std::os::raw::c_int {
724        let vfs_file = SQLiteVfsFile::from_file(pFile);
725        let app_data = Self::Store::app_data(vfs_file.vfs);
726
727        let f = |file: &mut Self::File| {
728            if let Err(err) = file.truncate(size as usize) {
729                app_data.store_err(err)
730            } else {
731                SQLITE_OK
732            }
733        };
734
735        match Self::Store::with_file_mut(vfs_file, f) {
736            Ok(code) => code,
737            Err(err) => app_data.store_err(err),
738        }
739    }
740
741    unsafe extern "C" fn xSync(
742        pFile: *mut sqlite3_file,
743        flags: ::std::os::raw::c_int,
744    ) -> ::std::os::raw::c_int {
745        unused!(flags);
746
747        let vfs_file = SQLiteVfsFile::from_file(pFile);
748        let app_data = Self::Store::app_data(vfs_file.vfs);
749
750        let f = |file: &mut Self::File| {
751            if let Err(err) = file.flush() {
752                app_data.store_err(err)
753            } else {
754                SQLITE_OK
755            }
756        };
757
758        match Self::Store::with_file_mut(vfs_file, f) {
759            Ok(code) => code,
760            Err(err) => app_data.store_err(err),
761        }
762    }
763
764    unsafe extern "C" fn xFileSize(
765        pFile: *mut sqlite3_file,
766        pSize: *mut sqlite3_int64,
767    ) -> ::std::os::raw::c_int {
768        let vfs_file = SQLiteVfsFile::from_file(pFile);
769        let app_data = Self::Store::app_data(vfs_file.vfs);
770
771        let f = |file: &Self::File| match file.size() {
772            Ok(size) => {
773                *pSize = size as sqlite3_int64;
774                SQLITE_OK
775            }
776            Err(err) => app_data.store_err(err),
777        };
778
779        match Self::Store::with_file(vfs_file, f) {
780            Ok(code) => code,
781            Err(err) => app_data.store_err(err),
782        }
783    }
784
785    unsafe extern "C" fn xLock(
786        pFile: *mut sqlite3_file,
787        eLock: ::std::os::raw::c_int,
788    ) -> ::std::os::raw::c_int {
789        unused!((pFile, eLock));
790        SQLITE_OK
791    }
792
793    unsafe extern "C" fn xUnlock(
794        pFile: *mut sqlite3_file,
795        eLock: ::std::os::raw::c_int,
796    ) -> ::std::os::raw::c_int {
797        unused!((pFile, eLock));
798        SQLITE_OK
799    }
800
801    unsafe extern "C" fn xCheckReservedLock(
802        pFile: *mut sqlite3_file,
803        pResOut: *mut ::std::os::raw::c_int,
804    ) -> ::std::os::raw::c_int {
805        unused!(pFile);
806        *pResOut = 0;
807        SQLITE_OK
808    }
809
810    unsafe extern "C" fn xFileControl(
811        pFile: *mut sqlite3_file,
812        op: ::std::os::raw::c_int,
813        pArg: *mut ::std::os::raw::c_void,
814    ) -> ::std::os::raw::c_int {
815        unused!((pFile, op, pArg));
816        SQLITE_NOTFOUND
817    }
818
819    unsafe extern "C" fn xSectorSize(pFile: *mut sqlite3_file) -> ::std::os::raw::c_int {
820        unused!(pFile);
821        512
822    }
823
824    unsafe extern "C" fn xDeviceCharacteristics(pFile: *mut sqlite3_file) -> ::std::os::raw::c_int {
825        unused!(pFile);
826        0
827    }
828}
829
830/// Some x methods simulated using JS
831#[allow(clippy::missing_safety_doc)]
832pub mod x_methods_shim {
833    use super::*;
834
835    /// thread::sleep is available when atomics are enabled
836    #[cfg(target_feature = "atomics")]
837    pub unsafe extern "C" fn xSleep(
838        _pVfs: *mut sqlite3_vfs,
839        microseconds: ::std::os::raw::c_int,
840    ) -> ::std::os::raw::c_int {
841        use std::{thread, time::Duration};
842        thread::sleep(Duration::from_micros(microseconds as u64));
843        SQLITE_OK
844    }
845
846    #[cfg(not(target_feature = "atomics"))]
847    pub unsafe extern "C" fn xSleep(
848        _pVfs: *mut sqlite3_vfs,
849        _microseconds: ::std::os::raw::c_int,
850    ) -> ::std::os::raw::c_int {
851        SQLITE_OK
852    }
853
854    /// https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L951
855    pub unsafe extern "C" fn xRandomness(
856        _pVfs: *mut sqlite3_vfs,
857        nByte: ::std::os::raw::c_int,
858        zOut: *mut ::std::os::raw::c_char,
859    ) -> ::std::os::raw::c_int {
860        for i in 0..nByte as usize {
861            *zOut.add(i) = (Math::random() * 255000.0) as _;
862        }
863        nByte
864    }
865
866    /// https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L870
867    pub unsafe extern "C" fn xCurrentTime(
868        _pVfs: *mut sqlite3_vfs,
869        pTimeOut: *mut f64,
870    ) -> ::std::os::raw::c_int {
871        *pTimeOut = 2440587.5 + (Date::new_0().get_time() / 86400000.0);
872        SQLITE_OK
873    }
874
875    /// https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L877
876    pub unsafe extern "C" fn xCurrentTimeInt64(
877        _pVfs: *mut sqlite3_vfs,
878        pOut: *mut sqlite3_int64,
879    ) -> ::std::os::raw::c_int {
880        *pOut = ((2440587.5 * 86400000.0) + Date::new_0().get_time()) as sqlite3_int64;
881        SQLITE_OK
882    }
883}
884
885#[derive(thiserror::Error, Debug)]
886pub enum ImportDbError {
887    #[error("Byte array size is invalid for an SQLite db.")]
888    InvalidDbSize,
889    #[error("Input does not contain an SQLite database header.")]
890    InvalidHeader,
891    #[error("Page size must be a power of two between 512 and 65536 inclusive")]
892    InvalidPageSize,
893}
894
895/// Simple verification when importing db, and return page size;
896pub fn check_import_db(bytes: &[u8]) -> Result<usize, ImportDbError> {
897    let length = bytes.len();
898
899    if length < 512 && length % 512 != 0 {
900        return Err(ImportDbError::InvalidDbSize);
901    }
902
903    if SQLITE3_HEADER
904        .as_bytes()
905        .iter()
906        .zip(bytes)
907        .any(|(x, y)| x != y)
908    {
909        return Err(ImportDbError::InvalidHeader);
910    }
911
912    // The database page size in bytes.
913    // Must be a power of two between 512 and 32768 inclusive, or the value 1 representing a page size of 65536.
914    let page_size = u16::from_be_bytes([bytes[16], bytes[17]]);
915    let page_size = if page_size == 1 {
916        65536
917    } else {
918        usize::from(page_size)
919    };
920
921    Ok(page_size)
922}
923
924/// 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.
925pub fn check_db_and_page_size(db_size: usize, page_size: usize) -> Result<(), ImportDbError> {
926    if !(page_size.is_power_of_two() && (512..=65536).contains(&page_size)) {
927        return Err(ImportDbError::InvalidPageSize);
928    }
929    if db_size % page_size != 0 {
930        return Err(ImportDbError::InvalidDbSize);
931    }
932    Ok(())
933}
934
935#[cfg(test)]
936mod tests {
937    use crate::vfs::utils::{copy_to_slice, copy_to_uint8_array_subarray};
938
939    use super::{copy_to_uint8_array, copy_to_vec};
940    use js_sys::Uint8Array;
941    use wasm_bindgen_test::wasm_bindgen_test;
942
943    #[wasm_bindgen_test]
944    fn test_js_utils() {
945        let buf1 = vec![1, 2, 3, 4];
946        let uint8 = copy_to_uint8_array(&buf1);
947        let buf2 = copy_to_vec(&uint8);
948        assert_eq!(buf1, buf2);
949
950        let mut buf3 = vec![0u8; 2];
951        copy_to_slice(&uint8.subarray(0, 2), &mut buf3);
952        assert_eq!(buf3, vec![1, 2]);
953
954        let buf4 = Uint8Array::new_with_length(3);
955        copy_to_uint8_array_subarray(&buf3, &buf4.subarray(1, 3));
956        assert!(buf4.get_index(0) == 0);
957        assert!(buf4.get_index(1) == 1);
958        assert!(buf4.get_index(2) == 2);
959    }
960}