D4: the machine makes directories too
mkdir and rmdir are the machine's own now, and a file goes where its path says rather than always in the root. A disk can be organised without the host tool touching it. Everything below the surface works in terms of a directory and a name rather than a path. sbfsWalkParent splits the last name off, walks the rest, and hands back the two - and the separator stays on the end of the head, which is what makes one rule cover every kind of path: "/x" leaves "/", which is the root; "x" leaves nothing, which is where the machine already is; and "A/x" leaves "A/", which is neither and needs no special case to say so. Saving works in those two as well, and had to. The careful order a save uses - make a temporary, write it, delete the original, rename the temporary - only works if the temporary is made in the SAME directory as the file, because the rename at the end changes a name and does not move anything. Renaming to a path naming a different directory is refused for that reason, rather than quietly being a lie the disk goes along with. Three things this cost, all found by running it: mkdir Apps/Deep made /Apps/Apps. The leaf was worked out into SbfsWanted and then the head was walked - and walking goes through sbfsPathNext, which puts every name it meets into SbfsWanted on the way past. The head's last name landed exactly where the leaf was. It has somewhere of its own now. rmdir took a directory with something still in it, which is the one failure the whole design is arranged to prevent. Looking for children clobbered DP2 and rebuilt it from the buffer and the entry count with the subtraction the wrong way round, so the pointer walked off the end of the block and found nothing. The comparison goes through a CALL now, like the two beside it, and DP2 comes back on the entry because a RET puts it there. SplitDisk's "in use but not reachable from the root" line is what caught it. Refusing a name longer than twenty two used to read the twenty third character of a shorter one, which is somebody else's string. It is measured now. Tests/agree.sh is new and is the gate this rung was for: the same disk built twice, once with SplitDisk and once with CosmOS, compared byte for byte. The two share no code and only a written specification, and every field one writes and the other only reads is checked there and nowhere else - which entry a thing lands in, which block, what a directory's unused fields hold, the version, the free count. It caught a wrong parent immediately when that was broken on purpose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
36ce9f6ccf
commit
da91a36d92
@@ -142,6 +142,16 @@ promptSay:
|
||||
CALL textSame
|
||||
BRQ doCd
|
||||
|
||||
SETD.0 CommandLine
|
||||
SETD.1 MkdirName
|
||||
CALL textSame
|
||||
BRQ doMkdir
|
||||
|
||||
SETD.0 CommandLine
|
||||
SETD.1 RmdirName
|
||||
CALL textSame
|
||||
BRQ doRmdir
|
||||
|
||||
SETD.0 CommandLine
|
||||
SETD.1 DeleteName
|
||||
CALL textSame
|
||||
@@ -1006,6 +1016,61 @@ cdNotDirectory:
|
||||
SETD.0 NotDirectory
|
||||
BRI fileComplain
|
||||
|
||||
; ---- mkdir and rmdir ----
|
||||
;
|
||||
; Two commands rather than one that works out what you meant, and delete stays for files
|
||||
; only. Each of the four says exactly what it will take, so none of them can be the one
|
||||
; that took away more than was asked for.
|
||||
doMkdir:
|
||||
SETD.0 DiskReady
|
||||
LDA.0
|
||||
BRA fileNoDisk
|
||||
|
||||
SETD.1 TextRest
|
||||
LDD.0.1
|
||||
LDA.0
|
||||
BRA mkdirWhat
|
||||
|
||||
CALL sbfsMakeDir
|
||||
BNQ mkdirFailed
|
||||
SETD.0 MadeText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
|
||||
mkdirWhat:
|
||||
SETD.0 MkdirWhat
|
||||
BRI fileComplain
|
||||
mkdirFailed:
|
||||
SETD.0 MkdirNo
|
||||
BRI fileComplain
|
||||
|
||||
doRmdir:
|
||||
SETD.0 DiskReady
|
||||
LDA.0
|
||||
BRA fileNoDisk
|
||||
|
||||
SETD.1 TextRest
|
||||
LDD.0.1
|
||||
LDA.0
|
||||
BRA rmdirWhat
|
||||
|
||||
; What a name means on the disk is about to change, so the remembered file goes.
|
||||
CALL fileForget
|
||||
CALL sbfsRemoveDir
|
||||
BNQ rmdirFailed
|
||||
SETD.0 RemovedText
|
||||
CALL printString
|
||||
CALL newLine
|
||||
BRI prompt
|
||||
|
||||
rmdirWhat:
|
||||
SETD.0 RmdirWhat
|
||||
BRI fileComplain
|
||||
rmdirFailed:
|
||||
SETD.0 RmdirNo
|
||||
BRI fileComplain
|
||||
|
||||
; ---- Writing out where the machine is ----
|
||||
;
|
||||
; Builds the working directory's path into CwdText and leaves CwdAt pointing at where it
|
||||
@@ -3122,6 +3187,18 @@ DirectoryText:
|
||||
"<dir>"
|
||||
NotDirectory:
|
||||
"that is not a directory"
|
||||
MadeText:
|
||||
"made"
|
||||
RemovedText:
|
||||
"removed"
|
||||
MkdirWhat:
|
||||
"mkdir what?"
|
||||
RmdirWhat:
|
||||
"rmdir what?"
|
||||
MkdirNo:
|
||||
"cannot make that: check the path, the name, and whether it is taken"
|
||||
RmdirNo:
|
||||
"cannot remove that: it must be a directory, and empty"
|
||||
Separator:
|
||||
"/"
|
||||
|
||||
@@ -3151,7 +3228,9 @@ load <file> read a program off the disk
|
||||
run [words] start what was loaded, and tell it those words
|
||||
<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"
|
||||
"cd [path] go to a directory, or to the root with nothing after it
|
||||
mkdir <path> make a directory
|
||||
rmdir <path> remove an empty one"
|
||||
HelpMoreText:
|
||||
"delete <file> take it off the disk
|
||||
rename <file> <to> call it something else
|
||||
@@ -3285,6 +3364,10 @@ RenameName:
|
||||
"rename"
|
||||
CdName:
|
||||
"cd"
|
||||
MkdirName:
|
||||
"mkdir"
|
||||
RmdirName:
|
||||
"rmdir"
|
||||
HelpName:
|
||||
"help"
|
||||
ExitName:
|
||||
|
||||
+597
-34
@@ -320,6 +320,173 @@ sbfsFindMissing:
|
||||
ADD
|
||||
RET
|
||||
|
||||
; ---- Splitting a path into where and what ----
|
||||
;
|
||||
; DP0 points at a path. Everything but the last name is walked, so what comes back is the
|
||||
; directory a thing should be MADE in and the name to make it under: SbfsAt is the
|
||||
; directory and SbfsWanted holds the name, padded to twenty two the way an entry holds one.
|
||||
; Q is zero if that worked.
|
||||
;
|
||||
; The head is copied rather than the path being cut in place. The path belongs to whoever
|
||||
; called, and a routine that writes a zero byte into somebody else's string is one that has
|
||||
; to put it back on every way out, including the ways out that failed.
|
||||
;
|
||||
; THE SEPARATOR STAYS ON THE END OF THE HEAD, and that is what makes one rule cover both
|
||||
; kinds of path. "/x" leaves a head of "/", which is the root; "x" leaves an empty head,
|
||||
; which is where the machine already is; and "A/x" leaves "A/", which is neither of those
|
||||
; and needs no special case to say so.
|
||||
sbfsWalkParent:
|
||||
RSTA
|
||||
SETD.1 SbfsHeadLen
|
||||
STA.1
|
||||
SETD.1 SbfsSpanAt
|
||||
STA.1
|
||||
|
||||
PSHD.0
|
||||
POPD.2 ; DP2 walks the path, DP0 stays on the front of it.
|
||||
|
||||
sbfsSplitScan:
|
||||
LDA.2
|
||||
BRA sbfsSplitEnd
|
||||
INIB 0x2F
|
||||
CCF
|
||||
SUB
|
||||
BNQ sbfsSplitStep
|
||||
|
||||
; A separator. The head runs to just past it, so the last one to be seen wins.
|
||||
SETD.1 SbfsSpanAt
|
||||
LDA.1
|
||||
INCA
|
||||
SETD.1 SbfsHeadLen
|
||||
STA.1
|
||||
|
||||
sbfsSplitStep:
|
||||
SETD.1 SbfsSpanAt
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
BRA sbfsSplitTooLong ; Round past two hundred and fifty five: not a path.
|
||||
INCD.2
|
||||
BRI sbfsSplitScan
|
||||
|
||||
sbfsSplitEnd:
|
||||
; The head, up to and including the separator that ended it.
|
||||
PSHD.0
|
||||
POPD.2
|
||||
SETD.1 SbfsHead
|
||||
SETD.0 SbfsHeadLen
|
||||
LDA.0
|
||||
INIB 0d95
|
||||
CCF
|
||||
SUB
|
||||
BNC sbfsSplitTooLong ; Longer than there is room to copy it into.
|
||||
|
||||
SETD.0 SbfsHeadLen
|
||||
LDA.0
|
||||
SETD.0 SbfsSpanAt
|
||||
STA.0 ; Counting it back down again.
|
||||
|
||||
sbfsHeadCopy:
|
||||
SETD.0 SbfsSpanAt
|
||||
LDA.0
|
||||
BRA sbfsHeadDone
|
||||
DECA
|
||||
STA.0
|
||||
LDA.2
|
||||
STA.1
|
||||
INCD.2
|
||||
INCD.1
|
||||
BRI sbfsHeadCopy
|
||||
|
||||
sbfsHeadDone:
|
||||
RSTA
|
||||
STA.1 ; The zero that ends the head.
|
||||
|
||||
; And the last name, which is whatever DP2 is now on. Measured before it is copied,
|
||||
; because twenty two is all an entry holds and a longer name is refused rather than cut
|
||||
; down - a name cut to twenty two characters is a different name and might well be
|
||||
; taken. Measuring is the only way to know: looking at the twenty third character of a
|
||||
; shorter name reads past the end of somebody else's string.
|
||||
PSHD.2
|
||||
POPD.0
|
||||
LDA.0
|
||||
BRA sbfsSplitNoName ; The path ended in a separator, so it names nothing.
|
||||
|
||||
RSTA
|
||||
SETD.1 SbfsSpanAt
|
||||
STA.1
|
||||
sbfsLeafMeasure:
|
||||
LDA.2
|
||||
BRA sbfsLeafMeasured
|
||||
SETD.1 SbfsSpanAt
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
INIB 0d23
|
||||
CCF
|
||||
SUB
|
||||
BRQ sbfsSplitTooLong
|
||||
INCD.2
|
||||
BRI sbfsLeafMeasure
|
||||
sbfsLeafMeasured:
|
||||
|
||||
; KEPT SOMEWHERE OF ITS OWN, and this is not tidiness. Walking the head goes through
|
||||
; sbfsPathNext, which puts every name it meets into SbfsWanted on its way past - so the
|
||||
; last name of the head would land exactly where the leaf was and the thing would be
|
||||
; created under the name of the directory it was going into. "mkdir Apps/Deep" made
|
||||
; /Apps/Apps.
|
||||
SETD.1 SbfsLeaf
|
||||
CALL sbfsKeepName
|
||||
|
||||
; Now where it goes. The head is walked exactly the way any other path is.
|
||||
SETD.0 SbfsHead
|
||||
CALL sbfsWalk
|
||||
BNQ sbfsSplitNoName
|
||||
|
||||
; And the leaf comes back out, now that nothing else is going to write there.
|
||||
SETD.0 SbfsLeaf
|
||||
SETD.1 SbfsWanted
|
||||
INIA 0d22
|
||||
SETD.2 SbfsCount
|
||||
STA.2
|
||||
sbfsLeafBack:
|
||||
LDA.0
|
||||
STA.1
|
||||
INCD.0
|
||||
INCD.1
|
||||
LDA.2
|
||||
DECA
|
||||
STA.2
|
||||
BNA sbfsLeafBack
|
||||
|
||||
; And it has to be somewhere things can be put. The root always is.
|
||||
SETD.0 SbfsAt
|
||||
LDA.0
|
||||
INCD.0
|
||||
LDB.0
|
||||
OR
|
||||
BRQ sbfsSplitGood
|
||||
SETD.0 SbfsFoundFlags
|
||||
LDA.0
|
||||
INIB 0x02
|
||||
AND
|
||||
BRQ sbfsSplitNoName
|
||||
|
||||
sbfsSplitGood:
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
sbfsSplitTooLong:
|
||||
sbfsSplitNoName:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; ---- Taking a path apart ----
|
||||
;
|
||||
; Copies the next name out of the path into SbfsWanted, padded with zeroes to twenty two
|
||||
@@ -502,6 +669,21 @@ sbfsScanFound:
|
||||
ADD ; Q is zero: found.
|
||||
RET
|
||||
|
||||
; DP2 is on an entry. Q is zero if it lives in the directory SbfsHoldAt names. The third
|
||||
; of these, and the reason all three are subroutines: a RET puts DP2 back on the entry.
|
||||
sbfsMatchHold:
|
||||
PSHD.2
|
||||
POPD.0
|
||||
DPUP.0 0d28
|
||||
SETD.2 SbfsHoldAt
|
||||
CALL sbfsSameByte
|
||||
BNQ sbfsMatchHoldDone
|
||||
INCD.0
|
||||
INCD.2
|
||||
CALL sbfsSameByte
|
||||
sbfsMatchHoldDone:
|
||||
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:
|
||||
@@ -910,6 +1092,24 @@ sbfsMatchYes:
|
||||
ADD
|
||||
RET
|
||||
|
||||
; Twenty two bytes from DP0 to DP1, which is one name exactly as an entry holds it. Not
|
||||
; sbfsCopyWord's job and not sbfsKeepName's either: this one is for a name that is already
|
||||
; padded and is only being put somewhere safe.
|
||||
sbfsCopyName:
|
||||
INIA 0d22
|
||||
SETD.2 SbfsCount
|
||||
STA.2
|
||||
sbfsCopyNameLoop:
|
||||
LDA.0
|
||||
STA.1
|
||||
INCD.0
|
||||
INCD.1
|
||||
LDA.2
|
||||
DECA
|
||||
STA.2
|
||||
BNA sbfsCopyNameLoop
|
||||
RET
|
||||
|
||||
; Copies the name at DP0 into DP1, twenty two bytes of it, padding with zeroes the way an
|
||||
; entry is padded so that the two can be compared as they stand.
|
||||
sbfsKeepName:
|
||||
@@ -1261,9 +1461,13 @@ sbfsExtentDone:
|
||||
; which has to be settled before it is made because nothing here can grow one afterwards.
|
||||
; Q is zero if it was made, and then SbfsFileStart says where its blocks are.
|
||||
sbfsCreate:
|
||||
SETD.1 SbfsWanted
|
||||
CALL sbfsKeepName
|
||||
; Where it goes and what it is called come out of the path together. Everything below
|
||||
; works from SbfsAt and SbfsWanted, so anything that already knows those two can start
|
||||
; at sbfsCreateAt and skip the walking.
|
||||
CALL sbfsWalkParent
|
||||
BNQ sbfsCreateFailed
|
||||
|
||||
sbfsCreateAt:
|
||||
CALL sbfsFileExtent
|
||||
CALL sbfsAllocate
|
||||
BNQ sbfsCreateFailed
|
||||
@@ -1341,19 +1545,16 @@ 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.
|
||||
; WHICH DIRECTORY IT IS IN, written rather than left to be zero by luck. A free entry
|
||||
; has been wiped by delete or has never been used, so those two bytes would say the root
|
||||
; on their own - 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 turning up 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
|
||||
SETD.0 SbfsAt
|
||||
CALL sbfsCopyWord
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
@@ -1597,6 +1798,272 @@ sbfsSameByte:
|
||||
XOR
|
||||
RET
|
||||
|
||||
; ---- Making a directory ----
|
||||
;
|
||||
; DP0 names one. Q is zero if it was made.
|
||||
;
|
||||
; A directory costs ONE ENTRY AND NO BLOCKS AT ALL: its start, its block count and its tail
|
||||
; all stay zero. That is what keeps the flat array of entries the whole allocation map,
|
||||
; which is the thing this filesystem is built on - with files laid down contiguously, every
|
||||
; block is inside some entry's range or it is not, and an entry with no range is in
|
||||
; nobody's way.
|
||||
sbfsMakeDir:
|
||||
CALL sbfsWalkParent
|
||||
BNQ sbfsMakeFailed
|
||||
|
||||
; Nothing of that name in there already. Two entries with one name in one directory is a
|
||||
; directory that cannot be searched sensibly: a search answers with whichever it meets
|
||||
; first, and the other becomes unreachable without ever having been deleted.
|
||||
CALL sbfsScanFor
|
||||
BRQ sbfsMakeFailed
|
||||
; A scan that found nothing leaves the walk where it was, so SbfsAt still says where
|
||||
; this is going. Walking the head again to be sure would put the head's last name back
|
||||
; into SbfsWanted and undo the leaf.
|
||||
|
||||
; A free entry, found the same way creating a file finds one.
|
||||
SETD.0 SbfsDirStart
|
||||
SETD.1 SbfsBlock
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsDirBlocks
|
||||
INCD.0
|
||||
LDA.0
|
||||
SETD.1 SbfsLeft
|
||||
STA.1
|
||||
|
||||
sbfsMakeBlock:
|
||||
CALL sbfsReadBlock
|
||||
BNQ sbfsMakeFailed
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
SETD.2 SbfsBuffer
|
||||
INIA 0d8
|
||||
SETD.1 SbfsCount
|
||||
STA.1
|
||||
|
||||
sbfsMakeEntry:
|
||||
LDA.2
|
||||
INIB 0x01
|
||||
AND
|
||||
BRQ sbfsMakeFill ; This one is free.
|
||||
DPUP.2 0d32
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsMakeEntry
|
||||
|
||||
SETD.0 SbfsBlock
|
||||
CALL sbfsStepWord
|
||||
SETD.1 SbfsLeft
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BRA sbfsMakeFailed ; The directory is full.
|
||||
BRI sbfsMakeBlock
|
||||
|
||||
sbfsMakeFill:
|
||||
PSHD.2
|
||||
POPD.3
|
||||
INIA 0x03
|
||||
STA.3 ; In use, and a directory.
|
||||
|
||||
; No blocks, no start and no tail. Written out rather than left alone, because a free
|
||||
; entry is not the only thing that lands here.
|
||||
PSHD.3
|
||||
POPD.1
|
||||
INCD.1
|
||||
INIB 0d5
|
||||
sbfsMakeZero:
|
||||
RSTA
|
||||
STA.1
|
||||
INCD.1
|
||||
DECB
|
||||
BNB sbfsMakeZero
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d28
|
||||
SETD.0 SbfsAt
|
||||
CALL sbfsCopyWord
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d06
|
||||
SETD.0 SbfsWanted
|
||||
INIA 0d22
|
||||
SETD.2 SbfsCount
|
||||
STA.2
|
||||
sbfsMakeName:
|
||||
LDA.0
|
||||
STA.1
|
||||
INCD.0
|
||||
INCD.1
|
||||
LDA.2
|
||||
DECA
|
||||
STA.2
|
||||
BNA sbfsMakeName
|
||||
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferIn
|
||||
CALL sbfsWriteBlock
|
||||
BNQ sbfsMakeFailed
|
||||
|
||||
CALL sbfsRaiseVersion
|
||||
RET
|
||||
|
||||
sbfsMakeFailed:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; The disk now has a directory on it, so it is a version two disk and has to say so.
|
||||
;
|
||||
; THIS IS THE ONLY THING THAT RAISES THE NUMBER, because it is the only thing that makes
|
||||
; the difference between the two versions real. A disk stays readable by anything that has
|
||||
; never heard of a directory right up until it actually has one.
|
||||
sbfsRaiseVersion:
|
||||
RSTA
|
||||
SETD.0 SbfsBlock
|
||||
STA.0
|
||||
INCD.0
|
||||
STA.0
|
||||
CALL sbfsReadBlock
|
||||
BNQ sbfsRaiseDone
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
SETD.0 SbfsBuffer
|
||||
DPUP.0 0d04
|
||||
INIA 0d2
|
||||
STA.0
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferIn
|
||||
CALL sbfsWriteBlock
|
||||
sbfsRaiseDone:
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; ---- Removing a directory ----
|
||||
;
|
||||
; DP0 names one. Q is zero if it went.
|
||||
;
|
||||
; ANYTHING STILL INSIDE IT IS A REFUSAL, and that 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 removed from under them would turn up inside whatever took its
|
||||
; place. Nothing points downward, so there would be no way to find them afterwards and no
|
||||
; way to notice. Emptying it first is the only safe order there is.
|
||||
sbfsRemoveDir:
|
||||
CALL sbfsFind
|
||||
BNQ sbfsRemoveFailed
|
||||
|
||||
SETD.0 SbfsFoundFlags
|
||||
LDA.0
|
||||
INIB 0x02
|
||||
AND
|
||||
BRQ sbfsRemoveFailed ; A file. Deleting is for those.
|
||||
|
||||
; Where it is, kept while the disk is asked whether anything lives in it - the asking
|
||||
; reads other blocks and leaves the walk somewhere else entirely.
|
||||
SETD.0 SbfsAt
|
||||
SETD.1 SbfsHoldAt
|
||||
CALL sbfsCopyWord
|
||||
|
||||
CALL sbfsHasChildren
|
||||
BRQ sbfsRemoveFailed
|
||||
|
||||
; Found again, because looking for children read over the block the entry was in.
|
||||
SETD.0 SbfsHoldAt
|
||||
SETD.1 SbfsTarget
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsTarget
|
||||
CALL sbfsBackWord
|
||||
CALL sbfsAtIndex
|
||||
BNQ sbfsRemoveFailed
|
||||
|
||||
CALL sbfsWipeFound
|
||||
RET
|
||||
|
||||
sbfsRemoveFailed:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; Q is zero if anything at all says its parent is SbfsHoldAt. Nothing points downward, so
|
||||
; the only way to know what is in a directory is to ask everything whether it is in it.
|
||||
sbfsHasChildren:
|
||||
SETD.0 SbfsDirStart
|
||||
SETD.1 SbfsBlock
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsDirBlocks
|
||||
INCD.0
|
||||
LDA.0
|
||||
SETD.1 SbfsLeft
|
||||
STA.1
|
||||
|
||||
sbfsChildBlock:
|
||||
CALL sbfsReadBlock
|
||||
BRQ sbfsChildLoaded
|
||||
RET ; The read failed, and Q says so - which reads as "yes", and
|
||||
; refusing to remove something on a disk that will not read is the
|
||||
; right way round to be wrong.
|
||||
sbfsChildLoaded:
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
SETD.2 SbfsBuffer
|
||||
INIA 0d8
|
||||
SETD.1 SbfsCount
|
||||
STA.1
|
||||
|
||||
sbfsChildEntry:
|
||||
LDA.2
|
||||
INIB 0x01
|
||||
AND
|
||||
BRQ sbfsChildNext
|
||||
|
||||
; Through a CALL, so that DP2 comes back on the entry without anything having to work
|
||||
; out where that was. Doing the comparison in line here meant clobbering DP2 and then
|
||||
; rebuilding it from the buffer and the count - which had the subtraction the wrong way
|
||||
; round, walked the pointer off the end of the block, and let rmdir take a directory
|
||||
; with something still in it. Exactly the failure this routine exists to prevent.
|
||||
CALL sbfsMatchHold
|
||||
BRQ sbfsChildYes
|
||||
|
||||
sbfsChildNext:
|
||||
DPUP.2 0d32
|
||||
SETD.1 SbfsCount
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BNA sbfsChildEntry
|
||||
|
||||
SETD.0 SbfsBlock
|
||||
CALL sbfsStepWord
|
||||
SETD.1 SbfsLeft
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
BRA sbfsChildNone
|
||||
BRI sbfsChildBlock
|
||||
|
||||
sbfsChildYes:
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Q is zero: something lives there.
|
||||
RET
|
||||
|
||||
sbfsChildNone:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; ---- Deleting ----
|
||||
;
|
||||
; Frees a file. DP0 names it, and Q is zero if it went.
|
||||
@@ -1627,6 +2094,16 @@ sbfsDelete:
|
||||
AND
|
||||
BNQ sbfsDeleteFailed
|
||||
|
||||
CALL sbfsWipeFound
|
||||
RET
|
||||
|
||||
; Throws away whatever the last find landed on, wherever it landed. DP3 is on the entry and
|
||||
; SbfsBlock is the directory block it came out of, which is everything needed to change it
|
||||
; and put it back.
|
||||
;
|
||||
; Removing a directory arrives here too. A directory has no blocks, so the count that goes
|
||||
; back to the free total is nought and the sum is right without being a special case.
|
||||
sbfsWipeFound:
|
||||
; How much room it was taking, worked out before the entry that says so is thrown away.
|
||||
CALL sbfsFileExtent
|
||||
|
||||
@@ -1693,16 +2170,24 @@ sbfsRename:
|
||||
SETD.2 SbfsSavedName
|
||||
STD.0.2
|
||||
|
||||
; The new name is a path like any other, so only its last name is the name. Renaming
|
||||
; something to "/A/notes.txt" used to call it that, all thirteen characters of it.
|
||||
PSHD.1
|
||||
POPD.0
|
||||
CALL sbfsWalkParent
|
||||
BNQ sbfsRenameFailed
|
||||
SETD.0 SbfsAt
|
||||
SETD.1 SbfsHoldAt
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsWanted
|
||||
SETD.1 SbfsNewName
|
||||
CALL sbfsKeepName ; Twenty two bytes, padded, the way an entry holds one.
|
||||
CALL sbfsCopyName
|
||||
|
||||
; Refused if something already answers to the new name. Two entries with one name is a
|
||||
; disk that cannot be searched sensibly: a search answers with whichever it meets first,
|
||||
; so the other becomes unreachable without ever having been deleted.
|
||||
SETD.0 SbfsNewName
|
||||
CALL sbfsFind
|
||||
; Refused if something already answers to that name in that directory. Two entries with
|
||||
; one name in one place is a directory that cannot be searched sensibly: a search answers
|
||||
; with whichever it meets first, and the other becomes unreachable without ever having
|
||||
; been deleted.
|
||||
CALL sbfsScanFor
|
||||
BRQ sbfsRenameFailed
|
||||
|
||||
SETD.2 SbfsSavedName
|
||||
@@ -1710,6 +2195,14 @@ sbfsRename:
|
||||
CALL sbfsFind
|
||||
BNQ sbfsRenameFailed
|
||||
|
||||
; RENAMING DOES NOT MOVE ANYTHING. Only the twenty two bytes of the name change, and the
|
||||
; parent is not among them - so a new path naming a different directory would be a lie
|
||||
; the disk went along with. Refused instead.
|
||||
SETD.0 SbfsUpParent
|
||||
SETD.2 SbfsHoldAt
|
||||
CALL sbfsCompareWord
|
||||
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.
|
||||
@@ -1782,12 +2275,26 @@ 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.
|
||||
; WHERE IT GOES, WORKED OUT ONCE. Everything below is done in terms of a directory and
|
||||
; a name rather than a path, and that is what makes the careful order below work in a
|
||||
; subdirectory: the temporary has to be made in the SAME directory as the file, because
|
||||
; the rename at the end changes a name and does not move anything.
|
||||
SETD.2 SbfsSaveName
|
||||
LDD.0.2
|
||||
CALL sbfsFind
|
||||
CALL sbfsWalkParent
|
||||
BNQ sbfsSaveFailed
|
||||
SETD.0 SbfsAt
|
||||
SETD.1 SbfsSaveParent
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsWanted
|
||||
SETD.1 SbfsSaveLeaf
|
||||
CALL sbfsCopyName
|
||||
|
||||
; Refused up front if that 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
|
||||
; then instead, having already written a temporary nothing would ever come back for.
|
||||
CALL sbfsSaveWhere
|
||||
CALL sbfsScanFor
|
||||
BNQ sbfsSaveNotThere
|
||||
SETD.0 SbfsFoundFlags
|
||||
LDA.0
|
||||
@@ -1798,8 +2305,11 @@ 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
|
||||
CALL sbfsDelete
|
||||
CALL sbfsSaveTemp
|
||||
CALL sbfsScanFor
|
||||
BNQ sbfsSaveNoTemp
|
||||
CALL sbfsWipeFound
|
||||
sbfsSaveNoTemp:
|
||||
|
||||
SETD.0 SbfsFileBlocks
|
||||
SETD.2 SbfsSaveBlocks
|
||||
@@ -1809,8 +2319,8 @@ sbfsSaveNotThere:
|
||||
SETD.1 SbfsFileTail
|
||||
STA.1
|
||||
|
||||
SETD.0 SbfsTempName
|
||||
CALL sbfsCreate
|
||||
CALL sbfsSaveTemp
|
||||
CALL sbfsCreateAt
|
||||
BNQ sbfsSaveFailed
|
||||
|
||||
SETD.2 SbfsSaveData
|
||||
@@ -1818,16 +2328,51 @@ sbfsSaveNotThere:
|
||||
CALL sbfsWriteFile
|
||||
BNQ sbfsSaveFailed
|
||||
|
||||
; Now, and not before, the old one goes. It may not exist, which is what saving something
|
||||
; for the first time looks like from here.
|
||||
SETD.2 SbfsSaveName
|
||||
LDD.0.2
|
||||
CALL sbfsDelete
|
||||
; Now, and not before, the old one goes. It may not be there at all, which is what
|
||||
; saving something for the first time looks like from here.
|
||||
CALL sbfsSaveWhere
|
||||
CALL sbfsScanFor
|
||||
BNQ sbfsSaveNoOld
|
||||
CALL sbfsWipeFound
|
||||
sbfsSaveNoOld:
|
||||
|
||||
; And the temporary takes its name. Found again first, because everything above has been
|
||||
; reading other blocks over the one it lives in.
|
||||
CALL sbfsSaveTemp
|
||||
CALL sbfsScanFor
|
||||
BNQ sbfsSaveFailed
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d06
|
||||
SETD.0 SbfsSaveLeaf
|
||||
CALL sbfsCopyName
|
||||
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferIn
|
||||
CALL sbfsWriteBlock
|
||||
RET
|
||||
|
||||
; The directory the file is going in, and the name it is going under. Said again before
|
||||
; each step, because every step in between goes to the disk and leaves the walk somewhere
|
||||
; else entirely.
|
||||
sbfsSaveWhere:
|
||||
SETD.0 SbfsSaveParent
|
||||
SETD.1 SbfsAt
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsSaveLeaf
|
||||
SETD.1 SbfsWanted
|
||||
CALL sbfsCopyName
|
||||
RET
|
||||
|
||||
; The same directory, under the name the half finished file is written as.
|
||||
sbfsSaveTemp:
|
||||
SETD.0 SbfsSaveParent
|
||||
SETD.1 SbfsAt
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 SbfsTempName
|
||||
SETD.2 SbfsSaveName
|
||||
LDD.1.2
|
||||
CALL sbfsRename
|
||||
SETD.1 SbfsWanted
|
||||
CALL sbfsKeepName
|
||||
RET
|
||||
|
||||
sbfsSaveFailed:
|
||||
@@ -1895,6 +2440,20 @@ SbfsCwd:
|
||||
; plus one, so that zero is the root and a version one disk's zeroes already say it.
|
||||
SbfsPathAt:
|
||||
0x00 0x00
|
||||
|
||||
; ---- What splitting a path off its last name keeps ----
|
||||
SbfsHead:
|
||||
#Reserve 0d96
|
||||
SbfsHeadLen:
|
||||
0x00
|
||||
SbfsHoldAt:
|
||||
0x00 0x00
|
||||
|
||||
; The last name of a path, kept out of the way while the rest of the path is walked.
|
||||
SbfsLeaf:
|
||||
#Reserve 0d23
|
||||
SbfsSpanAt:
|
||||
0x00
|
||||
SbfsPathState:
|
||||
0x00
|
||||
SbfsPathLeft:
|
||||
@@ -1958,6 +2517,10 @@ SbfsSaveBlocks:
|
||||
0x00 0x00
|
||||
SbfsSaveTail:
|
||||
0x00
|
||||
SbfsSaveParent:
|
||||
0x00 0x00
|
||||
SbfsSaveLeaf:
|
||||
#Reserve 0d23
|
||||
|
||||
; What a document is called while it is being written and is not yet the real thing. A
|
||||
; name nothing else is likely to want, and short enough to leave room for a long one.
|
||||
|
||||
Reference in New Issue
Block a user