Skip to content

File Systems

Enrico Fraccaroli edited this page Jan 28, 2026 · 10 revisions

Filesystem

This page covers the Virtual File System (VFS) layer and supported filesystem implementations.

Overview

MentOS implements a Virtual File System abstraction that supports multiple filesystem types:

  • EXT2 - Primary persistent filesystem on disk
  • ProcFS - Virtual filesystem at /proc with process information
  • Pipes - Named and unnamed pipes for IPC

Key Files:

  • kernel/inc/fs/vfs.h - VFS abstraction layer
  • kernel/src/fs/vfs.c - VFS implementation
  • kernel/src/fs/ext2.c - EXT2 filesystem driver
  • kernel/src/fs/procfs.c - Process filesystem
  • kernel/src/fs/pipe.c - Pipe implementation
  • kernel/src/fs/namei.c - Path name resolution
  • kernel/src/fs/fcntl.c - File control
  • kernel/src/fs/ioctl.c - Device control
  • kernel/src/fs/vfs.c - VFS core

Virtual File System Architecture

The VFS abstracts filesystem operations:

User Programs
     ↓
POSIX API (open, read, write, etc.)
     ↓
VFS Layer (vfs.h/vfs.c)
  ├─ File descriptors
  ├─ Superblocks (mount points)
  ├─ Inodes (files/directories)
  ├─ Directory entries
  └─ Generic file operations
     ↓
Filesystem Implementations
  ├─ EXT2 Driver
  ├─ ProcFS Driver
  └─ Pipe Driver
     ↓
Hardware/Block Device (ATA disk, RAM, etc.)

VFS Data Structures

File (vfs_file_t)

Represents an open file instance:

typedef struct vfs_file {
    vfs_inode_t *ino;           // Associated inode
    vfs_dentry_t *dentry;       // Directory entry
    unsigned long f_pos;        // Current read/write position
    int f_flags;                // Open flags (O_RDONLY, O_WRONLY, etc.)
    mode_t f_mode;              // File mode/permissions
    struct file_operations *f_ops;  // Filesystem-specific operations
    void *private_data;         // Filesystem-specific data
} vfs_file_t;

// File operations
struct file_operations {
    ssize_t (*read)(vfs_file_t *file, void *buf, size_t count);
    ssize_t (*write)(vfs_file_t *file, const void *buf, size_t count);
    off_t (*lseek)(vfs_file_t *file, off_t offset, int whence);
    int (*ioctl)(vfs_file_t *file, unsigned int cmd, unsigned long arg);
};

Inode (vfs_inode_t)

Represents a file or directory metadata:

typedef struct vfs_inode {
    ino_t i_ino;                // Inode number (unique per filesystem)
    mode_t i_mode;              // Type (regular, directory, symlink) + permissions
    nlink_t i_nlink;            // Hard link count
    uid_t i_uid;                // Owner user ID
    gid_t i_gid;                // Owner group ID
    off_t i_size;               // File size in bytes
    time_t i_atime;             // Last access time
    time_t i_mtime;             // Last modification time
    time_t i_ctime;             // Last inode change time
    unsigned long i_blocks;     // Number of disk blocks
    unsigned long i_blksize;    // Preferred I/O block size
    struct inode_operations *i_ops;
    void *i_sb;                 // Pointer to superblock
    void *i_private;            // Filesystem-specific data
} vfs_inode_t;

// Inode operations
struct inode_operations {
    int (*mkdir)(vfs_inode_t *dir, const char *name, mode_t mode);
    int (*rmdir)(vfs_inode_t *dir, const char *name);
    int (*unlink)(vfs_inode_t *dir, const char *name);
    vfs_inode_t *(*lookup)(vfs_inode_t *dir, const char *name);
};

Superblock (super_block_t)

Represents a mounted filesystem:

typedef struct super_block {
    dev_t s_dev;                // Device identifier
    unsigned long s_blocksize;  // Filesystem block size
    vfs_inode_t *root;          // Root inode of filesystem
    list_head_t s_list;         // List of superblocks
    file_system_type_t *s_type; // Filesystem type operations
    void *s_fs_info;            // Filesystem-specific data
} super_block_t;

Directory Entry (vfs_dentry_t)

Represents a filename and link to inode:

typedef struct vfs_dentry {
    char *d_name;               // Filename
    vfs_inode_t *d_inode;       // Inode pointer
    vfs_dentry_t *d_parent;     // Parent directory
    list_head_t d_child;        // Child entries
    list_head_t d_sibling;      // Siblings in parent
} vfs_dentry_t;

File Operations

Opening Files

sys_open() - Open or create file

int fd = open("/path/to/file", O_RDONLY | O_CREAT, 0644);

Open Flags:

  • O_RDONLY - Read-only
  • O_WRONLY - Write-only
  • O_RDWR - Read and write
  • O_CREAT - Create if doesn't exist
  • O_EXCL - Fail if exists (with O_CREAT)
  • O_TRUNC - Truncate to zero length
  • O_APPEND - Append to end

