m³OS docs GitHub

Phase 28

Ext2 Filesystem

Replace FAT32 as the primary persistent filesystem with ext2, which natively stores Unix ownership (uid/gid), permission modes, and timestamps in every inode. After this phase, file permissions set via `chmod`/`chown` survive reboots and the FAT32 permissions index file workaround from Phase 27 is no longer needed.

Complete

Learning goal

Understand on-disk filesystem layout: superblock, block group descriptors, inode tables, block/inode bitmaps.

Category

infrastructure

Why this phase matters

Replace FAT32 as the primary persistent filesystem with ext2, which natively stores Unix ownership (uid/gid), permission modes, and timestamps in every inode. After this phase, file permissions set via chmod/chown survive reboots and the FAT32 permissions index file workaround from Phase 27 is no longer needed.

How it works in m3OS

Phase 28 replaces the FAT32 permissions overlay with a native ext2 filesystem implementation. ext2 stores Unix ownership (uid/gid), permission modes, and timestamps directly in inodes, eliminating the .m3os_permissions index file workaround from Phase 27. The implementation covers ext2 revision 0 with 4K blocks, direct + single-indirect + double-indirect block pointers, linked-list directory entries, and block/inode bitmap management. The ext2 partition is mounted at / and serves as the primary persistent filesystem.

How it builds on earlier phases

This phase builds on User Accounts and turns that foundation into Ext2 Filesystem.

Implementation outline

  • Add ext2 on-disk structure definitions to kernel-core with unit tests.
  • Implement superblock and block group descriptor parsing.
  • Implement inode reading and block pointer traversal (direct + indirect).
  • Implement directory entry parsing and path resolution.
  • Implement read-only file access through the VFS layer.
  • Implement block/inode bitmap management (allocation and freeing).
  • Implement file creation, writing, truncation, and deletion.
  • Implement directory creation and deletion.
  • Implement VfsMetadata trait: chmod/chown modify inode fields directly.
  • Update xtask to create ext2 images with mkfs.ext2.
  • Mount ext2 as /data instead of FAT32.
  • Migrate /etc/passwd, /etc/shadow, /etc/group to ext2.
  • Remove FAT32 permissions index file code.

Task progress snapshot

84 of 84 tracked tasks are complete.

Documentation targets

  • Documentation deliverables have not been listed yet.

Deferred follow-on work

  • ext3/ext4 journaling
  • Triple-indirect block pointers
  • Extended attributes (xattr)
  • Directory hash tree indexing
  • Sparse superblock backups
  • Block group locality heuristics
  • Online filesystem resize
  • fsck / filesystem repair tool
  • Symbolic links (stored in inode for short links, in data blocks for long)
  • Hard link count management beyond basic tracking
  • File timestamps beyond mtime (atime, ctime updates)

Source materials

Important code

Code spotlight cards

Code spotlight

Define `Ext2Superblock` struct

kernel-core/src/fs/ext2.rs L84-L107

Define `Ext2Superblock` struct. Why it matters: The superblock contains all filesystem geometry needed to locate every other structure on disk. Focus on `Ext2Superblock` and `inodes_count` in `kernel-core/src/fs/ext2.rs` while you trace the snippet.

/// ext2 superblock — 1024 bytes at byte offset 1024 on disk.
#[derive(Debug, Clone, Copy)]
pub struct Ext2Superblock {
    pub inodes_count: u32,
    pub blocks_count: u32,
    pub r_blocks_count: u32,
    pub free_blocks_count: u32,
    pub free_inodes_count: u32,
    pub first_data_block: u32,
    pub log_block_size: u32,
    pub log_frag_size: u32,
    pub blocks_per_group: u32,
    pub frags_per_group: u32,
    pub inodes_per_group: u32,
    pub mtime: u32,
    pub wtime: u32,
    pub mnt_count: u16,
    pub max_mnt_count: u16,
    pub magic: u16,
    pub state: u16,
    pub errors: u16,
    pub minor_rev_level: u16,
    pub lastcheck: u32,
    pub checkinterval: u32,

Code spotlight

Implement `read_inode()`

kernel/src/fs/ext2.rs L119-L142

Implement `read_inode()`. Why it matters: Every file and directory access begins with reading the target inode from disk. Focus on `read_inode` in `kernel/src/fs/ext2.rs` while you trace the snippet.

