1use std::{borrow::Cow, collections::HashMap, fmt::Debug, sync::Arc};
2
3use culprit::{Culprit, ResultExt};
4use graft::{GraftErr, LogicalErr, rt::runtime::Runtime};
5use parking_lot::Mutex;
6use sqlite_plugin::{
7 flags::{AccessFlags, CreateMode, LockLevel, OpenKind, OpenMode, OpenOpts},
8 vars::{
9 self, SQLITE_BUSY, SQLITE_BUSY_SNAPSHOT, SQLITE_CANTOPEN, SQLITE_INTERNAL, SQLITE_IOERR,
10 SQLITE_NOTFOUND,
11 },
12 vfs::{Pragma, PragmaErr, SqliteErr, Vfs, VfsResult},
13};
14use thiserror::Error;
15
16use crate::{
17 file::{FileHandle, VfsFile, mem_file::MemFile, vol_file::VolFile},
18 pragma::GraftPragma,
19};
20
21#[derive(Debug, Error)]
22pub enum ErrCtx {
23 #[error("Graft error: {0}")]
24 Graft(#[from] GraftErr),
25
26 #[error("Unknown Pragma")]
27 UnknownPragma,
28
29 #[error("Pragma error: {0}")]
30 PragmaErr(Cow<'static, str>),
31
32 #[error("Tag not found")]
33 TagNotFound,
34
35 #[error("Transaction is busy")]
36 Busy,
37
38 #[error("The transaction snapshot is no longer current")]
39 BusySnapshot,
40
41 #[error("Invalid lock transition")]
42 InvalidLockTransition,
43
44 #[error("Invalid volume state")]
45 InvalidVolumeState,
46
47 #[error(transparent)]
48 IoErr(#[from] std::io::Error),
49
50 #[error(transparent)]
51 FmtErr(#[from] std::fmt::Error),
52}
53
54impl ErrCtx {
55 #[inline]
56 fn wrap<T>(cb: impl FnOnce() -> culprit::Result<T, ErrCtx>) -> VfsResult<T> {
57 match cb() {
58 Ok(t) => Ok(t),
59 Err(err) => Err(err.ctx().sqlite_err()),
60 }
61 }
62
63 fn sqlite_err(&self) -> SqliteErr {
64 match self {
65 ErrCtx::UnknownPragma => SQLITE_NOTFOUND,
66 ErrCtx::TagNotFound => SQLITE_CANTOPEN,
67 ErrCtx::Busy => SQLITE_BUSY,
68 ErrCtx::BusySnapshot => SQLITE_BUSY_SNAPSHOT,
69 ErrCtx::Graft(err) => Self::map_graft_err(err),
70 _ => SQLITE_INTERNAL,
71 }
72 }
73
74 fn map_graft_err(err: &GraftErr) -> SqliteErr {
75 match err {
76 GraftErr::Storage(_) => SQLITE_IOERR,
77 GraftErr::Remote(_) => SQLITE_IOERR,
78 GraftErr::Logical(err) => match err {
79 LogicalErr::VolumeNotFound(_) => SQLITE_IOERR,
80 LogicalErr::VolumeConcurrentWrite(_) => SQLITE_BUSY_SNAPSHOT,
81 LogicalErr::VolumeNeedsRecovery(_)
82 | LogicalErr::VolumeDiverged(_)
83 | LogicalErr::VolumeRemoteMismatch { .. } => SQLITE_INTERNAL,
84 },
85 }
86 }
87}
88
89impl<T> From<ErrCtx> for culprit::Result<T, ErrCtx> {
90 fn from(err: ErrCtx) -> culprit::Result<T, ErrCtx> {
91 Err(Culprit::new(err))
92 }
93}
94
95pub struct GraftVfs {
96 runtime: Runtime,
97 locks: Mutex<HashMap<String, Arc<Mutex<()>>>>,
99}
100
101impl GraftVfs {
102 pub fn new(runtime: Runtime) -> Self {
103 Self { runtime, locks: Default::default() }
104 }
105}
106
107impl Vfs for GraftVfs {
108 type Handle = FileHandle;
109
110 fn device_characteristics(&self) -> i32 {
111 vars::SQLITE_IOCAP_ATOMIC512 |
113 vars::SQLITE_IOCAP_ATOMIC1K |
114 vars::SQLITE_IOCAP_ATOMIC2K |
115 vars::SQLITE_IOCAP_ATOMIC4K |
116 vars::SQLITE_IOCAP_POWERSAFE_OVERWRITE |
120 vars::SQLITE_IOCAP_SAFE_APPEND |
123 vars::SQLITE_IOCAP_SEQUENTIAL
125 }
126
127 fn access(&self, path: &str, flags: AccessFlags) -> VfsResult<bool> {
128 tracing::trace!("access: path={path:?}; flags={flags:?}");
129 ErrCtx::wrap(move || self.runtime.tag_exists(path).or_into_ctx())
130 }
131
132 fn open(&self, path: Option<&str>, opts: OpenOpts) -> VfsResult<Self::Handle> {
133 tracing::trace!("open: path={path:?}, opts={opts:?}");
134 ErrCtx::wrap(move || {
135 if opts.kind() == OpenKind::MainDb
137 && let Some(tag) = path
138 {
139 let can_create = matches!(
140 opts.mode(),
141 OpenMode::ReadWrite {
142 create: CreateMode::Create | CreateMode::MustCreate
143 }
144 );
145
146 let vid = if can_create {
147 if let Some(vid) = self.runtime.tag_get(tag).or_into_ctx()? {
149 vid
150 } else {
151 let volume = self.runtime.volume_open(None, None, None).or_into_ctx()?;
152 self.runtime
153 .tag_replace(tag, volume.vid.clone())
154 .or_into_ctx()?;
155 volume.vid
156 }
157 } else {
158 self.runtime
160 .tag_get(tag)
161 .or_into_ctx()?
162 .ok_or(ErrCtx::TagNotFound)?
163 };
164
165 let reserved_lock = self.locks.lock().entry(tag.to_owned()).or_default().clone();
167
168 return Ok(VolFile::new(
169 self.runtime.clone(),
170 tag.to_owned(),
171 vid,
172 opts,
173 reserved_lock,
174 )
175 .into());
176 }
177
178 Ok(MemFile::default().into())
180 })
181 }
182
183 fn delete(&self, path: &str) -> VfsResult<()> {
184 tracing::trace!("delete: path={path:?}");
187 Ok(())
188 }
189
190 fn close(&self, handle: Self::Handle) -> VfsResult<()> {
191 tracing::trace!("close: file={handle:?}");
192 ErrCtx::wrap(move || {
193 match handle {
194 FileHandle::MemFile(_) => Ok(()),
195 FileHandle::VolFile(vol_file) => {
196 if vol_file.opts().delete_on_close() {
197 }
200
201 let mut locks = self.locks.lock();
203 let reserved_lock = locks
204 .get(&vol_file.tag)
205 .expect("reserved lock missing from lock manager");
206
207 if Arc::strong_count(reserved_lock) == 1 {
212 locks.remove(&vol_file.tag);
213 }
214
215 Ok(())
216 }
217 }
218 })
219 }
220
221 fn pragma(
222 &self,
223 handle: &mut Self::Handle,
224 pragma: Pragma<'_>,
225 ) -> Result<Option<String>, PragmaErr> {
226 tracing::trace!("pragma: file={handle:?}, pragma={pragma:?}");
227 if let FileHandle::VolFile(file) = handle {
228 match GraftPragma::try_from(&pragma)?.eval(&self.runtime, file) {
229 Ok(val) => Ok(val),
230 Err(err) => Err(PragmaErr::Fail(
231 err.ctx().sqlite_err(),
232 Some(format!("{err}")),
233 )),
234 }
235 } else {
236 Err(PragmaErr::NotFound)
237 }
238 }
239
240 fn lock(&self, handle: &mut Self::Handle, level: LockLevel) -> VfsResult<()> {
241 tracing::trace!("lock: file={handle:?}, level={level:?}");
242 ErrCtx::wrap(move || handle.lock(level))
243 }
244
245 fn unlock(&self, handle: &mut Self::Handle, level: LockLevel) -> VfsResult<()> {
246 tracing::trace!("unlock: file={handle:?}, level={level:?}");
247 ErrCtx::wrap(move || handle.unlock(level))
248 }
249
250 fn file_size(&self, handle: &mut Self::Handle) -> VfsResult<usize> {
251 tracing::trace!("file_size: handle={handle:?}");
252 ErrCtx::wrap(move || handle.file_size())
253 }
254
255 fn truncate(&self, handle: &mut Self::Handle, size: usize) -> VfsResult<()> {
256 tracing::trace!("truncate: handle={handle:?}, size={size}");
257 ErrCtx::wrap(move || handle.truncate(size))
258 }
259
260 fn write(&self, handle: &mut Self::Handle, offset: usize, data: &[u8]) -> VfsResult<usize> {
261 tracing::trace!(
262 "write: handle={handle:?}, offset={offset}, len={}",
263 data.len()
264 );
265 ErrCtx::wrap(move || handle.write(offset, data))
266 }
267
268 fn read(&self, handle: &mut Self::Handle, offset: usize, data: &mut [u8]) -> VfsResult<usize> {
269 tracing::trace!(
270 "read: handle={handle:?}, offset={offset}, len={}",
271 data.len()
272 );
273 ErrCtx::wrap(move || handle.read(offset, data))
274 }
275}