diff --git a/crates/servers/storage-server/src/fs/ext4/mod.rs b/crates/servers/storage-server/src/fs/ext4/mod.rs index a807c37..91dec20 100644 --- a/crates/servers/storage-server/src/fs/ext4/mod.rs +++ b/crates/servers/storage-server/src/fs/ext4/mod.rs @@ -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 Ext4 { 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 { + 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 { if !inode.uses_extents() { return None; } let hdr = &inode.block_data; @@ -232,6 +290,8 @@ impl FileSystem for Ext4 { 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)?; diff --git a/crates/servers/storage-server/src/tests/ext4.rs b/crates/servers/storage-server/src/tests/ext4.rs index 1644105..f5fba55 100644 --- a/crates/servers/storage-server/src/tests/ext4.rs +++ b/crates/servers/storage-server/src/tests/ext4.rs @@ -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::::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();