Mode Bits (for O_CREAT):

  • 0644 - rw-r--r-- (user read/write, others read)
  • 0755 - rwxr-xr-x (user all, others read/execute)

VFS Internals:

open("/home/user/file.txt", O_RDONLY, 0)
  ↓
1. Parse path: /home/user/file.txt
   - Root inode: get_root_inode()
   - Look up "home" in root
   - Look up "user" in /home
   - Look up "file.txt" in /home/user
   
2. Get inode for file.txt

3. Create file descriptor table entry
   - Allocate struct vfs_file
   - Set position to 0
   - Link to inode
   - Add to process's fd table

4. Return file descriptor (fd ≥ 3)

Reading and Writing

read() - Read data

ssize_t n = read(fd, buf, 100);

write() - Write data

ssize_t n = write(fd, "Hello", 5);

File Position:

  • Maintains position in each open file instance
  • Automatically advances after read/write
  • Can be changed with lseek()

Directory Operations

Directory Listing (getdents)

struct dirent {
    ino_t d_ino;           // Inode number
    unsigned short d_reclen;   // Record length
    unsigned short d_namlen;   // Name length
    char d_name[256];      // Filename
};

ssize_t n = getdents(fd, dirp, bufsize);

EXT2 Filesystem

EXT2 (Second Extended Filesystem) is the primary persistent storage filesystem.

Key Files:

  • kernel/src/fs/ext2.c - EXT2 implementation

Disk Layout

Boot Block | Superblock | Group 0 | Group 1 | ... | Data Blocks
1 block    | 1 block    | ...     | ...     |     |

Superblock (1024 bytes):

struct ext2_superblock {
    uint32_t s_inodes_count;           // Total inodes
    uint32_t s_blocks_count;           // Total blocks
    uint32_t s_r_blocks_count;         // Reserved blocks
    uint32_t s_free_blocks_count;      // Free blocks
    uint32_t s_free_inodes_count;      // Free inodes
    uint32_t s_first_data_block;       // First block number
    uint32_t s_log_block_size;         // Log2(block size) - 10
    uint32_t s_log_frag_size;          // Log2(fragment size)
    uint32_t s_blocks_per_group;       // Blocks per group
    uint32_t s_frags_per_group;        // Fragments per group
    uint32_t s_inodes_per_group;       // Inodes per group
    uint32_t s_mtime;                  // Mount time
    uint32_t s_wtime;                  // Write time
    uint16_t s_mnt_count;              // Mount count
    uint16_t s_max_mnt_count;          // Max mounts before fsck
    // ... more fields
};

Block Group Descriptor:

struct ext2_group_desc {
    uint32_t bg_block_bitmap;      // Block bitmap block
    uint32_t bg_inode_bitmap;      // Inode bitmap block
    uint32_t bg_inode_table;       // Inode table start block
    uint16_t bg_free_blocks_count; // Free blocks in group
    uint16_t bg_free_inodes_count; // Free inodes in group
    uint16_t bg_used_dirs_count;   // Directories in group
};

Inode (128 bytes):

struct ext2_inode {
    uint16_t i_mode;           // Type and permissions
    uint16_t i_uid;            // User ID
    uint32_t i_size;           // File size
    uint32_t i_atime;          // Access time
    uint32_t i_ctime;          // Creation/change time
    uint32_t i_mtime;          // Modification time
    uint32_t i_dtime;          // Deletion time
    uint16_t i_gid;            // Group ID
    uint16_t i_links_count;    // Hard link count
    uint32_t i_blocks;         // Blocks used
    uint32_t i_flags;          // Flags
    uint32_t i_osd1;           // OS dependent
    uint32_t i_block[15];      // 12 direct + 3 indirect pointers
    uint32_t i_generation;     // File version
    uint32_t i_file_acl;       // Extended attributes
    uint32_t i_dir_acl;        // Directory ACL
    uint32_t i_faddr;          // Fragment address
    uint32_t i_osd2[3];        // OS dependent
};

Block Addressing:

  • First 12 entries (i_block[0-11]): Direct block pointers
  • Entry 12 (i_block[12]): Indirect block (points to block of pointers)
  • Entry 13 (i_block[13]): Double indirect
  • Entry 14 (i_block[14]): Triple indirect

Directory Entries

struct ext2_dir_entry {
    uint32_t inode;           // Inode number
    uint16_t rec_len;         // Record length (multiple of 4)
    uint8_t name_len;         // Name length
    uint8_t file_type;        // File type (regular, dir, symlink, etc.)
    char name[EXT2_NAME_LEN]; // Filename
};

File Types:

  • 0 - Unknown
  • 1 - Regular file
  • 2 - Directory
  • 3 - Character device
  • 4 - Block device
  • 5 - Named pipe (FIFO)
  • 6 - Socket
  • 7 - Symbolic link

