1//! SQLite's default VFS in the browser: the files of `fs`, so that a replica, its write-ahead
2//! log and the notebook it caches are kept and written out alike.
3#![allow(unsafe_code)]
4
5use super::{FILES, Node, Shared, normal, with};
6use rsqlite_vfs::{
7 OsCallback, SQLiteIoMethods, SQLiteVfs, SQLiteVfsFile, VfsError, VfsFile, VfsResult, VfsStore,
8 ffi::{
9 SQLITE_BUSY, SQLITE_CANTOPEN, SQLITE_IOERR, SQLITE_IOERR_DELETE, SQLITE_LOCK_NONE,
10 SQLITE_LOCK_RESERVED, SQLITE_OK, sqlite3_file, sqlite3_vfs,
11 },
12};
13use std::{
14 cell::RefCell,
15 collections::BTreeMap,
16 path::{Path, PathBuf},
17 rc::Rc,
18 sync::Once,
19 time::Duration,
20};
21
22thread_local! {
23 // Replica connections are exclusive, including while browser durability is pending.
24 static LOCKS: RefCell<BTreeMap<PathBuf, (usize, i32)>> = const { RefCell::new(BTreeMap::new()) };
25}
26
27/// Registers the VFS as SQLite's default, once.
28pub(crate) fn install() {
29 static INSTALLED: Once = Once::new();
30 INSTALLED.call_once(|| {
31 rsqlite_vfs::register_vfs::<Methods, Vfs>("snowbound", (), true)
32 .expect("SQLite takes the browser's VFS");
33 });
34}
35
36struct File(Shared);
37
38impl VfsFile for File {
39 fn read(&self, output: &mut [u8], offset: usize) -> VfsResult<bool> {
40 let data = self.0.borrow();
41 let rest = data.bytes.get(offset..).unwrap_or_default();
42 let count = rest.len().min(output.len());
43 output[..count].copy_from_slice(&rest[..count]);
44 output[count..].fill(0);
45 Ok(count == output.len())
46 }
47
48 fn write(&mut self, input: &[u8], offset: usize) -> VfsResult<()> {
49 let mut data = self.0.borrow_mut();
50 let end = offset + input.len();
51 if data.bytes.len() < end {
52 data.bytes.resize(end, 0);
53 }
54 data.bytes[offset..end].copy_from_slice(input);
55 data.wrote(offset..end);
56 Ok(())
57 }
58
59 fn truncate(&mut self, size: usize) -> VfsResult<()> {
60 let mut data = self.0.borrow_mut();
61 data.bytes.truncate(size);
62 data.wrote(size..size);
63 Ok(())
64 }
65
66 fn flush(&mut self) -> VfsResult<()> {
67 self.0.borrow_mut().flush();
68 Ok(())
69 }
70
71 fn size(&self) -> VfsResult<usize> {
72 Ok(self.0.borrow().bytes.len())
73 }
74}
75
76fn error(code: i32, error: std::io::Error) -> VfsError {
77 VfsError::new(code, error.to_string())
78}
79
80struct Store;
81
82impl Store {
83 fn data(file: &SQLiteVfsFile) -> VfsResult<Shared> {
84 // SAFETY: SQLite hands back the file `xOpen` named, whose name lives until `xClose`.
85 let name = unsafe { file.name() };
86 with(|files| files.file(&normal(Path::new(name)))).map_err(|io| error(SQLITE_IOERR, io))
87 }
88}
89
90impl VfsStore<File, ()> for Store {
91 fn add_file(_: *mut sqlite3_vfs, name: &str, _: i32) -> VfsResult<()> {
92 // SQLite's own files beside a replica, such as its write-ahead log, need no folder made.
93 let path = normal(Path::new(name));
94 with(|files| files.create(path))
95 .map(drop)
96 .map_err(|io| error(SQLITE_CANTOPEN, io))
97 }
98
99 fn contains_file(_: *mut sqlite3_vfs, name: &str) -> VfsResult<bool> {
100 let path = normal(Path::new(name));
101 Ok(FILES.with_borrow(|files| matches!(files.nodes.get(&path), Some(Node::File(_)))))
102 }
103
104 fn delete_file(_: *mut sqlite3_vfs, name: &str) -> VfsResult<()> {
105 super::remove_file(name).map_err(|io| error(SQLITE_IOERR_DELETE, io))
106 }
107
108 fn with_file<F: Fn(&File) -> VfsResult<i32>>(file: &SQLiteVfsFile, act: F) -> VfsResult<i32> {
109 act(&File(Self::data(file)?))
110 }
111
112 fn with_file_mut<F: Fn(&mut File) -> VfsResult<i32>>(
113 file: &SQLiteVfsFile,
114 act: F,
115 ) -> VfsResult<i32> {
116 act(&mut File(Rc::clone(&Self::data(file)?)))
117 }
118}
119
120struct Methods;
121
122impl SQLiteIoMethods for Methods {
123 type File = File;
124 type AppData = ();
125 type Store = Store;
126
127 const VERSION: i32 = 1;
128
129 unsafe extern "C" fn xLock(file: *mut sqlite3_file, level: i32) -> i32 {
130 // SAFETY: SQLite supplies its live VFS file for every I/O callback.
131 let path = unsafe { normal(Path::new(SQLiteVfsFile::from_file(file).name())) };
132 LOCKS.with_borrow_mut(|locks| {
133 if locks
134 .get(&path)
135 .is_some_and(|(owner, _)| *owner != file.addr())
136 {
137 return SQLITE_BUSY;
138 }
139 locks.insert(path, (file.addr(), level));
140 SQLITE_OK
141 })
142 }
143
144 unsafe extern "C" fn xUnlock(file: *mut sqlite3_file, level: i32) -> i32 {
145 // SAFETY: SQLite supplies its live VFS file for every I/O callback.
146 let path = unsafe { normal(Path::new(SQLiteVfsFile::from_file(file).name())) };
147 LOCKS.with_borrow_mut(|locks| {
148 if let Some((owner, current)) = locks.get_mut(&path)
149 && *owner == file.addr()
150 {
151 if level == SQLITE_LOCK_NONE {
152 locks.remove(&path);
153 } else {
154 *current = level;
155 }
156 }
157 });
158 SQLITE_OK
159 }
160
161 unsafe extern "C" fn xCheckReservedLock(file: *mut sqlite3_file, reserved: *mut i32) -> i32 {
162 // SAFETY: SQLite supplies its live VFS file and writable result pointer.
163 unsafe {
164 let path = normal(Path::new(SQLiteVfsFile::from_file(file).name()));
165 *reserved = i32::from(LOCKS.with_borrow(|locks| {
166 locks
167 .get(&path)
168 .is_some_and(|(_, level)| *level >= SQLITE_LOCK_RESERVED)
169 }));
170 }
171 SQLITE_OK
172 }
173
174 unsafe extern "C" fn xClose(file: *mut sqlite3_file) -> i32 {
175 // SAFETY: SQLite closes this live file once; release its lock before freeing its name.
176 unsafe {
177 Self::xUnlock(file, SQLITE_LOCK_NONE);
178 Self::xCloseImpl(file)
179 }
180 }
181}
182
183struct Vfs;
184
185impl OsCallback for Vfs {
186 fn sleep(_: Duration) {}
187
188 fn random(bytes: &mut [u8]) {
189 let _ = getrandom::fill(bytes);
190 }
191
192 fn epoch_timestamp_in_ms() -> i64 {
193 js_sys::Date::now() as i64
194 }
195}
196
197impl SQLiteVfs<Methods> for Vfs {
198 const VERSION: i32 = 1;
199
200 fn sleep(duration: Duration) {
201 <Self as OsCallback>::sleep(duration)
202 }
203
204 fn random(bytes: &mut [u8]) {
205 <Self as OsCallback>::random(bytes)
206 }
207
208 fn epoch_timestamp_in_ms() -> i64 {
209 <Self as OsCallback>::epoch_timestamp_in_ms()
210 }
211}