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