Skip to main content
Aeolos implements a Unix-like Virtual Filesystem (VFS) layer that provides a unified interface to multiple filesystem implementations. Currently, two filesystems are supported: ramfs (in-memory filesystem) and ttyfs (terminal/TTY filesystem).

VFS Architecture

The VFS uses a tree structure with two types of nodes:
  • inodes - Represent the actual file/directory data and metadata
  • tree nodes - Provide naming and hierarchy (multiple names can point to same inode)
This separation allows for hard links where multiple paths reference the same underlying data.

Node Types

From kernel/fs/vfs/vfs.h:19:
kernel/fs/vfs/vfs.h
typedef enum {
    VFS_NODE_FILE,          // Regular file
    VFS_NODE_FOLDER,        // Directory
    VFS_NODE_PIPE,          // Named pipe (FIFO)
    VFS_NODE_BLOCK_DEVICE,  // Block device
    VFS_NODE_CHAR_DEVICE,   // Character device
    VFS_NODE_MOUNTPOINT     // Filesystem mount point
} vfs_node_type_t;

Tree Node Structure

From kernel/fs/vfs/vfs.h:53:
kernel/fs/vfs/vfs.h
struct _vfs_tnode_t {
    char name[VFS_MAX_NAME_LEN];  // Name of this entry
    vfs_inode_t* inode;           // Pointer to inode
    vfs_inode_t* parent;          // Parent directory
    vfs_tnode_t* sibling;         // Next sibling in directory
};

Inode Structure

From kernel/fs/vfs/vfs.h:60:
kernel/fs/vfs/vfs.h
struct _vfs_inode_t {
    vfs_node_type_t type;           // Type of node
    size_t size;                    // Size in bytes
    uint32_t perms;                 // Unix-style permissions
    uint32_t uid;                   // Owner user ID
    uint32_t refcount;              // Number of references
    vfs_fsinfo_t* fs;               // Filesystem driver
    void* ident;                    // Filesystem-specific data
    vfs_tnode_t* mountpoint;        // If mounted, points to mount location
    vec_struct(vfs_tnode_t*) child; // Children (for directories)
};

Filesystem Driver Structure

From kernel/fs/vfs/vfs.h:35:
kernel/fs/vfs/vfs.h
typedef struct vfs_fsinfo_t {
    char name[16];      // Filesystem name
    bool istemp;        // True if temporary (ramfs, tmpfs)

    vfs_inode_t* (*mount)(vfs_inode_t* device);
    int64_t (*mknode)(vfs_tnode_t* this);

    // Return value is number of bytes read/written
    int64_t (*read)(vfs_inode_t* this, size_t offset, size_t len, void* buff);
    int64_t (*write)(vfs_inode_t* this, size_t offset, size_t len, const void* buff);

    // Return value is -1 on error, 0 on success
    int64_t (*sync)(vfs_inode_t* this);
    int64_t (*refresh)(vfs_inode_t* this);
    int64_t (*setlink)(vfs_tnode_t* this, vfs_inode_t* target);
    int64_t (*ioctl)(vfs_inode_t* this, int64_t req_param, void* req_data);
} vfs_fsinfo_t;

VFS Initialization

The VFS is initialized in kernel/fs/vfs/vfs.c:54:
kernel/fs/vfs/vfs.c
void vfs_init()
{
    klog_warn("partial stub\n");

    // initialize the root folder and mount ramfs there
    vfs_root.inode = vfs_alloc_inode(VFS_NODE_FOLDER, 0777, 0, NULL, NULL);
    vfs_register_fs(&ramfs);
    vfs_mount(NULL, "/", "ramfs");

    klog_ok("done\n");
}
This creates the root directory and mounts ramfs at /.

Filesystem Registration

Filesystems register themselves with vfs_register_fs() at kernel/fs/vfs/vfs.c:37:
kernel/fs/vfs/vfs.c
void vfs_register_fs(vfs_fsinfo_t* fs)
{
    vec_push_back(&vfs_fslist, fs);
}
Registered filesystems can be retrieved by name with vfs_get_fs() at kernel/fs/vfs/vfs.c:43:
kernel/fs/vfs/vfs.c
vfs_fsinfo_t* vfs_get_fs(char* name)
{
    for (size_t i = 0; i < vfs_fslist.len; i++)
        if (strncmp(name, vfs_fslist.data[i]->name, sizeof(((vfs_fsinfo_t) { 0 }).name)) == 0)
            return vfs_fslist.data[i];

    klog_err("filesystem %s not found\n", name);
    return NULL;
}

