From 5d5714914f9aeadb8f50792c75625c74359107b6 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 18 Oct 2026 23:08:07 +0200 Subject: [PATCH] refactor(ext4): split into submodules, add io/superblock/bgd with write-back --- .../servers/storage-server/src/fs/ext4/bgd.rs | 39 +++ .../servers/storage-server/src/fs/ext4/io.rs | 50 +++ .../servers/storage-server/src/fs/ext4/mod.rs | 323 ++++++++++++++++++ .../storage-server/src/fs/ext4/superblock.rs | 76 +++++ 4 files changed, 488 insertions(+) create mode 100644 crates/servers/storage-server/src/fs/ext4/bgd.rs create mode 100644 crates/servers/storage-server/src/fs/ext4/io.rs create mode 100644 crates/servers/storage-server/src/fs/ext4/mod.rs create mode 100644 crates/servers/storage-server/src/fs/ext4/superblock.rs diff --git a/crates/servers/storage-server/src/fs/ext4/bgd.rs b/crates/servers/storage-server/src/fs/ext4/bgd.rs new file mode 100644 index 0000000..38ccbd6 --- /dev/null +++ b/crates/servers/storage-server/src/fs/ext4/bgd.rs @@ -0,0 +1,39 @@ +use port_block_device::BlockDevice; +use super::io::{read_block, write_block, read_u32, write_u32, read_u16, write_u16}; + +pub struct BlockGroupDescriptor { + pub block_bitmap: u32, + pub inode_bitmap: u32, + pub inode_table: u32, + pub free_blocks_count: u16, + pub free_inodes_count: u16, +} + +impl BlockGroupDescriptor { + pub fn read(device: &mut B, gdt_block: u32, bg: u32, spb: usize) -> Option { + let mut buf = [0u8; 4096]; + if !read_block(device, gdt_block as u64, spb, &mut buf) { return None; } + let off = (bg as usize) * 32; + if off + 32 > buf.len() { return None; } + Some(Self { + block_bitmap: read_u32(&buf, off), + inode_bitmap: read_u32(&buf, off + 4), + inode_table: read_u32(&buf, off + 8), + free_blocks_count: read_u16(&buf, off + 12), + free_inodes_count: read_u16(&buf, off + 14), + }) + } + + pub fn write_back(&self, device: &mut B, gdt_block: u32, bg: u32, spb: usize) -> bool { + let mut buf = [0u8; 4096]; + if !read_block(device, gdt_block as u64, spb, &mut buf) { return false; } + let off = (bg as usize) * 32; + if off + 32 > buf.len() { return false; } + write_u32(&mut buf, off, self.block_bitmap); + write_u32(&mut buf, off + 4, self.inode_bitmap); + write_u32(&mut buf, off + 8, self.inode_table); + write_u16(&mut buf, off + 12, self.free_blocks_count); + write_u16(&mut buf, off + 14, self.free_inodes_count); + write_block(device, gdt_block as u64, spb, &buf) + } +} diff --git a/crates/servers/storage-server/src/fs/ext4/io.rs b/crates/servers/storage-server/src/fs/ext4/io.rs new file mode 100644 index 0000000..49e52ca --- /dev/null +++ b/crates/servers/storage-server/src/fs/ext4/io.rs @@ -0,0 +1,50 @@ +use port_block_device::{BlockDevice, SectorId}; + +pub const SECTOR_SIZE: usize = 512; +pub const BLOCK_SIZE: usize = 4096; + +pub fn read_u32(data: &[u8], offset: usize) -> u32 { + u32::from_le_bytes([data[offset], data[offset + 1], data[offset + 2], data[offset + 3]]) +} + +pub fn write_u32(buf: &mut [u8], offset: usize, val: u32) { + buf[offset..offset + 4].copy_from_slice(&val.to_le_bytes()); +} + +pub fn read_u16(data: &[u8], offset: usize) -> u16 { + u16::from_le_bytes([data[offset], data[offset + 1]]) +} + +pub fn write_u16(buf: &mut [u8], offset: usize, val: u16) { + buf[offset..offset + 2].copy_from_slice(&val.to_le_bytes()); +} + +pub fn read_block(device: &mut B, block: u64, spb: usize, buf: &mut [u8]) -> bool { + let start_sector = (block as usize) * spb; + for i in 0..spb { + let offset = i * SECTOR_SIZE; + if offset + SECTOR_SIZE > buf.len() { break; } + if !device.read_sector( + SectorId::new((start_sector + i) as u32), + &mut buf[offset..offset + SECTOR_SIZE], + ) { + return false; + } + } + true +} + +pub fn write_block(device: &mut B, block: u64, spb: usize, buf: &[u8]) -> bool { + let start_sector = (block as usize) * spb; + for i in 0..spb { + let offset = i * SECTOR_SIZE; + if offset + SECTOR_SIZE > buf.len() { break; } + if !device.write_sector( + SectorId::new((start_sector + i) as u32), + &buf[offset..offset + SECTOR_SIZE], + ) { + return false; + } + } + true +} diff --git a/crates/servers/storage-server/src/fs/ext4/mod.rs b/crates/servers/storage-server/src/fs/ext4/mod.rs new file mode 100644 index 0000000..a807c37 --- /dev/null +++ b/crates/servers/storage-server/src/fs/ext4/mod.rs @@ -0,0 +1,323 @@ +pub mod io; +pub mod superblock; +pub mod bgd; + +use port_block_device::BlockDevice; +use crate::filesystem::{DirEntry, FileHandle, FileName, FileSystem}; +use io::{read_block, read_u32, read_u16}; +use superblock::Superblock; +use bgd::BlockGroupDescriptor; + +const ROOT_INODE: u32 = 2; +const MAX_OPEN_FILES: usize = 8; +const EXTENT_MAGIC: u16 = 0xF30A; + +pub struct Inode { + pub mode: u16, + pub size: u64, + pub flags: u32, + pub block_data: [u8; 60], +} + +impl Inode { + pub fn is_dir(&self) -> bool { self.mode & 0xF000 == 0x4000 } + pub fn is_file(&self) -> bool { self.mode & 0xF000 == 0x8000 } + pub fn uses_extents(&self) -> bool { self.flags & 0x80000 != 0 } +} + +#[derive(Clone, Copy)] +pub struct Extent { + pub logical_block: u32, + pub length: u16, + pub physical_block: u64, +} + +impl Extent { + pub fn parse(data: &[u8]) -> Self { + let start_hi = read_u16(data, 6) as u64; + let start_lo = read_u32(data, 8) as u64; + Self { + logical_block: read_u32(data, 0), + length: read_u16(data, 4), + physical_block: (start_hi << 32) | start_lo, + } + } +} + +#[derive(Clone, Copy)] +struct OpenFile { + inode_num: u32, + size: usize, + offset: usize, + name: FileName, + block_data: [u8; 60], +} + +pub struct Ext4 { + device: B, + sb: Superblock, + gdt_block: u32, + open_files: [Option; MAX_OPEN_FILES], +} + +impl Ext4 { + pub fn mount(mut device: B) -> Option { + let mut buf = [0u8; 1024]; + if !device.read_sector(port_block_device::SectorId::new(2), &mut buf[..512]) { return None; } + if !device.read_sector(port_block_device::SectorId::new(3), &mut buf[512..]) { return None; } + + let sb = Superblock::parse(&buf)?; + let gdt_block = sb.first_data_block + 1; + + Some(Self { + device, + sb, + gdt_block, + open_files: [const { None }; MAX_OPEN_FILES], + }) + } + + fn read_inode(&mut self, inode_num: u32) -> Option { + let bgd = BlockGroupDescriptor::read( + &mut self.device, self.gdt_block, + self.sb.inode_block_group(inode_num), + self.sb.sectors_per_block(), + )?; + let index = self.sb.inode_index_in_group(inode_num); + + let inode_offset = (index as usize) * (self.sb.inode_size as usize); + let block_offset = inode_offset / self.sb.block_size; + let offset_in_block = inode_offset % self.sb.block_size; + + let mut block_buf = [0u8; 4096]; + read_block( + &mut self.device, + (bgd.inode_table + block_offset as u32) as u64, + self.sb.sectors_per_block(), + &mut block_buf, + ); + + let d = &block_buf[offset_in_block..]; + if d.len() < 128 { return None; } + + let size_lo = read_u32(d, 0x04) as u64; + let size_hi = read_u32(d, 0x6C) as u64; + let size = size_lo | (size_hi << 32); + + let mut block_data = [0u8; 60]; + block_data.copy_from_slice(&d[0x28..0x64]); + + Some(Inode { + mode: read_u16(d, 0x00), + size, + flags: read_u32(d, 0x20), + block_data, + }) + } + + fn read_extents(&self, inode: &Inode) -> Option { + if !inode.uses_extents() { return None; } + let hdr = &inode.block_data; + let magic = read_u16(hdr, 0); + if magic != EXTENT_MAGIC { return None; } + let entries = read_u16(hdr, 2); + let depth = read_u16(hdr, 6); + if depth != 0 || entries == 0 { return None; } + Some(Extent::parse(&hdr[12..24])) + } + + fn map_logical_block(&mut self, inode: &Inode, logical_block: u32) -> Option { + if !inode.uses_extents() { return None; } + let hdr = &inode.block_data; + let magic = read_u16(hdr, 0); + if magic != EXTENT_MAGIC { return None; } + let entries = read_u16(hdr, 2); + let depth = read_u16(hdr, 6); + if depth != 0 { + return None; + } + for i in 0..entries as usize { + let off = 12 + i * 12; + if off + 12 > hdr.len() { break; } + let ext = Extent::parse(&hdr[off..off + 12]); + if logical_block >= ext.logical_block + && logical_block < ext.logical_block + ext.length as u32 + { + return Some(ext.physical_block + (logical_block - ext.logical_block) as u64); + } + } + None + } + + fn find_in_dir(&mut self, dir_inode: u32, name: &[u8]) -> Option { + let inode = self.read_inode(dir_inode)?; + if !inode.is_dir() { return None; } + + let extent = self.read_extents(&inode)?; + let mut block_buf = [0u8; 4096]; + + for blk in 0..extent.length as u64 { + read_block( + &mut self.device, + extent.physical_block + blk, + self.sb.sectors_per_block(), + &mut block_buf, + ); + let mut pos = 0; + while pos + 8 <= self.sb.block_size { + let entry_inode = read_u32(&block_buf, pos); + let rec_len = read_u16(&block_buf, pos + 4) as usize; + let name_len = block_buf[pos + 6] as usize; + + if rec_len == 0 { break; } + if entry_inode != 0 && name_len == name.len() { + if &block_buf[pos + 8..pos + 8 + name_len] == name { + return Some(entry_inode); + } + } + pos += rec_len; + } + } + None + } + + fn resolve_path(&mut self, path: &[u8]) -> Option { + let mut current = ROOT_INODE; + for component in path.split(|&b| b == b'/') { + if component.is_empty() { continue; } + current = self.find_in_dir(current, component)?; + } + Some(current) + } + + fn alloc_handle(&self) -> Option { + for i in 0..MAX_OPEN_FILES { + if self.open_files[i].is_none() { + return Some(FileHandle::new(i as u16)); + } + } + None + } +} + +impl FileSystem for Ext4 { + fn open(&mut self, path: &[u8]) -> Option { + let inode_num = self.resolve_path(path)?; + let inode = self.read_inode(inode_num)?; + if !inode.is_file() { return None; } + + let handle = self.alloc_handle()?; + self.open_files[handle.raw() as usize] = Some(OpenFile { + inode_num, + size: inode.size as usize, + offset: 0, + name: FileName::from_bytes(path), + block_data: inode.block_data, + }); + Some(handle) + } + + fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option { + let file = *self.open_files[handle.raw() as usize].as_ref()?; + if file.offset >= file.size { return Some(0); } + + let remaining = file.size - file.offset; + let to_read = buf.len().min(remaining); + let bs = self.sb.block_size; + + let logical_block = (file.offset / bs) as u32; + let byte_in_block = file.offset % bs; + + let inode = Inode { + mode: 0, + size: file.size as u64, + flags: 0x80000, + block_data: file.block_data, + }; + let phys_block = self.map_logical_block(&inode, logical_block)?; + + let mut block_buf = [0u8; 4096]; + read_block(&mut self.device, phys_block, self.sb.sectors_per_block(), &mut block_buf); + + let available = bs - byte_in_block; + let chunk = to_read.min(available); + buf[..chunk].copy_from_slice(&block_buf[byte_in_block..byte_in_block + chunk]); + + self.open_files[handle.raw() as usize].as_mut().map(|f| f.offset += chunk); + Some(chunk) + } + + fn write(&mut self, _handle: FileHandle, _buf: &[u8]) -> Option { + None + } + + fn close(&mut self, handle: FileHandle) { + self.open_files[handle.raw() as usize] = None; + } + + fn list_dir(&mut self, path: &[u8], entries: &mut [DirEntry]) -> Option { + let dir_inode_num = if path.is_empty() || path == b"/" { + ROOT_INODE + } else { + self.resolve_path(path)? + }; + + let inode = self.read_inode(dir_inode_num)?; + if !inode.is_dir() { return None; } + + let extent = self.read_extents(&inode)?; + let mut block_buf = [0u8; 4096]; + let mut count = 0; + + for blk in 0..extent.length as u64 { + read_block( + &mut self.device, + extent.physical_block + blk, + self.sb.sectors_per_block(), + &mut block_buf, + ); + let mut pos = 0; + while pos + 8 <= self.sb.block_size && count < entries.len() { + let entry_inode = read_u32(&block_buf, pos); + let rec_len = read_u16(&block_buf, pos + 4) as usize; + let name_len = block_buf[pos + 6] as usize; + let file_type = block_buf[pos + 7]; + + if rec_len == 0 { break; } + if entry_inode != 0 && name_len > 0 { + let name = &block_buf[pos + 8..pos + 8 + name_len]; + if name != b"." && name != b".." { + let child_size = if file_type == 1 { + self.read_inode(entry_inode) + .map(|i| i.size as usize) + .unwrap_or(0) + } else { 0 }; + + entries[count] = DirEntry { + name: FileName::from_bytes(name), + size: child_size, + is_dir: file_type == 2, + }; + count += 1; + } + } + pos += rec_len; + } + } + Some(count) + } + + fn create(&mut self, _path: &[u8]) -> Option { + None + } + + fn delete(&mut self, _path: &[u8]) -> bool { + false + } + + fn file_size(&mut self, handle: FileHandle) -> Option { + self.open_files[handle.raw() as usize].as_ref().map(|f| f.size) + } + + fn mkdir(&mut self, _path: &[u8]) -> bool { false } +} diff --git a/crates/servers/storage-server/src/fs/ext4/superblock.rs b/crates/servers/storage-server/src/fs/ext4/superblock.rs new file mode 100644 index 0000000..95cd40c --- /dev/null +++ b/crates/servers/storage-server/src/fs/ext4/superblock.rs @@ -0,0 +1,76 @@ +use port_block_device::{BlockDevice, SectorId}; +use super::io::{read_u32, write_u32, read_u16, write_u16, SECTOR_SIZE}; + +const EXT4_MAGIC: u16 = 0xEF53; + +pub struct Superblock { + pub inodes_count: u32, + pub blocks_count: u32, + pub block_size: usize, + pub blocks_per_group: u32, + pub inodes_per_group: u32, + pub inode_size: u16, + pub first_data_block: u32, + pub free_blocks_count: u32, + pub free_inodes_count: u32, +} + +impl Superblock { + pub fn parse(data: &[u8]) -> Option { + if data.len() < 256 { return None; } + let magic = read_u16(data, 0x38); + if magic != EXT4_MAGIC { return None; } + + let log_block_size = read_u32(data, 0x18); + Some(Self { + inodes_count: read_u32(data, 0x00), + blocks_count: read_u32(data, 0x04), + block_size: 1024 << log_block_size, + blocks_per_group: read_u32(data, 0x20), + inodes_per_group: read_u32(data, 0x28), + inode_size: read_u16(data, 0x58), + first_data_block: read_u32(data, 0x14), + free_blocks_count: read_u32(data, 0x0C), + free_inodes_count: read_u32(data, 0x10), + }) + } + + pub fn sectors_per_block(&self) -> usize { + self.block_size / SECTOR_SIZE + } + + pub fn inode_block_group(&self, inode: u32) -> u32 { + (inode - 1) / self.inodes_per_group + } + + pub fn inode_index_in_group(&self, inode: u32) -> u32 { + (inode - 1) % self.inodes_per_group + } + + pub fn write_back(&self, device: &mut B) -> bool { + let mut buf = [0u8; 1024]; + if !device.read_sector(SectorId::new(2), &mut buf[..512]) { return false; } + if !device.read_sector(SectorId::new(3), &mut buf[512..]) { return false; } + + write_u32(&mut buf, 0x00, self.inodes_count); + write_u32(&mut buf, 0x04, self.blocks_count); + write_u32(&mut buf, 0x0C, self.free_blocks_count); + write_u32(&mut buf, 0x10, self.free_inodes_count); + write_u32(&mut buf, 0x14, self.first_data_block); + let log_block_size = match self.block_size { + 1024 => 0u32, + 2048 => 1, + 4096 => 2, + _ => 2, + }; + write_u32(&mut buf, 0x18, log_block_size); + write_u32(&mut buf, 0x20, self.blocks_per_group); + write_u32(&mut buf, 0x28, self.inodes_per_group); + write_u16(&mut buf, 0x38, EXT4_MAGIC); + write_u16(&mut buf, 0x58, self.inode_size); + + if !device.write_sector(SectorId::new(2), &buf[..512]) { return false; } + if !device.write_sector(SectorId::new(3), &buf[512..]) { return false; } + true + } +}