#![warn(clippy::missing_errors_doc)]
pub(crate) mod crypto;
pub(crate) mod data_page;
pub(crate) mod defrag;
pub(crate) mod error;
pub(crate) mod freemap;
pub(crate) mod freemap_tree;
pub(crate) mod handle;
pub(crate) mod handle_table;
mod lru;
pub(crate) mod membership_index;
pub(crate) mod overflow;
pub(crate) mod page;
pub(crate) mod page_cache;
pub(crate) mod page_io;
mod spillway;
pub(crate) mod stats;
pub(crate) mod superblock;
pub(crate) mod transaction;
#[cfg(test)]
mod recovery_tests;
pub use error::{ChiselError, Result};
pub use defrag::{DefragOptions, DefragStats};
pub use handle::{Handle, Tag, TagDropProgress};
pub use page::PAGE_SIZE;
pub use stats::{ChiselCounters, Stats};
pub use superblock::{
SlotDefect, SuperblockDefect, DEFAULT_SUPERBLOCK_COUNT, MAX_SUPERBLOCKS, MIN_SUPERBLOCKS,
NAMED_ROOT_COUNT, NAMED_ROOT_NAME_LEN,
};
pub use page::format_major;
pub use crypto::{Argon2Params, Key};
use std::path::Path;
use page_cache::PageCache;
use page_io::PageIo;
use transaction::TransactionManager;
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct Options {
pub cache_max_bytes: u64,
pub spillway_max_bytes: u64,
pub drain_insertion: DrainInsertion,
pub create_if_missing: bool,
pub read_only: bool,
pub superblock_count: u32,
pub encryption_key: Option<Key>,
pub argon2_params: Option<Argon2Params>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DrainInsertion {
LruTail,
Mru,
}
#[derive(Debug, Clone)]
pub(crate) enum SpillwayLocation {
Path(std::path::PathBuf),
InMemory,
}
impl Default for Options {
fn default() -> Options {
let cache_max_bytes = 8 * 1024 * 1024; Options {
cache_max_bytes,
spillway_max_bytes: cache_max_bytes.saturating_mul(1024),
drain_insertion: DrainInsertion::LruTail,
create_if_missing: true,
read_only: false,
superblock_count: superblock::DEFAULT_SUPERBLOCK_COUNT,
encryption_key: None,
argon2_params: None,
}
}
}
impl Options {
pub fn cache_max_bytes(mut self, bytes: u64) -> Self {
self.cache_max_bytes = bytes;
self
}
pub fn spillway_max_bytes(mut self, bytes: u64) -> Self {
self.spillway_max_bytes = bytes;
self
}
pub fn drain_insertion(mut self, policy: DrainInsertion) -> Self {
self.drain_insertion = policy;
self
}
pub fn create_if_missing(mut self, create: bool) -> Self {
self.create_if_missing = create;
self
}
pub fn read_only(mut self, read_only: bool) -> Self {
self.read_only = read_only;
self
}
pub fn superblock_count(mut self, count: u32) -> Self {
self.superblock_count = count;
self
}
pub fn encryption_key(mut self, key: Key) -> Self {
self.encryption_key = Some(key);
self
}
pub fn argon2_params(mut self, params: Argon2Params) -> Self {
self.argon2_params = Some(params);
self
}
}
pub struct Chisel {
txm: TransactionManager,
}
impl Chisel {
pub fn open(path: &Path, options: Options) -> Result<Chisel> {
if options.superblock_count < superblock::MIN_SUPERBLOCKS
|| options.superblock_count > superblock::MAX_SUPERBLOCKS
{
return Err(ChiselError::InvalidSuperblockCount {
value: options.superblock_count,
});
}
let file_exists = path.exists()
&& std::fs::metadata(path)
.map(|m| m.len() > 0)
.unwrap_or(false);
if !file_exists && !options.create_if_missing {
return Err(ChiselError::FileNotFound);
}
let io = PageIo::open(path, options.read_only)?;
let existed = io.page_count()? > 0;
let cache = PageCache::new(
io,
options.cache_max_bytes,
options.spillway_max_bytes,
options.drain_insertion,
SpillwayLocation::Path(path.to_path_buf()),
);
let txm = if existed {
TransactionManager::open_existing(cache, options.encryption_key.clone())?
} else {
TransactionManager::create_new(
cache,
options.superblock_count,
options.encryption_key.clone(),
options.argon2_params,
)?
};
Ok(Chisel { txm })
}
pub fn open_in_memory() -> Result<Chisel> {
Self::open_in_memory_with_options(Options::default())
}
pub fn open_in_memory_with_options(options: Options) -> Result<Chisel> {
if options.read_only {
return Err(ChiselError::ReadOnlyMode);
}
if options.superblock_count < superblock::MIN_SUPERBLOCKS
|| options.superblock_count > superblock::MAX_SUPERBLOCKS
{
return Err(ChiselError::InvalidSuperblockCount {
value: options.superblock_count,
});
}
let io = PageIo::open_in_memory()?;
let cache = PageCache::new(
io,
options.cache_max_bytes,
options.spillway_max_bytes,
options.drain_insertion,
SpillwayLocation::InMemory,
);
let txm = TransactionManager::create_new(
cache,
options.superblock_count,
options.encryption_key.clone(),
options.argon2_params,
)?;
Ok(Chisel { txm })
}
#[must_use = "Chisel::close may surface fsync/close errors in a future release; \
ignore explicitly with `let _ = db.close();` if intentional"]
pub fn close(self) -> Result<()> {
drop(self);
Ok(())
}
pub fn begin(&mut self) -> Result<()> {
self.txm.begin()
}
pub fn commit(&mut self) -> Result<()> {
self.txm.commit()
}
pub fn rollback(&mut self) -> Result<()> {
self.txm.rollback()
}
pub fn savepoint(&mut self, name: &str) -> Result<()> {
self.txm.savepoint(name)
}
pub fn rollback_to(&mut self, name: &str) -> Result<()> {
self.txm.rollback_to(name)
}
pub fn release(&mut self, name: &str) -> Result<()> {
self.txm.release(name)
}
pub fn allocate(&mut self, value: &[u8]) -> Result<Handle> {
self.txm.allocate(value).map(Handle::from)
}
pub fn allocate_tagged(&mut self, value: &[u8], tag: Tag) -> Result<Handle> {
self.txm.allocate_tagged(value, tag.get()).map(Handle::from)
}
pub fn tag(&self, handle: Handle) -> Result<Option<Tag>> {
self.txm.tag(handle.get()).map(Tag::new) }
pub fn client_byte(&self, handle: Handle) -> Result<u8> {
self.txm.client_byte(handle.get())
}
pub fn set_client_byte(&mut self, handle: Handle, byte: u8) -> Result<()> {
self.txm.set_client_byte(handle.get(), byte)
}
pub fn handles_with_tag(&self, tag: Tag) -> Result<Vec<Handle>> {
Ok(self
.txm
.handles_with_tag(tag.get())?
.into_iter()
.map(Handle::from)
.collect())
}
pub fn read(&self, handle: Handle) -> Result<Vec<u8>> {
self.txm.read(handle.get())
}
pub fn update(&mut self, handle: Handle, value: &[u8]) -> Result<()> {
self.txm.update(handle.get(), value)
}
pub fn delete(&mut self, handle: Handle) -> Result<()> {
self.txm.delete(handle.get())
}
pub fn delete_tagged(&mut self, handle: Handle, tag: Tag) -> Result<()> {
self.txm.delete_tagged(handle.get(), tag.get())
}
pub fn delete_with_tag(&mut self, tag: Tag, max: usize) -> Result<TagDropProgress> {
let (ids, complete) = self.txm.delete_with_tag(tag.get(), max)?;
Ok(TagDropProgress {
deleted: ids.into_iter().map(Handle::from).collect(),
complete,
})
}
pub fn delete_many(&mut self, handles: &[Handle]) -> Result<()> {
let raw: Vec<u64> = handles.iter().map(|h| h.get()).collect();
self.txm.delete_many(&raw)
}
pub fn set_root_name(&mut self, name: &str, handle: Handle) -> Result<()> {
self.txm.set_root_name(name, handle.get())
}
pub fn get_root_name(&self, name: &str) -> Result<Option<Handle>> {
Ok(self.txm.get_root_name(name)?.map(Handle::from))
}
pub fn clear_root_name(&mut self, name: &str) -> Result<()> {
self.txm.clear_root_name(name)
}
pub fn handles(&self) -> Result<Vec<Handle>> {
Ok(self.txm.handles()?.into_iter().map(Handle::from).collect())
}
pub fn stats(&self) -> Result<Stats> {
let handles = self.txm.handles()?;
let page_count = self.txm.file_page_count()?;
let spillway_cap = self.txm.spillway_capacity()?;
Ok(Stats {
handle_count: handles.len() as u64,
total_pages: page_count,
file_size_bytes: page_count.saturating_mul(PAGE_SIZE as u64),
spillway_logical_bytes: spillway_cap.map(|(logical, _)| logical),
spillway_max_bytes: spillway_cap.map(|(_, max)| max),
})
}
pub fn counters(&self) -> Result<ChiselCounters> {
self.txm.counters()
}
pub fn file_size_bytes(&self) -> Result<u64> {
let page_count = self.txm.file_page_count()?;
Ok(page_count.saturating_mul(PAGE_SIZE as u64))
}
pub fn is_poisoned(&self) -> bool {
self.txm.is_poisoned()
}
pub fn defrag(&mut self, options: DefragOptions) -> Result<DefragStats> {
defrag::defrag(&mut self.txm, &options)
}
pub fn set_cache_max_bytes(&mut self, bytes: u64) -> Result<()> {
self.txm.set_cache_max_bytes(bytes)
}
pub fn set_spillway_max_bytes(&mut self, bytes: u64) -> Result<()> {
self.txm.set_spillway_max_bytes(bytes)
}
pub fn set_drain_insertion(&mut self, policy: DrainInsertion) -> Result<()> {
self.txm.set_drain_insertion(policy)
}
pub fn add_key(&mut self, existing: &crypto::Key, new: &crypto::Key) -> Result<()> {
self.txm.add_key(existing, new)
}
pub fn rotate_key(&mut self, old: &crypto::Key, new: &crypto::Key) -> Result<()> {
self.txm.rotate_key(old, new)
}
pub fn remove_key(&mut self, key: &crypto::Key) -> Result<()> {
self.txm.remove_key(key)
}
}
#[cfg(test)]
mod options_encryption_tests {
use super::*;
#[test]
fn encryption_options_default_none_and_set() {
let o = Options::default();
assert!(o.encryption_key.is_none());
assert!(o.argon2_params.is_none());
let raw = Key::Raw(zeroize::Zeroizing::new(vec![0u8; 32]));
let o = Options::default()
.encryption_key(raw)
.argon2_params(Argon2Params {
m_cost: 19456,
t_cost: 2,
p_cost: 1,
});
assert!(matches!(o.encryption_key, Some(Key::Raw(_))));
let p = o.argon2_params.expect("set above");
assert_eq!((p.m_cost, p.t_cost, p.p_cost), (19456, 2, 1));
}
}