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 } }