menios icon indicating copy to clipboard operation
menios copied to clipboard

Implement ext2 filesystem support (read-only initially)

Open pbalduino opened this issue 4 months ago • 0 comments

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

  1. Superblock parsing: Validate magic, block size, counts
  2. Root directory: List root directory entries
  3. File reading: Read test.txt, verify contents
  4. Subdirectory: List dir1/, read small.txt
  5. Medium file: Read 40KB file (uses multiple direct blocks)
  6. Large file error: Try reading >48KB file, verify error message
  7. Permission check: Verify mode bits are read correctly
  8. 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)

pbalduino avatar Sep 29 '25 23:09 pbalduino