Skip to content

Commit

Permalink
superblock: Overhaul for magic-first reusable detection
Browse files Browse the repository at this point in the history
 - Switch to enum dispatch, no unnecessary dyn cruft
 - Reusable pattern for decoding superblock structs
 - Simplistic magic matching on bytes

Signed-off-by: Ikey Doherty <[email protected]>
  • Loading branch information
ikeycode committed Jan 19, 2025
1 parent bd72ca7 commit 5867600
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 343 deletions.
57 changes: 13 additions & 44 deletions crates/superblock/src/btrfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
//! This module provides functionality for reading and parsing BTRFS filesystem superblocks,
//! which contain critical metadata about the filesystem including UUIDs and labels.
use crate::{Error, Kind, Superblock};
use log;
use std::io::{self, Read};
use crate::{Detection, Error};
use uuid::Uuid;
use zerocopy::*;

Expand Down Expand Up @@ -103,57 +101,28 @@ pub const START_POSITION: u64 = 0x10000;
/// Magic number identifying a BTRFS superblock ("_BHRfS_M")
pub const MAGIC: U64<LittleEndian> = U64::new(0x4D5F53665248425F);

/// Attempt to decode the BTRFS superblock from the given read stream.
///
/// This will read past the initial superblock offset and validate the magic number
/// before returning the parsed superblock structure.
pub fn from_reader<R: Read>(reader: &mut R) -> Result<Btrfs, Error> {
// Drop unwanted bytes (Seek not possible with zstd streamed inputs)
io::copy(&mut reader.by_ref().take(START_POSITION), &mut io::sink())?;
impl Detection for Btrfs {
type Magic = U64<LittleEndian>;

const OFFSET: u64 = START_POSITION;

const MAGIC_OFFSET: u64 = START_POSITION + 0x40;

let data = Btrfs::read_from_io(reader).map_err(|_| Error::InvalidSuperblock)?;
const SIZE: usize = std::mem::size_of::<Btrfs>();

if data.magic != MAGIC {
Err(Error::InvalidMagic)
} else {
log::trace!(
"valid magic field: UUID={}, [volume label: \"{}\"]",
data.uuid()?,
data.label()?
);
Ok(data)
fn is_valid_magic(magic: &Self::Magic) -> bool {
*magic == MAGIC
}
}

impl Superblock for Btrfs {
impl Btrfs {
/// Return the encoded UUID for this superblock as a string
fn uuid(&self) -> Result<String, Error> {
pub fn uuid(&self) -> Result<String, Error> {
Ok(Uuid::from_bytes(self.fsid).hyphenated().to_string())
}

/// Return the filesystem type
fn kind(&self) -> Kind {
super::Kind::Btrfs
}

/// Return the volume label as a string
fn label(&self) -> Result<String, Error> {
pub fn label(&self) -> Result<String, Error> {
Ok(std::str::from_utf8(&self.label)?.trim_end_matches('\0').to_owned())
}
}

#[cfg(test)]
mod tests {
use std::fs;

use crate::{btrfs::from_reader, Superblock};

#[test_log::test]
fn test_basic() {
let mut fi = fs::File::open("tests/btrfs.img.zst").expect("cannot open ext4 img");
let mut stream = zstd::stream::Decoder::new(&mut fi).expect("Unable to decode stream");
let sb = from_reader(&mut stream).expect("Cannot parse superblock");
assert_eq!(sb.uuid().unwrap(), "829d6a03-96a5-4749-9ea2-dbb6e59368b2");
assert_eq!(sb.label().unwrap(), "blsforme testing");
}
}
62 changes: 13 additions & 49 deletions crates/superblock/src/ext4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
//! The superblock contains critical metadata about the filesystem including UUID, volume label,
//! and various configuration parameters.
use crate::{Error, Kind, Superblock};
use log;
use std::io::{self, Read};
use crate::{Detection, Error};
use uuid::Uuid;
use zerocopy::*;

Expand Down Expand Up @@ -195,62 +193,28 @@ pub const MAGIC: U16<LittleEndian> = U16::new(0xEF53);
/// Start position of superblock in filesystem
pub const START_POSITION: u64 = 1024;

/// Attempt to decode the EXT4 superblock from the given read stream.
///
/// # Arguments
/// * `reader` - Any type that implements Read to read the superblock data from
///
/// # Returns
/// * `Ok(Ext4)` - Successfully parsed superblock
/// * `Err(Error)` - Failed to parse superblock or invalid magic number
pub fn from_reader<R: Read>(reader: &mut R) -> Result<Ext4, Error> {
// Drop unwanted bytes (Seek not possible with zstd streamed inputs)
io::copy(&mut reader.by_ref().take(START_POSITION), &mut io::sink())?;
impl Detection for Ext4 {
type Magic = U16<LittleEndian>;

let data = Ext4::read_from_io(reader).map_err(|_| Error::InvalidSuperblock)?;
const OFFSET: u64 = START_POSITION;

if data.magic != MAGIC {
Err(Error::InvalidMagic)
} else {
log::trace!(
"valid magic field: UUID={} [volume label: \"{}\"]",
data.uuid()?,
data.label().unwrap_or_else(|_| "[invalid utf8]".into())
);
Ok(data)
const MAGIC_OFFSET: u64 = START_POSITION + 0x38;

const SIZE: usize = std::mem::size_of::<Ext4>();

fn is_valid_magic(magic: &Self::Magic) -> bool {
*magic == MAGIC
}
}

impl super::Superblock for Ext4 {
impl Ext4 {
/// Return the encoded UUID for this superblock
fn uuid(&self) -> Result<String, Error> {
pub fn uuid(&self) -> Result<String, Error> {
Ok(Uuid::from_bytes(self.uuid).hyphenated().to_string())
}

/// Return the volume label as valid utf8
fn label(&self) -> Result<String, super::Error> {
pub fn label(&self) -> Result<String, super::Error> {
Ok(std::str::from_utf8(&self.volume_name)?.into())
}

fn kind(&self) -> Kind {
Kind::Ext4
}
}

#[cfg(test)]
mod tests {

use std::fs;

use crate::{ext4::from_reader, Superblock};

#[test_log::test]
fn test_basic() {
let mut fi = fs::File::open("tests/ext4.img.zst").expect("cannot open ext4 img");
let mut stream = zstd::stream::Decoder::new(&mut fi).expect("Unable to decode stream");
let sb = from_reader(&mut stream).expect("Cannot parse superblock");
let label = sb.label().expect("Cannot determine volume name");
assert_eq!(label, "blsforme testing");
assert_eq!(sb.uuid().unwrap(), "731af94c-9990-4eed-944d-5d230dbe8a0d");
}
}
72 changes: 17 additions & 55 deletions crates/superblock/src/f2fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
//! - Encryption settings
//! - Device information
use crate::{Error, Kind, Superblock};
use std::io::{self, Read};
use crate::{Detection, Error};
use uuid::Uuid;
use zerocopy::*;

Expand Down Expand Up @@ -143,76 +142,39 @@ pub struct Device {
pub total_segments: U32<LittleEndian>,
}

/// F2FS superblock magic number for validation
pub const MAGIC: U32<LittleEndian> = U32::new(0xF2F52010);
/// Starting position of superblock in bytes
pub const START_POSITION: u64 = 1024;
impl Detection for F2FS {
type Magic = U32<LittleEndian>;

/// Attempts to parse and decode an F2FS superblock from the given reader
///
/// # Arguments
///
/// * `reader` - Any type implementing Read trait to read superblock data from
///
/// # Returns
///
/// * `Ok(F2FS)` - Successfully parsed superblock
/// * `Err(Error)` - Failed to read or parse superblock
pub fn from_reader<R: Read>(reader: &mut R) -> Result<F2FS, Error> {
// Drop unwanted bytes (Seek not possible with zstd streamed inputs)
io::copy(&mut reader.by_ref().take(START_POSITION), &mut io::sink())?;
const OFFSET: u64 = START_POSITION;

// Safe zero-copy deserialization
let data = F2FS::read_from_io(reader).map_err(|_| Error::InvalidSuperblock)?;
const MAGIC_OFFSET: u64 = START_POSITION;

if data.magic != MAGIC {
return Err(Error::InvalidMagic);
}
const SIZE: usize = std::mem::size_of::<F2FS>();

log::trace!(
"valid magic field: UUID={} [volume label: \"{}\"]",
data.uuid()?,
data.label().unwrap_or_else(|_| "[invalid utf8]".into())
);
Ok(data)
fn is_valid_magic(magic: &Self::Magic) -> bool {
*magic == MAGIC
}
}

impl Superblock for F2FS {
/// F2FS superblock magic number for validation
pub const MAGIC: U32<LittleEndian> = U32::new(0xF2F52010);
/// Starting position of superblock in bytes
pub const START_POSITION: u64 = 1024;

impl F2FS {
/// Returns the filesystem UUID as a hyphenated string
fn uuid(&self) -> Result<String, Error> {
pub fn uuid(&self) -> Result<String, Error> {
Ok(Uuid::from_bytes(self.uuid).hyphenated().to_string())
}

/// Returns the volume label as a UTF-16 decoded string
///
/// Handles null termination and invalid UTF-16 sequences
fn label(&self) -> Result<String, Error> {
pub fn label(&self) -> Result<String, Error> {
// Convert the array of U16<LittleEndian> to u16
let vol: Vec<u16> = self.volume_name.iter().map(|x| x.get()).collect();
let prelim_label = String::from_utf16(&vol)?;
// Need valid grapheme step and skip (u16)\0 nul termination in fixed block size
Ok(prelim_label.trim_end_matches('\0').to_owned())
}

/// Returns the filesystem type as F2FS
fn kind(&self) -> Kind {
Kind::F2FS
}
}

#[cfg(test)]
mod tests {

use crate::{f2fs::from_reader, Superblock};
use std::fs;

#[test_log::test]
fn test_basic() {
let mut fi = fs::File::open("tests/f2fs.img.zst").expect("cannot open f2fs img");
let mut stream = zstd::stream::Decoder::new(&mut fi).expect("Unable to decode stream");
let sb = from_reader(&mut stream).expect("Cannot parse superblock");
let label = sb.label().expect("Cannot determine volume name");
assert_eq!(label, "blsforme testing");
assert_eq!(sb.uuid().unwrap(), "d2c85810-4e75-4274-bc7d-a78267af7443");
}
}
Loading

0 comments on commit 5867600

Please sign in to comment.