current state

This commit is contained in:
Moritz Gmeiner 2025-07-29 20:35:41 +02:00
commit d02a01a301
9 changed files with 455 additions and 63 deletions

60
Cargo.lock generated
View file

@ -8,22 +8,82 @@ version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.1" version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "bitflags-derive"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09648aa9dde04191fe237a510379c9eca42a97733b2065cc337922fba2bd378a"
dependencies = [
"bitflags",
"bitflags-derive-macros",
]
[[package]]
name = "bitflags-derive-macros"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d242ebf64d65693821c7e73a26d87757e83f795918fd42334da3dafaafddbd64"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "chrono"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "ext4" name = "ext4"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags", "bitflags",
"bitflags-derive",
"chrono",
"num-derive",
"num-traits",
"static_assertions", "static_assertions",
"thiserror", "thiserror",
] ]
[[package]]
name = "num-derive"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.95" version = "1.0.95"

View file

@ -10,5 +10,9 @@ path = "src/dump.rs"
[dependencies] [dependencies]
anyhow = "1.0.98" anyhow = "1.0.98"
bitflags = "2.9.1" bitflags = "2.9.1"
bitflags-derive = "0.0.4"
chrono = { version = "0.4.41", default-features = false, features = ["std"] }
num-derive = "0.4.2"
num-traits = "0.2.19"
static_assertions = "1.1.0" static_assertions = "1.1.0"
thiserror = "2.0.12" thiserror = "2.0.12"

View file

@ -1,6 +1,8 @@
imports_granularity = "Module" imports_granularity = "Module"
group_imports = "StdExternalCrate" group_imports = "StdExternalCrate"
fn_call_width = 80
# Activation of features, almost objectively better ;) # Activation of features, almost objectively better ;)
use_try_shorthand = true use_try_shorthand = true
use_field_init_shorthand = true use_field_init_shorthand = true

View file

