Once: start something else on the next start, and only that one

A program that owns the whole machine had 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 in something written five minutes ago.

Once 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, which is the only moment there is: after the jump the loader does
not exist.

Consumed by being read rather than by working, so a one shot that hangs
cannot hang twice - the request is gone before the image ran, and the next
start reads boot.cfg like any other.

THE BOOT STATE IS NOT TOUCHED, and the first version got that wrong. It
marked the start the way any other start is marked, and then every
successful bare metal boot reported that it had never arrived - because a
program with the whole machine has no filesystem to clear a mark with and
is doing nothing wrong by not having one. Found by running it: the image
printed its line and the next start still said the last one did not.

Three disks, each a start further along, so none of the tests depends on
another having run.

The loop is closed on the machine now: write it in Edit, assemble it with
Asm, ask for it with Once, restart, watch it own the machine, and the
system comes back without being asked.
This commit is contained in:
Anachronaut
2026-08-27 20:02:42 -04:00
parent 89c667848b
commit 7b28f48f52
10 changed files with 287 additions and 0 deletions
+58
View File
@@ -70,6 +70,57 @@ start:
; ;
; Read before the image is, because the configuration is staged where the image will go: ; 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. ; 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.0 ConfigPath
SETD.2 StageAt SETD.2 StageAt
LDD.1.2 LDD.1.2
@@ -541,6 +592,8 @@ NoSystemText:
; Where the configuration lives, and what to start when it does not say. ; Where the configuration lives, and what to start when it does not say.
ConfigPath: ConfigPath:
"/System/Boot/boot.cfg" "/System/Boot/boot.cfg"
OncePath:
"/System/Boot/once.cfg"
SystemName: SystemName:
"/System/Boot/cosmos.bin" "/System/Boot/cosmos.bin"
KeySystem: KeySystem:
@@ -570,6 +623,11 @@ NoFallbackText:
NothingText: NothingText:
"nothing to start "nothing to start
" "
OnceText:
"just this once: "
OnceFailedText:
"it did not start, so carrying on
"
BootName: BootName:
#Reserve 0d128 #Reserve 0d128
+138
View File
@@ -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
+1
View File
@@ -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. | | 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. | | 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. | | 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. | | 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. | | 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. | | 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. |
+28
View File
@@ -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 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. 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: ### Knowing Whether The Last Start Arrived:
The loader marks the disk before it hands over, and the system clears the mark when it The loader marks the disk before it hands over, and the system clears the mark when it
+6
View File
@@ -0,0 +1,6 @@
stage two
CosmOS
>
halted
Execution halted.
[exit 0]
+9
View File
@@ -0,0 +1,9 @@
CosmOS
> next start: /System/Boot/bare.bin, once
finished
> System <dir>
Apps <dir>
0 files, 2 directories
> halted
Execution halted.
[exit 0]
+5
View File
@@ -0,0 +1,5 @@
stage two
just this once: /System/Boot/bare.bin
bare metal: no system, just this
Execution halted.
[exit 0]
+3
View File
@@ -0,0 +1,3 @@
Once /System/Boot/bare.bin
dir /System/Boot
exit
+22
View File
@@ -528,3 +528,25 @@ done
{ printf 'short line\n'; printf 'x%.0s' $(seq 1 200); printf '\nanother short one\n'; } \ { printf 'short line\n'; printf 'x%.0s' $(seq 1 200); printf '\nanother short one\n'; } \
> "$WORK/toolong.txt" > "$WORK/toolong.txt"
"$TOOL" put "$DISKS/editlong.img" "$WORK/toolong.txt" /long.txt >/dev/null "$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"
+17
View File
@@ -466,6 +466,23 @@ statusApp | CosmOS/Apps/Status.asm | assemble | -
# been looked at. # been looked at.
cosmosEditLong | CosmOS/Source/cosmos.asm | run | editLong.in | 200000000 | disks/editlong.img cosmosEditLong | CosmOS/Source/cosmos.asm | run | editLong.in | 200000000 | disks/editlong.img
# ---- Starting something else just this once ----
#
# A bare metal program has nowhere to run: starting it from the shell 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, so the image runs on
# the next start and on no other, whatever becomes of it.
#
# Three disks, each a start further on, so none of them depends on another having run.
onceAsked | CosmOS/Source/cosmos.asm | run | once.in | 200000000 | disks/onceasked.img
# The start the request was for. It starts an image with no operating system in it at all,
# and THE BOOT STATE IS NOT TOUCHED: a one shot is already self limiting, and marking it
# would report every successful bare metal boot as a start that never arrived.
onceDue | Boot/stage1.asm | rom | - | 200000000 | disks/oncedue.img
# And the start after, which is an ordinary one again.
onceAfter | Boot/stage1.asm | rom | - | 200000000 | disks/onceafter.img
onceApp | CosmOS/Apps/Once.asm | assemble | - | -
# A failed start with NOTHING to fall back to. The mark must not become a reason to refuse # A failed start with NOTHING to fall back to. The mark must not become a reason to refuse
# to start at all - a failure that was passing recovers here, and one that is not leaves # to start at all - a failure that was passing recovers here, and one that is not leaves
# the machine exactly where it would have been without any of this. # the machine exactly where it would have been without any of this.