feat(ext4): block and inode bitmap allocation/deallocation

This commit is contained in:
2026-10-18 23:12:33 +02:00
parent 381669d9dd
commit fe0693b969
4 changed files with 239 additions and 59 deletions

View File

@@ -12,57 +12,79 @@ fn write_u16(buf: &mut [u8], offset: usize, val: u16) {
}
fn format_ext4_disk() -> MockBlockDevice {
let mut dev = MockBlockDevice::new(64);
let mut dev = MockBlockDevice::new(256);
let mut buf = [0u8; 512];
// Superblock at sectors 2-3 (byte offset 1024)
// Sector 2: first 512 bytes of superblock
write_u32(&mut buf, 0x00, 32); // s_inodes_count
write_u32(&mut buf, 0x04, 64); // s_blocks_count
write_u32(&mut buf, 0x14, 1); // s_first_data_block = 1 (1K blocks would, but we use 0 for 4K)
write_u32(&mut buf, 0x18, 2); // s_log_block_size = 2 → 1024 << 2 = 4096
write_u32(&mut buf, 0x20, 64); // s_blocks_per_group
write_u32(&mut buf, 0x28, 32); // s_inodes_per_group
write_u16(&mut buf, 0x38, 0xEF53); // s_magic
write_u16(&mut buf, 0x58, 128); // s_inode_size
write_u32(&mut buf, 0x14, 0); // s_first_data_block = 0 for 4K blocks
// Layout (4K blocks):
// Block 0: superblock (at byte offset 1024, sectors 2-3)
// Block 1: GDT (sectors 8-15)
// Block 2: block bitmap (sectors 16-23)
// Block 3: inode bitmap (sectors 24-31)
// Block 4: inode table (sectors 32-39), 32 inodes * 128 bytes = 4096
// Block 5: root dir data (sectors 40-47)
// Block 6: hello file data (sectors 48-55)
// Blocks 7-31: free (25 blocks)
// Superblock at sectors 2-3
write_u32(&mut buf, 0x00, 32); // s_inodes_count
write_u32(&mut buf, 0x04, 32); // s_blocks_count
write_u32(&mut buf, 0x0C, 25); // s_free_blocks_count (blocks 7-31)
write_u32(&mut buf, 0x10, 29); // s_free_inodes_count (inodes 4-32)
write_u32(&mut buf, 0x14, 0); // s_first_data_block = 0 for 4K blocks
write_u32(&mut buf, 0x18, 2); // s_log_block_size = 2 → 4096
write_u32(&mut buf, 0x20, 32); // s_blocks_per_group
write_u32(&mut buf, 0x28, 32); // s_inodes_per_group
write_u16(&mut buf, 0x38, 0xEF53); // s_magic
write_u16(&mut buf, 0x58, 128); // s_inode_size
dev.write_sector(SectorId::new(2), &buf);
// Block group descriptor at block 1 (sectors 8-15 for 4K blocks)
// Block group descriptor at block 1 (sector 8)
let mut gdt = [0u8; 512];
write_u32(&mut gdt, 8, 3); // bg_inode_table = block 3
write_u32(&mut gdt, 0, 2); // bg_block_bitmap = block 2
write_u32(&mut gdt, 4, 3); // bg_inode_bitmap = block 3
write_u32(&mut gdt, 8, 4); // bg_inode_table = block 4
write_u16(&mut gdt, 12, 25); // bg_free_blocks_count
write_u16(&mut gdt, 14, 29); // bg_free_inodes_count
dev.write_sector(SectorId::new(8), &gdt);
// Inode table at block 3 (sectors 24-31)
// Root inode (inode 2) at offset 128 in the table
// Block bitmap at block 2 (sector 16): blocks 0-6 used, 7-31 free
let mut bbitmap = [0u8; 512];
bbitmap[0] = 0x7F; // bits 0-6 set = blocks 0-6 used
dev.write_sector(SectorId::new(16), &bbitmap);
// Inode bitmap at block 3 (sector 24): inodes 1-3 used, 4-32 free
let mut ibitmap = [0u8; 512];
ibitmap[0] = 0x07; // bits 0-2 set = inodes 1-3 used
dev.write_sector(SectorId::new(24), &ibitmap);
// Inode table at block 4 (sector 32)
let mut inode_sector = [0u8; 512];
// Inode 2 (root dir) at byte offset 128 = still in first sector
let ioff = 128; // inode 2 index = 1, * 128 bytes = 128
write_u16(&mut inode_sector, ioff + 0x00, 0x4000 | 0o755); // mode = directory
write_u32(&mut inode_sector, ioff + 0x04, 4096); // size_lo
write_u32(&mut inode_sector, ioff + 0x20, 0x80000); // flags = uses extents
// Extent header at inode+0x28
// Inode 2 (root dir) at offset 128
let ioff = 128;
write_u16(&mut inode_sector, ioff + 0x00, 0x4000 | 0o755);
write_u32(&mut inode_sector, ioff + 0x04, 4096);
write_u16(&mut inode_sector, ioff + 0x1A, 2); // links_count
write_u32(&mut inode_sector, ioff + 0x1C, 8); // blocks_count (512-byte units: 1 block = 8)
write_u32(&mut inode_sector, ioff + 0x20, 0x80000);
let eh = ioff + 0x28;
write_u16(&mut inode_sector, eh + 0, 0xF30A); // magic
write_u16(&mut inode_sector, eh + 2, 1); // entries
write_u16(&mut inode_sector, eh + 4, 4); // max
write_u16(&mut inode_sector, eh + 6, 0); // depth
// Extent leaf at inode+0x28+12
write_u16(&mut inode_sector, eh + 0, 0xF30A);
write_u16(&mut inode_sector, eh + 2, 1);
write_u16(&mut inode_sector, eh + 4, 4);
write_u16(&mut inode_sector, eh + 6, 0);
let ext = eh + 12;
write_u32(&mut inode_sector, ext + 0, 0); // logical block
write_u16(&mut inode_sector, ext + 4, 1); // length
write_u16(&mut inode_sector, ext + 6, 0); // physical hi
write_u32(&mut inode_sector, ext + 8, 4); // physical lo → block 4
write_u32(&mut inode_sector, ext + 0, 0);
write_u16(&mut inode_sector, ext + 4, 1);
write_u16(&mut inode_sector, ext + 6, 0);
write_u32(&mut inode_sector, ext + 8, 5); // root dir data at block 5
// Inode 3 (a regular file "hello") at byte offset 256
// Inode 3 (file "hello") at offset 256
let ioff2 = 256;
write_u16(&mut inode_sector, ioff2 + 0x00, 0x8000 | 0o644); // mode = regular file
write_u32(&mut inode_sector, ioff2 + 0x04, 13); // size = 13 bytes
write_u32(&mut inode_sector, ioff2 + 0x20, 0x80000); // flags = extents
write_u16(&mut inode_sector, ioff2 + 0x00, 0x8000 | 0o644);
write_u32(&mut inode_sector, ioff2 + 0x04, 13);
write_u16(&mut inode_sector, ioff2 + 0x1A, 1); // links_count
write_u32(&mut inode_sector, ioff2 + 0x1C, 8); // blocks_count
write_u32(&mut inode_sector, ioff2 + 0x20, 0x80000);
let eh2 = ioff2 + 0x28;
write_u16(&mut inode_sector, eh2 + 0, 0xF30A);
write_u16(&mut inode_sector, eh2 + 2, 1);
@@ -72,23 +94,19 @@ fn format_ext4_disk() -> MockBlockDevice {
write_u32(&mut inode_sector, ext2 + 0, 0);
write_u16(&mut inode_sector, ext2 + 4, 1);
write_u16(&mut inode_sector, ext2 + 6, 0);
write_u32(&mut inode_sector, ext2 + 8, 5); // data at block 5
write_u32(&mut inode_sector, ext2 + 8, 6); // file data at block 6
dev.write_sector(SectorId::new(24), &inode_sector);
dev.write_sector(SectorId::new(32), &inode_sector);
// Root directory at block 4 (sectors 32-39)
// Root directory at block 5 (sector 40)
let mut dir = [0u8; 512];
let mut pos = 0;
// Entry: "." → inode 2
write_u32(&mut dir, pos + 0, 2);
write_u16(&mut dir, pos + 4, 12); // rec_len
dir[pos + 6] = 1; // name_len
dir[pos + 7] = 2; // file_type = dir
write_u16(&mut dir, pos + 4, 12);
dir[pos + 6] = 1;
dir[pos + 7] = 2;
dir[pos + 8] = b'.';
pos += 12;
// Entry: ".." → inode 2
write_u32(&mut dir, pos + 0, 2);
write_u16(&mut dir, pos + 4, 12);
dir[pos + 6] = 2;
@@ -96,20 +114,17 @@ fn format_ext4_disk() -> MockBlockDevice {
dir[pos + 8] = b'.';
dir[pos + 9] = b'.';
pos += 12;
// Entry: "hello" → inode 3
write_u32(&mut dir, pos + 0, 3);
write_u16(&mut dir, pos + 4, 488); // rec_len = rest of block
dir[pos + 6] = 5; // name_len
dir[pos + 7] = 1; // file_type = regular
write_u16(&mut dir, pos + 4, 488);
dir[pos + 6] = 5;
dir[pos + 7] = 1;
dir[pos + 8..pos + 13].copy_from_slice(b"hello");
dev.write_sector(SectorId::new(40), &dir);
dev.write_sector(SectorId::new(32), &dir);
// File data at block 5 (sectors 40-47)
// File data at block 6 (sector 48)
let mut data = [0u8; 512];
data[..13].copy_from_slice(b"Hello, ext4!\n");
dev.write_sector(SectorId::new(40), &data);
dev.write_sector(SectorId::new(48), &data);
dev
}
@@ -242,7 +257,7 @@ fn format_ext4_two_extents() -> MockBlockDevice {
dev.write_sector(SectorId::new(32), &dir);
// Block 5: filled with 'A'
let mut data_a = [b'A'; 512];
let data_a = [b'A'; 512];
for s in 0..8 {
dev.write_sector(SectorId::new(40 + s), &data_a);
}
@@ -304,6 +319,29 @@ fn init_empty_inode_has_valid_extent_header() {
assert_eq!(inode.block_data[3], 0);
}
#[test]
fn alloc_block_returns_first_free() {
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();
let block = fs.alloc_block().unwrap();
assert_eq!(block, 7);
}
#[test]
fn alloc_inode_returns_first_free() {
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();
let inode = fs.alloc_inode().unwrap();
assert_eq!(inode, 4);
}
#[test]
fn free_block_makes_it_available_again() {
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();
let b1 = fs.alloc_block().unwrap();
fs.free_block(b1);
let b2 = fs.alloc_block().unwrap();
assert_eq!(b1, b2);
}
#[test]
fn read_past_eof_returns_zero() {
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();