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};
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, 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, 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, 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, Immutable, Unaligned)]
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, 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, Immutable, Unaligned)]
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, 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, 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, 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, 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, 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, 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
554impl From<ReaderError> for ParsingError {
555    fn from(err: ReaderError) -> ParsingError {
556        match err {
557            ReaderError::Read(addr) => ParsingError::SourceReadError(addr),
558            ReaderError::OutOfBounds(addr, max) => {
559                ParsingError::InvalidAddress(InvalidAddressErrorType::Upper, addr, max)
560            }
561        }
562    }
563}
564
565/// Directory Entry types.
566#[derive(Debug, PartialEq)]
567#[repr(u8)]
568pub enum EntryType {
569    Unknown = 0x0,
570    RegularFile = 0x1,
571    Directory = 0x2,
572    CharacterDevice = 0x3,
573    BlockDevice = 0x4,
574    FIFO = 0x5,
575    Socket = 0x6,
576    SymLink = 0x7,
577}
578
579impl EntryType {
580    pub fn from_u8(value: u8) -> Result<EntryType, ParsingError> {
581        match value {
582            0x0 => Ok(EntryType::Unknown),
583            0x1 => Ok(EntryType::RegularFile),
584            0x2 => Ok(EntryType::Directory),
585            0x3 => Ok(EntryType::CharacterDevice),
586            0x4 => Ok(EntryType::BlockDevice),
587            0x5 => Ok(EntryType::FIFO),
588            0x6 => Ok(EntryType::Socket),
589            0x7 => Ok(EntryType::SymLink),
590            _ => Err(ParsingError::BadEntryType(value)),
591        }
592    }
593}
594
595/// Feature Incompatible flags.
596///
597/// Stored in SuperBlock::e2fs_features_incompat.
598///
599/// All flags listed by a given filesystem must be supported by us in order for us to attempt
600/// mounting it.
601///
602/// With our limited support at the time, there are also flags that we require to exist, like
603/// `EntryHasFileType`.
604#[derive(PartialEq)]
605#[repr(u32)]
606pub enum FeatureIncompat {
607    Compression = 0x1,
608    /// Currently required flag.
609    EntryHasFileType = 0x2,
610
611    // TODO(https://fxbug.dev/42073143): We will permit journaling because our initial use will not have
612    // entries in the journal. Will need to add proper support in the future.
613    /// We do not support journaling, but assuming an empty journal, we can still read.
614    ///
615    /// Run `fsck` in Linux to repair the filesystem first before attempting to mount a journaled
616    /// ext4 image.
617    HasJournal = 0x4,
618    JournalSeparate = 0x8,
619    MetaBlockGroups = 0x10,
620    /// Required flag. Lack of this flag means the filesystem is not ext4, and we are not
621    /// backward compatible.
622    Extents = 0x40,
623    Is64Bit = 0x80,
624    MultiMountProtection = 0x100,
625
626    // TODO(https://fxbug.dev/42073143): Should be relatively trivial to support.
627    /// No explicit support, we will permit the flag as it works for our needs.
628    FlexibleBlockGroups = 0x200,
629    ExtendedAttributeINodes = 0x400,
630    ExtendedDirectoryEntry = 0x1000,
631    /// We do not calculate checksums, so this is permitted but not actionable.
632    MetadataChecksum = 0x2000,
633    LargeDirectory = 0x4000,
634    SmallFilesInINode = 0x8000,
635    EncryptedINodes = 0x10000,
636}
637
638/// Required "feature incompatible" flags.
639pub const REQUIRED_FEATURE_INCOMPAT: u32 =
640    FeatureIncompat::Extents as u32 | FeatureIncompat::EntryHasFileType as u32;
641
642/// Banned "feature incompatible" flags.
643pub const BANNED_FEATURE_INCOMPAT: u32 = FeatureIncompat::Compression as u32
644    | FeatureIncompat::MultiMountProtection as u32
645    | FeatureIncompat::ExtendedAttributeINodes as u32
646    | FeatureIncompat::ExtendedDirectoryEntry as u32
647    | FeatureIncompat::SmallFilesInINode as u32
648    | FeatureIncompat::EncryptedINodes as u32;
649
650// TODO(mbrunson): Update this trait to follow error conventions similar to ExtentTreeNode::parse.
651/// All functions to help parse data into respective structs.
652pub trait ParseToStruct: FromBytes + KnownLayout + Immutable + Unaligned + Sized {
653    fn from_reader_with_offset(reader: &dyn Reader, offset: u64) -> Result<Self, ParsingError> {
654        if offset < FIRST_BG_PADDING {
655            return Err(ParsingError::InvalidAddress(
656                InvalidAddressErrorType::Lower,
657                offset,
658                FIRST_BG_PADDING,
659            ));
660        }
661        let mut object = Self::new_zeroed();
662        // SAFETY: Convert buffer to a byte slice. It's safe to read from this slice because object
663        // was created with new_zeroed, so its bit pattern is entirely initialized. It's safe to
664        // write to this slice because Self implements FromBytes, meaning any bit pattern written
665        // to the slice is valid to interpret as Self.
666        let buffer = unsafe {
667            std::slice::from_raw_parts_mut(&mut object as *mut Self as *mut u8, size_of::<Self>())
668        };
669        reader.read(offset, buffer)?;
670        Ok(object)
671    }
672
673    /// Casts the &[u8] data to &Self.
674    ///
675    /// `Self` is the ext4 struct that represents the given `data`.
676    fn to_struct_ref(data: &[u8], error_type: ParsingError) -> Result<&Self, ParsingError> {
677        Ref::<&[u8], Self>::from_bytes(data).map(|res| Ref::into_ref(res)).map_err(|_| error_type)
678    }
679}
680
681/// Apply to all EXT4 structs as seen above.
682impl<T: FromBytes + KnownLayout + Immutable + Unaligned> ParseToStruct for T {}
683
684impl<B: SplitByteSlice> ExtentTreeNode<B> {
685    /// Parses a slice of bytes to create an `ExtentTreeNode`.
686    ///
687    /// `data` must be large enough to construct an ExtentHeader. If not, `None`
688    /// is returned. `data` is consumed by this operation.
689    pub fn parse(data: B) -> Option<Self> {
690        Ref::<B, ExtentHeader>::from_prefix(data)
691            .ok()
692            .map(|(header, entries)| Self { header, entries })
693    }
694}
695
696impl SuperBlock {
697    /// Parse the Super Block at its default location.
698    pub fn parse(reader: &dyn Reader) -> Result<SuperBlock, ParsingError> {
699        // Super Block in Block Group 0 is at offset 1024.
700        // Assuming there is no corruption, there is no need to read any other
701        // copy of the Super Block.
702        let sb = SuperBlock::from_reader_with_offset(reader, FIRST_BG_PADDING)?;
703        sb.check_magic()?;
704        sb.feature_check()?;
705        sb.check_inode_size()?;
706        sb.check_block_group_descriptor_size()?;
707        Ok(sb)
708    }
709
710    pub fn check_magic(&self) -> Result<(), ParsingError> {
711        if self.e2fs_magic.get() == SB_MAGIC {
712            Ok(())
713        } else {
714            Err(ParsingError::InvalidSuperBlockMagic(self.e2fs_magic.get()))
715        }
716    }
717
718    pub fn check_inode_size(&self) -> Result<(), ParsingError> {
719        let inode_size: u64 = self.e2fs_inode_size.into();
720        if inode_size < MINIMUM_INODE_SIZE {
721            Err(ParsingError::InvalidInodeSize(self.e2fs_inode_size.get()))
722        } else {
723            Ok(())
724        }
725    }
726
727    fn check_block_group_descriptor_size(&self) -> Result<(), ParsingError> {
728        let desc_size: usize = self.e3fs_desc_size.into();
729        let is_64bit = self.is_64bit();
730        // NB: Some images have a desc_size of 0.  Derive from is_64bit in that case.
731        if desc_size == 0
732            || is_64bit && desc_size == size_of::<BlockGroupDesc64>()
733            || !is_64bit && desc_size == size_of::<BlockGroupDesc32>()
734        {
735            Ok(())
736        } else {
737            Err(ParsingError::InvalidBlockGroupDescSize(self.e3fs_desc_size.get()))
738        }
739    }
740
741    /// Gets file system block size.
742    ///
743    /// Per spec, the only valid block sizes are 1KiB, 2KiB, 4KiB, and 64KiB. We will only
744    /// permit these values.
745    pub fn block_size(&self) -> Result<u64, ParsingError> {
746        let bs = 2u64
747            .checked_pow(self.e2fs_log_bsize.get() + 10)
748            .ok_or_else(|| ParsingError::BlockSizeInvalid(self.e2fs_log_bsize.get()))?;
749        if bs == 1024 || bs == 2048 || bs == 4096 || bs == 65536 {
750            Ok(bs)
751        } else {
752            Err(ParsingError::BlockSizeInvalid(self.e2fs_log_bsize.get()))
753        }
754    }
755
756    /// Returns the size of the block group descriptors
757    pub fn block_group_descriptor_size(&self) -> usize {
758        // NB: We ignore the size recorded in the superblock, since we checked it already and it has
759        // to be either 4 or 8
760        if self.is_64bit() {
761            size_of::<BlockGroupDesc64>()
762        } else {
763            size_of::<BlockGroupDesc32>()
764        }
765    }
766
767    /// Returns whether the filesystem is 64bit enabled
768    pub fn is_64bit(&self) -> bool {
769        self.e2fs_features_incompat.get() & FeatureIncompat::Is64Bit as u32 != 0
770    }
771
772    fn feature_check(&self) -> Result<(), ParsingError> {
773        let banned = self.e2fs_features_incompat.get() & BANNED_FEATURE_INCOMPAT;
774        if banned > 0 {
775            return Err(ParsingError::BannedFeatureIncompat(banned));
776        }
777        let required = self.e2fs_features_incompat.get() & REQUIRED_FEATURE_INCOMPAT;
778        if required != REQUIRED_FEATURE_INCOMPAT {
779            return Err(ParsingError::RequiredFeatureIncompat(
780                required ^ REQUIRED_FEATURE_INCOMPAT,
781            ));
782        }
783        Ok(())
784    }
785}
786
787impl INode {
788    /// INode contains the root of its Extent tree within `e2di_blocks`.
789    /// Read `e2di_blocks` and return the root Extent Header.
790    pub fn extent_tree_node(&self) -> Result<ExtentTreeNode<&[u8]>, ParsingError> {
791        let eh = ExtentTreeNode::<&[u8]>::parse(
792            // Bounds here are known and static on a field that is defined to be
793            // the same size as an ExtentTreeNode.
794            &self.e2di_blocks,
795        )
796        .ok_or(ParsingError::InvalidExtentHeader)?;
797        eh.header.check_magic()?;
798        Ok(eh)
799    }
800
801    /// Size of the file/directory/entry represented by this INode.
802    pub fn size(&self) -> u64 {
803        (self.e2di_size_high.get() as u64) << 32 | self.e2di_size.get() as u64
804    }
805
806    pub fn facl(&self) -> u64 {
807        (self.e2di_facl_high.get() as u64) << 32 | self.e2di_facl.get() as u64
808    }
809
810    fn check_inode_size(sb: &SuperBlock) -> Result<(), ParsingError> {
811        let inode_size: usize = sb.e2fs_inode_size.into();
812        if inode_size < std::mem::size_of::<INode>() {
813            Err(ParsingError::Incompatible("Inode size too small.".to_string()))
814        } else {
815            Ok(())
816        }
817    }
818
819    pub fn e4di_extra_isize(&self, sb: &SuperBlock) -> Result<LEU16, ParsingError> {
820        INode::check_inode_size(sb)?;
821        Ok(self.e4di_extra_isize)
822    }
823
824    pub fn e4di_chksum_hi(&self, sb: &SuperBlock) -> Result<LEU16, ParsingError> {
825        INode::check_inode_size(sb)?;
826        Ok(self.e4di_chksum_hi)
827    }
828
829    pub fn e4di_ctime_extra(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
830        INode::check_inode_size(sb)?;
831        Ok(self.e4di_ctime_extra)
832    }
833
834    pub fn e4di_mtime_extra(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
835        INode::check_inode_size(sb)?;
836        Ok(self.e4di_mtime_extra)
837    }
838
839    pub fn e4di_atime_extra(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
840        INode::check_inode_size(sb)?;
841        Ok(self.e4di_atime_extra)
842    }
843
844    pub fn e4di_crtime(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
845        INode::check_inode_size(sb)?;
846        Ok(self.e4di_crtime)
847    }
848
849    pub fn e4di_crtime_extra(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
850        INode::check_inode_size(sb)?;
851        Ok(self.e4di_crtime_extra)
852    }
853
854    pub fn e4di_version_hi(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
855        INode::check_inode_size(sb)?;
856        Ok(self.e4di_version_hi)
857    }
858
859    pub fn e4di_projid(&self, sb: &SuperBlock) -> Result<LEU32, ParsingError> {
860        INode::check_inode_size(sb)?;
861        Ok(self.e4di_projid)
862    }
863}
864
865impl ExtentHeader {
866    pub fn check_magic(&self) -> Result<(), ParsingError> {
867        if self.eh_magic.get() == EH_MAGIC {
868            Ok(())
869        } else {
870            Err(ParsingError::InvalidExtentHeaderMagic(self.eh_magic.get()))
871        }
872    }
873}
874
875impl DirEntry2 {
876    /// Name of the file/directory/entry as a string.
877    pub fn name(&self) -> Result<&str, ParsingError> {
878        str::from_utf8(&self.e2d_name[0..self.e2d_namlen as usize]).map_err(|_| {
879            ParsingError::DirEntry2NonUtf8(self.e2d_name[0..self.e2d_namlen as usize].to_vec())
880        })
881    }
882
883    pub fn name_bytes(&self) -> &[u8] {
884        &self.e2d_name[0..self.e2d_namlen as usize]
885    }
886
887    /// Generate a hash table of the given directory entries.
888    ///
889    /// Key: name of entry
890    /// Value: DirEntry2 struct
891    pub fn as_hash_map(
892        entries: Vec<DirEntry2>,
893    ) -> Result<HashMap<String, DirEntry2>, ParsingError> {
894        let mut entry_map: HashMap<String, DirEntry2> = HashMap::with_capacity(entries.len());
895
896        for entry in entries {
897            entry_map.insert(entry.name()?.to_string(), entry);
898        }
899        Ok(entry_map)
900    }
901}
902
903impl Extent {
904    /// Block number that this Extent points to.
905    pub fn target_block_num(&self) -> u64 {
906        (self.e_start_hi.get() as u64) << 32 | self.e_start_lo.get() as u64
907    }
908}
909
910impl ExtentIndex {
911    /// Block number that this ExtentIndex points to.
912    pub fn target_block_num(&self) -> u64 {
913        (self.ei_leaf_hi.get() as u64) << 32 | self.ei_leaf_lo.get() as u64
914    }
915}
916
917#[cfg(test)]
918mod test {
919    use super::{
920        Extent, ExtentHeader, ExtentIndex, FeatureIncompat, ParseToStruct, SuperBlock, EH_MAGIC,
921        FIRST_BG_PADDING, LEU16, LEU32, LEU64, REQUIRED_FEATURE_INCOMPAT, SB_MAGIC,
922    };
923    use crate::readers::VecReader;
924    use std::fs;
925
926    impl Default for SuperBlock {
927        fn default() -> SuperBlock {
928            SuperBlock {
929                e2fs_icount: LEU32::new(0),
930                e2fs_bcount: LEU32::new(0),
931                e2fs_rbcount: LEU32::new(0),
932                e2fs_fbcount: LEU32::new(0),
933                e2fs_ficount: LEU32::new(0),
934                e2fs_first_dblock: LEU32::new(0),
935                e2fs_log_bsize: LEU32::new(0),
936                e2fs_log_fsize: LEU32::new(0),
937                e2fs_bpg: LEU32::new(0),
938                e2fs_fpg: LEU32::new(0),
939                e2fs_ipg: LEU32::new(0),
940                e2fs_mtime: LEU32::new(0),
941                e2fs_wtime: LEU32::new(0),
942                e2fs_mnt_count: LEU16::new(0),
943                e2fs_max_mnt_count: LEU16::new(0),
944                e2fs_magic: LEU16::new(0),
945                e2fs_state: LEU16::new(0),
946                e2fs_beh: LEU16::new(0),
947                e2fs_minrev: LEU16::new(0),
948                e2fs_lastfsck: LEU32::new(0),
949                e2fs_fsckintv: LEU32::new(0),
950                e2fs_creator: LEU32::new(0),
951                e2fs_rev: LEU32::new(0),
952                e2fs_ruid: LEU16::new(0),
953                e2fs_rgid: LEU16::new(0),
954                e2fs_first_ino: LEU32::new(0),
955                e2fs_inode_size: LEU16::new(0),
956                e2fs_block_group_nr: LEU16::new(0),
957                e2fs_features_compat: LEU32::new(0),
958                e2fs_features_incompat: LEU32::new(0),
959                e2fs_features_rocompat: LEU32::new(0),
960                e2fs_uuid: [0; 16],
961                e2fs_vname: [0; 16],
962                e2fs_fsmnt: [0; 64],
963                e2fs_algo: LEU32::new(0),
964                e2fs_prealloc: 0,
965                e2fs_dir_prealloc: 0,
966                e2fs_reserved_ngdb: LEU16::new(0),
967                e3fs_journal_uuid: [0; 16],
968                e3fs_journal_inum: LEU32::new(0),
969                e3fs_journal_dev: LEU32::new(0),
970                e3fs_last_orphan: LEU32::new(0),
971                e3fs_hash_seed: [LEU32::new(0); 4],
972                e3fs_def_hash_version: 0,
973                e3fs_jnl_backup_type: 0,
974                e3fs_desc_size: LEU16::new(0),
975                e3fs_default_mount_opts: LEU32::new(0),
976                e3fs_first_meta_bg: LEU32::new(0),
977                e3fs_mkfs_time: LEU32::new(0),
978                e3fs_jnl_blks: [LEU32::new(0); 17],
979                e4fs_bcount_hi: LEU32::new(0),
980                e4fs_rbcount_hi: LEU32::new(0),
981                e4fs_fbcount_hi: LEU32::new(0),
982                e4fs_min_extra_isize: LEU16::new(0),
983                e4fs_want_extra_isize: LEU16::new(0),
984                e4fs_flags: LEU32::new(0),
985                e4fs_raid_stride: LEU16::new(0),
986                e4fs_mmpintv: LEU16::new(0),
987                e4fs_mmpblk: LEU64::new(0),
988                e4fs_raid_stripe_wid: LEU32::new(0),
989                e4fs_log_gpf: 0,
990                e4fs_chksum_type: 0,
991                e4fs_encrypt: 0,
992                e4fs_reserved_pad: 0,
993                e4fs_kbytes_written: LEU64::new(0),
994                e4fs_snapinum: LEU32::new(0),
995                e4fs_snapid: LEU32::new(0),
996                e4fs_snaprbcount: LEU64::new(0),
997                e4fs_snaplist: LEU32::new(0),
998                e4fs_errcount: LEU32::new(0),
999                e4fs_first_errtime: LEU32::new(0),
1000                e4fs_first_errino: LEU32::new(0),
1001                e4fs_first_errblk: LEU64::new(0),
1002                e4fs_first_errfunc: [0; 32],
1003                e4fs_first_errline: LEU32::new(0),
1004                e4fs_last_errtime: LEU32::new(0),
1005                e4fs_last_errino: LEU32::new(0),
1006                e4fs_last_errline: LEU32::new(0),
1007                e4fs_last_errblk: LEU64::new(0),
1008                e4fs_last_errfunc: [0; 32],
1009                e4fs_mount_opts: [0; 64],
1010                e4fs_usrquota_inum: LEU32::new(0),
1011                e4fs_grpquota_inum: LEU32::new(0),
1012                e4fs_overhead_clusters: LEU32::new(0),
1013                e4fs_backup_bgs: [LEU32::new(0); 2],
1014                e4fs_encrypt_algos: [0; 4],
1015                e4fs_encrypt_pw_salt: [0; 16],
1016                e4fs_lpf_ino: LEU32::new(0),
1017                e4fs_proj_quota_inum: LEU32::new(0),
1018                e4fs_chksum_seed: LEU32::new(0),
1019                e4fs_reserved: [LEU32::new(0); 98],
1020                e4fs_sbchksum: LEU32::new(0),
1021            }
1022        }
1023    }
1024
1025    // NOTE: Impls for `INode` and `DirEntry2` depend on calculated data locations. Testing these
1026    // functions are being done in `parser.rs` where those locations are being calculated.
1027
1028    // SuperBlock has a known data location and enables us to test `ParseToStruct` functions.
1029
1030    /// Covers these functions:
1031    /// - ParseToStruct::{read_from_offset, to_struct_arc, to_struct_ref, validate}
1032    /// - SuperBlock::{block_size, check_magic}
1033    #[fuchsia::test]
1034    fn parse_superblock() {
1035        let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
1036        let reader = VecReader::new(data);
1037        let sb = SuperBlock::parse(&reader).expect("Parsed Super Block");
1038        // Block size of the 1file.img is 1KiB.
1039        assert_eq!(sb.block_size().unwrap(), FIRST_BG_PADDING);
1040
1041        // Validate magic number.
1042        assert!(sb.check_magic().is_ok());
1043
1044        let mut sb = SuperBlock::default();
1045        assert!(sb.check_magic().is_err());
1046
1047        sb.e2fs_magic = LEU16::new(SB_MAGIC);
1048        assert!(sb.check_magic().is_ok());
1049
1050        // Validate block size.
1051        sb.e2fs_log_bsize = LEU32::new(0); // 1KiB
1052        assert!(sb.block_size().is_ok());
1053        sb.e2fs_log_bsize = LEU32::new(1); // 2KiB
1054        assert!(sb.block_size().is_ok());
1055        sb.e2fs_log_bsize = LEU32::new(2); // 4KiB
1056        assert!(sb.block_size().is_ok());
1057        sb.e2fs_log_bsize = LEU32::new(6); // 64KiB
1058        assert!(sb.block_size().is_ok());
1059
1060        // Others are disallowed, checking values neighboring valid ones.
1061        sb.e2fs_log_bsize = LEU32::new(3);
1062        assert!(sb.block_size().is_err());
1063        sb.e2fs_log_bsize = LEU32::new(5);
1064        assert!(sb.block_size().is_err());
1065        sb.e2fs_log_bsize = LEU32::new(7);
1066        assert!(sb.block_size().is_err());
1067        // How exhaustive do we get?
1068        sb.e2fs_log_bsize = LEU32::new(20);
1069        assert!(sb.block_size().is_err());
1070    }
1071
1072    /// Covers ParseToStruct::from_reader_with_offset.
1073    #[fuchsia::test]
1074    fn parse_to_struct_from_reader_with_offset() {
1075        let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
1076        let reader = VecReader::new(data);
1077        let sb = SuperBlock::from_reader_with_offset(&reader, FIRST_BG_PADDING)
1078            .expect("Parsed Super Block");
1079        assert!(sb.check_magic().is_ok());
1080    }
1081
1082    /// Covers SuperBlock::feature_check
1083    #[fuchsia::test]
1084    fn incompatible_feature_flags() {
1085        let data = fs::read("/pkg/data/1file.img").expect("Unable to read file");
1086        let reader = VecReader::new(data);
1087        let sb = SuperBlock::parse(&reader).expect("Parsed Super Block");
1088        assert_eq!(sb.e2fs_magic.get(), SB_MAGIC);
1089        assert!(sb.feature_check().is_ok());
1090
1091        let mut sb = SuperBlock::default();
1092        match sb.feature_check() {
1093            Ok(_) => assert!(false, "Feature flags should be incorrect."),
1094            Err(e) => assert_eq!(
1095                format!("{}", e),
1096                format!(
1097                    "Required feature flags (feature_incompat): 0x{:X}",
1098                    REQUIRED_FEATURE_INCOMPAT
1099                )
1100            ),
1101        }
1102
1103        // Test that an exact match is not necessary.
1104        sb.e2fs_features_incompat = LEU32::new(REQUIRED_FEATURE_INCOMPAT | 0xF00000);
1105        assert!(sb.feature_check().is_ok());
1106
1107        // Test can report subset.
1108        sb.e2fs_features_incompat = LEU32::new(FeatureIncompat::Extents as u32);
1109        match sb.feature_check() {
1110            Ok(_) => assert!(false, "Feature flags should be incorrect."),
1111            Err(e) => assert_eq!(
1112                format!("{}", e),
1113                format!(
1114                    "Required feature flags (feature_incompat): 0x{:X}",
1115                    REQUIRED_FEATURE_INCOMPAT ^ FeatureIncompat::Extents as u32
1116                )
1117            ),
1118        }
1119
1120        // Test banned flag.
1121        sb.e2fs_features_incompat = LEU32::new(FeatureIncompat::Compression as u32);
1122        match sb.feature_check() {
1123            Ok(_) => assert!(false, "Feature flags should be incorrect."),
1124            Err(e) => assert_eq!(
1125                format!("{}", e),
1126                format!(
1127                    "Incompatible feature flags (feature_incompat): 0x{:X}",
1128                    FeatureIncompat::Compression as u32
1129                )
1130            ),
1131        }
1132    }
1133
1134    /// Covers Extent::target_block_num.
1135    #[fuchsia::test]
1136    fn extent_target_block_num() {
1137        let e = Extent {
1138            e_blk: LEU32::new(0),
1139            e_len: LEU16::new(0),
1140            e_start_hi: LEU16::new(0x4444),
1141            e_start_lo: LEU32::new(0x6666_8888),
1142        };
1143        assert_eq!(e.target_block_num(), 0x4444_6666_8888);
1144    }
1145
1146    /// Covers ExtentIndex::target_block_num.
1147    #[fuchsia::test]
1148    fn extent_index_target_block_num() {
1149        let e = ExtentIndex {
1150            ei_blk: LEU32::new(0),
1151            ei_leaf_lo: LEU32::new(0x6666_8888),
1152            ei_leaf_hi: LEU16::new(0x4444),
1153            ei_unused: LEU16::new(0),
1154        };
1155        assert_eq!(e.target_block_num(), 0x4444_6666_8888);
1156    }
1157
1158    /// Covers ExtentHeader::check_magic.
1159    #[fuchsia::test]
1160    fn extent_header_check_magic() {
1161        let e = ExtentHeader {
1162            eh_magic: LEU16::new(EH_MAGIC),
1163            eh_ecount: LEU16::new(0),
1164            eh_max: LEU16::new(0),
1165            eh_depth: LEU16::new(0),
1166            eh_gen: LEU32::new(0),
1167        };
1168        assert!(e.check_magic().is_ok());
1169
1170        let e = ExtentHeader {
1171            eh_magic: LEU16::new(0x1234),
1172            eh_ecount: LEU16::new(0),
1173            eh_max: LEU16::new(0),
1174            eh_depth: LEU16::new(0),
1175            eh_gen: LEU32::new(0),
1176        };
1177        assert!(e.check_magic().is_err());
1178    }
1179}