current state
This commit is contained in:
parent
75d23682c0
commit
d02a01a301
9 changed files with 455 additions and 63 deletions
60
Cargo.lock
generated
60
Cargo.lock
generated
|
|
@ -8,22 +8,82 @@ version = "1.0.98"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "ext4"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"bitflags-derive",
|
||||
"chrono",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"static_assertions",
|
||||
"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]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
|
|
|
|||
|
|
@ -10,5 +10,9 @@ path = "src/dump.rs"
|
|||
[dependencies]
|
||||
anyhow = "1.0.98"
|
||||
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"
|
||||
thiserror = "2.0.12"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
imports_granularity = "Module"
|
||||
group_imports = "StdExternalCrate"
|
||||
|
||||
fn_call_width = 80
|
||||
|
||||
# Activation of features, almost objectively better ;)
|
||||
use_try_shorthand = true
|
||||
use_field_init_shorthand = true
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ pub fn main() -> anyhow::Result<()> {
|
|||
|
||||
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 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}");
|
||||
|
||||
|
|
|
|||
45
src/inode.rs
Normal file
45
src/inode.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
117
src/lib.rs
117
src/lib.rs
|
|
@ -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::utils::group_has_superblock;
|
||||
|
||||
mod group_descriptor;
|
||||
mod inode;
|
||||
mod mmp;
|
||||
mod superblock;
|
||||
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 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];
|
||||
|
||||
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<()> {
|
||||
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] {
|
||||
// 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> {
|
||||
pub struct Ext4Fs<S: SliceLike> {
|
||||
#[allow(dead_code)]
|
||||
file: F,
|
||||
data: S,
|
||||
|
||||
superblock: superblock::Superblock,
|
||||
}
|
||||
|
||||
impl<F: FileLike> Ext4Fs<F> {
|
||||
pub fn load(file: F) -> Result<Ext4Fs<F>, anyhow::Error> {
|
||||
let mut file = file;
|
||||
impl<S: SliceLike> Ext4Fs<S> {
|
||||
pub fn load(data: S) -> Result<Ext4Fs<S>, Ext4FsError> {
|
||||
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)?;
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use static_assertions::const_assert_eq;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Mmp {
|
||||
|
|
@ -12,6 +14,8 @@ pub struct Mmp {
|
|||
checksum: u32, // Checksum of the MMP block
|
||||
}
|
||||
|
||||
const_assert_eq!(std::mem::size_of::<Mmp>(), 1024);
|
||||
|
||||
impl Mmp {
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Mmp, ()> {
|
||||
assert_eq!(bytes.len(), std::mem::size_of::<Mmp>());
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
use std::time::{Duration, SystemTime};
|
||||
|
||||
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 crate::utils::{combine_lo_hi, crc32};
|
||||
use crate::utils::{combine_lo_hi, crc32, uuid_to_string};
|
||||
|
||||
bitflags! {
|
||||
#[derive(FlagsDisplay)]
|
||||
pub struct State: u16 {
|
||||
const VALID_FS = 0x0001; // cleanly unmounted
|
||||
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]
|
||||
#[repr(u32)]
|
||||
pub enum CreatorOs {
|
||||
|
|
@ -23,15 +38,17 @@ pub enum CreatorOs {
|
|||
Lites = 4,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, FromPrimitive)]
|
||||
#[repr(u32)]
|
||||
#[non_exhaustive]
|
||||
pub enum Revision {
|
||||
GoodOldExt4 = 0,
|
||||
DynamicInodes = 1,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct CompatibleFlags: u32 {
|
||||
#[derive(FlagsDisplay)]
|
||||
pub struct CompatibleFeatures: 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
|
||||
|
|
@ -64,6 +81,7 @@ bitflags! {
|
|||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(FlagsDisplay)]
|
||||
pub struct IncompatibleFeatures: u32 {
|
||||
const COMPRESSION = 0x1; // Compression
|
||||
const FILETYPE = 0x2; /* Directory entries record the file type. See
|
||||
|
|
@ -97,6 +115,7 @@ bitflags! {
|
|||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(FlagsDisplay)]
|
||||
pub struct RoCompatibleFeatures: u32 {
|
||||
const SPARSE_SUPER = 0x1; /* Sparse superblocks. See the earlier discussion of this
|
||||
feature */
|
||||
|
|
@ -150,25 +169,27 @@ pub enum DefHashVersion {
|
|||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(FlagsDisplay)]
|
||||
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 DEBUG = 0x1;
|
||||
const BSDGROUPS = 0x2;
|
||||
const XATTR_USER = 0x4;
|
||||
const ACL = 0x8;
|
||||
const UID16 = 0x10;
|
||||
const JMODE_DATA = 0x20;
|
||||
const JMODE_ORDERED = 0x40;
|
||||
const JMODE_WBACK = 0x60;
|
||||
const NOBARRIER = 0x100;
|
||||
const BLOCK_VALIDITY = 0x200;
|
||||
const DISCARD = 0x400;
|
||||
const NODELALLOC = 0x800;
|
||||
|
||||
const _ = !0;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(FlagsDisplay)]
|
||||
pub struct Flags: u32 {
|
||||
const SignedHash = 0x1; // Signed dirhash in use
|
||||
const UnsignedHash = 0x2; // Unsigned dirhash in use
|
||||
|
|
@ -192,8 +213,10 @@ pub enum EncryptAlgos {
|
|||
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}")]
|
||||
#[error("invalid checksum: expected {expected:#X}, but found {actual:#X}")]
|
||||
InvalidChecksum { expected: u32, actual: u32 },
|
||||
#[error("invalid magic signature: expected 0xEF53, but found {0:#X}")]
|
||||
InvalidMagic(u16),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -210,7 +233,7 @@ pub struct Superblock {
|
|||
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
|
||||
blocks_per_group: 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
|
||||
|
|
@ -361,6 +384,58 @@ pub struct Superblock {
|
|||
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
|
||||
pub const SUPERBLOCK_SIZE: usize = 1024;
|
||||
|
||||
|
|
@ -374,6 +449,10 @@ impl Superblock {
|
|||
|
||||
let checksum = super_block.calc_checksum();
|
||||
|
||||
if super_block.magic != 0xEF53 {
|
||||
return Err(SuperblockError::InvalidMagic(super_block.magic));
|
||||
}
|
||||
|
||||
if super_block.checksum != checksum {
|
||||
return Err(SuperblockError::InvalidChecksum {
|
||||
expected: checksum,
|
||||
|
|
@ -404,14 +483,26 @@ impl Superblock {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
@ -419,11 +510,118 @@ impl Superblock {
|
|||
self.free_inodes_count
|
||||
}
|
||||
|
||||
pub fn blocksize(&self) -> u64 {
|
||||
pub fn block_size(&self) -> u64 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
src/utils.rs
44
src/utils.rs
|
|
@ -81,3 +81,47 @@ pub fn group_has_superblock(group_num: u64) -> bool {
|
|||
pub fn combine_lo_hi(lo: u32, hi: u32) -> u64 {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue