1use bitflags::bitflags;
8use crc::{CRC_32_ISCSI, Crc};
9use std::sync::Arc;
10use thiserror::Error;
11use zerocopy::IntoBytes;
12use zerocopy::byteorder::little_endian::U32 as LEU32;
13
14pub mod readers;
15use readers::{Reader, ReaderError, ReaderExt};
16
17pub mod format;
18pub mod mapper;
19pub use mapper::VolumeMapper;
20
21bitflags! {
22 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
23 pub struct FeatureCompat: u32 {
24 const SB_CHKSUM = 0x00000001;
27 }
28}
29
30bitflags! {
31 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
32 pub struct FeatureIncompat: u32 {
33 const ZERO_PADDING = 0x00000001;
37 }
38}
39
40bitflags! {
41 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
44 pub struct CompressionAdvise: u16 {
45 const COMPACTED_2B = 0x0001;
48 }
49}
50
51pub const COMPACT_ENTRY_LOBITS: u32 = 12;
54
55#[derive(Debug, Error, Clone, PartialEq)]
57pub enum ErofsError {
58 #[error("Unsupported feature incompat flags: 0x{:X}. Only 0x{:X} is supported", _0, _1)]
59 UnsupportedFeatureIncompat(u32, u32),
60
61 #[error("Parsing error: {}", _0)]
62 Parse(#[from] ParsingError),
63 #[error("Reader error: {}", _0)]
64 ReadError(#[from] ReaderError),
65}
66
67#[cfg(target_os = "fuchsia")]
68impl ErofsError {
69 pub fn to_status(self) -> zx::Status {
70 match self {
71 Self::UnsupportedFeatureIncompat(_, _) => zx::Status::NOT_SUPPORTED,
72 Self::Parse(_) => zx::Status::IO_DATA_INTEGRITY,
73 Self::ReadError(_) => zx::Status::IO,
74 }
75 }
76}
77
78#[derive(Debug, Error, Clone, PartialEq)]
80pub enum ParsingError {
81 #[error("Invalid super block magic: 0x{:X}, should be 0x{:X}", _0, format::EROFS_MAGIC)]
82 InvalidSuperBlockMagic(u32),
83 #[error("Checksum mismatch: expected 0x{:X}, computed 0x{:X}", _0, _1)]
84 ChecksumMismatch(u32, u32),
85 #[error("Invalid block size bits: {}, must be between 9 and 12", _0)]
86 InvalidBlockSizeBits(u8),
87
88 #[error("Invalid inode data layout: 0x{:X}", _0)]
89 InvalidInodeDataLayout(u16),
90 #[error("Expected compressed inode layout, found {:?}", _0)]
91 UnexpectedInodeDataLayout(InodeDataLayout),
92 #[error("Missing compression map header on compressed inode")]
93 MissingCompressionHeader,
94 #[error("Unexpected compression algorithm type: {}", _0)]
95 UnexpectedCompressionAlgorithm(u8),
96 #[error("Invalid directory entry")]
97 InvalidDirectoryEntry,
98 #[error("Invalid file type: {}", _0)]
99 InvalidFileType(u8),
100 #[error("Directory entry name was not valid utf8: {}", _0)]
101 InvalidDirectoryEntryName(#[source] std::str::Utf8Error),
102 #[error("Inline data layout missing inline data")]
103 InlineDataLayoutMissingInlineData,
104
105 #[error("Invalid root node")]
106 InvalidRootNode,
107 #[error("Node has an invalid U value for its data layout")]
108 InvalidUValue,
109 #[error("Invalid nid: {}", _0)]
110 InvalidNid(u64),
111 #[error("Integer overflow during calculation")]
112 Overflow,
113 #[error("Decompression failed: {}", _0)]
114 DecompressionFailed(#[from] lz4::Error),
115 #[error("Missing shared xattr area but inode has shared xattrs")]
116 MissingSharedXattrArea,
117 #[error("Xattr entry extends past the end of the inline xattr region")]
118 XattrEntryOutOfBounds,
119 #[error("Invalid xattr namespace index: {}", _0)]
120 InvalidXattrNamespace(u8),
121
122 #[error("Invalid logical cluster type {}", _0)]
123 InvalidLClusterType(u16),
124 #[error("Expected HEAD logical cluster at lcn {}", _0)]
125 ExpectedHeadLCluster(u64),
126 #[error(
127 "Logical cluster number {} out of bounds (total clusters: {})",
128 cluster_index,
129 total_lclusters
130 )]
131 LClusterOutOfBounds { cluster_index: u64, total_lclusters: u64 },
132 #[error("Invalid NonHead delta0 {} at cluster index {}", delta0, cluster_index)]
133 InvalidLClusterDelta { cluster_index: u64, delta0: u16 },
134 #[error("Corrupted compact cluster index pack: {}", _0)]
135 CorruptedCompactClusterPack(&'static str),
136 #[error(
137 "Compact cluster pack offset {}..{} out of bounds (pack size: {})",
138 byte_offset,
139 byte_offset + 4,
140 pack_size
141 )]
142 CompactPackOutOfBounds { byte_offset: usize, pack_size: usize },
143 #[error("Logical offset {} is before start of initial cluster {}", offset, cluster_start)]
144 InvalidClusterOffset { offset: u64, cluster_start: u64 },
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148enum InodeDataUnion {
149 DataBlkAddrPlain(u32),
150 DataBlkAddrInline(u32),
151 CompressedBlocks(u32),
152}
153
154impl InodeDataUnion {
155 fn parse(data: [u8; 4], format: InodeFormat) -> Self {
156 match format.data_layout {
157 InodeDataLayout::FlatPlain => {
158 InodeDataUnion::DataBlkAddrPlain(u32::from_le_bytes(data))
159 }
160 InodeDataLayout::FlatInline => {
162 InodeDataUnion::DataBlkAddrInline(u32::from_le_bytes(data))
163 }
164 InodeDataLayout::CompressedFull | InodeDataLayout::CompressedCompact => {
165 InodeDataUnion::CompressedBlocks(u32::from_le_bytes(data))
166 }
167 }
168 }
169}
170
171#[derive(Debug, Clone)]
172pub struct NodeInner {
173 inode_offset: u64,
174 format: InodeFormat,
175 mode: u16,
176 size: u64,
177 data_union: InodeDataUnion,
178 ino: u32,
179 nid: u64,
180 link_count: u32,
181 uid: u32,
182 gid: u32,
183 mtime_ns: u64,
184 xattr_icount: u16,
185 compression_header: Option<CompressionHeader>,
186}
187
188impl NodeInner {
189 fn is_dir(&self) -> bool {
190 (self.mode & 0xf000) == 0x4000
191 }
192
193 fn is_symlink(&self) -> bool {
194 (self.mode & 0xf000) == 0xa000
195 }
196
197 fn inode_offset(&self) -> u64 {
198 self.inode_offset
199 }
200
201 pub fn data_layout(&self) -> InodeDataLayout {
203 self.format.data_layout
204 }
205
206 fn blkaddr(&self, block_size: u64) -> Option<u64> {
209 match self.data_union {
210 InodeDataUnion::DataBlkAddrPlain(addr) => Some(addr.into()),
211 InodeDataUnion::DataBlkAddrInline(addr) => {
212 debug_assert!(self.size / block_size > 0);
213 Some(addr.into())
214 }
215 InodeDataUnion::CompressedBlocks(_) => None,
216 }
217 }
218
219 pub fn blkaddr_offset(&self, block_size: u64, offset: u64) -> Result<u64, ParsingError> {
222 self.blkaddr(block_size)
223 .ok_or(ParsingError::InvalidUValue)?
224 .checked_mul(block_size)
225 .ok_or(ParsingError::Overflow)?
226 .checked_add(offset)
227 .ok_or(ParsingError::Overflow)
228 }
229
230 fn metadata_size(&self) -> u64 {
231 match self.format.version {
232 InodeVersion::Compact => 32,
233 InodeVersion::Extended => 64,
234 }
235 }
236
237 fn inline_xattr_size(&self) -> u64 {
238 if self.xattr_icount == 0 { 0 } else { ((self.xattr_icount as u64 - 1) * 4) + 12 }
239 }
240
241 pub fn metadata_end_offset(&self) -> Result<u64, ParsingError> {
243 self.inode_offset()
244 .checked_add(self.metadata_size())
245 .ok_or(ParsingError::Overflow)?
246 .checked_add(self.inline_xattr_size())
247 .ok_or(ParsingError::Overflow)
248 }
249
250 fn map_header_offset(&self) -> Result<u64, ParsingError> {
252 let metadata_end = self.metadata_end_offset()?;
253 Ok(metadata_end.next_multiple_of(8))
254 }
255
256 fn index_table_offset(&self) -> Result<u64, ParsingError> {
258 let map_header_offset = self.map_header_offset()?;
259 match self.format.data_layout {
260 InodeDataLayout::CompressedFull => {
261 Ok(map_header_offset + format::LEGACY_MAP_HEADER_SIZE)
262 }
263 InodeDataLayout::CompressedCompact => {
264 Ok(map_header_offset + std::mem::size_of::<format::CompressionMapHeader>() as u64)
265 }
266 _ => Err(ParsingError::UnexpectedInodeDataLayout(self.format.data_layout)),
267 }
268 }
269
270 pub fn compression_header(&self) -> Option<&CompressionHeader> {
272 self.compression_header.as_ref()
273 }
274
275 pub fn compact_entry_pos(
277 &self,
278 block_size: u64,
279 cluster_index: u64,
280 ) -> Result<CompactEntry, ParsingError> {
281 if self.format.data_layout != InodeDataLayout::CompressedCompact {
282 return Err(ParsingError::UnexpectedInodeDataLayout(self.format.data_layout));
283 }
284 let total_clusters = self.total_lclusters(block_size);
285 if cluster_index >= total_clusters {
286 return Err(ParsingError::LClusterOutOfBounds {
287 cluster_index,
288 total_lclusters: total_clusters,
289 });
290 }
291
292 let table_offset = self.index_table_offset()?;
293 let header =
294 self.compression_header.as_ref().ok_or(ParsingError::MissingCompressionHeader)?;
295 let is_compact_2b = header.advise.contains(CompressionAdvise::COMPACTED_2B);
296
297 let initial_4b_count = ((32 - (table_offset % 32)) / 4) & 7;
299 let middle_2b_count = if is_compact_2b && initial_4b_count < total_clusters {
300 (total_clusters - initial_4b_count) & !15
301 } else {
302 0
303 };
304
305 let (pack_pos, layout, entry_index) = if cluster_index < initial_4b_count {
306 let pack_idx = cluster_index / 2;
307 (table_offset + pack_idx * 8, CompactPackLayout::Pack4B, (cluster_index % 2) as usize)
308 } else if cluster_index < initial_4b_count + middle_2b_count {
309 let rel_lcn = cluster_index - initial_4b_count;
310 let base_2b_offset = table_offset + initial_4b_count * 4;
311 let pack_idx = rel_lcn / 16;
312 (base_2b_offset + pack_idx * 32, CompactPackLayout::Pack2B, (rel_lcn % 16) as usize)
313 } else {
314 let rel_lcn = cluster_index - initial_4b_count - middle_2b_count;
315 let base_trailing_offset = table_offset + initial_4b_count * 4 + middle_2b_count * 2;
316 let pack_idx = rel_lcn / 2;
317 (base_trailing_offset + pack_idx * 8, CompactPackLayout::Pack4B, (rel_lcn % 2) as usize)
318 };
319
320 Ok(CompactEntry { pack_offset_bytes: pack_pos, layout, entry_index })
321 }
322
323 pub fn index_end_offset(&self, block_size: u64) -> Result<u64, ParsingError> {
326 let max_lcn = self.total_lclusters(block_size);
327 match self.format.data_layout {
328 InodeDataLayout::CompressedFull => {
329 let index_table_offset = self.index_table_offset()?;
330 index_table_offset
331 .checked_add(max_lcn.checked_mul(8).ok_or(ParsingError::Overflow)?)
332 .ok_or(ParsingError::Overflow)
333 }
334 InodeDataLayout::CompressedCompact => {
335 if max_lcn == 0 {
336 return self.index_table_offset();
337 }
338 let pos = self.compact_entry_pos(block_size, max_lcn - 1)?;
339 pos.pack_offset_bytes.checked_add(pos.pack_size()).ok_or(ParsingError::Overflow)
340 }
341 _ => Err(ParsingError::UnexpectedInodeDataLayout(self.format.data_layout)),
342 }
343 }
344
345 pub fn total_lclusters(&self, lcluster_size: u64) -> u64 {
347 self.size.div_ceil(lcluster_size)
348 }
349
350 pub fn size(&self) -> u64 {
351 self.size
352 }
353 pub fn ino(&self) -> u32 {
354 self.ino
355 }
356 pub fn nid(&self) -> u64 {
357 self.nid
358 }
359 pub fn link_count(&self) -> u32 {
360 self.link_count
361 }
362 pub fn uid(&self) -> u32 {
363 self.uid
364 }
365 pub fn gid(&self) -> u32 {
366 self.gid
367 }
368 pub fn mtime_ns(&self) -> u64 {
369 self.mtime_ns
370 }
371 pub fn mode(&self) -> u16 {
372 self.mode
373 }
374
375 pub fn storage_size(&self, block_size: u64) -> u64 {
377 match self.data_union {
378 InodeDataUnion::CompressedBlocks(blocks) => (blocks as u64) * block_size,
379 _ => self.size,
380 }
381 }
382}
383
384#[derive(Debug, Clone)]
386pub struct DirectoryNode(NodeInner);
387
388impl std::ops::Deref for DirectoryNode {
389 type Target = NodeInner;
390 fn deref(&self) -> &Self::Target {
391 &self.0
392 }
393}
394
395#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
397pub enum FileType {
398 #[default]
399 Unknown = 0,
400 RegFile = 1,
401 Dir = 2,
402 ChrDev = 3,
403 BlkDev = 4,
404 Fifo = 5,
405 Sock = 6,
406 Symlink = 7,
407}
408
409impl TryFrom<u8> for FileType {
410 type Error = ParsingError;
411
412 fn try_from(value: u8) -> Result<Self, Self::Error> {
413 match value {
414 0 => Ok(FileType::Unknown),
415 1 => Ok(FileType::RegFile),
416 2 => Ok(FileType::Dir),
417 3 => Ok(FileType::ChrDev),
418 4 => Ok(FileType::BlkDev),
419 5 => Ok(FileType::Fifo),
420 6 => Ok(FileType::Sock),
421 7 => Ok(FileType::Symlink),
422 _ => Err(ParsingError::InvalidFileType(value)),
423 }
424 }
425}
426
427#[derive(Debug, Clone, Default)]
429pub struct DirectoryEntry {
430 pub nid: u64,
431 pub file_type: FileType,
432 pub name: String,
433}
434
435#[derive(Debug, Clone)]
437pub struct FileNode(NodeInner);
438
439impl std::ops::Deref for FileNode {
440 type Target = NodeInner;
441 fn deref(&self) -> &Self::Target {
442 &self.0
443 }
444}
445
446#[derive(Debug, Clone)]
448pub struct SymlinkNode(NodeInner);
449
450impl std::ops::Deref for SymlinkNode {
451 type Target = NodeInner;
452 fn deref(&self) -> &Self::Target {
453 &self.0
454 }
455}
456
457#[derive(Debug, Clone)]
459pub enum Node {
460 Directory(DirectoryNode),
461 File(FileNode),
462 Symlink(SymlinkNode),
463}
464
465impl Node {
466 fn new(inner: NodeInner) -> Self {
467 if inner.is_dir() {
468 Node::Directory(DirectoryNode(inner))
469 } else if inner.is_symlink() {
470 Node::Symlink(SymlinkNode(inner))
471 } else {
472 Node::File(FileNode(inner))
473 }
474 }
475
476 fn parse_compact(
477 nid: u64,
478 inode_offset: u64,
479 format: InodeFormat,
480 inode: format::InodeCompact,
481 build_time_ns: u64,
482 reader: &dyn Reader,
483 ) -> Result<Self, ErofsError> {
484 let data_union = InodeDataUnion::parse(inode.i_u, format);
485 let mut inner = NodeInner {
486 inode_offset,
487 format,
488 mode: inode.mode.get(),
489 size: inode.size.get().into(),
490 data_union,
491 ino: inode.ino.get(),
492 nid,
493 link_count: inode.link_count.get().into(),
494 uid: inode.uid.get().into(),
495 gid: inode.gid.get().into(),
496 mtime_ns: build_time_ns,
497 xattr_icount: inode.xattr_icount.get(),
498 compression_header: None,
499 };
500 if matches!(
501 format.data_layout,
502 InodeDataLayout::CompressedFull | InodeDataLayout::CompressedCompact
503 ) {
504 let map_header_offset = inner.map_header_offset()?;
505 inner.compression_header = Some(CompressionHeader::read(reader, map_header_offset)?);
506 }
507 Ok(Self::new(inner))
508 }
509
510 fn parse_extended(
511 nid: u64,
512 inode_offset: u64,
513 format: InodeFormat,
514 inode: format::InodeExtended,
515 reader: &dyn Reader,
516 ) -> Result<Self, ErofsError> {
517 let data_union = InodeDataUnion::parse(inode.i_u, format);
518 let mtime_ns = inode
519 .mtime
520 .get()
521 .checked_mul(1_000_000_000)
522 .and_then(|t| t.checked_add(inode.mtime_ns.get().into()))
523 .ok_or(ParsingError::Overflow)?;
524 let mut inner = NodeInner {
525 inode_offset,
526 format,
527 mode: inode.mode.get(),
528 size: inode.size.get(),
529 data_union,
530 ino: inode.ino.get(),
531 nid,
532 link_count: inode.link_count.get(),
533 uid: inode.uid.get(),
534 gid: inode.gid.get(),
535 mtime_ns,
536 xattr_icount: inode.xattr_icount.get(),
537 compression_header: None,
538 };
539 if matches!(
540 format.data_layout,
541 InodeDataLayout::CompressedFull | InodeDataLayout::CompressedCompact
542 ) {
543 let map_header_offset = inner.map_header_offset()?;
544 inner.compression_header = Some(CompressionHeader::read(reader, map_header_offset)?);
545 }
546 Ok(Self::new(inner))
547 }
548
549 fn from_nid(
550 nid: u64,
551 meta_addr: u64,
552 build_time_ns: u64,
553 reader: &dyn Reader,
554 ) -> Result<Self, ErofsError> {
555 let node_offset =
556 nid.checked_mul(format::INODE_SLOT_SIZE).ok_or(ParsingError::InvalidNid(nid))?;
557 let inode_offset =
558 meta_addr.checked_add(node_offset).ok_or(ParsingError::InvalidNid(nid))?;
559 let mut head = [0u8; 2];
561 reader.read(inode_offset, &mut head)?;
562 let format = InodeFormat::parse(u16::from_le_bytes(head))?;
563 let node = match format.version {
564 InodeVersion::Compact => Self::parse_compact(
565 nid,
566 inode_offset,
567 format,
568 reader.read_object(inode_offset)?,
569 build_time_ns,
570 reader,
571 )?,
572 InodeVersion::Extended => Self::parse_extended(
573 nid,
574 inode_offset,
575 format,
576 reader.read_object(inode_offset)?,
577 reader,
578 )?,
579 };
580 Ok(node)
581 }
582}
583
584impl std::ops::Deref for Node {
585 type Target = NodeInner;
586 fn deref(&self) -> &Self::Target {
587 match self {
588 Node::Directory(d) => d,
589 Node::File(f) => f,
590 Node::Symlink(s) => s,
591 }
592 }
593}
594
595#[derive(Debug, Clone, Copy)]
597enum ExtentKind {
598 Sparse,
600 Plain { byte_offset: u64 },
602 Compressed { block_addr: u32 },
604}
605
606#[derive(Debug, Clone, Copy)]
609struct CompressionExtent {
610 logical_start: u64,
611 logical_len: u32,
612 kind: ExtentKind,
613}
614
615pub struct ErofsFilesystem {
617 reader: Arc<dyn Reader>,
618 feature_incompat: FeatureIncompat,
619 block_size: u64,
620 meta_addr: u64,
621 xattr_addr: u64,
622 root_node: DirectoryNode,
623 total_bytes: u64,
624 total_inodes: u64,
625 build_time_ns: u64,
626}
627
628impl ErofsFilesystem {
629 pub fn new(reader: Arc<dyn Reader>) -> Result<Self, ErofsError> {
631 let (super_block, feature_incompat) = Self::parse_superblock(&reader)?;
632 let block_size = 1u64 << super_block.block_size_bits;
633 let meta_block_addr = super_block.meta_block_addr.get().into();
634 let meta_addr = block_size.checked_mul(meta_block_addr).ok_or(ParsingError::Overflow)?;
635 let total_inodes = super_block.inode_count.get();
636 let build_time_ns = super_block
637 .epoch
638 .get()
639 .checked_mul(1_000_000_000)
640 .and_then(|t| t.checked_add(super_block.fixed_nsec.get().into()))
641 .ok_or(ParsingError::Overflow)?;
642 let total_bytes = (super_block.blocks.get() as u64) * block_size;
643 let xattr_block_addr = super_block.xattr_block_addr.get().into();
644 let xattr_addr = block_size.checked_mul(xattr_block_addr).ok_or(ParsingError::Overflow)?;
645 let root_nid = super_block.root_nid.get().into();
646 let root_node = match Node::from_nid(root_nid, meta_addr, build_time_ns, &reader)? {
647 Node::Directory(node) => node,
648 _ => return Err(ParsingError::InvalidRootNode.into()),
649 };
650 Ok(Self {
651 reader,
652 feature_incompat,
653 block_size,
654 meta_addr,
655 xattr_addr,
656 root_node,
657 total_bytes,
658 total_inodes,
659 build_time_ns,
660 })
661 }
662
663 pub fn feature_incompat(&self) -> FeatureIncompat {
665 self.feature_incompat
666 }
667
668 fn parse_superblock(
669 reader: &dyn Reader,
670 ) -> Result<(format::SuperBlock, FeatureIncompat), ErofsError> {
671 let sb: format::SuperBlock = reader.read_object(format::SUPERBLOCK_OFFSET)?;
672 if sb.magic.get() != format::EROFS_MAGIC {
673 return Err(ParsingError::InvalidSuperBlockMagic(sb.magic.get()).into());
674 }
675 if sb.block_size_bits < 9 || sb.block_size_bits > 12 {
678 return Err(ParsingError::InvalidBlockSizeBits(sb.block_size_bits).into());
679 }
680 let feature_compat = FeatureCompat::from_bits_truncate(sb.feature_compat.get());
682 if feature_compat.contains(FeatureCompat::SB_CHKSUM) {
683 Self::check_superblock_checksum(reader, &sb)?;
684 }
685 let incompat_raw = sb.feature_incompat.get();
686 let incompat = FeatureIncompat::from_bits(incompat_raw).ok_or(
687 ErofsError::UnsupportedFeatureIncompat(incompat_raw, FeatureIncompat::all().bits()),
688 )?;
689 Ok((sb, incompat))
690 }
691
692 fn check_superblock_checksum(
693 reader: &dyn Reader,
694 sb: &format::SuperBlock,
695 ) -> Result<(), ErofsError> {
696 let block_size = 1usize << sb.block_size_bits;
697 let len = block_size - (format::SUPERBLOCK_OFFSET as usize) % block_size;
698 let mut buf = vec![0u8; len];
699 reader.read(format::SUPERBLOCK_OFFSET, &mut buf)?;
700
701 buf[4..8].copy_from_slice(&[0u8; 4]);
703
704 let crc = Crc::<u32>::new(&CRC_32_ISCSI);
705 let checksum = crc.checksum(&buf);
706 let checksum = !checksum;
709
710 if checksum != sb.checksum.get() {
711 Err(ParsingError::ChecksumMismatch(sb.checksum.get(), checksum).into())
712 } else {
713 Ok(())
714 }
715 }
716
717 pub fn block_size(&self) -> u64 {
719 self.block_size
720 }
721
722 pub fn node(&self, nid: u64) -> Result<Node, ErofsError> {
724 Node::from_nid(nid, self.meta_addr, self.build_time_ns, &self.reader)
725 }
726
727 pub fn root_node(&self) -> DirectoryNode {
729 self.root_node.clone()
730 }
731
732 pub fn total_bytes(&self) -> u64 {
733 self.total_bytes
734 }
735
736 pub fn total_inodes(&self) -> u64 {
737 self.total_inodes
738 }
739
740 pub fn read_file_range(
742 &self,
743 node: &FileNode,
744 offset: u64,
745 buf: &mut [u8],
746 ) -> Result<usize, ErofsError> {
747 self.read_node_range(&node.0, offset, buf)
748 }
749
750 pub fn read_symlink(&self, node: &SymlinkNode) -> Result<Vec<u8>, ErofsError> {
752 let mut target = vec![0u8; node.size() as usize];
753 let read_bytes = self.read_node_range(&node.0, 0, &mut target)?;
754 target.truncate(read_bytes);
755 Ok(target)
756 }
757
758 fn read_node_range(
766 &self,
767 node: &NodeInner,
768 offset: u64,
769 buf: &mut [u8],
770 ) -> Result<usize, ErofsError> {
771 if offset >= node.size {
772 return Ok(0);
773 }
774 let read_len = std::cmp::min(buf.len() as u64, node.size - offset) as usize;
775 let buf = &mut buf[..read_len];
776 let block_size = self.block_size();
777
778 match node.format.data_layout {
779 InodeDataLayout::FlatPlain => {
780 let read_offset = node.blkaddr_offset(block_size, offset)?;
781 self.reader.read(read_offset, buf)?;
782 Ok(read_len)
783 }
784 InodeDataLayout::FlatInline => {
785 let full_blocks_len = (node.size / block_size) * block_size;
788 let mut bytes_read = 0;
789
790 if offset < full_blocks_len {
791 let current_read_len =
794 std::cmp::min(read_len as u64, full_blocks_len - offset) as usize;
795 let read_offset = node.blkaddr_offset(block_size, offset)?;
796 self.reader.read(read_offset, &mut buf[..current_read_len])?;
797 bytes_read += current_read_len;
798 }
799
800 if bytes_read < read_len {
801 let remaining_len = read_len - bytes_read;
802 let current_offset = offset + bytes_read as u64;
803 let inline_data_offset = node.metadata_end_offset()?;
804 let tail_offset = current_offset - full_blocks_len;
805 let tail_read_offset = inline_data_offset
806 .checked_add(tail_offset)
807 .ok_or(ParsingError::Overflow)?;
808 self.reader.read(tail_read_offset, &mut buf[bytes_read..])?;
809 bytes_read += remaining_len;
810 }
811
812 Ok(bytes_read)
813 }
814 InodeDataLayout::CompressedFull | InodeDataLayout::CompressedCompact => {
815 self.read_compressed_range(node, offset, buf)
816 }
817 }
818 }
819
820 fn read_lcluster_entry(
821 &self,
822 node: &NodeInner,
823 cluster_index: u64,
824 ) -> Result<LClusterEntry, ErofsError> {
825 match node.format.data_layout {
826 InodeDataLayout::CompressedFull => {
827 let index_table_offset = node.index_table_offset()?;
828 let entry_offset = index_table_offset + cluster_index * 8;
829 LClusterEntry::read_full_entry(self.reader.as_ref(), entry_offset)
830 }
831 InodeDataLayout::CompressedCompact => {
832 self.read_compact_lcluster_entry(node, cluster_index)
833 }
834 _ => Err(ParsingError::UnexpectedInodeDataLayout(node.format.data_layout).into()),
835 }
836 }
837
838 fn read_compact_lcluster_entry(
839 &self,
840 node: &NodeInner,
841 cluster_index: u64,
842 ) -> Result<LClusterEntry, ErofsError> {
843 let pos = node.compact_entry_pos(self.block_size(), cluster_index)?;
844 let pack = CompactPack::read(self.reader.as_ref(), &pos)?;
845 let entry = pack.entry(pos.entry_index)?;
846
847 if entry.cluster_type() == LClusterType::NonHead {
848 let delta0 = if pos.entry_index + 1 != pos.entry_count() {
849 entry.delta0()
850 } else {
851 if pos.entry_index == 0 {
852 return Err(ParsingError::CorruptedCompactClusterPack(
853 "missing preceding entry for delta0 inference",
854 )
855 .into());
856 }
857 let prev_entry = pack.entry(pos.entry_index - 1)?;
858 if prev_entry.cluster_type() != LClusterType::NonHead {
859 1
860 } else {
861 prev_entry.delta0() + 1
862 }
863 };
864
865 Ok(LClusterEntry::NonHead { delta0 })
866 } else {
867 let base_pblk = pack.base_pblk()?;
868
869 let mut entry_i = pos.entry_index;
870 let mut nblk = 1u32;
871
872 while entry_i > 0 {
873 entry_i -= 1;
874 let prev_entry = pack.entry(entry_i)?;
875 if prev_entry.cluster_type() == LClusterType::NonHead {
876 let step = prev_entry.delta0() as usize;
877 if entry_i >= step {
878 entry_i -= step;
879 nblk += 1;
880 } else {
881 break;
882 }
883 } else {
884 nblk += 1;
885 }
886 }
887
888 let extent_start_offset = entry.extent_start_offset();
889 let block_addr = if base_pblk == u32::MAX { 0 } else { base_pblk + nblk };
890 Ok(LClusterEntry::Head(LClusterHead {
891 cluster_type: entry.cluster_type(),
892 block_addr,
893 extent_start_offset,
894 }))
895 }
896 }
897
898 fn resolve_head_lcluster(
901 &self,
902 node: &NodeInner,
903 cluster_index: u64,
904 ) -> Result<(u64, LClusterHead), ErofsError> {
905 let entry = self.read_lcluster_entry(node, cluster_index)?;
906 match entry {
907 LClusterEntry::Head(head) => Ok((cluster_index, head)),
908 LClusterEntry::NonHead { delta0 } => {
909 let head_cluster_index = cluster_index
910 .checked_sub(delta0 as u64)
911 .ok_or(ParsingError::InvalidLClusterDelta { cluster_index, delta0 })?;
912 let head_entry = self.read_lcluster_entry(node, head_cluster_index)?;
913 match head_entry {
914 LClusterEntry::Head(head) => Ok((head_cluster_index, head)),
915 LClusterEntry::NonHead { .. } => {
916 Err(ParsingError::ExpectedHeadLCluster(head_cluster_index).into())
917 }
918 }
919 }
920 }
921 }
922
923 fn find_head_cluster(
927 &self,
928 node: &NodeInner,
929 logical_offset: u64,
930 ) -> Result<(u64, LClusterHead, u64), ErofsError> {
931 let lcluster_size = self.block_size();
932 let cluster_index = logical_offset / lcluster_size;
933
934 let (mut head_cluster_index, mut head) = self.resolve_head_lcluster(node, cluster_index)?;
935 let mut logical_start =
936 head.logical_start(head_cluster_index, lcluster_size).ok_or(ParsingError::Overflow)?;
937
938 if logical_offset < logical_start {
939 let prev_cluster_index =
940 head_cluster_index.checked_sub(1).ok_or(ParsingError::InvalidClusterOffset {
941 offset: logical_offset,
942 cluster_start: logical_start,
943 })?;
944 (head_cluster_index, head) = self.resolve_head_lcluster(node, prev_cluster_index)?;
945 logical_start = head
946 .logical_start(head_cluster_index, lcluster_size)
947 .ok_or(ParsingError::Overflow)?;
948 }
949
950 Ok((head_cluster_index, head, logical_start))
951 }
952
953 fn get_extent_at(
957 &self,
958 node: &NodeInner,
959 logical_offset: u64,
960 ) -> Result<CompressionExtent, ErofsError> {
961 let block_size = self.block_size();
962 let lcluster_size = block_size;
963
964 let (head_cluster_index, head, logical_start) =
965 self.find_head_cluster(node, logical_offset)?;
966
967 let mut logical_end = node.size;
968 let max_cluster_index = node.total_lclusters(lcluster_size);
969 for next_cluster_index in (head_cluster_index + 1)..max_cluster_index {
970 let next_entry = self.read_lcluster_entry(node, next_cluster_index)?;
971 if let LClusterEntry::Head(next_head) = next_entry {
972 if let Some(next_start) = next_head.logical_start(next_cluster_index, lcluster_size)
973 {
974 logical_end = next_start;
975 break;
976 }
977 }
978 }
979
980 let logical_len =
981 logical_end.checked_sub(logical_start).ok_or(ParsingError::Overflow)? as u32;
982
983 let kind = if head.block_addr == 0 {
984 ExtentKind::Sparse
985 } else if head.is_plain() {
986 ExtentKind::Plain { byte_offset: head.block_addr as u64 * block_size }
987 } else {
988 ExtentKind::Compressed { block_addr: head.block_addr }
989 };
990
991 Ok(CompressionExtent { logical_start, logical_len, kind })
992 }
993
994 fn read_compressed_range(
998 &self,
999 node: &NodeInner,
1000 mut offset: u64,
1001 mut buf: &mut [u8],
1002 ) -> Result<usize, ErofsError> {
1003 let read_len = buf.len();
1005 let block_size = self.block_size();
1006
1007 while !buf.is_empty() {
1008 let extent = self.get_extent_at(node, offset)?;
1009 let offset_in_cluster = (offset - extent.logical_start) as usize;
1010 let available = extent.logical_len as usize - offset_in_cluster;
1011 let copy_len = std::cmp::min(buf.len(), available);
1012 let (head, tail) = buf.split_at_mut(copy_len);
1013
1014 match &extent.kind {
1015 ExtentKind::Sparse => {
1016 head.fill(0);
1017 }
1018 ExtentKind::Plain { byte_offset: disk_offset } => {
1019 self.reader.read(*disk_offset + offset_in_cluster as u64, head)?;
1020 }
1021 ExtentKind::Compressed { block_addr } => {
1022 let mut compressed_buf = vec![0u8; block_size as usize];
1023 self.reader.read(*block_addr as u64 * block_size, &mut compressed_buf)?;
1024 let margin = if self.feature_incompat.contains(FeatureIncompat::ZERO_PADDING) {
1025 compressed_buf.iter().position(|&b| b != 0).unwrap_or(0)
1026 } else {
1027 0
1028 };
1029 let compressed_data = &compressed_buf[margin..];
1030
1031 if offset_in_cluster == 0 && copy_len == extent.logical_len as usize {
1032 lz4::decompress_into(compressed_data, head)
1033 .map_err(|e| ParsingError::DecompressionFailed(e))?;
1034 } else {
1035 let mut decompressed_buf = vec![0u8; extent.logical_len as usize];
1036 lz4::decompress_into(compressed_data, &mut decompressed_buf)
1037 .map_err(|e| ParsingError::DecompressionFailed(e))?;
1038 head.copy_from_slice(
1039 &decompressed_buf[offset_in_cluster..offset_in_cluster + copy_len],
1040 );
1041 }
1042 }
1043 }
1044
1045 buf = tail;
1046 offset += copy_len as u64;
1047 }
1048
1049 Ok(read_len)
1050 }
1051
1052 pub fn read_directory(
1065 &self,
1066 node: &DirectoryNode,
1067 mut entry_offset: usize,
1068 entries: &mut [DirectoryEntry],
1069 ) -> Result<usize, ErofsError> {
1070 let block_size = self.block_size();
1071 let block_size_usize: usize = block_size as usize;
1072 let mut entries_filled = 0;
1073 let mut current_entry_index = 0;
1074 let mut block_data = vec![0u8; block_size_usize];
1075
1076 for block in 0.. {
1077 let base_offset = block * block_size;
1078 let bytes_read = self.read_node_range(&node.0, base_offset, &mut block_data)?;
1079 if bytes_read < format::DIRENT_SIZE {
1080 return Ok(entries_filled);
1082 }
1083 block_data[bytes_read..].fill(0);
1084
1085 let (dirent0, _) = zerocopy::Ref::<&[u8], format::Dirent>::from_prefix(&block_data)
1087 .map_err(|_| ParsingError::InvalidDirectoryEntry)?;
1088 let nameoff0 = dirent0.nameoff.get() as usize;
1089 if nameoff0 < format::DIRENT_SIZE || nameoff0 >= block_size_usize {
1090 return Err(ParsingError::InvalidDirectoryEntry.into());
1091 }
1092 let entry_count = nameoff0 / format::DIRENT_SIZE;
1093
1094 if current_entry_index + entry_count <= entry_offset {
1096 current_entry_index += entry_count;
1097 continue;
1098 }
1099
1100 let dirents_raw = block_data
1102 .get(..entry_count * format::DIRENT_SIZE)
1103 .ok_or(ParsingError::InvalidDirectoryEntry)?;
1104 let dirents: &[format::Dirent] =
1105 &*zerocopy::Ref::<&[u8], [format::Dirent]>::from_bytes(dirents_raw)
1106 .map_err(|_| ParsingError::InvalidDirectoryEntry)?;
1107
1108 let block_entry_offset = entry_offset - current_entry_index;
1109 let space = entries.len() - entries_filled;
1110 let block_entry_end = std::cmp::min(
1111 entry_count,
1112 block_entry_offset.checked_add(space).ok_or(ParsingError::Overflow)?,
1113 );
1114
1115 for i in block_entry_offset..block_entry_end {
1116 let last_entry = i + 1 == entry_count;
1117 let nameoff = dirents[i].nameoff.get() as usize;
1118
1119 let name_bytes = if last_entry {
1120 let name_data =
1123 block_data.get(nameoff..).ok_or(ParsingError::InvalidDirectoryEntry)?;
1124 name_data.split(|&x| x == 0).next().unwrap()
1125 } else {
1126 let nameoff_next = dirents[i + 1].nameoff.get() as usize;
1127 block_data
1128 .get(nameoff..nameoff_next)
1129 .ok_or(ParsingError::InvalidDirectoryEntry)?
1130 };
1131
1132 let name = std::str::from_utf8(name_bytes)
1133 .map_err(|e| ParsingError::InvalidDirectoryEntryName(e))?
1134 .to_string();
1135 entries[entries_filled] = DirectoryEntry {
1136 nid: dirents[i].nid.get(),
1137 file_type: dirents[i].file_type.try_into()?,
1138 name,
1139 };
1140 entries_filled += 1;
1141 if entries_filled == entries.len() {
1142 return Ok(entries_filled);
1143 }
1144 }
1145
1146 current_entry_index =
1147 current_entry_index.checked_add(entry_count).ok_or(ParsingError::Overflow)?;
1148 entry_offset = current_entry_index;
1149 }
1150
1151 Ok(entries_filled)
1152 }
1153
1154 pub fn lookup(&self, dir: &DirectoryNode, name: &str) -> Result<Option<Node>, ErofsError> {
1156 let mut entry_offset = 0;
1157 let mut buffer = vec![DirectoryEntry::default(); 16];
1158
1159 loop {
1160 let filled = self.read_directory(dir, entry_offset, &mut buffer)?;
1161 for i in 0..filled {
1162 if buffer[i].name == name {
1163 let node = self.node(buffer[i].nid)?;
1164 return Ok(Some(node));
1165 }
1166 }
1167 if filled < buffer.len() {
1168 break;
1169 }
1170 entry_offset += filled;
1171 }
1172
1173 Ok(None)
1174 }
1175
1176 pub fn iter_xattrs<'a>(&'a self, node: &NodeInner) -> Result<XattrIterator<'a>, ErofsError> {
1178 if node.xattr_icount == 0 {
1179 return Ok(XattrIterator {
1180 reader: self.reader.as_ref(),
1181 xattr_addr: self.xattr_addr,
1182 shared_ids: Vec::new(),
1183 inline_offset: 0,
1184 inline_end: 0,
1185 });
1186 }
1187 let xattr_metadata_size = node.inline_xattr_size();
1188 let xattr_metadata_start =
1189 node.inode_offset().checked_add(node.metadata_size()).ok_or(ParsingError::Overflow)?;
1190
1191 let header: format::XattrInlineBodyHeader =
1193 self.reader.read_object(xattr_metadata_start)?;
1194 let shared_count = header.shared_count as usize;
1195
1196 let shared_ids_size = shared_count as u64 * 4;
1197 let inline_entries_start = xattr_metadata_start + 12 + shared_ids_size;
1198 let inline_end = xattr_metadata_start + xattr_metadata_size;
1199
1200 if inline_entries_start > inline_end {
1201 return Err(ParsingError::XattrEntryOutOfBounds.into());
1202 }
1203
1204 let shared_ids = if shared_count > 0 {
1205 let mut ids = vec![LEU32::ZERO; shared_count];
1206 self.reader.read(xattr_metadata_start + 12, ids.as_mut_bytes())?;
1207 ids
1208 } else {
1209 Vec::new()
1210 };
1211
1212 Ok(XattrIterator {
1213 reader: self.reader.as_ref(),
1214 xattr_addr: self.xattr_addr,
1215 shared_ids,
1216 inline_offset: inline_entries_start,
1217 inline_end,
1218 })
1219 }
1220
1221 pub fn list_xattrs(&self, node: &NodeInner) -> Result<Vec<Vec<u8>>, ErofsError> {
1223 let mut names = Vec::new();
1224 for entry in self.iter_xattrs(node)? {
1225 let entry = entry?;
1226 names.push(entry.read_name(self.reader.as_ref())?);
1227 }
1228 Ok(names)
1229 }
1230
1231 pub fn get_xattr(&self, node: &NodeInner, name: &[u8]) -> Result<Option<Vec<u8>>, ErofsError> {
1233 for entry in self.iter_xattrs(node)? {
1234 let entry = entry?;
1235 if entry.matches_name(self.reader.as_ref(), name)? {
1236 return Ok(Some(entry.read_value(self.reader.as_ref())?));
1237 }
1238 }
1239 Ok(None)
1240 }
1241}
1242
1243pub struct XattrIterator<'a> {
1245 reader: &'a dyn Reader,
1246 xattr_addr: u64,
1247 shared_ids: Vec<LEU32>,
1248 inline_offset: u64,
1249 inline_end: u64,
1250}
1251
1252impl XattrIterator<'_> {
1253 fn next_inner(&mut self) -> Result<Option<XattrEntryHeader>, ErofsError> {
1254 if let Some(shared_id) = self.shared_ids.pop() {
1255 let shared_entry_offset = self.xattr_addr + (shared_id.get() as u64 * 4);
1256 if self.shared_ids.is_empty() {
1257 self.shared_ids = Vec::new();
1258 }
1259 return Ok(Some(XattrEntryHeader::parse(self.reader, shared_entry_offset)?));
1260 }
1261
1262 if self.inline_offset < self.inline_end {
1263 if self.inline_offset + 4 > self.inline_end {
1264 return Err(ParsingError::XattrEntryOutOfBounds.into());
1265 }
1266
1267 let header = XattrEntryHeader::parse(self.reader, self.inline_offset)?;
1268 let next_offset = self
1269 .inline_offset
1270 .checked_add(header.entry_aligned_size)
1271 .ok_or(ParsingError::Overflow)?;
1272 if next_offset > self.inline_end {
1273 return Err(ParsingError::XattrEntryOutOfBounds.into());
1274 }
1275 self.inline_offset = next_offset;
1276 Ok(Some(header))
1277 } else {
1278 Ok(None)
1279 }
1280 }
1281}
1282
1283impl Iterator for XattrIterator<'_> {
1284 type Item = Result<XattrEntryHeader, ErofsError>;
1285
1286 fn next(&mut self) -> Option<Self::Item> {
1287 match self.next_inner() {
1288 Err(e) => {
1293 self.shared_ids = Vec::new();
1294 self.inline_offset = self.inline_end;
1295 Some(Err(e))
1296 }
1297 Ok(None) => None,
1298 Ok(Some(x)) => Some(Ok(x)),
1299 }
1300 }
1301}
1302
1303#[derive(Debug, Clone, Copy)]
1305pub struct XattrEntryHeader {
1306 pub offset: u64,
1307 pub prefix: &'static [u8],
1308 pub name_index: u8,
1309 pub name_len: usize,
1310 pub value_size: usize,
1311 pub entry_aligned_size: u64,
1312}
1313
1314impl XattrEntryHeader {
1315 pub fn parse(reader: &dyn Reader, offset: u64) -> Result<Self, ErofsError> {
1317 let entry: format::XattrEntry = reader.read_object(offset)?;
1318 let prefix = Self::get_xattr_prefix(entry.name_index)?;
1319 let name_len = entry.name_len as usize;
1320 let value_size = entry.value_size.get() as usize;
1321
1322 let entry_aligned_size = 4usize
1323 .checked_add(name_len)
1324 .and_then(|s| s.checked_add(value_size))
1325 .and_then(|s| s.checked_next_multiple_of(4))
1326 .ok_or(ParsingError::Overflow)? as u64;
1327
1328 Ok(Self {
1329 offset,
1330 prefix,
1331 name_index: entry.name_index,
1332 name_len,
1333 value_size,
1334 entry_aligned_size,
1335 })
1336 }
1337
1338 pub fn matches_name(&self, reader: &dyn Reader, name: &[u8]) -> Result<bool, ReaderError> {
1340 let Some(suffix) = name.strip_prefix(self.prefix) else {
1341 return Ok(false);
1342 };
1343 if suffix.len() != self.name_len {
1344 return Ok(false);
1345 }
1346 if self.name_len == 0 {
1347 return Ok(true);
1349 }
1350 let mut buf = vec![0u8; self.name_len];
1351 reader.read(self.offset + 4, &mut buf)?;
1352 Ok(buf == suffix)
1353 }
1354
1355 pub fn read_name(&self, reader: &dyn Reader) -> Result<Vec<u8>, ReaderError> {
1357 let mut name_bytes = Vec::with_capacity(self.prefix.len() + self.name_len);
1358 name_bytes.extend_from_slice(self.prefix);
1359 if self.name_len > 0 {
1360 name_bytes.resize(self.prefix.len() + self.name_len, 0);
1361 reader.read(self.offset + 4, &mut name_bytes[self.prefix.len()..])?;
1362 }
1363 Ok(name_bytes)
1364 }
1365
1366 pub fn read_value(&self, reader: &dyn Reader) -> Result<Vec<u8>, ReaderError> {
1368 let mut value_bytes = vec![0u8; self.value_size];
1369 reader.read(self.offset + 4 + self.name_len as u64, &mut value_bytes)?;
1370 Ok(value_bytes)
1371 }
1372
1373 pub fn read_payload(&self, reader: &dyn Reader) -> Result<(Vec<u8>, Vec<u8>), ReaderError> {
1375 let name = self.read_name(reader)?;
1376 let value = self.read_value(reader)?;
1377 Ok((name, value))
1378 }
1379
1380 fn get_xattr_prefix(index: u8) -> Result<&'static [u8], ParsingError> {
1381 match index {
1382 1 => Ok(b"user."),
1383 2 => Ok(b"system.posix_acl_access"),
1384 3 => Ok(b"system.posix_acl_default"),
1385 4 => Ok(b"trusted."),
1386 6 => Ok(b"security."),
1387 _ => Err(ParsingError::InvalidXattrNamespace(index)),
1388 }
1389 }
1390}
1391
1392#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1395pub enum InodeVersion {
1396 Compact,
1397 Extended,
1398}
1399
1400#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1402pub enum InodeDataLayout {
1403 FlatPlain,
1406 CompressedFull,
1409 FlatInline,
1415 CompressedCompact,
1417}
1418
1419#[derive(Debug, Clone, Copy)]
1421pub struct InodeFormat {
1422 pub version: InodeVersion,
1423 pub data_layout: InodeDataLayout,
1424}
1425
1426impl InodeFormat {
1427 pub fn parse(format: u16) -> Result<Self, ParsingError> {
1429 let version =
1430 if format & 0x1 == 0 { InodeVersion::Compact } else { InodeVersion::Extended };
1431 let data_layout_raw = (format >> 1) & 0x7;
1432 let data_layout = match data_layout_raw {
1433 0 => InodeDataLayout::FlatPlain,
1434 1 => InodeDataLayout::CompressedFull,
1435 2 => InodeDataLayout::FlatInline,
1436 3 => InodeDataLayout::CompressedCompact,
1437 _ => return Err(ParsingError::InvalidInodeDataLayout(data_layout_raw)),
1438 };
1439 Ok(Self { version, data_layout })
1440 }
1441}
1442
1443#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1444pub enum LClusterType {
1445 Plain,
1446 Head1,
1447 NonHead,
1448 Head2,
1449}
1450
1451impl LClusterType {
1452 pub fn is_head(&self) -> bool {
1453 matches!(self, Self::Head1 | Self::Head2 | Self::Plain)
1454 }
1455}
1456
1457impl TryFrom<u16> for LClusterType {
1458 type Error = ParsingError;
1459
1460 fn try_from(advise: u16) -> Result<Self, Self::Error> {
1461 match advise & 3 {
1462 0 => Ok(Self::Plain),
1463 1 => Ok(Self::Head1),
1464 2 => Ok(Self::NonHead),
1465 3 => Ok(Self::Head2),
1466 other => Err(ParsingError::InvalidLClusterType(other)),
1467 }
1468 }
1469}
1470
1471#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1475pub struct LClusterHead {
1476 pub cluster_type: LClusterType,
1478 pub block_addr: u32,
1481 pub extent_start_offset: u16,
1486}
1487
1488impl LClusterHead {
1489 pub fn is_plain(&self) -> bool {
1490 self.cluster_type == LClusterType::Plain
1491 }
1492
1493 pub fn logical_start(&self, cluster_index: u64, lcluster_size: u64) -> Option<u64> {
1494 cluster_index.checked_mul(lcluster_size)?.checked_add(self.extent_start_offset as u64)
1495 }
1496}
1497
1498#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1502pub enum LClusterEntry {
1503 Head(LClusterHead),
1505 NonHead {
1508 delta0: u16,
1510 },
1511}
1512
1513impl LClusterEntry {
1514 pub fn read_full_entry(reader: &dyn Reader, offset: u64) -> Result<Self, ErofsError> {
1517 let raw: format::LClusterIndex = reader.read_object(offset)?;
1518 let advise = raw.advisory_flags.get();
1519 let cluster_type = LClusterType::try_from(advise)?;
1520 let extent_start_offset = raw.extent_start_offset.get();
1521
1522 if cluster_type.is_head() {
1523 let block_addr = u32::from_le_bytes(raw.data_union);
1524 Ok(Self::Head(LClusterHead { cluster_type, block_addr, extent_start_offset }))
1525 } else {
1526 let delta0 = u16::from_le_bytes([raw.data_union[0], raw.data_union[1]]);
1527 Ok(Self::NonHead { delta0 })
1528 }
1529 }
1530}
1531
1532#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1534pub enum CompactPackLayout {
1535 Pack4B,
1537 Pack2B,
1539}
1540
1541impl CompactPackLayout {
1542 pub const fn pack_size(&self) -> u64 {
1543 match self {
1544 Self::Pack4B => 8,
1545 Self::Pack2B => 32,
1546 }
1547 }
1548
1549 pub const fn entry_count(&self) -> usize {
1550 match self {
1551 Self::Pack4B => 2,
1552 Self::Pack2B => 16,
1553 }
1554 }
1555
1556 pub const fn encode_bits(&self) -> usize {
1557 match self {
1558 Self::Pack4B => 16,
1559 Self::Pack2B => 14,
1560 }
1561 }
1562}
1563
1564#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1566pub struct CompactEntry {
1567 pub pack_offset_bytes: u64,
1569 pub layout: CompactPackLayout,
1571 pub entry_index: usize,
1573}
1574
1575impl CompactEntry {
1576 pub fn pack_size(&self) -> u64 {
1577 self.layout.pack_size()
1578 }
1579
1580 pub fn entry_count(&self) -> usize {
1581 self.layout.entry_count()
1582 }
1583
1584 pub fn encode_bits(&self) -> usize {
1585 self.layout.encode_bits()
1586 }
1587}
1588
1589#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1591pub struct CompactPack {
1592 layout: CompactPackLayout,
1593 data: [u8; 32],
1594}
1595
1596impl CompactPack {
1597 pub fn read(reader: &dyn Reader, pos: &CompactEntry) -> Result<Self, ErofsError> {
1598 let mut data = [0u8; 32];
1599 let pack_size = pos.pack_size() as usize;
1600 reader.read(pos.pack_offset_bytes, &mut data[..pack_size])?;
1601 Ok(Self { layout: pos.layout, data })
1602 }
1603
1604 pub fn from_bytes(layout: CompactPackLayout, buf: &[u8]) -> Result<Self, ParsingError> {
1605 let min_size = layout.pack_size() as usize;
1606 if buf.len() < min_size {
1607 return Err(ParsingError::CompactPackOutOfBounds {
1608 byte_offset: 0,
1609 pack_size: buf.len(),
1610 });
1611 }
1612 let mut data = [0u8; 32];
1613 data[..min_size].copy_from_slice(&buf[..min_size]);
1614 Ok(Self { layout, data })
1615 }
1616
1617 pub fn base_pblk(&self) -> Result<u32, ParsingError> {
1618 let size = self.layout.pack_size() as usize;
1619 let bytes: [u8; 4] = self
1620 .data
1621 .get(size - 4..size)
1622 .ok_or(ParsingError::CompactPackOutOfBounds { byte_offset: size - 4, pack_size: size })?
1623 .try_into()
1624 .map_err(|_| ParsingError::CompactPackOutOfBounds {
1625 byte_offset: size - 4,
1626 pack_size: size,
1627 })?;
1628 Ok(u32::from_le_bytes(bytes))
1629 }
1630
1631 pub fn entry(&self, entry_index: usize) -> Result<CompactPackEntry, ParsingError> {
1632 let pack_size = self.layout.pack_size() as usize;
1633 let bit_offset = self.layout.encode_bits() * entry_index;
1634 let byte_offset = bit_offset / 8;
1635 let bit_shift = bit_offset & 7;
1636 let bytes: [u8; 4] = self
1637 .data
1638 .get(byte_offset..byte_offset + 4)
1639 .ok_or(ParsingError::CompactPackOutOfBounds { byte_offset, pack_size })?
1640 .try_into()
1641 .map_err(|_| ParsingError::CompactPackOutOfBounds { byte_offset, pack_size })?;
1642 let v = u32::from_le_bytes(bytes) >> bit_shift;
1643 let mask = (1u32 << COMPACT_ENTRY_LOBITS) - 1;
1644 let data = (v & mask) as u16;
1645 let type_raw = ((v >> COMPACT_ENTRY_LOBITS) & 3) as u16;
1646 let cluster_type = LClusterType::try_from(type_raw)?;
1647 Ok(CompactPackEntry { data, cluster_type })
1648 }
1649}
1650
1651#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1653pub struct CompactPackEntry {
1654 data: u16,
1657 cluster_type: LClusterType,
1659}
1660
1661impl CompactPackEntry {
1662 pub fn cluster_type(&self) -> LClusterType {
1663 self.cluster_type
1664 }
1665
1666 pub fn extent_start_offset(&self) -> u16 {
1669 self.data
1670 }
1671
1672 pub fn delta0(&self) -> u16 {
1674 self.data
1675 }
1676}
1677
1678#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1679pub struct CompressionHeader {
1680 pub advise: CompressionAdvise,
1681}
1682
1683impl CompressionHeader {
1684 pub fn read(reader: &dyn Reader, offset: u64) -> Result<Self, ErofsError> {
1685 let raw: format::CompressionMapHeader = reader.read_object(offset)?;
1686 if raw.algorithm_type != 0 {
1687 return Err(ParsingError::UnexpectedCompressionAlgorithm(raw.algorithm_type).into());
1688 }
1689 let advise = CompressionAdvise::from_bits_truncate(raw.advisory_flags.get());
1690 Ok(Self { advise })
1691 }
1692}
1693
1694#[cfg(test)]
1695mod tests {
1696 use super::*;
1697 use crate::readers::VecReader;
1698 use std::fs;
1699 use test_case::test_case;
1700 use zerocopy::byteorder::little_endian::{U16 as LEU16, U32 as LEU32, U64 as LEU64};
1701
1702 #[test]
1703 fn test_lcluster_type_and_parsing() {
1704 assert_eq!(LClusterType::try_from(0).unwrap(), LClusterType::Plain);
1705 assert_eq!(LClusterType::try_from(1).unwrap(), LClusterType::Head1);
1706 assert_eq!(LClusterType::try_from(2).unwrap(), LClusterType::NonHead);
1707 assert_eq!(LClusterType::try_from(3).unwrap(), LClusterType::Head2);
1708 assert!(LClusterType::Head1.is_head());
1709 assert!(LClusterType::Plain.is_head());
1710 assert!(!LClusterType::NonHead.is_head());
1711
1712 let raw_head = format::LClusterIndex {
1714 advisory_flags: LEU16::new(1),
1715 extent_start_offset: LEU16::new(128),
1716 data_union: 0x1000u32.to_le_bytes(),
1717 };
1718 let reader = VecReader::new(raw_head.as_bytes().to_vec());
1719 let parsed_head = LClusterEntry::read_full_entry(&reader, 0).unwrap();
1720 assert_eq!(
1721 parsed_head,
1722 LClusterEntry::Head(LClusterHead {
1723 cluster_type: LClusterType::Head1,
1724 block_addr: 0x1000,
1725 extent_start_offset: 128,
1726 })
1727 );
1728
1729 let raw_nonhead = format::LClusterIndex {
1731 advisory_flags: LEU16::new(2),
1732 extent_start_offset: LEU16::new(0),
1733 data_union: [4, 0, 0, 0],
1734 };
1735 let reader_nonhead = VecReader::new(raw_nonhead.as_bytes().to_vec());
1736 let parsed_nonhead = LClusterEntry::read_full_entry(&reader_nonhead, 0).unwrap();
1737 assert_eq!(parsed_nonhead, LClusterEntry::NonHead { delta0: 4 });
1738 }
1739
1740 #[test]
1741 fn test_compact_entry_pos_and_index_end_offset() {
1742 let block_size = 4096u64;
1743 let num_lclusters = 10u64;
1744 let node = NodeInner {
1745 inode_offset: 4096,
1746 format: InodeFormat {
1747 version: InodeVersion::Compact,
1748 data_layout: InodeDataLayout::CompressedCompact,
1749 },
1750 mode: 0o100644,
1751 size: num_lclusters * block_size,
1752 data_union: InodeDataUnion::CompressedBlocks(0),
1753 ino: 1,
1754 nid: 1,
1755 link_count: 1,
1756 uid: 0,
1757 gid: 0,
1758 mtime_ns: 0,
1759 xattr_icount: 0,
1760 compression_header: Some(CompressionHeader { advise: CompressionAdvise::empty() }),
1761 };
1762
1763 assert_eq!(node.map_header_offset().unwrap(), 4128);
1765 assert_eq!(node.index_table_offset().unwrap(), 4136);
1767
1768 let pos0 = node.compact_entry_pos(block_size, 0).unwrap();
1770 assert_eq!(pos0.pack_offset_bytes, 4136);
1771 assert_eq!(pos0.layout, CompactPackLayout::Pack4B);
1772 assert_eq!(pos0.pack_size(), 8);
1773 assert_eq!(pos0.entry_index, 0);
1774 assert_eq!(pos0.encode_bits(), 16);
1775 assert_eq!(pos0.entry_count(), 2);
1776
1777 let pos1 = node.compact_entry_pos(block_size, 1).unwrap();
1778 assert_eq!(pos1.pack_offset_bytes, 4136);
1779 assert_eq!(pos1.layout, CompactPackLayout::Pack4B);
1780 assert_eq!(pos1.entry_index, 1);
1781
1782 let pos2 = node.compact_entry_pos(block_size, 2).unwrap();
1783 assert_eq!(pos2.pack_offset_bytes, 4144);
1784 assert_eq!(pos2.layout, CompactPackLayout::Pack4B);
1785 assert_eq!(pos2.entry_index, 0);
1786
1787 assert_eq!(
1789 node.compact_entry_pos(block_size, num_lclusters),
1790 Err(ParsingError::LClusterOutOfBounds { cluster_index: 10, total_lclusters: 10 })
1791 );
1792
1793 let mut no_header_node = node.clone();
1795 no_header_node.compression_header = None;
1796 assert_eq!(
1797 no_header_node.compact_entry_pos(block_size, 0),
1798 Err(ParsingError::MissingCompressionHeader)
1799 );
1800
1801 let mut plain_node = node.clone();
1803 plain_node.format.data_layout = InodeDataLayout::FlatPlain;
1804 assert_eq!(
1805 plain_node.compact_entry_pos(block_size, 0),
1806 Err(ParsingError::UnexpectedInodeDataLayout(InodeDataLayout::FlatPlain))
1807 );
1808 assert_eq!(
1809 plain_node.index_end_offset(block_size),
1810 Err(ParsingError::UnexpectedInodeDataLayout(InodeDataLayout::FlatPlain))
1811 );
1812
1813 assert_eq!(node.index_end_offset(block_size).unwrap(), 4176);
1815
1816 let mut empty_compact_node = node.clone();
1818 empty_compact_node.size = 0;
1819 assert_eq!(empty_compact_node.index_end_offset(block_size).unwrap(), 4136);
1820
1821 let mut full_node = node.clone();
1823 full_node.format.data_layout = InodeDataLayout::CompressedFull;
1824 assert_eq!(full_node.index_table_offset().unwrap(), 4144);
1826 assert_eq!(full_node.index_end_offset(block_size).unwrap(), 4224);
1828 }
1829
1830 #[test]
1831 fn test_unexpected_compression_algorithm() {
1832 let raw = format::CompressionMapHeader {
1833 reserved_1: [0; 2],
1834 inline_data_size: LEU16::new(0),
1835 advisory_flags: LEU16::new(0),
1836 algorithm_type: 2, lcluster_bits: 0,
1838 };
1839 let reader = VecReader::new(raw.as_bytes().to_vec());
1840 let result = CompressionHeader::read(&reader, 0);
1841 assert_eq!(result, Err(ErofsError::Parse(ParsingError::UnexpectedCompressionAlgorithm(2))));
1842 }
1843
1844 #[test]
1845 fn test_compact_entry_decode() {
1846 let entry1_raw = (2u16 << 12) | 3;
1851 let mut pack_buf = [0u8; 8];
1852 pack_buf[0..2].copy_from_slice(&0x1080u16.to_le_bytes());
1853 pack_buf[2..4].copy_from_slice(&entry1_raw.to_le_bytes());
1854 pack_buf[4..8].copy_from_slice(&0x00000020u32.to_le_bytes());
1855
1856 let pack = CompactPack::from_bytes(CompactPackLayout::Pack4B, &pack_buf).unwrap();
1857 assert_eq!(pack.base_pblk().unwrap(), 0x20);
1858
1859 let entry0 = pack.entry(0).unwrap();
1860 assert_eq!(entry0.extent_start_offset(), 128);
1861 assert_eq!(entry0.cluster_type(), LClusterType::Head1);
1862
1863 let entry1 = pack.entry(1).unwrap();
1864 assert_eq!(entry1.cluster_type(), LClusterType::NonHead);
1865 assert_eq!(entry1.delta0(), 3);
1866 }
1867
1868 fn create_synthetic_sparse_filesystem() -> (ErofsFilesystem, FileNode) {
1869 let block_size = 4096usize;
1870 let num_blocks = 8;
1871 let mut image = vec![0u8; num_blocks * block_size];
1872
1873 let sb = format::SuperBlock {
1875 magic: LEU32::new(format::EROFS_MAGIC),
1876 checksum: LEU32::new(0),
1877 feature_compat: LEU32::new(0),
1878 block_size_bits: 12,
1879 sb_ext_slots: 0,
1880 root_nid: LEU16::new(0),
1881 inode_count: LEU64::new(2),
1882 epoch: LEU64::new(0),
1883 fixed_nsec: LEU32::new(0),
1884 blocks: LEU32::new(num_blocks as u32),
1885 meta_block_addr: LEU32::new(1),
1886 xattr_block_addr: LEU32::new(0),
1887 uuid: [0; 16],
1888 volume_name: [0; 16],
1889 feature_incompat: LEU32::new(0),
1890 available_compr_algs: LEU16::new(0),
1891 extra_devices: LEU32::new(0),
1892 dirblkbits: 0,
1893 reserved: [0; 37],
1894 };
1895 image[format::SUPERBLOCK_OFFSET as usize
1896 ..format::SUPERBLOCK_OFFSET as usize + std::mem::size_of::<format::SuperBlock>()]
1897 .copy_from_slice(sb.as_bytes());
1898
1899 let root_inode = format::InodeCompact {
1901 format: LEU16::new(0),
1902 xattr_icount: LEU16::new(0),
1903 mode: LEU16::new(0o040755),
1904 link_count: LEU16::new(2),
1905 size: LEU32::new(0),
1906 reserved_1: [0; 4],
1907 i_u: [0; 4],
1908 ino: LEU32::new(1),
1909 uid: LEU16::new(0),
1910 gid: LEU16::new(0),
1911 reserved_2: [0; 4],
1912 };
1913 image[4096..4096 + 32].copy_from_slice(root_inode.as_bytes());
1914
1915 let file_size = 16384u32; let file_inode = format::InodeCompact {
1918 format: LEU16::new((InodeDataLayout::CompressedFull as u16) << 1),
1919 xattr_icount: LEU16::new(0),
1920 mode: LEU16::new(0o100644),
1921 link_count: LEU16::new(1),
1922 size: LEU32::new(file_size),
1923 reserved_1: [0; 4],
1924 i_u: [0; 4],
1925 ino: LEU32::new(2),
1926 uid: LEU16::new(0),
1927 gid: LEU16::new(0),
1928 reserved_2: [0; 4],
1929 };
1930 image[4128..4128 + 32].copy_from_slice(file_inode.as_bytes());
1931
1932 let map_header = format::CompressionMapHeader {
1934 reserved_1: [0; 2],
1935 inline_data_size: LEU16::new(0),
1936 advisory_flags: LEU16::new(0),
1937 algorithm_type: 0,
1938 lcluster_bits: 0,
1939 };
1940 image[4160..4160 + 8].copy_from_slice(map_header.as_bytes());
1941
1942 let cluster0 = format::LClusterIndex {
1945 advisory_flags: LEU16::new(0),
1946 extent_start_offset: LEU16::new(0),
1947 data_union: 4u32.to_le_bytes(),
1948 };
1949 let cluster1 = format::LClusterIndex {
1951 advisory_flags: LEU16::new(0),
1952 extent_start_offset: LEU16::new(0),
1953 data_union: 0u32.to_le_bytes(),
1954 };
1955 let cluster2 = format::LClusterIndex {
1957 advisory_flags: LEU16::new(0),
1958 extent_start_offset: LEU16::new(0),
1959 data_union: 0u32.to_le_bytes(),
1960 };
1961 let cluster3 = format::LClusterIndex {
1963 advisory_flags: LEU16::new(0),
1964 extent_start_offset: LEU16::new(0),
1965 data_union: 5u32.to_le_bytes(),
1966 };
1967 image[4176..4176 + 8].copy_from_slice(cluster0.as_bytes());
1968 image[4184..4184 + 8].copy_from_slice(cluster1.as_bytes());
1969 image[4192..4192 + 8].copy_from_slice(cluster2.as_bytes());
1970 image[4200..4200 + 8].copy_from_slice(cluster3.as_bytes());
1971
1972 image[16384..16384 + 4096].fill(0xAA);
1974 image[20480..20480 + 4096].fill(0xBB);
1975
1976 let reader = Arc::new(VecReader::new(image));
1977 let fs = ErofsFilesystem::new(reader).expect("failed to parse synthetic fs");
1978 let node = fs.node(1).expect("failed to get node 1");
1979 let Node::File(file_node) = node else { panic!("expected file node") };
1980
1981 (fs, file_node)
1982 }
1983
1984 #[test]
1985 fn test_synthetic_sparse_extents() {
1986 let (fs, file_node) = create_synthetic_sparse_filesystem();
1987
1988 let extent0 = fs.get_extent_at(&file_node.0, 0).unwrap();
1990 assert_eq!(extent0.logical_start, 0);
1991 assert_eq!(extent0.logical_len, 4096);
1992 match extent0.kind {
1993 ExtentKind::Plain { byte_offset: disk_offset } => {
1994 assert_eq!(disk_offset, 4 * 4096);
1995 }
1996 other => panic!("expected Plain extent at 0, got {:?}", other),
1997 }
1998
1999 let extent1 = fs.get_extent_at(&file_node.0, 4096).unwrap();
2001 assert_eq!(extent1.logical_start, 4096);
2002 assert_eq!(extent1.logical_len, 4096);
2003 assert!(matches!(extent1.kind, ExtentKind::Sparse));
2004
2005 let extent_mid = fs.get_extent_at(&file_node.0, 5000).unwrap();
2007 assert_eq!(extent_mid.logical_start, 4096);
2008 assert_eq!(extent_mid.logical_len, 4096);
2009 assert!(matches!(extent_mid.kind, ExtentKind::Sparse));
2010
2011 let extent2 = fs.get_extent_at(&file_node.0, 8192).unwrap();
2013 assert_eq!(extent2.logical_start, 8192);
2014 assert_eq!(extent2.logical_len, 4096);
2015 assert!(matches!(extent2.kind, ExtentKind::Sparse));
2016
2017 let extent3 = fs.get_extent_at(&file_node.0, 12288).unwrap();
2019 assert_eq!(extent3.logical_start, 12288);
2020 assert_eq!(extent3.logical_len, 4096);
2021 match extent3.kind {
2022 ExtentKind::Plain { byte_offset: disk_offset } => {
2023 assert_eq!(disk_offset, 5 * 4096);
2024 }
2025 other => panic!("expected Plain extent at 12288, got {:?}", other),
2026 }
2027 }
2028
2029 #[test]
2030 fn test_synthetic_sparse_reads() {
2031 let (fs, file_node) = create_synthetic_sparse_filesystem();
2032
2033 let mut full_buf = vec![0u8; 16384];
2035 let bytes_read = fs.read_file_range(&file_node, 0, &mut full_buf).unwrap();
2036 assert_eq!(bytes_read, 16384);
2037 assert_eq!(&full_buf[0..4096], &[0xAA; 4096]);
2038 assert_eq!(&full_buf[4096..12288], &[0x00; 8192]);
2039 assert_eq!(&full_buf[12288..16384], &[0xBB; 4096]);
2040
2041 let mut sparse_buf = vec![0xFFu8; 2000];
2043 let bytes_read = fs.read_file_range(&file_node, 5000, &mut sparse_buf).unwrap();
2044 assert_eq!(bytes_read, 2000);
2045 assert_eq!(&sparse_buf, &[0x00; 2000]);
2046
2047 let mut span_start_buf = vec![0xFFu8; 200];
2049 let bytes_read = fs.read_file_range(&file_node, 4000, &mut span_start_buf).unwrap();
2050 assert_eq!(bytes_read, 200);
2051 assert_eq!(&span_start_buf[..96], &[0xAA; 96]); assert_eq!(&span_start_buf[96..], &[0x00; 104]); let mut span_end_buf = vec![0xFFu8; 200];
2056 let bytes_read = fs.read_file_range(&file_node, 12200, &mut span_end_buf).unwrap();
2057 assert_eq!(bytes_read, 200);
2058 assert_eq!(&span_end_buf[..88], &[0x00; 88]); assert_eq!(&span_end_buf[88..], &[0xBB; 112]); let mut multi_sparse_buf = vec![0xFFu8; 9000];
2063 let bytes_read = fs.read_file_range(&file_node, 4000, &mut multi_sparse_buf).unwrap();
2064 assert_eq!(bytes_read, 9000);
2065 assert_eq!(&multi_sparse_buf[..96], &[0xAA; 96]); assert_eq!(&multi_sparse_buf[96..8288], &[0x00; 8192]); assert_eq!(&multi_sparse_buf[8288..9000], &[0xBB; 712]); }
2069
2070 fn load_image(file: &str) -> Vec<u8> {
2071 fs::read(format!("/pkg/data/{file}")).expect("failed to read test file")
2072 }
2073
2074 #[test_case("simple.erofs" ; "4096 block size")]
2075 #[test_case("simple_512.erofs" ; "512 block size")]
2076 #[test_case("simple_lz4.erofs" ; "4096 block size lz4 compressed")]
2077 #[test_case("simple_lz4_legacy.erofs" ; "4096 block size lz4 legacy compressed")]
2078 #[fuchsia::test]
2079 fn test_parse_superblock(file: &str) {
2080 let runfiles = load_image(file);
2081 let reader = Arc::new(VecReader::new(runfiles.clone()));
2082 let _fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2084
2085 let mut mutated_runfiles = runfiles.clone();
2088 mutated_runfiles[1088] ^= 0xFF;
2089
2090 let reader = Arc::new(VecReader::new(mutated_runfiles));
2091 let fs = ErofsFilesystem::new(reader);
2092 assert!(fs.is_err());
2093 match fs.err().unwrap() {
2094 ErofsError::Parse(ParsingError::ChecksumMismatch(_, _)) => {}
2095 e => panic!("Expected ChecksumMismatch error, got {:?}", e),
2096 }
2097 }
2098
2099 #[test_case("simple.erofs" ; "4096 block size")]
2100 #[test_case("simple_512.erofs" ; "512 block size")]
2101 #[test_case("simple_lz4.erofs" ; "4096 block size lz4 compressed")]
2102 #[test_case("simple_lz4_legacy.erofs" ; "4096 block size lz4 legacy compressed")]
2103 #[fuchsia::test]
2104 fn test_list_dir(file: &str) {
2105 let runfiles = load_image(file);
2106 let reader = Arc::new(VecReader::new(runfiles));
2107 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2108 let root_node = fs.root_node();
2109
2110 let mut buf = vec![DirectoryEntry::default(); 16];
2111 let filled = fs.read_directory(&root_node, 0, &mut buf).expect("failed to read directory");
2112
2113 let names: Vec<String> = buf[..filled].iter().map(|e| e.name.clone()).collect();
2114 assert_eq!(
2115 names,
2116 vec![
2117 ".",
2118 "..",
2119 "file1",
2120 "large_dir",
2121 "mixed_compression",
2122 "photosynthesis",
2123 "quantum",
2124 "symlink_to_file1",
2125 ]
2126 );
2127 }
2128
2129 #[test_case("simple.erofs" ; "4096 block size")]
2130 #[test_case("simple_512.erofs" ; "512 block size")]
2131 #[test_case("simple_lz4.erofs" ; "4096 block size lz4 compressed")]
2132 #[test_case("simple_lz4_legacy.erofs" ; "4096 block size lz4 legacy compressed")]
2133 #[fuchsia::test]
2134 fn test_overflow_nid(file: &str) {
2135 let runfiles = load_image(file);
2136 let reader = Arc::new(VecReader::new(runfiles));
2137 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2138 let result = fs.node(u64::MAX);
2139 assert!(result.is_err());
2140 assert_eq!(result.unwrap_err(), ErofsError::Parse(ParsingError::InvalidNid(u64::MAX)));
2141 }
2142
2143 #[test_case("simple.erofs", "file1" ; "4096 block size file1")]
2144 #[test_case("simple_512.erofs", "file1" ; "512 block size file1")]
2145 #[test_case("simple_lz4.erofs", "file1" ; "4096 block size lz4 file1")]
2146 #[test_case("simple_lz4_legacy.erofs", "file1" ; "4096 block size lz4 legacy file1")]
2147 #[test_case("simple.erofs", "photosynthesis" ; "4096 block size photosynthesis")]
2148 #[test_case("simple_512.erofs", "photosynthesis" ; "512 block size photosynthesis")]
2149 #[test_case("simple_lz4.erofs", "photosynthesis" ; "4096 block size lz4 photosynthesis")]
2150 #[test_case("simple_lz4_legacy.erofs", "photosynthesis" ; "4096 block size lz4 legacy photosynthesis")]
2151 #[test_case("simple_lz4.erofs", "quantum" ; "4096 block size lz4 quantum")]
2152 #[test_case("simple_lz4_legacy.erofs", "quantum" ; "4096 block size lz4 legacy quantum")]
2153 #[test_case("simple.erofs", "mixed_compression" ; "4096 block size mixed_compression")]
2154 #[test_case("simple_512.erofs", "mixed_compression" ; "512 block size mixed_compression")]
2155 #[test_case("simple_lz4.erofs", "mixed_compression" ; "4096 block size lz4 mixed_compression")]
2156 #[test_case("simple_lz4_legacy.erofs", "mixed_compression" ; "4096 block size lz4 legacy mixed_compression")]
2157 #[fuchsia::test]
2158 fn test_read_file_range(file: &str, name: &str) {
2159 let runfiles = load_image(file);
2160 let reader = Arc::new(VecReader::new(runfiles));
2161 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2162 let root_node = fs.root_node();
2163
2164 let node = fs.lookup(&root_node, name).expect("failed to lookup").expect("file not found");
2165 let file_node = match node {
2166 Node::File(f) => f,
2167 _ => panic!("Expected file node"),
2168 };
2169
2170 let size = file_node.size() as usize;
2171 let mut buf = vec![0u8; size];
2172 let bytes_read = fs.read_file_range(&file_node, 0, &mut buf).expect("failed to read");
2173 assert_eq!(bytes_read, size);
2174 let expected =
2175 fs::read(format!("/pkg/data/simple/{}", name)).expect("failed to read source file");
2176 assert_eq!(buf, expected);
2177
2178 let mut buf = vec![0u8; 5];
2180 let bytes_read =
2181 fs.read_file_range(&file_node, 5, &mut buf).expect("failed to read partial");
2182 assert_eq!(bytes_read, 5);
2183 assert_eq!(&buf, &expected[5..10]);
2184
2185 if size > 5000 {
2187 let mut buf = vec![0u8; 100];
2188 let bytes_read =
2189 fs.read_file_range(&file_node, 5000, &mut buf).expect("failed to read at 5000");
2190 assert_eq!(bytes_read, 100);
2191 assert_eq!(&buf, &expected[5000..5100]);
2192 }
2193 if size > 15000 {
2194 let mut buf = vec![0u8; 200];
2195 let bytes_read =
2196 fs.read_file_range(&file_node, 15000, &mut buf).expect("failed to read at 15000");
2197 assert_eq!(bytes_read, 200);
2198 assert_eq!(&buf, &expected[15000..15200]);
2199 }
2200
2201 let mut buf = vec![0u8; 100];
2203 let bytes_read = fs
2204 .read_file_range(&file_node, (size - 5) as u64, &mut buf)
2205 .expect("failed to read past eof");
2206 assert_eq!(bytes_read, 5);
2207 if name == "file1" {
2208 assert_eq!(&buf[..5], b"file\n");
2209 }
2210
2211 let mut buf = vec![0u8; 100];
2213 let bytes_read =
2214 fs.read_file_range(&file_node, size as u64, &mut buf).expect("failed to read");
2215 assert_eq!(bytes_read, 0);
2216 }
2217
2218 #[test_case("simple_lz4.erofs" ; "4096 block size lz4 compressed")]
2219 #[test_case("simple_lz4_legacy.erofs" ; "4096 block size lz4 legacy compressed")]
2220 #[fuchsia::test]
2221 fn test_mixed_compression(file: &str) {
2222 let runfiles = load_image(file);
2223 let fs = ErofsFilesystem::new(Arc::new(VecReader::new(runfiles))).unwrap();
2224 let root = fs.root_node();
2225 let node = fs.lookup(&root, "mixed_compression").unwrap().unwrap();
2226 let Node::File(file_node) = node else { panic!() };
2227
2228 let lcluster_size = fs.block_size();
2229 let totalidx = file_node.total_lclusters(lcluster_size);
2230 let mut has_compressed_head = false;
2231 let mut has_plain_cluster = false;
2232 for lcn in 0..totalidx {
2233 if let Ok(LClusterEntry::Head(head)) = fs.read_lcluster_entry(&file_node.0, lcn) {
2234 if head.cluster_type.is_head() && head.cluster_type != LClusterType::Plain {
2235 has_compressed_head = true;
2236 }
2237 if head.cluster_type == LClusterType::Plain {
2238 has_plain_cluster = true;
2239 }
2240 }
2241 }
2242 assert!(
2243 has_compressed_head && has_plain_cluster,
2244 "mixed_compression should contain some plain clusters"
2245 );
2246
2247 let size = file_node.size() as usize;
2248 let mut buf = vec![0u8; size];
2249 let bytes_read = fs.read_file_range(&file_node, 0, &mut buf).expect("failed to read");
2250 assert_eq!(bytes_read, size);
2251
2252 let expected =
2253 fs::read("/pkg/data/simple/mixed_compression").expect("failed to read source file");
2254 assert_eq!(buf, expected);
2255 }
2256
2257 #[test_case("simple.erofs" ; "4096 block size")]
2258 #[test_case("simple_512.erofs" ; "512 block size")]
2259 #[test_case("simple_lz4.erofs" ; "4096 block size lz4 compressed")]
2260 #[test_case("simple_lz4_legacy.erofs" ; "4096 block size lz4 legacy compressed")]
2261 #[fuchsia::test]
2262 fn test_read_symlink(file: &str) {
2263 let runfiles = load_image(file);
2264 let reader = Arc::new(VecReader::new(runfiles));
2265 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2266 let root_node = fs.root_node();
2267
2268 let node = fs
2269 .lookup(&root_node, "symlink_to_file1")
2270 .expect("failed to lookup")
2271 .expect("symlink not found");
2272 let symlink_node = match node {
2273 Node::Symlink(s) => s,
2274 _ => panic!("Expected symlink node"),
2275 };
2276
2277 let target = fs.read_symlink(&symlink_node).expect("failed to read symlink");
2278 assert_eq!(target, b"file1");
2279
2280 let selinux_val = fs.get_xattr(&symlink_node, b"security.selinux").unwrap().unwrap();
2281 assert_eq!(selinux_val, b"u:object_r:symlink_t:s0");
2282 }
2283
2284 #[test_case("simple.erofs" ; "4096 block size")]
2285 #[test_case("simple_512.erofs" ; "512 block size")]
2286 #[fuchsia::test]
2287 fn test_read_directory_pagination(file: &str) {
2288 let runfiles = load_image(file);
2289 let reader = Arc::new(VecReader::new(runfiles));
2290 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2291 let root_node = fs.root_node();
2292
2293 let expected_names = vec![
2294 ".",
2295 "..",
2296 "file1",
2297 "large_dir",
2298 "mixed_compression",
2299 "photosynthesis",
2300 "quantum",
2301 "symlink_to_file1",
2302 ];
2303
2304 let mut buf = vec![DirectoryEntry::default(); 2];
2306
2307 let filled = fs.read_directory(&root_node, 0, &mut buf).expect("failed to read dir");
2309 assert_eq!(filled, 2);
2310 assert_eq!(buf[0].name, expected_names[0]);
2311 assert_eq!(buf[1].name, expected_names[1]);
2312
2313 let filled = fs.read_directory(&root_node, 2, &mut buf).expect("failed to read dir");
2315 assert_eq!(filled, 2);
2316 assert_eq!(buf[0].name, expected_names[2]);
2317 assert_eq!(buf[1].name, expected_names[3]);
2318
2319 let filled = fs.read_directory(&root_node, 4, &mut buf).expect("failed to read dir");
2321 assert_eq!(filled, 2);
2322 assert_eq!(buf[0].name, expected_names[4]);
2323 assert_eq!(buf[1].name, expected_names[5]);
2324
2325 let filled = fs.read_directory(&root_node, 6, &mut buf).expect("failed to read dir");
2327 assert_eq!(filled, 2);
2328 assert_eq!(buf[0].name, expected_names[6]);
2329 assert_eq!(buf[1].name, expected_names[7]);
2330
2331 let filled = fs.read_directory(&root_node, 8, &mut buf).expect("failed to read dir");
2333 assert_eq!(filled, 0);
2334
2335 let mut buf1 = vec![DirectoryEntry::default(); 1];
2337 for i in 0..expected_names.len() {
2338 let filled = fs.read_directory(&root_node, i, &mut buf1).expect("failed to read dir");
2339 assert_eq!(filled, 1);
2340 assert_eq!(buf1[0].name, expected_names[i]);
2341 }
2342 let filled = fs
2343 .read_directory(&root_node, expected_names.len(), &mut buf1)
2344 .expect("failed to read dir");
2345 assert_eq!(filled, 0);
2346 }
2347
2348 #[test_case("simple.erofs" ; "4096 block size")]
2349 #[test_case("simple_512.erofs" ; "512 block size")]
2350 #[test_case("simple_lz4.erofs" ; "4096 block size lz4 compressed")]
2351 #[test_case("simple_lz4_legacy.erofs" ; "4096 block size lz4 legacy compressed")]
2352 #[fuchsia::test]
2353 fn test_read_directory_large_dir(file: &str) {
2354 let runfiles = load_image(file);
2357 let reader = Arc::new(VecReader::new(runfiles));
2358 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2359 let root_node = fs.root_node();
2360
2361 let large_dir_node = fs
2362 .lookup(&root_node, "large_dir")
2363 .expect("failed to look up large_dir")
2364 .expect("large_dir not found");
2365
2366 let large_dir = match large_dir_node {
2367 Node::Directory(d) => d,
2368 _ => panic!("Expected directory node"),
2369 };
2370
2371 assert_eq!(fs.get_xattr(&root_node, b"security.selinux").unwrap(), None);
2372 let selinux_val = fs.get_xattr(&large_dir, b"security.selinux").unwrap().unwrap();
2373 assert_eq!(
2374 selinux_val,
2375 b"u:object_r:very_long_selinux_context_exceeding_the_inline_limit_of_two_hundred_and_fifty_six_bytes_and_requiring_the_use_of_extended_attributes_instead_of_returning_the_context_inline_in_the_node_attributes_table_representation_as_dictated_by_the_fuchsia_io_node_fidl_specification:s0"
2376 );
2377 let file1_node = fs.lookup(&large_dir, "file_number_1").unwrap().unwrap();
2378 let file1_selinux = fs.get_xattr(&file1_node, b"security.selinux").unwrap().unwrap();
2379 assert_eq!(
2380 file1_selinux,
2381 b"u:object_r:very_long_selinux_context_exceeding_the_inline_limit_of_two_hundred_and_fifty_six_bytes_and_requiring_the_use_of_extended_attributes_instead_of_returning_the_context_inline_in_the_node_attributes_table_representation_as_dictated_by_the_fuchsia_io_node_fidl_specification:s0"
2382 );
2383
2384 let mut entry_offset = 2;
2386 let mut buffer = vec![DirectoryEntry::default(); 16];
2387 loop {
2388 let filled = fs.read_directory(&large_dir, entry_offset, &mut buffer).unwrap();
2389 for i in 0..filled {
2390 assert_eq!(buffer[i].name[..12], format!("file_number_"));
2392 }
2393 if filled < buffer.len() {
2394 break;
2395 }
2396 entry_offset += filled;
2397 }
2398 }
2399
2400 #[test_case("simple.erofs" ; "4096 block size")]
2401 #[test_case("simple_512.erofs" ; "512 block size")]
2402 #[test_case("simple_lz4.erofs" ; "4096 block size lz4 compressed")]
2403 #[test_case("simple_lz4_legacy.erofs" ; "4096 block size lz4 legacy compressed")]
2404 #[fuchsia::test]
2405 fn test_filesystem_metadata(file: &str) {
2406 let runfiles = load_image(file);
2407 let reader = Arc::new(VecReader::new(runfiles));
2408 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2409
2410 assert!(fs.total_bytes() > 0);
2411 assert!(fs.total_inodes() > 0);
2412 }
2413
2414 #[test_case("simple.erofs" ; "4096 block size")]
2415 #[test_case("simple_512.erofs" ; "512 block size")]
2416 #[fuchsia::test]
2417 fn test_node_metadata(file: &str) {
2418 let runfiles = load_image(file);
2419 let reader = Arc::new(VecReader::new(runfiles));
2420 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2421 let root_node = fs.root_node();
2422
2423 assert!(root_node.link_count() >= 2);
2424 assert!(root_node.mtime_ns() > 0);
2425
2426 let file1_node = fs.lookup(&root_node, "file1").unwrap().unwrap();
2427 assert_eq!(file1_node.link_count(), 1);
2428 assert!(file1_node.mtime_ns() > 0);
2429 }
2430
2431 #[test_case("simple.erofs" ; "4096 block size")]
2432 #[test_case("simple_512.erofs" ; "512 block size")]
2433 #[fuchsia::test]
2434 fn test_xattrs(file: &str) {
2435 let runfiles = load_image(file);
2436 let reader = Arc::new(VecReader::new(runfiles));
2437 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2438 let root_node = fs.root_node();
2439
2440 let file1_node = fs.lookup(&root_node, "file1").unwrap().unwrap();
2442
2443 let xattr_names = fs.list_xattrs(&file1_node).unwrap();
2444 assert!(xattr_names.contains(&b"user.flavor".to_vec()));
2446 assert!(xattr_names.contains(&b"user.security".to_vec()));
2447 assert!(xattr_names.contains(&b"user.shared".to_vec()));
2448 assert!(xattr_names.contains(&b"security.selinux".to_vec()));
2449 assert_eq!(xattr_names.len(), 4);
2450
2451 let flavor_val = fs.get_xattr(&file1_node, b"user.flavor").unwrap().unwrap();
2452 assert_eq!(flavor_val, b"vanilla");
2453
2454 let security_val = fs.get_xattr(&file1_node, b"user.security").unwrap().unwrap();
2455 assert_eq!(security_val, b"high");
2456
2457 let shared_val = fs.get_xattr(&file1_node, b"user.shared").unwrap().unwrap();
2458 assert_eq!(shared_val, b"same_value");
2459
2460 let selinux_val = fs.get_xattr(&file1_node, b"security.selinux").unwrap().unwrap();
2461 assert_eq!(selinux_val, b"u:object_r:file1_t:s0");
2462
2463 let photo_node = fs.lookup(&root_node, "photosynthesis").unwrap().unwrap();
2465
2466 let photo_xattrs = fs.list_xattrs(&photo_node).unwrap();
2467 assert_eq!(photo_xattrs, vec![b"user.shared".to_vec()]);
2468
2469 let photo_shared_val = fs.get_xattr(&photo_node, b"user.shared").unwrap().unwrap();
2470 assert_eq!(photo_shared_val, b"same_value");
2471 assert_eq!(fs.get_xattr(&photo_node, b"security.selinux").unwrap(), None);
2472
2473 let quantum_node = fs.lookup(&root_node, "quantum").unwrap().unwrap();
2475 assert_eq!(fs.get_xattr(&quantum_node, b"security.selinux").unwrap(), None);
2476
2477 let file_node = match photo_node {
2479 Node::File(f) => f,
2480 _ => panic!("Expected file node"),
2481 };
2482 let size = file_node.size() as usize;
2483 let mut buf = vec![0u8; size];
2484 let bytes_read = fs.read_file_range(&file_node, 0, &mut buf).expect("failed to read");
2485 assert_eq!(bytes_read, size);
2486 assert!(size > 0);
2487
2488 let val = fs.get_xattr(&file1_node, b"user.non_existent").unwrap();
2490 assert_eq!(val, None);
2491 }
2492
2493 #[fuchsia::test]
2497 fn test_shared_xattr_parsing() {
2498 let block_size = 4096usize;
2499 let mut buf = vec![0u8; 3 * block_size];
2500
2501 let sb = format::SuperBlock {
2503 magic: LEU32::new(format::EROFS_MAGIC),
2504 checksum: LEU32::new(0),
2505 feature_compat: LEU32::new(0),
2506 block_size_bits: 12,
2507 sb_ext_slots: 0,
2508 root_nid: LEU16::new(0),
2509 inode_count: LEU64::new(1),
2510 epoch: LEU64::new(0),
2511 fixed_nsec: LEU32::new(0),
2512 blocks: LEU32::new(3),
2513 meta_block_addr: LEU32::new(1),
2514 xattr_block_addr: LEU32::new(0),
2515 uuid: [0; 16],
2516 volume_name: [0; 16],
2517 feature_incompat: LEU32::new(0),
2518 available_compr_algs: LEU16::new(0),
2519 extra_devices: LEU32::new(0),
2520 dirblkbits: 0,
2521 reserved: [0; 37],
2522 };
2523 buf[1024..1024 + 128].copy_from_slice(sb.as_bytes());
2524
2525 let inode = format::InodeCompact {
2527 format: LEU16::new(0),
2528 xattr_icount: LEU16::new(2),
2529 mode: LEU16::new(0o040755),
2530 link_count: LEU16::new(1),
2531 size: LEU32::new(0),
2532 reserved_1: [0; 4],
2533 i_u: [0; 4],
2534 ino: LEU32::new(0),
2535 uid: LEU16::new(0),
2536 gid: LEU16::new(0),
2537 reserved_2: [0; 4],
2538 };
2539 buf[4096..4096 + 32].copy_from_slice(inode.as_bytes());
2540
2541 let header = format::XattrInlineBodyHeader {
2543 name_filter: LEU32::new(0),
2544 shared_count: 1,
2545 reserved: [0; 7],
2546 };
2547 buf[4128..4128 + 12].copy_from_slice(header.as_bytes());
2548 buf[4140..4144].copy_from_slice(&512u32.to_le_bytes());
2550
2551 let xentry = format::XattrEntry {
2553 name_len: 6,
2554 name_index: 1, value_size: LEU16::new(10),
2556 };
2557 buf[2048..2052].copy_from_slice(xentry.as_bytes());
2558 buf[2052..2058].copy_from_slice(b"shared");
2559 buf[2058..2068].copy_from_slice(b"same_value");
2560
2561 let reader = Arc::new(VecReader::new(buf));
2562 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2563 let root_node = fs.root_node();
2564
2565 let xattr_names = fs.list_xattrs(&root_node).expect("failed to list xattrs");
2566 assert_eq!(xattr_names, vec![b"user.shared".to_vec()]);
2567
2568 let shared_val =
2569 fs.get_xattr(&root_node, b"user.shared").expect("failed to get xattr").unwrap();
2570 assert_eq!(shared_val, b"same_value");
2571 }
2572
2573 #[fuchsia::test]
2574 fn test_xattr_iterator_fusing_on_error() {
2575 let block_size = 4096usize;
2576 let mut buf = vec![0u8; 3 * block_size];
2577
2578 let sb = format::SuperBlock {
2579 magic: LEU32::new(format::EROFS_MAGIC),
2580 checksum: LEU32::new(0),
2581 feature_compat: LEU32::new(0),
2582 block_size_bits: 12,
2583 sb_ext_slots: 0,
2584 root_nid: LEU16::new(0),
2585 inode_count: LEU64::new(1),
2586 epoch: LEU64::new(0),
2587 fixed_nsec: LEU32::new(0),
2588 blocks: LEU32::new(3),
2589 meta_block_addr: LEU32::new(1),
2590 xattr_block_addr: LEU32::new(2),
2591 uuid: [0; 16],
2592 volume_name: [0; 16],
2593 feature_incompat: LEU32::new(0),
2594 available_compr_algs: LEU16::new(0),
2595 extra_devices: LEU32::new(0),
2596 dirblkbits: 0,
2597 reserved: [0; 37],
2598 };
2599 buf[1024..1024 + 128].copy_from_slice(sb.as_bytes());
2600
2601 let inode = format::InodeCompact {
2602 format: LEU16::new(0),
2603 xattr_icount: LEU16::new(3),
2604 mode: LEU16::new(0o040755),
2605 link_count: LEU16::new(1),
2606 size: LEU32::new(0),
2607 reserved_1: [0; 4],
2608 i_u: [0; 4],
2609 ino: LEU32::new(0),
2610 uid: LEU16::new(0),
2611 gid: LEU16::new(0),
2612 reserved_2: [0; 4],
2613 };
2614 buf[4096..4096 + 32].copy_from_slice(inode.as_bytes());
2615
2616 let header = format::XattrInlineBodyHeader {
2617 name_filter: LEU32::new(0),
2618 shared_count: 2,
2619 reserved: [0; 7],
2620 };
2621 buf[4128..4128 + 12].copy_from_slice(header.as_bytes());
2622 buf[4140..4144].copy_from_slice(&0u32.to_le_bytes());
2623 buf[4144..4148].copy_from_slice(&1u32.to_le_bytes());
2624
2625 let invalid_xentry =
2628 format::XattrEntry { name_len: 6, name_index: 99, value_size: LEU16::new(10) };
2629 buf[8192..8196].copy_from_slice(invalid_xentry.as_bytes());
2630
2631 let reader = Arc::new(VecReader::new(buf));
2632 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
2633 let root_node = fs.root_node();
2634
2635 let mut iter = fs.iter_xattrs(&root_node).expect("failed to create iterator");
2636 assert!(iter.next().unwrap().is_err());
2637 assert!(iter.next().is_none());
2639 }
2640}