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.
Learning goal
Understand how a per-process working directory is stored and used during path resolution.
Category
infrastructure
Components touched
Builds on
Unlocked by this phase
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: Stringfield toProcessinkernel/src/process/mod.rs. Initialize it to"/"for the init process. Copy it infork. Update it inchdirafter 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_opento allow opening directories. When the target is a directory (orO_DIRECTORYis set), allocate an fd whose backing object is a directory handle rather than a file handle. Return ENOTDIR ifO_DIRECTORYis set but the target is not a directory. - Implement
sys_getdents64for tmpfs: calllist_dir()on the tmpfs node (the function already exists but is unreachable), serialize each child into alinux_dirent64struct, 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 aVecof children. Update the xtask image builder to place files under their directory paths (e.g.,/bin/shell,/etc/motd). - Implement
sys_getdents64for 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 isAT_FDCWD, resolve against the processcwd; otherwise look up the directory fd in the fd table and resolve against its path. - Update
sys_getcwdto copy the process’scwdstring into the userspace buffer instead of always returning"/". - Update
sys_chdirto callresolve_path, verify the result is an existing directory on either tmpfs or ramdisk, and store the resolved absolute path inprocess.cwd. - Wire the shell’s
lscommand to useopen(path, O_DIRECTORY)followed bygetdents64in 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/umountsyscalls - Symbolic links and
readlink - Hard links and link counts
- Inode numbers (currently synthesized or zero)
- Permission bits and
chmod/chown ..across mount boundariesfstat/fstatatfor directory fdsrenameat2and cross-directory rename
Source materials
Important code
Code spotlight cards
Code spotlight
String field to Process in kernel/src/process/mod.rs. Initialize
String field to Process in kernel/src/process/mod.rs. Initialize
Code spotlight
kernel/src/fs/ramdisk.rs
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
Phase 17
CompleteMemory Reclamation
Close the memory leaks that have accumulated since Phase 2. The bump frame allocator is replaced with a free-list allocator that can actually reclaim frames, the kernel heap grows on demand instead of being fixed at 1 MiB, kernel stacks are freed when processes exit, and `fork` switches from eager full-copy to copy-on-write so parent and child share physical pages until one of them writes.
Phase 19
CompleteSignal Handlers
Enable userspace programs to install and execute signal handlers. Implement the signal trampoline mechanism so ring-3 code can catch `SIGINT`, `SIGSEGV`, `SIGCHLD`, and other signals, run a user-supplied handler, and return cleanly to the interrupted execution without kernel assistance beyond the initial delivery and the `sigreturn` syscall.