use crate::spec::{BLOCKS_PER_GROUP, BLOCK_SIZE, INODE_SIZE};
use crate::{Error, Result};
pub const INODES_PER_BLOCK: u32 = (BLOCK_SIZE / INODE_SIZE) as u32;
#[derive(Debug, Clone)]
pub struct Geometry {
pub blocks: u64,
pub groups: u32,
pub inodes_per_group: u32,
pub gdt_blocks: u32,
pub journal_blocks: u32,
pub groups_per_flex: u32,
}
impl Geometry {
pub fn new(
size_bytes: u64,
total_inodes: Option<u32>,
journal_blocks: Option<u32>,
) -> Result<Geometry> {
if size_bytes == 0 || size_bytes % BLOCK_SIZE as u64 != 0 {
return Err(Error::Unsupported(format!(
"image size {size_bytes} is not a positive multiple of {BLOCK_SIZE}"
)));
}
let blocks = size_bytes / BLOCK_SIZE as u64;
if blocks >= u64::from(u32::MAX) {
return Err(Error::Unsupported(
"image needs >= 2^32 blocks (64bit feature not emitted)".into(),
));
}
if blocks < 2048 {
return Err(Error::Unsupported(
"image too small for a journal (< 2048 blocks)".into(),
));
}
let groups = blocks.div_ceil(u64::from(BLOCKS_PER_GROUP)) as u32;
let wanted = match total_inodes {
Some(n) => u64::from(n),
None => blocks * BLOCK_SIZE as u64 / 16384,
};
let ipg = wanted
.div_ceil(u64::from(groups))
.next_multiple_of(u64::from(INODES_PER_BLOCK))
.clamp(u64::from(INODES_PER_BLOCK), 32768) as u32;
let gdt_blocks = (groups * 32).div_ceil(BLOCK_SIZE as u32);
let journal_blocks = match journal_blocks {
Some(j) => j,
None => default_journal_blocks(blocks),
};
if u64::from(journal_blocks) * 4 > blocks {
return Err(Error::Unsupported(
"journal larger than a quarter of the fs".into(),
));
}
Ok(Geometry {
blocks,
groups,
inodes_per_group: ipg,
gdt_blocks,
journal_blocks,
groups_per_flex: 16,
})
}
pub fn inodes_count(&self) -> u32 {
self.inodes_per_group * self.groups
}
pub fn itable_blocks(&self) -> u32 {
self.inodes_per_group / INODES_PER_BLOCK
}
pub fn group_start(&self, g: u32) -> u64 {
u64::from(g) * u64::from(BLOCKS_PER_GROUP)
}
pub fn blocks_in_group(&self, g: u32) -> u32 {
(self.blocks - self.group_start(g)).min(u64::from(BLOCKS_PER_GROUP)) as u32
}
pub fn has_backup(&self, g: u32) -> bool {
fn is_power_of(mut n: u32, base: u32) -> bool {
while n % base == 0 {
n /= base;
}
n == 1
}
g == 1 || (g > 1 && (is_power_of(g, 3) || is_power_of(g, 5) || is_power_of(g, 7)))
}
pub fn backup_groups(&self) -> Vec<u32> {
(1..self.groups).filter(|&g| self.has_backup(g)).collect()
}
pub fn overhead(&self) -> u64 {
let backups = self.backup_groups().len() as u64;
1 + u64::from(self.gdt_blocks)
+ backups * (1 + u64::from(self.gdt_blocks))
+ 2 * u64::from(self.groups)
+ u64::from(self.groups) * u64::from(self.itable_blocks())
+ u64::from(self.journal_blocks)
}
}
pub fn default_journal_blocks(fs_blocks: u64) -> u32 {
match fs_blocks {
0..=32767 => 1024,
32768..=262143 => 4096,
262144..=524287 => 8192,
524288..=4194303 => 16384,
4194304..=8388607 => 32768,
8388608..=16777215 => 65536,
16777216..=33554431 => 131072,
_ => 262144,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn journal_tiers_match_mke2fs() {
assert_eq!(default_journal_blocks(4096), 1024);
assert_eq!(default_journal_blocks(131072), 4096);
assert_eq!(default_journal_blocks(305000), 8192);
assert_eq!(default_journal_blocks(2097152), 16384);
assert_eq!(default_journal_blocks(16777216), 131072);
}
#[test]
fn ipg_matches_mke2fs() {
let g = Geometry::new(305000 * 4096, None, None).unwrap();
assert_eq!(g.groups, 10);
assert_eq!(g.inodes_per_group, 7632);
assert_eq!(g.itable_blocks(), 477);
let g = Geometry::new(139264 * 4096, None, None).unwrap();
assert_eq!(g.groups, 5);
assert_eq!(g.inodes_per_group, 6976);
let g = Geometry::new(131072 * 4096, None, None).unwrap();
assert_eq!(g.inodes_per_group, 8192);
}
#[test]
fn backup_groups_sparse_super() {
let g = Geometry::new(16777216 * 4096, None, None).unwrap(); assert_eq!(
g.backup_groups(),
vec![1, 3, 5, 7, 9, 25, 27, 49, 81, 125, 243, 343]
);
}
#[test]
fn overhead_matches_mke2fs() {
let g = Geometry::new(131072 * 4096, None, None).unwrap();
assert_eq!(g.overhead(), 6158);
let g = Geometry::new(16777216 * 4096, None, None).unwrap();
assert_eq!(g.overhead(), 394305);
}
#[test]
fn one_block_last_group_is_valid() {
let g = Geometry::new(32769 * 4096, None, None).unwrap();
assert_eq!(g.groups, 2);
assert_eq!(g.blocks_in_group(1), 1);
}
#[test]
fn rejects_bad_sizes() {
assert!(Geometry::new(4095, None, None).is_err()); assert!(Geometry::new(1024 * 4096, None, None).is_err()); }
}