use super::block_dev::{BlockDeviceHandle, BLOCK_SIZE};
use super::file::{File, FileError, FileType, SeekFrom};
use super::inode::Inode;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use spin::Mutex;
const BLKFS_MAGIC: u32 = 0x424C4B46; const MAX_INODES: usize = 128;
const DIRECT_BLOCKS: usize = 12;
const MAX_FILE_SIZE: usize = DIRECT_BLOCKS * BLOCK_SIZE;
const DIR_ENTRY_SIZE: usize = 256;
const DIR_ENTRY_NAME_BYTES: usize = 250;
const SUPERBLOCK_BLOCK: usize = 0;
const INODE_TABLE_START: usize = 1;
const INODE_BLOCK_SIZE: usize = 1;
const DATA_BLOCK_START: usize = INODE_TABLE_START + MAX_INODES * INODE_BLOCK_SIZE;
pub const BLKFS_MAX_INODES: usize = MAX_INODES;
pub const BLKFS_MAX_FILE_SIZE: usize = MAX_FILE_SIZE;
pub const BLKFS_MAX_NAME_BYTES: usize = DIR_ENTRY_NAME_BYTES;
pub const BLKFS_MIN_BLOCKS: usize = DATA_BLOCK_START + 1;
#[repr(C, packed)]
#[derive(Clone, Copy)]
struct Superblock {
magic: u32,
total_blocks: u32,
free_blocks: u32,
inode_count: u32,
root_inode: u32, _reserved: [u8; 488],
}
impl Superblock {
fn new(total_blocks: usize) -> Self {
let free_blocks = total_blocks.saturating_sub(DATA_BLOCK_START);
Superblock {
magic: BLKFS_MAGIC,
total_blocks: total_blocks as u32,
free_blocks: free_blocks as u32,
inode_count: 0,
root_inode: 0,
_reserved: [0u8; 488],
}
}
fn data_start_block(&self) -> usize {
DATA_BLOCK_START
}
}
#[repr(C, packed)]
#[derive(Clone, Copy)]
struct DiskInode {
ino: u32,
file_type: u8, mode: u32,
size: u32,
created: u64,
modified: u64,
nlinks: u32,
blocks: [u32; DIRECT_BLOCKS], _reserved: [u8; 6],
}
impl DiskInode {
fn zero() -> Self {
DiskInode {
ino: 0,
file_type: 0,
mode: 0,
size: 0,
created: 0,
modified: 0,
nlinks: 0,
blocks: [0u32; DIRECT_BLOCKS],
_reserved: [0u8; 6],
}
}
fn block_bytes(&self) -> [u8; BLOCK_SIZE] {
let mut buf = [0u8; BLOCK_SIZE];
let src = unsafe {
core::slice::from_raw_parts(
self as *const DiskInode as *const u8,
core::mem::size_of::<DiskInode>(),
)
};
buf[..src.len()].copy_from_slice(src);
buf
}
fn from_bytes(bytes: &[u8; BLOCK_SIZE]) -> Self {
unsafe { core::ptr::read(bytes.as_ptr() as *const DiskInode) }
}
}
pub struct BlkInode {
ino: usize,
disk: DiskInode,
dev: BlockDeviceHandle,
}
impl BlkInode {
fn load(dev: &BlockDeviceHandle, ino: usize) -> Result<Self, FileError> {
if ino >= MAX_INODES {
return Err(FileError::NotFound);
}
let block_no = INODE_TABLE_START + ino * INODE_BLOCK_SIZE;
let mut buf = [0u8; BLOCK_SIZE];
dev.read_block(block_no, &mut buf)?;
let disk = DiskInode::from_bytes(&buf);
if disk.ino as usize != ino || disk.file_type == 0 {
return Err(FileError::NotFound);
}
Ok(BlkInode {
ino,
disk,
dev: Arc::clone(dev),
})
}
fn store(&self) -> Result<(), FileError> {
let block_no = INODE_TABLE_START + self.ino * INODE_BLOCK_SIZE;
let buf = self.disk.block_bytes();
self.dev.write_block(block_no, &buf)
}
fn alloc_block(&mut self) -> Result<usize, FileError> {
for i in 0..DIRECT_BLOCKS {
if self.disk.blocks[i] == 0 {
let sb = Self::read_superblock(&self.dev)?;
let data_start = sb.data_start_block();
if sb.free_blocks == 0 {
return Err(FileError::IoError);
}
let used_data_blocks =
(sb.total_blocks as usize).saturating_sub(data_start + sb.free_blocks as usize);
let new_block = data_start + used_data_blocks;
if new_block >= sb.total_blocks as usize {
return Err(FileError::IoError);
}
self.disk.blocks[i] = new_block as u32;
let mut sb = sb;
sb.free_blocks -= 1;
Self::write_superblock(&self.dev, &sb)?;
self.store()?;
return Ok(new_block);
}
}
Err(FileError::IoError)
}
fn read_superblock(dev: &BlockDeviceHandle) -> Result<Superblock, FileError> {
let mut buf = [0u8; BLOCK_SIZE];
dev.read_block(SUPERBLOCK_BLOCK, &mut buf)?;
Ok(unsafe { core::ptr::read(buf.as_ptr() as *const Superblock) })
}
fn write_superblock(dev: &BlockDeviceHandle, sb: &Superblock) -> Result<(), FileError> {
let mut buf = [0u8; BLOCK_SIZE];
let src = unsafe {
core::slice::from_raw_parts(
sb as *const Superblock as *const u8,
core::mem::size_of::<Superblock>(),
)
};
buf[..src.len()].copy_from_slice(src);
dev.write_block(SUPERBLOCK_BLOCK, &buf)
}
pub fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize, FileError> {
if self.disk.file_type != 1 && self.disk.file_type != 2 {
return Err(FileError::IsDirectory);
}
let size = self.disk.size as usize;
if offset >= size {
return Ok(0);
}
let end = core::cmp::min(offset.saturating_add(buf.len()), size);
let mut read_total = 0usize;
let mut pos = offset;
while pos < end {
let block_idx = pos / BLOCK_SIZE;
if block_idx >= DIRECT_BLOCKS {
break;
}
let block_off = pos % BLOCK_SIZE;
let disk_block = self.disk.blocks[block_idx] as usize;
if disk_block == 0 {
break;
}
let mut block_buf = [0u8; BLOCK_SIZE];
self.dev.read_block(disk_block, &mut block_buf)?;
let chunk = core::cmp::min(BLOCK_SIZE - block_off, end - pos);
buf[read_total..read_total + chunk]
.copy_from_slice(&block_buf[block_off..block_off + chunk]);
read_total += chunk;
pos += chunk;
}
Ok(read_total)
}
pub fn write_at(&mut self, offset: usize, buf: &[u8]) -> Result<usize, FileError> {
if self.disk.file_type != 1 && self.disk.file_type != 2 {
return Err(FileError::IsDirectory);
}
let write_end = offset
.checked_add(buf.len())
.ok_or(FileError::InvalidOperation)?;
if write_end > MAX_FILE_SIZE {
return Err(FileError::InvalidOperation);
}
let needed_blocks = if write_end == 0 {
0
} else {
(write_end - 1) / BLOCK_SIZE + 1
};
for i in 0..needed_blocks.min(DIRECT_BLOCKS) {
if self.disk.blocks[i] == 0 {
self.alloc_block()?;
}
}
let mut written = 0usize;
let mut pos = offset;
while pos < write_end {
let block_idx = pos / BLOCK_SIZE;
let block_off = pos % BLOCK_SIZE;
if block_idx >= DIRECT_BLOCKS || self.disk.blocks[block_idx] == 0 {
break;
}
let disk_block = self.disk.blocks[block_idx] as usize;
let mut block_buf = [0u8; BLOCK_SIZE];
self.dev.read_block(disk_block, &mut block_buf)?;
let chunk = core::cmp::min(BLOCK_SIZE - block_off, write_end - pos);
block_buf[block_off..block_off + chunk].copy_from_slice(&buf[written..written + chunk]);
self.dev.write_block(disk_block, &block_buf)?;
written += chunk;
pos += chunk;
}
if pos > self.disk.size as usize {
self.disk.size = pos as u32;
}
self.disk.modified += 1;
self.store()?;
Ok(written)
}
pub fn add_entry(&mut self, name: &str, child_ino: usize) -> Result<(), FileError> {
if self.disk.file_type != 2 {
return Err(FileError::NotDirectory);
}
validate_entry_name(name)?;
if child_ino >= MAX_INODES {
return Err(FileError::InvalidOperation);
}
match self.lookup(name) {
Ok(_) => return Err(FileError::AlreadyExists),
Err(FileError::NotFound) => {}
Err(e) => return Err(e),
}
let mut entry = [0u8; DIR_ENTRY_SIZE];
let name_bytes = name.as_bytes();
let name_len = name_bytes.len();
entry[..name_len].copy_from_slice(&name_bytes[..name_len]);
entry[name_len] = 0; entry[252..256].copy_from_slice(&(child_ino as u32).to_le_bytes());
let offset = self.disk.size as usize;
self.write_at(offset, &entry)?;
self.disk.modified += 1;
self.store()?;
Ok(())
}
pub fn lookup(&self, name: &str) -> Result<usize, FileError> {
if self.disk.file_type != 2 {
return Err(FileError::NotDirectory);
}
validate_entry_name(name)?;
let size = self.disk.size as usize;
let mut offset = 0usize;
while offset < size {
let mut entry = [0u8; DIR_ENTRY_SIZE];
let n = self.read_at(offset, &mut entry)?;
if n < DIR_ENTRY_SIZE {
break;
}
let entry_name_len = entry[..252].iter().position(|&b| b == 0).unwrap_or(252);
let entry_name = core::str::from_utf8(&entry[..entry_name_len]).unwrap_or("");
let child_ino =
u32::from_le_bytes([entry[252], entry[253], entry[254], entry[255]]) as usize;
if entry_name == name {
return Ok(child_ino);
}
offset += DIR_ENTRY_SIZE;
}
Err(FileError::NotFound)
}
pub fn remove_entry(&mut self, name: &str) -> Result<(), FileError> {
if self.disk.file_type != 2 {
return Err(FileError::NotDirectory);
}
validate_entry_name(name)?;
let _ino = self.lookup(name)?; let mut entry = [0u8; DIR_ENTRY_SIZE];
entry[0] = 0; let size = self.disk.size as usize;
let mut offset = 0usize;
while offset < size {
let mut buf = [0u8; DIR_ENTRY_SIZE];
let n = self.read_at(offset, &mut buf)?;
if n < DIR_ENTRY_SIZE {
break;
}
let entry_name_len = buf[..252].iter().position(|&b| b == 0).unwrap_or(252);
let entry_name = core::str::from_utf8(&buf[..entry_name_len]).unwrap_or("");
if entry_name == name {
self.write_at(offset, &entry)?;
return Ok(());
}
offset += DIR_ENTRY_SIZE;
}
Err(FileError::NotFound)
}
pub fn list_entries(&self) -> Result<Vec<String>, FileError> {
if self.disk.file_type != 2 {
return Err(FileError::NotDirectory);
}
let mut names = Vec::new();
let size = self.disk.size as usize;
let mut offset = 0usize;
while offset < size {
let mut buf = [0u8; DIR_ENTRY_SIZE];
let n = self.read_at(offset, &mut buf)?;
if n < DIR_ENTRY_SIZE {
break;
}
if buf[0] == 0 {
offset += DIR_ENTRY_SIZE;
continue; }
let entry_name_len = buf[..252].iter().position(|&b| b == 0).unwrap_or(252);
if entry_name_len > 0 {
if let Ok(name) = core::str::from_utf8(&buf[..entry_name_len]) {
names.push(String::from(name));
}
}
offset += DIR_ENTRY_SIZE;
}
Ok(names)
}
}
fn validate_entry_name(name: &str) -> Result<(), FileError> {
let bytes = name.as_bytes();
if bytes.is_empty()
|| bytes.len() > DIR_ENTRY_NAME_BYTES
|| bytes.iter().any(|&b| b == b'/' || b == 0)
{
return Err(FileError::InvalidOperation);
}
Ok(())
}
impl Inode for BlkInode {
fn ino(&self) -> usize {
self.ino
}
fn file_type(&self) -> FileType {
match self.disk.file_type {
1 => FileType::RegularFile,
2 => FileType::Directory,
_ => FileType::RegularFile,
}
}
fn size(&self) -> usize {
self.disk.size as usize
}
fn mode(&self) -> u32 {
self.disk.mode
}
}
pub struct BlkFile {
inode: BlkInode,
offset: usize,
}
impl BlkFile {
pub fn new(inode: BlkInode) -> Self {
BlkFile { inode, offset: 0 }
}
}
impl File for BlkFile {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, FileError> {
let n = self.inode.read_at(self.offset, buf)?;
self.offset += n;
Ok(n)
}
fn write(&mut self, buf: &[u8]) -> Result<usize, FileError> {
let n = self.inode.write_at(self.offset, buf)?;
self.offset += n;
Ok(n)
}
fn seek(&mut self, pos: SeekFrom) -> Result<usize, FileError> {
let size = self.inode.size();
let new_offset = match pos {
SeekFrom::Start(o) => o,
SeekFrom::Current(delta) => {
if delta >= 0 {
self.offset.saturating_add(delta as usize)
} else {
self.offset.saturating_sub((-delta) as usize)
}
}
SeekFrom::End(delta) => {
if delta >= 0 {
size.saturating_add(delta as usize)
} else {
size.saturating_sub((-delta) as usize)
}
}
};
self.offset = new_offset;
Ok(self.offset)
}
}
pub struct BlkFS {
dev: BlockDeviceHandle,
next_ino: Mutex<usize>,
}
impl BlkFS {
pub fn create(dev: BlockDeviceHandle) -> Result<Self, FileError> {
let total_blocks = dev.block_count();
if total_blocks < BLKFS_MIN_BLOCKS {
return Err(FileError::InvalidOperation);
}
let sb = Superblock::new(total_blocks);
let mut sb_buf = [0u8; BLOCK_SIZE];
let src = unsafe {
core::slice::from_raw_parts(
&sb as *const Superblock as *const u8,
core::mem::size_of::<Superblock>(),
)
};
sb_buf[..src.len()].copy_from_slice(src);
dev.write_block(SUPERBLOCK_BLOCK, &sb_buf)?;
let zero_block = DiskInode::zero().block_bytes();
for i in 0..MAX_INODES {
dev.write_block(INODE_TABLE_START + i * INODE_BLOCK_SIZE, &zero_block)?;
}
let root_disk = DiskInode {
ino: 0,
file_type: 2, mode: 0o755,
size: 0,
created: 0,
modified: 1,
nlinks: 1,
blocks: [0u32; DIRECT_BLOCKS],
_reserved: [0u8; 6],
};
let root_buf = root_disk.block_bytes();
dev.write_block(INODE_TABLE_START, &root_buf)?;
let mut sb = sb;
sb.root_inode = 0;
sb.inode_count = 1;
let mut sb_buf2 = [0u8; BLOCK_SIZE];
let src2 = unsafe {
core::slice::from_raw_parts(
&sb as *const Superblock as *const u8,
core::mem::size_of::<Superblock>(),
)
};
sb_buf2[..src2.len()].copy_from_slice(src2);
dev.write_block(SUPERBLOCK_BLOCK, &sb_buf2)?;
Ok(BlkFS {
dev,
next_ino: Mutex::new(1),
})
}
pub fn open(dev: BlockDeviceHandle) -> Result<Self, FileError> {
let mut sb_buf = [0u8; BLOCK_SIZE];
dev.read_block(SUPERBLOCK_BLOCK, &mut sb_buf)?;
let sb: Superblock = unsafe { core::ptr::read(sb_buf.as_ptr() as *const Superblock) };
let total_blocks = sb.total_blocks as usize;
let inode_count = sb.inode_count as usize;
if sb.magic != BLKFS_MAGIC
|| total_blocks < BLKFS_MIN_BLOCKS
|| total_blocks > dev.block_count()
|| inode_count == 0
|| inode_count > MAX_INODES
|| sb.root_inode != 0
{
return Err(FileError::InvalidOperation);
}
let next_ino = inode_count;
Ok(BlkFS {
dev,
next_ino: Mutex::new(next_ino),
})
}
fn alloc_ino(&self) -> Result<usize, FileError> {
let mut next = self.next_ino.lock();
if *next >= MAX_INODES {
return Err(FileError::IoError);
}
let ino = *next;
*next += 1;
Ok(ino)
}
fn record_inode_allocated(&self, ino: usize) -> Result<(), FileError> {
if ino >= MAX_INODES {
return Err(FileError::InvalidOperation);
}
let mut sb = BlkInode::read_superblock(&self.dev)?;
let needed = ino + 1;
if sb.inode_count as usize >= needed {
return Ok(());
}
sb.inode_count = needed as u32;
BlkInode::write_superblock(&self.dev, &sb)
}
pub fn device(&self) -> BlockDeviceHandle {
Arc::clone(&self.dev)
}
pub fn sync(&self) -> Result<(), FileError> {
self.dev.flush()
}
pub fn root_inode(&self) -> Result<BlkInode, FileError> {
BlkInode::load(&self.dev, 0)
}
pub fn create_file(&self, parent: &mut BlkInode, name: &str) -> Result<BlkInode, FileError> {
validate_entry_name(name)?;
match parent.lookup(name) {
Ok(_) => return Err(FileError::AlreadyExists),
Err(FileError::NotFound) => {}
Err(e) => return Err(e),
}
let ino = self.alloc_ino()?;
let disk = DiskInode {
ino: ino as u32,
file_type: 1,
mode: 0o644,
size: 0,
created: 0,
modified: 0,
nlinks: 1,
blocks: [0u32; DIRECT_BLOCKS],
_reserved: [0u8; 6],
};
let buf = disk.block_bytes();
self.dev
.write_block(INODE_TABLE_START + ino * INODE_BLOCK_SIZE, &buf)?;
parent.add_entry(name, ino)?;
self.record_inode_allocated(ino)?;
BlkInode::load(&self.dev, ino)
}
pub fn create_directory(
&self,
parent: &mut BlkInode,
name: &str,
) -> Result<BlkInode, FileError> {
validate_entry_name(name)?;
match parent.lookup(name) {
Ok(_) => return Err(FileError::AlreadyExists),
Err(FileError::NotFound) => {}
Err(e) => return Err(e),
}
let ino = self.alloc_ino()?;
let disk = DiskInode {
ino: ino as u32,
file_type: 2,
mode: 0o755,
size: 0,
created: 0,
modified: 0,
nlinks: 1,
blocks: [0u32; DIRECT_BLOCKS],
_reserved: [0u8; 6],
};
let buf = disk.block_bytes();
self.dev
.write_block(INODE_TABLE_START + ino * INODE_BLOCK_SIZE, &buf)?;
parent.add_entry(name, ino)?;
self.record_inode_allocated(ino)?;
BlkInode::load(&self.dev, ino)
}
pub fn lookup(&self, parent: &BlkInode, name: &str) -> Result<BlkInode, FileError> {
let ino = parent.lookup(name)?;
BlkInode::load(&self.dev, ino)
}
pub fn open_file(&self, inode: BlkInode) -> Result<BlkFile, FileError> {
if inode.file_type() != FileType::RegularFile {
return Err(FileError::IsDirectory);
}
Ok(BlkFile::new(inode))
}
}