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:
@@ -149,6 +149,13 @@ static int entryIsDirectory(const uint8_t *entry) {
|
||||
return (entry[SBFS_ENTRY_FLAGS] & SBFS_FLAG_DIRECTORY) != 0;
|
||||
}
|
||||
|
||||
// A save that was interrupted between writing its temporary and committing it. The blocks
|
||||
// are genuinely spoken for - entryInUse says so, and the allocator must keep believing it
|
||||
// - but nothing has claimed them under a name anybody asked for.
|
||||
static int entryIsTemporary(const uint8_t *entry) {
|
||||
return (entry[SBFS_ENTRY_FLAGS] & SBFS_FLAG_TEMPORARY) != 0;
|
||||
}
|
||||
|
||||
// The index of the entry this one lives in, or -1 for the root. Stored as index plus one
|
||||
// so that a zeroed field - which is what every version one entry has - means the root.
|
||||
static int entryParent(const uint8_t *entry) {
|
||||
@@ -470,6 +477,7 @@ static int entryPath(const Directory *directory, int index, char *into, size_t r
|
||||
typedef struct {
|
||||
int files;
|
||||
int directories;
|
||||
int temporaries;
|
||||
} Tally;
|
||||
|
||||
// Prints one directory and everything under it, depth first and in entry order, which is
|
||||
@@ -498,9 +506,11 @@ static void listTree(const Directory *directory, int parent, char *prefix, size_
|
||||
tally->directories++;
|
||||
listTree(directory, i, prefix, at + 1 + n, tally);
|
||||
} else {
|
||||
printf("%-34s %8u %7u %7u\n", prefix, entrySize(entry),
|
||||
readWord(entry + SBFS_ENTRY_START), entryBlocksUsed(entry));
|
||||
printf("%-34s %8u %7u %7u%s\n", prefix, entrySize(entry),
|
||||
readWord(entry + SBFS_ENTRY_START), entryBlocksUsed(entry),
|
||||
entryIsTemporary(entry) ? " unfinished" : "");
|
||||
tally->files++;
|
||||
tally->temporaries += entryIsTemporary(entry);
|
||||
}
|
||||
prefix[at] = '\0';
|
||||
}
|
||||
@@ -522,7 +532,7 @@ static int commandList(const char *path, const char *within) {
|
||||
super.version);
|
||||
printf("%-34s %8s %7s %7s\n", "NAME", "BYTES", "START", "BLOCKS");
|
||||
char prefix[SBFS_PATH_BYTES];
|
||||
Tally tally = { 0, 0 };
|
||||
Tally tally = { 0, 0, 0 };
|
||||
int start = -1;
|
||||
if (within != NULL) {
|
||||
const char *why = NULL;
|
||||
@@ -573,6 +583,16 @@ static int commandList(const char *path, const char *within) {
|
||||
}
|
||||
printf(".\n");
|
||||
|
||||
// A save that stopped between deleting the old entry and naming the new one. The
|
||||
// bytes are all there under the temporary's name and one rename brings them back,
|
||||
// which is the whole of the recovery this format offers - so the thing that matters
|
||||
// is that a listing says so rather than showing a file with an odd name.
|
||||
if (tally.temporaries > 0) {
|
||||
printf("%d unfinished write%s: the blocks are held and the data is there, under"
|
||||
" that name. Rename it to keep it, delete it to let the blocks go.\n",
|
||||
tally.temporaries, tally.temporaries == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
// Everything in use should have been reached by walking down from the root. Anything
|
||||
// that was not is pointing at a parent that is not there, or at itself, and that is
|
||||
// worth saying out loud rather than quietly leaving off the listing.
|
||||
|
||||
Reference in New Issue
Block a user