Say what a safe save actually promises, and where it stops
Both sbfsSaveFile and sbfsStreamDone write a temporary, delete the original and rename the temporary onto its name, and the README explained why that order and not the obvious one. It never said what the resulting guarantee is, which invited the stronger reading: "safe save" sounds like it survives anything. It survives everything that goes wrong while it is running - no run long enough, no free blocks, a refused write, a name that turns out to be a directory, a writer that gives up - and in all of those the original is untouched. It does not survive the machine stopping, because the commit is two block writes with a gap between them: stop there and the old file is gone and the new one is still called sbfs.part. The bytes are all present and one rename by hand recovers them, but nothing does that on its own. Written down in the README beside the ordering it qualifies, and at both commit points in sbfs.asm, where a reader is when the question occurs to them. Closing the gap wants a journal or a second directory, which is a lot of disk for two writes; a boot-time consistency check is the cheaper answer if it ever matters, and it would want temporaries flagged in the entry rather than recognised by name.
This commit is contained in:
@@ -2632,6 +2632,11 @@ sbfsStreamFits:
|
||||
|
||||
; Now, and not before, the old one goes. It may not be there at all, which is what
|
||||
; writing something for the first time looks like from here.
|
||||
;
|
||||
; The same two writes and the same gap between them as sbfsSaveFile has, and the same
|
||||
; answer: what stops here is recoverable by hand and by nothing else. The free count
|
||||
; goes back afterwards, and a machine stopping THERE only leaves the note wrong, which
|
||||
; the directory can always settle.
|
||||
CALL sbfsStreamWhere
|
||||
CALL sbfsScanFor
|
||||
BNQ sbfsStreamNoOld
|
||||
@@ -2827,6 +2832,14 @@ sbfsSaveNoTemp:
|
||||
|
||||
; 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.
|
||||
;
|
||||
; EVERYTHING ABOVE THIS POINT CAN FAIL AND COST NOTHING. Below it there are two block
|
||||
; writes and a gap between them, and a machine that stops in that gap has deleted the
|
||||
; old file and not yet named the new one. The bytes are all there under the temporary's
|
||||
; name and one rename gets them back, but nothing does that automatically and nothing
|
||||
; here claims to: this is safe against the ways a save fails while it is running, not
|
||||
; against the machine stopping. Closing the gap wants a journal, which is a great deal
|
||||
; of disk to buy back two writes.
|
||||
CALL sbfsSaveWhere
|
||||
CALL sbfsScanFor
|
||||
BNQ sbfsSaveNoOld
|
||||
|
||||
Reference in New Issue
Block a user