diff --git a/crates/servers/storage-server/src/fs/ext4/mod.rs b/crates/servers/storage-server/src/fs/ext4/mod.rs index 4f62b64..5c28a8b 100644 --- a/crates/servers/storage-server/src/fs/ext4/mod.rs +++ b/crates/servers/storage-server/src/fs/ext4/mod.rs @@ -296,6 +296,63 @@ impl Ext4 { } 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 FileSystem for Ext4 { @@ -347,12 +404,76 @@ impl FileSystem for Ext4 { Some(chunk) } - fn write(&mut self, _handle: FileHandle, _buf: &[u8]) -> Option { - None + fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option { + 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 { @@ -407,12 +528,58 @@ impl FileSystem for Ext4 { Some(count) } - fn create(&mut self, _path: &[u8]) -> Option { - None + fn create(&mut self, path: &[u8]) -> Option { + 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 { diff --git a/crates/servers/storage-server/src/tests/ext4.rs b/crates/servers/storage-server/src/tests/ext4.rs index f022ccb..2469be5 100644 --- a/crates/servers/storage-server/src/tests/ext4.rs +++ b/crates/servers/storage-server/src/tests/ext4.rs @@ -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();