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