use crate::le::{put_u16, put_u32, put_u64, u16 as le16, u32 as le32, u64 as le64};
use crate::spec::consts::SB_MAGIC;
use crate::{corrupt, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(missing_docs)] pub struct Superblock {
pub inodes_count: u32,
pub blocks_count: u64,
pub r_blocks_count: u64,
pub free_blocks_count: u64,
pub free_inodes_count: u32,
pub first_data_block: u32,
pub log_block_size: u32,
pub log_cluster_size: u32,
pub blocks_per_group: u32,
pub clusters_per_group: u32,
pub inodes_per_group: u32,
pub mtime: u32,
pub wtime: u32,
pub mnt_count: u16,
pub max_mnt_count: u16,
pub state: u16,
pub errors: u16,
pub minor_rev_level: u16,
pub lastcheck: u32,
pub checkinterval: u32,
pub creator_os: u32,
pub rev_level: u32,
pub def_resuid: u16,
pub def_resgid: u16,
pub first_ino: u32,
pub inode_size: u16,
pub block_group_nr: u16,
pub feature_compat: u32,
pub feature_incompat: u32,
pub feature_ro_compat: u32,
pub uuid: [u8; 16],
pub volume_name: [u8; 16],
pub last_mounted: [u8; 64],
pub reserved_gdt_blocks: u16,
pub journal_uuid: [u8; 16],
pub journal_inum: u32,
pub journal_dev: u32,
pub last_orphan: u32,
pub hash_seed: [u32; 4],
pub def_hash_version: u8,
pub jnl_backup_type: u8,
pub desc_size: u16,
pub default_mount_opts: u32,
pub first_meta_bg: u32,
pub mkfs_time: u32,
pub jnl_blocks: [u32; 17],
pub min_extra_isize: u16,
pub want_extra_isize: u16,
pub flags: u32,
pub log_groups_per_flex: u8,
pub checksum_type: u8,
pub kbytes_written: u64,
pub overhead_clusters: u32,
pub lpf_ino: u32,
pub checksum_seed: u32,
pub checksum: u32,
}
impl Superblock {
pub const LEN: usize = 1024;
pub fn decode(b: &[u8]) -> Result<Superblock> {
if b.len() < Self::LEN {
return Err(corrupt("superblock", "short buffer"));
}
if le16(b, 0x38) != SB_MAGIC {
return Err(corrupt(
"superblock",
format!("bad magic {:#06x}", le16(b, 0x38)),
));
}
let mut sb = Superblock {
inodes_count: le32(b, 0x00),
blocks_count: u64::from(le32(b, 0x04)),
r_blocks_count: u64::from(le32(b, 0x08)),
free_blocks_count: u64::from(le32(b, 0x0C)),
free_inodes_count: le32(b, 0x10),
first_data_block: le32(b, 0x14),
log_block_size: le32(b, 0x18),
log_cluster_size: le32(b, 0x1C),
blocks_per_group: le32(b, 0x20),
clusters_per_group: le32(b, 0x24),
inodes_per_group: le32(b, 0x28),
mtime: le32(b, 0x2C),
wtime: le32(b, 0x30),
mnt_count: le16(b, 0x34),
max_mnt_count: le16(b, 0x36),
state: le16(b, 0x3A),
errors: le16(b, 0x3C),
minor_rev_level: le16(b, 0x3E),
lastcheck: le32(b, 0x40),
checkinterval: le32(b, 0x44),
creator_os: le32(b, 0x48),
rev_level: le32(b, 0x4C),
def_resuid: le16(b, 0x50),
def_resgid: le16(b, 0x52),
first_ino: le32(b, 0x54),
inode_size: le16(b, 0x58),
block_group_nr: le16(b, 0x5A),
feature_compat: le32(b, 0x5C),
feature_incompat: le32(b, 0x60),
feature_ro_compat: le32(b, 0x64),
uuid: b[0x68..0x78].try_into().unwrap(),
volume_name: b[0x78..0x88].try_into().unwrap(),
last_mounted: b[0x88..0xC8].try_into().unwrap(),
reserved_gdt_blocks: le16(b, 0xCE),
journal_uuid: b[0xD0..0xE0].try_into().unwrap(),
journal_inum: le32(b, 0xE0),
journal_dev: le32(b, 0xE4),
last_orphan: le32(b, 0xE8),
hash_seed: [le32(b, 0xEC), le32(b, 0xF0), le32(b, 0xF4), le32(b, 0xF8)],
def_hash_version: b[0xFC],
jnl_backup_type: b[0xFD],
desc_size: le16(b, 0xFE),
default_mount_opts: le32(b, 0x100),
first_meta_bg: le32(b, 0x104),
mkfs_time: le32(b, 0x108),
jnl_blocks: {
let mut j = [0u32; 17];
for (i, w) in j.iter_mut().enumerate() {
*w = le32(b, 0x10C + 4 * i);
}
j
},
min_extra_isize: le16(b, 0x15C),
want_extra_isize: le16(b, 0x15E),
flags: le32(b, 0x160),
log_groups_per_flex: b[0x174],
checksum_type: b[0x175],
kbytes_written: le64(b, 0x178),
overhead_clusters: le32(b, 0x248),
lpf_ino: le32(b, 0x268),
checksum_seed: le32(b, 0x270),
checksum: le32(b, 0x3FC),
};
if sb.feature_incompat & crate::spec::incompat::BIT64 != 0 {
sb.blocks_count |= u64::from(le32(b, 0x150)) << 32;
sb.r_blocks_count |= u64::from(le32(b, 0x154)) << 32;
sb.free_blocks_count |= u64::from(le32(b, 0x158)) << 32;
}
Ok(sb)
}
pub fn encode(&self, out: &mut [u8]) {
let b = &mut out[..Self::LEN];
b.fill(0);
put_u32(b, 0x00, self.inodes_count);
put_u32(b, 0x04, self.blocks_count as u32);
put_u32(b, 0x08, self.r_blocks_count as u32);
put_u32(b, 0x0C, self.free_blocks_count as u32);
put_u32(b, 0x10, self.free_inodes_count);
put_u32(b, 0x14, self.first_data_block);
put_u32(b, 0x18, self.log_block_size);
put_u32(b, 0x1C, self.log_cluster_size);
put_u32(b, 0x20, self.blocks_per_group);
put_u32(b, 0x24, self.clusters_per_group);
put_u32(b, 0x28, self.inodes_per_group);
put_u32(b, 0x2C, self.mtime);
put_u32(b, 0x30, self.wtime);
put_u16(b, 0x34, self.mnt_count);
put_u16(b, 0x36, self.max_mnt_count);
put_u16(b, 0x38, SB_MAGIC);
put_u16(b, 0x3A, self.state);
put_u16(b, 0x3C, self.errors);
put_u16(b, 0x3E, self.minor_rev_level);
put_u32(b, 0x40, self.lastcheck);
put_u32(b, 0x44, self.checkinterval);
put_u32(b, 0x48, self.creator_os);
put_u32(b, 0x4C, self.rev_level);
put_u16(b, 0x50, self.def_resuid);
put_u16(b, 0x52, self.def_resgid);
put_u32(b, 0x54, self.first_ino);
put_u16(b, 0x58, self.inode_size);
put_u16(b, 0x5A, self.block_group_nr);
put_u32(b, 0x5C, self.feature_compat);
put_u32(b, 0x60, self.feature_incompat);
put_u32(b, 0x64, self.feature_ro_compat);
b[0x68..0x78].copy_from_slice(&self.uuid);
b[0x78..0x88].copy_from_slice(&self.volume_name);
b[0x88..0xC8].copy_from_slice(&self.last_mounted);
put_u16(b, 0xCE, self.reserved_gdt_blocks);
b[0xD0..0xE0].copy_from_slice(&self.journal_uuid);
put_u32(b, 0xE0, self.journal_inum);
put_u32(b, 0xE4, self.journal_dev);
put_u32(b, 0xE8, self.last_orphan);
for (i, w) in self.hash_seed.iter().enumerate() {
put_u32(b, 0xEC + 4 * i, *w);
}
b[0xFC] = self.def_hash_version;
b[0xFD] = self.jnl_backup_type;
put_u16(b, 0xFE, self.desc_size);
put_u32(b, 0x100, self.default_mount_opts);
put_u32(b, 0x104, self.first_meta_bg);
put_u32(b, 0x108, self.mkfs_time);
for (i, w) in self.jnl_blocks.iter().enumerate() {
put_u32(b, 0x10C + 4 * i, *w);
}
if self.feature_incompat & crate::spec::incompat::BIT64 != 0 {
put_u32(b, 0x150, (self.blocks_count >> 32) as u32);
put_u32(b, 0x154, (self.r_blocks_count >> 32) as u32);
put_u32(b, 0x158, (self.free_blocks_count >> 32) as u32);
}
put_u16(b, 0x15C, self.min_extra_isize);
put_u16(b, 0x15E, self.want_extra_isize);
put_u32(b, 0x160, self.flags);
b[0x174] = self.log_groups_per_flex;
b[0x175] = self.checksum_type;
put_u64(b, 0x178, self.kbytes_written);
put_u32(b, 0x248, self.overhead_clusters);
put_u32(b, 0x268, self.lpf_ino);
put_u32(b, 0x270, self.checksum_seed);
put_u32(b, 0x3FC, self.checksum);
}
pub fn block_size(&self) -> u64 {
1024u64 << self.log_block_size
}
pub fn group_count(&self) -> u64 {
let bpg = u64::from(self.blocks_per_group);
(self.blocks_count - u64::from(self.first_data_block)).div_ceil(bpg)
}
pub fn desc_size(&self) -> usize {
if self.desc_size == 0 {
32
} else {
self.desc_size as usize
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn vector(name: &str) -> Vec<u8> {
std::fs::read(format!(
"{}/testdata/vectors/{name}",
env!("CARGO_MANIFEST_DIR")
))
.unwrap()
}
#[test]
fn roundtrip_primary_and_backup() {
for name in ["sb_primary.bin", "sb_backup_g1.bin"] {
let raw = vector(name);
let sb = Superblock::decode(&raw).unwrap();
let mut out = vec![0u8; Superblock::LEN];
sb.encode(&mut out);
assert_eq!(out, raw, "{name} does not round-trip byte-exactly");
}
}
#[test]
fn fixture_fields() {
let sb = Superblock::decode(&vector("sb_primary.bin")).unwrap();
assert_eq!(sb.block_size(), 4096);
assert_eq!(sb.blocks_count, 131072);
assert_eq!(sb.inodes_per_group, 8192);
assert_eq!(sb.group_count(), 4);
assert_eq!(sb.desc_size(), 32);
assert_eq!(sb.feature_compat, crate::spec::compat::WRITER);
assert_eq!(sb.feature_incompat, crate::spec::incompat::WRITER);
assert_eq!(sb.feature_ro_compat, crate::spec::ro_compat::WRITER);
assert_eq!(sb.first_ino, 11);
assert_eq!(sb.inode_size, 256);
assert_eq!(sb.checksum_type, 1);
assert_eq!(sb.flags & 0x3, 0x1, "signed_directory_hash");
assert_eq!(sb.def_hash_version, 1, "half_md4");
assert_eq!(sb.log_groups_per_flex, 4);
}
#[test]
fn rejects_bad_magic() {
let mut raw = vector("sb_primary.bin");
raw[0x38] = 0;
assert!(Superblock::decode(&raw).is_err());
}
}