@ -20,11 +20,11 @@ pub fn main() -> anyhow::Result<()> {
let superblock = Superblock::from_bytes(&buf)?; let superblock = Superblock::from_bytes(&buf)?;
println!("Superblock 0 (offset: 1024): {:?}", superblock); println!("Superblock 0 (offset: 1024): {}", superblock);
let num_groups = superblock.blocks_count() / superblock.blocks_per_group() as u64; let num_groups = superblock.blocks_count() / superblock.blocks_per_group() as u64;
let group_size = (superblock.blocks_per_group() as u64) * superblock.blocksize(); let group_size = (superblock.blocks_per_group() as u64) * superblock.block_size();
println!("group_size: 0x{group_size:08X}"); println!("group_size: 0x{group_size:08X}");

45
src/inode.rs Normal file
View file

@ -0,0 +1,45 @@
use static_assertions::const_assert_eq;
#[derive(Debug)]
#[repr(C)]
pub struct Inode {
mode: u16,
uid: u16,
size_lo: u32,
atime: u32,
ctime: u32,
mtime: u32,
dtime: u32,
gid: u16,
links_count: u16,
blocks_lo: u32,
flags: u32,
osd1: [u8; 4],
block: [u32; 15],
generation: u32,
file_acl_lo: u32,
size_high: u32,
obso_faddr: u32,
usd2: [u8; 12],
extra_isize: u16,
checksum_hi: u16,
ctime_extra: u32,
mtime_extra: u32,
atime_extra: u32,
crtime: u32,
crtime_extra: u32,
version_hi: u32,
proj_id: u32,
}
const_assert_eq!(std::mem::size_of::<Inode>(), 160);
impl Inode {
pub fn from_bytes(bytes: &[u8; std::mem::size_of::<Inode>()]) -> Result<Inode, ()> {
assert_eq!(bytes.len(), std::mem::size_of::<Inode>());
let inode = unsafe { std::ptr::read(bytes.as_ptr() as *const Inode) };
Ok(inode)
}
}

View file

@ -1,19 +1,38 @@
use std::io::SeekFrom; use std::io::{Read as _, Seek as _, SeekFrom, Write as _};
use crate::superblock::SuperblockError;
pub use crate::superblock::{SUPERBLOCK_SIZE, Superblock}; pub use crate::superblock::{SUPERBLOCK_SIZE, Superblock};
pub use crate::utils::group_has_superblock; pub use crate::utils::group_has_superblock;
mod group_descriptor; mod group_descriptor;
mod inode;
mod mmp; mod mmp;
mod superblock; mod superblock;
mod utils; mod utils;
pub trait FileLike { const SUPPORTED_COMPATIBLE_FEATURES: superblock::CompatibleFeatures =
superblock::CompatibleFeatures::empty();
const SUPPORTED_INCOMPATIBLE_FEATURES: superblock::IncompatibleFeatures =
superblock::IncompatibleFeatures::empty();
const SUPPORTED_RO_COMPATIBLE_FEATURES: superblock::RoCompatibleFeatures =
superblock::RoCompatibleFeatures::empty();
#[derive(Debug, thiserror::Error)]
pub enum Ext4FsError {
#[error("Filesystem has unsupported incompatible features enabled: {0}")]
IncompatibleFeatures(String),
#[error("Superblock error: {0}")]
SuperblockError(#[from] SuperblockError),
#[error("Unknown error: {0}")]
UnknownError(#[from] anyhow::Error),
}
pub trait SliceLike {
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> anyhow::Result<()>; 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 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]> { fn read_array_at_offset<const N: usize>(&mut self, offset: u64) -> anyhow::Result<[u8; N]> {
let mut buf = [0; N]; let mut buf = [0; N];
self.read_at_offset(offset, &mut buf)?; self.read_at_offset(offset, &mut buf)?;
@ -22,7 +41,37 @@ pub trait FileLike {
} }
} }
impl<F: std::io::Read + std::io::Write + std::io::Seek> FileLike for F { impl SliceLike 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(())
}
}
impl SliceLike for std::fs::File {
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> anyhow::Result<()> { fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> anyhow::Result<()> {
self.seek(SeekFrom::Start(offset))?; self.seek(SeekFrom::Start(offset))?;
@ -40,52 +89,38 @@ impl<F: std::io::Read + std::io::Write + std::io::Seek> FileLike for F {
} }
} }
// impl FileLike for &mut [u8] { pub struct Ext4Fs<S: SliceLike> {
// 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)] #[allow(dead_code)]
file: F, data: S,
superblock: superblock::Superblock, superblock: superblock::Superblock,
} }
impl<F: FileLike> Ext4Fs<F> { impl<S: SliceLike> Ext4Fs<S> {
pub fn load(file: F) -> Result<Ext4Fs<F>, anyhow::Error> { pub fn load(data: S) -> Result<Ext4Fs<S>, Ext4FsError> {
let mut file = file; let mut file = data;
let superblock_bytes = file.read_at_offset_const::<SUPERBLOCK_SIZE>(1024)?; let superblock_bytes = file.read_array_at_offset::<SUPERBLOCK_SIZE>(1024)?;
let superblock = Superblock::from_bytes(&superblock_bytes)?; let superblock = Superblock::from_bytes(&superblock_bytes)?;
Ok(Ext4Fs { file, superblock }) let unsupported_incompat_features = superblock
.feature_incompat()
.difference(SUPPORTED_INCOMPATIBLE_FEATURES);
if !unsupported_incompat_features.is_empty() {
let mut s = "".to_owned();
bitflags::parser::to_writer(&unsupported_incompat_features, &mut s)
.map_err(|e| anyhow::Error::new(e))?;
return Err(Ext4FsError::IncompatibleFeatures(s));
}
Ok(Ext4Fs {
data: file,
superblock,
})
} }
pub fn super_block(&self) -> &Superblock { pub fn super_block(&self) -> &Superblock {

View file

@ -1,3 +1,5 @@
use static_assertions::const_assert_eq;
#[derive(Debug)] #[derive(Debug)]
#[repr(C)] #[repr(C)]
pub struct Mmp { pub struct Mmp {
@ -12,6 +14,8 @@ pub struct Mmp {
checksum: u32, // Checksum of the MMP block checksum: u32, // Checksum of the MMP block
} }
const_assert_eq!(std::mem::size_of::<Mmp>(), 1024);
impl Mmp { impl Mmp {
pub fn from_bytes(bytes: &[u8]) -> Result<Mmp, ()> { pub fn from_bytes(bytes: &[u8]) -> Result<Mmp, ()> {
assert_eq!(bytes.len(), std::mem::size_of::<Mmp>()); assert_eq!(bytes.len(), std::mem::size_of::<Mmp>());

View file

@ -1,9 +1,16 @@
use std::time::{Duration, SystemTime};
use bitflags::bitflags; use bitflags::bitflags;
use bitflags_derive::FlagsDisplay;
use chrono::{DateTime, Utc};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use static_assertions::const_assert_eq; use static_assertions::const_assert_eq;
use crate::utils::{combine_lo_hi, crc32}; use crate::utils::{combine_lo_hi, crc32, uuid_to_string};
bitflags! { bitflags! {
#[derive(FlagsDisplay)]
pub struct State: u16 { pub struct State: u16 {
const VALID_FS = 0x0001; // cleanly unmounted const VALID_FS = 0x0001; // cleanly unmounted
const ERROR_FS = 0x0002; // errors detected const ERROR_FS = 0x0002; // errors detected
@ -13,6 +20,14 @@ bitflags! {
} }
} }
#[derive(Debug, FromPrimitive)]
#[repr(u16)]
pub enum Errors {
CONTINUE = 1,
RemountRo = 2,
Panic = 3,
}
#[non_exhaustive] #[non_exhaustive]
#[repr(u32)] #[repr(u32)]
pub enum CreatorOs { pub enum CreatorOs {
@ -23,15 +38,17 @@ pub enum CreatorOs {
Lites = 4, Lites = 4,
} }
#[non_exhaustive] #[derive(Debug, FromPrimitive)]
#[repr(u32)] #[repr(u32)]
#[non_exhaustive]
pub enum Revision { pub enum Revision {
GoodOldExt4 = 0, GoodOldExt4 = 0,
DynamicInodes = 1, DynamicInodes = 1,
} }
bitflags! { bitflags! {
pub struct CompatibleFlags: u32 { #[derive(FlagsDisplay)]
pub struct CompatibleFeatures: u32 {
const DIR_PREALLOC = 0x1; // Directory preallocation const DIR_PREALLOC = 0x1; // Directory preallocation
const IMAGIC_INODES = 0x2; // “imagic inodes”. Not clear from the code what this does const IMAGIC_INODES = 0x2; // “imagic inodes”. Not clear from the code what this does
const HAS_JOURNAL = 0x4; // Has a journal const HAS_JOURNAL = 0x4; // Has a journal
@ -64,6 +81,7 @@ bitflags! {
} }
bitflags! { bitflags! {
#[derive(FlagsDisplay)]
pub struct IncompatibleFeatures: u32 { pub struct IncompatibleFeatures: u32 {
const COMPRESSION = 0x1; // Compression const COMPRESSION = 0x1; // Compression
const FILETYPE = 0x2; /* Directory entries record the file type. See const FILETYPE = 0x2; /* Directory entries record the file type. See
@ -97,6 +115,7 @@ bitflags! {
} }
bitflags! { bitflags! {
#[derive(FlagsDisplay)]
pub struct RoCompatibleFeatures: u32 { pub struct RoCompatibleFeatures: u32 {
const SPARSE_SUPER = 0x1; /* Sparse superblocks. See the earlier discussion of this const SPARSE_SUPER = 0x1; /* Sparse superblocks. See the earlier discussion of this
feature */ feature */
@ -150,25 +169,27 @@ pub enum DefHashVersion {
} }
bitflags! { bitflags! {
#[derive(FlagsDisplay)]
pub struct DefaultMountOpts: u32 { pub struct DefaultMountOpts: u32 {
const EXT4_DEFM_DEBUG = 0x1; const DEBUG = 0x1;
const EXT4_DEFM_BSDGROUPS = 0x2; const BSDGROUPS = 0x2;
const EXT4_DEFM_XATTR_USER = 0x4; const XATTR_USER = 0x4;
const EXT4_DEFM_ACL = 0x8; const ACL = 0x8;
const EXT4_DEFM_UID16 = 0x10; const UID16 = 0x10;
const EXT4_DEFM_JMODE_DATA = 0x20; const JMODE_DATA = 0x20;
const EXT4_DEFM_JMODE_ORDERED = 0x40; const JMODE_ORDERED = 0x40;
const EXT4_DEFM_JMODE_WBACK = 0x60; const JMODE_WBACK = 0x60;
const EXT4_DEFM_NOBARRIER = 0x100; const NOBARRIER = 0x100;
const EXT4_DEFM_BLOCK_VALIDITY = 0x200; const BLOCK_VALIDITY = 0x200;
const EXT4_DEFM_DISCARD = 0x400; const DISCARD = 0x400;
const EXT4_DEFM_NODELALLOC = 0x800; const NODELALLOC = 0x800;
const _ = !0; const _ = !0;
} }
} }
bitflags! { bitflags! {
#[derive(FlagsDisplay)]
pub struct Flags: u32 { pub struct Flags: u32 {
const SignedHash = 0x1; // Signed dirhash in use const SignedHash = 0x1; // Signed dirhash in use
const UnsignedHash = 0x2; // Unsigned dirhash in use const UnsignedHash = 0x2; // Unsigned dirhash in use
@ -192,8 +213,10 @@ pub enum EncryptAlgos {
pub enum SuperblockError { pub enum SuperblockError {
#[error("invalid value {value} for field {field}")] #[error("invalid value {value} for field {field}")]
InvalidFieldValue { field: String, value: String }, InvalidFieldValue { field: String, value: String },
#[error("invalid checksum: expected {expected:X}, but found {actual:X}")] #[error("invalid checksum: expected {expected:#X}, but found {actual:#X}")]
InvalidChecksum { expected: u32, actual: u32 }, InvalidChecksum { expected: u32, actual: u32 },
#[error("invalid magic signature: expected 0xEF53, but found {0:#X}")]
InvalidMagic(u16),
} }
#[derive(Debug)] #[derive(Debug)]
@ -210,9 +233,9 @@ pub struct Superblock {
log_cluster_size: u32, /* Cluster size is 2 ^ (10 + s_log_cluster_size) blocks if bigalloc 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 * is enabled. Otherwise s_log_cluster_size must equal
* s_log_block_size */ * s_log_block_size */
blocks_per_groups: u32, // Blocks per group blocks_per_group: u32, // Blocks per group
clusters_per_group: u32, /* Clusters per group, if bigalloc is enabled. Otherwise clusters_per_group: u32, /* Clusters per group, if bigalloc is enabled. Otherwise
* s_clusters_per_group must equal s_blocks_per_group */ * s_clusters_per_group must equal s_blocks_per_group */
inodes_per_group: u32, // Inodes per group inodes_per_group: u32, // Inodes per group
mtime: u32, // Mount time, in seconds since the epoch mtime: u32, // Mount time, in seconds since the epoch
wtime: u32, // Write time, in seconds since the epoch wtime: u32, // Write time, in seconds since the epoch
@ -361,6 +384,58 @@ pub struct Superblock {
checksum: u32, // Superblock checksum checksum: u32, // Superblock checksum
} }
impl std::fmt::Display for Superblock {
fn fmt(&self, mut f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Superblock {{")?;
writeln!(f, " total inode count: {}", self.inodes_count())?;
writeln!(f, " total block count: {}", self.blocks_count())?;
writeln!(f, " reserved blocks: {}", self.r_blocks_count())?;
writeln!(f, " free blocks: {}", self.free_blocks_count())?;
writeln!(f, " free inodes: {}", self.free_inodes_count())?;
writeln!(f, " block size: {}", self.block_size())?;
writeln!(f, " cluster size: {}", self.cluster_size())?;
writeln!(f, " blocks per group: {}", self.blocks_per_group())?;
writeln!(f, " clusters per group: {}", self.clusters_per_group)?;
writeln!(f, " inodes per group: {}", self.inodes_per_group)?;
writeln!(f, " mtime: {}", DateTime::<Utc>::from(self.mtime()))?;
writeln!(f, " wtime: {}", DateTime::<Utc>::from(self.wtime()))?;
writeln!(f, " mount count: {}", self.mnt_count)?;
writeln!(f, " max mount count: {}", self.max_mnt_count)?;
writeln!(f, " state: {}", self.state())?;
writeln!(f, " errors: {:?}", self.errors())?;
writeln!(f, " last check: {}", DateTime::<Utc>::from(self.lastcheck()))?;
writeln!(f, " check interval: {}", self.checkinterval())?;
writeln!(f, " revision: {:?}", self.rev_level())?;
writeln!(f, " feature compat: {}", self.feature_compat())?;
writeln!(f, " feature incompat: {}", self.feature_incompat())?;
writeln!(f, " feature ro_compat: {}", self.feature_ro_compat())?;
writeln!(f, " uuid: {}", self.uuid_str())?;
writeln!(f, " volume name: \"{}\"", self.volume_name().unwrap_or(""))?;
writeln!(f, " last mounted: \"{}\"", self.last_mounted().unwrap_or(""))?;
if self
.feature_compat()
.contains(CompatibleFeatures::HAS_JOURNAL)
{
writeln!(f, " journal uuid: {}", self.journal_uuid_str())?;
}
writeln!(f, " default mount opts: {}", self.default_mount_opts())?;
writeln!(f, " mkfs time: {}", DateTime::<Utc>::from(self.mkfs_time()))?;
Ok(())
}
}
// offset of checksum + size of checksum // offset of checksum + size of checksum
pub const SUPERBLOCK_SIZE: usize = 1024; pub const SUPERBLOCK_SIZE: usize = 1024;
@ -374,6 +449,10 @@ impl Superblock {
let checksum = super_block.calc_checksum(); let checksum = super_block.calc_checksum();
if super_block.magic != 0xEF53 {
return Err(SuperblockError::InvalidMagic(super_block.magic));
}
if super_block.checksum != checksum { if super_block.checksum != checksum {
return Err(SuperblockError::InvalidChecksum { return Err(SuperblockError::InvalidChecksum {
expected: checksum, expected: checksum,
@ -404,14 +483,26 @@ impl Superblock {
} }
pub fn blocks_count(&self) -> u64 { pub fn blocks_count(&self) -> u64 {
if !self.is_64bit() {
return self.blocks_count_lo as u64;
}
combine_lo_hi(self.blocks_count_lo, self.blocks_count_hi) combine_lo_hi(self.blocks_count_lo, self.blocks_count_hi)
} }
pub fn r_blocks_count(&self) -> u64 { pub fn r_blocks_count(&self) -> u64 {
if !self.is_64bit() {
return self.r_blocks_count_lo as u64;
}
combine_lo_hi(self.r_blocks_count_lo, self.r_blocks_count_hi) combine_lo_hi(self.r_blocks_count_lo, self.r_blocks_count_hi)
} }
pub fn free_blocks_count(&self) -> u64 { pub fn free_blocks_count(&self) -> u64 {
if !self.is_64bit() {
return self.free_blocks_count_lo as u64;
}
combine_lo_hi(self.free_blocks_count_lo, self.free_blocks_count_hi) combine_lo_hi(self.free_blocks_count_lo, self.free_blocks_count_hi)
} }
@ -419,11 +510,118 @@ impl Superblock {
self.free_inodes_count self.free_inodes_count
} }
pub fn blocksize(&self) -> u64 { pub fn block_size(&self) -> u64 {
1 << (10 + self.log_block_size) 1 << (10 + self.log_block_size)
} }
pub fn cluster_size(&self) -> u64 {
1 << (10 + self.log_cluster_size)
}
pub fn blocks_per_group(&self) -> u32 { pub fn blocks_per_group(&self) -> u32 {
self.blocks_per_groups self.blocks_per_group
}
pub fn clusters_per_group(&self) -> u32 {
self.clusters_per_group
}
pub fn inodes_per_group(&self) -> u32 {
self.inodes_per_group
}
pub fn mtime(&self) -> SystemTime {
SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs(self.mtime as u64))
.unwrap()
}
pub fn wtime(&self) -> SystemTime {
SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs(self.wtime as u64))
.unwrap()
}
pub fn state(&self) -> State {
State::from_bits_retain(self.state)
}
pub fn errors(&self) -> Errors {
FromPrimitive::from_u16(self.errors).unwrap()
}
pub fn lastcheck(&self) -> SystemTime {
SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs(self.lastcheck as u64))
.unwrap()
}
pub fn checkinterval(&self) -> u32 {
self.checkinterval
}
pub fn rev_level(&self) -> Revision {
FromPrimitive::from_u32(self.rev_level).unwrap()
}
pub fn feature_compat(&self) -> CompatibleFeatures {
CompatibleFeatures::from_bits_retain(self.feature_compat)
}
pub fn feature_incompat(&self) -> IncompatibleFeatures {
IncompatibleFeatures::from_bits_retain(self.feature_incompat)
}
pub fn feature_ro_compat(&self) -> RoCompatibleFeatures {
RoCompatibleFeatures::from_bits_retain(self.feature_ro_compat)
}
pub fn uuid(&self) -> u128 {
u128::from_le_bytes(self.uuid)
}
pub fn uuid_str(&self) -> String {
uuid_to_string(self.uuid)
}
pub fn volume_name(&self) -> Option<&str> {
std::str::from_utf8(self.volume_name.as_slice()).ok()
}
pub fn last_mounted(&self) -> Option<&str> {
std::str::from_utf8(self.last_mounted.as_slice()).ok()
}
pub fn journal_uuid(&self) -> u128 {
assert!(
self.feature_compat()
.contains(CompatibleFeatures::HAS_JOURNAL)
);
u128::from_le_bytes(self.journal_uuid)
}
pub fn journal_uuid_str(&self) -> String {
assert!(
self.feature_compat()
.contains(CompatibleFeatures::HAS_JOURNAL)
);
uuid_to_string(self.journal_uuid)
}
pub fn default_mount_opts(&self) -> DefaultMountOpts {
DefaultMountOpts::from_bits_retain(self.default_mount_opts)
}
pub fn mkfs_time(&self) -> SystemTime {
SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs(self.mkfs_time as u64))
.unwrap()
}
pub fn is_64bit(&self) -> bool {
self.feature_incompat()
.contains(IncompatibleFeatures::_64BIT)
} }
} }

View file

@ -81,3 +81,47 @@ pub fn group_has_superblock(group_num: u64) -> bool {
pub fn combine_lo_hi(lo: u32, hi: u32) -> u64 { pub fn combine_lo_hi(lo: u32, hi: u32) -> u64 {
lo as u64 | ((hi as u64) << 32) lo as u64 | ((hi as u64) << 32)
} }
pub fn uuid_to_string(src: [u8; 16]) -> String {
let mut s = String::with_capacity(36);
// adapted from Uuid crate
const LUT: [char; 16] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
];
let groups = [(0, 8), (9, 13), (14, 18), (19, 23), (24, 36)];
let mut group_idx = 0;
let mut i = 0;
while group_idx < 5 {
let (start, end) = groups[group_idx];
let mut j = start;
while j < end {
let x = src[i];
i += 1;
s.push(LUT[(x >> 4) as usize]);
s.push(LUT[(x & 0x0f) as usize]);
j += 2;
}
if group_idx < 4 {
s.push('-');
}
group_idx += 1;
}
assert_eq!(s.len(), 36);
assert_eq!(s.capacity(), 36);
s
}