1use bitflags::bitflags;
8use crc::{CRC_32_ISCSI, Crc};
9use std::sync::Arc;
10use thiserror::Error;
11
12pub mod readers;
13use readers::{Reader, ReaderError, ReaderExt};
14
15pub mod format;
16
17bitflags! {
18 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19 pub struct FeatureCompat: u32 {
20 const SB_CHKSUM = 0x00000001;
23 }
24}
25
26#[derive(Debug, Error, Clone, PartialEq)]
28pub enum ErofsError {
29 #[error("Unsupported compression algorithms: 0x{:X}", _0)]
30 UnsupportedCompressionAlgs(u16),
31 #[error("Unsupported feature incompat flags: 0x{:X}. Only 0x{:X} is supported", _0, _1)]
32 UnsupportedFeatureIncompat(u32, u32),
33
34 #[error("Parsing error: {}", _0)]
35 Parse(#[from] ParsingError),
36 #[error("Reader error: {}", _0)]
37 ReadError(#[from] ReaderError),
38}
39
40#[cfg(target_os = "fuchsia")]
41impl ErofsError {
42 pub fn to_status(self) -> zx::Status {
43 match self {
44 Self::UnsupportedCompressionAlgs(_) => zx::Status::NOT_SUPPORTED,
45 Self::UnsupportedFeatureIncompat(_, _) => zx::Status::NOT_SUPPORTED,
46 Self::Parse(_) => zx::Status::IO_DATA_INTEGRITY,
47 Self::ReadError(_) => zx::Status::IO,
48 }
49 }
50}
51
52#[derive(Debug, Error, Clone, PartialEq)]
54pub enum ParsingError {
55 #[error("Invalid super block magic: 0x{:X}, should be 0x{:X}", _0, format::EROFS_MAGIC)]
56 InvalidSuperBlockMagic(u32),
57 #[error("Checksum mismatch: expected 0x{:X}, computed 0x{:X}", _0, _1)]
58 ChecksumMismatch(u32, u32),
59 #[error("Invalid block size bits: {}, must be between 9 and 12", _0)]
60 InvalidBlockSizeBits(u8),
61
62 #[error("Invalid inode data layout: 0x{:X}", _0)]
63 InvalidInodeDataLayout(u16),
64 #[error("Invalid directory entry")]
65 InvalidDirectoryEntry,
66 #[error("Invalid file type: {}", _0)]
67 InvalidFileType(u8),
68 #[error("Directory entry name was not valid utf8")]
69 InvalidDirectoryEntryName(#[source] std::str::Utf8Error),
70 #[error("Inline data layout missing inline data")]
71 InlineDataLayoutMissingInlineData,
72
73 #[error("Invalid root node")]
74 InvalidRootNode,
75 #[error("Node has an invalid U value for its data layout")]
76 InvalidUValue,
77 #[error("Invalid nid: {}", _0)]
78 InvalidNid(u64),
79 #[error("Integer overflow during calculation")]
80 Overflow,
81}
82
83#[derive(Debug, Clone, Copy)]
84enum InodeDataUnion {
85 DataBlkAddrPlain(u32),
86 DataBlkAddrInline(u32),
87}
88
89impl InodeDataUnion {
90 fn parse(data: [u8; 4], format: InodeFormat) -> Self {
91 match format.data_layout {
92 InodeDataLayout::FlatPlain => {
93 InodeDataUnion::DataBlkAddrPlain(u32::from_le_bytes(data))
94 }
95 InodeDataLayout::FlatInline => {
97 InodeDataUnion::DataBlkAddrInline(u32::from_le_bytes(data))
98 }
99 }
100 }
101}
102
103#[derive(Debug, Clone)]
104pub struct NodeInner {
105 inode_offset: u64,
106 format: InodeFormat,
107 mode: u16,
108 size: u64,
109 data_union: InodeDataUnion,
110 ino: u32,
111 nid: u64,
112 link_count: u32,
113 uid: u32,
114 gid: u32,
115 mtime_ns: u64,
116}
117
118impl NodeInner {
119 fn is_dir(&self) -> bool {
120 self.mode & 0x4000 != 0
121 }
122
123 fn inode_offset(&self) -> u64 {
124 self.inode_offset
125 }
126
127 fn blkaddr(&self, block_size: u64) -> u64 {
131 match self.data_union {
132 InodeDataUnion::DataBlkAddrPlain(addr) => addr.into(),
133 InodeDataUnion::DataBlkAddrInline(addr) => {
134 debug_assert!(self.size / block_size > 0);
135 addr.into()
136 }
137 }
138 }
139
140 fn blkaddr_offset(&self, block_size: u64, offset: u64) -> Result<u64, ParsingError> {
143 self.blkaddr(block_size)
144 .checked_mul(block_size)
145 .ok_or(ParsingError::Overflow)?
146 .checked_add(offset)
147 .ok_or(ParsingError::Overflow)
148 }
149
150 fn metadata_size(&self) -> u64 {
151 match self.format.version {
152 InodeVersion::Compact => 32,
153 InodeVersion::Extended => 64,
154 }
155 }
156
157 pub fn size(&self) -> u64 {
158 self.size
159 }
160 pub fn ino(&self) -> u32 {
161 self.ino
162 }
163 pub fn nid(&self) -> u64 {
164 self.nid
165 }
166 pub fn link_count(&self) -> u32 {
167 self.link_count
168 }
169 pub fn uid(&self) -> u32 {
170 self.uid
171 }
172 pub fn gid(&self) -> u32 {
173 self.gid
174 }
175 pub fn mtime_ns(&self) -> u64 {
176 self.mtime_ns
177 }
178 pub fn mode(&self) -> u16 {
179 self.mode
180 }
181}
182
183#[derive(Debug, Clone)]
185pub struct DirectoryNode(NodeInner);
186
187impl std::ops::Deref for DirectoryNode {
188 type Target = NodeInner;
189 fn deref(&self) -> &Self::Target {
190 &self.0
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
196pub enum FileType {
197 #[default]
198 Unknown = 0,
199 RegFile = 1,
200 Dir = 2,
201 ChrDev = 3,
202 BlkDev = 4,
203 Fifo = 5,
204 Sock = 6,
205 Symlink = 7,
206}
207
208impl TryFrom<u8> for FileType {
209 type Error = ParsingError;
210
211 fn try_from(value: u8) -> Result<Self, Self::Error> {
212 match value {
213 0 => Ok(FileType::Unknown),
214 1 => Ok(FileType::RegFile),
215 2 => Ok(FileType::Dir),
216 3 => Ok(FileType::ChrDev),
217 4 => Ok(FileType::BlkDev),
218 5 => Ok(FileType::Fifo),
219 6 => Ok(FileType::Sock),
220 7 => Ok(FileType::Symlink),
221 _ => Err(ParsingError::InvalidFileType(value)),
222 }
223 }
224}
225
226#[derive(Debug, Clone, Default)]
228pub struct DirectoryEntry {
229 pub nid: u64,
230 pub file_type: FileType,
231 pub name: String,
232}
233
234#[derive(Debug, Clone)]
236pub struct FileNode(NodeInner);
237
238impl std::ops::Deref for FileNode {
239 type Target = NodeInner;
240 fn deref(&self) -> &Self::Target {
241 &self.0
242 }
243}
244
245#[derive(Debug, Clone)]
247pub enum Node {
248 Directory(DirectoryNode),
249 File(FileNode),
250}
251
252impl Node {
253 fn new(inner: NodeInner) -> Self {
254 if inner.is_dir() {
255 Node::Directory(DirectoryNode(inner))
256 } else {
257 Node::File(FileNode(inner))
258 }
259 }
260
261 fn parse_compact(
262 nid: u64,
263 inode_offset: u64,
264 format: InodeFormat,
265 inode: format::InodeCompact,
266 build_time_ns: u64,
267 ) -> Result<Self, ParsingError> {
268 let data_union = InodeDataUnion::parse(inode.i_u, format);
269 Ok(Self::new(NodeInner {
270 inode_offset,
271 format,
272 mode: inode.mode.get(),
273 size: inode.size.get().into(),
274 data_union,
275 ino: inode.ino.get(),
276 nid,
277 link_count: inode.link_count.get().into(),
278 uid: inode.uid.get().into(),
279 gid: inode.gid.get().into(),
280 mtime_ns: build_time_ns,
281 }))
282 }
283
284 fn parse_extended(
285 nid: u64,
286 inode_offset: u64,
287 format: InodeFormat,
288 inode: format::InodeExtended,
289 ) -> Result<Self, ParsingError> {
290 let data_union = InodeDataUnion::parse(inode.i_u, format);
291 let mtime_ns = inode
292 .mtime
293 .get()
294 .checked_mul(1_000_000_000)
295 .and_then(|t| t.checked_add(inode.mtime_ns.get().into()))
296 .ok_or(ParsingError::Overflow)?;
297 Ok(Self::new(NodeInner {
298 inode_offset,
299 format,
300 mode: inode.mode.get(),
301 size: inode.size.get(),
302 data_union,
303 ino: inode.ino.get(),
304 nid,
305 link_count: inode.link_count.get(),
306 uid: inode.uid.get(),
307 gid: inode.gid.get(),
308 mtime_ns,
309 }))
310 }
311
312 fn from_nid(
313 nid: u64,
314 meta_addr: u64,
315 build_time_ns: u64,
316 reader: &dyn Reader,
317 ) -> Result<Self, ErofsError> {
318 let node_offset =
319 nid.checked_mul(format::INODE_SLOT_SIZE).ok_or(ParsingError::InvalidNid(nid))?;
320 let inode_offset =
321 meta_addr.checked_add(node_offset).ok_or(ParsingError::InvalidNid(nid))?;
322 let mut head = [0u8; 2];
324 reader.read(inode_offset, &mut head)?;
325 let format = InodeFormat::parse(u16::from_le_bytes(head))?;
326 let node = match format.version {
327 InodeVersion::Compact => Self::parse_compact(
328 nid,
329 inode_offset,
330 format,
331 reader.read_object(inode_offset)?,
332 build_time_ns,
333 )?,
334 InodeVersion::Extended => {
335 Self::parse_extended(nid, inode_offset, format, reader.read_object(inode_offset)?)?
336 }
337 };
338 Ok(node)
339 }
340}
341
342impl std::ops::Deref for Node {
343 type Target = NodeInner;
344 fn deref(&self) -> &Self::Target {
345 match self {
346 Node::Directory(d) => d,
347 Node::File(f) => f,
348 }
349 }
350}
351
352pub struct ErofsFilesystem {
354 reader: Arc<dyn Reader>,
355 block_size: u64,
356 meta_addr: u64,
357 root_node: DirectoryNode,
358 total_bytes: u64,
359 total_inodes: u64,
360 build_time_ns: u64,
361}
362
363impl ErofsFilesystem {
364 pub fn new(reader: Arc<dyn Reader>) -> Result<Self, ErofsError> {
366 let super_block = Self::parse_superblock(&reader)?;
367 let block_size = 1u64 << super_block.block_size_bits;
368 let meta_block_addr = super_block.meta_block_addr.get().into();
369 let meta_addr = block_size.checked_mul(meta_block_addr).ok_or(ParsingError::Overflow)?;
370 let total_inodes = super_block.inode_count.get();
371 let build_time_ns = super_block
372 .epoch
373 .get()
374 .checked_mul(1_000_000_000)
375 .and_then(|t| t.checked_add(super_block.fixed_nsec.get().into()))
376 .ok_or(ParsingError::Overflow)?;
377 let total_bytes = (super_block.blocks.get() as u64) * block_size;
378 let root_nid = super_block.root_nid.get().into();
379 let root_node = match Node::from_nid(root_nid, meta_addr, build_time_ns, &reader)? {
380 Node::Directory(node) => node,
381 _ => return Err(ParsingError::InvalidRootNode.into()),
382 };
383 Ok(Self {
384 reader,
385 block_size,
386 meta_addr,
387 root_node,
388 total_bytes,
389 total_inodes,
390 build_time_ns,
391 })
392 }
393
394 fn parse_superblock(reader: &dyn Reader) -> Result<format::SuperBlock, ErofsError> {
395 let sb: format::SuperBlock = reader.read_object(format::SUPERBLOCK_OFFSET)?;
396 if sb.magic.get() != format::EROFS_MAGIC {
397 return Err(ParsingError::InvalidSuperBlockMagic(sb.magic.get()).into());
398 }
399 if sb.block_size_bits < 9 || sb.block_size_bits > 12 {
402 return Err(ParsingError::InvalidBlockSizeBits(sb.block_size_bits).into());
403 }
404 let feature_compat = FeatureCompat::from_bits_truncate(sb.feature_compat.get());
406 if feature_compat.contains(FeatureCompat::SB_CHKSUM) {
407 Self::check_superblock_checksum(reader, &sb)?;
408 }
409 if sb.feature_incompat.get() != 0 {
411 return Err(ErofsError::UnsupportedFeatureIncompat(sb.feature_incompat.get(), 0));
412 }
413 if sb.available_compr_algs.get() != 0 {
416 return Err(ErofsError::UnsupportedCompressionAlgs(sb.available_compr_algs.get()));
417 }
418 Ok(sb)
419 }
420
421 fn check_superblock_checksum(
422 reader: &dyn Reader,
423 sb: &format::SuperBlock,
424 ) -> Result<(), ErofsError> {
425 let block_size = 1usize << sb.block_size_bits;
426 let len = block_size - (format::SUPERBLOCK_OFFSET as usize) % block_size;
427 let mut buf = vec![0u8; len];
428 reader.read(format::SUPERBLOCK_OFFSET, &mut buf)?;
429
430 buf[4..8].copy_from_slice(&[0u8; 4]);
432
433 let crc = Crc::<u32>::new(&CRC_32_ISCSI);
434 let checksum = crc.checksum(&buf);
435 let checksum = !checksum;
438
439 if checksum != sb.checksum.get() {
440 Err(ParsingError::ChecksumMismatch(sb.checksum.get(), checksum).into())
441 } else {
442 Ok(())
443 }
444 }
445
446 pub fn block_size(&self) -> u64 {
448 self.block_size
449 }
450
451 pub fn node(&self, nid: u64) -> Result<Node, ErofsError> {
453 Node::from_nid(nid, self.meta_addr, self.build_time_ns, &self.reader)
454 }
455
456 pub fn root_node(&self) -> DirectoryNode {
458 self.root_node.clone()
459 }
460
461 pub fn total_bytes(&self) -> u64 {
462 self.total_bytes
463 }
464
465 pub fn total_inodes(&self) -> u64 {
466 self.total_inodes
467 }
468
469 pub fn read_file_range(
471 &self,
472 node: &FileNode,
473 offset: u64,
474 buf: &mut [u8],
475 ) -> Result<usize, ErofsError> {
476 self.read_node_range(&node.0, offset, buf)
477 }
478
479 fn read_node_range(
487 &self,
488 node: &NodeInner,
489 offset: u64,
490 buf: &mut [u8],
491 ) -> Result<usize, ErofsError> {
492 if offset >= node.size {
493 return Ok(0);
494 }
495 let read_len = std::cmp::min(buf.len() as u64, node.size - offset) as usize;
496 let buf = &mut buf[..read_len];
497 let block_size = self.block_size();
498
499 match node.format.data_layout {
500 InodeDataLayout::FlatPlain => {
501 let read_offset = node.blkaddr_offset(block_size, offset)?;
502 self.reader.read(read_offset, buf)?;
503 Ok(read_len)
504 }
505 InodeDataLayout::FlatInline => {
506 let full_blocks_len = (node.size / block_size) * block_size;
509 let mut bytes_read = 0;
510
511 if offset < full_blocks_len {
512 let current_read_len =
515 std::cmp::min(read_len as u64, full_blocks_len - offset) as usize;
516 let read_offset = node.blkaddr_offset(block_size, offset)?;
517 self.reader.read(read_offset, &mut buf[..current_read_len])?;
518 bytes_read += current_read_len;
519 }
520
521 if bytes_read < read_len {
522 let remaining_len = read_len - bytes_read;
523 let current_offset = offset + bytes_read as u64;
524 let inline_data_offset = node
526 .inode_offset()
527 .checked_add(node.metadata_size())
528 .ok_or(ParsingError::Overflow)?;
529 let tail_offset = current_offset - full_blocks_len;
530 let tail_read_offset = inline_data_offset
531 .checked_add(tail_offset)
532 .ok_or(ParsingError::Overflow)?;
533 self.reader.read(tail_read_offset, &mut buf[bytes_read..])?;
534 bytes_read += remaining_len;
535 }
536
537 Ok(bytes_read)
538 }
539 }
540 }
541
542 pub fn read_directory(
555 &self,
556 node: &DirectoryNode,
557 mut entry_offset: usize,
558 entries: &mut [DirectoryEntry],
559 ) -> Result<usize, ErofsError> {
560 let block_size = self.block_size();
561 let block_size_usize: usize = block_size as usize;
562 let mut entries_filled = 0;
563 let mut current_entry_index = 0;
564 let mut block_data = vec![0u8; block_size_usize];
565
566 for block in 0.. {
567 let base_offset = block * block_size;
568 let bytes_read = self.read_node_range(&node.0, base_offset, &mut block_data)?;
569 if bytes_read < format::DIRENT_SIZE {
570 return Ok(entries_filled);
572 }
573 block_data[bytes_read..].fill(0);
574
575 let (dirent0, _) = zerocopy::Ref::<&[u8], format::Dirent>::from_prefix(&block_data)
577 .map_err(|_| ParsingError::InvalidDirectoryEntry)?;
578 let nameoff0 = dirent0.nameoff.get() as usize;
579 if nameoff0 < format::DIRENT_SIZE || nameoff0 >= block_size_usize {
580 return Err(ParsingError::InvalidDirectoryEntry.into());
581 }
582 let entry_count = nameoff0 / format::DIRENT_SIZE;
583
584 if current_entry_index + entry_count <= entry_offset {
586 current_entry_index += entry_count;
587 continue;
588 }
589
590 let dirents_raw = block_data
592 .get(..entry_count * format::DIRENT_SIZE)
593 .ok_or(ParsingError::InvalidDirectoryEntry)?;
594 let dirents: &[format::Dirent] =
595 &*zerocopy::Ref::<&[u8], [format::Dirent]>::from_bytes(dirents_raw)
596 .map_err(|_| ParsingError::InvalidDirectoryEntry)?;
597
598 let block_entry_offset = entry_offset - current_entry_index;
599 let space = entries.len() - entries_filled;
600 let block_entry_end = std::cmp::min(
601 entry_count,
602 block_entry_offset.checked_add(space).ok_or(ParsingError::Overflow)?,
603 );
604
605 for i in block_entry_offset..block_entry_end {
606 let last_entry = i + 1 == entry_count;
607 let nameoff = dirents[i].nameoff.get() as usize;
608
609 let name_bytes = if last_entry {
610 let name_data =
613 block_data.get(nameoff..).ok_or(ParsingError::InvalidDirectoryEntry)?;
614 name_data.split(|&x| x == 0).next().unwrap()
615 } else {
616 let nameoff_next = dirents[i + 1].nameoff.get() as usize;
617 block_data
618 .get(nameoff..nameoff_next)
619 .ok_or(ParsingError::InvalidDirectoryEntry)?
620 };
621
622 let name = std::str::from_utf8(name_bytes)
623 .map_err(|e| ParsingError::InvalidDirectoryEntryName(e))?
624 .to_string();
625 entries[entries_filled] = DirectoryEntry {
626 nid: dirents[i].nid.get(),
627 file_type: dirents[i].file_type.try_into()?,
628 name,
629 };
630 entries_filled += 1;
631 if entries_filled == entries.len() {
632 return Ok(entries_filled);
633 }
634 }
635
636 current_entry_index =
637 current_entry_index.checked_add(entry_count).ok_or(ParsingError::Overflow)?;
638 entry_offset = current_entry_index;
639 }
640
641 Ok(entries_filled)
642 }
643
644 pub fn lookup(&self, dir: &DirectoryNode, name: &str) -> Result<Option<Node>, ErofsError> {
646 let mut entry_offset = 0;
647 let mut buffer = vec![DirectoryEntry::default(); 16];
648
649 loop {
650 let filled = self.read_directory(dir, entry_offset, &mut buffer)?;
651 for i in 0..filled {
652 if buffer[i].name == name {
653 let node = self.node(buffer[i].nid)?;
654 return Ok(Some(node));
655 }
656 }
657 if filled < buffer.len() {
658 break;
659 }
660 entry_offset += filled;
661 }
662
663 Ok(None)
664 }
665}
666
667#[derive(Debug, Clone, Copy, PartialEq, Eq)]
670pub enum InodeVersion {
671 Compact,
672 Extended,
673}
674
675#[derive(Debug, Clone, Copy, PartialEq, Eq)]
677pub enum InodeDataLayout {
678 FlatPlain,
681 FlatInline,
687}
688
689#[derive(Debug, Clone, Copy)]
691pub struct InodeFormat {
692 pub version: InodeVersion,
693 pub data_layout: InodeDataLayout,
694}
695
696impl InodeFormat {
697 pub fn parse(format: u16) -> Result<Self, ParsingError> {
699 let version =
700 if format & 0x1 == 0 { InodeVersion::Compact } else { InodeVersion::Extended };
701 let data_layout_raw = (format >> 1) & 0x7;
702 let data_layout = match data_layout_raw {
703 0 => InodeDataLayout::FlatPlain,
704 2 => InodeDataLayout::FlatInline,
705 _ => return Err(ParsingError::InvalidInodeDataLayout(data_layout_raw)),
706 };
707 Ok(Self { version, data_layout })
708 }
709}
710
711#[cfg(test)]
712mod tests {
713 use super::*;
714 use crate::readers::VecReader;
715 use std::fs;
716 use test_case::test_case;
717
718 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
719 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
720 #[fuchsia::test]
721 fn test_parse_superblock(path: &str) {
722 let runfiles = fs::read(path).expect("failed to read test file");
723 let reader = Arc::new(VecReader::new(runfiles.clone()));
724 let _fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
726
727 let mut mutated_runfiles = runfiles.clone();
730 mutated_runfiles[1088] ^= 0xFF;
731
732 let reader = Arc::new(VecReader::new(mutated_runfiles));
733 let fs = ErofsFilesystem::new(reader);
734 assert!(fs.is_err());
735 match fs.err().unwrap() {
736 ErofsError::Parse(ParsingError::ChecksumMismatch(_, _)) => {}
737 e => panic!("Expected ChecksumMismatch error, got {:?}", e),
738 }
739 }
740
741 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
742 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
743 #[fuchsia::test]
744 fn test_list_dir(path: &str) {
745 let runfiles = fs::read(path).expect("failed to read test file");
746 let reader = Arc::new(VecReader::new(runfiles));
747 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
748 let root_node = fs.root_node();
749
750 let mut buf = vec![DirectoryEntry::default(); 16];
751 let filled = fs.read_directory(&root_node, 0, &mut buf).expect("failed to read directory");
752
753 let names: Vec<String> = buf[..filled].iter().map(|e| e.name.clone()).collect();
754 assert_eq!(names, vec![".", "..", "file1", "large_dir", "photosynthesis", "quantum"]);
755 }
756
757 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
758 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
759 #[fuchsia::test]
760 fn test_overflow_nid(path: &str) {
761 let runfiles = fs::read(path).expect("failed to read test file");
762 let reader = Arc::new(VecReader::new(runfiles));
763 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
764 let result = fs.node(u64::MAX);
765 assert!(result.is_err());
766 assert_eq!(result.unwrap_err(), ErofsError::Parse(ParsingError::InvalidNid(u64::MAX)));
767 }
768
769 #[test_case("/pkg/data/simple.erofs", "file1" ; "4096 block size file1")]
770 #[test_case("/pkg/data/simple_512.erofs", "file1" ; "512 block size file1")]
771 #[test_case("/pkg/data/simple.erofs", "photosynthesis" ; "4096 block size photosynthesis")]
772 #[test_case("/pkg/data/simple_512.erofs", "photosynthesis" ; "512 block size photosynthesis")]
773 #[fuchsia::test]
774 fn test_read_file_range(path: &str, name: &str) {
775 let runfiles = fs::read(path).expect("failed to read test file");
776 let reader = Arc::new(VecReader::new(runfiles));
777 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
778 let root_node = fs.root_node();
779
780 let node = fs.lookup(&root_node, name).expect("failed to lookup").expect("file not found");
781 let file_node = match node {
782 Node::File(f) => f,
783 _ => panic!("Expected file node"),
784 };
785
786 let size = file_node.size() as usize;
787 let mut buf = vec![0u8; size];
788 let bytes_read = fs.read_file_range(&file_node, 0, &mut buf).expect("failed to read");
789 assert_eq!(bytes_read, size);
790 if name == "file1" {
791 assert_eq!(&buf[..14], b"this is a file");
792 }
793
794 let mut buf = vec![0u8; 5];
796 let bytes_read = fs.read_file_range(&file_node, 5, &mut buf).expect("failed to read");
797 assert_eq!(bytes_read, 5);
798 if name == "file1" {
799 assert_eq!(&buf, b"is a ");
800 }
801
802 let mut buf = vec![0u8; 100];
804 let bytes_read =
805 fs.read_file_range(&file_node, (size - 5) as u64, &mut buf).expect("failed to read");
806 assert_eq!(bytes_read, 5);
807 if name == "file1" {
808 assert_eq!(&buf[..5], b"file\n");
809 }
810
811 let mut buf = vec![0u8; 100];
813 let bytes_read =
814 fs.read_file_range(&file_node, size as u64, &mut buf).expect("failed to read");
815 assert_eq!(bytes_read, 0);
816 }
817
818 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
819 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
820 #[fuchsia::test]
821 fn test_read_directory_pagination(path: &str) {
822 let runfiles = fs::read(path).expect("failed to read test file");
823 let reader = Arc::new(VecReader::new(runfiles));
824 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
825 let root_node = fs.root_node();
826
827 let expected_names = vec![".", "..", "file1", "large_dir", "photosynthesis", "quantum"];
828
829 let mut buf = vec![DirectoryEntry::default(); 2];
831
832 let filled = fs.read_directory(&root_node, 0, &mut buf).expect("failed to read dir");
834 assert_eq!(filled, 2);
835 assert_eq!(buf[0].name, expected_names[0]);
836 assert_eq!(buf[1].name, expected_names[1]);
837
838 let filled = fs.read_directory(&root_node, 2, &mut buf).expect("failed to read dir");
840 assert_eq!(filled, 2);
841 assert_eq!(buf[0].name, expected_names[2]);
842 assert_eq!(buf[1].name, expected_names[3]);
843
844 let filled = fs.read_directory(&root_node, 5, &mut buf).expect("failed to read dir");
846 assert_eq!(filled, 1);
847 assert_eq!(buf[0].name, expected_names[5]);
848
849 let filled = fs.read_directory(&root_node, 6, &mut buf).expect("failed to read dir");
851 assert_eq!(filled, 0);
852
853 let mut buf1 = vec![DirectoryEntry::default(); 1];
855 for i in 0..expected_names.len() {
856 let filled = fs.read_directory(&root_node, i, &mut buf1).expect("failed to read dir");
857 assert_eq!(filled, 1);
858 assert_eq!(buf1[0].name, expected_names[i]);
859 }
860 let filled = fs
861 .read_directory(&root_node, expected_names.len(), &mut buf1)
862 .expect("failed to read dir");
863 assert_eq!(filled, 0);
864 }
865
866 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
867 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
868 #[fuchsia::test]
869 fn test_read_directory_large_dir(path: &str) {
870 let runfiles = fs::read(path).expect("failed to read test file");
873 let reader = Arc::new(VecReader::new(runfiles));
874 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
875 let root_node = fs.root_node();
876
877 let large_dir_node = fs
878 .lookup(&root_node, "large_dir")
879 .expect("failed to look up large_dir")
880 .expect("large_dir not found");
881
882 let large_dir = match large_dir_node {
883 Node::Directory(d) => d,
884 _ => panic!("Expected directory node"),
885 };
886
887 let mut entry_offset = 2;
889 let mut buffer = vec![DirectoryEntry::default(); 16];
890 loop {
891 let filled = fs.read_directory(&large_dir, entry_offset, &mut buffer).unwrap();
892 for i in 0..filled {
893 assert_eq!(buffer[i].name[..12], format!("file_number_"));
895 }
896 if filled < buffer.len() {
897 break;
898 }
899 entry_offset += filled;
900 }
901 }
902
903 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
904 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
905 #[fuchsia::test]
906 fn test_filesystem_metadata(path: &str) {
907 let runfiles = fs::read(path).expect("failed to read test file");
908 let reader = Arc::new(VecReader::new(runfiles));
909 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
910
911 assert!(fs.total_bytes() > 0);
912 assert!(fs.total_inodes() > 0);
913 }
914
915 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
916 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
917 #[fuchsia::test]
918 fn test_node_metadata(path: &str) {
919 let runfiles = fs::read(path).expect("failed to read test file");
920 let reader = Arc::new(VecReader::new(runfiles));
921 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
922 let root_node = fs.root_node();
923
924 assert!(root_node.link_count() >= 2);
925 assert!(root_node.mtime_ns() > 0);
926
927 let file1_node = fs.lookup(&root_node, "file1").unwrap().unwrap();
928 assert_eq!(file1_node.link_count(), 1);
929 assert!(file1_node.mtime_ns() > 0);
930 }
931}