1use crate::readers::Reader;
36use crate::structs::{
37 BlockGroupDesc32, BlockGroupDesc64, DirEntry2, DirEntryHeader, EntryType, Extent, ExtentHeader,
38 ExtentIndex, ExtentTreeNode, FIRST_BG_PADDING, INode, InvalidAddressErrorType, MIN_EXT4_SIZE,
39 MINIMUM_INODE_SIZE, ParseToStruct, ParsingError, ROOT_INODE_NUM, SuperBlock, XattrEntryHeader,
40 XattrHeader,
41};
42use once_cell::sync::OnceCell;
43use std::collections::BTreeMap;
44use std::mem::{size_of, size_of_val};
45use std::path::{Component, Path};
46use std::str;
47use zerocopy::byteorder::little_endian::U32 as LEU32;
48use zerocopy::{IntoBytes, SplitByteSlice};
49
50assert_eq_size!(u64, usize);
52
53pub struct Parser {
54 reader: Box<dyn Reader>,
55 super_block: OnceCell<SuperBlock>,
56}
57
58pub type XattrMap = BTreeMap<Vec<u8>, Vec<u8>>;
59
60pub trait DataWriter {
62 fn reserve(&mut self, size: u64) -> Result<(), ParsingError>;
66
67 fn append(&mut self, bytes: &[u8]) -> Result<(), ParsingError>;
72
73 fn pad_to(&mut self, target_size: u64) -> Result<(), ParsingError>;
79}
80
81impl DataWriter for Vec<u8> {
82 fn reserve(&mut self, size: u64) -> Result<(), ParsingError> {
83 let size: usize = size
84 .try_into()
85 .map_err(|_| ParsingError::Incompatible("Target size out of bounds".to_string()))?;
86 self.reserve(size);
87 Ok(())
88 }
89
90 fn append(&mut self, bytes: &[u8]) -> Result<(), ParsingError> {
91 self.extend_from_slice(bytes);
92 Ok(())
93 }
94
95 fn pad_to(&mut self, target_size: u64) -> Result<(), ParsingError> {
96 let target_size: usize = target_size
97 .try_into()
98 .map_err(|_| ParsingError::Incompatible("Target size out of bounds".to_string()))?;
99 let len = self.len();
100 if len > target_size {
101 return Err(ParsingError::NotSupported(format!(
102 "invalid target size {target_size} for {len}"
103 )));
104 }
105 self.resize(target_size, 0);
106 Ok(())
107 }
108}
109
110#[cfg(target_os = "fuchsia")]
111pub struct VmoWriter {
112 vmo: zx::Vmo,
113 stream: zx::Stream,
114}
115
116#[cfg(target_os = "fuchsia")]
117impl VmoWriter {
118 pub fn new(size: u64) -> Result<Self, zx::Status> {
120 let vmo = zx::Vmo::create(size)?;
121 vmo.set_stream_size(0)?;
122 let stream = zx::Stream::create(
123 zx::StreamOptions::MODE_APPEND | zx::StreamOptions::MODE_WRITE,
124 &vmo,
125 0,
126 )?;
127 Ok(Self { vmo, stream })
128 }
129
130 pub fn into_vmo(self) -> zx::Vmo {
136 self.vmo
137 }
138}
139
140#[cfg(target_os = "fuchsia")]
141impl DataWriter for VmoWriter {
142 fn reserve(&mut self, size: u64) -> Result<(), ParsingError> {
143 if size > self.vmo.get_size().map_err(ParsingError::VmoError)? {
144 return Err(ParsingError::VmoError(zx::Status::NOT_SUPPORTED));
145 }
146 Ok(())
147 }
148
149 fn append(&mut self, bytes: &[u8]) -> Result<(), ParsingError> {
150 let written = self
151 .stream
152 .write(zx::StreamWriteOptions::empty(), bytes)
153 .map_err(ParsingError::VmoError)?;
154 if written != bytes.len() {
155 return Err(ParsingError::VmoError(zx::Status::IO_REFUSED));
156 }
157 Ok(())
158 }
159
160 fn pad_to(&mut self, target_size: u64) -> Result<(), ParsingError> {
161 let cur = self.vmo.get_stream_size().map_err(ParsingError::VmoError)?;
162 if cur > target_size {
163 return Err(ParsingError::NotSupported(format!(
164 "invalid target size {target_size} for {cur}"
165 )));
166 }
167 self.vmo.set_stream_size(target_size).map_err(ParsingError::VmoError)?;
170 Ok(())
171 }
172}
173
174enum BlockGroupDescriptor {
176 BGD32(BlockGroupDesc32),
177 BGD64(BlockGroupDesc64),
178}
179
180impl BlockGroupDescriptor {
181 fn inode_table_block(&self) -> u64 {
182 match self {
183 Self::BGD32(bgd) => u64::from(bgd.ext2bgd_i_tables),
184 Self::BGD64(bgd) => {
185 u64::from(bgd.base.ext2bgd_i_tables) + (u64::from(bgd.ext4bgd_i_tables_hi) << 32)
186 }
187 }
188 }
189}
190
191impl Parser {
199 pub fn new(reader: Box<dyn Reader>) -> Self {
200 Parser { reader, super_block: OnceCell::new() }
201 }
202
203 fn super_block(&self) -> Result<&SuperBlock, ParsingError> {
211 self.super_block.get_or_try_init(|| SuperBlock::parse(&self.reader))
212 }
213
214 pub fn block_size(&self) -> Result<u64, ParsingError> {
216 self.super_block()?.block_size()
217 }
218
219 pub(crate) fn block(&self, block_number: u64) -> Result<Box<[u8]>, ParsingError> {
221 if block_number == 0 {
222 return Err(ParsingError::InvalidAddress(
223 InvalidAddressErrorType::Lower,
224 0,
225 FIRST_BG_PADDING,
226 ));
227 }
228 let block_size = self.block_size()?;
229 let address = block_number
230 .checked_mul(block_size)
231 .ok_or(ParsingError::BlockNumberOutOfBounds(block_number))?;
232
233 let mut data = vec![0u8; block_size.try_into().unwrap()];
234 self.reader.read(address, data.as_mut_slice()).map_err(Into::<ParsingError>::into)?;
235
236 Ok(data.into_boxed_slice())
237 }
238
239 pub(crate) fn inode_addr(&self, inode_number: u32) -> Result<u64, ParsingError> {
241 if inode_number < 1 {
242 return Err(ParsingError::InvalidInode(inode_number));
244 }
245 let sb = self.super_block()?;
246 let block_size = self.block_size()?;
247
248 let bgd_table_offset = if block_size >= MIN_EXT4_SIZE {
259 block_size
262 } else {
263 block_size * 2
266 };
267
268 let bgd_offset = (inode_number - 1) as u64 / sb.e2fs_ipg.get() as u64
269 * sb.block_group_descriptor_size() as u64;
270 let bgd = if sb.is_64bit() {
271 BlockGroupDescriptor::BGD64(BlockGroupDesc64::from_reader_with_offset(
272 &self.reader,
273 bgd_table_offset + bgd_offset,
274 )?)
275 } else {
276 BlockGroupDescriptor::BGD32(BlockGroupDesc32::from_reader_with_offset(
277 &self.reader,
278 bgd_table_offset + bgd_offset,
279 )?)
280 };
281
282 let inode_table_offset =
285 (inode_number - 1) as u64 % sb.e2fs_ipg.get() as u64 * sb.e2fs_inode_size.get() as u64;
286 let inode_addr = (bgd.inode_table_block() * block_size) + inode_table_offset;
287 if inode_addr < MIN_EXT4_SIZE {
288 return Err(ParsingError::InvalidAddress(
289 InvalidAddressErrorType::Lower,
290 inode_addr,
291 MIN_EXT4_SIZE,
292 ));
293 }
294 Ok(inode_addr)
295 }
296
297 pub fn inode(&self, inode_number: u32) -> Result<INode, ParsingError> {
299 INode::from_reader_with_offset(&self.reader, self.inode_addr(inode_number)?)
300 }
301
302 pub fn root_inode(&self) -> Result<INode, ParsingError> {
304 self.inode(ROOT_INODE_NUM)
305 }
306
307 fn extent_data<W: DataWriter>(
309 &self,
310 extent: &Extent,
311 writer: &mut W,
312 mut allowance: u64,
313 ) -> Result<u64, ParsingError> {
314 let block_number = extent.target_block_num();
315 let block_count = extent.e_len.get() as u64;
316 let block_size = self.block_size()?;
317 let mut read_len;
318
319 writer.reserve(block_size * block_count)?;
320 let mut total_written = 0u64;
321
322 for i in 0..block_count {
323 let block_data = self.block(block_number + i as u64)?;
324 if allowance >= block_size {
325 read_len = block_size;
326 } else {
327 read_len = allowance;
328 }
329 let slice = &block_data[0..read_len.try_into().unwrap()];
330 writer.append(slice)?;
331 total_written += read_len;
332 allowance -= read_len;
333 }
334
335 Ok(total_written)
336 }
337
338 pub fn read_extents(&self, inode_num: u32) -> Result<(u64, Vec<Extent>), ParsingError> {
341 let inode = self.inode(inode_num)?;
342
343 const IFMT: u16 = 0xf000;
345 const IFREG: u16 = 0x8000;
346 if u16::from(inode.e2di_mode) & IFMT != IFREG {
347 return Err(ParsingError::NotFile);
348 }
349
350 let root_extent_tree_node = inode.extent_tree_node()?;
351 let mut extents = Vec::new();
352
353 self.iterate_extents_in_tree(&root_extent_tree_node, &mut |extent| {
354 extents.push(extent.clone());
355 Ok(())
356 })?;
357
358 Ok((inode.size(), extents))
359 }
360
361 fn read_extent_data<W: DataWriter>(
370 &self,
371 extent: &Extent,
372 writer: &mut W,
373 allowance: &mut u64,
374 ) -> Result<(), ParsingError> {
375 let extent_len = self.extent_data(extent, writer, *allowance)?;
376 if extent_len > *allowance {
377 return Err(ParsingError::ExtentUnexpectedLength(extent_len, *allowance));
378 }
379 *allowance -= extent_len;
380 Ok(())
381 }
382
383 fn read_dir_entries(
385 &self,
386 extent: &Extent,
387 entries: &mut Vec<DirEntry2>,
388 ) -> Result<(), ParsingError> {
389 let block_size = self.block_size()?;
390 let target_block_offset = extent.target_block_num() * block_size;
391
392 for block_index in 0..extent.e_len.get() {
395 let mut dir_entry_offset = 0u64;
396 while (dir_entry_offset + size_of::<DirEntryHeader>() as u64) < block_size {
397 let offset =
398 dir_entry_offset + target_block_offset + (block_index as u64 * block_size);
399
400 let de_header = DirEntryHeader::from_reader_with_offset(&self.reader, offset)?;
401 let mut de = DirEntry2 {
402 e2d_ino: de_header.e2d_ino,
403 e2d_reclen: de_header.e2d_reclen,
404 e2d_namlen: de_header.e2d_namlen,
405 e2d_type: de_header.e2d_type,
406 e2d_name: [0u8; 255],
407 };
408 self.reader.read(
409 offset + size_of::<DirEntryHeader>() as u64,
410 &mut de.e2d_name[..de.e2d_namlen as usize],
411 )?;
412
413 dir_entry_offset += de.e2d_reclen.get() as u64;
414
415 if de.e2d_ino.get() != 0 {
416 entries.push(de);
417 }
418 }
419 }
420 Ok(())
421 }
422
423 fn iterate_extents_in_leaf<B: SplitByteSlice, F: FnMut(&Extent) -> Result<(), ParsingError>>(
425 &self,
426 extent_tree_node: &ExtentTreeNode<B>,
427 extent_handler: &mut F,
428 ) -> Result<(), ParsingError> {
429 for e_index in 0..extent_tree_node.header.eh_ecount.get() {
430 let start = size_of::<Extent>() * e_index as usize;
431 let end = start + size_of::<Extent>() as usize;
432 let e = Extent::to_struct_ref(
433 &(extent_tree_node.entries)[start..end],
434 ParsingError::InvalidExtent(start as u64),
435 )?;
436
437 extent_handler(e)?;
438 }
439
440 Ok(())
441 }
442
443 pub(crate) fn iterate_extents_in_tree<
445 B: SplitByteSlice,
446 F: FnMut(&Extent) -> Result<(), ParsingError>,
447 >(
448 &self,
449 extent_tree_node: &ExtentTreeNode<B>,
450 extent_handler: &mut F,
451 ) -> Result<(), ParsingError> {
452 let block_size = self.block_size()?;
453
454 match extent_tree_node.header.eh_depth.get() {
455 0 => {
456 self.iterate_extents_in_leaf(extent_tree_node, extent_handler)?;
457 }
458 1..=4 => {
459 for e_index in 0..extent_tree_node.header.eh_ecount.get() {
460 let start: usize = size_of::<Extent>() * e_index as usize;
461 let end = start + size_of::<Extent>();
462 let e = ExtentIndex::to_struct_ref(
463 &(extent_tree_node.entries)[start..end],
464 ParsingError::InvalidExtent(start as u64),
465 )?;
466
467 let next_level_offset = e.target_block_num() as u64 * block_size;
468
469 let next_extent_header =
470 ExtentHeader::from_reader_with_offset(&self.reader, next_level_offset)?;
471
472 let entry_count = next_extent_header.eh_ecount.get() as usize;
473 let entry_size = match next_extent_header.eh_depth.get() {
474 0 => size_of::<Extent>(),
475 _ => size_of::<ExtentIndex>(),
476 };
477 let node_size = size_of::<ExtentHeader>() + (entry_count * entry_size);
478
479 let mut data = vec![0u8; node_size];
480 self.reader.read(next_level_offset, data.as_mut_slice())?;
481
482 let next_level_node = ExtentTreeNode::parse(data.as_slice())
483 .ok_or(ParsingError::InvalidExtent(next_level_offset))?;
484
485 self.iterate_extents_in_tree(&next_level_node, extent_handler)?;
486 }
487 }
488 _ => return Err(ParsingError::InvalidExtentHeader),
489 };
490
491 Ok(())
492 }
493
494 pub fn entries_from_inode(&self, inode: &INode) -> Result<Vec<DirEntry2>, ParsingError> {
498 let root_extent_tree_node = inode.extent_tree_node()?;
499 let mut dir_entries = Vec::new();
500
501 self.iterate_extents_in_tree(&root_extent_tree_node, &mut |extent| {
502 self.read_dir_entries(extent, &mut dir_entries)
503 })?;
504
505 Ok(dir_entries)
506 }
507
508 pub fn entry_at_path(&self, path: &Path) -> Result<DirEntry2, ParsingError> {
514 let root_inode = self.root_inode()?;
515 let root_entries = self.entries_from_inode(&root_inode)?;
516 let mut entry_map = DirEntry2::as_hash_map(root_entries)?;
517
518 let mut components = path.components().peekable();
519 let mut component = components.next();
520
521 while component != None {
522 match component {
523 Some(Component::RootDir) => {
524 }
526 Some(Component::Normal(name)) => {
527 let name = name.to_str().ok_or(ParsingError::InvalidInputPath)?;
528 if let Some(entry) = entry_map.remove(name) {
529 if components.peek() == None {
530 return Ok(entry);
531 }
532 match EntryType::from_u8(entry.e2d_type)? {
533 EntryType::Directory => {
534 let inode = self.inode(entry.e2d_ino.get())?;
535 entry_map =
536 DirEntry2::as_hash_map(self.entries_from_inode(&inode)?)?;
537 }
538 _ => {
539 break;
540 }
541 }
542 }
543 }
544 _ => {
545 break;
546 }
547 }
548 component = components.next();
549 }
550
551 match path.to_str() {
552 Some(s) => Err(ParsingError::PathNotFound(s.to_string())),
553 None => Err(ParsingError::PathNotFound(
554 "Bad path - was not able to convert into string".to_string(),
555 )),
556 }
557 }
558
559 pub fn read_data_into<W: DataWriter>(
564 &self,
565 inode_num: u32,
566 writer: &mut W,
567 ) -> Result<(), ParsingError> {
568 let inode = self.inode(inode_num)?;
569 let mut size_remaining = inode.size();
570 writer.reserve(size_remaining.try_into().unwrap())?;
571
572 if u16::from(inode.e2di_mode) & 0xa000 != 0 && u32::from(inode.e2di_nblock) == 0 {
574 let inline_data = &inode.e2di_blocks[..inode.size().try_into().unwrap()];
575 writer.append(inline_data)?;
576 return Ok(());
577 }
578
579 let root_extent_tree_node = inode.extent_tree_node()?;
580 let mut extents = Vec::new();
581
582 self.iterate_extents_in_tree(&root_extent_tree_node, &mut |extent| {
583 extents.push(extent.clone());
584 Ok(())
585 })?;
586
587 let block_size = self.block_size()?;
588 let mut current_offset = 0u64;
589
590 for extent in extents {
596 let buffer_offset = extent.e_blk.get() as u64 * block_size;
597
598 if buffer_offset > current_offset {
601 let gap = buffer_offset - current_offset;
602 size_remaining -= gap;
603 writer.pad_to(buffer_offset)?;
604 current_offset = buffer_offset;
605 }
606
607 let size_before = size_remaining;
608 self.read_extent_data(&extent, writer, &mut size_remaining)?;
609 let written = size_before - size_remaining;
610 current_offset += written;
611 }
612
613 writer.pad_to(inode.size())?;
617
618 Ok(())
619 }
620
621 pub fn read_data(&self, inode_num: u32) -> Result<Vec<u8>, ParsingError> {
623 let mut data = Vec::new();
624 self.read_data_into(inode_num, &mut data)?;
625 Ok(data)
626 }
627
628 pub fn index<R, E>(&self, inode: INode, prefix: Vec<&str>, receiver: &mut R) -> Result<bool, E>
640 where
641 E: From<ParsingError>,
642 R: FnMut(&Parser, Vec<&str>, &DirEntry2) -> Result<bool, E>,
643 {
644 let entries = self.entries_from_inode(&inode)?;
645 for entry in entries {
646 let entry_name = entry.name()?;
647 if entry_name == "." || entry_name == ".." {
648 continue;
649 }
650 let mut name = Vec::new();
651 name.append(&mut prefix.clone());
652 name.push(entry_name);
653 if !receiver(self, name.clone(), &entry)? {
654 return Ok(false);
655 }
656 if EntryType::from_u8(entry.e2d_type)? == EntryType::Directory {
657 let inode = self.inode(entry.e2d_ino.get())?;
658 if !self.index(inode, name, receiver)? {
659 return Ok(false);
660 }
661 }
662 }
663
664 Ok(true)
665 }
666
667 pub fn inode_xattrs(&self, inode_number: u32) -> Result<XattrMap, ParsingError> {
669 let mut xattrs = BTreeMap::new();
670
671 let inode_addr = self.inode_addr(inode_number).expect("Couldn't get inode address");
672 let inode =
673 INode::from_reader_with_offset(&self.reader, inode_addr).expect("Failed reader");
674
675 let sb = self.super_block().expect("No super block for inode");
676 let xattr_magic_addr = inode_addr
677 + MINIMUM_INODE_SIZE
678 + u64::from(inode.e4di_extra_isize(sb).unwrap_or_default());
679
680 let mut magic = LEU32::ZERO;
681 self.reader.read(xattr_magic_addr, magic.as_mut_bytes()).expect("Failed to read xattr");
682 if magic.get() == Self::XATTR_MAGIC {
683 let first_entry = xattr_magic_addr + size_of_val(&magic) as u64;
684 self.read_xattr_entries_from_inode(
685 first_entry,
686 inode_addr + (sb.e2fs_inode_size.get() as u64),
687 &mut xattrs,
688 )?;
689 }
690
691 let block_number: u64 = inode.facl();
692 if block_number > 0 {
693 let block = self.block(block_number).expect("Couldn't find block");
694 Self::read_xattr_entries_from_block(&block, &mut xattrs)?;
695 }
696
697 Ok(xattrs)
698 }
699
700 const XATTR_ALIGNMENT: u64 = 4;
701 const XATTR_MAGIC: u32 = 0xea020000;
702
703 fn round_up_to_align(x: u64, align: u64) -> u64 {
704 let spare = x % align;
705 if spare > 0 { x.checked_add(align - spare).expect("Overflow when aligning") } else { x }
706 }
707
708 fn is_valid_xattr_entry_header(header: &XattrEntryHeader) -> bool {
709 !(header.e_name_len == 0
710 && header.e_name_index == 0
711 && header.e_value_offs.get() == 0
712 && header.e_value_inum.get() == 0)
713 }
714
715 fn xattr_prefix_for_name_index(header: &XattrEntryHeader) -> Vec<u8> {
716 match header.e_name_index {
717 1 => b"user.".to_vec(),
718 2 => b"system.posix_acl_access.".to_vec(),
719 3 => b"system.posix_acl_default.".to_vec(),
720 4 => b"trusted.".to_vec(),
721 6 => b"security.".to_vec(),
722 7 => b"system.".to_vec(),
723 8 => b"system.richacl".to_vec(),
724 _ => b"".to_vec(),
725 }
726 }
727
728 fn read_xattr_entries_from_inode(
730 &self,
731 mut entries_addr: u64,
732 inode_end: u64,
733 xattrs: &mut XattrMap,
734 ) -> Result<(), ParsingError> {
735 let value_base_addr = entries_addr;
736 while entries_addr + (std::mem::size_of::<XattrEntryHeader>() as u64) < inode_end {
737 let head = XattrEntryHeader::from_reader_with_offset(&self.reader, entries_addr)?;
738 if !Self::is_valid_xattr_entry_header(&head) {
739 break;
740 }
741
742 let prefix = Self::xattr_prefix_for_name_index(&head);
743 let mut name = Vec::with_capacity(prefix.len() + head.e_name_len as usize);
744 name.extend_from_slice(&prefix);
745 name.resize(prefix.len() + head.e_name_len as usize, 0);
746
747 self.reader.read(
748 entries_addr + size_of::<XattrEntryHeader>() as u64,
749 &mut name[prefix.len()..],
750 )?;
751
752 let mut value = vec![0u8; head.e_value_size.get() as usize];
753 self.reader.read(value_base_addr + u64::from(head.e_value_offs), &mut value)?;
754 xattrs.insert(name, value);
755
756 entries_addr += size_of::<XattrEntryHeader>() as u64 + head.e_name_len as u64;
757 entries_addr = Self::round_up_to_align(entries_addr, Self::XATTR_ALIGNMENT);
758 }
759 Ok(())
760 }
761
762 fn read_xattr_entries_from_block(
764 block: &[u8],
765 xattrs: &mut XattrMap,
766 ) -> Result<(), ParsingError> {
767 let head = XattrHeader::to_struct_ref(
768 &block[..std::mem::size_of::<XattrHeader>()],
769 ParsingError::Incompatible("Invalid XattrHeader".to_string()),
770 )?;
771
772 if head.e_magic.get() != Self::XATTR_MAGIC {
773 return Ok(());
774 }
775
776 let mut offset = Self::round_up_to_align(
777 std::mem::size_of::<XattrHeader>() as u64,
778 Self::XATTR_ALIGNMENT * 2,
779 ) as usize;
780
781 while offset + std::mem::size_of::<XattrEntryHeader>() < block.len() {
782 let head = XattrEntryHeader::to_struct_ref(
783 &block[offset..offset + std::mem::size_of::<XattrEntryHeader>()],
784 ParsingError::Incompatible("Invalid XattrEntryHeader".to_string()),
785 )?;
786
787 if !Self::is_valid_xattr_entry_header(&head) {
788 break;
789 }
790
791 let name_start = offset + std::mem::size_of::<XattrEntryHeader>();
792 let name_end = name_start + head.e_name_len as usize;
793 let mut name = Self::xattr_prefix_for_name_index(&head);
794 name.extend_from_slice(&block[name_start..name_end]);
795
796 let value_start = head.e_value_offs.get() as usize;
797 let value_end = value_start + head.e_value_size.get() as usize;
798 let value = block[value_start..value_end].to_vec();
799 xattrs.insert(name, value);
800
801 offset = Self::round_up_to_align(name_end as u64, 4) as usize;
802 }
803
804 Ok(())
805 }
806}
807
808#[cfg(test)]
809mod tests {
810 use crate::parser::Parser;
811 #[cfg(target_os = "fuchsia")]
812 use crate::parser::{DataWriter, VmoWriter};
813 use crate::readers::VecReader;
814 use crate::structs::{EntryType, ParsingError};
815 use maplit::hashmap;
816 use sha2::{Digest, Sha256};
817 #[cfg(target_os = "fuchsia")]
818 use std::assert_matches;
819 use std::collections::{HashMap, HashSet};
820 use std::path::Path;
821 use std::{fs, str};
822 use test_case::test_case;
823
824 #[fuchsia::test]
825 fn list_root_1_file() {
826 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
827 let parser = Parser::new(Box::new(VecReader::new(data)));
828 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
829 let root_inode = parser.root_inode().expect("Parse INode");
830 let entries = parser.entries_from_inode(&root_inode).expect("List entries");
831 let mut expected_entries = vec!["file1", "lost+found", "..", "."];
832
833 for de in &entries {
834 assert_eq!(expected_entries.pop().unwrap(), de.name().unwrap());
835 }
836 assert_eq!(expected_entries.len(), 0);
837 }
838
839 #[test_case(
840 "/pkg/data/nest.img",
841 vec!["inner", "file1", "lost+found", "..", "."];
842 "fs with a single directory")]
843 #[test_case(
844 "/pkg/data/extents.img",
845 vec!["trailingzeropages", "a", "smallfile", "largefile", "sparsefile", "lost+found", "..", "."];
846 "fs with multiple files with multiple extents")]
847 fn list_root(ext4_path: &str, mut expected_entries: Vec<&str>) {
848 let data = fs::read(ext4_path).expect("Unable to read file");
849 let parser = Parser::new(Box::new(VecReader::new(data)));
850 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
851 let root_inode = parser.root_inode().expect("Parse INode");
852 let entries = parser.entries_from_inode(&root_inode).expect("List entries");
853
854 for de in &entries {
855 assert_eq!(expected_entries.pop().unwrap(), de.name().unwrap());
856 }
857 assert_eq!(expected_entries.len(), 0);
858 }
859
860 #[fuchsia::test]
861 fn get_from_path() {
862 let data = fs::read("/pkg/data/nest.img").expect("Unable to read file");
863 let parser = Parser::new(Box::new(VecReader::new(data)));
864 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
865
866 let entry = parser.entry_at_path(Path::new("/inner")).expect("Entry at path");
867 assert_eq!(entry.e2d_ino.get(), 12);
868 assert_eq!(entry.name().unwrap(), "inner");
869
870 let entry = parser.entry_at_path(Path::new("/inner/file2")).expect("Entry at path");
871 assert_eq!(entry.e2d_ino.get(), 17);
872 assert_eq!(entry.name().unwrap(), "file2");
873 }
874
875 #[fuchsia::test]
876 fn read_data() {
877 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
878 let parser = Parser::new(Box::new(VecReader::new(data)));
879 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
880
881 let entry = parser.entry_at_path(Path::new("file1")).expect("Entry at path");
882 assert_eq!(entry.e2d_ino.get(), 15);
883 assert_eq!(entry.name().unwrap(), "file1");
884
885 let data = parser.read_data(entry.e2d_ino.into()).expect("File data");
886 let compare = "file1 contents.\n";
887 assert_eq!(data.len(), compare.len());
888 assert_eq!(str::from_utf8(data.as_slice()).expect("File data"), compare);
889 }
890
891 #[fuchsia::test]
892 fn fail_inode_zero() {
893 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
894 let parser = Parser::new(Box::new(VecReader::new(data)));
895 assert!(parser.inode(0).is_err());
896 }
897
898 #[fuchsia::test]
899 fn index() {
900 let data = fs::read("/pkg/data/nest.img").expect("Unable to read file");
901 let parser = Parser::new(Box::new(VecReader::new(data)));
902 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
903
904 let mut count = 0;
905 let mut entries: HashSet<u32> = HashSet::new();
906 let root_inode = parser.root_inode().expect("Root inode");
907
908 parser
909 .index(root_inode, Vec::new(), &mut |_, _, entry| {
910 count += 1;
911
912 assert_ne!(entries.contains(&entry.e2d_ino.get()), true);
914 entries.insert(entry.e2d_ino.get());
915
916 Ok::<bool, ParsingError>(true)
917 })
918 .expect("Index");
919
920 assert_eq!(count, 4);
921 }
922
923 #[fuchsia::test]
924 fn xattr() {
925 let data = fs::read("/pkg/data/xattr.img").expect("Unable to read file");
926 let parser = Parser::new(Box::new(VecReader::new(data)));
927 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
928 let root_inode = parser.root_inode().expect("Root inode");
929 let mut found_files = HashSet::new();
930
931 parser
932 .index(root_inode, Vec::new(), &mut |_, _, entry| {
933 let name = entry.e2d_name;
934 let inode = entry.e2d_ino.get();
935 let attributes = parser.inode_xattrs(inode).expect("Extended attributes");
936 match name {
937 name if &name[0..10] == b"lost+found" => {
938 assert_eq!(attributes.len(), 0);
939 found_files.insert("lost+found");
940 }
941 name if &name[0..5] == b"file1" => {
942 assert_eq!(attributes.len(), 1);
943 assert_eq!(attributes[&b"user.test".to_vec()], b"test value".to_vec());
944 found_files.insert("file1");
945 }
946 name if &name[0..9] == b"file_many" => {
947 assert_eq!(attributes.len(), 6);
948 assert_eq!(
949 attributes[&b"user.long".to_vec()],
950 b"vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv".to_vec()
951 );
952 found_files.insert("file_many");
953 }
954 name if &name[0..6] == b"subdir" => {
955 assert_eq!(attributes.len(), 1);
956 assert_eq!(attributes[&b"user.type".to_vec()], b"dir".to_vec());
957 found_files.insert("subdir");
958 }
959 name if &name[0..5] == b"file2" => {
960 assert_eq!(attributes.len(), 2);
961 assert_eq!(
962 attributes[&b"user.test_one".to_vec()],
963 b"test value 1".to_vec()
964 );
965 assert_eq!(
966 attributes[&b"user.test_two".to_vec()],
967 b"test value 2".to_vec()
968 );
969 found_files.insert("file2");
970 }
971 _ => {}
972 }
973 Ok::<bool, ParsingError>(true)
974 })
975 .expect("Index");
976
977 assert_eq!(found_files.len(), 5);
978 }
979
980 #[test_case(
981 "/pkg/data/extents.img",
982 hashmap!{
983 "largefile".to_string() => "de2cf635ae4e0e727f1e412f978001d6a70d2386dc798d4327ec8c77a8e4895d".to_string(),
984 "smallfile".to_string() => "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03".to_string(),
985 "sparsefile".to_string() => "3f411e42c1417cd8845d7144679812be3e120318d843c8c6e66d8b2c47a700e9".to_string(),
986 "trailingzeropages".to_string() => "afc5cc689fd3cb8d00c147d60dc911a70d36b7afb03cc7f15de9c78a52be978d".to_string(),
987 "a/multi/dir/path/within/this/crowded/extents/test/img/empty".to_string() => "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string(),
988 },
989 vec!["a/multi/dir/path/within/this/crowded/extents/test/img", "lost+found"];
990 "fs with multiple files with multiple extents")]
991 #[test_case(
992 "/pkg/data/1file.img",
993 hashmap!{
994 "file1".to_string() => "6bc35bfb2ca96c75a1fecde205693c19a827d4b04e90ace330048f3e031487dd".to_string(),
995 },
996 vec!["lost+found"];
997 "fs with one small file")]
998 #[test_case(
999 "/pkg/data/nest.img",
1000 hashmap!{
1001 "file1".to_string() => "6bc35bfb2ca96c75a1fecde205693c19a827d4b04e90ace330048f3e031487dd".to_string(),
1002 "inner/file2".to_string() => "215ca145cbac95c9e2a6f5ff91ca1887c837b18e5f58fd2a7a16e2e5a3901e10".to_string(),
1003 },
1004 vec!["inner", "lost+found"];
1005 "fs with a single directory")]
1006 #[test_case(
1007 "/pkg/data/nest64.img",
1008 hashmap!{
1009 "file1".to_string() => "6bc35bfb2ca96c75a1fecde205693c19a827d4b04e90ace330048f3e031487dd".to_string(),
1010 "inner/file2".to_string() => "215ca145cbac95c9e2a6f5ff91ca1887c837b18e5f58fd2a7a16e2e5a3901e10".to_string(),
1011 },
1012 vec!["inner", "lost+found"];
1013 "fs with 64bit enabled and a single directory")]
1014 #[test_case(
1015 "/pkg/data/longdir.img",
1016 {
1017 let mut hash = HashMap::new();
1018 for i in 1..=1000 {
1019 hash.insert(i.to_string(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string());
1020 }
1021 hash
1022 },
1023 vec!["lost+found"];
1024 "fs with many entries in a directory")]
1025 fn check_data(
1026 ext4_path: &str,
1027 mut file_hashes: HashMap<String, String>,
1028 expected_dirs: Vec<&str>,
1029 ) {
1030 let data = fs::read(ext4_path).expect("Unable to read file");
1031 let parser = Parser::new(Box::new(VecReader::new(data)));
1032 assert!(parser.super_block().expect("Super Block").check_magic().is_ok());
1033
1034 let root_inode = parser.root_inode().expect("Root inode");
1035
1036 parser
1037 .index(root_inode, Vec::new(), &mut |my_self, path, entry| {
1038 let entry_type = EntryType::from_u8(entry.e2d_type).expect("Entry Type");
1039 let file_path = path.join("/");
1040
1041 match entry_type {
1042 EntryType::RegularFile => {
1043 let data = my_self.read_data(entry.e2d_ino.into()).expect("File data");
1044
1045 let mut hasher = Sha256::new();
1046 hasher.update(&data);
1047 assert_eq!(
1048 file_hashes.remove(&file_path).unwrap(),
1049 hex::encode(hasher.finalize())
1050 );
1051 }
1052 EntryType::Directory => {
1053 let mut found = false;
1054
1055 for expected_dir in expected_dirs.iter() {
1057 if expected_dir.starts_with(&file_path) {
1058 found = true;
1059 break;
1060 }
1061 }
1062 assert!(found, "Unexpected path {}", file_path);
1063 }
1064 _ => {
1065 assert!(false, "No other types should exist in this image.");
1066 }
1067 }
1068 Ok::<bool, ParsingError>(true)
1069 })
1070 .expect("Index");
1071 assert!(file_hashes.is_empty(), "Expected files were not found {:?}", file_hashes);
1072 }
1073
1074 #[cfg(target_os = "fuchsia")]
1075 #[test_case(true; "pad")]
1076 #[test_case(true; "no_pad")]
1077 #[fuchsia::test]
1078 fn vmo_writer_size_and_stream_size(pad: bool) {
1079 let size = zx::system_get_page_size() as u64;
1080 let mut writer = VmoWriter::new(size).expect("failed to create VmoWriter");
1081 writer.reserve(100).expect("reserve failed");
1082 let contents = b"hello world";
1083 writer.append(contents).expect("append failed");
1084 let expect_stream_size = if pad {
1085 let pad = 500;
1086 writer.pad_to(pad).expect("pad_to failed");
1087 pad
1088 } else {
1089 contents.len() as u64
1090 };
1091
1092 let vmo = writer.into_vmo();
1093 assert_eq!(vmo.get_size().expect("get_size failed"), size);
1094 assert_eq!(vmo.get_stream_size().expect("get_stream_size failed"), expect_stream_size);
1095 }
1096
1097 #[cfg(target_os = "fuchsia")]
1098 #[fuchsia::test]
1099 fn vmo_writer_write_or_reserve_past_size_fails() {
1100 let size = zx::system_get_page_size() as u64;
1101
1102 let mut writer = VmoWriter::new(size).expect("failed to create VmoWriter");
1103 assert_eq!(
1104 writer.reserve(size + 1),
1105 Err(ParsingError::VmoError(zx::Status::NOT_SUPPORTED))
1106 );
1107
1108 let mut writer = VmoWriter::new(size).expect("failed to create VmoWriter");
1109 let large_buffer = vec![0u8; (size + 1) as usize];
1110 assert_eq!(
1111 writer.append(&large_buffer),
1112 Err(ParsingError::VmoError(zx::Status::IO_REFUSED))
1113 );
1114
1115 let mut writer = VmoWriter::new(size).expect("failed to create VmoWriter");
1116 assert_eq!(writer.pad_to(size + 1), Err(ParsingError::VmoError(zx::Status::OUT_OF_RANGE)));
1117
1118 let mut writer = VmoWriter::new(size).expect("failed to create VmoWriter");
1119 writer.append(&vec![0u8; size as usize]).expect("append failed");
1120 assert_eq!(writer.append(b"a"), Err(ParsingError::VmoError(zx::Status::OUT_OF_RANGE)));
1121 }
1122
1123 #[cfg(target_os = "fuchsia")]
1124 #[fuchsia::test]
1125 fn vmo_writer_pad_to_lower_than_offset_fails() {
1126 let size = zx::system_get_page_size() as u64;
1127 let mut writer = VmoWriter::new(size).expect("failed to create VmoWriter");
1128 writer.append(b"hello world").expect("append failed");
1129 assert_matches!(writer.pad_to(5), Err(ParsingError::NotSupported(_)));
1130 }
1131
1132 #[cfg(target_os = "fuchsia")]
1133 #[fuchsia::test]
1134 fn vmo_writer_pad_to_sets_stream_size_and_zeroes() {
1135 let size = zx::system_get_page_size() as u64;
1136 let mut writer = VmoWriter::new(size).expect("failed to create VmoWriter");
1137 let contents = b"hello";
1138 writer.append(contents).expect("append failed");
1139 let pad: usize = 100;
1140 writer.pad_to(pad as u64).expect("pad_to failed");
1141
1142 let vmo = writer.into_vmo();
1143 assert_eq!(vmo.get_stream_size().expect("get_stream_size failed"), pad as u64);
1144
1145 let mut buf = vec![0u8; pad];
1146 vmo.read(&mut buf, 0).expect("vmo read failed");
1147 assert_eq!(&buf[0..contents.len()], b"hello");
1148 assert_eq!(&buf[contents.len()..pad], &vec![0u8; pad - contents.len()]);
1149 }
1150
1151 #[cfg(target_os = "fuchsia")]
1152 #[fuchsia::test]
1153 fn vmo_writer_with_no_writes_has_zero_stream_size() {
1154 let size = zx::system_get_page_size() as u64;
1155 let writer = VmoWriter::new(size).expect("failed to create VmoWriter");
1156 let vmo = writer.into_vmo();
1157 assert_eq!(vmo.get_size().expect("get_size"), size);
1158 assert_eq!(vmo.get_stream_size().expect("get_stream_size"), 0);
1159 }
1160
1161 #[cfg(target_os = "fuchsia")]
1162 #[test_case(true; "pad")]
1163 #[test_case(true; "no_pad")]
1164 #[fuchsia::test]
1165 fn vmo_writer_multiple_appends(pad: bool) {
1166 let size = zx::system_get_page_size() as u64;
1167 let mut writer = VmoWriter::new(size).expect("failed to create VmoWriter");
1168 let mut expect = vec![];
1169 for i in 0..5 {
1170 let b = format!("hello payload {i}");
1171 expect.extend_from_slice(b.as_bytes());
1172 writer.append(b.as_bytes()).expect("append");
1173 if pad {
1174 let pad = [0u8; 10];
1175 expect.extend_from_slice(&pad);
1176 writer.pad_to(expect.len() as u64).expect("pad");
1177 }
1178 }
1179
1180 let vmo = writer.into_vmo();
1181 let mut buf = vec![0u8; expect.len()];
1182 assert_eq!(vmo.get_stream_size().expect("get_stream_size"), expect.len() as u64);
1183 vmo.read(&mut buf, 0).expect("read");
1184 assert_eq!(buf, expect);
1185 }
1186}