File Attributes

Stored in inode i_mode:

Type bits (top 4 bits):

  • 0x8000 - Regular file
  • 0x4000 - Directory
  • 0xA000 - Symbolic link

Permission bits (bottom 9 bits):

  • 0700 - User permissions (rwx)
  • 0070 - Group permissions (rwx)
  • 0007 - Other permissions (rwx)

ProcFS - Process Filesystem

ProcFS provides a window into kernel data structures through the filesystem.

Key Files:

  • kernel/src/fs/procfs.c - ProcFS implementation

/proc Directory Structure

/proc/
├── uptime          - System uptime
├── version         - Kernel version
├── cpuinfo         - CPU information
├── loadavg         - Load average
├── meminfo         - Memory information
├── modules         - Loaded modules
├── [PID]/          - Process directories
│   ├── cmdline     - Command line
│   ├── cwd         - Current working directory (symlink)
│   ├── exe         - Executable (symlink)
│   ├── environ     - Environment variables
│   ├── fd/         - File descriptors (symlinks)
│   ├── maps        - Memory mappings
│   ├── stat        - Process statistics
│   └── status      - Process status
├── [PID]/task/     - Thread directories
└── self            - Link to current process

Reading /proc Files

int fd = open("/proc/uptime", O_RDONLY);
char buf[256];
read(fd, buf, 256);  // Read uptime in seconds

Updating /proc on Demand

Most /proc files are generated on-demand:

// When user reads /proc/[PID]/stat:
1. Kernel reads request
2. Looks up process by PID
3. Gathers statistics (CPU time, memory, etc.)
4. Formats as text
5. Returns to user

Pipes

Pipes provide unidirectional inter-process communication.

Creating a pipe:

int fds[2];
pipe(fds);
// fds[0] = read end
// fds[1] = write end

Usage Pattern:

int fds[2];
pipe(fds);

if (fork() == 0) {
    // Child process - read from pipe
    close(fds[1]);  // Close write end
    char buf[256];
    read(fds[0], buf, 256);
    printf("Received: %s\n", buf);
} else {
    // Parent process - write to pipe
    close(fds[0]);  // Close read end
    write(fds[1], "Hello", 5);
}

File Synchronization

sync() - Flush all filesystems

sync();  // Writes all dirty data to disk

fsync() - Sync specific file

fsync(fd);  // Syncs file to disk

O_SYNC flag - Synchronous writes

fd = open("/file", O_WRONLY | O_SYNC);
write(fd, data, size);  // Blocks until written to disk

Mount Points

The VFS maintains mount points:

Root Filesystem (/)
├── /home          - EXT2 partition
├── /proc          - ProcFS (virtual)
└── /dev           - Device files

Registering a filesystem:

// In kernel startup
vfs_register_filesystem(&ext2_fs_type);
vfs_register_superblock("root", "/", &ext2_fs_type, root_inode);

Path Resolution

The kernel resolves paths through successive lookups:

/home/user/file.txt
  ↓
1. Get root inode
2. Look up "home" in root → inode_home
3. Check inode_home is directory
4. Look up "user" in inode_home → inode_user
5. Check inode_user is directory
6. Look up "file.txt" in inode_user → inode_file
7. Return inode_file

Symlink Following:

/home/user/link → /home/data/file.txt
  ↓
1. Look up /home/user/link → symlink inode
2. Read link target: /home/data/file.txt
3. Continue lookup from /home/data/file.txt

File Permissions

File access check:

bool can_access(inode_t *inode, int flags) {
    uid_t uid = current->uid;
    gid_t gid = current->gid;
    mode_t mode = inode->i_mode;
    
    // Owner check
    if (uid == inode->i_uid) {
        return (mode & 0700) & flags;
    }
    // Group check
    else if (gid == inode->i_gid) {
        return (mode & 0070) & flags;
    }
    // Others check
    else {
        return (mode & 0007) & flags;
    }
}

Extended Attributes

EXT2 supports extended attributes for storing metadata:

// Get extended attribute
ssize_t len = getxattr("/path", "user.name", buf, bufsize);

// Set extended attribute
setxattr("/path", "user.name", value, len, 0);

Creating a Filesystem

Using mtools (for building)

In the build process:

# Create EXT2 filesystem image
dd if=/dev/zero of=rootfs.img bs=1024 count=16384
mkfs.ext2 -F rootfs.img

# Mount and populate
mount -t ext2 rootfs.img /tmp/mnt
cp programs /tmp/mnt/bin
umount /tmp/mnt

Further Reading

  • System Calls - File operation syscalls
  • Kernel - VFS implementation details
  • Development Guide - Using filesystem in programs
  • EXT2 Specification - Complete filesystem format
  • Linux kernel documentation - VFS architecture

Clone this wiki locally