Cedar: Loader driver for running programs on top of the basic boot file
LoaderDriver.mesa
Andrew Birrell August 2, 1983 1:19 pm
Derived loosely from BasicLoader.mesa of June 9, 1982 9:51 am
DIRECTORY
Ascii USING [BS, ControlA, ControlQ, ControlW, ControlX, CR, DEL, LF, SP, TAB],
DebuggerSwap USING [CallDebugger],
File USING [EraseVolume, Error, FindVolumeFromName, Volume],
FS USING [Error, ErrorDesc, Open, OpenFile],
FSFileStream USING [Create],
GermSwap USING [switches ],
IO USING[ BreakProc, EndOfStream, EraseChar, GetChar, GetToken, noWhereStream, PutChar, PutF, PutRope, PutToRope, STREAM ],
Loader USING [Error, ErrorType, Instantiate, Start],
PrincOps USING [ControlModule, NullControl],
Rope USING [ Cat, Concat, Digit, Equal, Fetch, Find, FromChar, Index, Length, Letter, ROPE, Substr ],
SimpleTerminal USING [TurnOff, TurnOn],
SystemVersion USING [release],
UserCredentials USING [Get];
LoaderDriver:
CEDAR
PROGRAM
IMPORTS DebuggerSwap, File, FS, FSFileStream, GermSwap, IO, Loader, Rope, SimpleTerminal, SystemVersion, UserCredentials =
BEGIN
in, out: IO.STREAM;
ttyOff: BOOL ← FALSE;
GetFileStr:
PROC[cmName: Rope.
ROPE]
RETURNS[
IO.
STREAM] =
TEMP, until FileIO works
{ RETURN[ FSFileStream.Create[FS.Open[cmName], oldReadOnly] ] };
MyBreak:
IO.BreakProc =
BEGIN
SELECT char
FROM
Ascii.SP, Ascii.CR, Ascii.TAB, Ascii.LF, ', => RETURN[sepr];
'/ => RETURN[break];
ENDCASE => RETURN[other];
END;
PutFSError:
PROC[error:
FS.ErrorDesc] =
BEGIN
IF error.code = notFound
THEN out.PutRope["not found"]
ELSE
BEGIN
out.PutRope["FS.Error: "];
out.PutRope[IF error.explanation.Length[] # 0 THEN error.explanation ELSE "no message"];
END;
END;
PutLoaderError:
PROC[type: Loader.ErrorType, message: Rope.
ROPE] =
BEGIN
out.PutRope["Loader.Error["];
out.PutRope[
SELECT type
FROM
invalidBcd => "invalid BCD",
fileNotFound => "file not found (?)",
versionMismatch => "version mismatch",
loadStateFull => "loadstate full",
insufficientVM => "insufficient VM",
ENDCASE => "unknown"];
IF message.Length[] # 0 THEN { out.PutChar[',]; out.PutRope[message] };
out.PutChar[']];
END;
Loadee:
TYPE =
RECORD[
rest: REF Loadee ← NIL,
name: Rope.ROPE ← NIL,
cm: PrincOps.ControlModule ← PrincOps.NullControl,
codeLinks: BOOL ← TRUE,
callDebugger: BOOL ← FALSE,
turnOffTerminal: BOOL ← FALSE];
TryFile:
PROC[cmName: Rope.
ROPE]
RETURNS[done:
BOOL ←
TRUE] =
BEGIN
fileStr: IO.STREAM;
GetToken:
PROC
RETURNS[Rope.
ROPE] =
{ RETURN[ fileStr.GetToken[MyBreak] ] };
loadees: REF Loadee ← NIL;
last: REF Loadee ← NIL;
loadFailed: BOOL ← FALSE;
nextToken: Rope.ROPE ← NIL;
out.PutF["\nTrying \"%g\" ... ", [rope[cmName]]];
fileStr ← GetFileStr[cmName ! FS.Error => { PutFSError[error]; GOTO failed }];
out.PutRope["ok\nParsing the command file ... "];
DO
-- parse input for each loadee
this: REF Loadee ← NEW[Loadee ← [] ];
end: BOOL ← FALSE;
swSep: Rope.ROPE = "/";
IF nextToken = NIL THEN nextToken ← GetToken[ ! IO.EndOfStream => EXIT];
IF nextToken.Equal[swSep]
THEN end ← TRUE
ELSE
BEGIN
IF last = NIL THEN loadees ← this ELSE last.rest ← this;
last ← this;
this.name ← nextToken;
IF this.name.Find["."] < 0 THEN this.name ← this.name.Cat[".bcd"];
nextToken ← GetToken[ ! IO.EndOfStream => { nextToken ← NIL; CONTINUE }];
END;
IF nextToken.Equal[swSep]
THEN
BEGIN
positive: BOOL ← TRUE;
nextToken ← GetToken[ ! IO.EndOfStream => { nextToken ← NIL; CONTINUE }];
FOR i: NAT IN NAT[0..nextToken.Length[])
DO
SELECT nextToken.Fetch[i]
FROM
'-, '~ => { positive ← FALSE; LOOP };
'l, 'L => this.codeLinks ← positive;
'd, 'D => this.callDebugger ← positive;
't, 'T => this.turnOffTerminal ← positive;
'e, 'E => end ← positive;
ENDCASE => NULL;
positive ← TRUE
ENDLOOP;
nextToken ← NIL;
END;
IF end THEN EXIT;
ENDLOOP;
out.PutRope["ok"];
FOR this: REF Loadee ← loadees, this.rest UNTIL this = NIL
DO
-- load them
BEGIN
thisFile: FS.OpenFile;
unbound: BOOL;
out.PutF["\nLoading \"%g\"", [rope[this.name]] ];
IF NOT this.codeLinks THEN out.PutRope[" (frame links)"];
out.PutRope[" ... "];
thisFile ←
FS.Open[this.name !
FS.Error => { PutFSError[error]; GOTO thisFailed } ];
TRUSTED{[cm: this.cm, unboundImports: unbound] ←
Loader.Instantiate[file: thisFile, codeLinks: this.codeLinks !
FS.Error => { PutFSError[error]; GOTO thisFailed };
Loader.Error => { PutLoaderError[type, message]; GOTO thisFailed } ]};
out.PutRope["ok"];
IF unbound THEN out.PutRope[" (has unbound imports)"];
EXITS thisFailed => loadFailed ← TRUE;
END;
ENDLOOP;
IF loadFailed
THEN out.PutRope["\nThere were loading errors. I'm giving up."]
ELSE
BEGIN
FOR this: REF Loadee ← loadees, this.rest UNTIL this = NIL
DO
-- start them
IF this.turnOffTerminal AND NOT ttyOff
THEN { out.PutRope["\nTerminal off.\n"]; SimpleTerminal.TurnOff[]; ttyOff ← TRUE; out ← IO.noWhereStream };
IF this.callDebugger
THEN TRUSTED{DebuggerSwap.CallDebugger["/d switch in basic loadees"]};
out.PutF["\nStarting \"%g\" ... ", [rope[this.name]] ];
TRUSTED{Loader.Start[this.cm]};
out.PutRope["ok"];
ENDLOOP;
out.PutRope["\nEnd of the command file"];
END;
EXITS failed => done ← FALSE;
END;
TryErase:
PROC[name: Rope.
ROPE]
RETURNS[
BOOL] =
BEGIN
volume: File.Volume = File.FindVolumeFromName[name];
IF volume = NIL THEN { out.PutRope[" ... volume not found"]; RETURN[FALSE] };
out.PutRope[" ... erasing ... "];
File.EraseVolume[volume ! File.Error =>
BEGIN
out.PutRope[
SELECT why
FROM
wentOffline => "volume is offline",
nonCedarVolume => "can't erase a non-Cedar volume",
inconsistent => "the volume root page seems to be inconsistent",
software => "label-check: consult an expert",
hardware => "hard disk error: consult an expert",
ENDCASE => ERROR];
GOTO bad
END ];
out.PutRope["ok"];
RETURN[TRUE]
EXITS bad => RETURN[FALSE]
END;
OptionalCommand:
PROC[condition, prompt, default: Rope.
ROPE,
work: PROC[Rope.ROPE] RETURNS[BOOL]]
RETURNS[BOOL] =
BEGIN
"GetID" copied from UserCredentialsImpl
Rubout: ERROR = CODE;
GetID:
PROC [default: Rope.
ROPE, echo:
BOOL ←
TRUE]
RETURNS [id: Rope.
ROPE] = {
OPEN Ascii;
firstTime: BOOLEAN ← TRUE;
c: CHAR;
EraseAll:
PROC = {
IF echo
THEN
FOR i: INT DECREASING IN [0..id.Length[]) DO out.EraseChar[id.Fetch[i]]; ENDLOOP;
id ← NIL;
};
Done:
PROC [c:
CHAR]
RETURNS [
BOOL] =
INLINE {
IF firstTime
THEN {
SELECT c
FROM
ControlA, BS, ControlQ, ControlW, ControlX, CR, SP => NULL;
ENDCASE => EraseAll[];
firstTime ← FALSE;
};
RETURN[c = SP OR c = CR]
};
id ← default;
IF echo THEN out.PutRope[default];
c ← in.GetChar[];
UNTIL Done[c]
DO
SELECT c
FROM
DEL => ERROR Rubout;
ControlA,
BS => {
len: INT ← id.Length[];
IF len > 0
THEN {
len ← len - 1;
IF echo THEN out.EraseChar[id.Fetch[len]];
id ← id.Substr[len: len];
};
};
ControlW, ControlQ => {
text to be backed up is of the form ...<non-alpha><alpha><non-alpha>, the <alpha> and following <non-alpha> are to be removed.
alpha: BOOL ← FALSE;
FOR i:
INT
DECREASING
IN [0..id.Length[])
DO
ch: CHAR = id.Fetch[i];
IF Rope.Letter[ch] OR Rope.Digit[ch] THEN alpha ← TRUE
ELSE IF alpha THEN {id ← id.Substr[len: i + 1]; EXIT};
IF echo THEN out.EraseChar[ch];
REPEAT
FINISHED => id ← NIL;
ENDLOOP;
};
ControlX => EraseAll[];
ENDCASE => {id ← id.Concat[Rope.FromChar[c]]; IF echo THEN out.PutChar[c]};
c ← in.GetChar[];
ENDLOOP;
};
arg: Rope.ROPE ← default;
DO out.PutChar['\n]; out.PutRope[condition]; out.PutRope[" [Y or N]"];
SELECT in.GetChar[]
FROM
'y, 'Y, '\n =>
BEGIN
out.PutRope[" yes"];
DO
BEGIN
ENABLE Rubout => { out.PutRope[" XXX"]; EXIT };
out.PutChar['\n]; out.PutRope[prompt]; out.PutRope[": "];
arg ← GetID[arg];
IF work[arg] THEN RETURN[TRUE];
END
ENDLOOP;
END;
'n, 'N => { out.PutRope[" no"]; RETURN[FALSE] };
ENDCASE => out.PutRope["\nPlease respond with \"Y\" or \"N\""];
ENDLOOP;
END;
Main:
PROC =
BEGIN
version: Rope.
ROPE = Rope.Cat[
".",
IO.PutToRope[[cardinal[SystemVersion.release.major]]],
".",
IO.PutToRope[[cardinal[SystemVersion.release.minor]]],
".",
IO.PutToRope[[cardinal[SystemVersion.release.patch]]] ];
loadees: Rope.ROPE = Rope.Cat["BasicLoadees", version];
user: Rope.ROPE = UserCredentials.Get[].name;
briefUser: Rope.ROPE = user.Substr[0, user.Index[0, "."]];
[in: in, out: out] ← SimpleTerminal.TurnOn[];
ttyOff ← FALSE;
out.PutRope["This is the \"Basic\" boot file."];
IF GermSwap.switches[l]
THEN [] ← OptionalCommand["Do you want to erase a volume?",
"Volume name",
"NucleusTest",
TryErase];
IF GermSwap.switches[l]
AND OptionalCommand["Do you want to specify an explicit \"Loadees\" command file name?",
"Command file name",
Rope.Cat["[Ivy]<", briefUser, ">", "UtilityLoadees.cm"],
TryFile]
THEN NULL
ELSE
BEGIN
out.PutRope["\nLooking for a \"BasicLoadees\" command file."];
IF TryFile[Rope.Cat["[Ivy]<", briefUser, ">", loadees]]
OR (NOT GermSwap.switches[d] AND TryFile[Rope.Cat["[Indigo]<Cedar>Top>", loadees]])
OR TryFile[Rope.Cat["[Indigo]<PreCedar>Top>", loadees]]
OR TryFile[Rope.Cat["[Indigo]<Nucleus>Top>", loadees]]
THEN NULL
ELSE out.PutRope["\nCan't find any command file."];
END;
END;
Main[];
END.