| 1 | //! A VFS over the platform's, made the process's default by `install`, that fails what a |
| 2 | //! test arms: once, the WAL's sync or commit frame or the next database open; or every WAL |
| 3 | //! write of the database `FAILING` names, for as long as it names it. |
| 4 | #![allow(dead_code)] |
| 5 | |
| 6 | use rusqlite::ffi; |
| 7 | use std::{ |
| 8 | ffi::{CStr, c_char, c_int, c_void}, |
| 9 | path::PathBuf, |
| 10 | sync::{ |
| 11 | Mutex, Once, OnceLock, |
| 12 | atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}, |
| 13 | }, |
| 14 | }; |
| 15 | |
| 16 | /// The WAL's sync at commit fails before it reaches the disk. |
| 17 | pub const SYNC_BEFORE: u8 = 1; |
| 18 | /// The WAL's sync at commit reaches the disk and still reports failure. |
| 19 | pub const SYNC_AFTER: u8 = 2; |
| 20 | /// The commit frame's header is never written. |
| 21 | pub const WRITE_COMMIT: u8 = 4; |
| 22 | /// The next open of a database fails, as the cold reopen after a failed commit. |
| 23 | pub const OPEN: u8 = 8; |
| 24 | /// After a fault, the next sync fails, as the checkpoint closing the warm connection makes, |
| 25 | /// which keeps the WAL. |
| 26 | pub const KEEP_WAL: u8 = 16; |
| 27 | |
| 28 | pub static ARMED: AtomicU8 = AtomicU8::new(0); |
| 29 | /// Between a fault and the next database open, as the replica reopens the cache cold. |
| 30 | static FIRED: AtomicBool = AtomicBool::new(false); |
| 31 | pub static WAL_WRITES_AFTER_FAULT: AtomicUsize = AtomicUsize::new(0); |
| 32 | static REAL: OnceLock<usize> = OnceLock::new(); |
| 33 | /// The platform's WAL file methods, the shim's copy of them, and the copy failing every write. |
| 34 | static WAL: OnceLock<(usize, usize, usize)> = OnceLock::new(); |
| 35 | /// While set, every write to this database's WAL fails, for WALs opened while it was set. |
| 36 | pub static FAILING: Mutex<Option<PathBuf>> = Mutex::new(None); |
| 37 | /// Held by a test for as long as it sets `FAILING`, which one test sets at a time. |
| 38 | pub static EXCLUSIVE: Mutex<()> = Mutex::new(()); |
| 39 | |
| 40 | fn fire(fault: u8) -> bool { |
| 41 | let fired = ARMED.fetch_and(!fault, Ordering::SeqCst) & fault != 0; |
| 42 | if fired { |
| 43 | FIRED.store(true, Ordering::SeqCst); |
| 44 | } |
| 45 | fired |
| 46 | } |
| 47 | |
| 48 | fn real_wal() -> &'static ffi::sqlite3_io_methods { |
| 49 | unsafe { &*(WAL.get().unwrap().0 as *const ffi::sqlite3_io_methods) } |
| 50 | } |
| 51 | |
| 52 | unsafe extern "C" fn open( |
| 53 | _: *mut ffi::sqlite3_vfs, |
| 54 | name: *const c_char, |
| 55 | file: *mut ffi::sqlite3_file, |
| 56 | flags: c_int, |
| 57 | out: *mut c_int, |
| 58 | ) -> c_int { |
| 59 | if flags & ffi::SQLITE_OPEN_MAIN_DB != 0 { |
| 60 | FIRED.store(false, Ordering::SeqCst); |
| 61 | if fire(OPEN) { |
| 62 | FIRED.store(false, Ordering::SeqCst); |
| 63 | unsafe { (*file).pMethods = std::ptr::null() }; |
| 64 | return ffi::SQLITE_CANTOPEN; |
| 65 | } |
| 66 | } |
| 67 | let real = *REAL.get().unwrap() as *mut ffi::sqlite3_vfs; |
| 68 | let opened = unsafe { (*real).xOpen.unwrap()(real, name, file, flags, out) }; |
| 69 | if opened == ffi::SQLITE_OK && flags & ffi::SQLITE_OPEN_WAL != 0 { |
| 70 | let methods = unsafe { (*file).pMethods }; |
| 71 | let &(platform, shim, failing) = WAL.get_or_init(|| { |
| 72 | let mut shim = unsafe { *methods }; |
| 73 | shim.xWrite = Some(write); |
| 74 | shim.xSync = Some(sync); |
| 75 | let mut failing = shim; |
| 76 | failing.xWrite = Some(fail); |
| 77 | let leak = |methods| Box::leak(Box::new(methods)) as *const _ as usize; |
| 78 | (methods as usize, leak(shim), leak(failing)) |
| 79 | }); |
| 80 | assert_eq!(platform, methods as usize); |
| 81 | let name = unsafe { CStr::from_ptr(name) }.to_string_lossy(); |
| 82 | let fails = FAILING |
| 83 | .lock() |
| 84 | .unwrap() |
| 85 | .as_ref() |
| 86 | .is_some_and(|database| name.starts_with(&*database.to_string_lossy())); |
| 87 | let methods = if fails { failing } else { shim }; |
| 88 | unsafe { (*file).pMethods = methods as *const ffi::sqlite3_io_methods }; |
| 89 | } |
| 90 | opened |
| 91 | } |
| 92 | |
| 93 | unsafe extern "C" fn write( |
| 94 | file: *mut ffi::sqlite3_file, |
| 95 | data: *const c_void, |
| 96 | amount: c_int, |
| 97 | offset: ffi::sqlite3_int64, |
| 98 | ) -> c_int { |
| 99 | // A frame header names the database size only in the frame that commits. |
| 100 | if amount == 24 |
| 101 | && unsafe { std::slice::from_raw_parts(data.cast::<u8>().add(4), 4) } != [0; 4] |
| 102 | && fire(WRITE_COMMIT) |
| 103 | { |
| 104 | return ffi::SQLITE_IOERR_WRITE; |
| 105 | } |
| 106 | if FIRED.load(Ordering::SeqCst) { |
| 107 | WAL_WRITES_AFTER_FAULT.fetch_add(1, Ordering::SeqCst); |
| 108 | } |
| 109 | unsafe { real_wal().xWrite.unwrap()(file, data, amount, offset) } |
| 110 | } |
| 111 | |
| 112 | unsafe extern "C" fn fail( |
| 113 | file: *mut ffi::sqlite3_file, |
| 114 | data: *const c_void, |
| 115 | amount: c_int, |
| 116 | offset: ffi::sqlite3_int64, |
| 117 | ) -> c_int { |
| 118 | if FAILING.lock().unwrap().is_some() { |
| 119 | return ffi::SQLITE_IOERR_WRITE; |
| 120 | } |
| 121 | unsafe { write(file, data, amount, offset) } |
| 122 | } |
| 123 | |
| 124 | unsafe extern "C" fn sync(file: *mut ffi::sqlite3_file, flags: c_int) -> c_int { |
| 125 | if fire(SYNC_BEFORE) || FIRED.load(Ordering::SeqCst) && fire(KEEP_WAL) { |
| 126 | return ffi::SQLITE_IOERR_FSYNC; |
| 127 | } |
| 128 | let synced = unsafe { real_wal().xSync.unwrap()(file, flags) }; |
| 129 | if synced == ffi::SQLITE_OK && fire(SYNC_AFTER) { |
| 130 | return ffi::SQLITE_IOERR_FSYNC; |
| 131 | } |
| 132 | synced |
| 133 | } |
| 134 | |
| 135 | pub fn install() { |
| 136 | static INSTALL: Once = Once::new(); |
| 137 | INSTALL.call_once(|| unsafe { |
| 138 | let real = ffi::sqlite3_vfs_find(std::ptr::null()); |
| 139 | REAL.set(real as usize).unwrap(); |
| 140 | let mut shim = *real; |
| 141 | shim.zName = c"commit-fault".as_ptr(); |
| 142 | shim.pNext = std::ptr::null_mut(); |
| 143 | shim.xOpen = Some(open); |
| 144 | assert_eq!( |
| 145 | ffi::sqlite3_vfs_register(Box::leak(Box::new(shim)), 1), |
| 146 | ffi::SQLITE_OK |
| 147 | ); |
| 148 | }); |
| 149 | } |