use crate::block_io::BlockDevice;
use crate::checksum::{linux_crc32c, Checksummer};
use crate::dir::{self, DirEntryType};
use crate::error::{Error, Result};
use crate::features::{Compat, FsFlavor, Incompat, RoCompat};
const EXT4_MAGIC: u16 = 0xEF53;
const EXT4_VALID_FS: u16 = 0x0001;
const EXT4_ROOT_INO: u32 = 2;
const EXT4_GOOD_OLD_INODE_SIZE: u16 = 128;
const I_EXTRA_ISIZE: u16 = 32; const ROOT_MODE: u16 = 0o40755; const EXTENT_MAGIC: u16 = 0xF30A;
pub fn format_filesystem(
dev: &dyn BlockDevice,
label: Option<&str>,
uuid: Option<[u8; 16]>,
size_bytes: u64,
block_size: u32,
) -> Result<()> {
format_filesystem_with_flavor(dev, label, uuid, size_bytes, block_size, FsFlavor::Ext4)
}
pub fn format_filesystem_with_flavor(
dev: &dyn BlockDevice,
label: Option<&str>,
uuid: Option<[u8; 16]>,
size_bytes: u64,
block_size: u32,
flavor: FsFlavor,
) -> Result<()> {
let inode_size: u16 = match flavor {
FsFlavor::Ext2 | FsFlavor::Ext3 => 128,
FsFlavor::Ext4 => 256,
};
let desc_size: u16 = match flavor {
FsFlavor::Ext2 | FsFlavor::Ext3 => 32, FsFlavor::Ext4 => 64,
};
let csum_enabled = matches!(flavor, FsFlavor::Ext4);
let dir_csum_tail: usize = if csum_enabled { 12 } else { 0 };
let ext3_journal_blocks: u32 = if matches!(flavor, FsFlavor::Ext3) {
1024
} else {
0
};
const EXT3_JOURNAL_INODE: u32 = 8;
if !block_size.is_power_of_two() || !(1024..=65536).contains(&block_size) {
return Err(Error::InvalidArgument("mkfs: block_size out of range"));
}
if size_bytes < block_size as u64 * 64 {
return Err(Error::InvalidArgument("mkfs: device too small"));
}
if !dev.is_writable() {
return Err(Error::ReadOnly);
}
let log_block_size = (block_size.trailing_zeros() as i32 - 10) as u32;
let blocks_per_group: u32 = 8 * block_size; let blocks_count: u64 = size_bytes / block_size as u64;
if blocks_count < 64 {
return Err(Error::InvalidArgument("mkfs: too few blocks"));
}
if blocks_count > blocks_per_group as u64 {
return Err(Error::InvalidArgument(
"mkfs: multi-group volumes not yet supported (v1 single-group only)",
));
}
let group_count: u64 = 1;
let inodes_per_group: u32 = 8192;
let inode_table_blocks: u32 =
(inodes_per_group as u64 * inode_size as u64).div_ceil(block_size as u64) as u32;
let first_data_block: u32 = if block_size == 1024 { 1 } else { 0 };
let bgt_block: u64 = first_data_block as u64 + 1;
let blk_bitmap: u64 = first_data_block as u64 + 2;
let ino_bitmap: u64 = first_data_block as u64 + 3;
let inode_table_start: u64 = first_data_block as u64 + 4;
let root_dir_block: u64 = inode_table_start + inode_table_blocks as u64;
let journal_data_start: u64 = root_dir_block + 1;
let journal_data_end: u64 = journal_data_start + ext3_journal_blocks as u64;
if journal_data_end >= blocks_count {
return Err(Error::InvalidArgument(
"mkfs: device too small for layout (journal won't fit)",
));
}
const RESERVED_INODES: u32 = 10;
let used_blocks: u64 = if matches!(flavor, FsFlavor::Ext3) {
journal_data_end } else {
root_dir_block + 1
};
let free_blocks: u64 = blocks_count - used_blocks;
let free_inodes: u32 = inodes_per_group - RESERVED_INODES;
let uuid = uuid.unwrap_or_else(generate_uuid);
let journal_inum_for_sb: u32 = if matches!(flavor, FsFlavor::Ext3) {
EXT3_JOURNAL_INODE
} else {
0
};
let mut sb = build_superblock(
blocks_count,
free_blocks,
free_inodes,
first_data_block,
log_block_size,
blocks_per_group,
inodes_per_group,
&uuid,
label.unwrap_or(""),
flavor,
inode_size,
desc_size,
journal_inum_for_sb,
);
if csum_enabled {
let sb_csum = linux_crc32c(!0, &sb[..0x3FC]);
sb[0x3FC..0x400].copy_from_slice(&sb_csum.to_le_bytes());
}
let csum_seed = linux_crc32c(!0, &uuid);
let csum = Checksummer {
seed: csum_seed,
enabled: csum_enabled,
};
let mut bgd = vec![0u8; desc_size as usize];
write_bgd_group0(
&mut bgd,
blk_bitmap,
ino_bitmap,
inode_table_start,
free_blocks as u32,
free_inodes,
1, desc_size,
);
let mut block_bitmap = vec![0u8; block_size as usize];
for b in 0..used_blocks {
let byte = (b / 8) as usize;
let bit = (b % 8) as u8;
if byte < block_bitmap.len() {
block_bitmap[byte] |= 1 << bit;
}
}
for b in blocks_count..blocks_per_group as u64 {
let byte = (b / 8) as usize;
if byte >= block_bitmap.len() {
break;
}
let bit = (b % 8) as u8;
block_bitmap[byte] |= 1 << bit;
}
let mut inode_bitmap = vec![0u8; block_size as usize];
for i in 0..RESERVED_INODES {
inode_bitmap[(i / 8) as usize] |= 1 << (i % 8);
}
let bitmap_bits = block_size * 8;
for i in inodes_per_group..bitmap_bits {
inode_bitmap[(i / 8) as usize] |= 1 << (i % 8);
}
let mut inode_table = vec![0u8; inode_table_blocks as usize * block_size as usize];
let root_inode_off = (EXT4_ROOT_INO as usize - 1) * inode_size as usize;
write_root_inode(
&mut inode_table[root_inode_off..root_inode_off + inode_size as usize],
root_dir_block,
block_size,
flavor,
inode_size,
);
if csum_enabled {
let slot = &mut inode_table[root_inode_off..root_inode_off + inode_size as usize];
if let Some((lo, hi)) = csum.compute_inode_checksum(EXT4_ROOT_INO, 0, slot) {
slot[0x7C..0x7E].copy_from_slice(&lo.to_le_bytes());
slot[0x82..0x84].copy_from_slice(&hi.to_le_bytes());
}
}
let mut journal_indirect_writes: Vec<(u64, Vec<u8>)> = Vec::new();
if matches!(flavor, FsFlavor::Ext3) {
let jino_off = (EXT3_JOURNAL_INODE as usize - 1) * inode_size as usize;
let n_indirect =
crate::indirect_mut::count_indirect_blocks(ext3_journal_blocks, block_size);
let journal_indirect_start: u64 = journal_data_end;
let mut next_indirect = journal_indirect_start;
let plan = crate::indirect_mut::plan_contiguous(
ext3_journal_blocks,
journal_data_start,
block_size,
|| {
let v = next_indirect;
next_indirect += 1;
Ok(v)
},
)?;
write_journal_inode(
&mut inode_table[jino_off..jino_off + inode_size as usize],
ext3_journal_blocks as u64 * block_size as u64,
(ext3_journal_blocks as u64 + n_indirect) * block_size as u64 / 512,
&plan.i_block,
);
journal_indirect_writes = plan.block_writes;
for ib in &plan.indirect_blocks_allocated {
let byte = (*ib / 8) as usize;
let bit = (*ib % 8) as u8;
if byte < block_bitmap.len() {
block_bitmap[byte] |= 1 << bit;
}
}
}
let mut root_dir = vec![0u8; block_size as usize];
let usable = block_size as usize - dir_csum_tail;
root_dir[0..4].copy_from_slice(&0u32.to_le_bytes()); root_dir[4..6].copy_from_slice(&(usable as u16).to_le_bytes());
dir::add_entry_to_block(
&mut root_dir,
EXT4_ROOT_INO,
b".",
DirEntryType::Directory,
true,
dir_csum_tail,
)?;
dir::add_entry_to_block(
&mut root_dir,
EXT4_ROOT_INO,
b"..",
DirEntryType::Directory,
true,
dir_csum_tail,
)?;
if csum_enabled {
let end = root_dir.len();
root_dir[end - 12..end - 8].copy_from_slice(&0u32.to_le_bytes()); root_dir[end - 8..end - 6].copy_from_slice(&12u16.to_le_bytes()); root_dir[end - 6] = 0; root_dir[end - 5] = 0xDE; root_dir[end - 4..end].copy_from_slice(&0u32.to_le_bytes());
let mut c = linux_crc32c(csum.seed, &EXT4_ROOT_INO.to_le_bytes());
c = linux_crc32c(c, &0u32.to_le_bytes()); c = linux_crc32c(c, &root_dir[..root_dir.len() - 12]);
root_dir[end - 4..end].copy_from_slice(&c.to_le_bytes());
}
if csum_enabled {
let bb_sz = (blocks_per_group as usize) / 8;
let ib_sz = (inodes_per_group as usize) / 8;
let bb_csum = csum.crc(&block_bitmap[..bb_sz.min(block_bitmap.len())]);
let ib_csum = csum.crc(&inode_bitmap[..ib_sz.min(inode_bitmap.len())]);
bgd[0x18..0x1A].copy_from_slice(&((bb_csum & 0xFFFF) as u16).to_le_bytes());
bgd[0x1A..0x1C].copy_from_slice(&((ib_csum & 0xFFFF) as u16).to_le_bytes());
if desc_size >= 64 {
bgd[0x38..0x3A].copy_from_slice(&((bb_csum >> 16) as u16).to_le_bytes());
bgd[0x3A..0x3C].copy_from_slice(&((ib_csum >> 16) as u16).to_le_bytes());
}
let mut tmp = bgd.clone();
tmp[0x1E] = 0;
tmp[0x1F] = 0;
let bgd_csum_full = csum.crc_with_prefix(0u32, &tmp);
let bgd_csum16 = (bgd_csum_full & 0xFFFF) as u16;
bgd[0x1E..0x20].copy_from_slice(&bgd_csum16.to_le_bytes());
}
let header_zero_len = (block_size as usize).max(2048);
let zeros = vec![0u8; header_zero_len];
dev.write_at(0, &zeros)?;
dev.write_at(crate::superblock::SUPERBLOCK_OFFSET, &sb)?;
dev.flush()?;
let mut bgt_block_buf = vec![0u8; block_size as usize];
bgt_block_buf[..desc_size as usize].copy_from_slice(&bgd);
dev.write_at(bgt_block * block_size as u64, &bgt_block_buf)?;
dev.write_at(blk_bitmap * block_size as u64, &block_bitmap)?;
dev.write_at(ino_bitmap * block_size as u64, &inode_bitmap)?;
dev.write_at(inode_table_start * block_size as u64, &inode_table)?;
dev.write_at(root_dir_block * block_size as u64, &root_dir)?;
if matches!(flavor, FsFlavor::Ext3) {
let jsb_block = build_jbd2_superblock(block_size, ext3_journal_blocks, &uuid);
dev.write_at(journal_data_start * block_size as u64, &jsb_block)?;
for (blk, buf) in &journal_indirect_writes {
dev.write_at(blk * block_size as u64, buf)?;
}
}
dev.flush()?;
let _ = group_count; Ok(())
}
fn build_jbd2_superblock(block_size: u32, max_len: u32, uuid: &[u8; 16]) -> Vec<u8> {
let mut buf = vec![0u8; block_size as usize];
buf[0x00..0x04].copy_from_slice(&crate::jbd2::JBD2_MAGIC_NUMBER.to_be_bytes());
buf[0x04..0x08].copy_from_slice(&crate::jbd2::JBD2_SUPERBLOCK_V2.to_be_bytes());
buf[0x08..0x0C].copy_from_slice(&1u32.to_be_bytes()); buf[0x0C..0x10].copy_from_slice(&block_size.to_be_bytes());
buf[0x10..0x14].copy_from_slice(&max_len.to_be_bytes());
buf[0x14..0x18].copy_from_slice(&1u32.to_be_bytes()); buf[0x18..0x1C].copy_from_slice(&1u32.to_be_bytes()); buf[0x1C..0x20].copy_from_slice(&0u32.to_be_bytes()); buf[0x30..0x40].copy_from_slice(uuid);
buf[0x40..0x44].copy_from_slice(&1u32.to_be_bytes()); buf
}
fn write_journal_inode(slot: &mut [u8], size_bytes: u64, blocks_512: u64, i_block: &[u8; 60]) {
slot[0x00..0x02].copy_from_slice(&0u16.to_le_bytes());
slot[0x04..0x08].copy_from_slice(&((size_bytes & 0xFFFF_FFFF) as u32).to_le_bytes());
slot[0x1A..0x1C].copy_from_slice(&1u16.to_le_bytes());
slot[0x1C..0x20].copy_from_slice(&((blocks_512 & 0xFFFF_FFFF) as u32).to_le_bytes());
slot[0x20..0x24].copy_from_slice(&0u32.to_le_bytes());
slot[0x28..0x28 + 60].copy_from_slice(i_block);
}
fn generate_uuid() -> [u8; 16] {
let mut out = [0u8; 16];
if let Ok(mut f) = std::fs::File::open("/dev/urandom") {
use std::io::Read;
if f.read_exact(&mut out).is_ok() {
out[6] = (out[6] & 0x0F) | 0x40;
out[8] = (out[8] & 0x3F) | 0x80;
return out;
}
}
let mut state = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0xDEADBEEF)
^ (std::process::id() as u64).wrapping_mul(0x9E3779B97F4A7C15);
for b in out.iter_mut() {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
*b = (state >> 56) as u8;
}
out[6] = (out[6] & 0x0F) | 0x40;
out[8] = (out[8] & 0x3F) | 0x80;
out
}
#[allow(clippy::too_many_arguments)]
fn build_superblock(
blocks_count: u64,
free_blocks: u64,
free_inodes: u32,
first_data_block: u32,
log_block_size: u32,
blocks_per_group: u32,
inodes_per_group: u32,
uuid: &[u8; 16],
label: &str,
flavor: FsFlavor,
inode_size: u16,
desc_size: u16,
journal_inum: u32,
) -> Vec<u8> {
let inodes_count: u32 = inodes_per_group;
let mut sb = vec![0u8; 1024];
let blocks_lo = (blocks_count & 0xFFFF_FFFF) as u32;
let blocks_hi = (blocks_count >> 32) as u32;
let free_lo = (free_blocks & 0xFFFF_FFFF) as u32;
let free_hi = (free_blocks >> 32) as u32;
sb[0x00..0x04].copy_from_slice(&inodes_count.to_le_bytes());
sb[0x04..0x08].copy_from_slice(&blocks_lo.to_le_bytes());
sb[0x0C..0x10].copy_from_slice(&free_lo.to_le_bytes());
sb[0x10..0x14].copy_from_slice(&free_inodes.to_le_bytes());
sb[0x14..0x18].copy_from_slice(&first_data_block.to_le_bytes());
sb[0x18..0x1C].copy_from_slice(&log_block_size.to_le_bytes());
sb[0x1C..0x20].copy_from_slice(&log_block_size.to_le_bytes());
sb[0x20..0x24].copy_from_slice(&blocks_per_group.to_le_bytes());
sb[0x24..0x28].copy_from_slice(&blocks_per_group.to_le_bytes());
sb[0x28..0x2C].copy_from_slice(&inodes_per_group.to_le_bytes());
sb[0x34..0x36].copy_from_slice(&0u16.to_le_bytes()); sb[0x36..0x38].copy_from_slice(&0xFFFFu16.to_le_bytes());
sb[0x38..0x3A].copy_from_slice(&EXT4_MAGIC.to_le_bytes());
sb[0x3A..0x3C].copy_from_slice(&EXT4_VALID_FS.to_le_bytes()); sb[0x3C..0x3E].copy_from_slice(&1u16.to_le_bytes()); sb[0x3E..0x40].copy_from_slice(&0u16.to_le_bytes());
sb[0x48..0x4C].copy_from_slice(&0u32.to_le_bytes());
sb[0x4C..0x50].copy_from_slice(&1u32.to_le_bytes()); sb[0x50..0x52].copy_from_slice(&0u16.to_le_bytes()); sb[0x52..0x54].copy_from_slice(&0u16.to_le_bytes());
sb[0x54..0x58].copy_from_slice(&11u32.to_le_bytes()); sb[0x58..0x5A].copy_from_slice(&inode_size.to_le_bytes());
sb[0x5A..0x5C].copy_from_slice(&0u16.to_le_bytes());
let (feat_compat, feat_incompat, feat_ro_compat): (u32, u32, u32) = match flavor {
FsFlavor::Ext2 => (0u32, Incompat::FILETYPE.bits(), 0u32),
FsFlavor::Ext3 => (
Compat::HAS_JOURNAL.bits(),
Incompat::FILETYPE.bits(),
0u32,
),
FsFlavor::Ext4 => (
0u32,
Incompat::FILETYPE.bits() | Incompat::EXTENTS.bits() | Incompat::BIT64.bits(),
RoCompat::METADATA_CSUM.bits(),
),
};
sb[0x5C..0x60].copy_from_slice(&feat_compat.to_le_bytes());
sb[0x60..0x64].copy_from_slice(&feat_incompat.to_le_bytes());
sb[0x64..0x68].copy_from_slice(&feat_ro_compat.to_le_bytes());
sb[0x68..0x78].copy_from_slice(uuid);
let lbl = label.as_bytes();
let n = lbl.len().min(16);
sb[0x78..0x78 + n].copy_from_slice(&lbl[..n]);
sb[0xE0..0xE4].copy_from_slice(&journal_inum.to_le_bytes());
sb[0xE4..0xE8].copy_from_slice(&0xC1A2B3C4u32.to_le_bytes());
sb[0xE8..0xEC].copy_from_slice(&0xD5E6F7A8u32.to_le_bytes());
sb[0xEC..0xF0].copy_from_slice(&0xB9CADBECu32.to_le_bytes());
sb[0xF0..0xF4].copy_from_slice(&0xFD0E1F2Au32.to_le_bytes());
sb[0xFC] = 1; let on_disk_desc_size: u16 = if matches!(flavor, FsFlavor::Ext4) {
desc_size
} else {
0
};
sb[0xFE..0x100].copy_from_slice(&on_disk_desc_size.to_le_bytes());
sb[0x150..0x154].copy_from_slice(&blocks_hi.to_le_bytes());
sb[0x158..0x15C].copy_from_slice(&free_hi.to_le_bytes());
if inode_size >= 160 {
sb[0x15C..0x15E].copy_from_slice(&I_EXTRA_ISIZE.to_le_bytes());
sb[0x15E..0x160].copy_from_slice(&I_EXTRA_ISIZE.to_le_bytes());
}
sb[0x160..0x164].copy_from_slice(&0x1u32.to_le_bytes());
if matches!(flavor, FsFlavor::Ext4) {
sb[0x175] = 1;
}
sb
}
#[allow(clippy::too_many_arguments)]
fn write_bgd_group0(
out: &mut [u8],
block_bitmap_block: u64,
inode_bitmap_block: u64,
inode_table_block: u64,
free_blocks: u32,
free_inodes: u32,
used_dirs: u32,
desc_size: u16,
) {
out[0x00..0x04].copy_from_slice(&(block_bitmap_block as u32).to_le_bytes());
out[0x04..0x08].copy_from_slice(&(inode_bitmap_block as u32).to_le_bytes());
out[0x08..0x0C].copy_from_slice(&(inode_table_block as u32).to_le_bytes());
out[0x0C..0x0E].copy_from_slice(&(free_blocks as u16).to_le_bytes());
out[0x0E..0x10].copy_from_slice(&(free_inodes as u16).to_le_bytes());
out[0x10..0x12].copy_from_slice(&(used_dirs as u16).to_le_bytes());
out[0x12..0x14].copy_from_slice(&0u16.to_le_bytes()); out[0x1C..0x1E].copy_from_slice(&0u16.to_le_bytes());
if desc_size >= 64 {
out[0x20..0x24].copy_from_slice(&((block_bitmap_block >> 32) as u32).to_le_bytes());
out[0x24..0x28].copy_from_slice(&((inode_bitmap_block >> 32) as u32).to_le_bytes());
out[0x28..0x2C].copy_from_slice(&((inode_table_block >> 32) as u32).to_le_bytes());
out[0x2C..0x2E].copy_from_slice(&0u16.to_le_bytes()); out[0x2E..0x30].copy_from_slice(&0u16.to_le_bytes()); out[0x30..0x32].copy_from_slice(&0u16.to_le_bytes()); out[0x32..0x34].copy_from_slice(&0u16.to_le_bytes()); out[0x38..0x3A].copy_from_slice(&0u16.to_le_bytes()); out[0x3A..0x3C].copy_from_slice(&0u16.to_le_bytes()); }
}
fn write_root_inode(
slot: &mut [u8],
root_dir_block: u64,
block_size: u32,
flavor: FsFlavor,
inode_size: u16,
) {
slot[0x00..0x02].copy_from_slice(&ROOT_MODE.to_le_bytes());
slot[0x04..0x08].copy_from_slice(&(block_size).to_le_bytes());
slot[0x1A..0x1C].copy_from_slice(&2u16.to_le_bytes());
let i_blocks = block_size / 512;
slot[0x1C..0x20].copy_from_slice(&i_blocks.to_le_bytes());
if flavor.uses_extents() {
slot[0x20..0x24].copy_from_slice(&crate::inode::InodeFlags::EXTENTS.bits().to_le_bytes());
slot[0x28..0x2A].copy_from_slice(&EXTENT_MAGIC.to_le_bytes());
slot[0x2A..0x2C].copy_from_slice(&1u16.to_le_bytes()); slot[0x2C..0x2E].copy_from_slice(&4u16.to_le_bytes()); slot[0x2E..0x30].copy_from_slice(&0u16.to_le_bytes()); slot[0x30..0x34].copy_from_slice(&0u32.to_le_bytes()); slot[0x34..0x38].copy_from_slice(&0u32.to_le_bytes()); slot[0x38..0x3A].copy_from_slice(&1u16.to_le_bytes()); slot[0x3A..0x3C].copy_from_slice(&((root_dir_block >> 32) as u16).to_le_bytes()); slot[0x3C..0x40].copy_from_slice(&(root_dir_block as u32).to_le_bytes());
} else {
debug_assert!(
root_dir_block <= u32::MAX as u64,
"ext2 i_block pointer overflow"
);
slot[0x20..0x24].copy_from_slice(&0u32.to_le_bytes()); slot[0x28..0x2C].copy_from_slice(&(root_dir_block as u32).to_le_bytes());
}
slot[0x64..0x68].copy_from_slice(&0u32.to_le_bytes());
if inode_size >= 160 && slot.len() > 0x80 {
slot[0x80..0x82].copy_from_slice(&I_EXTRA_ISIZE.to_le_bytes());
}
}