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