use crate::Result;
pub const XFS_SB_MAGIC: u32 = 0x5846_5342;
pub const XFS_SB_VERSION_NUMBITS: u16 = 0x000f;
pub const XFS_SB_VERSION_5: u16 = 5;
pub const XFS_SB_VERSION_4: u16 = 4;
pub const XFS_MIN_BLOCKSIZE: u32 = 512;
pub const XFS_MAX_BLOCKSIZE: u32 = 65_536;
#[derive(Debug, Clone)]
pub struct Superblock {
pub magic: u32,
pub blocksize: u32,
pub dblocks: u64,
pub rblocks: u64,
pub uuid: [u8; 16],
pub rootino: u64,
pub agblocks: u32,
pub agcount: u32,
pub versionnum: u16,
pub sectsize: u16,
pub inodesize: u16,
pub inopblock: u16,
pub blocklog: u8,
pub sectlog: u8,
pub inodelog: u8,
pub inopblog: u8,
pub agblklog: u8,
pub dirblklog: u8,
pub features2: u32,
pub features_compat: u32,
pub features_ro_compat: u32,
pub features_incompat: u32,
pub features_log_incompat: u32,
}
impl Superblock {
pub fn decode(buf: &[u8]) -> Result<Self> {
if buf.len() < 264 {
return Err(crate::Error::InvalidImage(
"xfs: superblock buffer too small".into(),
));
}
let magic = u32::from_be_bytes(buf[0..4].try_into().unwrap());
if magic != XFS_SB_MAGIC {
return Err(crate::Error::InvalidImage(format!(
"xfs: bad superblock magic {magic:#010x} (expected XFSB)"
)));
}
let blocksize = u32::from_be_bytes(buf[4..8].try_into().unwrap());
if !(XFS_MIN_BLOCKSIZE..=XFS_MAX_BLOCKSIZE).contains(&blocksize)
|| !blocksize.is_power_of_two()
{
return Err(crate::Error::InvalidImage(format!(
"xfs: bad blocksize {blocksize}"
)));
}
let dblocks = u64::from_be_bytes(buf[8..16].try_into().unwrap());
let rblocks = u64::from_be_bytes(buf[16..24].try_into().unwrap());
let mut uuid = [0u8; 16];
uuid.copy_from_slice(&buf[32..48]);
let rootino = u64::from_be_bytes(buf[56..64].try_into().unwrap());
let agblocks = u32::from_be_bytes(buf[84..88].try_into().unwrap());
let agcount = u32::from_be_bytes(buf[88..92].try_into().unwrap());
let versionnum = u16::from_be_bytes(buf[100..102].try_into().unwrap());
let sectsize = u16::from_be_bytes(buf[102..104].try_into().unwrap());
let inodesize = u16::from_be_bytes(buf[104..106].try_into().unwrap());
let inopblock = u16::from_be_bytes(buf[106..108].try_into().unwrap());
let blocklog = buf[120];
let sectlog = buf[121];
let inodelog = buf[122];
let inopblog = buf[123];
let agblklog = buf[124];
let features2 = u32::from_be_bytes(buf[200..204].try_into().unwrap());
let features_compat = u32::from_be_bytes(buf[208..212].try_into().unwrap());
let features_ro_compat = u32::from_be_bytes(buf[212..216].try_into().unwrap());
let features_incompat = u32::from_be_bytes(buf[216..220].try_into().unwrap());
let features_log_incompat = u32::from_be_bytes(buf[220..224].try_into().unwrap());
let dirblklog = buf[192];
if 1u32 << blocklog != blocksize {
return Err(crate::Error::InvalidImage(format!(
"xfs: blocklog {blocklog} disagrees with blocksize {blocksize}"
)));
}
if inodesize == 0 || !inodesize.is_power_of_two() {
return Err(crate::Error::InvalidImage(format!(
"xfs: bad inodesize {inodesize}"
)));
}
if 1u16 << inodelog != inodesize {
return Err(crate::Error::InvalidImage(format!(
"xfs: inodelog {inodelog} disagrees with inodesize {inodesize}"
)));
}
if inopblock == 0 || (1u16 << inopblog) != inopblock {
return Err(crate::Error::InvalidImage(format!(
"xfs: inopblog {inopblog} disagrees with inopblock {inopblock}"
)));
}
if agcount == 0 {
return Err(crate::Error::InvalidImage("xfs: agcount is 0".into()));
}
if agblocks == 0 {
return Err(crate::Error::InvalidImage("xfs: agblocks is 0".into()));
}
let version = versionnum & XFS_SB_VERSION_NUMBITS;
if version != XFS_SB_VERSION_4 && version != XFS_SB_VERSION_5 {
return Err(crate::Error::Unsupported(format!(
"xfs: unsupported sb_versionnum {versionnum:#06x} (low nibble = {version})"
)));
}
Ok(Self {
magic,
blocksize,
dblocks,
rblocks,
uuid,
rootino,
agblocks,
agcount,
versionnum,
sectsize,
inodesize,
inopblock,
blocklog,
sectlog,
inodelog,
inopblog,
agblklog,
dirblklog,
features2,
features_compat,
features_ro_compat,
features_incompat,
features_log_incompat,
})
}
pub fn is_v5(&self) -> bool {
(self.versionnum & XFS_SB_VERSION_NUMBITS) == XFS_SB_VERSION_5
}
pub fn total_bytes(&self) -> u64 {
self.dblocks.saturating_mul(self.blocksize as u64)
}
pub fn dir_block_size(&self) -> u32 {
self.blocksize << (self.dirblklog as u32)
}
}
#[cfg(test)]
#[allow(clippy::too_many_arguments)]
pub(super) fn synth_sb_for_tests(
blocksize: u32,
dblocks: u64,
agblocks: u32,
agcount: u32,
inodesize: u16,
inopblock: u16,
rootino: u64,
version: u16,
) -> Vec<u8> {
let mut buf = vec![0u8; 264];
buf[0..4].copy_from_slice(&XFS_SB_MAGIC.to_be_bytes());
buf[4..8].copy_from_slice(&blocksize.to_be_bytes());
buf[8..16].copy_from_slice(&dblocks.to_be_bytes());
buf[56..64].copy_from_slice(&rootino.to_be_bytes());
buf[84..88].copy_from_slice(&agblocks.to_be_bytes());
buf[88..92].copy_from_slice(&agcount.to_be_bytes());
buf[100..102].copy_from_slice(&version.to_be_bytes());
buf[102..104].copy_from_slice(&(512u16).to_be_bytes());
buf[104..106].copy_from_slice(&inodesize.to_be_bytes());
buf[106..108].copy_from_slice(&inopblock.to_be_bytes());
buf[120] = blocksize.trailing_zeros() as u8;
buf[121] = 9; buf[122] = inodesize.trailing_zeros() as u8;
buf[123] = inopblock.trailing_zeros() as u8;
buf[124] = next_pow2_log_for_tests(agblocks);
buf
}
#[cfg(test)]
fn next_pow2_log_for_tests(n: u32) -> u8 {
let mut l = 0u8;
let mut x: u64 = 1;
while x < n as u64 {
x <<= 1;
l += 1;
}
l
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::too_many_arguments)]
fn synth(
blocksize: u32,
dblocks: u64,
agblocks: u32,
agcount: u32,
inodesize: u16,
inopblock: u16,
rootino: u64,
version: u16,
) -> Vec<u8> {
super::synth_sb_for_tests(
blocksize, dblocks, agblocks, agcount, inodesize, inopblock, rootino, version,
)
}
#[test]
fn decode_minimal() {
let buf = synth(4096, 8192, 2048, 4, 512, 8, 128, XFS_SB_VERSION_5);
let sb = Superblock::decode(&buf).unwrap();
assert!(sb.is_v5());
assert_eq!(sb.blocksize, 4096);
assert_eq!(sb.dblocks, 8192);
assert_eq!(sb.agblocks, 2048);
assert_eq!(sb.agcount, 4);
assert_eq!(sb.inodesize, 512);
assert_eq!(sb.inopblock, 8);
assert_eq!(sb.rootino, 128);
assert_eq!(sb.total_bytes(), 8192 * 4096);
assert_eq!(sb.blocklog, 12);
assert_eq!(sb.inodelog, 9);
assert_eq!(sb.inopblog, 3);
assert_eq!(sb.agblklog, 11);
}
#[test]
fn decode_rejects_bad_magic() {
let mut buf = synth(4096, 8192, 2048, 4, 512, 8, 128, XFS_SB_VERSION_5);
buf[0] = 0;
assert!(matches!(
Superblock::decode(&buf),
Err(crate::Error::InvalidImage(_))
));
}
#[test]
fn decode_rejects_inconsistent_blocklog() {
let mut buf = synth(4096, 8192, 2048, 4, 512, 8, 128, XFS_SB_VERSION_5);
buf[120] = 11; assert!(matches!(
Superblock::decode(&buf),
Err(crate::Error::InvalidImage(_))
));
}
#[test]
fn decode_rejects_v3() {
let buf = synth(4096, 8192, 2048, 4, 512, 8, 128, 3);
assert!(matches!(
Superblock::decode(&buf),
Err(crate::Error::Unsupported(_))
));
}
}