feat(ext4): directory entry find/add/remove operations

This commit is contained in:
2026-10-18 23:14:02 +02:00
parent fe0693b969
commit bff18df349
3 changed files with 274 additions and 0 deletions

View File

@@ -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<B: BlockDevice>(
device: &mut B,
sb: &Superblock,
gdt_block: u32,
dir_inode: &Inode,
name: &[u8],
) -> Option<u32> {
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<B: BlockDevice>(
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<B: BlockDevice>(
device: &mut B,
sb: &Superblock,
gdt_block: u32,
dir_inode: &Inode,
name: &[u8],
) -> Option<u32> {
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<usize> = 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<B: BlockDevice>(
device: &mut B,
sb: &Superblock,
dir_inode: &Inode,
entries: &mut [DirEntry],
) -> Option<usize> {
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)
}

View File

@@ -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<B: BlockDevice> Ext4<B> {
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<u32> {
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<u32> {
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<Extent> {
if !inode.uses_extents() { return None; }
let hdr = &inode.block_data;

View File

@@ -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();