Configuration files, and boot.cfg as the first of them
One setting to a line: a key, a space, the rest of the line is the value. A semicolon starts a comment. The format was noticed rather than designed - textSplit already cuts the first word off a line and leaves the rest, and textSame already insists two strings end together, so reading a setting is those two routines and a loop. It is also what the shell reads, which makes a configuration line a command line the machine reads instead of a person typing one. The format was chosen by asking what the BOOT LOADER could manage, because it is the worst case in every direction: a few kilobytes, no operating system to report to, and if it fails the machine does not start. Two formats would be worse than one and the loader cannot have the richer one. CONFIGURATION IS ADVICE. A missing file, a missing key, an unusable value, a line too long to read: all of them mean use the default and none is a failure. BUT QUIET IS NOT SILENT - a setting somebody meant, which did not take effect, says so. That was the user's addition and it is the better rule: the default alone leaves the only symptom being that the machine did not do what somebody asked. So two routines. cfgGet reads and says nothing, because reading three settings should not report one bad line three times. cfgCheck reads the file once and reports, and is handed the caller's list of keys - whether a key means anything is the only part of this a shared reader cannot judge. /System/Boot/ holds the boot files, and stage two reads boot.cfg for what to start, with a fallback to try if it does not work and a name compiled in for when the file says nothing. THE TEST FOUND A REAL BUG, and it is the interaction I would not have thought to look for. First-match-wins met an empty value: a file with system system /System/Boot/bare.bin matched the first line, handed back nothing, and the machine tried to start a file with no name while a good setting sat underneath. An unusable value is an absent one - which is what "configuration is advice" says, and this is where it earns its keep. cfgBare starts an image with no operating system in it at all, which is what loading an ordinary boot image buys: a program wanting the whole machine is a file like any other, chosen the same way the system is. Three disks differing ONLY in boot.cfg, so each is a test of the file rather than of the machinery under it.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
; bare.asm
|
||||
; A machine with no operating system on it at all.
|
||||
;
|
||||
; THIS IS THE POINT OF LOADING AN ORDINARY IMAGE. The second stage does not know what
|
||||
; CosmOS is; it knows what a boot image is, and this is one. So a program that wants the
|
||||
; whole machine to itself - no shell, no filesystem, no services - is not a special case
|
||||
; needing a special path. It is a file, written under CosmOS like any other, and started by
|
||||
; naming it in /System/Boot/boot.cfg.
|
||||
;
|
||||
; It owns everything from address zero upward, and the only thing it can be sure of is the
|
||||
; console, because that is all it asked for.
|
||||
;
|
||||
; Written by Anachronaut
|
||||
|
||||
#Program
|
||||
|
||||
start:
|
||||
SETD.0 Said
|
||||
sayLoop:
|
||||
LDA.0
|
||||
BRA sayDone
|
||||
OUTA 0x00
|
||||
INCD.0
|
||||
BRI sayLoop
|
||||
sayDone:
|
||||
HALT
|
||||
|
||||
#Data
|
||||
|
||||
Said:
|
||||
"bare metal: no system, just this
|
||||
"
|
||||
|
||||
#Vectors
|
||||
|
||||
Boot start
|
||||
+131
-8
@@ -66,9 +66,74 @@ start:
|
||||
CALL sbfsMount
|
||||
BNQ noFilesystem
|
||||
|
||||
; ---- What to 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.
|
||||
SETD.0 ConfigPath
|
||||
SETD.2 StageAt
|
||||
LDD.1.2
|
||||
INIA 0d4
|
||||
CALL cfgLoad
|
||||
|
||||
; Said before anything is started, so that a setting somebody meant which did not happen
|
||||
; is on the screen above whatever happened instead.
|
||||
SETD.0 KnownKeys
|
||||
CALL cfgCheck
|
||||
|
||||
SETD.0 KeySystem
|
||||
CALL cfgGet
|
||||
SETD.1 BootName
|
||||
BNQ systemDefault
|
||||
SETD.2 CfgValue
|
||||
LDD.0.2
|
||||
RCAL copyString
|
||||
BRI systemChosen
|
||||
systemDefault:
|
||||
SETD.0 SystemName
|
||||
RCAL copyString
|
||||
systemChosen:
|
||||
|
||||
; And what to fall back to, kept now because the configuration is about to be written
|
||||
; over by the image itself.
|
||||
RSTA
|
||||
SETD.0 HaveFallback
|
||||
STA.0
|
||||
SETD.0 KeyFallback
|
||||
CALL cfgGet
|
||||
BNQ noFallbackSet
|
||||
SETD.1 FallbackName
|
||||
SETD.2 CfgValue
|
||||
LDD.0.2
|
||||
RCAL copyString
|
||||
INIA 0x01
|
||||
SETD.0 HaveFallback
|
||||
STA.0
|
||||
noFallbackSet:
|
||||
|
||||
SETD.0 BootName
|
||||
RCAL tryImage
|
||||
|
||||
; It did not start. Whatever went wrong has already said so, and if there is something
|
||||
; else to try then trying it is the whole reason for having said it rather than stopping.
|
||||
SETD.0 HaveFallback
|
||||
LDA.0
|
||||
BRA noSystem
|
||||
SETD.0 FallbackText
|
||||
RCAL say
|
||||
SETD.0 FallbackName
|
||||
RCAL tryImage
|
||||
BRI noSystem
|
||||
|
||||
; ---- Starting one particular image ----
|
||||
;
|
||||
; DP0 names it. Returns only if it could not be started, having said why; everything that
|
||||
; works ends in the jump at the bottom and never comes back.
|
||||
tryImage:
|
||||
SETD.1 TryName
|
||||
STD.0.1
|
||||
CALL sbfsFind
|
||||
BNQ noSystem
|
||||
BNQ tryMissing
|
||||
|
||||
; ---- The image, read whole into somewhere nothing else is using ----
|
||||
SETD.2 StageAt
|
||||
@@ -205,33 +270,59 @@ vectorsDone:
|
||||
unreadable:
|
||||
SETD.0 UnreadableText
|
||||
RCAL say
|
||||
HALT
|
||||
RCAL sayName
|
||||
RRET
|
||||
notAnImage:
|
||||
SETD.0 NotImageText
|
||||
RCAL say
|
||||
HALT
|
||||
RCAL sayName
|
||||
RRET
|
||||
wrongVersion:
|
||||
SETD.0 VersionText
|
||||
RCAL say
|
||||
HALT
|
||||
RCAL sayName
|
||||
RRET
|
||||
wantsMore:
|
||||
SETD.0 FeatureText
|
||||
RCAL say
|
||||
HALT
|
||||
RCAL sayName
|
||||
RRET
|
||||
|
||||
noFilesystem:
|
||||
SETD.0 NoDiskText
|
||||
RCAL say
|
||||
HALT
|
||||
|
||||
noSystem:
|
||||
tryMissing:
|
||||
SETD.0 NoSystemText
|
||||
RCAL say
|
||||
SETD.0 SystemName
|
||||
RCAL sayName
|
||||
RRET
|
||||
|
||||
; Whichever image was being tried, said after the reason it did not work.
|
||||
sayName:
|
||||
SETD.0 TryName
|
||||
LDD.0.0
|
||||
RCAL say
|
||||
RCAL newLine
|
||||
RRET
|
||||
|
||||
noSystem:
|
||||
SETD.0 NothingText
|
||||
RCAL say
|
||||
HALT
|
||||
|
||||
; DP0 names a string and DP1 where to put it, terminator and all.
|
||||
copyString:
|
||||
LDA.0
|
||||
STA.1
|
||||
BRA copyDone
|
||||
INCD.0
|
||||
INCD.1
|
||||
BRI copyString
|
||||
copyDone:
|
||||
RRET
|
||||
|
||||
; ---- Comparing a marker and stepping past it ----
|
||||
;
|
||||
; DP0 is in the image, DP2 names what it should say, A is how many bytes. DP0 is left past
|
||||
@@ -398,8 +489,38 @@ NoSystemText:
|
||||
|
||||
; What to start. A name for now; the plan is for this to be read out of a file so that any
|
||||
; number of systems can sit on one disk and the choice is an ordinary safe write.
|
||||
; Where the configuration lives, and what to start when it does not say.
|
||||
ConfigPath:
|
||||
"/System/Boot/boot.cfg"
|
||||
SystemName:
|
||||
"/System/cosmos.bin"
|
||||
"/System/Boot/cosmos.bin"
|
||||
KeySystem:
|
||||
"system"
|
||||
KeyFallback:
|
||||
"fallback"
|
||||
|
||||
; The keys this knows. Anything else in the file is a setting nothing asked for, and
|
||||
; cfgCheck says so rather than letting it look as though it worked.
|
||||
KnownKeys:
|
||||
"system"
|
||||
"fallback"
|
||||
""
|
||||
|
||||
FallbackText:
|
||||
"trying the fallback
|
||||
"
|
||||
NothingText:
|
||||
"nothing to start
|
||||
"
|
||||
|
||||
BootName:
|
||||
#Reserve 0d128
|
||||
FallbackName:
|
||||
#Reserve 0d128
|
||||
HaveFallback:
|
||||
0x00
|
||||
TryName:
|
||||
0x00 0x00
|
||||
|
||||
UnreadableText:
|
||||
"could not read it
|
||||
@@ -449,6 +570,8 @@ StageAt:
|
||||
0x20 0x00
|
||||
|
||||
#Include sbfs.asm
|
||||
#Include text.asm
|
||||
#Include config.asm
|
||||
|
||||
; ---- The end of everything ----
|
||||
;
|
||||
|
||||
@@ -0,0 +1,469 @@
|
||||
; config.asm
|
||||
; Reading a configuration file.
|
||||
;
|
||||
; One setting to a line: a key, a space, and the rest of the line is the value. A semicolon
|
||||
; starts a comment and a blank line is nothing. That format was not designed so much as
|
||||
; noticed - textSplit already cuts the first word off a line and leaves the rest, and
|
||||
; textSame already compares two strings and insists they end together, so reading a setting
|
||||
; is those two routines and a loop. It is also the shape the shell reads, which means a
|
||||
; configuration line is a command line the machine reads instead of a person typing one.
|
||||
;
|
||||
; ---- Configuration is advice ----
|
||||
;
|
||||
; A file that is not there, a key that is not in it, and a value that makes no sense are
|
||||
; all the same answer: use the default. Nothing here reports a failure for any of them,
|
||||
; because a program that cannot run without its configuration has turned its configuration
|
||||
; into a single point of failure - and for the thing that starts the machine, that would
|
||||
; mean a mistyped file is a machine that does not start.
|
||||
;
|
||||
; ---- But quiet is not the same as silent ----
|
||||
;
|
||||
; A setting that was meant and did not take effect should say so, or the only symptom is
|
||||
; that the machine did not do what somebody asked it to. So there are TWO routines rather
|
||||
; than one: cfgGet reads, and never says anything; cfgCheck reads the whole file once and
|
||||
; reports what it did not understand.
|
||||
;
|
||||
; They are separate because they know different things. A malformed line is malformed to
|
||||
; anybody, so cfgGet's own scan could spot one - but reading three settings would then
|
||||
; report the same bad line three times. And an unknown KEY is not something this can judge
|
||||
; at all: only the caller knows which keys mean anything to it, which is why cfgCheck is
|
||||
; given a table of them.
|
||||
;
|
||||
; Written by Anachronaut
|
||||
|
||||
#Program
|
||||
|
||||
; ---- Reading the file in ----
|
||||
;
|
||||
; DP0 names a path, DP1 names a buffer, and A is how many blocks the buffer holds.
|
||||
;
|
||||
; Q is zero if there is something to read, INCLUDING WHEN THERE IS NO FILE. A missing
|
||||
; configuration file is a file with no settings in it, which is a perfectly ordinary thing
|
||||
; for a disk to have, and the caller wanting the defaults gets them either way.
|
||||
cfgLoad:
|
||||
SETD.2 CfgRoom
|
||||
STA.2
|
||||
SETD.2 CfgBufferAt
|
||||
STD.1.2
|
||||
|
||||
; Nothing loaded until something is.
|
||||
RSTA
|
||||
SETD.2 CfgLength
|
||||
STA.2
|
||||
INCD.2
|
||||
STA.2
|
||||
|
||||
CALL sbfsFind
|
||||
BNQ cfgNoFile
|
||||
|
||||
; A file too big for the buffer is read as far as it fits rather than refused: the
|
||||
; settings at the top still work, and the ones past the end are missing keys, which is
|
||||
; a case every caller already handles.
|
||||
SETD.2 SbfsFileBlocks
|
||||
INCD.2
|
||||
LDA.2
|
||||
SETD.2 CfgRoom
|
||||
LDB.2
|
||||
CCF
|
||||
SUB
|
||||
BNC cfgTooBig
|
||||
|
||||
SETD.2 CfgBufferAt
|
||||
LDD.1.2
|
||||
CALL sbfsRead
|
||||
BNQ cfgNoFile
|
||||
|
||||
; How many bytes of it are real. A file is whole blocks and then a tail, which is the
|
||||
; block count in the high byte and the tail in the low one.
|
||||
SETD.2 SbfsFileBlocks
|
||||
INCD.2
|
||||
LDA.2
|
||||
SETD.2 CfgLength
|
||||
STA.2
|
||||
SETD.2 SbfsFileTail
|
||||
LDA.2
|
||||
SETD.2 CfgLength
|
||||
INCD.2
|
||||
STA.2
|
||||
|
||||
cfgReady:
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
cfgTooBig:
|
||||
; As much of it as there is room for, which is every whole block of the buffer.
|
||||
SETD.2 CfgRoom
|
||||
LDA.2
|
||||
SETD.2 SbfsFileBlocks
|
||||
INCD.2
|
||||
STA.2
|
||||
RSTA
|
||||
SETD.2 SbfsFileTail
|
||||
STA.2
|
||||
SETD.2 CfgBufferAt
|
||||
LDD.1.2
|
||||
CALL sbfsRead
|
||||
BNQ cfgNoFile
|
||||
SETD.2 CfgRoom
|
||||
LDA.2
|
||||
SETD.2 CfgLength
|
||||
STA.2
|
||||
RSTA
|
||||
INCD.2
|
||||
STA.2
|
||||
BRI cfgReady
|
||||
|
||||
cfgNoFile:
|
||||
; No file, or a disk that would not give it up. Neither is a failure: it is a file with
|
||||
; nothing in it, and every key will be missing, which is what a default is for.
|
||||
RSTA
|
||||
SETD.2 CfgLength
|
||||
STA.2
|
||||
INCD.2
|
||||
STA.2
|
||||
BRI cfgReady
|
||||
|
||||
; ---- Walking it a line at a time ----
|
||||
;
|
||||
; cfgRewind puts the walk back at the top. cfgLine copies the next line into CfgLine and
|
||||
; leaves Q zero if there was one.
|
||||
;
|
||||
; A line longer than the buffer holds is copied as far as it goes and the rest of it is
|
||||
; skipped, with CfgLong set to say so. Ignoring it is what the format asks for; saying that
|
||||
; it was ignored is what cfgCheck is for.
|
||||
cfgRewind:
|
||||
SETD.0 CfgBufferAt
|
||||
SETD.1 CfgWalk
|
||||
CALL sbfsCopyWord
|
||||
SETD.0 CfgLength
|
||||
SETD.1 CfgLeft
|
||||
CALL sbfsCopyWord
|
||||
RET
|
||||
|
||||
cfgLine:
|
||||
RSTA
|
||||
SETD.0 CfgLong
|
||||
STA.0
|
||||
SETD.0 CfgFill
|
||||
STA.0
|
||||
|
||||
SETD.0 CfgLeft
|
||||
LDA.0
|
||||
INCD.0
|
||||
LDB.0
|
||||
OR
|
||||
BRQ cfgLineNone
|
||||
|
||||
SETD.2 CfgWalk
|
||||
LDD.0.2
|
||||
SETD.1 CfgLine
|
||||
|
||||
cfgLineLoop:
|
||||
SETD.2 CfgLeft
|
||||
LDA.2
|
||||
INCD.2
|
||||
LDB.2
|
||||
OR
|
||||
BRQ cfgLineEnd
|
||||
|
||||
LDA.0
|
||||
INIB 0x0A
|
||||
CCF
|
||||
SUB
|
||||
BRQ cfgLineBreak
|
||||
|
||||
; Room for it, or the line is one of the long ones and only its beginning is kept.
|
||||
SETD.2 CfgFill
|
||||
LDB.2
|
||||
INIA 0d128
|
||||
CCF
|
||||
SUB
|
||||
BRQ cfgLineOverflow
|
||||
LDA.0
|
||||
STA.1
|
||||
INCD.1
|
||||
LDA.2
|
||||
INCA
|
||||
STA.2
|
||||
|
||||
cfgLineStep:
|
||||
INCD.0
|
||||
RCAL cfgSpent
|
||||
BRI cfgLineLoop
|
||||
|
||||
cfgLineOverflow:
|
||||
INIA 0x01
|
||||
SETD.2 CfgLong
|
||||
STA.2
|
||||
BRI cfgLineStep
|
||||
|
||||
cfgLineBreak:
|
||||
INCD.0
|
||||
RCAL cfgSpent
|
||||
cfgLineEnd:
|
||||
RSTA
|
||||
STA.1 ; The zero that makes what was copied a string.
|
||||
SETD.2 CfgWalk
|
||||
STD.0.2
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
cfgLineNone:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; One byte of the file accounted for.
|
||||
cfgSpent:
|
||||
SETD.2 CfgLeft
|
||||
INCD.2
|
||||
LDA.2
|
||||
BNA cfgSpentLow
|
||||
SETD.2 CfgLeft
|
||||
LDA.2
|
||||
DECA
|
||||
STA.2
|
||||
INCD.2
|
||||
INIA 0xFF
|
||||
STA.2
|
||||
RRET
|
||||
cfgSpentLow:
|
||||
DECA
|
||||
STA.2
|
||||
RRET
|
||||
|
||||
; ---- What a line turns out to be ----
|
||||
;
|
||||
; Q is zero if there is a setting on it, and then CfgLine names the key and CfgValue names
|
||||
; the value. Q is not zero for a blank line or a comment, which are not settings and are
|
||||
; not mistakes either.
|
||||
cfgParse:
|
||||
RSTA
|
||||
SETD.0 CfgNoValue
|
||||
STA.0
|
||||
|
||||
; A LINE THAT WAS CUT SHORT IS NOT A SETTING. Its key may look perfectly good and its
|
||||
; value is whatever fitted, so treating it as one would hand back an answer that is wrong
|
||||
; rather than missing - and a missing setting gets the default, which is the safe thing.
|
||||
; Reported by cfgCheck, ignored by everything.
|
||||
SETD.0 CfgLong
|
||||
LDA.0
|
||||
BNA cfgParseNothing
|
||||
|
||||
SETD.0 CfgLine
|
||||
LDA.0
|
||||
BRA cfgParseNothing
|
||||
INIB 0x3B ; A semicolon starts a comment.
|
||||
CCF
|
||||
SUB
|
||||
BRQ cfgParseNothing
|
||||
|
||||
CALL textSplit
|
||||
SETD.0 TextRest
|
||||
SETD.1 CfgValue
|
||||
CALL sbfsCopyWord
|
||||
|
||||
; A KEY WITH NOTHING AFTER IT IS NOT A SETTING, and finding that out here rather than in
|
||||
; each caller is what makes first-match-wins safe. A file with
|
||||
;
|
||||
; system
|
||||
; system /System/Boot/cosmos.bin
|
||||
;
|
||||
; in it would otherwise match the first line, hand back an empty value, and the machine
|
||||
; would try to start a file with no name while a perfectly good setting sat underneath.
|
||||
; An unusable value is an absent one, which is what C3 says and where it earns its keep.
|
||||
SETD.2 CfgValue
|
||||
LDD.0.2
|
||||
LDA.0
|
||||
BRA cfgParseNoValue
|
||||
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
cfgParseNoValue:
|
||||
; Told apart from a blank line, because one of them is a mistake and the other is not.
|
||||
INIA 0x01
|
||||
SETD.0 CfgNoValue
|
||||
STA.0
|
||||
cfgParseNothing:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; ---- Asking for one setting ----
|
||||
;
|
||||
; DP0 names the key. Q is zero if the file had it, and CfgValue then names the value.
|
||||
; Says nothing about anything, ever.
|
||||
cfgGet:
|
||||
SETD.1 CfgWanted
|
||||
STD.0.1
|
||||
CALL cfgRewind
|
||||
|
||||
cfgGetLoop:
|
||||
CALL cfgLine
|
||||
BNQ cfgGetMissing
|
||||
CALL cfgParse
|
||||
BNQ cfgGetLoop
|
||||
|
||||
SETD.0 CfgLine
|
||||
SETD.2 CfgWanted
|
||||
LDD.1.2
|
||||
CALL textSame
|
||||
BNQ cfgGetLoop
|
||||
|
||||
RSTA
|
||||
RSTB
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
cfgGetMissing:
|
||||
RSTA
|
||||
INIB 0d1
|
||||
CCF
|
||||
ADD
|
||||
RET
|
||||
|
||||
; ---- Saying what was not understood ----
|
||||
;
|
||||
; DP0 names a table of the keys the caller knows: strings one after another, ended by an
|
||||
; empty one. Every line is looked at once, and anything that will not take effect is
|
||||
; reported with enough of itself to be found and fixed.
|
||||
;
|
||||
; Nothing here changes what any setting does. The defaults have already been used by the
|
||||
; time this runs, or will be; this exists so that a setting somebody meant, which did not
|
||||
; happen, does not do so in silence.
|
||||
cfgCheck:
|
||||
SETD.1 CfgKnown
|
||||
STD.0.1
|
||||
CALL cfgRewind
|
||||
|
||||
cfgCheckLoop:
|
||||
CALL cfgLine
|
||||
BNQ cfgCheckDone
|
||||
|
||||
SETD.0 CfgLong
|
||||
LDA.0
|
||||
BRA cfgCheckParse
|
||||
SETD.0 CfgLongText
|
||||
RCAL cfgSay
|
||||
SETD.0 CfgLine
|
||||
RCAL cfgSay
|
||||
RCAL cfgNewLine
|
||||
BRI cfgCheckLoop ; Said once. What its key looks like is not worth a second remark.
|
||||
|
||||
cfgCheckParse:
|
||||
CALL cfgParse
|
||||
BRQ cfgCheckKnown
|
||||
|
||||
; Not a setting. A blank line or a comment is not a mistake and gets no remark; a key
|
||||
; somebody started and did not finish is, and gets one.
|
||||
SETD.0 CfgNoValue
|
||||
LDA.0
|
||||
BRA cfgCheckLoop
|
||||
SETD.0 CfgEmptyText
|
||||
RCAL cfgSay
|
||||
SETD.0 CfgLine
|
||||
RCAL cfgSay
|
||||
RCAL cfgNewLine
|
||||
BRI cfgCheckLoop
|
||||
|
||||
cfgCheckKnown:
|
||||
SETD.2 CfgKnown
|
||||
LDD.1.2
|
||||
|
||||
cfgCheckNext:
|
||||
LDA.1
|
||||
BRA cfgCheckUnknown ; The empty name that ends the table.
|
||||
SETD.0 CfgLine
|
||||
CALL textSame
|
||||
BRQ cfgCheckLoop ; A key this caller knows, so there is nothing to say.
|
||||
|
||||
; Past this name and on to the next one.
|
||||
cfgCheckSkip:
|
||||
LDA.1
|
||||
BRA cfgCheckSkipped
|
||||
INCD.1
|
||||
BRI cfgCheckSkip
|
||||
cfgCheckSkipped:
|
||||
INCD.1
|
||||
BRI cfgCheckNext
|
||||
|
||||
cfgCheckUnknown:
|
||||
SETD.0 CfgUnknownText
|
||||
RCAL cfgSay
|
||||
SETD.0 CfgLine
|
||||
RCAL cfgSay
|
||||
RCAL cfgNewLine
|
||||
BRI cfgCheckLoop
|
||||
|
||||
cfgCheckDone:
|
||||
RET
|
||||
|
||||
; ---- Saying things ----
|
||||
;
|
||||
; Its own rather than the console's, because the first thing to read a configuration file
|
||||
; is the boot loader and everything it uses has to fit in a boot slot.
|
||||
cfgSay:
|
||||
LDA.0
|
||||
BRA cfgSaid
|
||||
OUTA 0x00
|
||||
INCD.0
|
||||
BRI cfgSay
|
||||
cfgSaid:
|
||||
RRET
|
||||
|
||||
cfgNewLine:
|
||||
INIA 0x0A
|
||||
OUTA 0x00
|
||||
RRET
|
||||
|
||||
#Data
|
||||
|
||||
CfgLongText:
|
||||
"a line too long to read: "
|
||||
CfgEmptyText:
|
||||
"a setting with no value: "
|
||||
CfgUnknownText:
|
||||
"a setting nothing asked for: "
|
||||
|
||||
CfgBufferAt:
|
||||
0x00 0x00
|
||||
CfgRoom:
|
||||
0x00
|
||||
CfgLength:
|
||||
0x00 0x00
|
||||
CfgWalk:
|
||||
0x00 0x00
|
||||
CfgLeft:
|
||||
0x00 0x00
|
||||
CfgFill:
|
||||
0x00
|
||||
CfgLong:
|
||||
0x00
|
||||
CfgNoValue:
|
||||
0x00
|
||||
CfgWanted:
|
||||
0x00 0x00
|
||||
CfgKnown:
|
||||
0x00 0x00
|
||||
CfgValue:
|
||||
0x00 0x00
|
||||
|
||||
; One line, and the number is the one the format says: a key of twenty two and a path of
|
||||
; the length a path is allowed to be leaves room to spare in a hundred and twenty eight.
|
||||
CfgLine:
|
||||
#Reserve 0d129
|
||||
@@ -306,8 +306,8 @@ has versions - all of that lives in the boot area, on the disk, where it can be
|
||||
It reads the live slot into Program Memory, jumps to the first byte, and prints one
|
||||
character and stops if there is nothing to start.
|
||||
|
||||
`Programs/Boot/stage2.asm` is what sits in the slot. It mounts the filesystem, finds the
|
||||
system by name, and takes the image apart: code into Program Memory, data into Data
|
||||
`Programs/Boot/stage2.asm` is what sits in the slot. It mounts the filesystem, reads
|
||||
`/System/Boot/boot.cfg` to find out what to start, and takes the image apart: code into Program Memory, data into Data
|
||||
Memory, and each vector written through the controller, which is the only thing that can
|
||||
write Program Memory at all. The entry point is noticed on the boot vector's way past,
|
||||
because Program Memory cannot be read back to look it up afterwards.
|
||||
@@ -318,6 +318,53 @@ normal image format is not a boot-specific mechanism, so **bare metal SplitBit s
|
||||
a special case.** A program that wants no operating system under it is just an image,
|
||||
written under CosmOS like any other, and startable because it is a file.
|
||||
|
||||
### Configuration:
|
||||
|
||||
One setting to a line: a key, a space, and the rest of the line is the value. A semicolon
|
||||
starts a comment and a blank line is nothing.
|
||||
|
||||
```
|
||||
; /System/Boot/boot.cfg
|
||||
system /System/Boot/cosmos.bin
|
||||
fallback /System/Boot/cosmos-previous.bin
|
||||
```
|
||||
|
||||
That format was noticed rather than designed. `textSplit` already cuts the first word off a
|
||||
line and leaves the rest; `textSame` already compares two strings and insists they end
|
||||
together, so `system` and `systemd` are different words. Reading a setting is those two
|
||||
routines and a loop - and it is the shape the shell already reads, so **a configuration
|
||||
line is a command line the machine reads instead of a person typing one.**
|
||||
|
||||
**Configuration is advice.** A missing file, a missing key, a value that makes no sense, a
|
||||
line too long to read: all of them mean *use the default*, and none of them is a failure. A
|
||||
program that cannot run without its configuration has turned its configuration into a
|
||||
single point of failure, and for the thing that starts the machine that would mean a
|
||||
mistyped file is a machine that will not start.
|
||||
|
||||
**But quiet is not silent.** A setting somebody meant, which did not take effect, says so:
|
||||
|
||||
```
|
||||
a setting nothing asked for: fallbcak
|
||||
a setting with no value: system
|
||||
a line too long to read: ...
|
||||
```
|
||||
|
||||
So `config.asm` has two routines rather than one. `cfgGet` reads and never says anything -
|
||||
reading three settings should not report the same bad line three times. `cfgCheck` reads
|
||||
the file once and reports what it did not understand, and is given the caller's list of
|
||||
keys, because whether a key means anything is the only part of this the reader cannot
|
||||
judge for itself.
|
||||
|
||||
Keys are matched exactly and case sensitively, the first line that matches wins, and
|
||||
unknown keys are ignored rather than refused - so a file written by a newer system loses
|
||||
the settings an older one never had and keeps the ones it did. A line is at most 128 bytes
|
||||
and a key at most 22, which is what a name is everywhere else on this machine.
|
||||
|
||||
Deliberately absent: sections, nesting, types, includes, substitution, conditions. Each is
|
||||
a step from reading a file toward running one, and the boot loader is the worst place on
|
||||
this machine to put an interpreter. The test to apply when the pressure arrives is: **if
|
||||
this file cannot be read, can the machine still start?**
|
||||
|
||||
Stage two arranges its own Data Segment. Stage one places Program Memory and nothing else,
|
||||
since knowing where a payload's data ended would mean knowing a format, so the image is
|
||||
written into the slot as code followed by data and stage two's first act is to blit its
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
stage two
|
||||
bare metal: no system, just this
|
||||
Execution halted.
|
||||
[exit 0]
|
||||
@@ -0,0 +1,6 @@
|
||||
stage two
|
||||
a setting nothing asked for: fallbcak
|
||||
a setting with no value: system
|
||||
bare metal: no system, just this
|
||||
Execution halted.
|
||||
[exit 0]
|
||||
@@ -0,0 +1,16 @@
|
||||
stage two
|
||||
no /System/Boot/missing.bin
|
||||
trying the fallback
|
||||
CosmOS
|
||||
> saved it
|
||||
read it back, 22 bytes:
|
||||
a file kept by asking
|
||||
renamed it
|
||||
deleted it
|
||||
and it is gone
|
||||
finished
|
||||
> made
|
||||
> /Notes> 0 files
|
||||
/Notes> halted
|
||||
Execution halted.
|
||||
[exit 0]
|
||||
@@ -1,4 +1,5 @@
|
||||
stage two
|
||||
no /System/cosmos.bin
|
||||
no /System/Boot/cosmos.bin
|
||||
nothing to start
|
||||
Execution halted.
|
||||
[exit 0]
|
||||
|
||||
@@ -12,6 +12,7 @@ Programs/CosmOS/Assembler/readTest.asm redundant-setd 1
|
||||
Programs/CosmOS/Assembler/token.asm redundant-assignment 4
|
||||
Programs/CosmOS/Assembler/token.asm redundant-setd 2
|
||||
Programs/CosmOS/Assembler/tokenTest.asm redundant-setd 1
|
||||
Programs/CosmOS/Source/config.asm redundant-assignment 1
|
||||
Programs/CosmOS/Source/cosmos.asm redundant-assignment 1
|
||||
Programs/CosmOS/Source/cosmos.asm redundant-setd 9
|
||||
Programs/CosmOS/Source/sbfs.asm branch-to-next 1
|
||||
|
||||
+35
-1
@@ -401,8 +401,11 @@ tail -c +17 "$WORK/stage2.sbx" > "$WORK/stage2.raw"
|
||||
|
||||
"$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" put "$DISKS/selfboot.img" "$WORK/bootCosmos.bin" /System/cosmos.bin >/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
|
||||
@@ -412,3 +415,34 @@ tail -c +17 "$WORK/stage2.sbx" > "$WORK/stage2.raw"
|
||||
# says so rather than jumping somewhere.
|
||||
"$TOOL" format "$DISKS/nosystem.img" 512 4 32 >/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
|
||||
|
||||
@@ -401,6 +401,24 @@ selfBootNoSystem | Boot/stage1.asm | run | -
|
||||
# that is what is IN the ROM: the makefile builds rom.c from that file, so assembling it
|
||||
# here says the thing the emulator carries is a thing that still assembles.
|
||||
romBoot | Boot/stage1.asm | rom | selfBoot.in | 90000000 | disks/selfboot.img
|
||||
|
||||
# ---- What starts is a setting, not a name compiled into the loader ----
|
||||
#
|
||||
# Three disks differing only in /System/Boot/boot.cfg, so each is a test of the file rather
|
||||
# than of the machinery under it.
|
||||
#
|
||||
# cfgBare starts an image with NO OPERATING SYSTEM IN IT, which is what loading an ordinary
|
||||
# boot image buys: a program wanting the whole machine is a file like any other, and is
|
||||
# chosen the same way the system is.
|
||||
cfgBare | Boot/stage1.asm | rom | - | 90000000 | disks/cfgbare.img
|
||||
# cfgBroken has every kind of mistake in it and a good setting underneath them. The machine
|
||||
# STILL STARTS - configuration is advice - and says what it could not use on the way, so a
|
||||
# setting somebody meant which did not happen is not silent.
|
||||
cfgBroken | Boot/stage1.asm | rom | - | 90000000 | disks/cfgbroken.img
|
||||
# cfgFallback names a system that is not there. What was tried, and what was tried next.
|
||||
cfgFallback | Boot/stage1.asm | rom | selfBoot.in | 90000000 | disks/cfgfallback.img
|
||||
# And the bare image on its own, which has to keep assembling.
|
||||
bareMetal | Boot/bare.asm | assemble | - | -
|
||||
# Reading a disk that has directories on it. The machine can walk a path at this point but
|
||||
# cannot make a directory, so the disk is built by the host tool and read here - which is
|
||||
# the two implementations checking each other rather than either checking itself.
|
||||
|
||||
Reference in New Issue
Block a user