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 const MIN_BLOCK_SIZE: u32 = 1024;
pub const MAX_BLOCK_SIZE: u32 = 65536;
#[must_use]
pub fn is_valid_block_size(block_size: u32) -> bool {
block_size.is_power_of_two() && (MIN_BLOCK_SIZE..=MAX_BLOCK_SIZE).contains(&block_size)
}
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, desc_size, csum_enabled, dir_csum_tail) = flavor.geometry();
let ext3_journal_blocks: u32 = if matches!(flavor, FsFlavor::Ext3) {
1024
} else {
0
};
const EXT3_JOURNAL_INODE: u32 = 8;
if !is_valid_block_size(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 matches!(flavor, FsFlavor::Ext4) && block_size >= 2048 {
return format_block_groups(dev, label, uuid, size_bytes, block_size);
}
if blocks_count > blocks_per_group as u64 {
return Err(Error::InvalidArgument(
"mkfs: multi-group volumes require ext4 with block_size >= 2048",
));
}
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;
let journal_indirect_blocks: u64 = if matches!(flavor, FsFlavor::Ext3) {
crate::indirect_mut::count_indirect_blocks(ext3_journal_blocks, block_size)
} else {
0
};
let journal_end: u64 = journal_data_end + journal_indirect_blocks;
if journal_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_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(
inodes_per_group,
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_group(
&mut bgd,
blk_bitmap,
ino_bitmap,
inode_table_start,
free_blocks,
free_inodes,
1, desc_size,
);
let fdb = first_data_block as u64;
let mut block_bitmap = vec![0u8; block_size as usize];
set_bitmap_range(&mut block_bitmap, 0, used_blocks - fdb);
set_bitmap_range(
&mut block_bitmap,
blocks_count - fdb,
blocks_per_group as u64,
);
let mut inode_bitmap = vec![0u8; block_size as usize];
set_bitmap_range(&mut inode_bitmap, 0, RESERVED_INODES as u64);
set_bitmap_range(
&mut inode_bitmap,
inodes_per_group as u64,
block_size as u64 * 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,
&csum,
);
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 = journal_indirect_blocks;
let mut next_indirect = journal_data_end;
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;
}
let root_dir = build_root_dir(block_size, dir_csum_tail, &csum)?;
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())]);
finalize_bgd_checksum(&mut bgd, 0, bb_csum, ib_csum, desc_size, &csum);
}
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()?;
Ok(())
}
fn set_bitmap_range(bitmap: &mut [u8], start: u64, end: u64) {
for bit in start..end {
let byte = (bit / 8) as usize;
if byte >= bitmap.len() {
break;
}
bitmap[byte] |= 1 << (bit % 8);
}
}
fn build_root_dir(block_size: u32, dir_csum_tail: usize, csum: &Checksummer) -> Result<Vec<u8>> {
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());
}
Ok(root_dir)
}
fn finalize_bgd_checksum(
bgd: &mut [u8],
group: u32,
bb_csum: u32,
ib_csum: u32,
desc_size: u16,
csum: &Checksummer,
) {
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.to_vec();
tmp[0x1E] = 0;
tmp[0x1F] = 0;
let bgd_csum = (csum.crc_with_prefix(group, &tmp) & 0xFFFF) as u16;
bgd[0x1E..0x20].copy_from_slice(&bgd_csum.to_le_bytes());
}
#[allow(clippy::too_many_lines)]
fn format_block_groups(
dev: &dyn BlockDevice,
label: Option<&str>,
uuid: Option<[u8; 16]>,
size_bytes: u64,
block_size: u32,
) -> Result<()> {
const RESERVED_INODES: u32 = 10;
let (inode_size, desc_size, _, dir_csum_tail) = FsFlavor::Ext4.geometry();
let bs = block_size as u64;
let log_block_size = (block_size.trailing_zeros() as i32 - 10) as u32;
let blocks_per_group: u32 = 8 * block_size;
let bpg = blocks_per_group as u64;
let inodes_per_group: u32 = 8192;
let inode_table_blocks: u32 = (inodes_per_group as u64 * inode_size as u64).div_ceil(bs) as u32;
let blocks_count: u64 = size_bytes / bs;
let group_count: u64 = blocks_count.div_ceil(bpg);
let gdt_blocks: u32 = (group_count * desc_size as u64).div_ceil(bs) as u32;
if bpg <= 4 + gdt_blocks as u64 + inode_table_blocks as u64 {
return Err(Error::InvalidArgument(
"mkfs: group descriptor table does not fit in a single block group",
));
}
let inodes_count: u64 = group_count * inodes_per_group as u64;
if inodes_count > u32::MAX as u64 {
return Err(Error::InvalidArgument("mkfs: too many inodes for layout"));
}
let total_free_inodes: u64 = inodes_count - RESERVED_INODES as u64;
let uuid = uuid.unwrap_or_else(generate_uuid);
let csum = Checksummer {
seed: linux_crc32c(!0, &uuid),
enabled: true,
};
let mut gdt = vec![0u8; gdt_blocks as usize * block_size as usize];
let mut total_free_blocks: u64 = 0;
let mut root_dir_block: u64 = 0;
for g in 0..group_count {
let gstart = g * bpg;
let glen = bpg.min(blocks_count - gstart);
let group_meta = if group_has_super(g) {
1 + gdt_blocks as u64
} else {
0
};
let bb_block = gstart + group_meta;
let ib_block = bb_block + 1;
let it_block = ib_block + 1;
let data_start = it_block + inode_table_blocks as u64;
let used = (data_start - gstart) + u64::from(g == 0);
if g == 0 {
root_dir_block = data_start;
}
let free = glen.checked_sub(used).ok_or(Error::InvalidArgument(
"mkfs: group overhead exceeds group size",
))?;
total_free_blocks += free;
let mut block_bitmap = vec![0u8; block_size as usize];
set_bitmap_range(&mut block_bitmap, 0, used);
set_bitmap_range(&mut block_bitmap, glen, bpg);
let bb_csum = csum.crc(&block_bitmap[..blocks_per_group as usize / 8]);
let mut inode_bitmap = vec![0u8; block_size as usize];
if g == 0 {
set_bitmap_range(&mut inode_bitmap, 0, RESERVED_INODES as u64);
}
set_bitmap_range(&mut inode_bitmap, inodes_per_group as u64, bs * 8);
let ib_csum = csum.crc(&inode_bitmap[..inodes_per_group as usize / 8]);
let mut inode_table = vec![0u8; inode_table_blocks as usize * block_size as usize];
if g == 0 {
let off = (EXT4_ROOT_INO as usize - 1) * inode_size as usize;
write_root_inode(
&mut inode_table[off..off + inode_size as usize],
root_dir_block,
block_size,
FsFlavor::Ext4,
inode_size,
&csum,
);
}
let free_inodes_g = if g == 0 {
inodes_per_group - RESERVED_INODES
} else {
inodes_per_group
};
let used_dirs_g = u32::from(g == 0);
let bgd = &mut gdt[g as usize * desc_size as usize..(g as usize + 1) * desc_size as usize];
write_bgd_group(
bgd,
bb_block,
ib_block,
it_block,
free,
free_inodes_g,
used_dirs_g,
desc_size,
);
finalize_bgd_checksum(bgd, g as u32, bb_csum, ib_csum, desc_size, &csum);
dev.write_at(bb_block * bs, &block_bitmap)?;
dev.write_at(ib_block * bs, &inode_bitmap)?;
dev.write_at(it_block * bs, &inode_table)?;
}
let mut sb = build_superblock(
inodes_count as u32,
blocks_count,
total_free_blocks,
total_free_inodes as u32,
0,
log_block_size,
blocks_per_group,
inodes_per_group,
&uuid,
label.unwrap_or(""),
FsFlavor::Ext4,
inode_size,
desc_size,
0,
);
let c = linux_crc32c(!0, &sb[..0x3FC]);
sb[0x3FC..0x400].copy_from_slice(&c.to_le_bytes());
let root_dir = build_root_dir(block_size, dir_csum_tail, &csum)?;
dev.write_at(0, &vec![0u8; block_size as usize])?;
dev.write_at(crate::superblock::SUPERBLOCK_OFFSET, &sb)?;
dev.write_at(bs, &gdt)?;
dev.write_at(root_dir_block * bs, &root_dir)?;
for g in 1..group_count {
if !group_has_super(g) {
continue;
}
let gstart = g * bpg;
let mut sb_blk = vec![0u8; block_size as usize];
sb_blk[..sb.len()].copy_from_slice(&sb);
sb_blk[0x5A..0x5C].copy_from_slice(&(g as u16).to_le_bytes());
let c = linux_crc32c(!0, &sb_blk[..0x3FC]);
sb_blk[0x3FC..0x400].copy_from_slice(&c.to_le_bytes());
dev.write_at(gstart * bs, &sb_blk)?;
dev.write_at((gstart + 1) * bs, &gdt)?;
}
dev.flush()?;
Ok(())
}
pub(crate) use crate::superblock::classic_sparse_super as group_has_super;
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(
inodes_count: u32,
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 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() | RoCompat::SPARSE_SUPER.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_group(
out: &mut [u8],
block_bitmap_block: u64,
inode_bitmap_block: u64,
inode_table_block: u64,
free_blocks: u64,
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(&((free_blocks >> 16) as u16).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,
csum: &Checksummer,
) {
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());
}
if csum.enabled {
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());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_block_sizes_are_powers_of_two_in_range() {
for bs in [1024, 2048, 4096, 8192, 16384, 32768, 65536] {
assert!(is_valid_block_size(bs), "{bs} should be accepted");
}
for bs in [0, 1, 512, 1023, 3000, 4095, 4097, 131_072] {
assert!(!is_valid_block_size(bs), "{bs} should be rejected");
}
}
#[test]
fn sparse_super_backups_land_in_powers_of_three_five_and_seven() {
let with_super: Vec<u64> = (0..60).filter(|g| group_has_super(*g)).collect();
assert_eq!(
with_super,
vec![0, 1, 3, 5, 7, 9, 25, 27, 49],
"sparse-super group set"
);
}
#[test]
fn set_bitmap_range_sets_a_half_open_run_of_bits() {
let mut bitmap = vec![0u8; 4];
set_bitmap_range(&mut bitmap, 3, 11);
assert_eq!(bitmap, [0b1111_1000, 0b0000_0111, 0, 0]);
}
}