diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index b947ba9..66e6818 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -693,6 +693,55 @@ compares the images byte for byte. Every field one writes and the other only rea checked there and nowhere else: which entry a thing lands in, which block, what a directory's unused fields hold, the version, the free count. +### What A Listing Says About The Disk: + +`dir` ends with what is there and what is left: + +```text +43 files, 3 directories +46 of 64 entries, 1893 blocks free +``` + +**Entries come first because they are the ceiling nobody notices until they hit it.** A disk +of small files runs out of directory slots long before it runs out of blocks, and saying both +means never having to work out which one is about to bite. + +**The free figure is counted, not asked for.** The superblock keeps a free count and +`sbfs.asm` calls it *"a note rather than the truth"* in three separate places; `sbfsSpace` +reads the whole directory table instead, which costs a read per directory block and is the +answer rather than a guess. SplitDisk reads the note as well and says so when the two +disagree, which is the right place for that check: the host tool is what you audit a disk +with, and this is what you work on one with. + +**And it is a fact about the disk, not about where you are standing.** The first version of +this added the blocks up as the listing walked past them, which cost nothing extra and was +wrong - that walk stops only on entries in the working directory, so the same disk came out as +1,996 blocks free from the root and 2,025 from `/Apps`. The two implementations of this format +disagreeing is what said so. + +A third line appears when it needs to: + +```text +4 of 16 entries, 37 blocks free +the longest run is 25 +``` + +**Files are laid down contiguously**, so the free total is not what decides whether a file +will fit - the longest unbroken run is. A disk with a thousand blocks free in ten pieces +refuses a file of two hundred, and nothing else in the listing would hint at it. + +**It is said only when it differs from the free total**, which on a healthy disk it does not. +Deleting is what fragments a contiguous store, and a disk that has only been appended to has +one gap, at the end. A line printed under every listing saying the same number twice is noise; +one that appears only when something is wrong is a line somebody reads. + +Finding it costs a pass of the directory table per gap. There is no sort on this machine and +the entries are in no order, so a candidate walks the disk instead: each pass looks for the +used extent nearest at or after it, and anything the candidate is standing inside pushes it to +the far end and starts the pass again - the same trick allocating uses, for the same reason. +So the measurement costs almost nothing on a disk with one gap and more the more fragmented +the disk is, which is the right way round: it is slow exactly when it has something to say. + ### Starting An Application By Name: A word the shell has no command for is not immediately an error. Before saying so, the diff --git a/Programs/CosmOS/Source/cosmos.asm b/Programs/CosmOS/Source/cosmos.asm index e509ffd..edcd497 100644 --- a/Programs/CosmOS/Source/cosmos.asm +++ b/Programs/CosmOS/Source/cosmos.asm @@ -3938,6 +3938,102 @@ dirFolderCount: CALL printString dirNoFolders: CALL newLine + + ; ---- And what is left of the disk ---- + ; + ; A listing that says what is there and not what is left says half of what anybody wants + ; to know. SplitDisk has printed this since it was written; the machine's own listing had + ; nothing to say about the disk it was listing. + ; + ; COUNTED RATHER THAN ASKED. The superblock keeps a free count, and sbfs.asm calls it "a + ; note rather than the truth" in three separate places. The truth is what the entries add + ; up to, and the walk above has just added them up - so this costs no disk read at all, + ; where believing the note would cost one and be a guess. SplitDisk reads the note as well + ; and says so when the two disagree, which is the right place for that check: the host tool + ; is what you audit a disk with, and this is what you work on one with. + ; + ; ---- AND IT IS THE DISK, NOT THIS DIRECTORY ---- + ; + ; The first version of this added the blocks up as the listing walked past them, which cost + ; no extra read and was wrong: that walk stops only on entries in the working directory, so + ; the same disk was called 1,996 blocks free from the root and 2,025 from /Apps. Free space + ; is a fact about the disk, so sbfsSpace reads the whole directory table for it - and the + ; two implementations of this format disagreeing is what said so. + CALL sbfsSpace + BNQ dirNoSpace + + ; Entries first, because they are the ceiling nobody notices until they hit it - a disk of + ; small files runs out of directory slots long before it runs out of blocks, and saying + ; both means never having to work out which one is about to bite. + SETD.0 DirEntries + SETD.2 SbfsDirBlocks + CALL sbfsSetWord + INIA 0d3 + SETD.0 DirDoubles + STA.0 +dirEntriesTimes: + ; Eight entries to a directory block, which is three doublings on a machine with no + ; multiply. Adding a number to itself is what a doubling is. + SETD.0 DirEntries + SETD.2 DirEntries + CALL sbfsAddWord + SETD.0 DirDoubles + LDA.0 + DECA + STA.0 + BNA dirEntriesTimes + + SETD.0 SbfsUsedEntries + CALL printWordDecimal + SETD.0 OfText + CALL printString + SETD.0 DirEntries + CALL printWordDecimal + SETD.0 EntriesText + CALL printString + + ; What is left is the disk, less where the files begin, less what they hold. + SETD.0 DirFree + SETD.2 SbfsDiskBlocks + CALL sbfsSetWord + CALL sbfsFirstData + SETD.0 DirFree + SETD.2 SbfsCandidate + CALL sbfsSubWord + SETD.0 DirFree + SETD.2 SbfsUsedBlocks + CALL sbfsSubWord + + SETD.0 DirFree + CALL printWordDecimal + SETD.0 FreeText + CALL printString + CALL newLine + + ; ---- And how much of that is in one piece ---- + ; + ; Said only when it is not all of it, which is the common case and the quiet one. Files are + ; laid down contiguously, so the free total is not what decides whether a file will fit - + ; the longest run is. A disk with a thousand blocks free in ten pieces refuses a file of two + ; hundred, and nothing in the listing would have hinted at it. + ; + ; A LISTING THAT SAID "longest run 1893" UNDER "1893 blocks free" WOULD BE NOISE on every + ; healthy disk, and a line that only appears when something is wrong is a line somebody + ; reads. It is also what makes the measurement worth its cost: a disk with one gap at the + ; end, which is what an append-only disk is, walks the table twice and says nothing. + CALL sbfsLargestRun + BNQ dirNoSpace + SETD.0 DirFree + SETD.2 SbfsBiggest + CALL sbfsCompareWord + BRQ dirNoSpace + + SETD.0 RunText + CALL printString + SETD.0 SbfsBiggest + CALL printWordDecimal + CALL newLine +dirNoSpace: BRI prompt dirNoDisk: @@ -7927,6 +8023,15 @@ IsDirectory: "that is a directory" AndText: ", " +OfText: +" of " +EntriesText: +" entries, " +FreeText: +" blocks free" +RunText: +"the longest run is " + FoldersText: " directories" FolderText: @@ -8219,6 +8324,14 @@ DirFolders: 0x00 DirTaken: 0x00 +; What the entries add up to, what is left, and how many slots there are to fill. Counted on +; the way past rather than asked of the superblock, whose count sbfs.asm calls a note. +DirFree: + 0x00 0x00 +DirEntries: + 0x00 0x00 +DirDoubles: + 0x00 DiskReady: 0x00 LoadedOk: diff --git a/Programs/CosmOS/Source/sbfs.asm b/Programs/CosmOS/Source/sbfs.asm index f1b83b2..cbb46d9 100644 --- a/Programs/CosmOS/Source/sbfs.asm +++ b/Programs/CosmOS/Source/sbfs.asm @@ -1916,6 +1916,278 @@ sbfsEntryBounds: sbfsBoundsDone: RET +; ---- What the whole disk is holding ---- +; +; SbfsUsedBlocks becomes what every entry on the disk holds between them, and SbfsUsedEntries +; how many slots are filled. Q is zero if the disk could be read. +; +; EVERY ENTRY, NOT EVERY ENTRY IN A DIRECTORY. Free space is a fact about the disk and not +; about where you are standing, so this walks the directory table itself the way allocating +; does, rather than using sbfsFirst and sbfsNext - which stop only on entries whose parent is +; the working directory, and would have called the same disk emptier from one directory than +; from another. +; +; A DIRECTORY HOLDS NO BLOCKS and adds nought, which is true by its fields rather than by a +; test here. An unfinished save holds all of its, and is counted, because the blocks are +; spoken for whatever the entry is called - and a listing that left them out would promise +; room that is not there. +; +; The count in the superblock is not consulted. sbfs.asm calls it "a note rather than the +; truth" in three places, and this is the truth: it costs a read of every directory block, +; which is what the honest answer costs. +sbfsSpace: + RSTA + SETD.0 SbfsUsedBlocks + STA.0 + INCD.0 + STA.0 + SETD.0 SbfsUsedEntries + STA.0 + INCD.0 + STA.0 + + SETD.0 SbfsDirStart + SETD.1 SbfsBlock + CALL sbfsCopyWord + SETD.0 SbfsDirBlocks + INCD.0 + LDA.0 + SETD.1 SbfsLeft + STA.1 + +sbfsSpaceBlock: + CALL sbfsReadBlock + BNQ sbfsSpaceFailed + SETD.1 SbfsBuffer + CALL sbfsBufferOut + SETD.2 SbfsBuffer + INIA 0d8 + SETD.1 SbfsCount + STA.1 + +sbfsSpaceEntry: + LDA.2 + INIB 0x01 + AND + BRQ sbfsSpaceNext ; A free slot holds nothing and fills nothing. + + SETD.0 SbfsUsedEntries + CALL sbfsStepWord + + ; The blocks this entry holds, which are at the same offset in an entry as they are + ; everywhere else. sbfsEntryBounds wants DP2 on the entry and gives back both ends, and the + ; length is the difference - worked out that way rather than read directly, so that one + ; routine owns where in an entry those fields are. + PSHD.2 + CALL sbfsEntryBounds + SETD.0 SbfsEntryEnd + SETD.2 SbfsEntryStart + CALL sbfsSubWord + SETD.0 SbfsUsedBlocks + SETD.2 SbfsEntryEnd + CALL sbfsAddWord + POPD.2 + +sbfsSpaceNext: + DPUP.2 0d32 + SETD.1 SbfsCount + LDA.1 + DECA + STA.1 + BNA sbfsSpaceEntry + + SETD.0 SbfsBlock + CALL sbfsStepWord + SETD.1 SbfsLeft + LDA.1 + DECA + STA.1 + BNA sbfsSpaceBlock + + RSTA + RSTB + CCF + ADD + RET + +sbfsSpaceFailed: + INIA 0x01 + RSTB + CCF + ADD + RET + +; ---- The longest run of free blocks there is ---- +; +; SbfsBiggest becomes the largest number of blocks that are free AND NEXT TO EACH OTHER. +; Q is zero if the disk could be read. +; +; This is the number that says whether a file will fit. Files are laid down contiguously - +; first fit, with the directory itself as the map and no allocation table anywhere - so a disk +; with a thousand blocks free in ten scattered pieces will refuse a file of two hundred, and +; the free total gives no hint of it. +; +; ---- The same trick allocating uses, for the same reason ---- +; +; There is no sort on this machine and the entries are in no order, so the gaps cannot simply +; be listed. Instead a candidate walks the disk: each pass looks for the used extent nearest +; at or after it, and anything the candidate is standing inside pushes the candidate to the +; far end of it and starts the pass again. +; +; So the cost is a pass of the directory table per gap, rather than per file - and it is a +; measurement that costs nothing on a disk with one gap at the end, which is what a disk that +; has only been appended to looks like, and costs more the more fragmented the disk is. Which +; is the right way round: it is slow exactly when it has something to say. +sbfsLargestRun: + RSTA + SETD.0 SbfsBiggest + STA.0 + INCD.0 + STA.0 + CALL sbfsFirstData + +sbfsRunPass: + ; Nothing is in the way yet this time round, and the nearest thing ahead is the end of the + ; disk - which is the answer when there is nothing ahead at all. + RSTA + SETD.0 SbfsRunMoved + STA.0 + SETD.0 SbfsRunNext + SETD.2 SbfsDiskBlocks + CALL sbfsSetWord + + SETD.0 SbfsDirStart + SETD.1 SbfsBlock + CALL sbfsCopyWord + SETD.0 SbfsDirBlocks + INCD.0 + LDA.0 + SETD.1 SbfsLeft + STA.1 + +sbfsRunBlock: + CALL sbfsReadBlock + BNQ sbfsRunFailed + SETD.1 SbfsBuffer + CALL sbfsBufferOut + SETD.2 SbfsBuffer + INIA 0d8 + SETD.1 SbfsCount + STA.1 + +sbfsRunEntry: + LDA.2 + INIB 0x01 + AND + BRQ sbfsRunNextEntry ; A free slot is in nobody's way. + LDA.2 + INIB 0x02 + AND + BNQ sbfsRunNextEntry ; Nor is a directory, which holds no blocks - see allocating. + + CALL sbfsEntryBounds + PSHD.2 ; The comparisons want DP2, and the walk wants it back. + + ; Behind the candidate entirely, so it says nothing about what is ahead. + SETD.0 SbfsEntryEnd + SETD.2 SbfsCandidate + CALL sbfsCompareWord + BRC sbfsRunClear + BRQ sbfsRunClear + + ; Beginning at or before the candidate, so the candidate is standing inside it. It moves to + ; the far end and the pass begins again, exactly as allocating does. + SETD.0 SbfsEntryStart + SETD.2 SbfsCandidate + CALL sbfsCompareWord + BRC sbfsRunStraddles + BRQ sbfsRunStraddles + + ; Ahead of the candidate. Is it the nearest thing ahead so far? + SETD.0 SbfsEntryStart + SETD.2 SbfsRunNext + CALL sbfsCompareWord + BNC sbfsRunClear + SETD.0 SbfsRunNext + SETD.2 SbfsEntryStart + CALL sbfsSetWord + BRI sbfsRunClear + +sbfsRunStraddles: + SETD.0 SbfsCandidate + SETD.2 SbfsEntryEnd + CALL sbfsSetWord + INIA 0x01 + SETD.0 SbfsRunMoved + STA.0 + +sbfsRunClear: + POPD.2 + +sbfsRunNextEntry: + DPUP.2 0d32 + SETD.1 SbfsCount + LDA.1 + DECA + STA.1 + BNA sbfsRunEntry + + SETD.0 SbfsBlock + CALL sbfsStepWord + SETD.1 SbfsLeft + LDA.1 + DECA + STA.1 + BNA sbfsRunBlock + + ; The pass is over. If the candidate moved, everything measured against where it used to be + ; was measured against the wrong place, so the pass is worth nothing and is done again. + SETD.0 SbfsRunMoved + LDA.0 + BNA sbfsRunPass + + ; Nothing moved, so the gap from the candidate to the nearest thing ahead is a real one. + SETD.0 SbfsRunGap + SETD.2 SbfsRunNext + CALL sbfsSetWord + SETD.0 SbfsRunGap + SETD.2 SbfsCandidate + CALL sbfsSubWord + + SETD.0 SbfsBiggest + SETD.2 SbfsRunGap + CALL sbfsCompareWord + BNC sbfsRunKept ; What is already remembered is the same or bigger. + SETD.0 SbfsBiggest + SETD.2 SbfsRunGap + CALL sbfsSetWord + +sbfsRunKept: + ; Past the end of the disk is the end of the walk. Otherwise the candidate goes to the + ; thing that stopped it, which the next pass finds itself standing inside and steps over. + SETD.0 SbfsRunNext + SETD.2 SbfsDiskBlocks + CALL sbfsCompareWord + BNC sbfsRunDone + SETD.0 SbfsCandidate + SETD.2 SbfsRunNext + CALL sbfsSetWord + BRI sbfsRunPass + +sbfsRunDone: + RSTA + RSTB + CCF + ADD + RET + +sbfsRunFailed: + INIA 0x01 + RSTB + CCF + ADD + RET + ; Finds a run of SbfsWantBlocks free blocks and puts where it begins in SbfsFileStart. ; Q is zero if there was room. ; @@ -3691,6 +3963,24 @@ SbfsScratch1: SbfsPathWanted: 0x00 +; What every entry on the disk holds between them, and how many slots are filled. Worked out +; by sbfsSpace, which reads the whole directory table to do it. +; The longest run of free blocks, and the sweep that finds it: where it has got to ahead of +; the candidate, whether anything pushed the candidate this pass, and the gap being measured. +SbfsBiggest: + 0x00 0x00 +SbfsRunNext: + 0x00 0x00 +SbfsRunGap: + 0x00 0x00 +SbfsRunMoved: + 0x00 + +SbfsUsedBlocks: + 0x00 0x00 +SbfsUsedEntries: + 0x00 0x00 + SbfsFileStart: 0x00 0x00 SbfsFileBlocks: diff --git a/Source/DiskTool/SplitDisk.c b/Source/DiskTool/SplitDisk.c index c05efe4..a0ca3a9 100644 --- a/Source/DiskTool/SplitDisk.c +++ b/Source/DiskTool/SplitDisk.c @@ -433,6 +433,56 @@ static uint16_t countFree(const Directory *directory, const Superblock *super) { return (uint16_t)(super->diskBlocks - overhead - used); } +// ---- The longest run of free blocks there is ---- +// +// The number that says whether a file will fit. Files are laid down contiguously - first fit, +// with the directory itself as the map - so a disk with a thousand blocks free in ten +// scattered pieces refuses a file of two hundred, and the free total gives no hint of it. +// +// A sweep rather than a sort: a candidate walks the disk, each pass looking for the used +// extent nearest at or after it, and anything the candidate is standing inside pushes it to +// the far end and starts the pass again. That is what sbfs.asm does, because that machine has +// no sort - and the two implementations of this format are worth more agreeing than they are +// each being clever separately. +static uint16_t largestRun(const Directory *directory, const Superblock *super) { + const uint32_t firstData = (uint32_t)super->directoryStart + super->directoryBlocks; + uint32_t candidate = firstData; + uint32_t biggest = 0; + for (;;) { + uint32_t next = super->diskBlocks; + int moved = 0; + for (int i = 0; i < directory->entries; i++) { + const uint8_t *entry = entryAt(directory, i); + if (!entryInUse(entry) || entryIsDirectory(entry)) { + continue; + } + const uint32_t start = readWord(entry + SBFS_ENTRY_START); + const uint32_t end = start + entryBlocksUsed(entry); + if (end <= candidate) { + continue; + } + if (start <= candidate) { + candidate = end; + moved = 1; + continue; + } + if (start < next) { + next = start; + } + } + if (moved) { + continue; + } + if (next - candidate > biggest) { + biggest = next - candidate; + } + if (next >= super->diskBlocks) { + return (uint16_t)biggest; + } + candidate = next; + } +} + // ---- Commands ---- static int commandFormat(const char *path, uint16_t blocks, uint16_t directoryBlocks, @@ -647,6 +697,15 @@ static int commandList(const char *path, const char *within) { } printf(".\n"); + // Said only when it is not all of it. On a disk that has only been appended to the two + // are the same number, and printing it twice would be noise on every healthy disk - a + // line that appears only when something is wrong is a line somebody reads. + uint16_t run = largestRun(&directory, &super); + if (run != counted) { + printf("The longest run of free blocks is %u, so nothing bigger than that will" + " fit.\n", run); + } + // 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 diff --git a/SplitBit Test Manual.md b/SplitBit Test Manual.md index f7a97da..b6651e3 100644 --- a/SplitBit Test Manual.md +++ b/SplitBit Test Manual.md @@ -125,7 +125,7 @@ from `make`, not from here. ### 1. Recorded output `Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares -everything it printed against a file in `Tests/expected`. 217 tests, of which 155 run, 35 +everything it printed against a file in `Tests/expected`. 218 tests, of which 156 run, 35 only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image given at all. @@ -447,7 +447,7 @@ the console's line editing was in when it broke twice in two days. ## Fixture Disks: -`Tests/makedisks.sh` builds 28 images with SplitDisk before anything runs, into +`Tests/makedisks.sh` builds 29 images with SplitDisk before anything runs, into `Tests/build/disks`. **That is the point of them.** A SplitBit program reading one of these is being checked against a filesystem written by different code from the same written specification, rather than against itself. diff --git a/Tests/expected/cfgFallback.out b/Tests/expected/cfgFallback.out index c7866da..6a93ae1 100644 --- a/Tests/expected/cfgFallback.out +++ b/Tests/expected/cfgFallback.out @@ -15,6 +15,7 @@ made > cd Notes /Notes> dir 0 files +8 of 32 entries, 308 blocks free /Notes> exit halted Execution halted. diff --git a/Tests/expected/cosmos.out b/Tests/expected/cosmos.out index a755708..34f9082 100644 --- a/Tests/expected/cosmos.out +++ b/Tests/expected/cosmos.out @@ -29,6 +29,7 @@ across.txt 700 empty.txt 0 aName22CharactersLong! 22 12 files +12 of 16 entries, 48 blocks free > > frobnicate I do not know: frobnicate diff --git a/Tests/expected/cosmosBuild.out b/Tests/expected/cosmosBuild.out index 85ff880..5fdf1f4 100644 --- a/Tests/expected/cosmosBuild.out +++ b/Tests/expected/cosmosBuild.out @@ -1,6 +1,7 @@ CosmOS > dir 0 files +0 of 16 entries, 125 blocks free > mkdir Apps made > mkdir Apps/Deep @@ -11,10 +12,12 @@ made Apps Notes 0 files, 2 directories +3 of 16 entries, 125 blocks free > cd Apps /Apps> dir Deep 0 files, 1 directory +3 of 16 entries, 125 blocks free /Apps> mkdir Deep cannot make that: check the path, the name, and whether it is taken /Apps> mkdir /Notes/Deep @@ -23,6 +26,7 @@ made /Notes> dir Deep 0 files, 1 directory +4 of 16 entries, 125 blocks free /Notes> rmdir /Apps cannot remove that: it must be a directory, and empty /Notes> rmdir /Apps/Deep @@ -44,6 +48,7 @@ removed removed > dir 0 files +0 of 16 entries, 125 blocks free > exit halted Execution halted. diff --git a/Tests/expected/cosmosClaim.out b/Tests/expected/cosmosClaim.out index 99456dd..2f17c79 100644 --- a/Tests/expected/cosmosClaim.out +++ b/Tests/expected/cosmosClaim.out @@ -7,6 +7,7 @@ finished Claim.sbx 580 claim.dat 266 2 files +2 of 16 entries, 56 blocks free > exit halted Execution halted. diff --git a/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out index fc08a1c..b4f5894 100644 --- a/Tests/expected/cosmosCrossDisk.out +++ b/Tests/expected/cosmosCrossDisk.out @@ -51,6 +51,7 @@ pass.script 20 holds.script 87 crossed.txt 560 36 files, 2 directories +47 of 64 entries, 1890 blocks free > exit halted Execution halted. diff --git a/Tests/expected/cosmosCwd.out b/Tests/expected/cosmosCwd.out index ddeab11..305da73 100644 --- a/Tests/expected/cosmosCwd.out +++ b/Tests/expected/cosmosCwd.out @@ -4,11 +4,13 @@ Apps A B 0 files, 3 directories +9 of 32 entries, 240 blocks free > cd /A /A> dir Say.sbx 53 notes.txt 25 2 files +9 of 32 entries, 240 blocks free /A> Type notes.txt these are the notes in A finished @@ -29,6 +31,7 @@ Apps A B 0 files, 3 directories +9 of 32 entries, 240 blocks free > cd /B /B> Type ../A/notes.txt these are the notes in A @@ -50,6 +53,7 @@ Apps A B 0 files, 3 directories +9 of 32 entries, 240 blocks free > exit halted Execution halted. diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index b626db8..9688d1b 100644 --- a/Tests/expected/cosmosDrives.out +++ b/Tests/expected/cosmosDrives.out @@ -40,6 +40,7 @@ args.script 64 pass.script 20 holds.script 87 35 files, 2 directories +46 of 64 entries, 1893 blocks free > drive 1 > dir other.txt 28 @@ -47,6 +48,7 @@ notes 2things twoblocks.txt 560 2 files, 2 directories +4 of 32 entries, 503 blocks free > cd /notes /notes> drive 0 > drive 1 diff --git a/Tests/expected/cosmosFault.out b/Tests/expected/cosmosFault.out index e1b15e0..4d5d63c 100644 --- a/Tests/expected/cosmosFault.out +++ b/Tests/expected/cosmosFault.out @@ -57,6 +57,7 @@ args.script 64 pass.script 20 holds.script 87 35 files, 2 directories +46 of 64 entries, 1893 blocks free > exit halted Execution halted. diff --git a/Tests/expected/cosmosFiles.out b/Tests/expected/cosmosFiles.out index 29d19d1..ac1ef38 100644 --- a/Tests/expected/cosmosFiles.out +++ b/Tests/expected/cosmosFiles.out @@ -3,17 +3,21 @@ CosmOS one.txt 13 two.txt 14 2 files +2 of 8 entries, 28 blocks free > rename one.txt first.txt renamed > dir first.txt 13 two.txt 14 2 files +2 of 8 entries, 28 blocks free > delete first.txt gone > dir two.txt 14 1 file +1 of 8 entries, 29 blocks free +the longest run is 28 > delete first.txt no such file > rename two.txt diff --git a/Tests/expected/cosmosFlip.out b/Tests/expected/cosmosFlip.out index dfc4432..f317625 100644 --- a/Tests/expected/cosmosFlip.out +++ b/Tests/expected/cosmosFlip.out @@ -47,6 +47,7 @@ args.script 64 pass.script 20 holds.script 87 35 files, 2 directories +46 of 64 entries, 1893 blocks free > exit halted Execution halted. diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index 6a5fdd3..7daa17d 100644 --- a/Tests/expected/cosmosGrid.out +++ b/Tests/expected/cosmosGrid.out @@ -47,6 +47,7 @@ args.script 64 pass.script 20 holds.script 87 35 files, 2 directories +46 of 64 entries, 1893 blocks free > exit halted Execution halted. diff --git a/Tests/expected/cosmosInvoke.out b/Tests/expected/cosmosInvoke.out index 5ab1c29..3597e1d 100644 --- a/Tests/expected/cosmosInvoke.out +++ b/Tests/expected/cosmosInvoke.out @@ -17,6 +17,7 @@ notes.sbx 21 hello.sh 38 Apps 6 files, 1 directory +8 of 16 entries, 54 blocks free > Greet a program with no extension it says: a program with no extension finished diff --git a/Tests/expected/cosmosMonitor.out b/Tests/expected/cosmosMonitor.out index 3180d84..355eb3b 100644 --- a/Tests/expected/cosmosMonitor.out +++ b/Tests/expected/cosmosMonitor.out @@ -133,6 +133,7 @@ args.script 64 pass.script 20 holds.script 87 35 files, 2 directories +46 of 64 entries, 1893 blocks free > exit halted Execution halted. diff --git a/Tests/expected/cosmosMonitorRun.out b/Tests/expected/cosmosMonitorRun.out index 474890a..6a870da 100644 --- a/Tests/expected/cosmosMonitorRun.out +++ b/Tests/expected/cosmosMonitorRun.out @@ -52,6 +52,7 @@ args.script 64 pass.script 20 holds.script 87 35 files, 2 directories +46 of 64 entries, 1893 blocks free > exit halted Execution halted. diff --git a/Tests/expected/cosmosRamDisk.out b/Tests/expected/cosmosRamDisk.out index ca77a2c..d0bde38 100644 --- a/Tests/expected/cosmosRamDisk.out +++ b/Tests/expected/cosmosRamDisk.out @@ -2,12 +2,14 @@ CosmOS > drive 1 > dir 0 files +0 of 128 entries, 2031 blocks free > Copy 0:/Say.sbx 1:/Say.sbx copied finished > dir Say.sbx 156 1 file +1 of 128 entries, 2030 blocks free > drive 1 > exit diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index f84b9ad..5c6dd4a 100644 --- a/Tests/expected/cosmosRun.out +++ b/Tests/expected/cosmosRun.out @@ -40,6 +40,7 @@ args.script 64 pass.script 20 holds.script 87 35 files, 2 directories +46 of 64 entries, 1893 blocks free > load load what? > load nosuch.sbx diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index e43deec..bd4a3a9 100644 --- a/Tests/expected/cosmosSlowDisk.out +++ b/Tests/expected/cosmosSlowDisk.out @@ -38,6 +38,7 @@ args.script 64 pass.script 20 holds.script 87 35 files, 2 directories +46 of 64 entries, 1893 blocks free > load Say.sbx loaded, starting at 5000 > run the disk took its time diff --git a/Tests/expected/cosmosSpace.out b/Tests/expected/cosmosSpace.out new file mode 100644 index 0000000..8ab4f34 --- /dev/null +++ b/Tests/expected/cosmosSpace.out @@ -0,0 +1,55 @@ +CosmOS +> dir +greet.sbx 211 +hello.sbx 53 +Life.sbx 1396 +Snake.sbx 2164 +Keys.sbx 664 +Say.sbx 156 +Where.sbx 827 +Break.sbx 149 +Grid.sbx 571 +Press.sbx 872 +Mode.sbx 48 +Flip.sbx 173 +Sprite.sbx 442 +Depth.sbx 672 +Packages +Pad.sbx 264 +Crash.sbx 632 +vars.script 50 +blocks.script 343 +loops.script 272 +tune.sbx 318 +Play.sbx 2526 +notes.txt 21 +Apps +hi.script 121 +bad.script 45 +plain.script 24 +cross.script 280 +nonl.script 38 +outer.script 376 +inner.script 44 +loop.script 35 +hush.script 42 +aloud.script 59 +args.script 64 +pass.script 20 +holds.script 87 +35 files, 2 directories +46 of 64 entries, 1893 blocks free +> drive 1 +> dir +f1.txt 1500 +f3.txt 1500 +f5.txt 1500 +f6.txt 1500 +4 files +4 of 16 entries, 37 blocks free +the longest run is 25 +> drive 0 +> exit +halted +Execution halted. +[exit 0] diff --git a/Tests/expected/cosmosSprite.out b/Tests/expected/cosmosSprite.out index 9882fe8..31be263 100644 --- a/Tests/expected/cosmosSprite.out +++ b/Tests/expected/cosmosSprite.out @@ -47,6 +47,7 @@ args.script 64 pass.script 20 holds.script 87 35 files, 2 directories +46 of 64 entries, 1893 blocks free > exit halted Execution halted. diff --git a/Tests/expected/cosmosTree.out b/Tests/expected/cosmosTree.out index e6adb1c..6c3b3aa 100644 --- a/Tests/expected/cosmosTree.out +++ b/Tests/expected/cosmosTree.out @@ -3,6 +3,7 @@ CosmOS Apps rooted.txt 21 1 file, 1 directory +5 of 32 entries, 248 blocks free > load /Apps/Say.sbx loaded, starting at 5000 > run the shallow one @@ -43,6 +44,8 @@ gone Apps rooted.txt 21 1 file, 1 directory +4 of 32 entries, 249 blocks free +the longest run is 248 > exit halted Execution halted. diff --git a/Tests/expected/cosmosTreeWrite.out b/Tests/expected/cosmosTreeWrite.out index 0791ab2..4f6970d 100644 --- a/Tests/expected/cosmosTreeWrite.out +++ b/Tests/expected/cosmosTreeWrite.out @@ -3,6 +3,7 @@ CosmOS Folder Edit.sbx 2243 1 file, 1 directory +3 of 16 entries, 244 blocks free > load Edit.sbx loaded, starting at 5000 > run note.txt @@ -20,6 +21,7 @@ Folder Edit.sbx 2243 note.txt 67 2 files, 1 directory +4 of 16 entries, 243 blocks free > exit halted Execution halted. diff --git a/Tests/expected/onceAsked.out b/Tests/expected/onceAsked.out index acb33dd..49c0e8d 100644 --- a/Tests/expected/onceAsked.out +++ b/Tests/expected/onceAsked.out @@ -6,6 +6,7 @@ finished System Apps 0 files, 2 directories +8 of 32 entries, 305 blocks free > exit halted Execution halted. diff --git a/Tests/expected/romBoot.out b/Tests/expected/romBoot.out index bf05c04..dcab782 100644 --- a/Tests/expected/romBoot.out +++ b/Tests/expected/romBoot.out @@ -13,6 +13,7 @@ made > cd Notes /Notes> dir 0 files +7 of 32 entries, 309 blocks free /Notes> exit halted Execution halted. diff --git a/Tests/expected/selfBoot.out b/Tests/expected/selfBoot.out index bf05c04..dcab782 100644 --- a/Tests/expected/selfBoot.out +++ b/Tests/expected/selfBoot.out @@ -13,6 +13,7 @@ made > cd Notes /Notes> dir 0 files +7 of 32 entries, 309 blocks free /Notes> exit halted Execution halted. diff --git a/Tests/input/cosmosSpace.in b/Tests/input/cosmosSpace.in new file mode 100644 index 0000000..28ad846 --- /dev/null +++ b/Tests/input/cosmosSpace.in @@ -0,0 +1,5 @@ +dir +drive 1 +dir +drive 0 +exit diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index bcb7b85..b5ba28c 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -230,6 +230,26 @@ printf '#! script\nfor a in 1 2\n for b in x y\n echo $a$b\n end\nend\nset printf 'this is not a program' > notes.txt "$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null +# ---- A disk with holes in it ---- +# +# Six files laid down one after another, then the second and the fourth taken away. Files are +# laid down CONTIGUOUSLY on this format, so what is left is thirty seven free blocks in three +# pieces - two of six between the survivors and twenty five at the end - and the largest file +# that will fit is twenty five blocks, not thirty seven. +# +# That gap between "how much is free" and "how much will fit" is the whole reason a listing +# says the longest run, and it is invisible on every other disk here: a disk that has only +# been appended to has one gap, at the end, and the two numbers are the same. Deleting is what +# fragments a contiguous store, so a disk that has never had anything deleted from it cannot +# demonstrate any of this. +"$TOOL" format "$DISKS/holes.img" 64 2 >/dev/null +python3 -c "open('holey.txt','w').write('x' * 1500)" +for i in 1 2 3 4 5 6; do + "$TOOL" put "$DISKS/holes.img" holey.txt "f$i.txt" >/dev/null +done +"$TOOL" delete "$DISKS/holes.img" f2.txt >/dev/null +"$TOOL" delete "$DISKS/holes.img" f4.txt >/dev/null + # ---- A second disk, for the second drive ---- # # Deliberately nothing like the first: its own file and its own directory, so that a test diff --git a/Tests/manifest b/Tests/manifest index 5788354..6023826 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -1033,6 +1033,24 @@ cosmosWhere | CosmOS/Source/cosmos.asm | run | cosmosWhe # a name twice. Then the same walk in /Apps, because a walk that only ever ran at the root # would not have proved it walks where you are. cosmosWalk | CosmOS/Source/cosmos.asm | run | cosmosWalk.in | 200000000 | disks/cosmos.img +# ---- What a listing says about the disk it is listing ---- +# +# dir said what was there and nothing about what was left. SplitDisk has printed the free +# figure since it was written, so the machine's own listing was the poorer of the two +# implementations at describing the same disk. +# +# COUNTED RATHER THAN ASKED. The superblock keeps a free count and sbfs.asm calls it "a note +# rather than the truth" in three places; this reads the whole directory table instead. The +# first version added the blocks up as the LISTING walked past them, which cost nothing extra +# and was wrong - that walk stops only on entries in the working directory, so the same disk +# came out as 1,996 blocks free from the root and 2,025 from /Apps. Listing both directories +# here is what would say so again. +# +# And then a disk with holes in it, which is the case the longest run exists for: thirty seven +# blocks free in three pieces, so the largest file that will fit is twenty five. Said only +# when it differs from the free total, so the first listing is silent about it and the second +# is not - a line that appears only when something is wrong is a line somebody reads. +cosmosSpace | CosmOS/Source/cosmos.asm | run | cosmosSpace.in | 200000000 | disks/cosmos.img+disks/holes.img # ---- Starting itself ---- # # /System/Boot/startup.sh runs before anybody can type. This one is also the check on #quiet