├── .dockerignore ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── src ├── ffi.rs └── lib.rs └── test-vfs ├── Cargo.toml ├── Dockerfile ├── action.yml ├── docker └── test-vfs │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── lib.rs ├── entrypoint.sh ├── patch.sh ├── patch ├── Makefile.in.patch ├── test │ └── dbstatus.test.patch └── test_ext.c ├── src ├── file_lock.rs ├── lib.rs ├── lock.rs ├── range_lock.rs └── vfs.rs └── tests └── lock_test.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main, tests-uds] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | ci: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | 20 | - name: Install stable toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | profile: minimal 24 | toolchain: stable 25 | override: true 26 | components: rustfmt, clippy 27 | 28 | - name: Build 29 | run: cargo build 30 | 31 | - name: Run unit tests 32 | run: cargo test --workspace 33 | 34 | - name: Run clippy 35 | run: cargo clippy -- -D warnings 36 | 37 | - name: Check formatting 38 | run: cargo fmt -- --check 39 | 40 | - name: Run SQLite tests 41 | uses: ./test-vfs 42 | with: 43 | args: test/full.test 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.github 4 | !.dockerignore 5 | target 6 | wasm-pack.log 7 | build/ 8 | /db 9 | /test-vfs/sqlite 10 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "cfg-if" 27 | version = "1.0.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 30 | 31 | [[package]] 32 | name = "env_logger" 33 | version = "0.7.1" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 36 | dependencies = [ 37 | "atty", 38 | "humantime", 39 | "log", 40 | "regex", 41 | "termcolor", 42 | ] 43 | 44 | [[package]] 45 | name = "getrandom" 46 | version = "0.2.7" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 49 | dependencies = [ 50 | "cfg-if", 51 | "libc", 52 | "wasi", 53 | ] 54 | 55 | [[package]] 56 | name = "hermit-abi" 57 | version = "0.1.19" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 60 | dependencies = [ 61 | "libc", 62 | ] 63 | 64 | [[package]] 65 | name = "humantime" 66 | version = "1.3.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 69 | dependencies = [ 70 | "quick-error", 71 | ] 72 | 73 | [[package]] 74 | name = "libc" 75 | version = "0.2.126" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 78 | 79 | [[package]] 80 | name = "log" 81 | version = "0.4.14" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 84 | dependencies = [ 85 | "cfg-if", 86 | ] 87 | 88 | [[package]] 89 | name = "memchr" 90 | version = "2.4.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 93 | 94 | [[package]] 95 | name = "num_threads" 96 | version = "0.1.2" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "71a1eb3a36534514077c1e079ada2fb170ef30c47d203aa6916138cf882ecd52" 99 | dependencies = [ 100 | "libc", 101 | ] 102 | 103 | [[package]] 104 | name = "ppv-lite86" 105 | version = "0.2.16" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 108 | 109 | [[package]] 110 | name = "pretty_env_logger" 111 | version = "0.4.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" 114 | dependencies = [ 115 | "env_logger", 116 | "log", 117 | ] 118 | 119 | [[package]] 120 | name = "quick-error" 121 | version = "1.2.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 124 | 125 | [[package]] 126 | name = "rand" 127 | version = "0.8.5" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 130 | dependencies = [ 131 | "libc", 132 | "rand_chacha", 133 | "rand_core", 134 | ] 135 | 136 | [[package]] 137 | name = "rand_chacha" 138 | version = "0.3.1" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 141 | dependencies = [ 142 | "ppv-lite86", 143 | "rand_core", 144 | ] 145 | 146 | [[package]] 147 | name = "rand_core" 148 | version = "0.6.3" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 151 | dependencies = [ 152 | "getrandom", 153 | ] 154 | 155 | [[package]] 156 | name = "regex" 157 | version = "1.5.5" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" 160 | dependencies = [ 161 | "aho-corasick", 162 | "memchr", 163 | "regex-syntax", 164 | ] 165 | 166 | [[package]] 167 | name = "regex-syntax" 168 | version = "0.6.25" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 171 | 172 | [[package]] 173 | name = "sqlite-vfs" 174 | version = "0.2.0" 175 | dependencies = [ 176 | "log", 177 | "time", 178 | ] 179 | 180 | [[package]] 181 | name = "termcolor" 182 | version = "1.1.3" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 185 | dependencies = [ 186 | "winapi-util", 187 | ] 188 | 189 | [[package]] 190 | name = "test-vfs" 191 | version = "0.1.0" 192 | dependencies = [ 193 | "libc", 194 | "log", 195 | "pretty_env_logger", 196 | "rand", 197 | "sqlite-vfs", 198 | ] 199 | 200 | [[package]] 201 | name = "time" 202 | version = "0.3.6" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "c8d54b9298e05179c335de2b9645d061255bcd5155f843b3e328d2cfe0a5b413" 205 | dependencies = [ 206 | "libc", 207 | "num_threads", 208 | ] 209 | 210 | [[package]] 211 | name = "wasi" 212 | version = "0.11.0+wasi-snapshot-preview1" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 215 | 216 | [[package]] 217 | name = "winapi" 218 | version = "0.3.9" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 221 | dependencies = [ 222 | "winapi-i686-pc-windows-gnu", 223 | "winapi-x86_64-pc-windows-gnu", 224 | ] 225 | 226 | [[package]] 227 | name = "winapi-i686-pc-windows-gnu" 228 | version = "0.4.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 231 | 232 | [[package]] 233 | name = "winapi-util" 234 | version = "0.1.5" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 237 | dependencies = [ 238 | "winapi", 239 | ] 240 | 241 | [[package]] 242 | name = "winapi-x86_64-pc-windows-gnu" 243 | version = "0.4.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 246 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlite-vfs" 3 | version = "0.2.0" 4 | authors = ["Markus Ast "] 5 | license = "MIT OR Apache-2.0" 6 | edition = "2021" 7 | description = "Build SQLite virtual file systems (VFS) by implementing a simple Rust trait." 8 | repository = "https://github.com/rkusa/sqlite-vfs" 9 | documentation = "https://docs.rs/sqlite-vfs" 10 | keywords = ["sqlite", "vfs"] 11 | 12 | [workspace] 13 | members = ["test-vfs"] 14 | 15 | [dependencies] 16 | log = "0.4" 17 | time = "0.3" 18 | 19 | [features] 20 | default = [] 21 | 22 | # Only enable when building with the included `./test-vfs`. 23 | sqlite_test = [] 24 | 25 | # Enable an delegate to parent VFS: `xSetSystemCall`, `xGetSystemCall` and `xNextSystemCall` 26 | syscall = [] 27 | 28 | # Enable an delegate to parent VFS: `xDlOpen`, `xDlError`, `xDlSym` and `xDlClose` 29 | loadext = [] 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT OR Apache-2.0 -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PLATFORM=linux/arm64 2 | 3 | test: test-vfs/.dockerbuild 4 | mkdir -p $(shell pwd)/target/x86_64-unknown-linux-gnu 5 | docker run --rm --platform $(PLATFORM) \ 6 | --mount type=bind,src=$(shell pwd),dst=/github/workspace,readonly \ 7 | --mount type=bind,src=${HOME}/.cargo/git,dst=/usr/local/cargo/git,readonly \ 8 | --mount type=bind,src=${HOME}/.cargo/registry,dst=/usr/local/cargo/registry,readonly \ 9 | --mount type=bind,src=$(shell pwd)/target/x86_64-unknown-linux-gnu,dst=/github/workspace/target \ 10 | --mount type=tmpfs,destination=/home/sqlite/build/testdir \ 11 | -e RUST_LOG=${RUST_LOG} \ 12 | -t sqlite-vfs-test \ 13 | test/full.test 14 | 15 | test-vfs/.dockerbuild: test-vfs/Dockerfile test-vfs/entrypoint.sh test-vfs/docker/test-vfs/Cargo.toml test-vfs/patch.sh test-vfs/patch/* test-vfs/patch/test/* test-vfs/docker/test-vfs/src/*.rs 16 | docker build --platform $(PLATFORM) \ 17 | -f test-vfs/Dockerfile \ 18 | --progress=plain \ 19 | -t sqlite-vfs-test test-vfs 20 | touch test-vfs/.dockerbuild 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `sqlite-vfs` 2 | 3 | Build SQLite virtual file systems (VFS) by implementing a simple Rust trait. 4 | 5 | [Documentation](https://docs.rs/sqlite-vfs) | [Example](https://github.com/rkusa/wasm-sqlite/blob/main/wasm/src/vfs.rs) 6 | 7 | This library is build for my own use-case. It doesn't expose everything a SQLite VFS provides (e.g. memory mapped files). Feel free to propose additions if the current state doesn't work for your use-case. 8 | 9 | ## Status 10 | 11 | This library is still in _prototype_ state and not ready to be used (except for maybe prototypes). While progress will be slow, it is actively worked on. 12 | 13 | - ✅ It passes most of SQLite's TCL test harness. 14 | - ⚠️ CI only runs `full.test` and not `all.test`. 15 | - ⚠️ [Some tests](./test-vfs/patch.sh) are skipped. 16 | - ✅ Successfully runs experiments like [`do-sqlite`](https://github.com/rkusa/do-sqlite). 17 | - ⚠️ It uses `unsafe` Rust, which hasn't been peer-reviewed yet. 18 | - ⚠️ It is not used in any production-capacity yet. 19 | 20 | ## Limitations 21 | 22 | - WAL is not supported (but in progress) 23 | - Memory mapping not supported (`xFetch`/`xUnfetch`) 24 | - Loading extensions not supported (`xDl*`) 25 | - Tests run only on UNIX right now (due to `std::os::unix` usage in tests) 26 | - Directory sync is not supported 27 | - Sector size is always 1024 28 | - Custom device characteristic are not supported (`xDeviceCharacteristics`) 29 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case, non_camel_case_types, unused)] 2 | 3 | #[cfg(feature = "sqlite_test")] 4 | extern "C" { 5 | pub fn sqlite3_inc_sync_count(); 6 | pub fn sqlite3_inc_fullsync_count(); 7 | pub fn sqlite3_set_current_time(current_time: i32); 8 | pub fn sqlite3_get_current_time() -> i32; 9 | pub fn sqlite3_dec_diskfull_pending(); 10 | pub fn sqlite3_get_diskfull_pending() -> i32; 11 | pub fn sqlite3_set_diskfull(); 12 | pub fn sqlite3_inc_open_file_count(); 13 | pub fn sqlite3_dec_open_file_count(); 14 | pub fn sqlite3_dec_io_error_pending() -> i32; 15 | pub fn sqlite3_get_io_error_persist() -> i32; 16 | pub fn sqlite3_get_io_error_hit() -> i32; 17 | pub fn sqlite3_inc_io_error_hit(); 18 | pub fn sqlite3_set_io_error_hit(hit: i32); 19 | pub fn sqlite3_get_io_error_benign() -> i32; 20 | // pub fn sqlite3_set_io_error_benign(benign: i32); 21 | pub fn sqlite3_inc_io_error_hardhit(); 22 | } 23 | 24 | // Excerpt of 25 | // github.com/rusqlite/rusqlite/blob/master/libsqlite3-sys/sqlite3/bindgen_bundled_version.rs 26 | 27 | pub const SQLITE_OK: i32 = 0; 28 | pub const SQLITE_ERROR: i32 = 1; 29 | pub const SQLITE_INTERNAL: i32 = 2; 30 | pub const SQLITE_PERM: i32 = 3; 31 | pub const SQLITE_ABORT: i32 = 4; 32 | pub const SQLITE_BUSY: i32 = 5; 33 | pub const SQLITE_LOCKED: i32 = 6; 34 | pub const SQLITE_NOMEM: i32 = 7; 35 | pub const SQLITE_READONLY: i32 = 8; 36 | pub const SQLITE_INTERRUPT: i32 = 9; 37 | pub const SQLITE_IOERR: i32 = 10; 38 | pub const SQLITE_CORRUPT: i32 = 11; 39 | pub const SQLITE_NOTFOUND: i32 = 12; 40 | pub const SQLITE_FULL: i32 = 13; 41 | pub const SQLITE_CANTOPEN: i32 = 14; 42 | pub const SQLITE_PROTOCOL: i32 = 15; 43 | pub const SQLITE_EMPTY: i32 = 16; 44 | pub const SQLITE_SCHEMA: i32 = 17; 45 | pub const SQLITE_TOOBIG: i32 = 18; 46 | pub const SQLITE_CONSTRAINT: i32 = 19; 47 | pub const SQLITE_MISMATCH: i32 = 20; 48 | pub const SQLITE_MISUSE: i32 = 21; 49 | pub const SQLITE_NOLFS: i32 = 22; 50 | pub const SQLITE_AUTH: i32 = 23; 51 | pub const SQLITE_FORMAT: i32 = 24; 52 | pub const SQLITE_RANGE: i32 = 25; 53 | pub const SQLITE_NOTADB: i32 = 26; 54 | pub const SQLITE_NOTICE: i32 = 27; 55 | pub const SQLITE_WARNING: i32 = 28; 56 | pub const SQLITE_ROW: i32 = 100; 57 | pub const SQLITE_DONE: i32 = 101; 58 | pub const SQLITE_ERROR_MISSING_COLLSEQ: i32 = 257; 59 | pub const SQLITE_ERROR_RETRY: i32 = 513; 60 | pub const SQLITE_ERROR_SNAPSHOT: i32 = 769; 61 | pub const SQLITE_IOERR_READ: i32 = 266; 62 | pub const SQLITE_IOERR_SHORT_READ: i32 = 522; 63 | pub const SQLITE_IOERR_WRITE: i32 = 778; 64 | pub const SQLITE_IOERR_FSYNC: i32 = 1034; 65 | pub const SQLITE_IOERR_DIR_FSYNC: i32 = 1290; 66 | pub const SQLITE_IOERR_TRUNCATE: i32 = 1546; 67 | pub const SQLITE_IOERR_FSTAT: i32 = 1802; 68 | pub const SQLITE_IOERR_UNLOCK: i32 = 2058; 69 | pub const SQLITE_IOERR_RDLOCK: i32 = 2314; 70 | pub const SQLITE_IOERR_DELETE: i32 = 2570; 71 | pub const SQLITE_IOERR_BLOCKED: i32 = 2826; 72 | pub const SQLITE_IOERR_NOMEM: i32 = 3082; 73 | pub const SQLITE_IOERR_ACCESS: i32 = 3338; 74 | pub const SQLITE_IOERR_CHECKRESERVEDLOCK: i32 = 3594; 75 | pub const SQLITE_IOERR_LOCK: i32 = 3850; 76 | pub const SQLITE_IOERR_CLOSE: i32 = 4106; 77 | pub const SQLITE_IOERR_DIR_CLOSE: i32 = 4362; 78 | pub const SQLITE_IOERR_SHMOPEN: i32 = 4618; 79 | pub const SQLITE_IOERR_SHMSIZE: i32 = 4874; 80 | pub const SQLITE_IOERR_SHMLOCK: i32 = 5130; 81 | pub const SQLITE_IOERR_SHMMAP: i32 = 5386; 82 | pub const SQLITE_IOERR_SEEK: i32 = 5642; 83 | pub const SQLITE_IOERR_DELETE_NOENT: i32 = 5898; 84 | pub const SQLITE_IOERR_MMAP: i32 = 6154; 85 | pub const SQLITE_IOERR_GETTEMPPATH: i32 = 6410; 86 | pub const SQLITE_IOERR_CONVPATH: i32 = 6666; 87 | pub const SQLITE_IOERR_VNODE: i32 = 6922; 88 | pub const SQLITE_IOERR_AUTH: i32 = 7178; 89 | pub const SQLITE_IOERR_BEGIN_ATOMIC: i32 = 7434; 90 | pub const SQLITE_IOERR_COMMIT_ATOMIC: i32 = 7690; 91 | pub const SQLITE_IOERR_ROLLBACK_ATOMIC: i32 = 7946; 92 | pub const SQLITE_IOERR_DATA: i32 = 8202; 93 | pub const SQLITE_IOERR_CORRUPTFS: i32 = 8458; 94 | pub const SQLITE_LOCKED_SHAREDCACHE: i32 = 262; 95 | pub const SQLITE_LOCKED_VTAB: i32 = 518; 96 | pub const SQLITE_BUSY_RECOVERY: i32 = 261; 97 | pub const SQLITE_BUSY_SNAPSHOT: i32 = 517; 98 | pub const SQLITE_BUSY_TIMEOUT: i32 = 773; 99 | pub const SQLITE_CANTOPEN_NOTEMPDIR: i32 = 270; 100 | pub const SQLITE_CANTOPEN_ISDIR: i32 = 526; 101 | pub const SQLITE_CANTOPEN_FULLPATH: i32 = 782; 102 | pub const SQLITE_CANTOPEN_CONVPATH: i32 = 1038; 103 | pub const SQLITE_CANTOPEN_DIRTYWAL: i32 = 1294; 104 | pub const SQLITE_CANTOPEN_SYMLINK: i32 = 1550; 105 | pub const SQLITE_CORRUPT_VTAB: i32 = 267; 106 | pub const SQLITE_CORRUPT_SEQUENCE: i32 = 523; 107 | pub const SQLITE_CORRUPT_INDEX: i32 = 779; 108 | pub const SQLITE_READONLY_RECOVERY: i32 = 264; 109 | pub const SQLITE_READONLY_CANTLOCK: i32 = 520; 110 | pub const SQLITE_READONLY_ROLLBACK: i32 = 776; 111 | pub const SQLITE_READONLY_DBMOVED: i32 = 1032; 112 | pub const SQLITE_READONLY_CANTINIT: i32 = 1288; 113 | pub const SQLITE_READONLY_DIRECTORY: i32 = 1544; 114 | pub const SQLITE_ABORT_ROLLBACK: i32 = 516; 115 | pub const SQLITE_CONSTRAINT_CHECK: i32 = 275; 116 | pub const SQLITE_CONSTRAINT_COMMITHOOK: i32 = 531; 117 | pub const SQLITE_CONSTRAINT_FOREIGNKEY: i32 = 787; 118 | pub const SQLITE_CONSTRAINT_FUNCTION: i32 = 1043; 119 | pub const SQLITE_CONSTRAINT_NOTNULL: i32 = 1299; 120 | pub const SQLITE_CONSTRAINT_PRIMARYKEY: i32 = 1555; 121 | pub const SQLITE_CONSTRAINT_TRIGGER: i32 = 1811; 122 | pub const SQLITE_CONSTRAINT_UNIQUE: i32 = 2067; 123 | pub const SQLITE_CONSTRAINT_VTAB: i32 = 2323; 124 | pub const SQLITE_CONSTRAINT_ROWID: i32 = 2579; 125 | pub const SQLITE_CONSTRAINT_PINNED: i32 = 2835; 126 | pub const SQLITE_NOTICE_RECOVER_WAL: i32 = 283; 127 | pub const SQLITE_NOTICE_RECOVER_ROLLBACK: i32 = 539; 128 | pub const SQLITE_WARNING_AUTOINDEX: i32 = 284; 129 | pub const SQLITE_AUTH_USER: i32 = 279; 130 | pub const SQLITE_OK_LOAD_PERMANENTLY: i32 = 256; 131 | pub const SQLITE_OK_SYMLINK: i32 = 512; 132 | pub const SQLITE_OPEN_READONLY: i32 = 1; 133 | pub const SQLITE_OPEN_READWRITE: i32 = 2; 134 | pub const SQLITE_OPEN_CREATE: i32 = 4; 135 | pub const SQLITE_OPEN_DELETEONCLOSE: i32 = 8; 136 | pub const SQLITE_OPEN_EXCLUSIVE: i32 = 16; 137 | pub const SQLITE_OPEN_AUTOPROXY: i32 = 32; 138 | pub const SQLITE_OPEN_URI: i32 = 64; 139 | pub const SQLITE_OPEN_MEMORY: i32 = 128; 140 | pub const SQLITE_OPEN_MAIN_DB: i32 = 256; 141 | pub const SQLITE_OPEN_TEMP_DB: i32 = 512; 142 | pub const SQLITE_OPEN_TRANSIENT_DB: i32 = 1024; 143 | pub const SQLITE_OPEN_MAIN_JOURNAL: i32 = 2048; 144 | pub const SQLITE_OPEN_TEMP_JOURNAL: i32 = 4096; 145 | pub const SQLITE_OPEN_SUBJOURNAL: i32 = 8192; 146 | pub const SQLITE_OPEN_SUPER_JOURNAL: i32 = 16384; 147 | pub const SQLITE_OPEN_NOMUTEX: i32 = 32768; 148 | pub const SQLITE_OPEN_FULLMUTEX: i32 = 65536; 149 | pub const SQLITE_OPEN_SHAREDCACHE: i32 = 131072; 150 | pub const SQLITE_OPEN_PRIVATECACHE: i32 = 262144; 151 | pub const SQLITE_OPEN_WAL: i32 = 524288; 152 | pub const SQLITE_OPEN_NOFOLLOW: i32 = 16777216; 153 | pub const SQLITE_OPEN_MASTER_JOURNAL: i32 = 16384; 154 | pub const SQLITE_IOCAP_ATOMIC: i32 = 1; 155 | pub const SQLITE_IOCAP_ATOMIC512: i32 = 2; 156 | pub const SQLITE_IOCAP_ATOMIC1K: i32 = 4; 157 | pub const SQLITE_IOCAP_ATOMIC2K: i32 = 8; 158 | pub const SQLITE_IOCAP_ATOMIC4K: i32 = 16; 159 | pub const SQLITE_IOCAP_ATOMIC8K: i32 = 32; 160 | pub const SQLITE_IOCAP_ATOMIC16K: i32 = 64; 161 | pub const SQLITE_IOCAP_ATOMIC32K: i32 = 128; 162 | pub const SQLITE_IOCAP_ATOMIC64K: i32 = 256; 163 | pub const SQLITE_IOCAP_SAFE_APPEND: i32 = 512; 164 | pub const SQLITE_IOCAP_SEQUENTIAL: i32 = 1024; 165 | pub const SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN: i32 = 2048; 166 | pub const SQLITE_IOCAP_POWERSAFE_OVERWRITE: i32 = 4096; 167 | pub const SQLITE_IOCAP_IMMUTABLE: i32 = 8192; 168 | pub const SQLITE_IOCAP_BATCH_ATOMIC: i32 = 16384; 169 | pub const SQLITE_LOCK_NONE: i32 = 0; 170 | pub const SQLITE_LOCK_SHARED: i32 = 1; 171 | pub const SQLITE_LOCK_RESERVED: i32 = 2; 172 | pub const SQLITE_LOCK_PENDING: i32 = 3; 173 | pub const SQLITE_LOCK_EXCLUSIVE: i32 = 4; 174 | pub const SQLITE_SYNC_NORMAL: i32 = 2; 175 | pub const SQLITE_SYNC_FULL: i32 = 3; 176 | pub const SQLITE_SYNC_DATAONLY: i32 = 16; 177 | pub const SQLITE_FCNTL_LOCKSTATE: i32 = 1; 178 | pub const SQLITE_FCNTL_GET_LOCKPROXYFILE: i32 = 2; 179 | pub const SQLITE_FCNTL_SET_LOCKPROXYFILE: i32 = 3; 180 | pub const SQLITE_FCNTL_LAST_ERRNO: i32 = 4; 181 | pub const SQLITE_FCNTL_SIZE_HINT: i32 = 5; 182 | pub const SQLITE_FCNTL_CHUNK_SIZE: i32 = 6; 183 | pub const SQLITE_FCNTL_FILE_POINTER: i32 = 7; 184 | pub const SQLITE_FCNTL_SYNC_OMITTED: i32 = 8; 185 | pub const SQLITE_FCNTL_WIN32_AV_RETRY: i32 = 9; 186 | pub const SQLITE_FCNTL_PERSIST_WAL: i32 = 10; 187 | pub const SQLITE_FCNTL_OVERWRITE: i32 = 11; 188 | pub const SQLITE_FCNTL_VFSNAME: i32 = 12; 189 | pub const SQLITE_FCNTL_POWERSAFE_OVERWRITE: i32 = 13; 190 | pub const SQLITE_FCNTL_PRAGMA: i32 = 14; 191 | pub const SQLITE_FCNTL_BUSYHANDLER: i32 = 15; 192 | pub const SQLITE_FCNTL_TEMPFILENAME: i32 = 16; 193 | pub const SQLITE_FCNTL_MMAP_SIZE: i32 = 18; 194 | pub const SQLITE_FCNTL_TRACE: i32 = 19; 195 | pub const SQLITE_FCNTL_HAS_MOVED: i32 = 20; 196 | pub const SQLITE_FCNTL_SYNC: i32 = 21; 197 | pub const SQLITE_FCNTL_COMMIT_PHASETWO: i32 = 22; 198 | pub const SQLITE_FCNTL_WIN32_SET_HANDLE: i32 = 23; 199 | pub const SQLITE_FCNTL_WAL_BLOCK: i32 = 24; 200 | pub const SQLITE_FCNTL_ZIPVFS: i32 = 25; 201 | pub const SQLITE_FCNTL_RBU: i32 = 26; 202 | pub const SQLITE_FCNTL_VFS_POINTER: i32 = 27; 203 | pub const SQLITE_FCNTL_JOURNAL_POINTER: i32 = 28; 204 | pub const SQLITE_FCNTL_WIN32_GET_HANDLE: i32 = 29; 205 | pub const SQLITE_FCNTL_PDB: i32 = 30; 206 | pub const SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: i32 = 31; 207 | pub const SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: i32 = 32; 208 | pub const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: i32 = 33; 209 | pub const SQLITE_FCNTL_LOCK_TIMEOUT: i32 = 34; 210 | pub const SQLITE_FCNTL_DATA_VERSION: i32 = 35; 211 | pub const SQLITE_FCNTL_SIZE_LIMIT: i32 = 36; 212 | pub const SQLITE_FCNTL_CKPT_DONE: i32 = 37; 213 | pub const SQLITE_FCNTL_RESERVE_BYTES: i32 = 38; 214 | pub const SQLITE_FCNTL_CKPT_START: i32 = 39; 215 | pub const SQLITE_FCNTL_EXTERNAL_READER: i32 = 40; 216 | pub const SQLITE_FCNTL_CKSM_FILE: i32 = 41; 217 | pub const SQLITE_GET_LOCKPROXYFILE: i32 = 2; 218 | pub const SQLITE_SET_LOCKPROXYFILE: i32 = 3; 219 | pub const SQLITE_LAST_ERRNO: i32 = 4; 220 | pub const SQLITE_ACCESS_EXISTS: i32 = 0; 221 | pub const SQLITE_ACCESS_READWRITE: i32 = 1; 222 | pub const SQLITE_ACCESS_READ: i32 = 2; 223 | pub const SQLITE_SHM_UNLOCK: i32 = 1; 224 | pub const SQLITE_SHM_LOCK: i32 = 2; 225 | pub const SQLITE_SHM_SHARED: i32 = 4; 226 | pub const SQLITE_SHM_EXCLUSIVE: i32 = 8; 227 | pub const SQLITE_SHM_NLOCK: i32 = 8; 228 | pub const SQLITE_CONFIG_SINGLETHREAD: i32 = 1; 229 | pub const SQLITE_CONFIG_MULTITHREAD: i32 = 2; 230 | pub const SQLITE_CONFIG_SERIALIZED: i32 = 3; 231 | pub const SQLITE_CONFIG_MALLOC: i32 = 4; 232 | pub const SQLITE_CONFIG_GETMALLOC: i32 = 5; 233 | pub const SQLITE_CONFIG_SCRATCH: i32 = 6; 234 | pub const SQLITE_CONFIG_PAGECACHE: i32 = 7; 235 | pub const SQLITE_CONFIG_HEAP: i32 = 8; 236 | pub const SQLITE_CONFIG_MEMSTATUS: i32 = 9; 237 | pub const SQLITE_CONFIG_MUTEX: i32 = 10; 238 | pub const SQLITE_CONFIG_GETMUTEX: i32 = 11; 239 | pub const SQLITE_CONFIG_LOOKASIDE: i32 = 13; 240 | pub const SQLITE_CONFIG_PCACHE: i32 = 14; 241 | pub const SQLITE_CONFIG_GETPCACHE: i32 = 15; 242 | pub const SQLITE_CONFIG_LOG: i32 = 16; 243 | pub const SQLITE_CONFIG_URI: i32 = 17; 244 | pub const SQLITE_CONFIG_PCACHE2: i32 = 18; 245 | pub const SQLITE_CONFIG_GETPCACHE2: i32 = 19; 246 | pub const SQLITE_CONFIG_COVERING_INDEX_SCAN: i32 = 20; 247 | pub const SQLITE_CONFIG_SQLLOG: i32 = 21; 248 | pub const SQLITE_CONFIG_MMAP_SIZE: i32 = 22; 249 | pub const SQLITE_CONFIG_WIN32_HEAPSIZE: i32 = 23; 250 | pub const SQLITE_CONFIG_PCACHE_HDRSZ: i32 = 24; 251 | pub const SQLITE_CONFIG_PMASZ: i32 = 25; 252 | pub const SQLITE_CONFIG_STMTJRNL_SPILL: i32 = 26; 253 | pub const SQLITE_CONFIG_SMALL_MALLOC: i32 = 27; 254 | pub const SQLITE_CONFIG_SORTERREF_SIZE: i32 = 28; 255 | pub const SQLITE_CONFIG_MEMDB_MAXSIZE: i32 = 29; 256 | pub const SQLITE_DBCONFIG_MAINDBNAME: i32 = 1000; 257 | pub const SQLITE_DBCONFIG_LOOKASIDE: i32 = 1001; 258 | pub const SQLITE_DBCONFIG_ENABLE_FKEY: i32 = 1002; 259 | pub const SQLITE_DBCONFIG_ENABLE_TRIGGER: i32 = 1003; 260 | pub const SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: i32 = 1004; 261 | pub const SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: i32 = 1005; 262 | pub const SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: i32 = 1006; 263 | pub const SQLITE_DBCONFIG_ENABLE_QPSG: i32 = 1007; 264 | pub const SQLITE_DBCONFIG_TRIGGER_EQP: i32 = 1008; 265 | pub const SQLITE_DBCONFIG_RESET_DATABASE: i32 = 1009; 266 | pub const SQLITE_DBCONFIG_DEFENSIVE: i32 = 1010; 267 | pub const SQLITE_DBCONFIG_WRITABLE_SCHEMA: i32 = 1011; 268 | pub const SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: i32 = 1012; 269 | pub const SQLITE_DBCONFIG_DQS_DML: i32 = 1013; 270 | pub const SQLITE_DBCONFIG_DQS_DDL: i32 = 1014; 271 | pub const SQLITE_DBCONFIG_ENABLE_VIEW: i32 = 1015; 272 | pub const SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: i32 = 1016; 273 | pub const SQLITE_DBCONFIG_TRUSTED_SCHEMA: i32 = 1017; 274 | pub const SQLITE_DBCONFIG_MAX: i32 = 1017; 275 | pub const SQLITE_DENY: i32 = 1; 276 | pub const SQLITE_IGNORE: i32 = 2; 277 | pub const SQLITE_CREATE_INDEX: i32 = 1; 278 | pub const SQLITE_CREATE_TABLE: i32 = 2; 279 | pub const SQLITE_CREATE_TEMP_INDEX: i32 = 3; 280 | pub const SQLITE_CREATE_TEMP_TABLE: i32 = 4; 281 | pub const SQLITE_CREATE_TEMP_TRIGGER: i32 = 5; 282 | pub const SQLITE_CREATE_TEMP_VIEW: i32 = 6; 283 | pub const SQLITE_CREATE_TRIGGER: i32 = 7; 284 | pub const SQLITE_CREATE_VIEW: i32 = 8; 285 | pub const SQLITE_DELETE: i32 = 9; 286 | pub const SQLITE_DROP_INDEX: i32 = 10; 287 | pub const SQLITE_DROP_TABLE: i32 = 11; 288 | pub const SQLITE_DROP_TEMP_INDEX: i32 = 12; 289 | pub const SQLITE_DROP_TEMP_TABLE: i32 = 13; 290 | pub const SQLITE_DROP_TEMP_TRIGGER: i32 = 14; 291 | pub const SQLITE_DROP_TEMP_VIEW: i32 = 15; 292 | pub const SQLITE_DROP_TRIGGER: i32 = 16; 293 | pub const SQLITE_DROP_VIEW: i32 = 17; 294 | pub const SQLITE_INSERT: i32 = 18; 295 | pub const SQLITE_PRAGMA: i32 = 19; 296 | pub const SQLITE_READ: i32 = 20; 297 | pub const SQLITE_SELECT: i32 = 21; 298 | pub const SQLITE_TRANSACTION: i32 = 22; 299 | pub const SQLITE_UPDATE: i32 = 23; 300 | pub const SQLITE_ATTACH: i32 = 24; 301 | pub const SQLITE_DETACH: i32 = 25; 302 | pub const SQLITE_ALTER_TABLE: i32 = 26; 303 | pub const SQLITE_REINDEX: i32 = 27; 304 | pub const SQLITE_ANALYZE: i32 = 28; 305 | pub const SQLITE_CREATE_VTABLE: i32 = 29; 306 | pub const SQLITE_DROP_VTABLE: i32 = 30; 307 | pub const SQLITE_FUNCTION: i32 = 31; 308 | pub const SQLITE_SAVEPOINT: i32 = 32; 309 | pub const SQLITE_COPY: i32 = 0; 310 | pub const SQLITE_RECURSIVE: i32 = 33; 311 | pub const SQLITE_TRACE_STMT: i32 = 1; 312 | pub const SQLITE_TRACE_PROFILE: i32 = 2; 313 | pub const SQLITE_TRACE_ROW: i32 = 4; 314 | pub const SQLITE_TRACE_CLOSE: i32 = 8; 315 | pub const SQLITE_LIMIT_LENGTH: i32 = 0; 316 | pub const SQLITE_LIMIT_SQL_LENGTH: i32 = 1; 317 | pub const SQLITE_LIMIT_COLUMN: i32 = 2; 318 | pub const SQLITE_LIMIT_EXPR_DEPTH: i32 = 3; 319 | pub const SQLITE_LIMIT_COMPOUND_SELECT: i32 = 4; 320 | pub const SQLITE_LIMIT_VDBE_OP: i32 = 5; 321 | pub const SQLITE_LIMIT_FUNCTION_ARG: i32 = 6; 322 | pub const SQLITE_LIMIT_ATTACHED: i32 = 7; 323 | pub const SQLITE_LIMIT_LIKE_PATTERN_LENGTH: i32 = 8; 324 | pub const SQLITE_LIMIT_VARIABLE_NUMBER: i32 = 9; 325 | pub const SQLITE_LIMIT_TRIGGER_DEPTH: i32 = 10; 326 | pub const SQLITE_LIMIT_WORKER_THREADS: i32 = 11; 327 | pub const SQLITE_PREPARE_PERSISTENT: i32 = 1; 328 | pub const SQLITE_PREPARE_NORMALIZE: i32 = 2; 329 | pub const SQLITE_PREPARE_NO_VTAB: i32 = 4; 330 | pub const SQLITE_INTEGER: i32 = 1; 331 | pub const SQLITE_FLOAT: i32 = 2; 332 | pub const SQLITE_BLOB: i32 = 4; 333 | pub const SQLITE_NULL: i32 = 5; 334 | pub const SQLITE_TEXT: i32 = 3; 335 | pub const SQLITE3_TEXT: i32 = 3; 336 | pub const SQLITE_UTF8: i32 = 1; 337 | pub const SQLITE_UTF16LE: i32 = 2; 338 | pub const SQLITE_UTF16BE: i32 = 3; 339 | pub const SQLITE_UTF16: i32 = 4; 340 | pub const SQLITE_ANY: i32 = 5; 341 | pub const SQLITE_UTF16_ALIGNED: i32 = 8; 342 | pub const SQLITE_DETERMINISTIC: i32 = 2048; 343 | pub const SQLITE_DIRECTONLY: i32 = 524288; 344 | pub const SQLITE_SUBTYPE: i32 = 1048576; 345 | pub const SQLITE_INNOCUOUS: i32 = 2097152; 346 | pub const SQLITE_WIN32_DATA_DIRECTORY_TYPE: i32 = 1; 347 | pub const SQLITE_WIN32_TEMP_DIRECTORY_TYPE: i32 = 2; 348 | pub const SQLITE_TXN_NONE: i32 = 0; 349 | pub const SQLITE_TXN_READ: i32 = 1; 350 | pub const SQLITE_TXN_WRITE: i32 = 2; 351 | pub const SQLITE_INDEX_SCAN_UNIQUE: i32 = 1; 352 | pub const SQLITE_INDEX_CONSTRAINT_EQ: i32 = 2; 353 | pub const SQLITE_INDEX_CONSTRAINT_GT: i32 = 4; 354 | pub const SQLITE_INDEX_CONSTRAINT_LE: i32 = 8; 355 | pub const SQLITE_INDEX_CONSTRAINT_LT: i32 = 16; 356 | pub const SQLITE_INDEX_CONSTRAINT_GE: i32 = 32; 357 | pub const SQLITE_INDEX_CONSTRAINT_MATCH: i32 = 64; 358 | pub const SQLITE_INDEX_CONSTRAINT_LIKE: i32 = 65; 359 | pub const SQLITE_INDEX_CONSTRAINT_GLOB: i32 = 66; 360 | pub const SQLITE_INDEX_CONSTRAINT_REGEXP: i32 = 67; 361 | pub const SQLITE_INDEX_CONSTRAINT_NE: i32 = 68; 362 | pub const SQLITE_INDEX_CONSTRAINT_ISNOT: i32 = 69; 363 | pub const SQLITE_INDEX_CONSTRAINT_ISNOTNULL: i32 = 70; 364 | pub const SQLITE_INDEX_CONSTRAINT_ISNULL: i32 = 71; 365 | pub const SQLITE_INDEX_CONSTRAINT_IS: i32 = 72; 366 | pub const SQLITE_INDEX_CONSTRAINT_FUNCTION: i32 = 150; 367 | pub const SQLITE_MUTEX_FAST: i32 = 0; 368 | pub const SQLITE_MUTEX_RECURSIVE: i32 = 1; 369 | pub const SQLITE_MUTEX_STATIC_MAIN: i32 = 2; 370 | pub const SQLITE_MUTEX_STATIC_MEM: i32 = 3; 371 | pub const SQLITE_MUTEX_STATIC_MEM2: i32 = 4; 372 | pub const SQLITE_MUTEX_STATIC_OPEN: i32 = 4; 373 | pub const SQLITE_MUTEX_STATIC_PRNG: i32 = 5; 374 | pub const SQLITE_MUTEX_STATIC_LRU: i32 = 6; 375 | pub const SQLITE_MUTEX_STATIC_LRU2: i32 = 7; 376 | pub const SQLITE_MUTEX_STATIC_PMEM: i32 = 7; 377 | pub const SQLITE_MUTEX_STATIC_APP1: i32 = 8; 378 | pub const SQLITE_MUTEX_STATIC_APP2: i32 = 9; 379 | pub const SQLITE_MUTEX_STATIC_APP3: i32 = 10; 380 | pub const SQLITE_MUTEX_STATIC_VFS1: i32 = 11; 381 | pub const SQLITE_MUTEX_STATIC_VFS2: i32 = 12; 382 | pub const SQLITE_MUTEX_STATIC_VFS3: i32 = 13; 383 | pub const SQLITE_MUTEX_STATIC_MASTER: i32 = 2; 384 | pub const SQLITE_TESTCTRL_FIRST: i32 = 5; 385 | pub const SQLITE_TESTCTRL_PRNG_SAVE: i32 = 5; 386 | pub const SQLITE_TESTCTRL_PRNG_RESTORE: i32 = 6; 387 | pub const SQLITE_TESTCTRL_PRNG_RESET: i32 = 7; 388 | pub const SQLITE_TESTCTRL_BITVEC_TEST: i32 = 8; 389 | pub const SQLITE_TESTCTRL_FAULT_INSTALL: i32 = 9; 390 | pub const SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: i32 = 10; 391 | pub const SQLITE_TESTCTRL_PENDING_BYTE: i32 = 11; 392 | pub const SQLITE_TESTCTRL_ASSERT: i32 = 12; 393 | pub const SQLITE_TESTCTRL_ALWAYS: i32 = 13; 394 | pub const SQLITE_TESTCTRL_RESERVE: i32 = 14; 395 | pub const SQLITE_TESTCTRL_OPTIMIZATIONS: i32 = 15; 396 | pub const SQLITE_TESTCTRL_ISKEYWORD: i32 = 16; 397 | pub const SQLITE_TESTCTRL_SCRATCHMALLOC: i32 = 17; 398 | pub const SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: i32 = 17; 399 | pub const SQLITE_TESTCTRL_LOCALTIME_FAULT: i32 = 18; 400 | pub const SQLITE_TESTCTRL_EXPLAIN_STMT: i32 = 19; 401 | pub const SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD: i32 = 19; 402 | pub const SQLITE_TESTCTRL_NEVER_CORRUPT: i32 = 20; 403 | pub const SQLITE_TESTCTRL_VDBE_COVERAGE: i32 = 21; 404 | pub const SQLITE_TESTCTRL_BYTEORDER: i32 = 22; 405 | pub const SQLITE_TESTCTRL_ISINIT: i32 = 23; 406 | pub const SQLITE_TESTCTRL_SORTER_MMAP: i32 = 24; 407 | pub const SQLITE_TESTCTRL_IMPOSTER: i32 = 25; 408 | pub const SQLITE_TESTCTRL_PARSER_COVERAGE: i32 = 26; 409 | pub const SQLITE_TESTCTRL_RESULT_INTREAL: i32 = 27; 410 | pub const SQLITE_TESTCTRL_PRNG_SEED: i32 = 28; 411 | pub const SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS: i32 = 29; 412 | pub const SQLITE_TESTCTRL_SEEK_COUNT: i32 = 30; 413 | pub const SQLITE_TESTCTRL_TRACEFLAGS: i32 = 31; 414 | pub const SQLITE_TESTCTRL_LAST: i32 = 31; 415 | pub const SQLITE_STATUS_MEMORY_USED: i32 = 0; 416 | pub const SQLITE_STATUS_PAGECACHE_USED: i32 = 1; 417 | pub const SQLITE_STATUS_PAGECACHE_OVERFLOW: i32 = 2; 418 | pub const SQLITE_STATUS_SCRATCH_USED: i32 = 3; 419 | pub const SQLITE_STATUS_SCRATCH_OVERFLOW: i32 = 4; 420 | pub const SQLITE_STATUS_MALLOC_SIZE: i32 = 5; 421 | pub const SQLITE_STATUS_PARSER_STACK: i32 = 6; 422 | pub const SQLITE_STATUS_PAGECACHE_SIZE: i32 = 7; 423 | pub const SQLITE_STATUS_SCRATCH_SIZE: i32 = 8; 424 | pub const SQLITE_STATUS_MALLOC_COUNT: i32 = 9; 425 | pub const SQLITE_DBSTATUS_LOOKASIDE_USED: i32 = 0; 426 | pub const SQLITE_DBSTATUS_CACHE_USED: i32 = 1; 427 | pub const SQLITE_DBSTATUS_SCHEMA_USED: i32 = 2; 428 | pub const SQLITE_DBSTATUS_STMT_USED: i32 = 3; 429 | pub const SQLITE_DBSTATUS_LOOKASIDE_HIT: i32 = 4; 430 | pub const SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE: i32 = 5; 431 | pub const SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL: i32 = 6; 432 | pub const SQLITE_DBSTATUS_CACHE_HIT: i32 = 7; 433 | pub const SQLITE_DBSTATUS_CACHE_MISS: i32 = 8; 434 | pub const SQLITE_DBSTATUS_CACHE_WRITE: i32 = 9; 435 | pub const SQLITE_DBSTATUS_DEFERRED_FKS: i32 = 10; 436 | pub const SQLITE_DBSTATUS_CACHE_USED_SHARED: i32 = 11; 437 | pub const SQLITE_DBSTATUS_CACHE_SPILL: i32 = 12; 438 | pub const SQLITE_DBSTATUS_MAX: i32 = 12; 439 | pub const SQLITE_STMTSTATUS_FULLSCAN_STEP: i32 = 1; 440 | pub const SQLITE_STMTSTATUS_SORT: i32 = 2; 441 | pub const SQLITE_STMTSTATUS_AUTOINDEX: i32 = 3; 442 | pub const SQLITE_STMTSTATUS_VM_STEP: i32 = 4; 443 | pub const SQLITE_STMTSTATUS_REPREPARE: i32 = 5; 444 | pub const SQLITE_STMTSTATUS_RUN: i32 = 6; 445 | pub const SQLITE_STMTSTATUS_MEMUSED: i32 = 99; 446 | pub const SQLITE_CHECKPOINT_PASSIVE: i32 = 0; 447 | pub const SQLITE_CHECKPOINT_FULL: i32 = 1; 448 | pub const SQLITE_CHECKPOINT_RESTART: i32 = 2; 449 | pub const SQLITE_CHECKPOINT_TRUNCATE: i32 = 3; 450 | pub const SQLITE_VTAB_CONSTRAINT_SUPPORT: i32 = 1; 451 | pub const SQLITE_VTAB_INNOCUOUS: i32 = 2; 452 | pub const SQLITE_VTAB_DIRECTONLY: i32 = 3; 453 | pub const SQLITE_ROLLBACK: i32 = 1; 454 | pub const SQLITE_FAIL: i32 = 3; 455 | pub const SQLITE_REPLACE: i32 = 5; 456 | pub const SQLITE_SCANSTAT_NLOOP: i32 = 0; 457 | pub const SQLITE_SCANSTAT_NVISIT: i32 = 1; 458 | pub const SQLITE_SCANSTAT_EST: i32 = 2; 459 | pub const SQLITE_SCANSTAT_NAME: i32 = 3; 460 | pub const SQLITE_SCANSTAT_EXPLAIN: i32 = 4; 461 | pub const SQLITE_SCANSTAT_SELECTID: i32 = 5; 462 | pub const SQLITE_SERIALIZE_NOCOPY: i32 = 1; 463 | pub const SQLITE_DESERIALIZE_FREEONCLOSE: i32 = 1; 464 | pub const SQLITE_DESERIALIZE_RESIZEABLE: i32 = 2; 465 | pub const SQLITE_DESERIALIZE_READONLY: i32 = 4; 466 | pub const NOT_WITHIN: i32 = 0; 467 | pub const PARTLY_WITHIN: i32 = 1; 468 | pub const FULLY_WITHIN: i32 = 2; 469 | pub const FTS5_TOKENIZE_QUERY: i32 = 1; 470 | pub const FTS5_TOKENIZE_PREFIX: i32 = 2; 471 | pub const FTS5_TOKENIZE_DOCUMENT: i32 = 4; 472 | pub const FTS5_TOKENIZE_AUX: i32 = 8; 473 | pub const FTS5_TOKEN_COLOCATED: i32 = 1; 474 | 475 | pub type sqlite3_int64 = std::os::raw::c_longlong; 476 | pub type sqlite3_syscall_ptr = ::std::option::Option; 477 | 478 | #[repr(C)] 479 | #[derive(Debug, Copy, Clone)] 480 | pub struct sqlite3_io_methods { 481 | pub iVersion: ::std::os::raw::c_int, 482 | pub xClose: ::std::option::Option< 483 | unsafe extern "C" fn(arg1: *mut sqlite3_file) -> ::std::os::raw::c_int, 484 | >, 485 | pub xRead: ::std::option::Option< 486 | unsafe extern "C" fn( 487 | arg1: *mut sqlite3_file, 488 | arg2: *mut ::std::os::raw::c_void, 489 | iAmt: ::std::os::raw::c_int, 490 | iOfst: sqlite3_int64, 491 | ) -> ::std::os::raw::c_int, 492 | >, 493 | pub xWrite: ::std::option::Option< 494 | unsafe extern "C" fn( 495 | arg1: *mut sqlite3_file, 496 | arg2: *const ::std::os::raw::c_void, 497 | iAmt: ::std::os::raw::c_int, 498 | iOfst: sqlite3_int64, 499 | ) -> ::std::os::raw::c_int, 500 | >, 501 | pub xTruncate: ::std::option::Option< 502 | unsafe extern "C" fn(arg1: *mut sqlite3_file, size: sqlite3_int64) -> ::std::os::raw::c_int, 503 | >, 504 | pub xSync: ::std::option::Option< 505 | unsafe extern "C" fn( 506 | arg1: *mut sqlite3_file, 507 | flags: ::std::os::raw::c_int, 508 | ) -> ::std::os::raw::c_int, 509 | >, 510 | pub xFileSize: ::std::option::Option< 511 | unsafe extern "C" fn( 512 | arg1: *mut sqlite3_file, 513 | pSize: *mut sqlite3_int64, 514 | ) -> ::std::os::raw::c_int, 515 | >, 516 | pub xLock: ::std::option::Option< 517 | unsafe extern "C" fn( 518 | arg1: *mut sqlite3_file, 519 | arg2: ::std::os::raw::c_int, 520 | ) -> ::std::os::raw::c_int, 521 | >, 522 | pub xUnlock: ::std::option::Option< 523 | unsafe extern "C" fn( 524 | arg1: *mut sqlite3_file, 525 | arg2: ::std::os::raw::c_int, 526 | ) -> ::std::os::raw::c_int, 527 | >, 528 | pub xCheckReservedLock: ::std::option::Option< 529 | unsafe extern "C" fn( 530 | arg1: *mut sqlite3_file, 531 | pResOut: *mut ::std::os::raw::c_int, 532 | ) -> ::std::os::raw::c_int, 533 | >, 534 | pub xFileControl: ::std::option::Option< 535 | unsafe extern "C" fn( 536 | arg1: *mut sqlite3_file, 537 | op: ::std::os::raw::c_int, 538 | pArg: *mut ::std::os::raw::c_void, 539 | ) -> ::std::os::raw::c_int, 540 | >, 541 | pub xSectorSize: ::std::option::Option< 542 | unsafe extern "C" fn(arg1: *mut sqlite3_file) -> ::std::os::raw::c_int, 543 | >, 544 | pub xDeviceCharacteristics: ::std::option::Option< 545 | unsafe extern "C" fn(arg1: *mut sqlite3_file) -> ::std::os::raw::c_int, 546 | >, 547 | pub xShmMap: ::std::option::Option< 548 | unsafe extern "C" fn( 549 | arg1: *mut sqlite3_file, 550 | iPg: ::std::os::raw::c_int, 551 | pgsz: ::std::os::raw::c_int, 552 | arg2: ::std::os::raw::c_int, 553 | arg3: *mut *mut ::std::os::raw::c_void, 554 | ) -> ::std::os::raw::c_int, 555 | >, 556 | pub xShmLock: ::std::option::Option< 557 | unsafe extern "C" fn( 558 | arg1: *mut sqlite3_file, 559 | offset: ::std::os::raw::c_int, 560 | n: ::std::os::raw::c_int, 561 | flags: ::std::os::raw::c_int, 562 | ) -> ::std::os::raw::c_int, 563 | >, 564 | pub xShmBarrier: ::std::option::Option, 565 | pub xShmUnmap: ::std::option::Option< 566 | unsafe extern "C" fn( 567 | arg1: *mut sqlite3_file, 568 | deleteFlag: ::std::os::raw::c_int, 569 | ) -> ::std::os::raw::c_int, 570 | >, 571 | pub xFetch: ::std::option::Option< 572 | unsafe extern "C" fn( 573 | arg1: *mut sqlite3_file, 574 | iOfst: sqlite3_int64, 575 | iAmt: ::std::os::raw::c_int, 576 | pp: *mut *mut ::std::os::raw::c_void, 577 | ) -> ::std::os::raw::c_int, 578 | >, 579 | pub xUnfetch: ::std::option::Option< 580 | unsafe extern "C" fn( 581 | arg1: *mut sqlite3_file, 582 | iOfst: sqlite3_int64, 583 | p: *mut ::std::os::raw::c_void, 584 | ) -> ::std::os::raw::c_int, 585 | >, 586 | } 587 | 588 | #[repr(C)] 589 | #[derive(Debug, Copy, Clone)] 590 | pub struct sqlite3_file { 591 | pub pMethods: *const sqlite3_io_methods, 592 | } 593 | 594 | #[repr(C)] 595 | #[derive(Debug, Copy, Clone)] 596 | pub struct sqlite3_vfs { 597 | pub iVersion: ::std::os::raw::c_int, 598 | pub szOsFile: ::std::os::raw::c_int, 599 | pub mxPathname: ::std::os::raw::c_int, 600 | pub pNext: *mut sqlite3_vfs, 601 | pub zName: *const ::std::os::raw::c_char, 602 | pub pAppData: *mut ::std::os::raw::c_void, 603 | pub xOpen: ::std::option::Option< 604 | unsafe extern "C" fn( 605 | arg1: *mut sqlite3_vfs, 606 | zName: *const ::std::os::raw::c_char, 607 | arg2: *mut sqlite3_file, 608 | flags: ::std::os::raw::c_int, 609 | pOutFlags: *mut ::std::os::raw::c_int, 610 | ) -> ::std::os::raw::c_int, 611 | >, 612 | pub xDelete: ::std::option::Option< 613 | unsafe extern "C" fn( 614 | arg1: *mut sqlite3_vfs, 615 | zName: *const ::std::os::raw::c_char, 616 | syncDir: ::std::os::raw::c_int, 617 | ) -> ::std::os::raw::c_int, 618 | >, 619 | pub xAccess: ::std::option::Option< 620 | unsafe extern "C" fn( 621 | arg1: *mut sqlite3_vfs, 622 | zName: *const ::std::os::raw::c_char, 623 | flags: ::std::os::raw::c_int, 624 | pResOut: *mut ::std::os::raw::c_int, 625 | ) -> ::std::os::raw::c_int, 626 | >, 627 | pub xFullPathname: ::std::option::Option< 628 | unsafe extern "C" fn( 629 | arg1: *mut sqlite3_vfs, 630 | zName: *const ::std::os::raw::c_char, 631 | nOut: ::std::os::raw::c_int, 632 | zOut: *mut ::std::os::raw::c_char, 633 | ) -> ::std::os::raw::c_int, 634 | >, 635 | pub xDlOpen: ::std::option::Option< 636 | unsafe extern "C" fn( 637 | arg1: *mut sqlite3_vfs, 638 | zFilename: *const ::std::os::raw::c_char, 639 | ) -> *mut ::std::os::raw::c_void, 640 | >, 641 | pub xDlError: ::std::option::Option< 642 | unsafe extern "C" fn( 643 | arg1: *mut sqlite3_vfs, 644 | nByte: ::std::os::raw::c_int, 645 | zErrMsg: *mut ::std::os::raw::c_char, 646 | ), 647 | >, 648 | pub xDlSym: ::std::option::Option< 649 | unsafe extern "C" fn( 650 | arg1: *mut sqlite3_vfs, 651 | arg2: *mut ::std::os::raw::c_void, 652 | zSymbol: *const ::std::os::raw::c_char, 653 | ) -> ::std::option::Option< 654 | unsafe extern "C" fn( 655 | arg1: *mut sqlite3_vfs, 656 | arg2: *mut ::std::os::raw::c_void, 657 | zSymbol: *const ::std::os::raw::c_char, 658 | ), 659 | >, 660 | >, 661 | pub xDlClose: ::std::option::Option< 662 | unsafe extern "C" fn(arg1: *mut sqlite3_vfs, arg2: *mut ::std::os::raw::c_void), 663 | >, 664 | pub xRandomness: ::std::option::Option< 665 | unsafe extern "C" fn( 666 | arg1: *mut sqlite3_vfs, 667 | nByte: ::std::os::raw::c_int, 668 | zOut: *mut ::std::os::raw::c_char, 669 | ) -> ::std::os::raw::c_int, 670 | >, 671 | pub xSleep: ::std::option::Option< 672 | unsafe extern "C" fn( 673 | arg1: *mut sqlite3_vfs, 674 | microseconds: ::std::os::raw::c_int, 675 | ) -> ::std::os::raw::c_int, 676 | >, 677 | pub xCurrentTime: ::std::option::Option< 678 | unsafe extern "C" fn(arg1: *mut sqlite3_vfs, arg2: *mut f64) -> ::std::os::raw::c_int, 679 | >, 680 | pub xGetLastError: ::std::option::Option< 681 | unsafe extern "C" fn( 682 | arg1: *mut sqlite3_vfs, 683 | arg2: ::std::os::raw::c_int, 684 | arg3: *mut ::std::os::raw::c_char, 685 | ) -> ::std::os::raw::c_int, 686 | >, 687 | pub xCurrentTimeInt64: ::std::option::Option< 688 | unsafe extern "C" fn( 689 | arg1: *mut sqlite3_vfs, 690 | arg2: *mut sqlite3_int64, 691 | ) -> ::std::os::raw::c_int, 692 | >, 693 | pub xSetSystemCall: ::std::option::Option< 694 | unsafe extern "C" fn( 695 | arg1: *mut sqlite3_vfs, 696 | zName: *const ::std::os::raw::c_char, 697 | arg2: sqlite3_syscall_ptr, 698 | ) -> ::std::os::raw::c_int, 699 | >, 700 | pub xGetSystemCall: ::std::option::Option< 701 | unsafe extern "C" fn( 702 | arg1: *mut sqlite3_vfs, 703 | zName: *const ::std::os::raw::c_char, 704 | ) -> sqlite3_syscall_ptr, 705 | >, 706 | pub xNextSystemCall: ::std::option::Option< 707 | unsafe extern "C" fn( 708 | arg1: *mut sqlite3_vfs, 709 | zName: *const ::std::os::raw::c_char, 710 | ) -> *const ::std::os::raw::c_char, 711 | >, 712 | } 713 | 714 | extern "C" { 715 | pub fn sqlite3_vfs_register( 716 | arg1: *mut sqlite3_vfs, 717 | makeDflt: ::std::os::raw::c_int, 718 | ) -> ::std::os::raw::c_int; 719 | 720 | pub fn sqlite3_snprintf( 721 | arg1: ::std::os::raw::c_int, 722 | arg2: *mut ::std::os::raw::c_char, 723 | arg3: *const ::std::os::raw::c_char, 724 | ... 725 | ) -> *mut ::std::os::raw::c_char; 726 | 727 | pub fn sqlite3_vfs_find(z_vfs_name: *const ::std::os::raw::c_char) -> *mut sqlite3_vfs; 728 | 729 | pub fn sqlite3_uri_boolean( 730 | z_filename: *const ::std::os::raw::c_char, 731 | z_param: *const ::std::os::raw::c_char, 732 | b_dflt: i32, 733 | ) -> i32; 734 | } 735 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::question_mark)] 2 | //! Create a custom SQLite virtual file system by implementing the [Vfs] trait and registering it 3 | //! using [register]. 4 | 5 | use std::borrow::Cow; 6 | use std::collections::HashMap; 7 | use std::ffi::{c_void, CStr, CString}; 8 | use std::io::ErrorKind; 9 | use std::mem::{size_of, ManuallyDrop, MaybeUninit}; 10 | use std::ops::Range; 11 | use std::os::raw::{c_char, c_int}; 12 | use std::pin::Pin; 13 | use std::ptr::null_mut; 14 | use std::slice; 15 | use std::sync::{Arc, Mutex}; 16 | use std::time::Duration; 17 | 18 | mod ffi; 19 | 20 | /// A file opened by [Vfs]. 21 | pub trait DatabaseHandle: Sync { 22 | /// An optional trait used to store a WAL (write-ahead log). 23 | type WalIndex: wip::WalIndex; 24 | 25 | /// Return the current size in bytes of the database. 26 | fn size(&self) -> Result; 27 | 28 | /// Reads the exact number of byte required to fill `buf` from the given `offset`. 29 | fn read_exact_at(&mut self, buf: &mut [u8], offset: u64) -> Result<(), std::io::Error>; 30 | 31 | /// Attempts to write an entire `buf` starting from the given `offset`. 32 | fn write_all_at(&mut self, buf: &[u8], offset: u64) -> Result<(), std::io::Error>; 33 | 34 | /// Make sure all writes are committed to the underlying storage. If `data_only` is set to 35 | /// `true`, only the data and not the metadata (like size, access time, etc) should be synced. 36 | fn sync(&mut self, data_only: bool) -> Result<(), std::io::Error>; 37 | 38 | /// Set the database file to the specified `size`. Truncates or extends the underlying storage. 39 | fn set_len(&mut self, size: u64) -> Result<(), std::io::Error>; 40 | 41 | /// Lock the database. Returns whether the requested lock could be acquired. 42 | /// Locking sequence: 43 | /// - The lock is never moved from [LockKind::None] to anything higher than [LockKind::Shared]. 44 | /// - A [LockKind::Pending] is never requested explicitly. 45 | /// - A [LockKind::Shared] is always held when a [LockKind::Reserved] lock is requested 46 | fn lock(&mut self, lock: LockKind) -> Result; 47 | 48 | /// Unlock the database. 49 | fn unlock(&mut self, lock: LockKind) -> Result { 50 | self.lock(lock) 51 | } 52 | 53 | /// Check if the database this handle points to holds a [LockKind::Reserved], 54 | /// [LockKind::Pending] or [LockKind::Exclusive] lock. 55 | fn reserved(&mut self) -> Result; 56 | 57 | /// Return the current [LockKind] of the this handle. 58 | fn current_lock(&self) -> Result; 59 | 60 | /// Change the chunk size of the database to `chunk_size`. 61 | fn set_chunk_size(&self, _chunk_size: usize) -> Result<(), std::io::Error> { 62 | Ok(()) 63 | } 64 | 65 | /// Check if the underlying data of the handle got moved or deleted. When moved, the handle can 66 | /// still be read from, but not written to anymore. 67 | fn moved(&self) -> Result { 68 | Ok(false) 69 | } 70 | 71 | fn wal_index(&self, readonly: bool) -> Result; 72 | } 73 | 74 | /// A virtual file system for SQLite. 75 | pub trait Vfs: Sync { 76 | /// The file returned by [Vfs::open]. 77 | type Handle: DatabaseHandle; 78 | 79 | /// Open the database `db` (of type `opts.kind`). 80 | fn open(&self, db: &str, opts: OpenOptions) -> Result; 81 | 82 | /// Delete the database `db`. 83 | fn delete(&self, db: &str) -> Result<(), std::io::Error>; 84 | 85 | /// Check if a database `db` already exists. 86 | fn exists(&self, db: &str) -> Result; 87 | 88 | /// Generate and return a path for a temporary database. 89 | fn temporary_name(&self) -> String; 90 | 91 | /// Populate the `buffer` with random data. 92 | fn random(&self, buffer: &mut [i8]); 93 | 94 | /// Sleep for `duration`. Return the duration actually slept. 95 | fn sleep(&self, duration: Duration) -> Duration; 96 | 97 | /// Check access to `db`. The default implementation always returns `true`. 98 | fn access(&self, _db: &str, _write: bool) -> Result { 99 | Ok(true) 100 | } 101 | 102 | /// Retrieve the full pathname of a database `db`. 103 | fn full_pathname<'a>(&self, db: &'a str) -> Result, std::io::Error> { 104 | Ok(db.into()) 105 | } 106 | } 107 | 108 | #[doc(hidden)] 109 | pub mod wip { 110 | use super::*; 111 | 112 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 113 | #[repr(u16)] 114 | pub enum WalIndexLock { 115 | None = 1, 116 | Shared, 117 | Exclusive, 118 | } 119 | 120 | pub trait WalIndex: Sync { 121 | fn enabled() -> bool { 122 | true 123 | } 124 | 125 | fn map(&mut self, region: u32) -> Result<[u8; 32768], std::io::Error>; 126 | fn lock(&mut self, locks: Range, lock: WalIndexLock) -> Result; 127 | fn delete(self) -> Result<(), std::io::Error>; 128 | 129 | fn pull(&mut self, _region: u32, _data: &mut [u8; 32768]) -> Result<(), std::io::Error> { 130 | Ok(()) 131 | } 132 | 133 | fn push(&mut self, _region: u32, _data: &[u8; 32768]) -> Result<(), std::io::Error> { 134 | Ok(()) 135 | } 136 | } 137 | } 138 | 139 | #[derive(Debug, Clone, PartialEq)] 140 | pub struct OpenOptions { 141 | /// The object type that is being opened. 142 | pub kind: OpenKind, 143 | 144 | /// The access an object is opened with. 145 | pub access: OpenAccess, 146 | 147 | /// The file should be deleted when it is closed. 148 | delete_on_close: bool, 149 | } 150 | 151 | /// The object type that is being opened. 152 | #[derive(Debug, Clone, Copy, PartialEq)] 153 | pub enum OpenKind { 154 | MainDb, 155 | MainJournal, 156 | TempDb, 157 | TempJournal, 158 | TransientDb, 159 | SubJournal, 160 | SuperJournal, 161 | Wal, 162 | } 163 | 164 | /// The access an object is opened with. 165 | #[derive(Debug, Clone, Copy, PartialEq)] 166 | pub enum OpenAccess { 167 | /// Read access. 168 | Read, 169 | 170 | /// Write access (includes read access). 171 | Write, 172 | 173 | /// Create the file if it does not exist (includes write and read access). 174 | Create, 175 | 176 | /// Create the file, but throw if it it already exist (includes write and read access). 177 | CreateNew, 178 | } 179 | 180 | /// The access an object is opened with. 181 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 182 | pub enum LockKind { 183 | /// No locks are held. The database may be neither read nor written. Any internally cached data 184 | /// is considered suspect and subject to verification against the database file before being 185 | /// used. Other processes can read or write the database as their own locking states permit. 186 | /// This is the default state. 187 | None, 188 | 189 | /// The database may be read but not written. Any number of processes can hold 190 | /// [LockKind::Shared] locks at the same time, hence there can be many simultaneous readers. But 191 | /// no other thread or process is allowed to write to the database file while one or more 192 | /// [LockKind::Shared] locks are active. 193 | Shared, 194 | 195 | /// A [LockKind::Reserved] lock means that the process is planning on writing to the database 196 | /// file at some point in the future but that it is currently just reading from the file. Only a 197 | /// single [LockKind::Reserved] lock may be active at one time, though multiple 198 | /// [LockKind::Shared] locks can coexist with a single [LockKind::Reserved] lock. 199 | /// [LockKind::Reserved] differs from [LockKind::Pending] in that new [LockKind::Shared] locks 200 | /// can be acquired while there is a [LockKind::Reserved] lock. 201 | Reserved, 202 | 203 | /// A [LockKind::Pending] lock means that the process holding the lock wants to write to the 204 | /// database as soon as possible and is just waiting on all current [LockKind::Shared] locks to 205 | /// clear so that it can get an [LockKind::Exclusive] lock. No new [LockKind::Shared] locks are 206 | /// permitted against the database if a [LockKind::Pending] lock is active, though existing 207 | /// [LockKind::Shared] locks are allowed to continue. 208 | Pending, 209 | 210 | /// An [LockKind::Exclusive] lock is needed in order to write to the database file. Only one 211 | /// [LockKind::Exclusive] lock is allowed on the file and no other locks of any kind are allowed 212 | /// to coexist with an [LockKind::Exclusive] lock. In order to maximize concurrency, SQLite 213 | /// works to minimize the amount of time that [LockKind::Exclusive] locks are held. 214 | Exclusive, 215 | } 216 | 217 | struct State { 218 | name: CString, 219 | vfs: Arc, 220 | #[cfg(any(feature = "syscall", feature = "loadext"))] 221 | parent_vfs: *mut ffi::sqlite3_vfs, 222 | io_methods: ffi::sqlite3_io_methods, 223 | last_error: Arc>>, 224 | next_id: usize, 225 | } 226 | 227 | /// Register a virtual file system ([Vfs]) to SQLite. 228 | pub fn register>( 229 | name: &str, 230 | vfs: V, 231 | as_default: bool, 232 | ) -> Result<(), RegisterError> { 233 | let io_methods = ffi::sqlite3_io_methods { 234 | iVersion: 2, 235 | xClose: Some(io::close::), 236 | xRead: Some(io::read::), 237 | xWrite: Some(io::write::), 238 | xTruncate: Some(io::truncate::), 239 | xSync: Some(io::sync::), 240 | xFileSize: Some(io::file_size::), 241 | xLock: Some(io::lock::), 242 | xUnlock: Some(io::unlock::), 243 | xCheckReservedLock: Some(io::check_reserved_lock::), 244 | xFileControl: Some(io::file_control::), 245 | xSectorSize: Some(io::sector_size::), 246 | xDeviceCharacteristics: Some(io::device_characteristics::), 247 | xShmMap: Some(io::shm_map::), 248 | xShmLock: Some(io::shm_lock::), 249 | xShmBarrier: Some(io::shm_barrier::), 250 | xShmUnmap: Some(io::shm_unmap::), 251 | xFetch: None, 252 | xUnfetch: None, 253 | }; 254 | let name = CString::new(name)?; 255 | let name_ptr = name.as_ptr(); 256 | let ptr = Box::into_raw(Box::new(State { 257 | name, 258 | vfs: Arc::new(vfs), 259 | #[cfg(any(feature = "syscall", feature = "loadext"))] 260 | parent_vfs: unsafe { ffi::sqlite3_vfs_find(std::ptr::null_mut()) }, 261 | io_methods, 262 | last_error: Default::default(), 263 | next_id: 0, 264 | })); 265 | let vfs = Box::into_raw(Box::new(ffi::sqlite3_vfs { 266 | #[cfg(not(feature = "syscall"))] 267 | iVersion: 2, 268 | #[cfg(feature = "syscall")] 269 | iVersion: 3, 270 | szOsFile: size_of::>() as i32, 271 | mxPathname: MAX_PATH_LENGTH as i32, // max path length supported by VFS 272 | pNext: null_mut(), 273 | zName: name_ptr, 274 | pAppData: ptr as _, 275 | xOpen: Some(vfs::open::), 276 | xDelete: Some(vfs::delete::), 277 | xAccess: Some(vfs::access::), 278 | xFullPathname: Some(vfs::full_pathname::), 279 | xDlOpen: Some(vfs::dlopen::), 280 | xDlError: Some(vfs::dlerror::), 281 | xDlSym: Some(vfs::dlsym::), 282 | xDlClose: Some(vfs::dlclose::), 283 | xRandomness: Some(vfs::randomness::), 284 | xSleep: Some(vfs::sleep::), 285 | xCurrentTime: Some(vfs::current_time::), 286 | xGetLastError: Some(vfs::get_last_error::), 287 | xCurrentTimeInt64: Some(vfs::current_time_int64::), 288 | 289 | #[cfg(not(feature = "syscall"))] 290 | xSetSystemCall: None, 291 | #[cfg(not(feature = "syscall"))] 292 | xGetSystemCall: None, 293 | #[cfg(not(feature = "syscall"))] 294 | xNextSystemCall: None, 295 | 296 | #[cfg(feature = "syscall")] 297 | xSetSystemCall: Some(vfs::set_system_call::), 298 | #[cfg(feature = "syscall")] 299 | xGetSystemCall: Some(vfs::get_system_call::), 300 | #[cfg(feature = "syscall")] 301 | xNextSystemCall: Some(vfs::next_system_call::), 302 | })); 303 | 304 | let result = unsafe { ffi::sqlite3_vfs_register(vfs, as_default as i32) }; 305 | if result != ffi::SQLITE_OK { 306 | return Err(RegisterError::Register(result)); 307 | } 308 | 309 | // TODO: return object that allows to unregister (and cleanup the memory)? 310 | 311 | Ok(()) 312 | } 313 | 314 | // TODO: add to [Vfs]? 315 | const MAX_PATH_LENGTH: usize = 512; 316 | 317 | #[repr(C)] 318 | struct FileState { 319 | base: ffi::sqlite3_file, 320 | ext: MaybeUninit>, 321 | } 322 | 323 | #[repr(C)] 324 | struct FileExt { 325 | vfs: Arc, 326 | vfs_name: CString, 327 | db_name: String, 328 | file: F, 329 | delete_on_close: bool, 330 | /// The last error; shared with the VFS. 331 | last_error: Arc>>, 332 | /// The last error number of this file/connection (not shared with the VFS). 333 | last_errno: i32, 334 | wal_index: Option<(F::WalIndex, bool)>, 335 | wal_index_regions: HashMap>>, 336 | wal_index_locks: HashMap, 337 | has_exclusive_lock: bool, 338 | id: usize, 339 | chunk_size: Option, 340 | persist_wal: bool, 341 | powersafe_overwrite: bool, 342 | } 343 | 344 | // Example mem-fs implementation: 345 | // https://github.com/sqlite/sqlite/blob/a959bf53110bfada67a3a52187acd57aa2f34e19/ext/misc/memvfs.c 346 | mod vfs { 347 | use super::*; 348 | 349 | /// Open a new file handler. 350 | pub unsafe extern "C" fn open>( 351 | p_vfs: *mut ffi::sqlite3_vfs, 352 | z_name: *const c_char, 353 | p_file: *mut ffi::sqlite3_file, 354 | flags: c_int, 355 | p_out_flags: *mut c_int, 356 | ) -> c_int { 357 | let state = match vfs_state::(p_vfs) { 358 | Ok(state) => state, 359 | Err(_) => return ffi::SQLITE_ERROR, 360 | }; 361 | 362 | let name = if z_name.is_null() { 363 | None 364 | } else { 365 | match CStr::from_ptr(z_name).to_str() { 366 | Ok(name) => Some(name), 367 | Err(_) => { 368 | return state.set_last_error( 369 | ffi::SQLITE_CANTOPEN, 370 | std::io::Error::new( 371 | ErrorKind::Other, 372 | format!( 373 | "open failed: database must be valid utf8 (received: {:?})", 374 | CStr::from_ptr(z_name) 375 | ), 376 | ), 377 | ) 378 | } 379 | } 380 | }; 381 | log::trace!("open z_name={:?} flags={}", name, flags); 382 | 383 | let mut opts = match OpenOptions::from_flags(flags) { 384 | Some(opts) => opts, 385 | None => { 386 | return state.set_last_error( 387 | ffi::SQLITE_CANTOPEN, 388 | std::io::Error::new(ErrorKind::Other, "invalid open flags"), 389 | ); 390 | } 391 | }; 392 | 393 | if z_name.is_null() && !opts.delete_on_close { 394 | return state.set_last_error( 395 | ffi::SQLITE_CANTOPEN, 396 | std::io::Error::new( 397 | ErrorKind::Other, 398 | "delete on close expected for temporary database", 399 | ), 400 | ); 401 | } 402 | 403 | let out_file = match (p_file as *mut FileState).as_mut() { 404 | Some(f) => f, 405 | None => { 406 | return state.set_last_error( 407 | ffi::SQLITE_CANTOPEN, 408 | std::io::Error::new(ErrorKind::Other, "invalid file pointer"), 409 | ); 410 | } 411 | }; 412 | 413 | let mut powersafe_overwrite = true; 414 | if flags & ffi::SQLITE_OPEN_URI > 0 && name.is_some() { 415 | let param = b"psow\0"; 416 | if ffi::sqlite3_uri_boolean(z_name, param.as_ptr() as *const c_char, 1) == 0 { 417 | powersafe_overwrite = false; 418 | } 419 | } 420 | 421 | let name = name.map_or_else(|| state.vfs.temporary_name(), String::from); 422 | let result = state.vfs.open(&name, opts.clone()); 423 | let result = match result { 424 | Ok(f) => Ok(f), 425 | // handle creation failure due to readonly directory 426 | Err(err) 427 | if err.kind() == ErrorKind::PermissionDenied 428 | && matches!( 429 | opts.kind, 430 | OpenKind::SuperJournal | OpenKind::MainJournal | OpenKind::Wal 431 | ) 432 | && matches!(opts.access, OpenAccess::Create | OpenAccess::CreateNew) 433 | && !state.vfs.exists(&name).unwrap_or(false) => 434 | { 435 | return state.set_last_error(ffi::SQLITE_READONLY_DIRECTORY, err); 436 | } 437 | 438 | // Try again as readonly 439 | Err(err) 440 | if err.kind() == ErrorKind::PermissionDenied && opts.access != OpenAccess::Read => 441 | { 442 | opts.access = OpenAccess::Read; 443 | state.vfs.open(&name, opts.clone()).map_err(|_| err) 444 | } 445 | 446 | // e.g. tried to open a directory 447 | Err(err) if err.kind() == ErrorKind::Other && opts.access == OpenAccess::Read => { 448 | return state.set_last_error(ffi::SQLITE_IOERR, err); 449 | } 450 | 451 | Err(err) => Err(err), 452 | }; 453 | let file = match result { 454 | Ok(f) => f, 455 | Err(err) => { 456 | return state.set_last_error(ffi::SQLITE_CANTOPEN, err); 457 | } 458 | }; 459 | 460 | if let Some(p_out_flags) = p_out_flags.as_mut() { 461 | *p_out_flags = opts.to_flags(); 462 | } 463 | 464 | out_file.base.pMethods = &state.io_methods; 465 | out_file.ext.write(FileExt { 466 | vfs: state.vfs.clone(), 467 | vfs_name: state.name.clone(), 468 | db_name: name, 469 | file, 470 | delete_on_close: opts.delete_on_close, 471 | last_error: Arc::clone(&state.last_error), 472 | last_errno: 0, 473 | wal_index: None, 474 | wal_index_regions: Default::default(), 475 | wal_index_locks: Default::default(), 476 | has_exclusive_lock: false, 477 | id: state.next_id, 478 | chunk_size: None, 479 | persist_wal: false, 480 | powersafe_overwrite, 481 | }); 482 | state.next_id = state.next_id.overflowing_add(1).0; 483 | 484 | #[cfg(feature = "sqlite_test")] 485 | ffi::sqlite3_inc_open_file_count(); 486 | 487 | ffi::SQLITE_OK 488 | } 489 | 490 | /// Delete the file located at `z_path`. If the `sync_dir` argument is true, ensure the 491 | /// file-system modifications are synced to disk before returning. 492 | pub unsafe extern "C" fn delete( 493 | p_vfs: *mut ffi::sqlite3_vfs, 494 | z_path: *const c_char, 495 | _sync_dir: c_int, 496 | ) -> c_int { 497 | // #[cfg(feature = "sqlite_test")] 498 | // if simulate_io_error() { 499 | // return ffi::SQLITE_ERROR; 500 | // } 501 | 502 | let state = match vfs_state::(p_vfs) { 503 | Ok(state) => state, 504 | Err(_) => return ffi::SQLITE_DELETE, 505 | }; 506 | 507 | let path = match CStr::from_ptr(z_path).to_str() { 508 | Ok(name) => name, 509 | Err(_) => { 510 | return state.set_last_error( 511 | ffi::SQLITE_ERROR, 512 | std::io::Error::new( 513 | ErrorKind::Other, 514 | format!( 515 | "delete failed: database must be valid utf8 (received: {:?})", 516 | CStr::from_ptr(z_path) 517 | ), 518 | ), 519 | ) 520 | } 521 | }; 522 | log::trace!("delete name={}", path); 523 | 524 | match state.vfs.delete(path) { 525 | Ok(_) => ffi::SQLITE_OK, 526 | Err(err) => { 527 | if err.kind() == ErrorKind::NotFound { 528 | ffi::SQLITE_IOERR_DELETE_NOENT 529 | } else { 530 | state.set_last_error(ffi::SQLITE_DELETE, err) 531 | } 532 | } 533 | } 534 | } 535 | 536 | /// Test for access permissions. Return true if the requested permission is available, or false 537 | /// otherwise. 538 | pub unsafe extern "C" fn access( 539 | p_vfs: *mut ffi::sqlite3_vfs, 540 | z_path: *const c_char, 541 | flags: c_int, 542 | p_res_out: *mut c_int, 543 | ) -> c_int { 544 | #[cfg(feature = "sqlite_test")] 545 | if simulate_io_error() { 546 | return ffi::SQLITE_IOERR_ACCESS; 547 | } 548 | 549 | let state = match vfs_state::(p_vfs) { 550 | Ok(state) => state, 551 | Err(_) => return ffi::SQLITE_ERROR, 552 | }; 553 | 554 | let path = match CStr::from_ptr(z_path).to_str() { 555 | Ok(name) => name, 556 | Err(_) => { 557 | log::warn!( 558 | "access failed: database must be valid utf8 (received: {:?})", 559 | CStr::from_ptr(z_path) 560 | ); 561 | 562 | if let Ok(p_res_out) = p_res_out.as_mut().ok_or_else(null_ptr_error) { 563 | *p_res_out = false as i32; 564 | } 565 | 566 | return ffi::SQLITE_OK; 567 | } 568 | }; 569 | log::trace!("access z_name={} flags={}", path, flags); 570 | 571 | let result = match flags { 572 | ffi::SQLITE_ACCESS_EXISTS => state.vfs.exists(path), 573 | ffi::SQLITE_ACCESS_READ => state.vfs.access(path, false), 574 | ffi::SQLITE_ACCESS_READWRITE => state.vfs.access(path, true), 575 | _ => return ffi::SQLITE_IOERR_ACCESS, 576 | }; 577 | 578 | if let Err(err) = result.and_then(|ok| { 579 | let p_res_out: &mut c_int = p_res_out.as_mut().ok_or_else(null_ptr_error)?; 580 | *p_res_out = ok as i32; 581 | Ok(()) 582 | }) { 583 | return state.set_last_error(ffi::SQLITE_IOERR_ACCESS, err); 584 | } 585 | 586 | ffi::SQLITE_OK 587 | } 588 | 589 | /// Populate buffer `z_out` with the full canonical pathname corresponding to the pathname in 590 | /// `z_path`. `z_out` is guaranteed to point to a buffer of at least (INST_MAX_PATHNAME+1) 591 | /// bytes. 592 | pub unsafe extern "C" fn full_pathname( 593 | p_vfs: *mut ffi::sqlite3_vfs, 594 | z_path: *const c_char, 595 | n_out: c_int, 596 | z_out: *mut c_char, 597 | ) -> c_int { 598 | // #[cfg(feature = "sqlite_test")] 599 | // if simulate_io_error() { 600 | // return ffi::SQLITE_ERROR; 601 | // } 602 | 603 | let state = match vfs_state::(p_vfs) { 604 | Ok(state) => state, 605 | Err(_) => return ffi::SQLITE_ERROR, 606 | }; 607 | 608 | let path = match CStr::from_ptr(z_path).to_str() { 609 | Ok(name) => name, 610 | Err(_) => { 611 | return state.set_last_error( 612 | ffi::SQLITE_ERROR, 613 | std::io::Error::new( 614 | ErrorKind::Other, 615 | format!( 616 | "full_pathname failed: database must be valid utf8 (received: {:?})", 617 | CStr::from_ptr(z_path) 618 | ), 619 | ), 620 | ) 621 | } 622 | }; 623 | log::trace!("full_pathname name={}", path); 624 | 625 | let name = match state.vfs.full_pathname(path).and_then(|name| { 626 | CString::new(name.to_string()).map_err(|_| { 627 | std::io::Error::new(ErrorKind::Other, "name must not contain a nul byte") 628 | }) 629 | }) { 630 | Ok(name) => name, 631 | Err(err) => return state.set_last_error(ffi::SQLITE_ERROR, err), 632 | }; 633 | 634 | let name = name.to_bytes_with_nul(); 635 | if name.len() > n_out as usize || name.len() > MAX_PATH_LENGTH { 636 | return state.set_last_error( 637 | ffi::SQLITE_CANTOPEN, 638 | std::io::Error::new(ErrorKind::Other, "full pathname is too long"), 639 | ); 640 | } 641 | let out = slice::from_raw_parts_mut(z_out as *mut u8, name.len()); 642 | out.copy_from_slice(name); 643 | 644 | ffi::SQLITE_OK 645 | } 646 | 647 | /// Open the dynamic library located at `z_path` and return a handle. 648 | #[allow(unused_variables)] 649 | pub unsafe extern "C" fn dlopen( 650 | p_vfs: *mut ffi::sqlite3_vfs, 651 | z_path: *const c_char, 652 | ) -> *mut c_void { 653 | log::trace!("dlopen"); 654 | 655 | #[cfg(feature = "loadext")] 656 | { 657 | let state = match vfs_state::(p_vfs) { 658 | Ok(state) => state, 659 | Err(_) => return null_mut(), 660 | }; 661 | 662 | if let Some(dlopen) = state.parent_vfs.as_ref().and_then(|v| v.xDlOpen) { 663 | return dlopen(state.parent_vfs, z_path); 664 | } 665 | } 666 | 667 | null_mut() 668 | } 669 | 670 | /// Populate the buffer `z_err_msg` (size `n_byte` bytes) with a human readable utf-8 string 671 | /// describing the most recent error encountered associated with dynamic libraries. 672 | #[allow(unused_variables)] 673 | pub unsafe extern "C" fn dlerror( 674 | p_vfs: *mut ffi::sqlite3_vfs, 675 | n_byte: c_int, 676 | z_err_msg: *mut c_char, 677 | ) { 678 | log::trace!("dlerror"); 679 | 680 | #[cfg(feature = "loadext")] 681 | { 682 | let state = match vfs_state::(p_vfs) { 683 | Ok(state) => state, 684 | Err(_) => return, 685 | }; 686 | 687 | if let Some(dlerror) = state.parent_vfs.as_ref().and_then(|v| v.xDlError) { 688 | return dlerror(state.parent_vfs, n_byte, z_err_msg); 689 | } 690 | 691 | return; 692 | } 693 | 694 | #[cfg(not(feature = "loadext"))] 695 | { 696 | let msg = concat!("Loadable extensions are not supported", "\0"); 697 | ffi::sqlite3_snprintf(n_byte, z_err_msg, msg.as_ptr() as _); 698 | } 699 | } 700 | 701 | /// Return a pointer to the symbol `z_sym` in the dynamic library pHandle. 702 | #[allow(unused_variables)] 703 | pub unsafe extern "C" fn dlsym( 704 | p_vfs: *mut ffi::sqlite3_vfs, 705 | p: *mut c_void, 706 | z_sym: *const c_char, 707 | ) -> Option { 708 | log::trace!("dlsym"); 709 | 710 | #[cfg(feature = "loadext")] 711 | { 712 | let state = match vfs_state::(p_vfs) { 713 | Ok(state) => state, 714 | Err(_) => return None, 715 | }; 716 | 717 | if let Some(dlsym) = state.parent_vfs.as_ref().and_then(|v| v.xDlSym) { 718 | return dlsym(state.parent_vfs, p, z_sym); 719 | } 720 | } 721 | 722 | None 723 | } 724 | 725 | /// Close the dynamic library handle `p_handle`. 726 | #[allow(unused_variables)] 727 | pub unsafe extern "C" fn dlclose(p_vfs: *mut ffi::sqlite3_vfs, p_handle: *mut c_void) { 728 | log::trace!("dlclose"); 729 | 730 | #[cfg(feature = "loadext")] 731 | { 732 | let state = match vfs_state::(p_vfs) { 733 | Ok(state) => state, 734 | Err(_) => return, 735 | }; 736 | 737 | if let Some(dlclose) = state.parent_vfs.as_ref().and_then(|v| v.xDlClose) { 738 | return dlclose(state.parent_vfs, p_handle); 739 | } 740 | } 741 | } 742 | 743 | /// Populate the buffer pointed to by `z_buf_out` with `n_byte` bytes of random data. 744 | pub unsafe extern "C" fn randomness( 745 | p_vfs: *mut ffi::sqlite3_vfs, 746 | n_byte: c_int, 747 | z_buf_out: *mut c_char, 748 | ) -> c_int { 749 | log::trace!("randomness"); 750 | 751 | let bytes = slice::from_raw_parts_mut(z_buf_out as *mut i8, n_byte as usize); 752 | if cfg!(feature = "sqlite_test") { 753 | // During testing, the buffer is simply initialized to all zeroes for repeatability 754 | bytes.fill(0); 755 | } else { 756 | let state = match vfs_state::(p_vfs) { 757 | Ok(state) => state, 758 | Err(_) => return 0, 759 | }; 760 | 761 | state.vfs.random(bytes); 762 | } 763 | bytes.len() as c_int 764 | } 765 | 766 | /// Sleep for `n_micro` microseconds. Return the number of microseconds actually slept. 767 | pub unsafe extern "C" fn sleep(p_vfs: *mut ffi::sqlite3_vfs, n_micro: c_int) -> c_int { 768 | log::trace!("sleep"); 769 | 770 | let state = match vfs_state::(p_vfs) { 771 | Ok(state) => state, 772 | Err(_) => return ffi::SQLITE_ERROR, 773 | }; 774 | state 775 | .vfs 776 | .sleep(Duration::from_micros(n_micro as u64)) 777 | .as_micros() as c_int 778 | } 779 | 780 | /// Return the current time as a Julian Day number in `p_time_out`. 781 | pub unsafe extern "C" fn current_time( 782 | p_vfs: *mut ffi::sqlite3_vfs, 783 | p_time_out: *mut f64, 784 | ) -> c_int { 785 | log::trace!("current_time"); 786 | 787 | let mut i = 0i64; 788 | current_time_int64::(p_vfs, &mut i); 789 | 790 | *p_time_out = i as f64 / 86400000.0; 791 | ffi::SQLITE_OK 792 | } 793 | 794 | pub unsafe extern "C" fn current_time_int64( 795 | _p_vfs: *mut ffi::sqlite3_vfs, 796 | p: *mut i64, 797 | ) -> i32 { 798 | log::trace!("current_time_int64"); 799 | 800 | const UNIX_EPOCH: i64 = 24405875 * 8640000; 801 | let now = time::OffsetDateTime::now_utc().unix_timestamp() + UNIX_EPOCH; 802 | #[cfg(feature = "sqlite_test")] 803 | let now = if ffi::sqlite3_get_current_time() > 0 { 804 | ffi::sqlite3_get_current_time() as i64 * 1000 + UNIX_EPOCH 805 | } else { 806 | now 807 | }; 808 | 809 | *p = now; 810 | ffi::SQLITE_OK 811 | } 812 | 813 | #[cfg(feature = "syscall")] 814 | pub unsafe extern "C" fn set_system_call( 815 | p_vfs: *mut ffi::sqlite3_vfs, 816 | z_name: *const ::std::os::raw::c_char, 817 | p_new_func: ffi::sqlite3_syscall_ptr, 818 | ) -> ::std::os::raw::c_int { 819 | let state = match vfs_state::(p_vfs) { 820 | Ok(state) => state, 821 | Err(_) => return ffi::SQLITE_ERROR, 822 | }; 823 | 824 | if let Some(set_system_call) = state.parent_vfs.as_ref().and_then(|v| v.xSetSystemCall) { 825 | return set_system_call(state.parent_vfs, z_name, p_new_func); 826 | } 827 | 828 | ffi::SQLITE_ERROR 829 | } 830 | 831 | #[cfg(feature = "syscall")] 832 | pub unsafe extern "C" fn get_system_call( 833 | p_vfs: *mut ffi::sqlite3_vfs, 834 | z_name: *const ::std::os::raw::c_char, 835 | ) -> ffi::sqlite3_syscall_ptr { 836 | let state = match vfs_state::(p_vfs) { 837 | Ok(state) => state, 838 | Err(_) => return None, 839 | }; 840 | 841 | if let Some(get_system_call) = state.parent_vfs.as_ref().and_then(|v| v.xGetSystemCall) { 842 | return get_system_call(state.parent_vfs, z_name); 843 | } 844 | 845 | None 846 | } 847 | 848 | #[cfg(feature = "syscall")] 849 | pub unsafe extern "C" fn next_system_call( 850 | p_vfs: *mut ffi::sqlite3_vfs, 851 | z_name: *const ::std::os::raw::c_char, 852 | ) -> *const ::std::os::raw::c_char { 853 | let state = match vfs_state::(p_vfs) { 854 | Ok(state) => state, 855 | Err(_) => return std::ptr::null(), 856 | }; 857 | 858 | if let Some(next_system_call) = state.parent_vfs.as_ref().and_then(|v| v.xNextSystemCall) { 859 | return next_system_call(state.parent_vfs, z_name); 860 | } 861 | 862 | std::ptr::null() 863 | } 864 | 865 | pub unsafe extern "C" fn get_last_error( 866 | p_vfs: *mut ffi::sqlite3_vfs, 867 | n_byte: c_int, 868 | z_err_msg: *mut c_char, 869 | ) -> c_int { 870 | let state = match vfs_state::(p_vfs) { 871 | Ok(state) => state, 872 | Err(_) => return ffi::SQLITE_ERROR, 873 | }; 874 | if let Some((eno, err)) = state.last_error.lock().unwrap().as_ref() { 875 | let msg = match CString::new(err.to_string()) { 876 | Ok(msg) => msg, 877 | Err(_) => return ffi::SQLITE_ERROR, 878 | }; 879 | 880 | let msg = msg.to_bytes_with_nul(); 881 | if msg.len() > n_byte as usize { 882 | return ffi::SQLITE_ERROR; 883 | } 884 | let out = slice::from_raw_parts_mut(z_err_msg as *mut u8, msg.len()); 885 | out.copy_from_slice(msg); 886 | 887 | return *eno; 888 | } 889 | ffi::SQLITE_OK 890 | } 891 | } 892 | 893 | mod io { 894 | use std::collections::hash_map::Entry; 895 | use std::mem; 896 | 897 | use super::*; 898 | use wip::WalIndex; 899 | 900 | /// Close a file. 901 | pub unsafe extern "C" fn close( 902 | p_file: *mut ffi::sqlite3_file, 903 | ) -> c_int { 904 | if let Some(f) = (p_file as *mut FileState).as_mut() { 905 | let ext = f.ext.assume_init_mut(); 906 | if ext.delete_on_close { 907 | if let Err(err) = ext.vfs.delete(&ext.db_name) { 908 | return ext.set_last_error(ffi::SQLITE_DELETE, err); 909 | } 910 | } 911 | 912 | let ext = mem::replace(&mut f.ext, MaybeUninit::uninit()); 913 | let ext = ext.assume_init(); // extract the value to drop it 914 | log::trace!("[{}] close ({})", ext.id, ext.db_name); 915 | } 916 | 917 | #[cfg(feature = "sqlite_test")] 918 | ffi::sqlite3_dec_open_file_count(); 919 | 920 | ffi::SQLITE_OK 921 | } 922 | 923 | /// Read data from a file. 924 | pub unsafe extern "C" fn read( 925 | p_file: *mut ffi::sqlite3_file, 926 | z_buf: *mut c_void, 927 | i_amt: c_int, 928 | i_ofst: ffi::sqlite3_int64, 929 | ) -> c_int { 930 | let state = match file_state::(p_file) { 931 | Ok(f) => f, 932 | Err(_) => return ffi::SQLITE_IOERR_CLOSE, 933 | }; 934 | log::trace!( 935 | "[{}] read offset={} len={} ({})", 936 | state.id, 937 | i_ofst, 938 | i_amt, 939 | state.db_name 940 | ); 941 | 942 | let out = slice::from_raw_parts_mut(z_buf as *mut u8, i_amt as usize); 943 | if let Err(err) = state.file.read_exact_at(out, i_ofst as u64) { 944 | let kind = err.kind(); 945 | if kind == ErrorKind::UnexpectedEof { 946 | return ffi::SQLITE_IOERR_SHORT_READ; 947 | } else { 948 | return state.set_last_error(ffi::SQLITE_IOERR_READ, err); 949 | } 950 | } 951 | 952 | ffi::SQLITE_OK 953 | } 954 | 955 | /// Write data to a file. 956 | pub unsafe extern "C" fn write( 957 | p_file: *mut ffi::sqlite3_file, 958 | z: *const c_void, 959 | i_amt: c_int, 960 | i_ofst: ffi::sqlite3_int64, 961 | ) -> c_int { 962 | let state = match file_state::(p_file) { 963 | Ok(f) => f, 964 | Err(_) => return ffi::SQLITE_IOERR_WRITE, 965 | }; 966 | log::trace!( 967 | "[{}] write offset={} len={} ({})", 968 | state.id, 969 | i_ofst, 970 | i_amt, 971 | state.db_name 972 | ); 973 | 974 | let data = slice::from_raw_parts(z as *mut u8, i_amt as usize); 975 | let result = state.file.write_all_at(data, i_ofst as u64); 976 | 977 | #[cfg(feature = "sqlite_test")] 978 | let result = if simulate_io_error() { 979 | Err(ErrorKind::Other.into()) 980 | } else { 981 | result 982 | }; 983 | 984 | #[cfg(feature = "sqlite_test")] 985 | let result = if simulate_diskfull_error() { 986 | Err(ErrorKind::WriteZero.into()) 987 | } else { 988 | result 989 | }; 990 | 991 | match result { 992 | Ok(_) => {} 993 | Err(err) if err.kind() == ErrorKind::WriteZero => { 994 | return ffi::SQLITE_FULL; 995 | } 996 | Err(err) => return state.set_last_error(ffi::SQLITE_IOERR_WRITE, err), 997 | } 998 | 999 | ffi::SQLITE_OK 1000 | } 1001 | 1002 | /// Truncate a file. 1003 | pub unsafe extern "C" fn truncate( 1004 | p_file: *mut ffi::sqlite3_file, 1005 | size: ffi::sqlite3_int64, 1006 | ) -> c_int { 1007 | let state = match file_state::(p_file) { 1008 | Ok(f) => f, 1009 | Err(_) => return ffi::SQLITE_IOERR_FSYNC, 1010 | }; 1011 | 1012 | let size: u64 = if let Some(chunk_size) = state.chunk_size { 1013 | (((size as usize + chunk_size - 1) / chunk_size) * chunk_size) as u64 1014 | } else { 1015 | size as u64 1016 | }; 1017 | 1018 | log::trace!("[{}] truncate size={} ({})", state.id, size, state.db_name); 1019 | 1020 | // #[cfg(feature = "sqlite_test")] 1021 | // if simulate_io_error() { 1022 | // return ffi::SQLITE_IOERR_TRUNCATE; 1023 | // } 1024 | 1025 | if let Err(err) = state.file.set_len(size) { 1026 | return state.set_last_error(ffi::SQLITE_IOERR_TRUNCATE, err); 1027 | } 1028 | 1029 | ffi::SQLITE_OK 1030 | } 1031 | 1032 | /// Persist changes to a file. 1033 | pub unsafe extern "C" fn sync( 1034 | p_file: *mut ffi::sqlite3_file, 1035 | flags: c_int, 1036 | ) -> c_int { 1037 | let state = match file_state::(p_file) { 1038 | Ok(f) => f, 1039 | Err(_) => return ffi::SQLITE_IOERR_FSYNC, 1040 | }; 1041 | log::trace!("[{}] sync ({})", state.id, state.db_name); 1042 | 1043 | #[cfg(feature = "sqlite_test")] 1044 | { 1045 | let is_full_sync = flags & 0x0F == ffi::SQLITE_SYNC_FULL; 1046 | if is_full_sync { 1047 | ffi::sqlite3_inc_fullsync_count(); 1048 | } 1049 | ffi::sqlite3_inc_sync_count(); 1050 | } 1051 | 1052 | if let Err(err) = state.file.sync(flags & ffi::SQLITE_SYNC_DATAONLY > 0) { 1053 | return state.set_last_error(ffi::SQLITE_IOERR_FSYNC, err); 1054 | } 1055 | 1056 | // #[cfg(feature = "sqlite_test")] 1057 | // if simulate_io_error() { 1058 | // return ffi::SQLITE_ERROR; 1059 | // } 1060 | 1061 | ffi::SQLITE_OK 1062 | } 1063 | 1064 | /// Return the current file-size of a file. 1065 | pub unsafe extern "C" fn file_size( 1066 | p_file: *mut ffi::sqlite3_file, 1067 | p_size: *mut ffi::sqlite3_int64, 1068 | ) -> c_int { 1069 | let state = match file_state::(p_file) { 1070 | Ok(f) => f, 1071 | Err(_) => return ffi::SQLITE_IOERR_FSTAT, 1072 | }; 1073 | log::trace!("[{}] file_size ({})", state.id, state.db_name); 1074 | 1075 | if let Err(err) = state.file.size().and_then(|n| { 1076 | let p_size: &mut ffi::sqlite3_int64 = p_size.as_mut().ok_or_else(null_ptr_error)?; 1077 | *p_size = n as ffi::sqlite3_int64; 1078 | Ok(()) 1079 | }) { 1080 | return state.set_last_error(ffi::SQLITE_IOERR_FSTAT, err); 1081 | } 1082 | 1083 | // #[cfg(feature = "sqlite_test")] 1084 | // if simulate_io_error() { 1085 | // return ffi::SQLITE_ERROR; 1086 | // } 1087 | 1088 | ffi::SQLITE_OK 1089 | } 1090 | 1091 | /// Lock a file. 1092 | pub unsafe extern "C" fn lock( 1093 | p_file: *mut ffi::sqlite3_file, 1094 | e_lock: c_int, 1095 | ) -> c_int { 1096 | let state = match file_state::(p_file) { 1097 | Ok(f) => f, 1098 | Err(_) => return ffi::SQLITE_IOERR_LOCK, 1099 | }; 1100 | log::trace!("[{}] lock ({})", state.id, state.db_name); 1101 | 1102 | let lock = match LockKind::from_i32(e_lock) { 1103 | Some(lock) => lock, 1104 | None => return ffi::SQLITE_IOERR_LOCK, 1105 | }; 1106 | match state.file.lock(lock) { 1107 | Ok(true) => { 1108 | state.has_exclusive_lock = lock == LockKind::Exclusive; 1109 | log::trace!("[{}] lock={:?} ({})", state.id, lock, state.db_name); 1110 | 1111 | // If just acquired a exclusive database lock while not having any exclusive lock 1112 | // on the wal index, make sure the wal index is up to date. 1113 | if state.has_exclusive_lock { 1114 | let has_exclusive_wal_index = state 1115 | .wal_index_locks 1116 | .iter() 1117 | .any(|(_, lock)| *lock == wip::WalIndexLock::Exclusive); 1118 | 1119 | if !has_exclusive_wal_index { 1120 | log::trace!( 1121 | "[{}] acquired exclusive db lock, pulling wal index changes", 1122 | state.id, 1123 | ); 1124 | 1125 | if let Some((wal_index, _)) = state.wal_index.as_mut() { 1126 | for (region, data) in &mut state.wal_index_regions { 1127 | if let Err(err) = wal_index.pull(*region as u32, data) { 1128 | log::error!( 1129 | "[{}] pulling wal index changes failed: {}", 1130 | state.id, 1131 | err 1132 | ) 1133 | } 1134 | } 1135 | } 1136 | } 1137 | } 1138 | 1139 | ffi::SQLITE_OK 1140 | } 1141 | Ok(false) => { 1142 | log::trace!( 1143 | "[{}] busy (denied {:?}) ({})", 1144 | state.id, 1145 | lock, 1146 | state.db_name 1147 | ); 1148 | ffi::SQLITE_BUSY 1149 | } 1150 | Err(err) => state.set_last_error(ffi::SQLITE_IOERR_LOCK, err), 1151 | } 1152 | } 1153 | 1154 | /// Unlock a file. 1155 | pub unsafe extern "C" fn unlock( 1156 | p_file: *mut ffi::sqlite3_file, 1157 | e_lock: c_int, 1158 | ) -> c_int { 1159 | let state = match file_state::(p_file) { 1160 | Ok(f) => f, 1161 | Err(_) => return ffi::SQLITE_IOERR_UNLOCK, 1162 | }; 1163 | log::trace!("[{}] unlock ({})", state.id, state.db_name); 1164 | 1165 | let lock = match LockKind::from_i32(e_lock) { 1166 | Some(lock) => lock, 1167 | None => return ffi::SQLITE_IOERR_UNLOCK, 1168 | }; 1169 | match state.file.unlock(lock) { 1170 | Ok(true) => { 1171 | state.has_exclusive_lock = lock == LockKind::Exclusive; 1172 | log::trace!("[{}] unlock={:?} ({})", state.id, lock, state.db_name); 1173 | ffi::SQLITE_OK 1174 | } 1175 | Ok(false) => ffi::SQLITE_BUSY, 1176 | Err(err) => state.set_last_error(ffi::SQLITE_IOERR_UNLOCK, err), 1177 | } 1178 | } 1179 | 1180 | /// Check if another file-handle holds a [LockKind::Reserved] lock on a file. 1181 | pub unsafe extern "C" fn check_reserved_lock( 1182 | p_file: *mut ffi::sqlite3_file, 1183 | p_res_out: *mut c_int, 1184 | ) -> c_int { 1185 | let state = match file_state::(p_file) { 1186 | Ok(f) => f, 1187 | Err(_) => return ffi::SQLITE_IOERR_CHECKRESERVEDLOCK, 1188 | }; 1189 | log::trace!("[{}] check_reserved_lock ({})", state.id, state.db_name); 1190 | 1191 | // #[cfg(feature = "sqlite_test")] 1192 | // if simulate_io_error() { 1193 | // return ffi::SQLITE_IOERR_CHECKRESERVEDLOCK; 1194 | // } 1195 | 1196 | if let Err(err) = state.file.reserved().and_then(|is_reserved| { 1197 | let p_res_out: &mut c_int = p_res_out.as_mut().ok_or_else(null_ptr_error)?; 1198 | *p_res_out = is_reserved as c_int; 1199 | Ok(()) 1200 | }) { 1201 | return state.set_last_error(ffi::SQLITE_IOERR_UNLOCK, err); 1202 | } 1203 | 1204 | ffi::SQLITE_OK 1205 | } 1206 | 1207 | /// File control method. For custom operations on a mem-file. 1208 | pub unsafe extern "C" fn file_control( 1209 | p_file: *mut ffi::sqlite3_file, 1210 | op: c_int, 1211 | p_arg: *mut c_void, 1212 | ) -> c_int { 1213 | let state = match file_state::(p_file) { 1214 | Ok(f) => f, 1215 | Err(_) => return ffi::SQLITE_NOTFOUND, 1216 | }; 1217 | log::trace!("[{}] file_control op={} ({})", state.id, op, state.db_name); 1218 | 1219 | // Docs: https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html 1220 | match op { 1221 | // The following op codes are alreay handled by sqlite before, so no need to handle them 1222 | // in a custom VFS. 1223 | ffi::SQLITE_FCNTL_FILE_POINTER 1224 | | ffi::SQLITE_FCNTL_VFS_POINTER 1225 | | ffi::SQLITE_FCNTL_JOURNAL_POINTER 1226 | | ffi::SQLITE_FCNTL_DATA_VERSION 1227 | | ffi::SQLITE_FCNTL_RESERVE_BYTES => ffi::SQLITE_NOTFOUND, 1228 | 1229 | // The following op codes are no longer used and thus ignored. 1230 | ffi::SQLITE_FCNTL_SYNC_OMITTED => ffi::SQLITE_NOTFOUND, 1231 | 1232 | // Used for debugging. Write current state of the lock into (int)pArg. 1233 | ffi::SQLITE_FCNTL_LOCKSTATE => match state.file.current_lock() { 1234 | Ok(lock) => { 1235 | if let Some(p_arg) = (p_arg as *mut i32).as_mut() { 1236 | *p_arg = lock as i32; 1237 | } 1238 | ffi::SQLITE_OK 1239 | } 1240 | Err(err) => state.set_last_error(ffi::SQLITE_ERROR, err), 1241 | }, 1242 | 1243 | // Relevant for proxy-type locking. Not implemented. 1244 | ffi::SQLITE_FCNTL_GET_LOCKPROXYFILE | ffi::SQLITE_FCNTL_SET_LOCKPROXYFILE => { 1245 | ffi::SQLITE_NOTFOUND 1246 | } 1247 | 1248 | // Write last error number into (int)pArg. 1249 | ffi::SQLITE_FCNTL_LAST_ERRNO => { 1250 | if let Some(p_arg) = (p_arg as *mut i32).as_mut() { 1251 | *p_arg = state.last_errno; 1252 | } 1253 | ffi::SQLITE_OK 1254 | } 1255 | 1256 | // Give the VFS layer a hint of how large the database file will grow to be during the 1257 | // current transaction. 1258 | ffi::SQLITE_FCNTL_SIZE_HINT => { 1259 | let size_hint = match (p_arg as *mut i64) 1260 | .as_ref() 1261 | .cloned() 1262 | .and_then(|s| u64::try_from(s).ok()) 1263 | { 1264 | Some(chunk_size) => chunk_size, 1265 | None => { 1266 | return state.set_last_error( 1267 | ffi::SQLITE_NOTFOUND, 1268 | std::io::Error::new(ErrorKind::Other, "expect size hint arg"), 1269 | ); 1270 | } 1271 | }; 1272 | 1273 | // #[cfg(feature = "sqlite_test")] 1274 | // let _benign = simulate_io_error_benign(); 1275 | 1276 | let current = match state.file.size() { 1277 | Ok(size) => size, 1278 | Err(err) => return state.set_last_error(ffi::SQLITE_ERROR, err), 1279 | }; 1280 | 1281 | if current > size_hint { 1282 | return ffi::SQLITE_OK; 1283 | } 1284 | 1285 | if let Some(chunk_size) = state.chunk_size { 1286 | let chunk_size = chunk_size as u64; 1287 | let size = ((size_hint + chunk_size - 1) / chunk_size) * chunk_size; 1288 | if let Err(err) = state.file.set_len(size) { 1289 | return state.set_last_error(ffi::SQLITE_IOERR_TRUNCATE, err); 1290 | } 1291 | } else if let Err(err) = state.file.set_len(size_hint) { 1292 | return state.set_last_error(ffi::SQLITE_IOERR_TRUNCATE, err); 1293 | } 1294 | 1295 | // #[cfg(feature = "sqlite_test")] 1296 | // if simulate_io_error() { 1297 | // return ffi::SQLITE_IOERR_TRUNCATE; 1298 | // } 1299 | 1300 | ffi::SQLITE_OK 1301 | } 1302 | 1303 | // Request that the VFS extends and truncates the database file in chunks of a size 1304 | // specified by the user. Return an error as this is not forwarded to the [Vfs] trait 1305 | // right now. 1306 | ffi::SQLITE_FCNTL_CHUNK_SIZE => { 1307 | let chunk_size = match (p_arg as *mut i32) 1308 | .as_ref() 1309 | .cloned() 1310 | .and_then(|s| usize::try_from(s).ok()) 1311 | { 1312 | Some(chunk_size) => chunk_size, 1313 | None => { 1314 | return state.set_last_error( 1315 | ffi::SQLITE_NOTFOUND, 1316 | std::io::Error::new(ErrorKind::Other, "expect chunk_size arg"), 1317 | ); 1318 | } 1319 | }; 1320 | 1321 | if let Err(err) = state.file.set_chunk_size(chunk_size) { 1322 | return state.set_last_error(ffi::SQLITE_ERROR, err); 1323 | } 1324 | 1325 | state.chunk_size = Some(chunk_size); 1326 | 1327 | ffi::SQLITE_OK 1328 | } 1329 | 1330 | // Configure automatic retry counts and intervals for certain disk I/O operations for 1331 | // the windows VFS in order to provide robustness in the presence of anti-virus 1332 | // programs. Not implemented. 1333 | ffi::SQLITE_FCNTL_WIN32_AV_RETRY => ffi::SQLITE_NOTFOUND, 1334 | 1335 | // Enable or disable the persistent WAL setting. 1336 | ffi::SQLITE_FCNTL_PERSIST_WAL => { 1337 | if let Some(p_arg) = (p_arg as *mut i32).as_mut() { 1338 | if *p_arg < 0 { 1339 | // query current setting 1340 | *p_arg = state.persist_wal as i32; 1341 | } else { 1342 | state.persist_wal = *p_arg == 1; 1343 | } 1344 | }; 1345 | 1346 | ffi::SQLITE_OK 1347 | } 1348 | 1349 | // Indicate that, unless it is rolled back for some reason, the entire database file 1350 | // will be overwritten by the current transaction. Not implemented. 1351 | ffi::SQLITE_FCNTL_OVERWRITE => ffi::SQLITE_NOTFOUND, 1352 | 1353 | // Used to obtain the names of all VFSes in the VFS stack. 1354 | ffi::SQLITE_FCNTL_VFSNAME => { 1355 | if let Some(p_arg) = (p_arg as *mut *const c_char).as_mut() { 1356 | let name = ManuallyDrop::new(state.vfs_name.clone()); 1357 | *p_arg = name.as_ptr(); 1358 | }; 1359 | 1360 | ffi::SQLITE_OK 1361 | } 1362 | 1363 | // Set or query the persistent "powersafe-overwrite" or "PSOW" setting. 1364 | ffi::SQLITE_FCNTL_POWERSAFE_OVERWRITE => { 1365 | if let Some(p_arg) = (p_arg as *mut i32).as_mut() { 1366 | if *p_arg < 0 { 1367 | // query current setting 1368 | *p_arg = state.powersafe_overwrite as i32; 1369 | } else { 1370 | state.powersafe_overwrite = *p_arg == 1; 1371 | } 1372 | }; 1373 | 1374 | ffi::SQLITE_OK 1375 | } 1376 | 1377 | // Optionally intercept PRAGMA statements. Always fall back to normal pragma processing. 1378 | ffi::SQLITE_FCNTL_PRAGMA => ffi::SQLITE_NOTFOUND, 1379 | 1380 | // May be invoked by SQLite on the database file handle shortly after it is opened in 1381 | // order to provide a custom VFS with access to the connection's busy-handler callback. 1382 | // Not implemented. 1383 | ffi::SQLITE_FCNTL_BUSYHANDLER => ffi::SQLITE_NOTFOUND, 1384 | 1385 | // Generate a temporary filename. Not implemented. 1386 | ffi::SQLITE_FCNTL_TEMPFILENAME => { 1387 | if let Some(p_arg) = (p_arg as *mut *const c_char).as_mut() { 1388 | let name = state.vfs.temporary_name(); 1389 | // unwrap() is fine as os strings are an arbitrary sequences of non-zero bytes 1390 | let name = CString::new(name.as_bytes()).unwrap(); 1391 | let name = ManuallyDrop::new(name); 1392 | *p_arg = name.as_ptr(); 1393 | }; 1394 | 1395 | ffi::SQLITE_OK 1396 | } 1397 | 1398 | // Query or set the maximum number of bytes that will be used for memory-mapped I/O. 1399 | // Not implemented. 1400 | ffi::SQLITE_FCNTL_MMAP_SIZE => ffi::SQLITE_NOTFOUND, 1401 | 1402 | // Advisory information to the VFS about what the higher layers of the SQLite stack are 1403 | // doing. 1404 | ffi::SQLITE_FCNTL_TRACE => { 1405 | let trace = CStr::from_ptr(p_arg as *const c_char); 1406 | log::trace!("{}", trace.to_string_lossy()); 1407 | ffi::SQLITE_OK 1408 | } 1409 | 1410 | // Check whether or not the file has been renamed, moved, or deleted since it was first 1411 | // opened. 1412 | ffi::SQLITE_FCNTL_HAS_MOVED => match state.file.moved() { 1413 | Ok(moved) => { 1414 | if let Some(p_arg) = (p_arg as *mut i32).as_mut() { 1415 | *p_arg = moved as i32; 1416 | } 1417 | ffi::SQLITE_OK 1418 | } 1419 | Err(err) => state.set_last_error(ffi::SQLITE_ERROR, err), 1420 | }, 1421 | 1422 | // Sent to the VFS immediately before the xSync method is invoked on a database file 1423 | // descriptor. Silently ignored. 1424 | ffi::SQLITE_FCNTL_SYNC => ffi::SQLITE_OK, 1425 | 1426 | // Sent to the VFS after a transaction has been committed immediately but before the 1427 | // database is unlocked. Silently ignored. 1428 | ffi::SQLITE_FCNTL_COMMIT_PHASETWO => ffi::SQLITE_OK, 1429 | 1430 | // Used for debugging. Swap the file handle with the one pointed to by the pArg 1431 | // argument. This capability is used during testing and only needs to be supported when 1432 | // SQLITE_TEST is defined. Not implemented. 1433 | ffi::SQLITE_FCNTL_WIN32_SET_HANDLE => ffi::SQLITE_NOTFOUND, 1434 | 1435 | // Signal to the VFS layer that it might be advantageous to block on the next WAL lock 1436 | // if the lock is not immediately available. The WAL subsystem issues this signal during 1437 | // rare circumstances in order to fix a problem with priority inversion. 1438 | // Not implemented. 1439 | ffi::SQLITE_FCNTL_WAL_BLOCK => ffi::SQLITE_NOTFOUND, 1440 | 1441 | // Implemented by zipvfs only. 1442 | ffi::SQLITE_FCNTL_ZIPVFS => ffi::SQLITE_NOTFOUND, 1443 | 1444 | // Implemented by the special VFS used by the RBU extension only. 1445 | ffi::SQLITE_FCNTL_RBU => ffi::SQLITE_NOTFOUND, 1446 | 1447 | // Obtain the underlying native file handle associated with a file handle. 1448 | // Not implemented. 1449 | ffi::SQLITE_FCNTL_WIN32_GET_HANDLE => ffi::SQLITE_NOTFOUND, 1450 | 1451 | // Usage is not documented. Not implemented. 1452 | ffi::SQLITE_FCNTL_PDB => ffi::SQLITE_NOTFOUND, 1453 | 1454 | // Used for "batch write mode". Not supported. 1455 | ffi::SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 1456 | | ffi::SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 1457 | | ffi::SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE => ffi::SQLITE_NOTFOUND, 1458 | 1459 | // Configure a VFS to block for up to M milliseconds before failing when attempting to 1460 | // obtain a file lock using the xLock or xShmLock methods of the VFS. Not implemented. 1461 | ffi::SQLITE_FCNTL_LOCK_TIMEOUT => ffi::SQLITE_NOTFOUND, 1462 | 1463 | // Used by in-memory VFS. 1464 | ffi::SQLITE_FCNTL_SIZE_LIMIT => ffi::SQLITE_NOTFOUND, 1465 | 1466 | // Invoked from within a checkpoint in wal mode after the client has finished copying 1467 | // pages from the wal file to the database file, but before the *-shm file is updated to 1468 | // record the fact that the pages have been checkpointed. Silently ignored. 1469 | ffi::SQLITE_FCNTL_CKPT_DONE => ffi::SQLITE_OK, 1470 | 1471 | // Invoked from within a checkpoint in wal mode before the client starts to copy pages 1472 | // from the wal file to the database file. Silently ignored. 1473 | ffi::SQLITE_FCNTL_CKPT_START => ffi::SQLITE_OK, 1474 | 1475 | // Detect whether or not there is a database client in another process with a wal-mode 1476 | // transaction open on the database or not. Not implemented because it is a 1477 | // unix-specific feature. 1478 | ffi::SQLITE_FCNTL_EXTERNAL_READER => ffi::SQLITE_NOTFOUND, 1479 | 1480 | // Unknown use-case. Ignored. 1481 | ffi::SQLITE_FCNTL_CKSM_FILE => ffi::SQLITE_NOTFOUND, 1482 | 1483 | _ => ffi::SQLITE_NOTFOUND, 1484 | } 1485 | } 1486 | 1487 | /// Return the sector-size in bytes for a file. 1488 | pub unsafe extern "C" fn sector_size(_p_file: *mut ffi::sqlite3_file) -> c_int { 1489 | log::trace!("sector_size"); 1490 | 1491 | 1024 1492 | } 1493 | 1494 | /// Return the device characteristic flags supported by a file. 1495 | pub unsafe extern "C" fn device_characteristics( 1496 | p_file: *mut ffi::sqlite3_file, 1497 | ) -> c_int { 1498 | let state = match file_state::(p_file) { 1499 | Ok(f) => f, 1500 | Err(_) => return ffi::SQLITE_IOERR_SHMMAP, 1501 | }; 1502 | 1503 | log::trace!("[{}] device_characteristics", state.id,); 1504 | 1505 | // The following characteristics are needed to match the expected behavior of the tests. 1506 | 1507 | // after reboot following a crash or power loss, the only bytes in a file that were written 1508 | // at the application level might have changed and that adjacent bytes, even bytes within 1509 | // the same sector are guaranteed to be unchanged 1510 | if state.powersafe_overwrite { 1511 | ffi::SQLITE_IOCAP_POWERSAFE_OVERWRITE 1512 | } else { 1513 | 0 1514 | } 1515 | } 1516 | 1517 | /// Create a shared memory file mapping. 1518 | pub unsafe extern "C" fn shm_map( 1519 | p_file: *mut ffi::sqlite3_file, 1520 | region_ix: i32, 1521 | region_size: i32, 1522 | b_extend: i32, 1523 | pp: *mut *mut c_void, 1524 | ) -> i32 { 1525 | let state = match file_state::(p_file) { 1526 | Ok(f) => f, 1527 | Err(_) => return ffi::SQLITE_IOERR_SHMMAP, 1528 | }; 1529 | log::trace!( 1530 | "[{}] shm_map pg={} sz={} extend={} ({})", 1531 | state.id, 1532 | region_ix, 1533 | region_size, 1534 | b_extend, 1535 | state.db_name 1536 | ); 1537 | 1538 | if !F::WalIndex::enabled() { 1539 | return ffi::SQLITE_IOERR_SHMLOCK; 1540 | } 1541 | 1542 | if region_size != 32768 { 1543 | return state.set_last_error( 1544 | ffi::SQLITE_IOERR_SHMMAP, 1545 | std::io::Error::new( 1546 | ErrorKind::Other, 1547 | format!( 1548 | "encountered region size other than 32kB; got {}", 1549 | region_size 1550 | ), 1551 | ), 1552 | ); 1553 | } 1554 | 1555 | let (wal_index, readonly) = match state.wal_index.as_mut() { 1556 | Some((wal_index, readonly)) => (wal_index, *readonly), 1557 | None => { 1558 | let (wal_index, readonly) = state.wal_index.get_or_insert( 1559 | match state 1560 | .file 1561 | .wal_index(false) 1562 | .map(|wal_index| (wal_index, false)) 1563 | .or_else(|err| { 1564 | if err.kind() == ErrorKind::PermissionDenied { 1565 | // Try again as readonly 1566 | state 1567 | .file 1568 | .wal_index(true) 1569 | .map(|wal_index| (wal_index, true)) 1570 | .map_err(|_| err) 1571 | } else { 1572 | Err(err) 1573 | } 1574 | }) { 1575 | Ok((wal_index, readonly)) => (wal_index, readonly), 1576 | Err(err) => { 1577 | return state.set_last_error(ffi::SQLITE_IOERR_SHMMAP, err); 1578 | } 1579 | }, 1580 | ); 1581 | (wal_index, *readonly) 1582 | } 1583 | }; 1584 | 1585 | let entry = state.wal_index_regions.entry(region_ix as u32); 1586 | match entry { 1587 | Entry::Occupied(mut entry) => { 1588 | *pp = entry.get_mut().as_mut_ptr() as *mut c_void; 1589 | } 1590 | Entry::Vacant(entry) => { 1591 | let mut m = match wal_index.map(region_ix as u32) { 1592 | Ok(m) => Box::pin(m), 1593 | Err(err) => { 1594 | return state.set_last_error(ffi::SQLITE_IOERR_SHMMAP, err); 1595 | } 1596 | }; 1597 | *pp = m.as_mut_ptr() as *mut c_void; 1598 | entry.insert(m); 1599 | } 1600 | } 1601 | 1602 | if readonly { 1603 | ffi::SQLITE_READONLY 1604 | } else { 1605 | ffi::SQLITE_OK 1606 | } 1607 | } 1608 | 1609 | /// Perform locking on a shared-memory segment. 1610 | pub unsafe extern "C" fn shm_lock( 1611 | p_file: *mut ffi::sqlite3_file, 1612 | offset: i32, 1613 | n: i32, 1614 | flags: i32, 1615 | ) -> i32 { 1616 | let state = match file_state::(p_file) { 1617 | Ok(f) => f, 1618 | Err(_) => return ffi::SQLITE_IOERR_SHMMAP, 1619 | }; 1620 | let locking = flags & ffi::SQLITE_SHM_LOCK > 0; 1621 | let exclusive = flags & ffi::SQLITE_SHM_EXCLUSIVE > 0; 1622 | log::trace!( 1623 | "[{}] shm_lock offset={} n={} lock={} exclusive={} (flags={}) ({})", 1624 | state.id, 1625 | offset, 1626 | n, 1627 | locking, 1628 | exclusive, 1629 | flags, 1630 | state.db_name 1631 | ); 1632 | 1633 | let range = offset as u8..(offset + n) as u8; 1634 | let lock = match (locking, exclusive) { 1635 | (true, true) => wip::WalIndexLock::Exclusive, 1636 | (true, false) => wip::WalIndexLock::Shared, 1637 | (false, _) => wip::WalIndexLock::None, 1638 | }; 1639 | 1640 | let (wal_index, readonly) = match state.wal_index.as_mut() { 1641 | Some((wal_index, readonly)) => (wal_index, *readonly), 1642 | None => { 1643 | return state.set_last_error( 1644 | ffi::SQLITE_IOERR_SHMLOCK, 1645 | std::io::Error::new( 1646 | ErrorKind::Other, 1647 | "trying to lock wal index, which isn't created yet", 1648 | ), 1649 | ) 1650 | } 1651 | }; 1652 | 1653 | if locking { 1654 | let has_exclusive = state 1655 | .wal_index_locks 1656 | .iter() 1657 | .any(|(_, lock)| *lock == wip::WalIndexLock::Exclusive); 1658 | 1659 | if !has_exclusive { 1660 | log::trace!( 1661 | "[{}] does not have wal index write lock, pulling changes", 1662 | state.id 1663 | ); 1664 | for (region, data) in &mut state.wal_index_regions { 1665 | if let Err(err) = wal_index.pull(*region as u32, data) { 1666 | return state.set_last_error(ffi::SQLITE_IOERR_SHMLOCK, err); 1667 | } 1668 | } 1669 | } 1670 | } else { 1671 | let releases_any_exclusive = state.wal_index_locks.iter().any(|(region, lock)| { 1672 | *lock == wip::WalIndexLock::Exclusive && range.contains(region) 1673 | }); 1674 | 1675 | // push index changes when moving from any exclusive lock to no exclusive locks 1676 | if releases_any_exclusive && !readonly { 1677 | log::trace!( 1678 | "[{}] releasing an exclusive lock, pushing wal index changes", 1679 | state.id, 1680 | ); 1681 | for (region, data) in &mut state.wal_index_regions { 1682 | if let Err(err) = wal_index.push(*region as u32, data) { 1683 | return state.set_last_error(ffi::SQLITE_IOERR_SHMLOCK, err); 1684 | } 1685 | } 1686 | } 1687 | } 1688 | 1689 | match wal_index.lock(range.clone(), lock) { 1690 | Ok(true) => { 1691 | for region in range { 1692 | state.wal_index_locks.insert(region, lock); 1693 | } 1694 | ffi::SQLITE_OK 1695 | } 1696 | Ok(false) => ffi::SQLITE_BUSY, 1697 | Err(err) => state.set_last_error(ffi::SQLITE_IOERR_SHMLOCK, err), 1698 | } 1699 | } 1700 | 1701 | /// Memory barrier operation on shared memory. 1702 | pub unsafe extern "C" fn shm_barrier(p_file: *mut ffi::sqlite3_file) { 1703 | let state = match file_state::(p_file) { 1704 | Ok(f) => f, 1705 | Err(_) => return, 1706 | }; 1707 | log::trace!("[{}] shm_barrier ({})", state.id, state.db_name); 1708 | 1709 | let (wal_index, readonly) = if let Some((wal_index, readonly)) = state.wal_index.as_mut() { 1710 | (wal_index, *readonly) 1711 | } else { 1712 | return; 1713 | }; 1714 | 1715 | if state.has_exclusive_lock && !readonly { 1716 | log::trace!( 1717 | "[{}] has exclusive db lock, pushing wal index changes", 1718 | state.id, 1719 | ); 1720 | for (region, data) in &mut state.wal_index_regions { 1721 | if let Err(err) = wal_index.push(*region as u32, data) { 1722 | log::error!("[{}] pushing wal index changes failed: {}", state.id, err) 1723 | } 1724 | } 1725 | 1726 | return; 1727 | } 1728 | 1729 | let has_exclusive = state 1730 | .wal_index_locks 1731 | .iter() 1732 | .any(|(_, lock)| *lock == wip::WalIndexLock::Exclusive); 1733 | 1734 | if !has_exclusive { 1735 | log::trace!( 1736 | "[{}] does not have wal index write lock, pulling changes", 1737 | state.id 1738 | ); 1739 | for (region, data) in &mut state.wal_index_regions { 1740 | if let Err(err) = wal_index.pull(*region as u32, data) { 1741 | log::error!("[{}] pulling wal index changes failed: {}", state.id, err) 1742 | } 1743 | } 1744 | } 1745 | } 1746 | 1747 | /// Unmap a shared memory segment. 1748 | pub unsafe extern "C" fn shm_unmap( 1749 | p_file: *mut ffi::sqlite3_file, 1750 | delete_flags: i32, 1751 | ) -> i32 { 1752 | let state = match file_state::(p_file) { 1753 | Ok(f) => f, 1754 | Err(_) => return ffi::SQLITE_IOERR_SHMMAP, 1755 | }; 1756 | log::trace!( 1757 | "[{}] shm_unmap delete={} ({})", 1758 | state.id, 1759 | delete_flags == 1, 1760 | state.db_name 1761 | ); 1762 | 1763 | state.wal_index_regions.clear(); 1764 | state.wal_index_locks.clear(); 1765 | 1766 | if delete_flags == 1 { 1767 | if let Some((wal_index, readonly)) = state.wal_index.take() { 1768 | if !readonly { 1769 | if let Err(err) = wal_index.delete() { 1770 | return state.set_last_error(ffi::SQLITE_ERROR, err); 1771 | } 1772 | } 1773 | } 1774 | } 1775 | 1776 | ffi::SQLITE_OK 1777 | } 1778 | } 1779 | 1780 | // #[cfg(feature = "sqlite_test")] 1781 | // struct Benign; 1782 | 1783 | // #[cfg(feature = "sqlite_test")] 1784 | // #[inline] 1785 | // unsafe fn simulate_io_error_benign() -> Benign { 1786 | // ffi::sqlite3_set_io_error_benign(1); 1787 | // Benign 1788 | // } 1789 | 1790 | // #[cfg(feature = "sqlite_test")] 1791 | // impl Drop for Benign { 1792 | // fn drop(&mut self) { 1793 | // unsafe { ffi::sqlite3_set_io_error_benign(0) } 1794 | // } 1795 | // } 1796 | 1797 | // Note: When adding additional simulate_io_error() calls, retest: 1798 | // - malloc.test 1799 | // - ioerr2.test 1800 | // - backup_ioerr.test 1801 | #[cfg(feature = "sqlite_test")] 1802 | #[inline] 1803 | unsafe fn simulate_io_error() -> bool { 1804 | if (ffi::sqlite3_get_io_error_persist() != 0 && ffi::sqlite3_get_io_error_hit() != 0) 1805 | || ffi::sqlite3_dec_io_error_pending() == 1 1806 | { 1807 | ffi::sqlite3_inc_io_error_hit(); 1808 | if ffi::sqlite3_get_io_error_benign() == 0 { 1809 | ffi::sqlite3_inc_io_error_hardhit(); 1810 | } 1811 | 1812 | return true; 1813 | } 1814 | 1815 | false 1816 | } 1817 | 1818 | #[cfg(feature = "sqlite_test")] 1819 | #[inline] 1820 | unsafe fn simulate_diskfull_error() -> bool { 1821 | if ffi::sqlite3_get_diskfull_pending() != 0 { 1822 | if ffi::sqlite3_get_diskfull_pending() == 1 { 1823 | if ffi::sqlite3_get_io_error_benign() == 0 { 1824 | ffi::sqlite3_inc_io_error_hardhit(); 1825 | } 1826 | ffi::sqlite3_set_diskfull(); 1827 | ffi::sqlite3_set_io_error_hit(1); 1828 | return true; 1829 | } else { 1830 | ffi::sqlite3_dec_diskfull_pending(); 1831 | } 1832 | } 1833 | 1834 | false 1835 | } 1836 | 1837 | impl State { 1838 | fn set_last_error(&mut self, no: i32, err: std::io::Error) -> i32 { 1839 | // log::error!("{} ({})", err, no); 1840 | *(self.last_error.lock().unwrap()) = Some((no, err)); 1841 | no 1842 | } 1843 | } 1844 | 1845 | impl FileExt { 1846 | fn set_last_error(&mut self, no: i32, err: std::io::Error) -> i32 { 1847 | // log::error!("{} ({})", err, no); 1848 | *(self.last_error.lock().unwrap()) = Some((no, err)); 1849 | self.last_errno = no; 1850 | no 1851 | } 1852 | } 1853 | 1854 | fn null_ptr_error() -> std::io::Error { 1855 | std::io::Error::new(ErrorKind::Other, "received null pointer") 1856 | } 1857 | 1858 | unsafe fn vfs_state<'a, V>(ptr: *mut ffi::sqlite3_vfs) -> Result<&'a mut State, std::io::Error> { 1859 | let vfs: &mut ffi::sqlite3_vfs = ptr.as_mut().ok_or_else(null_ptr_error)?; 1860 | let state = (vfs.pAppData as *mut State) 1861 | .as_mut() 1862 | .ok_or_else(null_ptr_error)?; 1863 | Ok(state) 1864 | } 1865 | 1866 | unsafe fn file_state<'a, V, F: DatabaseHandle>( 1867 | ptr: *mut ffi::sqlite3_file, 1868 | ) -> Result<&'a mut FileExt, std::io::Error> { 1869 | let f = (ptr as *mut FileState) 1870 | .as_mut() 1871 | .ok_or_else(null_ptr_error)?; 1872 | let ext = f.ext.assume_init_mut(); 1873 | Ok(ext) 1874 | } 1875 | 1876 | impl OpenOptions { 1877 | fn from_flags(flags: i32) -> Option { 1878 | Some(OpenOptions { 1879 | kind: OpenKind::from_flags(flags)?, 1880 | access: OpenAccess::from_flags(flags)?, 1881 | delete_on_close: flags & ffi::SQLITE_OPEN_DELETEONCLOSE > 0, 1882 | }) 1883 | } 1884 | 1885 | fn to_flags(&self) -> i32 { 1886 | self.kind.to_flags() 1887 | | self.access.to_flags() 1888 | | if self.delete_on_close { 1889 | ffi::SQLITE_OPEN_DELETEONCLOSE 1890 | } else { 1891 | 0 1892 | } 1893 | } 1894 | } 1895 | 1896 | impl OpenKind { 1897 | fn from_flags(flags: i32) -> Option { 1898 | match flags { 1899 | flags if flags & ffi::SQLITE_OPEN_MAIN_DB > 0 => Some(Self::MainDb), 1900 | flags if flags & ffi::SQLITE_OPEN_MAIN_JOURNAL > 0 => Some(Self::MainJournal), 1901 | flags if flags & ffi::SQLITE_OPEN_TEMP_DB > 0 => Some(Self::TempDb), 1902 | flags if flags & ffi::SQLITE_OPEN_TEMP_JOURNAL > 0 => Some(Self::TempJournal), 1903 | flags if flags & ffi::SQLITE_OPEN_TRANSIENT_DB > 0 => Some(Self::TransientDb), 1904 | flags if flags & ffi::SQLITE_OPEN_SUBJOURNAL > 0 => Some(Self::SubJournal), 1905 | flags if flags & ffi::SQLITE_OPEN_SUPER_JOURNAL > 0 => Some(Self::SuperJournal), 1906 | flags if flags & ffi::SQLITE_OPEN_WAL > 0 => Some(Self::Wal), 1907 | _ => None, 1908 | } 1909 | } 1910 | 1911 | fn to_flags(self) -> i32 { 1912 | match self { 1913 | OpenKind::MainDb => ffi::SQLITE_OPEN_MAIN_DB, 1914 | OpenKind::MainJournal => ffi::SQLITE_OPEN_MAIN_JOURNAL, 1915 | OpenKind::TempDb => ffi::SQLITE_OPEN_TEMP_DB, 1916 | OpenKind::TempJournal => ffi::SQLITE_OPEN_TEMP_JOURNAL, 1917 | OpenKind::TransientDb => ffi::SQLITE_OPEN_TRANSIENT_DB, 1918 | OpenKind::SubJournal => ffi::SQLITE_OPEN_SUBJOURNAL, 1919 | OpenKind::SuperJournal => ffi::SQLITE_OPEN_SUPER_JOURNAL, 1920 | OpenKind::Wal => ffi::SQLITE_OPEN_WAL, 1921 | } 1922 | } 1923 | } 1924 | 1925 | impl OpenAccess { 1926 | fn from_flags(flags: i32) -> Option { 1927 | match flags { 1928 | flags 1929 | if (flags & ffi::SQLITE_OPEN_CREATE > 0) 1930 | && (flags & ffi::SQLITE_OPEN_EXCLUSIVE > 0) => 1931 | { 1932 | Some(Self::CreateNew) 1933 | } 1934 | flags if flags & ffi::SQLITE_OPEN_CREATE > 0 => Some(Self::Create), 1935 | flags if flags & ffi::SQLITE_OPEN_READWRITE > 0 => Some(Self::Write), 1936 | flags if flags & ffi::SQLITE_OPEN_READONLY > 0 => Some(Self::Read), 1937 | _ => None, 1938 | } 1939 | } 1940 | 1941 | fn to_flags(self) -> i32 { 1942 | match self { 1943 | OpenAccess::Read => ffi::SQLITE_OPEN_READONLY, 1944 | OpenAccess::Write => ffi::SQLITE_OPEN_READWRITE, 1945 | OpenAccess::Create => ffi::SQLITE_OPEN_READWRITE | ffi::SQLITE_OPEN_CREATE, 1946 | OpenAccess::CreateNew => { 1947 | ffi::SQLITE_OPEN_READWRITE | ffi::SQLITE_OPEN_CREATE | ffi::SQLITE_OPEN_EXCLUSIVE 1948 | } 1949 | } 1950 | } 1951 | } 1952 | 1953 | impl LockKind { 1954 | fn from_i32(lock: i32) -> Option { 1955 | Some(match lock { 1956 | ffi::SQLITE_LOCK_NONE => Self::None, 1957 | ffi::SQLITE_LOCK_SHARED => Self::Shared, 1958 | ffi::SQLITE_LOCK_RESERVED => Self::Reserved, 1959 | ffi::SQLITE_LOCK_PENDING => Self::Pending, 1960 | ffi::SQLITE_LOCK_EXCLUSIVE => Self::Exclusive, 1961 | _ => return None, 1962 | }) 1963 | } 1964 | 1965 | fn to_i32(self) -> i32 { 1966 | match self { 1967 | Self::None => ffi::SQLITE_LOCK_NONE, 1968 | Self::Shared => ffi::SQLITE_LOCK_SHARED, 1969 | Self::Reserved => ffi::SQLITE_LOCK_RESERVED, 1970 | Self::Pending => ffi::SQLITE_LOCK_PENDING, 1971 | Self::Exclusive => ffi::SQLITE_LOCK_EXCLUSIVE, 1972 | } 1973 | } 1974 | } 1975 | 1976 | impl PartialOrd for LockKind { 1977 | fn partial_cmp(&self, other: &Self) -> Option { 1978 | self.to_i32().partial_cmp(&other.to_i32()) 1979 | } 1980 | } 1981 | 1982 | impl Default for LockKind { 1983 | fn default() -> Self { 1984 | Self::None 1985 | } 1986 | } 1987 | 1988 | #[derive(Default)] 1989 | pub struct WalDisabled; 1990 | 1991 | impl wip::WalIndex for WalDisabled { 1992 | fn enabled() -> bool { 1993 | false 1994 | } 1995 | 1996 | fn map(&mut self, _region: u32) -> Result<[u8; 32768], std::io::Error> { 1997 | Err(std::io::Error::new(ErrorKind::Other, "wal is disabled")) 1998 | } 1999 | 2000 | fn lock( 2001 | &mut self, 2002 | _locks: Range, 2003 | _lock: wip::WalIndexLock, 2004 | ) -> Result { 2005 | Err(std::io::Error::new(ErrorKind::Other, "wal is disabled")) 2006 | } 2007 | 2008 | fn delete(self) -> Result<(), std::io::Error> { 2009 | Ok(()) 2010 | } 2011 | } 2012 | 2013 | #[derive(Debug)] 2014 | pub enum RegisterError { 2015 | Nul(std::ffi::NulError), 2016 | Register(i32), 2017 | } 2018 | 2019 | impl std::error::Error for RegisterError { 2020 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 2021 | match self { 2022 | Self::Nul(err) => Some(err), 2023 | Self::Register(_) => None, 2024 | } 2025 | } 2026 | } 2027 | 2028 | impl std::fmt::Display for RegisterError { 2029 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 2030 | match self { 2031 | Self::Nul(_) => f.write_str("interior nul byte in name found"), 2032 | Self::Register(code) => { 2033 | write!(f, "registering sqlite vfs failed with error code: {}", code) 2034 | } 2035 | } 2036 | } 2037 | } 2038 | 2039 | impl From for RegisterError { 2040 | fn from(err: std::ffi::NulError) -> Self { 2041 | Self::Nul(err) 2042 | } 2043 | } 2044 | 2045 | #[cfg(test)] 2046 | mod tests { 2047 | use super::*; 2048 | 2049 | #[test] 2050 | fn test_lock_order() { 2051 | assert!(LockKind::None < LockKind::Shared); 2052 | assert!(LockKind::Shared < LockKind::Reserved); 2053 | assert!(LockKind::Reserved < LockKind::Pending); 2054 | assert!(LockKind::Pending < LockKind::Exclusive); 2055 | } 2056 | } 2057 | -------------------------------------------------------------------------------- /test-vfs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-vfs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | libc = "0.2" 11 | log = "0.4" 12 | pretty_env_logger = "0.4" 13 | rand = "0.8" 14 | sqlite-vfs = { path = "../", default-features = false, features = ["sqlite_test", "syscall", "loadext"] } 15 | -------------------------------------------------------------------------------- /test-vfs/Dockerfile: -------------------------------------------------------------------------------- 1 | # cannot use a musl based image, as the following expects dynamically linked libs 2 | FROM rust:1-slim 3 | 4 | # add build dependencies 5 | RUN apt-get update && apt-get install -y \ 6 | curl build-essential tcl-dev valgrind unzip \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | # create non-root user to execute the tests (some tests fail if executed as root) 10 | RUN addgroup -gid 1000 --system sqlite && \ 11 | adduser -uid 1000 --system --gid 1000 --home /home/sqlite --shell /bin/bash sqlite && \ 12 | passwd -d sqlite 13 | WORKDIR /home/sqlite 14 | 15 | # download and extract sqlite source 16 | RUN curl -LO https://www.sqlite.org/2022/sqlite-src-3370200.zip && unzip sqlite-src-3370200.zip 17 | 18 | # build a stub of the test vfs, which allows to build and cache sqlite before building the actual 19 | # test vfs 20 | ADD --chown=sqlite docker/test-vfs /home/sqlite/test-vfs 21 | RUN cd test-vfs && cargo build 22 | 23 | # build sqlite test programs 24 | COPY patch/Makefile.in.patch Makefile.in.patch 25 | RUN patch -u sqlite-src-3370200/Makefile.in Makefile.in.patch 26 | RUN mkdir build && cd build && ../sqlite-src-3370200/configure 27 | COPY patch/test_ext.c sqlite-src-3370200/src/test_ext.c 28 | # -DSQLITE_EXTRA_INIT=sqlite3_register_test_vfs: custom init method that initializes the custom 29 | # test vfs 30 | # -DSQLITE_MAX_MMAP_SIZE=0: disable memory mapping as it is not supported for the custom vfs 31 | # -DSQLITE_DISABLE_DIRSYNC: disable to skip some tests that are not relevant for a custom vfs 32 | RUN cd build && make \ 33 | OPTS="-DSQLITE_EXTRA_INIT=sqlite3_register_test_vfs -DSQLITE_MAX_MMAP_SIZE=0 -DSQLITE_DISABLE_DIRSYNC" \ 34 | LIBS="-L/home/sqlite/test-vfs/target/debug/ -ltest_vfs" \ 35 | USE_AMALGAMATION=0 \ 36 | testfixture 37 | RUN chown -R sqlite:sqlite /home/sqlite/build 38 | RUN rm -rf /home/sqlite/test-vfs 39 | 40 | # Patch/skip some tests 41 | COPY patch/test patch 42 | COPY patch.sh . 43 | RUN cd sqlite-src-3370200 && ../patch.sh && rm -rf ../{patch,patch.sh} 44 | 45 | RUN mkdir /home/sqlite/lib/ 46 | ENV LD_LIBRARY_PATH /home/sqlite/lib/ 47 | 48 | WORKDIR /github/workspace 49 | COPY entrypoint.sh / 50 | ENTRYPOINT ["/entrypoint.sh"] 51 | -------------------------------------------------------------------------------- /test-vfs/action.yml: -------------------------------------------------------------------------------- 1 | name: SQLite Tests 2 | description: Run SQLite's TCL tests against a custom VFS 3 | runs: 4 | using: docker 5 | image: Dockerfile 6 | -------------------------------------------------------------------------------- /test-vfs/docker/test-vfs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "test-vfs" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /test-vfs/docker/test-vfs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-vfs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [workspace] 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | -------------------------------------------------------------------------------- /test-vfs/docker/test-vfs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[no_mangle] 2 | pub extern "C" fn sqlite3_register_test_vfs() -> i32 { 3 | 0 4 | } 5 | -------------------------------------------------------------------------------- /test-vfs/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd /github/workspace 5 | cargo build -p test-vfs 6 | cp target/debug/libtest_vfs.so /home/sqlite/lib/ 7 | 8 | # open the directory with the pre-build sqlite 9 | cd /home/sqlite/build 10 | 11 | su -c "time ./testfixture ../sqlite-src-3370200/$1 --verbose=false" \ 12 | sqlite 13 | -------------------------------------------------------------------------------- /test-vfs/patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # The struct created per opened db file is bigger for the test-vfs compared to the original os_unix 5 | # one, which is why some expected values in the following test need to be updated. 6 | patch test/dbstatus.test ../patch/dbstatus.test.patch 7 | 8 | # Remove external_reader.test as the unix-specific SQLITE_FCNTL_EXTERNAL_READER is not implemented. 9 | rm test/external_reader.test 10 | 11 | # Remove oserror.test as it tests specifics of the default unix/windows VFS modules. 12 | rm test/oserror.test 13 | 14 | # Well, I couldn't figure out how to build the needed `libtestloadext.so`, so they are skipped for 15 | # now. 16 | rm test/loadext.test 17 | rm test/loadext2.test 18 | 19 | # Remove long running tests that while being green, don't contribute tests relevant for a VFS 20 | # implementation so it is not forth waiting for them. 21 | rm test/backup_ioerr.test 22 | rm test/speed1.test 23 | rm test/speed1p.test 24 | rm test/speed2.test 25 | rm test/speed3.test 26 | rm test/speed4.test 27 | rm test/speed4p.test 28 | 29 | # Remove tests that only test a specific built-in VFS 30 | rm test/unixexcl.test 31 | 32 | # Page size for wal index is currently hard coded to 32768 and not retrieved from the actual page 33 | # size of the system. The following test changes the page size via a syscall and tests that the 34 | # changes apply. This is currently not supported in the VFS. 35 | rm test/wal64k.test 36 | 37 | # TODO: The following tests still need to be fixed. 38 | rm test/memsubsys2.test 39 | rm test/superlock.test 40 | rm test/symlink.test 41 | 42 | # WAL is still work in progress. Disable the WAL tests that aren't green for now. 43 | rm test/wal5.test 44 | rm test/wal6.test 45 | rm test/walro.test 46 | rm test/walro2.test 47 | rm test/walthread.test 48 | rm test/walvfs.test 49 | -------------------------------------------------------------------------------- /test-vfs/patch/Makefile.in.patch: -------------------------------------------------------------------------------- 1 | --- a/Makefile.in 2 | +++ b/Makefile.in 3 | @@ -193,7 +193,7 @@ LIBOBJS0 = alter.lo analyze.lo attach.lo auth.lo \ 4 | vdbe.lo vdbeapi.lo vdbeaux.lo vdbeblob.lo vdbemem.lo vdbesort.lo \ 5 | vdbetrace.lo vdbevtab.lo \ 6 | wal.lo walker.lo where.lo wherecode.lo whereexpr.lo \ 7 | - window.lo utf.lo vtab.lo 8 | + window.lo utf.lo vtab.lo test_ext.lo 9 | 10 | # Object files for the amalgamation. 11 | # 12 | @@ -966,6 +966,9 @@ status.lo: $(TOP)/src/status.c $(HDR) 13 | table.lo: $(TOP)/src/table.c $(HDR) 14 | $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/table.c 15 | 16 | +test_ext.lo: $(TOP)/src/test_ext.c $(HDR) 17 | + $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/test_ext.c 18 | + 19 | threads.lo: $(TOP)/src/threads.c $(HDR) 20 | $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/threads.c 21 | -------------------------------------------------------------------------------- /test-vfs/patch/test/dbstatus.test.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test-vfs/dbstatus.test b/test-vfs/dbstatus.test 2 | index 8d5d834..de81a44 100644 3 | --- a/test-vfs/dbstatus.test 4 | +++ b/test-vfs/dbstatus.test 5 | @@ -397,20 +397,20 @@ ifcapable shared_cache { 6 | CREATE TABLE t1(a, b, c); 7 | INSERT INTO t1 VALUES(1, 2, 3); 8 | } 9 | - do_cacheused_test 4.0.1 db { 4568 4568 } 10 | + do_cacheused_test 4.0.1 db { 5112 5112 } 11 | do_execsql_test 4.1 { 12 | CREATE TEMP TABLE tt(a, b, c); 13 | INSERT INTO tt VALUES(1, 2, 3); 14 | } 15 | - do_cacheused_test 4.1.1 db { 9000 9000 } 16 | + do_cacheused_test 4.1.1 db { 10112 10112 } 17 | 18 | sqlite3 db2 file:test.db?cache=shared 19 | - do_cacheused_test 4.2.1 db2 { 4568 2284 } 20 | - do_cacheused_test 4.2.2 db { 9000 6716 } 21 | + do_cacheused_test 4.2.1 db2 { 5112 2556 } 22 | + do_cacheused_test 4.2.2 db { 10112 7556 } 23 | db close 24 | - do_cacheused_test 4.2.3 db2 { 4568 4568 } 25 | + do_cacheused_test 4.2.3 db2 { 5112 5112 } 26 | sqlite3 db file:test.db?cache=shared 27 | - do_cacheused_test 4.2.4 db2 { 4568 2284 } 28 | + do_cacheused_test 4.2.4 db2 { 5112 2556 } 29 | db2 close 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test-vfs/patch/test_ext.c: -------------------------------------------------------------------------------- 1 | extern int sqlite3_sync_count; 2 | 3 | void sqlite3_inc_sync_count() { 4 | sqlite3_sync_count++; 5 | } 6 | 7 | extern int sqlite3_fullsync_count; 8 | 9 | void sqlite3_inc_fullsync_count() { 10 | sqlite3_fullsync_count++; 11 | } 12 | 13 | extern int sqlite3_current_time; 14 | 15 | void sqlite3_set_current_time(int current_time) { 16 | sqlite3_current_time = current_time; 17 | } 18 | 19 | int sqlite3_get_current_time() { 20 | return sqlite3_current_time; 21 | } 22 | 23 | extern int sqlite3_diskfull_pending; 24 | 25 | void sqlite3_dec_diskfull_pending() { 26 | sqlite3_diskfull_pending--; 27 | } 28 | 29 | int sqlite3_get_diskfull_pending() { 30 | return sqlite3_diskfull_pending; 31 | } 32 | 33 | extern int sqlite3_diskfull; 34 | 35 | void sqlite3_set_diskfull() { 36 | sqlite3_diskfull = 1; 37 | } 38 | 39 | extern int sqlite3_open_file_count; 40 | 41 | void sqlite3_inc_open_file_count() { 42 | sqlite3_open_file_count++; 43 | } 44 | 45 | void sqlite3_dec_open_file_count() { 46 | sqlite3_open_file_count--; 47 | } 48 | 49 | extern int sqlite3_io_error_pending; 50 | 51 | int sqlite3_dec_io_error_pending() { 52 | return sqlite3_io_error_pending--; 53 | } 54 | 55 | extern int sqlite3_io_error_persist; 56 | 57 | int sqlite3_get_io_error_persist() { 58 | return sqlite3_io_error_persist; 59 | } 60 | 61 | extern int sqlite3_io_error_hit; 62 | 63 | int sqlite3_get_io_error_hit() { 64 | return sqlite3_io_error_hit; 65 | } 66 | 67 | void sqlite3_inc_io_error_hit() { 68 | sqlite3_io_error_hit++; 69 | } 70 | 71 | void sqlite3_set_io_error_hit(int hit) { 72 | sqlite3_io_error_hit = hit; 73 | } 74 | 75 | extern int sqlite3_io_error_benign; 76 | 77 | int sqlite3_get_io_error_benign() { 78 | return sqlite3_io_error_benign; 79 | } 80 | 81 | // void sqlite3_set_io_error_benign(int benign) { 82 | // sqlite3_io_error_benign = benign; 83 | // } 84 | 85 | extern int sqlite3_io_error_hardhit; 86 | 87 | int sqlite3_inc_io_error_hardhit() { 88 | sqlite3_io_error_hardhit++; 89 | } 90 | -------------------------------------------------------------------------------- /test-vfs/src/file_lock.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::os::unix::io::{AsRawFd, RawFd}; 3 | use std::os::unix::prelude::FromRawFd; 4 | 5 | pub use sqlite_vfs::LockKind; 6 | 7 | use crate::lock::{flock_exclusive, flock_shared, flock_unlock}; 8 | 9 | pub struct FileLock { 10 | file: Option, 11 | fd: RawFd, 12 | } 13 | 14 | impl FileLock { 15 | pub fn new(file: File) -> Self { 16 | Self { 17 | fd: file.as_raw_fd(), 18 | file: Some(file), 19 | } 20 | } 21 | 22 | pub fn file(&mut self) -> &mut File { 23 | self.file 24 | .get_or_insert_with(|| unsafe { File::from_raw_fd(self.fd) }) 25 | } 26 | 27 | pub fn unlock(&self) { 28 | flock_unlock(self.fd); 29 | } 30 | 31 | pub fn shared(&self) -> bool { 32 | flock_shared(self.fd) 33 | } 34 | 35 | pub fn wait_shared(&self) { 36 | flock_wait_shared(self.fd) 37 | } 38 | 39 | pub fn exclusive(&self) -> bool { 40 | flock_exclusive(self.fd) 41 | } 42 | 43 | pub fn wait_exclusive(&self) { 44 | flock_wait_exclusive(self.fd) 45 | } 46 | } 47 | 48 | pub(crate) fn flock_wait_shared(fd: RawFd) { 49 | unsafe { 50 | if libc::flock(fd, libc::LOCK_SH) == 0 { 51 | return; 52 | } 53 | } 54 | 55 | let err = std::io::Error::last_os_error(); 56 | panic!("lock shared failed: {}", err); 57 | } 58 | 59 | pub(crate) fn flock_wait_exclusive(fd: RawFd) { 60 | unsafe { 61 | if libc::flock(fd, libc::LOCK_EX) == 0 { 62 | return; 63 | } 64 | } 65 | 66 | let err = std::io::Error::last_os_error(); 67 | panic!("lock exclusive failed: {}", err); 68 | } 69 | 70 | impl Drop for FileLock { 71 | fn drop(&mut self) { 72 | self.unlock(); 73 | self.file.take(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test-vfs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use sqlite_vfs::{register, RegisterError}; 2 | 3 | pub mod file_lock; 4 | pub mod lock; 5 | pub mod range_lock; 6 | pub mod vfs; 7 | 8 | pub const SQLITE_OK: i32 = 0; 9 | pub const SQLITE_ERROR: i32 = 1; 10 | 11 | #[no_mangle] 12 | pub extern "C" fn sqlite3_register_test_vfs() -> i32 { 13 | pretty_env_logger::try_init().ok(); 14 | // pretty_env_logger::formatted_builder() 15 | // .filter(Some("sqlite_vfs"), log::LevelFilter::Trace) 16 | // .try_init() 17 | // .ok(); 18 | 19 | match register("test-vfs", vfs::TestVfs::default(), true) { 20 | Ok(_) => SQLITE_OK, 21 | Err(RegisterError::Nul(_)) => SQLITE_ERROR, 22 | Err(RegisterError::Register(code)) => code, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test-vfs/src/lock.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, OpenOptions}; 2 | use std::os::unix::fs::MetadataExt; 3 | use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; 4 | use std::os::unix::prelude::FromRawFd; 5 | use std::path::Path; 6 | use std::{env, io}; 7 | 8 | pub use sqlite_vfs::LockKind; 9 | 10 | /// SQLite's default locking on UNIX systems is quite involved to work around certain limitations 11 | /// of POSIX locks. See https://github.com/sqlite/sqlite/blob/master/src/os_unix.c#L1026-L1114 for 12 | /// details. 13 | /// 14 | /// Since I don't want to re-implement that, I am going with something simpler which should suffice 15 | /// the use-case of a VFS only used for tests. The locking uses BSD locks instead of POSIX locks. 16 | /// Since SQLite has five different lock states, one single BSD lock is not enough (one BSD lock is 17 | /// either unlocked, shared or exclusive). This is why each database lock consists out of two BSD 18 | /// locks. They work as follows to achieve the SQLite lock states: 19 | /// 20 | /// | {name}.db /tmp/{ino}.lck 21 | /// 22 | /// unlocked unlocked unlocked 23 | /// 24 | /// shared shared shared -> unlocked ¹ 25 | /// 26 | /// reserved shared exclusive -> shared ² 27 | /// 28 | /// pending shared exclusive 29 | /// 30 | /// exclusive exclusive exclusive 31 | /// 32 | /// 33 | /// ¹ The shared lock is first acquired, but then unlocked again. The shared lock is not kept to 34 | /// allow the creation of an exclusive lock for a reserved lock. 35 | /// 36 | /// ² The reserved lock must still allow new shared locks, which is why it is only tested for 37 | /// exclusivity first (to make sure that there is neither a pending nor exclusive lock) and then 38 | /// downgraded to a shared lock. Keeping the shared lock prevents any other reserved lock as there 39 | /// can only be one. 40 | pub struct Lock { 41 | fd1: RawFd, 42 | fd1_owned: bool, 43 | fd2: RawFd, 44 | current: LockKind, 45 | } 46 | 47 | impl Lock { 48 | pub fn new(path: impl AsRef) -> io::Result { 49 | let path = path.as_ref(); 50 | let f1 = File::open(path)?; 51 | 52 | let f2 = OpenOptions::new() 53 | .read(true) 54 | .write(true) 55 | .create(true) 56 | .open(env::temp_dir().join(format!("{}.lck", f1.metadata()?.ino())))?; 57 | 58 | Ok(Lock { 59 | fd1: f1.into_raw_fd(), 60 | fd1_owned: true, 61 | fd2: f2.into_raw_fd(), 62 | current: LockKind::None, 63 | }) 64 | } 65 | 66 | pub fn from_file(f1: &File) -> io::Result { 67 | let f2 = OpenOptions::new() 68 | .read(true) 69 | .write(true) 70 | .create(true) 71 | .open(env::temp_dir().join(format!("{}.lck", f1.metadata()?.ino())))?; 72 | 73 | Ok(Lock { 74 | fd1: f1.as_raw_fd(), 75 | fd1_owned: false, 76 | fd2: f2.into_raw_fd(), 77 | current: LockKind::None, 78 | }) 79 | } 80 | 81 | pub fn current(&self) -> LockKind { 82 | self.current 83 | } 84 | 85 | pub fn reserved(&self) -> bool { 86 | if self.current > LockKind::Shared { 87 | return true; 88 | } 89 | 90 | if flock_exclusive(self.fd2) { 91 | flock_unlock(self.fd2); 92 | false 93 | } else { 94 | true 95 | } 96 | } 97 | 98 | /// Transition the lock to the given [LockKind]. 99 | /// 100 | /// # Panics 101 | /// 102 | /// Panics for invalid lock transitions or failed unlocks (which are not expected to happen). 103 | pub fn lock(&mut self, to: LockKind) -> bool { 104 | if self.current == to { 105 | return true; 106 | } 107 | 108 | // Never move from unlocked to anything higher than shared 109 | if self.current == LockKind::None && to != LockKind::Shared { 110 | panic!( 111 | "cannot transition from unlocked to anything higher than shared (tried: {:?})", 112 | to 113 | ) 114 | } 115 | 116 | match to { 117 | LockKind::None => { 118 | flock_unlock(self.fd1); 119 | 120 | if matches!(self.current, LockKind::Pending | LockKind::Exclusive) { 121 | flock_unlock(self.fd2); 122 | } 123 | 124 | self.current = LockKind::None; 125 | 126 | return true; 127 | } 128 | 129 | LockKind::Shared => { 130 | if self.current != LockKind::Reserved { 131 | if !flock_shared(self.fd1) { 132 | return false; 133 | } 134 | } 135 | 136 | if flock_shared(self.fd2) { 137 | flock_unlock(self.fd2); 138 | self.current = LockKind::Shared; 139 | true 140 | } else if matches!(self.current, LockKind::Pending | LockKind::Exclusive) { 141 | panic!("failed to transition to shared from {:?}", self.current); 142 | } else if self.current == LockKind::None { 143 | flock_unlock(self.fd1); 144 | false 145 | } else { 146 | false 147 | } 148 | } 149 | 150 | LockKind::Reserved => { 151 | // A shared lock is always held when a reserved lock is requested 152 | if self.current != LockKind::Shared { 153 | panic!( 154 | "must hold a shared lock when requesting a reserved lock (current: {:?})", 155 | self.current 156 | ) 157 | } 158 | 159 | if flock_exclusive(self.fd2) { 160 | flock_shared(self.fd2); 161 | self.current = LockKind::Reserved; 162 | true 163 | } else { 164 | false 165 | } 166 | } 167 | 168 | LockKind::Pending => { 169 | panic!("cannot explicitly request pending lock (request explicit lock instead)") 170 | } 171 | 172 | LockKind::Exclusive => { 173 | if self.current != LockKind::Pending && !flock_exclusive(self.fd2) { 174 | return false; 175 | } 176 | 177 | if !flock_exclusive(self.fd1) { 178 | self.current = LockKind::Pending; 179 | return true; 180 | } 181 | 182 | self.current = LockKind::Exclusive; 183 | true 184 | } 185 | } 186 | } 187 | } 188 | 189 | pub(crate) fn flock_unlock(fd: RawFd) { 190 | unsafe { 191 | if libc::flock(fd, libc::LOCK_UN | libc::LOCK_NB) != 0 { 192 | panic!("unlock failed: {}", std::io::Error::last_os_error()); 193 | } 194 | } 195 | } 196 | 197 | pub(crate) fn flock_shared(fd: RawFd) -> bool { 198 | unsafe { 199 | if libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB) == 0 { 200 | return true; 201 | } 202 | } 203 | 204 | let err = std::io::Error::last_os_error(); 205 | if err.raw_os_error().unwrap() == libc::EWOULDBLOCK { 206 | return false; 207 | } 208 | 209 | panic!("lock shared failed: {}", err); 210 | } 211 | 212 | pub(crate) fn flock_exclusive(fd: RawFd) -> bool { 213 | unsafe { 214 | if libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) == 0 { 215 | return true; 216 | } 217 | } 218 | 219 | let err = std::io::Error::last_os_error(); 220 | if err.raw_os_error().unwrap() == libc::EWOULDBLOCK { 221 | return false; 222 | } 223 | 224 | panic!("lock exclusive failed: {}", err); 225 | } 226 | 227 | impl Drop for Lock { 228 | fn drop(&mut self) { 229 | self.lock(LockKind::None); 230 | 231 | // Close file descriptors. 232 | unsafe { 233 | if self.fd1_owned { 234 | File::from_raw_fd(self.fd1); 235 | } 236 | File::from_raw_fd(self.fd2); 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /test-vfs/src/range_lock.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fs::{File, OpenOptions}; 3 | use std::ops::Range; 4 | use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd}; 5 | use std::{env, io}; 6 | 7 | use sqlite_vfs::wip::WalIndexLock as LockKind; 8 | 9 | use crate::file_lock::FileLock; 10 | use crate::lock::{flock_exclusive, flock_shared, flock_unlock}; 11 | 12 | /// SQLite's default locking on UNIX systems is quite involved to work around certain limitations 13 | /// of POSIX locks. See https://github.com/sqlite/sqlite/blob/master/src/os_unix.c#L1026-L1114 for 14 | /// details. 15 | /// 16 | /// Since I don't want to re-implement that, I am going with something simpler which should suffice 17 | /// the use-case of a VFS only used for tests. The locking uses BSD locks instead of POSIX locks. 18 | /// 19 | /// BSD locks unfortunately don't support locks on by ranges, which is why the following creates 20 | /// a file per assumed byte. This is quite heavy on file access and usage of file descriptors, but 21 | /// should suffice for the purpose of a test vfs. 22 | pub struct RangeLock { 23 | ino: u64, 24 | locks: HashMap, 25 | } 26 | 27 | impl RangeLock { 28 | pub fn new(ino: u64) -> Self { 29 | Self { 30 | ino, 31 | locks: Default::default(), 32 | } 33 | } 34 | 35 | pub fn lock(&mut self, range: Range, to: LockKind) -> io::Result { 36 | // get exclusive lock on file descriptor that acts as a mutex 37 | let mutex = FileLock::new( 38 | OpenOptions::new() 39 | .read(true) 40 | .write(true) 41 | .create(true) 42 | .open(env::temp_dir().join(format!("{}_m.lck", self.ino)))?, 43 | ); 44 | mutex.wait_exclusive(); // is unlocked as soon as mutex is dropped 45 | 46 | for i in range.clone() { 47 | let (fd, current) = match self.locks.get(&i) { 48 | Some(fd) => fd, 49 | None => { 50 | let f = OpenOptions::new() 51 | .read(true) 52 | .write(true) 53 | .create(true) 54 | .open(env::temp_dir().join(format!("{}_{}.lck", self.ino, i)))?; 55 | self.locks 56 | .entry(i) 57 | .or_insert((f.into_raw_fd(), LockKind::None)) 58 | } 59 | }; 60 | 61 | if *current == to { 62 | continue; 63 | } 64 | 65 | let ok = match to { 66 | LockKind::None => { 67 | flock_unlock(*fd); 68 | true 69 | } 70 | LockKind::Shared => flock_shared(*fd), 71 | LockKind::Exclusive => flock_exclusive(*fd), 72 | }; 73 | if !ok { 74 | // revert locks 75 | for i in range.start..=i { 76 | if let Some((fd, current)) = self.locks.get_mut(&i) { 77 | match current { 78 | LockKind::None => flock_unlock(*fd), 79 | LockKind::Shared => { 80 | flock_shared(*fd); 81 | } 82 | LockKind::Exclusive => { 83 | flock_exclusive(*fd); 84 | } 85 | } 86 | } 87 | } 88 | 89 | return Ok(false); 90 | } 91 | } 92 | 93 | if to == LockKind::None { 94 | // Remove to free up file descriptors 95 | for i in range { 96 | if let Some((fd, _)) = self.locks.remove(&i) { 97 | unsafe { File::from_raw_fd(fd) }; 98 | } 99 | } 100 | } else { 101 | // update current locks once all where successful 102 | for i in range { 103 | if let Some((_, current)) = self.locks.get_mut(&i) { 104 | *current = to; 105 | } 106 | } 107 | } 108 | 109 | Ok(true) 110 | } 111 | } 112 | 113 | impl Drop for RangeLock { 114 | fn drop(&mut self) { 115 | // unlock all 116 | for (_, (fd, lock)) in std::mem::take(&mut self.locks) { 117 | if lock == LockKind::None { 118 | continue; 119 | } 120 | 121 | flock_unlock(fd); 122 | unsafe { File::from_raw_fd(fd) }; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /test-vfs/src/vfs.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fs::{self, File, Permissions}; 3 | use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; 4 | use std::os::unix::fs::{MetadataExt, PermissionsExt}; 5 | use std::path::{Path, PathBuf}; 6 | use std::sync::atomic::{AtomicUsize, Ordering}; 7 | 8 | use sqlite_vfs::wip::{WalIndex, WalIndexLock}; 9 | use sqlite_vfs::{LockKind, OpenAccess, OpenKind, OpenOptions, Vfs}; 10 | 11 | use crate::file_lock::FileLock; 12 | use crate::lock::Lock; 13 | use crate::range_lock::RangeLock; 14 | 15 | /// [Vfs] test implementation based on Rust's [std::fs:File]. This implementation is not meant for 16 | /// any use-cases except running SQLite unit tests, as the locking is only managed in process 17 | /// memory. 18 | #[derive(Default)] 19 | pub struct TestVfs { 20 | temp_counter: AtomicUsize, 21 | } 22 | 23 | pub struct Connection { 24 | path: PathBuf, 25 | file: File, 26 | file_ino: u64, 27 | lock: Option, 28 | } 29 | 30 | pub struct WalConnection { 31 | path: PathBuf, 32 | file_lock: FileLock, 33 | wal_lock: RangeLock, 34 | readonly: bool, 35 | } 36 | 37 | impl Vfs for TestVfs { 38 | type Handle = Connection; 39 | 40 | fn open(&self, db: &str, opts: OpenOptions) -> Result { 41 | let path = normalize_path(Path::new(&db)); 42 | if path.is_dir() { 43 | return Err(io::Error::new(ErrorKind::Other, "cannot open directory")); 44 | } 45 | 46 | let mut o = fs::OpenOptions::new(); 47 | o.read(true).write(opts.access != OpenAccess::Read); 48 | let is_create = match opts.access { 49 | OpenAccess::Create => { 50 | o.create(true); 51 | true 52 | } 53 | OpenAccess::CreateNew => { 54 | o.create_new(true); 55 | true 56 | } 57 | _ => false, 58 | }; 59 | let file = o.open(&path)?; 60 | let metadata = file.metadata()?; 61 | let file_ino = metadata.ino(); 62 | 63 | if is_create && matches!(opts.kind, OpenKind::Wal | OpenKind::MainJournal) { 64 | if let Ok(mode) = permissions(&path) { 65 | fs::set_permissions(&path, Permissions::from_mode(mode)).ok(); 66 | } 67 | } 68 | 69 | if opts.kind == OpenKind::Wal { 70 | // ensure wal index access 71 | let path = path.with_extension(format!( 72 | "{}-shm", 73 | path.extension() 74 | .and_then(|ext| ext.to_str()) 75 | .map(|ext| ext.split_once('-').map(|(f, _)| f).unwrap_or(ext)) 76 | .unwrap_or("db") 77 | )); 78 | if path.exists() 79 | && fs::metadata(&path) 80 | .map(|m| m.permissions().mode()) 81 | .unwrap_or(0o100000) 82 | <= 0o100000 83 | { 84 | return Err(std::io::Error::new( 85 | ErrorKind::Other, 86 | "cannot read .db-shm file", 87 | )); 88 | } 89 | } 90 | 91 | Ok(Connection { 92 | path, 93 | // Lock needs to be created right away to ensure there is a free file descriptor for the 94 | // additional lock file. 95 | lock: if opts.kind == OpenKind::MainDb { 96 | Some(Lock::from_file(&file)?) 97 | } else { 98 | None 99 | }, 100 | file, 101 | file_ino, 102 | }) 103 | } 104 | 105 | fn delete(&self, db: &str) -> Result<(), std::io::Error> { 106 | let path = normalize_path(Path::new(&db)); 107 | fs::remove_file(path) 108 | } 109 | 110 | fn exists(&self, db: &str) -> Result { 111 | Ok(Path::new(db).is_file()) 112 | } 113 | 114 | fn access(&self, db: &str, write: bool) -> Result { 115 | let metadata = fs::metadata(db)?; 116 | let readonly = metadata.permissions().readonly(); 117 | Ok(!write || (write && !readonly)) 118 | } 119 | 120 | fn temporary_name(&self) -> String { 121 | std::env::temp_dir() 122 | .join(format!( 123 | "etilqs_{:x}_{:x}.db", 124 | std::process::id(), 125 | self.temp_counter.fetch_add(1, Ordering::AcqRel), 126 | )) 127 | .to_string_lossy() 128 | .to_string() 129 | } 130 | 131 | fn full_pathname<'a>(&self, db: &'a str) -> Result, std::io::Error> { 132 | let path = Path::new(&db); 133 | let path = if path.is_absolute() { 134 | path.to_path_buf() 135 | } else { 136 | std::env::current_dir()?.join(path) 137 | }; 138 | let path = normalize_path(&path); 139 | Ok(path 140 | .to_str() 141 | .ok_or_else(|| { 142 | std::io::Error::new( 143 | ErrorKind::Other, 144 | "cannot convert canonicalized path to string", 145 | ) 146 | })? 147 | .to_string() 148 | .into()) 149 | } 150 | 151 | fn random(&self, buffer: &mut [i8]) { 152 | rand::Rng::fill(&mut rand::thread_rng(), buffer); 153 | } 154 | 155 | fn sleep(&self, duration: std::time::Duration) -> std::time::Duration { 156 | std::thread::sleep(duration); 157 | 158 | // Well, this function is only supposed to sleep at least `n_micro`μs, but there are 159 | // tests that expect the return to match exactly `n_micro`. As those tests are flaky as 160 | // a result, we are cheating here. 161 | duration 162 | } 163 | } 164 | 165 | impl sqlite_vfs::DatabaseHandle for Connection { 166 | type WalIndex = WalConnection; 167 | 168 | fn size(&self) -> Result { 169 | self.file.metadata().map(|m| m.len()) 170 | } 171 | 172 | fn read_exact_at(&mut self, buf: &mut [u8], offset: u64) -> Result<(), std::io::Error> { 173 | self.file.seek(SeekFrom::Start(offset))?; 174 | self.file.read_exact(buf) 175 | } 176 | 177 | fn write_all_at(&mut self, buf: &[u8], offset: u64) -> Result<(), std::io::Error> { 178 | self.file.seek(SeekFrom::Start(offset))?; 179 | self.file.write_all(buf)?; 180 | Ok(()) 181 | } 182 | 183 | fn sync(&mut self, data_only: bool) -> Result<(), std::io::Error> { 184 | if data_only { 185 | self.file.sync_data() 186 | } else { 187 | self.file.sync_all() 188 | } 189 | } 190 | 191 | fn set_len(&mut self, len: u64) -> Result<(), std::io::Error> { 192 | self.file.set_len(len) 193 | } 194 | 195 | fn lock(&mut self, to: LockKind) -> Result { 196 | let lock = match &mut self.lock { 197 | Some(lock) => lock, 198 | None => self.lock.get_or_insert(Lock::from_file(&self.file)?), 199 | }; 200 | 201 | // Return false if exclusive was requested and only pending was acquired. 202 | Ok(lock.lock(to) && lock.current() == to) 203 | } 204 | 205 | fn reserved(&mut self) -> Result { 206 | let lock = match &mut self.lock { 207 | Some(lock) => lock, 208 | None => self.lock.get_or_insert(Lock::from_file(&self.file)?), 209 | }; 210 | 211 | Ok(lock.reserved()) 212 | } 213 | 214 | fn current_lock(&self) -> Result { 215 | Ok(self 216 | .lock 217 | .as_ref() 218 | .map(|l| l.current()) 219 | .unwrap_or(LockKind::None)) 220 | } 221 | 222 | fn moved(&self) -> Result { 223 | let ino = fs::metadata(&self.path).map(|m| m.ino()).unwrap_or(0); 224 | Ok(ino == 0 || ino != self.file_ino) 225 | } 226 | 227 | fn wal_index(&self, readonly: bool) -> Result { 228 | let path = self.path.with_extension(format!( 229 | "{}-shm", 230 | self.path 231 | .extension() 232 | .and_then(|ext| ext.to_str()) 233 | .map(|ext| ext.split_once('-').map(|(f, _)| f).unwrap_or(ext)) 234 | .unwrap_or("db") 235 | )); 236 | let is_new = !path.exists(); 237 | 238 | let mut opts = fs::OpenOptions::new(); 239 | opts.read(true); 240 | if !readonly { 241 | opts.write(true).create(true).truncate(false); 242 | } 243 | 244 | let file = opts.open(&path)?; 245 | let mut file_lock = FileLock::new(file); 246 | if !readonly && file_lock.exclusive() { 247 | // If it is the first connection to open the database, truncate the index. 248 | let new_file = fs::OpenOptions::new() 249 | .read(true) 250 | .write(true) 251 | .create(true) 252 | .truncate(true) 253 | .open(&path) 254 | .map_err(|err| err)?; 255 | 256 | let new_lock = FileLock::new(new_file); 257 | 258 | if is_new { 259 | let mode = permissions(&self.path)?; 260 | let perm = Permissions::from_mode(mode); 261 | // Match permissions of main db file, but don't downgrade to readonly. 262 | if !perm.readonly() { 263 | fs::set_permissions(&path, perm)?; 264 | } 265 | } 266 | 267 | // Transition previous lock to shared before getting a shared on the new file 268 | // descriptor to make sure that there isn't any other concurrent process/thread getting 269 | // an exclusive lock during the transition. 270 | assert!(file_lock.shared()); 271 | assert!(new_lock.shared()); 272 | 273 | file_lock = new_lock; 274 | } else { 275 | file_lock.wait_shared(); 276 | } 277 | 278 | Ok(WalConnection { 279 | path, 280 | file_lock, 281 | wal_lock: RangeLock::new(self.file_ino), 282 | readonly, 283 | }) 284 | } 285 | } 286 | 287 | impl WalIndex for WalConnection { 288 | fn map(&mut self, region: u32) -> Result<[u8; 32768], std::io::Error> { 289 | let mut data = [0u8; 32768]; 290 | self.pull(region, &mut data)?; 291 | Ok(data) 292 | } 293 | 294 | fn lock( 295 | &mut self, 296 | locks: std::ops::Range, 297 | lock: WalIndexLock, 298 | ) -> Result { 299 | self.wal_lock.lock(locks, lock) 300 | } 301 | 302 | fn delete(self) -> Result<(), std::io::Error> { 303 | fs::remove_file(&self.path) 304 | } 305 | 306 | fn pull(&mut self, region: u32, data: &mut [u8; 32768]) -> Result<(), std::io::Error> { 307 | let current_size = self.file_lock.file().metadata()?.size(); 308 | let min_size = (region as u64 + 1) * 32768; 309 | if !self.readonly && current_size < min_size { 310 | self.file_lock.file().set_len(min_size)?; 311 | } 312 | 313 | self.file_lock 314 | .file() 315 | .seek(SeekFrom::Start(region as u64 * 32768))?; 316 | match self.file_lock.file().read_exact(data) { 317 | Ok(()) => Ok(()), 318 | Err(err) if self.readonly && err.kind() == ErrorKind::UnexpectedEof => Ok(()), 319 | Err(err) => Err(err), 320 | } 321 | } 322 | 323 | fn push(&mut self, region: u32, data: &[u8; 32768]) -> Result<(), std::io::Error> { 324 | let current_size = self.file_lock.file().metadata()?.size(); 325 | let min_size = (region as u64 + 1) * 32768; 326 | if current_size < min_size { 327 | self.file_lock.file().set_len(min_size)?; 328 | } 329 | 330 | self.file_lock 331 | .file() 332 | .seek(SeekFrom::Start(region as u64 * 32768))?; 333 | self.file_lock.file().write_all(data)?; 334 | self.file_lock.file().sync_all()?; 335 | 336 | Ok(()) 337 | } 338 | } 339 | 340 | // Source: https://github.com/rust-lang/cargo/blob/7a3b56b4860c0e58dab815549a93198a1c335b64/crates/cargo-util/src/paths.rs#L81 341 | fn normalize_path(path: &Path) -> PathBuf { 342 | use std::path::Component; 343 | 344 | let mut components = path.components().peekable(); 345 | let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { 346 | components.next(); 347 | PathBuf::from(c.as_os_str()) 348 | } else { 349 | PathBuf::new() 350 | }; 351 | 352 | for component in components { 353 | match component { 354 | Component::Prefix(..) => unreachable!(), 355 | Component::RootDir => { 356 | ret.push(component.as_os_str()); 357 | } 358 | Component::CurDir => {} 359 | Component::ParentDir => { 360 | ret.pop(); 361 | } 362 | Component::Normal(c) => { 363 | ret.push(c); 364 | } 365 | } 366 | } 367 | ret 368 | } 369 | 370 | fn permissions(path: &Path) -> io::Result { 371 | let path = path.with_extension( 372 | path.extension() 373 | .and_then(|ext| ext.to_str()) 374 | .map(|ext| ext.split_once('-').map(|(f, _)| f).unwrap_or(ext)) 375 | .unwrap_or("db"), 376 | ); 377 | Ok(fs::metadata(&path)?.permissions().mode()) 378 | } 379 | -------------------------------------------------------------------------------- /test-vfs/tests/lock_test.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | use test_vfs::lock::{Lock, LockKind}; 5 | 6 | fn test_file(name: &str) -> PathBuf { 7 | let path = PathBuf::from(env!("CARGO_TARGET_TMPDIR")) 8 | .join(name) 9 | .with_extension("txt"); 10 | fs::write(&path, "").unwrap(); 11 | path 12 | } 13 | 14 | #[test] 15 | fn test_none() { 16 | let path = test_file(".test_none"); 17 | let lock = Lock::new(&path).unwrap(); 18 | assert_eq!(lock.current(), LockKind::None); 19 | } 20 | 21 | #[test] 22 | fn test_shared() { 23 | let path = test_file(".test_shared"); 24 | let mut lock = Lock::new(&path).unwrap(); 25 | assert!(lock.lock(LockKind::Shared)); 26 | assert_eq!(lock.current(), LockKind::Shared); 27 | } 28 | 29 | #[test] 30 | fn test_reserved() { 31 | let path = test_file(".test_reserved"); 32 | let mut lock = Lock::new(&path).unwrap(); 33 | assert!(lock.lock(LockKind::Shared)); 34 | assert!(lock.lock(LockKind::Reserved)); 35 | assert_eq!(lock.current(), LockKind::Reserved); 36 | } 37 | 38 | #[test] 39 | fn test_exclusive() { 40 | let path = test_file(".test_exclusive"); 41 | let mut lock = Lock::new(&path).unwrap(); 42 | assert!(lock.lock(LockKind::Shared)); 43 | assert!(lock.lock(LockKind::Exclusive)); 44 | assert_eq!(lock.current(), LockKind::Exclusive); 45 | } 46 | 47 | #[test] 48 | fn test_exclusive_via_reserved() { 49 | let path = test_file(".test_exclusive_via_reserved"); 50 | let mut lock = Lock::new(&path).unwrap(); 51 | assert!(lock.lock(LockKind::Shared)); 52 | assert!(lock.lock(LockKind::Reserved)); 53 | assert!(lock.lock(LockKind::Exclusive)); 54 | assert_eq!(lock.current(), LockKind::Exclusive); 55 | } 56 | 57 | #[test] 58 | #[should_panic( 59 | expected = "cannot transition from unlocked to anything higher than shared (tried: Reserved)" 60 | )] 61 | fn test_none_to_reserved_panic() { 62 | let path = test_file(".test_none_to_reserved_panic"); 63 | let mut lock = Lock::new(&path).unwrap(); 64 | lock.lock(LockKind::Reserved); 65 | } 66 | 67 | #[test] 68 | #[should_panic( 69 | expected = "cannot transition from unlocked to anything higher than shared (tried: Exclusive)" 70 | )] 71 | fn test_none_to_exclusive_panic() { 72 | let path = test_file(".test_none_to_exclusive_panic"); 73 | let mut lock = Lock::new(&path).unwrap(); 74 | lock.lock(LockKind::Exclusive); 75 | } 76 | 77 | #[test] 78 | #[should_panic(expected = "cannot explicitly request pending lock (request explicit lock instead)")] 79 | fn test_shared_to_pending_panic() { 80 | let path = test_file(".test_shared_to_pending_panic"); 81 | let mut lock = Lock::new(&path).unwrap(); 82 | assert!(lock.lock(LockKind::Shared)); 83 | lock.lock(LockKind::Pending); 84 | } 85 | 86 | #[test] 87 | #[should_panic(expected = "cannot explicitly request pending lock (request explicit lock instead)")] 88 | fn test_reserved_to_pending_panic() { 89 | let path = test_file(".test_reserved_to_pending_panic"); 90 | let mut lock = Lock::new(&path).unwrap(); 91 | assert!(lock.lock(LockKind::Shared)); 92 | assert!(lock.lock(LockKind::Reserved)); 93 | lock.lock(LockKind::Pending); 94 | } 95 | 96 | #[test] 97 | fn test_reserved_once() { 98 | let path = test_file(".reserved_once"); 99 | let mut lock1 = Lock::new(&path).unwrap(); 100 | assert!(lock1.lock(LockKind::Shared)); 101 | 102 | let mut lock2 = Lock::new(&path).unwrap(); 103 | assert!(lock2.lock(LockKind::Shared)); 104 | 105 | assert!(lock1.lock(LockKind::Reserved)); 106 | assert!(!lock2.lock(LockKind::Reserved)); 107 | 108 | assert!(lock1.lock(LockKind::Shared)); 109 | assert!(lock2.lock(LockKind::Reserved)); 110 | } 111 | 112 | #[test] 113 | fn test_shared_while_reserved() { 114 | let path = test_file(".shared_while_reserved"); 115 | let mut lock1 = Lock::new(&path).unwrap(); 116 | assert!(lock1.lock(LockKind::Shared)); 117 | assert!(lock1.lock(LockKind::Reserved)); 118 | 119 | let mut lock2 = Lock::new(&path).unwrap(); 120 | assert!(lock2.lock(LockKind::Shared)); 121 | } 122 | 123 | #[test] 124 | fn test_pending() { 125 | let path = test_file(".test_pending"); 126 | let mut lock1 = Lock::new(&path).unwrap(); 127 | assert!(lock1.lock(LockKind::Shared)); 128 | 129 | let mut lock2 = Lock::new(&path).unwrap(); 130 | assert!(lock2.lock(LockKind::Shared)); 131 | assert!(lock2.lock(LockKind::Exclusive)); 132 | assert_eq!(lock2.current(), LockKind::Pending); 133 | } 134 | 135 | #[test] 136 | fn test_pending_once() { 137 | let path = test_file(".test_pending_once"); 138 | let mut lock1 = Lock::new(&path).unwrap(); 139 | assert!(lock1.lock(LockKind::Shared)); 140 | 141 | let mut lock2 = Lock::new(&path).unwrap(); 142 | assert!(lock2.lock(LockKind::Shared)); 143 | assert!(lock2.lock(LockKind::Exclusive)); 144 | 145 | assert!(!lock1.lock(LockKind::Exclusive)); 146 | 147 | assert_eq!(lock1.current(), LockKind::Shared); 148 | assert_eq!(lock2.current(), LockKind::Pending); 149 | } 150 | 151 | #[test] 152 | fn test_pending_to_exclusive() { 153 | let path = test_file(".test_pending_to_exclusive"); 154 | let mut lock1 = Lock::new(&path).unwrap(); 155 | assert!(lock1.lock(LockKind::Shared)); 156 | 157 | let mut lock2 = Lock::new(&path).unwrap(); 158 | assert!(lock2.lock(LockKind::Shared)); 159 | assert!(lock2.lock(LockKind::Exclusive)); 160 | 161 | assert!(lock1.lock(LockKind::None)); 162 | assert!(lock2.lock(LockKind::Exclusive)); 163 | 164 | assert_eq!(lock1.current(), LockKind::None); 165 | assert_eq!(lock2.current(), LockKind::Exclusive); 166 | } 167 | --------------------------------------------------------------------------------