use crate::Result;
use crate::block::BlockDevice;
use super::constants::{
F2FS_BLK_CSUM_OFFSET, F2FS_BLKSIZE, NAT_ENTRY_PER_BLOCK, NAT_ENTRY_SIZE, S_IFDIR,
};
use super::superblock::{F2FS_MAGIC, F2FS_ROOT_INO_DEFAULT, SB_OFFSET_BACKUP, SB_OFFSET_PRIMARY};
#[derive(Debug, Clone)]
pub struct FormatOpts {
pub uuid: [u8; 16],
pub volume_label: String,
pub log_blocks_per_seg: u32,
pub segs_per_sec: u32,
pub secs_per_zone: u32,
pub root_mode: u16,
pub root_uid: u32,
pub root_gid: u32,
pub mtime: u32,
}
impl Default for FormatOpts {
fn default() -> Self {
Self {
uuid: [0; 16],
volume_label: String::new(),
log_blocks_per_seg: 9,
segs_per_sec: 1,
secs_per_zone: 1,
root_mode: 0o755,
root_uid: 0,
root_gid: 0,
mtime: 0,
}
}
}
impl FormatOpts {
pub fn apply_options(
&mut self,
map: &mut crate::format_opts::OptionMap,
) -> crate::Result<()> {
if let Some(s) = map.take_str("volume_label") {
self.volume_label = s;
}
if let Some(n) = map.take_u32("log_blocks_per_seg")? {
self.log_blocks_per_seg = n;
}
if let Some(n) = map.take_u32("segs_per_sec")? {
self.segs_per_sec = n;
}
if let Some(n) = map.take_u32("secs_per_zone")? {
self.secs_per_zone = n;
}
if let Some(n) = map.take_u16("root_mode")? {
self.root_mode = n;
}
if let Some(n) = map.take_u32("root_uid")? {
self.root_uid = n;
}
if let Some(n) = map.take_u32("root_gid")? {
self.root_gid = n;
}
if let Some(t) = map.take_u32("mtime")? {
self.mtime = t;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct Geometry {
pub total_blocks: u64,
pub blocks_per_seg: u32,
pub log_blocks_per_seg: u32,
pub segs_per_sec: u32,
pub secs_per_zone: 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 cp_blkaddr: u32,
pub sit_blkaddr: u32,
pub nat_blkaddr: u32,
pub ssa_blkaddr: u32,
pub main_blkaddr: u32,
}
impl Geometry {
pub fn segment_count(&self) -> u32 {
self.segment_count_ckpt
+ self.segment_count_sit
+ self.segment_count_nat
+ self.segment_count_ssa
+ self.segment_count_main
}
pub fn cp_pack_blkaddrs(&self) -> [u32; 2] {
[self.cp_blkaddr, self.cp_blkaddr + self.blocks_per_seg]
}
pub fn max_nat_entries(&self) -> u32 {
let pages_per_pack = (self.segment_count_nat * self.blocks_per_seg) / 2;
pages_per_pack * NAT_ENTRY_PER_BLOCK as u32
}
}
pub fn plan_geometry(total_blocks: u64, opts: &FormatOpts) -> Result<Geometry> {
if total_blocks < 64 {
return Err(crate::Error::InvalidArgument(format!(
"f2fs: device too small ({total_blocks} blocks; need ≥ 64)"
)));
}
if !(1..=9).contains(&opts.log_blocks_per_seg) {
return Err(crate::Error::InvalidArgument(format!(
"f2fs: log_blocks_per_seg must be in 1..=9, got {}",
opts.log_blocks_per_seg
)));
}
let blocks_per_seg: u32 = 1u32 << opts.log_blocks_per_seg;
let sb_blocks = 2u32;
let segment_count_ckpt = 2u32;
let segment_count_sit = 2u32;
let segment_count_nat = 2u32;
let segment_count_ssa = 1u32;
let meta_blocks = sb_blocks
+ (segment_count_ckpt + segment_count_sit + segment_count_nat + segment_count_ssa)
* blocks_per_seg;
if (meta_blocks as u64) >= total_blocks {
return Err(crate::Error::InvalidArgument(format!(
"f2fs: device has {total_blocks} blocks, need > {meta_blocks} for metadata"
)));
}
let main_blocks = total_blocks as u32 - meta_blocks;
let segment_count_main = main_blocks / blocks_per_seg;
if segment_count_main == 0 {
return Err(crate::Error::InvalidArgument(
"f2fs: not enough room for any main-area segment".into(),
));
}
let cp_blkaddr = sb_blocks;
let sit_blkaddr = cp_blkaddr + segment_count_ckpt * blocks_per_seg;
let nat_blkaddr = sit_blkaddr + segment_count_sit * blocks_per_seg;
let ssa_blkaddr = nat_blkaddr + segment_count_nat * blocks_per_seg;
let main_blkaddr = ssa_blkaddr + segment_count_ssa * blocks_per_seg;
Ok(Geometry {
total_blocks,
blocks_per_seg,
log_blocks_per_seg: opts.log_blocks_per_seg,
segs_per_sec: opts.segs_per_sec.max(1),
secs_per_zone: opts.secs_per_zone.max(1),
segment_count_ckpt,
segment_count_sit,
segment_count_nat,
segment_count_ssa,
segment_count_main,
cp_blkaddr,
sit_blkaddr,
nat_blkaddr,
ssa_blkaddr,
main_blkaddr,
})
}
pub(crate) fn write_superblocks(
dev: &mut dyn BlockDevice,
geom: &Geometry,
opts: &FormatOpts,
) -> Result<()> {
let mut buf = vec![0u8; 0x400];
buf[0x00..0x04].copy_from_slice(&F2FS_MAGIC.to_le_bytes());
buf[0x04..0x06].copy_from_slice(&1u16.to_le_bytes()); buf[0x06..0x08].copy_from_slice(&15u16.to_le_bytes()); buf[0x08..0x0C].copy_from_slice(&9u32.to_le_bytes()); buf[0x0C..0x10].copy_from_slice(&3u32.to_le_bytes()); buf[0x10..0x14].copy_from_slice(&12u32.to_le_bytes()); buf[0x14..0x18].copy_from_slice(&geom.log_blocks_per_seg.to_le_bytes());
buf[0x18..0x1C].copy_from_slice(&geom.segs_per_sec.to_le_bytes());
buf[0x1C..0x20].copy_from_slice(&geom.secs_per_zone.to_le_bytes());
buf[0x24..0x2C].copy_from_slice(&geom.total_blocks.to_le_bytes());
let section_count = geom.segment_count_main / geom.segs_per_sec.max(1);
buf[0x2C..0x30].copy_from_slice(§ion_count.to_le_bytes());
buf[0x30..0x34].copy_from_slice(&geom.segment_count().to_le_bytes());
buf[0x34..0x38].copy_from_slice(&geom.segment_count_ckpt.to_le_bytes());
buf[0x38..0x3C].copy_from_slice(&geom.segment_count_sit.to_le_bytes());
buf[0x3C..0x40].copy_from_slice(&geom.segment_count_nat.to_le_bytes());
buf[0x40..0x44].copy_from_slice(&geom.segment_count_ssa.to_le_bytes());
buf[0x44..0x48].copy_from_slice(&geom.segment_count_main.to_le_bytes());
buf[0x48..0x4C].copy_from_slice(&geom.cp_blkaddr.to_le_bytes()); buf[0x4C..0x50].copy_from_slice(&geom.cp_blkaddr.to_le_bytes());
buf[0x50..0x54].copy_from_slice(&geom.sit_blkaddr.to_le_bytes());
buf[0x54..0x58].copy_from_slice(&geom.nat_blkaddr.to_le_bytes());
buf[0x58..0x5C].copy_from_slice(&geom.ssa_blkaddr.to_le_bytes());
buf[0x5C..0x60].copy_from_slice(&geom.main_blkaddr.to_le_bytes());
buf[0x60..0x64].copy_from_slice(&F2FS_ROOT_INO_DEFAULT.to_le_bytes());
buf[0x64..0x68].copy_from_slice(&1u32.to_le_bytes()); buf[0x68..0x6C].copy_from_slice(&2u32.to_le_bytes());
buf[0x6C..0x7C].copy_from_slice(&opts.uuid);
let mut units = opts
.volume_label
.encode_utf16()
.take(511)
.collect::<Vec<u16>>();
units.push(0);
for (i, c) in units.iter().enumerate() {
let o = 0x7C + i * 2;
if o + 2 > buf.len() {
break;
}
buf[o..o + 2].copy_from_slice(&c.to_le_bytes());
}
buf[0x3F8..0x3FC].copy_from_slice(&0u32.to_le_bytes());
dev.write_at(SB_OFFSET_PRIMARY, &buf)?;
dev.write_at(SB_OFFSET_BACKUP, &buf)?;
Ok(())
}
pub(crate) fn wipe_metadata_region(dev: &mut dyn BlockDevice, geom: &Geometry) -> Result<()> {
let bs = F2FS_BLKSIZE as u64;
let start = (geom.cp_blkaddr as u64) * bs;
let end = (geom.main_blkaddr as u64) * bs;
dev.zero_range(start, end - start)?;
Ok(())
}
pub(crate) fn encode_nat_page(entries: &[(u8, u32, u32)]) -> Vec<u8> {
let mut page = vec![0u8; F2FS_BLKSIZE];
for (i, (version, ino, block_addr)) in entries.iter().enumerate() {
let o = i * NAT_ENTRY_SIZE;
if o + NAT_ENTRY_SIZE > F2FS_BLKSIZE {
break;
}
page[o] = *version;
page[o + 1..o + 5].copy_from_slice(&ino.to_le_bytes());
page[o + 5..o + 9].copy_from_slice(&block_addr.to_le_bytes());
}
page
}
pub(crate) fn encode_ssa_page() -> Vec<u8> {
vec![0u8; F2FS_BLKSIZE]
}
#[allow(dead_code)]
pub(crate) fn encode_root_inode_block(opts: &FormatOpts) -> Vec<u8> {
use super::constants::{ADDRS_PER_INODE, F2FS_INLINE_DENTRY, NIDS_PER_INODE};
use super::inode::I_ADDR_OFFSET;
let mut buf = vec![0u8; F2FS_BLKSIZE];
let mode = S_IFDIR | (opts.root_mode & 0x0FFF);
buf[0x00..0x02].copy_from_slice(&mode.to_le_bytes());
buf[0x03] = F2FS_INLINE_DENTRY;
buf[0x04..0x08].copy_from_slice(&opts.root_uid.to_le_bytes());
buf[0x08..0x0C].copy_from_slice(&opts.root_gid.to_le_bytes());
buf[0x0C..0x10].copy_from_slice(&2u32.to_le_bytes()); buf[0x10..0x18].copy_from_slice(&(F2FS_BLKSIZE as u64).to_le_bytes());
buf[0x18..0x20].copy_from_slice(&0u64.to_le_bytes()); buf[0x20..0x28].copy_from_slice(&(opts.mtime as u64).to_le_bytes());
buf[0x28..0x30].copy_from_slice(&(opts.mtime as u64).to_le_bytes());
buf[0x30..0x38].copy_from_slice(&(opts.mtime as u64).to_le_bytes());
let _ = (ADDRS_PER_INODE, NIDS_PER_INODE, I_ADDR_OFFSET);
let crc = super::constants::f2fs_crc32(&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)]
mod tests {
use super::*;
#[test]
fn geometry_rejects_tiny_devices() {
let opts = FormatOpts {
log_blocks_per_seg: 2,
..FormatOpts::default()
};
assert!(plan_geometry(8, &opts).is_err());
}
#[test]
fn geometry_lays_out_meta_then_main() {
let opts = FormatOpts {
log_blocks_per_seg: 2,
..FormatOpts::default()
};
let g = plan_geometry(256, &opts).unwrap();
assert!(g.cp_blkaddr < g.sit_blkaddr);
assert!(g.sit_blkaddr < g.nat_blkaddr);
assert!(g.nat_blkaddr < g.ssa_blkaddr);
assert!(g.ssa_blkaddr < g.main_blkaddr);
let used_blocks = 2u32
+ (g.segment_count_ckpt
+ g.segment_count_sit
+ g.segment_count_nat
+ g.segment_count_ssa
+ g.segment_count_main)
* g.blocks_per_seg;
assert!((used_blocks as u64) <= g.total_blocks);
assert_eq!(
g.main_blkaddr,
used_blocks - g.segment_count_main * g.blocks_per_seg
);
}
}