D3: the machine knows where it is

cd moves it, dir lists the directory it is in, and the prompt says which one -
but only when that is not the root, so a machine nobody has moved about on
looks exactly as it always did and every recorded test that never says "cd"
keeps its recorded prompt.

A path beginning with a separator is measured from the root and anything else
from where the machine is, so a bare name means a file in the current
directory. NO PROGRAM HAD TO BE TOLD: the working directory lives in sbfs.asm
beside the thing that resolves paths, because it is what a relative path MEANS.
Keeping it in the shell would have meant either handing it down on every call
or pasting it onto the front of every name, and the second of those is how a
name that is already absolute gets ruined.

Nothing stores the path. The working directory is an entry index and two bytes,
and the text on the prompt is built each time by walking the chain of parents
upward, writing names from the end of a buffer towards the front - which is the
order they arrive in, and saves reversing them afterwards.

sbfsFind splits into a walk and a check. "cd /" and "cd .." both end at the
root quite legitimately, and had no way to say so through a routine whose only
word for the root was "missing".

Typing a program's name now tries two places in order: where you are, then
/Apps. The first makes a program you are working on the one that runs; the
second lets Snake work from anywhere. A word already beginning with a separator
has said where to look, so only that place is tried.

osChangeDir exists so that "a program may move about, and the shell puts the
working directory back" is a thing that can happen rather than a promise about
nothing. Both halves of that were unfalsifiable without it: with no way for a
program to move, removing the restore changed no test. Wander is the program
that moves - it goes where it is told and reads a file there by a bare name -
and with it on the disk, removing the restore fails.

The remembered file is dropped whenever what a relative path means changes: a
cd, a program calling osChangeDir, a program exiting. Removing all of them
fails the test and removing any one of them does not, because today every path
into that cache belongs to a program that exits. It is kept in all three
because the cost is a call and the failure is a file's blocks being handed out
under another file's name.