    /// Read an inode by number (1-based).
    pub fn read_inode(&self, inode_num: u32) -> Result<Ext2Inode, Ext2Error> {
        let group =
            kernel_core::fs::ext2::inode_block_group(inode_num, self.superblock.inodes_per_group);
        let index = kernel_core::fs::ext2::inode_index_in_group(
            inode_num,
            self.superblock.inodes_per_group,
        );

        let bgd = self
            .bgd_table
            .get(group as usize)
            .ok_or(Ext2Error::CorruptedEntry)?;

        let inode_size = self.superblock.inode_size as u32;
        let byte_offset = (index as u64) * (inode_size as u64);
        let block_offset = byte_offset / (self.block_size as u64);
        let offset_in_block = (byte_offset % (self.block_size as u64)) as usize;

        let block_num = bgd.inode_table + block_offset as u32;
        let block_data = self.read_block(block_num)?;

        Ext2Inode::parse(&block_data[offset_in_block..])
    }

Code spotlight

Replace `mkfs.fat` with `mkfs.ext2`

xtask/src/main.rs See file

Replace `mkfs.fat` with `mkfs.ext2`. Why it matters: The data partition must be ext2 format for native Unix metadata support. Focus on `mkfs.fat` and `mkfs.ext2` in `xtask/src/main.rs` while you trace the snippet.

Key files

  • kernel-core/src/fs/ — Add ext2 data structure definitions to kernel-core/src/fs/: superblock,
  • kernel-core/src/fs/ext2.rs — All on-disk structures are defined in kernel-core/src/fs/ext2.rs for
  • kernel/src/fs/ext2.rs — The kernel-side driver (kernel/src/fs/ext2.rs) wraps all I/O in the
  • kernel/src/process/mod.rs — The FdBackend enum in kernel/src/process/mod.rs has an Ext2Disk
  • xtask/src/main.rs — xtask/src/main.rs creates the ext2 disk image at build time:

How real OSes differ

  • **ext3/ext4 journaling**: crash recovery via a write-ahead log. ext2 has no journal — an unclean shutdown requires `e2fsck` to repair.
  • **Extent trees** (ext4): replace block pointer lists for better large-file performance.
  • **Triple-indirect blocks**: ext2 supports triple-indirect for files >64 MB. We defer this — double-indirect handles up to ~64 MB with 4K blocks.
  • **Sparse superblock**: backup superblocks only in certain block groups.
  • **Directory indexing** (ext3+ htree): hash-based directory lookup for large directories. We use linear scan.
  • **Extended attributes** (xattr): ACLs, SELinux labels, etc.
  • **Block group locality**: real implementations try to allocate files near their directory's block group for locality. We use simple first-fit.
  • **Delayed allocation** (ext4): allocate blocks at writeback time, not at write time, for better layout.
  • **Online resize and defragmentation**.
  • Our implementation covers the core ext2 specification (revision 0) which is sufficient for persistent Unix-style file storage with native permissions.

Success criteria

  • Boot mounts an ext2 partition at `/data` (or `/`).
  • Files created with `touch`/`edit` are stored on ext2 and survive reboot.
  • `ls -l` (or fstat) shows correct uid, gid, and permission mode from ext2 inodes.
  • `chmod 600 /etc/shadow` persists across reboot.
  • `chown user:user /home/user/file` persists across reboot.
  • Permission enforcement from Phase 27 works identically with ext2 backend.
  • `cargo xtask check` passes.
  • The ext2 image can be mounted and inspected on the host with standard Linux tools (`mount -o loop`, `debugfs`, `ls -la`).
  • No regressions in existing functionality.

Related phases

Keep exploring the roadmap