1use std::fs::File;
14use std::io;
15use std::os::unix::fs::FileExt;
16use std::sync::Arc;
17
18use stipplefs::read::{Image, Reader};
19use stipplefs::source::ByteSource;
20
21use crate::{Block, EntryKind, Flags, Footer, Manifest};
22
23pub const SLICE_SIZE: u64 = 64 * 1024;
26
27#[derive(Clone)]
29pub enum Codec {
30 Zstd(Option<Arc<[u8]>>),
34 Stipple(&'static Reader<'static>),
36}
37
38impl Codec {
39 pub fn open(file: &File, footer: &Footer) -> io::Result<Codec> {
42 if footer.is_stipple() {
43 let source = FileSource {
44 size: file.metadata()?.len(),
45 file: file.try_clone()?,
46 };
47 let image = Image::new(source, footer.payload_offset, footer.payload_size)
48 .map_err(stipple_error)?;
49 let image: &'static Image<'static> = Box::leak(Box::new(image));
50 let reader = image.reader().map_err(stipple_error)?;
51 return Ok(Codec::Stipple(Box::leak(Box::new(reader))));
52 }
53 if !footer.flags.contains(Flags::HAS_DICT) || footer.dict_size == 0 {
54 return Ok(Codec::Zstd(None));
55 }
56 let mut dict = vec![0u8; footer.dict_size as usize];
57 file.read_exact_at(&mut dict, footer.dict_offset)?;
58 Ok(Codec::Zstd(Some(dict.into())))
59 }
60
61 pub fn dict(&self) -> Option<&[u8]> {
63 match self {
64 Codec::Zstd(dict) => dict.as_deref(),
65 Codec::Stipple(_) => None,
66 }
67 }
68}
69
70pub fn read_slice(reader: &Reader<'_>, block: &Block) -> io::Result<Vec<u8>> {
73 let ino = u32::try_from(block.compressed_size)
74 .map_err(|_| invalid("a block names an inode outside the image"))?;
75 let mut buf = vec![0u8; crate::reader::block_original_size(block)?];
76 let read = reader
77 .read(ino, block.payload_offset, &mut buf)
78 .map_err(stipple_error)?;
79 if read != buf.len() {
80 return Err(invalid(
81 "a file in the image is shorter than the manifest says",
82 ));
83 }
84 Ok(buf)
85}
86
87pub fn file_order(manifest: &Manifest, codec: &Codec) -> Vec<usize> {
94 let mut files: Vec<usize> = (0..manifest.entries.len())
95 .filter(|&i| manifest.entries[i].kind == EntryKind::File)
96 .collect();
97 if let Codec::Stipple(reader) = codec {
98 files.sort_by_cached_key(|&i| {
99 manifest.entries[i]
100 .blocks
101 .first()
102 .and_then(|b| u32::try_from(b.compressed_size).ok())
103 .and_then(|ino| reader.file_extents(ino).first().map(|e| e.0))
104 .unwrap_or(u64::MAX)
105 });
106 }
107 files
108}
109
110struct FileSource {
112 file: File,
113 size: u64,
114}
115
116impl ByteSource for FileSource {
117 fn size(&self) -> u64 {
118 self.size
119 }
120
121 fn read_at(&self, offset: u64, buf: &mut [u8]) -> stipplefs::error::Result<()> {
122 Ok(self.file.read_exact_at(buf, offset)?)
123 }
124}
125
126fn invalid(msg: &str) -> io::Error {
127 io::Error::new(io::ErrorKind::InvalidData, msg.to_string())
128}
129
130fn stipple_error(e: stipplefs::error::Error) -> io::Error {
131 io::Error::new(io::ErrorKind::InvalidData, format!("stipplefs: {e}"))
132}