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"
|
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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
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::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 {
|
||||||
|
|
|
||||||
|
|
@ -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>());
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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 {
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue