menios
menios copied to clipboard
Implement ext2 filesystem support (read-only initially)
Goal
Implement read-only ext2 filesystem support as Phase 1 of the full ext2 implementation (#227). This provides the foundation for mounting and reading Linux-formatted ext2 partitions.
Context
This is the first milestone in the ext2 roadmap. Read-only support allows meniOS to:
- Mount existing ext2 partitions safely
- Read files and directories from Linux filesystems
- Test the core ext2 infrastructure before adding write capabilities
- Validate superblock, block group, and inode parsing
Read-only mode is safer for initial implementation and testing, preventing any risk of filesystem corruption.
Dependencies
Required (Blocking)
- #62 - Block device driver ✅ COMPLETE
- #63 - Block cache ✅ COMPLETE
- #65 - VFS layer ✅ COMPLETE
- #96 - File descriptor management (for file operations)
Part Of
- #227 - Full ext2 implementation (this is Phase 1)
Blocks (Downstream)
- #228 - Dual partition mount (needs read-only ext2 first)
- #229 - Binary migration (needs full write support from #227)
Priority
Medium - First step toward proper UNIX filesystem, not blocking critical milestones
Justification
- Foundation for full ext2 support
- Enables mounting Linux partitions read-only
- Lower risk than full read-write implementation
- Not immediately needed for Doom or GCC milestones
- Good incremental progress toward #227
Scope
Phase 1.1: Core Structures (3-4 days)
- Parse ext2 superblock at offset 1024
- Validate magic number (0xEF53) and filesystem state
- Read block group descriptor table
- Calculate filesystem geometry (block size, groups, etc.)
- Basic error detection
Phase 1.2: Inode Reading (3-4 days)
- Implement inode number → block calculation
- Read inode structures from inode tables
- Support direct block pointers (i_block[0-11])
- Extract file metadata (size, mode, timestamps)
- Validate inode structures
Phase 1.3: Directory Traversal (3-4 days)
- Parse ext2_dir_entry structures
- Implement directory listing
- Support name lookup in directories
- Handle variable-length directory entries
- Validate directory structures
Phase 1.4: File Reading (2-3 days)
- Read file data from direct blocks
- Implement read() for files ≤48KB (direct blocks only)
- VFS integration for file operations
- Handle EOF and partial reads
Phase 1.5: Testing & Integration (3-4 days)
- Create test ext2 images
- Unit tests for parsing functions
- Integration tests with VFS
- Verify read operations
- Document limitations
Total Estimated: 2-3 weeks (14-19 days)
Implementation Details
Superblock Reading
// Read and validate ext2 superblock
int ext2_read_super(struct block_device *bdev, struct ext2_superblock *sb) {
// Superblock starts at byte offset 1024
uint8_t buffer[1024];
if (block_read(bdev, 2, buffer, 1024) < 0) {
return -EIO;
}
memcpy(sb, buffer, sizeof(struct ext2_superblock));
// Validate magic number
if (sb->s_magic != EXT2_SUPER_MAGIC) {
return -EINVAL;
}
// Check filesystem state
if (sb->s_state != EXT2_VALID_FS) {
kprintf("WARNING: ext2 filesystem not cleanly unmounted\n");
}
return 0;
}
Block Group Descriptors
// Read block group descriptor table
int ext2_read_group_descs(struct superblock *sb) {
struct ext2_sb_info *sbi = sb->s_fs_info;
uint32_t block_size = 1024 << sbi->s_log_block_size;
uint32_t groups = (sbi->s_blocks_count + sbi->s_blocks_per_group - 1)
/ sbi->s_blocks_per_group;
// Group descriptors start in block after superblock
uint32_t gd_block = sbi->s_first_data_block + 1;
sbi->s_group_desc = kmalloc(groups * sizeof(struct ext2_group_desc));
return block_read(sb->s_bdev, gd_block, sbi->s_group_desc,
groups * sizeof(struct ext2_group_desc));
}
Inode Location
// Calculate inode location on disk
int ext2_get_inode_block(struct superblock *sb, uint32_t ino,
uint32_t *block, uint32_t *offset) {
struct ext2_sb_info *sbi = sb->s_fs_info;
// Inode numbers start at 1
if (ino < 1 || ino > sbi->s_inodes_count) {
return -EINVAL;
}
ino--; // Convert to 0-based
// Find block group
uint32_t group = ino / sbi->s_inodes_per_group;
uint32_t index = ino % sbi->s_inodes_per_group;
if (group >= sbi->s_groups_count) {
return -EINVAL;
}
// Get inode table start from group descriptor
struct ext2_group_desc *gd = &sbi->s_group_desc[group];
uint32_t inode_table = gd->bg_inode_table;
// Calculate block and offset within block
uint32_t inode_size = sbi->s_inode_size;
uint32_t block_size = 1024 << sbi->s_log_block_size;
uint32_t inodes_per_block = block_size / inode_size;
*block = inode_table + (index / inodes_per_block);
*offset = (index % inodes_per_block) * inode_size;
return 0;
}
Directory Reading
// Read directory entries
int ext2_readdir_ro(struct file *file, void *dirent, filldir_t filldir) {
struct inode *inode = file->f_inode;
uint32_t offset = file->f_pos;
// Read directory data blocks
uint8_t *block_buf = kmalloc(block_size);
while (offset < inode->i_size) {
uint32_t block_num = offset / block_size;
uint32_t block_offset = offset % block_size;
// Read directory block (direct blocks only for Phase 1)
if (block_num >= EXT2_NDIR_BLOCKS) {
break; // Skip indirect blocks in read-only Phase 1
}
ext2_read_block(inode->i_sb, inode->i_block[block_num], block_buf);
struct ext2_dir_entry *de = (struct ext2_dir_entry *)(block_buf + block_offset);
// Validate directory entry
if (de->inode == 0 || de->rec_len == 0) {
break;
}
// Call filldir callback
if (filldir(dirent, de->name, de->name_len, offset, de->inode, de->file_type) < 0) {
break;
}
offset += de->rec_len;
}
kfree(block_buf);
file->f_pos = offset;
return 0;
}
File Reading
// Read file data (direct blocks only)
ssize_t ext2_file_read_ro(struct file *file, char *buffer, size_t count, loff_t *offset) {
struct inode *inode = file->f_inode;
uint32_t block_size = 1024 << inode->i_sb->s_log_block_size;
// Don't read past EOF
if (*offset >= inode->i_size) {
return 0;
}
if (*offset + count > inode->i_size) {
count = inode->i_size - *offset;
}
ssize_t bytes_read = 0;
uint8_t *block_buf = kmalloc(block_size);
while (count > 0) {
uint32_t block_num = *offset / block_size;
uint32_t block_offset = *offset % block_size;
uint32_t to_read = min(count, block_size - block_offset);
// Only support direct blocks in Phase 1
if (block_num >= EXT2_NDIR_BLOCKS) {
kprintf("ext2_read: file too large (>48KB), indirect blocks not supported in read-only mode\n");
break;
}
// Read block
if (ext2_read_block(inode->i_sb, inode->i_block[block_num], block_buf) < 0) {
break;
}
// Copy to user buffer
memcpy(buffer, block_buf + block_offset, to_read);
buffer += to_read;
*offset += to_read;
bytes_read += to_read;
count -= to_read;
}
kfree(block_buf);
return bytes_read;
}
VFS Integration
Mount Operation
struct superblock *ext2_mount_ro(struct block_device *bdev, uint32_t flags) {
struct superblock *sb = kmalloc(sizeof(struct superblock));
struct ext2_sb_info *sbi = kmalloc(sizeof(struct ext2_sb_info));
sb->s_bdev = bdev;
sb->s_fs_info = sbi;
sb->s_op = &ext2_super_ops_ro;
// Read superblock
if (ext2_read_super(bdev, &sbi->s_es) < 0) {
kprintf("ext2_mount: failed to read superblock\n");
goto fail;
}
// Read block group descriptors
if (ext2_read_group_descs(sb) < 0) {
kprintf("ext2_mount: failed to read group descriptors\n");
goto fail;
}
// Get root inode (inode 2)
sb->s_root = ext2_iget(sb, EXT2_ROOT_INO);
if (!sb->s_root) {
kprintf("ext2_mount: failed to read root inode\n");
goto fail;
}
kprintf("ext2_mount: mounted successfully (read-only)\n");
kprintf(" Block size: %u\n", 1024 << sbi->s_log_block_size);
kprintf(" Inodes: %u\n", sbi->s_inodes_count);
kprintf(" Blocks: %u\n", sbi->s_blocks_count);
kprintf(" Block groups: %u\n", sbi->s_groups_count);
return sb;
fail:
kfree(sbi);
kfree(sb);
return NULL;
}
Filesystem Registration
struct filesystem_type ext2_fs_type_ro = {
.name = "ext2ro",
.mount = ext2_mount_ro,
.unmount = ext2_unmount_ro,
.flags = FS_REQUIRES_DEV | FS_READONLY,
};
void ext2_init_ro(void) {
register_filesystem(&ext2_fs_type_ro);
kprintf("ext2: read-only driver registered\n");
}
Limitations (Phase 1)
Not Supported
- ❌ File writing (read-only mode)
- ❌ File creation/deletion
- ❌ Directory creation/deletion
- ❌ Indirect blocks (files >48KB)
- ❌ Symbolic links (can read link inode, but not follow)
- ❌ Hard links (multiple inodes pointing to same file)
- ❌ Extended attributes
- ❌ Journaling
- ❌ File locking
Supported
- ✅ Superblock and block group parsing
- ✅ Inode reading (metadata)
- ✅ Directory listing
- ✅ File reading (≤48KB via direct blocks)
- ✅ Permission checking (read-only)
- ✅ Timestamps
- ✅ File size and type detection
- ✅ VFS integration
Testing Strategy
Test ext2 Image Creation
# Create 10MB ext2 filesystem
dd if=/dev/zero of=test_ext2.img bs=1M count=10
mkfs.ext2 -F test_ext2.img
# Mount and populate
mkdir -p /tmp/ext2test
sudo mount -o loop test_ext2.img /tmp/ext2test
# Create test files
echo "Hello ext2" > /tmp/ext2test/test.txt
mkdir /tmp/ext2test/dir1
echo "Small file" > /tmp/ext2test/dir1/small.txt
dd if=/dev/urandom of=/tmp/ext2test/medium.dat bs=1K count=40 # 40KB
sudo umount /tmp/ext2test
Test Cases
- Superblock parsing: Validate magic, block size, counts
- Root directory: List root directory entries
- File reading: Read test.txt, verify contents
- Subdirectory: List dir1/, read small.txt
- Medium file: Read 40KB file (uses multiple direct blocks)
- Large file error: Try reading >48KB file, verify error message
- Permission check: Verify mode bits are read correctly
- Concurrent read: Multiple processes reading same file
Definition of Done
- [ ] Superblock reading and validation working
- [ ] Block group descriptor parsing working
- [ ] Inode reading functional
- [ ] Directory listing works (direct blocks only)
- [ ] File reading works for files ≤48KB
- [ ] VFS integration complete
- [ ] Can mount real ext2 filesystem read-only
- [ ] Test suite passing
- [ ] Documentation updated
- [ ] No memory leaks in mount/unmount cycle
Next Steps (After Phase 1)
Phase 2 (part of #227):
- Indirect block support (single, double, triple)
- Files up to 4GB
- Symbolic link following
Phase 3 (part of #227):
- Write support (block/inode allocation)
- File creation and modification
- Directory operations
Files to Create
- src/kernel/fs/ext2/super_ro.c - Read-only superblock operations
- src/kernel/fs/ext2/inode_ro.c - Read-only inode operations
- src/kernel/fs/ext2/file_ro.c - Read-only file operations
- src/kernel/fs/ext2/dir_ro.c - Read-only directory operations
- include/kernel/fs/ext2.h - ext2 structures and definitions
- test/test_ext2_ro.c - Read-only ext2 tests
Performance Goals
- Mount time < 50ms for small filesystems
- File read performance comparable to FAT32
- Directory listing < 10ms for cached directories
- Memory overhead < 100KB per mounted filesystem
Error Handling
- Validate superblock magic number
- Check filesystem state (cleanly unmounted)
- Detect corrupted structures gracefully
- Return proper error codes (EINVAL, EIO, etc.)
- Prevent crashes on malformed filesystems
Related Issues
- #227 - Full ext2 implementation (parent issue)
- #65 - VFS layer ✅ COMPLETE
- #62 - Block device driver ✅ COMPLETE
- #63 - Block cache ✅ COMPLETE
- #228 - Dual partition mount (blocked by this)
- #229 - Binary migration (blocked by #227 write support)
Current Status
Ready to Start - All dependencies satisfied, can begin Phase 1.1 (Core Structures)