added superblock, group descriptors, and mmp structured

This commit is contained in:
Moritz Gmeiner 2025-07-18 13:59:34 +02:00
commit 75d23682c0
10 changed files with 824 additions and 2 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
/tests

81
Cargo.lock generated
View file

@ -3,5 +3,84 @@
version = 4 version = 4
[[package]] [[package]]
name = "ext4-rs" name = "anyhow"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "ext4"
version = "0.1.0" version = "0.1.0"
dependencies = [
"anyhow",
"bitflags",
"static_assertions",
"thiserror",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"

View file

@ -1,6 +1,14 @@
[package] [package]
name = "ext4-rs" name = "ext4"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[[bin]]
name = "dump"
path = "src/dump.rs"
[dependencies] [dependencies]
anyhow = "1.0.98"
bitflags = "2.9.1"
static_assertions = "1.1.0"
thiserror = "2.0.12"

3
README.md Normal file
View file

@ -0,0 +1,3 @@
for specification, see: [https://docs.kernel.org/filesystems/ext4/](https://docs.kernel.org/filesystems/ext4/)
[https://blogs.oracle.com/linux/post/understanding-ext4-disk-layout-part-1](https://blogs.oracle.com/linux/post/understanding-ext4-disk-layout-part-1)

48
src/dump.rs Normal file
View file

@ -0,0 +1,48 @@
use std::io::{Read, Seek};
use ext4::{Superblock, group_has_superblock};
pub fn main() -> anyhow::Result<()> {
let args = std::env::args();
if args.len() != 2 {
anyhow::bail!("usage: dump <path>");
}
let mut file = std::fs::File::open(args.skip(1).next().unwrap())?;
// skip first 1024 bytes
file.seek(std::io::SeekFrom::Start(1024))?;
let mut buf = [0; 1024];
file.read_exact(&mut buf)?;
let superblock = Superblock::from_bytes(&buf)?;
println!("Superblock 0 (offset: 1024): {:?}", superblock);
let num_groups = superblock.blocks_count() / superblock.blocks_per_group() as u64;
let group_size = (superblock.blocks_per_group() as u64) * superblock.blocksize();
println!("group_size: 0x{group_size:08X}");
file.seek(std::io::SeekFrom::Start(0))?;
for group in 1..num_groups {
if group_has_superblock(group) {
let offset = group * group_size;
file.seek(std::io::SeekFrom::Start(offset))?;
file.read_exact(&mut buf)?;
let superblock2 = Superblock::from_bytes(&buf)?;
println!("Superblock {group: >2} (offset: 0x{:08X})", offset);
}
}
Ok(())
}

55
src/group_descriptor.rs Normal file
View file

@ -0,0 +1,55 @@
use bitflags::bitflags;
use static_assertions::const_assert_eq;
// #[derive(Debug, thiserror::Error)]
// pub struct GroupDescriptorError {}
bitflags! {
pub struct Flags: u16 {
const INODE_UNINIT = 0x1; // inode table and bitmap are not initialized
const BLOCK_UNINIT = 0x2; // block bitmap is not initialized
const INODE_ZEROED = 0x4; // inode table is zeroed
const _ = !0;
}
}
#[derive(Debug)]
#[repr(C)]
pub struct GroupDescriptor {
block_bitmap_lo: u32,
inode_bitmap_lo: u32,
inode_table_lo: u32,
free_blocks_count_lo: u16,
free_inodes_count_lo: u16,
used_dirs_count_lo: u16,
flags: u16,
exclude_bitmap_lo: u32,
block_bitmap_csum_lo: u16,
inode_bitmap_csum_lo: u16,
itable_unused_lo: u16,
checksum: u16,
block_bitmap_hi: u32,
inode_bitmap_hblock_bitmap_csum_hii: u32,
inode_table_hi: u32,
free_blocks_count_hi: u16,
free_inodes_count_hi: u16,
used_dirs_count_hi: u16,
itable_unused_hi: u16,
exclude_bitmap_hi: u32,
block_bitmap_csum_hi: u16,
inode_bitmap_csum_hi: u16,
reserved: u32,
}
const_assert_eq!(std::mem::size_of::<GroupDescriptor>(), 64);
impl GroupDescriptor {
pub fn from_bytes(bytes: &[u8]) -> Result<GroupDescriptor, ()> {
assert_eq!(bytes.len(), std::mem::size_of::<GroupDescriptor>());
let group_desc = unsafe { std::ptr::read(bytes.as_ptr() as *const GroupDescriptor) };
Ok(group_desc)
}
}

View file

@ -1 +1,94 @@
use std::io::SeekFrom;
pub use crate::superblock::{SUPERBLOCK_SIZE, Superblock};
pub use crate::utils::group_has_superblock;
mod group_descriptor;
mod mmp;
mod superblock;
mod utils;
pub trait FileLike {
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> anyhow::Result<()>;
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> anyhow::Result<()>;
fn read_at_offset_const<const N: usize>(&mut self, offset: u64) -> anyhow::Result<[u8; N]> {
let mut buf = [0; N];
self.read_at_offset(offset, &mut buf)?;
Ok(buf)
}
}
impl<F: std::io::Read + std::io::Write + std::io::Seek> FileLike for F {
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> anyhow::Result<()> {
self.seek(SeekFrom::Start(offset))?;
self.read_exact(buf)?;
Ok(())
}
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> anyhow::Result<()> {
self.seek(SeekFrom::Start(offset))?;
self.write_all(bytes)?;
Ok(())
}
}
// impl FileLike for &mut [u8] {
// fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> anyhow::Result<()> {
// anyhow::ensure!(
// offset as usize + buf.len() <= self.len(),
// "reading {} bytes at offset {} is out of bounds for slice of len {}",
// buf.len(),
// offset,
// self.len()
// );
//
// buf.copy_from_slice(&self[offset as usize..][..buf.len()]);
//
// Ok(())
// }
//
// fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> anyhow::Result<()> {
// anyhow::ensure!(
// offset as usize + bytes.len() <= self.len(),
// "writing {} bytes at offset {} is out of bounds for slice of len {}",
// bytes.len(),
// offset,
// self.len()
// );
//
// self[offset as usize..][..bytes.len()].copy_from_slice(bytes);
//
// Ok(())
// }
// }
pub struct Ext4Fs<F: FileLike> {
#[allow(dead_code)]
file: F,
superblock: superblock::Superblock,
}
impl<F: FileLike> Ext4Fs<F> {
pub fn load(file: F) -> Result<Ext4Fs<F>, anyhow::Error> {
let mut file = file;
let superblock_bytes = file.read_at_offset_const::<SUPERBLOCK_SIZE>(1024)?;
let superblock = Superblock::from_bytes(&superblock_bytes)?;
Ok(Ext4Fs { file, superblock })
}
pub fn super_block(&self) -> &Superblock {
&self.superblock
}
}

23
src/mmp.rs Normal file
View file

@ -0,0 +1,23 @@
#[derive(Debug)]
#[repr(C)]
pub struct Mmp {
magic: u32, // Magic number for MMP (0x004D4D50 (“MMP”))
seq: u32, // Sequence number, updated periodically
time: u64, // Time that the MMP block was last updated
node_name: [u8; 64], // Hostname of the node that opened the filesystem
bdev_name: [u8; 32], // Block device name of the filesystem
check_interval: u16, // The MMP re-check interval, in seconds
pad1: u16, // zero
pad2: [u32; 226], // zero
checksum: u32, // Checksum of the MMP block
}
impl Mmp {
pub fn from_bytes(bytes: &[u8]) -> Result<Mmp, ()> {
assert_eq!(bytes.len(), std::mem::size_of::<Mmp>());
let group_desc = unsafe { std::ptr::read(bytes.as_ptr() as *const Mmp) };
Ok(group_desc)
}
}

429
src/superblock.rs Normal file
View file

@ -0,0 +1,429 @@
use bitflags::bitflags;
use static_assertions::const_assert_eq;
use crate::utils::{combine_lo_hi, crc32};
bitflags! {
pub struct State: u16 {
const VALID_FS = 0x0001; // cleanly unmounted
const ERROR_FS = 0x0002; // errors detected
const ORPHAN_FS = 0x0004; // orphans being recovered
const _ = !0;
}
}
#[non_exhaustive]
#[repr(u32)]
pub enum CreatorOs {
Linux = 0,
Hurd = 1,
Masix = 2,
FreeBSD = 3,
Lites = 4,
}
#[non_exhaustive]
#[repr(u32)]
pub enum Revision {
GoodOldExt4 = 0,
DynamicInodes = 1,
}
bitflags! {
pub struct CompatibleFlags: u32 {
const DIR_PREALLOC = 0x1; // Directory preallocation
const IMAGIC_INODES = 0x2; // “imagic inodes”. Not clear from the code what this does
const HAS_JOURNAL = 0x4; // Has a journal
const EXT_ATTR = 0x8; // Supports extended attributes
const RESIZE_INODE = 0x10; // Has reserved GDT blocks for filesystem expansion
const DIR_INDEX = 0x20; // Has directory indices
const LAZY_BG = 0x40; /* “Lazy BG”. Not in Linux kernel, seems to have been for
uninitialized block groups? */
const EXCLUDE_INODE = 0x80; // “Exclude inode”. Not used.
const EXCLUDE_BITMAP = 0x100; /* “Exclude bitmap”. Seems to be used to indicate the
presence of snapshot-related exclude bitmaps? Not in
kernel or used in e2fsprogs */
const SPARSE_SUPER2 = 0x200; /* Sparse Super Block, v2. If this flag is set, the SB
field s_backup_bgs points to the two block that
contain backup superblocks */
const FAST_COMMIT = 0x400; /* Fast commits supported. Although fast commits blocks
are backward incompatible, fast commit blocks are not
always present in the journal. If fast commit blocks
are present in the journal, JBD2 incompat feature
(JBD2_FEATURE_INFAST_COMMIT) gets set */
const STABLE_INODES = 0x800;
const ORPHAN_FILE = 0x1000; /* Orphan file allocated. This is the special file for
more efficient tracking of unlinked but still open
inodes. When there may be any entries the file, we
additionally set proper rocompat feature
ORPHAN_PRESENT */
const _ = !0;
}
}
bitflags! {
pub struct IncompatibleFeatures: u32 {
const COMPRESSION = 0x1; // Compression
const FILETYPE = 0x2; /* Directory entries record the file type. See
ext4_dir_entry_2 below */
const RECOVER = 0x4; // Filesystem needs recovery
const JOURNAL_DEV = 0x8; // Filesystem has a separate journal device
const META_BG = 0x10; /* Meta block groups. See the earlier discussion of this
feature */
const EXTENTS = 0x40; // Files in this filesystem use extents
const _64BIT = 0x80; // Enable a filesystem size of 2^64 blocks
const MMP = 0x100; // Multiple mount protection
const FLEX_BG = 0x200; /* Flexible block groups. See the earlier discussion of this
feature */
const EA_INODE = 0x400; // Inodes can be used to store large extended attribute values
const DIRDATA = 0x1_000; // Data in directory entry
const CSUM_SEED = 0x2_000; /* Metadata checksum seed is stored in the superblock. This
feature enables the administrator to change the UUID of a
metadata_csum filesystem while the filesystem is mounted;
without it, the checksum definition requires all metadata
blocks to be rewritten */
const LARGEDIR = 0x4_000; /* Large directory >2GB or 3-level htree (LARGEDIR). Prior to
this feature, directories could not be larger than 4GiB and
could not have an htree more than 2 levels deep.
If this feature is enabled, directories can be larger than
4GiB and have a maximum htree depth of 3. */
const INLINE_DATA = 0x8_000; // Data in inode
const ENCRYPT = 0x10_000; // Encrypted inodes are present on the filesystem
const _ = !0;
}
}
bitflags! {
pub struct RoCompatibleFeatures: u32 {
const SPARSE_SUPER = 0x1; /* Sparse superblocks. See the earlier discussion of this
feature */
const LARGE_FILE = 0x2; /* This filesystem has been used to store a file greater
than 2GiB */
const BTREE_DIR = 0x4; // Not used in kernel or e2fsprogs
const HUGE_FILE = 0x8; /* This filesystem has files whose sizes are represented
in units of logical blocks, not 512-byte sectors. This
implies a very large file indeed! */
const GDT_CSUM = 0x10; /* Group descriptors have checksums. In addition to
detecting corruption, this is useful for lazy
formatting with uninitialized groups */
const DIR_NLINK = 0x20; /* Indicates that the old ext3 32,000 subdirectory limit
no longer applies.
A directorys i_links_count will be set to 1 if it is
incremented past 64,999*/
const EXTRA_ISIZE = 0x40; // Indicates that large inodes exist on this filesystem
const HAS_SNAPSHOT = 0x80; // This filesystem has a snapshot
const QUOTA = 0x100; // Quota
const BIGALLOC = 0x200; /* This filesystem supports “bigalloc”, which means that
file extents are tracked in units of clusters
(of blocks) instead of blocks */
const METADATA_CSUM = 0x400; /* This filesystem supports metadata checksumming.
(implies RO_COMPAT_GDT_CSUM, though GDT_CSUM must not
be set) */
const REPLICA = 0x800; /* Filesystem supports replicas. This feature is neither
in the kernel nor e2fsprogs. */
const READONLY = 0x1_000; /* Read-only filesystem image; the kernel will not mount
this image read-write and most tools will refuse to
write to the image */
const PROJECT = 0x2_000; // Filesystem tracks project quotas
const VERITY = 0x8_000; // Verity inodes may be present on the filesystem
const ORPHAN_PRESENT = 0x10_000; /* Indicates orphan file may have valid orphan entries and
thus we need to clean them up when mounting the
filesystem */
const _ = !0;
}
}
#[derive(Debug)]
#[repr(u8)]
#[non_exhaustive]
pub enum DefHashVersion {
Legacy = 0x0,
HalfMd4 = 0x1,
Tea = 0x2,
LegacyUnsigned = 0x3,
HalfMd4Unsigned = 0x4,
TeaUnsigned = 0x5,
}
bitflags! {
pub struct DefaultMountOpts: u32 {
const EXT4_DEFM_DEBUG = 0x1;
const EXT4_DEFM_BSDGROUPS = 0x2;
const EXT4_DEFM_XATTR_USER = 0x4;
const EXT4_DEFM_ACL = 0x8;
const EXT4_DEFM_UID16 = 0x10;
const EXT4_DEFM_JMODE_DATA = 0x20;
const EXT4_DEFM_JMODE_ORDERED = 0x40;
const EXT4_DEFM_JMODE_WBACK = 0x60;
const EXT4_DEFM_NOBARRIER = 0x100;
const EXT4_DEFM_BLOCK_VALIDITY = 0x200;
const EXT4_DEFM_DISCARD = 0x400;
const EXT4_DEFM_NODELALLOC = 0x800;
const _ = !0;
}
}
bitflags! {
pub struct Flags: u32 {
const SignedHash = 0x1; // Signed dirhash in use
const UnsignedHash = 0x2; // Unsigned dirhash in use
const TestFilesys = 0x4; // to test development code
const _ = !0;
}
}
#[derive(Debug)]
#[non_exhaustive]
#[repr(u32)]
pub enum EncryptAlgos {
Invalid = 0x0,
Aes256Xts = 0x1,
Aes256Gcm = 0x2,
Aes256Cbc = 0x3,
}
#[derive(Debug, thiserror::Error)]
pub enum SuperblockError {
#[error("invalid value {value} for field {field}")]
InvalidFieldValue { field: String, value: String },
#[error("invalid checksum: expected {expected:X}, but found {actual:X}")]
InvalidChecksum { expected: u32, actual: u32 },
}
#[derive(Debug)]
#[repr(C)]
pub struct Superblock {
inodes_count: u32, // Total inode count
blocks_count_lo: u32, // Total block count
r_blocks_count_lo: u32, // This number of blocks can only be allocated by the super-user
free_blocks_count_lo: u32, // Free block count
free_inodes_count: u32, // Free inode count
first_data_block: u32, /* First data block. This must be at least 1 for 1k-block
* filesystems and is typically 0 for all other block sizes */
log_block_size: u32, // Block size is 2 ^ (10 + s_log_block_size)
log_cluster_size: u32, /* Cluster size is 2 ^ (10 + s_log_cluster_size) blocks if bigalloc
* is enabled. Otherwise s_log_cluster_size must equal
* s_log_block_size */
blocks_per_groups: u32, // Blocks per group
clusters_per_group: u32, /* Clusters per group, if bigalloc is enabled. Otherwise
* s_clusters_per_group must equal s_blocks_per_group */
inodes_per_group: u32, // Inodes per group
mtime: u32, // Mount time, in seconds since the epoch
wtime: u32, // Write time, in seconds since the epoch
mnt_count: u16, // Number of mounts since the last fsck
max_mnt_count: u16, // Number of mounts beyond which a fsck is needed
magic: u16, // Magic signature, 0xEF53
state: u16, // File system state. See super_state for more info
errors: u16, // Behaviour when detecting errors. See super_errors for more info
minor_rev_level: u16, // Minor revision level
lastcheck: u32, // Time of last check, in seconds since the epoch
checkinterval: u32, // Maximum time between checks, in seconds
creator_os: u32, // Creator OS. See the table super_creator for more info
rev_level: u32, // Revision level. See the table super_revision for more info
def_resuid: u16, // Default uid for reserved blocks
def_resgid: u16, // Default gid for reserved blocks
// These fields are for EXT4_DYNAMIC_REV superblocks only.
//
// Note: the difference between the compatible feature set and the
// incompatible feature set is that if there is a bit set in the
// incompatible feature set that the kernel doesnt know about, it
// should refuse to mount the filesystem.
//
// e2fscks requirements are more strict; if it doesnt know about a
// feature in either the compatible or incompatible feature set, it
// must abort and not try to meddle with things it doesnt
// understand...
first_ino: u32, // First non-reserved inode
inode_size: u16, // Size of inode structure, in bytes
block_group_nr: u16, // Block group # of this superblock
feature_compat: u32, /* Compatible feature set flags. Kernel can still read/write this fs
* even if it doesnt understand a flag; fsck should not do that. See
* the super_compat table for more info */
feature_incompat: u32, /* Incompatible feature set. If the kernel or fsck doesnt understand
* one of these bits, it should stop. See the super_incompat table
* for more info. */
feature_ro_compat: u32, /* Readonly-compatible feature set. If the kernel doesnt understand
* one of these bits, it can still mount read-only. See the
* super_rocompat table for more info */
uuid: [u8; 16], // 128-bit UUID for volume
volume_name: [u8; 16], // Volume label
last_mounted: [u8; 64], // Directory where filesystem was last mounted
algorithm_usage_bitmap: u32, // For compression (Not used in e2fsprogs/Linux)
// Performance hints. Directory preallocation should only happen if
// the EXT4_FEATURE_INCOMPAT_DIR_PREALLOC flag is on.
prealloc_blocks: u8, /* #. of blocks to try to preallocate for ... files? (Not used in
* e2fsprogs/Linux) */
prealloc_dir_blocks: u8, /* #. of blocks to preallocate for directories. (Not used in
* e2fsprogs/Linux) */
reserved_gdt_blocks: u16, // Number of reserved GDT entries for future filesystem expansion
// Journalling support is valid only if
// EXT4_FEATURE_COMPAT_HAS_JOURNAL is set
journal_uuid: [u8; 16], // UUID of journal superblock
journal_inum: u32, // inode number of journal file
journal_dev: u32, /* Device number of journal file, if the external journal feature
* flag is set */
last_orphan: u32, // Start of list of orphaned inodes to delete
hash_seed: [u32; 4], // HTREE hash seed
def_hash_version: u8, /* Default hash algorithm to use for directory hashes. See
* super_def_hash for more info */
jnl_backup_type: u8, /* If this value is 0 or EXT3_JNL_BACKUP_BLOCKS (1), then the
* s_jnl_blocks field contains a duplicate copy of the inodes
* i_block[] array and i_size */
desc_size: u16, /* Size of group descriptors, in bytes, if the 64bit incompat feature flag
* is set */
default_mount_opts: u32, // Default mount options. See the super_mountopts table for more info
first_meta_bg: u32, // First metablock block group, if the meta_bg feature is enabled
mkfs_time: u32, // When the filesystem was created, in seconds since the epoch
jnl_blocks: [u32; 17], /* Backup copy of the journal inodes i_block[] array in the first
* 15 elements and i_size_high and i_size in the 16th and 17th
* elements, respectively */
// 64bit support is valid only if EXT4_FEATURE_COMPAT_64BIT is set
blocks_count_hi: u32, // High 32-bits of the block count
r_blocks_count_hi: u32, // High 32-bits of the reserved block count
free_blocks_count_hi: u32, // High 32-bits of the free block count
min_extra_isize: u16, // All inodes have at least # bytes
want_extra_isize: u16, // New inodes should reserve # bytes
flags: u32, // Miscellaneous flags. See the super_flags table for more info
raid_stride: u16, /* RAID stride. This is the number of logical blocks read from or
* written to the disk before moving to the next disk. This
* affects the placement of filesystem metadata, which will
* hopefully make RAID storage faster */
mmp_interval: u16, /* #. seconds to wait in multi-mount prevention (MMP) checking. In
* theory, MMP is a mechanism to record in the superblock which host and
* device have mounted the filesystem, in order to prevent multiple
* mounts. This feature does not seem to be implemented... */
mmp_block: u64, // Block # for multi-mount protection data
raid_stripe_width: u32, /* RAID stripe width. This is the number of logical blocks read from
* or written to the disk before coming back to the current disk.
* This is used by the block allocator to try to reduce the number
* of read-modify-write operations in a RAID5/6 */
log_groups_per_flex: u8, // Size of a flexible block group is 2 ^ s_log_groups_per_flex
checksum_type: u8, /* Metadata checksum algorithm type. The only valid value is 1
* (crc32c) */
encryption_level: u8, // Versioning level for encryption
reserved_pad: u8, // Padding to next 32bits
kbytes_written: u64, // Number of KiB written to this filesystem over its lifetime
snapshot_inum: u32, /* inode number of active snapshot. (Not used in
* e2fsprogs/Linux.) */
snapshot_id: i32, // Sequential ID of active snapshot. (Not used in e2fsprogs/Linux.)
snapshot_r_blocks_count: u64, /* Number of blocks reserved for active snapshots future use.
* (Not used in e2fsprogs/Linux.) */
snapshot_list: u32, /* inode number of the head of the on-disk snapshot list. (Not used in
* e2fsprogs/Linux.) */
error_count: u32, // Number of errors seen
first_error_time: u32, // First time an error happened, in seconds since the epoch
first_error_ino: u32, // inode involved in first error
first_error_block: u64, // Number of block involved of first error
first_error_func: [u8; 32], // Name of function where the error happened
first_error_line: u32, // Line number where error happened
last_error_time: u32, // Time of most recent error, in seconds since the epoch
last_error_ino: u32, // inode involved in most recent error
last_error_line: u32, // Line number where most recent error happened
last_error_block: u64, // Number of block involved in most recent error
last_error_func: [u8; 32], // Name of function where the most recent error happened
mount_opts: [u8; 64], // ASCIIZ string of mount options
usr_quota_inum: u32, // Inode number of user quota file
grp_quota_inum: u32, // Inode number of group quota file
overhead_blocks: u32, /* Overhead blocks/clusters in fs. (Huh? This field is always
* zero, which means that the
* kernel calculates it dynamically.) */
backup_bgs: [u32; 2], // Block groups containing superblock backups (if sparse_super2)
encrypt_algos: [u8; 4], /* Encryption algorithms in use. There can be up to four algorithms in
* use at any time; valid algorithm codes are given in the
* super_encrypt table below */
encrypt_pw_salt: [u8; 16], // Salt for the string2key algorithm for encryption
lpf_ino: u32, // Inode number of lost+found
prj_quota_inum: u32, // Inode that tracks project quotas
checksum_seed: u32, /* Checksum seed used for metadata_csum calculations. This value
* is crc32c(~0, $orig_fs_uuid) */
wtime_hi: u8, // Upper 8 bits of the s_wtime field
mtime_hi: u8, // Upper 8 bits of the s_mtime field
mkfs_time_hi: u8, // Upper 8 bits of the s_mkfs_time field
lastcheck_hi: u8, // Upper 8 bits of the s_lastcheck field
first_error_time_hi: u8, // Upper 8 bits of the s_first_error_time field
last_error_time_hi: u8, // Upper 8 bits of the s_last_error_time field
first_error_errcode: u8,
last_error_errcode: u8,
encoding: u16, // Filename charset encoding
encoding_flags: u16, // Filename charset encoding flags
orphan_file_inum: u32, // Orphan file inode number
reserved: [u32; 94], // Padding to the end of the block
checksum: u32, // Superblock checksum
}
// offset of checksum + size of checksum
pub const SUPERBLOCK_SIZE: usize = 1024;
const_assert_eq!(std::mem::size_of::<Superblock>(), SUPERBLOCK_SIZE);
impl Superblock {
pub fn from_bytes(bytes: &[u8]) -> Result<Superblock, SuperblockError> {
assert_eq!(bytes.len(), std::mem::size_of::<Superblock>());
let super_block = unsafe { std::ptr::read(bytes.as_ptr() as *const Superblock) };
let checksum = super_block.calc_checksum();
if super_block.checksum != checksum {
return Err(SuperblockError::InvalidChecksum {
expected: checksum,
actual: super_block.checksum,
});
}
Ok(super_block)
}
pub fn calc_checksum(&self) -> u32 {
// let mut crc: u32 = !0;
let bytes = unsafe {
let ptr = self as *const Superblock as *const u8;
let len = std::mem::size_of::<Superblock>();
let slice = std::slice::from_raw_parts(ptr, len);
&slice[..len - 4]
};
crc32(bytes)
}
pub fn inodes_count(&self) -> u32 {
self.inodes_count
}
pub fn blocks_count(&self) -> u64 {
combine_lo_hi(self.blocks_count_lo, self.blocks_count_hi)
}
pub fn r_blocks_count(&self) -> u64 {
combine_lo_hi(self.r_blocks_count_lo, self.r_blocks_count_hi)
}
pub fn free_blocks_count(&self) -> u64 {
combine_lo_hi(self.free_blocks_count_lo, self.free_blocks_count_hi)
}
pub fn free_inodes_count(&self) -> u32 {
self.free_inodes_count
}
pub fn blocksize(&self) -> u64 {
1 << (10 + self.log_block_size)
}
pub fn blocks_per_group(&self) -> u32 {
self.blocks_per_groups
}
}

83
src/utils.rs Normal file
View file

@ -0,0 +1,83 @@
const CRC_TABLE: [u32; 256] = [
0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb,
0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b, 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24,
0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384,
0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b,
0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35,
0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa,
0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a,
0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595,
0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957,
0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198,
0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927, 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38,
0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7,
0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789,
0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46,
0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6,
0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829,
0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93,
0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c,
0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc,
0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c, 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033,
0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d,
0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982,
0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622,
0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed,
0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f,
0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0,
0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540,
0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f,
0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee, 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1,
0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e,
0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e,
0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351,
];
pub fn crc32(bytes: &[u8]) -> u32 {
let mut crc: u32 = !0;
for &byte in bytes {
crc = (crc >> 8) ^ CRC_TABLE[(crc as u8 ^ byte) as usize];
}
crc
}
// a group has a superblock if it is a power of 3, of 5, or of 7
// (see https://docs.kernel.org/filesystems/ext4/globals.html#super-block)
pub fn group_has_superblock(group_num: u64) -> bool {
// this function calculates the max power of n that can be represented by a u64
const fn max_power_in_u64(n: u64) -> u64 {
let mut max = n;
while let Some(new_max) = max.checked_mul(n) {
max = new_max;
}
max
}
const MAX_POWER_OF_3: u64 = max_power_in_u64(3);
const MAX_POWER_OF_5: u64 = max_power_in_u64(5);
const MAX_POWER_OF_7: u64 = max_power_in_u64(7);
// fast path: groups 0 and 1 have superblocks
if group_num <= 1 {
return true;
}
// fast path: even groups above 1 never have superblocks
if group_num % 2 == 0 {
return false;
}
// the way this works is that a smaller power of n will always divide a larger power of n, thus
// for u64s, x is a power of n iff it divides the largest power of n
MAX_POWER_OF_3 % group_num == 0
|| MAX_POWER_OF_5 % group_num == 0
|| MAX_POWER_OF_7 % group_num == 0
}
pub fn combine_lo_hi(lo: u32, hi: u32) -> u64 {
lo as u64 | ((hi as u64) << 32)
}