feat(ext4): block and inode bitmap allocation/deallocation
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user