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 =
Rubout: PUBLIC ERROR = CODE;
GetID:
PUBLIC
PROC [in, out:
IO.
STREAM, default: Rope.
ROPE, init: Rope.
ROPE ←
NIL, echo:
BOOL ←
TRUE]
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: BOOL ← FALSE;
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 = BOOL ← FALSE;
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.ROPE ← NIL;
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.
ROPE ←
NIL] =
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:
INT ←
LAST[
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:
INT ←
LAST[
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.ROPE ← NIL;
lastPattern: Rope.ROPE ← NIL;
lastDest: Rope.ROPE ← NIL;
GetFile:
PUBLIC
PROC[
in, out: IO.STREAM,
prompt: Rope.ROPE ← NIL,
extension: Rope.ROPE ← NIL,
default: Rope.ROPE ← NIL,
wDir: Rope.ROPE ← NIL,
check: BOOL ← FALSE,
pattern: BOOL ← FALSE]
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.ROPE ← NIL;
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.ROPE ← NIL;
DO end:
CHAR;
errors: BOOL ← FALSE;
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.
ROPE ←
NIL]
RETURNS[v: File.Volume] =
BEGIN
Help:
PROC =
BEGIN
first: BOOL ← TRUE;
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: BOOL ← FALSE; -- 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[] };