diff --git a/crates/superblock/src/btrfs.rs b/crates/superblock/src/btrfs.rs index 9fff906..6efcd60 100644 --- a/crates/superblock/src/btrfs.rs +++ b/crates/superblock/src/btrfs.rs @@ -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::*; @@ -103,57 +101,28 @@ pub const START_POSITION: u64 = 0x10000; /// Magic number identifying a BTRFS superblock ("_BHRfS_M") pub const MAGIC: U64 = 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(reader: &mut R) -> Result { - // 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; + + 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::(); - 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 { + pub fn uuid(&self) -> Result { 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 { + pub fn label(&self) -> Result { 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"); - } -} diff --git a/crates/superblock/src/ext4.rs b/crates/superblock/src/ext4.rs index 8034e0a..569538d 100644 --- a/crates/superblock/src/ext4.rs +++ b/crates/superblock/src/ext4.rs @@ -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::*; @@ -195,62 +193,28 @@ pub const MAGIC: U16 = 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(reader: &mut R) -> Result { - // 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; - 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::(); + + 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 { + pub fn uuid(&self) -> Result { Ok(Uuid::from_bytes(self.uuid).hyphenated().to_string()) } /// Return the volume label as valid utf8 - fn label(&self) -> Result { + pub fn label(&self) -> Result { 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"); - } } diff --git a/crates/superblock/src/f2fs.rs b/crates/superblock/src/f2fs.rs index 9efce97..d78eb72 100644 --- a/crates/superblock/src/f2fs.rs +++ b/crates/superblock/src/f2fs.rs @@ -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::*; @@ -143,76 +142,39 @@ pub struct Device { pub total_segments: U32, } -/// F2FS superblock magic number for validation -pub const MAGIC: U32 = U32::new(0xF2F52010); -/// Starting position of superblock in bytes -pub const START_POSITION: u64 = 1024; +impl Detection for F2FS { + type Magic = U32; -/// 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(reader: &mut R) -> Result { - // 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::(); - 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 = 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 { + pub fn uuid(&self) -> Result { 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 { + pub fn label(&self) -> Result { // Convert the array of U16 to u16 let vol: Vec = 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"); - } } diff --git a/crates/superblock/src/lib.rs b/crates/superblock/src/lib.rs index 766fa92..5ec65f4 100644 --- a/crates/superblock/src/lib.rs +++ b/crates/superblock/src/lib.rs @@ -7,9 +7,10 @@ //! This module provides functionality to detect and read superblocks from different //! filesystem types including Btrfs, Ext4, F2FS, LUKS2, and XFS. -use std::io::{self, Read, Seek}; +use std::io::{self, BufReader, Cursor, Read, Seek}; use thiserror::Error; +use zerocopy::FromBytes; pub mod btrfs; pub mod ext4; @@ -17,43 +18,22 @@ pub mod f2fs; pub mod luks2; pub mod xfs; -/// Supported filesystem types that can be detected and read -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Kind { - /// Btrfs filesystem - Btrfs, - /// Ext4 filesystem - Ext4, - /// LUKS2 encrypted container - LUKS2, - /// F2FS (Flash-Friendly File System) - F2FS, - /// XFS filesystem - XFS, -} +/// Common interface for superblock detection +pub trait Detection: Sized + FromBytes { + /// The magic number type for this superblock + type Magic: FromBytes + PartialEq + Eq; -impl std::fmt::Display for Kind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self { - Kind::Btrfs => f.write_str("btrfs"), - Kind::Ext4 => f.write_str("ext4"), - Kind::LUKS2 => f.write_str("luks2"), - Kind::F2FS => f.write_str("f2fs"), - Kind::XFS => f.write_str("xfs"), - } - } -} + /// The offset in bytes where the superblock is located + const OFFSET: u64; -/// Common interface for reading filesystem superblocks -pub trait Superblock: std::fmt::Debug + Sync + Send { - /// Returns the filesystem type of this superblock - fn kind(&self) -> self::Kind; + /// The offset within the superblock where the magic number is located + const MAGIC_OFFSET: u64; - /// Returns the filesystem UUID if available - fn uuid(&self) -> Result; + /// The size in bytes of the superblock + const SIZE: usize; - /// Returns the volume label if available - fn label(&self) -> Result; + /// Check if the magic number is valid for this superblock type + fn is_valid_magic(magic: &Self::Magic) -> bool; } /// Errors that can occur when reading superblocks @@ -63,9 +43,9 @@ pub enum Error { #[error("unknown superblock")] UnknownSuperblock, - /// The superblock data was invalid for the expected filesystem type - #[error("decoding wrong superblock")] - InvalidSuperblock, + /// Invalid JSON + #[error("invalid json")] + InvalidJson(#[from] serde_json::Error), /// The requested feature is not implemented for this filesystem type #[error("unsupported feature")] @@ -79,58 +59,141 @@ pub enum Error { #[error("invalid utf16 in decode: {0}")] Utf16Decoding(#[from] std::string::FromUtf16Error), - /// The superblock magic number was incorrect - #[error("invalid magic in superblock")] - InvalidMagic, - /// An I/O error occurred #[error("io: {0}")] IO(#[from] io::Error), } -/// Attempts to detect and read a filesystem superblock from the given reader -/// -/// # Arguments -/// -/// * `reader` - Any type implementing Read + Seek traits -/// -/// # Returns -/// -/// Returns a boxed Superblock implementation if a known filesystem is detected, -/// otherwise returns an Error. -pub fn for_reader(reader: &mut R) -> Result, Error> { - reader.rewind()?; - - // try ext4 - if let Ok(block) = ext4::from_reader(reader) { - return Ok(Box::new(block)); +/// Attempts to detect a superblock of the given type from the reader +pub fn detect_superblock(reader: &mut R) -> Result, Error> { + let mut reader = BufReader::new(reader); + reader.seek(io::SeekFrom::Start(T::MAGIC_OFFSET))?; + let mut magic_buf = vec![0u8; std::mem::size_of::()]; + reader.read_exact(&mut magic_buf)?; + + match T::Magic::read_from_bytes(&magic_buf) { + Ok(magic) if T::is_valid_magic(&magic) => { + reader.seek(io::SeekFrom::Start(T::OFFSET))?; + let mut block_buf = vec![0u8; T::SIZE]; + reader.read_exact(&mut block_buf)?; + if let Ok(block) = FromBytes::read_from_bytes(&block_buf) { + Ok(Some(block)) + } else { + Ok(None) + } + } + _ => Ok(None), } +} - // try btrfs - reader.rewind()?; - if let Ok(block) = btrfs::from_reader(reader) { - return Ok(Box::new(block)); +/// Supported filesystem types that can be detected and read +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Kind { + /// Btrfs filesystem + Btrfs, + /// Ext4 filesystem + Ext4, + /// LUKS2 encrypted container + LUKS2, + /// F2FS (Flash-Friendly File System) + F2FS, + /// XFS filesystem + XFS, +} + +impl std::fmt::Display for Kind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + Kind::Btrfs => f.write_str("btrfs"), + Kind::Ext4 => f.write_str("ext4"), + Kind::LUKS2 => f.write_str("luks2"), + Kind::F2FS => f.write_str("f2fs"), + Kind::XFS => f.write_str("xfs"), + } } +} - // try f2fs - reader.rewind()?; - if let Ok(block) = f2fs::from_reader(reader) { - return Ok(Box::new(block)); +pub enum Superblock { + Btrfs(Box), + Ext4(Box), + F2FS(Box), + LUKS2(Box), + XFS(Box), +} + +impl Superblock { + /// Returns the filesystem type of this superblock + pub fn kind(&self) -> Kind { + match self { + Superblock::Btrfs(_) => Kind::Btrfs, + Superblock::Ext4(_) => Kind::Ext4, + Superblock::F2FS(_) => Kind::F2FS, + Superblock::LUKS2(_) => Kind::LUKS2, + Superblock::XFS(_) => Kind::XFS, + } } - // try xfs - reader.rewind()?; - if let Ok(block) = xfs::from_reader(reader) { - return Ok(Box::new(block)); + /// Returns the filesystem UUID if available + pub fn uuid(&self) -> Result { + match self { + Superblock::Btrfs(block) => block.uuid(), + Superblock::Ext4(block) => block.uuid(), + Superblock::F2FS(block) => block.uuid(), + Superblock::LUKS2(block) => block.uuid(), + Superblock::XFS(block) => block.uuid(), + } } - // try luks2 - reader.rewind()?; - if let Ok(block) = luks2::from_reader(reader) { - return Ok(Box::new(block)); + /// Returns the volume label if available + pub fn label(&self) -> Result { + match self { + Superblock::Btrfs(block) => block.label(), + Superblock::Ext4(block) => block.label(), + Superblock::F2FS(block) => block.label(), + Superblock::LUKS2(block) => block.label(), + Superblock::XFS(block) => block.label(), + } } +} + +impl Superblock { + /// Attempt to detect and read a filesystem superblock from raw bytes + /// + /// This is more efficient than using a reader as it avoids multiple seeks + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = Cursor::new(bytes); - Err(Error::UnknownSuperblock) + // Try each filesystem type in order of likelihood + if let Some(sb) = detect_superblock::(&mut cursor)? { + return Ok(Self::Ext4(Box::new(sb))); + } + if let Some(sb) = detect_superblock::(&mut cursor)? { + return Ok(Self::Btrfs(Box::new(sb))); + } + if let Some(sb) = detect_superblock::(&mut cursor)? { + return Ok(Self::F2FS(Box::new(sb))); + } + if let Some(sb) = detect_superblock::(&mut cursor)? { + return Ok(Self::XFS(Box::new(sb))); + } + if let Some(sb) = detect_superblock::(&mut cursor)? { + return Ok(Self::LUKS2(Box::new(sb))); + } + + Err(Error::UnknownSuperblock) + } + + /// Attempt to detect and read a filesystem superblock from a reader + /// + /// Note: This will read the minimum necessary bytes to detect the superblock, + /// which is more efficient than reading the entire device. + pub fn from_reader(reader: &mut R) -> Result { + let mut bytes = Vec::with_capacity(128 * 1024); // 128KB should cover all superblock offsets + reader.rewind()?; + reader.read_to_end(&mut bytes)?; + + Self::from_bytes(&bytes) + } } #[cfg(test)] @@ -142,22 +205,37 @@ mod tests { use crate::Kind; - use super::for_reader; + use super::Superblock; #[test_log::test] fn test_determination() { let tests = vec![ - ("btrfs", Kind::Btrfs), - ("ext4", Kind::Ext4), - ("f2fs", Kind::F2FS), - ("luks+ext4", Kind::LUKS2), - ("xfs", Kind::XFS), + ( + "btrfs", + Kind::Btrfs, + "blsforme testing", + "829d6a03-96a5-4749-9ea2-dbb6e59368b2", + ), + ( + "ext4", + Kind::Ext4, + "blsforme testing", + "731af94c-9990-4eed-944d-5d230dbe8a0d", + ), + ( + "f2fs", + Kind::F2FS, + "blsforme testing", + "d2c85810-4e75-4274-bc7d-a78267af7443", + ), + ("luks+ext4", Kind::LUKS2, "", "be373cae-2bd1-4ad5-953f-3463b2e53e59"), + ("xfs", Kind::XFS, "BLSFORME", "45e8a3bf-8114-400f-95b0-380d0fb7d42d"), ]; // Pre-allocate a buffer for determination tests - let mut memory: Vec = Vec::with_capacity(6 * 1024 * 1024); + let mut memory: Vec = Vec::with_capacity(512 * 1024); - for (fsname, _kind) in tests.into_iter() { + for (fsname, kind, label, uuid) in tests.into_iter() { // Swings and roundabouts: Unpack ztd image in memory to get the Seekable trait we need // While each Superblock API is non-seekable, we enforce superblock::for_reader to be seekable // to make sure we pre-read a blob and pass it in for rewind/speed. @@ -167,12 +245,25 @@ mod tests { let mut stream = zstd::stream::Decoder::new(&mut fi).expect("Unable to decode stream"); stream .read_to_end(&mut memory) - .expect("Could not unpack ext4 filesystem in memory"); + .expect("Could not unpack filesystem in memory"); let mut cursor = Cursor::new(&mut memory); - let block = for_reader(&mut cursor).expect("Failed to find right block implementation"); + let block = Superblock::from_reader(&mut cursor).expect("Failed to find right block implementation"); eprintln!("{fsname}.img.zstd: superblock matched to {}", block.kind()); - assert!(matches!(block.kind(), _kind)); + assert_eq!(block.kind(), kind); + assert_eq!(block.label().unwrap(), label); + assert_eq!(block.uuid().unwrap(), uuid); + + // Is it possible to get the JSON config out of LUKS2? + if let Superblock::LUKS2(block) = block { + let config = block.read_config(&mut cursor).expect("Cannot read LUKS2 config"); + eprintln!("{}", serde_json::to_string_pretty(&config).unwrap()); + assert!(config.config.json_size > 0); + assert!(config.config.keyslots_size > 0); + + let keyslot = config.keyslots.get(&0).unwrap(); + assert_eq!(keyslot.area.encryption, "aes-xts-plain64"); + } } } } diff --git a/crates/superblock/src/luks2.rs b/crates/superblock/src/luks2.rs index 250592a..528bcdf 100644 --- a/crates/superblock/src/luks2.rs +++ b/crates/superblock/src/luks2.rs @@ -16,29 +16,3 @@ mod superblock; pub use config::*; pub use superblock::*; - -#[cfg(test)] -mod tests { - - use std::fs; - - use crate::{luks2, Superblock}; - - #[test_log::test] - fn test_basic() { - let mut fi = fs::File::open("tests/luks+ext4.img.zst").expect("cannot open luks2 img"); - let mut stream = zstd::stream::Decoder::new(&mut fi).expect("Unable to decode stream"); - let sb = luks2::from_reader(&mut stream).expect("Cannot parse superblock"); - assert_eq!(sb.uuid().unwrap(), "be373cae-2bd1-4ad5-953f-3463b2e53e59"); - assert_eq!(sb.version.get(), 2); - // Try reading the JSON config - let config = sb.read_config(&mut stream).expect("Cannot read LUKS2 config"); - // pretty print as json - eprintln!("{}", serde_json::to_string_pretty(&config).unwrap()); - assert!(config.config.json_size > 0); - assert!(config.config.keyslots_size > 0); - - let keyslot = config.keyslots.get(&0).unwrap(); - assert_eq!(keyslot.area.encryption, "aes-xts-plain64"); - } -} diff --git a/crates/superblock/src/luks2/superblock.rs b/crates/superblock/src/luks2/superblock.rs index 0b10093..caaff05 100644 --- a/crates/superblock/src/luks2/superblock.rs +++ b/crates/superblock/src/luks2/superblock.rs @@ -21,10 +21,12 @@ //! - JSON metadata area containing encryption parameters //! -use std::{io::Read, ops::Sub}; +use std::{ + io::{Read, Seek}, + ops::Sub, +}; -use crate::{Error, Kind, Superblock}; -use log; +use crate::{Detection, Error}; use zerocopy::*; use super::Luks2Config; @@ -79,53 +81,44 @@ pub struct Luks2 { } /// Magic number constants for LUKS2 format identification -pub struct Magic; +pub struct MagicMatch; -impl Magic { +impl MagicMatch { /// Standard LUKS2 magic number pub const LUKS2: [u8; MAGIC_LEN] = [b'L', b'U', b'K', b'S', 0xba, 0xbe]; /// Alternative LUKS2 magic number (reversed) pub const SKUL2: [u8; MAGIC_LEN] = [b'S', b'K', b'U', b'L', 0xba, 0xbe]; } -/// Attempt to decode the LUKS2 superblock from the given read stream -pub fn from_reader(reader: &mut R) -> Result { - let data = Luks2::read_from_io(reader).map_err(|_| Error::InvalidSuperblock)?; - - match data.magic { - Magic::LUKS2 | Magic::SKUL2 => { - log::trace!( - "valid magic field: UUID={} [volume label: \"{}\"]", - data.uuid()?, - data.label().unwrap_or_else(|_| "[invalid utf8]".into()) - ); - Ok(data) - } - _ => Err(Error::InvalidMagic), - } -} +impl Detection for Luks2 { + type Magic = [u8; MAGIC_LEN]; + + const OFFSET: u64 = 0; + + const MAGIC_OFFSET: u64 = 0; + + const SIZE: usize = std::mem::size_of::(); -impl Superblock for Luks2 { - fn kind(&self) -> Kind { - Kind::LUKS2 + fn is_valid_magic(magic: &Self::Magic) -> bool { + *magic == MagicMatch::LUKS2 || *magic == MagicMatch::SKUL2 } +} +impl Luks2 { /// Get the UUID of the LUKS2 volume /// /// Note: LUKS2 stores string UUID rather than 128-bit sequence - fn uuid(&self) -> Result { + pub fn uuid(&self) -> Result { Ok(std::str::from_utf8(&self.uuid)?.trim_end_matches('\0').to_owned()) } /// Get the label of the LUKS2 volume /// /// Note: Label is often empty, set in config instead - fn label(&self) -> Result { + pub fn label(&self) -> Result { Ok(std::str::from_utf8(&self.label)?.trim_end_matches('\0').to_owned()) } -} -impl Luks2 { /// Read and parse the JSON configuration areas from the LUKS2 header /// /// # Arguments @@ -135,18 +128,15 @@ impl Luks2 { /// # Returns /// /// Returns parsed Luks2Config on success, Error on failure - pub fn read_config(&self, reader: &mut R) -> Result { + pub fn read_config(&self, reader: &mut R) -> Result { let mut json_data = vec![0u8; self.hdr_size.get().sub(4096) as usize]; + // Skip the header and read the JSON data + reader.seek(std::io::SeekFrom::Start(std::mem::size_of::() as u64))?; reader.read_exact(&mut json_data)?; // clip the json_data at the first nul byte let raw_input = std::str::from_utf8(&json_data)?.trim_end_matches('\0'); - match serde_json::from_str(raw_input) { - Ok(config) => Ok(config), - Err(e) => { - eprintln!("Error: {:?}", e); - Err(Error::InvalidSuperblock) - } - } + let config: Luks2Config = serde_json::from_str(raw_input)?; + Ok(config) } } diff --git a/crates/superblock/src/xfs.rs b/crates/superblock/src/xfs.rs index 1a8be50..4465aba 100644 --- a/crates/superblock/src/xfs.rs +++ b/crates/superblock/src/xfs.rs @@ -12,8 +12,7 @@ //! - Quota tracking data //! - Log and realtime extent details -use crate::{Error, Kind, Superblock}; -use std::io::Read; +use crate::Detection; use uuid::Uuid; use zerocopy::*; @@ -165,60 +164,28 @@ pub struct XFS { /// XFS superblock magic number ('XFSB' in ASCII) pub const MAGIC: U32 = U32::new(0x58465342); -/// Attempts to read and decode an XFS superblock from the given reader -/// -/// # Arguments -/// * `reader` - Any type implementing Read trait to read superblock data from -/// -/// # Returns -/// * `Ok(XFS)` - Successfully parsed superblock -/// * `Err(Error)` - Failed to read/parse superblock or invalid magic number -pub fn from_reader(reader: &mut R) -> Result { - let data = XFS::read_from_io(reader).map_err(|_| Error::InvalidSuperblock)?; - - if data.magicnum != 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) - } -} - -impl Superblock for XFS { - /// Returns the filesystem type - fn kind(&self) -> Kind { - Kind::XFS - } - +impl XFS { /// Returns the filesystem UUID as a properly formatted string - fn uuid(&self) -> Result { + pub fn uuid(&self) -> Result { Ok(Uuid::from_bytes(self.uuid).hyphenated().to_string()) } /// Returns the volume label as a UTF-8 string, trimming any null termination - fn label(&self) -> Result { + pub fn label(&self) -> Result { Ok(std::str::from_utf8(&self.fname)?.trim_end_matches('\0').to_owned()) } } -#[cfg(test)] -mod tests { - - use crate::{xfs::from_reader, Superblock}; - use std::fs; - - #[test_log::test] - fn test_basic() { - let mut fi = fs::File::open("tests/xfs.img.zst").expect("cannot open xfs 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"); - assert_eq!(sb.uuid().unwrap(), "45e8a3bf-8114-400f-95b0-380d0fb7d42d"); - assert_eq!(sb.versionnum, 46245); +impl Detection for XFS { + type Magic = U32; + + const OFFSET: u64 = 0x0; + + const MAGIC_OFFSET: u64 = 0x0; + + const SIZE: usize = std::mem::size_of::(); + + fn is_valid_magic(magic: &Self::Magic) -> bool { + *magic == MAGIC } }