use std::cell::UnsafeCell;
use std::convert::TryFrom;
use std::ffi::{CStr, CString, OsStr};
use std::mem::{self, ManuallyDrop};
use std::os::raw::{c_char, c_int, c_void};
use std::path::Path;
use std::ptr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Weak};
use libsqlite3_sys::*;
use log::error;
use crate::{
crypt::{master_key::MasterKey, xex},
support::error::Error,
};
pub struct XexVfs {
wrapper: Arc<VfsWrapper>,
}
impl XexVfs {
pub fn new(master_key: Arc<MasterKey>) -> Result<Self, Error> {
let delegate_vfs = unsafe { sqlite3_vfs_find(ptr::null()) };
if delegate_vfs.is_null() {
return Err(Error::Sqlite(SQLITE_NOTFOUND));
}
let delegate_vfs = unsafe { &mut *delegate_vfs };
static SEQ: AtomicU64 = AtomicU64::new(0);
let seq = SEQ.fetch_add(1, Ordering::Relaxed);
let name = CString::new(format!("xex{seq}")).unwrap();
let app_data = Box::into_raw(Box::new(VfsAppData {
master_key,
wrapper: Weak::new(),
delegate_vfs,
}));
let raw_vfs = sqlite3_vfs {
iVersion: 2, szOsFile: mem::size_of::<File>() as c_int,
mxPathname: delegate_vfs.mxPathname,
pNext: ptr::null_mut(),
zName: name.as_ptr(),
pAppData: app_data.cast(),
xOpen: Some(vfs_open),
xDelete: Some(vfs_delete),
xAccess: Some(vfs_access),
xFullPathname: Some(vfs_full_pathname),
xDlOpen: None,
xDlError: None,
xDlSym: None,
xDlClose: None,
xRandomness: delegate_vfs
.xRandomness
.is_some()
.then_some(vfs_randomness),
xSleep: delegate_vfs.xSleep.is_some().then_some(vfs_sleep),
xCurrentTime: delegate_vfs
.xCurrentTime
.is_some()
.then_some(vfs_current_time),
xGetLastError: delegate_vfs
.xGetLastError
.is_some()
.then_some(vfs_get_last_error),
xCurrentTimeInt64: delegate_vfs
.xCurrentTimeInt64
.is_some()
.then_some(vfs_current_time_int64),
xSetSystemCall: None,
xGetSystemCall: None,
xNextSystemCall: None,
};
let wrapper = Arc::new(VfsWrapper {
vfs: UnsafeCell::new(raw_vfs),
name,
});
unsafe {
(*app_data).wrapper = Arc::downgrade(&wrapper);
}
let err = unsafe {
sqlite3_vfs_register(UnsafeCell::raw_get(&wrapper.vfs), 0)
};
if 0 != err {
return Err(Error::Sqlite(err));
}
Ok(Self { wrapper })
}
pub fn name(&self) -> &str {
self.wrapper.name.to_str().unwrap()
}
}
struct VfsWrapper {
vfs: UnsafeCell<sqlite3_vfs>,
name: CString,
}
unsafe impl Send for VfsWrapper {}
unsafe impl Sync for VfsWrapper {}
struct VfsAppData {
master_key: Arc<MasterKey>,
wrapper: Weak<VfsWrapper>,
delegate_vfs: *mut sqlite3_vfs,
}
#[repr(C)]
struct File {
base_class: sqlite3_file,
delegate_file: *mut sqlite3_file,
delegate_vfs: *mut sqlite3_vfs,
xex: xex::Xex,
vfs_wrapper: Arc<VfsWrapper>,
}
impl Drop for VfsWrapper {
fn drop(&mut self) {
unsafe {
let vfs = UnsafeCell::raw_get(&self.vfs);
sqlite3_vfs_unregister(vfs);
if !(*vfs).pAppData.is_null() {
let _ = Box::<VfsAppData>::from_raw((*vfs).pAppData.cast());
}
}
}
}
macro_rules! invoke_file_delegate {
($this:ident->$method:ident($($arg:expr),* $(,)*)) => {{
let delegate: &mut sqlite3_file = &mut *$this.delegate_file;
let io_methods: &sqlite3_io_methods = &*delegate.pMethods;
if let Some(f) = io_methods.$method {
f(delegate $(,$arg)*)
} else {
0
}
}}
}
macro_rules! invoke_vfs_delegate {
($this:ident->$method:ident($($arg:expr),* $(,)*)) => {{
let delegate: &mut sqlite3_vfs = &mut *$this.delegate_vfs;
if let Some(f) = delegate.$method {
f(delegate $(,$arg)*)
} else {
SQLITE_NOTFOUND
}
}}
}
struct DelegateBacking {
delegate_file: *mut sqlite3_file,
}
impl xex::Backing for DelegateBacking {
type Error = c_int;
fn read(&mut self, dst: &mut [u8], offset: u64) -> Result<(), c_int> {
let dst_len =
c_int::try_from(dst.len()).map_err(|_| SQLITE_IOERR_READ)?;
let offset = i64::try_from(offset).map_err(|_| SQLITE_IOERR_SEEK)?;
unsafe {
zero_or_err(invoke_file_delegate!(self->xRead(
dst.as_mut_ptr().cast(),
dst_len,
offset,
)))
}
}
fn write(&mut self, src: &[u8], offset: u64) -> Result<(), c_int> {
let src_len =
c_int::try_from(src.len()).map_err(|_| SQLITE_IOERR_WRITE)?;
let offset = i64::try_from(offset).map_err(|_| SQLITE_IOERR_SEEK)?;
unsafe {
zero_or_err(invoke_file_delegate!(self->xWrite(
src.as_ptr().cast(),
src_len,
offset,
)))
}
}
fn len(&mut self) -> Result<u64, c_int> {
let mut size = 0i64;
unsafe {
zero_or_err(invoke_file_delegate!(self->xFileSize(&mut size)))?;
}
u64::try_from(size).map_err(|_| SQLITE_IOERR_FSTAT)
}
fn encryption_error() -> c_int {
SQLITE_IOERR_AUTH
}
}
fn zero_or_err(rc: c_int) -> Result<(), c_int> {
if 0 == rc {
Ok(())
} else {
Err(rc)
}
}
unsafe fn vfs_app_data<'a>(vfs: *mut sqlite3_vfs) -> &'a VfsAppData {
&*(*vfs).pAppData.cast()
}
unsafe extern "C" fn vfs_open(
vfs: *mut sqlite3_vfs,
name: *const c_char,
file: *mut sqlite3_file,
flags: c_int,
out_flags: *mut c_int,
) -> c_int {
let app_data = vfs_app_data(vfs);
let Some(vfs_wrapper) = app_data.wrapper.upgrade() else {
error!("BUG: File opened after VfsWrapper dropped");
return SQLITE_IOERR_CONVPATH;
};
let Some(mut name_str) = CStr::from_ptr(name.cast())
.to_str()
.ok()
.map(Path::new)
.and_then(Path::file_name)
.and_then(OsStr::to_str)
else {
return SQLITE_IOERR_CONVPATH;
};
name_str = name_str.split_once('?').map(|s| s.0).unwrap_or(name_str);
let xex = match xex::Xex::new(&app_data.master_key, name_str) {
Ok(xex) => xex,
Err(err) => {
error!("BUG: Failed to init XEX: {err:?}");
return SQLITE_IOERR_AUTH;
},
};
let child_file: *mut sqlite3_file =
sqlite3_malloc((*app_data.delegate_vfs).szOsFile).cast();
if child_file.is_null() {
return SQLITE_NOMEM;
}
let err = invoke_vfs_delegate!(
app_data->xOpen(name, child_file, flags, out_flags));
if 0 != err {
sqlite3_free(child_file.cast());
return err;
}
ptr::write(
file.cast(),
File {
base_class: sqlite3_file {
pMethods: &FILE_IO_METHODS,
},
delegate_file: child_file,
delegate_vfs: app_data.delegate_vfs,
xex,
vfs_wrapper,
},
);
0
}
unsafe extern "C" fn vfs_delete(
vfs: *mut sqlite3_vfs,
name: *const c_char,
sync_dir: c_int,
) -> c_int {
let app_data = vfs_app_data(vfs);
invoke_vfs_delegate!(app_data->xDelete(name, sync_dir))
}
unsafe extern "C" fn vfs_access(
vfs: *mut sqlite3_vfs,
name: *const c_char,
flags: c_int,
out: *mut c_int,
) -> c_int {
let app_data = vfs_app_data(vfs);
invoke_vfs_delegate!(app_data->xAccess(name, flags, out))
}
unsafe extern "C" fn vfs_full_pathname(
vfs: *mut sqlite3_vfs,
name: *const c_char,
nout: c_int,
out: *mut c_char,
) -> c_int {
let app_data = vfs_app_data(vfs);
invoke_vfs_delegate!(app_data->xFullPathname(name, nout, out))
}
unsafe extern "C" fn vfs_randomness(
vfs: *mut sqlite3_vfs,
n: c_int,
out: *mut c_char,
) -> c_int {
let app_data = vfs_app_data(vfs);
invoke_vfs_delegate!(app_data->xRandomness(n, out))
}
unsafe extern "C" fn vfs_sleep(vfs: *mut sqlite3_vfs, us: c_int) -> c_int {
let app_data = vfs_app_data(vfs);
invoke_vfs_delegate!(app_data->xSleep(us))
}
unsafe extern "C" fn vfs_current_time(
vfs: *mut sqlite3_vfs,
out: *mut f64,
) -> c_int {
let app_data = vfs_app_data(vfs);
invoke_vfs_delegate!(app_data->xCurrentTime(out))
}
unsafe extern "C" fn vfs_get_last_error(
vfs: *mut sqlite3_vfs,
arg2: c_int,
arg3: *mut c_char,
) -> c_int {
let app_data = vfs_app_data(vfs);
invoke_vfs_delegate!(app_data->xGetLastError(arg2, arg3))
}
unsafe extern "C" fn vfs_current_time_int64(
vfs: *mut sqlite3_vfs,
out: *mut i64,
) -> c_int {
let app_data = vfs_app_data(vfs);
invoke_vfs_delegate!(app_data->xCurrentTimeInt64(out))
}
impl Drop for File {
fn drop(&mut self) {
unsafe {
sqlite3_free(self.delegate_file.cast());
}
self.delegate_file = ptr::null_mut();
}
}
unsafe extern "C" fn file_close(f: *mut sqlite3_file) -> c_int {
let f: &mut ManuallyDrop<File> = &mut *f.cast();
let err = invoke_file_delegate!(f->xClose());
ManuallyDrop::drop(f);
err
}
unsafe extern "C" fn file_read(
f: *mut sqlite3_file,
dst: *mut c_void,
len: c_int,
offset: i64,
) -> c_int {
let f: &mut File = &mut *f.cast();
let Ok(len) = usize::try_from(len) else {
return SQLITE_IOERR_READ;
};
let Ok(offset) = u64::try_from(offset) else {
return SQLITE_IOERR_SEEK;
};
let dst = std::slice::from_raw_parts_mut(dst.cast::<u8>(), len);
let mut backing = DelegateBacking {
delegate_file: f.delegate_file,
};
match f.xex.read(&mut backing, dst, offset) {
Ok(()) => 0,
Err(SQLITE_IOERR_SHORT_READ) => {
let Ok(file_len) = xex::Backing::len(&mut backing) else {
return SQLITE_IOERR_SEEK;
};
let read_amount = usize::try_from(file_len.saturating_sub(offset))
.unwrap_or(dst.len())
.min(dst.len());
if let Err(err) =
f.xex.read(&mut backing, &mut dst[..read_amount], offset)
{
if SQLITE_IOERR_SHORT_READ == err {
return SQLITE_IOERR_READ;
} else {
return err;
}
}
dst[read_amount..].fill(0);
SQLITE_IOERR_SHORT_READ
},
Err(err) => err,
}
}
unsafe extern "C" fn file_write(
f: *mut sqlite3_file,
src: *const c_void,
len: c_int,
offset: i64,
) -> c_int {
let f: &mut File = &mut *f.cast();
let Ok(len) = usize::try_from(len) else {
return SQLITE_IOERR_WRITE;
};
let Ok(offset) = u64::try_from(offset) else {
return SQLITE_IOERR_SEEK;
};
let src = std::slice::from_raw_parts(src.cast::<u8>(), len);
let mut backing = DelegateBacking {
delegate_file: f.delegate_file,
};
f.xex.write(&mut backing, src, offset).err().unwrap_or(0)
}
unsafe extern "C" fn file_truncate(
f: *mut sqlite3_file,
mut size: i64,
) -> c_int {
let block_size = crate::crypt::AES_BLOCK as i64;
size = (size + block_size - 1) / block_size * block_size;
let f: &mut File = &mut *f.cast();
invoke_file_delegate!(f->xTruncate(size))
}
unsafe extern "C" fn file_sync(f: *mut sqlite3_file, flags: c_int) -> c_int {
let f: &mut File = &mut *f.cast();
invoke_file_delegate!(f->xSync(flags))
}
unsafe extern "C" fn file_file_size(
f: *mut sqlite3_file,
dst: *mut i64,
) -> c_int {
let f: &mut File = &mut *f.cast();
invoke_file_delegate!(f->xFileSize(dst))
}
unsafe extern "C" fn file_lock(f: *mut sqlite3_file, i: c_int) -> c_int {
let f: &mut File = &mut *f.cast();
invoke_file_delegate!(f->xLock(i))
}
unsafe extern "C" fn file_unlock(f: *mut sqlite3_file, i: c_int) -> c_int {
let f: &mut File = &mut *f.cast();
invoke_file_delegate!(f->xUnlock(i))
}
unsafe extern "C" fn file_check_reserved_lock(
f: *mut sqlite3_file,
out: *mut c_int,
) -> c_int {
let f: &mut File = &mut *f.cast();
invoke_file_delegate!(f->xCheckReservedLock(out))
}
unsafe extern "C" fn file_file_control(
f: *mut sqlite3_file,
op: c_int,
arg: *mut c_void,
) -> c_int {
let f: &mut File = &mut *f.cast();
match op {
SQLITE_FCNTL_LOCKSTATE
| SQLITE_FCNTL_SIZE_HINT
| SQLITE_FCNTL_SIZE_LIMIT
| SQLITE_FCNTL_CHUNK_SIZE
| SQLITE_FCNTL_SYNC
| SQLITE_FCNTL_COMMIT_PHASETWO
| SQLITE_FCNTL_WIN32_AV_RETRY
| SQLITE_FCNTL_PERSIST_WAL
| SQLITE_FCNTL_POWERSAFE_OVERWRITE
| SQLITE_FCNTL_OVERWRITE
| SQLITE_FCNTL_PRAGMA
| SQLITE_FCNTL_BUSYHANDLER
| SQLITE_FCNTL_TRACE
| SQLITE_FCNTL_HAS_MOVED
| SQLITE_FCNTL_WAL_BLOCK
| SQLITE_FCNTL_BEGIN_ATOMIC_WRITE
| SQLITE_FCNTL_COMMIT_ATOMIC_WRITE
| SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE
| SQLITE_FCNTL_LOCK_TIMEOUT
| SQLITE_FCNTL_CKPT_START
| SQLITE_FCNTL_CKPT_DONE
| SQLITE_FCNTL_EXTERNAL_READER => {
invoke_file_delegate!(f->xFileControl(op, arg))
},
SQLITE_FCNTL_VFS_POINTER => {
*arg.cast::<*mut sqlite3_vfs>() = f.delegate_vfs;
0
},
SQLITE_FCNTL_VFSNAME => {
SQLITE_NOTFOUND
},
_ => SQLITE_NOTFOUND,
}
}
unsafe extern "C" fn file_sector_size(f: *mut sqlite3_file) -> c_int {
let f: &mut File = &mut *f.cast();
invoke_file_delegate!(f->xSectorSize())
}
unsafe extern "C" fn file_device_characteristics(
f: *mut sqlite3_file,
) -> c_int {
let f: &mut File = &mut *f.cast();
let mut dc = invoke_file_delegate!(f->xDeviceCharacteristics());
if 0 != dc & SQLITE_IOCAP_ATOMIC {
dc &= !SQLITE_IOCAP_ATOMIC;
dc |= SQLITE_IOCAP_ATOMIC512;
}
dc
}
unsafe extern "C" fn file_shm_map(
f: *mut sqlite3_file,
pg: c_int,
pgsz: c_int,
arg: c_int,
out: *mut *mut c_void,
) -> c_int {
let f: &mut File = &mut *f.cast();
invoke_file_delegate!(f->xShmMap(pg, pgsz, arg, out))
}
unsafe extern "C" fn file_shm_lock(
f: *mut sqlite3_file,
offset: c_int,
n: c_int,
flags: c_int,
) -> c_int {
let f: &mut File = &mut *f.cast();
invoke_file_delegate!(f->xShmLock(offset, n, flags))
}
unsafe extern "C" fn file_shm_barrier(f: *mut sqlite3_file) {
let f: &mut File = &mut *f.cast();
let delegate: &mut sqlite3_file = &mut *f.delegate_file;
let io_methods: &sqlite3_io_methods = &*delegate.pMethods;
if let Some(f) = io_methods.xShmBarrier {
f(delegate);
}
}
unsafe extern "C" fn file_shm_unmap(
f: *mut sqlite3_file,
delete_flag: c_int,
) -> c_int {
let f: &mut File = &mut *f.cast();
invoke_file_delegate!(f->xShmUnmap(delete_flag))
}
static FILE_IO_METHODS: sqlite3_io_methods = sqlite3_io_methods {
iVersion: 2,
xClose: Some(file_close),
xRead: Some(file_read),
xWrite: Some(file_write),
xTruncate: Some(file_truncate),
xSync: Some(file_sync),
xFileSize: Some(file_file_size),
xLock: Some(file_lock),
xUnlock: Some(file_unlock),
xCheckReservedLock: Some(file_check_reserved_lock),
xFileControl: Some(file_file_control),
xSectorSize: Some(file_sector_size),
xDeviceCharacteristics: Some(file_device_characteristics),
xShmMap: Some(file_shm_map),
xShmLock: Some(file_shm_lock),
xShmBarrier: Some(file_shm_barrier),
xShmUnmap: Some(file_shm_unmap),
xFetch: None,
xUnfetch: None,
};