feat(ext4): inode read/write, init_empty_inode, multi-extent read

This commit is contained in:
2026-10-18 23:09:57 +02:00
parent 5d5714914f
commit 381669d9dd
2 changed files with 210 additions and 0 deletions

View File

@@ -16,6 +16,8 @@ pub struct Inode {
pub mode: u16, pub mode: u16,
pub size: u64, pub size: u64,
pub flags: u32, pub flags: u32,
pub links_count: u16,
pub blocks_count: u32,
pub block_data: [u8; 60], pub block_data: [u8; 60],
} }
@@ -111,10 +113,66 @@ impl<B: BlockDevice> Ext4<B> {
mode: read_u16(d, 0x00), mode: read_u16(d, 0x00),
size, size,
flags: read_u32(d, 0x20), flags: read_u32(d, 0x20),
links_count: read_u16(d, 0x1A),
blocks_count: read_u32(d, 0x1C),
block_data, block_data,
}) })
} }
fn write_inode(&mut self, inode_num: u32, inode: &Inode) -> bool {
let bgd = match BlockGroupDescriptor::read(
&mut self.device, self.gdt_block,
self.sb.inode_block_group(inode_num),
self.sb.sectors_per_block(),
) {
Some(b) => b,
None => return false,
};
let index = self.sb.inode_index_in_group(inode_num);
let inode_offset = (index as usize) * (self.sb.inode_size as usize);
let block_offset = inode_offset / self.sb.block_size;
let offset_in_block = inode_offset % self.sb.block_size;
let mut block_buf = [0u8; 4096];
let block_num = (bgd.inode_table + block_offset as u32) as u64;
read_block(&mut self.device, block_num, self.sb.sectors_per_block(), &mut block_buf);
let d = &mut block_buf[offset_in_block..];
io::write_u16(d, 0x00, inode.mode);
io::write_u32(d, 0x04, inode.size as u32);
io::write_u16(d, 0x1A, inode.links_count);
io::write_u32(d, 0x1C, inode.blocks_count);
io::write_u32(d, 0x20, inode.flags);
d[0x28..0x64].copy_from_slice(&inode.block_data);
io::write_u32(d, 0x6C, (inode.size >> 32) as u32);
io::write_block(&mut self.device, block_num, self.sb.sectors_per_block(), &block_buf)
}
pub fn init_empty_inode(mode: u16) -> Inode {
let mut block_data = [0u8; 60];
io::write_u16(&mut block_data, 0, EXTENT_MAGIC);
io::write_u16(&mut block_data, 2, 0); // entries = 0
io::write_u16(&mut block_data, 4, 4); // max = 4
io::write_u16(&mut block_data, 6, 0); // depth = 0
Inode {
mode,
size: 0,
flags: 0x80000,
links_count: 1,
blocks_count: 0,
block_data,
}
}
pub fn read_inode_pub(&mut self, inode_num: u32) -> Option<Inode> {
self.read_inode(inode_num)
}
pub fn write_inode_pub(&mut self, inode_num: u32, inode: &Inode) -> bool {
self.write_inode(inode_num, inode)
}
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;
@@ -232,6 +290,8 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
mode: 0, mode: 0,
size: file.size as u64, size: file.size as u64,
flags: 0x80000, flags: 0x80000,
links_count: 0,
blocks_count: 0,
block_data: file.block_data, block_data: file.block_data,
}; };
let phys_block = self.map_logical_block(&inode, logical_block)?; let phys_block = self.map_logical_block(&inode, logical_block)?;

View File

