diff --git a/Programs/Boot/stage2.asm b/Programs/Boot/stage2.asm index c160811..e851cb4 100644 --- a/Programs/Boot/stage2.asm +++ b/Programs/Boot/stage2.asm @@ -70,6 +70,57 @@ start: ; ; Read before the image is, because the configuration is staged where the image will go: ; there is one large free area down here and no reason to have two. + ; ---- Something to start just this once ---- + ; + ; A file naming an image to run instead of the configured system, and then to forget + ; about. It is the same format as boot.cfg and is read the same way, because a second + ; format for one setting would be a second format. + ; + ; CONSUMED BY BEING READ, not by working. It is deleted before the jump, which is the + ; only moment there is: after the jump this program does not exist. That also means a + ; one shot that hangs cannot hang twice - the request is already gone, and the mark on + ; the disk brings the ordinary system back. + SETD.0 OncePath + SETD.2 StageAt + LDD.1.2 + INIA 0d4 + CALL cfgLoad + + SETD.0 KeySystem + CALL cfgGet + BNQ noOnce + + SETD.1 BootName + SETD.2 CfgValue + LDD.0.2 + RCAL copyString + + ; Gone before it is used, so that whatever happens next happens only once. + SETD.0 OncePath + CALL sbfsDelete + + SETD.0 OnceText + RCAL say + SETD.0 BootName + RCAL say + RCAL newLine + + ; ---- AND THE MARK IS NOT TOUCHED ---- + ; + ; A one shot is already self limiting: the request was deleted a moment ago, so whatever + ; happens now, the next start reads boot.cfg like any other. Marking it as well would + ; report every successful bare metal boot as a start that never arrived - which is what + ; the first version did, because a program that owns the whole machine has no filesystem + ; to clear a mark with and is not doing anything wrong by not having one. + SETD.0 BootName + RCAL tryImage + + ; It did not start, and there is nothing to fall back to that was asked for. Whatever + ; boot.cfg says is the thing to try, so carry on into it. + SETD.0 OnceFailedText + RCAL say + +noOnce: SETD.0 ConfigPath SETD.2 StageAt LDD.1.2 @@ -541,6 +592,8 @@ NoSystemText: ; Where the configuration lives, and what to start when it does not say. ConfigPath: "/System/Boot/boot.cfg" +OncePath: +"/System/Boot/once.cfg" SystemName: "/System/Boot/cosmos.bin" KeySystem: @@ -570,6 +623,11 @@ NoFallbackText: NothingText: "nothing to start " +OnceText: +"just this once: " +OnceFailedText: +"it did not start, so carrying on +" BootName: #Reserve 0d128 diff --git a/Programs/CosmOS/Apps/Once.asm b/Programs/CosmOS/Apps/Once.asm new file mode 100644 index 0000000..e8f9df4 --- /dev/null +++ b/Programs/CosmOS/Apps/Once.asm @@ -0,0 +1,138 @@ +; Once.asm +; Starts something else next time, and only next time. +; +; > Once /System/Boot/mine.bin +; next start: /System/Boot/mine.bin, once +; > reboot +; +; Writes /System/Boot/once.cfg, which the loader reads before boot.cfg and DELETES BEFORE +; IT JUMPS. So the image runs on the next start and on no other, whatever happens to it - +; a one shot that hangs cannot hang twice, because the request is gone before it ran. +; +; ---- What this is for ---- +; +; 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, which is a poor place to find a mistake. This is the +; missing step: write it, ask for it once, and the system comes back by itself. +; +; The file is the same format as boot.cfg because a second format for one setting would be +; a second format. It says `system` for the same reason. +; +; Written by Anachronaut + +#Include services.asm + +#Program + + #Base 0x4000 + +start: + SETD.0 Wanted + INIB 0d128 + SWI osArgument + MVQA + BNA noName + + SETD.0 Wanted + LDA.0 + BRA noName + + ; The line, built as "system " and then the name. One write, because the file is the + ; whole of the request and half of it would be a request for half a thing. + SETD.0 Prefix + SETD.1 Line + RCAL copyString + SETD.0 Wanted + RCAL copyString + INIA 0x0A + STA.1 + INCD.1 + RSTA + STA.1 + + ; How long it came to, which is what the write is told. + SETD.0 Line + RCAL measure + + SETD.0 OncePath + SETD.1 Line + SWI osFileSave + MVQA + BNA noWrite + + SETD.0 DoneText + SWI osPrintString + SETD.0 Wanted + SWI osPrintString + SETD.0 OnceText + SWI osPrintString + RSTA + SWI osExit + +noName: + SETD.0 Usage + SWI osPrintString + INIA 0d2 + SWI osExit + +noWrite: + SETD.0 NoWriteText + SWI osPrintString + INIA 0d1 + SWI osExit + +; DP0 names a string and DP1 where it goes. DP1 is left on the zero at the end, so one +; string can be written straight after another. +copyString: + LDA.0 + BRA copyDone + STA.1 + INCD.0 + INCD.1 + BRI copyString +copyDone: + RRET + +; DP0 names the line. osFileSave wants a size, and a file of whole blocks and a tail is +; that count with the blocks in A and the tail in B - one block is never full here. +measure: + RSTB +measureLoop: + LDA.0 + BRA measureDone + INCD.0 + MVQB + INIA 0d1 + CCF + ADD + MVQB + BRI measureLoop +measureDone: + RSTA + RRET + +#Data + + #Base 0x2000 + +Prefix: +"system " +Usage: +"once what? try: Once /System/Boot/something.bin +" +DoneText: +"next start: " +OnceText: +", once +" +NoWriteText: +"it would not write +" +OncePath: +"/System/Boot/once.cfg" + +Wanted: + #Reserve 0d129 +Line: + #Reserve 0d160 diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index 248c921..8aeb5de 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -361,6 +361,7 @@ from every assembly file in it. Several are old programs written for the bare ma | Snake | A game. Draws a whole screen with cursor addressing and steers with single keys, asking the console once a frame and never waiting. | | Keys | The console interrupting rather than being asked. The only one that brings a vector of its own, which is what the version two format exists for. | | Say | Prints whatever it was told, which is the shortest thing that shows osArgument working. | +| Once | Asks the loader to start something else on the next start, and only that one, in 569 bytes. | | Status | Says what the last program made of what it was asked to do, in 222 bytes. The shell keeps the number and does not print it; this is how a person looks. | | Settle | Says how the last start went and tells the machine to stop falling back, in 353 bytes. A program rather than a shell word, because the shell is for what cannot be done without it. | | Files | Writes a file, reads it back, renames it and deletes it, in 675 bytes, including nothing but the service names. It is what says a program does not need a filesystem inside it. | diff --git a/README.md b/README.md index 7e3ba69..700b1c1 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,34 @@ so a machine interrupted while updating its only boot slot would not boot at all the one failure on this disk with no way back. Writing the slot that is *not* live and then moving one byte in the superblock turns that into a machine that boots what it had before. +### 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, which is a poor place to find a mistake. + +``` +> Once /System/Boot/mine.bin +next start: /System/Boot/mine.bin, once +``` + +That writes `/System/Boot/once.cfg`, in the same format as `boot.cfg` and read with the same +routines, because a second format for one setting would be a second format. The loader reads +it before `boot.cfg` and **deletes it before it jumps** - the only moment there is, since +after the jump the loader does not exist. + +**Consumed by being read, not by working.** A one shot that hangs cannot hang twice: the +request is gone before the image ran, so the next start reads `boot.cfg` like any other. + +And **the boot state is not touched** by a one shot, which the first version got wrong. A +program with the whole machine has no filesystem to clear a mark with, and is doing nothing +wrong by not having one - so marking it reported every successful bare metal boot as a start +that never arrived. + +Which closes the loop on the machine itself: write a bare metal program in `Edit`, assemble +it with `Asm`, ask for it with `Once`, restart, watch it run, and the system comes back +without being asked. + ### Knowing Whether The Last Start Arrived: The loader marks the disk before it hands over, and the system clears the mark when it diff --git a/Tests/expected/onceAfter.out b/Tests/expected/onceAfter.out new file mode 100644 index 0000000..9119205 --- /dev/null +++ b/Tests/expected/onceAfter.out @@ -0,0 +1,6 @@ +stage two +CosmOS +> +halted +Execution halted. +[exit 0] diff --git a/Tests/expected/onceAsked.out b/Tests/expected/onceAsked.out new file mode 100644 index 0000000..289a758 --- /dev/null +++ b/Tests/expected/onceAsked.out @@ -0,0 +1,9 @@ +CosmOS +> next start: /System/Boot/bare.bin, once +finished +> System