Skip to main content

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