D4: the machine makes directories too

mkdir and rmdir are the machine's own now, and a file goes where its path says
rather than always in the root. A disk can be organised without the host tool
touching it.

Everything below the surface works in terms of a directory and a name rather
than a path. sbfsWalkParent splits the last name off, walks the rest, and hands
back the two - and the separator stays on the end of the head, which is what
makes one rule cover every kind of path: "/x" leaves "/", which is the root;
"x" leaves nothing, which is where the machine already is; and "A/x" leaves
"A/", which is neither and needs no special case to say so.

Saving works in those two as well, and had to. The careful order a save uses -
make a temporary, write it, delete the original, rename the temporary - only
works if the temporary is made in the SAME directory as the file, because the
rename at the end changes a name and does not move anything. Renaming to a path
naming a different directory is refused for that reason, rather than quietly
being a lie the disk goes along with.

Three things this cost, all found by running it:

mkdir Apps/Deep made /Apps/Apps. The leaf was worked out into SbfsWanted and
then the head was walked - and walking goes through sbfsPathNext, which puts
every name it meets into SbfsWanted on the way past. The head's last name
landed exactly where the leaf was. It has somewhere of its own now.

rmdir took a directory with something still in it, which is the one failure the
whole design is arranged to prevent. Looking for children clobbered DP2 and
rebuilt it from the buffer and the entry count with the subtraction the wrong
way round, so the pointer walked off the end of the block and found nothing.
The comparison goes through a CALL now, like the two beside it, and DP2 comes
back on the entry because a RET puts it there. SplitDisk's "in use but not
reachable from the root" line is what caught it.

Refusing a name longer than twenty two used to read the twenty third character
of a shorter one, which is somebody else's string. It is measured now.

Tests/agree.sh is new and is the gate this rung was for: the same disk built
twice, once with SplitDisk and once with CosmOS, compared byte for byte. The
two share no code and only a written specification, and every field one writes
and the other only reads is checked there and nowhere else - which entry a
thing lands in, which block, what a directory's unused fields hold, the
version, the free count. It caught a wrong parent immediately when that was
broken on purpose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-25 09:18:29 -04:00
co-authored by Claude Opus 5
parent 36ce9f6ccf
commit da91a36d92
13 changed files with 948 additions and 43 deletions
+42 -4
View File
@@ -23,6 +23,8 @@ for itself.
- Working Directory: `cd` moves the machine, `dir` lists where it is, and the prompt says
where that is once it is not the root. A program may move too, and the shell puts the
working directory back when the program stops.
- Making Directories: `mkdir` and `rmdir` on the machine, and files written where their
path says, so a disk can be organised without the host tool.
- Loadable Applications: Validate SBEX files, copy their Program and Data segments into
the addresses for which they were assembled, and start them at their declared entry
point.
@@ -92,6 +94,8 @@ CosmOS currently provides these built-in commands:
| `load <path>` | Read and validate an SBEX application, then place its code and data where its header requests. |
| `run [words]` | Start the loaded application and make the rest of the line available to it as an argument. |
| `cd [path]` | Go to a directory, or to the root with nothing after it. |
| `mkdir <path>` | Make a directory. |
| `rmdir <path>` | Remove one, if it is empty. |
| `<name> [words]` | Any word the shell does not recognise is looked for on the disk as `<name>.sbx`, and loaded and started if it is there. |
| `delete <file>` | Remove a file from the filesystem and release its blocks. |
| `rename <file> <to>` | Give a file a different name without moving its contents. |
@@ -194,10 +198,44 @@ path as it was typed, so `notes.txt` is the same key in two directories and noth
the entry it holds would look wrong. It is the kind of stale that gets believed rather
than noticed.
At this stage CosmOS **reads** directories and does not make them: there is no `mkdir`, and
files a program writes go in the directory you are in. Deleting or renaming a directory is
refused - deleting one would free its entry index, and since a parent is written as an
index, the next file created would take that index and inherit its children.
### Making And Removing Directories:
`mkdir` and `rmdir` are the machine's own, so a disk can be organised without the host
tool. A file a program writes goes where its path says, and a bare name means the
directory you are in.
**A directory costs one entry and no blocks at all.** Its start, block count and tail are
all zero, which is what keeps the flat array of entries the whole allocation map - with
files laid down contiguously, every block is inside some entry's range or it is not, and
an entry with no range is in nobody's way.
Making the first directory on a disk is what raises it from version one to version two,
because it is the only thing that makes the difference between them real. A disk stays
readable by anything that has never heard of a directory right up until it actually has
one.
Four things are refused, and each refusal is the reason a separate command exists:
**`rmdir` will not take a file and `delete` will not take a directory.** Neither can be
the one that removed more than was asked for.
**A directory with anything in it is refused.** This is not politeness. A parent is an
entry *index*, and a freed index is handed to the next thing created - so the children of
a directory removed from under them would turn up inside whatever took its place. Nothing
points downward, so there would be no way to find them afterwards and no way to notice.
**A name already used in that directory is refused.** Two entries with one name in one
place is a directory that cannot be searched sensibly: a search answers with whichever it
meets first, and the other becomes unreachable without ever having been deleted. The same
name in a *different* directory is fine, and is the point of the exercise.
**`rename` will not move anything.** Only the twenty two bytes of the name change and the
parent is not among them, so `rename a/x b/y` would be a lie the disk went along with.
`Tests/agree.sh` builds the same disk twice, once with SplitDisk and once with CosmOS, and
compares the images byte for byte. Every field one writes and the other only reads is
checked there and nowhere else: which entry a thing lands in, which block, what a
directory's unused fields hold, the version, the free count.
### Starting An Application By Name: