use super::constants::{GROUP_DESC_SIZE, INODE_SIZE_DYNAMIC};
#[derive(Debug, Clone)]
pub struct Layout {
pub block_size: u32,
pub blocks_count: u32,
pub inodes_count: u32,
pub first_data_block: u32,
pub blocks_per_group: u32,
pub inodes_per_group: u32,
pub inode_size: u16,
pub desc_size: usize,
pub inode_table_blocks: u32,
pub gdt_blocks: u32,
pub groups: Vec<GroupLayout>,
}
impl Layout {
pub fn num_groups(&self) -> u32 {
self.groups.len() as u32
}
}
#[derive(Debug, Clone, Copy)]
pub struct GroupLayout {
pub start_block: u32,
pub end_block: u32,
pub has_superblock: bool,
pub block_bitmap: u32,
pub inode_bitmap: u32,
pub inode_table: u32,
pub data_start: u32,
pub meta_blocks: u32,
}
pub fn group_has_sparse_super(g: u32) -> bool {
if g <= 1 {
return true;
}
is_power_of(g, 3) || is_power_of(g, 5) || is_power_of(g, 7)
}
fn is_power_of(mut n: u32, base: u32) -> bool {
if n == 0 {
return false;
}
while n % base == 0 {
n /= base;
}
n == 1
}
pub fn plan(block_size: u32, blocks_count: u32, inodes_count: u32) -> crate::Result<Layout> {
plan_with(block_size, blocks_count, inodes_count, false)
}
pub fn plan_with(
block_size: u32,
blocks_count: u32,
inodes_count: u32,
sparse_super: bool,
) -> crate::Result<Layout> {
if !block_size.is_power_of_two() || block_size < 1024 {
return Err(crate::Error::InvalidArgument(format!(
"ext: block_size must be a power of two ≥ 1024, got {block_size}"
)));
}
if blocks_count < 32 {
return Err(crate::Error::InvalidArgument(format!(
"ext: blocks_count {blocks_count} too small"
)));
}
if inodes_count < 11 {
return Err(crate::Error::InvalidArgument(format!(
"ext: inodes_count must include the reserved range (≥ 11), got {inodes_count}"
)));
}
let first_data_block: u32 = if block_size == 1024 { 1 } else { 0 };
let max_per_group = 8 * block_size;
let blocks_per_group = max_per_group.min(blocks_count);
if blocks_per_group % 8 != 0 {
return Err(crate::Error::InvalidArgument(format!(
"ext: blocks_count {blocks_count} must be a multiple of 8 for a \
single-group filesystem (blocks_per_group must be byte-aligned)"
)));
}
let group_input_blocks = blocks_count - first_data_block;
let num_groups = group_input_blocks.div_ceil(blocks_per_group);
let mut inodes_per_group = inodes_count.div_ceil(num_groups);
let max_inodes_per_group = 8 * block_size;
if inodes_per_group > max_inodes_per_group {
return Err(crate::Error::InvalidArgument(format!(
"ext: too many inodes per group ({inodes_per_group}, max {max_inodes_per_group})"
)));
}
inodes_per_group = inodes_per_group.div_ceil(8) * 8;
let inodes_count = inodes_per_group * num_groups;
let inode_size = INODE_SIZE_DYNAMIC;
let inode_table_bytes = inodes_per_group as u64 * inode_size as u64;
let inode_table_blocks = inode_table_bytes.div_ceil(block_size as u64) as u32;
let gdt_bytes = num_groups as u64 * GROUP_DESC_SIZE as u64;
let gdt_blocks = gdt_bytes.div_ceil(block_size as u64) as u32;
let mut groups = Vec::with_capacity(num_groups as usize);
for g in 0..num_groups {
let start = first_data_block + g * blocks_per_group;
let nominal_end = start + blocks_per_group - 1;
let end = nominal_end.min(blocks_count - 1);
let has_sb = if sparse_super {
group_has_sparse_super(g)
} else {
true
};
let mut next = start;
if has_sb {
next += 1 + gdt_blocks;
}
let block_bitmap = next;
let inode_bitmap = next + 1;
let inode_table = next + 2;
let data_start = inode_table + inode_table_blocks;
let meta_blocks = data_start - start;
groups.push(GroupLayout {
start_block: start,
end_block: end,
has_superblock: has_sb,
block_bitmap,
inode_bitmap,
inode_table,
data_start,
meta_blocks,
});
}
Ok(Layout {
block_size,
blocks_count,
inodes_count,
first_data_block,
blocks_per_group,
inodes_per_group,
inode_size,
desc_size: GROUP_DESC_SIZE,
inode_table_blocks,
gdt_blocks,
groups,
})
}
pub fn from_superblock(sb: &super::superblock::Superblock) -> crate::Result<Layout> {
let block_size = sb.block_size();
if !block_size.is_power_of_two() || block_size < 1024 {
return Err(crate::Error::InvalidImage(format!(
"ext: bad block_size {block_size}"
)));
}
let group_input_blocks = sb.blocks_count - sb.first_data_block;
let num_groups = group_input_blocks.div_ceil(sb.blocks_per_group);
let inode_table_blocks =
(sb.inodes_per_group as u64 * sb.inode_size as u64).div_ceil(block_size as u64) as u32;
let desc_size = sb.group_desc_size();
let gdt_blocks = (num_groups as u64 * desc_size as u64).div_ceil(block_size as u64) as u32;
let sparse_super =
sb.feature_ro_compat & super::constants::feature::RO_COMPAT_SPARSE_SUPER != 0;
let mut groups = Vec::with_capacity(num_groups as usize);
for g in 0..num_groups {
let start = sb.first_data_block + g * sb.blocks_per_group;
let nominal_end = start + sb.blocks_per_group - 1;
let end = nominal_end.min(sb.blocks_count - 1);
let has_sb = if sparse_super {
group_has_sparse_super(g)
} else {
true
};
let meta_first = start + if has_sb { 1 + gdt_blocks } else { 0 };
let block_bitmap = meta_first;
let inode_bitmap = meta_first + 1;
let inode_table = meta_first + 2;
let data_start = inode_table + inode_table_blocks;
let meta_blocks = data_start - start;
groups.push(GroupLayout {
start_block: start,
end_block: end,
has_superblock: has_sb,
block_bitmap,
inode_bitmap,
inode_table,
data_start,
meta_blocks,
});
}
Ok(Layout {
block_size,
blocks_count: sb.blocks_count,
inodes_count: sb.inodes_count,
first_data_block: sb.first_data_block,
blocks_per_group: sb.blocks_per_group,
inodes_per_group: sb.inodes_per_group,
inode_size: sb.inode_size,
desc_size,
inode_table_blocks,
gdt_blocks,
groups,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_group_1kib() {
let layout = plan(1024, 1024, 16).unwrap();
assert_eq!(layout.first_data_block, 1);
assert_eq!(layout.blocks_per_group, 1024);
assert_eq!(layout.num_groups(), 1);
assert_eq!(layout.inodes_per_group, 16);
assert_eq!(layout.inode_size, 128);
assert_eq!(layout.inode_table_blocks, 2);
assert_eq!(layout.gdt_blocks, 1);
let g0 = &layout.groups[0];
assert_eq!(g0.start_block, 1);
assert_eq!(g0.end_block, 1023);
assert!(g0.has_superblock);
assert_eq!(g0.block_bitmap, 3); assert_eq!(g0.inode_bitmap, 4);
assert_eq!(g0.inode_table, 5);
assert_eq!(g0.data_start, 7); assert_eq!(g0.meta_blocks, 6);
}
#[test]
fn multiple_groups_1kib() {
let layout = plan(1024, 32 * 1024, 256).unwrap();
assert_eq!(layout.num_groups(), 4);
assert_eq!(layout.blocks_per_group, 8192);
let g0 = &layout.groups[0];
let g1 = &layout.groups[1];
assert_eq!(g0.start_block, 1);
assert_eq!(g0.end_block, 8192);
assert_eq!(g1.start_block, 8193);
assert_eq!(g1.end_block, 16384);
}
#[test]
fn block_size_4096() {
let layout = plan(4096, 1024, 64).unwrap();
assert_eq!(layout.first_data_block, 0);
assert_eq!(layout.blocks_per_group, 1024);
assert_eq!(layout.num_groups(), 1);
}
#[test]
fn rejects_invalid_block_size() {
assert!(plan(512, 1024, 16).is_err());
assert!(plan(3000, 1024, 16).is_err()); }
#[test]
fn rejects_too_few_blocks() {
assert!(plan(1024, 8, 16).is_err());
}
#[test]
fn inodes_round_up_to_multiple_of_8() {
let layout = plan(1024, 1024, 11).unwrap();
assert_eq!(layout.inodes_per_group % 8, 0);
assert!(layout.inodes_per_group >= 11);
}
}