feat(ext4): file create, write, and delete with extent management
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
fn delete(&mut self, _path: &[u8]) -> bool {
|
||||
false
|
||||
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 {
|
||||
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> {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user