1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use fsqlite_error::Result;
use fsqlite_types::LockLevel;
use fsqlite_types::cx::Cx;
use fsqlite_types::flags::{AccessFlags, SyncFlags, VfsOpenFlags};
use crate::shm::ShmRegion;
static DEFAULT_RANDOMNESS_CALL_SEQ: AtomicU64 = AtomicU64::new(0);
/// A virtual filesystem implementation.
///
/// This trait abstracts all file system operations, allowing different
/// backends: real files (Unix), in-memory (testing), or custom implementations.
///
/// Modeled after C SQLite's `sqlite3_vfs` struct from `os.h`.
pub trait Vfs: Send + Sync {
/// The file handle type produced by this VFS.
type File: VfsFile;
/// The name of this VFS (e.g., "unix", "memory").
fn name(&self) -> &'static str;
/// Open a file.
///
/// `path` is `None` for temporary files that should be auto-named.
/// `flags` describes what kind of file (main DB, journal, WAL, etc.)
/// and how to open it (create, read-write, exclusive, etc.).
///
/// Returns the opened file and the flags that were actually used (the VFS
/// may add flags like `READWRITE` when `CREATE` is specified).
fn open(
&self,
cx: &Cx,
path: Option<&Path>,
flags: VfsOpenFlags,
) -> Result<(Self::File, VfsOpenFlags)>;
/// Delete a file.
///
/// If `sync_dir` is true, the directory entry removal should be synced
/// to ensure durability.
fn delete(&self, cx: &Cx, path: &Path, sync_dir: bool) -> Result<()>;
/// Check file access.
///
/// Returns true if the file at `path` satisfies the access check
/// described by `flags`.
fn access(&self, cx: &Cx, path: &Path, flags: AccessFlags) -> Result<bool>;
/// Resolve a potentially relative path into an absolute path.
fn full_pathname(&self, cx: &Cx, path: &Path) -> Result<PathBuf>;
/// Generate a random byte sequence for temporary file naming.
///
/// Fills `buf` with bytes suitable for temporary file naming.
///
/// The default implementation is deterministic (xorshift seeded from a
/// process-local counter) for reproducible tests; real VFS implementations
/// should override this and use OS-provided randomness to avoid collisions.
fn randomness(&self, cx: &Cx, buf: &mut [u8]) {
// Default: fill with pseudo-random bytes using a simple xorshift.
// Real VFS implementations should use OS-provided randomness.
let _ = cx; // Usage to silence unused variable warning
let seq = DEFAULT_RANDOMNESS_CALL_SEQ.fetch_add(1, Ordering::Relaxed);
let mut state: u64 = 0x5DEE_CE66_D1A4_F681 ^ seq.wrapping_mul(0x9E37_79B9_7F4A_7C15);
for chunk in buf.chunks_mut(8) {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
let bytes = state.to_le_bytes();
for (dst, &src) in chunk.iter_mut().zip(bytes.iter()) {
*dst = src;
}
}
}
/// Return the current time as a Julian day number (days since noon
/// on November 24, 4714 B.C.).
fn current_time(&self, cx: &Cx) -> f64 {
// Default: derive from `Cx` time capability (no ambient authority).
cx.current_time_julian_day()
}
/// Returns true if this VFS operates entirely in-process memory.
/// In-memory VFS backends can skip file locking, journal recovery,
/// and other I/O-oriented work in the pager hot path.
fn is_memory(&self) -> bool {
false
}
}
/// A file handle opened by a VFS.
///
/// Corresponds to C SQLite's `sqlite3_file` + `sqlite3_io_methods`.
pub trait VfsFile: Send + Sync {
/// Close the file.
///
/// After this call, the file handle should not be used.
fn close(&mut self, cx: &Cx) -> Result<()>;
/// Read `buf.len()` bytes starting at byte offset `offset`.
///
/// Returns the number of bytes actually read. If fewer bytes are read
/// than requested (short read), the remaining bytes in `buf` are zeroed.
fn read(&self, cx: &Cx, buf: &mut [u8], offset: u64) -> Result<usize>;
/// Write `buf` starting at byte offset `offset`.
fn write(&mut self, cx: &Cx, buf: &[u8], offset: u64) -> Result<()>;
/// Write multiple page-sized buffers in one logical operation.
///
/// The default implementation preserves existing semantics by issuing the
/// writes sequentially through [`Self::write`]. VFS backends may override
/// this to amortize locking or syscall overhead for hot pager commit paths.
fn write_page_batch(&mut self, cx: &Cx, writes: &[(u64, &[u8])]) -> Result<()> {
for (offset, data) in writes {
self.write(cx, data, *offset)?;
}
Ok(())
}
/// Truncate the file to `size` bytes.
fn truncate(&mut self, cx: &Cx, size: u64) -> Result<()>;
/// Sync the file contents to stable storage.
///
/// `flags` indicates the type of sync (normal, full, data-only).
fn sync(&mut self, cx: &Cx, flags: SyncFlags) -> Result<()>;
/// Return the current file size in bytes.
fn file_size(&self, cx: &Cx) -> Result<u64>;
/// Acquire a file lock at the given level.
///
/// SQLite's five-level locking: None < Shared < Reserved < Pending < Exclusive.
fn lock(&mut self, cx: &Cx, level: LockLevel) -> Result<()>;
/// Release the file lock to the given level.
fn unlock(&mut self, cx: &Cx, level: LockLevel) -> Result<()>;
/// Check if another process holds a reserved lock.
///
/// Returns true if a RESERVED or higher lock is held by another connection.
fn check_reserved_lock(&self, cx: &Cx) -> Result<bool>;
/// Return the sector size for this file.
///
/// The sector size is the minimum write granularity for the underlying
/// storage. Defaults to 4096 bytes.
fn sector_size(&self) -> u32 {
4096
}
/// Return device characteristics flags.
///
/// These flags describe capabilities of the underlying storage device,
/// such as whether it supports atomic writes. Returns 0 for no special
/// characteristics.
fn device_characteristics(&self) -> u32 {
0
}
// --- Shared-memory methods (required for WAL mode) ---
/// Map a region of shared memory. `region` is a 0-based index of 32KB
/// regions. If `extend` is true and the region does not exist, create it.
/// Returns a safe [`ShmRegion`] handle with bounds-checked accessors.
/// (Equivalent to sqlite3_io_methods.xShmMap)
fn shm_map(&mut self, cx: &Cx, region: u32, size: u32, extend: bool) -> Result<ShmRegion>;
/// Acquire or release a shared-memory lock.
/// `offset` and `n` define a range of lock slots.
/// `flags`: SHM_LOCK | (SHM_SHARED | SHM_EXCLUSIVE).
/// (Equivalent to sqlite3_io_methods.xShmLock)
fn shm_lock(&mut self, cx: &Cx, offset: u32, n: u32, flags: u32) -> Result<()>;
/// Memory barrier for shared memory -- ensures all prior SHM writes are
/// visible to other processes before subsequent reads.
/// (Equivalent to sqlite3_io_methods.xShmBarrier)
fn shm_barrier(&self);
/// Unmap all shared-memory regions. If `delete` is true, also delete
/// the underlying SHM file.
/// (Equivalent to sqlite3_io_methods.xShmUnmap)
fn shm_unmap(&mut self, cx: &Cx, delete: bool) -> Result<()>;
/// Set the busy-timeout for cross-process file-lock contention.
///
/// When `ms > 0`, the VFS should retry `F_SETLK` with exponential
/// backoff instead of returning `SQLITE_BUSY` immediately on
/// `EAGAIN`/`EACCES`. A value of `0` disables retries (fail-fast).
///
/// Default implementation is a no-op (memory and stub VFS backends
/// have no OS-level lock contention).
fn set_busy_timeout_ms(&mut self, _ms: u64) {}
}
#[cfg(test)]
mod tests {
use super::*;
/// Verify that the trait is object-safe for VfsFile (can be used as dyn).
#[test]
fn vfs_file_is_object_safe() {
fn _accepts_dyn(_f: &dyn VfsFile) {}
}
/// Verify default implementations exist and don't panic.
#[test]
fn vfs_file_defaults() {
struct DummyFile;
impl VfsFile for DummyFile {
fn close(&mut self, _cx: &Cx) -> Result<()> {
Ok(())
}
fn read(&self, _cx: &Cx, _buf: &mut [u8], _offset: u64) -> Result<usize> {
Ok(0)
}
fn write(&mut self, _cx: &Cx, _buf: &[u8], _offset: u64) -> Result<()> {
Ok(())
}
fn truncate(&mut self, _cx: &Cx, _size: u64) -> Result<()> {
Ok(())
}
fn sync(&mut self, _cx: &Cx, _flags: SyncFlags) -> Result<()> {
Ok(())
}
fn file_size(&self, _cx: &Cx) -> Result<u64> {
Ok(0)
}
fn lock(&mut self, _cx: &Cx, _level: LockLevel) -> Result<()> {
Ok(())
}
fn unlock(&mut self, _cx: &Cx, _level: LockLevel) -> Result<()> {
Ok(())
}
fn check_reserved_lock(&self, _cx: &Cx) -> Result<bool> {
Ok(false)
}
fn shm_map(
&mut self,
_cx: &Cx,
_region: u32,
_size: u32,
_extend: bool,
) -> Result<ShmRegion> {
Err(fsqlite_error::FrankenError::Unsupported)
}
fn shm_lock(&mut self, _cx: &Cx, _offset: u32, _n: u32, _flags: u32) -> Result<()> {
Err(fsqlite_error::FrankenError::Unsupported)
}
fn shm_barrier(&self) {}
fn shm_unmap(&mut self, _cx: &Cx, _delete: bool) -> Result<()> {
Ok(())
}
}
let file = DummyFile;
assert_eq!(file.sector_size(), 4096);
assert_eq!(file.device_characteristics(), 0);
}
/// Verify that VfsFile trait defaults are what we expect.
#[test]
fn vfs_file_sector_size_default_is_4096() {
struct Stub;
impl VfsFile for Stub {
fn close(&mut self, _: &Cx) -> Result<()> {
Ok(())
}
fn read(&self, _: &Cx, _: &mut [u8], _: u64) -> Result<usize> {
Ok(0)
}
fn write(&mut self, _: &Cx, _: &[u8], _: u64) -> Result<()> {
Ok(())
}
fn truncate(&mut self, _: &Cx, _: u64) -> Result<()> {
Ok(())
}
fn sync(&mut self, _: &Cx, _: SyncFlags) -> Result<()> {
Ok(())
}
fn file_size(&self, _: &Cx) -> Result<u64> {
Ok(0)
}
fn lock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
Ok(())
}
fn unlock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
Ok(())
}
fn check_reserved_lock(&self, _: &Cx) -> Result<bool> {
Ok(false)
}
fn shm_map(&mut self, _: &Cx, _: u32, _: u32, _: bool) -> Result<ShmRegion> {
Err(fsqlite_error::FrankenError::Unsupported)
}
fn shm_lock(&mut self, _: &Cx, _: u32, _: u32, _: u32) -> Result<()> {
Err(fsqlite_error::FrankenError::Unsupported)
}
fn shm_barrier(&self) {}
fn shm_unmap(&mut self, _: &Cx, _: bool) -> Result<()> {
Ok(())
}
}
let file = Stub;
assert_eq!(file.sector_size(), 4096);
assert_eq!(file.device_characteristics(), 0);
}
/// Verify that default Vfs::randomness produces different sequences.
#[test]
fn vfs_default_randomness_varies() {
use crate::memory::MemoryVfs;
use crate::traits::Vfs;
let cx = Cx::new();
let vfs = MemoryVfs::new();
let mut buf1 = [0u8; 32];
let mut buf2 = [0u8; 32];
vfs.randomness(&cx, &mut buf1);
vfs.randomness(&cx, &mut buf2);
assert_ne!(buf1, buf2);
}
/// Verify that default Vfs::current_time reads from Cx.
#[test]
fn vfs_default_current_time_from_cx() {
use crate::memory::MemoryVfs;
use crate::traits::Vfs;
let cx = Cx::new();
cx.set_unix_millis_for_testing(0);
let vfs = MemoryVfs::new();
let t1 = vfs.current_time(&cx);
// Unix epoch in Julian days is 2440587.5
#[allow(clippy::approx_constant)]
let expected = 2_440_587.5;
assert!(
(t1 - expected).abs() < 1e-6,
"at unix epoch, julian day should be ~2440587.5, got {t1}"
);
}
/// Verify randomness with a zero-length buffer doesn't panic.
#[test]
fn vfs_randomness_zero_length_buffer() {
use crate::memory::MemoryVfs;
use crate::traits::Vfs;
let cx = Cx::new();
let vfs = MemoryVfs::new();
let mut buf = [];
vfs.randomness(&cx, &mut buf);
}
/// Verify randomness with a 1-byte buffer.
#[test]
fn vfs_randomness_single_byte() {
use crate::memory::MemoryVfs;
use crate::traits::Vfs;
let cx = Cx::new();
let vfs = MemoryVfs::new();
let mut buf = [0u8; 1];
vfs.randomness(&cx, &mut buf);
// Can't assert much about the value, just that it doesn't panic.
}
}