feat(ext4): file create, write, and delete with extent management

This commit is contained in:
2026-10-18 23:15:38 +02:00
parent bff18df349
commit 2ede01bdd2
2 changed files with 219 additions and 7 deletions

View File

@@ -296,6 +296,63 @@ impl<B: BlockDevice> Ext4<B> {
}
None
}
fn resolve_parent<'a>(&mut self, path: &'a [u8]) -> Option<(u32, &'a [u8])> {
let path = if path.starts_with(b"/") { &path[1..] } else { path };
if let Some(last_slash) = path.iter().rposition(|&b| b == b'/') {
let parent_path = &path[..last_slash];
let filename = &path[last_slash + 1..];
let parent_inode = self.resolve_path(parent_path)?;
Some((parent_inode, filename))
} else {
Some((ROOT_INODE, path))
}
}
fn append_extent(block_data: &mut [u8], logical_block: u32, physical_block: u64, length: u16) -> bool {
let magic = read_u16(block_data, 0);
if magic != EXTENT_MAGIC { return false; }
let entries = read_u16(block_data, 2);
let max = read_u16(block_data, 4);
if entries > 0 {
let last_off = 12 + (entries as usize - 1) * 12;
let last = Extent::parse(&block_data[last_off..last_off + 12]);
if last.physical_block + last.length as u64 == physical_block
&& last.logical_block + last.length as u32 == logical_block
{
let new_len = last.length + length;
io::write_u16(block_data, last_off + 4, new_len);
return true;
}
}
if entries >= max { return false; }
let off = 12 + entries as usize * 12;
if off + 12 > block_data.len() { return false; }
io::write_u32(block_data, off, logical_block);
io::write_u16(block_data, off + 4, length);
io::write_u16(block_data, off + 6, (physical_block >> 32) as u16);
io::write_u32(block_data, off + 8, physical_block as u32);
io::write_u16(block_data, 2, entries + 1);
true
}
fn free_all_blocks(&mut self, inode: &Inode) {
if !inode.uses_extents() { return; }
let hdr = &inode.block_data;
let magic = read_u16(hdr, 0);
if magic != EXTENT_MAGIC { return; }
let entries = read_u16(hdr, 2);
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]);
for b in 0..ext.length as u32 {
self.free_block(ext.physical_block as u32 + b);
}
}
}
}
impl<B: BlockDevice> FileSystem for Ext4<B> {
@@ -347,12 +404,76 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
Some(chunk)
}
fn write(&mut self, _handle: FileHandle, _buf: &[u8]) -> Option<usize> {
None
fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option<usize> {
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 = self.alloc_block()?;
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;
self.write_inode(inode_num, &inode);
}
Some(written)
}
fn close(&mut self, handle: FileHandle) {
self.open_files[handle.raw() as usize] = None;
if let Some(file) = self.open_files[handle.raw() as usize].take() {
if 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> {
@@ -407,12 +528,58 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
Some(count)
}
fn create(&mut self, _path: &[u8]) -> Option<FileHandle> {
None
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, self.gdt_block, &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, self.gdt_block, &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,
name: FileName::from_bytes(path),
block_data: new_inode.block_data,
});
Some(handle)
}
fn delete(&mut self, _path: &[u8]) -> bool {
false
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::remove_entry(
&mut self.device, &self.sb, self.gdt_block, &parent_inode, filename,
) {
Some(n) => n,
None => return false,
};
if let Some(child_inode) = self.read_inode(child_inode_num) {
self.free_all_blocks(&child_inode);
}
self.free_inode(child_inode_num);
true
}
fn file_size(&mut self, handle: FileHandle) -> Option<usize> {

View File

@@ -368,6 +368,51 @@ fn list_dir_after_add() {
assert_eq!(count, 2);
}
#[test]
fn create_file_and_read_back() {
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();
let h = fs.create(b"newfile").unwrap();
assert_eq!(fs.file_size(h), Some(0));
fs.close(h);
let mut entries = [DirEntry::empty(); 8];
let count = fs.list_dir(b"/", &mut entries).unwrap();
assert_eq!(count, 2);
}
#[test]
fn write_and_read_back() {
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();
let h = fs.create(b"writable").unwrap();
let written = fs.write(h, b"Hello, world!").unwrap();
assert_eq!(written, 13);
fs.close(h);
let h = fs.open(b"writable").unwrap();
let mut buf = [0u8; 32];
let n = fs.read(h, &mut buf).unwrap();
assert_eq!(n, 13);
assert_eq!(&buf[..13], b"Hello, world!");
fs.close(h);
}
#[test]
fn delete_file_frees_resources() {
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();
let h = fs.create(b"temp").unwrap();
fs.write(h, b"data").unwrap();
fs.close(h);
assert!(fs.delete(b"temp"));
assert!(fs.open(b"temp").is_none());
let h2 = fs.create(b"reuse").unwrap();
assert!(h2.raw() >= 0);
fs.close(h2);
}
#[test]
fn create_with_missing_parent_fails() {
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();
assert!(fs.create(b"nodir/file.txt").is_none());
}
#[test]
fn read_past_eof_returns_zero() {
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();