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)]
104struct NodeInner {
105 inode_offset: u64,
106 format: InodeFormat,
107 mode: u16,
108 size: u64,
109 data_union: InodeDataUnion,
110 ino: u32,
111}
112
113impl NodeInner {
114 fn is_dir(&self) -> bool {
115 self.mode & 0x4000 != 0
116 }
117
118 fn inode_offset(&self) -> u64 {
119 self.inode_offset
120 }
121
122 fn blkaddr(&self, block_size: u64) -> u64 {
126 match self.data_union {
127 InodeDataUnion::DataBlkAddrPlain(addr) => addr.into(),
128 InodeDataUnion::DataBlkAddrInline(addr) => {
129 debug_assert!(self.size / block_size > 0);
130 addr.into()
131 }
132 }
133 }
134
135 fn blkaddr_offset(&self, block_size: u64, offset: u64) -> Result<u64, ParsingError> {
138 self.blkaddr(block_size)
139 .checked_mul(block_size)
140 .ok_or(ParsingError::Overflow)?
141 .checked_add(offset)
142 .ok_or(ParsingError::Overflow)
143 }
144
145 fn metadata_size(&self) -> u64 {
146 match self.format.version {
147 InodeVersion::Compact => 32,
148 InodeVersion::Extended => 64,
149 }
150 }
151}
152
153#[derive(Debug, Clone)]
155pub struct DirectoryNode(NodeInner);
156
157impl DirectoryNode {
158 pub fn size(&self) -> u64 {
159 self.0.size
160 }
161 pub fn ino(&self) -> u32 {
162 self.0.ino
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
168pub enum FileType {
169 #[default]
170 Unknown = 0,
171 RegFile = 1,
172 Dir = 2,
173 ChrDev = 3,
174 BlkDev = 4,
175 Fifo = 5,
176 Sock = 6,
177 Symlink = 7,
178}
179
180impl TryFrom<u8> for FileType {
181 type Error = ParsingError;
182
183 fn try_from(value: u8) -> Result<Self, Self::Error> {
184 match value {
185 0 => Ok(FileType::Unknown),
186 1 => Ok(FileType::RegFile),
187 2 => Ok(FileType::Dir),
188 3 => Ok(FileType::ChrDev),
189 4 => Ok(FileType::BlkDev),
190 5 => Ok(FileType::Fifo),
191 6 => Ok(FileType::Sock),
192 7 => Ok(FileType::Symlink),
193 _ => Err(ParsingError::InvalidFileType(value)),
194 }
195 }
196}
197
198#[derive(Debug, Clone, Default)]
200pub struct DirectoryEntry {
201 pub nid: u64,
202 pub file_type: FileType,
203 pub name: String,
204}
205
206#[derive(Debug, Clone)]
208pub struct FileNode(NodeInner);
209
210impl FileNode {
211 pub fn size(&self) -> u64 {
212 self.0.size
213 }
214 pub fn ino(&self) -> u32 {
215 self.0.ino
216 }
217}
218
219#[derive(Debug, Clone)]
221pub enum Node {
222 Directory(DirectoryNode),
223 File(FileNode),
224}
225
226impl Node {
227 fn new(inner: NodeInner) -> Self {
228 if inner.is_dir() {
229 Node::Directory(DirectoryNode(inner))
230 } else {
231 Node::File(FileNode(inner))
232 }
233 }
234
235 fn parse_compact(
236 inode_offset: u64,
237 format: InodeFormat,
238 inode: format::InodeCompact,
239 ) -> Result<Self, ParsingError> {
240 let data_union = InodeDataUnion::parse(inode.i_u, format);
241 Ok(Self::new(NodeInner {
242 inode_offset,
243 format,
244 mode: inode.mode.get(),
245 size: inode.size.get().into(),
246 data_union,
247 ino: inode.ino.get(),
248 }))
249 }
250
251 fn parse_extended(
252 inode_offset: u64,
253 format: InodeFormat,
254 inode: format::InodeExtended,
255 ) -> Result<Self, ParsingError> {
256 let data_union = InodeDataUnion::parse(inode.i_u, format);
257 Ok(Self::new(NodeInner {
258 inode_offset,
259 format,
260 mode: inode.mode.get(),
261 size: inode.size.get(),
262 data_union,
263 ino: inode.ino.get(),
264 }))
265 }
266
267 fn from_nid(nid: u64, meta_addr: u64, reader: &dyn Reader) -> Result<Self, ErofsError> {
268 let node_offset =
269 nid.checked_mul(format::INODE_SLOT_SIZE).ok_or(ParsingError::InvalidNid(nid))?;
270 let inode_offset =
271 meta_addr.checked_add(node_offset).ok_or(ParsingError::InvalidNid(nid))?;
272 let mut head = [0u8; 2];
274 reader.read(inode_offset, &mut head)?;
275 let format = InodeFormat::parse(u16::from_le_bytes(head))?;
276 let node = match format.version {
277 InodeVersion::Compact => {
278 Self::parse_compact(inode_offset, format, reader.read_object(inode_offset)?)?
279 }
280 InodeVersion::Extended => {
281 Self::parse_extended(inode_offset, format, reader.read_object(inode_offset)?)?
282 }
283 };
284 Ok(node)
285 }
286
287 pub fn size(&self) -> u64 {
288 match self {
289 Node::Directory(node) => node.size(),
290 Node::File(node) => node.size(),
291 }
292 }
293
294 pub fn ino(&self) -> u32 {
295 match self {
296 Node::Directory(node) => node.ino(),
297 Node::File(node) => node.ino(),
298 }
299 }
300}
301
302pub struct ErofsFilesystem {
304 reader: Arc<dyn Reader>,
305 block_size: u64,
306 meta_addr: u64,
307 root_node: DirectoryNode,
308}
309
310impl ErofsFilesystem {
311 pub fn new(reader: Arc<dyn Reader>) -> Result<Self, ErofsError> {
313 let super_block = Self::parse_superblock(&reader)?;
314 let block_size = 1u64 << super_block.block_size_bits;
315 let meta_block_addr = super_block.meta_block_addr.get().into();
316 let meta_addr = block_size.checked_mul(meta_block_addr).ok_or(ParsingError::Overflow)?;
317 let root_nid = super_block.root_nid.get().into();
318 let root_node = match Node::from_nid(root_nid, meta_addr, &reader)? {
319 Node::Directory(node) => node,
320 _ => return Err(ParsingError::InvalidRootNode.into()),
321 };
322 Ok(Self { reader, block_size, meta_addr, root_node })
323 }
324
325 fn parse_superblock(reader: &dyn Reader) -> Result<format::SuperBlock, ErofsError> {
326 let sb: format::SuperBlock = reader.read_object(format::SUPERBLOCK_OFFSET)?;
327 if sb.magic.get() != format::EROFS_MAGIC {
328 return Err(ParsingError::InvalidSuperBlockMagic(sb.magic.get()).into());
329 }
330 if sb.block_size_bits < 9 || sb.block_size_bits > 12 {
333 return Err(ParsingError::InvalidBlockSizeBits(sb.block_size_bits).into());
334 }
335 let feature_compat = FeatureCompat::from_bits_truncate(sb.feature_compat.get());
337 if feature_compat.contains(FeatureCompat::SB_CHKSUM) {
338 Self::check_superblock_checksum(reader, &sb)?;
339 }
340 if sb.feature_incompat.get() != 0 {
342 return Err(ErofsError::UnsupportedFeatureIncompat(sb.feature_incompat.get(), 0));
343 }
344 if sb.available_compr_algs.get() != 0 {
347 return Err(ErofsError::UnsupportedCompressionAlgs(sb.available_compr_algs.get()));
348 }
349 Ok(sb)
350 }
351
352 fn check_superblock_checksum(
353 reader: &dyn Reader,
354 sb: &format::SuperBlock,
355 ) -> Result<(), ErofsError> {
356 let block_size = 1usize << sb.block_size_bits;
357 let len = block_size - (format::SUPERBLOCK_OFFSET as usize) % block_size;
358 let mut buf = vec![0u8; len];
359 reader.read(format::SUPERBLOCK_OFFSET, &mut buf)?;
360
361 buf[4..8].copy_from_slice(&[0u8; 4]);
363
364 let crc = Crc::<u32>::new(&CRC_32_ISCSI);
365 let checksum = crc.checksum(&buf);
366 let checksum = !checksum;
369
370 if checksum != sb.checksum.get() {
371 Err(ParsingError::ChecksumMismatch(sb.checksum.get(), checksum).into())
372 } else {
373 Ok(())
374 }
375 }
376
377 pub fn block_size(&self) -> u64 {
379 self.block_size
380 }
381
382 pub fn node(&self, nid: u64) -> Result<Node, ErofsError> {
384 Node::from_nid(nid, self.meta_addr, &self.reader)
385 }
386
387 pub fn root_node(&self) -> DirectoryNode {
389 self.root_node.clone()
390 }
391
392 pub fn read_file_range(
394 &self,
395 node: &FileNode,
396 offset: u64,
397 buf: &mut [u8],
398 ) -> Result<usize, ErofsError> {
399 self.read_node_range(&node.0, offset, buf)
400 }
401
402 fn read_node_range(
410 &self,
411 node: &NodeInner,
412 offset: u64,
413 buf: &mut [u8],
414 ) -> Result<usize, ErofsError> {
415 if offset >= node.size {
416 return Ok(0);
417 }
418 let read_len = std::cmp::min(buf.len() as u64, node.size - offset) as usize;
419 let buf = &mut buf[..read_len];
420 let block_size = self.block_size();
421
422 match node.format.data_layout {
423 InodeDataLayout::FlatPlain => {
424 let read_offset = node.blkaddr_offset(block_size, offset)?;
425 self.reader.read(read_offset, buf)?;
426 Ok(read_len)
427 }
428 InodeDataLayout::FlatInline => {
429 let full_blocks_len = (node.size / block_size) * block_size;
432 let mut bytes_read = 0;
433
434 if offset < full_blocks_len {
435 let current_read_len =
438 std::cmp::min(read_len as u64, full_blocks_len - offset) as usize;
439 let read_offset = node.blkaddr_offset(block_size, offset)?;
440 self.reader.read(read_offset, &mut buf[..current_read_len])?;
441 bytes_read += current_read_len;
442 }
443
444 if bytes_read < read_len {
445 let remaining_len = read_len - bytes_read;
446 let current_offset = offset + bytes_read as u64;
447 let inline_data_offset = node
449 .inode_offset()
450 .checked_add(node.metadata_size())
451 .ok_or(ParsingError::Overflow)?;
452 let tail_offset = current_offset - full_blocks_len;
453 let tail_read_offset = inline_data_offset
454 .checked_add(tail_offset)
455 .ok_or(ParsingError::Overflow)?;
456 self.reader.read(tail_read_offset, &mut buf[bytes_read..])?;
457 bytes_read += remaining_len;
458 }
459
460 Ok(bytes_read)
461 }
462 }
463 }
464
465 pub fn read_directory(
478 &self,
479 node: &DirectoryNode,
480 mut entry_offset: usize,
481 entries: &mut [DirectoryEntry],
482 ) -> Result<usize, ErofsError> {
483 let block_size = self.block_size();
484 let block_size_usize: usize = block_size as usize;
485 let mut entries_filled = 0;
486 let mut current_entry_index = 0;
487 let mut block_data = vec![0u8; block_size_usize];
488
489 for block in 0.. {
490 let base_offset = block * block_size;
491 let bytes_read = self.read_node_range(&node.0, base_offset, &mut block_data)?;
492 if bytes_read < format::DIRENT_SIZE {
493 return Ok(entries_filled);
495 }
496 block_data[bytes_read..].fill(0);
497
498 let (dirent0, _) = zerocopy::Ref::<&[u8], format::Dirent>::from_prefix(&block_data)
500 .map_err(|_| ParsingError::InvalidDirectoryEntry)?;
501 let nameoff0 = dirent0.nameoff.get() as usize;
502 if nameoff0 < format::DIRENT_SIZE || nameoff0 >= block_size_usize {
503 return Err(ParsingError::InvalidDirectoryEntry.into());
504 }
505 let entry_count = nameoff0 / format::DIRENT_SIZE;
506
507 if current_entry_index + entry_count <= entry_offset {
509 current_entry_index += entry_count;
510 continue;
511 }
512
513 let dirents_raw = block_data
515 .get(..entry_count * format::DIRENT_SIZE)
516 .ok_or(ParsingError::InvalidDirectoryEntry)?;
517 let dirents: &[format::Dirent] =
518 &*zerocopy::Ref::<&[u8], [format::Dirent]>::from_bytes(dirents_raw)
519 .map_err(|_| ParsingError::InvalidDirectoryEntry)?;
520
521 let block_entry_offset = entry_offset - current_entry_index;
522 let space = entries.len() - entries_filled;
523 let block_entry_end = std::cmp::min(
524 entry_count,
525 block_entry_offset.checked_add(space).ok_or(ParsingError::Overflow)?,
526 );
527
528 for i in block_entry_offset..block_entry_end {
529 let last_entry = i + 1 == entry_count;
530 let nameoff = dirents[i].nameoff.get() as usize;
531
532 let name_bytes = if last_entry {
533 let name_data =
536 block_data.get(nameoff..).ok_or(ParsingError::InvalidDirectoryEntry)?;
537 name_data.split(|&x| x == 0).next().unwrap()
538 } else {
539 let nameoff_next = dirents[i + 1].nameoff.get() as usize;
540 block_data
541 .get(nameoff..nameoff_next)
542 .ok_or(ParsingError::InvalidDirectoryEntry)?
543 };
544
545 let name = std::str::from_utf8(name_bytes)
546 .map_err(|e| ParsingError::InvalidDirectoryEntryName(e))?
547 .to_string();
548 entries[entries_filled] = DirectoryEntry {
549 nid: dirents[i].nid.get(),
550 file_type: dirents[i].file_type.try_into()?,
551 name,
552 };
553 entries_filled += 1;
554 if entries_filled == entries.len() {
555 return Ok(entries_filled);
556 }
557 }
558
559 current_entry_index =
560 current_entry_index.checked_add(entry_count).ok_or(ParsingError::Overflow)?;
561 entry_offset = current_entry_index;
562 }
563
564 Ok(entries_filled)
565 }
566
567 pub fn lookup(&self, dir: &DirectoryNode, name: &str) -> Result<Option<Node>, ErofsError> {
569 let mut entry_offset = 0;
570 let mut buffer = vec![DirectoryEntry::default(); 16];
571
572 loop {
573 let filled = self.read_directory(dir, entry_offset, &mut buffer)?;
574 for i in 0..filled {
575 if buffer[i].name == name {
576 let node = self.node(buffer[i].nid)?;
577 return Ok(Some(node));
578 }
579 }
580 if filled < buffer.len() {
581 break;
582 }
583 entry_offset += filled;
584 }
585
586 Ok(None)
587 }
588}
589
590#[derive(Debug, Clone, Copy, PartialEq, Eq)]
593pub enum InodeVersion {
594 Compact,
595 Extended,
596}
597
598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
600pub enum InodeDataLayout {
601 FlatPlain,
604 FlatInline,
610}
611
612#[derive(Debug, Clone, Copy)]
614pub struct InodeFormat {
615 pub version: InodeVersion,
616 pub data_layout: InodeDataLayout,
617}
618
619impl InodeFormat {
620 pub fn parse(format: u16) -> Result<Self, ParsingError> {
622 let version =
623 if format & 0x1 == 0 { InodeVersion::Compact } else { InodeVersion::Extended };
624 let data_layout_raw = (format >> 1) & 0x7;
625 let data_layout = match data_layout_raw {
626 0 => InodeDataLayout::FlatPlain,
627 2 => InodeDataLayout::FlatInline,
628 _ => return Err(ParsingError::InvalidInodeDataLayout(data_layout_raw)),
629 };
630 Ok(Self { version, data_layout })
631 }
632}
633
634#[cfg(test)]
635mod tests {
636 use super::*;
637 use crate::readers::VecReader;
638 use std::fs;
639 use test_case::test_case;
640
641 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
642 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
643 #[fuchsia::test]
644 fn test_parse_superblock(path: &str) {
645 let runfiles = fs::read(path).expect("failed to read test file");
646 let reader = Arc::new(VecReader::new(runfiles.clone()));
647 let _fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
649
650 let mut mutated_runfiles = runfiles.clone();
653 mutated_runfiles[1088] ^= 0xFF;
654
655 let reader = Arc::new(VecReader::new(mutated_runfiles));
656 let fs = ErofsFilesystem::new(reader);
657 assert!(fs.is_err());
658 match fs.err().unwrap() {
659 ErofsError::Parse(ParsingError::ChecksumMismatch(_, _)) => {}
660 e => panic!("Expected ChecksumMismatch error, got {:?}", e),
661 }
662 }
663
664 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
665 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
666 #[fuchsia::test]
667 fn test_list_dir(path: &str) {
668 let runfiles = fs::read(path).expect("failed to read test file");
669 let reader = Arc::new(VecReader::new(runfiles));
670 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
671 let root_node = fs.root_node();
672
673 let mut buf = vec![DirectoryEntry::default(); 16];
674 let filled = fs.read_directory(&root_node, 0, &mut buf).expect("failed to read directory");
675
676 let names: Vec<String> = buf[..filled].iter().map(|e| e.name.clone()).collect();
677 assert_eq!(names, vec![".", "..", "file1", "large_dir", "photosynthesis", "quantum"]);
678 }
679
680 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
681 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
682 #[fuchsia::test]
683 fn test_overflow_nid(path: &str) {
684 let runfiles = fs::read(path).expect("failed to read test file");
685 let reader = Arc::new(VecReader::new(runfiles));
686 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
687 let result = fs.node(u64::MAX);
688 assert!(result.is_err());
689 assert_eq!(result.unwrap_err(), ErofsError::Parse(ParsingError::InvalidNid(u64::MAX)));
690 }
691
692 #[test_case("/pkg/data/simple.erofs", "file1" ; "4096 block size file1")]
693 #[test_case("/pkg/data/simple_512.erofs", "file1" ; "512 block size file1")]
694 #[test_case("/pkg/data/simple.erofs", "photosynthesis" ; "4096 block size photosynthesis")]
695 #[test_case("/pkg/data/simple_512.erofs", "photosynthesis" ; "512 block size photosynthesis")]
696 #[fuchsia::test]
697 fn test_read_file_range(path: &str, name: &str) {
698 let runfiles = fs::read(path).expect("failed to read test file");
699 let reader = Arc::new(VecReader::new(runfiles));
700 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
701 let root_node = fs.root_node();
702
703 let node = fs.lookup(&root_node, name).expect("failed to lookup").expect("file not found");
704 let file_node = match node {
705 Node::File(f) => f,
706 _ => panic!("Expected file node"),
707 };
708
709 let size = file_node.size() as usize;
710 let mut buf = vec![0u8; size];
711 let bytes_read = fs.read_file_range(&file_node, 0, &mut buf).expect("failed to read");
712 assert_eq!(bytes_read, size);
713 if name == "file1" {
714 assert_eq!(&buf[..14], b"this is a file");
715 }
716
717 let mut buf = vec![0u8; 5];
719 let bytes_read = fs.read_file_range(&file_node, 5, &mut buf).expect("failed to read");
720 assert_eq!(bytes_read, 5);
721 if name == "file1" {
722 assert_eq!(&buf, b"is a ");
723 }
724
725 let mut buf = vec![0u8; 100];
727 let bytes_read =
728 fs.read_file_range(&file_node, (size - 5) as u64, &mut buf).expect("failed to read");
729 assert_eq!(bytes_read, 5);
730 if name == "file1" {
731 assert_eq!(&buf[..5], b"file\n");
732 }
733
734 let mut buf = vec![0u8; 100];
736 let bytes_read =
737 fs.read_file_range(&file_node, size as u64, &mut buf).expect("failed to read");
738 assert_eq!(bytes_read, 0);
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_read_directory_pagination(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 expected_names = vec![".", "..", "file1", "large_dir", "photosynthesis", "quantum"];
751
752 let mut buf = vec![DirectoryEntry::default(); 2];
754
755 let filled = fs.read_directory(&root_node, 0, &mut buf).expect("failed to read dir");
757 assert_eq!(filled, 2);
758 assert_eq!(buf[0].name, expected_names[0]);
759 assert_eq!(buf[1].name, expected_names[1]);
760
761 let filled = fs.read_directory(&root_node, 2, &mut buf).expect("failed to read dir");
763 assert_eq!(filled, 2);
764 assert_eq!(buf[0].name, expected_names[2]);
765 assert_eq!(buf[1].name, expected_names[3]);
766
767 let filled = fs.read_directory(&root_node, 5, &mut buf).expect("failed to read dir");
769 assert_eq!(filled, 1);
770 assert_eq!(buf[0].name, expected_names[5]);
771
772 let filled = fs.read_directory(&root_node, 6, &mut buf).expect("failed to read dir");
774 assert_eq!(filled, 0);
775
776 let mut buf1 = vec![DirectoryEntry::default(); 1];
778 for i in 0..expected_names.len() {
779 let filled = fs.read_directory(&root_node, i, &mut buf1).expect("failed to read dir");
780 assert_eq!(filled, 1);
781 assert_eq!(buf1[0].name, expected_names[i]);
782 }
783 let filled = fs
784 .read_directory(&root_node, expected_names.len(), &mut buf1)
785 .expect("failed to read dir");
786 assert_eq!(filled, 0);
787 }
788
789 #[test_case("/pkg/data/simple.erofs" ; "4096 block size")]
790 #[test_case("/pkg/data/simple_512.erofs" ; "512 block size")]
791 #[fuchsia::test]
792 fn test_read_directory_large_dir(path: &str) {
793 let runfiles = fs::read(path).expect("failed to read test file");
796 let reader = Arc::new(VecReader::new(runfiles));
797 let fs = ErofsFilesystem::new(reader).expect("failed to parse superblock");
798 let root_node = fs.root_node();
799
800 let large_dir_node = fs
801 .lookup(&root_node, "large_dir")
802 .expect("failed to look up large_dir")
803 .expect("large_dir not found");
804
805 let large_dir = match large_dir_node {
806 Node::Directory(d) => d,
807 _ => panic!("Expected directory node"),
808 };
809
810 let mut entry_offset = 2;
812 let mut buffer = vec![DirectoryEntry::default(); 16];
813 loop {
814 let filled = fs.read_directory(&large_dir, entry_offset, &mut buffer).unwrap();
815 for i in 0..filled {
816 assert_eq!(buffer[i].name[..12], format!("file_number_"));
818 }
819 if filled < buffer.len() {
820 break;
821 }
822 entry_offset += filled;
823 }
824 }
825}