use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use lamfold::{
checked_block_len, checked_full_read_len, decode, lz4_block_with_dict, microlzma_block_decode,
BlockSource, Codec, DirEntry, FileKind, FoldError, FoldFrontend, Metadata, NodeId, Result,
SubstrateCtx,
};
const SUPER_OFFSET: u64 = 1024;
const MAGIC: u32 = 0xE0F5_E1E2;
const FLAT_PLAIN: u8 = 0;
const FLAT_INLINE: u8 = 2;
const COMPRESSED_FULL: u8 = 1;
const COMPRESSED_COMPACT: u8 = 3;
const ADVISE_COMPACTED_2B: u16 = 0x0001;
const ADVISE_BIG_PCLUSTER: u16 = 0x0002 | 0x0004;
const ADVISE_INLINE_PCLUSTER: u16 = 0x0008;
const ADVISE_FRAGMENT: u16 = 0x0020;
const COMPACT_LOBITS: u32 = 12;
const COMPACT_ENCBITS: u32 = 14;
const COMPACT_CBLKCNT_FLAG: u16 = 0x0800;
const ALGO_LZ4: u8 = 0;
const ALGO_LZMA: u8 = 1;
const ALGO_DEFLATE: u8 = 2;
const ALGO_ZSTD: u8 = 3;
const MICROLZMA_PROPS: u8 = 0x5d;
const COMPR_ALGS_OFF: u64 = 1024 + 84;
const COMPR_CFGS_OFF: u64 = 1024 + 128;
const COMPR_ALG_LZMA_BIT: u16 = 0x0002;
const Z_EROFS_LEGACY_HEADER_SIZE: u64 = 16;
const LC_TYPE_PLAIN: u16 = 0;
const LC_TYPE_HEAD1: u16 = 1;
const LC_TYPE_NONHEAD: u16 = 2;
const LC_TYPE_HEAD2: u16 = 3;
const LZ4_WINDOW: usize = 65_536;
struct ErofsInode {
kind: FileKind,
size: u64,
mode: u16,
layout: u8,
raw_blkaddr: u32,
inline_off: u64,
}
struct Head {
start: u64,
ty: u16,
blkaddr: u32,
cblkcnt: u32,
}
pub struct Erofs<S: BlockSource> {
src: S,
block_size: u64,
meta_off: u64,
root_nid: u64,
inodes: BTreeMap<u64, ErofsInode>,
decoded: BTreeMap<u64, Vec<u8>>,
lzma_dict_size: Option<u32>,
}
impl<S: BlockSource> Erofs<S> {
fn parse_inode(&mut self, nid: u64) -> Result<()> {
if self.inodes.contains_key(&nid) {
return Ok(());
}
let off = nid
.checked_mul(32)
.and_then(|x| self.meta_off.checked_add(x))
.ok_or(FoldError::Corrupt("erofs: inode offset overflow"))?;
let mut hdr = [0u8; 64];
self.src.read_at(off, &mut hdr[..32])?;
let format = le_u16(&hdr, 0)?;
let extended = format & 1 != 0;
let inode_size = if extended { 64 } else { 32 };
if extended {
self.src.read_at(off + 32, &mut hdr[32..64])?;
}
let xattr_icount = le_u16(&hdr, 2)?;
let xattr_size = if xattr_icount == 0 {
0u64
} else {
12 + (u64::from(xattr_icount) - 1) * 4
};
let mode = le_u16(&hdr, 4)?;
let size = if extended {
le_u64(&hdr, 8)?
} else {
u64::from(le_u32(&hdr, 8)?)
};
let raw_blkaddr = le_u32(&hdr, 16)?;
let layout = ((format >> 1) & 7) as u8;
let kind = match mode & 0xF000 {
0x8000 => FileKind::Regular,
0x4000 => FileKind::Directory,
0xA000 => FileKind::Symlink,
_ => FileKind::Other,
};
self.inodes.insert(
nid,
ErofsInode {
kind,
size,
mode,
layout,
raw_blkaddr,
inline_off: off + inode_size + xattr_size,
},
);
Ok(())
}
fn inode(&self, nid: u64) -> Result<&ErofsInode> {
self.inodes.get(&nid).ok_or(FoldError::NotFound)
}
fn read_inode_data(&mut self, nid: u64, off: u64, buf: &mut [u8]) -> Result<usize> {
let inode = self.inode(nid)?;
let layout = inode.layout;
let size = inode.size;
let raw_blkaddr = inode.raw_blkaddr;
let inline_off = inode.inline_off;
if off >= size {
return Ok(0);
}
let end = core::cmp::min(off + buf.len() as u64, size);
let total = (end - off) as usize;
let bs = self.block_size;
match layout {
FLAT_PLAIN => {
let base = u64::from(raw_blkaddr) * bs;
self.src.read_at(base + off, &mut buf[..total])?;
Ok(total)
}
FLAT_INLINE => {
let full_bytes = (size / bs) * bs;
let base = u64::from(raw_blkaddr) * bs;
let mut p = off;
while p < end {
let (disk, seg_end) = if p < full_bytes {
(base + p, core::cmp::min(end, full_bytes))
} else {
(inline_off + (p - full_bytes), end)
};
let n = (seg_end - p) as usize;
let bo = (p - off) as usize;
self.src.read_at(disk, &mut buf[bo..bo + n])?;
p = seg_end;
}
Ok(total)
}
COMPRESSED_FULL | COMPRESSED_COMPACT => {
self.materialize_compressed(nid)?;
let data = self
.decoded
.get(&nid)
.ok_or(FoldError::Corrupt("erofs: decode cache miss"))?;
let lo = off as usize;
let seg = data
.get(lo..lo + total)
.ok_or(FoldError::Corrupt("erofs: decoded read past end"))?;
buf[..total].copy_from_slice(seg);
Ok(total)
}
_ => Err(FoldError::Unsupported(
"erofs: chunk-indexed datalayout not supported",
)),
}
}
fn parse_lzma_cfg(&mut self) {
let mut algs = [0u8; 2];
if self.src.read_at(COMPR_ALGS_OFF, &mut algs).is_err() {
return;
}
let algs = u16::from_le_bytes(algs);
if algs & COMPR_ALG_LZMA_BIT == 0 {
return;
}
let mut cur = COMPR_CFGS_OFF;
for alg in 0..16u32 {
if algs & (1 << alg) == 0 {
continue;
}
let mut sz = [0u8; 2];
if self.src.read_at(cur, &mut sz).is_err() {
return;
}
let size = u64::from(u16::from_le_bytes(sz));
if alg == u32::from(ALGO_LZMA) {
let mut pay = [0u8; 6];
if self.src.read_at(cur + 2, &mut pay).is_err() {
return;
}
let dict = u32::from_le_bytes([pay[0], pay[1], pay[2], pay[3]]);
let format = u16::from_le_bytes([pay[4], pay[5]]);
if format == 0 {
self.lzma_dict_size = Some(dict);
}
return;
}
cur += 2 + size;
}
}
fn materialize_compressed(&mut self, nid: u64) -> Result<()> {
if self.decoded.contains_key(&nid) {
return Ok(());
}
let inode = self.inode(nid)?;
let i_size = inode.size;
let layout = inode.layout;
let mh = (inode.inline_off + 7) & !7;
let cap = checked_full_read_len(i_size)?;
let mut hdr = [0u8; 8];
self.src.read_at(mh, &mut hdr)?;
let advise = u16::from_le_bytes([hdr[4], hdr[5]]);
let head1_algo = hdr[6] & 0x0f;
let head2_algo = (hdr[6] >> 4) & 0x0f;
let lcbits = u32::from(hdr[7] & 7) + 12;
let lcsize = 1u64 << lcbits;
let n_lc = i_size.div_ceil(lcsize);
if advise & (ADVISE_INLINE_PCLUSTER | ADVISE_FRAGMENT) != 0 {
return Err(FoldError::Unsupported(
"erofs: ztailpacking / fragment pclusters not supported",
));
}
let heads = match layout {
COMPRESSED_FULL => self.build_heads_full(mh, lcsize, n_lc)?,
COMPRESSED_COMPACT => {
if lcbits != 12 {
return Err(FoldError::Unsupported(
"erofs: compact index with lclusterbits != 12",
));
}
if advise & ADVISE_COMPACTED_2B == 0 {
return Err(FoldError::Unsupported(
"erofs: all-4B compact index (COMPACTED_2B clear) not supported",
));
}
self.build_heads_compact(mh + 8, lcsize, n_lc, advise)?
}
_ => return Err(FoldError::Corrupt("erofs: inode is not compressed")),
};
let mut out: Vec<u8> = Vec::with_capacity(cap);
for k in 0..heads.len() {
let h = &heads[k];
let next = heads.get(k + 1).map_or(i_size, |n| n.start);
if next < h.start {
return Err(FoldError::Corrupt("erofs: non-monotonic pcluster starts"));
}
let outlen = (next - h.start) as usize;
if outlen == 0 {
continue;
}
let algo = if h.ty == LC_TYPE_HEAD2 {
head2_algo
} else {
head1_algo
};
let span = self.read_span(h.blkaddr, h.cblkcnt)?;
let seg = decode_pcluster(h.ty, algo, &span, outlen, &out, self.lzma_dict_size)?;
out.extend_from_slice(&seg);
}
self.decoded.insert(nid, out);
Ok(())
}
fn read_span(&mut self, blkaddr: u32, cblkcnt: u32) -> Result<Vec<u8>> {
let bs = self.block_size;
let phys = u64::from(blkaddr) * bs;
let want = u64::from(cblkcnt) * bs;
let avail = core::cmp::min(want, self.src.len().saturating_sub(phys));
if avail == 0 {
return Err(FoldError::Corrupt("erofs: pcluster span out of range"));
}
let mut span = vec![0u8; avail as usize];
self.src.read_at(phys, &mut span)?;
Ok(span)
}
fn build_heads_full(&mut self, mh: u64, lcsize: u64, n_lc: u64) -> Result<Vec<Head>> {
let idx0 = mh + Z_EROFS_LEGACY_HEADER_SIZE;
let mut heads = Vec::new();
for i in 0..n_lc {
let mut e = [0u8; 8];
self.src.read_at(idx0 + i * 8, &mut e)?;
let ty = u16::from_le_bytes([e[0], e[1]]) & 3;
if ty == LC_TYPE_NONHEAD {
continue;
}
let clusterofs = u64::from(u16::from_le_bytes([e[2], e[3]]));
let blkaddr = u32::from_le_bytes([e[4], e[5], e[6], e[7]]);
let start = if heads.is_empty() {
0
} else {
i * lcsize + clusterofs
};
heads.push(Head {
start,
ty,
blkaddr,
cblkcnt: 1,
});
}
Ok(heads)
}
fn build_heads_compact(
&mut self,
ebase: u64,
lcsize: u64,
n_lc: u64,
advise: u16,
) -> Result<Vec<Head>> {
let ebrem = (ebase & 31) as u32;
let c4i = core::cmp::min(u64::from(((32 - ebrem) / 4) & 7), n_lc);
let c2b = (n_lc - c4i) / 16 * 16;
let c4f = n_lc - c4i - c2b;
let idx_len = c4i.div_ceil(2) * 8 + c2b / 16 * 32 + c4f.div_ceil(2) * 8;
let cap = checked_full_read_len(idx_len)?;
let mut buf = vec![0u8; cap];
self.src.read_at(ebase, &mut buf)?;
let mut raw = Vec::with_capacity(n_lc as usize);
let mut pos = 0usize;
let mut rid = 0u32;
read_4b_groups(&buf, &mut pos, c4i, &mut rid, &mut raw)?;
read_2b_packs(&buf, &mut pos, c2b, &mut rid, &mut raw)?;
read_4b_groups(&buf, &mut pos, c4f, &mut rid, &mut raw)?;
resolve_compact_heads(&raw, lcsize, advise, ebase)
}
fn parse_dir_block(block: &[u8], out: &mut Vec<(String, u64, u8)>) -> Result<()> {
if block.len() < 12 {
return Ok(());
}
let first_nameoff = usize::from(le_u16(block, 8)?);
let count = first_nameoff / 12;
for i in 0..count {
let base = i * 12;
let nid = le_u64(block, base)?;
let nameoff = usize::from(le_u16(block, base + 8)?);
let file_type = *block
.get(base + 10)
.ok_or(FoldError::Corrupt("erofs: dirent"))?;
let name_end = if i + 1 < count {
usize::from(le_u16(block, (i + 1) * 12 + 8)?)
} else {
block.len()
};
let raw = block
.get(nameoff..name_end)
.ok_or(FoldError::Corrupt("erofs: dirent name OOB"))?;
let name = raw.split(|&b| b == 0).next().unwrap_or(raw);
if name.is_empty() || name == b"." || name == b".." {
continue;
}
out.push((String::from_utf8_lossy(name).into_owned(), nid, file_type));
}
Ok(())
}
}
impl<S: BlockSource> FoldFrontend<S> for Erofs<S> {
const TAG: &'static str = "erofs";
fn probe(src: &mut S) -> Result<bool> {
if src.len() < SUPER_OFFSET + 4 {
return Ok(false);
}
let mut m = [0u8; 4];
src.read_at(SUPER_OFFSET, &mut m)?;
Ok(u32::from_le_bytes(m) == MAGIC)
}
fn open(src: S, _cx: &mut SubstrateCtx<'_>) -> Result<Self> {
if src.len() < SUPER_OFFSET + 128 {
return Err(FoldError::Corrupt("erofs: source shorter than superblock"));
}
let mut src = src;
let mut sb = [0u8; 128];
src.read_at(SUPER_OFFSET, &mut sb)?;
if le_u32(&sb, 0)? != MAGIC {
return Err(FoldError::Corrupt("erofs: bad magic"));
}
let blkszbits = sb[12];
if !(9..=16).contains(&blkszbits) {
return Err(FoldError::Corrupt("erofs: implausible blkszbits"));
}
let block_size = 1u64 << blkszbits;
let root_nid = u64::from(le_u16(&sb, 14)?);
let meta_off = u64::from(le_u32(&sb, 40)?) * block_size;
let mut me = Erofs {
src,
block_size,
meta_off,
root_nid,
inodes: BTreeMap::new(),
decoded: BTreeMap::new(),
lzma_dict_size: None,
};
me.parse_lzma_cfg();
me.parse_inode(root_nid)?;
Ok(me)
}
fn root(&self) -> NodeId {
self.root_nid
}
fn lookup(
&mut self,
dir: NodeId,
name: &str,
cx: &mut SubstrateCtx<'_>,
) -> Result<Option<NodeId>> {
Ok(self
.read_dir(dir, cx)?
.into_iter()
.find(|e| e.name == name)
.map(|e| e.node))
}
fn read_dir(&mut self, dir: NodeId, _cx: &mut SubstrateCtx<'_>) -> Result<Vec<DirEntry>> {
self.parse_inode(dir)?;
let inode = self.inode(dir)?;
if inode.kind != FileKind::Directory {
return Err(FoldError::NotDirectory);
}
let size = checked_full_read_len(inode.size)?;
let mut data = vec![0u8; size];
self.read_inode_data(dir, 0, &mut data)?;
let bs = self.block_size as usize;
let mut raw = Vec::new();
let mut start = 0;
while start < data.len() {
let block_end = core::cmp::min(start + bs, data.len());
Self::parse_dir_block(&data[start..block_end], &mut raw)?;
start = block_end;
}
let mut out = Vec::with_capacity(raw.len());
for (name, nid, file_type) in raw {
out.push(DirEntry {
name,
node: nid,
kind: ft_kind(file_type),
});
}
Ok(out)
}
fn metadata(&mut self, node: NodeId, _cx: &mut SubstrateCtx<'_>) -> Result<Metadata> {
self.parse_inode(node)?;
let inode = self.inode(node)?;
Ok(Metadata {
kind: inode.kind,
size: inode.size,
mode: u32::from(inode.mode) & 0o7777,
})
}
fn read_at(
&mut self,
node: NodeId,
off: u64,
buf: &mut [u8],
cx: &mut SubstrateCtx<'_>,
) -> Result<usize> {
self.parse_inode(node)?;
let inode = self.inode(node)?;
if inode.kind == FileKind::Directory {
return Err(FoldError::IsDirectory);
}
let size = inode.size;
if off >= size {
return Ok(0);
}
let bs = self.block_size;
let end = core::cmp::min(off + buf.len() as u64, size);
let mut block = vec![0u8; bs as usize];
let mut produced = 0;
let mut block_start = (off / bs) * bs;
while block_start < end {
let block_len = core::cmp::min(bs, size - block_start) as usize;
self.read_inode_data(node, block_start, &mut block[..block_len])?;
cx.verifier
.verify_block(node, block_start, &block[..block_len])?;
let copy_start = core::cmp::max(off, block_start);
let copy_end = core::cmp::min(end, block_start + block_len as u64);
if copy_end > copy_start {
let src_lo = (copy_start - block_start) as usize;
let dst_lo = (copy_start - off) as usize;
let cnt = (copy_end - copy_start) as usize;
buf[dst_lo..dst_lo + cnt].copy_from_slice(&block[src_lo..src_lo + cnt]);
produced += cnt;
}
block_start += bs;
}
Ok(produced)
}
fn read_link(&mut self, node: NodeId, _cx: &mut SubstrateCtx<'_>) -> Result<Option<Vec<u8>>> {
self.parse_inode(node)?;
let inode = self.inode(node)?;
if inode.kind != FileKind::Symlink {
return Ok(None);
}
let len = checked_full_read_len(inode.size)?;
let mut target = vec![0u8; len];
self.read_inode_data(node, 0, &mut target)?;
Ok(Some(target))
}
}
struct RawEntry {
ty: u16,
value: u16,
region: u32,
base: u32,
}
fn read_4b_groups(
buf: &[u8],
pos: &mut usize,
count: u64,
rid: &mut u32,
raw: &mut Vec<RawEntry>,
) -> Result<()> {
let mut produced = 0u64;
while produced < count {
let g = buf
.get(*pos..*pos + 8)
.ok_or(FoldError::Corrupt("erofs: compact 4b group out of range"))?;
let base = u32::from_le_bytes([g[4], g[5], g[6], g[7]]);
for half in [
u16::from_le_bytes([g[0], g[1]]),
u16::from_le_bytes([g[2], g[3]]),
] {
if produced >= count {
break;
}
raw.push(RawEntry {
ty: (half >> COMPACT_LOBITS) & 3,
value: half & 0x0fff,
region: *rid,
base,
});
produced += 1;
}
*pos += 8;
*rid += 1;
}
Ok(())
}
fn read_2b_packs(
buf: &[u8],
pos: &mut usize,
count: u64,
rid: &mut u32,
raw: &mut Vec<RawEntry>,
) -> Result<()> {
let mut produced = 0u64;
while produced < count {
let pack = buf
.get(*pos..*pos + 32)
.ok_or(FoldError::Corrupt("erofs: compact 2b pack out of range"))?;
let base = u32::from_le_bytes([pack[28], pack[29], pack[30], pack[31]]);
for s in 0..16u32 {
if produced >= count {
break;
}
let v = read_bits_lsb(pack, s * COMPACT_ENCBITS)?;
raw.push(RawEntry {
ty: (v >> COMPACT_LOBITS) as u16 & 3,
value: (v & 0x0fff) as u16,
region: *rid,
base,
});
produced += 1;
}
*pos += 32;
*rid += 1;
}
Ok(())
}
fn read_bits_lsb(buf: &[u8], bit_pos: u32) -> Result<u32> {
let mut v = 0u32;
for k in 0..COMPACT_ENCBITS {
let bp = bit_pos + k;
let byte = *buf
.get((bp >> 3) as usize)
.ok_or(FoldError::Corrupt("erofs: compact bit offset out of range"))?;
v |= u32::from((byte >> (bp & 7)) & 1) << k;
}
Ok(v)
}
fn resolve_compact_heads(
raw: &[RawEntry],
lcsize: u64,
advise: u16,
ebase: u64,
) -> Result<Vec<Head>> {
let big = advise & ADVISE_BIG_PCLUSTER != 0;
let first_is_4b = ((32 - (ebase & 31)) / 4) & 7 != 0;
let first_base = raw.first().map_or(0, |e| e.base);
let mut heads: Vec<Head> = Vec::new();
let mut region_heads: BTreeMap<u32, u32> = BTreeMap::new();
let mut cur_head: Option<usize> = None;
for (i, e) in raw.iter().enumerate() {
if e.ty == LC_TYPE_NONHEAD {
if big && e.value & COMPACT_CBLKCNT_FLAG != 0 {
if let Some(h) = cur_head {
heads[h].cblkcnt = u32::from(e.value & 0x07ff);
}
}
continue;
}
let clusterofs = u64::from(e.value);
let start = if heads.is_empty() {
0
} else {
i as u64 * lcsize + clusterofs
};
let blkaddr = if big {
0 } else {
let c = region_heads.entry(e.region).or_insert(0);
let b = e
.base
.checked_add(1)
.and_then(|x| x.checked_add(*c))
.ok_or(FoldError::Corrupt("erofs: compact blkaddr overflow"))?;
*c += 1;
b
};
heads.push(Head {
start,
ty: e.ty,
blkaddr,
cblkcnt: 1,
});
cur_head = Some(heads.len() - 1);
}
if big {
let mut blk = if first_is_4b {
first_base
} else {
first_base
.checked_add(1)
.ok_or(FoldError::Corrupt("erofs: compact base overflow"))?
};
for k in 0..heads.len() {
if k > 0 {
blk = blk
.checked_add(heads[k - 1].cblkcnt)
.ok_or(FoldError::Corrupt("erofs: compact blkaddr overflow"))?;
}
heads[k].blkaddr = blk;
}
}
Ok(heads)
}
fn decode_pcluster(
ty: u16,
algo: u8,
span: &[u8],
outlen: usize,
prior_out: &[u8],
lzma_dict: Option<u32>,
) -> Result<Vec<u8>> {
match ty {
LC_TYPE_PLAIN => {
let _ = checked_block_len(outlen as u64)?;
span.get(..outlen)
.map(<[u8]>::to_vec)
.ok_or(FoldError::Corrupt(
"erofs: plain pcluster shorter than output",
))
}
LC_TYPE_HEAD1 | LC_TYPE_HEAD2 => decode_head(algo, span, outlen, prior_out, lzma_dict),
_ => Err(FoldError::Corrupt("erofs: non-head pcluster in decode")),
}
}
fn decode_head(
algo: u8,
span: &[u8],
outlen: usize,
prior_out: &[u8],
lzma_dict: Option<u32>,
) -> Result<Vec<u8>> {
match algo {
ALGO_LZ4 => {
let win = &prior_out[prior_out.len().saturating_sub(LZ4_WINDOW)..];
lz4_pcluster(span, outlen, win)
}
ALGO_DEFLATE => deflate_pcluster(span, outlen),
ALGO_ZSTD => zstd_pcluster(span, outlen),
ALGO_LZMA => {
let dict = lzma_dict.ok_or(FoldError::Unsupported("erofs: lzma config unavailable"))?;
lzma_pcluster(span, outlen, dict)
}
_ => Err(FoldError::Unsupported(
"erofs: unknown compressed head algorithm",
)),
}
}
fn lz4_pcluster(span: &[u8], outlen: usize, win: &[u8]) -> Result<Vec<u8>> {
let fnz = span.iter().position(|&b| b != 0).unwrap_or(0);
for start in [0usize, fnz] {
let tail = span.get(start..).unwrap_or(&[]);
if let Ok(v) = lz4_block_with_dict(tail, outlen, win) {
if v.len() == outlen {
return Ok(v);
}
}
if start == fnz {
break;
}
}
Err(FoldError::Corrupt("erofs lz4: no valid stream start"))
}
fn deflate_pcluster(span: &[u8], outlen: usize) -> Result<Vec<u8>> {
let fnz = span.iter().position(|&b| b != 0).unwrap_or(0);
let mut last = FoldError::Corrupt("erofs deflate: no valid stream start");
for start in [0usize, fnz] {
let tail = span.get(start..).unwrap_or(&[]);
match decode(Codec::Deflate, tail, outlen) {
Ok(v) if v.len() == outlen => return Ok(v),
Ok(_) => {}
Err(e @ (FoldError::Unsupported(_) | FoldError::FileTooLarge { .. })) => return Err(e),
Err(e) => last = e,
}
}
Err(last)
}
fn zstd_pcluster(span: &[u8], outlen: usize) -> Result<Vec<u8>> {
const MAGIC: [u8; 4] = [0x28, 0xb5, 0x2f, 0xfd];
let start = span
.windows(4)
.position(|w| w == MAGIC)
.ok_or(FoldError::Corrupt("erofs zstd: frame magic not found"))?;
decode(Codec::Zstd, &span[start..], outlen)
}
fn lzma_pcluster(span: &[u8], outlen: usize, dict_size: u32) -> Result<Vec<u8>> {
let start = span
.iter()
.position(|&b| b != 0)
.ok_or(FoldError::Corrupt("erofs lzma: all-zero span"))?;
microlzma_block_decode(&span[start..], MICROLZMA_PROPS, dict_size, outlen)
}
fn ft_kind(file_type: u8) -> FileKind {
match file_type {
1 => FileKind::Regular,
2 => FileKind::Directory,
7 => FileKind::Symlink,
_ => FileKind::Other,
}
}
fn le_u16(b: &[u8], o: usize) -> Result<u16> {
b.get(o..o + 2)
.and_then(|s| s.try_into().ok())
.map(u16::from_le_bytes)
.ok_or(FoldError::Corrupt("erofs: truncated u16"))
}
fn le_u32(b: &[u8], o: usize) -> Result<u32> {
b.get(o..o + 4)
.and_then(|s| s.try_into().ok())
.map(u32::from_le_bytes)
.ok_or(FoldError::Corrupt("erofs: truncated u32"))
}
fn le_u64(b: &[u8], o: usize) -> Result<u64> {
b.get(o..o + 8)
.and_then(|s| s.try_into().ok())
.map(u64::from_le_bytes)
.ok_or(FoldError::Corrupt("erofs: truncated u64"))
}