feat(ext4): inode read/write, init_empty_inode, multi-extent read
This commit is contained in:
@@ -16,6 +16,8 @@ pub struct Inode {
|
||||
pub mode: u16,
|
||||
pub size: u64,
|
||||
pub flags: u32,
|
||||
pub links_count: u16,
|
||||
pub blocks_count: u32,
|
||||
pub block_data: [u8; 60],
|
||||
}
|
||||
|
||||
@@ -111,10 +113,66 @@ impl<B: BlockDevice> Ext4<B> {
|
||||
mode: read_u16(d, 0x00),
|
||||
size,
|
||||
flags: read_u32(d, 0x20),
|
||||
links_count: read_u16(d, 0x1A),
|
||||
blocks_count: read_u32(d, 0x1C),
|
||||
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> {
|
||||
if !inode.uses_extents() { return None; }
|
||||
let hdr = &inode.block_data;
|
||||
@@ -232,6 +290,8 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
|
||||
mode: 0,
|
||||
size: file.size as u64,
|
||||
flags: 0x80000,
|
||||
links_count: 0,
|
||||
blocks_count: 0,
|
||||
block_data: file.block_data,
|
||||
};
|
||||
let phys_block = self.map_logical_block(&inode, logical_block)?;
|
||||
|
||||
@@ -154,6 +154,156 @@ fn open_nonexistent_returns_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]
|
||||
fn read_past_eof_returns_zero() {
|
||||
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user