use std::collections::BTreeMap;
use std::io::Read;
use crate::Result;
use crate::block::BlockDevice;
use crate::fs::{DeviceKind, FileMeta, FileSource};
use super::checkpoint::Checkpoint;
use super::constants::{
ADDRS_PER_BLOCK, ADDRS_PER_INODE, F2FS_BLK_CSUM_OFFSET, F2FS_BLKSIZE, F2FS_DATA_EXIST,
F2FS_FT_BLKDEV, F2FS_FT_CHRDEV, F2FS_FT_DIR, F2FS_FT_FIFO, F2FS_FT_REG_FILE, F2FS_FT_SOCK,
F2FS_FT_SYMLINK, F2FS_INLINE_DATA, F2FS_INLINE_DENTRY, F2FS_SLOT_LEN, NID_DIRECT_1,
NID_DIRECT_2, NID_INDIRECT_1, NID_INDIRECT_2, NID_TRIPLE_INDIRECT, NIDS_PER_BLOCK,
NIDS_PER_INODE, NR_DENTRY_IN_BLOCK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFREG,
S_IFSOCK, SIZE_OF_DENTRY_BITMAP, SIZE_OF_DIR_ENTRY, SIZE_OF_RESERVED,
};
use super::dir::INLINE_DENTRY_NR;
use super::format::Geometry;
use super::inode::I_ADDR_OFFSET;
use super::superblock::Superblock;
const PUMP_BUF: usize = 64 * 1024;
pub const MAX_INLINE_DATA: usize = ADDRS_PER_INODE * 4 + NIDS_PER_INODE * 4 - 64;
#[derive(Debug, Clone)]
pub(crate) struct InodeRec {
pub nid: u32,
pub mode: u16,
pub uid: u32,
pub gid: u32,
pub links: u32,
pub size: u64,
pub blocks: u64,
pub atime: u32,
pub ctime: u32,
pub mtime: u32,
pub flags: u32,
pub inline_flags: u8,
pub i_addr: [u32; ADDRS_PER_INODE],
pub i_nid: [u32; NIDS_PER_INODE],
pub inline_payload: Vec<u8>,
pub on_disk_block: u32,
}
#[derive(Debug, Clone)]
pub(crate) struct DirectNode {
pub nid: u32,
pub on_disk_block: u32,
pub addrs: [u32; ADDRS_PER_BLOCK],
}
#[derive(Debug, Clone)]
pub(crate) struct IndirectNode {
pub nid: u32,
pub on_disk_block: u32,
pub nids: [u32; NIDS_PER_BLOCK],
}
#[derive(Debug, Clone)]
pub(crate) struct Dentry {
pub hash: u32,
pub ino: u32,
pub file_type: u8,
pub name: Vec<u8>,
}
type DirChildren = BTreeMap<u32, Vec<Dentry>>;
#[derive(Debug)]
pub struct Writer {
pub(crate) geom: Geometry,
#[allow(dead_code)]
pub(crate) sb: Superblock,
pub(crate) next_nid: u32,
pub(crate) next_node_blk: u32,
pub(crate) next_data_blk: u32,
pub(crate) node_segments: Vec<u32>,
pub(crate) data_segments: Vec<u32>,
pub(crate) next_free_seg: u32,
pub(crate) inodes: BTreeMap<u32, InodeRec>,
pub(crate) direct_nodes: BTreeMap<u32, DirectNode>,
pub(crate) indirect_nodes: BTreeMap<u32, IndirectNode>,
pub(crate) children: DirChildren,
pub(crate) spilled_dirs: BTreeMap<u32, ()>,
}
impl Writer {
pub(crate) fn new(geom: Geometry, sb: Superblock) -> Self {
let mut inodes = BTreeMap::new();
let mut children = BTreeMap::new();
let root_phys = geom.main_blkaddr;
inodes.insert(
3,
InodeRec {
nid: 3,
mode: S_IFDIR | 0o755,
uid: 0,
gid: 0,
links: 2,
size: F2FS_BLKSIZE as u64,
blocks: 0,
atime: 0,
ctime: 0,
mtime: 0,
flags: 0,
inline_flags: F2FS_INLINE_DENTRY,
i_addr: [0; ADDRS_PER_INODE],
i_nid: [0; NIDS_PER_INODE],
inline_payload: Vec::new(),
on_disk_block: root_phys,
},
);
children.insert(3, Vec::new());
let data_seg_offset = 3u32 * geom.blocks_per_seg;
Self {
geom,
sb,
next_nid: 4,
next_node_blk: root_phys + 1, next_data_blk: root_phys + data_seg_offset,
node_segments: vec![0],
data_segments: vec![3],
next_free_seg: 6,
inodes,
direct_nodes: BTreeMap::new(),
indirect_nodes: BTreeMap::new(),
children,
spilled_dirs: BTreeMap::new(),
}
}
fn alloc_nid(&mut self) -> u32 {
let n = self.next_nid;
self.next_nid += 1;
n
}
fn seg_to_blk(&self, seg: u32) -> u32 {
self.geom.main_blkaddr + seg * self.geom.blocks_per_seg
}
fn alloc_fresh_segment(&mut self) -> Result<u32> {
let s = self.next_free_seg;
if s >= self.geom.segment_count_main {
return Err(crate::Error::InvalidArgument(
"f2fs: main area exhausted (no free segments)".into(),
));
}
self.next_free_seg += 1;
Ok(s)
}
fn alloc_node_block(&mut self) -> Result<u32> {
let active_seg = *self
.node_segments
.last()
.expect("node_segments seeded in new()");
let seg_end = self.seg_to_blk(active_seg) + self.geom.blocks_per_seg;
if self.next_node_blk >= seg_end {
let fresh = self.alloc_fresh_segment()?;
self.node_segments.push(fresh);
self.next_node_blk = self.seg_to_blk(fresh);
}
let blk = self.next_node_blk;
self.next_node_blk += 1;
Ok(blk)
}
fn alloc_data_block(&mut self) -> Result<u32> {
let active_seg = *self
.data_segments
.last()
.expect("data_segments seeded in new()");
let seg_end = self.seg_to_blk(active_seg) + self.geom.blocks_per_seg;
if self.next_data_blk >= seg_end {
let fresh = self.alloc_fresh_segment()?;
self.data_segments.push(fresh);
self.next_data_blk = self.seg_to_blk(fresh);
}
let blk = self.next_data_blk;
self.next_data_blk += 1;
Ok(blk)
}
fn resolve_for_create(&self, path: &std::path::Path) -> Result<(u32, Vec<u8>, Option<u32>)> {
let s = path
.to_str()
.ok_or_else(|| crate::Error::InvalidArgument("f2fs: non-UTF-8 path".into()))?;
if s == "/" || s.is_empty() {
return Err(crate::Error::InvalidArgument(
"f2fs: can't create root".into(),
));
}
let parts: Vec<&str> = s
.trim_matches('/')
.split('/')
.filter(|p| !p.is_empty())
.collect();
if parts.is_empty() {
return Err(crate::Error::InvalidArgument(format!(
"f2fs: empty path {s:?}"
)));
}
let mut cur = 3u32;
for comp in &parts[..parts.len() - 1] {
let kids = self.children.get(&cur).ok_or_else(|| {
crate::Error::InvalidArgument(format!("f2fs: parent of {s:?} not a known dir"))
})?;
let found = kids
.iter()
.find(|d| d.name == comp.as_bytes())
.ok_or_else(|| {
crate::Error::InvalidArgument(format!("f2fs: no such dir {comp:?} in {s:?}"))
})?;
cur = found.ino;
}
let leaf = parts[parts.len() - 1].as_bytes().to_vec();
let existing = self
.children
.get(&cur)
.and_then(|kids| kids.iter().find(|d| d.name == leaf).map(|d| d.ino));
Ok((cur, leaf, existing))
}
fn stream_into_inode(
&mut self,
dev: &mut dyn BlockDevice,
nid: u32,
mut src: Box<dyn crate::fs::ReadSeek + Send>,
total: u64,
) -> Result<(u64, u64)> {
if (total as usize) <= MAX_INLINE_DATA {
let mut buf = vec![0u8; total as usize];
if !buf.is_empty() {
src.read_exact(&mut buf)?;
}
let ino = self
.inodes
.get_mut(&nid)
.ok_or_else(|| crate::Error::InvalidImage("f2fs: ghost nid".into()))?;
ino.inline_flags |= F2FS_INLINE_DATA | F2FS_DATA_EXIST;
ino.inline_payload = buf;
return Ok((total, 0));
}
let bs = F2FS_BLKSIZE as u64;
let mut pump = vec![0u8; PUMP_BUF];
let mut block_buf = vec![0u8; F2FS_BLKSIZE];
let mut pump_pos = 0usize;
let mut pump_len = 0usize;
let mut logical_idx: u64 = 0;
let mut written: u64 = 0;
let mut blocks_alloc: u64 = 0;
while written < total {
if pump_pos == pump_len {
let want = ((total - written) as usize).min(pump.len());
let n = src.read(&mut pump[..want])?;
if n == 0 {
return Err(crate::Error::Io(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"f2fs: source ended early",
)));
}
pump_len = n;
pump_pos = 0;
}
block_buf.fill(0);
let mut filled = 0usize;
let remaining = (total - written) as usize;
let block_target = F2FS_BLKSIZE.min(remaining);
while filled < block_target {
if pump_pos == pump_len {
let want = ((total - written - filled as u64) as usize).min(pump.len());
let n = src.read(&mut pump[..want])?;
if n == 0 {
return Err(crate::Error::Io(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"f2fs: source ended early",
)));
}
pump_len = n;
pump_pos = 0;
}
let take = (pump_len - pump_pos).min(block_target - filled);
block_buf[filled..filled + take].copy_from_slice(&pump[pump_pos..pump_pos + take]);
pump_pos += take;
filled += take;
}
let phys = self.alloc_data_block()?;
dev.write_at(phys as u64 * bs, &block_buf)?;
self.place_data_block(nid, logical_idx, phys)?;
logical_idx += 1;
written += filled as u64;
blocks_alloc += 1;
}
Ok((total, blocks_alloc))
}
fn place_data_block(&mut self, nid: u32, idx: u64, phys: u32) -> Result<()> {
if idx < ADDRS_PER_INODE as u64 {
let ino = self
.inodes
.get_mut(&nid)
.ok_or_else(|| crate::Error::InvalidImage("f2fs: ghost nid".into()))?;
ino.i_addr[idx as usize] = phys;
return Ok(());
}
let mut rel = idx - ADDRS_PER_INODE as u64;
for nid_slot in [NID_DIRECT_1, NID_DIRECT_2] {
let span = ADDRS_PER_BLOCK as u64;
if rel < span {
let dnode_nid = self.ensure_direct_node(nid, nid_slot)?;
let d = self
.direct_nodes
.get_mut(&dnode_nid)
.expect("just inserted");
d.addrs[rel as usize] = phys;
return Ok(());
}
rel -= span;
}
for nid_slot in [NID_INDIRECT_1, NID_INDIRECT_2] {
let span = (NIDS_PER_BLOCK as u64) * (ADDRS_PER_BLOCK as u64);
if rel < span {
let outer = (rel / ADDRS_PER_BLOCK as u64) as usize;
let inner = (rel % ADDRS_PER_BLOCK as u64) as usize;
let ind_nid = self.ensure_indirect_node(nid, nid_slot)?;
let dnode_nid = self.ensure_dnode_under_indirect(ind_nid, outer)?;
let d = self
.direct_nodes
.get_mut(&dnode_nid)
.expect("just inserted");
d.addrs[inner] = phys;
return Ok(());
}
rel -= span;
}
let triple_span = (NIDS_PER_BLOCK as u64).pow(2) * (ADDRS_PER_BLOCK as u64);
if rel < triple_span {
let outer = (rel / ((NIDS_PER_BLOCK as u64) * (ADDRS_PER_BLOCK as u64))) as usize;
let mid = ((rel / (ADDRS_PER_BLOCK as u64)) % (NIDS_PER_BLOCK as u64)) as usize;
let inner = (rel % (ADDRS_PER_BLOCK as u64)) as usize;
let top_nid = self.ensure_indirect_node(nid, NID_TRIPLE_INDIRECT)?;
let mid_nid = self.ensure_indirect_under_indirect(top_nid, outer)?;
let dnode_nid = self.ensure_dnode_under_indirect(mid_nid, mid)?;
let d = self
.direct_nodes
.get_mut(&dnode_nid)
.expect("just inserted");
d.addrs[inner] = phys;
return Ok(());
}
Err(crate::Error::Unsupported(format!(
"f2fs: file exceeds the triple-indirect addressable limit (idx={idx})"
)))
}
fn ensure_indirect_under_indirect(&mut self, parent: u32, outer: usize) -> Result<u32> {
let ind = self
.indirect_nodes
.get_mut(&parent)
.ok_or_else(|| crate::Error::InvalidImage("f2fs: ghost triple-top nid".into()))?;
if ind.nids[outer] != 0 {
return Ok(ind.nids[outer]);
}
let inid = {
let n = self.next_nid;
self.next_nid += 1;
n
};
let phys = self.alloc_node_block()?;
self.indirect_nodes.get_mut(&parent).unwrap().nids[outer] = inid;
self.indirect_nodes.insert(
inid,
IndirectNode {
nid: inid,
on_disk_block: phys,
nids: [0; NIDS_PER_BLOCK],
},
);
Ok(inid)
}
fn ensure_direct_node(&mut self, parent_nid: u32, slot: usize) -> Result<u32> {
let ino = self
.inodes
.get_mut(&parent_nid)
.ok_or_else(|| crate::Error::InvalidImage("f2fs: ghost nid".into()))?;
if ino.i_nid[slot] != 0 {
return Ok(ino.i_nid[slot]);
}
let dnid = {
let n = self.next_nid;
self.next_nid += 1;
n
};
let phys = self.alloc_node_block()?;
self.inodes.get_mut(&parent_nid).unwrap().i_nid[slot] = dnid;
self.direct_nodes.insert(
dnid,
DirectNode {
nid: dnid,
on_disk_block: phys,
addrs: [0; ADDRS_PER_BLOCK],
},
);
Ok(dnid)
}
fn ensure_indirect_node(&mut self, parent_nid: u32, slot: usize) -> Result<u32> {
let ino = self
.inodes
.get_mut(&parent_nid)
.ok_or_else(|| crate::Error::InvalidImage("f2fs: ghost nid".into()))?;
if ino.i_nid[slot] != 0 {
return Ok(ino.i_nid[slot]);
}
let inid = {
let n = self.next_nid;
self.next_nid += 1;
n
};
let phys = self.alloc_node_block()?;
self.inodes.get_mut(&parent_nid).unwrap().i_nid[slot] = inid;
self.indirect_nodes.insert(
inid,
IndirectNode {
nid: inid,
on_disk_block: phys,
nids: [0; NIDS_PER_BLOCK],
},
);
Ok(inid)
}
fn ensure_dnode_under_indirect(&mut self, parent: u32, outer: usize) -> Result<u32> {
let ind = self
.indirect_nodes
.get_mut(&parent)
.ok_or_else(|| crate::Error::InvalidImage("f2fs: ghost indirect nid".into()))?;
if ind.nids[outer] != 0 {
return Ok(ind.nids[outer]);
}
let dnid = {
let n = self.next_nid;
self.next_nid += 1;
n
};
let phys = self.alloc_node_block()?;
self.indirect_nodes.get_mut(&parent).unwrap().nids[outer] = dnid;
self.direct_nodes.insert(
dnid,
DirectNode {
nid: dnid,
on_disk_block: phys,
addrs: [0; ADDRS_PER_BLOCK],
},
);
Ok(dnid)
}
pub fn add_file(
&mut self,
dev: &mut dyn BlockDevice,
path: &std::path::Path,
src: FileSource,
meta: FileMeta,
) -> Result<u32> {
let (parent_nid, leaf, existing) = self.resolve_for_create(path)?;
if existing.is_some() {
return Err(crate::Error::InvalidArgument(format!(
"f2fs: path exists: {path:?}"
)));
}
let nid = self.alloc_nid();
let inode_blk = self.alloc_node_block()?;
let mode = S_IFREG | (meta.mode & 0x0FFF);
self.inodes.insert(
nid,
InodeRec {
nid,
mode,
uid: meta.uid,
gid: meta.gid,
links: 1,
size: 0,
blocks: 0,
atime: meta.atime,
ctime: meta.ctime,
mtime: meta.mtime,
flags: 0,
inline_flags: 0,
i_addr: [0; ADDRS_PER_INODE],
i_nid: [0; NIDS_PER_INODE],
inline_payload: Vec::new(),
on_disk_block: inode_blk,
},
);
let (reader, total) = src.open()?;
let (size, blocks) = self.stream_into_inode(dev, nid, reader, total)?;
let ino = self.inodes.get_mut(&nid).unwrap();
ino.size = size;
ino.blocks = blocks;
self.attach_to_parent(parent_nid, &leaf, nid, F2FS_FT_REG_FILE)?;
Ok(nid)
}
pub fn add_dir(
&mut self,
_dev: &mut dyn BlockDevice,
path: &std::path::Path,
meta: FileMeta,
) -> Result<u32> {
let (parent_nid, leaf, existing) = self.resolve_for_create(path)?;
if existing.is_some() {
return Err(crate::Error::InvalidArgument(format!(
"f2fs: path exists: {path:?}"
)));
}
let nid = self.alloc_nid();
let inode_blk = self.alloc_node_block()?;
let mode = S_IFDIR | (meta.mode & 0x0FFF);
self.inodes.insert(
nid,
InodeRec {
nid,
mode,
uid: meta.uid,
gid: meta.gid,
links: 2,
size: F2FS_BLKSIZE as u64,
blocks: 0,
atime: meta.atime,
ctime: meta.ctime,
mtime: meta.mtime,
flags: 0,
inline_flags: F2FS_INLINE_DENTRY,
i_addr: [0; ADDRS_PER_INODE],
i_nid: [0; NIDS_PER_INODE],
inline_payload: Vec::new(),
on_disk_block: inode_blk,
},
);
self.children.insert(nid, Vec::new());
if let Some(parent) = self.inodes.get_mut(&parent_nid) {
parent.links += 1;
}
self.attach_to_parent(parent_nid, &leaf, nid, F2FS_FT_DIR)?;
Ok(nid)
}
pub fn add_symlink(
&mut self,
_dev: &mut dyn BlockDevice,
path: &std::path::Path,
target: &std::path::Path,
meta: FileMeta,
) -> Result<u32> {
let (parent_nid, leaf, existing) = self.resolve_for_create(path)?;
if existing.is_some() {
return Err(crate::Error::InvalidArgument(format!(
"f2fs: path exists: {path:?}"
)));
}
let target_bytes = target
.to_str()
.ok_or_else(|| crate::Error::InvalidArgument("f2fs: non-UTF-8 symlink target".into()))?
.as_bytes()
.to_vec();
if target_bytes.len() > MAX_INLINE_DATA {
return Err(crate::Error::Unsupported(
"f2fs: writer only handles symlink targets up to MAX_INLINE_DATA".into(),
));
}
let nid = self.alloc_nid();
let inode_blk = self.alloc_node_block()?;
let mode = S_IFLNK | (meta.mode & 0x0FFF);
self.inodes.insert(
nid,
InodeRec {
nid,
mode,
uid: meta.uid,
gid: meta.gid,
links: 1,
size: target_bytes.len() as u64,
blocks: 0,
atime: meta.atime,
ctime: meta.ctime,
mtime: meta.mtime,
flags: 0,
inline_flags: F2FS_INLINE_DATA | F2FS_DATA_EXIST,
i_addr: [0; ADDRS_PER_INODE],
i_nid: [0; NIDS_PER_INODE],
inline_payload: target_bytes,
on_disk_block: inode_blk,
},
);
self.attach_to_parent(parent_nid, &leaf, nid, F2FS_FT_SYMLINK)?;
Ok(nid)
}
pub fn add_device(
&mut self,
_dev: &mut dyn BlockDevice,
path: &std::path::Path,
kind: DeviceKind,
major: u32,
minor: u32,
meta: FileMeta,
) -> Result<u32> {
let (parent_nid, leaf, existing) = self.resolve_for_create(path)?;
if existing.is_some() {
return Err(crate::Error::InvalidArgument(format!(
"f2fs: path exists: {path:?}"
)));
}
let (mode_type, ft) = match kind {
DeviceKind::Char => (S_IFCHR, F2FS_FT_CHRDEV),
DeviceKind::Block => (S_IFBLK, F2FS_FT_BLKDEV),
DeviceKind::Fifo => (S_IFIFO, F2FS_FT_FIFO),
DeviceKind::Socket => (S_IFSOCK, F2FS_FT_SOCK),
};
let nid = self.alloc_nid();
let inode_blk = self.alloc_node_block()?;
let mut payload = vec![0u8; 8];
let devt =
((major as u64) << 8) | (minor as u64 & 0xFF) | ((minor as u64 & 0xFFFFFF00) << 12);
payload[..8].copy_from_slice(&devt.to_le_bytes());
self.inodes.insert(
nid,
InodeRec {
nid,
mode: mode_type | (meta.mode & 0x0FFF),
uid: meta.uid,
gid: meta.gid,
links: 1,
size: 0,
blocks: 0,
atime: meta.atime,
ctime: meta.ctime,
mtime: meta.mtime,
flags: 0,
inline_flags: F2FS_INLINE_DATA | F2FS_DATA_EXIST,
i_addr: [0; ADDRS_PER_INODE],
i_nid: [0; NIDS_PER_INODE],
inline_payload: payload,
on_disk_block: inode_blk,
},
);
self.attach_to_parent(parent_nid, &leaf, nid, ft)?;
Ok(nid)
}
pub fn add_hardlink(
&mut self,
_dev: &mut dyn BlockDevice,
src_path: &std::path::Path,
dst_path: &std::path::Path,
) -> Result<u32> {
let src_nid = self.resolve_existing(src_path)?;
let src = self
.inodes
.get(&src_nid)
.ok_or_else(|| {
crate::Error::InvalidArgument(format!(
"f2fs: hardlink source {src_path:?} not tracked by this writer"
))
})?
.clone();
if src.mode & super::constants::S_IFMT == S_IFDIR {
return Err(crate::Error::InvalidArgument(
"f2fs: cannot hard-link a directory".into(),
));
}
if src.mode & super::constants::S_IFMT == S_IFLNK {
return Err(crate::Error::InvalidArgument(
"f2fs: cannot hard-link a symbolic link".into(),
));
}
let (parent_nid, leaf, existing) = self.resolve_for_create(dst_path)?;
if existing.is_some() {
return Err(crate::Error::InvalidArgument(format!(
"f2fs: path exists: {dst_path:?}"
)));
}
let src_mut = self
.inodes
.get_mut(&src_nid)
.expect("source inode disappeared between lookup and update");
src_mut.links = src_mut.links.saturating_add(1);
let ft = super::dir::file_type_from_mode(src.mode);
self.attach_to_parent(parent_nid, &leaf, src_nid, ft)?;
Ok(src_nid)
}
fn resolve_existing(&self, path: &std::path::Path) -> Result<u32> {
let s = path
.to_str()
.ok_or_else(|| crate::Error::InvalidArgument("f2fs: non-UTF-8 path".into()))?;
if s == "/" || s.is_empty() {
return Ok(3);
}
let parts: Vec<&str> = s
.trim_matches('/')
.split('/')
.filter(|p| !p.is_empty())
.collect();
let mut cur = 3u32;
for comp in &parts {
let kids = self.children.get(&cur).ok_or_else(|| {
crate::Error::InvalidArgument(format!("f2fs: parent of {s:?} not a known dir"))
})?;
let found = kids
.iter()
.find(|d| d.name == comp.as_bytes())
.ok_or_else(|| {
crate::Error::InvalidArgument(format!("f2fs: no such entry {comp:?} in {s:?}"))
})?;
cur = found.ino;
}
Ok(cur)
}
fn attach_to_parent(
&mut self,
parent_nid: u32,
name: &[u8],
child_nid: u32,
file_type: u8,
) -> Result<()> {
let dentry = Dentry {
hash: 0,
ino: child_nid,
file_type,
name: name.to_vec(),
};
self.children.entry(parent_nid).or_default().push(dentry);
let kids = self.children.get(&parent_nid).unwrap();
let inline_ok = fits_in_inline(kids);
if !inline_ok {
let ino = self
.inodes
.get_mut(&parent_nid)
.ok_or_else(|| crate::Error::InvalidImage("f2fs: ghost parent".into()))?;
if ino.inline_flags & F2FS_INLINE_DENTRY != 0 {
ino.inline_flags &= !F2FS_INLINE_DENTRY;
ino.inline_payload.clear();
}
self.spilled_dirs.insert(parent_nid, ());
if ino.i_addr[0] == 0 {
let phys = self.alloc_data_block()?;
self.inodes.get_mut(&parent_nid).unwrap().i_addr[0] = phys;
self.inodes.get_mut(&parent_nid).unwrap().blocks = 1;
}
}
Ok(())
}
pub fn remove(&mut self, _dev: &mut dyn BlockDevice, path: &std::path::Path) -> Result<()> {
let (parent_nid, leaf, existing) = self.resolve_for_create(path)?;
let child = existing
.ok_or_else(|| crate::Error::InvalidArgument(format!("f2fs: not found: {path:?}")))?;
if let Some(grand) = self.children.get(&child) {
if !grand.is_empty() {
return Err(crate::Error::InvalidArgument(format!(
"f2fs: directory not empty: {path:?}"
)));
}
}
if let Some(kids) = self.children.get_mut(&parent_nid) {
kids.retain(|d| d.name != leaf);
}
self.inodes.remove(&child);
self.children.remove(&child);
if let Some(parent) = self.inodes.get_mut(&parent_nid) {
if parent.links > 1 {
parent.links -= 1;
}
}
Ok(())
}
pub fn flush(&mut self, dev: &mut dyn BlockDevice) -> Result<()> {
let bs = F2FS_BLKSIZE as u64;
for (&dir_nid, _) in self.spilled_dirs.clone().iter() {
let kids = self.children.get(&dir_nid).cloned().unwrap_or_default();
let chunks = split_dentries_per_block(&kids);
if chunks.is_empty() {
continue;
}
let mut block_addrs: Vec<u32> = Vec::with_capacity(chunks.len());
let first_phys = {
let ino = self
.inodes
.get(&dir_nid)
.ok_or_else(|| crate::Error::InvalidImage("f2fs: ghost dir".into()))?;
ino.i_addr[0]
};
if first_phys == 0 {
let phys = self.alloc_data_block()?;
self.inodes.get_mut(&dir_nid).unwrap().i_addr[0] = phys;
block_addrs.push(phys);
} else {
block_addrs.push(first_phys);
}
for idx in 1..chunks.len() {
let phys = self.alloc_data_block()?;
self.place_data_block(dir_nid, idx as u64, phys)?;
block_addrs.push(phys);
}
for (idx, chunk) in chunks.iter().enumerate() {
let blk = encode_block_dentry(chunk);
dev.write_at(block_addrs[idx] as u64 * bs, &blk)?;
}
let ino = self.inodes.get_mut(&dir_nid).unwrap();
ino.size = (chunks.len() as u64) * F2FS_BLKSIZE as u64;
ino.blocks = chunks.len() as u64;
}
for (_, d) in self.direct_nodes.iter() {
let ino = find_owner_of_dnode(self, d.nid);
let blk = encode_direct_node_with_crc(&d.addrs, d.nid, ino);
dev.write_at(d.on_disk_block as u64 * bs, &blk)?;
}
for (_, ind) in self.indirect_nodes.iter() {
let ino = find_owner_of_indirect(self, ind.nid);
let blk = encode_indirect_node_with_crc(&ind.nids, ind.nid, ino);
dev.write_at(ind.on_disk_block as u64 * bs, &blk)?;
}
let mut parent_of: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
parent_of.insert(3, 3); for (&parent, kids) in &self.children {
for kid in kids {
parent_of.insert(kid.ino, parent);
}
}
let mut blocks_for: std::collections::HashMap<u32, u64> = std::collections::HashMap::new();
for (&nid, ino) in self.inodes.iter() {
let mut count: u64 = 1; for a in ino.i_addr.iter() {
if *a != 0 {
count += 1;
}
}
blocks_for.insert(nid, count);
}
for (_, d) in self.direct_nodes.iter() {
let owner = find_owner_of_dnode(self, d.nid);
*blocks_for.entry(owner).or_insert(1) += 1;
for a in d.addrs.iter() {
if *a != 0 {
*blocks_for.entry(owner).or_insert(1) += 1;
}
}
}
for (_, ind) in self.indirect_nodes.iter() {
let owner = find_owner_of_indirect(self, ind.nid);
*blocks_for.entry(owner).or_insert(1) += 1;
}
for (_, ino) in self.inodes.iter() {
let kids = if ino.inline_flags & F2FS_INLINE_DENTRY != 0 {
self.children.get(&ino.nid).cloned()
} else {
None
};
let parent_nid = *parent_of.get(&ino.nid).unwrap_or(&ino.nid);
let blocks_count = *blocks_for.get(&ino.nid).unwrap_or(&1);
let blk = encode_inode_block(ino, kids.as_deref(), parent_nid, blocks_count);
dev.write_at(ino.on_disk_block as u64 * bs, &blk)?;
}
let nat_pages_per_pack =
(self.geom.segment_count_nat * self.geom.blocks_per_seg) as usize / 2;
let mut pages: Vec<Vec<(u8, u32, u32)>> = vec![Vec::new(); nat_pages_per_pack];
let mut all_nodes: Vec<(u32, u32, u32)> = Vec::new(); for (_, ino) in self.inodes.iter() {
all_nodes.push((ino.nid, ino.nid, ino.on_disk_block));
}
for (_, d) in self.direct_nodes.iter() {
all_nodes.push((d.nid, find_owner_of_dnode(self, d.nid), d.on_disk_block));
}
for (_, ind) in self.indirect_nodes.iter() {
all_nodes.push((
ind.nid,
find_owner_of_indirect(self, ind.nid),
ind.on_disk_block,
));
}
all_nodes.push((1, 1, 1)); all_nodes.push((2, 2, 1));
for (nid, owner, blk) in all_nodes {
let page_idx = (nid as usize) / super::constants::NAT_ENTRY_PER_BLOCK;
let slot = (nid as usize) % super::constants::NAT_ENTRY_PER_BLOCK;
if page_idx >= pages.len() {
return Err(crate::Error::InvalidArgument(format!(
"f2fs: nid {nid} exceeds NAT capacity"
)));
}
while pages[page_idx].len() <= slot {
pages[page_idx].push((0, 0, 0));
}
pages[page_idx][slot] = (0, owner, blk);
}
for (pidx, slots) in pages.iter().enumerate() {
let page = super::format::encode_nat_page(slots);
for half in [0u32, 1u32] {
let phys = self.geom.nat_blkaddr + half * (nat_pages_per_pack as u32) + pidx as u32;
dev.write_at(phys as u64 * bs, &page)?;
}
}
let main_segs = self.geom.segment_count_main as usize;
let bps = self.geom.blocks_per_seg as usize;
let sit_segments_per_half = self.geom.segment_count_sit / 2;
let sit_pages_per_half = (sit_segments_per_half * self.geom.blocks_per_seg) as usize;
let sit_entries_per_block = F2FS_BLKSIZE / 74;
let mut sit_pages: Vec<Vec<u8>> = vec![vec![0u8; F2FS_BLKSIZE]; sit_pages_per_half];
let mut seg_state: Vec<(u16, usize)> = vec![(0, 0); main_segs];
seg_state[1] = (4, 0); seg_state[2] = (5, 0); seg_state[4] = (1, 0); seg_state[5] = (2, 0); let node_active_blk = self.next_node_blk;
for (i, &seg) in self.node_segments.iter().enumerate() {
let count = if i + 1 < self.node_segments.len() {
bps
} else {
(node_active_blk - self.seg_to_blk(seg)) as usize
};
seg_state[seg as usize] = (3, count); }
let data_active_blk = self.next_data_blk;
for (i, &seg) in self.data_segments.iter().enumerate() {
let count = if i + 1 < self.data_segments.len() {
bps
} else {
(data_active_blk - self.seg_to_blk(seg)) as usize
};
seg_state[seg as usize] = (0, count); }
for (segno, (curseg_type, valid_bits)) in seg_state.iter().enumerate() {
let page_idx = segno / sit_entries_per_block;
if page_idx >= sit_pages.len() {
break;
}
let entry_idx = segno % sit_entries_per_block;
let off = entry_idx * 74;
let page = &mut sit_pages[page_idx];
let vblocks = (curseg_type << 10) | ((*valid_bits as u16) & 0x03FF);
page[off..off + 2].copy_from_slice(&vblocks.to_le_bytes());
for bit in 0..*valid_bits {
let byte_idx = off + 2 + (bit / 8);
let mask = 1u8 << (7 - (bit % 8));
page[byte_idx] |= mask;
}
}
for half in 0..2u32 {
for (pidx, page) in sit_pages.iter().enumerate() {
let phys = self.geom.sit_blkaddr + half * (sit_pages_per_half as u32) + pidx as u32;
dev.write_at(phys as u64 * bs, page)?;
}
}
let ssa_blocks = self.geom.segment_count_ssa * self.geom.blocks_per_seg;
let ssa_blk = super::format::encode_ssa_page();
for i in 0..ssa_blocks {
dev.write_at((self.geom.ssa_blkaddr + i) as u64 * bs, &ssa_blk)?;
}
let bps_u32 = self.geom.blocks_per_seg;
let node_used_total: u32 = self
.node_segments
.iter()
.enumerate()
.map(|(i, &seg)| {
if i + 1 < self.node_segments.len() {
bps_u32
} else {
self.next_node_blk - self.seg_to_blk(seg)
}
})
.sum();
let data_used_total: u32 = self
.data_segments
.iter()
.enumerate()
.map(|(i, &seg)| {
if i + 1 < self.data_segments.len() {
bps_u32
} else {
self.next_data_blk - self.seg_to_blk(seg)
}
})
.sum();
let valid_blocks = (node_used_total as u64) + (data_used_total as u64);
let main_segs = self.geom.segment_count_main;
let rsvd_segment_count = 5u32.min(main_segs.saturating_sub(2));
let overprov_segment_count = 5u32.min(main_segs.saturating_sub(2));
let bps_u64 = self.geom.blocks_per_seg as u64;
let usable_segs = main_segs
.saturating_sub(rsvd_segment_count)
.saturating_sub(overprov_segment_count);
let user_block_count = (usable_segs as u64) * bps_u64;
let log_bps = self.geom.log_blocks_per_seg;
let sit_ver_bitmap_bytesize = ((self.geom.segment_count_sit / 2) << log_bps) / 8;
let nat_ver_bitmap_bytesize = ((self.geom.segment_count_nat / 2) << log_bps) / 8;
let hot_node_seg = *self.node_segments.last().unwrap();
let hot_data_seg = *self.data_segments.last().unwrap();
let hot_node_blkoff = (self.next_node_blk - self.seg_to_blk(hot_node_seg)) as u16;
let hot_data_blkoff = (self.next_data_blk - self.seg_to_blk(hot_data_seg)) as u16;
let cur_node_segno = [hot_node_seg, 1, 2];
let cur_node_blkoff = [hot_node_blkoff, 0, 0];
let cur_data_segno = [hot_data_seg, 4, 5];
let cur_data_blkoff = [hot_data_blkoff, 0, 0];
let valid_node_count =
(self.inodes.len() + self.direct_nodes.len() + self.indirect_nodes.len()) as u32;
let valid_inode_count = self.inodes.len() as u32;
let cursegs: [u32; 6] = [
cur_data_segno[0],
cur_data_segno[1],
cur_data_segno[2],
cur_node_segno[0],
cur_node_segno[1],
cur_node_segno[2],
];
let used: std::collections::HashSet<u32> = self
.node_segments
.iter()
.chain(self.data_segments.iter())
.copied()
.collect();
let mut free_segment_count = 0u32;
for seg in 0..self.geom.segment_count_main {
if cursegs.contains(&seg) {
continue;
}
if used.contains(&seg) {
continue;
}
free_segment_count += 1;
}
let next_free_nid = self.next_nid;
let cp = Checkpoint {
version: 1,
user_block_count,
valid_block_count: valid_blocks,
rsvd_segment_count,
overprov_segment_count,
flags: super::constants::CP_UMOUNT_FLAG,
cp_pack_start_sum: 1,
cp_pack_total_block_count: 8,
cp_payload: 0,
head_blkaddr: self.geom.cp_blkaddr,
nat_ver_bitmap_bytesize,
sit_ver_bitmap_bytesize,
cur_nat_pack: 0,
cur_sit_pack: 0,
nat_journal: Vec::new(),
cur_node_segno,
cur_node_blkoff,
cur_data_segno,
cur_data_blkoff,
free_segment_count,
valid_node_count,
valid_inode_count,
next_free_nid,
};
let cp_bytes = super::checkpoint::encode_cp_head_writer(&cp);
let total = cp.cp_pack_total_block_count as u64;
dev.write_at(self.geom.cp_blkaddr as u64 * bs, &cp_bytes)?;
let zero = vec![0u8; F2FS_BLKSIZE];
for off in 1..(total - 1) {
dev.write_at((self.geom.cp_blkaddr as u64 + off) * bs, &zero)?;
}
dev.write_at((self.geom.cp_blkaddr as u64 + total - 1) * bs, &cp_bytes)?;
let cp1_base = (self.geom.cp_blkaddr + self.geom.blocks_per_seg) as u64;
for off in 0..total {
dev.write_at((cp1_base + off) * bs, &zero)?;
}
dev.sync()?;
Ok(())
}
}
fn find_owner_of_dnode(w: &Writer, dnid: u32) -> u32 {
for (_, ino) in w.inodes.iter() {
for s in ino.i_nid.iter() {
if *s == dnid {
return ino.nid;
}
}
}
for (_, ind) in w.indirect_nodes.iter() {
if ind.nids.contains(&dnid) {
return find_owner_of_indirect(w, ind.nid);
}
}
dnid
}
fn find_owner_of_indirect(w: &Writer, inid: u32) -> u32 {
for (_, ino) in w.inodes.iter() {
for s in ino.i_nid.iter() {
if *s == inid {
return ino.nid;
}
}
}
for (_, parent) in w.indirect_nodes.iter() {
if parent.nid == inid {
continue;
}
if parent.nids.contains(&inid) {
return find_owner_of_indirect(w, parent.nid);
}
}
inid
}
fn split_dentries_per_block(entries: &[Dentry]) -> Vec<Vec<Dentry>> {
if entries.is_empty() {
return Vec::new();
}
let mut out: Vec<Vec<Dentry>> = Vec::new();
let mut cur: Vec<Dentry> = Vec::new();
let mut cur_slots = 0usize;
for e in entries {
let need = e.name.len().div_ceil(F2FS_SLOT_LEN).max(1);
if cur_slots + need > NR_DENTRY_IN_BLOCK {
out.push(std::mem::take(&mut cur));
cur_slots = 0;
}
cur.push(e.clone());
cur_slots += need;
}
if !cur.is_empty() {
out.push(cur);
}
out
}
fn fits_in_inline(entries: &[Dentry]) -> bool {
let mut slot = 0usize;
for e in entries {
let need = e.name.len().div_ceil(F2FS_SLOT_LEN).max(1);
if slot + need > INLINE_DENTRY_NR {
return false;
}
slot += need;
}
true
}
fn encode_block_dentry(entries: &[Dentry]) -> Vec<u8> {
let mut buf = vec![0u8; F2FS_BLKSIZE];
let bitmap_off = 0usize;
let dentries_off = SIZE_OF_DENTRY_BITMAP + SIZE_OF_RESERVED;
let names_off = dentries_off + NR_DENTRY_IN_BLOCK * SIZE_OF_DIR_ENTRY;
let mut slot = 0;
for e in entries {
let need = e.name.len().div_ceil(F2FS_SLOT_LEN).max(1);
if slot + need > NR_DENTRY_IN_BLOCK {
break;
}
set_bit(
&mut buf[bitmap_off..bitmap_off + SIZE_OF_DENTRY_BITMAP],
slot,
);
let de_off = dentries_off + slot * SIZE_OF_DIR_ENTRY;
buf[de_off..de_off + 4].copy_from_slice(&e.hash.to_le_bytes());
buf[de_off + 4..de_off + 8].copy_from_slice(&e.ino.to_le_bytes());
buf[de_off + 8..de_off + 10].copy_from_slice(&(e.name.len() as u16).to_le_bytes());
buf[de_off + 10] = e.file_type;
let name_start = names_off + slot * F2FS_SLOT_LEN;
let name_end = name_start + e.name.len();
buf[name_start..name_end].copy_from_slice(&e.name);
slot += need;
}
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
}
fn encode_inline_dentry_payload(dir_nid: u32, parent_nid: u32, entries: &[Dentry]) -> Vec<u8> {
let dot_entries = [
Dentry {
hash: 0,
ino: dir_nid,
file_type: F2FS_FT_DIR,
name: b".".to_vec(),
},
Dentry {
hash: 0,
ino: parent_nid,
file_type: F2FS_FT_DIR,
name: b"..".to_vec(),
},
];
let all: Vec<&Dentry> = dot_entries.iter().chain(entries.iter()).collect();
let bitmap_bytes = INLINE_DENTRY_NR.div_ceil(8);
let reserved = 7usize;
let dentries_off = bitmap_bytes + reserved;
let names_off = dentries_off + INLINE_DENTRY_NR * SIZE_OF_DIR_ENTRY;
let total = names_off + INLINE_DENTRY_NR * F2FS_SLOT_LEN;
let mut buf = vec![0u8; total];
let mut slot = 0usize;
for e in all {
let need = e.name.len().div_ceil(F2FS_SLOT_LEN).max(1);
if slot + need > INLINE_DENTRY_NR {
break;
}
set_bit(&mut buf[..bitmap_bytes], slot);
let de_off = dentries_off + slot * SIZE_OF_DIR_ENTRY;
buf[de_off..de_off + 4].copy_from_slice(&e.hash.to_le_bytes());
buf[de_off + 4..de_off + 8].copy_from_slice(&e.ino.to_le_bytes());
buf[de_off + 8..de_off + 10].copy_from_slice(&(e.name.len() as u16).to_le_bytes());
buf[de_off + 10] = e.file_type;
let name_start = names_off + slot * F2FS_SLOT_LEN;
let name_end = name_start + e.name.len();
buf[name_start..name_end].copy_from_slice(&e.name);
slot += need;
}
buf
}
#[inline]
fn set_bit(bitmap: &mut [u8], i: usize) {
let byte = i / 8;
let bit = i % 8;
if byte < bitmap.len() {
bitmap[byte] |= 1 << bit;
}
}
fn write_node_footer(buf: &mut [u8], nid: u32, ino: u32) {
const NODE_FOOTER_OFFSET: usize = F2FS_BLKSIZE - 24;
let o = NODE_FOOTER_OFFSET;
buf[o..o + 4].copy_from_slice(&nid.to_le_bytes());
buf[o + 4..o + 8].copy_from_slice(&ino.to_le_bytes());
buf[o + 8..o + 12].copy_from_slice(&0u32.to_le_bytes()); buf[o + 12..o + 20].copy_from_slice(&1u64.to_le_bytes()); buf[o + 20..o + 24].copy_from_slice(&0u32.to_le_bytes()); }
fn encode_inode_block(
ino: &InodeRec,
inline_children: Option<&[Dentry]>,
parent_nid: u32,
blocks: u64,
) -> Vec<u8> {
let mut buf = vec![0u8; F2FS_BLKSIZE];
buf[0x00..0x02].copy_from_slice(&ino.mode.to_le_bytes());
buf[0x03] = ino.inline_flags;
buf[0x04..0x08].copy_from_slice(&ino.uid.to_le_bytes());
buf[0x08..0x0C].copy_from_slice(&ino.gid.to_le_bytes());
buf[0x0C..0x10].copy_from_slice(&ino.links.to_le_bytes());
buf[0x10..0x18].copy_from_slice(&ino.size.to_le_bytes());
buf[0x18..0x20].copy_from_slice(&blocks.to_le_bytes());
buf[0x20..0x28].copy_from_slice(&(ino.atime as u64).to_le_bytes());
buf[0x28..0x30].copy_from_slice(&(ino.ctime as u64).to_le_bytes());
buf[0x30..0x38].copy_from_slice(&(ino.mtime as u64).to_le_bytes());
buf[0x50..0x54].copy_from_slice(&ino.flags.to_le_bytes());
buf[0x54..0x58].copy_from_slice(&parent_nid.to_le_bytes());
const INLINE_PAYLOAD_OFFSET: usize = I_ADDR_OFFSET + 4;
if ino.inline_flags & F2FS_INLINE_DENTRY != 0 {
let kids = inline_children.unwrap_or(&[]);
let payload = encode_inline_dentry_payload(ino.nid, parent_nid, kids);
let n = payload.len().min(buf.len() - INLINE_PAYLOAD_OFFSET - 8);
buf[INLINE_PAYLOAD_OFFSET..INLINE_PAYLOAD_OFFSET + n].copy_from_slice(&payload[..n]);
} else if ino.inline_flags & F2FS_INLINE_DATA != 0 {
let payload = &ino.inline_payload;
let n = payload.len().min(buf.len() - INLINE_PAYLOAD_OFFSET - 8);
buf[INLINE_PAYLOAD_OFFSET..INLINE_PAYLOAD_OFFSET + n].copy_from_slice(&payload[..n]);
} else {
for (i, a) in ino.i_addr.iter().enumerate() {
let o = I_ADDR_OFFSET + i * 4;
buf[o..o + 4].copy_from_slice(&a.to_le_bytes());
}
let nid_off = I_ADDR_OFFSET + ADDRS_PER_INODE * 4;
for (i, a) in ino.i_nid.iter().enumerate() {
let o = nid_off + i * 4;
buf[o..o + 4].copy_from_slice(&a.to_le_bytes());
}
}
write_node_footer(&mut buf, ino.nid, ino.nid);
buf
}
fn encode_direct_node_with_crc(ptrs: &[u32; ADDRS_PER_BLOCK], nid: u32, ino: u32) -> Vec<u8> {
let mut buf = vec![0u8; F2FS_BLKSIZE];
const FOOTER_OFFSET: usize = F2FS_BLKSIZE - 24;
for (i, p) in ptrs.iter().enumerate() {
let o = i * 4;
if o + 4 > FOOTER_OFFSET {
break;
}
buf[o..o + 4].copy_from_slice(&p.to_le_bytes());
}
write_node_footer(&mut buf, nid, ino);
buf
}
fn encode_indirect_node_with_crc(nids: &[u32; NIDS_PER_BLOCK], nid: u32, ino: u32) -> Vec<u8> {
let mut buf = vec![0u8; F2FS_BLKSIZE];
const FOOTER_OFFSET: usize = F2FS_BLKSIZE - 24;
for (i, n) in nids.iter().enumerate() {
let o = i * 4;
if o + 4 > FOOTER_OFFSET {
break;
}
buf[o..o + 4].copy_from_slice(&n.to_le_bytes());
}
write_node_footer(&mut buf, nid, ino);
buf
}
#[allow(dead_code)]
fn _dummy_use_of_reader_type(_: Box<dyn crate::fs::ReadSeek + Send>) {}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::MemoryBackend;
#[test]
fn fits_in_inline_handles_long_names() {
let mut entries: Vec<Dentry> = Vec::new();
for i in 0..30 {
entries.push(Dentry {
hash: 0,
ino: 100 + i,
file_type: F2FS_FT_REG_FILE,
name: format!("longishfilename_{i}").into_bytes(),
});
}
assert!(fits_in_inline(&entries));
let mut overflow: Vec<Dentry> = Vec::new();
for i in 0..200 {
overflow.push(Dentry {
hash: 0,
ino: i,
file_type: F2FS_FT_REG_FILE,
name: b"x".to_vec(),
});
}
assert!(!fits_in_inline(&overflow));
}
#[test]
fn split_dentries_per_block_packs_greedily() {
assert!(split_dentries_per_block(&[]).is_empty());
let one = vec![Dentry {
hash: 0,
ino: 7,
file_type: F2FS_FT_REG_FILE,
name: b"a".to_vec(),
}];
assert_eq!(split_dentries_per_block(&one).len(), 1);
let mut many: Vec<Dentry> = Vec::new();
for i in 0..300 {
many.push(Dentry {
hash: 0,
ino: i,
file_type: F2FS_FT_REG_FILE,
name: b"x".to_vec(),
});
}
let chunks = split_dentries_per_block(&many);
assert_eq!(chunks.len(), 2);
assert_eq!(chunks[0].len(), 214);
assert_eq!(chunks[1].len(), 86);
}
#[test]
fn place_data_block_builds_triple_indirect_tree() {
let mut dev = MemoryBackend::new(2 * 1024 * 1024);
let opts = super::super::FormatOpts {
log_blocks_per_seg: 2,
..super::super::FormatOpts::default()
};
let mut fs = super::super::F2fs::format(&mut dev, &opts).unwrap();
let writer = fs.writer.as_mut().expect("formatted handle has a writer");
let inode_nid = writer.next_nid;
writer.next_nid += 1;
let inode_blk = writer.alloc_node_block().unwrap();
writer.inodes.insert(
inode_nid,
InodeRec {
nid: inode_nid,
mode: S_IFREG | 0o644,
uid: 0,
gid: 0,
links: 1,
size: 0,
blocks: 0,
atime: 0,
ctime: 0,
mtime: 0,
flags: 0,
inline_flags: 0,
i_addr: [0; ADDRS_PER_INODE],
i_nid: [0; NIDS_PER_INODE],
inline_payload: Vec::new(),
on_disk_block: inode_blk,
},
);
let double_span = (NIDS_PER_BLOCK as u64) * (ADDRS_PER_BLOCK as u64) * 2;
let triple_base = ADDRS_PER_INODE as u64 + (ADDRS_PER_BLOCK as u64) * 2 + double_span;
let idx = triple_base + 7;
let phys = writer.alloc_data_block().unwrap();
writer.place_data_block(inode_nid, idx, phys).unwrap();
let ino = writer.inodes.get(&inode_nid).unwrap();
let top_nid = ino.i_nid[NID_TRIPLE_INDIRECT];
assert!(top_nid != 0, "triple top nid was never assigned");
let top = writer.indirect_nodes.get(&top_nid).unwrap();
let mid_nid = top.nids[0];
assert!(mid_nid != 0, "triple mid nid was never assigned");
let mid = writer.indirect_nodes.get(&mid_nid).unwrap();
let dnode_nid = mid.nids[0];
assert!(dnode_nid != 0, "direct node under triple-mid missing");
let dnode = writer.direct_nodes.get(&dnode_nid).unwrap();
assert_eq!(dnode.addrs[7], phys);
assert_eq!(ino.i_addr[0], 0);
}
#[test]
fn triple_indirect_round_trip_synthetic() {
let mut dev = MemoryBackend::new(2 * 1024 * 1024);
let opts = super::super::FormatOpts {
log_blocks_per_seg: 2,
..super::super::FormatOpts::default()
};
let mut fs = super::super::F2fs::format(&mut dev, &opts).unwrap();
let inode_nid = {
let writer = fs.writer.as_mut().unwrap();
let nid = writer.next_nid;
writer.next_nid += 1;
let inode_blk = writer.alloc_node_block().unwrap();
writer.inodes.insert(
nid,
InodeRec {
nid,
mode: S_IFREG | 0o644,
uid: 0,
gid: 0,
links: 1,
size: 0,
blocks: 0,
atime: 0,
ctime: 0,
mtime: 0,
flags: 0,
inline_flags: 0,
i_addr: [0; ADDRS_PER_INODE],
i_nid: [0; NIDS_PER_INODE],
inline_payload: Vec::new(),
on_disk_block: inode_blk,
},
);
writer
.attach_to_parent(3, b"sparse.bin", nid, F2FS_FT_REG_FILE)
.unwrap();
nid
};
let double_span = (NIDS_PER_BLOCK as u64) * (ADDRS_PER_BLOCK as u64) * 2;
let triple_base = ADDRS_PER_INODE as u64 + (ADDRS_PER_BLOCK as u64) * 2 + double_span;
let idx = triple_base;
let phys = {
let writer = fs.writer.as_mut().unwrap();
let p = writer.alloc_data_block().unwrap();
writer.place_data_block(inode_nid, idx, p).unwrap();
let ino = writer.inodes.get_mut(&inode_nid).unwrap();
ino.size = idx * F2FS_BLKSIZE as u64 + 1;
ino.blocks = 1;
p
};
dev.write_at(phys as u64 * F2FS_BLKSIZE as u64, &{
let mut b = vec![0u8; F2FS_BLKSIZE];
b[0] = 0xAB;
b
})
.unwrap();
fs.flush(&mut dev).unwrap();
let mut fs2 = super::super::F2fs::open(&mut dev).unwrap();
let mut r = fs2.open_file_reader(&mut dev, "/sparse.bin").unwrap();
let total_bytes = idx * F2FS_BLKSIZE as u64 + 1;
let mut last = 0u8;
let mut left = total_bytes;
let mut buf = vec![0u8; 64 * 1024];
while left > 0 {
let want = (left as usize).min(buf.len());
let n = r.read(&mut buf[..want]).unwrap();
assert!(n > 0, "reader ran dry before EOF");
last = buf[n - 1];
left -= n as u64;
}
assert_eq!(last, 0xAB);
}
#[test]
fn owner_chain_climbs_triple_tree() {
let mut dev = MemoryBackend::new(2 * 1024 * 1024);
let opts = super::super::FormatOpts {
log_blocks_per_seg: 2,
..super::super::FormatOpts::default()
};
let mut fs = super::super::F2fs::format(&mut dev, &opts).unwrap();
let writer = fs.writer.as_mut().unwrap();
let inode_nid = writer.next_nid;
writer.next_nid += 1;
let inode_blk = writer.alloc_node_block().unwrap();
writer.inodes.insert(
inode_nid,
InodeRec {
nid: inode_nid,
mode: S_IFREG | 0o644,
uid: 0,
gid: 0,
links: 1,
size: 0,
blocks: 0,
atime: 0,
ctime: 0,
mtime: 0,
flags: 0,
inline_flags: 0,
i_addr: [0; ADDRS_PER_INODE],
i_nid: [0; NIDS_PER_INODE],
inline_payload: Vec::new(),
on_disk_block: inode_blk,
},
);
let double_span = (NIDS_PER_BLOCK as u64) * (ADDRS_PER_BLOCK as u64) * 2;
let triple_base = ADDRS_PER_INODE as u64 + (ADDRS_PER_BLOCK as u64) * 2 + double_span;
let phys = writer.alloc_data_block().unwrap();
writer
.place_data_block(inode_nid, triple_base, phys)
.unwrap();
let ino = writer.inodes.get(&inode_nid).unwrap();
let top_nid = ino.i_nid[NID_TRIPLE_INDIRECT];
let top = writer.indirect_nodes.get(&top_nid).unwrap();
let mid_nid = top.nids[0];
assert_eq!(super::find_owner_of_indirect(writer, mid_nid), inode_nid);
}
}