diff --git a/crates/servers/storage-server/src/fs/ext4/dir.rs b/crates/servers/storage-server/src/fs/ext4/dir.rs new file mode 100644 index 0000000..c8f7ca0 --- /dev/null +++ b/crates/servers/storage-server/src/fs/ext4/dir.rs @@ -0,0 +1,229 @@ +use port_block_device::BlockDevice; +use crate::filesystem::{DirEntry, FileName}; +use super::io::{read_block, write_block, read_u32, write_u32, read_u16, write_u16}; +use super::superblock::Superblock; +use super::bgd::BlockGroupDescriptor; +use super::{Inode, Extent, EXTENT_MAGIC}; + +fn align4(n: usize) -> usize { + (n + 3) & !3 +} + +pub fn find_entry( + device: &mut B, + sb: &Superblock, + gdt_block: u32, + dir_inode: &Inode, + name: &[u8], +) -> Option { + if !dir_inode.is_dir() || !dir_inode.uses_extents() { return None; } + + let hdr = &dir_inode.block_data; + let magic = read_u16(hdr, 0); + if magic != EXTENT_MAGIC { return None; } + let entries = read_u16(hdr, 2); + let spb = sb.sectors_per_block(); + + 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]); + + let mut block_buf = [0u8; 4096]; + for blk in 0..ext.length as u64 { + read_block(device, ext.physical_block + blk, spb, &mut block_buf); + let mut pos = 0; + while pos + 8 <= 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 +} + +pub fn add_entry( + device: &mut B, + sb: &Superblock, + gdt_block: u32, + dir_inode: &Inode, + name: &[u8], + child_inode: u32, + file_type: u8, +) -> bool { + if !dir_inode.is_dir() || !dir_inode.uses_extents() { return false; } + + let hdr = &dir_inode.block_data; + let magic = read_u16(hdr, 0); + if magic != EXTENT_MAGIC { return false; } + let entries = read_u16(hdr, 2); + let spb = sb.sectors_per_block(); + let new_rec_needed = align4(8 + name.len()); + + 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]); + + let mut block_buf = [0u8; 4096]; + for blk in 0..ext.length as u64 { + let phys = ext.physical_block + blk; + read_block(device, phys, spb, &mut block_buf); + let mut pos = 0; + let mut prev_pos = 0; + while pos + 8 <= 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; } + + let actual_size = if entry_inode != 0 { + align4(8 + name_len) + } else { + 0 + }; + + let free_space = rec_len - actual_size; + if free_space >= new_rec_needed { + if entry_inode != 0 { + // Shrink this entry to its actual size + write_u16(&mut block_buf, pos + 4, actual_size as u16); + // Write new entry after it + let new_pos = pos + actual_size; + write_u32(&mut block_buf, new_pos, child_inode); + write_u16(&mut block_buf, new_pos + 4, free_space as u16); + block_buf[new_pos + 6] = name.len() as u8; + block_buf[new_pos + 7] = file_type; + block_buf[new_pos + 8..new_pos + 8 + name.len()].copy_from_slice(name); + } else { + // Reuse deleted entry + write_u32(&mut block_buf, pos, child_inode); + // Keep rec_len as is + block_buf[pos + 6] = name.len() as u8; + block_buf[pos + 7] = file_type; + block_buf[pos + 8..pos + 8 + name.len()].copy_from_slice(name); + } + write_block(device, phys, spb, &block_buf); + return true; + } + + prev_pos = pos; + pos += rec_len; + } + } + } + false +} + +pub fn remove_entry( + device: &mut B, + sb: &Superblock, + gdt_block: u32, + dir_inode: &Inode, + name: &[u8], +) -> Option { + if !dir_inode.is_dir() || !dir_inode.uses_extents() { return None; } + + let hdr = &dir_inode.block_data; + let magic = read_u16(hdr, 0); + if magic != EXTENT_MAGIC { return None; } + let entries = read_u16(hdr, 2); + let spb = sb.sectors_per_block(); + + 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]); + + let mut block_buf = [0u8; 4096]; + for blk in 0..ext.length as u64 { + let phys = ext.physical_block + blk; + read_block(device, phys, spb, &mut block_buf); + let mut pos = 0; + let mut prev_pos: Option = None; + while pos + 8 <= 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 { + if let Some(pp) = prev_pos { + // Merge with previous entry + let prev_rec = read_u16(&block_buf, pp + 4) as usize; + write_u16(&mut block_buf, pp + 4, (prev_rec + rec_len) as u16); + } else { + // First entry — zero the inode + write_u32(&mut block_buf, pos, 0); + } + write_block(device, phys, spb, &block_buf); + return Some(entry_inode); + } + } + prev_pos = Some(pos); + pos += rec_len; + } + } + } + None +} + +pub fn list_entries( + device: &mut B, + sb: &Superblock, + dir_inode: &Inode, + entries: &mut [DirEntry], +) -> Option { + if !dir_inode.is_dir() || !dir_inode.uses_extents() { return None; } + + let hdr = &dir_inode.block_data; + let magic = read_u16(hdr, 0); + if magic != EXTENT_MAGIC { return None; } + let ext_entries = read_u16(hdr, 2); + let spb = sb.sectors_per_block(); + let mut count = 0; + + for i in 0..ext_entries as usize { + let off = 12 + i * 12; + if off + 12 > hdr.len() { break; } + let ext = Extent::parse(&hdr[off..off + 12]); + + let mut block_buf = [0u8; 4096]; + for blk in 0..ext.length as u64 { + read_block(device, ext.physical_block + blk, spb, &mut block_buf); + let mut pos = 0; + while pos + 8 <= 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".." { + entries[count] = DirEntry { + name: FileName::from_bytes(name), + size: 0, + is_dir: file_type == 2, + }; + count += 1; + } + } + pos += rec_len; + } + } + } + Some(count) +} diff --git a/crates/servers/storage-server/src/fs/ext4/mod.rs b/crates/servers/storage-server/src/fs/ext4/mod.rs index 488264c..4f62b64 100644 --- a/crates/servers/storage-server/src/fs/ext4/mod.rs +++ b/crates/servers/storage-server/src/fs/ext4/mod.rs @@ -2,6 +2,7 @@ pub mod io; pub mod superblock; pub mod bgd; pub mod bitmap; +pub mod dir; use port_block_device::BlockDevice; use crate::filesystem::{DirEntry, FileHandle, FileName, FileSystem}; @@ -194,6 +195,24 @@ impl Ext4 { bitmap::free_inode(&mut self.device, &mut self.sb, self.gdt_block, inode, spb); } + pub fn find_in_dir_pub(&mut self, dir_inode_num: u32, name: &[u8]) -> Option { + let inode = self.read_inode(dir_inode_num)?; + dir::find_entry(&mut self.device, &self.sb, self.gdt_block, &inode, name) + } + + pub fn add_dir_entry(&mut self, dir_inode_num: u32, name: &[u8], child_inode: u32, file_type: u8) -> bool { + let inode = match self.read_inode(dir_inode_num) { + Some(i) => i, + None => return false, + }; + dir::add_entry(&mut self.device, &self.sb, self.gdt_block, &inode, name, child_inode, file_type) + } + + pub fn remove_dir_entry(&mut self, dir_inode_num: u32, name: &[u8]) -> Option { + let inode = self.read_inode(dir_inode_num)?; + dir::remove_entry(&mut self.device, &self.sb, self.gdt_block, &inode, name) + } + fn read_extents(&self, inode: &Inode) -> Option { if !inode.uses_extents() { return None; } let hdr = &inode.block_data; diff --git a/crates/servers/storage-server/src/tests/ext4.rs b/crates/servers/storage-server/src/tests/ext4.rs index c80797f..f022ccb 100644 --- a/crates/servers/storage-server/src/tests/ext4.rs +++ b/crates/servers/storage-server/src/tests/ext4.rs @@ -342,6 +342,32 @@ fn free_block_makes_it_available_again() { assert_eq!(b1, b2); } +#[test] +fn add_and_find_dir_entry() { + let mut fs = Ext4::mount(format_ext4_disk()).unwrap(); + assert!(fs.find_in_dir_pub(2, b"hello").is_some()); + assert!(fs.add_dir_entry(2, b"world", 4, 1)); + assert_eq!(fs.find_in_dir_pub(2, b"world"), Some(4)); +} + +#[test] +fn remove_dir_entry() { + let mut fs = Ext4::mount(format_ext4_disk()).unwrap(); + assert!(fs.find_in_dir_pub(2, b"hello").is_some()); + let removed = fs.remove_dir_entry(2, b"hello"); + assert_eq!(removed, Some(3)); + assert!(fs.find_in_dir_pub(2, b"hello").is_none()); +} + +#[test] +fn list_dir_after_add() { + let mut fs = Ext4::mount(format_ext4_disk()).unwrap(); + fs.add_dir_entry(2, b"second", 4, 1); + let mut entries = [DirEntry::empty(); 8]; + let count = fs.list_dir(b"/", &mut entries).unwrap(); + assert_eq!(count, 2); +} + #[test] fn read_past_eof_returns_zero() { let mut fs = Ext4::mount(format_ext4_disk()).unwrap();