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:
@@ -175,6 +175,103 @@ else
|
||||
report FAIL "native compare" "Compare did not call the copied files equal"
|
||||
fi
|
||||
|
||||
# ---- A temporary is what the entry says it is, not what it is called ----
|
||||
#
|
||||
# Saving something writes a temporary first, and the temporary was told apart from a real
|
||||
# file by being called "sbfs.part" or "sbfs.out". Those are legal names. A file of your own
|
||||
# under either of them was deleted by the next save of anything at all in the same
|
||||
# directory - so this puts one there with the host, saves with the machine, and takes it
|
||||
# off again to see whether it survived.
|
||||
#
|
||||
# The entry now says outright that it is a temporary, and a file that does not say so
|
||||
# belongs to somebody, so the save is refused instead of helping itself to the name.
|
||||
#
|
||||
# ONE DISK EACH, and that is not tidiness. Both on one disk, the first save ate sbfs.part
|
||||
# and the streaming test's source file WAS sbfs.part - so with the guard removed the copy
|
||||
# failed for want of a source, never opened a stream, and the check passed while reporting
|
||||
# on nothing at all.
|
||||
"$ASM" -I "$ROOT/Programs/CosmOS/Source" \
|
||||
"$ROOT/Programs/CosmOS/Apps/Files.asm" -o Files.sbx >/dev/null 2>&1
|
||||
printf 'this file is mine and is not scratch\n' > mine.txt
|
||||
|
||||
"$TOOL" format part.img 512 4 >/dev/null
|
||||
"$TOOL" mkdir part.img /Apps >/dev/null
|
||||
"$TOOL" put part.img Files.sbx /Apps/Files.sbx >/dev/null
|
||||
"$TOOL" put part.img mine.txt /sbfs.part >/dev/null
|
||||
printf 'Files\nexit\n' | "$EMU" cosmos.bin --fast --disk part.img > part.txt 2>&1
|
||||
"$TOOL" get part.img /sbfs.part keptPart.txt >/dev/null 2>&1
|
||||
if cmp -s mine.txt keptPart.txt; then
|
||||
report ok "a file called sbfs.part" "a whole-file save left it alone"
|
||||
else
|
||||
report FAIL "a file called sbfs.part" "saving a different file destroyed it"
|
||||
fi
|
||||
if grep -q "would not save" part.txt; then
|
||||
report ok "and the save was refused" "it did not take a name it did not own"
|
||||
else
|
||||
report FAIL "and the save was refused" "the save reported success"
|
||||
fi
|
||||
|
||||
"$TOOL" format out.img 512 4 >/dev/null
|
||||
"$TOOL" mkdir out.img /Apps >/dev/null
|
||||
"$TOOL" put out.img Copy.sbx /Apps/Copy.sbx >/dev/null
|
||||
"$TOOL" put out.img mine.txt /source.txt >/dev/null
|
||||
"$TOOL" put out.img mine.txt /sbfs.out >/dev/null
|
||||
printf 'Copy /source.txt /copied.txt\nexit\n' \
|
||||
| "$EMU" cosmos.bin --fast --cycles 100000000 --disk out.img > out.txt 2>&1
|
||||
"$TOOL" get out.img /sbfs.out keptOut.txt >/dev/null 2>&1
|
||||
if cmp -s mine.txt keptOut.txt; then
|
||||
report ok "a file called sbfs.out" "a streamed write left it alone"
|
||||
else
|
||||
report FAIL "a file called sbfs.out" "streaming a different file destroyed it"
|
||||
fi
|
||||
|
||||
# The source is still there too, which says the refusal happened before anything was
|
||||
# deleted rather than half way through.
|
||||
"$TOOL" get out.img /source.txt keptSource.txt >/dev/null 2>&1
|
||||
if cmp -s mine.txt keptSource.txt; then
|
||||
report ok "and it stopped early" "the source was never touched"
|
||||
else
|
||||
report FAIL "and it stopped early" "the copy got far enough to disturb the source"
|
||||
fi
|
||||
|
||||
# ---- And a write that stopped is visible from both sides ----
|
||||
#
|
||||
# Nothing here can crash the machine half way through a commit, so the wreckage is forged:
|
||||
# the flag is set by hand on a finished file, which is byte for byte what a save that was
|
||||
# interrupted between writing its temporary and naming it would have left. Both listings
|
||||
# have to say so, because the bytes are recoverable and only a listing can point at them.
|
||||
"$TOOL" format wreck.img 512 4 >/dev/null
|
||||
"$TOOL" put wreck.img mine.txt /stranded.txt >/dev/null
|
||||
python3 - wreck.img <<'PATCH'
|
||||
import sys
|
||||
image = open(sys.argv[1], "r+b")
|
||||
image.seek(8)
|
||||
start = int.from_bytes(image.read(2), "big") # First directory block.
|
||||
image.seek(start * 256)
|
||||
directory = bytearray(image.read(256))
|
||||
for at in range(0, 256, 32):
|
||||
if directory[at] & 0x01 and directory[at + 6:at + 14] == b"stranded":
|
||||
directory[at] |= 0x04 # Never committed.
|
||||
image.seek(start * 256)
|
||||
image.write(directory)
|
||||
break
|
||||
else:
|
||||
sys.exit("could not find the entry to strand")
|
||||
image.close()
|
||||
PATCH
|
||||
|
||||
if "$TOOL" list wreck.img | grep -q "unfinished write"; then
|
||||
report ok "the host sees the wreckage" "listed and explained"
|
||||
else
|
||||
report FAIL "the host sees the wreckage" "SplitDisk listed it as an ordinary file"
|
||||
fi
|
||||
if printf 'dir\nexit\n' | "$EMU" cosmos.bin --fast --disk wreck.img 2>&1 \
|
||||
| grep -q "<unfinished>"; then
|
||||
report ok "the machine sees it too" "dir marks it rather than sizing it"
|
||||
else
|
||||
report FAIL "the machine sees it too" "dir showed it as an ordinary file"
|
||||
fi
|
||||
|
||||
# ---- And each can read what the other wrote ----
|
||||
#
|
||||
# Matching bytes and being readable are not the same claim. A field both of them write
|
||||
|
||||
Reference in New Issue
Block a user