Repository navigation
Expand file tree
/
Copy pathlib.rs
More file actions
1644 lines (1475 loc) · 59 KB
/
Copy pathlib.rs
File metadata and controls
1644 lines (1475 loc) · 59 KB
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
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//! Low-level utilities and traits for implementing custom SQLite Virtual File Systems (VFS).
//!
//! Includes a platform-independent, single-threaded in-memory VFS in [`memvfs`].
#![no_std]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
extern crate alloc;
/// SQLite C types and bindings used to implement a VFS.
#[rustfmt::skip]
pub mod ffi;
mod error;
mod filename;
pub mod memvfs;
mod options;
pub mod transfer;
pub use error::{RawVfsErrorCode, SystemErrorCode, VfsErrorCode};
pub use filename::{OpenRequest, VfsFilename};
pub use options::{
AccessMode, DeviceCharacteristics, FileKind, LockLevel, OpenAccess, OpenOptions, SectorSize,
SyncMode, SyncOptions,
};
use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
use alloc::{boxed::Box, ffi::CString};
use core::time::Duration;
use core::{ffi::CStr, ops::Deref};
use ffi::*;
// Write a bounded, NUL-terminated UTF-8 diagnostic. A zero-sized or absent
// buffer is valid for callers which only want an error code.
unsafe fn write_message(out: *mut core::ffi::c_char, capacity: i32, message: &str) {
if out.is_null() || capacity <= 0 {
return;
}
let mut count = message.len().min(capacity as usize - 1);
while !message.is_char_boundary(count) {
count -= 1;
}
message.as_ptr().copy_to(out.cast(), count);
out.add(count).write(0);
}
/// Chunked temporary storage, limited by address space and available memory.
///
/// Truncation only shrinks. Sync and locks are no-ops: no persistence or
/// coordination between connections.
pub struct MemChunksFile {
chunks: Vec<Box<[u8]>>,
chunk_size: Option<usize>,
file_size: usize,
}
impl Default for MemChunksFile {
fn default() -> Self {
Self::new(512)
}
}
impl MemChunksFile {
fn allocate_chunk(size: usize) -> VfsResult<Box<[u8]>> {
let mut bytes = Vec::new();
bytes.try_reserve_exact(size).map_err(|_| no_memory())?;
bytes.resize(size, 0);
Ok(bytes.into_boxed_slice())
}
/// Creates an empty file with the given chunk size in bytes.
///
/// # Panics
///
/// Panics if `chunk_size` is zero.
pub fn new(chunk_size: usize) -> Self {
assert!(chunk_size != 0, "chunk size can't be zero");
MemChunksFile {
chunks: Vec::new(),
chunk_size: Some(chunk_size),
file_size: 0,
}
}
/// Uses the first successful nonempty write's length as the chunk size.
pub fn waiting_for_write() -> Self {
MemChunksFile {
chunks: Vec::new(),
chunk_size: None,
file_size: 0,
}
}
}
impl VfsFile for MemChunksFile {
fn read(&mut self, buf: &mut [u8], offset: u64) -> VfsResult<usize> {
if buf.is_empty() || offset >= self.file_size as u64 {
return Ok(0);
}
// Bounded by the in-memory file size above.
let offset = offset as usize;
let Some(chunk_size) = self.chunk_size else {
return Ok(0);
};
if chunk_size == buf.len()
&& offset % chunk_size == 0
&& self.file_size - offset >= buf.len()
{
buf.copy_from_slice(&self.chunks[offset / chunk_size]);
Ok(buf.len())
} else {
let mut size = core::cmp::min(buf.len(), self.file_size - offset);
let chunk_idx = offset / chunk_size;
let mut remaining_idx = offset % chunk_size;
let mut offset = 0;
for chunk in &self.chunks[chunk_idx..] {
let n = core::cmp::min(chunk_size - remaining_idx, size);
buf[offset..offset + n].copy_from_slice(&chunk[remaining_idx..remaining_idx + n]);
offset += n;
size -= n;
remaining_idx = 0;
if size == 0 {
break;
}
}
Ok(offset)
}
}
fn write(&mut self, buf: &[u8], offset: u64) -> VfsResult<()> {
if buf.is_empty() {
return Ok(());
}
let offset = usize::try_from(offset).map_err(|_| {
VfsError::new(
VfsErrorCode::Full,
"file offset exceeds address space".into(),
)
})?;
let end = offset.checked_add(buf.len()).ok_or_else(|| {
VfsError::new(VfsErrorCode::Full, "file size exceeds address space".into())
})?;
let chunk_size = self.chunk_size.unwrap_or(buf.len());
let required = (end - 1) / chunk_size + 1;
let original_chunks = self.chunks.len();
self.chunks
.try_reserve(required.saturating_sub(original_chunks))
.map_err(|_| no_memory())?;
for _ in original_chunks..required {
match Self::allocate_chunk(chunk_size) {
Ok(chunk) => self.chunks.push(chunk),
Err(error) => {
// Preserve all original bytes and EOF on allocation failure.
self.chunks.truncate(original_chunks);
return Err(error);
}
}
}
self.chunk_size = Some(chunk_size);
let new_length = self.file_size.max(end);
if chunk_size == buf.len() && offset % chunk_size == 0 {
self.chunks[offset / chunk_size].copy_from_slice(buf);
} else {
let mut size = buf.len();
let chunk_start_idx = offset / chunk_size;
let chunk_end_idx = (end - 1) / chunk_size;
let mut remaining_idx = offset % chunk_size;
let mut offset = 0;
for idx in chunk_start_idx..=chunk_end_idx {
let n = core::cmp::min(chunk_size - remaining_idx, size);
self.chunks[idx][remaining_idx..remaining_idx + n]
.copy_from_slice(&buf[offset..offset + n]);
offset += n;
size -= n;
remaining_idx = 0;
if size == 0 {
break;
}
}
}
self.file_size = new_length;
Ok(())
}
fn truncate(&mut self, size: u64) -> VfsResult<()> {
if size > self.file_size as u64 {
return Err(VfsError::new(
VfsErrorCode::IoTruncate,
"cannot extend an in-memory file by truncating".into(),
));
}
let size = usize::try_from(size).map_err(|_| {
VfsError::new(VfsErrorCode::Full, "file size exceeds address space".into())
})?;
if let Some(chunk_size) = self.chunk_size {
if size == 0 {
core::mem::take(&mut self.chunks);
} else {
let idx = ((size - 1) / chunk_size) + 1;
self.chunks.drain(idx..);
// Keep allocated bytes beyond EOF zeroed, so a later sparse
// write cannot expose data removed by this truncation.
let tail = size % chunk_size;
if size < self.file_size && tail != 0 {
self.chunks[idx - 1][tail..].fill(0);
}
}
}
self.file_size = size;
Ok(())
}
fn sync(&mut self, _options: SyncOptions) -> VfsResult<()> {
Ok(())
}
// A standalone memory buffer has no shared lock manager.
fn lock(&mut self, _level: LockLevel) -> VfsResult<()> {
Ok(())
}
fn unlock(&mut self, _level: LockLevel) -> VfsResult<()> {
Ok(())
}
fn check_reserved_lock(&self) -> VfsResult<bool> {
Ok(false)
}
fn size(&self) -> VfsResult<u64> {
Ok(self.file_size as u64)
}
}
/// File layout used by the default `xOpen`/`xClose` callbacks.
/// Customize handle creation/cleanup through [`VfsStore`]. Custom I/O callbacks
/// can borrow the handle; fully custom layouts must supply matching callbacks.
#[repr(C)]
pub struct SQLiteVfsFile {
/// SQLite header; must remain the first field.
io_methods: sqlite3_file,
/// Owning VFS.
vfs: *mut sqlite3_vfs,
/// Original validated open request.
options: OpenOptions,
/// SQLite-owned filename, valid until `xClose`. Null for anonymous files.
name_ptr: *const u8,
/// Filename length in bytes.
name_length: usize,
/// An owned box of the selected [`VfsStore::File`] type, initialized by
/// `xOpen` and consumed by `xClose`. Must not be accessed after close.
handle_ptr: *mut core::ffi::c_void,
}
impl SQLiteVfsFile {
/// Casts without dereferencing. Dereferencing requires a live, initialized
/// `SQLiteVfsFile` and valid lifetime/aliasing.
pub fn from_file(file: *mut sqlite3_file) -> *mut SQLiteVfsFile {
file.cast()
}
/// Returns the owning VFS without transferring ownership.
pub fn vfs(&self) -> *mut sqlite3_vfs {
self.vfs
}
/// Returns the original options supplied to a successful default `xOpen`.
pub fn options(&self) -> OpenOptions {
self.options
}
/// Returns the filename, or `None` for an anonymous file.
///
/// # Safety
///
/// `name_ptr` is null for an anonymous file; otherwise it and `name_length`
/// must describe valid UTF-8 bytes which remain alive and unmodified for
/// the borrow. The file must not have been closed.
///
/// ```compile_fail
/// use rsqlite_vfs::SQLiteVfsFile;
/// fn extend(file: &SQLiteVfsFile) -> Option<&'static str> {
/// unsafe { file.name() }
/// }
/// ```
pub unsafe fn name(&self) -> Option<&str> {
if self.name_ptr.is_null() {
return None;
}
Some(core::str::from_utf8_unchecked(core::slice::from_raw_parts(
self.name_ptr,
self.name_length,
)))
}
/// Borrows the backend handle for a custom I/O callback.
///
/// # Safety
///
/// `F` must be the exact file type used to open this live SQLite file.
/// The handle must be valid and not mutably borrowed for this borrow.
///
/// ```compile_fail
/// use rsqlite_vfs::SQLiteVfsFile;
/// fn extend<F: 'static>(file: &SQLiteVfsFile) -> &'static F {
/// unsafe { file.handle::<F>() }
/// }
/// ```
pub unsafe fn handle<F>(&self) -> &F {
&*self.handle_ptr.cast::<F>()
}
/// Mutably borrows the backend handle for a custom I/O callback.
///
/// # Safety
///
/// `F` must be the exact file type used to open this live SQLite file.
/// The handle must be valid and exclusively accessible for this borrow.
///
/// ```compile_fail
/// use rsqlite_vfs::SQLiteVfsFile;
/// fn extend<F: 'static>(file: &mut SQLiteVfsFile) -> &'static mut F {
/// unsafe { file.handle_mut::<F>() }
/// }
/// ```
pub unsafe fn handle_mut<F>(&mut self) -> &mut F {
&mut *self.handle_ptr.cast::<F>()
}
/// Returns a pointer to the SQLite file header from an exclusive borrow.
///
/// The pointer must not be used after this file is moved or freed, or while
/// conflicting references exist.
pub fn sqlite3_file(&mut self) -> *mut sqlite3_file {
core::ptr::from_mut(self).cast()
}
}
/// Errors from VFS lookup, registration or unregistration.
///
/// Match variants rather than the human-readable display text.
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum RegisterVfsError {
#[error("VFS name must not be empty")]
EmptyName,
#[error("VFS name contains a NUL byte")]
ToCStr,
#[error("failed to register VFS (SQLite error {0})")]
RegisterVfs(VfsErrorCode),
#[error("failed to unregister VFS (SQLite error {0})")]
UnregisterVfs(VfsErrorCode),
#[error("VFS name is already registered: {0:?}")]
NameConflict(String),
}
/// Looks up a VFS without retaining it. May initialize SQLite.
///
/// # Safety
///
/// Obey the linked SQLite library's global initialization and threading rules.
/// In single-thread mode, serialize this call with all other SQLite use.
/// Coordinate with VFS owners to prevent freeing a registration during lookup
/// or while using the returned pointer.
pub unsafe fn registered_vfs(vfs_name: &str) -> Result<Option<*mut sqlite3_vfs>, RegisterVfsError> {
let name = CString::new(vfs_name).map_err(|_| RegisterVfsError::ToCStr)?;
let vfs = sqlite3_vfs_find(name.as_ptr());
Ok((!vfs.is_null()).then_some(vfs))
}
/// Owns a VFS registration.
///
/// Dropping this handle leaves the VFS and its data alive for SQLite.
/// Keep the handle for explicit [`Self::unregister`], or use [`Self::into_raw`]
/// for process-lifetime registration.
#[must_use = "keep the registration for explicit cleanup, or call into_raw for permanent registration"]
pub struct VfsRegistration<T> {
allocations: core::mem::ManuallyDrop<VfsAllocations<T>>,
}
// Own raw allocations from the moment any pointer is exposed to SQLite or a
// custom constructor. Moving/reborrowing a Box afterwards would invalidate
// pointers derived from an earlier exclusive borrow under Rust's alias rules.
// Drop also handles constructor panic and registration failure.
struct VfsAllocations<T> {
vfs: *mut sqlite3_vfs,
app_data: *mut VfsAppData<T>,
name: *mut core::ffi::c_char,
}
impl<T> Drop for VfsAllocations<T> {
fn drop(&mut self) {
unsafe {
// Reconstitute all owners before dropping backend data, so the
// structure/name are reclaimed even if its destructor unwinds.
let _name = CString::from_raw(self.name);
let _vfs = (!self.vfs.is_null()).then(|| Box::from_raw(self.vfs));
let _app_data = Box::from_raw(self.app_data);
}
}
}
impl<T> core::fmt::Debug for VfsRegistration<T> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("VfsRegistration")
.field("vfs", &self.allocations.vfs)
.finish_non_exhaustive()
}
}
impl<T> VfsRegistration<T> {
/// Returns the SQLite pointer without transferring allocation ownership.
///
/// It must not be freed separately or used after successful [`Self::unregister`].
pub fn as_ptr(&self) -> *mut sqlite3_vfs {
self.allocations.vfs
}
/// Gives up managed cleanup and retains all allocations.
///
/// The pointer remains valid for the process lifetime unless manually freed.
/// Calling SQLite's raw unregister function alone will not free it.
pub fn into_raw(self) -> *mut sqlite3_vfs {
self.allocations.vfs
}
/// Unregisters and frees the VFS, name and data.
///
/// # Errors
///
/// On failure, returns the intact handle and error for retry.
///
/// # Safety
///
/// Close all connections/files and retire callbacks, saved pointers and
/// delegating wrappers. Serialize with VFS lookup, registration and use;
/// obey SQLite initialization and backend threading rules. Owned allocations
/// must not have been freed or replaced; prior raw unregistration is allowed.
pub unsafe fn unregister(self) -> Result<(), (Self, RegisterVfsError)> {
let code = sqlite3_vfs_unregister(self.allocations.vfs);
if code != SQLITE_OK {
return Err((
self,
RegisterVfsError::UnregisterVfs(
VfsErrorCode::from_raw(code).expect("SQLite must return a valid error code"),
),
));
}
drop(core::mem::ManuallyDrop::into_inner(self.allocations));
Ok(())
}
}
/// Registers a VFS, rejecting empty or occupied names.
///
/// The returned handle requires explicit cleanup; drop does not unregister it.
///
/// # Safety
///
/// `V::vfs`, `IO::METHODS` and callbacks must agree on versions, file size/layout
/// and app-data types. The constructor must preserve supplied name/app-data
/// pointers without taking ownership. Callback resources must outlive all uses,
/// including open files after raw unregistration.
///
/// Obey SQLite/backend threading and aliasing rules; single-thread mode requires
/// serialization with all SQLite use. In every mode, serialize this entire
/// lookup-and-register operation against same-name registrations, including C
/// callers. The constructor must not reentrantly register that name.
pub unsafe fn register_vfs<IO: SQLiteIoMethods, V: SQLiteVfs<IO>>(
vfs_name: &str,
app_data: <IO::Store as VfsStore>::AppData,
default_vfs: bool,
) -> Result<VfsRegistration<<IO::Store as VfsStore>::AppData>, RegisterVfsError> {
if vfs_name.is_empty() {
return Err(RegisterVfsError::EmptyName);
}
let name = CString::new(vfs_name).map_err(|_| RegisterVfsError::ToCStr)?;
if !sqlite3_vfs_find(name.as_ptr()).is_null() {
return Err(RegisterVfsError::NameConflict(vfs_name.into()));
}
let mut allocations = VfsAllocations {
vfs: core::ptr::null_mut(),
app_data: Box::into_raw(Box::new(VfsAppData::new(app_data))),
name: name.into_raw(),
};
// SAFETY: The allocations are live and retained for SQLite. The caller
// guarantees that the custom constructor and callbacks satisfy the contract.
allocations.vfs = Box::into_raw(Box::new(V::vfs(allocations.name, allocations.app_data)));
let ret = sqlite3_vfs_register(allocations.vfs, i32::from(default_vfs));
if ret != SQLITE_OK {
return Err(RegisterVfsError::RegisterVfs(
VfsErrorCode::from_raw(ret).expect("SQLite must return a valid error code"),
));
}
Ok(VfsRegistration {
allocations: core::mem::ManuallyDrop::new(allocations),
})
}
/// A SQLite error code with a backend diagnostic and optional OS error.
///
/// Use [`Self::code`] for error handling; messages are human-readable diagnostics,
/// not a stable format for programmatic matching.
#[derive(thiserror::Error, Debug, Clone)]
#[error("{message} (SQLite error {code})")]
pub struct VfsError {
code: VfsErrorCode,
message: Cow<'static, str>,
system_error: Option<SystemErrorCode>,
}
impl VfsError {
/// Creates an error without an OS error number.
pub fn new(code: VfsErrorCode, message: Cow<'static, str>) -> Self {
VfsError {
code,
message,
system_error: None,
}
}
/// Attaches the underlying OS error without changing the SQLite result code.
pub fn with_system_error(mut self, code: SystemErrorCode) -> Self {
self.system_error = Some(code);
self
}
/// Returns the OS error, if any, for `xGetLastError` / `sqlite3_system_errno`.
pub fn system_error(&self) -> Option<SystemErrorCode> {
self.system_error
}
/// Returns the SQLite result code, including any extended error code.
pub fn code(&self) -> VfsErrorCode {
self.code
}
/// Returns the raw SQLite result code, preserving extended error codes.
pub fn raw_code(&self) -> i32 {
self.code.as_raw()
}
/// Returns the backend's error message without additional formatting.
pub fn message(&self) -> &str {
&self.message
}
}
/// A specialized `Result` type for VFS operations.
pub type VfsResult<T> = Result<T, VfsError>;
fn no_memory() -> VfsError {
VfsError::new(
VfsErrorCode::NoMemory,
"unable to allocate memory for file data".into(),
)
}
/// The handle and access mode actually obtained by the backend.
///
/// A read-write request may fall back to read-only; the default `xOpen` reports
/// the actual access to SQLite through `pOutFlags`.
pub struct OpenedFile<F> {
/// The newly opened backend handle.
pub file: F,
/// The handle's actual access mode, which may differ from the request.
pub access: OpenAccess,
}
/// Owns `sqlite3_vfs::pAppData`. Threading and synchronization follow `T`
/// and the backend; this wrapper adds neither.
pub struct VfsAppData<T> {
data: T,
}
impl<T> VfsAppData<T> {
/// Borrows app data without borrowing the SQLite-owned VFS structure.
///
/// SQLite may independently mutate the registry's `pNext` field.
///
/// # Safety
///
/// `vfs` must be valid and aligned, with `pAppData` unchanged during this
/// call. It must point to an aligned `VfsAppData<T>` of exactly this type,
/// live for the caller-chosen `'a`. Obey Rust shared-reference rules and
/// backend threading requirements.
pub unsafe fn get<'a>(vfs: *const sqlite3_vfs) -> &'a Self {
&*core::ptr::addr_of!((*vfs).pAppData).read().cast()
}
/// Wraps backend data without adding synchronization.
pub fn new(t: T) -> Self {
VfsAppData { data: t }
}
/// Transfers ownership to a raw pointer suitable for `sqlite3_vfs::pAppData`.
/// Reclaim it with [`Self::from_raw`] only after all users have stopped.
pub fn leak(self) -> *mut Self {
Box::into_raw(Box::new(self))
}
/// Reclaims the allocation created by [`Self::leak`], returning its contents.
///
/// # Safety
///
/// Takes ownership of a pointer returned by `leak`, exactly once. No references
/// to the allocation may remain in use, including through SQLite callbacks.
pub unsafe fn from_raw(t: *mut Self) -> VfsAppData<T> {
*Box::from_raw(t)
}
fn store_err<S: VfsStore<AppData = T>>(&self, err: VfsError) -> i32 {
let code = err.raw_code();
S::record_error(&self.data, err);
code
}
}
/// Provides shared access to application data. Synchronization is the backend's responsibility.
impl<T> Deref for VfsAppData<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
/// I/O on an independently opened handle; underlying file data may be shared.
///
/// Offsets/sizes are byte counts independent of pointer width. Reject values
/// beyond backend limits rather than truncating them; buffers remain address-space limited.
pub trait VfsFile {
/// Handles an optional preallocation hint, never shrinking the file.
///
/// Return `true` when handled, `false` when unsupported. This is only an
/// optimization; do not assume SQLite always sends it before writing.
fn size_hint(&mut self, _size: u64) -> VfsResult<bool> {
Ok(false)
}
/// Minimum write unit which can disturb neighboring bytes.
fn sector_size(&self) -> SectorSize {
SectorSize::DEFAULT
}
/// Returns guarantees actually provided by this file's storage.
fn device_characteristics(&self) -> DeviceCharacteristics {
DeviceCharacteristics::NONE
}
/// Reads at most `buf.len()` bytes, returning the number written to `buf`.
///
/// Fill as much as available; return zero at/beyond EOF or for an empty buffer.
/// Short reads mean EOF and are not retried. Return `Ok(count)`, never
/// [`VfsErrorCode::IoShortRead`]: `xRead` needs the count to zero-fill the unread tail and
/// report `SQLITE_IOERR_SHORT_READ`.
fn read(&mut self, buf: &mut [u8], offset: u64) -> VfsResult<usize>;
/// Writes the entire buffer or returns an error; partial success must not
/// be reported as `Ok(())`.
fn write(&mut self, buf: &[u8], offset: u64) -> VfsResult<()>;
/// Truncates the logical file to `size` bytes, as requested by `xTruncate`.
/// Return an error if the requested size cannot be applied.
fn truncate(&mut self, size: u64) -> VfsResult<()>;
/// Synchronizes prior writes according to SQLite's requested semantics.
///
/// [`SyncMode::Full`] is not `PRAGMA synchronous=FULL`.
fn sync(&mut self, options: SyncOptions) -> VfsResult<()>;
/// Returns the logical file length in bytes, not its allocated storage size.
fn size(&self) -> VfsResult<u64>;
/// Upgrades to `level`, leaving an already higher lock unchanged.
///
/// Return [`VfsErrorCode::Busy`] for contention; failed upgrades may retain
/// [`LockLevel::Pending`]. SQLite never requests [`LockLevel::None`] here.
fn lock(&mut self, level: LockLevel) -> VfsResult<()>;
/// Downgrades to [`LockLevel::Shared`] or [`LockLevel::None`], leaving an
/// already lower lock unchanged.
fn unlock(&mut self, level: LockLevel) -> VfsResult<()>;
/// Returns whether any connection holds a reserved or stronger lock.
///
/// Include this connection and, where relevant, other processes.
fn check_reserved_lock(&self) -> VfsResult<bool>;
}
/// Synchronous management of the backend's current named files.
///
/// Includes journal/WAL files, but excludes anonymous files and unused slots.
/// Queries report backend failures rather than treating them as missing files.
pub trait VfsFilesManager {
/// Backend file-management error.
type Error;
/// Removes only this file, returning whether it existed. Close its database
/// first; companion journal/WAL files are not removed automatically.
fn remove(&self, filename: &str) -> Result<bool, Self::Error>;
/// Removes all files. Close all databases first. Backend capacity may remain;
/// failure may leave some files removed, so this is not an atomic operation.
fn clear(&self) -> Result<(), Self::Error>;
/// Returns whether the name is present in the current view.
fn contains(&self, filename: &str) -> Result<bool, Self::Error>;
/// Returns all filenames in unspecified order.
fn names(&self) -> Result<Vec<String>, Self::Error>;
/// Returns the number of named files without allocating a list.
fn len(&self) -> Result<usize, Self::Error>;
/// Returns whether the current view contains no named files.
fn is_empty(&self) -> Result<bool, Self::Error> {
self.len().map(|len| len == 0)
}
}
/// Opens backend handles and manages the file namespace.
///
/// I/O uses the returned handle directly, without looking up its filename again.
pub trait VfsStore {
/// Backend handle retained from `xOpen` until `xClose`.
type File: VfsFile + 'static;
/// Per-VFS data shared by callbacks; synchronization is backend-specific.
type AppData: 'static;
/// Records optional diagnostics for `xGetLastError`. Concurrent backends must
/// associate diagnostics with the calling thread, not just serialize access
/// to a shared last-error slot. The default discards diagnostics;
/// callback result codes are still returned to SQLite unchanged.
fn record_error(_data: &Self::AppData, _error: VfsError) {}
/// Returns the calling thread's last diagnostic without consuming it.
///
/// Override together with [`Self::record_error`] to support `xGetLastError`;
/// the default reports no additional diagnostic.
fn last_error(_data: &Self::AppData) -> Option<VfsError> {
None
}
/// Returns a fresh handle for each `xOpen`.
///
/// Create only with `CREATE`; `CREATE | EXCLUSIVE` must reject existing files.
/// Report actual access in [`OpenedFile`]; read-only fallback is allowed,
/// read-write upgrades are not.
/// Keep the resource alive until close, regardless of namespace changes.
/// Clean up failed opens here: no close follows.
fn open_file(
data: &Self::AppData,
request: OpenRequest<'_>,
) -> VfsResult<OpenedFile<Self::File>>;
/// Closes the handle even on error.
///
/// `DELETEONCLOSE` must remove the opened resource, not a same-name replacement;
/// unlinking at open is allowed.
/// `name` is absent for anonymous opens; `options` is the original request,
/// not the actual access reported by [`OpenedFile`].
fn close_file(
data: &Self::AppData,
name: Option<&str>,
file: Self::File,
options: OpenOptions,
) -> VfsResult<()>;
/// Checks existence or permissions as requested, without opening the file.
fn access(data: &Self::AppData, name: &str, mode: AccessMode) -> VfsResult<bool>;
/// Returns a canonical, NUL-free UTF-8 name. Flat namespaces can return
/// `name` unchanged; filesystem backends must resolve aliases/relative paths.
fn full_pathname(data: &Self::AppData, name: &str) -> VfsResult<String>;
/// Deletes a file. If `sync_dir` is true, synchronize its containing
/// directory/namespace before returning success.
fn delete_file(data: &Self::AppData, name: &str, sync_dir: bool) -> VfsResult<()>;
}
/// Platform services used by the default VFS callbacks.
pub trait OsCallback {
/// Suspends execution for at least `dur`. The default `xSleep` reports this
/// requested duration to SQLite; a no-op is not a conforming sleep service.
fn sleep(&self, dur: Duration);
/// Returns the actual number of random bytes written, at most `buf.len()`.
/// Return zero when randomness is unavailable.
fn random(&self, buf: &mut [u8]) -> usize;
/// Returns UTC milliseconds since 1970-01-01 00:00:00 (the Unix epoch).
/// Negative values represent earlier times. Return an error if the clock
/// is unavailable; the default callbacks convert this value to Julian time.
fn epoch_timestamp_in_ms(&self) -> VfsResult<i64>;
}
/// SQLite VFS callbacks, delegating to typed [`VfsStore`] methods by default.
///
/// See the [SQLite VFS contract](https://www.sqlite.org/c3ref/vfs.html).
///
/// # Raw callback safety
///
/// Callers must satisfy the SQLite contract for each callback: pointers must
/// have the required lifetime, alignment and readable/writable extent. The VFS
/// must carry the matching store's app data, and `xOpen` needs storage for
/// [`SQLiteVfsFile`]. Shared app data requires the backend's synchronization;
/// mutable buffers must not alias other active accesses.
/// Backend panics are not converted to SQLite errors and cannot unwind through
/// these `extern "C"` callbacks. Return [`VfsResult`] errors for recoverable failures.
#[allow(clippy::missing_safety_doc)]
pub trait SQLiteVfs<IO: SQLiteIoMethods> {
/// Platform services used by the default callbacks.
type Os: OsCallback + ?Sized;
/// Borrows the services for this VFS instance. Threading follows the same
/// requirements as the backend app data; no global service is required.
fn os(data: &<IO::Store as VfsStore>::AppData) -> &Self::Os;
/// SQLite `sqlite3_vfs.iVersion`, not a crate or backend version.
/// Defaults to 2 for the integer time callback; version 3 adds system-call hooks.
const VERSION: ::core::ffi::c_int = 2;
/// Maximum pathname length in bytes advertised as `sqlite3_vfs.mxPathname`.
const MAX_PATH_SIZE: ::core::ffi::c_int = 1024;
/// Builds the raw SQLite VFS structure without registering it.
///
/// # Safety
///
/// `vfs_name` must be NUL-terminated and `app_data` must point to a valid
/// `VfsAppData<<IO::Store as VfsStore>::AppData>`; both must outlive the VFS
/// and open files. Versions, method table, layout and callbacks must agree.
/// Obey their threading/aliasing rules and [`register_vfs`]'s ownership contract.
unsafe fn vfs(
vfs_name: *const ::core::ffi::c_char,
app_data: *mut VfsAppData<<IO::Store as VfsStore>::AppData>,
) -> sqlite3_vfs {
sqlite3_vfs {
iVersion: Self::VERSION,
szOsFile: core::mem::size_of::<SQLiteVfsFile>() as i32,
mxPathname: Self::MAX_PATH_SIZE,
pNext: core::ptr::null_mut(),
zName: vfs_name,
pAppData: app_data.cast(),
xOpen: Some(Self::xOpen),
xDelete: Some(Self::xDelete),
xAccess: Some(Self::xAccess),
xFullPathname: Some(Self::xFullPathname),
xDlOpen: Some(Self::xDlOpen),
xDlError: Some(Self::xDlError),
xDlSym: None,
xDlClose: None,
xRandomness: Some(Self::xRandomness),
xSleep: Some(Self::xSleep),
xCurrentTime: Some(Self::xCurrentTime),
xGetLastError: Some(Self::xGetLastError),
xCurrentTimeInt64: Some(Self::xCurrentTimeInt64),
xSetSystemCall: None,
xGetSystemCall: None,
xNextSystemCall: None,
}
}
unsafe extern "C" fn xOpen(
pVfs: *mut sqlite3_vfs,
zName: sqlite3_filename,
pFile: *mut sqlite3_file,
flags: ::core::ffi::c_int,
pOutFlags: *mut ::core::ffi::c_int,
) -> ::core::ffi::c_int {
// SQLite must not call xClose when opening fails before a handle exists.
(*pFile).pMethods = core::ptr::null();
let app_data = VfsAppData::<<IO::Store as VfsStore>::AppData>::get(pVfs);
let options = match OpenOptions::from_raw_flags(flags) {
Ok(options) => options,
Err(err) => return app_data.store_err::<IO::Store>(err),
};
let filename = if zName.is_null() {
None
} else {
let raw = CStr::from_ptr(zName);
let path = match raw.to_str() {
Ok(path) => path,
Err(_) => {
return app_data.store_err::<IO::Store>(VfsError::new(
VfsErrorCode::CantOpen,
"filename is not valid UTF-8".into(),
));
}
};
Some(VfsFilename::from_sqlite(
path,
matches!(
options.kind(),
Some(FileKind::MainDb | FileKind::MainJournal | FileKind::Wal)
)
.then_some(zName),
))
};
let opened = match IO::Store::open_file(app_data, OpenRequest { filename, options }) {
Ok(handle) => handle,
Err(err) => return app_data.store_err::<IO::Store>(err),
};
let vfs_file = pFile.cast::<SQLiteVfsFile>();
(*vfs_file).vfs = pVfs;
(*vfs_file).options = options;
(*vfs_file).name_ptr = zName.cast();
(*vfs_file).name_length = filename.map_or(0, |name| name.path().len());
(*vfs_file).handle_ptr = Box::into_raw(Box::new(opened.file)).cast();
(*pFile).pMethods = &IO::METHODS;
if !pOutFlags.is_null() {
*pOutFlags = (flags & !(SQLITE_OPEN_READONLY | SQLITE_OPEN_READWRITE))
| match opened.access {
OpenAccess::ReadOnly => SQLITE_OPEN_READONLY,
OpenAccess::ReadWrite => SQLITE_OPEN_READWRITE,
};
}
SQLITE_OK
}
unsafe extern "C" fn xDelete(
pVfs: *mut sqlite3_vfs,
zName: *const ::core::ffi::c_char,
syncDir: ::core::ffi::c_int,
) -> ::core::ffi::c_int {
let app_data = VfsAppData::<<IO::Store as VfsStore>::AppData>::get(pVfs);
if zName.is_null() {
return app_data.store_err::<IO::Store>(VfsError::new(
VfsErrorCode::IoDelete,
"filename is null".into(),
));
}
let s = match CStr::from_ptr(zName).to_str() {
Ok(name) => name,
Err(_) => {
return app_data.store_err::<IO::Store>(VfsError::new(
VfsErrorCode::IoDelete,
"filename is not valid UTF-8".into(),
));
}
};
if let Err(err) = IO::Store::delete_file(app_data, s, syncDir != 0) {
app_data.store_err::<IO::Store>(err)
} else {
SQLITE_OK
}
}
/// Delegates the requested existence/permission check without opening a file.
unsafe extern "C" fn xAccess(
pVfs: *mut sqlite3_vfs,
zName: *const ::core::ffi::c_char,
flags: ::core::ffi::c_int,
pResOut: *mut ::core::ffi::c_int,
) -> ::core::ffi::c_int {
*pResOut = 0;
let app_data = VfsAppData::<<IO::Store as VfsStore>::AppData>::get(pVfs);
if zName.is_null() {
return app_data.store_err::<IO::Store>(VfsError::new(
VfsErrorCode::IoAccess,
"filename is null".into(),
));
}
let Some(mode) = AccessMode::from_raw(flags) else {
return app_data.store_err::<IO::Store>(VfsError::new(
VfsErrorCode::IoAccess,
"invalid access mode".into(),
));
};
let file = match CStr::from_ptr(zName).to_str() {
Ok(name) => name,
Err(_) => {
return app_data.store_err::<IO::Store>(VfsError::new(
VfsErrorCode::IoAccess,
"filename is not valid UTF-8".into(),
));
}
};