File Operations

Open File Descriptor

When a file is opened, a descriptor is created from kernel/fs/vfs/vfs.h:73:
kernel/fs/vfs/vfs.h
typedef struct {
    vfs_tnode_t* tnode;      // Tree node
    vfs_inode_t* inode;      // Inode
    vfs_openmode_t mode;     // Read/write mode
    size_t seek_pos;         // Current position
} vfs_node_desc_t;

Opening Files

Files are opened with vfs_open() which returns a handle (integer index into the task’s open file table):
vfs_handle_t vfs_open(char* path, vfs_openmode_t mode);
Open modes from kernel/fs/vfs/vfs.h:28:
kernel/fs/vfs/vfs.h
typedef enum {
    VFS_MODE_READ,
    VFS_MODE_WRITE,
    VFS_MODE_READWRITE
} vfs_openmode_t;

Reading and Writing

File I/O operations:
int64_t vfs_read(vfs_handle_t handle, size_t len, void* buff);

Creating and Linking

int64_t vfs_create(char* path, vfs_node_type_t type);

Directory Operations

Directory entries are read with vfs_getdent() which fills a vfs_dirent_t structure from kernel/fs/vfs/vfs.h:81:
kernel/fs/vfs/vfs.h
typedef struct {
    vfs_node_type_t type;
    size_t record_len;
    char name[VFS_MAX_NAME_LEN];
} vfs_dirent_t;

ramfs - RAM Filesystem

ramfs is a simple in-memory filesystem that stores all data in kernel heap memory.

ramfs Registration

From kernel/fs/ramfs/ramfs.c:7:
kernel/fs/ramfs/ramfs.c
vfs_fsinfo_t ramfs = {
    .name = "ramfs",
    .istemp = true,
    .mount = ramfs_mount,
    .mknode = ramfs_mknode,
    .sync = ramfs_sync,
    .refresh = ramfs_refresh,
    .read = ramfs_read,
    .write = ramfs_write,
    .setlink = ramfs_setlink
};

ramfs Identification Structure

ramfs uses a simple structure to track file data at kernel/fs/ramfs/ramfs.c:20:
kernel/fs/ramfs/ramfs.c
typedef struct {
    size_t alloc_size;  // Allocated buffer size
    void* data;         // Pointer to data buffer
} ramfs_ident_t;

ramfs Operations

Mounting

From kernel/fs/ramfs/ramfs.c:85:
kernel/fs/ramfs/ramfs.c
vfs_inode_t* ramfs_mount(vfs_inode_t* at)
{
    (void)at;
    vfs_inode_t* ret = vfs_alloc_inode(VFS_NODE_MOUNTPOINT, 0777, 0, &ramfs, NULL);
    ret->ident = create_ident();
    return ret;
}

Reading

From kernel/fs/ramfs/ramfs.c:32:
kernel/fs/ramfs/ramfs.c
int64_t ramfs_read(vfs_inode_t* this, size_t offset, size_t len, void* buff)
{
    ramfs_ident_t* id = (ramfs_ident_t*)this->ident;
    memcpy(((uint8_t*)id->data) + offset, buff, len);
    return len;
}

Writing

From kernel/fs/ramfs/ramfs.c:39:
kernel/fs/ramfs/ramfs.c
int64_t ramfs_write(vfs_inode_t* this, size_t offset, size_t len, const void* buff)
{
    ramfs_ident_t* id = (ramfs_ident_t*)this->ident;
    memcpy(buff, ((uint8_t*)id->data) + offset, len);
    return len;
}

Syncing

The sync operation resizes the buffer if needed at kernel/fs/ramfs/ramfs.c:47:
kernel/fs/ramfs/ramfs.c
int64_t ramfs_sync(vfs_inode_t* this)
{
    ramfs_ident_t* id = (ramfs_ident_t*)this->ident;
    if (this->size > id->alloc_size) {
        id->alloc_size = this->size;
        id->data = kmrealloc(id->data, id->alloc_size);
    }
    return 0;
}
ramfs automatically grows file buffers as needed during sync operations. Buffer sizes never shrink to avoid repeated allocations.

ttyfs - Terminal Filesystem

ttyfs provides terminal/TTY devices as files with circular input and output buffers.

ttyfs Registration

From kernel/fs/ttyfs/ttyfs.c:11:
kernel/fs/ttyfs/ttyfs.c
vfs_fsinfo_t ttyfs = {
    .name = "ttyfs",
    .istemp = true,
    .mount = ttyfs_mount,
    .mknode = ttyfs_mknode,
    .sync = ttyfs_sync,
    .refresh = ttyfs_refresh,
    .read = ttyfs_read,
    .write = ttyfs_write,
    .setlink = ttyfs_setlink
};

