feat(ext4): directory entry find/add/remove operations
This commit is contained in:
229
crates/servers/storage-server/src/fs/ext4/dir.rs
Normal file
229
crates/servers/storage-server/src/fs/ext4/dir.rs
Normal 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)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ pub mod io;
|
|||||||
pub mod superblock;
|
pub mod superblock;
|
||||||
pub mod bgd;
|
pub mod bgd;
|
||||||
pub mod bitmap;
|
pub mod bitmap;
|
||||||
|
pub mod dir;
|
||||||
|
|
||||||
use port_block_device::BlockDevice;
|
use port_block_device::BlockDevice;
|
||||||
use crate::filesystem::{DirEntry, FileHandle, FileName, FileSystem};
|
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);
|
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> {
|
fn read_extents(&self, inode: &Inode) -> Option<Extent> {
|
||||||
if !inode.uses_extents() { return None; }
|
if !inode.uses_extents() { return None; }
|
||||||
let hdr = &inode.block_data;
|
let hdr = &inode.block_data;
|
||||||
|
|||||||
@@ -342,6 +342,32 @@ fn free_block_makes_it_available_again() {
|
|||||||
assert_eq!(b1, b2);
|
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]
|
#[test]
|
||||||
fn read_past_eof_returns_zero() {
|
fn read_past_eof_returns_zero() {
|
||||||
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();
|
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user