The cwd fixture holds two files called notes.txt saying different things, and a
Say.sbx in /A that is really hello. Two copies of one program, or two copies of
one file, would have passed with the whole of this deleted.

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-24 22:22:45 -04:00
co-authored by Claude Opus 5
parent 588e02aff5
commit 36ce9f6ccf
14 changed files with 759 additions and 38 deletions
+105
View File
@@ -0,0 +1,105 @@
; Goes somewhere else and reads a file by a bare name once it is there.
;
; This exists to prove two things the shell promises and nothing else could test. First
; that osChangeDir works: the file it prints is named with no path at all, so the only way
; to reach it is to be standing in the right place. Second that the shell puts the working
; directory back afterwards: the prompt after this returns says where the shell was, not
; where this went.
;
; Written by Anachronaut
#Include services.asm
#Program
#Base 0x4000
start:
; Where to go is the argument. Nothing else about this program says a directory name, so
; running it anywhere else moves it anywhere else.
SETD.0 Where
INIB 0d40
SWI osArgument
SETD.0 Where
LDA.0
BRA noWhere
SETD.0 Where
SWI osChangeDir
BNQ noSuchPlace
SETD.0 Went
SWI osPrintString
; And now a bare name, which means nothing until you are somewhere.
SETD.0 Bare
CALL fileStreamOpen
BNQ noFile
wanderBlock:
CALL fileStreamNext
BNQ noFile
PSHD.3
POPB
POPA
SETD.2 Left
STA.2
INCD.2
STB.2
SETD.2 Left
LDA.2
INCD.2
LDB.2
OR
BRQ wanderDone
SETD.0 FileStreamBlock
SETD.2 Left
INCD.2
LDB.2
wanderByte:
LDA.0
OUTA 0x00
INCD.0
DECB
BNB wanderByte
BRI wanderBlock
wanderDone:
SWI osExit
noWhere:
SETD.0 NoWhereText
SWI osPrintString
SWI osExit
noSuchPlace:
SETD.0 NoPlaceText
SWI osPrintString
SWI osExit
noFile:
SETD.0 NoFileText
SWI osPrintString
SWI osExit
#Data
#Base 0x2000
Where:
#Reserve 0d40
Left:
0x00 0x00
Went:
"moved, and reading a bare name from there:
"
NoWhereText:
"wander where?
"
NoPlaceText:
"cannot go there
"
NoFileText:
"nothing of that name here
"
Bare:
"notes.txt"
#Include fileStream.asm
+54 -8
View File
@@ -18,8 +18,11 @@ for itself.
the end of input. the end of input.
- SBFS Filesystem: Mount, list, read, write, delete, and rename files on a SplitBit disk. - SBFS Filesystem: Mount, list, read, write, delete, and rename files on a SplitBit disk.
- Paths: Anywhere a filename is taken, a path may be given instead - names with `/` - Paths: Anywhere a filename is taken, a path may be given instead - names with `/`
between them, walked from the root, with `.` and `..`. Directories are read but not yet between them, with `.` and `..`. Directories are read but not yet made; the host tool
made; the host tool makes them. makes them.
- 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.
- Loadable Applications: Validate SBEX files, copy their Program and Data segments into - 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 the addresses for which they were assembled, and start them at their declared entry
point. point.
@@ -88,6 +91,7 @@ CosmOS currently provides these built-in commands:
| `dir` | List the files on the mounted disk and their sizes. | | `dir` | List the files on the mounted disk and their sizes. |
| `load <path>` | Read and validate an SBEX application, then place its code and data where its header requests. | | `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. | | `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. |
| `<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. | | `<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. | | `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. | | `rename <file> <to>` | Give a file a different name without moving its contents. |
@@ -155,11 +159,45 @@ Each *name* along a path is still the 22 characters a directory entry holds, and
one is refused rather than cut short, because a name cut to 22 characters is a different one is refused rather than cut short, because a name cut to 22 characters is a different
name that might well be some other file's. name that might well be some other file's.
At this stage CosmOS **reads** directories and does not make them. There is no `cd`, no ### The Working Directory:
`mkdir`, and no working directory: every path is from the root. Files a program writes go
in the root. Deleting or renaming a directory is refused - deleting one would free its `cd` moves the machine. A path beginning with `/` is measured from the root and anything
entry index, and since a parent is written as an index, the next file created would take else from where you are, so a bare name means a file in the current directory - which is
that index and inherit its children. the whole of what a working directory is, and no program had to be told.
```text
> cd /Apps
/Apps> dir
/Apps> cd Deep
/Apps/Deep> cd ..
/Apps> cd
>
```
`cd` with nothing after it goes to the root, which is the only place always there.
**The prompt says where you are, but only when that is not the root**, so a machine nobody
has moved about on looks exactly as it always did. Nothing stores the path: the working
directory is an entry index and two bytes, and the text on the prompt is worked out again
each time by walking the chain of parents upward.
`dir` lists the directory you are in rather than the whole disk.
A program can move too, with `osChangeDir`, and **the shell puts the working directory
back when the program stops** - the same discipline it already applies to the Stack and to
the vector table, and for the same reason. A program is entitled to move about; the shell
is entitled to find itself where it left off.
Whenever what a relative path means changes - a `cd`, a program calling `osChangeDir`, a
program exiting - CosmOS forgets the file it was remembering. That cache is keyed on the
path as it was typed, so `notes.txt` is the same key in two directories and nothing about
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.
### Starting An Application By Name: ### Starting An Application By Name:
@@ -183,6 +221,13 @@ cannot be started by typing what it is called, whatever happens to be inside it.
A path works here too, so `/Apps/Say hello` starts `/Apps/Say.sbx` and gives it `hello`. A path works here too, so `/Apps/Say hello` starts `/Apps/Say.sbx` and gives it `hello`.
**Two places are tried, in order: where you are, and then `/Apps`.** The first is what makes
a program you are working on the one that runs; the second is what lets `Snake` work from
anywhere without a copy of it in every directory. A word that already begins with `/` has
said where to look, so only that place is tried. Neither is stored anywhere, so there is
nothing to configure and nothing to go stale - a search path somebody could set would need
somewhere to live between one boot and the next, and there is no such place yet.
**A file that is found but is broken says so.** If `notes.sbx` exists and is not an SBEX **A file that is found but is broken says so.** If `notes.sbx` exists and is not an SBEX
program, typing `notes` reports `not a program` rather than `I do not know: notes`. program, typing `notes` reports `not a program` rather than `I do not know: notes`.
Reporting an unknown command about a file that is sitting on the disk would send somebody Reporting an unknown command about a file that is sitting on the disk would send somebody
@@ -214,6 +259,7 @@ from every assembly file in it. Several are old programs written for the bare ma
| Edit | A line editor. | | Edit | A line editor. |
| Stream | Reads an 84,000 byte file through a buffer of 256, which is what says a file bigger than Data Memory can be read at all. | | Stream | Reads an 84,000 byte file through a buffer of 256, which is what says a file bigger than Data Memory can be read at all. |
| Type | Prints a named text file a block at a time, including one too large to fit in Data Memory. | | Type | Prints a named text file a block at a time, including one too large to fit in Data Memory. |
| Wander | Goes to the directory it is given and reads a file there by a bare name. The only thing that moves the machine from inside a program, and so the only thing that can check the shell puts the working directory back afterwards. |
| More | A forward-only pager. Space advances a screen, Return one line, and q stops. | | More | A forward-only pager. Space advances a screen, Return one line, and q stops. |
### The Monitor: ### The Monitor:
@@ -332,7 +378,6 @@ fails later, in whatever part of the shell the program happened to cover. `make
measures both segments against the numbers in this table, so the table is checked rather measures both segments against the numbers in this table, so the table is checked rather
than merely written down. than merely written down.
Applications state their actual Program and Data addresses with `#Base`. The SplitBit Applications state their actual Program and Data addresses with `#Base`. The SplitBit
assembler then writes an SBEX loadable image containing those addresses, the entry point, assembler then writes an SBEX loadable image containing those addresses, the entry point,
the segment lengths, and any vectors the application needs. CosmOS does not relocate the segment lengths, and any vectors the application needs. CosmOS does not relocate
@@ -396,6 +441,7 @@ Those numbers are written down once, in `Programs/CosmOS/Source/services.asm`, w
| osFileRename | DP0 is the name a file has, DP1 the name it should have. Q is zero if it moved. | | osFileRename | DP0 is the name a file has, DP1 the name it should have. Q is zero if it moved. |
| osFileInfo | DP0 names a file. Q is zero if it is there, and DP3 comes back holding how many blocks it occupies. | | osFileInfo | DP0 names a file. Q is zero if it is there, and DP3 comes back holding how many blocks it occupies. |
| osFileBlock | DP0 names a file, DP1 says where to put a block of it, A and B together are which block counting from zero. Q is zero if it read, and DP3 comes back holding how many of the block's bytes belong to the file. | | osFileBlock | DP0 names a file, DP1 says where to put a block of it, A and B together are which block counting from zero. Q is zero if it read, and DP3 comes back holding how many of the block's bytes belong to the file. |
| osChangeDir | DP0 names a directory. Q is zero if the machine is now in it. What a program changes here, the shell puts back when the program stops. |
| osPrintNumber | A and B together are a number. Prints it in decimal, without leading zeroes. | | osPrintNumber | A and B together are a number. Prints it in decimal, without leading zeroes. |
| osBreak | Stops the program, shows every register as it had them, waits for a key, and carries on. | | osBreak | Stops the program, shows every register as it had them, waits for a key, and carries on. |
+342 -6
View File
@@ -79,6 +79,21 @@ bootNoDisk:
; shell. Only saying so leaves the monitor, or a program breaking the machine badly enough ; shell. Only saying so leaves the monitor, or a program breaking the machine badly enough
; to need starting again. ; to need starting again.
prompt: prompt:
; Where you are, but only when that is not obvious. At the root the prompt is the one it
; has always been, so a machine nobody has moved about on looks exactly as it did - and
; every recorded test that never says "cd" keeps its recorded prompt.
SETD.0 SbfsCwd
LDA.0
INCD.0
LDB.0
OR
BRQ promptMode
CALL shellPath
SETD.0 CwdAt
LDD.0.0
CALL printString
promptMode:
SETD.0 Mode SETD.0 Mode
LDA.0 LDA.0
BRA promptPlain BRA promptPlain
@@ -122,6 +137,11 @@ promptSay:
CALL textSame CALL textSame
BRQ doRun BRQ doRun
SETD.0 CommandLine
SETD.1 CdName
CALL textSame
BRQ doCd
SETD.0 CommandLine SETD.0 CommandLine
SETD.1 DeleteName SETD.1 DeleteName
CALL textSame CALL textSame
@@ -196,10 +216,19 @@ promptUnknown:
LDA.0 LDA.0
BRA promptSayUnknown ; No filesystem, so there is nothing to look through. BRA promptSayUnknown ; No filesystem, so there is nothing to look through.
; TWO PLACES, TRIED IN ORDER: where you are, and then the system's own place for
; programs. The first is what makes a program you are working on the one that runs; the
; second is what makes Snake work from anywhere without a copy of it in every directory.
; Neither is stored anywhere, so there is nothing to configure and nothing to go stale.
RSTA
SETD.0 NamePrefix
STA.0
promptSearch:
CALL nameProgram CALL nameProgram
SETD.0 NameOk SETD.0 NameOk
LDA.0 LDA.0
BRA promptSayUnknown ; Too long to be a file name, so it is not the name of one. BRA promptElsewhere ; Too long to be a path, so it is not the name of one.
CALL loadProgram CALL loadProgram
SETD.0 LoadStatus SETD.0 LoadStatus
@@ -207,14 +236,35 @@ promptUnknown:
BRA runLoaded ; It loaded, and the machine is its now. BRA runLoaded ; It loaded, and the machine is its now.
; No file of that name is not a fault. It is the ordinary case of a word this shell does ; No file of that name is not a fault. It is the ordinary case of a word this shell does
; not know, and it is reported in those words. Anything else means a file of that name IS ; not know, and it is the only answer worth looking somewhere else for. Anything else
; there and something is wrong with it, and answering "I do not know: Snake" about a ; means a file of that name IS there and something is wrong with it, and answering
; Snake.sbx that is sitting on the disk would send somebody looking in the wrong place. ; "I do not know: Snake" about a Snake.sbx that is sitting on the disk would send
; somebody looking in the wrong place.
INIB 0d2 INIB 0d2
CCF CCF
SUB SUB
BNQ loadFailed BNQ loadFailed
promptElsewhere:
; Was that already the second place?
SETD.0 NamePrefix
LDA.0
BNA promptSayUnknown
; A word beginning with a separator has said where to look, and looking somewhere else
; would be answering a different question from the one asked.
SETD.0 CommandLine
LDA.0
INIB 0x2F
CCF
SUB
BRQ promptSayUnknown
INIA 0x01
SETD.0 NamePrefix
STA.0
BRI promptSearch
promptSayUnknown: promptSayUnknown:
; Saying which word was not understood is worth the four instructions: it tells somebody ; Saying which word was not understood is worth the four instructions: it tells somebody
; who mistyped what they actually typed. ; who mistyped what they actually typed.
@@ -441,9 +491,27 @@ nameProgram:
SETD.0 NameOk SETD.0 NameOk
STA.0 ; Not a name until it turns out to be one. STA.0 ; Not a name until it turns out to be one.
SETD.0 CommandLine
SETD.1 ProgramName SETD.1 ProgramName
INIB 0d59 ; What is left of the buffer, less the four for the extension. INIB 0d59 ; What is left of the buffer, less the four for the extension.
; The system's own place for programs goes on the front, when that is what is being
; tried. Putting it here rather than pasting it on afterwards is what keeps the ".sbx"
; test below looking at the end of the whole thing.
SETD.0 NamePrefix
LDA.0
BRA nameFromLine
SETD.0 AppsPrefix
namePrefixCopy:
LDA.0
BRA nameFromLine
STA.1
INCD.0
INCD.1
DECB
BRI namePrefixCopy
nameFromLine:
SETD.0 CommandLine
nameCopy: nameCopy:
LDA.0 LDA.0
BRA nameCopied BRA nameCopied
@@ -824,6 +892,224 @@ loadRefuse:
STA.1 STA.1
RET RET
; ---- osChangeDir ----
;
; The service behind the shell's cd, and the reason the shell bothers to put the working
; directory back when a program stops: without something a program can call, that promise
; would have been about a thing that could not happen.
;
; DP0 names a directory. Q is zero if the machine is now in it.
handleChangeDir:
; DP0 arrives holding the path, because an interrupt frame keeps the caller's registers
; and this runs with them still in place. What it hands BACK has to be written into the
; frame, since RETI puts every register back the way the caller had it - which is why Q
; is stored at DP2 plus two below rather than simply being set.
SETD.2 DiskReady
LDA.2
BRA changeDirNo
CALL sbfsWalk
BNQ changeDirNo
; The root has no entry to ask about, and is always somewhere that can be stood in.
SETD.2 SbfsAt
LDA.2
INCD.2
LDB.2
OR
BRQ changeDirTake
SETD.2 SbfsFoundFlags
LDA.2
INIB 0x02
AND
BRQ changeDirNo
changeDirTake:
CALL fileForget ; A relative path means something else from here.
SETD.0 SbfsAt
SETD.1 SbfsCwd
CALL sbfsCopyWord
MVSD.2
DPUP.2 0d02
RSTA
STA.2 ; Q is zero: the machine is there now.
RETI
changeDirNo:
MVSD.2
DPUP.2 0d02
INIA 0d1
STA.2
RETI
; ---- cd ----
;
; Moves the machine. What changes is two bytes, because the working directory is an entry
; index and nothing else: no path is stored anywhere, and the one on the prompt is worked
; out again each time from the chain of parents.
;
; "cd" on its own goes to the root, which is the only place that is always there and the
; only sensible thing to mean by home on a machine with no idea who is using it.
doCd:
SETD.0 DiskReady
LDA.0
BRA fileNoDisk
SETD.1 TextRest
LDD.0.1
LDA.0
BRA cdRoot
CALL sbfsWalk
BNQ cdNoSuch
; It has to be a directory to stand in. Ending at the root is fine and is the one case
; with no entry to ask, since the root is not an entry.
SETD.0 SbfsAt
LDA.0
INCD.0
LDB.0
OR
BRQ cdTake
SETD.0 SbfsFoundFlags
LDA.0
INIB 0x02
AND
BRQ cdNotDirectory
cdTake:
; WHAT A RELATIVE PATH MEANS HAS JUST CHANGED, so the remembered file goes. It is keyed
; on the path as somebody typed it, and "notes.txt" is a different file from here than
; it was a moment ago. Nothing about the entry it remembers has changed, which is what
; makes this the kind of stale that is believed rather than noticed.
CALL fileForget
SETD.0 SbfsAt
SETD.1 SbfsCwd
CALL sbfsCopyWord
BRI prompt
cdRoot:
CALL fileForget
SETD.0 SbfsCwd
RSTA
STA.0
INCD.0
STA.0
BRI prompt
cdNoSuch:
SETD.0 NoSuchFile
BRI fileComplain
cdNotDirectory:
SETD.0 NotDirectory
BRI fileComplain
; ---- Writing out where the machine is ----
;
; Builds the working directory's path into CwdText and leaves CwdAt pointing at where it
; begins. Nothing stores a path, so this walks up the chain of parents.
;
; WRITTEN BACKWARDS, from the end of the buffer towards the front, because that is the
; order the names arrive in and reversing them afterwards would want somewhere to put them
; in the meantime. What comes back is a pointer into the middle of the buffer rather than
; to the front of it, which costs nothing to print from.
shellPath:
; The end of the buffer, holding the zero that ends the string.
SETD.0 CwdText
DPUP.0 0d126
RSTA
STA.0
SETD.1 CwdAt
STD.0.1
; Where the walk up starts.
SETD.0 SbfsCwd
SETD.1 CwdWalk
CALL sbfsCopyWord
shellPathStep:
SETD.0 CwdWalk
LDA.0
INCD.0
LDB.0
OR
BRQ shellPathDone ; The root, which is where every path begins.
SETD.0 CwdWalk
SETD.1 SbfsTarget
CALL sbfsCopyWord
SETD.0 SbfsTarget
CALL sbfsBackWord
CALL sbfsAtIndex
BNQ shellPathDone ; The disk would not read, so say as much as is known.
; The name goes in front of what is there, and a separator in front of that.
SETD.0 SbfsName
CALL shellPathPrepend
SETD.0 SbfsUpParent
SETD.1 CwdWalk
CALL sbfsCopyWord
BRI shellPathStep
shellPathDone:
; A machine at the root has written nothing at all, and the path to the root is the
; separator on its own.
SETD.0 CwdAt
LDD.1.0
LDA.1
BNA shellPathReady
SETD.0 Separator
CALL shellPathPrepend
shellPathReady:
RET
; DP0 names a string. Puts it in front of what CwdAt points at, with a separator before
; it, and moves CwdAt back over the lot.
;
; The string has to be measured before it can be written, since it is written from its
; last character backwards. Nothing here is long enough for that to be worth avoiding.
shellPathPrepend:
SETD.1 PathLength
RSTA
STA.1
pathMeasure:
LDA.0
BRA pathMeasured
SETD.1 PathLength
LDA.1
INCA
STA.1
INCD.0
BRI pathMeasure
pathMeasured:
; DP0 is on the zero at the end. Step back onto the last character, unless there is not
; one, in which case only the separator goes in.
SETD.1 CwdAt
LDD.2.1 ; DP2 is where the string already begins.
pathBack:
SETD.1 PathLength
LDA.1
BRA pathSeparator
DECA
STA.1
DECD.0
DECD.2
LDA.0
STA.2
BRI pathBack
pathSeparator:
DECD.2
INIA 0x2F
STA.2
SETD.1 CwdAt
STD.2.1
RET
; ---- delete and rename ---- ; ---- delete and rename ----
; ;
; The two things a disk needs that reading and writing do not provide, and the two that ; The two things a disk needs that reading and writing do not provide, and the two that
@@ -943,6 +1229,11 @@ runLoaded:
SETD.1 SystemStack SETD.1 SystemStack
STD.0.1 STD.0.1
; And where the machine is, so that the exit handler has something to put back.
SETD.0 SbfsCwd
SETD.1 SavedCwd
CALL sbfsCopyWord
; Whatever followed the word "run" is kept where the program can ask for it. Copied ; Whatever followed the word "run" is kept where the program can ask for it. Copied
; rather than pointed at, because what it is pointing at is the line the shell typed ; rather than pointed at, because what it is pointing at is the line the shell typed
; into, and a program is entitled to outlive the shell's opinion of that. ; into, and a program is entitled to outlive the shell's opinion of that.
@@ -1700,6 +1991,15 @@ handleExit:
; an interrupt at whatever those addresses hold next. ; an interrupt at whatever those addresses hold next.
CALL removeVectors CALL removeVectors
; And where the machine was before the program had it, for the same reason and by the
; same discipline as the Stack above and the vectors just now: a program is entitled to
; move about, and the shell is entitled to find itself where it left off. What a relative
; path means has changed back, so the remembered file goes with it.
SETD.0 SavedCwd
SETD.1 SbfsCwd
CALL sbfsCopyWord
CALL fileForget
; The console goes back to how the shell wants it, whatever the program left it in: line ; The console goes back to how the shell wants it, whatever the program left it in: line
; mode, and not interrupting. A program that wanted either is expected to put it back ; mode, and not interrupting. A program that wanted either is expected to put it back
; itself, but one that stopped early, or forgot, would otherwise hand back a shell with ; itself, but one that stopped early, or forgot, would otherwise hand back a shell with
@@ -2789,6 +3089,9 @@ doHelp:
SETD.0 HelpText SETD.0 HelpText
CALL printString CALL printString
CALL newLine CALL newLine
SETD.0 HelpCdText
CALL printString
CALL newLine
SETD.0 HelpMoreText SETD.0 HelpMoreText
CALL printString CALL printString
CALL newLine CALL newLine
@@ -2817,6 +3120,16 @@ Farewell:
"halted" "halted"
DirectoryText: DirectoryText:
"<dir>" "<dir>"
NotDirectory:
"that is not a directory"
Separator:
"/"
; Where a program is looked for when it is not where you are. One fixed place rather than a
; list somebody sets, because a list would need somewhere to live between one boot and the
; next, and there is no such place yet.
AppsPrefix:
"/Apps/"
IsDirectory: IsDirectory:
"that is a directory" "that is a directory"
AndText: AndText:
@@ -2836,7 +3149,9 @@ HelpText:
"dir list what is on the disk "dir list what is on the disk
load <file> read a program off the disk load <file> read a program off the disk
run [words] start what was loaded, and tell it those words run [words] start what was loaded, and tell it those words
<name> [words] load and start that program off the disk" <name> [words] look where you are and then in /Apps, and start that"
HelpCdText:
"cd [path] go to a directory, or to the root with nothing after it"
HelpMoreText: HelpMoreText:
"delete <file> take it off the disk "delete <file> take it off the disk
rename <file> <to> call it something else rename <file> <to> call it something else
@@ -2968,11 +3283,29 @@ DeleteName:
"delete" "delete"
RenameName: RenameName:
"rename" "rename"
CdName:
"cd"
HelpName: HelpName:
"help" "help"
ExitName: ExitName:
"exit" "exit"
; Where the machine is, written out for the prompt, and where in the buffer it begins.
; Built from the end backwards, so it starts somewhere in the middle.
CwdText:
#Reserve 0d127
CwdAt:
0x00 0x00
CwdWalk:
0x00 0x00
PathLength:
0x00
; What the working directory was when a program was started, so that it can be put back
; when the program stops.
SavedCwd:
0x00 0x00
DirFolders: DirFolders:
0x00 0x00
DirTaken: DirTaken:
@@ -3000,6 +3333,8 @@ ProgramName:
#Reserve 0d64 #Reserve 0d64
NameOk: NameOk:
0x00 0x00
NamePrefix:
0x00
NameLeft: NameLeft:
0x00 0x00
LoadedEntry: LoadedEntry:
@@ -3224,6 +3559,7 @@ CommandLine:
osFileRename handleFileRename osFileRename handleFileRename
osFileInfo handleFileInfo osFileInfo handleFileInfo
osFileBlock handleFileBlock osFileBlock handleFileBlock
osChangeDir handleChangeDir
osPrintNumber handlePrintNumber osPrintNumber handlePrintNumber
osBreak handleBreak osBreak handleBreak
Device 0x20 diskDone Device 0x20 diskDone
+128 -9
View File
@@ -104,35 +104,95 @@ sbfsGeometry:
; ---- Finding something by path ---- ; ---- Finding something by path ----
; ;
; DP0 points at a path ending in a zero byte: names with '/' between them, walked from the ; DP0 points at a path ending in a zero byte: names with '/' between them. A path that
; root. Q is zero if it was found, and then SbfsFileStart, SbfsFileBlocks and SbfsFileTail ; begins with a separator is walked from the root, and anything else from SbfsCwd, which
; is where the machine currently is.
;
; Q is zero if it was found, and then SbfsFileStart, SbfsFileBlocks and SbfsFileTail
; describe it, SbfsFoundFlags says what kind of thing it is, and DP3 is left on the entry ; describe it, SbfsFoundFlags says what kind of thing it is, and DP3 is left on the entry
; with SbfsBlock on the directory block it came out of - which is what deleting and ; with SbfsBlock on the directory block it came out of - which is what deleting and
; renaming need in order to change it and put it back. ; renaming need in order to change it and put it back.
; ;
; A BARE NAME IS A PATH OF ONE NAME, so everything written before there were directories ; A BARE NAME IS A PATH OF ONE NAME, so everything written before there were directories
; still works and still costs one walk of the directory. ; still works and still costs one walk of the directory. It now means a name in the
; working directory rather than a name in the root, which is the whole of what a working
; directory is, and no caller had to be told.
; ;
; Each name is looked for among the entries whose parent is where the walk has got to. ; Each name is looked for among the entries whose parent is where the walk has got to.
; There is no list of children anywhere: being a child is a fact written in the child, so ; There is no list of children anywhere: being a child is a fact written in the child, so
; finding them means looking at all of them. That is the same walk the flat version did ; finding them means looking at all of them. That is the same walk the flat version did
; with one more thing compared, which is why a flat disk costs what it always did - and on ; with one more thing compared, which is why a flat disk costs what it always did - and on
; a version one disk every entry says "the root", so the comparison is free and true. ; a version one disk every entry says "the root", so the comparison is free and true.
;
; sbfsFind is sbfsWalk with one thing added: the root is not a file. The two are separate
; because moving about wants the other answer - "cd /" and "cd .." both end at the root
; quite legitimately, and would have no way to say so through a routine that calls it a
; failure.
sbfsFind: sbfsFind:
CALL sbfsWalk
BNQ sbfsFindNoWalk
; Did the walk get anywhere at all? The root is a place, but it is not a file.
SETD.0 SbfsAt
LDA.0
INCD.0
LDB.0
OR
BRQ sbfsFindRoot
RSTA
RSTB
CCF
ADD ; Q is zero: found.
sbfsFindNoWalk:
RET
sbfsFindRoot:
RSTA
INIB 0d1
CCF
ADD
RET
; The walk itself. Q is zero if the whole path was walked, and SbfsAt says where it ended -
; which may be the root, and that is an answer rather than a failure.
sbfsWalk:
SETD.1 SbfsPathAt SETD.1 SbfsPathAt
STD.0.1 STD.0.1
; The walk starts at the root, which is zero: a parent is an entry index PLUS ONE, and ; Where it starts. A path beginning with a separator is measured from the root, which is
; the root is not an entry. ; zero because a parent is an entry index PLUS ONE and the root is not an entry.
; Anything else is measured from wherever the machine already is.
LDA.0
INIB 0x2F
CCF
SUB
BRQ sbfsWalkFromRoot
SETD.0 SbfsCwd
SETD.1 SbfsAt
CALL sbfsCopyWord
; Where the machine is, is a directory - it could not have been got to otherwise - but
; nothing here has read its entry, so what describes it is whatever the last find left
; behind. Say the one thing about it that the walk needs, and mark the rest as not to be
; believed, exactly the way going up does.
INIA 0x02
SETD.0 SbfsFoundFlags
STA.0
RSTA
SETD.0 SbfsFieldsValid
STA.0
BRI sbfsFindName
sbfsWalkFromRoot:
SETD.0 SbfsAt SETD.0 SbfsAt
RSTA RSTA
STA.0 STA.0
INCD.0 INCD.0
STA.0 STA.0
; Nothing described yet. An empty path comes back as missing, which is right: the root ; Nothing described yet. An empty path comes back as the root, which is right.
; is not a file.
RSTA RSTA
SETD.0 SbfsFieldsValid SETD.0 SbfsFieldsValid
STA.0 STA.0
@@ -224,13 +284,13 @@ sbfsFindUp:
BRI sbfsFindName BRI sbfsFindName
sbfsFindEnd: sbfsFindEnd:
; Did the walk get anywhere at all? The root is not a file. ; Ending at the root is a perfectly good answer here, and there is nothing to describe.
SETD.0 SbfsAt SETD.0 SbfsAt
LDA.0 LDA.0
INCD.0 INCD.0
LDB.0 LDB.0
OR OR
BRQ sbfsFindMissing BRQ sbfsFindGood
SETD.0 SbfsFieldsValid SETD.0 SbfsFieldsValid
LDA.0 LDA.0
@@ -442,6 +502,21 @@ sbfsScanFound:
ADD ; Q is zero: found. ADD ; Q is zero: found.
RET RET
; DP2 is on an entry. Q is zero if it lives in the directory the machine is in. The same
; comparison sbfsMatchParent makes, against the other of the two places a walk can be.
sbfsWalkHere:
PSHD.2
POPD.0
DPUP.0 0d28
SETD.2 SbfsCwd
CALL sbfsSameByte
BNQ sbfsWalkHereDone
INCD.0
INCD.2
CALL sbfsSameByte
sbfsWalkHereDone:
RET
; DP2 is on an entry. Q is zero if it lives in the directory the walk is looking in. ; DP2 is on an entry. Q is zero if it lives in the directory the walk is looking in.
; ;
; On a version one disk these two bytes are zero in every entry, and the walk starts at ; On a version one disk these two bytes are zero in every entry, and the walk starts at
@@ -534,6 +609,30 @@ sbfsAtFound:
PSHD.2 PSHD.2
POPD.3 POPD.3
CALL sbfsTakeEntry CALL sbfsTakeEntry
; And the name, which only this way of arriving at an entry needs: walking up a chain of
; parents to write out where the machine is wants the name at every step. Copied here
; rather than in sbfsTakeEntry, because every find in the system goes through that one
; and none of them wants twenty two bytes moved on its behalf.
PSHD.3
POPD.0
DPUP.0 0d06
SETD.1 SbfsName
INIA 0d22
SETD.2 SbfsCount
STA.2
sbfsAtName:
LDA.0
STA.1
INCD.0
INCD.1
LDA.2
DECA
STA.2
BNA sbfsAtName
RSTA
STA.1 ; The twenty third byte, which makes it a string.
RSTA RSTA
RSTB RSTB
CCF CCF
@@ -697,6 +796,13 @@ sbfsWalkEntry:
AND AND
BRQ sbfsWalkScan BRQ sbfsWalkScan
; And is it in the directory the machine is in? The entries of every directory on the
; disk are mixed together in one array - being a child is a fact written in the child,
; not a list kept anywhere - so a walk that wants one directory has to say which, and
; everything else is stepped over on the way past.
CALL sbfsWalkHere
BNQ sbfsWalkScan
CALL sbfsWalkTake CALL sbfsWalkTake
RSTA RSTA
RSTB RSTB
@@ -1770,6 +1876,19 @@ SbfsMatchLeft:
0x00 0x00
SbfsLeft: SbfsLeft:
0x00 0x00
; ---- Where the machine is ----
;
; The working directory, written the way a parent is written: an entry index plus one, so
; zero is the root and a freshly booted machine is already there. A path that does not
; begin with a separator is measured from here.
;
; It lives here rather than in the shell because it is what a relative path MEANS, and the
; thing that resolves a path is here. Keeping it in the shell would mean either handing it
; down on every call or having the shell paste it onto the front of every name, and the
; second of those is how a name that is already absolute gets ruined.
SbfsCwd:
0x00 0x00
; ---- What walking a path keeps ---- ; ---- What walking a path keeps ----
; ;
; SbfsAt is where the walk has got to, written the way a parent is written: an entry index ; SbfsAt is where the walk has got to, written the way a parent is written: an entry index
+13
View File
@@ -68,6 +68,19 @@
; 4 the disk would not read it (osFileBlock only) ; 4 the disk would not read it (osFileBlock only)
osFileInfo 0d26 ; DP0 names it. Q is zero if it is there, DP3 is how many blocks. osFileInfo 0d26 ; DP0 names it. Q is zero if it is there, DP3 is how many blocks.
osFileBlock 0d27 ; DP0 names it, DP1 says where, A and B are which block from zero. osFileBlock 0d27 ; DP0 names it, DP1 says where, A and B are which block from zero.
; ---- Moving about ----
;
; DP0 names a directory. Q is zero if the machine is now in it.
;
; WHAT A PROGRAM CHANGES HERE, THE SHELL PUTS BACK when the program stops - the same
; discipline the Stack and the vector table are held to, and for the same reason. A program
; is entitled to move about; the shell is entitled to find itself where it left off.
;
; This is what makes a bare name mean something to a program: everything a program opens is
; relative to here, so a program given a directory to work in can say "notes.txt" and mean
; the one in it.
osChangeDir 0d28
; Q is zero if it read, DP3 is how many of its bytes are the file's: ; Q is zero if it read, DP3 is how many of its bytes are the file's:
; a whole 0d256 except in a last block that is short. That count is ; a whole 0d256 except in a last block that is short. That count is
; why DP3 answers and not a register - 0d256 does not fit in a byte, ; why DP3 answers and not a register - 0d256 does not fit in a byte,
+2 -1
View File
@@ -2,7 +2,8 @@ CosmOS
> dir list what is on the disk > dir list what is on the disk
load <file> read a program off the disk load <file> read a program off the disk
run [words] start what was loaded, and tell it those words run [words] start what was loaded, and tell it those words
<name> [words] load and start that program off the disk <name> [words] look where you are and then in /Apps, and start that
cd [path] go to a directory, or to the root with nothing after it
delete <file> take it off the disk delete <file> take it off the disk
rename <file> <to> call it something else rename <file> <to> call it something else
monitor look at memory, change it, and jump into it monitor look at memory, change it, and jump into it
+2 -2
View File
@@ -3,11 +3,11 @@ CosmOS
> two stops, and what the registers were at each > two stops, and what the registers were at each
break at 400E break at 400E
A 11 B 22 Q 00 status 00 A 11 B 22 Q 00 status 00
DP0 2030 DP1 0730 DP2 0000 DP3 4000 SP FFFF DP0 2030 DP1 082E DP2 0000 DP3 4000 SP FFFF
press a key press a key
break at 4023 break at 4023
A 44 B 55 Q 00 status 00 A 44 B 55 Q 00 status 00
DP0 2000 DP1 0730 DP2 0000 DP3 4000 SP FFF5 DP0 2000 DP1 082E DP2 0000 DP3 4000 SP FFF5
press a key press a key
carried on to the end carried on to the end
finished finished
+36
View File
@@ -0,0 +1,36 @@
CosmOS
> Apps <dir>
A <dir>
B <dir>
0 files, 3 directories
> /A> Say.sbx 52
notes.txt 25
2 files
/A> these are the notes in A
finished
/A> /B> and these are the very different notes in B
finished
/B> it says: reached the one in Apps
finished
/B> /A> Hello, World!
finished
/A> > Apps <dir>
A <dir>
B <dir>
0 files, 3 directories
> /B> these are the notes in A
finished
/B> moved, and reading a bare name from there:
these are the notes in A
finished
/B> and these are the very different notes in B
finished
/B> no such file
/B> that is not a directory
/B> > Apps <dir>
A <dir>
B <dir>
0 files, 3 directories
> halted
Execution halted.
[exit 0]
+2 -1
View File
@@ -19,7 +19,8 @@ notes.sbx 21
> dir list what is on the disk > dir list what is on the disk
load <file> read a program off the disk load <file> read a program off the disk
run [words] start what was loaded, and tell it those words run [words] start what was loaded, and tell it those words
<name> [words] load and start that program off the disk <name> [words] look where you are and then in /Apps, and start that
cd [path] go to a directory, or to the root with nothing after it
delete <file> take it off the disk delete <file> take it off the disk
rename <file> <to> call it something else rename <file> <to> call it something else
monitor look at memory, change it, and jump into it monitor look at memory, change it, and jump into it
+2 -7
View File
@@ -1,10 +1,7 @@
CosmOS CosmOS
> Apps <dir> > Apps <dir>
Deep <dir>
Say.sbx 155
Say.sbx 52
rooted.txt 21 rooted.txt 21
3 files, 2 directories 1 file, 1 directory
> loaded, starting at 4000 > loaded, starting at 4000
> it says: the shallow one > it says: the shallow one
finished finished
@@ -26,10 +23,8 @@ finished
> that is a directory > that is a directory
> gone > gone
> Apps <dir> > Apps <dir>
Deep <dir>
Say.sbx 155
rooted.txt 21 rooted.txt 21
2 files, 2 directories 1 file, 1 directory
> halted > halted
Execution halted. Execution halted.
[exit 0] [exit 0]
+2 -4
View File
@@ -1,17 +1,15 @@
CosmOS CosmOS
> Folder <dir> > Folder <dir>
Inner <dir>
Edit.sbx 1996 Edit.sbx 1996
1 file, 2 directories 1 file, 1 directory
> loaded, starting at 4000 > loaded, starting at 4000
> note.txt, 0 lines > note.txt, 0 lines
> : : : > written, 67 bytes > : : : > written, 67 bytes
> finished > finished
> Folder <dir> > Folder <dir>
Inner <dir>
Edit.sbx 1996 Edit.sbx 1996
note.txt 67 note.txt 67
2 files, 2 directories 2 files, 1 directory
> halted > halted
Execution halted. Execution halted.
[exit 0] [exit 0]
+20
View File
@@ -0,0 +1,20 @@
dir
cd /A
dir
Type notes.txt
cd /B
Type notes.txt
Say reached the one in Apps
cd /A
Say reached the one in A
cd ..
dir
cd /B
Type ../A/notes.txt
Wander /A
Type notes.txt
cd nosuchplace
cd notes.txt
cd /
dir
exit
+31
View File
@@ -219,6 +219,37 @@ awk 'BEGIN { for (i = 0; i < 30; i++) printf "line %02d: ABCDEFGHIJKLMNOPQRSTUVW
printf 'this is not a program' > rooted.txt printf 'this is not a program' > rooted.txt
"$TOOL" put "$DISKS/tree.img" rooted.txt >/dev/null "$TOOL" put "$DISKS/tree.img" rooted.txt >/dev/null
# A disk for moving about on. Two directories hold a file of THE SAME NAME with different
# text in it, which is the fixture the working directory needs: "notes.txt" has to mean a
# different file from each of them, and the only way to see that it does is for the two to
# say different things.
#
# It is also what checks the remembered file is dropped when the machine moves. That cache
# is keyed on the path as somebody typed it, so "notes.txt" is the same key in both places
# and nothing about the entry it remembers looks wrong - it is the kind of stale that is
# believed rather than noticed.
#
# Type and Say go in /Apps, so that a program can be started from anywhere; hello goes in
# /A under the name Say.sbx, so that where you are is visibly tried before /Apps.
"$TOOL" format "$DISKS/cwd.img" 256 4 >/dev/null
"$TOOL" mkdir "$DISKS/cwd.img" /Apps >/dev/null
"$TOOL" mkdir "$DISKS/cwd.img" /A >/dev/null
"$TOOL" mkdir "$DISKS/cwd.img" /B >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Type.asm" -o "$WORK/Type.sbx" >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/Type.sbx" /Apps/Type.sbx >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/treeHello.sbx" /A/Say.sbx >/dev/null
printf 'these are the notes in A\n' > notesA.txt
printf 'and these are the very different notes in B\n' > notesB.txt
"$TOOL" put "$DISKS/cwd.img" notesA.txt /A/notes.txt >/dev/null
"$TOOL" put "$DISKS/cwd.img" notesB.txt /B/notes.txt >/dev/null
# Wander is the only thing that can move the machine from inside a program, which makes it
# the only thing that can check the shell puts the working directory back afterwards.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Wander.asm" -o "$WORK/Wander.sbx" >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/Wander.sbx" /Apps/Wander.sbx >/dev/null
# A disk for invoking a program by typing its name. Four files, each there to say one # A disk for invoking a program by typing its name. Four files, each there to say one
# thing about how a typed word turns into a file name. # thing about how a typed word turns into a file name.
# #
+20
View File
@@ -343,6 +343,25 @@ cosmosTree | CosmOS/Source/cosmos.asm | run | cosmosTre
# hold no blocks and must therefore be in nobody's way when a run of free ones is wanted. # hold no blocks and must therefore be in nobody's way when a run of free ones is wanted.
# The listing afterwards says where it landed and how big it is. # The listing afterwards says where it landed and how big it is.
cosmosTreeWrite | CosmOS/Source/cosmos.asm | run | cosmosTreeWrite.in | - | disks/treewrite.img cosmosTreeWrite | CosmOS/Source/cosmos.asm | run | cosmosTreeWrite.in | - | disks/treewrite.img
# The working directory. cd moves the machine, the prompt says where it is once that is
# not the root, and dir lists one directory rather than the whole disk.
#
# The two notes.txt say different things ON PURPOSE, and that is the whole test: reading
# "notes.txt" from /A and then from /B has to give two different files. If the remembered
# file were not dropped when the machine moves, the second read would hand back the first
# one - same key, same entry, nothing about it looking wrong.
#
# Then that a program is looked for where you are BEFORE /Apps: /A holds a Say.sbx that is
# really hello, so "Say" from /A prints the wrong thing on purpose, and "Say" from /B has
# to reach the real one in /Apps.
#
# And the two things only a program moving the machine itself can check. Wander calls
# osChangeDir, so after "Wander /A" from /B the prompt has to say /B again - the shell puts
# the working directory back the way it puts the Stack and the vector table back. Reading
# notes.txt straight afterwards has to give B's copy, which is the remembered file being
# dropped when the machine moved. Without Wander neither of those could be made to fail,
# because nothing else on the machine can move it.
cosmosCwd | CosmOS/Source/cosmos.asm | run | cosmosCwd.in | - | disks/cwd.img
# Typing a program's name starts it. The disk is built so that each line of the input asks # Typing a program's name starts it. The disk is built so that each line of the input asks
# a different question of the one rule: "Say hello there" is a bare name with an argument, # a different question of the one rule: "Say hello there" is a bare name with an argument,
# "Say.sbx" is the same file spelled out in full, and "run once more" says the program that # "Say.sbx" is the same file spelled out in full, and "run once more" says the program that
@@ -381,6 +400,7 @@ app-Edit | CosmOS/Apps/Edit.asm | assemble | -
app-Files | CosmOS/Apps/Files.asm | assemble | - | - app-Files | CosmOS/Apps/Files.asm | assemble | - | -
app-Stream | CosmOS/Apps/Stream.asm | assemble | - | - app-Stream | CosmOS/Apps/Stream.asm | assemble | - | -
app-Type | CosmOS/Apps/Type.asm | assemble | - | - app-Type | CosmOS/Apps/Type.asm | assemble | - | -
app-Wander | CosmOS/Apps/Wander.asm | assemble | - | -
app-More | CosmOS/Apps/More.asm | assemble | - | - app-More | CosmOS/Apps/More.asm | assemble | - | -
# The assembler that runs on the machine, and its parts. Checked on their own so that a # The assembler that runs on the machine, and its parts. Checked on their own so that a
# failure reads as "it does not assemble" rather than as a broken disk image. # failure reads as "it does not assemble" rather than as a broken disk image.