use no_std_io2::io::Read;
use std::io::{Seek, SeekFrom};
use std::sync::{Mutex, RwLock};
use solana_nohash_hasher::IntMap;
use crate::error::BackhandError;
use crate::kinds::Kind;
use crate::traits::types::Compressor;
use crate::v4::reader::BufReadSeek;
#[derive(Default, Clone, Debug)]
pub struct Cache {
pub(crate) fragment_cache: IntMap<u64, Vec<u8>>,
}
pub trait BlockReaderVersion<'b> {
type DataSize: Copy + Sync + 'b;
type Fragment: Sync + 'b;
type File: Sync + 'b;
type System: Sync + 'b;
fn data_size(data_size: &Self::DataSize) -> u32;
fn data_uncompressed(data_size: &Self::DataSize) -> bool;
fn fragment_start(fragment: &Self::Fragment) -> u64;
fn fragment_size(fragment: &Self::Fragment) -> Self::DataSize;
fn file_len(file: &Self::File) -> usize;
fn block_sizes(file: &Self::File) -> &[Self::DataSize];
fn blocks_start(file: &Self::File) -> u64;
fn block_offset(file: &Self::File) -> u32;
fn kind(system: &Self::System) -> &Kind;
fn block_size(system: &Self::System) -> u32;
fn compressor(system: &Self::System) -> Option<Compressor>;
fn reader(system: &Self::System) -> &Mutex<Box<dyn BufReadSeek + 'b>>;
fn cache(system: &Self::System) -> &RwLock<Cache>;
fn fragment_of<'a>(
system: &'a Self::System,
file: &'a Self::File,
) -> Result<Option<&'a Self::Fragment>, crate::error::BackhandError>;
}
pub enum BlockFragment<'a, 'b, V: BlockReaderVersion<'b>> {
Block(&'a V::DataSize),
Fragment(&'a V::Fragment),
}
pub struct BlockIterator<'a, 'b, V: BlockReaderVersion<'b>> {
pub blocks: &'a [V::DataSize],
pub fragment: Option<&'a V::Fragment>,
}
impl<'a, 'b, V: BlockReaderVersion<'b>> Iterator for BlockIterator<'a, 'b, V> {
type Item = BlockFragment<'a, 'b, V>;
fn next(&mut self) -> Option<Self::Item> {
self.blocks
.split_first()
.map(|(first, rest)| {
self.blocks = rest;
BlockFragment::Block(first)
})
.or_else(|| self.fragment.take().map(BlockFragment::Fragment))
}
}
#[derive(Clone, Copy, Debug)]
pub struct RawDataBlock {
pub(crate) fragment: bool,
pub(crate) uncompressed: bool,
}
pub struct FilesystemReaderFile<'a, 'b: 'a, V: BlockReaderVersion<'b>> {
pub(crate) system: &'a V::System,
pub(crate) file: &'a V::File,
}
impl<'b, V: BlockReaderVersion<'b>> Clone for FilesystemReaderFile<'_, 'b, V> {
fn clone(&self) -> Self {
*self
}
}
impl<'b, V: BlockReaderVersion<'b>> Copy for FilesystemReaderFile<'_, 'b, V> {}
impl<'a, 'b, V: BlockReaderVersion<'b>> FilesystemReaderFile<'a, 'b, V> {
pub fn new(system: &'a V::System, file: &'a V::File) -> Self {
Self { system, file }
}
pub fn fragment(&self) -> Option<&'a V::Fragment> {
self.fragment_checked().ok().flatten()
}
pub(crate) fn fragment_checked(&self) -> Result<Option<&'a V::Fragment>, BackhandError> {
V::fragment_of(self.system, self.file)
}
}
impl<'a, 'b, V: BlockReaderVersion<'b>> IntoIterator for FilesystemReaderFile<'a, 'b, V> {
type IntoIter = BlockIterator<'a, 'b, V>;
type Item = BlockFragment<'a, 'b, V>;
fn into_iter(self) -> Self::IntoIter {
BlockIterator { blocks: V::block_sizes(self.file), fragment: self.fragment() }
}
}
#[inline]
pub(crate) fn fragment_range<'b, V: BlockReaderVersion<'b>>(
system: &V::System,
file: &V::File,
frag_buf_len: usize,
) -> Result<std::ops::Range<usize>, BackhandError> {
let block_len = V::block_size(system) as usize;
let block_num = V::block_sizes(file).len();
let file_size = V::file_len(file);
let frag_start = V::block_offset(file) as usize;
(|| {
let frag_len = file_size.checked_sub(block_num.checked_mul(block_len)?)?;
let frag_end = frag_start.checked_add(frag_len)?;
(frag_end <= frag_buf_len).then_some(frag_start..frag_end)
})()
.ok_or(BackhandError::CorruptedOrInvalidSquashfs)
}
pub(crate) fn read_raw_data<'a, 'b, V: BlockReaderVersion<'b>>(
system: &V::System,
file: &V::File,
pos: &mut u64,
data: &mut Vec<u8>,
block: &BlockFragment<'a, 'b, V>,
) -> Result<RawDataBlock, BackhandError> {
let block_size = V::block_size(system) as usize;
match block {
BlockFragment::Block(block) => {
let block_len = V::data_size(block) as usize;
if block_len == 0 {
*data = vec![0; block_size];
return Ok(RawDataBlock { fragment: false, uncompressed: true });
}
if block_len > block_size {
return Err(BackhandError::CorruptedOrInvalidSquashfs);
}
data.resize(block_len, 0);
{
let mut reader = V::reader(system).lock().unwrap();
reader.seek(SeekFrom::Start(*pos))?;
reader.read_exact(data)?;
*pos = reader.stream_position()?;
}
Ok(RawDataBlock { fragment: false, uncompressed: V::data_uncompressed(block) })
}
BlockFragment::Fragment(fragment) => {
let frag_start = V::fragment_start(fragment);
let frag_data_size = V::fragment_size(fragment);
{
let cache = V::cache(system).read().unwrap();
if let Some(cache_bytes) = cache.fragment_cache.get(&frag_start) {
let range = fragment_range::<V>(system, file, cache_bytes.len())?;
trace!("fragment in cache: {:02x}:{range:02x?}", frag_start);
data.resize(range.end - range.start, 0);
data.copy_from_slice(&cache_bytes[range]);
return Ok(RawDataBlock { fragment: true, uncompressed: true });
}
}
trace!("fragment: reading from data");
let frag_len = V::data_size(&frag_data_size) as usize;
if frag_len > block_size {
return Err(BackhandError::CorruptedOrInvalidSquashfs);
}
data.resize(frag_len, 0);
{
let mut reader = V::reader(system).lock().unwrap();
reader.seek(SeekFrom::Start(frag_start))?;
reader.read_exact(data)?;
}
let uncompressed = V::data_uncompressed(&frag_data_size);
if uncompressed {
let range = fragment_range::<V>(system, file, data.len())?;
V::cache(system).write().unwrap().fragment_cache.insert(frag_start, data.clone());
data.drain(range.end..);
data.drain(..range.start);
}
Ok(RawDataBlock { fragment: true, uncompressed })
}
}
}
pub(crate) fn decompress<'b, V: BlockReaderVersion<'b>>(
system: &V::System,
file: &V::File,
data: RawDataBlock,
input_buf: &mut Vec<u8>,
output_buf: &mut Vec<u8>,
) -> Result<(), BackhandError> {
assert!(output_buf.is_empty());
if data.uncompressed {
std::mem::swap(input_buf, output_buf);
} else {
let block_size = V::block_size(system) as usize;
output_buf.reserve(block_size);
V::kind(system).decompress(input_buf, output_buf, V::compressor(system), block_size)?;
if data.fragment {
let fragment =
V::fragment_of(system, file)?.ok_or(BackhandError::CorruptedOrInvalidSquashfs)?;
let range = fragment_range::<V>(system, file, output_buf.len())?;
V::cache(system)
.write()
.unwrap()
.fragment_cache
.insert(V::fragment_start(fragment), output_buf.clone());
output_buf.drain(range.end..);
output_buf.drain(..range.start);
}
}
Ok(())
}