1use std::path::{Path, PathBuf};
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::sync::{Arc, Mutex};
4use std::task::{Context, Poll, Waker};
5
6use fsqlite_error::Result;
7use fsqlite_types::LockLevel;
8use fsqlite_types::cx::Cx;
9use fsqlite_types::flags::{AccessFlags, SyncFlags, VfsOpenFlags};
10
11use crate::shm::ShmRegion;
12
13#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub struct FileIdentity {
22 kind: FileIdentityKind,
23 namespace: u64,
24 object: [u8; 16],
25}
26
27#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
33enum FileIdentityKind {
34 Memory,
36 #[cfg(unix)]
37 Unix,
38 #[cfg(windows)]
39 WindowsFileId128,
40 #[cfg(windows)]
41 WindowsFileIndex64,
42}
43
44impl FileIdentity {
45 #[must_use]
51 pub(crate) fn from_memory_parts(namespace: u64, object: u64) -> Self {
52 let mut object_bytes = [0_u8; 16];
53 object_bytes[..8].copy_from_slice(&object.to_ne_bytes());
54 Self {
55 kind: FileIdentityKind::Memory,
56 namespace,
57 object: object_bytes,
58 }
59 }
60
61 #[cfg(not(target_arch = "wasm32"))]
69 pub fn from_file(file: &std::fs::File) -> std::io::Result<Option<Self>> {
70 #[cfg(unix)]
71 {
72 use std::os::unix::fs::MetadataExt as _;
73
74 let metadata = file.metadata()?;
75 Ok(Some(Self::from_unix_parts(metadata.dev(), metadata.ino())))
76 }
77
78 #[cfg(windows)]
79 {
80 use std::os::windows::io::AsRawHandle as _;
81
82 let handle = file.as_raw_handle();
83 let file_id_result = query_windows_file_id(handle);
84 Self::from_windows_query_result(file_id_result, || {
85 query_windows_legacy_file_index(handle)
86 })
87 }
88
89 #[cfg(not(any(unix, windows)))]
90 {
91 let _ = file;
92 Ok(None)
93 }
94 }
95
96 #[cfg(unix)]
97 pub(crate) fn from_unix_parts(device: u64, inode: u64) -> Self {
98 let mut object = [0_u8; 16];
99 object[..8].copy_from_slice(&inode.to_be_bytes());
100 Self {
101 kind: FileIdentityKind::Unix,
102 namespace: device,
103 object,
104 }
105 }
106
107 #[cfg(windows)]
108 fn from_windows_parts(volume_serial_number: u64, file_id: [u8; 16]) -> Option<Self> {
109 if file_id.iter().all(|byte| *byte == 0) || file_id.iter().all(|byte| *byte == u8::MAX) {
114 return None;
115 }
116 Some(Self {
117 kind: FileIdentityKind::WindowsFileId128,
118 namespace: volume_serial_number,
119 object: file_id,
120 })
121 }
122
123 #[cfg(windows)]
124 fn from_windows_legacy_parts(
125 volume_serial_number: u32,
126 file_index_high: u32,
127 file_index_low: u32,
128 ) -> Self {
129 let file_index = (u64::from(file_index_high) << 32) | u64::from(file_index_low);
130 let mut object = [0_u8; 16];
131 object[..8].copy_from_slice(&file_index.to_be_bytes());
132 Self {
133 kind: FileIdentityKind::WindowsFileIndex64,
134 namespace: u64::from(volume_serial_number),
135 object,
136 }
137 }
138
139 #[cfg(windows)]
140 fn from_windows_query_result<F>(
141 file_id_result: std::io::Result<(u64, [u8; 16])>,
142 legacy_query: F,
143 ) -> std::io::Result<Option<Self>>
144 where
145 F: FnOnce() -> std::io::Result<(u32, u32, u32)>,
146 {
147 match file_id_result {
148 Ok((volume_serial_number, file_id)) => {
149 Ok(Self::from_windows_parts(volume_serial_number, file_id))
150 }
151 Err(err) if is_windows_file_id_unsupported(&err) => {
152 let (volume_serial_number, file_index_high, file_index_low) = legacy_query()?;
153 Ok(Some(Self::from_windows_legacy_parts(
154 volume_serial_number,
155 file_index_high,
156 file_index_low,
157 )))
158 }
159 Err(err) => Err(err),
160 }
161 }
162
163 #[cfg(any(unix, windows))]
168 pub(crate) fn to_namespace_bytes(self) -> [u8; 25] {
169 let tag = match self.kind {
170 FileIdentityKind::Memory => 4,
174 #[cfg(unix)]
175 FileIdentityKind::Unix => 1,
176 #[cfg(windows)]
177 FileIdentityKind::WindowsFileId128 => 2,
178 #[cfg(windows)]
179 FileIdentityKind::WindowsFileIndex64 => 3,
180 };
181 let mut encoded = [0_u8; 25];
182 encoded[0] = tag;
183 encoded[1..9].copy_from_slice(&self.namespace.to_be_bytes());
184 encoded[9..].copy_from_slice(&self.object);
185 encoded
186 }
187
188 #[cfg(any(unix, windows))]
190 pub(crate) fn from_namespace_bytes(encoded: [u8; 25]) -> Option<Self> {
191 let mut namespace_bytes = [0_u8; 8];
192 namespace_bytes.copy_from_slice(&encoded[1..9]);
193 let namespace = u64::from_be_bytes(namespace_bytes);
194 let mut object = [0_u8; 16];
195 object.copy_from_slice(&encoded[9..]);
196
197 match encoded[0] {
198 4 if object[8..].iter().all(|byte| *byte == 0) => Some(Self {
199 kind: FileIdentityKind::Memory,
200 namespace,
201 object,
202 }),
203 #[cfg(unix)]
204 1 if object[8..].iter().all(|byte| *byte == 0) => Some(Self {
205 kind: FileIdentityKind::Unix,
206 namespace,
207 object,
208 }),
209 #[cfg(windows)]
210 2 => Self::from_windows_parts(namespace, object),
211 #[cfg(windows)]
212 3 if u32::try_from(namespace).is_ok() && object[8..].iter().all(|byte| *byte == 0) => {
213 Some(Self {
214 kind: FileIdentityKind::WindowsFileIndex64,
215 namespace,
216 object,
217 })
218 }
219 _ => None,
220 }
221 }
222}
223
224#[cfg(all(test, any(unix, windows)))]
225mod memory_file_identity_codec_tests {
226 use super::FileIdentity;
227
228 #[test]
229 fn memory_namespace_bytes_round_trip_with_distinct_tag() {
230 let identity = FileIdentity::from_memory_parts(17, 29);
231 let encoded = identity.to_namespace_bytes();
232
233 assert_eq!(encoded[0], 4);
234 assert_eq!(FileIdentity::from_namespace_bytes(encoded), Some(identity));
235 assert_ne!(
236 identity,
237 FileIdentity::from_memory_parts(18, 29),
238 "independent MemoryVfs instances must remain isolated"
239 );
240 assert_ne!(
241 identity,
242 FileIdentity::from_memory_parts(17, 30),
243 "distinct named files in one MemoryVfs must remain isolated"
244 );
245 }
246}
247
248#[cfg(windows)]
249fn query_windows_file_id(
250 handle: std::os::windows::io::RawHandle,
251) -> std::io::Result<(u64, [u8; 16])> {
252 use std::mem::size_of;
253 use windows_sys::Win32::Storage::FileSystem::{
254 FILE_ID_INFO, FileIdInfo, GetFileInformationByHandleEx,
255 };
256
257 let mut identity = FILE_ID_INFO::default();
258 let identity_size = u32::try_from(size_of::<FILE_ID_INFO>())
259 .map_err(|_| std::io::Error::other("FILE_ID_INFO size does not fit in a Windows DWORD"))?;
260 let succeeded = unsafe {
264 GetFileInformationByHandleEx(
265 handle,
266 FileIdInfo,
267 std::ptr::from_mut(&mut identity).cast(),
268 identity_size,
269 )
270 };
271 if succeeded == 0 {
272 return Err(std::io::Error::last_os_error());
273 }
274 Ok((identity.VolumeSerialNumber, identity.FileId.Identifier))
275}
276
277#[cfg(windows)]
278fn query_windows_legacy_file_index(
279 handle: std::os::windows::io::RawHandle,
280) -> std::io::Result<(u32, u32, u32)> {
281 use windows_sys::Win32::Storage::FileSystem::{
282 BY_HANDLE_FILE_INFORMATION, GetFileInformationByHandle,
283 };
284
285 let mut identity = BY_HANDLE_FILE_INFORMATION::default();
286 let succeeded = unsafe { GetFileInformationByHandle(handle, &raw mut identity) };
290 if succeeded == 0 {
291 return Err(std::io::Error::last_os_error());
292 }
293 Ok((
294 identity.dwVolumeSerialNumber,
295 identity.nFileIndexHigh,
296 identity.nFileIndexLow,
297 ))
298}
299
300#[cfg(windows)]
301fn is_windows_file_id_unsupported(err: &std::io::Error) -> bool {
302 use windows_sys::Win32::Foundation::{
303 ERROR_CALL_NOT_IMPLEMENTED, ERROR_INVALID_FUNCTION, ERROR_INVALID_PARAMETER,
304 ERROR_NOT_SUPPORTED,
305 };
306
307 err.raw_os_error().is_some_and(|raw_error| {
308 raw_error == ERROR_INVALID_FUNCTION as i32
309 || raw_error == ERROR_NOT_SUPPORTED as i32
310 || raw_error == ERROR_INVALID_PARAMETER as i32
311 || raw_error == ERROR_CALL_NOT_IMPLEMENTED as i32
312 })
313}
314
315impl std::fmt::Debug for FileIdentity {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 f.write_str("FileIdentity(..)")
318 }
319}
320
321#[cfg(all(test, unix))]
322mod unix_file_identity_codec_tests {
323 use super::FileIdentity;
324
325 #[test]
326 fn unix_namespace_bytes_round_trip_exactly() {
327 let identity = FileIdentity::from_unix_parts(0x0102_0304_0506_0708, 0x1122_3344_5566_7788);
328 let encoded = identity.to_namespace_bytes();
329
330 assert_eq!(encoded[0], 1);
331 assert_eq!(&encoded[1..9], &0x0102_0304_0506_0708_u64.to_be_bytes());
332 assert_eq!(&encoded[9..17], &0x1122_3344_5566_7788_u64.to_be_bytes());
333 assert_eq!(&encoded[17..], &[0_u8; 8]);
334 assert_eq!(FileIdentity::from_namespace_bytes(encoded), Some(identity));
335 }
336
337 #[test]
338 fn unix_namespace_bytes_reject_unknown_and_noncanonical_records() {
339 let identity = FileIdentity::from_unix_parts(7, 11);
340 let mut unknown_tag = identity.to_namespace_bytes();
341 unknown_tag[0] = u8::MAX;
342 assert_eq!(FileIdentity::from_namespace_bytes(unknown_tag), None);
343
344 let mut nonzero_tail = identity.to_namespace_bytes();
345 nonzero_tail[24] = 1;
346 assert_eq!(FileIdentity::from_namespace_bytes(nonzero_tail), None);
347 }
348}
349
350#[cfg(all(test, windows))]
351mod file_identity_tests {
352 use super::FileIdentity;
353 use std::cell::Cell;
354 use std::io;
355 use windows_sys::Win32::Foundation::{
356 ERROR_CALL_NOT_IMPLEMENTED, ERROR_INVALID_FUNCTION, ERROR_INVALID_PARAMETER,
357 ERROR_NOT_SUPPORTED,
358 };
359
360 #[test]
361 fn windows_identity_compares_all_file_id_bits() {
362 let file_id = [0x5a_u8; 16];
363 let mut different_high_byte = file_id;
364 different_high_byte[15] ^= 0xff;
365
366 assert_ne!(
367 FileIdentity::from_windows_parts(7, file_id),
368 FileIdentity::from_windows_parts(7, different_high_byte),
369 );
370 }
371
372 #[test]
373 fn windows_identity_rejects_reserved_sentinels() {
374 assert_eq!(FileIdentity::from_windows_parts(7, [0_u8; 16]), None);
375 assert_eq!(FileIdentity::from_windows_parts(7, [u8::MAX; 16]), None);
376 }
377
378 #[test]
379 fn windows_identity_prefers_full_file_id_without_legacy_query() {
380 let expected = [0x3c_u8; 16];
381 let legacy_queried = Cell::new(false);
382
383 let actual = FileIdentity::from_windows_query_result(Ok((19, expected)), || {
384 legacy_queried.set(true);
385 Ok((19, 0, 7))
386 })
387 .expect("full FileIdInfo result should succeed");
388
389 assert_eq!(actual, FileIdentity::from_windows_parts(19, expected));
390 assert!(!legacy_queried.get());
391 }
392
393 #[test]
394 fn windows_identity_falls_back_for_unsupported_file_id_queries() {
395 for code in [
396 ERROR_INVALID_FUNCTION,
397 ERROR_NOT_SUPPORTED,
398 ERROR_INVALID_PARAMETER,
399 ERROR_CALL_NOT_IMPLEMENTED,
400 ] {
401 let actual = FileIdentity::from_windows_query_result(
402 Err(io::Error::from_raw_os_error(code as i32)),
403 || Ok((23, 0x1122_3344, 0x5566_7788)),
404 )
405 .expect("unsupported FileIdInfo should use the legacy query")
406 .expect("legacy file index should produce an identity");
407
408 assert_eq!(
409 actual,
410 FileIdentity::from_windows_legacy_parts(23, 0x1122_3344, 0x5566_7788)
411 );
412 }
413 }
414
415 #[test]
416 fn windows_identity_does_not_fallback_for_real_io_errors() {
417 let legacy_queried = Cell::new(false);
418 let err =
419 FileIdentity::from_windows_query_result(Err(io::Error::from_raw_os_error(6)), || {
420 legacy_queried.set(true);
421 Ok((1, 2, 3))
422 })
423 .expect_err("invalid handles must remain hard errors");
424
425 assert_eq!(err.raw_os_error(), Some(6));
426 assert!(!legacy_queried.get());
427 }
428
429 #[test]
430 fn windows_identity_separates_full_and_legacy_representation_domains() {
431 let file_index = 0x1122_3344_5566_7788_u64;
432 let mut full_file_id = [0_u8; 16];
433 full_file_id[..8].copy_from_slice(&file_index.to_be_bytes());
434
435 assert_ne!(
436 FileIdentity::from_windows_parts(23, full_file_id).expect("non-sentinel full file ID"),
437 FileIdentity::from_windows_legacy_parts(23, 0x1122_3344, 0x5566_7788),
438 );
439 }
440
441 #[test]
442 fn windows_namespace_bytes_round_trip_both_identity_domains() {
443 let full_object = [0x5a_u8; 16];
444 let full = FileIdentity::from_windows_parts(0x0102_0304_0506_0708, full_object)
445 .expect("non-sentinel full file ID");
446 let full_encoded = full.to_namespace_bytes();
447 assert_eq!(full_encoded[0], 2);
448 assert_eq!(
449 &full_encoded[1..9],
450 &0x0102_0304_0506_0708_u64.to_be_bytes()
451 );
452 assert_eq!(&full_encoded[9..], &full_object);
453 assert_eq!(FileIdentity::from_namespace_bytes(full_encoded), Some(full));
454
455 let legacy = FileIdentity::from_windows_legacy_parts(0x0102_0304, 0x1122_3344, 0x5566_7788);
456 let legacy_encoded = legacy.to_namespace_bytes();
457 assert_eq!(legacy_encoded[0], 3);
458 assert_eq!(&legacy_encoded[1..9], &0x0102_0304_u64.to_be_bytes());
459 assert_eq!(
460 &legacy_encoded[9..17],
461 &0x1122_3344_5566_7788_u64.to_be_bytes()
462 );
463 assert_eq!(&legacy_encoded[17..], &[0_u8; 8]);
464 assert_eq!(
465 FileIdentity::from_namespace_bytes(legacy_encoded),
466 Some(legacy)
467 );
468 }
469
470 #[test]
471 fn windows_namespace_bytes_reject_noncanonical_records() {
472 let mut unknown_tag = [0_u8; 25];
473 unknown_tag[0] = u8::MAX;
474 assert_eq!(FileIdentity::from_namespace_bytes(unknown_tag), None);
475
476 let mut full_zero_sentinel = [0_u8; 25];
477 full_zero_sentinel[0] = 2;
478 assert_eq!(FileIdentity::from_namespace_bytes(full_zero_sentinel), None);
479
480 let mut full_ones_sentinel = [u8::MAX; 25];
481 full_ones_sentinel[0] = 2;
482 assert_eq!(FileIdentity::from_namespace_bytes(full_ones_sentinel), None);
483
484 let legacy = FileIdentity::from_windows_legacy_parts(7, 11, 13);
485 let mut legacy_nonzero_tail = legacy.to_namespace_bytes();
486 legacy_nonzero_tail[24] = 1;
487 assert_eq!(
488 FileIdentity::from_namespace_bytes(legacy_nonzero_tail),
489 None
490 );
491
492 let mut legacy_wide_namespace = legacy.to_namespace_bytes();
493 legacy_wide_namespace[1..9].copy_from_slice(&(u64::from(u32::MAX) + 1).to_be_bytes());
494 assert_eq!(
495 FileIdentity::from_namespace_bytes(legacy_wide_namespace),
496 None
497 );
498 }
499}
500
501#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
508pub enum SyncKind {
509 DataOnly,
512 DataAndMetadata,
515 FullDurable,
519}
520
521static DEFAULT_RANDOMNESS_CALL_SEQ: AtomicU64 = AtomicU64::new(0);
522
523pub trait Vfs: Send + Sync {
530 type File: VfsFile;
532
533 fn name(&self) -> &'static str;
535
536 fn open(
545 &self,
546 cx: &Cx,
547 path: Option<&Path>,
548 flags: VfsOpenFlags,
549 ) -> Result<(Self::File, VfsOpenFlags)>;
550
551 fn open_with_expected_identity(
560 &self,
561 cx: &Cx,
562 path: &Path,
563 flags: VfsOpenFlags,
564 expected_identity: FileIdentity,
565 ) -> Result<(Self::File, VfsOpenFlags)> {
566 let mut existing_flags = flags;
567 existing_flags.remove(VfsOpenFlags::CREATE | VfsOpenFlags::EXCLUSIVE);
568 let (file, actual_flags) = self.open(cx, Some(path), existing_flags)?;
569 if file.file_identity()? != Some(expected_identity) {
570 return Err(fsqlite_error::FrankenError::CannotOpen {
571 path: path.to_owned(),
572 });
573 }
574 Ok((file, actual_flags))
575 }
576
577 fn open_reserved_with_expected_identity(
584 &self,
585 cx: &Cx,
586 path: &Path,
587 flags: VfsOpenFlags,
588 expected_identity: FileIdentity,
589 ) -> Result<(Self::File, VfsOpenFlags)> {
590 let (file, actual_flags) =
591 self.open_with_expected_identity(cx, path, flags, expected_identity)?;
592 if file.file_size(cx)? != 0 {
593 return Err(fsqlite_error::FrankenError::CannotOpen {
594 path: path.to_owned(),
595 });
596 }
597
598 for suffix in ["-journal", "-wal", "-wal-fec", "-shm"] {
599 let mut artifact_path = path.as_os_str().to_owned();
600 artifact_path.push(suffix);
601 if self.path_entry_exists(cx, Path::new(&artifact_path))? {
602 return Err(fsqlite_error::FrankenError::CannotOpen {
603 path: path.to_owned(),
604 });
605 }
606 }
607 Ok((file, actual_flags))
608 }
609
610 fn delete(&self, cx: &Cx, path: &Path, sync_dir: bool) -> Result<()>;
615
616 fn sync_parent_directory(&self, _cx: &Cx, _path: &Path) -> Result<()> {
623 Ok(())
624 }
625
626 fn access(&self, cx: &Cx, path: &Path, flags: AccessFlags) -> Result<bool>;
631
632 fn path_entry_exists(&self, cx: &Cx, path: &Path) -> Result<bool> {
639 self.access(cx, path, AccessFlags::EXISTS)
640 }
641
642 fn full_pathname(&self, cx: &Cx, path: &Path) -> Result<PathBuf>;
644
645 fn randomness(&self, cx: &Cx, buf: &mut [u8]) {
653 let _ = cx; let seq = DEFAULT_RANDOMNESS_CALL_SEQ.fetch_add(1, Ordering::Relaxed);
657 let mut state: u64 = 0x5DEE_CE66_D1A4_F681 ^ seq.wrapping_mul(0x9E37_79B9_7F4A_7C15);
658 for chunk in buf.chunks_mut(8) {
659 state ^= state << 13;
660 state ^= state >> 7;
661 state ^= state << 17;
662 let bytes = state.to_le_bytes();
663 for (dst, &src) in chunk.iter_mut().zip(bytes.iter()) {
664 *dst = src;
665 }
666 }
667 }
668
669 fn current_time(&self, cx: &Cx) -> f64 {
672 cx.current_time_julian_day()
674 }
675
676 fn is_memory(&self) -> bool {
680 false
681 }
682}
683
684#[derive(Clone, Copy, Debug, Eq, PartialEq)]
690pub enum VfsWriteCompletionState {
691 Pending,
693 Success,
695 Error,
700}
701
702#[derive(Debug)]
703struct VfsWriteCompletionInner {
704 state: VfsWriteCompletionState,
705 next_waiter_id: u64,
706 waiters: Vec<(u64, Waker)>,
707 terminal_relay: Option<(VfsWriteCompletion, VfsWriteCompletionState)>,
708}
709
710#[derive(Clone, Debug)]
720pub struct VfsWriteCompletion {
721 inner: Arc<Mutex<VfsWriteCompletionInner>>,
722}
723
724impl VfsWriteCompletion {
725 #[must_use]
727 pub fn new() -> Self {
728 Self {
729 inner: Arc::new(Mutex::new(VfsWriteCompletionInner {
730 state: VfsWriteCompletionState::Pending,
731 next_waiter_id: 0,
732 waiters: Vec::new(),
733 terminal_relay: None,
734 })),
735 }
736 }
737
738 #[must_use]
740 pub fn state(&self) -> VfsWriteCompletionState {
741 self.inner
742 .lock()
743 .unwrap_or_else(std::sync::PoisonError::into_inner)
744 .state
745 }
746
747 #[must_use]
752 pub fn wait(&self) -> VfsWriteCompletionWait {
753 VfsWriteCompletionWait {
754 completion: self.clone(),
755 waiter_id: None,
756 }
757 }
758
759 #[doc(hidden)]
763 pub fn complete_success(&self) -> bool {
764 self.complete(VfsWriteCompletionState::Success)
765 }
766
767 #[doc(hidden)]
771 pub fn complete_error(&self) -> bool {
772 self.complete(VfsWriteCompletionState::Error)
773 }
774
775 #[doc(hidden)]
781 #[must_use]
782 pub fn error_mapped_child(&self) -> Self {
783 Self {
784 inner: Arc::new(Mutex::new(VfsWriteCompletionInner {
785 state: VfsWriteCompletionState::Pending,
786 next_waiter_id: 0,
787 waiters: Vec::new(),
788 terminal_relay: Some((self.clone(), VfsWriteCompletionState::Error)),
789 })),
790 }
791 }
792
793 fn complete(&self, terminal: VfsWriteCompletionState) -> bool {
794 debug_assert_ne!(terminal, VfsWriteCompletionState::Pending);
795 let (waiters, terminal_relay) = {
796 let mut inner = self
797 .inner
798 .lock()
799 .unwrap_or_else(std::sync::PoisonError::into_inner);
800 if inner.state != VfsWriteCompletionState::Pending {
801 return false;
802 }
803 inner.state = terminal;
804 (
805 std::mem::take(&mut inner.waiters),
806 inner.terminal_relay.take(),
807 )
808 };
809 for (_, waiter) in waiters {
810 waiter.wake();
811 }
812 if let Some((relay, mapped_terminal)) = terminal_relay {
813 relay.complete(mapped_terminal);
814 }
815 true
816 }
817}
818
819impl Default for VfsWriteCompletion {
820 fn default() -> Self {
821 Self::new()
822 }
823}
824
825#[cfg(not(target_arch = "wasm32"))]
832#[derive(Debug)]
833pub(crate) struct VfsWriteCompletionSource {
834 completion: VfsWriteCompletion,
835 armed: bool,
836}
837
838#[cfg(not(target_arch = "wasm32"))]
839impl VfsWriteCompletionSource {
840 pub(crate) fn new(completion: VfsWriteCompletion) -> Self {
841 Self {
842 completion,
843 armed: true,
844 }
845 }
846
847 pub(crate) fn complete_success(&mut self) {
848 self.completion.complete_success();
849 self.armed = false;
850 }
851
852 pub(crate) fn complete_error(&mut self) {
853 self.completion.complete_error();
854 self.armed = false;
855 }
856}
857
858#[cfg(not(target_arch = "wasm32"))]
859impl Drop for VfsWriteCompletionSource {
860 fn drop(&mut self) {
861 if self.armed {
862 self.completion.complete_error();
863 }
864 }
865}
866
867#[derive(Debug)]
869pub struct VfsWriteCompletionWait {
870 completion: VfsWriteCompletion,
871 waiter_id: Option<u64>,
872}
873
874impl std::future::Future for VfsWriteCompletionWait {
875 type Output = VfsWriteCompletionState;
876
877 fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
878 let this = self.as_mut().get_mut();
879 let completion_inner = Arc::clone(&this.completion.inner);
880 let mut inner = completion_inner
881 .lock()
882 .unwrap_or_else(std::sync::PoisonError::into_inner);
883 if inner.state != VfsWriteCompletionState::Pending {
884 if let Some(waiter_id) = this.waiter_id.take()
885 && let Some(index) = inner
886 .waiters
887 .iter()
888 .position(|(registered_id, _)| *registered_id == waiter_id)
889 {
890 inner.waiters.swap_remove(index);
891 }
892 return Poll::Ready(inner.state);
893 }
894
895 if let Some(waiter_id) = this.waiter_id {
896 let (_, registered_waker) = inner
897 .waiters
898 .iter_mut()
899 .find(|(registered_id, _)| *registered_id == waiter_id)
900 .expect("pending completion waiter must remain registered");
901 if !registered_waker.will_wake(cx.waker()) {
902 registered_waker.clone_from(cx.waker());
903 }
904 } else {
905 let waiter_id = loop {
906 let candidate = inner.next_waiter_id;
907 inner.next_waiter_id = inner.next_waiter_id.wrapping_add(1);
908 if inner
909 .waiters
910 .iter()
911 .all(|(registered_id, _)| *registered_id != candidate)
912 {
913 break candidate;
914 }
915 };
916 inner.waiters.push((waiter_id, cx.waker().clone()));
917 this.waiter_id = Some(waiter_id);
918 }
919 Poll::Pending
920 }
921}
922
923impl Drop for VfsWriteCompletionWait {
924 fn drop(&mut self) {
925 let Some(waiter_id) = self.waiter_id.take() else {
926 return;
927 };
928 let mut inner = self
929 .completion
930 .inner
931 .lock()
932 .unwrap_or_else(std::sync::PoisonError::into_inner);
933 if let Some(index) = inner
934 .waiters
935 .iter()
936 .position(|(registered_id, _)| *registered_id == waiter_id)
937 {
938 inner.waiters.swap_remove(index);
939 }
940 }
941}
942
943pub trait VfsFile: Send + Sync {
947 fn close(&mut self, cx: &Cx) -> Result<()>;
951
952 fn file_identity(&self) -> Result<Option<FileIdentity>> {
959 Ok(None)
960 }
961
962 fn read<'a>(
967 &'a self,
968 cx: &'a Cx,
969 buf: &'a mut [u8],
970 offset: u64,
971 ) -> impl std::future::Future<Output = Result<usize>> + Send + 'a;
972
973 fn write<'a>(
975 &'a self,
976 cx: &'a Cx,
977 buf: &'a [u8],
978 offset: u64,
979 ) -> impl std::future::Future<Output = Result<()>> + Send + 'a;
980
981 fn write_tracked<'a>(
990 &'a self,
991 cx: &'a Cx,
992 buf: &'a [u8],
993 offset: u64,
994 completion: VfsWriteCompletion,
995 ) -> impl std::future::Future<Output = Result<()>> + Send + 'a {
996 async move {
997 let result = self.write(cx, buf, offset).await;
998 if result.is_ok() {
999 completion.complete_success();
1000 } else {
1001 completion.complete_error();
1002 }
1003 result
1004 }
1005 }
1006
1007 fn write_page_batch<'a>(
1013 &'a self,
1014 cx: &'a Cx,
1015 writes: &'a [(u64, &'a [u8])],
1016 ) -> impl std::future::Future<Output = Result<()>> + Send + 'a {
1017 async move {
1018 for (offset, data) in writes {
1019 self.write(cx, data, *offset).await?;
1020 }
1021 Ok(())
1022 }
1023 }
1024
1025 fn truncate(&mut self, cx: &Cx, size: u64) -> Result<()>;
1027
1028 fn sync(&mut self, cx: &Cx, flags: SyncFlags) -> Result<()>;
1032
1033 fn durable_sync(&mut self, cx: &Cx, kind: SyncKind) -> Result<()> {
1040 let flags = match kind {
1041 SyncKind::DataOnly => SyncFlags::DATAONLY,
1042 SyncKind::DataAndMetadata | SyncKind::FullDurable => SyncFlags::FULL,
1043 };
1044 self.sync(cx, flags)
1045 }
1046
1047 fn file_size(&self, cx: &Cx) -> Result<u64>;
1049
1050 fn lock(&mut self, cx: &Cx, level: LockLevel) -> Result<()>;
1054
1055 fn unlock(&mut self, cx: &Cx, level: LockLevel) -> Result<()>;
1057
1058 fn lock_external_shared_snapshot(&mut self, cx: &Cx) -> Result<()>;
1066
1067 fn restore_external_shared_snapshot_attempt(&mut self, cx: &Cx) -> Result<()>;
1074
1075 fn lock_external_maintenance(&mut self, cx: &Cx, wal_mode: bool) -> Result<()>;
1082
1083 fn restore_external_maintenance_attempt(&mut self, cx: &Cx) -> Result<()>;
1095
1096 fn check_reserved_lock(&self, cx: &Cx) -> Result<bool>;
1100
1101 fn sector_size(&self) -> u32 {
1106 4096
1107 }
1108
1109 fn device_characteristics(&self) -> u32 {
1115 0
1116 }
1117
1118 fn shm_map(&mut self, cx: &Cx, region: u32, size: u32, extend: bool) -> Result<ShmRegion>;
1125
1126 fn shm_lock(&mut self, cx: &Cx, offset: u32, n: u32, flags: u32) -> Result<()>;
1131
1132 fn shm_barrier(&self);
1136
1137 fn shm_unmap(&mut self, cx: &Cx, delete: bool) -> Result<()>;
1141
1142 fn set_busy_timeout_ms(&mut self, _ms: u64) {}
1151}
1152
1153#[cfg(test)]
1154mod tests {
1155 #![allow(clippy::unused_async_trait_impl)]
1159
1160 use super::*;
1161
1162 fn poll_ready<F: std::future::Future>(future: F) -> F::Output {
1163 use std::task::{Context, Poll, Waker};
1164
1165 let mut future = std::pin::pin!(future);
1166 let mut task_cx = Context::from_waker(Waker::noop());
1167 match future.as_mut().poll(&mut task_cx) {
1168 Poll::Ready(output) => output,
1169 Poll::Pending => panic!("test future unexpectedly yielded"),
1170 }
1171 }
1172
1173 #[test]
1176 fn vfs_file_supports_static_dispatch_without_boxing() {
1177 fn accepts_static<T: VfsFile>(_file: &T) {}
1178
1179 let cx = Cx::new();
1180 let vfs = crate::memory::MemoryVfs::new();
1181 let (file, _) = vfs
1182 .open(&cx, None, VfsOpenFlags::CREATE | VfsOpenFlags::READWRITE)
1183 .expect("open memory file");
1184 accepts_static(&file);
1185 }
1186
1187 #[test]
1189 fn vfs_file_defaults() {
1190 struct DummyFile;
1191 impl VfsFile for DummyFile {
1192 fn close(&mut self, _cx: &Cx) -> Result<()> {
1193 Ok(())
1194 }
1195 async fn read(&self, _cx: &Cx, _buf: &mut [u8], _offset: u64) -> Result<usize> {
1196 Ok(0)
1197 }
1198 async fn write(&self, _cx: &Cx, _buf: &[u8], _offset: u64) -> Result<()> {
1199 Ok(())
1200 }
1201 fn truncate(&mut self, _cx: &Cx, _size: u64) -> Result<()> {
1202 Ok(())
1203 }
1204 fn sync(&mut self, _cx: &Cx, _flags: SyncFlags) -> Result<()> {
1205 Ok(())
1206 }
1207 fn file_size(&self, _cx: &Cx) -> Result<u64> {
1208 Ok(0)
1209 }
1210 fn lock(&mut self, _cx: &Cx, _level: LockLevel) -> Result<()> {
1211 Ok(())
1212 }
1213 fn unlock(&mut self, _cx: &Cx, _level: LockLevel) -> Result<()> {
1214 Ok(())
1215 }
1216 fn lock_external_shared_snapshot(&mut self, _: &Cx) -> Result<()> {
1217 Err(fsqlite_error::FrankenError::Unsupported)
1218 }
1219 fn restore_external_shared_snapshot_attempt(&mut self, _: &Cx) -> Result<()> {
1220 Ok(())
1221 }
1222 fn lock_external_maintenance(&mut self, _: &Cx, _: bool) -> Result<()> {
1223 Err(fsqlite_error::FrankenError::Unsupported)
1224 }
1225 fn restore_external_maintenance_attempt(&mut self, _: &Cx) -> Result<()> {
1226 Ok(())
1227 }
1228 fn check_reserved_lock(&self, _cx: &Cx) -> Result<bool> {
1229 Ok(false)
1230 }
1231 fn shm_map(
1232 &mut self,
1233 _cx: &Cx,
1234 _region: u32,
1235 _size: u32,
1236 _extend: bool,
1237 ) -> Result<ShmRegion> {
1238 Err(fsqlite_error::FrankenError::Unsupported)
1239 }
1240 fn shm_lock(&mut self, _cx: &Cx, _offset: u32, _n: u32, _flags: u32) -> Result<()> {
1241 Err(fsqlite_error::FrankenError::Unsupported)
1242 }
1243 fn shm_barrier(&self) {}
1244 fn shm_unmap(&mut self, _cx: &Cx, _delete: bool) -> Result<()> {
1245 Ok(())
1246 }
1247 }
1248
1249 let cx = Cx::new();
1250 let mut file = DummyFile;
1251 assert_eq!(file.sector_size(), 4096);
1252 assert_eq!(file.device_characteristics(), 0);
1253 assert!(matches!(
1254 file.lock_external_shared_snapshot(&cx),
1255 Err(fsqlite_error::FrankenError::Unsupported)
1256 ));
1257 file.restore_external_shared_snapshot_attempt(&cx)
1258 .expect("restoring an unsupported snapshot attempt is idempotent");
1259 assert!(matches!(
1260 file.lock_external_maintenance(&cx, true),
1261 Err(fsqlite_error::FrankenError::Unsupported)
1262 ));
1263 file.restore_external_maintenance_attempt(&cx)
1264 .expect("restoring an unsupported maintenance attempt is idempotent");
1265 }
1266
1267 #[test]
1269 fn vfs_file_sector_size_default_is_4096() {
1270 struct Stub;
1271 impl VfsFile for Stub {
1272 fn close(&mut self, _: &Cx) -> Result<()> {
1273 Ok(())
1274 }
1275 async fn read(&self, _: &Cx, _: &mut [u8], _: u64) -> Result<usize> {
1276 Ok(0)
1277 }
1278 async fn write(&self, _: &Cx, _: &[u8], _: u64) -> Result<()> {
1279 Ok(())
1280 }
1281 fn truncate(&mut self, _: &Cx, _: u64) -> Result<()> {
1282 Ok(())
1283 }
1284 fn sync(&mut self, _: &Cx, _: SyncFlags) -> Result<()> {
1285 Ok(())
1286 }
1287 fn file_size(&self, _: &Cx) -> Result<u64> {
1288 Ok(0)
1289 }
1290 fn lock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1291 Ok(())
1292 }
1293 fn unlock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1294 Ok(())
1295 }
1296 fn lock_external_shared_snapshot(&mut self, _: &Cx) -> Result<()> {
1297 Err(fsqlite_error::FrankenError::Unsupported)
1298 }
1299 fn restore_external_shared_snapshot_attempt(&mut self, _: &Cx) -> Result<()> {
1300 Ok(())
1301 }
1302 fn lock_external_maintenance(&mut self, _: &Cx, _: bool) -> Result<()> {
1303 Err(fsqlite_error::FrankenError::Unsupported)
1304 }
1305 fn restore_external_maintenance_attempt(&mut self, _: &Cx) -> Result<()> {
1306 Ok(())
1307 }
1308 fn check_reserved_lock(&self, _: &Cx) -> Result<bool> {
1309 Ok(false)
1310 }
1311 fn shm_map(&mut self, _: &Cx, _: u32, _: u32, _: bool) -> Result<ShmRegion> {
1312 Err(fsqlite_error::FrankenError::Unsupported)
1313 }
1314 fn shm_lock(&mut self, _: &Cx, _: u32, _: u32, _: u32) -> Result<()> {
1315 Err(fsqlite_error::FrankenError::Unsupported)
1316 }
1317 fn shm_barrier(&self) {}
1318 fn shm_unmap(&mut self, _: &Cx, _: bool) -> Result<()> {
1319 Ok(())
1320 }
1321 }
1322
1323 let file = Stub;
1324 assert_eq!(file.sector_size(), 4096);
1325 assert_eq!(file.device_characteristics(), 0);
1326 }
1327
1328 #[test]
1330 fn vfs_default_randomness_varies() {
1331 use crate::memory::MemoryVfs;
1332 use crate::traits::Vfs;
1333
1334 let cx = Cx::new();
1335 let vfs = MemoryVfs::new();
1336 let mut buf1 = [0u8; 32];
1337 let mut buf2 = [0u8; 32];
1338 vfs.randomness(&cx, &mut buf1);
1339 vfs.randomness(&cx, &mut buf2);
1340 assert_ne!(buf1, buf2);
1341 }
1342
1343 #[test]
1345 fn vfs_default_current_time_from_cx() {
1346 use crate::memory::MemoryVfs;
1347 use crate::traits::Vfs;
1348
1349 let cx = Cx::new();
1350 cx.set_unix_millis_for_testing(0);
1351 let vfs = MemoryVfs::new();
1352 let t1 = vfs.current_time(&cx);
1353 #[allow(clippy::approx_constant)]
1355 let expected = 2_440_587.5;
1356 assert!(
1357 (t1 - expected).abs() < 1e-6,
1358 "at unix epoch, julian day should be ~2440587.5, got {t1}"
1359 );
1360 }
1361
1362 #[test]
1364 fn vfs_randomness_zero_length_buffer() {
1365 use crate::memory::MemoryVfs;
1366 use crate::traits::Vfs;
1367
1368 let cx = Cx::new();
1369 let vfs = MemoryVfs::new();
1370 let mut buf = [];
1371 vfs.randomness(&cx, &mut buf);
1372 }
1373
1374 #[test]
1376 fn vfs_randomness_single_byte() {
1377 use crate::memory::MemoryVfs;
1378 use crate::traits::Vfs;
1379
1380 let cx = Cx::new();
1381 let vfs = MemoryVfs::new();
1382 let mut buf = [0u8; 1];
1383 vfs.randomness(&cx, &mut buf);
1384 }
1386
1387 #[test]
1388 fn vfs_is_memory_default_is_false() {
1389 use crate::memory::MemoryVfs;
1390 use crate::traits::Vfs;
1391
1392 let vfs = MemoryVfs::new();
1393 assert!(vfs.is_memory(), "MemoryVfs::is_memory must return true");
1394 }
1395
1396 #[test]
1397 fn vfs_trait_is_object_safe() {
1398 use crate::memory::MemoryVfs;
1399 fn _accepts_dyn(_v: &dyn Vfs<File = crate::memory::MemoryFile>) {}
1400 let _vfs = MemoryVfs::new();
1401 }
1402
1403 #[test]
1404 fn vfs_file_set_busy_timeout_is_noop() {
1405 struct Stub;
1406 impl VfsFile for Stub {
1407 fn close(&mut self, _: &Cx) -> Result<()> {
1408 Ok(())
1409 }
1410 async fn read(&self, _: &Cx, _: &mut [u8], _: u64) -> Result<usize> {
1411 Ok(0)
1412 }
1413 async fn write(&self, _: &Cx, _: &[u8], _: u64) -> Result<()> {
1414 Ok(())
1415 }
1416 fn truncate(&mut self, _: &Cx, _: u64) -> Result<()> {
1417 Ok(())
1418 }
1419 fn sync(&mut self, _: &Cx, _: SyncFlags) -> Result<()> {
1420 Ok(())
1421 }
1422 fn file_size(&self, _: &Cx) -> Result<u64> {
1423 Ok(0)
1424 }
1425 fn lock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1426 Ok(())
1427 }
1428 fn unlock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1429 Ok(())
1430 }
1431 fn lock_external_shared_snapshot(&mut self, _: &Cx) -> Result<()> {
1432 Err(fsqlite_error::FrankenError::Unsupported)
1433 }
1434 fn restore_external_shared_snapshot_attempt(&mut self, _: &Cx) -> Result<()> {
1435 Ok(())
1436 }
1437 fn lock_external_maintenance(&mut self, _: &Cx, _: bool) -> Result<()> {
1438 Err(fsqlite_error::FrankenError::Unsupported)
1439 }
1440 fn restore_external_maintenance_attempt(&mut self, _: &Cx) -> Result<()> {
1441 Ok(())
1442 }
1443 fn check_reserved_lock(&self, _: &Cx) -> Result<bool> {
1444 Ok(false)
1445 }
1446 fn shm_map(&mut self, _: &Cx, _: u32, _: u32, _: bool) -> Result<ShmRegion> {
1447 Err(fsqlite_error::FrankenError::Unsupported)
1448 }
1449 fn shm_lock(&mut self, _: &Cx, _: u32, _: u32, _: u32) -> Result<()> {
1450 Err(fsqlite_error::FrankenError::Unsupported)
1451 }
1452 fn shm_barrier(&self) {}
1453 fn shm_unmap(&mut self, _: &Cx, _: bool) -> Result<()> {
1454 Ok(())
1455 }
1456 }
1457
1458 let mut file = Stub;
1459 file.set_busy_timeout_ms(5000);
1460 file.set_busy_timeout_ms(0);
1461 }
1462
1463 #[test]
1464 fn vfs_file_write_page_batch_default_delegates_to_write() {
1465 use std::sync::atomic::{AtomicUsize, Ordering};
1466
1467 static WRITE_COUNT: AtomicUsize = AtomicUsize::new(0);
1468
1469 struct CountingFile;
1470 impl VfsFile for CountingFile {
1471 fn close(&mut self, _: &Cx) -> Result<()> {
1472 Ok(())
1473 }
1474 async fn read(&self, _: &Cx, _: &mut [u8], _: u64) -> Result<usize> {
1475 Ok(0)
1476 }
1477 async fn write(&self, _: &Cx, _: &[u8], _: u64) -> Result<()> {
1478 WRITE_COUNT.fetch_add(1, Ordering::Relaxed);
1479 Ok(())
1480 }
1481 fn truncate(&mut self, _: &Cx, _: u64) -> Result<()> {
1482 Ok(())
1483 }
1484 fn sync(&mut self, _: &Cx, _: SyncFlags) -> Result<()> {
1485 Ok(())
1486 }
1487 fn file_size(&self, _: &Cx) -> Result<u64> {
1488 Ok(0)
1489 }
1490 fn lock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1491 Ok(())
1492 }
1493 fn unlock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1494 Ok(())
1495 }
1496 fn lock_external_shared_snapshot(&mut self, _: &Cx) -> Result<()> {
1497 Err(fsqlite_error::FrankenError::Unsupported)
1498 }
1499 fn restore_external_shared_snapshot_attempt(&mut self, _: &Cx) -> Result<()> {
1500 Ok(())
1501 }
1502 fn lock_external_maintenance(&mut self, _: &Cx, _: bool) -> Result<()> {
1503 Err(fsqlite_error::FrankenError::Unsupported)
1504 }
1505 fn restore_external_maintenance_attempt(&mut self, _: &Cx) -> Result<()> {
1506 Ok(())
1507 }
1508 fn check_reserved_lock(&self, _: &Cx) -> Result<bool> {
1509 Ok(false)
1510 }
1511 fn shm_map(&mut self, _: &Cx, _: u32, _: u32, _: bool) -> Result<ShmRegion> {
1512 Err(fsqlite_error::FrankenError::Unsupported)
1513 }
1514 fn shm_lock(&mut self, _: &Cx, _: u32, _: u32, _: u32) -> Result<()> {
1515 Err(fsqlite_error::FrankenError::Unsupported)
1516 }
1517 fn shm_barrier(&self) {}
1518 fn shm_unmap(&mut self, _: &Cx, _: bool) -> Result<()> {
1519 Ok(())
1520 }
1521 }
1522
1523 WRITE_COUNT.store(0, Ordering::Relaxed);
1524 let cx = Cx::new();
1525 let file = CountingFile;
1526 let data = [0u8; 4096];
1527 let writes: Vec<(u64, &[u8])> = vec![(0, &data), (4096, &data), (8192, &data)];
1528 poll_ready(file.write_page_batch(&cx, &writes)).unwrap();
1529 assert_eq!(WRITE_COUNT.load(Ordering::Relaxed), 3);
1530 }
1531
1532 #[test]
1533 fn vfs_randomness_fills_large_buffer() {
1534 use crate::memory::MemoryVfs;
1535 use crate::traits::Vfs;
1536
1537 let cx = Cx::new();
1538 let vfs = MemoryVfs::new();
1539 let mut buf = [0u8; 256];
1540 vfs.randomness(&cx, &mut buf);
1541 let all_zero = buf.iter().all(|&b| b == 0);
1542 assert!(
1543 !all_zero,
1544 "256-byte randomness buffer should not be all zeros"
1545 );
1546 }
1547
1548 #[test]
1549 fn vfs_randomness_non_aligned_buffer() {
1550 use crate::memory::MemoryVfs;
1551 use crate::traits::Vfs;
1552
1553 let cx = Cx::new();
1554 let vfs = MemoryVfs::new();
1555 let mut buf = [0u8; 13];
1556 vfs.randomness(&cx, &mut buf);
1557 let all_zero = buf.iter().all(|&b| b == 0);
1558 assert!(!all_zero, "13-byte non-aligned buffer should be filled");
1559 }
1560
1561 #[test]
1562 fn vfs_write_page_batch_empty_is_noop() {
1563 struct Stub;
1564 impl VfsFile for Stub {
1565 fn close(&mut self, _: &Cx) -> Result<()> {
1566 Ok(())
1567 }
1568 async fn read(&self, _: &Cx, _: &mut [u8], _: u64) -> Result<usize> {
1569 Ok(0)
1570 }
1571 async fn write(&self, _: &Cx, _: &[u8], _: u64) -> Result<()> {
1572 panic!("write should not be called for empty batch");
1573 }
1574 fn truncate(&mut self, _: &Cx, _: u64) -> Result<()> {
1575 Ok(())
1576 }
1577 fn sync(&mut self, _: &Cx, _: SyncFlags) -> Result<()> {
1578 Ok(())
1579 }
1580 fn file_size(&self, _: &Cx) -> Result<u64> {
1581 Ok(0)
1582 }
1583 fn lock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1584 Ok(())
1585 }
1586 fn unlock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1587 Ok(())
1588 }
1589 fn lock_external_shared_snapshot(&mut self, _: &Cx) -> Result<()> {
1590 Err(fsqlite_error::FrankenError::Unsupported)
1591 }
1592 fn restore_external_shared_snapshot_attempt(&mut self, _: &Cx) -> Result<()> {
1593 Ok(())
1594 }
1595 fn lock_external_maintenance(&mut self, _: &Cx, _: bool) -> Result<()> {
1596 Err(fsqlite_error::FrankenError::Unsupported)
1597 }
1598 fn restore_external_maintenance_attempt(&mut self, _: &Cx) -> Result<()> {
1599 Ok(())
1600 }
1601 fn check_reserved_lock(&self, _: &Cx) -> Result<bool> {
1602 Ok(false)
1603 }
1604 fn shm_map(&mut self, _: &Cx, _: u32, _: u32, _: bool) -> Result<ShmRegion> {
1605 Err(fsqlite_error::FrankenError::Unsupported)
1606 }
1607 fn shm_lock(&mut self, _: &Cx, _: u32, _: u32, _: u32) -> Result<()> {
1608 Err(fsqlite_error::FrankenError::Unsupported)
1609 }
1610 fn shm_barrier(&self) {}
1611 fn shm_unmap(&mut self, _: &Cx, _: bool) -> Result<()> {
1612 Ok(())
1613 }
1614 }
1615
1616 let cx = Cx::new();
1617 let file = Stub;
1618 let writes: Vec<(u64, &[u8])> = vec![];
1619 poll_ready(file.write_page_batch(&cx, &writes)).unwrap();
1620 }
1621
1622 #[test]
1623 fn memory_vfs_name_is_memory() {
1624 use crate::memory::MemoryVfs;
1625 use crate::traits::Vfs;
1626
1627 let vfs = MemoryVfs::new();
1628 assert_eq!(vfs.name(), "memory");
1629 }
1630
1631 #[test]
1632 fn vfs_current_time_advances_with_unix_millis() {
1633 use crate::memory::MemoryVfs;
1634 use crate::traits::Vfs;
1635
1636 let cx = Cx::new();
1637 let vfs = MemoryVfs::new();
1638 cx.set_unix_millis_for_testing(0);
1639 let t0 = vfs.current_time(&cx);
1640 cx.set_unix_millis_for_testing(86_400_000);
1641 let t1 = vfs.current_time(&cx);
1642 let delta = t1 - t0;
1643 assert!(
1644 (delta - 1.0).abs() < 1e-6,
1645 "86400000ms = 1 Julian day, got delta {delta}"
1646 );
1647 }
1648
1649 #[test]
1650 fn write_page_batch_short_circuits_on_error() {
1651 use std::sync::atomic::{AtomicUsize, Ordering};
1652
1653 static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
1654
1655 struct FailOnSecond;
1656 impl VfsFile for FailOnSecond {
1657 fn close(&mut self, _: &Cx) -> Result<()> {
1658 Ok(())
1659 }
1660 async fn read(&self, _: &Cx, _: &mut [u8], _: u64) -> Result<usize> {
1661 Ok(0)
1662 }
1663 async fn write(&self, _: &Cx, _: &[u8], _: u64) -> Result<()> {
1664 let n = CALL_COUNT.fetch_add(1, Ordering::Relaxed);
1665 if n >= 1 {
1666 return Err(fsqlite_error::FrankenError::Io(std::io::Error::other(
1667 "injected",
1668 )));
1669 }
1670 Ok(())
1671 }
1672 fn truncate(&mut self, _: &Cx, _: u64) -> Result<()> {
1673 Ok(())
1674 }
1675 fn sync(&mut self, _: &Cx, _: SyncFlags) -> Result<()> {
1676 Ok(())
1677 }
1678 fn file_size(&self, _: &Cx) -> Result<u64> {
1679 Ok(0)
1680 }
1681 fn lock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1682 Ok(())
1683 }
1684 fn unlock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1685 Ok(())
1686 }
1687 fn lock_external_shared_snapshot(&mut self, _: &Cx) -> Result<()> {
1688 Err(fsqlite_error::FrankenError::Unsupported)
1689 }
1690 fn restore_external_shared_snapshot_attempt(&mut self, _: &Cx) -> Result<()> {
1691 Ok(())
1692 }
1693 fn lock_external_maintenance(&mut self, _: &Cx, _: bool) -> Result<()> {
1694 Err(fsqlite_error::FrankenError::Unsupported)
1695 }
1696 fn restore_external_maintenance_attempt(&mut self, _: &Cx) -> Result<()> {
1697 Ok(())
1698 }
1699 fn check_reserved_lock(&self, _: &Cx) -> Result<bool> {
1700 Ok(false)
1701 }
1702 fn shm_map(&mut self, _: &Cx, _: u32, _: u32, _: bool) -> Result<ShmRegion> {
1703 Err(fsqlite_error::FrankenError::Unsupported)
1704 }
1705 fn shm_lock(&mut self, _: &Cx, _: u32, _: u32, _: u32) -> Result<()> {
1706 Err(fsqlite_error::FrankenError::Unsupported)
1707 }
1708 fn shm_barrier(&self) {}
1709 fn shm_unmap(&mut self, _: &Cx, _: bool) -> Result<()> {
1710 Ok(())
1711 }
1712 }
1713
1714 CALL_COUNT.store(0, Ordering::Relaxed);
1715 let cx = Cx::new();
1716 let file = FailOnSecond;
1717 let data = [0u8; 64];
1718 let writes: Vec<(u64, &[u8])> = vec![(0, &data), (64, &data), (128, &data)];
1719 let result = poll_ready(file.write_page_batch(&cx, &writes));
1720 assert!(result.is_err());
1721 assert_eq!(
1722 CALL_COUNT.load(Ordering::Relaxed),
1723 2,
1724 "should stop after second write fails, not call third"
1725 );
1726 }
1727
1728 #[test]
1729 fn vfs_file_defaults_can_be_overridden() {
1730 struct CustomFile;
1731 impl VfsFile for CustomFile {
1732 fn close(&mut self, _: &Cx) -> Result<()> {
1733 Ok(())
1734 }
1735 async fn read(&self, _: &Cx, _: &mut [u8], _: u64) -> Result<usize> {
1736 Ok(0)
1737 }
1738 async fn write(&self, _: &Cx, _: &[u8], _: u64) -> Result<()> {
1739 Ok(())
1740 }
1741 fn truncate(&mut self, _: &Cx, _: u64) -> Result<()> {
1742 Ok(())
1743 }
1744 fn sync(&mut self, _: &Cx, _: SyncFlags) -> Result<()> {
1745 Ok(())
1746 }
1747 fn file_size(&self, _: &Cx) -> Result<u64> {
1748 Ok(0)
1749 }
1750 fn lock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1751 Ok(())
1752 }
1753 fn unlock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1754 Ok(())
1755 }
1756 fn lock_external_shared_snapshot(&mut self, _: &Cx) -> Result<()> {
1757 Err(fsqlite_error::FrankenError::Unsupported)
1758 }
1759 fn restore_external_shared_snapshot_attempt(&mut self, _: &Cx) -> Result<()> {
1760 Ok(())
1761 }
1762 fn lock_external_maintenance(&mut self, _: &Cx, _: bool) -> Result<()> {
1763 Err(fsqlite_error::FrankenError::Unsupported)
1764 }
1765 fn restore_external_maintenance_attempt(&mut self, _: &Cx) -> Result<()> {
1766 Ok(())
1767 }
1768 fn check_reserved_lock(&self, _: &Cx) -> Result<bool> {
1769 Ok(false)
1770 }
1771 fn sector_size(&self) -> u32 {
1772 512
1773 }
1774 fn device_characteristics(&self) -> u32 {
1775 0x0010
1776 }
1777 fn shm_map(&mut self, _: &Cx, _: u32, _: u32, _: bool) -> Result<ShmRegion> {
1778 Err(fsqlite_error::FrankenError::Unsupported)
1779 }
1780 fn shm_lock(&mut self, _: &Cx, _: u32, _: u32, _: u32) -> Result<()> {
1781 Err(fsqlite_error::FrankenError::Unsupported)
1782 }
1783 fn shm_barrier(&self) {}
1784 fn shm_unmap(&mut self, _: &Cx, _: bool) -> Result<()> {
1785 Ok(())
1786 }
1787 }
1788
1789 let file = CustomFile;
1790 assert_eq!(file.sector_size(), 512);
1791 assert_eq!(file.device_characteristics(), 0x0010);
1792 }
1793
1794 #[test]
1795 fn vfs_randomness_has_byte_level_entropy() {
1796 use crate::memory::MemoryVfs;
1797 use crate::traits::Vfs;
1798
1799 let cx = Cx::new();
1800 let vfs = MemoryVfs::new();
1801 let mut buf = [0u8; 64];
1802 vfs.randomness(&cx, &mut buf);
1803 let distinct: std::collections::HashSet<u8> = buf.iter().copied().collect();
1804 assert!(
1805 distinct.len() > 4,
1806 "64-byte buffer should have more than 4 distinct byte values, got {}",
1807 distinct.len()
1808 );
1809 }
1810
1811 #[test]
1812 fn vfs_current_time_default_returns_reasonable_julian_day() {
1813 use crate::memory::MemoryVfs;
1814 use crate::traits::Vfs;
1815
1816 let cx = Cx::new();
1817 let vfs = MemoryVfs::new();
1818 let jd = vfs.current_time(&cx);
1819 assert!(jd.is_finite(), "Julian day must be finite");
1820 assert!(
1821 jd > 2_440_000.0,
1822 "Julian day should be after ~1968, got {jd}"
1823 );
1824 }
1825
1826 #[test]
1827 fn durable_sync_default_delegates_to_sync() {
1828 use std::sync::atomic::{AtomicU8, Ordering};
1829 static LAST_FLAGS: AtomicU8 = AtomicU8::new(0);
1830
1831 struct RecordingFile;
1832 impl VfsFile for RecordingFile {
1833 fn close(&mut self, _: &Cx) -> Result<()> {
1834 Ok(())
1835 }
1836 async fn read(&self, _: &Cx, _: &mut [u8], _: u64) -> Result<usize> {
1837 Ok(0)
1838 }
1839 async fn write(&self, _: &Cx, _: &[u8], _: u64) -> Result<()> {
1840 Ok(())
1841 }
1842 fn truncate(&mut self, _: &Cx, _: u64) -> Result<()> {
1843 Ok(())
1844 }
1845 fn sync(&mut self, _: &Cx, flags: SyncFlags) -> Result<()> {
1846 LAST_FLAGS.store(flags.bits(), Ordering::Relaxed);
1847 Ok(())
1848 }
1849 fn file_size(&self, _: &Cx) -> Result<u64> {
1850 Ok(0)
1851 }
1852 fn lock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1853 Ok(())
1854 }
1855 fn unlock(&mut self, _: &Cx, _: LockLevel) -> Result<()> {
1856 Ok(())
1857 }
1858 fn lock_external_shared_snapshot(&mut self, _: &Cx) -> Result<()> {
1859 Err(fsqlite_error::FrankenError::Unsupported)
1860 }
1861 fn restore_external_shared_snapshot_attempt(&mut self, _: &Cx) -> Result<()> {
1862 Ok(())
1863 }
1864 fn lock_external_maintenance(&mut self, _: &Cx, _: bool) -> Result<()> {
1865 Err(fsqlite_error::FrankenError::Unsupported)
1866 }
1867 fn restore_external_maintenance_attempt(&mut self, _: &Cx) -> Result<()> {
1868 Ok(())
1869 }
1870 fn check_reserved_lock(&self, _: &Cx) -> Result<bool> {
1871 Ok(false)
1872 }
1873 fn shm_map(&mut self, _: &Cx, _: u32, _: u32, _: bool) -> Result<ShmRegion> {
1874 Err(fsqlite_error::FrankenError::Unsupported)
1875 }
1876 fn shm_lock(&mut self, _: &Cx, _: u32, _: u32, _: u32) -> Result<()> {
1877 Err(fsqlite_error::FrankenError::Unsupported)
1878 }
1879 fn shm_barrier(&self) {}
1880 fn shm_unmap(&mut self, _: &Cx, _: bool) -> Result<()> {
1881 Ok(())
1882 }
1883 }
1884
1885 let cx = Cx::new();
1886 let mut f = RecordingFile;
1887
1888 f.durable_sync(&cx, SyncKind::DataOnly).unwrap();
1889 assert_eq!(
1890 LAST_FLAGS.load(Ordering::Relaxed),
1891 SyncFlags::DATAONLY.bits()
1892 );
1893
1894 f.durable_sync(&cx, SyncKind::FullDurable).unwrap();
1895 assert_eq!(LAST_FLAGS.load(Ordering::Relaxed), SyncFlags::FULL.bits());
1896
1897 f.durable_sync(&cx, SyncKind::DataAndMetadata).unwrap();
1898 assert_eq!(LAST_FLAGS.load(Ordering::Relaxed), SyncFlags::FULL.bits());
1899 }
1900
1901 #[test]
1902 fn sync_kind_variants_are_distinct() {
1903 assert_ne!(SyncKind::DataOnly, SyncKind::DataAndMetadata);
1904 assert_ne!(SyncKind::DataAndMetadata, SyncKind::FullDurable);
1905 assert_ne!(SyncKind::DataOnly, SyncKind::FullDurable);
1906 }
1907
1908 #[test]
1909 fn vfs_file_async_data_path_is_implementable() {
1910 use crate::memory::MemoryFile;
1911
1912 fn assert_impl<T: VfsFile>() {}
1913 assert_impl::<MemoryFile>();
1914 }
1915
1916 #[test]
1917 fn write_completion_wait_is_cancel_safe_and_terminal_state_is_sticky() {
1918 use std::future::Future as _;
1919
1920 let completion = VfsWriteCompletion::new();
1921 let mut first_waiter = Box::pin(completion.wait());
1922 let mut task_cx = Context::from_waker(Waker::noop());
1923 assert!(matches!(
1924 first_waiter.as_mut().poll(&mut task_cx),
1925 Poll::Pending
1926 ));
1927 assert_eq!(
1928 completion
1929 .inner
1930 .lock()
1931 .unwrap_or_else(std::sync::PoisonError::into_inner)
1932 .waiters
1933 .len(),
1934 1
1935 );
1936 drop(first_waiter);
1937 assert!(
1938 completion
1939 .inner
1940 .lock()
1941 .unwrap_or_else(std::sync::PoisonError::into_inner)
1942 .waiters
1943 .is_empty(),
1944 "dropping a wait future must unregister it"
1945 );
1946
1947 assert!(completion.complete_success());
1948 assert!(!completion.complete_error());
1949 assert_eq!(completion.state(), VfsWriteCompletionState::Success);
1950 assert_eq!(
1951 poll_ready(completion.wait()),
1952 VfsWriteCompletionState::Success
1953 );
1954
1955 let faulted_write = VfsWriteCompletion::new();
1956 let partial_source = faulted_write.error_mapped_child();
1957 assert!(partial_source.complete_success());
1958 assert_eq!(
1959 faulted_write.state(),
1960 VfsWriteCompletionState::Error,
1961 "a completed partial-write source must map to outer Error"
1962 );
1963 }
1964
1965 #[test]
1966 fn async_memory_write_completion_is_immediate_and_bytes_are_real() {
1967 use std::future::Future as _;
1968 use std::task::{Context, Poll, Waker};
1969
1970 use crate::memory::{MemoryFile, MemoryVfs};
1971
1972 let cx = Cx::new();
1973 let vfs = MemoryVfs::new();
1974 let flags = fsqlite_types::flags::VfsOpenFlags::MAIN_DB
1975 | fsqlite_types::flags::VfsOpenFlags::CREATE
1976 | fsqlite_types::flags::VfsOpenFlags::READWRITE;
1977 let (file, _) = vfs.open(&cx, None, flags).unwrap();
1978
1979 let payload = b"hello async vfs";
1980 let waker = Waker::noop();
1981 let mut task_cx = Context::from_waker(waker);
1982 {
1983 let completion = VfsWriteCompletion::new();
1984 let mut write = std::pin::pin!(<MemoryFile as VfsFile>::write_tracked(
1985 &file,
1986 &cx,
1987 payload,
1988 0,
1989 completion.clone(),
1990 ));
1991 assert!(matches!(
1992 write.as_mut().poll(&mut task_cx),
1993 Poll::Ready(Ok(()))
1994 ));
1995 assert_eq!(
1996 completion.state(),
1997 VfsWriteCompletionState::Success,
1998 "the immediate memory mutation source must complete the token before Ready"
1999 );
2000 }
2001
2002 let mut buf = [0u8; 15];
2003 {
2004 let mut read = std::pin::pin!(<MemoryFile as VfsFile>::read(&file, &cx, &mut buf, 0));
2005 assert!(matches!(
2006 read.as_mut().poll(&mut task_cx),
2007 Poll::Ready(Ok(15))
2008 ));
2009 }
2010 assert_eq!(&buf, payload);
2011
2012 let writes: &[(u64, &[u8])] = &[(0, b"real"), (8, b"batch")];
2013 {
2014 let mut batch = std::pin::pin!(<MemoryFile as VfsFile>::write_page_batch(
2015 &file, &cx, writes,
2016 ));
2017 assert!(matches!(
2018 batch.as_mut().poll(&mut task_cx),
2019 Poll::Ready(Ok(()))
2020 ));
2021 }
2022
2023 let mut batch_buf = [0_u8; 13];
2024 {
2025 let mut verify =
2026 std::pin::pin!(<MemoryFile as VfsFile>::read(&file, &cx, &mut batch_buf, 0,));
2027 assert!(matches!(
2028 verify.as_mut().poll(&mut task_cx),
2029 Poll::Ready(Ok(13))
2030 ));
2031 }
2032 assert_eq!(&batch_buf, b"realo asbatch");
2033 }
2034}