use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use crate::fs::{FsSizePlan, split_parent_name};
use super::mdir::Geom;
use super::{LittleFsFormatOpts, ctz};
pub struct LittleFsSizePlan {
geom: Geom,
inline_max: u32,
dirs: BTreeMap<String, usize>,
data_blocks: u64,
}
impl LittleFsSizePlan {
pub fn new(opts: &LittleFsFormatOpts) -> Self {
let geom = Geom {
block_size: opts.block_size,
block_count: u32::MAX,
prog_size: opts.prog_size.max(1),
fcrc: opts.disk_version >= super::DISK_VERSION_2_1,
};
let inline_max =
super::pick_inline_max(&geom, opts.inline_max).unwrap_or(opts.block_size / 8);
let mut dirs = BTreeMap::new();
dirs.insert("/".to_string(), (4 + 8) + (4 + 24));
Self {
geom,
inline_max,
dirs,
data_blocks: 0,
}
}
fn charge(&mut self, path: &str, bytes: usize) {
let (parent, _) = split_parent_name(path);
*self.dirs.entry(parent.to_string()).or_insert(0) += bytes;
}
fn data_blocks_for(&self, len: u64) -> u64 {
if len == 0 {
return 0;
}
let last = len.min(u32::MAX as u64) as u32 - 1;
ctz::index_of(&self.geom, last).0 as u64 + 1
}
}
impl FsSizePlan for LittleFsSizePlan {
fn add_dir(&mut self, path: &str) {
let (_, name) = split_parent_name(path);
self.charge(path, (4 + name.len()) + (4 + 8));
self.dirs.entry(path.to_string()).or_insert(0);
}
fn add_file(&mut self, path: &str, len: u64) {
let (_, name) = split_parent_name(path);
let mut bytes = 4 + name.len();
if len <= self.inline_max as u64 {
bytes += 4 + len as usize;
} else {
bytes += 4 + 8;
self.data_blocks += self.data_blocks_for(len);
}
self.charge(path, bytes);
}
fn add_symlink(&mut self, _path: &str, _target: &str) {
}
fn add_device(&mut self, _path: &str) {
}
fn total_size(&self) -> u64 {
let limit = self.geom.split_limit().max(1);
let mut blocks: u64 = 0;
for bytes in self.dirs.values() {
let pairs = (bytes.div_ceil(limit)).max(1) as u64;
blocks += 2 * pairs;
}
blocks += self.data_blocks;
blocks += 2;
blocks.max(4) * self.geom.block_size as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
fn plan() -> LittleFsSizePlan {
LittleFsSizePlan::new(&LittleFsFormatOpts::default())
}
#[test]
fn empty_tree_is_the_superblock_pair_plus_slack() {
assert_eq!(plan().total_size(), 4 * 4096);
}
#[test]
fn inline_files_need_no_data_blocks() {
let mut p = plan();
p.add_file("/small.txt", 16);
assert_eq!(p.total_size(), 4 * 4096);
}
#[test]
fn large_files_are_charged_their_skip_list() {
let mut p = plan();
p.add_file("/big.bin", 4097);
assert_eq!(p.data_blocks, 2);
let mut q = plan();
q.add_file("/big.bin", 4096);
assert_eq!(q.data_blocks, 1);
}
#[test]
fn each_directory_costs_a_pair() {
let mut p = plan();
p.add_dir("/etc");
p.add_dir("/etc/ssl");
assert_eq!(p.total_size(), (3 * 2 + 2) * 4096);
}
}