m³OS docs GitHub

Phase 18

Directory VFS

Make the filesystem navigable. Right now `getdents64` returns ENOSYS, directories cannot be opened as file descriptors, every process thinks its working directory is `/`, and the ramdisk is a flat array with no directory hierarchy. This phase fixes all of that: directories become first-class objects, paths resolve relative to a real per-process working directory, and the ramdisk gains a tree structure so binaries live under `/bin` instead of floating in a flat namespace.

Complete

Learning goal

Understand how a per-process working directory is stored and used during path resolution.

Category

infrastructure

Why this phase matters

Make the filesystem navigable. Right now getdents64 returns ENOSYS, directories cannot be opened as file descriptors, every process thinks its working directory is /, and the ramdisk is a flat array with no directory hierarchy. This phase fixes all of that: directories become first-class objects, paths resolve relative to a real per-process working directory, and the ramdisk gains a tree structure so binaries live under /bin instead of floating in a flat namespace.

How it works in m3OS

The roadmap entry defines the learning scope for this phase; a deeper implementation doc has not been matched yet.

How it builds on earlier phases

This phase builds on Memory Reclamation and turns that foundation into Directory VFS.

Implementation outline

  • Add a cwd: String field to Process in kernel/src/process/mod.rs. Initialize it to "/" for the init process. Copy it in fork. Update it in chdir after verifying the target exists and is a directory.
  • Implement a resolve_path(cwd, relative) function that joins a relative path against the current working directory, normalizes . and .. components, and returns an absolute path. Use this function at the top of every path-accepting syscall.
  • Change sys_open to allow opening directories. When the target is a directory (or O_DIRECTORY is set), allocate an fd whose backing object is a directory handle rather than a file handle. Return ENOTDIR if O_DIRECTORY is set but the target is not a directory.
  • Implement sys_getdents64 for tmpfs: call list_dir() on the tmpfs node (the function already exists but is unreachable), serialize each child into a linux_dirent64 struct, and copy the buffer to userspace. Handle partial reads when the buffer is too small by returning the entries that fit and resuming on the next call using an offset stored in the fd.
  • Restructure the ramdisk from a flat Vec<(name, data)> to a tree of directory and file nodes. Each directory node holds a Vec of children. Update the xtask image builder to place files under their directory paths (e.g., /bin/shell, /etc/motd).
  • Implement sys_getdents64 for ramdisk directories: iterate the children of the directory node, synthesize . and .. entries, and serialize into the userspace buffer.
  • Implement sys_openat: if the first argument is AT_FDCWD, resolve against the process cwd; otherwise look up the directory fd in the fd table and resolve against its path.
  • Update sys_getcwd to copy the process’s cwd string into the userspace buffer instead of always returning "/".
  • Update sys_chdir to call resolve_path, verify the result is an existing directory on either tmpfs or ramdisk, and store the resolved absolute path in process.cwd.
  • Wire the shell’s ls command to use open(path, O_DIRECTORY) followed by getdents64 in a loop, printing each entry.

Task progress snapshot

No checklist tasks were discovered for this phase yet.

Documentation targets

  • Documentation deliverables have not been listed yet.

Deferred follow-on work

  • Full VFS abstraction layer with pluggable filesystem drivers
  • Mount points and mount / umount syscalls
  • Symbolic links and readlink
  • Hard links and link counts
  • Inode numbers (currently synthesized or zero)
  • Permission bits and chmod / chown
  • .. across mount boundaries
  • fstat / fstatat for directory fds
  • renameat2 and cross-directory rename

Source materials

Important code

Code spotlight cards

Code spotlight

String field to Process in kernel/src/process/mod.rs. Initialize

kernel/src/process/mod.rs See file

String field to Process in kernel/src/process/mod.rs. Initialize

Code spotlight

kernel/src/fs/ramdisk.rs

kernel/src/fs/ramdisk.rs See file

kernel/src/fs/ramdisk.rs.

Key files

  • kernel/src/process/mod.rs — String field to Process in kernel/src/process/mod.rs. Initialize
  • kernel/src/fs/ramdisk.rs — kernel/src/fs/ramdisk.rs.

How real OSes differ

  • Production kernels represent directories as in-kernel dentry caches backed by inodes on disk.
  • Path resolution walks a dentry tree with mount-point crossing, symlink following (with loop detection), and permission checks at every component.
  • The VFS layer in Linux is a ~30,000-line abstraction that lets dozens of filesystem drivers plug in behind the same interface.
  • `getdents64` in Linux reads from the page cache and handles concurrent modification via sequence locks.
  • This phase does the simplest correct thing: a `cwd` string, string-based path joins, and direct iteration over in-memory data structures with no caching layer.

Success criteria

  • `ls /bin` lists the ELF binaries placed there by the image builder.
  • `ls /tmp` lists files created at runtime.
  • `ls /` shows `bin`, `tmp`, `etc`, and any other top-level entries.
  • `cd /bin && pwd` prints `/bin`.
  • `cd nonexistent` returns an error and does not change the working directory.
  • `getcwd()` returns the correct absolute path after one or more `chdir` calls.
  • Opening a directory without `O_DIRECTORY` returns a valid directory fd.
  • Opening a regular file with `O_DIRECTORY` returns ENOTDIR.
  • `getdents64` on a directory with more entries than fit in one buffer call correctly resumes on the next call.
  • Relative paths (e.g., `open("shell", ...)` while cwd is `/bin`) resolve correctly.
  • `openat(dirfd, "file", ...)` resolves relative to the directory fd's path.
  • All existing tests continue to pass without modification.

Related phases

Keep exploring the roadmap