diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index 57f3cc7..5477018 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -757,6 +757,14 @@ A create can be refused for want of a run long enough even on a disk with plenty **That is what renaming is for.** It looks like a convenience and it is the safety mechanism: it is the only one of the three operations that moves no data - a name lives in the directory entry, so renaming writes twenty two bytes into one block - which makes it the only one that can be left until last and relied on not to fail. +#### Exactly what that promises: + +The ordering protects the original against **every way a save can fail while it is running**, and it is worth naming those, because they are the ones that actually happen: there is no run of free blocks long enough, or none at all; the disk refuses a block write; the name turns out to belong to a directory; the writer gives up part way through. In all of them the file that was already there is untouched, and what is lost is the temporary, which nothing had come to depend on yet. + +It is **not** power-loss atomic, and nothing about SBFS claims it is. The commit is two block writes - delete the old entry, then give the temporary its name - and a machine that stops between them leaves the old file gone and the new one under the temporary's name. Both writes are to the directory, so `sbfs.part` or `sbfs.out` is sitting there holding every byte of the work; the data survives and the name does not, and putting it right is one `rename` typed by hand. + +Closing that window means a journal or a second copy of the directory, and both are a great deal of machinery to buy back a two-write gap on a machine with no power failures to speak of. The honest description is the one to write down: **safe against the failures of ordinary operation, not against the machine stopping.** A future consistency check at boot could reclaim an abandoned temporary, and would want the entry to say it is one rather than to be recognised by its name. + Finding room is a walk through the directory rather than a lookup, because there is no allocation table. With files laid down contiguously the directory already says which blocks are spoken for, and a second copy of that would be a second thing to keep right. The free count in the superblock is kept up to date but it is a note rather than the truth: it can be worked out again from the directory, and the directory is the one to believe. A file's length is its block count times 256 plus its tail, which is the same as putting the block count in the high byte and the tail in the low one. Nothing pads a file out, so the bytes after the end of one are whatever else happened to be in that block, and it is the reading program's business to stop where the tail says. diff --git a/Programs/CosmOS/Source/sbfs.asm b/Programs/CosmOS/Source/sbfs.asm index 22f2d60..372349c 100644 --- a/Programs/CosmOS/Source/sbfs.asm +++ b/Programs/CosmOS/Source/sbfs.asm @@ -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