408 lines
14 KiB
Rust
408 lines
14 KiB
Rust
pub mod io;
|
|
pub mod superblock;
|
|
pub mod bgd;
|
|
pub mod bitmap;
|
|
pub mod dir;
|
|
mod extent;
|
|
mod inode;
|
|
mod path;
|
|
|
|
use port_block_device::BlockDevice;
|
|
use crate::filesystem::{DirEntry, FileHandle, FileSystem};
|
|
use io::read_block;
|
|
use superblock::Superblock;
|
|
|
|
const ROOT_INODE: u32 = 2;
|
|
const MAX_OPEN_FILES: usize = 8;
|
|
const EXTENT_MAGIC: u16 = 0xF30A;
|
|
|
|
|
|
pub use inode::{Extent, Inode};
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct OpenFile {
|
|
inode_num: u32,
|
|
size: usize,
|
|
offset: usize,
|
|
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],
|
|
})
|
|
}
|
|
|
|
/// One more directory in `inode`'s block group (e2fsck checks the count).
|
|
fn count_dir(&mut self, inode: u32) {
|
|
let (bg, spb) = (self.sb.inode_block_group(inode), self.sb.sectors_per_block());
|
|
if let Some(mut d) = bgd::BlockGroupDescriptor::read(&mut self.device, self.gdt_block, bg, spb) {
|
|
d.used_dirs_count += 1;
|
|
d.write_back(&mut self.device, self.gdt_block, bg, spb);
|
|
}
|
|
}
|
|
|
|
/// One directory fewer in `inode`'s block group.
|
|
fn uncount_dir(&mut self, inode: u32) {
|
|
let (bg, spb) = (self.sb.inode_block_group(inode), self.sb.sectors_per_block());
|
|
if let Some(mut d) = bgd::BlockGroupDescriptor::read(&mut self.device, self.gdt_block, bg, spb) {
|
|
d.used_dirs_count = d.used_dirs_count.saturating_sub(1);
|
|
d.write_back(&mut self.device, self.gdt_block, bg, spb);
|
|
}
|
|
}
|
|
|
|
/// The disk, for a test to look at what was written.
|
|
pub fn into_device(self) -> B {
|
|
self.device
|
|
}
|
|
}
|
|
|
|
impl<B: BlockDevice> FileSystem for Ext4<B> {
|
|
fn writable(&self) -> bool {
|
|
self.sb.writable && self.device.writable()
|
|
}
|
|
|
|
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,
|
|
block_data: inode.block_data,
|
|
});
|
|
Some(handle)
|
|
}
|
|
|
|
fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option<usize> {
|
|
if handle.raw() as usize >= MAX_OPEN_FILES { return None; }
|
|
let file = *self.open_files[handle.raw() as usize].as_ref()?;
|
|
if file.offset >= file.size { return Some(0); }
|
|
|
|
// As much as `buf` holds, across blocks: a short read means the end.
|
|
let to_read = buf.len().min(file.size - file.offset);
|
|
let bs = self.sb.block_size;
|
|
let inode = Inode {
|
|
mode: 0,
|
|
size: file.size as u64,
|
|
flags: 0x80000,
|
|
links_count: 0,
|
|
blocks_count: 0,
|
|
block_data: file.block_data,
|
|
};
|
|
let mut done = 0;
|
|
let mut block_buf = [0u8; 4096];
|
|
while done < to_read {
|
|
let offset = file.offset + done;
|
|
// A block no extent maps is a hole (mkfs leaves zero blocks
|
|
// out): it reads as zeros.
|
|
match self.map_logical_block(&inode, (offset / bs) as u32) {
|
|
Some(phys_block) => {
|
|
if !read_block(&mut self.device, phys_block, self.sb.sectors_per_block(), &mut block_buf) {
|
|
return None;
|
|
}
|
|
}
|
|
None => block_buf.fill(0),
|
|
}
|
|
let byte_in_block = offset % bs;
|
|
let chunk = (to_read - done).min(bs - byte_in_block);
|
|
buf[done..done + chunk].copy_from_slice(&block_buf[byte_in_block..byte_in_block + chunk]);
|
|
done += chunk;
|
|
}
|
|
|
|
if let Some(f) = self.open_files[handle.raw() as usize].as_mut() {
|
|
f.offset += done;
|
|
}
|
|
Some(done)
|
|
}
|
|
|
|
fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option<usize> {
|
|
if handle.raw() as usize >= MAX_OPEN_FILES { return None; }
|
|
let file = *self.open_files[handle.raw() as usize].as_ref()?;
|
|
let bs = self.sb.block_size;
|
|
let mut written = 0;
|
|
let mut offset = file.offset;
|
|
let mut block_data = file.block_data;
|
|
let mut size = file.size;
|
|
let inode_num = file.inode_num;
|
|
|
|
while written < buf.len() {
|
|
let logical_block = (offset / bs) as u32;
|
|
let byte_in_block = offset % bs;
|
|
|
|
let tmp_inode = Inode {
|
|
mode: 0, size: size as u64, flags: 0x80000,
|
|
links_count: 0, blocks_count: 0, block_data,
|
|
};
|
|
let phys_block = match self.map_logical_block(&tmp_inode, logical_block) {
|
|
Some(p) => p,
|
|
None => {
|
|
let new_block = match self.alloc_block() {
|
|
Some(b) => b,
|
|
None => break,
|
|
};
|
|
if !Self::append_extent(&mut block_data, logical_block, new_block as u64, 1) {
|
|
self.free_block(new_block);
|
|
break;
|
|
}
|
|
new_block as u64
|
|
}
|
|
};
|
|
|
|
let mut block_buf = [0u8; 4096];
|
|
if byte_in_block > 0 || (buf.len() - written) < bs {
|
|
read_block(&mut self.device, phys_block, self.sb.sectors_per_block(), &mut block_buf);
|
|
}
|
|
|
|
let available = bs - byte_in_block;
|
|
let chunk = available.min(buf.len() - written);
|
|
block_buf[byte_in_block..byte_in_block + chunk].copy_from_slice(&buf[written..written + chunk]);
|
|
io::write_block(&mut self.device, phys_block, self.sb.sectors_per_block(), &block_buf);
|
|
|
|
written += chunk;
|
|
offset += chunk;
|
|
if offset > size { size = offset; }
|
|
}
|
|
|
|
if written > 0 {
|
|
if let Some(f) = self.open_files[handle.raw() as usize].as_mut() {
|
|
f.offset = offset;
|
|
f.size = size;
|
|
f.block_data = block_data;
|
|
}
|
|
let mut inode = match self.read_inode(inode_num) {
|
|
Some(i) => i,
|
|
None => return Some(written),
|
|
};
|
|
inode.size = size as u64;
|
|
inode.block_data = block_data;
|
|
inode.blocks_count = Self::count_extent_blocks(&block_data) * (bs as u32 / 512);
|
|
self.write_inode(inode_num, &inode);
|
|
}
|
|
|
|
Some(written)
|
|
}
|
|
|
|
fn close(&mut self, handle: FileHandle) {
|
|
if handle.raw() as usize >= MAX_OPEN_FILES { return; }
|
|
if let Some(file) = self.open_files[handle.raw() as usize].take()
|
|
&& let Some(mut inode) = self.read_inode(file.inode_num) {
|
|
inode.size = file.size as u64;
|
|
inode.block_data = file.block_data;
|
|
self.write_inode(file.inode_num, &inode);
|
|
}
|
|
}
|
|
|
|
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)?;
|
|
let count = dir::list_entries(&mut self.device, &self.sb, &inode, entries)?;
|
|
for entry in entries.iter_mut().take(count) {
|
|
if !entry.is_dir {
|
|
let name = entry.name;
|
|
if let Some(child_ino) = dir::find_entry(&mut self.device, &self.sb, &inode, name.as_bytes())
|
|
&& let Some(child) = self.read_inode(child_ino) {
|
|
entry.size = child.size as usize;
|
|
}
|
|
}
|
|
}
|
|
Some(count)
|
|
}
|
|
|
|
fn create(&mut self, path: &[u8]) -> Option<FileHandle> {
|
|
let (parent_inode_num, filename) = self.resolve_parent(path)?;
|
|
let parent_inode = self.read_inode(parent_inode_num)?;
|
|
if !parent_inode.is_dir() { return None; }
|
|
|
|
if dir::find_entry(&mut self.device, &self.sb, &parent_inode, filename).is_some() {
|
|
return None;
|
|
}
|
|
|
|
let new_inode_num = self.alloc_inode()?;
|
|
let new_inode = Self::init_empty_inode(0x8000 | 0o644);
|
|
self.write_inode(new_inode_num, &new_inode);
|
|
|
|
let parent_inode = self.read_inode(parent_inode_num)?;
|
|
if !dir::add_entry(&mut self.device, &self.sb, &parent_inode, filename, new_inode_num, 1) {
|
|
self.free_inode(new_inode_num);
|
|
return None;
|
|
}
|
|
|
|
let handle = self.alloc_handle()?;
|
|
self.open_files[handle.raw() as usize] = Some(OpenFile {
|
|
inode_num: new_inode_num,
|
|
size: 0,
|
|
offset: 0,
|
|
block_data: new_inode.block_data,
|
|
});
|
|
Some(handle)
|
|
}
|
|
|
|
fn delete(&mut self, path: &[u8]) -> bool {
|
|
let (parent_inode_num, filename) = match self.resolve_parent(path) {
|
|
Some(v) => v,
|
|
None => return false,
|
|
};
|
|
let parent_inode = match self.read_inode(parent_inode_num) {
|
|
Some(i) => i,
|
|
None => return false,
|
|
};
|
|
|
|
let child_inode_num = match dir::find_entry(&mut self.device, &self.sb, &parent_inode, filename) {
|
|
Some(n) => n,
|
|
None => return false,
|
|
};
|
|
|
|
if let Some(child_inode) = self.read_inode(child_inode_num)
|
|
&& child_inode.is_dir() {
|
|
return false;
|
|
}
|
|
|
|
let parent_inode = match self.read_inode(parent_inode_num) {
|
|
Some(i) => i,
|
|
None => return false,
|
|
};
|
|
if dir::remove_entry(&mut self.device, &self.sb, &parent_inode, filename).is_none() {
|
|
return false;
|
|
}
|
|
|
|
if let Some(child_inode) = self.read_inode(child_inode_num) {
|
|
self.free_all_blocks(&child_inode);
|
|
}
|
|
self.clear_inode(child_inode_num);
|
|
self.free_inode(child_inode_num);
|
|
for slot in self.open_files.iter_mut() {
|
|
if let Some(f) = slot
|
|
&& f.inode_num == child_inode_num {
|
|
*slot = None;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
fn file_size(&mut self, handle: FileHandle) -> Option<usize> {
|
|
if handle.raw() as usize >= MAX_OPEN_FILES { return None; }
|
|
self.open_files[handle.raw() as usize].as_ref().map(|f| f.size)
|
|
}
|
|
|
|
fn mkdir(&mut self, path: &[u8]) -> bool {
|
|
let (parent_inode_num, dirname) = match self.resolve_parent(path) {
|
|
Some(v) => v,
|
|
None => return false,
|
|
};
|
|
let parent_inode = match self.read_inode(parent_inode_num) {
|
|
Some(i) => i,
|
|
None => return false,
|
|
};
|
|
if !parent_inode.is_dir() { return false; }
|
|
|
|
if dir::find_entry(&mut self.device, &self.sb, &parent_inode, dirname).is_some() {
|
|
return false;
|
|
}
|
|
|
|
let new_inode_num = match self.alloc_inode() {
|
|
Some(n) => n,
|
|
None => return false,
|
|
};
|
|
let new_block = match self.alloc_block() {
|
|
Some(b) => b,
|
|
None => { self.free_inode(new_inode_num); return false; }
|
|
};
|
|
|
|
let mut new_inode = Self::init_empty_inode(0x4000 | 0o755);
|
|
new_inode.links_count = 2;
|
|
new_inode.size = self.sb.block_size as u64;
|
|
new_inode.blocks_count = (self.sb.block_size / 512) as u32;
|
|
if !Self::append_extent(&mut new_inode.block_data, 0, new_block as u64, 1) {
|
|
self.free_block(new_block);
|
|
self.free_inode(new_inode_num);
|
|
return false;
|
|
}
|
|
self.write_inode(new_inode_num, &new_inode);
|
|
|
|
let mut dir_block = [0u8; 4096];
|
|
dir::init_dir_block(&mut dir_block, new_inode_num, parent_inode_num, self.sb.block_size);
|
|
io::write_block(&mut self.device, new_block as u64, self.sb.sectors_per_block(), &dir_block);
|
|
|
|
let parent_inode = match self.read_inode(parent_inode_num) {
|
|
Some(i) => i,
|
|
None => return false,
|
|
};
|
|
if !dir::add_entry(&mut self.device, &self.sb, &parent_inode, dirname, new_inode_num, 2) {
|
|
self.free_block(new_block);
|
|
self.free_inode(new_inode_num);
|
|
return false;
|
|
}
|
|
|
|
if let Some(mut pi) = self.read_inode(parent_inode_num) {
|
|
pi.links_count += 1;
|
|
self.write_inode(parent_inode_num, &pi);
|
|
}
|
|
self.count_dir(new_inode_num);
|
|
|
|
true
|
|
}
|
|
|
|
fn seek(&mut self, handle: FileHandle, offset: usize) -> bool {
|
|
match self.open_files.get_mut(handle.raw() as usize).and_then(Option::as_mut) {
|
|
Some(f) if offset <= f.size => {
|
|
f.offset = offset;
|
|
true
|
|
}
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn rmdir(&mut self, path: &[u8]) -> bool {
|
|
let Some((parent_num, name)) = self.resolve_parent(path) else { return false };
|
|
let Some(parent) = self.read_inode(parent_num) else { return false };
|
|
let Some(child_num) = dir::find_entry(&mut self.device, &self.sb, &parent, name) else { return false };
|
|
let Some(child) = self.read_inode(child_num).filter(Inode::is_dir) else { return false };
|
|
// Empty: nothing but `.` and `..`.
|
|
let mut entries = [DirEntry::empty(); 3];
|
|
let Some(n) = dir::list_entries(&mut self.device, &self.sb, &child, &mut entries) else { return false };
|
|
if entries[..n].iter().any(|e| e.name.as_bytes() != b"." && e.name.as_bytes() != b"..") {
|
|
return false;
|
|
}
|
|
if dir::remove_entry(&mut self.device, &self.sb, &parent, name).is_none() {
|
|
return false;
|
|
}
|
|
self.free_all_blocks(&child);
|
|
self.clear_inode(child_num);
|
|
self.free_inode(child_num);
|
|
self.uncount_dir(child_num);
|
|
// Its `..` linked the parent.
|
|
if let Some(mut p) = self.read_inode(parent_num) {
|
|
p.links_count = p.links_count.saturating_sub(1);
|
|
self.write_inode(parent_num, &p);
|
|
}
|
|
true
|
|
}
|
|
}
|