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 [`FragileComfirmed<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 FragileComfirmed<T> {
82    fragile: Fragile<T>,
83}
84
85unsafe impl<T> Send for FragileComfirmed<T> {}
86unsafe impl<T> Sync for FragileComfirmed<T> {}
87
88impl<T> FragileComfirmed<T> {
89    pub fn new(t: T) -> Self {
90        FragileComfirmed {
91            fragile: Fragile::new(t),
92        }
93    }
94}
95
96impl<T> Deref for FragileComfirmed<T> {
97    type Target = T;
98    fn deref(&self) -> &Self::Target {
99        self.fragile.get()
100    }
101}
102
103impl<T> DerefMut for FragileComfirmed<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<i32> {
153        let Some(chunk_size) = self.chunk_size else {
154            buf.fill(0);
155            return Ok(SQLITE_IOERR_SHORT_READ);
156        };
157
158        if self.file_size <= offset {
159            buf.fill(0);
160            return Ok(SQLITE_IOERR_SHORT_READ);
161        }
162
163        if chunk_size == buf.len() && offset % chunk_size == 0 {
164            buf.copy_from_slice(&self.chunks[offset / chunk_size]);
165            Ok(SQLITE_OK)
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(SQLITE_IOERR_SHORT_READ)
186            } else {
187                Ok(SQLITE_OK)
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 `SQLITE_OK` or `SQLITE_IOERR_SHORT_READ`
423    fn read(&self, buf: &mut [u8], offset: usize) -> VfsResult<i32>;
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    /// Get file path, use for `xOpen`
445    fn name2path(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<String> {
446        unused!(vfs);
447        Ok(file.into())
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) -> 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) -> i32>(vfs_file: &SQLiteVfsFile, f: F) -> VfsResult<i32>;
459}
460
461/// Abstraction of SQLite vfs
462#[allow(clippy::missing_safety_doc)]
463pub trait SQLiteVfs<IO: SQLiteIoMethods> {
464    const VERSION: ::std::os::raw::c_int;
465    const MAX_PATH_SIZE: ::std::os::raw::c_int = 1024;
466
467    fn vfs(
468        vfs_name: *const ::std::os::raw::c_char,
469        app_data: *mut VfsAppData<IO::AppData>,
470    ) -> sqlite3_vfs {
471        sqlite3_vfs {
472            iVersion: Self::VERSION,
473            szOsFile: std::mem::size_of::<SQLiteVfsFile>() as i32,
474            mxPathname: Self::MAX_PATH_SIZE,
475            pNext: std::ptr::null_mut(),
476            zName: vfs_name,
477            pAppData: app_data.cast(),
478            xOpen: Some(Self::xOpen),
479            xDelete: Some(Self::xDelete),
480            xAccess: Some(Self::xAccess),
481            xFullPathname: Some(Self::xFullPathname),
482            xDlOpen: None,
483            xDlError: None,
484            xDlSym: None,
485            xDlClose: None,
486            xRandomness: Some(x_methods_shim::xRandomness),
487            xSleep: Some(x_methods_shim::xSleep),
488            xCurrentTime: Some(x_methods_shim::xCurrentTime),
489            xGetLastError: Some(Self::xGetLastError),
490            xCurrentTimeInt64: Some(x_methods_shim::xCurrentTimeInt64),
491            xSetSystemCall: None,
492            xGetSystemCall: None,
493            xNextSystemCall: None,
494        }
495    }
496
497    unsafe extern "C" fn xOpen(
498        pVfs: *mut sqlite3_vfs,
499        zName: sqlite3_filename,
500        pFile: *mut sqlite3_file,
501        flags: ::std::os::raw::c_int,
502        pOutFlags: *mut ::std::os::raw::c_int,
503    ) -> ::std::os::raw::c_int {
504        let app_data = IO::Store::app_data(pVfs);
505
506        let name = if zName.is_null() {
507            random_name()
508        } else {
509            check_result!(CStr::from_ptr(zName).to_str()).into()
510        };
511
512        let name = match IO::Store::name2path(pVfs, &name) {
513            Ok(name) => name,
514            Err(err) => return app_data.store_err(err),
515        };
516
517        let exist = match IO::Store::contains_file(pVfs, &name) {
518            Ok(exist) => exist,
519            Err(err) => return app_data.store_err(err),
520        };
521
522        if !exist {
523            if flags & SQLITE_OPEN_CREATE == 0 {
524                return app_data.store_err(VfsError::new(
525                    SQLITE_CANTOPEN,
526                    format!("file not found: {name}"),
527                ));
528            }
529            if let Err(err) = IO::Store::add_file(pVfs, &name, flags) {
530                return app_data.store_err(err);
531            }
532        }
533
534        let leak = name.leak();
535        let vfs_file = pFile.cast::<SQLiteVfsFile>();
536        (*vfs_file).vfs = pVfs;
537        (*vfs_file).flags = flags;
538        (*vfs_file).name_ptr = leak.as_ptr();
539        (*vfs_file).name_length = leak.len();
540
541        (*pFile).pMethods = &IO::METHODS;
542
543        if !pOutFlags.is_null() {
544            *pOutFlags = flags;
545        }
546
547        SQLITE_OK
548    }
549
550    unsafe extern "C" fn xDelete(
551        pVfs: *mut sqlite3_vfs,
552        zName: *const ::std::os::raw::c_char,
553        syncDir: ::std::os::raw::c_int,
554    ) -> ::std::os::raw::c_int {
555        unused!(syncDir);
556
557        let app_data = IO::Store::app_data(pVfs);
558        bail!(zName.is_null(), SQLITE_IOERR_DELETE);
559        let s = check_result!(CStr::from_ptr(zName).to_str());
560        if let Err(err) = IO::Store::delete_file(pVfs, s) {
561            app_data.store_err(err)
562        } else {
563            SQLITE_OK
564        }
565    }
566
567    unsafe extern "C" fn xAccess(
568        pVfs: *mut sqlite3_vfs,
569        zName: *const ::std::os::raw::c_char,
570        flags: ::std::os::raw::c_int,
571        pResOut: *mut ::std::os::raw::c_int,
572    ) -> ::std::os::raw::c_int {
573        unused!(flags);
574
575        *pResOut = if zName.is_null() {
576            0
577        } else {
578            let app_data = IO::Store::app_data(pVfs);
579            let file = check_result!(CStr::from_ptr(zName).to_str());
580            let exist = match IO::Store::contains_file(pVfs, file) {
581                Ok(exist) => exist,
582                Err(err) => return app_data.store_err(err),
583            };
584            i32::from(exist)
585        };
586
587        SQLITE_OK
588    }
589
590    unsafe extern "C" fn xFullPathname(
591        pVfs: *mut sqlite3_vfs,
592        zName: *const ::std::os::raw::c_char,
593        nOut: ::std::os::raw::c_int,
594        zOut: *mut ::std::os::raw::c_char,
595    ) -> ::std::os::raw::c_int {
596        unused!(pVfs);
597        bail!(zName.is_null() || zOut.is_null(), SQLITE_CANTOPEN);
598        let len = CStr::from_ptr(zName).to_bytes_with_nul().len();
599        bail!(len > nOut as usize, SQLITE_CANTOPEN);
600        zName.copy_to(zOut, len);
601        SQLITE_OK
602    }
603
604    unsafe extern "C" fn xGetLastError(
605        pVfs: *mut sqlite3_vfs,
606        nOut: ::std::os::raw::c_int,
607        zOut: *mut ::std::os::raw::c_char,
608    ) -> ::std::os::raw::c_int {
609        let app_data = IO::Store::app_data(pVfs);
610        let Some((code, msg)) = app_data.pop_err() else {
611            return SQLITE_OK;
612        };
613        if !zOut.is_null() {
614            let nOut = nOut as usize;
615            let count = msg.len().min(nOut);
616            msg.as_ptr().copy_to(zOut.cast(), count);
617            let zero = match nOut.cmp(&msg.len()) {
618                std::cmp::Ordering::Less | std::cmp::Ordering::Equal => nOut,
619                std::cmp::Ordering::Greater => msg.len() + 1,
620            };
621            if zero > 0 {
622                std::ptr::write(zOut.add(zero - 1), 0);
623            }
624        }
625        code
626    }
627}
628
629/// Abstraction of SQLite vfs's io methods
630#[allow(clippy::missing_safety_doc)]
631pub trait SQLiteIoMethods {
632    type File: VfsFile;
633    type AppData: 'static;
634    type Store: VfsStore<Self::File, Self::AppData>;
635
636    const VERSION: ::std::os::raw::c_int;
637
638    const METHODS: sqlite3_io_methods = sqlite3_io_methods {
639        iVersion: Self::VERSION,
640        xClose: Some(Self::xClose),
641        xRead: Some(Self::xRead),
642        xWrite: Some(Self::xWrite),
643        xTruncate: Some(Self::xTruncate),
644        xSync: Some(Self::xSync),
645        xFileSize: Some(Self::xFileSize),
646        xLock: Some(Self::xLock),
647        xUnlock: Some(Self::xUnlock),
648        xCheckReservedLock: Some(Self::xCheckReservedLock),
649        xFileControl: Some(Self::xFileControl),
650        xSectorSize: Some(Self::xSectorSize),
651        xDeviceCharacteristics: Some(Self::xDeviceCharacteristics),
652        xShmMap: None,
653        xShmLock: None,
654        xShmBarrier: None,
655        xShmUnmap: None,
656        xFetch: None,
657        xUnfetch: None,
658    };
659
660    unsafe extern "C" fn xClose(pFile: *mut sqlite3_file) -> ::std::os::raw::c_int {
661        let vfs_file = SQLiteVfsFile::from_file(pFile);
662        let app_data = Self::Store::app_data(vfs_file.vfs);
663
664        if vfs_file.flags & SQLITE_OPEN_DELETEONCLOSE != 0 {
665            if let Err(err) = Self::Store::delete_file(vfs_file.vfs, vfs_file.name()) {
666                return app_data.store_err(err);
667            }
668        }
669
670        drop(Box::from_raw(vfs_file.name()));
671
672        SQLITE_OK
673    }
674
675    unsafe extern "C" fn xRead(
676        pFile: *mut sqlite3_file,
677        zBuf: *mut ::std::os::raw::c_void,
678        iAmt: ::std::os::raw::c_int,
679        iOfst: sqlite3_int64,
680    ) -> ::std::os::raw::c_int {
681        let vfs_file = SQLiteVfsFile::from_file(pFile);
682        let app_data = Self::Store::app_data(vfs_file.vfs);
683
684        let f = |file: &Self::File| {
685            let size = iAmt as usize;
686            let offset = iOfst as usize;
687            let slice = std::slice::from_raw_parts_mut(zBuf.cast::<u8>(), size);
688            match file.read(slice, offset) {
689                Ok(code) => code,
690                Err(err) => app_data.store_err(err),
691            }
692        };
693
694        match Self::Store::with_file(vfs_file, f) {
695            Ok(code) => code,
696            Err(err) => app_data.store_err(err),
697        }
698    }
699
700    unsafe extern "C" fn xWrite(
701        pFile: *mut sqlite3_file,
702        zBuf: *const ::std::os::raw::c_void,
703        iAmt: ::std::os::raw::c_int,
704        iOfst: sqlite3_int64,
705    ) -> ::std::os::raw::c_int {
706        let vfs_file = SQLiteVfsFile::from_file(pFile);
707        let app_data = Self::Store::app_data(vfs_file.vfs);
708
709        let f = |file: &mut Self::File| {
710            let (offset, size) = (iOfst as usize, iAmt as usize);
711            let slice = std::slice::from_raw_parts(zBuf.cast::<u8>(), size);
712            if let Err(err) = file.write(slice, offset) {
713                app_data.store_err(err)
714            } else {
715                SQLITE_OK
716            }
717        };
718
719        match Self::Store::with_file_mut(vfs_file, f) {
720            Ok(code) => code,
721            Err(err) => app_data.store_err(err),
722        }
723    }
724
725    unsafe extern "C" fn xTruncate(
726        pFile: *mut sqlite3_file,
727        size: sqlite3_int64,
728    ) -> ::std::os::raw::c_int {
729        let vfs_file = SQLiteVfsFile::from_file(pFile);
730        let app_data = Self::Store::app_data(vfs_file.vfs);
731
732        let f = |file: &mut Self::File| {
733            if let Err(err) = file.truncate(size as usize) {
734                app_data.store_err(err)
735            } else {
736                SQLITE_OK
737            }
738        };
739
740        match Self::Store::with_file_mut(vfs_file, f) {
741            Ok(code) => code,
742            Err(err) => app_data.store_err(err),
743        }
744    }
745
746    unsafe extern "C" fn xSync(
747        pFile: *mut sqlite3_file,
748        flags: ::std::os::raw::c_int,
749    ) -> ::std::os::raw::c_int {
750        unused!(flags);
751
752        let vfs_file = SQLiteVfsFile::from_file(pFile);
753        let app_data = Self::Store::app_data(vfs_file.vfs);
754
755        let f = |file: &mut Self::File| {
756            if let Err(err) = file.flush() {
757                app_data.store_err(err)
758            } else {
759                SQLITE_OK
760            }
761        };
762
763        match Self::Store::with_file_mut(vfs_file, f) {
764            Ok(code) => code,
765            Err(err) => app_data.store_err(err),
766        }
767    }
768
769    unsafe extern "C" fn xFileSize(
770        pFile: *mut sqlite3_file,
771        pSize: *mut sqlite3_int64,
772    ) -> ::std::os::raw::c_int {
773        let vfs_file = SQLiteVfsFile::from_file(pFile);
774        let app_data = Self::Store::app_data(vfs_file.vfs);
775
776        let f = |file: &Self::File| match file.size() {
777            Ok(size) => {
778                *pSize = size as sqlite3_int64;
779                SQLITE_OK
780            }
781            Err(err) => app_data.store_err(err),
782        };
783
784        match Self::Store::with_file(vfs_file, f) {
785            Ok(code) => code,
786            Err(err) => app_data.store_err(err),
787        }
788    }
789
790    unsafe extern "C" fn xLock(
791        pFile: *mut sqlite3_file,
792        eLock: ::std::os::raw::c_int,
793    ) -> ::std::os::raw::c_int {
794        unused!((pFile, eLock));
795        SQLITE_OK
796    }
797
798    unsafe extern "C" fn xUnlock(
799        pFile: *mut sqlite3_file,
800        eLock: ::std::os::raw::c_int,
801    ) -> ::std::os::raw::c_int {
802        unused!((pFile, eLock));
803        SQLITE_OK
804    }
805
806    unsafe extern "C" fn xCheckReservedLock(
807        pFile: *mut sqlite3_file,
808        pResOut: *mut ::std::os::raw::c_int,
809    ) -> ::std::os::raw::c_int {
810        unused!(pFile);
811        *pResOut = 0;
812        SQLITE_OK
813    }
814
815    unsafe extern "C" fn xFileControl(
816        pFile: *mut sqlite3_file,
817        op: ::std::os::raw::c_int,
818        pArg: *mut ::std::os::raw::c_void,
819    ) -> ::std::os::raw::c_int {
820        unused!((pFile, op, pArg));
821        SQLITE_NOTFOUND
822    }
823
824    unsafe extern "C" fn xSectorSize(pFile: *mut sqlite3_file) -> ::std::os::raw::c_int {
825        unused!(pFile);
826        512
827    }
828
829    unsafe extern "C" fn xDeviceCharacteristics(pFile: *mut sqlite3_file) -> ::std::os::raw::c_int {
830        unused!(pFile);
831        0
832    }
833}
834
835/// Some x methods simulated using JS
836#[allow(clippy::missing_safety_doc)]
837pub mod x_methods_shim {
838    use super::*;
839
840    /// thread::sleep is available when atomics is enabled
841    #[cfg(target_feature = "atomics")]
842    pub unsafe extern "C" fn xSleep(
843        _pVfs: *mut sqlite3_vfs,
844        microseconds: ::std::os::raw::c_int,
845    ) -> ::std::os::raw::c_int {
846        use std::{thread, time::Duration};
847        thread::sleep(Duration::from_micros(microseconds as u64));
848        SQLITE_OK
849    }
850
851    #[cfg(not(target_feature = "atomics"))]
852    pub unsafe extern "C" fn xSleep(
853        _pVfs: *mut sqlite3_vfs,
854        _microseconds: ::std::os::raw::c_int,
855    ) -> ::std::os::raw::c_int {
856        SQLITE_OK
857    }
858
859    /// https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L951
860    pub unsafe extern "C" fn xRandomness(
861        _pVfs: *mut sqlite3_vfs,
862        nByte: ::std::os::raw::c_int,
863        zOut: *mut ::std::os::raw::c_char,
864    ) -> ::std::os::raw::c_int {
865        for i in 0..nByte as usize {
866            *zOut.add(i) = (Math::random() * 255000.0) as _;
867        }
868        nByte
869    }
870
871    /// https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L870
872    pub unsafe extern "C" fn xCurrentTime(
873        _pVfs: *mut sqlite3_vfs,
874        pTimeOut: *mut f64,
875    ) -> ::std::os::raw::c_int {
876        *pTimeOut = 2440587.5 + (Date::new_0().get_time() / 86400000.0);
877        SQLITE_OK
878    }
879
880    /// https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L877
881    pub unsafe extern "C" fn xCurrentTimeInt64(
882        _pVfs: *mut sqlite3_vfs,
883        pOut: *mut sqlite3_int64,
884    ) -> ::std::os::raw::c_int {
885        *pOut = ((2440587.5 * 86400000.0) + Date::new_0().get_time()) as sqlite3_int64;
886        SQLITE_OK
887    }
888}
889
890#[derive(thiserror::Error, Debug)]
891pub enum ImportDbError {
892    #[error("Byte array size is invalid for an SQLite db.")]
893    InvalidDbSize,
894    #[error("Input does not contain an SQLite database header.")]
895    InvalidHeader,
896    #[error("Page size must be a power of two between 512 and 65536 inclusive")]
897    InvalidPageSize,
898}
899
900/// Simple verification when importing db, and return page size;
901pub fn check_import_db(bytes: &[u8]) -> Result<usize, ImportDbError> {
902    let length = bytes.len();
903
904    if length < 512 || length % 512 != 0 {
905        return Err(ImportDbError::InvalidDbSize);
906    }
907
908    if SQLITE3_HEADER
909        .as_bytes()
910        .iter()
911        .zip(bytes)
912        .any(|(x, y)| x != y)
913    {
914        return Err(ImportDbError::InvalidHeader);
915    }
916
917    // The database page size in bytes.
918    // Must be a power of two between 512 and 32768 inclusive, or the value 1 representing a page size of 65536.
919    let page_size = u16::from_be_bytes([bytes[16], bytes[17]]);
920    let page_size = if page_size == 1 {
921        65536
922    } else {
923        usize::from(page_size)
924    };
925
926    Ok(page_size)
927}
928
929/// 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.
930pub fn check_db_and_page_size(db_size: usize, page_size: usize) -> Result<(), ImportDbError> {
931    if !(page_size.is_power_of_two() && (512..=65536).contains(&page_size)) {
932        return Err(ImportDbError::InvalidPageSize);
933    }
934    if db_size % page_size != 0 {
935        return Err(ImportDbError::InvalidDbSize);
936    }
937    Ok(())
938}
939
940#[cfg(test)]
941mod tests {
942    use crate::SQLITE_IOERR_SHORT_READ;
943
944    use super::{MemChunksFile, VfsFile};
945    use wasm_bindgen_test::wasm_bindgen_test;
946
947    #[wasm_bindgen_test]
948    fn test_chunks_file() {
949        let mut file = MemChunksFile::new(512);
950        file.write(&[], 0).unwrap();
951        assert!(file.size().unwrap() == 0);
952
953        let mut buffer = [1; 2];
954        let ret = file.read(&mut buffer, 0).unwrap();
955        assert_eq!(ret, SQLITE_IOERR_SHORT_READ);
956        assert_eq!([0; 2], buffer);
957
958        file.write(&[1], 0).unwrap();
959        assert!(file.size().unwrap() == 1);
960        let mut buffer = [2; 2];
961        let ret = file.read(&mut buffer, 0).unwrap();
962        assert_eq!(ret, SQLITE_IOERR_SHORT_READ);
963        assert_eq!([1, 0], buffer);
964
965        let mut file = MemChunksFile::new(512);
966        file.write(&[1; 512], 0).unwrap();
967        assert!(file.size().unwrap() == 512);
968        assert!(file.chunks.len() == 1);
969
970        file.truncate(512).unwrap();
971        assert!(file.size().unwrap() == 512);
972        assert!(file.chunks.len() == 1);
973
974        file.write(&[41, 42, 43], 511).unwrap();
975        assert!(file.size().unwrap() == 514);
976        assert!(file.chunks.len() == 2);
977
978        let mut buffer = [0; 3];
979        let ret = file.read(&mut buffer, 511).unwrap();
980        assert_eq!(ret, 0);
981        assert_eq!(buffer, [41, 42, 43]);
982
983        file.truncate(513).unwrap();
984        assert!(file.size().unwrap() == 513);
985        assert!(file.chunks.len() == 2);
986
987        file.write(&[1], 2048).unwrap();
988        assert!(file.size().unwrap() == 2049);
989        assert!(file.chunks.len() == 5);
990
991        file.truncate(0).unwrap();
992        assert!(file.size().unwrap() == 0);
993        assert!(file.chunks.len() == 0);
994    }
995}