Skip to main content

ext4_read_only/
structs.rs

1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2012, 2010 Zheng Liu <lz@freebsd.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 * $FreeBSD$
29 */
30
31// Copyright 2019 The Fuchsia Authors. All rights reserved.
32// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
34
35use 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
43// Block Group 0 Padding
44pub const FIRST_BG_PADDING: u64 = 1024;
45// INode number of root directory '/'.
46pub const ROOT_INODE_NUM: u32 = 2;
47// EXT 2/3/4 magic number.
48pub const SB_MAGIC: u16 = 0xEF53;
49// Extent Header magic number.
50pub const EH_MAGIC: u16 = 0xF30A;
51// Any smaller would not even fit the first copy of the ext4 Super Block.
52pub const MIN_EXT4_SIZE: u64 = FIRST_BG_PADDING + size_of::<SuperBlock>() as u64;
53/// The smallest supported INode size.
54pub const MINIMUM_INODE_SIZE: u64 = 128;
55
56#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
57#[repr(C)]
58pub struct ExtentHeader {
59    /// Magic number: 0xF30A
60    pub eh_magic: LEU16,
61    /// Number of valid entries.
62    pub eh_ecount: LEU16,
63    /// Entry capacity.
64    pub eh_max: LEU16,
65    /// Depth distance this node is from its leaves.
66    /// `0` here means it is a leaf node.
67    pub eh_depth: LEU16,
68    /// Generation of extent tree.
69    pub eh_gen: LEU32,
70}
71// Make sure our struct's size matches the Ext4 spec.
72// https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
73assert_eq_size!(ExtentHeader, [u8; 12]);
74
75#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
76#[repr(C)]
77pub struct ExtentIndex {
78    /// Indexes logical blocks.
79    pub ei_blk: LEU32,
80    /// Points to the physical block of the next level.
81    pub ei_leaf_lo: LEU32,
82    /// High 16 bits of physical block.
83    pub ei_leaf_hi: LEU16,
84    pub ei_unused: LEU16,
85}
86// Make sure our struct's size matches the Ext4 spec.
87// https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
88assert_eq_size!(ExtentIndex, [u8; 12]);
89
90#[derive(Clone, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug)]
91#[repr(C)]
92pub struct Extent {
93    /// First logical block.
94    pub e_blk: LEU32,
95    /// Number of blocks.
96    pub e_len: LEU16,
97    /// High 16 bits of physical block.
98    pub e_start_hi: LEU16,
99    /// Low 32 bits of physical block.
100    pub e_start_lo: LEU32,
101}
102// Make sure our struct's size matches the Ext4 spec.
103// https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
104assert_eq_size!(Extent, [u8; 12]);
105
106#[derive(std::fmt::Debug)]
107#[repr(C)]
108pub struct DirEntry2 {
109    /// INode number of entry
110    pub e2d_ino: LEU32,
111    /// Length of this record.
112    pub e2d_reclen: LEU16,
113    /// Length of string in `e2d_name`.
114    pub e2d_namlen: u8,
115    /// File type of this entry.
116    pub e2d_type: u8,
117    /// Name of the entry.
118    pub e2d_name: [u8; 255],
119}
120
121#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, std::fmt::Debug)]
122#[repr(C)]
123pub struct DirEntryHeader {
124    /// INode number of entry
125    pub e2d_ino: LEU32,
126    /// Length of this record.
127    pub e2d_reclen: LEU16,
128    /// Length of string in `e2d_name`.
129    pub e2d_namlen: u8,
130    /// File type of this entry.
131    pub e2d_type: u8,
132}
133// Make sure our struct's size matches the Ext4 spec.
134// https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
135assert_eq_size!(DirEntryHeader, [u8; 8]);
136
137#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug)]
138#[repr(C)]
139pub struct SuperBlock {
140    /// INode count.
141    pub e2fs_icount: LEU32,
142    /// Block count.
143    pub e2fs_bcount: LEU32,
144    /// Reserved blocks count.
145    pub e2fs_rbcount: LEU32,
146    /// Free blocks count.
147    pub e2fs_fbcount: LEU32,
148    /// Free INodes count.
149    pub e2fs_ficount: LEU32,
150    /// First data block.
151    pub e2fs_first_dblock: LEU32,
152    /// Block Size = 2^(e2fs_log_bsize+10).
153    pub e2fs_log_bsize: LEU32,
154    /// Fragment size.
155    pub e2fs_log_fsize: LEU32,
156    /// Blocks per group.
157    pub e2fs_bpg: LEU32,
158    /// Fragments per group.
159    pub e2fs_fpg: LEU32,
160    /// INodes per group.
161    pub e2fs_ipg: LEU32,
162    /// Mount time.
163    pub e2fs_mtime: LEU32,
164    /// Write time.
165    pub e2fs_wtime: LEU32,
166    /// Mount count.
167    pub e2fs_mnt_count: LEU16,
168    /// Max mount count.
169    pub e2fs_max_mnt_count: LEU16,
170    /// Magic number: 0xEF53
171    pub e2fs_magic: LEU16,
172    /// Filesystem state.
173    pub e2fs_state: LEU16,
174    /// Behavior on errors.
175    pub e2fs_beh: LEU16,
176    /// Minor revision level.
177    pub e2fs_minrev: LEU16,
178    /// Time of last filesystem check.
179    pub e2fs_lastfsck: LEU32,
180    /// Max time between filesystem checks.
181    pub e2fs_fsckintv: LEU32,
182    /// Creator OS.
183    pub e2fs_creator: LEU32,
184    /// Revision level.
185    pub e2fs_rev: LEU32,
186    /// Default UID for reserved blocks.
187    pub e2fs_ruid: LEU16,
188    /// Default GID for reserved blocks.
189    pub e2fs_rgid: LEU16,
190    /// First non-reserved inode.
191    pub e2fs_first_ino: LEU32,
192    /// Size of INode structure.
193    pub e2fs_inode_size: LEU16,
194    /// Block group number of this super block.
195    pub e2fs_block_group_nr: LEU16,
196    /// Compatible feature set.
197    pub e2fs_features_compat: LEU32,
198    /// Incompatible feature set.
199    pub e2fs_features_incompat: LEU32,
200    /// RO-compatible feature set.
201    pub e2fs_features_rocompat: LEU32,
202    /// 128-bit uuid for volume.
203    pub e2fs_uuid: [u8; 16],
204    /// Volume name.
205    pub e2fs_vname: [u8; 16],
206    /// Name as mounted.
207    pub e2fs_fsmnt: [u8; 64],
208    /// Compression algorithm.
209    pub e2fs_algo: LEU32,
210    /// # of blocks for old prealloc.
211    pub e2fs_prealloc: u8,
212    /// # of blocks for old prealloc dirs.
213    pub e2fs_dir_prealloc: u8,
214    /// # of reserved gd blocks for resize.
215    pub e2fs_reserved_ngdb: LEU16,
216    /// UUID of journal super block.
217    pub e3fs_journal_uuid: [u8; 16],
218    /// INode number of journal file.
219    pub e3fs_journal_inum: LEU32,
220    /// Device number of journal file.
221    pub e3fs_journal_dev: LEU32,
222    /// Start of list of inodes to delete.
223    pub e3fs_last_orphan: LEU32,
224    /// HTREE hash seed.
225    pub e3fs_hash_seed: [LEU32; 4],
226    /// Default hash version to use.
227    pub e3fs_def_hash_version: u8,
228    /// Journal backup type.
229    pub e3fs_jnl_backup_type: u8,
230    /// size of group descriptor.
231    pub e3fs_desc_size: LEU16,
232    /// Default mount options.
233    pub e3fs_default_mount_opts: LEU32,
234    /// First metablock block group.
235    pub e3fs_first_meta_bg: LEU32,
236    /// When the filesystem was created.
237    pub e3fs_mkfs_time: LEU32,
238    /// Backup of the journal INode.
239    pub e3fs_jnl_blks: [LEU32; 17],
240    /// High bits of block count.
241    pub e4fs_bcount_hi: LEU32,
242    /// High bits of reserved blocks count.
243    pub e4fs_rbcount_hi: LEU32,
244    /// High bits of free blocks count.
245    pub e4fs_fbcount_hi: LEU32,
246    /// All inodes have some bytes.
247    pub e4fs_min_extra_isize: LEU16,
248    /// Inodes must reserve some bytes.
249    pub e4fs_want_extra_isize: LEU16,
250    /// Miscellaneous flags.
251    pub e4fs_flags: LEU32,
252    /// RAID stride.
253    pub e4fs_raid_stride: LEU16,
254    /// Seconds to wait in MMP checking.
255    pub e4fs_mmpintv: LEU16,
256    /// Block for multi-mount protection.
257    pub e4fs_mmpblk: LEU64,
258    /// Blocks on data disks (N * stride).
259    pub e4fs_raid_stripe_wid: LEU32,
260    /// FLEX_BG group size.
261    pub e4fs_log_gpf: u8,
262    /// Metadata checksum algorithm used.
263    pub e4fs_chksum_type: u8,
264    /// Versioning level for encryption.
265    pub e4fs_encrypt: u8,
266    pub e4fs_reserved_pad: u8,
267    /// Number of lifetime kilobytes.
268    pub e4fs_kbytes_written: LEU64,
269    /// INode number of active snapshot.
270    pub e4fs_snapinum: LEU32,
271    /// Sequential ID of active snapshot.
272    pub e4fs_snapid: LEU32,
273    /// Reserved blocks for active snapshot.
274    pub e4fs_snaprbcount: LEU64,
275    /// INode number for on-disk snapshot.
276    pub e4fs_snaplist: LEU32,
277    /// Number of filesystem errors.
278    pub e4fs_errcount: LEU32,
279    /// First time an error happened.
280    pub e4fs_first_errtime: LEU32,
281    /// INode involved in first error.
282    pub e4fs_first_errino: LEU32,
283    /// Block involved of first error.
284    pub e4fs_first_errblk: LEU64,
285    /// Function where error happened.
286    pub e4fs_first_errfunc: [u8; 32],
287    /// Line number where error happened.
288    pub e4fs_first_errline: LEU32,
289    /// Most recent time of an error.
290    pub e4fs_last_errtime: LEU32,
291    /// INode involved in last error.
292    pub e4fs_last_errino: LEU32,
293    /// Line number where error happened.
294    pub e4fs_last_errline: LEU32,
295    /// Block involved of last error.
296    pub e4fs_last_errblk: LEU64,
297    /// Function where error happened.
298    pub e4fs_last_errfunc: [u8; 32],
299    /// Mount options.
300    pub e4fs_mount_opts: [u8; 64],
301    /// INode for tracking user quota.
302    pub e4fs_usrquota_inum: LEU32,
303    /// INode for tracking group quota.
304    pub e4fs_grpquota_inum: LEU32,
305    /// Overhead blocks/clusters.
306    pub e4fs_overhead_clusters: LEU32,
307    /// Groups with sparse_super2 SBs.
308    pub e4fs_backup_bgs: [LEU32; 2],
309    /// Encryption algorithms in use.
310    pub e4fs_encrypt_algos: [u8; 4],
311    /// Salt used for string2key.
312    pub e4fs_encrypt_pw_salt: [u8; 16],
313    /// Location of the lost+found inode.
314    pub e4fs_lpf_ino: LEU32,
315    /// INode for tracking project quota.
316    pub e4fs_proj_quota_inum: LEU32,
317    /// Checksum seed.
318    pub e4fs_chksum_seed: LEU32,
319    /// Padding to the end of the block.
320    pub e4fs_reserved: [LEU32; 98],
321    /// Super block checksum.
322    pub e4fs_sbchksum: LEU32,
323}
324// Make sure our struct's size matches the Ext4 spec.
325// https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
326assert_eq_size!(SuperBlock, [u8; 1024]);
327
328#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
329#[repr(C)]
330pub struct BlockGroupDesc32 {
331    /// Blocks bitmap block.
332    pub ext2bgd_b_bitmap: LEU32,
333    /// INodes bitmap block.
334    pub ext2bgd_i_bitmap: LEU32,
335    /// INodes table block.
336    pub ext2bgd_i_tables: LEU32,
337    /// # Free blocks.
338    pub ext2bgd_nbfree: LEU16,
339    /// # Free INodes.
340    pub ext2bgd_nifree: LEU16,
341    /// # Directories.
342    pub ext2bgd_ndirs: LEU16,
343    /// Block group flags.
344    pub ext4bgd_flags: LEU16,
345    /// Snapshot exclusion bitmap location.
346    pub ext4bgd_x_bitmap: LEU32,
347    /// Block bitmap checksum.
348    pub ext4bgd_b_bmap_csum: LEU16,
349    /// INode bitmap checksum.
350    pub ext4bgd_i_bmap_csum: LEU16,
351    /// Unused INode count.
352    pub ext4bgd_i_unused: LEU16,
353    /// Group descriptor checksum.
354    pub ext4bgd_csum: LEU16,
355}
356// Make sure our struct's size matches the Ext4 spec.
357// https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
358assert_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}
376// Make sure our struct's size matches the Ext4 spec.
377// https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
378assert_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    /// Access permission flags.
391    pub e2di_mode: LEU16,
392    /// Owner UID.
393    pub e2di_uid: LEU16,
394    /// Size (in bytes).
395    pub e2di_size: LEU32,
396    /// Access time.
397    pub e2di_atime: LEU32,
398    /// Change time.
399    pub e2di_ctime: LEU32,
400    /// Modification time.
401    pub e2di_mtime: LEU32,
402    /// Deletion time.
403    pub e2di_dtime: LEU32,
404    /// Owner GID.
405    pub e2di_gid: LEU16,
406    /// File link count.
407    pub e2di_nlink: LEU16,
408    /// Block count.
409    pub e2di_nblock: LEU32,
410    /// Status flags.
411    pub e2di_flags: LEU32,
412    /// INode version.
413    pub e2di_version: [u8; 4],
414    /// Extent tree.
415    pub e2di_blocks: [u8; 60],
416    /// Generation.
417    pub e2di_gen: LEU32,
418    /// EA block.
419    pub e2di_facl: LEU32,
420    /// High bits for file size.
421    pub e2di_size_high: LEU32,
422    /// Fragment address (obsolete).
423    pub e2di_faddr: LEU32,
424    /// High bits for block count.
425    pub e2di_nblock_high: LEU16,
426    /// High bits for EA block.
427    pub e2di_facl_high: LEU16,
428    /// High bits for Owner UID.
429    pub e2di_uid_high: LEU16,
430    /// High bits for Owner GID.
431    pub e2di_gid_high: LEU16,
432    /// High bits for INode checksum.
433    pub e2di_chksum_lo: LEU16,
434    pub e2di_lx_reserved: LEU16,
435    // Note: The fields below are not always present, depending on the size of the inode. Users are
436    // expected to access them via methods on `INode`, which verify that the data is valid.
437    /// Size of the fields in the inode struct after and including this one.
438    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}
448// Make sure our struct's size matches the Ext4 spec.
449// https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
450assert_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    // TODO(https://fxbug.dev/42073143): A followup change will add the ability to include an address here.
510    #[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    /// Feature Incompatible flags
531    #[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    /// Message including what ext filesystem feature was found that we do not support
537    #[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/// Directory Entry types.
578#[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/// Feature Incompatible flags.
608///
609/// Stored in SuperBlock::e2fs_features_incompat.
610///
611/// All flags listed by a given filesystem must be supported by us in order for us to attempt
612/// mounting it.
613///
614/// With our limited support at the time, there are also flags that we require to exist, like
615/// `EntryHasFileType`.
616#[derive(PartialEq)]
617#[repr(u32)]
618pub enum FeatureIncompat {
619    Compression = 0x1,
620    /// Currently required flag.
621    EntryHasFileType = 0x2,
622
623    // TODO(https://fxbug.dev/42073143): We will permit journaling because our initial use will not have
624    // entries in the journal. Will need to add proper support in the future.
625    /// We do not support journaling, but assuming an empty journal, we can still read.
626    ///
627    /// Run `fsck` in Linux to repair the filesystem first before attempting to mount a journaled
628    /// ext4 image.
629    HasJournal = 0x4,
630    JournalSeparate = 0x8,
631    MetaBlockGroups = 0x10,
632    /// Required flag. Lack of this flag means the filesystem is not ext4, and we are not
633    /// backward compatible.
634    Extents = 0x40,
635    Is64Bit = 0x80,
636    MultiMountProtection = 0x100,
637
638    // TODO(https://fxbug.dev/42073143): Should be relatively trivial to support.
639    /// No explicit support, we will permit the flag as it works for our needs.
640    FlexibleBlockGroups = 0x200,
641    ExtendedAttributeINodes = 0x400,
642    ExtendedDirectoryEntry = 0x1000,
643    /// We do not calculate checksums, so this is permitted but not actionable.
644    MetadataChecksum = 0x2000,
645    LargeDirectory = 0x4000,
646    SmallFilesInINode = 0x8000,
647    EncryptedINodes = 0x10000,
648}
649
650/// Required "feature incompatible" flags.
651pub const REQUIRED_FEATURE_INCOMPAT: u32 =
652    FeatureIncompat::Extents as u32 | FeatureIncompat::EntryHasFileType as u32;
653
654/// Banned "feature incompatible" flags.
655pub 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
662// TODO(mbrunson): Update this trait to follow error conventions similar to ExtentTreeNode::parse.
663/// All functions to help parse data into respective structs.
664pub 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        // SAFETY: Convert buffer to a byte slice. It's safe to read from this slice because object
677        // was created with new_zeroed, so its bit pattern is entirely initialized. It's safe to
678        // write to this slice because Self implements FromBytes, meaning any bit pattern written
679        // to the slice is valid to interpret as Self.
680        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    /// Casts the &[u8] data to &Self.
688    ///
689    /// `Self` is the ext4 struct that represents the given `data`.
690    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
705/// Apply to all EXT4 structs as seen above.
706impl<T: FromBytes + KnownLayout + Immutable + Unaligned + IntoBytes> ParseToStruct for T {}
707
708impl<B: SplitByteSlice> ExtentTreeNode<B> {
709    /// Parses a slice of bytes to create an `ExtentTreeNode`.
710    ///
711    /// `data` must be large enough to construct an ExtentHeader. If not, `None`
712    /// is returned. `data` is consumed by this operation.
713    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    /// Parse the Super Block at its default location.
722    pub fn parse(reader: &dyn Reader) -> Result<SuperBlock, ParsingError> {
723        // Super Block in Block Group 0 is at offset 1024.
724        // Assuming there is no corruption, there is no need to read any other
725        // copy of the Super Block.
726        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        // NB: Some images have a desc_size of 0.  Derive from is_64bit in that case.
755        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    /// Gets file system block size.
766    ///
767    /// Per spec, the only valid block sizes are 1KiB, 2KiB, 4KiB, and 64KiB. We will only
768    /// permit these values.
769    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    /// Returns the size of the block group descriptors
781    pub fn block_group_descriptor_size(&self) -> usize {
782        // NB: We ignore the size recorded in the superblock, since we checked it already and it has
783        // to be either 4 or 8
784        if self.is_64bit() { size_of::<BlockGroupDesc64>() } else { size_of::<BlockGroupDesc32>() }
785    }
786
787    /// Returns whether the filesystem is 64bit enabled
788    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    /// INode contains the root of its Extent tree within `e2di_blocks`.
809    /// Read `e2di_blocks` and return the root Extent Header.
810    pub fn extent_tree_node(&self) -> Result<ExtentTreeNode<&[u8]>, ParsingError> {
811        let eh = ExtentTreeNode::<&[u8]>::parse(
812            // Bounds here are known and static on a field that is defined to be
813            // the same size as an ExtentTreeNode.
814            &self.e2di_blocks,
815        )
816        .ok_or(ParsingError::InvalidExtentHeader)?;
817        eh.header.check_magic()?;
818        Ok(eh)
819    }
820
821    /// Size of the file/directory/entry represented by this INode.
822    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    /// Name of the file/directory/entry as a string.
902    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    /// Generate a hash table of the given directory entries.
913    ///
914    /// Key: name of entry
915    /// Value: DirEntry2 struct
916    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    /// Block number that this Extent points to.
930    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    /// Block number that this ExtentIndex points to.
937    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    // NOTE: Impls for `INode` and `DirEntry2` depend on calculated data locations. Testing these
1051    // functions are being done in `parser.rs` where those locations are being calculated.
1052
1053    // SuperBlock has a known data location and enables us to test `ParseToStruct` functions.
1054
1055    /// Covers these functions:
1056    /// - ParseToStruct::{read_from_offset, to_struct_arc, to_struct_ref, validate}
1057    /// - SuperBlock::{block_size, check_magic}
1058    #[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        // Block size of the 1file.img is 1KiB.
1064        assert_eq!(sb.block_size().unwrap(), FIRST_BG_PADDING);
1065
1066        // Validate magic number.
1067        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        // Validate block size.
1076        sb.e2fs_log_bsize = LEU32::new(0); // 1KiB
1077        assert!(sb.block_size().is_ok());
1078        sb.e2fs_log_bsize = LEU32::new(1); // 2KiB
1079        assert!(sb.block_size().is_ok());
1080        sb.e2fs_log_bsize = LEU32::new(2); // 4KiB
1081        assert!(sb.block_size().is_ok());
1082        sb.e2fs_log_bsize = LEU32::new(6); // 64KiB
1083        assert!(sb.block_size().is_ok());
1084
1085        // Others are disallowed, checking values neighboring valid ones.
1086        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        // How exhaustive do we get?
1093        sb.e2fs_log_bsize = LEU32::new(20);
1094        assert!(sb.block_size().is_err());
1095    }
1096
1097    /// Covers ParseToStruct::from_reader_with_offset.
1098    #[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    /// Covers SuperBlock::feature_check
1108    #[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        // Test that an exact match is not necessary.
1129        sb.e2fs_features_incompat = LEU32::new(REQUIRED_FEATURE_INCOMPAT | 0xF00000);
1130        assert!(sb.feature_check().is_ok());
1131
1132        // Test can report subset.
1133        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        // Test banned flag.
1146        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    /// Covers Extent::target_block_num.
1160    #[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    /// Covers ExtentIndex::target_block_num.
1172    #[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    /// Covers ExtentHeader::check_magic.
1184    #[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}