1use crate::structs::MINIMUM_INODE_SIZE;
36
37use crate::readers::Reader;
38use crate::structs::{
39 BlockGroupDesc32, DirEntry2, DirEntryHeader, EntryType, Extent, ExtentHeader, ExtentIndex,
40 ExtentTreeNode, INode, InvalidAddressErrorType, ParseToStruct, ParsingError, SuperBlock,
41 XattrEntryHeader, XattrHeader, FIRST_BG_PADDING, MIN_EXT4_SIZE, ROOT_INODE_NUM,
42};
43use once_cell::sync::OnceCell;
44use std::collections::BTreeMap;
45use std::mem::{size_of, size_of_val};
46use std::path::{Component, Path};
47use std::str;
48use zerocopy::byteorder::little_endian::U32 as LEU32;
49use zerocopy::{IntoBytes, SplitByteSlice};
50
51assert_eq_size!(u64, usize);
53
54pub struct Parser {
55 reader: Box<dyn Reader>,
56 super_block: OnceCell<SuperBlock>,
57}
58
59pub type XattrMap = BTreeMap<Vec<u8>, Vec<u8>>;
60
61impl Parser {
69 pub fn new(reader: Box<dyn Reader>) -> Self {
70 Parser { reader, super_block: OnceCell::new() }
71 }
72
73 fn super_block(&self) -> Result<&SuperBlock, ParsingError> {
81 self.super_block.get_or_try_init(|| SuperBlock::parse(&self.reader))
82 }
83
84 pub fn block_size(&self) -> Result<u64, ParsingError> {
86 self.super_block()?.block_size()
87 }
88
89 fn block(&self, block_number: u64) -> Result<Box<[u8]>, ParsingError> {
91 if block_number == 0 {
92 return Err(ParsingError::InvalidAddress(
93 InvalidAddressErrorType::Lower,
94 0,
95 FIRST_BG_PADDING,
96 ));
97 }
98 let block_size = self.block_size()?;
99 let address = block_number
100 .checked_mul(block_size)
101 .ok_or(ParsingError::BlockNumberOutOfBounds(block_number))?;
102
103 let mut data = vec![0u8; block_size.try_into().unwrap()];
104 self.reader.read(address, data.as_mut_slice()).map_err(Into::<ParsingError>::into)?;
105
106 Ok(data.into_boxed_slice())
107 }
108
109 fn inode_addr(&self, inode_number: u32) -> Result<u64, ParsingError> {
111 if inode_number < 1 {
112 return Err(ParsingError::InvalidInode(inode_number));
114 }
115 let sb = self.super_block()?;
116 let block_size = self.block_size()?;
117
118 let bgd_table_offset = if block_size >= MIN_EXT4_SIZE {
129 block_size
132 } else {
133 block_size * 2
136 };
137
138 let bgd_offset = (inode_number - 1) as u64 / sb.e2fs_ipg.get() as u64
139 * size_of::<BlockGroupDesc32>() as u64;
140 let bgd =
141 BlockGroupDesc32::from_reader_with_offset(&self.reader, bgd_table_offset + bgd_offset)?;
142
143 let inode_table_offset =
146 (inode_number - 1) as u64 % sb.e2fs_ipg.get() as u64 * sb.e2fs_inode_size.get() as u64;
147 let inode_addr = (bgd.ext2bgd_i_tables.get() as u64 * block_size) + inode_table_offset;
148 if inode_addr < MIN_EXT4_SIZE {
149 return Err(ParsingError::InvalidAddress(
150 InvalidAddressErrorType::Lower,
151 inode_addr,
152 MIN_EXT4_SIZE,
153 ));
154 }
155 Ok(inode_addr)
156 }
157
158 pub fn inode(&self, inode_number: u32) -> Result<INode, ParsingError> {
160 INode::from_reader_with_offset(&self.reader, self.inode_addr(inode_number)?)
161 }
162
163 pub fn root_inode(&self) -> Result<INode, ParsingError> {
165 self.inode(ROOT_INODE_NUM)
166 }
167
168 fn extent_data(&self, extent: &Extent, mut allowance: u64) -> Result<Vec<u8>, ParsingError> {
170 let block_number = extent.target_block_num();
171 let block_count = extent.e_len.get() as u64;
172 let block_size = self.block_size()?;
173 let mut read_len;
174
175 let mut data = Vec::with_capacity((block_size * block_count).try_into().unwrap());
176
177 for i in 0..block_count {
178 let block_data = self.block(block_number + i as u64)?;
179 if allowance >= block_size {
180 read_len = block_size;
181 } else {
182 read_len = allowance;
183 }
184 let block_data = &block_data[0..read_len.try_into().unwrap()];
185 data.append(&mut block_data.to_vec());
186 allowance -= read_len;
187 }
188
189 Ok(data)
190 }
191
192 pub fn read_extents(&self, inode_num: u32) -> Result<(u64, Vec<Extent>), ParsingError> {
195 let inode = self.inode(inode_num)?;
196
197 const IFMT: u16 = 0xf000;
199 const IFREG: u16 = 0x8000;
200 if u16::from(inode.e2di_mode) & IFMT != IFREG {
201 return Err(ParsingError::NotFile);
202 }
203
204 let root_extent_tree_node = inode.extent_tree_node()?;
205 let mut extents = Vec::new();
206
207 self.iterate_extents_in_tree(&root_extent_tree_node, &mut |extent| {
208 extents.push(extent.clone());
209 Ok(())
210 })?;
211
212 Ok((inode.size(), extents))
213 }
214
215 fn read_extent_data(
224 &self,
225 extent: &Extent,
226 data: &mut Vec<u8>,
227 allowance: &mut u64,
228 ) -> Result<(), ParsingError> {
229 let mut extent_data = self.extent_data(&extent, *allowance)?;
230 let extent_len = extent_data.len() as u64;
231 if extent_len > *allowance {
232 return Err(ParsingError::ExtentUnexpectedLength(extent_len, *allowance));
233 }
234 *allowance -= extent_len;
235 data.append(&mut extent_data);
236 Ok(())
237 }
238
239 fn read_dir_entries(
241 &self,
242 extent: &Extent,
243 entries: &mut Vec<DirEntry2>,
244 ) -> Result<(), ParsingError> {
245 let block_size = self.block_size()?;
246 let target_block_offset = extent.target_block_num() * block_size;
247
248 for block_index in 0..extent.e_len.get() {
251 let mut dir_entry_offset = 0u64;
252 while (dir_entry_offset + size_of::<DirEntryHeader>() as u64) < block_size {
253 let offset =
254 dir_entry_offset + target_block_offset + (block_index as u64 * block_size);
255
256 let de_header = DirEntryHeader::from_reader_with_offset(&self.reader, offset)?;
257 let mut de = DirEntry2 {
258 e2d_ino: de_header.e2d_ino,
259 e2d_reclen: de_header.e2d_reclen,
260 e2d_namlen: de_header.e2d_namlen,
261 e2d_type: de_header.e2d_type,
262 e2d_name: [0u8; 255],
263 };
264 self.reader.read(
265 offset + size_of::<DirEntryHeader>() as u64,
266 &mut de.e2d_name[..de.e2d_namlen as usize],
267 )?;
268
269 dir_entry_offset += de.e2d_reclen.get() as u64;
270
271 if de.e2d_ino.get() != 0 {
272 entries.push(de);
273 }
274 }
275 }
276 Ok(())
277 }
278
279 fn iterate_extents_in_leaf<B: SplitByteSlice, F: FnMut(&Extent) -> Result<(), ParsingError>>(
281 &self,
282 extent_tree_node: &ExtentTreeNode<B>,
283 extent_handler: &mut F,
284 ) -> Result<(), ParsingError> {
285 for e_index in 0..extent_tree_node.header.eh_ecount.get() {
286 let start = size_of::<Extent>() * e_index as usize;
287 let end = start + size_of::<Extent>() as usize;
288 let e = Extent::to_struct_ref(
289 &(extent_tree_node.entries)[start..end],
290 ParsingError::InvalidExtent(start as u64),
291 )?;
292
293 extent_handler(e)?;
294 }
295
296 Ok(())
297 }
298
299 fn iterate_extents_in_tree<B: SplitByteSlice, F: FnMut(&Extent) -> Result<(), ParsingError>>(
301 &self,
302 extent_tree_node: &ExtentTreeNode<B>,
303 extent_handler: &mut F,
304 ) -> Result<(), ParsingError> {
305 let block_size = self.block_size()?;
306
307 match extent_tree_node.header.eh_depth.get() {
308 0 => {
309 self.iterate_extents_in_leaf(extent_tree_node, extent_handler)?;
310 }
311 1..=4 => {
312 for e_index in 0..extent_tree_node.header.eh_ecount.get() {
313 let start: usize = size_of::<Extent>() * e_index as usize;
314 let end = start + size_of::<Extent>();
315 let e = ExtentIndex::to_struct_ref(
316 &(extent_tree_node.entries)[start..end],
317 ParsingError::InvalidExtent(start as u64),
318 )?;
319
320 let next_level_offset = e.target_block_num() as u64 * block_size;
321
322 let next_extent_header =
323 ExtentHeader::from_reader_with_offset(&self.reader, next_level_offset)?;
324
325 let entry_count = next_extent_header.eh_ecount.get() as usize;
326 let entry_size = match next_extent_header.eh_depth.get() {
327 0 => size_of::<Extent>(),
328 _ => size_of::<ExtentIndex>(),
329 };
330 let node_size = size_of::<ExtentHeader>() + (entry_count * entry_size);
331
332 let mut data = vec![0u8; node_size];
333 self.reader.read(next_level_offset, data.as_mut_slice())?;
334
335 let next_level_node = ExtentTreeNode::parse(data.as_slice())
336 .ok_or(ParsingError::InvalidExtent(next_level_offset))?;
337
338 self.iterate_extents_in_tree(&next_level_node, extent_handler)?;
339 }
340 }
341 _ => return Err(ParsingError::InvalidExtentHeader),
342 };
343
344 Ok(())
345 }
346
347 pub fn entries_from_inode(&self, inode: &INode) -> Result<Vec<DirEntry2>, ParsingError> {
351 let root_extent_tree_node = inode.extent_tree_node()?;
352 let mut dir_entries = Vec::new();
353
354 self.iterate_extents_in_tree(&root_extent_tree_node, &mut |extent| {
355 self.read_dir_entries(extent, &mut dir_entries)
356 })?;
357
358 Ok(dir_entries)
359 }
360
361 pub fn entry_at_path(&self, path: &Path) -> Result<DirEntry2, ParsingError> {
367 let root_inode = self.root_inode()?;
368 let root_entries = self.entries_from_inode(&root_inode)?;
369 let mut entry_map = DirEntry2::as_hash_map(root_entries)?;
370
371 let mut components = path.components().peekable();
372 let mut component = components.next();
373
374 while component != None {
375 match component {
376 Some(Component::RootDir) => {
377 }
379 Some(Component::Normal(name)) => {
380 let name = name.to_str().ok_or(ParsingError::InvalidInputPath)?;
381 if let Some(entry) = entry_map.remove(name) {
382 if components.peek() == None {
383 return Ok(entry);
384 }
385 match EntryType::from_u8(entry.e2d_type)? {
386 EntryType::Directory => {
387 let inode = self.inode(entry.e2d_ino.get())?;
388 entry_map =
389 DirEntry2::as_hash_map(self.entries_from_inode(&inode)?)?;
390 }
391 _ => {
392 break;
393 }
394 }
395 }
396 }
397 _ => {
398 break;
399 }
400 }
401 component = components.next();
402 }
403
404 match path.to_str() {
405 Some(s) => Err(ParsingError::PathNotFound(s.to_string())),
406 None => Err(ParsingError::PathNotFound(
407 "Bad path - was not able to convert into string".to_string(),
408 )),
409 }
410 }
411
412 pub fn read_data(&self, inode_num: u32) -> Result<Vec<u8>, ParsingError> {
417 let inode = self.inode(inode_num)?;
418 let mut size_remaining = inode.size();
419 let mut data = Vec::with_capacity(size_remaining.try_into().unwrap());
420
421 if u16::from(inode.e2di_mode) & 0xa000 != 0 && u32::from(inode.e2di_nblock) == 0 {
423 data.extend_from_slice(&inode.e2di_blocks[..inode.size().try_into().unwrap()]);
424 return Ok(data);
425 }
426
427 let root_extent_tree_node = inode.extent_tree_node()?;
428 let mut extents = Vec::new();
429
430 self.iterate_extents_in_tree(&root_extent_tree_node, &mut |extent| {
431 extents.push(extent.clone());
432 Ok(())
433 })?;
434
435 let block_size = self.block_size()?;
436
437 for extent in extents {
443 let buffer_offset = extent.e_blk.get() as u64 * block_size;
444
445 if buffer_offset > data.len() as u64 {
448 size_remaining -= buffer_offset - data.len() as u64;
449 data.resize(buffer_offset.try_into().unwrap(), 0);
450 }
451
452 self.read_extent_data(&extent, &mut data, &mut size_remaining)?;
453 }
454
455 data.resize(inode.size().try_into().unwrap(), 0);
459 Ok(data)
460 }
461
462 pub fn index<R>(
474 &self,
475 inode: INode,
476 prefix: Vec<&str>,
477 receiver: &mut R,
478 ) -> Result<bool, ParsingError>
479 where
480 R: FnMut(&Parser, Vec<&str>, &DirEntry2) -> Result<bool, ParsingError>,
481 {
482 let entries = self.entries_from_inode(&inode)?;
483 for entry in entries {
484 let entry_name = entry.name()?;
485 if entry_name == "." || entry_name == ".." {
486 continue;
487 }
488 let mut name = Vec::new();
489 name.append(&mut prefix.clone());
490 name.push(entry_name);
491 if !receiver(self, name.clone(), &entry)? {
492 return Ok(false);
493 }
494 if EntryType::from_u8(entry.e2d_type)? == EntryType::Directory {
495 let inode = self.inode(entry.e2d_ino.get())?;
496 if !self.index(inode, name, receiver)? {
497 return Ok(false);
498 }
499 }
500 }
501
502 Ok(true)
503 }
504
505 pub fn inode_xattrs(&self, inode_number: u32) -> Result<XattrMap, ParsingError> {
507 let mut xattrs = BTreeMap::new();
508
509 let inode_addr = self.inode_addr(inode_number).expect("Couldn't get inode address");
510 let inode =
511 INode::from_reader_with_offset(&self.reader, inode_addr).expect("Failed reader");
512
513 let sb = self.super_block().expect("No super block for inode");
514 let xattr_magic_addr = inode_addr
515 + MINIMUM_INODE_SIZE
516 + u64::from(inode.e4di_extra_isize(sb).unwrap_or_default());
517
518 let mut magic = LEU32::ZERO;
519 self.reader.read(xattr_magic_addr, magic.as_mut_bytes()).expect("Failed to read xattr");
520 if magic.get() == Self::XATTR_MAGIC {
521 let first_entry = xattr_magic_addr + size_of_val(&magic) as u64;
522 self.read_xattr_entries_from_inode(
523 first_entry,
524 inode_addr + (sb.e2fs_inode_size.get() as u64),
525 &mut xattrs,
526 )?;
527 }
528
529 let block_number: u64 = inode.facl();
530 if block_number > 0 {
531 let block = self.block(block_number).expect("Couldn't find block");
532 Self::read_xattr_entries_from_block(&block, &mut xattrs)?;
533 }
534
535 Ok(xattrs)
536 }
537
538 const XATTR_ALIGNMENT: u64 = 4;
539 const XATTR_MAGIC: u32 = 0xea020000;
540
541 fn round_up_to_align(x: u64, align: u64) -> u64 {
542 let spare = x % align;
543 if spare > 0 {
544 x.checked_add(align - spare).expect("Overflow when aligning")
545 } else {
546 x
547 }
548 }
549
550 fn is_valid_xattr_entry_header(header: &XattrEntryHeader) -> bool {
551 !(header.e_name_len == 0
552 && header.e_name_index == 0
553 && header.e_value_offs.get() == 0
554 && header.e_value_inum.get() == 0)
555 }
556
557 fn xattr_prefix_for_name_index(header: &XattrEntryHeader) -> Vec<u8> {
558 match header.e_name_index {
559 1 => b"user.".to_vec(),
560 2 => b"system.posix_acl_access.".to_vec(),
561 3 => b"system.posix_acl_default.".to_vec(),
562 4 => b"trusted.".to_vec(),
563 6 => b"security.".to_vec(),
564 7 => b"system.".to_vec(),
565 8 => b"system.richacl".to_vec(),
566 _ => b"".to_vec(),
567 }
568 }
569
570 fn read_xattr_entries_from_inode(
572 &self,
573 mut entries_addr: u64,
574 inode_end: u64,
575 xattrs: &mut XattrMap,
576 ) -> Result<(), ParsingError> {
577 let value_base_addr = entries_addr;
578 while entries_addr + (std::mem::size_of::<XattrEntryHeader>() as u64) < inode_end {
579 let head = XattrEntryHeader::from_reader_with_offset(&self.reader, entries_addr)?;
580 if !Self::is_valid_xattr_entry_header(&head) {
581 break;
582 }
583
584 let prefix = Self::xattr_prefix_for_name_index(&head);
585 let mut name = Vec::with_capacity(prefix.len() + head.e_name_len as usize);
586 name.extend_from_slice(&prefix);
587 name.resize(prefix.len() + head.e_name_len as usize, 0);
588
589 self.reader.read(
590 entries_addr + size_of::<XattrEntryHeader>() as u64,
591 &mut name[prefix.len()..],
592 )?;
593
594 let mut value = vec![0u8; head.e_value_size.get() as usize];
595 self.reader.read(value_base_addr + u64::from(head.e_value_offs), &mut value)?;
596 xattrs.insert(name, value);
597
598 entries_addr += size_of::<XattrEntryHeader>() as u64 + head.e_name_len as u64;
599 entries_addr = Self::round_up_to_align(entries_addr, Self::XATTR_ALIGNMENT);
600 }
601 Ok(())
602 }
603
604 fn read_xattr_entries_from_block(
606 block: &[u8],
607 xattrs: &mut XattrMap,
608 ) -> Result<(), ParsingError> {
609 let head = XattrHeader::to_struct_ref(
610 &block[..std::mem::size_of::<XattrHeader>()],
611 ParsingError::Incompatible("Invalid XattrHeader".to_string()),
612 )?;
613
614 if head.e_magic.get() != Self::XATTR_MAGIC {
615 return Ok(());
616 }
617
618 let mut offset = Self::round_up_to_align(
619 std::mem::size_of::<XattrHeader>() as u64,
620 Self::XATTR_ALIGNMENT * 2,
621 ) as usize;
622
623 while offset + std::mem::size_of::<XattrEntryHeader>() < block.len() {
624 let head = XattrEntryHeader::to_struct_ref(
625 &block[offset..offset + std::mem::size_of::<XattrEntryHeader>()],
626 ParsingError::Incompatible("Invalid XattrEntryHeader".to_string()),
627 )?;
628
629 if !Self::is_valid_xattr_entry_header(&head) {
630 break;
631 }
632
633 let name_start = offset + std::mem::size_of::<XattrEntryHeader>();
634 let name_end = name_start + head.e_name_len as usize;
635 let mut name = Self::xattr_prefix_for_name_index(&head);
636 name.extend_from_slice(&block[name_start..name_end]);
637
638 let value_start = head.e_value_offs.get() as usize;
639 let value_end = value_start + head.e_value_size.get() as usize;
640 let value = block[value_start..value_end].to_vec();
641 xattrs.insert(name, value);
642
643 offset = Self::round_up_to_align(name_end as u64, 4) as usize;
644 }
645
646 Ok(())
647 }
648
649 #[cfg(target_os = "fuchsia")]
651 pub fn build_fuchsia_tree(
652 &self,
653 ) -> Result<std::sync::Arc<vfs::directory::immutable::Simple>, ParsingError> {
654 use vfs::file::vmo::read_only;
655 use vfs::tree_builder::TreeBuilder;
656
657 let root_inode = self.root_inode()?;
658 let mut tree = TreeBuilder::empty_dir();
659
660 self.index(root_inode, Vec::new(), &mut |my_self, path, entry| {
661 let entry_type = EntryType::from_u8(entry.e2d_type)?;
662 match entry_type {
663 EntryType::RegularFile => {
664 let data = my_self.read_data(entry.e2d_ino.into())?;
665 tree.add_entry(path.clone(), read_only(data))
666 .map_err(|_| ParsingError::BadFile(path.join("/")))?;
667 }
668 EntryType::Directory => {
669 tree.add_empty_dir(path.clone())
670 .map_err(|_| ParsingError::BadDirectory(path.join("/")))?;
671 }
672 _ => {
673 }
675 }
676 Ok(true)
677 })?;
678
679 Ok(tree.build())
680 }
681}
682
683#[cfg(test)]
684mod tests {
685 use crate::parser::Parser;
686 use crate::readers::VecReader;
687 use crate::structs::EntryType;
688 use maplit::hashmap;
689 use sha2::{Digest, Sha256};
690 use std::collections::{HashMap, HashSet};
691 use std::path::Path;
692 use std::{fs, str};
693 use test_case::test_case;
694
695 #[fuchsia::test]
696 fn list_root_1_file() {
697 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
698 let parser = Parser::new(Box::new(VecReader::new(data)));
699 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
700 let root_inode = parser.root_inode().expect("Parse INode");
701 let entries = parser.entries_from_inode(&root_inode).expect("List entries");
702 let mut expected_entries = vec!["file1", "lost+found", "..", "."];
703
704 for de in &entries {
705 assert_eq!(expected_entries.pop().unwrap(), de.name().unwrap());
706 }
707 assert_eq!(expected_entries.len(), 0);
708 }
709
710 #[test_case(
711 "/pkg/data/nest.img",
712 vec!["inner", "file1", "lost+found", "..", "."];
713 "fs with a single directory")]
714 #[test_case(
715 "/pkg/data/extents.img",
716 vec!["trailingzeropages", "a", "smallfile", "largefile", "sparsefile", "lost+found", "..", "."];
717 "fs with multiple files with multiple extents")]
718 fn list_root(ext4_path: &str, mut expected_entries: Vec<&str>) {
719 let data = fs::read(ext4_path).expect("Unable to read file");
720 let parser = Parser::new(Box::new(VecReader::new(data)));
721 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
722 let root_inode = parser.root_inode().expect("Parse INode");
723 let entries = parser.entries_from_inode(&root_inode).expect("List entries");
724
725 for de in &entries {
726 assert_eq!(expected_entries.pop().unwrap(), de.name().unwrap());
727 }
728 assert_eq!(expected_entries.len(), 0);
729 }
730
731 #[fuchsia::test]
732 fn get_from_path() {
733 let data = fs::read("/pkg/data/nest.img").expect("Unable to read file");
734 let parser = Parser::new(Box::new(VecReader::new(data)));
735 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
736
737 let entry = parser.entry_at_path(Path::new("/inner")).expect("Entry at path");
738 assert_eq!(entry.e2d_ino.get(), 12);
739 assert_eq!(entry.name().unwrap(), "inner");
740
741 let entry = parser.entry_at_path(Path::new("/inner/file2")).expect("Entry at path");
742 assert_eq!(entry.e2d_ino.get(), 17);
743 assert_eq!(entry.name().unwrap(), "file2");
744 }
745
746 #[fuchsia::test]
747 fn read_data() {
748 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
749 let parser = Parser::new(Box::new(VecReader::new(data)));
750 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
751
752 let entry = parser.entry_at_path(Path::new("file1")).expect("Entry at path");
753 assert_eq!(entry.e2d_ino.get(), 15);
754 assert_eq!(entry.name().unwrap(), "file1");
755
756 let data = parser.read_data(entry.e2d_ino.into()).expect("File data");
757 let compare = "file1 contents.\n";
758 assert_eq!(data.len(), compare.len());
759 assert_eq!(str::from_utf8(data.as_slice()).expect("File data"), compare);
760 }
761
762 #[fuchsia::test]
763 fn fail_inode_zero() {
764 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
765 let parser = Parser::new(Box::new(VecReader::new(data)));
766 assert!(parser.inode(0).is_err());
767 }
768
769 #[fuchsia::test]
770 fn index() {
771 let data = fs::read("/pkg/data/nest.img").expect("Unable to read file");
772 let parser = Parser::new(Box::new(VecReader::new(data)));
773 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
774
775 let mut count = 0;
776 let mut entries: HashSet<u32> = HashSet::new();
777 let root_inode = parser.root_inode().expect("Root inode");
778
779 parser
780 .index(root_inode, Vec::new(), &mut |_, _, entry| {
781 count += 1;
782
783 assert_ne!(entries.contains(&entry.e2d_ino.get()), true);
785 entries.insert(entry.e2d_ino.get());
786
787 Ok(true)
788 })
789 .expect("Index");
790
791 assert_eq!(count, 4);
792 }
793
794 #[fuchsia::test]
795 fn xattr() {
796 let data = fs::read("/pkg/data/xattr.img").expect("Unable to read file");
797 let parser = Parser::new(Box::new(VecReader::new(data)));
798 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
799 let root_inode = parser.root_inode().expect("Root inode");
800 let mut found_files = HashSet::new();
801
802 parser
803 .index(root_inode, Vec::new(), &mut |_, _, entry| {
804 let name = entry.e2d_name;
805 let inode = entry.e2d_ino.get();
806 let attributes = parser.inode_xattrs(inode).expect("Extended attributes");
807 match name {
808 name if &name[0..10] == b"lost+found" => {
809 assert_eq!(attributes.len(), 0);
810 found_files.insert("lost+found");
811 }
812 name if &name[0..5] == b"file1" => {
813 assert_eq!(attributes.len(), 1);
814 assert_eq!(attributes[&b"user.test".to_vec()], b"test value".to_vec());
815 found_files.insert("file1");
816 }
817 name if &name[0..9] == b"file_many" => {
818 assert_eq!(attributes.len(), 6);
819 assert_eq!(
820 attributes[&b"user.long".to_vec()],
821 b"vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv".to_vec()
822 );
823 found_files.insert("file_many");
824 }
825 name if &name[0..6] == b"subdir" => {
826 assert_eq!(attributes.len(), 1);
827 assert_eq!(attributes[&b"user.type".to_vec()], b"dir".to_vec());
828 found_files.insert("subdir");
829 }
830 name if &name[0..5] == b"file2" => {
831 assert_eq!(attributes.len(), 2);
832 assert_eq!(
833 attributes[&b"user.test_one".to_vec()],
834 b"test value 1".to_vec()
835 );
836 assert_eq!(
837 attributes[&b"user.test_two".to_vec()],
838 b"test value 2".to_vec()
839 );
840 found_files.insert("file2");
841 }
842 _ => {}
843 }
844 Ok(true)
845 })
846 .expect("Index");
847
848 assert_eq!(found_files.len(), 5);
849 }
850
851 #[test_case(
852 "/pkg/data/extents.img",
853 hashmap!{
854 "largefile".to_string() => "de2cf635ae4e0e727f1e412f978001d6a70d2386dc798d4327ec8c77a8e4895d".to_string(),
855 "smallfile".to_string() => "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03".to_string(),
856 "sparsefile".to_string() => "3f411e42c1417cd8845d7144679812be3e120318d843c8c6e66d8b2c47a700e9".to_string(),
857 "trailingzeropages".to_string() => "afc5cc689fd3cb8d00c147d60dc911a70d36b7afb03cc7f15de9c78a52be978d".to_string(),
858 "a/multi/dir/path/within/this/crowded/extents/test/img/empty".to_string() => "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string(),
859 },
860 vec!["a/multi/dir/path/within/this/crowded/extents/test/img", "lost+found"];
861 "fs with multiple files with multiple extents")]
862 #[test_case(
863 "/pkg/data/1file.img",
864 hashmap!{
865 "file1".to_string() => "6bc35bfb2ca96c75a1fecde205693c19a827d4b04e90ace330048f3e031487dd".to_string(),
866 },
867 vec!["lost+found"];
868 "fs with one small file")]
869 #[test_case(
870 "/pkg/data/nest.img",
871 hashmap!{
872 "file1".to_string() => "6bc35bfb2ca96c75a1fecde205693c19a827d4b04e90ace330048f3e031487dd".to_string(),
873 "inner/file2".to_string() => "215ca145cbac95c9e2a6f5ff91ca1887c837b18e5f58fd2a7a16e2e5a3901e10".to_string(),
874 },
875 vec!["inner", "lost+found"];
876 "fs with a single directory")]
877 #[test_case(
878 "/pkg/data/longdir.img",
879 {
880 let mut hash = HashMap::new();
881 for i in 1..=1000 {
882 hash.insert(i.to_string(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string());
883 }
884 hash
885 },
886 vec!["lost+found"];
887 "fs with many entries in a directory")]
888 fn check_data(
889 ext4_path: &str,
890 mut file_hashes: HashMap<String, String>,
891 expected_dirs: Vec<&str>,
892 ) {
893 let data = fs::read(ext4_path).expect("Unable to read file");
894 let parser = Parser::new(Box::new(VecReader::new(data)));
895 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
896
897 let root_inode = parser.root_inode().expect("Root inode");
898
899 parser
900 .index(root_inode, Vec::new(), &mut |my_self, path, entry| {
901 let entry_type = EntryType::from_u8(entry.e2d_type).expect("Entry Type");
902 let file_path = path.join("/");
903
904 match entry_type {
905 EntryType::RegularFile => {
906 let data = my_self.read_data(entry.e2d_ino.into()).expect("File data");
907
908 let mut hasher = Sha256::new();
909 hasher.update(&data);
910 assert_eq!(
911 file_hashes.remove(&file_path).unwrap(),
912 hex::encode(hasher.finalize())
913 );
914 }
915 EntryType::Directory => {
916 let mut found = false;
917
918 for expected_dir in expected_dirs.iter() {
920 if expected_dir.starts_with(&file_path) {
921 found = true;
922 break;
923 }
924 }
925 assert!(found, "Unexpected path {}", file_path);
926 }
927 _ => {
928 assert!(false, "No other types should exist in this image.");
929 }
930 }
931 Ok(true)
932 })
933 .expect("Index");
934 assert!(file_hashes.is_empty(), "Expected files were not found {:?}", file_hashes);
935 }
936}