use std::fs::File;
use std::path::{Path, PathBuf};
use ahash::AHashMap;
use memmap2::Mmap;
use super::error::{FuseError, FuseResult};
use super::format::{crc32, read_raw, Index, HEADER_SIZE, OBJ_HDR_SZ};
pub struct ReaderCore {
pub(crate) path: PathBuf,
pub(crate) mm: Mmap,
pub(crate) idx: Index,
}
impl ReaderCore {
pub fn open(path: impl AsRef<Path>, verify: bool) -> FuseResult<Self> {
let path = path.as_ref();
let file =
File::open(path).map_err(|e| FuseError::Io(format!("cannot open {:?}: {e}", path)))?;
let mm =
unsafe { Mmap::map(&file) }.map_err(|e| FuseError::Io(format!("mmap failed: {e}")))?;
let idx = Index::load(&mm, verify)?;
Ok(Self {
path: path.to_path_buf(),
mm,
idx,
})
}
pub fn get(&self, key: &[u8]) -> FuseResult<Option<Vec<u8>>> {
match self.idx.hash.get(key) {
None => Ok(None),
Some(&offset) => read_raw(&self.mm, offset).map(Some),
}
}
#[inline]
pub fn exists(&self, key: &[u8]) -> bool {
self.idx.hash.contains_key(key)
}
pub fn prefix(&self, prefix: &[u8]) -> FuseResult<Vec<(String, Vec<u8>)>> {
let pos = self
.idx
.sorted_keys
.partition_point(|k| k.as_slice() < prefix);
let mut out = Vec::new();
let mut i = pos;
while i < self.idx.sorted_keys.len() && self.idx.sorted_keys[i].starts_with(prefix) {
let key_str = String::from_utf8_lossy(&self.idx.sorted_keys[i]).into_owned();
let raw = read_raw(&self.mm, self.idx.sorted_offsets[i])?;
out.push((key_str, raw));
i += 1;
}
Ok(out)
}
pub fn keys(&self) -> Vec<String> {
self.idx
.sorted_keys
.iter()
.map(|k| String::from_utf8_lossy(k).into_owned())
.collect()
}
pub fn items(&self) -> FuseResult<Vec<(String, Vec<u8>)>> {
let mut cache: AHashMap<u64, Vec<u8>> = AHashMap::new();
let mut out = Vec::with_capacity(self.idx.num_keys as usize);
for (key_bytes, &offset) in self
.idx
.sorted_keys
.iter()
.zip(self.idx.sorted_offsets.iter())
{
let raw = match cache.get(&offset) {
Some(cached) => cached.clone(),
None => {
let b = read_raw(&self.mm, offset)?;
cache.insert(offset, b.clone());
b
}
};
out.push((String::from_utf8_lossy(key_bytes).into_owned(), raw));
}
Ok(out)
}
pub fn objects(&self) -> FuseResult<Vec<Vec<u8>>> {
let mut seen: AHashMap<u64, ()> = AHashMap::new();
let mut out = Vec::new();
for &offset in &self.idx.sorted_offsets {
if seen.insert(offset, ()).is_none() {
out.push(read_raw(&self.mm, offset)?);
}
}
Ok(out)
}
#[inline]
pub fn num_keys(&self) -> u32 {
self.idx.num_keys
}
#[inline]
pub fn num_objects(&self) -> u32 {
self.idx.num_objects
}
pub fn path(&self) -> &Path {
&self.path
}
#[inline]
pub fn file_size(&self) -> u64 {
self.idx.file_size
}
#[inline]
pub fn stored_crc(&self) -> u32 {
self.idx.stored_crc
}
#[inline]
pub fn index_offset(&self) -> u64 {
self.idx.index_offset
}
#[inline]
pub fn sorted_keys(&self) -> &[Vec<u8>] {
&self.idx.sorted_keys
}
#[inline]
pub fn sorted_offsets(&self) -> &[u64] {
&self.idx.sorted_offsets
}
#[inline]
pub fn lower_bound(&self, prefix: &[u8]) -> usize {
self.idx
.sorted_keys
.partition_point(|k| k.as_slice() < prefix)
}
#[inline]
pub fn read_at(&self, offset: u64) -> FuseResult<Vec<u8>> {
read_raw(&self.mm, offset)
}
pub fn verify(&self) -> FuseResult<bool> {
let data = self.mm.as_ref();
let computed = crc32(&data[HEADER_SIZE..]);
if computed != self.idx.stored_crc {
return Err(FuseError::Corrupt(format!(
"file CRC32 mismatch stored={:#010x} computed={:#010x}",
self.idx.stored_crc, computed
)));
}
let mut seen: AHashMap<u64, ()> = AHashMap::new();
for &offset in &self.idx.sorted_offsets {
if seen.insert(offset, ()).is_none() {
let o = offset as usize;
if o + OBJ_HDR_SZ > data.len() {
return Err(FuseError::Corrupt(format!(
"object offset {offset} out of bounds"
)));
}
let obj_len = u32::from_be_bytes(data[o..o + 4].try_into().unwrap()) as usize;
let stored = u32::from_be_bytes(data[o + 4..o + 8].try_into().unwrap());
let raw = &data[o + OBJ_HDR_SZ..o + OBJ_HDR_SZ + obj_len];
if crc32(raw) != stored {
return Err(FuseError::Corrupt(format!(
"object CRC32 mismatch at offset {offset}"
)));
}
}
}
Ok(true)
}
}