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.
- 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 `/`
between them, walked from the root, with `.` and `..`. Directories are read but not yet
made; the host tool makes them.
between them, with `.` and `..`. Directories are read but not yet made; the host tool
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
the addresses for which they were assembled, and start them at their declared entry
point.
@@ -88,6 +91,7 @@ CosmOS currently provides these built-in commands:
| `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. |
| `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. |
| `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. |
@@ -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
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
`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
entry index, and since a parent is written as an index, the next file created would take
that index and inherit its children.
### The Working Directory:
`cd` moves the machine. A path beginning with `/` is measured from the root and anything
else from where you are, so a bare name means a file in the current directory - which is
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:
@@ -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`.
**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
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
@@ -214,6 +259,7 @@ from every assembly file in it. Several are old programs written for the bare ma
| 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. |
| 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. |
### 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
than merely written down.
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,
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. |
| 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. |
| 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. |
| 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
; to need starting again.
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
LDA.0
BRA promptPlain
@@ -122,6 +137,11 @@ promptSay:
CALL textSame
BRQ doRun
SETD.0 CommandLine
SETD.1 CdName
CALL textSame
BRQ doCd
SETD.0 CommandLine
SETD.1 DeleteName
CALL textSame
@@ -196,10 +216,19 @@ promptUnknown:
LDA.0
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
SETD.0 NameOk
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
SETD.0 LoadStatus
@@ -207,14 +236,35 @@ promptUnknown:
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
; not know, and it is reported in those words. Anything else means a file of that name IS
; there and something is wrong with it, and answering "I do not know: Snake" about a
; Snake.sbx that is sitting on the disk would send somebody looking in the wrong place.
; not know, and it is the only answer worth looking somewhere else for. Anything else
; means a file of that name IS there and something is wrong with it, and answering
; "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
CCF
SUB
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:
; Saying which word was not understood is worth the four instructions: it tells somebody
; who mistyped what they actually typed.
@@ -441,9 +491,27 @@ nameProgram:
SETD.0 NameOk
STA.0 ; Not a name until it turns out to be one.
SETD.0 CommandLine
SETD.1 ProgramName
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:
LDA.0
BRA nameCopied
@@ -824,6 +892,224 @@ loadRefuse:
STA.1
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 ----
;
; 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
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
; 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.
@@ -1700,6 +1991,15 @@ handleExit:
; an interrupt at whatever those addresses hold next.
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
; 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
@@ -2789,6 +3089,9 @@ doHelp:
SETD.0 HelpText
CALL printString
CALL newLine
SETD.0 HelpCdText
CALL printString
CALL newLine
SETD.0 HelpMoreText
CALL printString
CALL newLine
@@ -2817,6 +3120,16 @@ Farewell:
"halted"
DirectoryText:
"<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:
"that is a directory"
AndText:
@@ -2836,7 +3149,9 @@ HelpText:
"dir list what is on the disk
load <file> read a program off the disk
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:
"delete <file> take it off the disk
rename <file> <to> call it something else
@@ -2968,11 +3283,29 @@ DeleteName:
"delete"
RenameName:
"rename"
CdName:
"cd"
HelpName:
"help"
ExitName:
"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:
0x00
DirTaken:
@@ -3000,6 +3333,8 @@ ProgramName:
#Reserve 0d64
NameOk:
0x00
NamePrefix:
0x00
NameLeft:
0x00
LoadedEntry:
@@ -3224,6 +3559,7 @@ CommandLine:
osFileRename handleFileRename
osFileInfo handleFileInfo
osFileBlock handleFileBlock
osChangeDir handleChangeDir
osPrintNumber handlePrintNumber
osBreak handleBreak
Device 0x20 diskDone
+128 -9
View File
@@ -104,35 +104,95 @@ sbfsGeometry:
; ---- Finding something by path ----
;
; DP0 points at a path ending in a zero byte: names with '/' between them, walked from the
; root. Q is zero if it was found, and then SbfsFileStart, SbfsFileBlocks and SbfsFileTail
; DP0 points at a path ending in a zero byte: names with '/' between them. A path that
; 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
; 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.
;
; 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.
; 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
; 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.
;
; 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:
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
STD.0.1
; The walk starts at the root, which is zero: a parent is an entry index PLUS ONE, and
; the root is not an entry.
; Where it starts. A path beginning with a separator is measured from the root, which is
; 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
RSTA
STA.0
INCD.0
STA.0
; Nothing described yet. An empty path comes back as missing, which is right: the root
; is not a file.
; Nothing described yet. An empty path comes back as the root, which is right.
RSTA
SETD.0 SbfsFieldsValid
STA.0
@@ -224,13 +284,13 @@ sbfsFindUp:
BRI sbfsFindName
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
LDA.0
INCD.0
LDB.0
OR
BRQ sbfsFindMissing
BRQ sbfsFindGood
SETD.0 SbfsFieldsValid
LDA.0
@@ -442,6 +502,21 @@ sbfsScanFound:
ADD ; Q is zero: found.
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.
;
; 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
POPD.3
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
RSTB
CCF
@@ -697,6 +796,13 @@ sbfsWalkEntry:
AND
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
RSTA
RSTB
@@ -1770,6 +1876,19 @@ SbfsMatchLeft:
0x00
SbfsLeft:
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 ----
;
; 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)
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.
; ---- 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:
; 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,