D2: the machine walks a path
sbfsFind takes a path where it used to take a name: names with '/' between them, walked from the root, with '.' and '..'. Each name is looked for among the entries whose parent is where the walk has got to. A bare name is a path of one name, so everything written before directories existed still works and still costs one walk of the directory. sbfsMount takes either version. On a version one disk every entry has zeroes where a parent goes and the walk starts at zero, so the comparison always agrees - which is how a flat disk reads correctly here with nothing done to it. PROGRAMS DID NOT HAVE TO BE TAUGHT ANY OF THIS. Resolution sits inside sbfsFind, below the services, so every osFile call keeps its signature and a path is simply a longer name. Type, More, Edit and the assembler gained subdirectories without a line changing in any of them. Four things this turned up, none of which was the path walk: load copied the path into a buffer sized for a NAME, so anything over 22 characters was cut short - and cut short into a path that often still resolved. "/Apps/Deep/../../Apps/Say.sbx" became "/Apps/Deep/../../Apps/" and reported that the program was a directory. That is the whole of what looked like a bug in '..', and it cost most of the time here. load on a directory SUCCEEDED. A directory has no blocks, so reading it reads nothing and leaves the staging area holding whatever was staged last - which, if that was a program, still says SBEX and still has a working entry address. It handed back the program before it. Refused outright now. delete and rename on a directory are refused, and save refuses one up front rather than failing at the rename and leaving a temporary behind. Deleting a directory frees an entry index, and a parent IS an index, so the next file created would take it and inherit the children. create writes the parent rather than leaving it zero by luck. It would be zero - delete wipes all thirty two bytes and a fresh entry never had any - but that is a fact about two other routines, and a file appearing inside a directory it was never put in is not a failure anybody would think to look for. dir marks directories and counts them apart from files, because at this point it was calling them files of no bytes. Two hazards written down in the design note turned out not to be real, and both were checked rather than argued about: The lookup cache holding 22 bytes of a longer path cannot hand back the wrong file - textSame wants both strings to end in the same place, so a cut down entry misses. It can never HIT either, though, so every path longer than a name went to the disk every time; it holds a whole path now. The allocator stepping over directories changes nothing any test can see. A directory has no start as well as no blocks, so its bounds are nought to nought and no candidate begins before it ends. The four instructions stay, with a comment saying they are not load bearing today and why they are there anyway. makedisks.sh resolves its build path before it cds. Given a relative one it carried on and quietly built disks missing some of their files, which is how the tree fixture lost a file and sent me looking for a bug in '..'. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
78e9eef472
commit
36a1b07b5b
@@ -269,6 +269,8 @@ doDir:
|
||||
RSTA
|
||||
SETD.0 DirSeen
|
||||
STA.0
|
||||
SETD.0 DirFolders
|
||||
STA.0
|
||||
|
||||
CALL sbfsFirst
|
||||
BRI dirCheck
|
||||
@@ -290,6 +292,15 @@ dirCheck:
|
||||
MVQA
|
||||
CALL printSpaces
|
||||
|
||||
; A directory says so instead of saying a size. It has no blocks, so the arithmetic
|
||||
; below would call it a file of no bytes - which is a different thing that happens to
|
||||
; look the same from here.
|
||||
SETD.0 SbfsFoundFlags
|
||||
LDA.0
|
||||
INIB 0x02
|
||||
AND
|
||||
BNQ dirIsDirectory
|
||||
|
||||
; A file's length is its block count times 256 plus its tail, which is the block count
|
||||
; in the high byte and the tail in the low one. Nothing has to multiply anything.
|
||||
SETD.0 SbfsFileBlocks
|
||||
@@ -308,7 +319,35 @@ dirCheck:
|
||||
CALL newLine
|
||||
BRI dirStep
|
||||
|
||||
dirIsDirectory:
|
||||
SETD.0 DirFolders
|
||||
LDA.0
|
||||
INCA
|
||||
STA.0
|
||||
SETD.0 DirectoryText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI dirStep
|
||||
|
||||
dirDone:
|
||||
; Directories were counted alongside the files and now come back out of the total, so
|
||||
; that "three files" means three files. Saying it any other way makes the number
|
||||
; disagree with the listing right above it, which is the sort of thing that teaches
|
||||
; somebody not to trust the listing.
|
||||
SETD.0 DirFolders
|
||||
LDA.0
|
||||
SETD.1 DirTaken
|
||||
STA.1
|
||||
SETD.0 DirSeen
|
||||
LDA.0
|
||||
SETD.1 DirTaken
|
||||
LDB.1
|
||||
CCF
|
||||
SUB
|
||||
MVQA
|
||||
SETD.0 DirSeen
|
||||
STA.0
|
||||
|
||||
SETD.0 DirSeen
|
||||
LDA.0
|
||||
CALL printByteDecimal
|
||||
@@ -323,6 +362,28 @@ dirOne:
|
||||
SETD.0 FileText
|
||||
dirCount:
|
||||
CALL printString
|
||||
|
||||
; And how many of them were directories, but only when there were any. A disk with none
|
||||
; on it should read exactly the way it always did.
|
||||
SETD.0 DirFolders
|
||||
LDA.0
|
||||
BRA dirNoFolders
|
||||
SETD.0 AndText
|
||||
CALL printString
|
||||
SETD.0 DirFolders
|
||||
LDA.0
|
||||
CALL printByteDecimal
|
||||
SETD.0 DirFolders
|
||||
LDA.0
|
||||
DECA
|
||||
BRA dirOneFolder
|
||||
SETD.0 FoldersText
|
||||
BRI dirFolderCount
|
||||
dirOneFolder:
|
||||
SETD.0 FolderText
|
||||
dirFolderCount:
|
||||
CALL printString
|
||||
dirNoFolders:
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
|
||||
@@ -370,10 +431,11 @@ widthDone:
|
||||
; typing what it is called, whatever is inside it. Only load reaches a file by its whole
|
||||
; name, which is why the monitor can still put any file at all in front of itself.
|
||||
;
|
||||
; NameOk is one if there is a name in ProgramName and zero if the word could not be made
|
||||
; into one. A directory entry holds twenty two characters and four of those are spoken for
|
||||
; by the extension, so eighteen is as long as a bare name can be. Being refused here reads
|
||||
; as an unknown command, which is the truth: no file of that name can exist.
|
||||
; NameOk is one if there is a path in ProgramName and zero if the word could not be made
|
||||
; into one. What limits it is the buffer, not the format: each NAME along a path is still
|
||||
; twenty two characters, and the path walker refuses a longer one rather than cutting it
|
||||
; down. Being refused here reads as an unknown command, which is the truth: nothing this
|
||||
; shell can reach is called that.
|
||||
nameProgram:
|
||||
RSTA
|
||||
SETD.0 NameOk
|
||||
@@ -381,7 +443,7 @@ nameProgram:
|
||||
|
||||
SETD.0 CommandLine
|
||||
SETD.1 ProgramName
|
||||
INIB 0d22 ; How much of the twenty two is left.
|
||||
INIB 0d59 ; What is left of the buffer, less the four for the extension.
|
||||
nameCopy:
|
||||
LDA.0
|
||||
BRA nameCopied
|
||||
@@ -390,7 +452,7 @@ nameCopy:
|
||||
INCD.1
|
||||
DECB
|
||||
BNB nameCopy
|
||||
RET ; Twenty two characters and still going. Not a name.
|
||||
RET ; Longer than the buffer holds, so it is not a path either.
|
||||
|
||||
nameCopied:
|
||||
; DP1 is on the byte after the word, which is where an extension would go, and B is what
|
||||
@@ -402,7 +464,7 @@ nameCopied:
|
||||
|
||||
; Only a word of four characters or more can already end in ".sbx". Stepping back four to
|
||||
; look at a shorter one would read whatever happens to sit in front of the buffer.
|
||||
INIA 0d18
|
||||
INIA 0d55
|
||||
CCF
|
||||
SUB
|
||||
BRC nameAppend
|
||||
@@ -468,10 +530,10 @@ doLoad:
|
||||
LDA.0
|
||||
BRA loadNothingNamed
|
||||
|
||||
; The name exactly as typed. load is how a file is reached by its whole name, so nothing
|
||||
; The path exactly as typed. load is how a file is reached by its whole name, so nothing
|
||||
; is added to it and nothing is assumed about what it ends in.
|
||||
SETD.1 ProgramName
|
||||
INIB 0d23
|
||||
INIB 0d64
|
||||
CALL copyText
|
||||
|
||||
CALL loadProgram
|
||||
@@ -512,6 +574,7 @@ loadNothingNamed:
|
||||
; 4 it is not a program
|
||||
; 5 a version of the format this loader does not know
|
||||
; 6 more vectors than there is room to keep
|
||||
; 7 it is a directory
|
||||
;
|
||||
; Two is the one worth telling apart from the others. It is the only outcome where nothing
|
||||
; was wrong with the disk or with a file, and so the only one a caller can fairly report as
|
||||
@@ -530,6 +593,17 @@ loadProgram:
|
||||
CALL sbfsFind
|
||||
BNQ loadMissing
|
||||
|
||||
; A DIRECTORY IS REFUSED HERE AND NOT LEFT TO THE MAGIC CHECK BELOW. It has no blocks,
|
||||
; so reading it reads nothing and leaves the staging area holding whatever was staged
|
||||
; last - which, if that was a program, still says "SBEX" and still has a working entry
|
||||
; address in it. Loading a directory would quietly hand back the program before it, and
|
||||
; running it would look like the directory had run.
|
||||
SETD.0 SbfsFoundFlags
|
||||
LDA.0
|
||||
INIB 0x02
|
||||
AND
|
||||
BNQ loadIsDirectory
|
||||
|
||||
SETD.1 0x80 0x00
|
||||
CALL sbfsRead
|
||||
BNQ loadUnreadable
|
||||
@@ -739,6 +813,10 @@ loadWrongVersion:
|
||||
loadTooManyVectors:
|
||||
INIA 0d6
|
||||
SETD.0 TooManyVectors
|
||||
BRI loadRefuse
|
||||
loadIsDirectory:
|
||||
INIA 0d7
|
||||
SETD.0 IsDirectory
|
||||
loadRefuse:
|
||||
SETD.1 LoadMessage
|
||||
STD.0.1
|
||||
@@ -774,9 +852,26 @@ doDelete:
|
||||
deleteWhat:
|
||||
SETD.0 DeleteWhat
|
||||
BRI fileComplain
|
||||
|
||||
deleteFailed:
|
||||
; Two refusals arrive here as one. Asking again costs a walk of the directory, which is
|
||||
; nothing on a path nobody takes twice, and it is the difference between "you typed a
|
||||
; name that is not there" and "that is a directory, and delete does not take those".
|
||||
SETD.1 TextRest
|
||||
LDD.0.1
|
||||
CALL sbfsFind
|
||||
BNQ deleteNoSuch
|
||||
SETD.0 SbfsFoundFlags
|
||||
LDA.0
|
||||
INIB 0x02
|
||||
AND
|
||||
BNQ deleteIsDirectory
|
||||
deleteNoSuch:
|
||||
SETD.0 NoSuchFile
|
||||
BRI fileComplain
|
||||
deleteIsDirectory:
|
||||
SETD.0 IsDirectory
|
||||
BRI fileComplain
|
||||
|
||||
doRename:
|
||||
SETD.0 DiskReady
|
||||
@@ -1230,8 +1325,15 @@ fileLookupSearch:
|
||||
|
||||
; Remember it. DP0 still names the file: a CALL puts the pointers back, which is the one
|
||||
; place that convention is a convenience rather than an obstacle.
|
||||
;
|
||||
; A WHOLE PATH IS KEPT, not the twenty two bytes a name has. Keeping twenty two of a
|
||||
; longer path cannot hand back the wrong file - textSame wants both strings to end in
|
||||
; the same place, so a cut down entry misses rather than matching something else - but
|
||||
; it can never match either, so every path longer than a name would go to the disk every
|
||||
; single time and the cache would quietly stop being one.
|
||||
SETD.1 FileCacheName
|
||||
CALL sbfsKeepName
|
||||
INIB 0d64
|
||||
CALL copyText
|
||||
SETD.0 FileCacheStart
|
||||
SETD.2 SbfsFileStart
|
||||
CALL sbfsSetWord
|
||||
@@ -2713,6 +2815,16 @@ Unknown:
|
||||
"I do not know: "
|
||||
Farewell:
|
||||
"halted"
|
||||
DirectoryText:
|
||||
"<dir>"
|
||||
IsDirectory:
|
||||
"that is a directory"
|
||||
AndText:
|
||||
", "
|
||||
FoldersText:
|
||||
" directories"
|
||||
FolderText:
|
||||
" directory"
|
||||
FilesText:
|
||||
" files"
|
||||
FileText:
|
||||
@@ -2861,6 +2973,10 @@ HelpName:
|
||||
ExitName:
|
||||
"exit"
|
||||
|
||||
DirFolders:
|
||||
0x00
|
||||
DirTaken:
|
||||
0x00
|
||||
DiskReady:
|
||||
0x00
|
||||
LoadedOk:
|
||||
@@ -2872,10 +2988,16 @@ LoadStatus:
|
||||
LoadMessage:
|
||||
0x00 0x00
|
||||
|
||||
; The name of the file to load, which load copies out of the line as typed and a typed
|
||||
; program name is built into. Twenty two characters and the zero that ends them.
|
||||
; THE PATH of the file to load, which load copies out of the line as typed and a typed
|
||||
; program name is built into.
|
||||
;
|
||||
; Sixty four rather than the twenty three a NAME needs. It held a name when a disk was
|
||||
; flat and there was nothing else to hold, and leaving it that size once paths existed cut
|
||||
; every path longer than twenty two characters down to twenty two - which is not a failure
|
||||
; that looks like one. "/Apps/Deep/../../Apps/Say.sbx" became "/Apps/Deep/../../Apps/",
|
||||
; resolved perfectly well, and reported that the program was a directory.
|
||||
ProgramName:
|
||||
#Reserve 0d23
|
||||
#Reserve 0d64
|
||||
NameOk:
|
||||
0x00
|
||||
NameLeft:
|
||||
@@ -2924,7 +3046,7 @@ PrintNumber:
|
||||
FileCacheValid:
|
||||
0x00
|
||||
FileCacheName:
|
||||
#Reserve 0d23
|
||||
#Reserve 0d64
|
||||
FileCacheStart:
|
||||
0x00 0x00
|
||||
FileCacheBlocks:
|
||||
|
||||
Reference in New Issue
Block a user