Chapter 38 — Turning /programs into a Real Directory

Chapter 38 — Turning /programs into a Real Directory
This entry is part 35 of 36 in the series Writing A Linux Style Operating System From Scratch

Post Stastics

  • This post has 1370 words.
  • Estimated read time is 6.52 minute(s).

Before reading the shell examples in this chapter, be explicit about the prompt transition:

toyix> run shell
ush>

toyix> is the kernel monitor.

ush> is the user shell.

So when this chapter shows commands like:

ush> ls /
ush> ls /programs
ush> cat /programs/demo

those commands must be typed inside the user shell after toyix> run shell.

In Chapter 37, Toyix gained its first real directory support at the root:

ush> ls /
file README
dir programs

But /programs was still only a compatibility text file. That was useful as a transitional step, but it was not a realistic filesystem shape for a system that already knows about embedded user programs.

Chapter 38 fixes that.

After this chapter, /programs becomes a real directory and each embedded program appears as a small read-only pseudo-file:

/
├── README
└── programs/
    ├── demo
    ├── counter
    ├── shell
    └── fstest

That means the user shell can now do:

ush> ls /programs
file demo
file counter
file shell
file fstest

ush> cat /programs/demo
Toyix embedded program: demo
Description: interactive stdin/stdout demo

We are still not executing programs by filesystem path yet. This chapter only makes the namespace real. The next chapter can build on that structure.


1. What this chapter adds

Modify:

kernel/vfs.c
kernel/program.c
user/fstest.c
user/shell.c
tests/smoke.py
README.md
CHANGELOG.md
index.md
docs/roadmap.md

No new syscalls are needed.

No ELF loading changes are needed.

No scheduler changes are needed.

The work is entirely in the RAMFS layout, the user-visible shell behavior, and the smoke coverage.


2. Why this chapter exists

Toyix already has two separate concepts:

filesystem paths
embedded program registry

Before Chapter 38, those concepts were still disconnected. The registry knew about:

demo
counter
shell
fstest

but the filesystem only exposed /programs as a regular text file.

Turning /programs into a directory is the first structural bridge between:

program names in the registry

and:

program-like paths in the filesystem namespace

That gives later chapters a clean place to add path-based execution, executable metadata, and eventually more dynamic program lookup.


3. New RAMFS layout

Before:

/                   directory
/README             regular file
/programs           regular file

After:

/                   directory
/README             regular file
/programs           directory
/programs/demo      regular pseudo-file
/programs/counter   regular pseudo-file
/programs/shell     regular pseudo-file
/programs/fstest    regular pseudo-file

The node count therefore changes from:

3 node(s)

to:

7 node(s)

The root still has only two entries:

README
programs

But now programs resolves to a directory instead of a regular file, so ls / prints:

file README
dir programs

4. How /programs works in this chapter

Each child under /programs is still a simple RAMFS regular file.

For example:

/programs/demo

contains:

Toyix embedded program: demo
Description: interactive stdin/stdout demo

The same pattern is used for:

/programs/counter
/programs/shell
/programs/fstest

These are not ELF images and they are not executable files yet.

They are only descriptive pseudo-files that let the filesystem expose the embedded program set in a realistic way using the code that already exists:

open
read
stat
readdir
seek

That choice keeps the implementation small and makes Chapter 38 a filesystem chapter instead of an execution chapter.


5. Kernel changes

The VFS/RAMFS work in kernel/vfs.c does four things:

  1. Replace the old /programs text blob with per-program pseudo-file text.
  2. Add a second directory-entry table for /programs.
  3. Change the /programs RAMFS node type from VFS_NODE_REGULAR to VFS_NODE_DIRECTORY.
  4. Add four new regular-file nodes under /programs.

The root directory table still looks like:

README    -> /README
programs  -> /programs

and /programs gets its own child-entry table:

demo    -> /programs/demo
counter -> /programs/counter
shell   -> /programs/shell
fstest  -> /programs/fstest

The existing vfs_readdir(), vfs_stat(), vfs_size(), and vfs_seek() code from Chapter 37 already knows how to deal with directories, so no new API surface is needed here.

The only new logic is the expanded static RAMFS data.


6. User-visible shell behavior

The shell does not gain new commands in Chapter 38.

The existing commands simply become more interesting because /programs is now a directory.

Examples:

ush> stat /programs
stat: path=/programs type=directory size=4
ush> ls /programs
file demo
file counter
file shell
file fstest
ush> stat /programs/demo
stat: path=/programs/demo type=file size=...
ush> cat /programs/demo
Toyix embedded program: demo
Description: interactive stdin/stdout demo

One small shell cleanup is included here too:

dir programs

now prints with normal spacing. In Chapter 37 the directory label helper would have produced an awkward double-space once programs became a directory, so the type-label output was tightened as part of this chapter.

Absolute-path-only behavior is still unchanged:

ush> ls programs
ls: could not stat programs

Current working directories and relative paths are still future work.


7. Filesystem smoke coverage

fstest now verifies more than root-directory enumeration.

It checks:

stat /programs
stat /programs/demo
readdir on /
readdir on /programs
read from /programs/demo

That gives user-mode coverage for the new nested directory layout instead of relying only on kernel-side VFS checks.

Expected fstest lines now include:

fstest: /programs type=directory size=4
fstest: /programs/demo type=file size=...
fstest: first /programs entry: demo
fstest: second /programs entry: counter
fstest: third /programs entry: shell
fstest: fourth /programs entry: fstest
fstest: /programs/demo read: Toyix embedded

8. Shell smoke coverage

The scripted shell test in kernel/program.c now exercises:

cat /programs/demo
stat /programs
stat /programs/demo
ls /
ls /programs

This is important because the chapter is not just about static RAMFS data. It is about the actual user-facing workflow that a reader will try after boot.

That is also why the Chapter 37 release lesson still matters here:

test-mode scripted shell input must not be confused with normal interactive boot behavior

So the automated shell script remains test-only under make test, while ordinary boots still reach a clean toyix> prompt.


9. Update tests/smoke.py

Chapter 36.5 moved smoke assertions into Python, so Chapter 38 extends tests/smoke.py rather than reviving shell grep chains.

The normal-boot checks now include:

VFS: initialized RAMFS with 7 node(s)
VFS test: /programs entries=4 type=directory
VFS test: first /programs entry: demo
VFS test: second /programs entry: counter
VFS test: third /programs entry: shell
VFS test: fourth /programs entry: fstest
VFS test: /programs/demo bytes: Toyix embedded
fstest: /programs type=directory size=4
fstest: /programs/demo type=file size=...
fstest: first /programs entry: demo
fstest: second /programs entry: counter
fstest: third /programs entry: shell
fstest: fourth /programs entry: fstest
Toyix embedded program: demo
stat: path=/programs type=directory size=4
stat: path=/programs/demo type=file size=...
dir programs
file demo
file counter
file shell
file fstest

Run the full local suite with:

python3 tests/smoke.py

The Chapter 38 success banner is:

All Chapter 38 checks passed.

10. Interactive test

Boot normally, wait for the clean kernel monitor, then run:

toyix> run shell

Inside the shell:

ush> ls /

Expected:

file README
dir programs

Then:

ush> ls /programs

Expected:

file demo
file counter
file shell
file fstest

Then:

ush> cat /programs/demo

Expected:

Toyix embedded program: demo
Description: interactive stdin/stdout demo

Then:

ush> stat /programs

Expected:

stat: path=/programs type=directory size=4

And:

ush> stat /programs/fstest

Expected:

stat: path=/programs/fstest type=file size=...

Remember:

`file demo` is ls output
it is not a shell command

Also remember:

paths are still absolute-only in Chapter 38

So:

ush> ls programs

should still fail.


11. What changed from Chapter 37

Before:

/programs was a compatibility text file

Now:

/programs is a real directory

Before:

ls / showed programs as a file

Now:

ls / shows programs as a directory

Before:

cat /programs printed a plain list

Now:

cat /programs/demo prints a descriptive pseudo-file

That is the whole point of the chapter: the visible filesystem structure now matches the conceptual program namespace much more closely.


Next Chapter

Now that /programs is a real directory, the next natural step is to teach the program-launch path to understand entries like:

/programs/counter

That means the next chapter can start bridging from:

run counter

toward:

path-based program launching

without first needing more RAMFS structure work.


Resources

Closure

Chapter 38 turns /programs from a transitional text file into a real directory of pseudo-files, which gives Toyix a much more realistic filesystem namespace for the embedded program set and sets up the next step toward path-based execution.

Happy Coding!

Writing A Linux Style Operating System From Scratch

Chapter 37 — Directories, SYS_READDIR, and ls

Leave a Reply

Your email address will not be published. Required fields are marked *