feat(ext4): block and inode bitmap allocation/deallocation
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use port_block_device::{BlockDevice, SectorId};
|
||||
|
||||
const SECTOR_SIZE: usize = 512;
|
||||
const MAX_SECTORS: usize = 64;
|
||||
const MAX_SECTORS: usize = 256;
|
||||
|
||||
pub struct MockBlockDevice {
|
||||
data: [[u8; SECTOR_SIZE]; MAX_SECTORS],
|
||||
|
||||
121
crates/servers/storage-server/src/fs/ext4/bitmap.rs
Normal file
121
crates/servers/storage-server/src/fs/ext4/bitmap.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use port_block_device::BlockDevice;
|
||||
use super::io::{read_block, write_block};
|
||||
use super::superblock::Superblock;
|
||||
use super::bgd::BlockGroupDescriptor;
|
||||
|
||||
pub fn alloc_block<B: BlockDevice>(
|
||||
device: &mut B,
|
||||
sb: &mut Superblock,
|
||||
gdt_block: u32,
|
||||
spb: usize,
|
||||
) -> Option<u32> {
|
||||
let bg = 0u32;
|
||||
let mut bgd = BlockGroupDescriptor::read(device, gdt_block, bg, spb)?;
|
||||
if bgd.free_blocks_count == 0 { return None; }
|
||||
|
||||
let mut bitmap = [0u8; 4096];
|
||||
if !read_block(device, bgd.block_bitmap as u64, spb, &mut bitmap) { return None; }
|
||||
|
||||
for byte_idx in 0..sb.blocks_count as usize {
|
||||
let byte = byte_idx / 8;
|
||||
let bit = byte_idx % 8;
|
||||
if byte >= bitmap.len() { break; }
|
||||
if bitmap[byte] & (1 << bit) == 0 {
|
||||
bitmap[byte] |= 1 << bit;
|
||||
if !write_block(device, bgd.block_bitmap as u64, spb, &bitmap) { return None; }
|
||||
bgd.free_blocks_count -= 1;
|
||||
bgd.write_back(device, gdt_block, bg, spb);
|
||||
sb.free_blocks_count -= 1;
|
||||
sb.write_back(device);
|
||||
return Some(byte_idx as u32);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn free_block<B: BlockDevice>(
|
||||
device: &mut B,
|
||||
sb: &mut Superblock,
|
||||
gdt_block: u32,
|
||||
block: u32,
|
||||
spb: usize,
|
||||
) {
|
||||
let bg = 0u32;
|
||||
let mut bgd = match BlockGroupDescriptor::read(device, gdt_block, bg, spb) {
|
||||
Some(b) => b,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut bitmap = [0u8; 4096];
|
||||
if !read_block(device, bgd.block_bitmap as u64, spb, &mut bitmap) { return; }
|
||||
|
||||
let byte = block as usize / 8;
|
||||
let bit = block as usize % 8;
|
||||
if byte < bitmap.len() {
|
||||
bitmap[byte] &= !(1 << bit);
|
||||
write_block(device, bgd.block_bitmap as u64, spb, &bitmap);
|
||||
bgd.free_blocks_count += 1;
|
||||
bgd.write_back(device, gdt_block, bg, spb);
|
||||
sb.free_blocks_count += 1;
|
||||
sb.write_back(device);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alloc_inode<B: BlockDevice>(
|
||||
device: &mut B,
|
||||
sb: &mut Superblock,
|
||||
gdt_block: u32,
|
||||
spb: usize,
|
||||
) -> Option<u32> {
|
||||
let bg = 0u32;
|
||||
let mut bgd = BlockGroupDescriptor::read(device, gdt_block, bg, spb)?;
|
||||
if bgd.free_inodes_count == 0 { return None; }
|
||||
|
||||
let mut bitmap = [0u8; 4096];
|
||||
if !read_block(device, bgd.inode_bitmap as u64, spb, &mut bitmap) { return None; }
|
||||
|
||||
for bit_idx in 0..sb.inodes_count as usize {
|
||||
let byte = bit_idx / 8;
|
||||
let bit = bit_idx % 8;
|
||||
if byte >= bitmap.len() { break; }
|
||||
if bitmap[byte] & (1 << bit) == 0 {
|
||||
bitmap[byte] |= 1 << bit;
|
||||
if !write_block(device, bgd.inode_bitmap as u64, spb, &bitmap) { return None; }
|
||||
bgd.free_inodes_count -= 1;
|
||||
bgd.write_back(device, gdt_block, bg, spb);
|
||||
sb.free_inodes_count -= 1;
|
||||
sb.write_back(device);
|
||||
return Some(bit_idx as u32 + 1); // inodes are 1-based
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn free_inode<B: BlockDevice>(
|
||||
device: &mut B,
|
||||
sb: &mut Superblock,
|
||||
gdt_block: u32,
|
||||
inode: u32,
|
||||
spb: usize,
|
||||
) {
|
||||
let bg = 0u32;
|
||||
let mut bgd = match BlockGroupDescriptor::read(device, gdt_block, bg, spb) {
|
||||
Some(b) => b,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut bitmap = [0u8; 4096];
|
||||
if !read_block(device, bgd.inode_bitmap as u64, spb, &mut bitmap) { return; }
|
||||
|
||||
let bit_idx = (inode - 1) as usize; // inodes are 1-based
|
||||
let byte = bit_idx / 8;
|
||||
let bit = bit_idx % 8;
|
||||
if byte < bitmap.len() {
|
||||
bitmap[byte] &= !(1 << bit);
|
||||
write_block(device, bgd.inode_bitmap as u64, spb, &bitmap);
|
||||
bgd.free_inodes_count += 1;
|
||||
bgd.write_back(device, gdt_block, bg, spb);
|
||||
sb.free_inodes_count += 1;
|
||||
sb.write_back(device);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod io;
|
||||
pub mod superblock;
|
||||
pub mod bgd;
|
||||
pub mod bitmap;
|
||||
|
||||
use port_block_device::BlockDevice;
|
||||
use crate::filesystem::{DirEntry, FileHandle, FileName, FileSystem};
|
||||
@@ -173,6 +174,26 @@ impl<B: BlockDevice> Ext4<B> {
|
||||
self.write_inode(inode_num, inode)
|
||||
}
|
||||
|
||||
pub fn alloc_block(&mut self) -> Option<u32> {
|
||||
let spb = self.sb.sectors_per_block();
|
||||
bitmap::alloc_block(&mut self.device, &mut self.sb, self.gdt_block, spb)
|
||||
}
|
||||
|
||||
pub fn free_block(&mut self, block: u32) {
|
||||
let spb = self.sb.sectors_per_block();
|
||||
bitmap::free_block(&mut self.device, &mut self.sb, self.gdt_block, block, spb);
|
||||
}
|
||||
|
||||
pub fn alloc_inode(&mut self) -> Option<u32> {
|
||||
let spb = self.sb.sectors_per_block();
|
||||
bitmap::alloc_inode(&mut self.device, &mut self.sb, self.gdt_block, spb)
|
||||
}
|
||||
|
||||
pub fn free_inode(&mut self, inode: u32) {
|
||||
let spb = self.sb.sectors_per_block();
|
||||
bitmap::free_inode(&mut self.device, &mut self.sb, self.gdt_block, inode, spb);
|
||||
}
|
||||
|
||||
fn read_extents(&self, inode: &Inode) -> Option<Extent> {
|
||||
if !inode.uses_extents() { return None; }
|
||||
let hdr = &inode.block_data;
|
||||
|
||||
@@ -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