refactor(ext4): split into submodules, add io/superblock/bgd with write-back

This commit is contained in:
2026-10-18 23:08:07 +02:00
parent d85cb5b55d
commit 5d5714914f
4 changed files with 488 additions and 0 deletions

View File

@@ -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<B: BlockDevice>(device: &mut B, gdt_block: u32, bg: u32, spb: usize) -> Option<Self> {
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<B: BlockDevice>(&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)
}
}

View File

@@ -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<B: BlockDevice>(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<B: BlockDevice>(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
}

View File

@@ -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<B: BlockDevice> {
device: B,
sb: Superblock,
gdt_block: u32,
open_files: [Option<OpenFile>; MAX_OPEN_FILES],
}
impl<B: BlockDevice> Ext4<B> {
pub fn mount(mut device: B) -> Option<Self> {
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<Inode> {
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<Extent> {
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<u64> {
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<u32> {
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<u32> {
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<FileHandle> {
for i in 0..MAX_OPEN_FILES {
if self.open_files[i].is_none() {
return Some(FileHandle::new(i as u16));
}
}
None
}
}
impl<B: BlockDevice> FileSystem for Ext4<B> {
fn open(&mut self, path: &[u8]) -> Option<FileHandle> {
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<usize> {
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<usize> {
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<usize> {
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<FileHandle> {
None
}
fn delete(&mut self, _path: &[u8]) -> bool {
false
}
fn file_size(&mut self, handle: FileHandle) -> Option<usize> {
self.open_files[handle.raw() as usize].as_ref().map(|f| f.size)
}
fn mkdir(&mut self, _path: &[u8]) -> bool { false }
}

View File

@@ -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<Self> {
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<B: BlockDevice>(&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
}
}