#!/usr/bin/env bash # Builds the disk images the tests read from. # # These are made with SplitDisk, which is the other implementation of the same format. # That is the point of them: a SplitBit program reading one of these is being checked # against something written by different code from a written specification, rather than # against itself. # # Written by Anachronaut set -eu # Made absolute before anything else. This script cds into its own working directory part # of the way down, so a relative build path would be measured from the wrong place from # there on - and the way that failed was not an error but a disk quietly missing some of # the files it was supposed to have, which is a much worse thing to debug. mkdir -p "$1" BUILD="$(cd "$1" && pwd)" ROOT="$(cd "$(dirname "$0")/.." && pwd)" TOOL="$ROOT/SplitDisk" DISKS="$BUILD/disks" WORK="$BUILD/.diskwork" [ -x "$TOOL" ] || { echo "SplitDisk is not built."; exit 1; } mkdir -p "$DISKS" "$WORK" # Two directory blocks, so that a file can be put beyond the first one and the walk from # block to block gets exercised rather than assumed. "$TOOL" format "$DISKS/sbfs.img" 64 2 >/dev/null cd "$WORK" printf 'hello from a file' > greeting.txt # Eight files fill the first directory block exactly, so everything after this lands in # the second one. for i in 1 2 3 4 5 6 7 8; do printf 'filler %d' "$i" > "filler$i.txt"; done # Longer than a block, so reading it has to cross from one to the next. The pattern # repeats every twenty six bytes, which makes a misplaced block obvious to read. awk 'BEGIN { for (i = 0; i < 700; i++) printf "%c", 65 + (i % 26) }' > across.txt : > empty.txt printf 'exactly twenty two!!!!' > longname.txt "$TOOL" put "$DISKS/sbfs.img" greeting.txt >/dev/null for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/null; done "$TOOL" put "$DISKS/sbfs.img" across.txt >/dev/null "$TOOL" put "$DISKS/sbfs.img" empty.txt >/dev/null "$TOOL" put "$DISKS/sbfs.img" longname.txt aName22CharactersLong! >/dev/null # A disk with a loadable program on it. The program is assembled here rather than kept as # bytes, so that what gets loaded is always built from the source beside it. "$TOOL" format "$DISKS/load.img" 64 2 >/dev/null # The assembler writes a loadable program itself, because the source says where it goes. "$ROOT/Assembler" "$ROOT/Programs/Loader/loadable.asm" -o "$WORK/hello.sbx" >/dev/null "$TOOL" put "$DISKS/load.img" "$WORK/hello.sbx" >/dev/null # A disk for CosmOS. greet.sbx asks the system for everything it does rather than talking # to the hardware itself, so loading and running it exercises the whole path: the loader, # the vector table, the service handlers, and giving the machine back at the end. # # hello.sbx is the opposite case, and that is why it is here: it is the original # hello.asm, written before any of this existed, and it still writes straight to port # 0x00 rather than calling osPrintString. A program is allowed to reach past the system # to the hardware, so something has to check that one still gives the machine back. # # notes.txt is there so that loading something that is not a program can be tried too. # 512K, and four directory blocks for 32 files. It was 64K and 16, which was ample for a # disk that held programs and nothing else - but the native assembler reads SOURCE from # here, and the CosmOS sources alone are 104,142 bytes against the 65,536 a 256 block disk # holds. A machine that is going to assemble itself has to be able to hold its own source. "$TOOL" format "$DISKS/cosmos.img" 2048 4 >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/greet.asm" -o "$WORK/greet.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/greet.sbx" >/dev/null # Assembled to a different working name so it cannot tread on load.img's hello.sbx above, # then put under the name the shell asks for. "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/hello.asm" -o "$WORK/appHello.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/appHello.sbx" hello.sbx >/dev/null # Life.sbx is the one that had to be taught to stop. It runs to a still life and returns # on its own, so the test needs no cycle limit: whether it ends is the thing being checked # and a limit would hide the answer by supplying one. "$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Life.asm" -o "$WORK/Life.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Life.sbx" >/dev/null # Snake.sbx is the one that is played rather than watched. It reads the console a key at a # time without ever waiting for one, so a script of moves drives it a move to a frame, and # what is recorded is a whole game: turning, eating, growing, and running into a wall. "$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Snake.asm" -o "$WORK/Snake.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Snake.sbx" >/dev/null # Keys.sbx brings a vector of its own, which is what the version two format exists for. It # is the only program here the system has to install anything for, so it is what says the # whole path works: written into the file, installed at run, taken back out at exit. "$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Keys.asm" -o "$WORK/Keys.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Keys.sbx" >/dev/null # Say.sbx is the first program that can be told anything. Everything before it did the same # thing however it was started. "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Say.sbx" >/dev/null # Break.sbx stops itself twice and shows what the registers were each time. It needs no disk # of its own: it only prints. "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Break.asm" -o "$WORK/Break.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Break.sbx" >/dev/null printf 'this is not a program' > notes.txt "$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null # ---- Scripts, including the ones that are meant to go wrong ---- # # Built here rather than committed, because two of them are about BYTE POSITIONS and a # committed file is one an editor can helpfully repair. cross.script puts a command across # the 256 byte boundary between two blocks, and nonl.script ends without a newline; both # were real faults before they were checks, and both are invisible in a text editor. printf '#! script\n; A comment, which never reaches the shell.\necho saying what it is doing\nSay from a script\n\necho\nSay and again\n' > hi.script "$TOOL" put "$DISKS/cosmos.img" hi.script >/dev/null # Stops at the bad line, so "after" must not appear. printf '#! script\nSay before\nnosuchcommand\nSay after\n' > bad.script "$TOOL" put "$DISKS/cosmos.img" bad.script >/dev/null # No #! on the front, so it is a text file and not a script. printf 'Say this has no shebang\n' > plain.script "$TOOL" put "$DISKS/cosmos.img" plain.script >/dev/null # A command that starts in the first block and finishes in the second. python3 -c " head = '#! script\n' pad = '' while len(head) + len(pad) < 246: pad += '; pad\n' open('cross.script','w').write(head + pad + 'Say across the block boundary\n') " "$TOOL" put "$DISKS/cosmos.img" cross.script >/dev/null # The last line has no newline after it and still has to run. printf '#! script\nSay with no newline after me' > nonl.script "$TOOL" put "$DISKS/cosmos.img" nonl.script >/dev/null # ---- One script inside another ---- # # outer.script resumes in its SECOND block, which is the case the whole design turns on: the # inner script reads its own block into the one buffer there is, so coming back means reading # the outer one's block again and landing on the byte it left off at. Generated, because # where the do line falls is the entire point and no editor should be able to move it. python3 -c " head = '#! script\n' pad = '' while len(head) + len(pad) < 300: pad += '; pad\n' open('outer.script','w').write(head + pad + 'echo outer in block one\ndo inner.script\necho outer resumed in block one\n') " "$TOOL" put "$DISKS/cosmos.img" outer.script >/dev/null printf '#! script\necho inner one\necho inner two\n' > inner.script "$TOOL" put "$DISKS/cosmos.img" inner.script >/dev/null # A script that runs itself, which is what the depth limit is for. printf '#! script\necho self\ndo loop.script\n' > loop.script "$TOOL" put "$DISKS/cosmos.img" loop.script >/dev/null # A disk of its own for the writing test, with one file already on it so that what it # writes has to be placed somewhere that does not tread on what is there. "$TOOL" format "$DISKS/write.img" 32 1 >/dev/null printf 'already here' > here.txt "$TOOL" put "$DISKS/write.img" here.txt >/dev/null # A disk of its own for the editing test, which deletes and renames things and would # otherwise leave the writing test's disk looking nothing like the writing test expects. # It starts with one file on it so that a document written here has to be placed around # something, and so that the disk can be compared afterwards against one that never had # any of it: the metadata should come back exactly as it was. "$TOOL" format "$DISKS/edit.img" 32 1 >/dev/null "$TOOL" put "$DISKS/edit.img" here.txt >/dev/null # A disk for the shell's delete and rename, which is its own because those change what is # on it. A fixture whose name has a directory in it is used as it stands rather than being # made fresh per test, so a test that writes to a shared one would quietly change what # every test after it sees. "$TOOL" format "$DISKS/files.img" 32 1 >/dev/null printf 'the first one' > one.txt printf 'the second one' > two.txt "$TOOL" put "$DISKS/files.img" one.txt >/dev/null "$TOOL" put "$DISKS/files.img" two.txt >/dev/null # A disk for the editor, holding nothing but the editor. Its own, because the whole point # of it is that it writes: a document made on a shared fixture would turn up in the file # listing of every test that came after it. "$TOOL" format "$DISKS/editor.img" 256 2 >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Edit.asm" -o "$WORK/Edit.sbx" >/dev/null "$TOOL" put "$DISKS/editor.img" "$WORK/Edit.sbx" >/dev/null # A disk that has directories on it AND gets written to, which nothing else covers: every # other version two fixture is read only, and every fixture that is written to is flat. # Making a file runs the allocator over a directory entry, and a directory has no blocks, # so it must be in nobody's way while a run of free ones is looked for. Its own disk, for # the same reason the editor has one: what it writes would otherwise turn up in the # listing of every test that came after it. "$TOOL" format "$DISKS/treewrite.img" 256 2 >/dev/null "$TOOL" mkdir "$DISKS/treewrite.img" /Folder >/dev/null "$TOOL" mkdir "$DISKS/treewrite.img" /Folder/Inner >/dev/null "$TOOL" put "$DISKS/treewrite.img" "$WORK/Edit.sbx" >/dev/null # A disk for the file services, holding nothing but the program that exercises them. Its # own, because that program writes: it tidies up after itself, but a run that stopped part # way would leave a document behind on a fixture every later test reads. "$TOOL" format "$DISKS/services.img" 256 2 >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Files.asm" -o "$WORK/Files.sbx" >/dev/null "$TOOL" put "$DISKS/services.img" "$WORK/Files.sbx" >/dev/null # A disk for streaming, and the only one here that has to be big: the file on it is bigger # than the machine's Data Memory, which is the entire point of the services it checks. # # THE FILE IS GENERATED RATHER THAN TAKEN FROM THE REPOSITORY. Concatenating the CosmOS # sources would be a truer picture of what streaming is for, and would change the recorded # checksum every time a line of CosmOS was edited - so a real difference would arrive in a # crowd of meaningless ones, which is the same trap the cycle counts used to set. This is # 84000 bytes, which is 328 whole blocks and 32 bytes over, so the short block at the end # is exercised rather than assumed. "$TOOL" format "$DISKS/stream.img" 1024 2 >/dev/null awk 'BEGIN { for (i = 0; i < 4000; i++) printf "streaming line %05d\n", i }' > big.txt awk 'BEGIN { for (i = 0; i < 50; i++) printf "small %05d\n", i }' > small.txt "$TOOL" put "$DISKS/stream.img" big.txt >/dev/null "$TOOL" put "$DISKS/stream.img" small.txt >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Stream.asm" -o "$WORK/Stream.sbx" >/dev/null "$TOOL" put "$DISKS/stream.img" "$WORK/Stream.sbx" >/dev/null # A disk for Type. The first file crosses several blocks and deliberately has no zero byte # for the application to mistake for an end marker. The second proves that zero blocks is # a valid empty file rather than an error. "$TOOL" format "$DISKS/type.img" 64 2 >/dev/null printf 'first line\nsecond line\n' > readable.txt awk 'BEGIN { for (i = 0; i < 30; i++) printf "line %02d: ABCDEFGHIJKLMNOPQRSTUVWXYZ\n", i }' >> readable.txt : > empty.txt "$TOOL" put "$DISKS/type.img" readable.txt >/dev/null "$TOOL" put "$DISKS/type.img" empty.txt >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Type.asm" -o "$WORK/Type.sbx" >/dev/null "$TOOL" put "$DISKS/type.img" "$WORK/Type.sbx" >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/More.asm" -o "$WORK/More.sbx" >/dev/null "$TOOL" put "$DISKS/type.img" "$WORK/More.sbx" >/dev/null # A disk for Copy and Compare. The files mark all three shapes a streamed tool has to # distinguish: no blocks, an exact whole block, and a part block. The large pair says the # programs do not quietly depend on Data Memory being able to hold either input, and they # differ at one byte while keeping the same length so Compare has to inspect content rather # than stopping at the descriptor. "$TOOL" format "$DISKS/copycompare.img" 1024 4 >/dev/null "$TOOL" mkdir "$DISKS/copycompare.img" /Apps >/dev/null "$TOOL" mkdir "$DISKS/copycompare.img" /Input >/dev/null "$TOOL" mkdir "$DISKS/copycompare.img" /Output >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Copy.asm" -o "$WORK/Copy.sbx" >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Compare.asm" -o "$WORK/Compare.sbx" >/dev/null "$TOOL" put "$DISKS/copycompare.img" "$WORK/Copy.sbx" /Apps/Copy.sbx >/dev/null "$TOOL" put "$DISKS/copycompare.img" "$WORK/Compare.sbx" /Apps/Compare.sbx >/dev/null : > copy-empty.dat awk 'BEGIN { for (i = 0; i < 256; i++) printf "%c", 65 + (i % 26) }' > copy-exact.dat awk 'BEGIN { for (i = 0; i < 257; i++) printf "%c", 65 + (i % 26) }' > copy-tail.dat awk 'BEGIN { for (i = 0; i < 84000; i++) printf "%c", 65 + (i % 26) }' > copy-large.dat awk 'BEGIN { for (i = 0; i < 84000; i++) printf "%c", (i == 65535 ? 33 : 65 + (i % 26)) }' \ > copy-different.dat "$TOOL" put "$DISKS/copycompare.img" copy-empty.dat /Input/empty.dat >/dev/null "$TOOL" put "$DISKS/copycompare.img" copy-exact.dat /Input/exact.dat >/dev/null "$TOOL" put "$DISKS/copycompare.img" copy-tail.dat /Input/tail.dat >/dev/null "$TOOL" put "$DISKS/copycompare.img" copy-large.dat /Input/large.dat >/dev/null "$TOOL" put "$DISKS/copycompare.img" copy-different.dat /Input/different.dat >/dev/null # A disk with directories on it, which is the whole of what version two adds. Built by the # host tool, because at this rung the machine can read a tree and not yet make one - and # that split is the point: the two implementations are checked against each other rather # than each against itself. # # ONE NAME IS PUT DOWN TWICE, in two directories, because a name meaning something # different in each place is what directories are for and what a flat filesystem cannot do. # # The two are DIFFERENT PROGRAMS on purpose. Say echoes whatever it is given and hello # prints one fixed line, so which of them ran is written in the output. Two copies of one # program would have been the easier fixture and a useless one: resolving to the wrong # Say.sbx would have printed exactly what resolving to the right one prints, and the test # would have passed while the path walk was ignoring directories entirely. "$TOOL" format "$DISKS/tree.img" 256 4 >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/hello.asm" -o "$WORK/treeHello.sbx" >/dev/null "$TOOL" mkdir "$DISKS/tree.img" /Apps >/dev/null "$TOOL" mkdir "$DISKS/tree.img" /Apps/Deep >/dev/null "$TOOL" put "$DISKS/tree.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null "$TOOL" put "$DISKS/tree.img" "$WORK/treeHello.sbx" /Apps/Deep/Say.sbx >/dev/null printf 'this is not a program' > rooted.txt "$TOOL" put "$DISKS/tree.img" rooted.txt >/dev/null # A disk for the program that tries to commit a file bigger than it reserved. Its own, # because what it leaves behind is a file whose size is the thing being checked. "$TOOL" format "$DISKS/claim.img" 64 2 >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Claim.asm" -o "$WORK/Claim.sbx" >/dev/null "$TOOL" put "$DISKS/claim.img" "$WORK/Claim.sbx" >/dev/null # A blank disk for the machine to build a tree on itself. It starts as a version ONE disk # with nothing at all on it, because half of what this checks is that making the first # directory raises the version - the number says what is on a disk rather than what made # it, so a disk with no directories is flat whoever formatted it. "$TOOL" format "$DISKS/build.img" 128 2 >/dev/null # A disk for moving about on. Two directories hold a file of THE SAME NAME with different # text in it, which is the fixture the working directory needs: "notes.txt" has to mean a # different file from each of them, and the only way to see that it does is for the two to # say different things. # # It is also what checks the remembered file is dropped when the machine moves. That cache # is keyed on the path as somebody typed it, so "notes.txt" is the same key in both places # and nothing about the entry it remembers looks wrong - it is the kind of stale that is # believed rather than noticed. # # Type and Say go in /Apps, so that a program can be started from anywhere; hello goes in # /A under the name Say.sbx, so that where you are is visibly tried before /Apps. "$TOOL" format "$DISKS/cwd.img" 256 4 >/dev/null "$TOOL" mkdir "$DISKS/cwd.img" /Apps >/dev/null "$TOOL" mkdir "$DISKS/cwd.img" /A >/dev/null "$TOOL" mkdir "$DISKS/cwd.img" /B >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Type.asm" -o "$WORK/Type.sbx" >/dev/null "$TOOL" put "$DISKS/cwd.img" "$WORK/Type.sbx" /Apps/Type.sbx >/dev/null "$TOOL" put "$DISKS/cwd.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null "$TOOL" put "$DISKS/cwd.img" "$WORK/treeHello.sbx" /A/Say.sbx >/dev/null printf 'these are the notes in A\n' > notesA.txt printf 'and these are the very different notes in B\n' > notesB.txt "$TOOL" put "$DISKS/cwd.img" notesA.txt /A/notes.txt >/dev/null "$TOOL" put "$DISKS/cwd.img" notesB.txt /B/notes.txt >/dev/null # Wander is the only thing that can move the machine from inside a program, which makes it # the only thing that can check the shell puts the working directory back afterwards. "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Wander.asm" -o "$WORK/Wander.sbx" >/dev/null "$TOOL" put "$DISKS/cwd.img" "$WORK/Wander.sbx" /Apps/Wander.sbx >/dev/null # A disk for invoking a program by typing its name. Four files, each there to say one # thing about how a typed word turns into a file name. # # dir.sbx is a working program under the name of a built-in command, which is the only way # to check that the built-ins really are tried first. If the search ever moved ahead of the # dispatch chain, this disk would start answering "dir" with a program. # # notes.sbx is text under a program's name, so that a file that IS found and IS NOT a # program can be told apart from a word that names nothing. notes.txt is the same text # under its own name, which no typed word can reach: "notes.txt" looks for notes.txt.sbx. "$TOOL" format "$DISKS/invoke.img" 64 2 >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null "$TOOL" put "$DISKS/invoke.img" "$WORK/Say.sbx" >/dev/null "$TOOL" put "$DISKS/invoke.img" "$WORK/Say.sbx" dir.sbx >/dev/null printf 'this is not a program' > notes.txt "$TOOL" put "$DISKS/invoke.img" notes.txt >/dev/null "$TOOL" put "$DISKS/invoke.img" notes.txt notes.sbx >/dev/null # A disk for the assembler that runs on the machine. It holds a source file and the two # programs that check the front end - the reader on its own and the tokenizer on its own - # because a fault in either of those would otherwise turn up much later as a mysterious # wrong byte in an output file. # # hello.asm is here rather than something written for the occasion because it is the # oldest program in the repository: the first thing this machine ever ran is the first # thing it assembles for itself. "$TOOL" format "$DISKS/asm.img" 2048 4 >/dev/null cp "$ROOT/Programs/Examples/hello.asm" hello.asm "$TOOL" put "$DISKS/asm.img" hello.asm >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" -I "$ROOT/Programs/CosmOS/Assembler" \ "$ROOT/Programs/CosmOS/Assembler/readTest.asm" -o "$WORK/readTest.sbx" >/dev/null "$TOOL" put "$DISKS/asm.img" "$WORK/readTest.sbx" >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" -I "$ROOT/Programs/CosmOS/Assembler" \ "$ROOT/Programs/CosmOS/Assembler/tokenTest.asm" -o "$WORK/tokenTest.sbx" >/dev/null "$TOOL" put "$DISKS/asm.img" "$WORK/tokenTest.sbx" >/dev/null # A tree deeper than the prompt can print, for the buffer that used to walk off the front # of itself. Eight directories of twenty two characters is 184 characters of path, and the # prompt is built backwards into 127 bytes - so before this was bounded, the walk wrote # down past the start of the buffer and over the shell's own command names. The word # "exit" was the first casualty, five bytes below. # # MADE HERE RATHER THAN BY THE TEST, because a path this long cannot be given to mkdir in # one piece: naming it is capped well below what walking into it a level at a time can # reach, which is exactly why the depth is unbounded in the first place. "$TOOL" format "$DISKS/deep.img" 512 8 >/dev/null DEEPPATH="" for i in 1 2 3 4 5 6 7 8; do DEEPPATH="$DEEPPATH/abcdefghijklmnopqrst0$i" "$TOOL" mkdir "$DISKS/deep.img" "$DEEPPATH" >/dev/null done # A disk whose superblock claims a directory bigger than the parent field can name. Entry # 65535 has no parent number - index plus one wraps to zero, which is the root - so # anything created inside it lands in the root instead while the tool reports success. # # THE CLAIM IS ALL THAT IS NEEDED, so this is a small disk that lies rather than a sixteen # megabyte one that tells the truth. Mounting reads block 0 and gets as far as the # geometry, which is where it is refused. SplitDisk will not make one of these any more, # so the superblock is written by hand. "$TOOL" format "$DISKS/bigdir.img" 64 2 >/dev/null printf '\x20\x00' | dd of="$DISKS/bigdir.img" bs=1 seek=10 conv=notrunc status=none # A disk that can be started: stage one reads the live boot slot into Program Memory and # jumps into it, and each slot holds a different payload so that choosing between them is # visible rather than assumed. # # The payload is written RAW. A loadable program carries a sixteen byte header saying where # its pieces go, and stage one does not read headers - it reads blocks, which is the whole # point of keeping it small enough to put in a ROM. "$TOOL" format "$DISKS/chain.img" 256 4 4 >/dev/null "$ROOT/Assembler" "$ROOT/Programs/Boot/slotTest.asm" -o "$WORK/slotTest.sbx" >/dev/null tail -c +17 "$WORK/slotTest.sbx" > "$WORK/slotTest.raw" "$TOOL" boot "$DISKS/chain.img" "$WORK/slotTest.raw" 0 >/dev/null # The other slot says something else, so a test that reads "booted" is reading slot zero # rather than reading whatever happens to be in Program Memory. sed 's/INIA 0x62 ; "b"/INIA 0x6F ; "o"/; s/INIA 0x74 ; "t"/INIA 0x68 ; "h"/' \ "$ROOT/Programs/Boot/slotTest.asm" > "$WORK/otherSlot.asm" "$ROOT/Assembler" "$WORK/otherSlot.asm" -o "$WORK/otherSlot.sbx" >/dev/null tail -c +17 "$WORK/otherSlot.sbx" > "$WORK/otherSlot.raw" "$TOOL" boot "$DISKS/chain.img" "$WORK/otherSlot.raw" 1 >/dev/null # And the same disk with the other slot chosen, so both are exercised. cp "$DISKS/chain.img" "$DISKS/chainAlt.img" "$TOOL" bootslot "$DISKS/chainAlt.img" 1 >/dev/null # And a slot whose payload has a Data Segment of its own. Stage one places Program Memory # and nothing else, so the payload copies its data down from just past its own code - which # is the mechanism the real second stage will need, since sbfs.asm has variables and a # string it compares against. "$TOOL" format "$DISKS/chainData.img" 256 4 4 >/dev/null "$ROOT/Assembler" "$ROOT/Programs/Boot/slotData.asm" -o "$WORK/slotData.sbx" >/dev/null tail -c +17 "$WORK/slotData.sbx" > "$WORK/slotData.raw" "$TOOL" boot "$DISKS/chainData.img" "$WORK/slotData.raw" 0 >/dev/null # ---- A disk the machine can start itself from ---- # # Stage one in the emulator's hands, stage two in a boot slot, and the system as an # ordinary file. Nothing here is a boot-specific format: /System/cosmos.bin is the same # SPBT image the emulator has always been handed directly, which is what makes a program # that wants no operating system startable the same way. # ---- Forty blocks a slot, the same as a disk that ships ---- # # It was thirty-two, which was ten thousand bytes of headroom when stage two was four # thousand bytes and none at all when it reached 8,234. A fixture tighter than the thing it # stands in for is a fixture that fails on a change the real disk would have taken, and the # failure says "the boot slot is too small" rather than what actually grew. "$TOOL" format "$DISKS/selfboot.img" 512 4 40 >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/Boot/stage2.asm" -o "$WORK/stage2.sbx" >/dev/null tail -c +17 "$WORK/stage2.sbx" > "$WORK/stage2.raw" "$TOOL" boot "$DISKS/selfboot.img" "$WORK/stage2.raw" 0 >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Source/cosmos.asm" -o "$WORK/bootCosmos.bin" >/dev/null "$ROOT/Assembler" "$ROOT/Programs/Boot/bare.asm" -o "$WORK/bare.bin" >/dev/null "$TOOL" mkdir "$DISKS/selfboot.img" /System >/dev/null "$TOOL" mkdir "$DISKS/selfboot.img" /System/Boot >/dev/null "$TOOL" put "$DISKS/selfboot.img" "$WORK/bootCosmos.bin" /System/Boot/cosmos.bin >/dev/null "$TOOL" put "$DISKS/selfboot.img" "$WORK/bare.bin" /System/Boot/bare.bin >/dev/null "$TOOL" mkdir "$DISKS/selfboot.img" /Apps >/dev/null "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Files.asm" -o "$WORK/bootFiles.sbx" >/dev/null "$TOOL" put "$DISKS/selfboot.img" "$WORK/bootFiles.sbx" /Apps/Files.sbx >/dev/null # And one with no system on it, so that a second stage which cannot find what to start # says so rather than jumping somewhere. "$TOOL" format "$DISKS/nosystem.img" 512 4 40 >/dev/null "$TOOL" boot "$DISKS/nosystem.img" "$WORK/stage2.raw" 0 >/dev/null # ---- Configuration choosing what starts ---- # # The same disk three ways, differing only in /System/Boot/boot.cfg. Nothing else about # any of them changes, which is what makes these tests of the FILE rather than of the # machinery under it. for variant in bare broken fallback; do cp "$DISKS/selfboot.img" "$DISKS/cfg$variant.img" done # A bare metal image, which is the whole point of loading an ordinary boot image: a program # wanting no operating system is a file like any other. printf 'system /System/Boot/bare.bin\n' > "$WORK/bare.cfg" "$TOOL" put "$DISKS/cfgbare.img" "$WORK/bare.cfg" /System/Boot/boot.cfg >/dev/null # Every kind of mistake at once, and a good setting underneath them. The machine still # starts, and says what it could not use on the way. { printf '; a file with things wrong in it\n' printf 'fallbcak /System/Boot/cosmos.bin\n' printf 'system\n' printf 'system /System/Boot/bare.bin\n' } > "$WORK/broken.cfg" "$TOOL" put "$DISKS/cfgbroken.img" "$WORK/broken.cfg" /System/Boot/boot.cfg >/dev/null # A system that is not there, and something to fall back to. { printf 'system /System/Boot/missing.bin\n' printf 'fallback /System/Boot/cosmos.bin\n' } > "$WORK/fallback.cfg" "$TOOL" put "$DISKS/cfgfallback.img" "$WORK/fallback.cfg" /System/Boot/boot.cfg >/dev/null # ---- A system that never reaches a prompt ---- # # The case the boot state exists for. Pointing the configuration at something that dies # before the shell is, without a mark on the disk, a machine that can never be told # anything again - so the loader marks it before handing over and the system clears the # mark on arrival, which makes "did not arrive" a thing the next start can see. "$ROOT/Assembler" "$ROOT/Programs/Boot/wedged.asm" -o "$WORK/wedged.bin" >/dev/null for stage in first second third; do cp "$DISKS/selfboot.img" "$DISKS/wedge$stage.img" "$TOOL" put "$DISKS/wedge$stage.img" "$WORK/wedged.bin" /System/Boot/wedged.bin >/dev/null { printf 'system /System/Boot/wedged.bin\n' printf 'fallback /System/Boot/cosmos.bin\n' } > "$WORK/wedge.cfg" "$TOOL" put "$DISKS/wedge$stage.img" "$WORK/wedge.cfg" /System/Boot/boot.cfg >/dev/null done # Each disk begins where the one before it ended, so the three tests read as three # consecutive starts of one machine without any of them depending on the others running. "$TOOL" bootstate "$DISKS/wedgesecond.img" 1 >/dev/null "$TOOL" bootstate "$DISKS/wedgethird.img" 2 >/dev/null # ---- Settling from the machine itself ---- # # The loop the boot state opens has to be closeable from inside: a machine that says # "settle it to try again" and gives you no way to do so has told you about a problem it # will not let you fix. Settle is a PROGRAM rather than a shell word, which is the shape # this system is growing into - one job each, reached through SWI, replaceable. "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Settle.asm" -o "$WORK/Settle.sbx" >/dev/null cp "$DISKS/wedgethird.img" "$DISKS/settle.img" "$TOOL" put "$DISKS/settle.img" "$WORK/Settle.sbx" /Apps/Settle.sbx >/dev/null # And one already settled, so that saying so is checked as well as doing it. cp "$DISKS/selfboot.img" "$DISKS/settled.img" "$TOOL" put "$DISKS/settled.img" "$WORK/Settle.sbx" /Apps/Settle.sbx >/dev/null # A configuration naming something broken with NOTHING to fall back to. The mark would # otherwise be the only thing between the machine and its own configuration, and refusing # on the strength of it turns "the last start failed" into "no start is permitted". cp "$DISKS/selfboot.img" "$DISKS/nofallback.img" "$TOOL" put "$DISKS/nofallback.img" "$WORK/wedged.bin" /System/Boot/wedged.bin >/dev/null printf 'system /System/Boot/cosmos.bin\n' > "$WORK/lonely.cfg" "$TOOL" put "$DISKS/nofallback.img" "$WORK/lonely.cfg" /System/Boot/boot.cfg >/dev/null "$TOOL" bootstate "$DISKS/nofallback.img" 1 >/dev/null # ---- What a program made of it ---- # # A status is only worth having if it survives the program that set it, so this runs three # in a row and asks after each: one that works, one that is asked wrongly, and one that # fails outright. Status is a PROGRAM and reaches the number through a service, which is # what will let something other than a person read it. "$TOOL" format "$DISKS/status.img" 512 4 >/dev/null "$TOOL" mkdir "$DISKS/status.img" /Apps >/dev/null for app in Status Say Type Settle; do "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/$app.asm" -o "$WORK/$app.sbx" >/dev/null "$TOOL" put "$DISKS/status.img" "$WORK/$app.sbx" /Apps/$app.sbx >/dev/null done # ---- A file with lines longer than the editor's buffer ---- # # Edit copied a file into an 81 byte line buffer with NO BOUND, and the buffer is followed # in memory by the head of the document and the pointer its allocator hands out. A source # file with 94 character lines wrote characters over both: a 31 line file opened as 3, and # opening it a second time walked a list that led back into itself for ever. # # Typing was always safe - osReadLine is told how much room there is - so a new document # behaved and a source file did not, which is exactly how it was found. "$TOOL" format "$DISKS/editlong.img" 512 4 >/dev/null "$TOOL" mkdir "$DISKS/editlong.img" /Apps >/dev/null for app in Edit Status; do "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/$app.asm" -o "$WORK/$app.sbx" >/dev/null "$TOOL" put "$DISKS/editlong.img" "$WORK/$app.sbx" /Apps/$app.sbx >/dev/null done "$TOOL" put "$DISKS/editlong.img" "$ROOT/Programs/CosmOS/Apps/hello.asm" /hello.asm >/dev/null # And one past what it can hold, which must be refused rather than shortened: a line this # cut would be written back cut, and the file damaged by having been looked at. { printf 'short line\n'; printf 'x%.0s' $(seq 1 200); printf '\nanother short one\n'; } \ > "$WORK/toolong.txt" "$TOOL" put "$DISKS/editlong.img" "$WORK/toolong.txt" /long.txt >/dev/null # ---- Starting something else just this once ---- # # A program that owns the whole machine has nowhere to run: it cannot be started from the # shell, because starting it means there is no shell, and pointing boot.cfg at it means a # machine that keeps starting it. Once writes a request the loader reads before boot.cfg # and deletes before it jumps. # # Three disks, each one start further along, so the three tests read as three consecutive # starts of one machine without any of them depending on another having run. "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Once.asm" -o "$WORK/Once.sbx" >/dev/null cp "$DISKS/selfboot.img" "$DISKS/onceasked.img" "$TOOL" put "$DISKS/onceasked.img" "$WORK/Once.sbx" /Apps/Once.sbx >/dev/null # The request, written by hand so that the second and third disks do not depend on the # first test having run to make it. printf 'system /System/Boot/bare.bin\n' > "$WORK/once.cfg" cp "$DISKS/onceasked.img" "$DISKS/oncedue.img" "$TOOL" put "$DISKS/oncedue.img" "$WORK/once.cfg" /System/Boot/once.cfg >/dev/null # And after it has been taken: the request is gone, and nothing else changed. cp "$DISKS/onceasked.img" "$DISKS/onceafter.img" # ---- Starting again without leaving the machine ---- # # The whole loop in one session: ask for a one shot, restart, watch it own the machine. # Before Reboot the only way to restart was to stop the emulator and run it again, which # meant the one thing the machine could not do was the thing Once was written for. "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Reboot.asm" -o "$WORK/Reboot.sbx" >/dev/null cp "$DISKS/onceasked.img" "$DISKS/reboot.img" "$TOOL" put "$DISKS/reboot.img" "$WORK/Reboot.sbx" /Apps/Reboot.sbx >/dev/null