@@ -154,6 +154,156 @@ fn open_nonexistent_returns_none() {
assert!(fs.open(b"nope").is_none()); assert!(fs.open(b"nope").is_none());
} }
fn format_ext4_two_extents() -> MockBlockDevice {
let mut dev = MockBlockDevice::new(64);
let mut buf = [0u8; 512];
// Superblock (same as format_ext4_disk)
write_u32(&mut buf, 0x00, 32);
write_u32(&mut buf, 0x04, 64);
write_u32(&mut buf, 0x18, 2);
write_u32(&mut buf, 0x20, 64);
write_u32(&mut buf, 0x28, 32);
write_u16(&mut buf, 0x38, 0xEF53);
write_u16(&mut buf, 0x58, 128);
write_u32(&mut buf, 0x14, 0);
dev.write_sector(SectorId::new(2), &buf);
// GDT
let mut gdt = [0u8; 512];
write_u32(&mut gdt, 8, 3);
dev.write_sector(SectorId::new(8), &gdt);
// Inode table at block 3
let mut inode_sector = [0u8; 512];
// Root inode (inode 2) at offset 128
let ioff = 128;
write_u16(&mut inode_sector, ioff + 0x00, 0x4000 | 0o755);
write_u32(&mut inode_sector, ioff + 0x04, 4096);
write_u32(&mut inode_sector, ioff + 0x20, 0x80000);
let eh = ioff + 0x28;
write_u16(&mut inode_sector, eh + 0, 0xF30A);
write_u16(&mut inode_sector, eh + 2, 1);
write_u16(&mut inode_sector, eh + 4, 4);
write_u16(&mut inode_sector, eh + 6, 0);
let ext = eh + 12;
write_u32(&mut inode_sector, ext + 0, 0);
write_u16(&mut inode_sector, ext + 4, 1);
write_u16(&mut inode_sector, ext + 6, 0);
write_u32(&mut inode_sector, ext + 8, 4);
// Inode 3 (file "big") at offset 256 — TWO extents
let ioff2 = 256;
write_u16(&mut inode_sector, ioff2 + 0x00, 0x8000 | 0o644);
write_u32(&mut inode_sector, ioff2 + 0x04, 8192); // 2 blocks
write_u32(&mut inode_sector, ioff2 + 0x20, 0x80000);
let eh2 = ioff2 + 0x28;
write_u16(&mut inode_sector, eh2 + 0, 0xF30A);
write_u16(&mut inode_sector, eh2 + 2, 2); // 2 extents
write_u16(&mut inode_sector, eh2 + 4, 4);
write_u16(&mut inode_sector, eh2 + 6, 0);
// Extent 1: logical=0, len=1, phys=5
let ext2a = eh2 + 12;
write_u32(&mut inode_sector, ext2a + 0, 0);
write_u16(&mut inode_sector, ext2a + 4, 1);
write_u16(&mut inode_sector, ext2a + 6, 0);
write_u32(&mut inode_sector, ext2a + 8, 5);
// Extent 2: logical=1, len=1, phys=7
let ext2b = eh2 + 24;
write_u32(&mut inode_sector, ext2b + 0, 1);
write_u16(&mut inode_sector, ext2b + 4, 1);
write_u16(&mut inode_sector, ext2b + 6, 0);
write_u32(&mut inode_sector, ext2b + 8, 7);
dev.write_sector(SectorId::new(24), &inode_sector);
// Root dir at block 4
let mut dir = [0u8; 512];
let mut pos = 0;
write_u32(&mut dir, pos + 0, 2);
write_u16(&mut dir, pos + 4, 12);
dir[pos + 6] = 1;
dir[pos + 7] = 2;
dir[pos + 8] = b'.';
pos += 12;
write_u32(&mut dir, pos + 0, 2);
write_u16(&mut dir, pos + 4, 12);
dir[pos + 6] = 2;
dir[pos + 7] = 2;
dir[pos + 8] = b'.';
dir[pos + 9] = b'.';
pos += 12;
write_u32(&mut dir, pos + 0, 3);
write_u16(&mut dir, pos + 4, 488);
dir[pos + 6] = 3;
dir[pos + 7] = 1;
dir[pos + 8..pos + 11].copy_from_slice(b"big");
dev.write_sector(SectorId::new(32), &dir);
// Block 5: filled with 'A'
let mut data_a = [b'A'; 512];
for s in 0..8 {
dev.write_sector(SectorId::new(40 + s), &data_a);
}
// Block 7: filled with 'B' (sectors 56-63)
let data_b = [b'B'; 512];
for s in 0..8 {
dev.write_sector(SectorId::new(56 + s), &data_b);
}
dev
}
#[test]
fn read_file_across_two_extents() {
let mut fs = Ext4::mount(format_ext4_two_extents()).unwrap();
let h = fs.open(b"big").unwrap();
assert_eq!(fs.file_size(h), Some(8192));
// First read: should get data from block 5 (all 'A's)
let mut buf = [0u8; 4096];
let n1 = fs.read(h, &mut buf).unwrap();
assert_eq!(n1, 4096);
assert!(buf.iter().all(|&b| b == b'A'));
// Second read: should get data from block 7 (all 'B's)
let n2 = fs.read(h, &mut buf).unwrap();
assert_eq!(n2, 4096);
assert!(buf.iter().all(|&b| b == b'B'));
fs.close(h);
}
#[test]
fn write_inode_preserves_data() {
let dev = format_ext4_disk();
let mut fs = Ext4::mount(dev).unwrap();
// Modify inode 3's size via write_inode, re-read, verify
let mut inode = fs.read_inode_pub(3).unwrap();
assert_eq!(inode.size, 13);
inode.size = 42;
assert!(fs.write_inode_pub(3, &inode));
let inode2 = fs.read_inode_pub(3).unwrap();
assert_eq!(inode2.size, 42);
}
#[test]
fn init_empty_inode_has_valid_extent_header() {
let inode = Ext4::<MockBlockDevice>::init_empty_inode(0x8000 | 0o644);
assert!(inode.is_file());
assert!(inode.uses_extents());
assert_eq!(inode.size, 0);
assert_eq!(inode.links_count, 1);
// Extent header magic
assert_eq!(inode.block_data[0], 0x0A);
assert_eq!(inode.block_data[1], 0xF3);
// entries = 0
assert_eq!(inode.block_data[2], 0);
assert_eq!(inode.block_data[3], 0);
}
#[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();