Cedar: Iago user interface subroutines
IagoOpsImpl.mesa
Andrew Birrell December 7, 1983 10:51 am
Last Edited by: Levin, September 22, 1983 1:32 pm
DIRECTORY
Ascii USING [BS, ControlA, ControlQ, ControlW, ControlX, CR, DEL, Digit, Letter, Lower, SP],
BasicTime USING[ Now, ToNSTime ],
Booting USING[ Switches, switches ],
Convert USING [ RopeFromInt ],
Disk USING [Channel, DriveAttributes, NextChannel, PageCount, PageNumber],
File USING [Error, FindVolumeFromName, GetVolumeName, NextVolume, RC, SystemVolume, Volume, VolumeFile, VolumeID],
FileExtra USING [ReserveNoPages, ReservePages],
FormatDisk --USING everything--,
FS USING[ Error, FileInfo ],
GermSwap USING[ Switch ],
IagoOps USING[ Command ],
IO USING[ EraseChar, GetChar, PutChar, PutF, PutFR, PutRope, STREAM ],
PhysicalVolume USING[ NextPhysical, Physical, PhysicalInfo ],
ProcessorFace USING[ processorID ],
Rope USING [ Cat, Fetch, Find, FromChar, Length, ROPE, Run, Substr ],
SystemVersion USING[ machineType ];
IagoOpsImpl: CEDAR PROGRAM
IMPORTS Ascii, BasicTime, Booting, Convert, Disk, File, FileExtra, FormatDisk, FS, IO, PhysicalVolume, ProcessorFace, Rope, SystemVersion
EXPORTS File--VolumeID--, IagoOps =
BEGIN
******** Input subroutines ********
Rubout: PUBLIC ERROR = CODE;
GetID: PUBLIC PROC [in, out: IO.STREAM, default: Rope.ROPE, init: Rope.ROPENIL, echo: BOOLTRUE] RETURNS [id: Rope.ROPE, c: CHAR] =
BEGIN
OPEN Ascii;
firstTime: BOOLEAN ← init = NIL;
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, DEL => NULL;
ENDCASE => EraseAll[];
firstTime ← FALSE;
};
RETURN[c = SP OR c = CR OR c = '?]
};
id ← Rope.Cat[init, 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: BOOLFALSE;
FOR i: INT DECREASING IN [0..id.Length[]) DO
ch: CHAR = id.Fetch[i];
IF Ascii.Letter[ch] OR Ascii.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.Cat[Rope.FromChar[c]]; IF echo THEN out.PutChar[c]};
c ← in.GetChar[];
ENDLOOP;
END;
GetCommand: PUBLIC PROC[in, out: IO.STREAM, diskReadable: BOOL] RETURNS[found: IagoOps.Command] = -- May raise Rubout
BEGIN
prompt: Rope.ROPE = "\n\n> ";
commands: ARRAY IagoOps.Command OF Rope.ROPE ← [
attach: "Attach File",
bootLogical: "Boot Logical Volume",
checkDrive: "Check Drive",
copy: "Copy File",
createLogical: "Create Logical Volume",
createPhysical: "Create Physical Volume",
createUserWorld: "Create User World",
createVM: "Create VM Backing File",
delete: "Delete Files",
describeDrives: "Describe Drives",
describeLV: "Describe Logical Volumes",
describeMachine: "Describe Machine",
describePV: "Describe Physical Volumes",
eraseLogical: "Erase Logical Volume",
flushCache: "Flush Cache",
format: "Format Disk",
installBoot: "Install Boot File",
installCredentials: "Install Credentials",
installGerm: "Install Germ",
installInitial: "Install Initial Microcode",
installMicrocode: "Install Cedar Microcode",
listCache: "List Cache",
listFileInfo: "List File Info",
listNames: "List Names",
login: "Login",
quit: "Quit",
rename: "Rename",
rollbackLogical: "Rollback Logical Volume",
scavenge: "Scavenge",
setKeep: "Set Keep",
setPhysicalBoot: "Set Physical Boot File",
setPhysicalGerm: "Set Physical Germ",
setPhysicalMicrocode: "Set Physical Microcode",
setWDir: "Set Working Directory"
];
BoolFalse: TYPE = BOOLFALSE;
allowed: PACKED ARRAY IagoOps.Command OF BoolFalse = [ -- if command is allowed with N switch
checkDrive: TRUE,
createPhysical: TRUE,
describeDrives: TRUE,
describeMachine: TRUE,
describePV: TRUE,
format: TRUE,
login: TRUE
];
prefix: Rope.ROPENIL;
out.PutRope[prompt];
DO end: CHAR;
matches: { none, one, many } ← none;
uniqueLength: INT;
[id: prefix, c: end] ← GetID[in: in, out: out, default: NIL, init: prefix];
uniqueLength ← prefix.Length[];
IF end = '? THEN out.PutChar['?];
FOR c: IagoOps.Command IN IagoOps.Command
DO IF (diskReadable OR allowed[c])
AND Rope.Run[s1: commands[c], s2: prefix, case: FALSE] = prefix.Length[]
THEN BEGIN
IF matches = none
THEN BEGIN
matches ← one; uniqueLength ← (commands[c]).Length[]; found ← c;
IF end = '? THEN out.PutRope[" ... one of:\n"];
END
ELSE BEGIN
stillMatches: INT = Rope.Run[s1: commands[c], s2: commands[found], case: FALSE];
uniqueLength ← MIN[uniqueLength, stillMatches];
matches ← many;
IF end = '? THEN out.PutRope[", "];
END;
IF end = '?
THEN out.PutRope[commands[c]];
END;
ENDLOOP;
IF end = '? AND matches # none
THEN { out.PutRope[prompt]; out.PutRope[prefix] }
ELSE BEGIN
IF uniqueLength = prefix.Length[]
THEN BEGIN
SELECT matches FROM
none => out.PutRope[" ... command not found"];
one => EXIT;
many => IF uniqueLength # 0 THEN out.PutRope[" ... ambiguous"];
ENDCASE => ERROR;
out.PutRope[prompt]; out.PutRope[prefix];
END
ELSE BEGIN
extra: Rope.ROPE =
(commands[found]).Substr[start: prefix.Length[], len: uniqueLength-prefix.Length[]];
out.PutRope[extra];
SELECT matches FROM
one => EXIT;
many => prefix ← prefix.Cat[extra];
ENDCASE => ERROR;
END;
END;
ENDLOOP;
END;
GetArg: PUBLIC PROC[in, out: IO.STREAM, prompt, default: Rope.ROPE, help: PROC] RETURNS[value: Rope.ROPENIL] =
BEGIN
DO end: CHAR;
out.PutRope[prompt];
out.PutRope[value];
[id: value, c: end] ← GetID[in: in, out: out, default: default, init: value];
default ← NIL;
IF end = '? THEN help[] ELSE EXIT;
ENDLOOP;
END;
Confirm: PUBLIC PROC[in, out: IO.STREAM] RETURNS[BOOL] =
BEGIN
DO c: CHAR = in.GetChar[];
SELECT c FROM
'y, 'Y, Ascii.CR => { out.PutRope["yes"]; RETURN[TRUE] };
'n, 'N => { out.PutRope["no"]; RETURN[FALSE] };
Ascii.DEL => ERROR Rubout[];
ENDCASE =>
out.PutRope["?\nConfirm by typing \"Y\" or CR, deny by typing \"N\", abort by typing DEL: "];
ENDLOOP;
END;
ConfirmDestruction: PUBLIC PROC[in, out: IO.STREAM, which: Rope.ROPE] RETURNS[BOOL] =
BEGIN
out.PutF["\nThis operation will destroy all files on %g.\nAre you sure you want to do this? ",
[rope[which]] ];
RETURN[Confirm[in, out]]
END;
GetNumber: PUBLIC PROC[in, out: IO.STREAM, default: INT, max: INTLAST[INT], prompt, help: Rope.ROPE] RETURNS[size: INT ← 0] =
BEGIN
Help: PROC =
{ out.PutRope[help] };
sizeRope: Rope.ROPE ← Convert.RopeFromInt[default];
DO BEGIN
sizeRope ← GetArg[
in: in,
out: out,
prompt: prompt,
default: sizeRope,
help: Help];
size ← 0;
FOR i: CARDINAL IN CARDINAL[0..sizeRope.Length[])
DO c: CHAR = sizeRope.Fetch[i];
IF c IN ['0..'9]
THEN size ← size * 10 + (c-'0)
ELSE { out.PutRope[" ... not a number"]; EXIT };
REPEAT FINISHED =>
IF size <= max
THEN EXIT--from outer loop--
ELSE out.PutF[" ... number should be less than %g", [integer[max]] ];
ENDLOOP;
END;
ENDLOOP;
END;
GetSize: PUBLIC PROC[in, out: IO.STREAM, default: INT, max: INTLAST[INT]] RETURNS[size: INT ← 0] =
{ RETURN[GetNumber[in, out, default, max, "\nSize in pages: ", "? type the desired number of pages (in decimal)"]] };
Defaults for "GetFile":
lastFile: Rope.ROPENIL;
lastPattern: Rope.ROPENIL;
lastDest: Rope.ROPENIL;
GetFile: PUBLIC PROC[
in, out: IO.STREAM,
prompt: Rope.ROPENIL,
extension: Rope.ROPENIL,
default: Rope.ROPENIL,
wDir: Rope.ROPENIL,
check: BOOLFALSE,
pattern: BOOLFALSE]
RETURNS[name: Rope.ROPE] =
BEGIN
Help: PROC =
BEGIN
out.PutRope["? type an FS file name"];
IF pattern THEN out.PutRope[" (or pattern)"];
out.PutRope[" such as \"[]<volume>a.b\" or \"[server]<directory>c.d\""];
END;
name ← SELECT TRUE FROM
default # NIL => default,
pattern => lastPattern,
check => lastFile,
ENDCASE => lastDest;
DO BEGIN
name ← GetArg[
in: in,
out: out,
prompt: Rope.Cat["\n", prompt, "file name", IF pattern THEN " or pattern: " ELSE ": "],
default: name,
help: Help];
IF name.Length[] = 0 THEN { Help[]; LOOP };
IF name.Find["."] < 0 AND extension.Length[] > 0
THEN { name ← name.Cat[extension]; out.PutRope[extension] };
IF check OR pattern
THEN BEGIN
lastPattern ← name;
IF lastPattern.Find["*"] < 0 THEN lastFile ← lastPattern;
END
ELSE lastDest ← name;
IF check
THEN BEGIN
name ← FS.FileInfo[name: name, wDir: wDir ! FS.Error => SELECT error.code FROM
$unknownServer, $unknownVolume, $unknownFile, $illegalName, $patternNotAllowed =>
{ out.PutRope[" ... "]; out.PutRope[error.explanation]; LOOP };
ENDCASE => NULL ].fullFName;
END;
EXIT;
END;
ENDLOOP;
END;
GetSwitches: PUBLIC PROC[in, out: IO.STREAM] RETURNS[switches: Booting.Switches] =
BEGIN
RopeDefaultNil: TYPE = Rope.ROPENIL;
switchTable: ARRAY GermSwap.Switch OF RopeDefaultNil ← [
zero: "worry-call debugger as early as possible",
one: "call debugger after MesaRuntime initialization",
two: "call debugger after VM initialization",
three: "call debugger after File initialization",
four: "call debugger after finding VM file and swapping in non-initial parts of boot file",
five: "call debugger in LoaderDriver after loading but before starting loadees",
a: "don't start Ethernet1 drivers",
b: "don't start Ethernet2 drivers",
c: "don't start Communication package",
d: "development version: don't try the Cedar directory in LoaderDriver/Installer",
f: "do full booting sequence, even if defaults would imply a rollback",
h: "hang in CPU loop with MP code instead of swapping to debugger",
i: "enable control-swat as early as possible (instead of waiting until after login)",
l: "long (and interactive) installation dialogue",
n: "don't touch the disk during initialization (forces entry to Iago)",
p: "don't read user profile from disk",
r: "rollback if the system volume has a valid checkpoint",
s: "use software safe storage ops instead of microcode",
t: "force teledebugging instead of disk world-swap",
v: "perform VM copying for checkpoint (internal use only!)",
w: "for world-swap debugger, assume debuggee outload file is valid/interesting"
];
value: Rope.ROPENIL;
DO end: CHAR;
errors: BOOLFALSE;
FromChar: PROC[c: CHAR] =
BEGIN
SELECT TRUE FROM
Ascii.Letter[c] => switches[VAL[GermSwap.Switch.a.ORD+(Ascii.Lower[c]-'a)]] ← TRUE;
Ascii.Digit[c] => switches[VAL[GermSwap.Switch.zero.ORD+(c-'0)]] ← TRUE;
ENDCASE =>
BEGIN
out.PutRope[IF errors THEN ", " ELSE "\nUnrecognized switches: "];
out.PutChar[c];
errors ← TRUE;
END;
END;
ToChar: PROC [sw: GermSwap.Switch] RETURNS [CHAR] =
BEGIN
IF sw IN [zero..nine]
THEN RETURN ['0+sw.ORD]
ELSE RETURN['A + (sw.ORD - GermSwap.Switch.a.ORD)]
END;
out.PutRope["\nSwitches: "];
out.PutRope[value];
[id: value, c: end] ← GetID[in: in, out: out, default: NIL, init: value];
IF end = '? THEN out.PutChar[end];
switches ← ALL[FALSE];
FOR i: INT IN [0..value.Length[]) DO FromChar[value.Fetch[i]] ENDLOOP;
IF end = '?
THEN BEGIN
all: BOOL = value.Length[] = 0;
IF all THEN out.PutRope[" Type one or more boot switches."];
out.PutRope["\nThe boot switches have the following meanings:"];
FOR sw: GermSwap.Switch IN GermSwap.Switch
DO IF (all AND switchTable[sw] # NIL) OR switches[sw]
THEN BEGIN
out.PutRope["\n "];
out.PutChar[ToChar[sw]];
out.PutRope[": "];
out.PutRope[IF switchTable[sw] = NIL
THEN "I don't know any meaning for this switch"
ELSE switchTable[sw]];
END;
ENDLOOP;
END
ELSE { IF NOT errors THEN EXIT };
ENDLOOP;
END;
clientVolName: PUBLIC Rope.ROPE
IF File.SystemVolume[] = NIL THEN "Cedar" ELSE File.GetVolumeName[File.SystemVolume[]];
GetLogical: PUBLIC PROC[in, out: IO.STREAM, direction: Rope.ROPENIL] RETURNS[v: File.Volume] =
BEGIN
Help: PROC =
BEGIN
first: BOOLTRUE;
out.PutRope["? one of:\n"];
FOR v: File.Volume ← File.NextVolume[NIL], File.NextVolume[v] UNTIL v = NIL
DO IF first THEN first ← FALSE ELSE out.PutRope[", "];
out.PutRope[File.GetVolumeName[v ! File.Error => CONTINUE]];
ENDLOOP;
END;
name: Rope.ROPE ← clientVolName;
DO
name ← GetArg[
in: in,
out: out,
prompt: Rope.Cat["\n", direction, "logical volume name: "],
default: name,
help: Help];
v ← File.FindVolumeFromName[name];
IF v # NIL THEN EXIT;
out.PutRope[" ... that volume doesn't exist"];
ENDLOOP;
clientVolName ← name;
END;
GetPhysical: PUBLIC PROC[in, out: IO.STREAM] RETURNS[p: PhysicalVolume.Physical] =
BEGIN
p ← PhysicalVolume.NextPhysical[NIL];
IF p # NIL
THEN out.PutF["\nOn physical volume \"%g\"", [rope[PhysicalVolume.PhysicalInfo[p].name]] ];
END;
GetDrive: PUBLIC PROC[in, out: IO.STREAM] RETURNS[d: Disk.Channel] =
BEGIN
d ← Disk.NextChannel[NIL];
IF d # NIL
THEN out.PutF["\nOn drive RD%g", [integer[Disk.DriveAttributes[d].ordinal]] ];
END;
FileError: PUBLIC PROC[why: File.RC] RETURNS[Rope.ROPE] =
BEGIN
RETURN[SELECT why FROM
wentOffline => "volume is offline",
nonCedarVolume => "can't do that to 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",
unknownFile => "the file has been deleted",
unknownPage => "accessing beyond the end of the file",
volumeFull => "volume full",
fragmented => "the volume is too fragmented",
mixedDevices => "the file (or part of it) is on the wrong disk",
ENDCASE => "unknown File.Error: consult an expert"]
END;
reserved: PhysicalVolume.Physical ← NIL;
interacted: BOOLFALSE; -- whether we've asked the user to pronounce on Alto regions
altoRegions: INT;
ReservePages: PUBLIC PROC[in, out: IO.STREAM, p: PhysicalVolume.Physical] =
BEGIN
IF Disk.DriveAttributes[PhysicalVolume.PhysicalInfo[p].channel].ordinal # 0
OR p = reserved
THEN RETURN;
[] ← CheckAltoRegions[in, out];
FileExtra.ReserveNoPages[p];
FileExtra.ReservePages[
physical: p, start: FormatDisk.hardUCodeStart, size: FormatDisk.hardUCodeSize];
FileExtra.ReservePages[
physical: p,
start: IF FormatDisk.altoRegionsBottomUp
THEN FormatDisk.altoRegionsStart
ELSE [FormatDisk.altoRegionsStart + FormatDisk.altoRegionsSize * (FormatDisk.altoRegionsMax - altoRegions)],
size: FormatDisk.altoRegionsSize * altoRegions];
reserved ← p;
END;
CheckAltoRegions: PUBLIC PROC[in, out: IO.STREAM] RETURNS[BOOL] =
BEGIN
IF NOT interacted THEN ReserveAltoRegions[in, out];
RETURN[ altoRegions#0 ]
END;
ReserveAltoRegions: PUBLIC PROC[in, out: IO.STREAM] =
BEGIN
reserved ← NIL;
interacted ← FALSE;
altoRegions ← GetNumber[
in: in,
out: out,
default: 0,
max: FormatDisk.altoRegionsMax,
prompt: Rope.Cat["\nHow many Alto ", FormatDisk.altoRegionJargon, " do you want to have on drive RD0? "],
help: IO.PutFR["? type the desired number of %g, in the range [ 0 .. %g )",
[rope[FormatDisk.altoRegionJargon]], [integer[FormatDisk.altoRegionsMax]] ]
];
interacted ← TRUE;
END;
NextRun: PUBLIC PROC[d: Disk.Channel, origin: Disk.PageNumber] RETURNS[firstPage: Disk.PageNumber, pageCount: Disk.PageCount-- -1 at end of disk --] =
BEGIN
altoCount: Disk.PageCount = FormatDisk.altoRegionsSize * altoRegions;
altoStart: Disk.PageNumber = IF FormatDisk.altoRegionsBottomUp
THEN FormatDisk.altoRegionsStart
ELSE [FormatDisk.altoRegionsStart + FormatDisk.altoRegionsSize * (FormatDisk.altoRegionsMax - altoRegions)];
diskCount: Disk.PageCount = Disk.DriveAttributes[d].nPages;
IF origin >= diskCount THEN RETURN[origin, -1];
IF Disk.DriveAttributes[d].ordinal # 0 THEN RETURN[origin, diskCount-origin];
First find a starting point outside reserved areas
firstPage ← origin;
IF firstPage >= altoStart
AND firstPage <= altoStart + altoCount
THEN firstPage ← [altoStart + altoCount];
IF firstPage >= FormatDisk.hardUCodeStart
AND firstPage <= FormatDisk.hardUCodeStart + FormatDisk.hardUCodeSize
THEN firstPage ← [FormatDisk.hardUCodeStart + FormatDisk.hardUCodeSize];
Then find how many pages we can manage
pageCount ← diskCount-firstPage;
IF firstPage < altoStart
AND firstPage+pageCount > altoStart
THEN pageCount ← altoStart-firstPage;
IF firstPage < FormatDisk.hardUCodeStart
AND firstPage+pageCount > FormatDisk.hardUCodeStart
THEN pageCount ← FormatDisk.hardUCodeStart-firstPage;
END;
--File.--VolumeID: PUBLIC TYPE = MACHINE DEPENDENT RECORD[
a,b,c: CARDINAL,
t: LONG CARDINAL
];
NewID: PUBLIC PROC RETURNS[new: VolumeID] =
BEGIN
id: RECORD[a,b,c: CARDINAL] = LOOPHOLE[ProcessorFace.processorID];
new.a ← id.a;
new.b ← id.b;
new.c ← id.c;
new.t ← BasicTime.ToNSTime[BasicTime.Now[]];
END;
PutID: PUBLIC PROC[out: IO.STREAM, id: VolumeID] =
BEGIN
out.PutF["[%b,%b,%b,%b]",
[cardinal[id.a]],
[cardinal[id.b]],
[cardinal[id.c]],
[cardinal[id.t]]
];
END;
InitialMicrocodeFileName: PUBLIC PROC RETURNS[Rope.ROPE] =
BEGIN
RETURN[
Rope.Cat["[Indigo]<",
IF Booting.switches[d] THEN "PreCedar" ELSE "Cedar",
">Top>",
SELECT SystemVersion.machineType FROM
dolphin => "InitialPilot",
dorado => "InitialEtherCedarDorado",
dicentra => "InitialEtherCedarDicentra",
ENDCASE => "SomethingButIDontKnowWhat",
".eb"]
]
END;
RemoteRootFileName: PUBLIC PROC[which: File.VolumeFile[checkpoint..bootFile]] RETURNS[Rope.ROPE] =
BEGIN
machine: Rope.ROPE = SELECT SystemVersion.machineType FROM
dolphin => "D0",
dorado => "Dorado",
dandelion => "DLion",
dicentra => "Dicentra",
ENDCASE => NIL;
dir: Rope.ROPE =
IF Booting.switches[d] THEN "[Indigo]<PreCedar>Top>" ELSE "[Indigo]<Cedar>Top>";
RETURN[SELECT which FROM
checkpoint => NIL,
microcode => Rope.Cat[dir, "Cedar", machine, ext[which]],
germ => Rope.Cat[dir, machine, ext[which]],
bootFile => Rope.Cat[dir, IF Booting.switches[d] THEN "BasicCedar" ELSE "Cedar", machine, ext[which]],
ENDCASE => ERROR]
END;
LocalRootFileName: PUBLIC PROC[which: File.VolumeFile] RETURNS[Rope.ROPE] =
BEGIN
RETURN[Rope.Cat[
SELECT which FROM
checkpoint => "Checkpoint",
microcode => "Microcode",
germ => "Germ",
bootFile => "BootFile",
debugger => "Debugger",
debuggee => "Debuggee",
VM => "VM",
VAM => "VAM",
client => "FS",
alpine => "Alpine",
ENDCASE => ERROR, ".DontDeleteMe"] ]
END;
ext: PUBLIC ARRAY File.VolumeFile[checkpoint..bootFile] OF Rope.ROPE ← [
checkpoint: ".outload",
microcode: ".eb",
germ: ".germ",
bootFile: ".boot"];
TRUSTED{ FormatDisk.Start[] };
END.