use super::*;
pub struct FsLayoutInfo {
total_blocks: u64,
block_size: u32,
blocks_per_group: u32,
inodes_per_group: u32,
inode_size: u16,
groups: u32,
desc_size: u16,
descs_per_block: u32,
gdt_blocks: u32,
inode_table_blocks: u32,
first_data_block: u32,
reserved_gdt_blocks: u32,
group0_block_bitmap: u32,
group0_inode_bitmap: u32,
group0_inode_table: u32,
group0_metadata_blocks: u32,
reserved_blocks: u64,
}
pub struct BlcokGroupLayout {
pub group_start_block: u64,
pub group_blcok_bitmap_startblocks: u64,
pub group_inode_bitmap_startblocks: u64,
pub group_inode_table_startblocks: u64,
pub metadata_blocks_in_group: u32,
}
pub fn compute_fs_layout(inode_size: u16, total_blocks: u64) -> FsLayoutInfo {
let block_size: u32 = 1024u32 << LOG_BLOCK_SIZE;
let blocks_per_group: u32 = 8 * block_size;
let inodes_per_group: u32 = blocks_per_group / 4;
let groups: u32 = total_blocks.div_ceil(blocks_per_group as u64) as u32;
let desc_size: u16 =
if DEFAULT_FEATURE_INCOMPAT & Ext4Superblock::EXT4_FEATURE_INCOMPAT_64BIT != 0 {
GROUP_DESC_SIZE
} else {
GROUP_DESC_SIZE_OLD
};
let descs_per_block: u32 = if desc_size == 0 {
0
} else {
block_size / desc_size as u32
};
let gdt_blocks: u32 = if descs_per_block == 0 {
0
} else {
groups.div_ceil(descs_per_block)
};
let inode_table_blocks: u32 = if block_size == 0 {
0
} else {
(inodes_per_group * inode_size as u32).div_ceil(block_size)
};
let first_data_block: u32 = if block_size > 1024 { 0 } else { 1 };
let reserved_gdt_blocks: u32 = RESERVED_GDT_BLOCKS;
let group0_start: u32 = first_data_block;
let reserved_gdt_start: u32 = group0_start + 2; let group0_block_bitmap: u32 = reserved_gdt_start + reserved_gdt_blocks;
let group0_inode_bitmap: u32 = group0_block_bitmap + 1;
let group0_inode_table: u32 = group0_inode_bitmap + 1;
let group0_metadata_blocks: u32 = (group0_inode_table + inode_table_blocks) - group0_start;
let reserved_blocks: u64 = total_blocks / 20;
FsLayoutInfo {
total_blocks,
block_size,
blocks_per_group,
inodes_per_group,
inode_size,
groups,
desc_size,
descs_per_block,
gdt_blocks,
inode_table_blocks,
first_data_block,
reserved_gdt_blocks,
group0_block_bitmap,
group0_inode_bitmap,
group0_inode_table,
group0_metadata_blocks,
reserved_blocks,
}
}
fn group_blocks_count(layout: &FsLayoutInfo, group_id: u32) -> u32 {
let group_start = u64::from(group_id) * u64::from(layout.blocks_per_group);
if group_start >= layout.total_blocks {
return 0;
}
let remaining = layout.total_blocks - group_start;
remaining.min(u64::from(layout.blocks_per_group)) as u32
}
fn group_free_blocks(layout: &FsLayoutInfo, group_id: u32, metadata_blocks: u32) -> u32 {
group_blocks_count(layout, group_id).saturating_sub(metadata_blocks)
}
fn mark_bitmap_range_allocated(bitmap: &mut [u8], start: u32, end: u32) {
let bits = (bitmap.len() * 8) as u32;
let end = end.min(bits);
for bit in start.min(bits)..end {
let byte_idx = (bit / 8) as usize;
let bit_idx = bit % 8;
bitmap[byte_idx] |= 1 << bit_idx;
}
}
fn mark_block_bitmap_padding(bitmap: &mut [u8], layout: &FsLayoutInfo, group_id: u32) {
let valid_blocks = group_blocks_count(layout, group_id);
mark_bitmap_range_allocated(bitmap, valid_blocks, layout.blocks_per_group);
}
pub fn mkfs<B: BlockDevice>(block_dev: &mut Jbd2Dev<B>) -> Ext4Result<()> {
debug!("Start initializing Ext4 filesystem...");
let old_journal_use = block_dev.is_use_journal();
block_dev.set_journal_use(false);
let total_blocks = block_dev.total_blocks();
let layout = compute_fs_layout(DEFAULT_INODE_SIZE, total_blocks);
let total_groups = layout.groups;
debug!(" Total blocks: {total_blocks}");
debug!(" Block size: {} bytes", layout.block_size);
debug!(" Block group count: {total_groups}");
debug!(" Blocks per group: {}", layout.blocks_per_group);
debug!(" Inodes per group: {}", layout.inodes_per_group);
let superblock = build_superblock(total_blocks, &layout);
write_superblock(block_dev, &superblock)?;
debug!("Superblock written");
write_superblock_redundant_backup(block_dev, &superblock, total_groups, &layout)?;
let mut descs: VecDeque<Ext4GroupDesc> = VecDeque::new();
for group_id in 0..total_groups {
let mut desc = build_uninit_group_desc(&superblock, group_id, &layout);
write_group_desc(block_dev, group_id, &mut desc)?;
descs.push_back(desc);
}
write_gdt_redundant_backup(block_dev, &descs, &superblock, total_groups, &layout)?;
debug!("{total_groups} block group descriptors written");
initialize_group_0(block_dev, &layout)?;
debug!("Block group 0 initialized (for root directory)");
initialize_other_groups_bitmaps(block_dev, &layout, &superblock)?;
let mut initialized_descs: VecDeque<Ext4GroupDesc> = VecDeque::new();
for group_id in 0..total_groups {
let mut desc = build_uninit_group_desc(&superblock, group_id, &layout);
if group_id == 0 {
desc.bg_flags = Ext4GroupDesc::EXT4_BG_INODE_ZEROED;
}
write_group_desc(block_dev, group_id, &mut desc)?;
initialized_descs.push_back(desc);
}
write_gdt_redundant_backup(
block_dev,
&initialized_descs,
&superblock,
total_groups,
&layout,
)?;
{
let mut fs = Ext4FileSystem::mount(block_dev).expect("Mount Failed!");
fs.umount(block_dev)?;
}
let verify_sb = read_superblock(block_dev)?;
block_dev.set_journal_use(old_journal_use);
if verify_sb.s_magic == EXT4_SUPER_MAGIC {
debug!(
"Format completed, superblock magic verified: {:#x}",
verify_sb.s_magic
);
Ok(())
} else {
debug!("Superblock magic verification failed");
Err(Ext4Error::corrupted())
}
}
fn build_superblock(total_blocks: u64, layout: &FsLayoutInfo) -> Ext4Superblock {
let mut sb = Ext4Superblock {
s_magic: EXT4_SUPER_MAGIC,
s_blocks_count_lo: (total_blocks & 0xFFFFFFFF) as u32,
s_blocks_count_hi: (total_blocks >> 32) as u32,
s_log_block_size: LOG_BLOCK_SIZE,
s_log_cluster_size: LOG_BLOCK_SIZE,
s_blocks_per_group: layout.blocks_per_group,
s_inodes_per_group: layout.inodes_per_group,
s_clusters_per_group: layout.blocks_per_group,
s_inodes_count: layout.groups * layout.inodes_per_group,
s_inode_size: layout.inode_size,
s_first_ino: RESERVED_INODES + 1,
s_first_data_block: layout.first_data_block,
s_r_blocks_count_lo: (layout.reserved_blocks & 0xFFFFFFFF) as u32,
s_r_blocks_count_hi: (layout.reserved_blocks >> 32) as u32,
..Default::default()
};
let uuid = generate_uuid();
sb.s_hash_seed = uuid.0;
let filesys_uuid = generate_uuid_8();
sb.s_uuid = filesys_uuid;
let metadata_blocks = layout.group0_metadata_blocks as u64;
let mut free_blocks = total_blocks
.saturating_sub(metadata_blocks)
.saturating_sub(layout.reserved_blocks);
if free_blocks > total_blocks {
free_blocks = 0;
}
sb.s_free_blocks_count_lo = (free_blocks & 0xFFFFFFFF) as u32;
sb.s_free_blocks_count_hi = (free_blocks >> 32) as u32;
sb.s_min_extra_isize = 32;
sb.s_want_extra_isize = 32;
sb.s_free_inodes_count = sb.s_inodes_count.saturating_sub(RESERVED_INODES);
sb.s_state = Ext4Superblock::EXT4_VALID_FS;
sb.s_errors = Ext4Superblock::EXT4_ERRORS_RO;
sb.s_creator_os = Ext4Superblock::EXT4_OS_LINUX;
sb.s_rev_level = Ext4Superblock::EXT4_DYNAMIC_REV;
sb.s_feature_compat = DEFAULT_FEATURE_COMPAT;
sb.s_feature_incompat = DEFAULT_FEATURE_INCOMPAT;
sb.s_feature_ro_compat = DEFAULT_FEATURE_RO_COMPAT;
sb.s_desc_size = layout.desc_size;
sb.s_reserved_gdt_blocks = layout.reserved_gdt_blocks as u16;
sb.s_checksum_type = if ext4_superblock_has_metadata_csum(&sb) {
1
} else {
0
};
sb.update_checksum();
sb
}
fn build_uninit_group_desc(
sb: &Ext4Superblock,
group_id: u32,
layout: &FsLayoutInfo,
) -> Ext4GroupDesc {
let mut desc = Ext4GroupDesc::default();
let gl = cloc_group_layout(
group_id,
sb,
layout.blocks_per_group,
layout.inode_table_blocks,
layout.group0_block_bitmap,
layout.group0_inode_bitmap,
layout.group0_inode_table,
layout.gdt_blocks,
);
desc.bg_block_bitmap_lo = gl.group_blcok_bitmap_startblocks as u32;
desc.bg_inode_bitmap_lo = gl.group_inode_bitmap_startblocks as u32;
desc.bg_inode_table_lo = gl.group_inode_table_startblocks as u32;
let free_blocks = group_free_blocks(layout, group_id, gl.metadata_blocks_in_group);
if group_id == 0 {
desc.bg_free_blocks_count_lo = free_blocks as u16;
desc.bg_free_inodes_count_lo =
layout.inodes_per_group.saturating_sub(RESERVED_INODES) as u16;
desc.bg_itable_unused_lo = layout.inodes_per_group.saturating_sub(RESERVED_INODES) as u16;
} else {
desc.bg_free_blocks_count_lo = free_blocks as u16;
desc.bg_free_inodes_count_lo = layout.inodes_per_group as u16;
desc.bg_itable_unused_lo = layout.inodes_per_group as u16;
}
desc.bg_free_blocks_count_hi = 0;
desc.bg_free_inodes_count_hi = 0;
desc.bg_used_dirs_count_lo = 0;
desc.bg_used_dirs_count_hi = 0;
desc.bg_flags = 0;
desc
}
fn write_superblock_redundant_backup<B: BlockDevice>(
block_dev: &mut Jbd2Dev<B>,
sb: &Ext4Superblock,
groups_count: u32,
fs_layout: &FsLayoutInfo,
) -> Ext4Result<()> {
let sprse_feature =
sb.has_feature_ro_compat(Ext4Superblock::EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER);
if sprse_feature {
for gid in 1..groups_count {
let group_layout = cloc_group_layout(
gid,
sb,
fs_layout.blocks_per_group,
fs_layout.inode_table_blocks,
fs_layout.group0_block_bitmap,
fs_layout.group0_inode_bitmap,
fs_layout.group0_inode_table,
fs_layout.gdt_blocks,
);
if need_redundant_backup(gid) {
let super_blocks = group_layout.group_start_block;
block_dev
.read_block(AbsoluteBN::new(super_blocks))
.expect("Superblock read failed!");
let buffer = block_dev.buffer_mut();
sb.to_disk_bytes(&mut buffer[0..SUPERBLOCK_SIZE]);
block_dev.write_block(AbsoluteBN::new(super_blocks), true)?;
}
}
}
Ok(())
}
pub(crate) fn write_superblock<B: BlockDevice>(
block_dev: &mut Jbd2Dev<B>,
sb: &Ext4Superblock,
) -> Ext4Result<()> {
if BLOCK_SIZE == 1024 {
block_dev.read_block(AbsoluteBN::from(1u32))?;
let buffer = block_dev.buffer_mut();
sb.to_disk_bytes(&mut buffer[0..SUPERBLOCK_SIZE]);
block_dev.write_block(AbsoluteBN::from(1u32), true)?;
} else {
block_dev.read_block(AbsoluteBN::from(0u32))?;
let buffer = block_dev.buffer_mut();
let offset = Ext4Superblock::SUPERBLOCK_OFFSET as usize;
let end = offset + Ext4Superblock::SUPERBLOCK_SIZE;
sb.to_disk_bytes(&mut buffer[offset..end]);
block_dev.write_block(AbsoluteBN::from(0u32), true)?;
}
Ok(())
}
pub(crate) fn read_superblock<B: BlockDevice>(
block_dev: &mut Jbd2Dev<B>,
) -> Ext4Result<Ext4Superblock> {
if BLOCK_SIZE == 1024 {
block_dev.read_block(AbsoluteBN::from(1u32))?;
let buffer = block_dev.buffer();
let sb = Ext4Superblock::from_disk_bytes(&buffer[0..SUPERBLOCK_SIZE]);
Ok(sb)
} else {
block_dev.read_block(AbsoluteBN::from(0u32))?;
let buffer = block_dev.buffer();
let offset = Ext4Superblock::SUPERBLOCK_OFFSET as usize;
let end = offset + Ext4Superblock::SUPERBLOCK_SIZE;
let sb = Ext4Superblock::from_disk_bytes(&buffer[offset..end]);
Ok(sb)
}
}
fn write_gdt_redundant_backup<B: BlockDevice>(
block_dev: &mut Jbd2Dev<B>,
descs: &VecDeque<Ext4GroupDesc>,
sb: &Ext4Superblock,
groups_count: u32,
fs_layout: &FsLayoutInfo,
) -> Ext4Result<()> {
let desc_size = sb.get_desc_size();
let desc_all_size = descs.len() * desc_size as usize;
let can_recive_size = fs_layout.gdt_blocks * fs_layout.descs_per_block * desc_size as u32;
if can_recive_size < desc_all_size as u32 {
return Err(Ext4Error::buffer_too_small(
can_recive_size as usize,
desc_all_size,
));
}
let sprse_feature =
sb.has_feature_ro_compat(Ext4Superblock::EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER);
if sprse_feature {
for gid in 1..groups_count {
if need_redundant_backup(gid) {
let group_layout = cloc_group_layout(
gid,
sb,
fs_layout.blocks_per_group,
fs_layout.inode_table_blocks,
fs_layout.group0_block_bitmap,
fs_layout.group0_inode_bitmap,
fs_layout.group0_inode_table,
fs_layout.gdt_blocks,
);
let gdt_start = group_layout.group_start_block + 1;
let mut desc_iter = descs.iter();
for gdt_block_id in gdt_start..group_layout.group_blcok_bitmap_startblocks {
block_dev.read_block(AbsoluteBN::new(gdt_block_id))?;
let buffer = block_dev.buffer_mut();
let mut current_offset = 0_usize;
for _ in 0..fs_layout.descs_per_block {
if let Some(desc) = desc_iter.next() {
desc.to_disk_bytes(
&mut buffer[current_offset..current_offset + desc_size as usize],
);
current_offset += desc_size as usize;
}
}
block_dev.write_block(AbsoluteBN::new(gdt_block_id), true)?;
}
}
}
}
Ok(())
}
fn write_group_desc<B: BlockDevice>(
block_dev: &mut Jbd2Dev<B>,
group_id: u32,
desc: &mut Ext4GroupDesc,
) -> Ext4Result<()> {
let superblock = read_superblock(block_dev)?;
let desc_size = superblock.get_desc_size() as usize;
let gdt_base: u64 = BLOCK_SIZE as u64;
let byte_offset = gdt_base + group_id as u64 * desc_size as u64;
let block_size_u64 = BLOCK_SIZE as u64;
let block_num = byte_offset / block_size_u64;
let in_block = (byte_offset % block_size_u64) as usize;
let end = in_block + desc_size;
let inode_bitmap_blk = desc.inode_bitmap() as u32;
block_dev.read_block(inode_bitmap_blk.into())?;
let inode_bitmap_bytes = block_dev.buffer().to_vec();
let block_bitmap_blk = desc.block_bitmap() as u32;
block_dev.read_block(block_bitmap_blk.into())?;
let block_bitmap_bytes = block_dev.buffer().to_vec();
desc.update_checksum(
&superblock,
group_id,
Some(&block_bitmap_bytes),
Some(&inode_bitmap_bytes),
);
block_dev.read_block(AbsoluteBN::new(block_num))?;
let buffer = block_dev.buffer_mut();
if end > buffer.len() {
return Err(Ext4Error::corrupted());
}
desc.to_disk_bytes(&mut buffer[in_block..end]);
block_dev.write_block(AbsoluteBN::new(block_num), true)?;
Ok(())
}
fn initialize_group_0<B: BlockDevice>(
block_dev: &mut Jbd2Dev<B>,
layout: &FsLayoutInfo,
) -> Ext4Result<()> {
let block_bitmap_blk = layout.group0_block_bitmap;
let inode_bitmap_blk = layout.group0_inode_bitmap;
let inode_table_blk = layout.group0_inode_table;
{
let buffer = block_dev.buffer_mut();
buffer.fill(0);
mark_bitmap_range_allocated(buffer, 0, layout.group0_metadata_blocks);
mark_block_bitmap_padding(buffer, layout, 0);
}
block_dev.write_block(block_bitmap_blk.into(), true)?;
{
let buffer = block_dev.buffer_mut();
buffer.fill(0);
for i in 0..RESERVED_INODES {
let byte_idx = (i / 8) as usize;
let bit_idx = i % 8;
buffer[byte_idx] |= 1 << bit_idx;
}
let bits_per_group = BLOCK_SIZE_U32 * 8;
for i in layout.inodes_per_group..bits_per_group {
let byte_idx: usize = (i / 8) as usize;
let bit_idx = i % 8;
buffer[byte_idx] |= 1 << bit_idx;
}
}
block_dev.write_block(inode_bitmap_blk.into(), true)?;
{
let buffer = block_dev.buffer_mut();
buffer.fill(0);
}
for i in 0..layout.inode_table_blocks {
block_dev.write_block((inode_table_blk + i).into(), true)?;
}
let mut desc = Ext4GroupDesc {
bg_flags: Ext4GroupDesc::EXT4_BG_INODE_ZEROED,
bg_free_blocks_count_lo: group_free_blocks(layout, 0, layout.group0_metadata_blocks) as u16,
bg_free_inodes_count_lo: layout.inodes_per_group.saturating_sub(RESERVED_INODES) as u16,
bg_itable_unused_lo: layout.inodes_per_group.saturating_sub(RESERVED_INODES) as u16,
bg_block_bitmap_lo: block_bitmap_blk,
bg_inode_bitmap_lo: inode_bitmap_blk,
bg_inode_table_lo: inode_table_blk,
..Default::default()
};
write_group_desc(block_dev, 0, &mut desc)?;
Ok(())
}
fn initialize_other_groups_bitmaps<B: BlockDevice>(
block_dev: &mut Jbd2Dev<B>,
layout: &FsLayoutInfo,
sb: &Ext4Superblock,
) -> Ext4Result<()> {
for group_id in 1..layout.groups {
let gl = cloc_group_layout(
group_id,
sb,
layout.blocks_per_group,
layout.inode_table_blocks,
layout.group0_block_bitmap,
layout.group0_inode_bitmap,
layout.group0_inode_table,
layout.gdt_blocks,
);
let block_bitmap_blk = gl.group_blcok_bitmap_startblocks as u32;
let inode_bitmap_blk = gl.group_inode_bitmap_startblocks as u32;
{
let buffer = block_dev.buffer_mut();
buffer.fill(0);
mark_bitmap_range_allocated(buffer, 0, gl.metadata_blocks_in_group);
mark_block_bitmap_padding(buffer, layout, group_id);
}
block_dev.write_block(block_bitmap_blk.into(), true)?;
{
let buffer = block_dev.buffer_mut();
buffer.fill(0);
let bits_per_group = BLOCK_SIZE_U32 * 8;
for i in layout.inodes_per_group..bits_per_group {
let byte_idx: usize = (i / 8) as usize;
let bit_idx = i % 8;
buffer[byte_idx] |= 1 << bit_idx;
}
}
block_dev.write_block(inode_bitmap_blk.into(), true)?;
}
Ok(())
}