324 lines
10 KiB
Rust
324 lines
10 KiB
Rust
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 }
|
|
}
|