use crate::Result;
use crate::block::BlockDevice;
pub const F2FS_MAGIC: u32 = 0xF2F5_2010;
pub const SB_OFFSET_PRIMARY: u64 = 1024;
pub const SB_OFFSET_BACKUP: u64 = 1024 + 0x1000;
pub const F2FS_ROOT_INO_DEFAULT: u32 = 3;
#[derive(Debug, Clone)]
pub struct Superblock {
pub magic: u32,
pub major_ver: u16,
pub minor_ver: u16,
pub log_sectorsize: u32,
pub log_blocksize: u32,
pub log_blocks_per_seg: u32,
pub segs_per_sec: u32,
pub secs_per_zone: u32,
pub block_count: u64,
pub segment_count: u32,
pub segment_count_ckpt: u32,
pub segment_count_sit: u32,
pub segment_count_nat: u32,
pub segment_count_ssa: u32,
pub segment_count_main: u32,
pub segment0_blkaddr: u32,
pub cp_blkaddr: u32,
pub sit_blkaddr: u32,
pub nat_blkaddr: u32,
pub ssa_blkaddr: u32,
pub main_blkaddr: u32,
pub root_ino: u32,
pub node_ino: u32,
pub meta_ino: u32,
pub cp_payload: u32,
pub volume_name: String,
}
impl Superblock {
pub fn decode(buf: &[u8]) -> Option<Self> {
if buf.len() < 0x400 {
return None;
}
let r32 = |o: usize| u32::from_le_bytes(buf[o..o + 4].try_into().ok().unwrap());
let r16 = |o: usize| u16::from_le_bytes(buf[o..o + 2].try_into().ok().unwrap());
let r64 = |o: usize| u64::from_le_bytes(buf[o..o + 8].try_into().ok().unwrap());
let magic = r32(0x00);
if magic != F2FS_MAGIC {
return None;
}
let major_ver = r16(0x04);
let minor_ver = r16(0x06);
let log_sectorsize = r32(0x08);
let log_blocksize = r32(0x10);
let log_blocks_per_seg = r32(0x14);
let segs_per_sec = r32(0x18);
let secs_per_zone = r32(0x1C);
let block_count = r64(0x24);
let _section_count = r32(0x2C);
let segment_count = r32(0x30);
let segment_count_ckpt = r32(0x34);
let segment_count_sit = r32(0x38);
let segment_count_nat = r32(0x3C);
let segment_count_ssa = r32(0x40);
let segment_count_main = r32(0x44);
let segment0_blkaddr = r32(0x48);
let cp_blkaddr = r32(0x4C);
let sit_blkaddr = r32(0x50);
let nat_blkaddr = r32(0x54);
let ssa_blkaddr = r32(0x58);
let main_blkaddr = r32(0x5C);
let root_ino = r32(0x60);
let node_ino = r32(0x64);
let meta_ino = r32(0x68);
let name_off = 0x7C;
let volume_name = if buf.len() >= name_off + 64 {
utf16_lossy_until_nul(&buf[name_off..name_off + 64])
} else {
String::new()
};
let cp_payload = if buf.len() >= 0x400 { r32(0x3F8) } else { 0 };
Some(Self {
magic,
major_ver,
minor_ver,
log_sectorsize,
log_blocksize,
log_blocks_per_seg,
segs_per_sec,
secs_per_zone,
block_count,
segment_count,
segment_count_ckpt,
segment_count_sit,
segment_count_nat,
segment_count_ssa,
segment_count_main,
segment0_blkaddr,
cp_blkaddr,
sit_blkaddr,
nat_blkaddr,
ssa_blkaddr,
main_blkaddr,
root_ino,
node_ino,
meta_ino,
cp_payload,
volume_name,
})
}
#[inline]
pub fn block_size(&self) -> u32 {
1u32 << self.log_blocksize
}
#[inline]
pub fn blocks_per_seg(&self) -> u32 {
1u32 << self.log_blocks_per_seg
}
}
fn utf16_lossy_until_nul(bytes: &[u8]) -> String {
let mut units: Vec<u16> = Vec::with_capacity(bytes.len() / 2);
for chunk in bytes.chunks_exact(2) {
let unit = u16::from_le_bytes([chunk[0], chunk[1]]);
if unit == 0 {
break;
}
units.push(unit);
}
String::from_utf16_lossy(&units)
}
pub fn load(dev: &mut dyn BlockDevice) -> Result<Superblock> {
if dev.total_size() < SB_OFFSET_BACKUP + 0x400 {
return Err(crate::Error::InvalidImage(
"f2fs: device too small to hold a superblock".into(),
));
}
let mut buf = vec![0u8; 0x400];
dev.read_at(SB_OFFSET_PRIMARY, &mut buf)?;
if let Some(sb) = Superblock::decode(&buf) {
return Ok(sb);
}
dev.read_at(SB_OFFSET_BACKUP, &mut buf)?;
if let Some(sb) = Superblock::decode(&buf) {
return Ok(sb);
}
Err(crate::Error::InvalidImage(
"f2fs: superblock magic not found in either primary or backup slot".into(),
))
}