ttyfs Identification Structure

From kernel/fs/ttyfs/ttyfs.c:23:
kernel/fs/ttyfs/ttyfs.c
typedef struct {
    char *buff_in, *buff_out;  // Circular buffers
    size_t in_start, in_size;  // Input buffer state
    size_t out_start, out_size; // Output buffer state
} ttyfs_ident_t;

#define TTY_BUFFER_SIZE 16

ttyfs Reading

Reading from a TTY drains the input buffer at kernel/fs/ttyfs/ttyfs.c:43:
kernel/fs/ttyfs/ttyfs.c
int64_t ttyfs_read(vfs_inode_t* this, size_t offset, size_t len, void* buff)
{
    ttyfs_ident_t* id = this->ident;

    // no of bytes that should be read
    size_t rlen = MIN(len, id->in_size);

    // read the bytes, wrap around if needed
    for (size_t i = 0; i < rlen; i++) {
        size_t index = (id->in_start + i) % TTY_BUFFER_SIZE;
        ((char*)buff)[i] = id->buff_in[index];
    }

    // update start and size of buffer
    id->in_start += rlen;
    id->in_start %= TTY_BUFFER_SIZE;
    id->in_size -= rlen;

    // TODO: block if read less than len bytes
    return rlen;
}

ttyfs Writing

Writing to a TTY appends to the output buffer at kernel/fs/ttyfs/ttyfs.c:65:
kernel/fs/ttyfs/ttyfs.c
int64_t ttyfs_write(vfs_inode_t* this, size_t offset, size_t len, const void* buff)
{
    ttyfs_ident_t* id = this->ident;

    // write bytes at end, wrap around if needed
    size_t end = id->out_start + id->out_size;
    for (size_t i = 0; i < len; i++) {
        size_t index = (end + i) % TTY_BUFFER_SIZE;
        id->buff_out[index] = ((char*)buff)[i];
    }

    // update start and size
    if (len > TTY_BUFFER_SIZE - id->out_size)
        id->out_start = (end + len) % TTY_BUFFER_SIZE;
    id->out_size = MIN(len + id->out_size, TTY_BUFFER_SIZE);

    return len;
}
ttyfs buffers are only 16 bytes. Writing more than the buffer size will overwrite old data. Future versions should implement blocking when buffers are full.

VFS Debugging

The VFS provides a debug function to dump the filesystem tree at kernel/fs/vfs/vfs.c:30:
kernel/fs/vfs/vfs.c
void vfs_debug()
{
    klog_info("dumping nodes\n");
    dumpnodes_helper(&vfs_root, 0);
    klog_printf("\n");
}
This recursively prints the tree structure showing:
  • Node level (depth in tree)
  • Node name
  • Inode address
  • Reference count

Usage Example

From kernel/kmain.c:23, the first kernel task demonstrates VFS usage:
kernel/kmain.c
void kinit(tid_t tid)
{
    (void)tid;
    klog_show();
    klog_ok("first kernel task started\n");

    initrd_init((stv2_struct_tag_modules*)stv2_find_struct_tag(bootinfo, STV2_STRUCT_TAG_MODULES_ID));

    klog_printf("\n");
    char buff[4096] = { 0 };
    vfs_handle_t fh = vfs_open("/docs/test.txt", VFS_MODE_READ);
    klog_info("reading \"/docs/test.txt\":\n\n");
    int64_t nb = vfs_read(fh, 4096, buff);
    klog_printf("%s\n", buff, nb);
    klog_info("bytes read: %d\n", nb);
    vfs_close(fh);

    vfs_debug();
    pmm_dumpstats();

    klog_warn("This OS is a work in progress. The computer will now halt.");
    sched_kill(tid);
}

Limitations and Future Work

Path Resolution

Path parsing and traversal logic is not fully implemented. The VFS needs complete path resolution with support for . and ...

Permissions

Unix-style permissions are stored but not enforced. Access control checks need to be added.

Caching

No inode or directory entry caching is implemented, which may impact performance.

Blocking I/O

Read/write operations are non-blocking. Support for blocking on TTY input is needed.

Symbolic Links

Only hard links are supported. Symbolic links are not yet implemented.

Mount Table

No mount table or mount options are tracked. Only the root mount is supported.

Build docs developers (and LLMs) love