use std::fs::File;
use std::io;
use std::os::unix::fs::FileExt;
use std::sync::Arc;
use stipplefs::read::{Image, Reader};
use stipplefs::source::ByteSource;
use crate::{Block, EntryKind, Flags, Footer, Manifest};
pub const SLICE_SIZE: u64 = 64 * 1024;
#[derive(Clone)]
pub enum Codec {
Zstd(Option<Arc<[u8]>>),
Stipple(&'static Reader<'static>),
}
impl Codec {
pub fn open(file: &File, footer: &Footer) -> io::Result<Codec> {
if footer.is_stipple() {
let source = FileSource {
size: file.metadata()?.len(),
file: file.try_clone()?,
};
let image = Image::new(source, footer.payload_offset, footer.payload_size)
.map_err(stipple_error)?;
let image: &'static Image<'static> = Box::leak(Box::new(image));
let reader = image.reader().map_err(stipple_error)?;
return Ok(Codec::Stipple(Box::leak(Box::new(reader))));
}
if !footer.flags.contains(Flags::HAS_DICT) || footer.dict_size == 0 {
return Ok(Codec::Zstd(None));
}
let mut dict = vec![0u8; footer.dict_size as usize];
file.read_exact_at(&mut dict, footer.dict_offset)?;
Ok(Codec::Zstd(Some(dict.into())))
}
pub fn dict(&self) -> Option<&[u8]> {
match self {
Codec::Zstd(dict) => dict.as_deref(),
Codec::Stipple(_) => None,
}
}
}
pub fn read_slice(reader: &Reader<'_>, block: &Block) -> io::Result<Vec<u8>> {
let ino = u32::try_from(block.compressed_size)
.map_err(|_| invalid("a block names an inode outside the image"))?;
let mut buf = vec![0u8; crate::reader::block_original_size(block)?];
let read = reader
.read(ino, block.payload_offset, &mut buf)
.map_err(stipple_error)?;
if read != buf.len() {
return Err(invalid(
"a file in the image is shorter than the manifest says",
));
}
Ok(buf)
}
pub fn file_order(manifest: &Manifest, codec: &Codec) -> Vec<usize> {
let mut files: Vec<usize> = (0..manifest.entries.len())
.filter(|&i| manifest.entries[i].kind == EntryKind::File)
.collect();
if let Codec::Stipple(reader) = codec {
files.sort_by_cached_key(|&i| {
manifest.entries[i]
.blocks
.first()
.and_then(|b| u32::try_from(b.compressed_size).ok())
.and_then(|ino| reader.file_extents(ino).first().map(|e| e.0))
.unwrap_or(u64::MAX)
});
}
files
}
struct FileSource {
file: File,
size: u64,
}
impl ByteSource for FileSource {
fn size(&self) -> u64 {
self.size
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> stipplefs::error::Result<()> {
Ok(self.file.read_exact_at(buf, offset)?)
}
}
fn invalid(msg: &str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, msg.to_string())
}
fn stipple_error(e: stipplefs::error::Error) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, format!("stipplefs: {e}"))
}