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
+571
-55
@@ -60,13 +60,25 @@ sbfsMagicSame:
|
||||
BNA sbfsMagicLoop
|
||||
|
||||
sbfsMagicDone:
|
||||
; The version has to be one we understand.
|
||||
; The version has to be one we understand, and there are two of them.
|
||||
;
|
||||
; Version one is flat: every file is in the root because there is nowhere else. Version
|
||||
; two gives each entry a parent, and writes it as the entry's index PLUS ONE - so the
|
||||
; zeroes a version one disk has in those bytes read as "in the root", which is exactly
|
||||
; where all of its files are. A version one disk needs nothing done to it to be read
|
||||
; here, and that is why both numbers are taken rather than one being converted.
|
||||
SETD.0 SbfsBuffer
|
||||
DPUP.0 0d04
|
||||
LDA.0
|
||||
INIB 0d1
|
||||
XOR
|
||||
BRQ sbfsGeometry
|
||||
SETD.0 SbfsBuffer
|
||||
DPUP.0 0d04
|
||||
LDA.0
|
||||
INIB 0d2
|
||||
XOR
|
||||
BRQ sbfsGeometry
|
||||
RET ; A version we do not know.
|
||||
|
||||
sbfsGeometry:
|
||||
@@ -90,60 +102,155 @@ sbfsGeometry:
|
||||
ADD ; Q is zero: mounted.
|
||||
RET
|
||||
|
||||
; Finds a file by name. DP0 points at a name, ending in a zero byte. Q is zero if it was
|
||||
; found, and then SbfsFileStart, SbfsFileBlocks and SbfsFileTail describe it.
|
||||
sbfsFind:
|
||||
SETD.1 SbfsWanted
|
||||
CALL sbfsKeepName
|
||||
; ---- 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
|
||||
; 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.
|
||||
;
|
||||
; 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.
|
||||
|
||||
; Start at the first directory block and work through them all.
|
||||
SETD.0 SbfsDirStart
|
||||
SETD.1 SbfsBlock
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsDirBlocks
|
||||
sbfsFind:
|
||||
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.
|
||||
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.
|
||||
RSTA
|
||||
SETD.0 SbfsFieldsValid
|
||||
STA.0
|
||||
|
||||
sbfsFindName:
|
||||
CALL sbfsPathNext
|
||||
SETD.0 SbfsPathState
|
||||
LDA.0
|
||||
BRA sbfsFindGotName
|
||||
INIB 0d1
|
||||
CCF
|
||||
SUB
|
||||
BRQ sbfsFindEnd ; The path ran out.
|
||||
BRI sbfsFindMissing ; A name longer than a name can be, so nothing is called it.
|
||||
|
||||
sbfsFindGotName:
|
||||
; "." and ".." belong to the filesystem and are answered here rather than looked for.
|
||||
; Nothing on a disk is ever called either of them, so this takes nothing away.
|
||||
SETD.0 SbfsWanted
|
||||
LDA.0
|
||||
INIB 0x2E
|
||||
CCF
|
||||
SUB
|
||||
BNQ sbfsFindLook
|
||||
INCD.0
|
||||
LDA.0
|
||||
SETD.1 SbfsLeft
|
||||
STA.1 ; Only the low byte: a directory of 256 blocks is 2048 files.
|
||||
BRA sbfsFindName ; "." on its own is where the walk already is.
|
||||
INIB 0x2E
|
||||
CCF
|
||||
SUB
|
||||
BNQ sbfsFindLook
|
||||
INCD.0
|
||||
LDA.0
|
||||
BRA sbfsFindUp ; ".." on its own goes back the way it came.
|
||||
|
||||
sbfsFindBlock:
|
||||
CALL sbfsReadBlock
|
||||
BRQ sbfsFindLoaded
|
||||
RET ; The read failed.
|
||||
sbfsFindLoaded:
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
|
||||
; Eight entries to a block, thirty two bytes each.
|
||||
SETD.2 SbfsBuffer
|
||||
INIA 0d8
|
||||
SETD.1 SbfsCount
|
||||
STA.1
|
||||
|
||||
sbfsFindEntry:
|
||||
LDA.2
|
||||
INIB 0x01
|
||||
sbfsFindLook:
|
||||
; Only a directory can be looked inside. Where the walk is now was put there by the last
|
||||
; scan, so what kind of thing it is has already been read out of its entry.
|
||||
SETD.0 SbfsAt
|
||||
LDA.0
|
||||
INCD.0
|
||||
LDB.0
|
||||
OR
|
||||
BRQ sbfsFindScan ; At the root, which can always be looked inside.
|
||||
SETD.0 SbfsFoundFlags
|
||||
LDA.0
|
||||
INIB 0x02
|
||||
AND
|
||||
BRQ sbfsFindNext ; The in use bit is down, so this entry is free.
|
||||
CALL sbfsMatchEntry
|
||||
BRQ sbfsFindFound
|
||||
BRQ sbfsFindMissing ; Not a directory, so there is nothing inside it.
|
||||
|
||||
sbfsFindNext:
|
||||
DPUP.2 0d32
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsFindEntry
|
||||
sbfsFindScan:
|
||||
CALL sbfsScanFor
|
||||
BNQ sbfsFindMissing
|
||||
BRI sbfsFindName
|
||||
|
||||
sbfsFindNextBlock:
|
||||
SETD.0 SbfsBlock
|
||||
CALL sbfsStepWord
|
||||
SETD.1 SbfsLeft
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BRA sbfsFindMissing
|
||||
BRI sbfsFindBlock
|
||||
sbfsFindUp:
|
||||
; ".." from the root is the root. Every filesystem answers this way, and it is the only
|
||||
; answer that cannot walk off the top of the disk.
|
||||
SETD.0 SbfsAt
|
||||
LDA.0
|
||||
INCD.0
|
||||
LDB.0
|
||||
OR
|
||||
BRQ sbfsFindName
|
||||
|
||||
; Where the walk is, as an index rather than as a parent.
|
||||
SETD.0 SbfsAt
|
||||
SETD.1 SbfsTarget
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsTarget
|
||||
CALL sbfsBackWord
|
||||
|
||||
CALL sbfsAtIndex
|
||||
BNQ sbfsFindMissing ; The disk would not read.
|
||||
|
||||
SETD.0 SbfsUpParent
|
||||
SETD.1 SbfsAt
|
||||
CALL sbfsCopyWord
|
||||
|
||||
; What it landed on is a directory, because a parent always is. What describes it, on
|
||||
; the other hand, is the entry it came FROM - so that is marked as not to be believed,
|
||||
; and loaded properly if the path stops here.
|
||||
INIA 0x02
|
||||
SETD.0 SbfsFoundFlags
|
||||
STA.0
|
||||
RSTA
|
||||
SETD.0 SbfsFieldsValid
|
||||
STA.0
|
||||
BRI sbfsFindName
|
||||
|
||||
sbfsFindEnd:
|
||||
; Did the walk get anywhere at all? The root is not a file.
|
||||
SETD.0 SbfsAt
|
||||
LDA.0
|
||||
INCD.0
|
||||
LDB.0
|
||||
OR
|
||||
BRQ sbfsFindMissing
|
||||
|
||||
SETD.0 SbfsFieldsValid
|
||||
LDA.0
|
||||
BNA sbfsFindGood
|
||||
|
||||
; The path ended on a "..", so what describes the entry is one place out of date.
|
||||
SETD.0 SbfsAt
|
||||
SETD.1 SbfsTarget
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsTarget
|
||||
CALL sbfsBackWord
|
||||
CALL sbfsAtIndex
|
||||
BNQ sbfsFindMissing
|
||||
|
||||
sbfsFindGood:
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero: found.
|
||||
RET
|
||||
|
||||
sbfsFindMissing:
|
||||
; Nothing of that name. Q has to be something other than zero to say so.
|
||||
@@ -153,13 +260,295 @@ sbfsFindMissing:
|
||||
ADD
|
||||
RET
|
||||
|
||||
sbfsFindFound:
|
||||
; ---- Taking a path apart ----
|
||||
;
|
||||
; Copies the next name out of the path into SbfsWanted, padded with zeroes to twenty two
|
||||
; the way an entry holds one, and moves SbfsPathAt past it.
|
||||
;
|
||||
; SbfsPathState says what happened: zero for a name, one for the end of the path, two for
|
||||
; a name longer than a name can be. THE LAST IS REFUSED RATHER THAN CUT SHORT, because a
|
||||
; name cut to twenty two characters is a different name, and might well be some other
|
||||
; file's.
|
||||
;
|
||||
; Separators are skipped rather than counted, so "/Apps/", "Apps" and "//Apps" all walk
|
||||
; the same way and a trailing one simply ends the path.
|
||||
sbfsPathNext:
|
||||
RSTA
|
||||
SETD.0 SbfsPathState
|
||||
STA.0
|
||||
|
||||
SETD.0 SbfsPathAt
|
||||
LDD.1.0
|
||||
|
||||
sbfsPathSkip:
|
||||
LDA.1
|
||||
BRA sbfsPathEnd
|
||||
INIB 0x2F
|
||||
CCF
|
||||
SUB
|
||||
BNQ sbfsPathName
|
||||
INCD.1
|
||||
BRI sbfsPathSkip
|
||||
|
||||
sbfsPathEnd:
|
||||
INIA 0d1
|
||||
SETD.0 SbfsPathState
|
||||
STA.0
|
||||
BRI sbfsPathSave
|
||||
|
||||
sbfsPathName:
|
||||
SETD.2 SbfsWanted
|
||||
INIA 0d22
|
||||
SETD.0 SbfsPathLeft
|
||||
STA.0
|
||||
|
||||
sbfsPathCopy:
|
||||
LDA.1
|
||||
BRA sbfsPathPad ; The path ended, so the name ended with it.
|
||||
INIB 0x2F
|
||||
CCF
|
||||
SUB
|
||||
BRQ sbfsPathPad ; A separator ends the name.
|
||||
|
||||
SETD.0 SbfsPathLeft
|
||||
LDA.0
|
||||
BRA sbfsPathTooLong ; Twenty two already, and there is still more of it.
|
||||
DECA
|
||||
STA.0
|
||||
|
||||
LDA.1
|
||||
STA.2
|
||||
INCD.1
|
||||
INCD.2
|
||||
BRI sbfsPathCopy
|
||||
|
||||
sbfsPathPad:
|
||||
; Zeroes out to the twenty two, so that comparing the whole field compares the name.
|
||||
SETD.0 SbfsPathLeft
|
||||
LDA.0
|
||||
BRA sbfsPathSave
|
||||
sbfsPathPadLoop:
|
||||
RSTA
|
||||
STA.2
|
||||
INCD.2
|
||||
SETD.0 SbfsPathLeft
|
||||
LDA.0
|
||||
DECA
|
||||
STA.0
|
||||
BNA sbfsPathPadLoop
|
||||
|
||||
sbfsPathSave:
|
||||
SETD.0 SbfsPathAt
|
||||
STD.1.0
|
||||
RET
|
||||
|
||||
sbfsPathTooLong:
|
||||
INIA 0d2
|
||||
SETD.0 SbfsPathState
|
||||
STA.0
|
||||
RET
|
||||
|
||||
; ---- Looking in one directory ----
|
||||
;
|
||||
; Looks for the name in SbfsWanted among the entries whose parent is SbfsAt. Q is zero if
|
||||
; it is there, and then the walk moves onto it and everything that describes it is read
|
||||
; out of its entry. DP3 is left on that entry.
|
||||
sbfsScanFor:
|
||||
SETD.0 SbfsDirStart
|
||||
SETD.1 SbfsBlock
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsDirBlocks
|
||||
INCD.0
|
||||
LDA.0
|
||||
SETD.1 SbfsLeft
|
||||
STA.1 ; Only the low byte: a directory of 256 blocks is 2048 entries.
|
||||
|
||||
; Entries are COUNTED rather than worked out from where they sit. An index becomes a
|
||||
; block and an offset by dividing by eight, and this machine has no divide; counting
|
||||
; costs one step per entry through a walk that was happening anyway.
|
||||
SETD.0 SbfsScanIndex
|
||||
RSTA
|
||||
STA.0
|
||||
INCD.0
|
||||
STA.0
|
||||
|
||||
sbfsScanBlock:
|
||||
CALL sbfsReadBlock
|
||||
BRQ sbfsScanLoaded
|
||||
RET ; The read failed.
|
||||
sbfsScanLoaded:
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
|
||||
; Eight entries to a block, thirty two bytes each.
|
||||
SETD.2 SbfsBuffer
|
||||
INIA 0d8
|
||||
SETD.1 SbfsCount
|
||||
STA.1
|
||||
|
||||
sbfsScanEntry:
|
||||
LDA.2
|
||||
INIB 0x01
|
||||
AND
|
||||
BRQ sbfsScanNext ; The in use bit is down, so this entry is free.
|
||||
CALL sbfsMatchParent
|
||||
BNQ sbfsScanNext ; It lives somewhere else.
|
||||
CALL sbfsMatchEntry
|
||||
BRQ sbfsScanFound
|
||||
|
||||
sbfsScanNext:
|
||||
SETD.0 SbfsScanIndex
|
||||
CALL sbfsStepWord
|
||||
DPUP.2 0d32
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsScanEntry
|
||||
|
||||
sbfsScanNextBlock:
|
||||
SETD.0 SbfsBlock
|
||||
CALL sbfsStepWord
|
||||
SETD.1 SbfsLeft
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BRA sbfsScanMissing
|
||||
BRI sbfsScanBlock
|
||||
|
||||
sbfsScanMissing:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
sbfsScanFound:
|
||||
; DP2 is on the entry. Keep it in DP3 while the pieces are taken out of it: DP3 is the
|
||||
; pointer a CALL does not put back, so it is the only one that survives a subroutine.
|
||||
; Copying it into DP0 has to be done here rather than in a routine of its own, for the
|
||||
; same reason: a routine that set DP0 would have the assignment undone by its own RET.
|
||||
PSHD.2
|
||||
POPD.3
|
||||
CALL sbfsTakeEntry
|
||||
|
||||
; The walk moves onto what it found, written the way a parent is written.
|
||||
SETD.0 SbfsScanIndex
|
||||
SETD.1 SbfsAt
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsAt
|
||||
CALL sbfsStepWord
|
||||
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero: found.
|
||||
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
|
||||
; zero, so this always agrees - which is exactly how a flat disk reads correctly here
|
||||
; without anything having been done to it.
|
||||
sbfsMatchParent:
|
||||
PSHD.2
|
||||
POPD.0
|
||||
DPUP.0 0d28
|
||||
SETD.2 SbfsAt
|
||||
CALL sbfsSameByte
|
||||
BNQ sbfsMatchParentDone
|
||||
INCD.0
|
||||
INCD.2
|
||||
CALL sbfsSameByte
|
||||
sbfsMatchParentDone:
|
||||
RET
|
||||
|
||||
; ---- Going back up ----
|
||||
;
|
||||
; Walks the directory to the entry SbfsTarget names and takes it apart, SbfsUpParent
|
||||
; included. Q is zero if it got there.
|
||||
;
|
||||
; WALKING TO AN INDEX RATHER THAN WORKING OUT WHERE IT SITS. An index becomes a block and
|
||||
; an offset by dividing by eight, and this machine has no divide. The walk costs a few
|
||||
; block reads and no arithmetic at all, and only ".." ever needs it.
|
||||
sbfsAtIndex:
|
||||
SETD.0 SbfsDirStart
|
||||
SETD.1 SbfsBlock
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsDirBlocks
|
||||
INCD.0
|
||||
LDA.0
|
||||
SETD.1 SbfsLeft
|
||||
STA.1
|
||||
|
||||
SETD.0 SbfsScanIndex
|
||||
RSTA
|
||||
STA.0
|
||||
INCD.0
|
||||
STA.0
|
||||
|
||||
sbfsAtBlock:
|
||||
CALL sbfsReadBlock
|
||||
BRQ sbfsAtLoaded
|
||||
RET ; The read failed.
|
||||
sbfsAtLoaded:
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
SETD.2 SbfsBuffer
|
||||
INIA 0d8
|
||||
SETD.1 SbfsCount
|
||||
STA.1
|
||||
|
||||
sbfsAtEntry:
|
||||
; Where the entry is has to be put down for the length of the comparison, because
|
||||
; comparing two numbers wants DP2 for one of them.
|
||||
PSHD.2
|
||||
SETD.0 SbfsScanIndex
|
||||
SETD.2 SbfsTarget
|
||||
CALL sbfsCompareWord
|
||||
POPD.2
|
||||
BRQ sbfsAtFound
|
||||
|
||||
SETD.0 SbfsScanIndex
|
||||
CALL sbfsStepWord
|
||||
DPUP.2 0d32
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsAtEntry
|
||||
|
||||
SETD.0 SbfsBlock
|
||||
CALL sbfsStepWord
|
||||
SETD.1 SbfsLeft
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BRA sbfsAtMissing
|
||||
BRI sbfsAtBlock
|
||||
|
||||
sbfsAtMissing:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
sbfsAtFound:
|
||||
PSHD.2
|
||||
POPD.3
|
||||
CALL sbfsTakeEntry
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; DP3 is on an entry. Takes it apart into everything that describes what was found.
|
||||
;
|
||||
; Written once and called from both the scan and the walk to an index, because two copies
|
||||
; of "which byte means what" is the kind of thing that stays right until one of them is
|
||||
; changed.
|
||||
sbfsTakeEntry:
|
||||
LDA.3
|
||||
SETD.0 SbfsFoundFlags
|
||||
STA.0
|
||||
|
||||
PSHD.3
|
||||
POPD.0
|
||||
@@ -180,10 +569,29 @@ sbfsFindFound:
|
||||
SETD.1 SbfsFileTail
|
||||
STA.1
|
||||
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero: found.
|
||||
PSHD.3
|
||||
POPD.0
|
||||
DPUP.0 0d28
|
||||
SETD.1 SbfsUpParent
|
||||
CALL sbfsCopyWord
|
||||
|
||||
INIA 0x01
|
||||
SETD.0 SbfsFieldsValid
|
||||
STA.0
|
||||
RET
|
||||
|
||||
; Takes one from the two byte number at DP0, the other way round from sbfsStepWord.
|
||||
sbfsBackWord:
|
||||
INCD.0
|
||||
LDA.0
|
||||
DECA
|
||||
STA.0
|
||||
BNC sbfsBackDone ; It did not borrow, so the high byte is untouched.
|
||||
DECD.0
|
||||
LDA.0
|
||||
DECA
|
||||
STA.0
|
||||
sbfsBackDone:
|
||||
RET
|
||||
|
||||
; ---- Walking the directory ----
|
||||
@@ -306,6 +714,15 @@ sbfsWalkEnd:
|
||||
; Takes the pieces out of the entry the walk stopped on. Everything lands in memory,
|
||||
; because that is the only place a subroutine can leave anything.
|
||||
sbfsWalkTake:
|
||||
; What kind of thing it is, so that a listing can say so. A directory has no blocks, and
|
||||
; without this it would be handed out as a file of no bytes - which is a different thing
|
||||
; that happens to look the same from here.
|
||||
SETD.1 SbfsWalkAt
|
||||
LDD.0.1
|
||||
LDA.0
|
||||
SETD.1 SbfsFoundFlags
|
||||
STA.1
|
||||
|
||||
SETD.1 SbfsWalkAt
|
||||
LDD.0.1
|
||||
INCD.0
|
||||
@@ -606,6 +1023,26 @@ sbfsAllocEntry:
|
||||
INIB 0x01
|
||||
AND
|
||||
BRQ sbfsAllocNext ; A free entry holds nothing, so it is in nobody's way.
|
||||
|
||||
; NOR DOES A DIRECTORY, and this says so rather than leaving it to be true by accident.
|
||||
;
|
||||
; It would be true by accident. A directory has no blocks AND no start, so the bounds
|
||||
; below come out as nought to nought, and the first half of the overlap test - does the
|
||||
; candidate begin before this entry ends - is false for every candidate there is, since
|
||||
; they all begin past the directory. Taking these four instructions out changes nothing
|
||||
; that any test can see, and that was checked rather than assumed.
|
||||
;
|
||||
; They stay because what keeps them right is a value written somewhere else. The day
|
||||
; something gives a directory entry a start - a note about where its children begin, a
|
||||
; use for a field that is spare today - the overlap test starts answering yes to runs
|
||||
; that straddle it, and blocks stop being usable for no reason anybody could see from
|
||||
; here. Four instructions to say "a directory is in nobody's way" where it is meant is
|
||||
; cheaper than finding that out.
|
||||
LDA.2
|
||||
INIB 0x02
|
||||
AND
|
||||
BNQ sbfsAllocNext
|
||||
|
||||
CALL sbfsEntryBounds
|
||||
|
||||
; The comparisons need DP2 for their own purposes, so where this entry is goes on the
|
||||
@@ -798,6 +1235,20 @@ sbfsCreateFill:
|
||||
LDA.0
|
||||
STA.1
|
||||
|
||||
; THE ROOT, SAID OUT LOUD. A new file goes in the root because nothing here can put one
|
||||
; anywhere else yet, and the two bytes that say so are written rather than assumed to be
|
||||
; zero already. They would be - a free entry has been wiped by delete or has never been
|
||||
; used - but that is a fact about two other routines, and a fact kept somewhere else is
|
||||
; one that can be changed without this noticing. A file appearing inside a directory it
|
||||
; was never put in is not a failure anybody would think to look for.
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d28
|
||||
RSTA
|
||||
STA.1
|
||||
INCD.1
|
||||
STA.1
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d06
|
||||
@@ -1059,6 +1510,17 @@ sbfsDelete:
|
||||
CALL sbfsFind
|
||||
BNQ sbfsDeleteFailed
|
||||
|
||||
; A DIRECTORY IS NOT DELETED HERE, AND THAT REFUSAL IS NOT POLITENESS. A parent is an
|
||||
; entry index, and a freed index is handed straight to the next thing put on the disk -
|
||||
; so the children of a directory wiped from under them would reappear inside whatever
|
||||
; took its place. Nothing points downward, so there is no way to find them afterwards
|
||||
; and no way to notice. Emptying it first is the only safe order there is.
|
||||
SETD.0 SbfsFoundFlags
|
||||
LDA.0
|
||||
INIB 0x02
|
||||
AND
|
||||
BNQ sbfsDeleteFailed
|
||||
|
||||
; How much room it was taking, worked out before the entry that says so is thrown away.
|
||||
CALL sbfsFileExtent
|
||||
|
||||
@@ -1110,6 +1572,10 @@ sbfsDeleteFailed:
|
||||
;
|
||||
; DP0 is the name a file has, DP1 is the name it should have. Q is zero if it was renamed.
|
||||
;
|
||||
; A file keeps the directory it is in: only the twenty two bytes of the name change, and
|
||||
; the parent is not among them. Moving something between directories is a different job
|
||||
; and is not this one.
|
||||
;
|
||||
; Only the twenty two bytes of the name change, so no data moves and no block is touched
|
||||
; but the one holding the entry. THAT IS WHAT MAKES A SAFE SAVE POSSIBLE. Writing a file
|
||||
; that has grown means putting it somewhere else, and the obvious order - throw the old one
|
||||
@@ -1138,6 +1604,15 @@ sbfsRename:
|
||||
CALL sbfsFind
|
||||
BNQ sbfsRenameFailed
|
||||
|
||||
; Not a directory. Renaming one would be safe enough, but the shell has no way to make
|
||||
; one yet, so allowing it here would only be a way to reach something that cannot be
|
||||
; got at any other way.
|
||||
SETD.0 SbfsFoundFlags
|
||||
LDA.0
|
||||
INIB 0x02
|
||||
AND
|
||||
BNQ sbfsRenameFailed
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d06 ; Past the flags, the start, the block count and the tail.
|
||||
@@ -1201,6 +1676,20 @@ sbfsSaveFile:
|
||||
SETD.1 SbfsSaveTail
|
||||
STA.1
|
||||
|
||||
; Refused up front if the name belongs to a directory. Left to itself the delete below
|
||||
; would refuse it, the rename at the end would refuse it too, and the save would fail
|
||||
; having already written a temporary that nothing would ever come back for.
|
||||
SETD.2 SbfsSaveName
|
||||
LDD.0.2
|
||||
CALL sbfsFind
|
||||
BNQ sbfsSaveNotThere
|
||||
SETD.0 SbfsFoundFlags
|
||||
LDA.0
|
||||
INIB 0x02
|
||||
AND
|
||||
BNQ sbfsSaveFailed
|
||||
sbfsSaveNotThere:
|
||||
|
||||
; A temporary left behind by a save that did not finish would be in the way. Whether
|
||||
; there was one is not worth asking about, since either answer leads here.
|
||||
SETD.0 SbfsTempName
|
||||
@@ -1281,6 +1770,33 @@ SbfsMatchLeft:
|
||||
0x00
|
||||
SbfsLeft:
|
||||
0x00
|
||||
; ---- What walking a path keeps ----
|
||||
;
|
||||
; SbfsAt is where the walk has got to, written the way a parent is written: an entry index
|
||||
; plus one, so that zero is the root and a version one disk's zeroes already say it.
|
||||
SbfsPathAt:
|
||||
0x00 0x00
|
||||
SbfsPathState:
|
||||
0x00
|
||||
SbfsPathLeft:
|
||||
0x00
|
||||
SbfsAt:
|
||||
0x00 0x00
|
||||
SbfsScanIndex:
|
||||
0x00 0x00
|
||||
SbfsTarget:
|
||||
0x00 0x00
|
||||
SbfsUpParent:
|
||||
0x00 0x00
|
||||
SbfsFoundFlags:
|
||||
0x00
|
||||
|
||||
; Whether what is in SbfsFileStart and the rest really describes where the walk is now.
|
||||
; Going up leaves them describing the entry it came from, and only the end of a path is
|
||||
; close enough to care.
|
||||
SbfsFieldsValid:
|
||||
0x00
|
||||
|
||||
SbfsIndex:
|
||||
0x00 0x00
|
||||
|
||||
|
||||
Reference in New Issue
Block a user