use crate::{
be,
error::{Error, Result, SuperblockReason},
format,
};
mod off {
pub(super) const MAGICNUM: usize = 0;
pub(super) const BLOCKSIZE: usize = 4;
pub(super) const RBLOCKS: usize = 16;
pub(super) const UUID: usize = 32;
pub(super) const LOGSTART: usize = 48;
pub(super) const ROOTINO: usize = 56;
pub(super) const AGBLOCKS: usize = 84;
pub(super) const AGCOUNT: usize = 88;
pub(super) const VERSIONNUM: usize = 100;
pub(super) const SECTSIZE: usize = 102;
pub(super) const INODESIZE: usize = 104;
pub(super) const FNAME: usize = 108; pub(super) const BLOCKLOG: usize = 121 - 1; pub(super) const INOPBLOG: usize = 123;
pub(super) const AGBLKLOG: usize = 124;
pub(super) const INPROGRESS: usize = 126;
pub(super) const DIRBLKLOG: usize = 192;
pub(super) const FEATURES2: usize = 200;
pub(super) const FEATURES_INCOMPAT: usize = 216;
pub(super) const CRC: usize = 224; }
const SB_CRC_OFF: usize = off::CRC;
#[derive(Debug, Clone)]
pub(crate) struct Superblock {
pub blocksize: u32,
pub agblocks: u32,
pub agcount: u32,
pub agblklog: u8,
pub inodesize: u16,
pub inopblog: u8,
pub rootino: u64,
pub v5: bool,
pub ftype: bool,
pub nrext64: bool,
pub dirblklog: u8,
pub uuid: [u8; 16],
fname: [u8; 12],
}
impl Superblock {
pub(crate) fn parse(sector: &[u8]) -> Result<Self> {
let bad = |r| Error::BadSuperblock(r);
if be::u32_at(sector, off::MAGICNUM).ok_or(bad(SuperblockReason::BadGeometry))?
!= format::SB_MAGIC
{
return Err(bad(SuperblockReason::BadMagic));
}
let versionnum =
be::u16_at(sector, off::VERSIONNUM).ok_or(bad(SuperblockReason::BadGeometry))?;
let version = versionnum & format::SB_VERSION_NUMBITS;
let v5 = match version {
v if v == format::SB_VERSION_5 => true,
v if v == format::SB_VERSION_4 => false,
_ => return Err(bad(SuperblockReason::UnsupportedVersion)),
};
let blocksize =
be::u32_at(sector, off::BLOCKSIZE).ok_or(bad(SuperblockReason::BadGeometry))?;
let blocklog =
be::u8_at(sector, off::BLOCKLOG).ok_or(bad(SuperblockReason::BadGeometry))?;
let inodesize =
be::u16_at(sector, off::INODESIZE).ok_or(bad(SuperblockReason::BadGeometry))?;
let inopblog =
be::u8_at(sector, off::INOPBLOG).ok_or(bad(SuperblockReason::BadGeometry))?;
let agblocks =
be::u32_at(sector, off::AGBLOCKS).ok_or(bad(SuperblockReason::BadGeometry))?;
let agcount = be::u32_at(sector, off::AGCOUNT).ok_or(bad(SuperblockReason::BadGeometry))?;
let agblklog =
be::u8_at(sector, off::AGBLKLOG).ok_or(bad(SuperblockReason::BadGeometry))?;
let rootino = be::u64_at(sector, off::ROOTINO).ok_or(bad(SuperblockReason::BadGeometry))?;
let dirblklog =
be::u8_at(sector, off::DIRBLKLOG).ok_or(bad(SuperblockReason::BadGeometry))?;
let uuid = be::uuid_at(sector, off::UUID).ok_or(bad(SuperblockReason::BadGeometry))?;
let mut fname = [0u8; 12];
fname.copy_from_slice(
sector
.get(off::FNAME..off::FNAME + 12)
.ok_or(bad(SuperblockReason::BadGeometry))?,
);
if blocklog >= 32
|| inopblog >= 32
|| agblklog >= 32
|| dirblklog >= 16
|| u64::from(blocksize) << dirblklog > 65536
{
return Err(bad(SuperblockReason::BadGeometry));
}
if !(512..=65536).contains(&blocksize)
|| !blocksize.is_power_of_two()
|| 1u32 << blocklog != blocksize
|| !(256..=2048).contains(&inodesize)
|| !inodesize.is_power_of_two()
|| agblocks == 0
|| agcount == 0
|| inopblog > 6
{
return Err(bad(SuperblockReason::BadGeometry));
}
if be::u64_at(sector, off::RBLOCKS).ok_or(bad(SuperblockReason::BadGeometry))? != 0 {
return Err(bad(SuperblockReason::RealtimeDevice));
}
if be::u64_at(sector, off::LOGSTART).ok_or(bad(SuperblockReason::BadGeometry))? == 0 {
return Err(bad(SuperblockReason::ExternalLog));
}
if be::u8_at(sector, off::INPROGRESS).ok_or(bad(SuperblockReason::BadGeometry))? != 0 {
return Err(bad(SuperblockReason::BadGeometry));
}
let (ftype, nrext64) = detect_features(sector, v5)?;
Ok(Superblock {
blocksize,
agblocks,
agcount,
agblklog,
inodesize,
inopblog,
rootino,
v5,
ftype,
nrext64,
dirblklog,
uuid,
fname,
})
}
pub(crate) fn label(&self) -> Option<&str> {
let end = self
.fname
.iter()
.position(|&b| b == 0)
.unwrap_or(self.fname.len());
let s = core::str::from_utf8(&self.fname[..end]).ok()?.trim();
if s.is_empty() {
None
} else {
Some(s)
}
}
pub(crate) fn inode_byte_offset(&self, ino: u64) -> u64 {
let agino_bits = u32::from(self.agblklog) + u32::from(self.inopblog);
let agno = ino >> agino_bits;
let agino = ino & ((1u64 << agino_bits) - 1);
let agbno = agino >> self.inopblog;
let in_block = agino & ((1u64 << self.inopblog) - 1);
let abs_block = agno * u64::from(self.agblocks) + agbno;
abs_block * u64::from(self.blocksize) + in_block * u64::from(self.inodesize)
}
pub(crate) fn fsblock_byte_offset(&self, fsbno: u64) -> u64 {
let agno = fsbno >> self.agblklog;
let agbno = fsbno & ((1u64 << self.agblklog) - 1);
let abs_block = agno * u64::from(self.agblocks) + agbno;
abs_block * u64::from(self.blocksize)
}
}
fn detect_features(sector: &[u8], v5: bool) -> Result<(bool, bool)> {
let bad = Error::BadSuperblock;
if !v5 {
let features2 = be::u32_at(sector, off::FEATURES2).unwrap_or(0);
return Ok((features2 & format::FEATURES2_FTYPE != 0, false));
}
let incompat =
be::u32_at(sector, off::FEATURES_INCOMPAT).ok_or(bad(SuperblockReason::BadGeometry))?;
if incompat & format::INCOMPAT_NEEDSREPAIR != 0 {
return Err(bad(SuperblockReason::DirtyLog));
}
if incompat & !format::INCOMPAT_SUPPORTED != 0 {
return Err(Error::UnsupportedFeature("feat_unknown_incompat"));
}
let sectsize =
usize::from(be::u16_at(sector, off::SECTSIZE).ok_or(bad(SuperblockReason::BadGeometry))?);
verify_sb_crc(sector.get(..sectsize).unwrap_or(sector))?;
Ok((
incompat & format::INCOMPAT_FTYPE != 0,
incompat & format::INCOMPAT_NREXT64 != 0,
))
}
fn verify_sb_crc(sector: &[u8]) -> Result<()> {
let stored = sector
.get(SB_CRC_OFF..SB_CRC_OFF + 4)
.map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
.ok_or(Error::BadSuperblock(SuperblockReason::BadGeometry))?;
let computed = crate::crc::checksum(sector, SB_CRC_OFF);
if !cfg!(fuzzing) && stored != computed {
return Err(Error::BadSuperblock(SuperblockReason::BadCrc));
}
Ok(())
}