use crate::Result;
use crate::block::BlockDevice;
use super::constants::{CP_COMPACT_SUM_FLAG, F2FS_BLK_CSUM_OFFSET, F2FS_BLKSIZE, NAT_ENTRY_SIZE};
use super::superblock::Superblock;
#[derive(Debug, Clone)]
pub struct Checkpoint {
pub version: u64,
pub user_block_count: u64,
pub valid_block_count: u64,
pub flags: u32,
pub cp_pack_start_sum: u32,
pub cp_pack_total_block_count: u32,
pub cp_payload: u32,
pub head_blkaddr: u32,
pub nat_ver_bitmap_bytesize: u32,
pub sit_ver_bitmap_bytesize: u32,
pub cur_nat_pack: u8,
pub cur_sit_pack: u8,
pub nat_journal: Vec<NatJournalEntry>,
}
#[derive(Debug, Clone, Copy)]
pub struct NatJournalEntry {
pub nid: u32,
pub ino: u32,
pub block_addr: u32,
pub version: u8,
}
impl Checkpoint {
pub fn load(dev: &mut dyn BlockDevice, sb: &Superblock) -> Result<Self> {
let bs = sb.block_size() as u64;
let blocks_per_seg = sb.blocks_per_seg();
let cp0_blk = sb.cp_blkaddr;
let cp1_blk = sb
.cp_blkaddr
.checked_add(blocks_per_seg)
.ok_or_else(|| crate::Error::InvalidImage("f2fs: cp_blkaddr overflow".into()))?;
let mut cp0 = Self::try_load(dev, sb, cp0_blk).ok();
let mut cp1 = Self::try_load(dev, sb, cp1_blk).ok();
if let Some(c) = cp0.as_mut() {
c.cur_nat_pack = 0;
c.cur_sit_pack = 0;
}
if let Some(c) = cp1.as_mut() {
c.cur_nat_pack = 1;
c.cur_sit_pack = 1;
}
let _ = bs;
match (cp0, cp1) {
(Some(a), Some(b)) => Ok(if b.version > a.version { b } else { a }),
(Some(a), None) => Ok(a),
(None, Some(b)) => Ok(b),
(None, None) => Err(crate::Error::InvalidImage(
"f2fs: neither checkpoint pack validates".into(),
)),
}
}
fn try_load(dev: &mut dyn BlockDevice, sb: &Superblock, head_blkaddr: u32) -> Result<Self> {
let bs = sb.block_size() as u64;
let mut head = vec![0u8; F2FS_BLKSIZE];
dev.read_at(head_blkaddr as u64 * bs, &mut head)?;
let cp = decode_cp_head(&head, head_blkaddr)?;
let want = u32::from_le_bytes(
head[F2FS_BLK_CSUM_OFFSET..F2FS_BLK_CSUM_OFFSET + 4]
.try_into()
.unwrap(),
);
let got = crc32fast::hash(&head[..F2FS_BLK_CSUM_OFFSET]);
if got != want {
return Err(crate::Error::InvalidImage(format!(
"f2fs: cp@{head_blkaddr}: crc mismatch (want {want:08x}, got {got:08x})"
)));
}
let sum_block = head_blkaddr
.checked_add(cp.cp_pack_start_sum)
.ok_or_else(|| crate::Error::InvalidImage("f2fs: cp summary overflow".into()))?;
let mut sumbuf = vec![0u8; F2FS_BLKSIZE];
dev.read_at(sum_block as u64 * bs, &mut sumbuf)?;
let nat_journal = decode_nat_journal(&sumbuf);
let mut out = cp;
out.nat_journal = nat_journal;
Ok(out)
}
pub fn nat_journal_lookup(&self, nid: u32) -> Option<NatJournalEntry> {
self.nat_journal.iter().copied().find(|e| e.nid == nid)
}
}
fn decode_cp_head(buf: &[u8], head_blkaddr: u32) -> Result<Checkpoint> {
if buf.len() < F2FS_BLKSIZE {
return Err(crate::Error::InvalidImage(
"f2fs: short read on CP head".into(),
));
}
let r32 = |o: usize| u32::from_le_bytes(buf[o..o + 4].try_into().unwrap());
let r64 = |o: usize| u64::from_le_bytes(buf[o..o + 8].try_into().unwrap());
let version = r64(0x00);
let user_block_count = r64(0x08);
let valid_block_count = r64(0x10);
let _free_segment_count = r32(0x20);
let nat_ver_bitmap_bytesize = r32(0x70);
let sit_ver_bitmap_bytesize = r32(0x74);
let cp_pack_total_block_count = r32(0x78);
let ckpt_flags = r32(0x7C);
let cp_pack_start_sum = r32(0x80);
let cp_payload = r32(0x84);
Ok(Checkpoint {
version,
user_block_count,
valid_block_count,
flags: ckpt_flags,
cp_pack_start_sum,
cp_pack_total_block_count,
cp_payload,
head_blkaddr,
nat_ver_bitmap_bytesize,
sit_ver_bitmap_bytesize,
cur_nat_pack: 0,
cur_sit_pack: 0,
nat_journal: Vec::new(),
})
}
fn decode_nat_journal(buf: &[u8]) -> Vec<NatJournalEntry> {
if buf.len() < 4 {
return Vec::new();
}
let n = u16::from_le_bytes([buf[0], buf[1]]) as usize;
let stride = 16usize;
let max = (buf.len().saturating_sub(4)) / stride;
let n = n.min(max);
let mut out = Vec::with_capacity(n);
for i in 0..n {
let o = 4 + i * stride;
if o + stride > buf.len() {
break;
}
let nid = u32::from_le_bytes(buf[o..o + 4].try_into().unwrap());
let ino = u32::from_le_bytes(buf[o + 4..o + 8].try_into().unwrap());
let block_addr = u32::from_le_bytes(buf[o + 8..o + 12].try_into().unwrap());
let version = buf[o + 12];
out.push(NatJournalEntry {
nid,
ino,
block_addr,
version,
});
}
out
}
#[cfg(test)]
pub(crate) fn encode_cp_head(cp: &Checkpoint) -> Vec<u8> {
let mut buf = vec![0u8; F2FS_BLKSIZE];
buf[0x00..0x08].copy_from_slice(&cp.version.to_le_bytes());
buf[0x08..0x10].copy_from_slice(&cp.user_block_count.to_le_bytes());
buf[0x10..0x18].copy_from_slice(&cp.valid_block_count.to_le_bytes());
buf[0x70..0x74].copy_from_slice(&cp.nat_ver_bitmap_bytesize.to_le_bytes());
buf[0x74..0x78].copy_from_slice(&cp.sit_ver_bitmap_bytesize.to_le_bytes());
buf[0x78..0x7C].copy_from_slice(&cp.cp_pack_total_block_count.to_le_bytes());
buf[0x7C..0x80].copy_from_slice(&cp.flags.to_le_bytes());
buf[0x80..0x84].copy_from_slice(&cp.cp_pack_start_sum.to_le_bytes());
buf[0x84..0x88].copy_from_slice(&cp.cp_payload.to_le_bytes());
let crc = crc32fast::hash(&buf[..F2FS_BLK_CSUM_OFFSET]);
buf[F2FS_BLK_CSUM_OFFSET..F2FS_BLK_CSUM_OFFSET + 4].copy_from_slice(&crc.to_le_bytes());
buf
}
#[cfg(test)]
pub(crate) fn encode_nat_journal_block(entries: &[NatJournalEntry]) -> Vec<u8> {
let mut buf = vec![0u8; F2FS_BLKSIZE];
buf[0..2].copy_from_slice(&(entries.len() as u16).to_le_bytes());
let stride = 16usize;
for (i, e) in entries.iter().enumerate() {
let o = 4 + i * stride;
buf[o..o + 4].copy_from_slice(&e.nid.to_le_bytes());
buf[o + 4..o + 8].copy_from_slice(&e.ino.to_le_bytes());
buf[o + 8..o + 12].copy_from_slice(&e.block_addr.to_le_bytes());
buf[o + 12] = e.version;
}
buf
}
#[allow(dead_code)]
const _CP_FLAGS_REFERENCED: u32 = CP_COMPACT_SUM_FLAG;
#[allow(dead_code)]
const _NAT_SIZE_REFERENCED: usize = NAT_ENTRY_SIZE;