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
28#[derive(Debug, Error, Clone, PartialEq)]
30pub enum ErofsError {
31 #[error("Unsupported compression algorithms: 0x{:X}", _0)]
32 UnsupportedCompressionAlgs(u16),
33 #[error("Unsupported feature incompat flags: 0x{:X}. Only 0x{:X} is supported", _0, _1)]
34 UnsupportedFeatureIncompat(u32, u32),
35
36 #[error("Parsing error: {}", _0)]
37 Parse(#[from] ParsingError),
38 #[error("Reader error: {}", _0)]
39 ReadError(#[from] ReaderError),
40}
41
42#[cfg(target_os = "fuchsia")]
43impl ErofsError {
44 pub fn to_status(self) -> zx::Status {
45 match self {
46 Self::UnsupportedCompressionAlgs(_) => zx::Status::NOT_SUPPORTED,
47 Self::UnsupportedFeatureIncompat(_, _) => zx::Status::NOT_SUPPORTED,
48 Self::Parse(_) => zx::Status::IO_DATA_INTEGRITY,
49 Self::ReadError(_) => zx::Status::IO,
50 }
51 }
52}
53
54#[derive(Debug, Error, Clone, PartialEq)]
56pub enum ParsingError {
57 #[error("Invalid super block magic: 0x{:X}, should be 0x{:X}", _0, format::EROFS_MAGIC)]
58 InvalidSuperBlockMagic(u32),
59 #[error("Checksum mismatch: expected 0x{:X}, computed 0x{:X}", _0, _1)]
60 ChecksumMismatch(u32, u32),
61 #[error("Invalid block size bits: {}, must be between 9 and 12", _0)]
62 InvalidBlockSizeBits(u8),
63
64 #[error("Invalid inode data layout: 0x{:X}", _0)]
65 InvalidInodeDataLayout(u16),
66 #[error("Invalid directory entry")]
67 InvalidDirectoryEntry,
68 #[error("Invalid file type: {}", _0)]
69 InvalidFileType(u8),
70 #[error("Directory entry name was not valid utf8")]
71 InvalidDirectoryEntryName(#[source] std::str::Utf8Error),
72 #[error("Inline data layout missing inline data")]
73 InlineDataLayoutMissingInlineData,
74
75 #[error("Invalid root node")]
76 InvalidRootNode,
77 #[error("Node has an invalid U value for its data layout")]
78 InvalidUValue,
79 #[error("Invalid nid: {}", _0)]
80 InvalidNid(u64),
81 #[error("Integer overflow during calculation")]
82 Overflow,
83 #[error("Missing shared xattr area but inode has shared xattrs")]
84 MissingSharedXattrArea,
85 #[error("Xattr entry extends past the end of the inline xattr region")]
86 XattrEntryOutOfBounds,
87 #[error("Invalid xattr namespace index: {}", _0)]
88 InvalidXattrNamespace(u8),
89}
90
91#[derive(Debug, Clone, Copy)]
92enum InodeDataUnion {
93 DataBlkAddrPlain(u32),
94 DataBlkAddrInline(u32),
95}
96
97impl InodeDataUnion {
98 fn parse(data: [u8; 4], format: InodeFormat) -> Self {
99 match format.data_layout {
100 InodeDataLayout::FlatPlain => {
101 InodeDataUnion::DataBlkAddrPlain(u32::from_le_bytes(data))
102 }
103 InodeDataLayout::FlatInline => {
105 InodeDataUnion::DataBlkAddrInline(u32::from_le_bytes(data))
106 }
107 }
108 }
109}
110
111#[derive(Debug, Clone)]
112pub struct NodeInner {
113 inode_offset: u64,
114 format: InodeFormat,
115 mode: u16,
116 size: u64,
117 data_union: InodeDataUnion,
118 ino: u32,
119 nid: u64,
120 link_count: u32,
121 uid: u32,
122 gid: u32,
123 mtime_ns: u64,
124 xattr_icount: u16,
125}
126
127impl NodeInner {
128 fn is_dir(&self) -> bool {
129 self.mode & 0x4000 != 0
130 }
131
132 fn inode_offset(&self) -> u64 {
133 self.inode_offset
134 }
135
136 fn blkaddr(&self, block_size: u64) -> u64 {
140 match self.data_union {
141 InodeDataUnion::DataBlkAddrPlain(addr) => addr.into(),
142 InodeDataUnion::DataBlkAddrInline(addr) => {
143 debug_assert!(self.size / block_size > 0);
144 addr.into()
145 }
146 }
147 }
148
149 fn blkaddr_offset(&self, block_size: u64, offset: u64) -> Result<u64, ParsingError> {
152 self.blkaddr(block_size)
153 .checked_mul(block_size)
154 .ok_or(ParsingError::Overflow)?
155 .checked_add(offset)
156 .ok_or(ParsingError::Overflow)
157 }
158
159 fn metadata_size(&self) -> u64 {
160 match self.format.version {
161 InodeVersion::Compact => 32,
162 InodeVersion::Extended => 64,
163 }
164 }
165
166 fn inline_xattr_size(&self) -> u64 {
167 if self.xattr_icount == 0 { 0 } else { ((self.xattr_icount as u64 - 1) * 4) + 12 }
168 }
169
170 pub fn size(&self) -> u64 {
171 self.size
172 }
173 pub fn ino(&self) -> u32 {
174 self.ino
175 }
176 pub fn nid(&self) -> u64 {
177 self.nid
178 }
179 pub fn link_count(&self) -> u32 {
180 self.link_count
181 }
182 pub fn uid(&self) -> u32 {
183 self.uid
184 }
185 pub fn gid(&self) -> u32 {
186 self.gid
187 }
188 pub fn mtime_ns(&self) -> u64 {
189 self.mtime_ns
190 }
191 pub fn mode(&self) -> u16 {
192 self.mode
193 }
194}
195
196#[derive(Debug, Clone)]
198pub struct DirectoryNode(NodeInner);
199
200impl std::ops::Deref for DirectoryNode {
201 type Target = NodeInner;
202 fn deref(&self) -> &Self::Target {
203 &self.0
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
209pub enum FileType {
210 #[default]
211 Unknown = 0,
212 RegFile = 1,
213 Dir = 2,
214 ChrDev = 3,
215 BlkDev = 4,
216 Fifo = 5,
217 Sock = 6,
218 Symlink = 7,
219}
220
221impl TryFrom<u8> for FileType {
222 type Error = ParsingError;
223
224 fn try_from(value: u8) -> Result<Self, Self::Error> {
225 match value {
226 0 => Ok(FileType::Unknown),
227 1 => Ok(FileType::RegFile),
228 2 => Ok(FileType::Dir),
229 3 => Ok(FileType::ChrDev),
230 4 => Ok(FileType::BlkDev),
231 5 => Ok(FileType::Fifo),
232 6 => Ok(FileType::Sock),
233 7 => Ok(FileType::Symlink),
234 _ => Err(ParsingError::InvalidFileType(value)),
235 }
236 }
237}
238
239#[derive(Debug, Clone, Default)]
241pub struct DirectoryEntry {
242 pub nid: u64,
243 pub file_type: FileType,
244 pub name: String,
245}
246
247#[derive(Debug, Clone)]
249pub struct FileNode(NodeInner);
250
251impl std::ops::Deref for FileNode {
252 type Target = NodeInner;
253 fn deref(&self) -> &Self::Target {
254 &self.0
255 }
256}
257
258#[derive(Debug, Clone)]
260pub enum Node {
261 Directory(DirectoryNode),
262 File(FileNode),
263}
264
265impl Node {
266 fn new(inner: NodeInner) -> Self {
267 if inner.is_dir() {
268 Node::Directory(DirectoryNode(inner))
269 } else {
270 Node::File(FileNode(inner))
271 }
272 }
273
274 fn parse_compact(
275 nid: u64,
276 inode_offset: u64,
277 format: InodeFormat,
278 inode: format::InodeCompact,
279 build_time_ns: u64,
280 ) -> Result<Self, ParsingError> {
281 let data_union = InodeDataUnion::parse(inode.i_u, format);
282 Ok(Self::new(NodeInner {
283 inode_offset,
284 format,
285 mode: inode.mode.get(),
286 size: inode.size.get().into(),
287 data_union,
288 ino: inode.ino.get(),
289 nid,
290 link_count: inode.link_count.get().into(),
291 uid: inode.uid.get().into(),
292 gid: inode.gid.get().into(),
293 mtime_ns: build_time_ns,
294 xattr_icount: inode.xattr_icount.get(),
295 }))
296 }
297
298 fn parse_extended(
299 nid: u64,
300 inode_offset: u64,
301 format: InodeFormat,
302 inode: format::InodeExtended,
303 ) -> Result<Self, ParsingError> {
304 let data_union = InodeDataUnion::parse(inode.i_u, format);
305 let mtime_ns = inode
306 .mtime
307 .get()
308 .checked_mul(1_000_000_000)
309 .and_then(|t| t.checked_add(inode.mtime_ns.get().into()))
310 .ok_or(ParsingError::Overflow)?;
311 Ok(Self::new(NodeInner {
312 inode_offset,
313 format,
314 mode: inode.mode.get(),
315 size: inode.size.get(),
316 data_union,
317 ino: inode.ino.get(),
318 nid,
319 link_count: inode.link_count.get(),
320 uid: inode.uid.get(),
321 gid: inode.gid.get(),
322 mtime_ns,
323 xattr_icount: inode.xattr_icount.get(),
324 }))
325 }
326
327 fn from_nid(
328 nid: u64,
329 meta_addr: u64,
330 build_time_ns: u64,
331 reader: &dyn Reader,
332 ) -> Result<Self, ErofsError> {
333 let node_offset =
334 nid.checked_mul(format::INODE_SLOT_SIZE).ok_or(ParsingError::InvalidNid(nid))?;
335 let inode_offset =
336 meta_addr.checked_add(node_offset).ok_or(ParsingError::InvalidNid(nid))?;
337 let mut head = [0u8; 2];
339 reader.read(inode_offset, &mut head)?;
340 let format = InodeFormat::parse(u16::from_le_bytes(head))?;
341 let node = match format.version {
342 InodeVersion::Compact => Self::parse_compact(
343 nid,
344 inode_offset,
345 format,
346 reader.read_object(inode_offset)?,
347 build_time_ns,
348 )?,
349 InodeVersion::Extended => {
350 Self::parse_extended(nid, inode_offset, format, reader.read_object(inode_offset)?)?
351 }
352 };
353 Ok(node)
354 }
355}
356
357impl std::ops::Deref for Node {
358 type Target = NodeInner;
359 fn deref(&self) -> &Self::Target {
360 match self {
361 Node::Directory(d) => d,
362 Node::File(f) => f,
363 }
364 }
365}
366
367pub struct ErofsFilesystem {
369 reader: Arc<dyn Reader>,
370 block_size: u64,
371 meta_addr: u64,
372 xattr_addr: u64,
373 root_node: DirectoryNode,
374 total_bytes: u64,
375 total_inodes: u64,
376 build_time_ns: u64,
377}
378
379impl ErofsFilesystem {
380 pub fn new(reader: Arc<dyn Reader>) -> Result<Self, ErofsError> {
382 let super_block = Self::parse_superblock(&reader)?;
383 let block_size = 1u64 << super_block.block_size_bits;
384 let meta_block_addr = super_block.meta_block_addr.get().into();
385 let meta_addr = block_size.checked_mul(meta_block_addr).ok_or(ParsingError::Overflow)?;
386 let total_inodes = super_block.inode_count.get();
387 let build_time_ns = super_block
388 .epoch
389 .get()
390 .checked_mul(1_000_000_000)
391 .and_then(|t| t.checked_add(super_block.fixed_nsec.get().into()))
392 .ok_or(ParsingError::Overflow)?;
393 let total_bytes = (super_block.blocks.get() as u64) * block_size;
394 let xattr_block_addr = super_block.xattr_block_addr.get().into();
395 let xattr_addr = block_size.checked_mul(xattr_block_addr).ok_or(ParsingError::Overflow)?;
396 let root_nid = super_block.root_nid.get().into();
397 let root_node = match Node::from_nid(root_nid, meta_addr, build_time_ns, &reader)? {
398 Node::Directory(node) => node,
399 _ => return Err(ParsingError::InvalidRootNode.into()),
400 };
401 Ok(Self {
402 reader,
403 block_size,
404 meta_addr,
405 xattr_addr,
406 root_node,
407 total_bytes,
408 total_inodes,
409 build_time_ns,
410 })
411 }
412
413 fn parse_superblock(reader: &dyn Reader) -> Result<format::SuperBlock, ErofsError> {
414 let sb: format::SuperBlock = reader.read_object(format::SUPERBLOCK_OFFSET)?;
415 if sb.magic.get() != format::EROFS_MAGIC {
416 return Err(ParsingError::InvalidSuperBlockMagic(sb.magic.get()).into());
417 }
418 if sb.block_size_bits < 9 || sb.block_size_bits > 12 {
421 return Err(ParsingError::InvalidBlockSizeBits(sb.block_size_bits).into());
422 }
423 let feature_compat = FeatureCompat::from_bits_truncate(sb.feature_compat.get());
425 if feature_compat.contains(FeatureCompat::SB_CHKSUM) {
426 Self::check_superblock_checksum(reader, &sb)?;
427 }
428 if sb.feature_incompat.get() != 0 {
430 return Err(ErofsError::UnsupportedFeatureIncompat(sb.feature_incompat.get(), 0));
431 }
432 if sb.available_compr_algs.get() != 0 {
435 return Err(ErofsError::UnsupportedCompressionAlgs(sb.available_compr_algs.get()));
436 }
437 Ok(sb)
438 }
439
440 fn check_superblock_checksum(
441 reader: &dyn Reader,
442 sb: &format::SuperBlock,
443 ) -> Result<(), ErofsError> {
444 let block_size = 1usize << sb.block_size_bits;
445 let len = block_size - (format::SUPERBLOCK_OFFSET as usize) % block_size;
446 let mut buf = vec![0u8; len];
447 reader.read(format::SUPERBLOCK_OFFSET, &mut buf)?;
448
449 buf[4..8].copy_from_slice(&[0u8; 4]);
451
452 let crc = Crc::<u32>::new(&CRC_32_ISCSI);
453 let checksum = crc.checksum(&buf);
454 let checksum = !checksum;
457
458 if checksum != sb.checksum.get() {
459 Err(ParsingError::ChecksumMismatch(sb.checksum.get(), checksum).into())
460 } else {
461 Ok(())
462 }
463 }
464
465 pub fn block_size(&self) -> u64 {
467 self.block_size
468 }
469
470 pub fn node(&self, nid: u64) -> Result<Node, ErofsError> {
472 Node::from_nid(nid, self.meta_addr, self.build_time_ns, &self.reader)
473 }
474
475 pub fn root_node(&self) -> DirectoryNode {
477 self.root_node.clone()
478 }
479
480 pub fn total_bytes(&self) -> u64 {
481 self.total_bytes
482 }
483
484 pub fn total_inodes(&self) -> u64 {
485 self.total_inodes
486 }
487
488 pub fn read_file_range(
490 &self,
491 node: &FileNode,
492 offset: u64,
493 buf: &mut [u8],
494 ) -> Result<usize, ErofsError> {
495 self.read_node_range(&node.0, offset, buf)
496 }
497
498 fn read_node_range(
506 &self,
507 node: &NodeInner,
508 offset: u64,
509 buf: &mut [u8],
510 ) -> Result<usize, ErofsError> {
511 if offset >= node.size {
512 return Ok(0);
513 }
514 let read_len = std::cmp::min(buf.len() as u64, node.size - offset) as usize;
515 let buf = &mut buf[..read_len];
516 let block_size = self.block_size();
517
518 match node.format.data_layout {
519 InodeDataLayout::FlatPlain => {
520 let read_offset = node.blkaddr_offset(block_size, offset)?;
521 self.reader.read(read_offset, buf)?;
522 Ok(read_len)
523 }
524 InodeDataLayout::FlatInline => {
525 let full_blocks_len = (node.size / block_size) * block_size;
528 let mut bytes_read = 0;
529
530 if offset < full_blocks_len {
531 let current_read_len =
534 std::cmp::min(read_len as u64, full_blocks_len - offset) as usize;
535 let read_offset = node.blkaddr_offset(block_size, offset)?;
536 self.reader.read(read_offset, &mut buf[..current_read_len])?;
537 bytes_read += current_read_len;
538 }
539
540 if bytes_read < read_len {
541 let remaining_len = read_len - bytes_read;
542 let current_offset = offset + bytes_read as u64;
543 let inline_xattr_size = node.inline_xattr_size();
544 let inline_data_offset = node
545 .inode_offset()
546 .checked_add(node.metadata_size())
547 .ok_or(ParsingError::Overflow)?
548 .checked_add(inline_xattr_size)
549 .ok_or(ParsingError::Overflow)?;
550 let tail_offset = current_offset - full_blocks_len;
551 let tail_read_offset = inline_data_offset
552 .checked_add(tail_offset)
553 .ok_or(ParsingError::Overflow)?;
554 self.reader.read(tail_read_offset, &mut buf[bytes_read..])?;
555 bytes_read += remaining_len;
556 }
557
558 Ok(bytes_read)
559 }
560 }
561 }
562
563 pub fn read_directory(
576 &self,
577 node: &DirectoryNode,
578 mut entry_offset: usize,
579 entries: &mut [DirectoryEntry],
580 ) -> Result<usize, ErofsError> {
581 let block_size = self.block_size();
582 let block_size_usize: usize = block_size as usize;
583 let mut entries_filled = 0;
584 let mut current_entry_index = 0;
585 let mut block_data = vec![0u8; block_size_usize];
586
587 for block in 0.. {
588 let base_offset = block * block_size;
589 let bytes_read = self.read_node_range(&node.0, base_offset, &mut block_data)?;
590 if bytes_read < format::DIRENT_SIZE {
591 return Ok(entries_filled);
593 }
594 block_data[bytes_read..].fill(0);
595
596 let (dirent0, _) = zerocopy::Ref::<&[u8], format::Dirent>::from_prefix(&block_data)
598 .map_err(|_| ParsingError::InvalidDirectoryEntry)?;
599 let nameoff0 = dirent0.nameoff.get() as usize;
600 if nameoff0 < format::DIRENT_SIZE || nameoff0 >= block_size_usize {
601 return Err(ParsingError::InvalidDirectoryEntry.into());
602 }
603 let entry_count = nameoff0 / format::DIRENT_SIZE;
604
605 if current_entry_index + entry_count <= entry_offset {
607 current_entry_index += entry_count;
608 continue;
609 }
610
611 let dirents_raw = block_data
613 .get(..entry_count * format::DIRENT_SIZE)
614 .ok_or(ParsingError::InvalidDirectoryEntry)?;
615 let dirents: &[format::Dirent] =
616 &*zerocopy::Ref::<&[u8], [format::Dirent]>::from_bytes(dirents_raw)
617 .map_err(|_| ParsingError::InvalidDirectoryEntry)?;
618
619 let block_entry_offset = entry_offset - current_entry_index;
620 let space = entries.len() - entries_filled;
621 let block_entry_end = std::cmp::min(
622 entry_count,
623 block_entry_offset.checked_add(space).ok_or(ParsingError::Overflow)?,
624 );
625
626 for i in block_entry_offset..block_entry_end {
627 let last_entry = i + 1 == entry_count;
628 let nameoff = dirents[i].nameoff.get() as usize;
629
630 let name_bytes = if last_entry {
631 let name_data =
634 block_data.get(nameoff..).ok_or(ParsingError::InvalidDirectoryEntry)?;
635 name_data.split(|&x| x == 0).next().unwrap()
636 } else {
637 let nameoff_next = dirents[i + 1].nameoff.get() as usize;
638 block_data
639 .get(nameoff..nameoff_next)
640 .ok_or(ParsingError::InvalidDirectoryEntry)?
641 };
642
643 let name = std::str::from_utf8(name_bytes)
644 .map_err(|e| ParsingError::InvalidDirectoryEntryName(e))?
645 .to_string();
646 entries[entries_filled] = DirectoryEntry {
647 nid: dirents[i].nid.get(),
648 file_type: dirents[i].file_type.try_into()?,
649 name,
650 };
651 entries_filled += 1;
652 if entries_filled == entries.len() {
653 return Ok(entries_filled);
654 }
655 }
656
657 current_entry_index =
658 current_entry_index.checked_add(entry_count).ok_or(ParsingError::Overflow)?;
659 entry_offset = current_entry_index;
660 }
661
662 Ok(entries_filled)
663 }
664
665 pub fn lookup(&self, dir: &DirectoryNode, name: &str) -> Result<Option<Node>, ErofsError> {
667 let mut entry_offset = 0;
668 let mut buffer = vec![DirectoryEntry::default(); 16];
669
670 loop {
671 let filled = self.read_directory(dir, entry_offset, &mut buffer)?;
672 for i in 0..filled {
673 if buffer[i].name == name {
674 let node = self.node(buffer[i].nid)?;
675 return Ok(Some(node));
676 }
677 }
678 if filled < buffer.len() {
679 break;
680 }
681 entry_offset += filled;
682 }
683
684 Ok(None)
685 }
686
687 pub fn iter_xattrs<'a>(&'a self, node: &NodeInner) -> Result<XattrIterator<'a>, ErofsError> {
689 if node.xattr_icount == 0 {
690 return Ok(XattrIterator {
691 reader: self.reader.as_ref(),
692 xattr_addr: self.xattr_addr,
693 shared_ids: Vec::new(),
694 inline_offset: 0,
695 inline_end: 0,
696 });
697 }
698 let xattr_metadata_size = node.inline_xattr_size();
699 let xattr_metadata_start =
700 node.inode_offset().checked_add(node.metadata_size()).ok_or(ParsingError::Overflow)?;
701
702 let header: format::XattrInlineBodyHeader =
704 self.reader.read_object(xattr_metadata_start)?;
705 let shared_count = header.shared_count as usize;
706
707 let shared_ids_size = shared_count as u64 * 4;
708 let inline_entries_start = xattr_metadata_start + 12 + shared_ids_size;
709 let inline_end = xattr_metadata_start + xattr_metadata_size;
710
711 if inline_entries_start > inline_end {
712 return Err(ParsingError::XattrEntryOutOfBounds.into());
713 }
714
715 let shared_ids = if shared_count > 0 {
716 let mut ids = vec![LEU32::ZERO; shared_count];
717 self.reader.read(xattr_metadata_start + 12, ids.as_mut_bytes())?;
718 ids
719 } else {
720 Vec::new()
721 };
722
723 Ok(XattrIterator {
724 reader: self.reader.as_ref(),
725 xattr_addr: self.xattr_addr,
726 shared_ids,
727 inline_offset: inline_entries_start,
728 inline_end,
729 })
730 }
731
732 pub fn list_xattrs(&self, node: &NodeInner) -> Result<Vec<Vec<u8>>, ErofsError> {
734 let mut names = Vec::new();
735 for entry in self.iter_xattrs(node)? {
736 let entry = entry?;
737 names.push(entry.read_name(self.reader.as_ref())?);
738 }
739 Ok(names)
740 }
741
742 pub fn get_xattr(&self, node: &NodeInner, name: &[u8]) -> Result<Option<Vec<u8>>, ErofsError> {
744 for entry in self.iter_xattrs(node)? {
745 let entry = entry?;
746 if entry.matches_name(self.reader.as_ref(), name)? {
747 return Ok(Some(entry.read_value(self.reader.as_ref())?));
748 }
749 }
750 Ok(None)
751 }
752}
753
754pub struct XattrIterator<'a> {
756 reader: &'a dyn Reader,
757 xattr_addr: u64,
758 shared_ids: Vec<LEU32>,
759 inline_offset: u64,
760 inline_end: u64,
761}
762
763impl XattrIterator<'_> {
764 fn next_inner(&mut self) -> Result<Option<XattrEntryHeader>, ErofsError> {
765 if let Some(shared_id) = self.shared_ids.pop() {
766 let shared_entry_offset = self.xattr_addr + (shared_id.get() as u64 * 4);
767 if self.shared_ids.is_empty() {
768 self.shared_ids = Vec::new();
769 }
770 return Ok(Some(XattrEntryHeader::parse(self.reader, shared_entry_offset)?));
771 }
772
773 if self.inline_offset < self.inline_end {
774 if self.inline_offset + 4 > self.inline_end {
775 return Err(ParsingError::XattrEntryOutOfBounds.into());
776 }
777
778 let header = XattrEntryHeader::parse(self.reader, self.inline_offset)?;
779 let next_offset = self
780 .inline_offset
781 .checked_add(header.entry_aligned_size)
782 .ok_or(ParsingError::Overflow)?;
783 if next_offset > self.inline_end {
784 return Err(ParsingError::XattrEntryOutOfBounds.into());
785 }
786 self.inline_offset = next_offset;
787 Ok(Some(header))
788 } else {
789 Ok(None)
790 }
791 }
792}
793
794impl Iterator for XattrIterator<'_> {
795 type Item = Result<XattrEntryHeader, ErofsError>;
796
797 fn next(&mut self) -> Option<Self::Item> {
798 match self.next_inner() {
799 Err(e) => {
804 self.shared_ids = Vec::new();
805 self.inline_offset = self.inline_end;
806 Some(Err(e))
807 }
808 Ok(None) => None,
809 Ok(Some(x)) => Some(Ok(x)),
810 }
811 }
812}
813
814#[derive(Debug, Clone, Copy)]
816pub struct XattrEntryHeader {
817 pub offset: u64,
818 pub prefix: &'static [u8],
819 pub name_index: u8,
820 pub name_len: usize,
821 pub value_size: usize,
822 pub entry_aligned_size: u64,
823}
824
825impl XattrEntryHeader {
826 pub fn parse(reader: &dyn Reader, offset: u64) -> Result<Self, ErofsError> {
828 let entry: format::XattrEntry = reader.read_object(offset)?;
829 let prefix = Self::get_xattr_prefix(entry.name_index)?;
830 let name_len = entry.name_len as usize;
831 let value_size = entry.value_size.get() as usize;
832
833 let entry_aligned_size = 4usize
834 .checked_add(name_len)
835 .and_then(|s| s.checked_add(value_size))
836 .and_then(|s| s.checked_next_multiple_of(4))
837 .ok_or(ParsingError::Overflow)? as u64;
838
839 Ok(Self {
840 offset,
841 prefix,
842 name_index: entry.name_index,
843 name_len,
844 value_size,
845 entry_aligned_size,
846 })
847 }
848
849 pub fn matches_name(&self, reader: &dyn Reader, name: &[u8]) -> Result<bool, ReaderError> {
851 let Some(suffix) = name.strip_prefix(self.prefix) else {
852 return Ok(false);
853 };
854 if suffix.len() != self.name_len {
855 return Ok(false);
856 }
857 if self.name_len == 0 {
858 return Ok(true);
860 }
861 let mut buf = vec![0u8; self.name_len];
862 reader.read(self.offset + 4, &mut buf)?;
863 Ok(buf == suffix)
864 }
865
866 pub fn read_name(&self, reader: &dyn Reader) -> Result<Vec<u8>, ReaderError> {
868 let mut name_bytes = Vec::with_capacity(self.prefix.len() + self.name_len);
869 name_bytes.extend_from_slice(self.prefix);
870 if self.name_len > 0 {
871 name_bytes.resize(self.prefix.len() + self.name_len, 0);
872 reader.read(self.offset + 4, &mut name_bytes[self.prefix.len()..])?;
873 }
874 Ok(name_bytes)
875 }
876
877 pub fn read_value(&self, reader: &dyn Reader) -> Result<Vec<u8>, ReaderError> {
879 let mut value_bytes = vec![0u8; self.value_size];
880 reader.read(self.offset + 4 + self.name_len as u64, &mut value_bytes)?;
881 Ok(value_bytes)
882 }
883
884 pub fn read_payload(&self, reader: &dyn Reader) -> Result<(Vec<u8>, Vec<u8>), ReaderError> {
886 let name = self.read_name(reader)?;
887 let value = self.read_value(reader)?;
888 Ok((name, value))
889 }
890
891 fn get_xattr_prefix(index: u8) -> Result<&'static [u8], ParsingError> {
892 match index {
893 1 => Ok(b"user."),
894 2 => Ok(b"system.posix_acl_access"),
895 3 => Ok(b"system.posix_acl_default"),
896 4 => Ok(b"trusted."),
897 6 => Ok(b"security."),
898 _ => Err(ParsingError::InvalidXattrNamespace(index)),
899 }
900 }
901}
902
903#[derive(Debug, Clone, Copy, PartialEq, Eq)]
906pub enum InodeVersion {
907 Compact,
908 Extended,
909}
910
911#[derive(Debug, Clone, Copy, PartialEq, Eq)]
913pub enum InodeDataLayout {
914 FlatPlain,
917 FlatInline,
923}
924
925#[derive(Debug, Clone, Copy)]
927pub struct InodeFormat {
928 pub version: InodeVersion,
929 pub data_layout: InodeDataLayout,
930}
931
932impl InodeFormat {
933 pub fn parse(format: u16) -> Result<Self, ParsingError> {
935 let version =
936 if format & 0x1 == 0 { InodeVersion::Compact } else { InodeVersion::Extended };
937 let data_layout_raw = (format >> 1) & 0x7;
938 let data_layout = match data_layout_raw {
939 0 => InodeDataLayout::FlatPlain,
940 2 => InodeDataLayout::FlatInline,
941 _ => return Err(ParsingError::InvalidInodeDataLayout(data_layout_raw)),
942 };
943 Ok(Self { version, data_layout })
944 }
945}
946
947#[cfg(test)]
948mod tests {
949 use super::*;
950 use crate::readers::VecReader;
951 use std::fs;
952 use test_case::test_case;
953 use zerocopy::byteorder::little_endian::{U16 as LEU16, U32 as LEU32, U64 as LEU64};
954
955 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
956 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
957 #[fuchsia::test]
958 fn test_parse_superblock(path: &str) {
959 let runfiles = fs::read(path).expect("failed to read test file");
960 let reader = Arc::new(VecReader::new(runfiles.clone()));
961 let _fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
963
964 let mut mutated_runfiles = runfiles.clone();
967 mutated_runfiles[1088] ^= 0xFF;
968
969 let reader = Arc::new(VecReader::new(mutated_runfiles));
970 let fs = ErofsFilesystem::new(reader);
971 assert!(fs.is_err());
972 match fs.err().unwrap() {
973 ErofsError::Parse(ParsingError::ChecksumMismatch(_, _)) => {}
974 e => panic!("Expected ChecksumMismatch error, got {:?}", e),
975 }
976 }
977
978 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
979 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
980 #[fuchsia::test]
981 fn test_list_dir(path: &str) {
982 let runfiles = fs::read(path).expect("failed to read test file");
983 let reader = Arc::new(VecReader::new(runfiles));
984 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
985 let root_node = fs.root_node();
986
987 let mut buf = vec![DirectoryEntry::default(); 16];
988 let filled = fs.read_directory(&root_node, 0, &mut buf).expect("failed to read directory");
989
990 let names: Vec<String> = buf[..filled].iter().map(|e| e.name.clone()).collect();
991 assert_eq!(names, vec![".", "..", "file1", "large_dir", "photosynthesis", "quantum"]);
992 }
993
994 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
995 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
996 #[fuchsia::test]
997 fn test_overflow_nid(path: &str) {
998 let runfiles = fs::read(path).expect("failed to read test file");
999 let reader = Arc::new(VecReader::new(runfiles));
1000 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
1001 let result = fs.node(u64::MAX);
1002 assert!(result.is_err());
1003 assert_eq!(result.unwrap_err(), ErofsError::Parse(ParsingError::InvalidNid(u64::MAX)));
1004 }
1005
1006 #[test_case("/pkg/data/simple.erofs", "file1" ; "4096 block size file1")]
1007 #[test_case("/pkg/data/simple_512.erofs", "file1" ; "512 block size file1")]
1008 #[test_case("/pkg/data/simple.erofs", "photosynthesis" ; "4096 block size photosynthesis")]
1009 #[test_case("/pkg/data/simple_512.erofs", "photosynthesis" ; "512 block size photosynthesis")]
1010 #[fuchsia::test]
1011 fn test_read_file_range(path: &str, name: &str) {
1012 let runfiles = fs::read(path).expect("failed to read test file");
1013 let reader = Arc::new(VecReader::new(runfiles));
1014 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
1015 let root_node = fs.root_node();
1016
1017 let node = fs.lookup(&root_node, name).expect("failed to lookup").expect("file not found");
1018 let file_node = match node {
1019 Node::File(f) => f,
1020 _ => panic!("Expected file node"),
1021 };
1022
1023 let size = file_node.size() as usize;
1024 let mut buf = vec![0u8; size];
1025 let bytes_read = fs.read_file_range(&file_node, 0, &mut buf).expect("failed to read");
1026 assert_eq!(bytes_read, size);
1027 if name == "file1" {
1028 assert_eq!(&buf[..14], b"this is a file");
1029 }
1030
1031 let mut buf = vec![0u8; 5];
1033 let bytes_read = fs.read_file_range(&file_node, 5, &mut buf).expect("failed to read");
1034 assert_eq!(bytes_read, 5);
1035 if name == "file1" {
1036 assert_eq!(&buf, b"is a ");
1037 }
1038
1039 let mut buf = vec![0u8; 100];
1041 let bytes_read =
1042 fs.read_file_range(&file_node, (size - 5) as u64, &mut buf).expect("failed to read");
1043 assert_eq!(bytes_read, 5);
1044 if name == "file1" {
1045 assert_eq!(&buf[..5], b"file\n");
1046 }
1047
1048 let mut buf = vec![0u8; 100];
1050 let bytes_read =
1051 fs.read_file_range(&file_node, size as u64, &mut buf).expect("failed to read");
1052 assert_eq!(bytes_read, 0);
1053 }
1054
1055 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
1056 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
1057 #[fuchsia::test]
1058 fn test_read_directory_pagination(path: &str) {
1059 let runfiles = fs::read(path).expect("failed to read test file");
1060 let reader = Arc::new(VecReader::new(runfiles));
1061 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
1062 let root_node = fs.root_node();
1063
1064 let expected_names = vec![".", "..", "file1", "large_dir", "photosynthesis", "quantum"];
1065
1066 let mut buf = vec![DirectoryEntry::default(); 2];
1068
1069 let filled = fs.read_directory(&root_node, 0, &mut buf).expect("failed to read dir");
1071 assert_eq!(filled, 2);
1072 assert_eq!(buf[0].name, expected_names[0]);
1073 assert_eq!(buf[1].name, expected_names[1]);
1074
1075 let filled = fs.read_directory(&root_node, 2, &mut buf).expect("failed to read dir");
1077 assert_eq!(filled, 2);
1078 assert_eq!(buf[0].name, expected_names[2]);
1079 assert_eq!(buf[1].name, expected_names[3]);
1080
1081 let filled = fs.read_directory(&root_node, 5, &mut buf).expect("failed to read dir");
1083 assert_eq!(filled, 1);
1084 assert_eq!(buf[0].name, expected_names[5]);
1085
1086 let filled = fs.read_directory(&root_node, 6, &mut buf).expect("failed to read dir");
1088 assert_eq!(filled, 0);
1089
1090 let mut buf1 = vec![DirectoryEntry::default(); 1];
1092 for i in 0..expected_names.len() {
1093 let filled = fs.read_directory(&root_node, i, &mut buf1).expect("failed to read dir");
1094 assert_eq!(filled, 1);
1095 assert_eq!(buf1[0].name, expected_names[i]);
1096 }
1097 let filled = fs
1098 .read_directory(&root_node, expected_names.len(), &mut buf1)
1099 .expect("failed to read dir");
1100 assert_eq!(filled, 0);
1101 }
1102
1103 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
1104 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
1105 #[fuchsia::test]
1106 fn test_read_directory_large_dir(path: &str) {
1107 let runfiles = fs::read(path).expect("failed to read test file");
1110 let reader = Arc::new(VecReader::new(runfiles));
1111 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
1112 let root_node = fs.root_node();
1113
1114 let large_dir_node = fs
1115 .lookup(&root_node, "large_dir")
1116 .expect("failed to look up large_dir")
1117 .expect("large_dir not found");
1118
1119 let large_dir = match large_dir_node {
1120 Node::Directory(d) => d,
1121 _ => panic!("Expected directory node"),
1122 };
1123
1124 let mut entry_offset = 2;
1126 let mut buffer = vec![DirectoryEntry::default(); 16];
1127 loop {
1128 let filled = fs.read_directory(&large_dir, entry_offset, &mut buffer).unwrap();
1129 for i in 0..filled {
1130 assert_eq!(buffer[i].name[..12], format!("file_number_"));
1132 }
1133 if filled < buffer.len() {
1134 break;
1135 }
1136 entry_offset += filled;
1137 }
1138 }
1139
1140 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
1141 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
1142 #[fuchsia::test]
1143 fn test_filesystem_metadata(path: &str) {
1144 let runfiles = fs::read(path).expect("failed to read test file");
1145 let reader = Arc::new(VecReader::new(runfiles));
1146 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
1147
1148 assert!(fs.total_bytes() > 0);
1149 assert!(fs.total_inodes() > 0);
1150 }
1151
1152 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
1153 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
1154 #[fuchsia::test]
1155 fn test_node_metadata(path: &str) {
1156 let runfiles = fs::read(path).expect("failed to read test file");
1157 let reader = Arc::new(VecReader::new(runfiles));
1158 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
1159 let root_node = fs.root_node();
1160
1161 assert!(root_node.link_count() >= 2);
1162 assert!(root_node.mtime_ns() > 0);
1163
1164 let file1_node = fs.lookup(&root_node, "file1").unwrap().unwrap();
1165 assert_eq!(file1_node.link_count(), 1);
1166 assert!(file1_node.mtime_ns() > 0);
1167 }
1168
1169 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
1170 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
1171 #[fuchsia::test]
1172 fn test_xattrs(path: &str) {
1173 let runfiles = fs::read(path).expect("failed to read test file");
1174 let reader = Arc::new(VecReader::new(runfiles));
1175 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
1176 let root_node = fs.root_node();
1177
1178 let file1_node = fs.lookup(&root_node, "file1").unwrap().unwrap();
1180
1181 let xattr_names = fs.list_xattrs(&file1_node).unwrap();
1182 assert!(xattr_names.contains(&b"user.flavor".to_vec()));
1184 assert!(xattr_names.contains(&b"user.security".to_vec()));
1185 assert!(xattr_names.contains(&b"user.shared".to_vec()));
1186 assert_eq!(xattr_names.len(), 3);
1187
1188 let flavor_val = fs.get_xattr(&file1_node, b"user.flavor").unwrap().unwrap();
1189 assert_eq!(flavor_val, b"vanilla");
1190
1191 let security_val = fs.get_xattr(&file1_node, b"user.security").unwrap().unwrap();
1192 assert_eq!(security_val, b"high");
1193
1194 let shared_val = fs.get_xattr(&file1_node, b"user.shared").unwrap().unwrap();
1195 assert_eq!(shared_val, b"same_value");
1196
1197 let photo_node = fs.lookup(&root_node, "photosynthesis").unwrap().unwrap();
1199
1200 let photo_xattrs = fs.list_xattrs(&photo_node).unwrap();
1201 assert_eq!(photo_xattrs, vec![b"user.shared".to_vec()]);
1202
1203 let photo_shared_val = fs.get_xattr(&photo_node, b"user.shared").unwrap().unwrap();
1204 assert_eq!(photo_shared_val, b"same_value");
1205
1206 let file_node = match photo_node {
1208 Node::File(f) => f,
1209 _ => panic!("Expected file node"),
1210 };
1211 let size = file_node.size() as usize;
1212 let mut buf = vec![0u8; size];
1213 let bytes_read = fs.read_file_range(&file_node, 0, &mut buf).expect("failed to read");
1214 assert_eq!(bytes_read, size);
1215 assert!(size > 0);
1216
1217 let val = fs.get_xattr(&file1_node, b"user.non_existent").unwrap();
1219 assert_eq!(val, None);
1220 }
1221
1222 #[fuchsia::test]
1226 fn test_shared_xattr_parsing() {
1227 let block_size = 4096usize;
1228 let mut buf = vec![0u8; 3 * block_size];
1229
1230 let sb = format::SuperBlock {
1232 magic: LEU32::new(format::EROFS_MAGIC),
1233 checksum: LEU32::new(0),
1234 feature_compat: LEU32::new(0),
1235 block_size_bits: 12,
1236 sb_ext_slots: 0,
1237 root_nid: LEU16::new(0),
1238 inode_count: LEU64::new(1),
1239 epoch: LEU64::new(0),
1240 fixed_nsec: LEU32::new(0),
1241 blocks: LEU32::new(3),
1242 meta_block_addr: LEU32::new(1),
1243 xattr_block_addr: LEU32::new(0),
1244 uuid: [0; 16],
1245 volume_name: [0; 16],
1246 feature_incompat: LEU32::new(0),
1247 available_compr_algs: LEU16::new(0),
1248 extra_devices: LEU32::new(0),
1249 dirblkbits: 0,
1250 reserved: [0; 37],
1251 };
1252 buf[1024..1024 + 128].copy_from_slice(sb.as_bytes());
1253
1254 let inode = format::InodeCompact {
1256 format: LEU16::new(0),
1257 xattr_icount: LEU16::new(2),
1258 mode: LEU16::new(0o040755),
1259 link_count: LEU16::new(1),
1260 size: LEU32::new(0),
1261 reserved_1: [0; 4],
1262 i_u: [0; 4],
1263 ino: LEU32::new(0),
1264 uid: LEU16::new(0),
1265 gid: LEU16::new(0),
1266 reserved_2: [0; 4],
1267 };
1268 buf[4096..4096 + 32].copy_from_slice(inode.as_bytes());
1269
1270 let header = format::XattrInlineBodyHeader {
1272 name_filter: LEU32::new(0),
1273 shared_count: 1,
1274 reserved: [0; 7],
1275 };
1276 buf[4128..4128 + 12].copy_from_slice(header.as_bytes());
1277 buf[4140..4144].copy_from_slice(&512u32.to_le_bytes());
1279
1280 let xentry = format::XattrEntry {
1282 name_len: 6,
1283 name_index: 1, value_size: LEU16::new(10),
1285 };
1286 buf[2048..2052].copy_from_slice(xentry.as_bytes());
1287 buf[2052..2058].copy_from_slice(b"shared");
1288 buf[2058..2068].copy_from_slice(b"same_value");
1289
1290 let reader = Arc::new(VecReader::new(buf));
1291 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
1292 let root_node = fs.root_node();
1293
1294 let xattr_names = fs.list_xattrs(&root_node).expect("failed to list xattrs");
1295 assert_eq!(xattr_names, vec![b"user.shared".to_vec()]);
1296
1297 let shared_val =
1298 fs.get_xattr(&root_node, b"user.shared").expect("failed to get xattr").unwrap();
1299 assert_eq!(shared_val, b"same_value");
1300 }
1301
1302 #[fuchsia::test]
1303 fn test_xattr_iterator_fusing_on_error() {
1304 let block_size = 4096usize;
1305 let mut buf = vec![0u8; 3 * block_size];
1306
1307 let sb = format::SuperBlock {
1308 magic: LEU32::new(format::EROFS_MAGIC),
1309 checksum: LEU32::new(0),
1310 feature_compat: LEU32::new(0),
1311 block_size_bits: 12,
1312 sb_ext_slots: 0,
1313 root_nid: LEU16::new(0),
1314 inode_count: LEU64::new(1),
1315 epoch: LEU64::new(0),
1316 fixed_nsec: LEU32::new(0),
1317 blocks: LEU32::new(3),
1318 meta_block_addr: LEU32::new(1),
1319 xattr_block_addr: LEU32::new(2),
1320 uuid: [0; 16],
1321 volume_name: [0; 16],
1322 feature_incompat: LEU32::new(0),
1323 available_compr_algs: LEU16::new(0),
1324 extra_devices: LEU32::new(0),
1325 dirblkbits: 0,
1326 reserved: [0; 37],
1327 };
1328 buf[1024..1024 + 128].copy_from_slice(sb.as_bytes());
1329
1330 let inode = format::InodeCompact {
1331 format: LEU16::new(0),
1332 xattr_icount: LEU16::new(3),
1333 mode: LEU16::new(0o040755),
1334 link_count: LEU16::new(1),
1335 size: LEU32::new(0),
1336 reserved_1: [0; 4],
1337 i_u: [0; 4],
1338 ino: LEU32::new(0),
1339 uid: LEU16::new(0),
1340 gid: LEU16::new(0),
1341 reserved_2: [0; 4],
1342 };
1343 buf[4096..4096 + 32].copy_from_slice(inode.as_bytes());
1344
1345 let header = format::XattrInlineBodyHeader {
1346 name_filter: LEU32::new(0),
1347 shared_count: 2,
1348 reserved: [0; 7],
1349 };
1350 buf[4128..4128 + 12].copy_from_slice(header.as_bytes());
1351 buf[4140..4144].copy_from_slice(&0u32.to_le_bytes());
1352 buf[4144..4148].copy_from_slice(&1u32.to_le_bytes());
1353
1354 let invalid_xentry =
1357 format::XattrEntry { name_len: 6, name_index: 99, value_size: LEU16::new(10) };
1358 buf[8192..8196].copy_from_slice(invalid_xentry.as_bytes());
1359
1360 let reader = Arc::new(VecReader::new(buf));
1361 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
1362 let root_node = fs.root_node();
1363
1364 let mut iter = fs.iter_xattrs(&root_node).expect("failed to create iterator");
1365 assert!(iter.next().unwrap().is_err());
1366 assert!(iter.next().is_none());
1368 }
1369}