Say a temporary is one in the entry, not in its name

Saving something that already exists writes a temporary, deletes the
original and gives the temporary its name, so that nothing is lost if the
writing fails. The temporary was told apart from a real file by being
called sbfs.part or sbfs.out - and those are legal names. Starting a save
deleted whatever answered to one as stale scratch, so saving anything at
all in a directory destroyed your own file of that name there, silently.

Flag bit 0x04 now says it. The property is not in the contents - the same
bytes become the finished file the instant the rename lands - so it belongs
in the entry, which is the thing the commit changes. sbfsCreateTempAt is
the door temporaries come in by, the commit writes the flags flat along
with the name, and cleanup wipes what it finds only if the entry says it is
ours. Anything else stops the save instead.

The bit is also the recovery. Both listings show an unfinished write rather
than sizing it, because the size in the entry is the room that was asked
for and not what was written: "<unfinished>" from dir, and a line from
SplitDisk saying the blocks are held and a rename brings the data back.
That was the gap in what the last commit documented - the data survived a
crash and nothing would show you where it was.

Four new agreement checks, three of which fail with the guards removed. The
fourth needed rebuilding first: both tests started on one disk, and the
first save ate the sbfs.part that was the second test's SOURCE, so the copy
failed for want of a file, never opened a stream, and passed while
reporting on nothing. A disk each. The fifth check forges the wreckage by
setting the flag on a finished file, since nothing here can crash a save
half way through.

No version bump: a committed file never carries the bit, so a disk this
writes is byte for byte the disk the old code wrote, which the whole-image
comparisons confirm. Only the wreckage differs, and older code reads that
as an ordinary file - which is what it did before.
This commit is contained in:
Anachronaut
2026-08-25 23:22:37 -04:00
parent d6cbbb5034
commit ce8fb721fe
6 changed files with 242 additions and 13 deletions
+66 -8
View File
@@ -9,6 +9,12 @@
; between them but the written specification, so anything that changes here has to change
; there in the same breath.
;
; ---- What an entry's flags mean ----
;
; 0x01 In use. A zero here is a free slot, whatever else the bytes hold.
; 0x02 A directory, which has no blocks at all.
; 0x04 A file being written and not yet committed. See sbfsSaveFile.
;
; The disk's buffer is registered as bank 3, and every block read lands there and is then
; blitted where it is wanted. The CPU never touches the buffer directly, because nothing
; can: memory a device brings is reachable only through the controller.
@@ -1559,6 +1565,22 @@ sbfsCreate:
BNQ sbfsCreateFailed
sbfsCreateAt:
; A plain file, unless somebody came in at the other door. Set here rather than left
; over from last time, so that one temporary does not make the next ordinary file one.
INIA 0x01
SETD.0 SbfsMakeFlags
STA.0
BRI sbfsCreateGo
; The same, for the half written file a save puts down before it dares touch the original.
; It is an ordinary entry in every other way - it holds blocks and answers to a name - and
; the flag is the whole of what says it is not finished. See sbfsSaveFile.
sbfsCreateTempAt:
INIA 0x05
SETD.0 SbfsMakeFlags
STA.0
sbfsCreateGo:
CALL sbfsFileExtent
CALL sbfsAllocate
BNQ sbfsCreateFailed
@@ -1614,8 +1636,9 @@ sbfsCreateFill:
; DP2 is on the entry. Fill it in, then put the whole block back on the disk.
PSHD.2
POPD.3
INIA 0x01
STA.3 ; In use.
SETD.0 SbfsMakeFlags
LDA.0
STA.3 ; In use, and possibly not finished being written.
PSHD.3
POPD.1
@@ -2450,11 +2473,17 @@ sbfsStreamStart:
BNQ sbfsStreamNo
sbfsStreamFresh:
; A temporary left by a stream that did not finish would be in the way. Whether there was
; one is not worth asking about, since either answer leads here.
; A temporary left by a stream that did not finish would be in the way, so it goes - but
; only if it really is one. See sbfsSaveFile: the flag says so and the name does not,
; because "sbfs.out" is a name somebody may have chosen for themselves.
CALL sbfsStreamTemp
CALL sbfsScanFor
BNQ sbfsStreamNoTemp
SETD.0 SbfsFoundFlags
LDA.0
INIB 0x04
AND
BRQ sbfsStreamNo ; Somebody's own file, under a name we wanted. Left alone.
CALL sbfsWipeFound
sbfsStreamNoTemp:
@@ -2468,7 +2497,7 @@ sbfsStreamNoTemp:
STA.1
CALL sbfsStreamTemp
CALL sbfsCreateAt
CALL sbfsCreateTempAt
BNQ sbfsStreamNo
; WHERE THE TEMPORARY BEGINS, KEPT NOW. Nothing moves a file once it is made, so every
@@ -2650,6 +2679,13 @@ sbfsStreamNoOld:
CALL sbfsScanFor
BNQ sbfsStreamNo
; Finished: the flag goes down in the same block write that gives it its name and its
; size. Flat rather than cleared, for the reason sbfsSaveFile gives.
PSHD.3
POPD.1
INIA 0x01
STA.1
PSHD.3
POPD.1
DPUP.1 0d06
@@ -2805,11 +2841,20 @@ sbfsSaveFile:
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.
; A temporary left behind by a save that did not finish would be in the way, so it goes.
;
; WHAT MAKES IT ONE IS THE FLAG AND NOT THE NAME. "sbfs.part" is a name a person is
; perfectly entitled to give a file of their own, and this used to delete whatever
; answered to it - so saving anything at all, once, quietly destroyed that file. The
; entry now says what it is, and something that is not ours stops the save instead.
CALL sbfsSaveTemp
CALL sbfsScanFor
BNQ sbfsSaveNoTemp
SETD.0 SbfsFoundFlags
LDA.0
INIB 0x04
AND
BRQ sbfsSaveFailed ; Somebody's own file, under a name we wanted. Left alone.
CALL sbfsWipeFound
sbfsSaveNoTemp:
@@ -2822,7 +2867,7 @@ sbfsSaveNoTemp:
STA.1
CALL sbfsSaveTemp
CALL sbfsCreateAt
CALL sbfsCreateTempAt
BNQ sbfsSaveFailed
SETD.2 SbfsSaveData
@@ -2852,6 +2897,14 @@ sbfsSaveNoOld:
CALL sbfsScanFor
BNQ sbfsSaveFailed
; FINISHED, WHICH IS THE FLAG AND THE NAME TOGETHER. Written flat rather than by
; clearing the one bit: a temporary is a file, so in use is the only other thing it can
; ever have been, and the whole byte is known.
PSHD.3
POPD.1
INIA 0x01
STA.1
PSHD.3
POPD.1
DPUP.1 0d06
@@ -2987,6 +3040,11 @@ SbfsUpParent:
SbfsFoundFlags:
0x00
; What the next entry made is to be marked with. sbfsCreateAt sets it to a plain file and
; sbfsCreateTempAt to a temporary, so it is never read without having just been written.
SbfsMakeFlags:
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.