1use crate::readers::{Reader, ReaderError, ReaderWriter};
36use std::collections::HashMap;
37use std::mem::size_of;
38use std::{fmt, str};
39use thiserror::Error;
40use zerocopy::byteorder::little_endian::{U16 as LEU16, U32 as LEU32, U64 as LEU64};
41use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, SplitByteSlice, Unaligned};
42
43pub const FIRST_BG_PADDING: u64 = 1024;
45pub const ROOT_INODE_NUM: u32 = 2;
47pub const SB_MAGIC: u16 = 0xEF53;
49pub const EH_MAGIC: u16 = 0xF30A;
51pub const MIN_EXT4_SIZE: u64 = FIRST_BG_PADDING + size_of::<SuperBlock>() as u64;
53pub const MINIMUM_INODE_SIZE: u64 = 128;
55
56#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
57#[repr(C)]
58pub struct ExtentHeader {
59 pub eh_magic: LEU16,
61 pub eh_ecount: LEU16,
63 pub eh_max: LEU16,
65 pub eh_depth: LEU16,
68 pub eh_gen: LEU32,
70}
71assert_eq_size!(ExtentHeader, [u8; 12]);
74
75#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
76#[repr(C)]
77pub struct ExtentIndex {
78 pub ei_blk: LEU32,
80 pub ei_leaf_lo: LEU32,
82 pub ei_leaf_hi: LEU16,
84 pub ei_unused: LEU16,
85}
86assert_eq_size!(ExtentIndex, [u8; 12]);
89
90#[derive(Clone, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug)]
91#[repr(C)]
92pub struct Extent {
93 pub e_blk: LEU32,
95 pub e_len: LEU16,
97 pub e_start_hi: LEU16,
99 pub e_start_lo: LEU32,
101}
102assert_eq_size!(Extent, [u8; 12]);
105
106#[derive(std::fmt::Debug)]
107#[repr(C)]
108pub struct DirEntry2 {
109 pub e2d_ino: LEU32,
111 pub e2d_reclen: LEU16,
113 pub e2d_namlen: u8,
115 pub e2d_type: u8,
117 pub e2d_name: [u8; 255],
119}
120
121#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, std::fmt::Debug)]
122#[repr(C)]
123pub struct DirEntryHeader {
124 pub e2d_ino: LEU32,
126 pub e2d_reclen: LEU16,
128 pub e2d_namlen: u8,
130 pub e2d_type: u8,
132}
133assert_eq_size!(DirEntryHeader, [u8; 8]);
136
137#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug)]
138#[repr(C)]
139pub struct SuperBlock {
140 pub e2fs_icount: LEU32,
142 pub e2fs_bcount: LEU32,
144 pub e2fs_rbcount: LEU32,
146 pub e2fs_fbcount: LEU32,
148 pub e2fs_ficount: LEU32,
150 pub e2fs_first_dblock: LEU32,
152 pub e2fs_log_bsize: LEU32,
154 pub e2fs_log_fsize: LEU32,
156 pub e2fs_bpg: LEU32,
158 pub e2fs_fpg: LEU32,
160 pub e2fs_ipg: LEU32,
162 pub e2fs_mtime: LEU32,
164 pub e2fs_wtime: LEU32,
166 pub e2fs_mnt_count: LEU16,
168 pub e2fs_max_mnt_count: LEU16,
170 pub e2fs_magic: LEU16,
172 pub e2fs_state: LEU16,
174 pub e2fs_beh: LEU16,
176 pub e2fs_minrev: LEU16,
178 pub e2fs_lastfsck: LEU32,
180 pub e2fs_fsckintv: LEU32,
182 pub e2fs_creator: LEU32,
184 pub e2fs_rev: LEU32,
186 pub e2fs_ruid: LEU16,
188 pub e2fs_rgid: LEU16,
190 pub e2fs_first_ino: LEU32,
192 pub e2fs_inode_size: LEU16,
194 pub e2fs_block_group_nr: LEU16,
196 pub e2fs_features_compat: LEU32,
198 pub e2fs_features_incompat: LEU32,
200 pub e2fs_features_rocompat: LEU32,
202 pub e2fs_uuid: [u8; 16],
204 pub e2fs_vname: [u8; 16],
206 pub e2fs_fsmnt: [u8; 64],
208 pub e2fs_algo: LEU32,
210 pub e2fs_prealloc: u8,
212 pub e2fs_dir_prealloc: u8,
214 pub e2fs_reserved_ngdb: LEU16,
216 pub e3fs_journal_uuid: [u8; 16],
218 pub e3fs_journal_inum: LEU32,
220 pub e3fs_journal_dev: LEU32,
222 pub e3fs_last_orphan: LEU32,
224 pub e3fs_hash_seed: [LEU32; 4],
226 pub e3fs_def_hash_version: u8,
228 pub e3fs_jnl_backup_type: u8,
230 pub e3fs_desc_size: LEU16,
232 pub e3fs_default_mount_opts: LEU32,
234 pub e3fs_first_meta_bg: LEU32,
236 pub e3fs_mkfs_time: LEU32,
238 pub e3fs_jnl_blks: [LEU32; 17],
240 pub e4fs_bcount_hi: LEU32,
242 pub e4fs_rbcount_hi: LEU32,
244 pub e4fs_fbcount_hi: LEU32,
246 pub e4fs_min_extra_isize: LEU16,
248 pub e4fs_want_extra_isize: LEU16,
250 pub e4fs_flags: LEU32,
252 pub e4fs_raid_stride: LEU16,
254 pub e4fs_mmpintv: LEU16,
256 pub e4fs_mmpblk: LEU64,
258 pub e4fs_raid_stripe_wid: LEU32,
260 pub e4fs_log_gpf: u8,
262 pub e4fs_chksum_type: u8,
264 pub e4fs_encrypt: u8,
266 pub e4fs_reserved_pad: u8,
267 pub e4fs_kbytes_written: LEU64,
269 pub e4fs_snapinum: LEU32,
271 pub e4fs_snapid: LEU32,
273 pub e4fs_snaprbcount: LEU64,
275 pub e4fs_snaplist: LEU32,
277 pub e4fs_errcount: LEU32,
279 pub e4fs_first_errtime: LEU32,
281 pub e4fs_first_errino: LEU32,
283 pub e4fs_first_errblk: LEU64,
285 pub e4fs_first_errfunc: [u8; 32],
287 pub e4fs_first_errline: LEU32,
289 pub e4fs_last_errtime: LEU32,
291 pub e4fs_last_errino: LEU32,
293 pub e4fs_last_errline: LEU32,
295 pub e4fs_last_errblk: LEU64,
297 pub e4fs_last_errfunc: [u8; 32],
299 pub e4fs_mount_opts: [u8; 64],
301 pub e4fs_usrquota_inum: LEU32,
303 pub e4fs_grpquota_inum: LEU32,
305 pub e4fs_overhead_clusters: LEU32,
307 pub e4fs_backup_bgs: [LEU32; 2],
309 pub e4fs_encrypt_algos: [u8; 4],
311 pub e4fs_encrypt_pw_salt: [u8; 16],
313 pub e4fs_lpf_ino: LEU32,
315 pub e4fs_proj_quota_inum: LEU32,
317 pub e4fs_chksum_seed: LEU32,
319 pub e4fs_reserved: [LEU32; 98],
321 pub e4fs_sbchksum: LEU32,
323}
324assert_eq_size!(SuperBlock, [u8; 1024]);
327
328#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
329#[repr(C)]
330pub struct BlockGroupDesc32 {
331 pub ext2bgd_b_bitmap: LEU32,
333 pub ext2bgd_i_bitmap: LEU32,
335 pub ext2bgd_i_tables: LEU32,
337 pub ext2bgd_nbfree: LEU16,
339 pub ext2bgd_nifree: LEU16,
341 pub ext2bgd_ndirs: LEU16,
343 pub ext4bgd_flags: LEU16,
345 pub ext4bgd_x_bitmap: LEU32,
347 pub ext4bgd_b_bmap_csum: LEU16,
349 pub ext4bgd_i_bmap_csum: LEU16,
351 pub ext4bgd_i_unused: LEU16,
353 pub ext4bgd_csum: LEU16,
355}
356assert_eq_size!(BlockGroupDesc32, [u8; 32]);
359
360#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
361#[repr(C)]
362pub struct BlockGroupDesc64 {
363 pub base: BlockGroupDesc32,
364 pub ext4bgd_b_bitmap_hi: LEU32,
365 pub ext4bgd_i_bitmap_hi: LEU32,
366 pub ext4bgd_i_tables_hi: LEU32,
367 pub ext4bgd_nbfree_hi: LEU16,
368 pub ext4bgd_nifree_hi: LEU16,
369 pub ext4bgd_ndirs_hi: LEU16,
370 pub ext4bgd_i_unused_hi: LEU16,
371 pub ext4bgd_x_bitmap_hi: LEU32,
372 pub ext4bgd_b_bmap_csum_hi: LEU16,
373 pub ext4bgd_i_bmap_csum_hi: LEU16,
374 pub ext4bgd_reserved: LEU32,
375}
376assert_eq_size!(BlockGroupDesc64, [u8; 64]);
379
380#[derive(KnownLayout, FromBytes, IntoBytes, Immutable)]
381#[repr(C)]
382pub struct ExtentTreeNode<B: SplitByteSlice> {
383 pub header: Ref<B, ExtentHeader>,
384 pub entries: B,
385}
386
387#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
388#[repr(C)]
389pub struct INode {
390 pub e2di_mode: LEU16,
392 pub e2di_uid: LEU16,
394 pub e2di_size: LEU32,
396 pub e2di_atime: LEU32,
398 pub e2di_ctime: LEU32,
400 pub e2di_mtime: LEU32,
402 pub e2di_dtime: LEU32,
404 pub e2di_gid: LEU16,
406 pub e2di_nlink: LEU16,
408 pub e2di_nblock: LEU32,
410 pub e2di_flags: LEU32,
412 pub e2di_version: [u8; 4],
414 pub e2di_blocks: [u8; 60],
416 pub e2di_gen: LEU32,
418 pub e2di_facl: LEU32,
420 pub e2di_size_high: LEU32,
422 pub e2di_faddr: LEU32,
424 pub e2di_nblock_high: LEU16,
426 pub e2di_facl_high: LEU16,
428 pub e2di_uid_high: LEU16,
430 pub e2di_gid_high: LEU16,
432 pub e2di_chksum_lo: LEU16,
434 pub e2di_lx_reserved: LEU16,
435 e4di_extra_isize: LEU16,
439 e4di_chksum_hi: LEU16,
440 e4di_ctime_extra: LEU32,
441 e4di_mtime_extra: LEU32,
442 e4di_atime_extra: LEU32,
443 e4di_crtime: LEU32,
444 e4di_crtime_extra: LEU32,
445 e4di_version_hi: LEU32,
446 e4di_projid: LEU32,
447}
448assert_eq_size!(INode, [u8; 160]);
451
452#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug)]
453#[repr(C)]
454pub struct XattrHeader {
455 pub e_magic: LEU32,
456 pub e_refcount: LEU32,
457 pub e_blocks: LEU32,
458 pub e_hash: LEU32,
459 pub e_checksum: LEU32,
460 e_reserved: [u8; 8],
461}
462
463#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug)]
464#[repr(C)]
465pub struct XattrEntryHeader {
466 pub e_name_len: u8,
467 pub e_name_index: u8,
468 pub e_value_offs: LEU16,
469 pub e_value_inum: LEU32,
470 pub e_value_size: LEU32,
471 pub e_hash: LEU32,
472}
473
474#[derive(Debug, PartialEq)]
475pub enum InvalidAddressErrorType {
476 Lower,
477 Upper,
478}
479
480impl fmt::Display for InvalidAddressErrorType {
481 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
482 match *self {
483 InvalidAddressErrorType::Lower => write!(f, "lower"),
484 InvalidAddressErrorType::Upper => write!(f, "upper"),
485 }
486 }
487}
488
489#[derive(Error, Debug, PartialEq)]
490pub enum ParsingError {
491 #[error("Unable to parse Super Block at 0x{:X}", _0)]
492 InvalidSuperBlock(u64),
493 #[error("Invalid Super Block magic number {} should be 0xEF53", _0)]
494 InvalidSuperBlockMagic(u16),
495 #[error("Invalid Super Block inode size {} should be {}", _0, std::mem::size_of::<INode>())]
496 InvalidInodeSize(u16),
497 #[error("Invalid Block Group Descriptor size {}", _0)]
498 InvalidBlockGroupDescSize(u16),
499 #[error("Block number {} out of bounds.", _0)]
500 BlockNumberOutOfBounds(u64),
501 #[error("SuperBlock e2fs_log_bsize value invalid: {}", _0)]
502 BlockSizeInvalid(u32),
503
504 #[error("Unable to parse Block Group Description at 0x{:X}", _0)]
505 InvalidBlockGroupDesc(u64),
506 #[error("Unable to parse INode {}", _0)]
507 InvalidInode(u32),
508
509 #[error("Unable to parse ExtentHeader from INode")]
511 InvalidExtentHeader,
512 #[error("Invalid Extent Header magic number {} should be 0xF30A", _0)]
513 InvalidExtentHeaderMagic(u16),
514 #[error("Unable to parse Extent at 0x{:X}", _0)]
515 InvalidExtent(u64),
516 #[error("Extent has more data {} than expected {}", _0, _1)]
517 ExtentUnexpectedLength(u64, u64),
518
519 #[error("Invalid Directory Entry at 0x{:X}", _0)]
520 InvalidDirEntry2(u64),
521 #[error("Directory Entry has invalid string in name field: {:?}", _0)]
522 DirEntry2NonUtf8(Vec<u8>),
523 #[error("Requested path contains invalid string")]
524 InvalidInputPath,
525 #[error("Non-existent path: {}", _0)]
526 PathNotFound(String),
527 #[error("Entry Type {} unknown", _0)]
528 BadEntryType(u8),
529
530 #[error("Incompatible feature flags (feature_incompat): 0x{:X}", _0)]
532 BannedFeatureIncompat(u32),
533 #[error("Required feature flags (feature_incompat): 0x{:X}", _0)]
534 RequiredFeatureIncompat(u32),
535
536 #[error("{}", _0)]
538 Incompatible(String),
539 #[error("Bad file at {}", _0)]
540 BadFile(String),
541 #[error("Bad directory at {}", _0)]
542 BadDirectory(String),
543
544 #[error("Attempted to access at 0x{:X} when the {} bound is 0x{:X}", _1, _0, _2)]
545 InvalidAddress(InvalidAddressErrorType, u64, u64),
546
547 #[error("Reader failed to read at 0x{:X}", _0)]
548 SourceReadError(u64),
549
550 #[error("Not a file")]
551 NotFile,
552
553 #[error("Writer failed to write at 0x{:X}", _0)]
554 WriterError(u64),
555
556 #[error("{} not supported", _0)]
557 NotSupported(String),
558
559 #[error("Writer failed to flush")]
560 WriterFlushError,
561}
562
563impl From<ReaderError> for ParsingError {
564 fn from(err: ReaderError) -> ParsingError {
565 match err {
566 ReaderError::Read(addr) => ParsingError::SourceReadError(addr),
567 ReaderError::OutOfBounds(addr, max) => {
568 ParsingError::InvalidAddress(InvalidAddressErrorType::Upper, addr, max)
569 }
570 ReaderError::Write(addr) => ParsingError::WriterError(addr),
571 ReaderError::NotSupported(msg) => ParsingError::NotSupported(msg),
572 ReaderError::WriteFlush => ParsingError::WriterFlushError,
573 }
574 }
575}
576
577#[derive(Debug, PartialEq)]
579#[repr(u8)]
580pub enum EntryType {
581 Unknown = 0x0,
582 RegularFile = 0x1,
583 Directory = 0x2,
584 CharacterDevice = 0x3,
585 BlockDevice = 0x4,
586 FIFO = 0x5,
587 Socket = 0x6,
588 SymLink = 0x7,
589}
590
591impl EntryType {
592 pub fn from_u8(value: u8) -> Result<EntryType, ParsingError> {
593 match value {
594 0x0 => Ok(EntryType::Unknown),
595 0x1 => Ok(EntryType::RegularFile),
596 0x2 => Ok(EntryType::Directory),
597 0x3 => Ok(EntryType::CharacterDevice),
598 0x4 => Ok(EntryType::BlockDevice),
599 0x5 => Ok(EntryType::FIFO),
600 0x6 => Ok(EntryType::Socket),
601 0x7 => Ok(EntryType::SymLink),
602 _ => Err(ParsingError::BadEntryType(value)),
603 }
604 }
605}
606
607#[derive(PartialEq)]
617#[repr(u32)]
618pub enum FeatureIncompat {
619 Compression = 0x1,
620 EntryHasFileType = 0x2,
622
623 HasJournal = 0x4,
630 JournalSeparate = 0x8,
631 MetaBlockGroups = 0x10,
632 Extents = 0x40,
635 Is64Bit = 0x80,
636 MultiMountProtection = 0x100,
637
638 FlexibleBlockGroups = 0x200,
641 ExtendedAttributeINodes = 0x400,
642 ExtendedDirectoryEntry = 0x1000,
643 MetadataChecksum = 0x2000,
645 LargeDirectory = 0x4000,
646 SmallFilesInINode = 0x8000,
647 EncryptedINodes = 0x10000,
648}
649
650pub const REQUIRED_FEATURE_INCOMPAT: u32 =
652 FeatureIncompat::Extents as u32 | FeatureIncompat::EntryHasFileType as u32;
653
654pub const BANNED_FEATURE_INCOMPAT: u32 = FeatureIncompat::Compression as u32
656 | FeatureIncompat::MultiMountProtection as u32
657 | FeatureIncompat::ExtendedAttributeINodes as u32
658 | FeatureIncompat::ExtendedDirectoryEntry as u32
659 | FeatureIncompat::SmallFilesInINode as u32
660 | FeatureIncompat::EncryptedINodes as u32;
661
662pub trait ParseToStruct:
665 FromBytes + KnownLayout + Immutable + Unaligned + IntoBytes + Sized
666{
667 fn from_reader_with_offset(reader: &dyn Reader, offset: u64) -> Result<Self, ParsingError> {
668 if offset < FIRST_BG_PADDING {
669 return Err(ParsingError::InvalidAddress(
670 InvalidAddressErrorType::Lower,
671 offset,
672 FIRST_BG_PADDING,
673 ));
674 }
675 let mut object = Self::new_zeroed();
676 let buffer = unsafe {
681 std::slice::from_raw_parts_mut(&mut object as *mut Self as *mut u8, size_of::<Self>())
682 };
683 reader.read(offset, buffer)?;
684 Ok(object)
685 }
686
687 fn to_struct_ref(data: &[u8], error_type: ParsingError) -> Result<&Self, ParsingError> {
691 Ref::<&[u8], Self>::from_bytes(data).map(|res| Ref::into_ref(res)).map_err(|_| error_type)
692 }
693
694 fn from_struct_to_writer(
695 &self,
696 writer: &dyn ReaderWriter,
697 offset: u64,
698 ) -> Result<(), ParsingError> {
699 let data = self.as_bytes();
700 writer.write(offset, data)?;
701 Ok(())
702 }
703}
704
705impl<T: FromBytes + KnownLayout + Immutable + Unaligned + IntoBytes> ParseToStruct for T {}
707
708impl<B: SplitByteSlice> ExtentTreeNode<B> {
709 pub fn parse(data: B) -> Option<Self> {
714 Ref::<B, ExtentHeader>::from_prefix(data)
715 .ok()
716 .map(|(header, entries)| Self { header, entries })
717 }
718}
719
720impl SuperBlock {
721 pub fn parse(reader: &dyn Reader) -> Result<SuperBlock, ParsingError> {
723 let sb = SuperBlock::from_reader_with_offset(reader, FIRST_BG_PADDING)?;
727 sb.check_magic()?;
728 sb.feature_check()?;
729 sb.check_inode_size()?;
730 sb.check_block_group_descriptor_size()?;
731 Ok(sb)
732 }
733
734 pub fn check_magic(&self) -> Result<(), ParsingError> {
735 if self.e2fs_magic.get() == SB_MAGIC {
736 Ok(())
737 } else {
738 Err(ParsingError::InvalidSuperBlockMagic(self.e2fs_magic.get()))
739 }
740 }
741
742 pub fn check_inode_size(&self) -> Result<(), ParsingError> {
743 let inode_size: u64 = self.e2fs_inode_size.into();
744 if inode_size < MINIMUM_INODE_SIZE {
745 Err(ParsingError::InvalidInodeSize(self.e2fs_inode_size.get()))
746 } else {
747 Ok(())
748 }
749 }
750
751 fn check_block_group_descriptor_size(&self) -> Result<(), ParsingError> {
752 let desc_size: usize = self.e3fs_desc_size.into();
753 let is_64bit = self.is_64bit();
754 if desc_size == 0
756 || is_64bit && desc_size == size_of::<BlockGroupDesc64>()
757 || !is_64bit && desc_size == size_of::<BlockGroupDesc32>()
758 {
759 Ok(())
760 } else {
761 Err(ParsingError::InvalidBlockGroupDescSize(self.e3fs_desc_size.get()))
762 }
763 }
764
765 pub fn block_size(&self) -> Result<u64, ParsingError> {
770 let bs = 2u64
771 .checked_pow(self.e2fs_log_bsize.get() + 10)
772 .ok_or_else(|| ParsingError::BlockSizeInvalid(self.e2fs_log_bsize.get()))?;
773 if bs == 1024 || bs == 2048 || bs == 4096 || bs == 65536 {
774 Ok(bs)
775 } else {
776 Err(ParsingError::BlockSizeInvalid(self.e2fs_log_bsize.get()))
777 }
778 }
779
780 pub fn block_group_descriptor_size(&self) -> usize {
782 if self.is_64bit() { size_of::<BlockGroupDesc64>() } else { size_of::<BlockGroupDesc32>() }
785 }
786
787 pub fn is_64bit(&self) -> bool {
789 self.e2fs_features_incompat.get() & FeatureIncompat::Is64Bit as u32 != 0
790 }
791
792 fn feature_check(&self) -> Result<(), ParsingError> {
793 let banned = self.e2fs_features_incompat.get() & BANNED_FEATURE_INCOMPAT;
794 if banned > 0 {
795 return Err(ParsingError::BannedFeatureIncompat(banned));
796 }
797 let required = self.e2fs_features_incompat.get() & REQUIRED_FEATURE_INCOMPAT;
798 if required != REQUIRED_FEATURE_INCOMPAT {
799 return Err(ParsingError::RequiredFeatureIncompat(
800 required ^ REQUIRED_FEATURE_INCOMPAT,
801 ));
802 }
803 Ok(())
804 }
805}
806
807impl INode {
808 pub fn extent_tree_node(&self) -> Result<ExtentTreeNode<&[u8]>, ParsingError> {
811 let eh = ExtentTreeNode::<&[u8]>::parse(
812 &self.e2di_blocks,
815 )
816 .ok_or(ParsingError::InvalidExtentHeader)?;
817 eh.header.check_magic()?;
818 Ok(eh)
819 }
820
821 pub fn size(&self) -> u64 {
823 (self.e2di_size_high.get() as u64) << 32 | self.e2di_size.get() as u64
824 }
825
826 pub fn update_size(&mut self, size: u64) {
827 self.e2di_size_high.set((size >> 32) as u32);
828 self.e2di_size.set((size & 0xFFFFFFFF) as u32);
829 }
830
831 pub fn facl(&self) -> u64 {
832 (self.e2di_facl_high.get() as u64) << 32 | self.e2di_facl.get() as u64
833 }
834
835 fn check_inode_size(sb: &SuperBlock) -> Result<(), ParsingError> {
836 let inode_size: usize = sb.e2fs_inode_size.into();
837 if inode_size < std::mem::size_of::<INode>() {
838 Err(ParsingError::Incompatible("Inode size too small.".to_string()))
839 } else {
840 Ok(())
841 }
842 }
843
844 pub fn e4di_extra_isize(&self, sb: &SuperBlock) -> Result<LEU16, ParsingError> {
845 INode::check_inode_size(sb)?;
846 Ok(self.e4di_extra_isize)
847 }
848
849 pub fn e4di_chksum_hi(&self, sb: &SuperBlock) -> Result<LEU16, ParsingError> {
850 INode::check_inode_size(sb)?;
851 Ok(self.e4di_chksum_hi)
852 }
853
854 pub fn e4di_ctime_extra(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
855 INode::check_inode_size(sb)?;
856 Ok(self.e4di_ctime_extra)
857 }
858
859 pub fn e4di_mtime_extra(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
860 INode::check_inode_size(sb)?;
861 Ok(self.e4di_mtime_extra)
862 }
863
864 pub fn e4di_atime_extra(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
865 INode::check_inode_size(sb)?;
866 Ok(self.e4di_atime_extra)
867 }
868
869 pub fn e4di_crtime(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
870 INode::check_inode_size(sb)?;
871 Ok(self.e4di_crtime)
872 }
873
874 pub fn e4di_crtime_extra(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
875 INode::check_inode_size(sb)?;
876 Ok(self.e4di_crtime_extra)
877 }
878
879 pub fn e4di_version_hi(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
880 INode::check_inode_size(sb)?;
881 Ok(self.e4di_version_hi)
882 }
883
884 pub fn e4di_projid(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
885 INode::check_inode_size(sb)?;
886 Ok(self.e4di_projid)
887 }
888}
889
890impl ExtentHeader {
891 pub fn check_magic(&self) -> Result<(), ParsingError> {
892 if self.eh_magic.get() == EH_MAGIC {
893 Ok(())
894 } else {
895 Err(ParsingError::InvalidExtentHeaderMagic(self.eh_magic.get()))
896 }
897 }
898}
899
900impl DirEntry2 {
901 pub fn name(&self) -> Result<&str, ParsingError> {
903 str::from_utf8(&self.e2d_name[0..self.e2d_namlen as usize]).map_err(|_| {
904 ParsingError::DirEntry2NonUtf8(self.e2d_name[0..self.e2d_namlen as usize].to_vec())
905 })
906 }
907
908 pub fn name_bytes(&self) -> &[u8] {
909 &self.e2d_name[0..self.e2d_namlen as usize]
910 }
911
912 pub fn as_hash_map(
917 entries: Vec<DirEntry2>,
918 ) -> Result<HashMap<String, DirEntry2>, ParsingError> {
919 let mut entry_map: HashMap<String, DirEntry2> = HashMap::with_capacity(entries.len());
920
921 for entry in entries {
922 entry_map.insert(entry.name()?.to_string(), entry);
923 }
924 Ok(entry_map)
925 }
926}
927
928impl Extent {
929 pub fn target_block_num(&self) -> u64 {
931 (self.e_start_hi.get() as u64) << 32 | self.e_start_lo.get() as u64
932 }
933}
934
935impl ExtentIndex {
936 pub fn target_block_num(&self) -> u64 {
938 (self.ei_leaf_hi.get() as u64) << 32 | self.ei_leaf_lo.get() as u64
939 }
940}
941
942#[cfg(test)]
943mod test {
944 use super::{
945 EH_MAGIC, Extent, ExtentHeader, ExtentIndex, FIRST_BG_PADDING, FeatureIncompat, LEU16,
946 LEU32, LEU64, ParseToStruct, REQUIRED_FEATURE_INCOMPAT, SB_MAGIC, SuperBlock,
947 };
948 use crate::readers::VecReader;
949 use std::fs;
950
951 impl Default for SuperBlock {
952 fn default() -> SuperBlock {
953 SuperBlock {
954 e2fs_icount: LEU32::new(0),
955 e2fs_bcount: LEU32::new(0),
956 e2fs_rbcount: LEU32::new(0),
957 e2fs_fbcount: LEU32::new(0),
958 e2fs_ficount: LEU32::new(0),
959 e2fs_first_dblock: LEU32::new(0),
960 e2fs_log_bsize: LEU32::new(0),
961 e2fs_log_fsize: LEU32::new(0),
962 e2fs_bpg: LEU32::new(0),
963 e2fs_fpg: LEU32::new(0),
964 e2fs_ipg: LEU32::new(0),
965 e2fs_mtime: LEU32::new(0),
966 e2fs_wtime: LEU32::new(0),
967 e2fs_mnt_count: LEU16::new(0),
968 e2fs_max_mnt_count: LEU16::new(0),
969 e2fs_magic: LEU16::new(0),
970 e2fs_state: LEU16::new(0),
971 e2fs_beh: LEU16::new(0),
972 e2fs_minrev: LEU16::new(0),
973 e2fs_lastfsck: LEU32::new(0),
974 e2fs_fsckintv: LEU32::new(0),
975 e2fs_creator: LEU32::new(0),
976 e2fs_rev: LEU32::new(0),
977 e2fs_ruid: LEU16::new(0),
978 e2fs_rgid: LEU16::new(0),
979 e2fs_first_ino: LEU32::new(0),
980 e2fs_inode_size: LEU16::new(0),
981 e2fs_block_group_nr: LEU16::new(0),
982 e2fs_features_compat: LEU32::new(0),
983 e2fs_features_incompat: LEU32::new(0),
984 e2fs_features_rocompat: LEU32::new(0),
985 e2fs_uuid: [0; 16],
986 e2fs_vname: [0; 16],
987 e2fs_fsmnt: [0; 64],
988 e2fs_algo: LEU32::new(0),
989 e2fs_prealloc: 0,
990 e2fs_dir_prealloc: 0,
991 e2fs_reserved_ngdb: LEU16::new(0),
992 e3fs_journal_uuid: [0; 16],
993 e3fs_journal_inum: LEU32::new(0),
994 e3fs_journal_dev: LEU32::new(0),
995 e3fs_last_orphan: LEU32::new(0),
996 e3fs_hash_seed: [LEU32::new(0); 4],
997 e3fs_def_hash_version: 0,
998 e3fs_jnl_backup_type: 0,
999 e3fs_desc_size: LEU16::new(0),
1000 e3fs_default_mount_opts: LEU32::new(0),
1001 e3fs_first_meta_bg: LEU32::new(0),
1002 e3fs_mkfs_time: LEU32::new(0),
1003 e3fs_jnl_blks: [LEU32::new(0); 17],
1004 e4fs_bcount_hi: LEU32::new(0),
1005 e4fs_rbcount_hi: LEU32::new(0),
1006 e4fs_fbcount_hi: LEU32::new(0),
1007 e4fs_min_extra_isize: LEU16::new(0),
1008 e4fs_want_extra_isize: LEU16::new(0),
1009 e4fs_flags: LEU32::new(0),
1010 e4fs_raid_stride: LEU16::new(0),
1011 e4fs_mmpintv: LEU16::new(0),
1012 e4fs_mmpblk: LEU64::new(0),
1013 e4fs_raid_stripe_wid: LEU32::new(0),
1014 e4fs_log_gpf: 0,
1015 e4fs_chksum_type: 0,
1016 e4fs_encrypt: 0,
1017 e4fs_reserved_pad: 0,
1018 e4fs_kbytes_written: LEU64::new(0),
1019 e4fs_snapinum: LEU32::new(0),
1020 e4fs_snapid: LEU32::new(0),
1021 e4fs_snaprbcount: LEU64::new(0),
1022 e4fs_snaplist: LEU32::new(0),
1023 e4fs_errcount: LEU32::new(0),
1024 e4fs_first_errtime: LEU32::new(0),
1025 e4fs_first_errino: LEU32::new(0),
1026 e4fs_first_errblk: LEU64::new(0),
1027 e4fs_first_errfunc: [0; 32],
1028 e4fs_first_errline: LEU32::new(0),
1029 e4fs_last_errtime: LEU32::new(0),
1030 e4fs_last_errino: LEU32::new(0),
1031 e4fs_last_errline: LEU32::new(0),
1032 e4fs_last_errblk: LEU64::new(0),
1033 e4fs_last_errfunc: [0; 32],
1034 e4fs_mount_opts: [0; 64],
1035 e4fs_usrquota_inum: LEU32::new(0),
1036 e4fs_grpquota_inum: LEU32::new(0),
1037 e4fs_overhead_clusters: LEU32::new(0),
1038 e4fs_backup_bgs: [LEU32::new(0); 2],
1039 e4fs_encrypt_algos: [0; 4],
1040 e4fs_encrypt_pw_salt: [0; 16],
1041 e4fs_lpf_ino: LEU32::new(0),
1042 e4fs_proj_quota_inum: LEU32::new(0),
1043 e4fs_chksum_seed: LEU32::new(0),
1044 e4fs_reserved: [LEU32::new(0); 98],
1045 e4fs_sbchksum: LEU32::new(0),
1046 }
1047 }
1048 }
1049
1050 #[fuchsia::test]
1059 fn parse_superblock() {
1060 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
1061 let reader = VecReader::new(data);
1062 let sb = SuperBlock::parse(&reader).expect("Parsed Super Block");
1063 assert_eq!(sb.block_size().unwrap(), FIRST_BG_PADDING);
1065
1066 assert!(sb.check_magic().is_ok());
1068
1069 let mut sb = SuperBlock::default();
1070 assert!(sb.check_magic().is_err());
1071
1072 sb.e2fs_magic = LEU16::new(SB_MAGIC);
1073 assert!(sb.check_magic().is_ok());
1074
1075 sb.e2fs_log_bsize = LEU32::new(0); assert!(sb.block_size().is_ok());
1078 sb.e2fs_log_bsize = LEU32::new(1); assert!(sb.block_size().is_ok());
1080 sb.e2fs_log_bsize = LEU32::new(2); assert!(sb.block_size().is_ok());
1082 sb.e2fs_log_bsize = LEU32::new(6); assert!(sb.block_size().is_ok());
1084
1085 sb.e2fs_log_bsize = LEU32::new(3);
1087 assert!(sb.block_size().is_err());
1088 sb.e2fs_log_bsize = LEU32::new(5);
1089 assert!(sb.block_size().is_err());
1090 sb.e2fs_log_bsize = LEU32::new(7);
1091 assert!(sb.block_size().is_err());
1092 sb.e2fs_log_bsize = LEU32::new(20);
1094 assert!(sb.block_size().is_err());
1095 }
1096
1097 #[fuchsia::test]
1099 fn parse_to_struct_from_reader_with_offset() {
1100 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
1101 let reader = VecReader::new(data);
1102 let sb = SuperBlock::from_reader_with_offset(&reader, FIRST_BG_PADDING)
1103 .expect("Parsed Super Block");
1104 assert!(sb.check_magic().is_ok());
1105 }
1106
1107 #[fuchsia::test]
1109 fn incompatible_feature_flags() {
1110 let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
1111 let reader = VecReader::new(data);
1112 let sb = SuperBlock::parse(&reader).expect("Parsed Super Block");
1113 assert_eq!(sb.e2fs_magic.get(), SB_MAGIC);
1114 assert!(sb.feature_check().is_ok());
1115
1116 let mut sb = SuperBlock::default();
1117 match sb.feature_check() {
1118 Ok(_) => assert!(false, "Feature flags should be incorrect."),
1119 Err(e) => assert_eq!(
1120 format!("{}", e),
1121 format!(
1122 "Required feature flags (feature_incompat): 0x{:X}",
1123 REQUIRED_FEATURE_INCOMPAT
1124 )
1125 ),
1126 }
1127
1128 sb.e2fs_features_incompat = LEU32::new(REQUIRED_FEATURE_INCOMPAT | 0xF00000);
1130 assert!(sb.feature_check().is_ok());
1131
1132 sb.e2fs_features_incompat = LEU32::new(FeatureIncompat::Extents as u32);
1134 match sb.feature_check() {
1135 Ok(_) => assert!(false, "Feature flags should be incorrect."),
1136 Err(e) => assert_eq!(
1137 format!("{}", e),
1138 format!(
1139 "Required feature flags (feature_incompat): 0x{:X}",
1140 REQUIRED_FEATURE_INCOMPAT ^ FeatureIncompat::Extents as u32
1141 )
1142 ),
1143 }
1144
1145 sb.e2fs_features_incompat = LEU32::new(FeatureIncompat::Compression as u32);
1147 match sb.feature_check() {
1148 Ok(_) => assert!(false, "Feature flags should be incorrect."),
1149 Err(e) => assert_eq!(
1150 format!("{}", e),
1151 format!(
1152 "Incompatible feature flags (feature_incompat): 0x{:X}",
1153 FeatureIncompat::Compression as u32
1154 )
1155 ),
1156 }
1157 }
1158
1159 #[fuchsia::test]
1161 fn extent_target_block_num() {
1162 let e = Extent {
1163 e_blk: LEU32::new(0),
1164 e_len: LEU16::new(0),
1165 e_start_hi: LEU16::new(0x4444),
1166 e_start_lo: LEU32::new(0x6666_8888),
1167 };
1168 assert_eq!(e.target_block_num(), 0x4444_6666_8888);
1169 }
1170
1171 #[fuchsia::test]
1173 fn extent_index_target_block_num() {
1174 let e = ExtentIndex {
1175 ei_blk: LEU32::new(0),
1176 ei_leaf_lo: LEU32::new(0x6666_8888),
1177 ei_leaf_hi: LEU16::new(0x4444),
1178 ei_unused: LEU16::new(0),
1179 };
1180 assert_eq!(e.target_block_num(), 0x4444_6666_8888);
1181 }
1182
1183 #[fuchsia::test]
1185 fn extent_header_check_magic() {
1186 let e = ExtentHeader {
1187 eh_magic: LEU16::new(EH_MAGIC),
1188 eh_ecount: LEU16::new(0),
1189 eh_max: LEU16::new(0),
1190 eh_depth: LEU16::new(0),
1191 eh_gen: LEU32::new(0),
1192 };
1193 assert!(e.check_magic().is_ok());
1194
1195 let e = ExtentHeader {
1196 eh_magic: LEU16::new(0x1234),
1197 eh_ecount: LEU16::new(0),
1198 eh_max: LEU16::new(0),
1199 eh_depth: LEU16::new(0),
1200 eh_gen: LEU32::new(0),
1201 };
1202 assert!(e.check_magic().is_err());
1203 }
1204}