use crate::error::{Error, Result};
use crate::fs::Filesystem;
use crate::inode::Inode;
pub const JBD2_MAGIC_NUMBER: u32 = 0xc03b_3998;
pub const JBD2_DESCRIPTOR_BLOCK: u32 = 1;
pub const JBD2_COMMIT_BLOCK: u32 = 2;
pub const JBD2_SUPERBLOCK_V1: u32 = 3;
pub const JBD2_SUPERBLOCK_V2: u32 = 4;
pub const JBD2_REVOKE_BLOCK: u32 = 5;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct JbdIncompat: u32 {
const REVOKE = 0x0000_0001;
const BIT64 = 0x0000_0002;
const ASYNC_COMMIT = 0x0000_0004;
const CSUM_V2 = 0x0000_0008;
const CSUM_V3 = 0x0000_0010;
const FAST_COMMIT = 0x0000_0020;
}
}
#[derive(Debug, Clone)]
pub struct JournalSuperblock {
pub block_type: u32,
pub header_sequence: u32,
pub block_size: u32,
pub max_len: u32,
pub first: u32,
pub sequence: u32,
pub start: u32,
pub errno: u32,
pub feature_compat: u32,
pub feature_incompat: u32,
pub feature_ro_compat: u32,
pub uuid: [u8; 16],
pub nr_users: u32,
pub checksum_type: u8,
pub num_fc_blocks: u32,
pub checksum: u32,
}
impl JournalSuperblock {
pub fn is_clean(&self) -> bool {
self.start == 0
}
pub fn uses_csum_v3(&self) -> bool {
self.feature_incompat & JbdIncompat::CSUM_V3.bits() != 0
}
pub fn uses_csum_v2_or_v3(&self) -> bool {
self.feature_incompat & (JbdIncompat::CSUM_V2.bits() | JbdIncompat::CSUM_V3.bits()) != 0
}
pub fn uses_64bit(&self) -> bool {
self.feature_incompat & JbdIncompat::BIT64.bits() != 0
}
pub fn has_revoke(&self) -> bool {
self.feature_incompat & JbdIncompat::REVOKE.bits() != 0
}
pub fn parse(raw: &[u8]) -> Result<Self> {
if raw.len() < 0x100 {
return Err(Error::Corrupt("jbd2 superblock buffer too small"));
}
let h_magic = u32::from_be_bytes(raw[0x00..0x04].try_into().unwrap());
if h_magic != JBD2_MAGIC_NUMBER {
return Err(Error::Corrupt("jbd2 superblock magic mismatch"));
}
let block_type = u32::from_be_bytes(raw[0x04..0x08].try_into().unwrap());
if block_type != JBD2_SUPERBLOCK_V1 && block_type != JBD2_SUPERBLOCK_V2 {
return Err(Error::Corrupt("jbd2 header is not a superblock block type"));
}
let header_sequence = u32::from_be_bytes(raw[0x08..0x0C].try_into().unwrap());
let block_size = u32::from_be_bytes(raw[0x0C..0x10].try_into().unwrap());
let max_len = u32::from_be_bytes(raw[0x10..0x14].try_into().unwrap());
let first = u32::from_be_bytes(raw[0x14..0x18].try_into().unwrap());
let sequence = u32::from_be_bytes(raw[0x18..0x1C].try_into().unwrap());
let start = u32::from_be_bytes(raw[0x1C..0x20].try_into().unwrap());
let errno = u32::from_be_bytes(raw[0x20..0x24].try_into().unwrap());
let mut out = Self {
block_type,
header_sequence,
block_size,
max_len,
first,
sequence,
start,
errno,
feature_compat: 0,
feature_incompat: 0,
feature_ro_compat: 0,
uuid: [0; 16],
nr_users: 0,
checksum_type: 0,
num_fc_blocks: 0,
checksum: 0,
};
if block_type == JBD2_SUPERBLOCK_V2 {
out.feature_compat = u32::from_be_bytes(raw[0x24..0x28].try_into().unwrap());
out.feature_incompat = u32::from_be_bytes(raw[0x28..0x2C].try_into().unwrap());
out.feature_ro_compat = u32::from_be_bytes(raw[0x2C..0x30].try_into().unwrap());
out.uuid.copy_from_slice(&raw[0x30..0x40]);
out.nr_users = u32::from_be_bytes(raw[0x40..0x44].try_into().unwrap());
out.checksum_type = raw[0x50];
out.num_fc_blocks = u32::from_be_bytes(raw[0x54..0x58].try_into().unwrap());
out.checksum = u32::from_be_bytes(raw[0xFC..0x100].try_into().unwrap());
}
Ok(out)
}
}
pub fn journal_block_to_physical(
fs: &Filesystem,
journal_inode: &Inode,
journal_block: u64,
) -> Result<Option<u64>> {
crate::indirect::map_logical_any(
&journal_inode.block,
journal_inode.flags,
fs.dev.as_ref(),
fs.sb.block_size(),
journal_block,
)
}
pub fn read_superblock(fs: &Filesystem) -> Result<Option<JournalSuperblock>> {
let jino_num = fs.sb.journal_inode;
if jino_num == 0 {
return Ok(None);
}
let raw = fs.read_inode_raw(jino_num)?;
let jinode = Inode::parse(&raw)?;
let phys = journal_block_to_physical(fs, &jinode, 0)?
.ok_or(Error::Corrupt("journal block 0 unmapped (sparse journal?)"))?;
let block_size = fs.sb.block_size() as u64;
let mut buf = vec![0u8; block_size as usize];
fs.dev.read_at(phys * block_size, &mut buf)?;
Ok(Some(JournalSuperblock::parse(&buf)?))
}
#[cfg(test)]
mod tests {
use super::*;
fn make_v2_sb() -> Vec<u8> {
let mut buf = vec![0u8; 1024];
buf[0x00..0x04].copy_from_slice(&JBD2_MAGIC_NUMBER.to_be_bytes());
buf[0x04..0x08].copy_from_slice(&JBD2_SUPERBLOCK_V2.to_be_bytes());
buf[0x08..0x0C].copy_from_slice(&1u32.to_be_bytes()); buf[0x0C..0x10].copy_from_slice(&4096u32.to_be_bytes()); buf[0x10..0x14].copy_from_slice(&8192u32.to_be_bytes()); buf[0x14..0x18].copy_from_slice(&1u32.to_be_bytes()); buf[0x18..0x1C].copy_from_slice(&42u32.to_be_bytes()); buf[0x1C..0x20].copy_from_slice(&0u32.to_be_bytes()); buf[0x20..0x24].copy_from_slice(&0u32.to_be_bytes()); buf[0x24..0x28].copy_from_slice(&0u32.to_be_bytes()); buf[0x28..0x2C].copy_from_slice(
&(JbdIncompat::REVOKE.bits() | JbdIncompat::CSUM_V3.bits()).to_be_bytes(),
);
buf[0x2C..0x30].copy_from_slice(&0u32.to_be_bytes()); buf[0x30..0x40].copy_from_slice(&[0xAA; 16]);
buf[0x40..0x44].copy_from_slice(&1u32.to_be_bytes()); buf[0x50] = 4; buf[0xFC..0x100].copy_from_slice(&0xDEADBEEFu32.to_be_bytes());
buf
}
#[test]
fn parses_v2_superblock() {
let buf = make_v2_sb();
let sb = JournalSuperblock::parse(&buf).unwrap();
assert_eq!(sb.block_type, JBD2_SUPERBLOCK_V2);
assert_eq!(sb.block_size, 4096);
assert_eq!(sb.max_len, 8192);
assert_eq!(sb.first, 1);
assert_eq!(sb.sequence, 42);
assert!(sb.is_clean());
assert!(sb.has_revoke());
assert!(sb.uses_csum_v2_or_v3());
assert!(!sb.uses_64bit());
assert_eq!(sb.checksum_type, 4);
assert_eq!(sb.checksum, 0xDEADBEEF);
}
#[test]
fn rejects_bad_magic() {
let mut buf = make_v2_sb();
buf[0] = 0;
assert!(JournalSuperblock::parse(&buf).is_err());
}
#[test]
fn rejects_non_superblock_block_type() {
let mut buf = make_v2_sb();
buf[0x04..0x08].copy_from_slice(&JBD2_DESCRIPTOR_BLOCK.to_be_bytes());
assert!(JournalSuperblock::parse(&buf).is_err());
}
#[test]
fn v1_leaves_feature_fields_zero() {
let mut buf = make_v2_sb();
buf[0x04..0x08].copy_from_slice(&JBD2_SUPERBLOCK_V1.to_be_bytes());
let sb = JournalSuperblock::parse(&buf).unwrap();
assert_eq!(sb.feature_incompat, 0);
assert_eq!(sb.uuid, [0; 16]);
assert_eq!(sb.checksum, 0);
}
#[test]
fn dirty_sb_is_not_clean() {
let mut buf = make_v2_sb();
buf[0x1C..0x20].copy_from_slice(&100u32.to_be_bytes()); let sb = JournalSuperblock::parse(&buf).unwrap();
assert!(!sb.is_clean());
assert_eq!(sb.start, 100);
}
}