Cedar: Iago user interface
IagoCommandsImpl.mesa
Andrew Birrell December 7, 1983 4:07 pm
Last Edited by: Levin, September 22, 1983 1:32 pm
DIRECTORY
Basics USING[ BITNOT, LowHalf ],
BasicTime USING[ FromPupTime, GetClockPulses, GMT, TimeNotKnown ],
Booting USING[ Boot, Switches, switches ],
DebuggerSwap USING [EnableSwatWatcher],
Disk USING [Channel, DriveAttributes, Label, NextChannel, PageCount, PageNumber],
File USING [EraseVolume, Error, FindVolumeFromName, FindVM, FP, GetRoot, GetVolumeID, GetVolumeName, GetVolumePages, Handle, Info, IsDebugger, LogicalInfo, MarkDebugger, NextVolume, nullFP, Open, RC, SetRoot, SetSize, SystemVolume, Volume, VolumeID, VolumeFile, wordsPerPage],
FileExtra USING [CreateLogicalVolume, CreatePhysicalVolume, PhysicalPageBad],
FormatDisk USING [BadPage, IdentifyInitialMicrocode, InstallInitialMicrocode, MicrocodeInstallFailure, Operation, Sweep],
FS USING[ Close, ComponentPositions, Copy, Create, Delete, EnumerateForInfo, EnumerateForNames, Error, ExpandName, GetInfo, InfoProc, NameProc, Open, OpenFile, Read, Rename, SetKeep, SetDefaultWDir ],
FSBackdoor USING[ CloseVolume, EnumerateCacheForNames, Flush, FNameFromHandle, GetFileHandle, NameProc, ScavengeDirectoryAndCache ],
GermSwap USING[ bootedFrom, Switch ],
IagoOps --USING everything--,
IO USING[ PutChar, PutF, PutRope, STREAM ],
PhysicalVolume USING[ GetSubVolumes, NextPhysical, Physical, PhysicalInfo, PhysicalRC, SetPhysicalRoot, SubVolumes ],
ProcessorFace USING[ processorID ],
PupDefs USING[ GetMyName ],
Rope USING [ Cat, Equal, Fetch, Find, Index, Length, ROPE, Run, Substr ],
SimpleTerminal USING [TurnOff, TurnOn],
SystemVersion USING[ machineType, uCodeCedar, uCodeDate, uCodeFloatingPoint, uCodeVersion ],
UserCredentials USING [ChangeState, Login, LoginOptions],
VM USING[ AddressForPageNumber, Allocate, Free, Interval, PagesForWords, Pin],
VMSideDoor USING [rmPages];
IagoCommandsImpl: CEDAR PROGRAM
IMPORTS Basics, BasicTime, Booting, DebuggerSwap, Disk, File, FileExtra, FormatDisk, FS, FSBackdoor, GermSwap, IagoOps, IO, PhysicalVolume, ProcessorFace, PupDefs, Rope, SimpleTerminal, SystemVersion, UserCredentials, VM, VMSideDoor =
BEGIN
******** The command subroutines (alphabetic order) ********
Attach: PROC[in, out: IO.STREAM] =
BEGIN
from: Rope.ROPE = IagoOps.GetFile[
in: in,
out: out,
prompt: "From ",
check: TRUE];
to: Rope.ROPE = IagoOps.GetFile[
in: in,
out: out,
prompt: "To "];
out.PutRope[" ... "];
FS.Copy[from: from, to: to, remoteCheck: FALSE, attach: TRUE];
out.PutRope["done"];
END;
BootLogical: PROC[in, out: IO.STREAM] =
BEGIN
v: File.Volume = IagoOps.GetLogical[in, out];
reject: Rope.ROPE = Booting.Boot[[logical[v]], IagoOps.GetSwitches[in, out]];
IF reject # NIL THEN { out.PutRope[" ... couldn't: "]; out.PutRope[reject] };
END;
Copy: PROC[in, out: IO.STREAM] =
BEGIN
from: Rope.ROPE = IagoOps.GetFile[
in: in,
out: out,
prompt: "From ",
check: TRUE];
to: Rope.ROPE = IagoOps.GetFile[
in: in,
out: out,
prompt: "To "];
out.PutRope[" ... "];
FS.Copy[from: from, to: to];
out.PutRope["done"];
END;
CreateLogical: PROC[in, out: IO.STREAM] =
BEGIN
name: Rope.ROPEIF File.FindVolumeFromName[IagoOps.clientVolName] = NIL
THEN IagoOps.clientVolName
ELSE NIL;
size: INT;
p: PhysicalVolume.Physical;
free: INT;
HelpCreation: PROC =
BEGIN
out.PutRope["? type a string which is not the same as the name of any existing volume"];
END;
DO
name ← IagoOps.GetArg[
in: in,
out: out,
prompt: Rope.Cat["\nNew volume name: "],
default: name,
help: HelpCreation];
IF File.FindVolumeFromName[name] # NIL
THEN out.PutRope[" ... already exists"]
ELSE EXIT
ENDLOOP;
p ← IagoOps.GetPhysical[in, out];
IagoOps.ReservePages[in, out, p];
free ← PhysicalVolume.PhysicalInfo[p].free;
size ← IagoOps.GetSize[in: in, out: out, default: free, max: free];
out.PutRope["\nAre you sure? "];
IF IagoOps.Confirm[in, out]
THEN BEGIN
new: File.Volume ← NIL;
out.PutRope[" ... "];
new ← FileExtra.CreateLogicalVolume[
id: IagoOps.NewID[], where: LIST[p], size: size, name: name];
out.PutRope["done"];
IagoOps.clientVolName ← name;
out.PutRope["\nDo you want to use this as the system volume for a disk world-swap debugger (\"CoCedar\")? "];
IF IagoOps.Confirm[in, out] THEN File.MarkDebugger[new, TRUE];
END;
END;
CloseFSVolumes: PROC =
BEGIN
FOR v: File.Volume ← File.NextVolume[NIL], File.NextVolume[v] UNTIL v = NIL
DO FSBackdoor.CloseVolume[v] ENDLOOP;
END;
CreatePhysical: PROC[in, out: IO.STREAM] RETURNS[done: BOOLFALSE] =
BEGIN
HelpCreation: PROC =
BEGIN
out.PutRope["? type a string which is not the same as the name of any existing volume"];
END;
d: Disk.Channel = IagoOps.GetDrive[in, out];
alto: BOOL = Disk.DriveAttributes[d].ordinal = 0 AND IagoOps.CheckAltoRegions[in, out];
name: Rope.ROPE = IagoOps.GetArg[
in: in,
out: out,
prompt: Rope.Cat["\nNew volume name: "],
default: PupDefs.GetMyName[],
help: HelpCreation];
IF IagoOps.ConfirmDestruction[in, out, Rope.Cat[
"the entire disk drive",
IF alto THEN " (excluding Alto regions)" ELSE NIL ] ]
THEN BEGIN
new: PhysicalVolume.Physical;
CloseFSVolumes[];
new ← FileExtra.CreatePhysicalVolume[where: d, name: name, id: IagoOps.NewID[]];
IagoOps.ReservePages[in, out, new];
done ← TRUE;
InstallCredentials[in, out];
DebuggerSwap.EnableSwatWatcher[TRUE];
END;
END;
CreateUserWorld: PROC[in, out: IO.STREAM] =
BEGIN
Install: PROC[where: File.Volume, which: File.VolumeFile[microcode..bootFile]] =
BEGIN
wDir: Rope.ROPE = Rope.Cat["[]<", File.GetVolumeName[where], ">"];
remote: Rope.ROPE = IagoOps.RemoteRootFileName[which];
local: Rope.ROPE = IagoOps.LocalRootFileName[which];
fsFile: FS.OpenFile;
file: File.Handle;
out.PutF["\nInstalling %g as %g%g ... ",
[rope[remote]], [rope[wDir]], [rope[local]] ];
FS.Copy[from: remote, to: local, wDir: wDir];
fsFile ← FS.Open[name: local, lock: write, wDir: wDir];
file ← FSBackdoor.GetFileHandle[fsFile];
File.SetRoot[which, file];
FS.Close[fsFile];
out.PutRope["done"];
END;
format, coCedar, protect: BOOL;
altoText: Rope.ROPENIL;
myName: Rope.ROPE = PupDefs.GetMyName[];
d: Disk.Channel = Disk.NextChannel[NIL];
newP: PhysicalVolume.Physical;
newC, newD: File.Volume;
out.PutChar['\n];
IagoOps.ReserveAltoRegions[in, out];
IF IagoOps.CheckAltoRegions[in, out]
THEN altoText ← " (excluding the Alto disk regions)";
out.PutRope["\n\nHas your disk already been formatted? (Confirm if the disk has previously been used for Cedar or Pilot, AND you have not reduced the amount of disk reserved for Alto volumes) "];
format ← NOT IagoOps.Confirm[in, out];
out.PutRope["\n\nDo you want to password-protect this disk so that only you can access it? "];
protect ← IagoOps.Confirm[in, out];
out.PutRope["\n\nDo you want to have a disk world-swap debugger (CoCedar) on this disk? "];
coCedar ← IagoOps.Confirm[in, out];
IF NOT Booting.switches[d]
THEN BEGIN
out.PutRope["\n\nDo you want me to use software from the PreCedar directory instead of the Cedar directory? "];
IF IagoOps.Confirm[in, out] THEN Booting.switches[d] ← TRUE;
END;
out.PutRope["\n\nI intend to perform the following operations:"];
IF format
THEN BEGIN
out.PutRope["\n - format the entire disk"];
out.PutRope[altoText];
out.PutChar[';];
END;
out.PutF["\n - create a new physical volume named \"%g\";", [rope[myName]] ];
IF coCedar THEN out.PutRope["\n - create a 35000 page debugger logical volume named \"Debugger\" with a 16000 page virtual memory;"];
out.PutF["\n - create a maximum-sized logical volume named \"Cedar\" with a 16000 page virtual memory;\n - install microcode, germ and boot files(s) from the \"%g\" directory;\n - make the \"Cedar\" volume be your physical boot volume;\n - boot the volume(s) to initialize the software.\n", [rope[IF Booting.switches[d] THEN "PreCedar" ELSE "Cedar"]] ];
IF NOT IagoOps.ConfirmDestruction[in, out, Rope.Cat["the entire disk on drive RD0", altoText]]
THEN RETURN;
CloseFSVolumes[];
out.PutChar['\n];
IF format THEN TRUSTED{ DoFormatOrScan[in: in, out: out, d: d, format: TRUE, passes: 1] };
out.PutRope["\nCreating physical volume ... "];
newP ← FileExtra.CreatePhysicalVolume[where: d, name: myName, id: IagoOps.NewID[]];
IagoOps.ReservePages[in, out, newP];
[] ← UserCredentials.ChangeState[IF protect THEN name ELSE nameHint];
out.PutRope["done"];
IF coCedar
THEN BEGIN
out.PutRope["\nCreating logical volume \"Debugger\" ... "];
newD ← FileExtra.CreateLogicalVolume[id: IagoOps.NewID[], where: LIST[newP],
size: 35000,
name: "Debugger"];
File.MarkDebugger[newD, TRUE];
out.PutRope["done. Erasing it ... "];
File.EraseVolume[newD];
out.PutRope["done"];
CreateVMFile[out, newD, 16000];
Install[where: newD, which: bootFile];
END;
out.PutRope["\nCreating logical volume \"Cedar\" ... "];
newC ← FileExtra.CreateLogicalVolume[
id: IagoOps.NewID[],
where: LIST[newP],
size: PhysicalVolume.PhysicalInfo[newP].free,
name: "Cedar"];
out.PutRope["done. Erasing it ... "];
File.EraseVolume[newC];
out.PutRope["done"];
CreateVMFile[out, newC, 16000];
Install[where: newC, which: microcode];
Install[where: newC, which: germ];
Install[where: newC, which: bootFile];
PhysicalVolume.SetPhysicalRoot[newC, microcode];
PhysicalVolume.SetPhysicalRoot[newC, germ];
PhysicalVolume.SetPhysicalRoot[newC, bootFile];
BEGIN
init: Rope.ROPE = IagoOps.InitialMicrocodeFileName[];
out.PutRope["\nInstalling "]; out.PutRope[init];
IF DoInstallation[out, d, init]
THEN [] ←
Booting.Boot[[logical[IF coCedar THEN newD ELSE newC]], [d: Booting.switches[d]] ];
END;
END;
CreateVM: PROC[in, out: IO.STREAM] =
BEGIN
v: File.Volume = IagoOps.GetLogical[in, out, "On "];
size: INT = IagoOps.GetSize[in: in, out: out, default: 16000, max: File.GetVolumePages[v].size];
oldFP: File.FP = File.GetRoot[v, VM].fp;
oldFile: File.Handle ← NIL;
IF oldFP # File.nullFP THEN oldFile ← File.Open[v, oldFP ! File.Error =>
IF why = unknownFile THEN CONTINUE];
IF oldFile # NIL
THEN BEGIN
out.PutF["\nThat volume already has a VM backing file of size %g. Do you want me to set its size to %g? ", [integer[File.Info[oldFile].size]], [integer[size]] ];
IF IagoOps.Confirm[in, out]
THEN { out.PutRope[" ... "]; File.SetSize[oldFile, size]; out.PutRope["done"] };
END
ELSE CreateVMFile[out, v, size]; -- Create the file ourselves
END;
CreateVMFile: PROC[out: IO.STREAM, v: File.Volume, size: INT] =
BEGIN
newFile: FS.OpenFile;
vmName: Rope.ROPE =
Rope.Cat["[]<", File.GetVolumeName[v], ">", IagoOps.LocalRootFileName[VM]];
out.PutF["\nCreating %g ... ", [rope[vmName]] ];
newFile ← FS.Create[name: vmName, pages: size];
File.SetRoot[VM, FSBackdoor.GetFileHandle[newFile]];
FS.Close[newFile];
out.PutRope["done"];
END;
Delete: PROC[in, out: IO.STREAM] =
BEGIN
count: INT ← 0;
DoName: FS.NameProc =
BEGIN
count ← count+1;
out.PutF["\nDeleting %g ... ", [rope[fullFName]]];
BEGIN
ENABLE FS.Error => { out.PutRope[error.explanation]; CONTINUE };
FS.Delete[fullFName];
out.PutRope["done"];
END;
RETURN[TRUE];
END;
pattern: Rope.ROPE ← IagoOps.GetFile[
in: in,
out: out,
pattern: TRUE];
IF Rope.Find[pattern, "!"] < 0 THEN pattern ← pattern.Cat["!L"];
out.PutRope[" ... "];
FS.EnumerateForNames[pattern, DoName];
IF count = 0
THEN out.PutRope["not found"]
ELSE out.PutF["\n%g files", [integer[count]] ];
END;
DescribeDrives: PROC[in, out: IO.STREAM] =
BEGIN
FOR c: Disk.Channel ← Disk.NextChannel[NIL], Disk.NextChannel[c] UNTIL c = NIL
DO ordinal: INT;
nPages: INT;
[ordinal: ordinal, nPages: nPages] ← Disk.DriveAttributes[c];
out.PutF["\nRD%g, size: %g pages",
[integer[ordinal]],
[integer[nPages]],
];
ENDLOOP;
END;
DescribeLV: PROC[in, out: IO.STREAM] =
BEGIN
FOR v: File.Volume ← File.NextVolume[NIL], File.NextVolume[v] UNTIL v = NIL
DO BEGIN
ENABLE File.Error => out.PutRope["\nUnexpected File.Error"]--and let it propagate--;
id: File.VolumeID;
rootStatus, vamStatus: File.RC;
name: Rope.ROPE;
size, free, freeboard: INT;
[id: id, size: size, rootStatus: rootStatus, name: name, vamStatus: vamStatus, free: free, freeboard: freeboard] ← File.LogicalInfo[v];
out.PutRope["\n\nID: "]; IagoOps.PutID[out, File.GetVolumeID[v]];
IF v = File.SystemVolume[] THEN out.PutRope[" (the system volume)"];
SELECT rootStatus FROM
ok, nonCedarVolume => NULL;
wentOffline => { out.PutRope["\nVolume went offline"]; LOOP };
inconsistent => { out.PutRope["\nRoot page is inconsistent (wrong version?)"]; LOOP };
software => { out.PutRope["\nCan't find the volume root page (label-check)"]; LOOP };
hardware => { out.PutRope["\nHard disk error reading the volume root page"]; LOOP };
ENDCASE => ERROR File.Error[rootStatus];
out.PutF["\nName: %g", [rope[name]] ];
IF File.IsDebugger[v] THEN out.PutRope["\nThis is a debugger volume"];
out.PutF["\nSize: %g", [integer[size]] ];
IF rootStatus = nonCedarVolume THEN { out.PutRope["\nNot a Cedar volume"]; LOOP };
SELECT vamStatus FROM
ok => out.PutF[", free pages: %g", [integer[free]] ];
wentOffline => out.PutRope["\nVolume went offline during VAM operation"];
inconsistent => out.PutRope["\nDon't understand the VAM"];
software => out.PutRope["\nCan't find the VAM (label-check)"];
hardware => out.PutRope["\nHard disk error reading the VAM"];
unknownFile => out.PutRope["\nCan't find the VAM (unknown-file)"];
unknownPage => out.PutRope["\nCan't find the VAM (unknown-page)"];
ENDCASE => ERROR File.Error[vamStatus];
FOR vf: File.VolumeFile IN File.VolumeFile
DO PutVolumeFile[out, v, vf] ENDLOOP;
END;
ENDLOOP;
END;
PutVolumeFile: PROC[out: IO.STREAM, v: File.Volume, vf: File.VolumeFile] =
BEGIN
fp: File.FP ← File.GetRoot[v, vf].fp;
file: File.Handle;
name: Rope.ROPE;
IF fp = File.nullFP THEN RETURN;
out.PutF["\n%g fp: [id:%b,da:%b]",
[rope[SELECT vf FROM
checkpoint => "Checkpoint",
microcode => "Microcode",
germ => "Germ",
bootFile => "BootFile",
debugger => "Debugger",
debuggee => "Debuggee",
VM => "VM",
VAM => "VAM",
client => "FS-root",
alpine => "Alpine-root",
ENDCASE => "Other-root"]],
[cardinal[LOOPHOLE[fp.id]]],
[cardinal[LOOPHOLE[fp.da]]]
];
file ← File.Open[v, fp ! File.Error =>
{ out.PutRope[" ... "]; out.PutRope[IagoOps.FileError[why]]; file ← NIL; CONTINUE } ];
IF file # NIL
THEN BEGIN
ENABLE FS.Error =>
BEGIN
out.PutRope[" ... "];
IF error.code = $invalidPropertyPage
THEN out.PutRope["not an FS file"]
ELSE out.PutRope[error.explanation];
CONTINUE
END;
out.PutF[", size: %g", [integer[File.Info[file].size]] ];
name ← FSBackdoor.FNameFromHandle[file];
out.PutRope[", name: "]; out.PutRope[name];
END;
END;
DescribeMachine: PROC[in, out: IO.STREAM] =
BEGIN
id: RECORD[a,b,c: CARDINAL] = LOOPHOLE[ProcessorFace.processorID];
out.PutF["\nThis processor is a %g, id = [%x,%x,%x], available real memory: %g pages",
[rope[SELECT SystemVersion.machineType FROM
dolphin => "Dolphin",
dorado => "Dorado",
dandelion => "Dandelion",
dicentra => "Dicentra",
ENDCASE => "{unknown processor type!}"]],
[integer[id.a]],
[integer[id.b]],
[integer[id.c]],
[integer[VMSideDoor.rmPages]] ];
out.PutF["\nThe microcode is version %g of %g.",
[integer[SystemVersion.uCodeVersion]],
[time[BasicTime.FromPupTime[SystemVersion.uCodeDate]]] ];
IF NOT SystemVersion.uCodeCedar
THEN out.PutRope[" No Cedar microcode."];
IF NOT SystemVersion.uCodeFloatingPoint
THEN out.PutRope[" No floating-point microcode."];
END;
DescribePV: PROC[in, out: IO.STREAM] =
BEGIN
FOR p: PhysicalVolume.Physical ← PhysicalVolume.NextPhysical[NIL],
PhysicalVolume.NextPhysical[p] UNTIL p = NIL
DO IagoOps.ReservePages[in, out, p] ENDLOOP;
FOR p: PhysicalVolume.Physical ← PhysicalVolume.NextPhysical[NIL],
PhysicalVolume.NextPhysical[p] UNTIL p = NIL
DO channel: Disk.Channel;
rootStatus: PhysicalVolume.PhysicalRC;
id: File.VolumeID;
name: Rope.ROPE;
size: Disk.PageCount;
free: Disk.PageCount;
[channel: channel, rootStatus: rootStatus, id: id, name: name, size: size, free: free] ←
PhysicalVolume.PhysicalInfo[p];
out.PutF["\n\nID: "]; IagoOps.PutID[out, id];
out.PutF["\nOn drive RD%g", [integer[Disk.DriveAttributes[channel].ordinal]] ];
SELECT rootStatus FROM
ok => NULL--drop through to after ENDCASE--;
wentOffline => { out.PutRope["\nVolume went offline"]; LOOP };
inconsistent => { out.PutRope["\nRoot page is inconsistent (wrong version?)"]; LOOP };
software => { out.PutRope["\nCan't find the volume root page (label-check)"]; LOOP };
hardware => { out.PutRope["\nHard disk error reading the volume root page"]; LOOP };
ENDCASE => ERROR File.Error[rootStatus];
TRUSTED BEGIN
microcodeInstalled: BOOL;
time: BasicTime.GMT;
name: Rope.ROPE;
[microcodeInstalled, time, name] ← FormatDisk.IdentifyInitialMicrocode[channel];
IF microcodeInstalled
THEN BEGIN
out.PutF["\nContains initial microcode %g of %g",
[rope[name]], [time[time]] ];
END;
END;
out.PutF["\nName: %g\nSize: %g, free pages: %g\nLogical sub-volumes:",
[rope[name]],
[integer[size]],
[integer[free]]
];
BEGIN
sv: PhysicalVolume.SubVolumes = PhysicalVolume.GetSubVolumes[p];
FOR i: CARDINAL IN [0..sv.count)
DO out.PutF["\n At physical page %g, logical id: ", [integer[sv[i].address]] ];
IagoOps.PutID[out, sv[i].id];
out.PutF[", pages [%g..%g)",
[integer[sv[i].start]],
[integer[sv[i].start+sv[i].size]]
];
ENDLOOP;
END;
ENDLOOP;
END;
EraseLogical: PROC[in, out: IO.STREAM] =
BEGIN
v: File.Volume = IagoOps.GetLogical[in, out];
IF IagoOps.ConfirmDestruction[in, out, "the entire logical volume"]
THEN BEGIN
out.PutRope["\nErasing ... "];
File.EraseVolume[v];
out.PutRope["done"];
FSBackdoor.CloseVolume[v];
END;
END;
FormatOrScan: UNSAFE PROC[in, out: IO.STREAM, format: BOOL] = UNCHECKED
BEGIN
d: Disk.Channel = IagoOps.GetDrive[in, out];
alto: BOOL = Disk.DriveAttributes[d].ordinal = 0 AND IagoOps.CheckAltoRegions[in, out];
passes: INT = IagoOps.GetNumber[in, out, 10, 999,
"\nHow many check passes should I make? ",
"? Type how often I should make transfers over the entire disk trying to find bad spots"];
IF NOT format
OR IagoOps.ConfirmDestruction[in, out, Rope.Cat[
"the entire disk drive",
IF alto THEN " (excluding Alto regions)" ELSE NIL ] ]
THEN DoFormatOrScan[in, out, d, format, passes];
END;
DoFormatOrScan: UNSAFE PROC[in, out: IO.STREAM, d: Disk.Channel, format: BOOL, passes: INT] = UNCHECKED
BEGIN
initLabel: Disk.Label;
pageSpace: VM.Interval = VM.Allocate[VM.PagesForWords[File.wordsPerPage]];
data: LONG POINTER = VM.AddressForPageNumber[pageSpace.page];
soft, hard: INT ← 0;
BadList: TYPE = LIST OF RECORD[
page: Disk.PageNumber, correctable: BOOLEAN, reported: BOOLFALSE];
badPages: BadList ← NIL;
SweepDisk: UNSAFE PROC[out: IO.STREAM, d: Disk.Channel, operation: FormatDisk.Operation] = UNCHECKED
BEGIN
origin: Disk.PageNumber ← [0];
label: Disk.Label ← initLabel;
correctableOnes: BOOLFALSE;
out.PutRope[SELECT operation FROM
format => "\nFormatting ... ",
write => "\nWriting ... ",
read => "\nReading ... ",
verify => "\nVerifying ... "
ENDCASE => ERROR];
DO firstPage: Disk.PageNumber; pageCount: Disk.PageCount;
[firstPage, pageCount] ← IagoOps.NextRun[d, origin];
IF pageCount < 0 THEN EXIT;
FormatDisk.Sweep[d, operation, firstPage, pageCount, @label, data];
origin ← [firstPage+pageCount];
ENDLOOP;
DO someBad: BOOLFALSE;
FOR b: BadList ← badPages, b.rest UNTIL b = NIL
DO IF (b.first.correctable = correctableOnes) AND NOT b.first.reported
THEN BEGIN
out.PutF[IF someBad THEN ", %b"
ELSE IF correctableOnes
THEN "\nSoft errors: %b"
ELSE "\nHard errors: %b",
[integer[b.first.page]] ];
someBad ← TRUE;
b.first.reported ← TRUE;
END;
ENDLOOP;
IF NOT correctableOnes AND NOT someBad THEN out.PutRope["no hard errors"];
IF correctableOnes THEN EXIT ELSE correctableOnes ← TRUE;
ENDLOOP;
END;
IF format THEN CloseFSVolumes[];
BEGIN
ENABLE
BEGIN
UNWIND => VM.Free[pageSpace];
FormatDisk.BadPage--[page: Disk.PageNumber, correctable: BOOLEAN]-- =>
BEGIN
FOR b: BadList ← badPages, b.rest UNTIL b = NIL
DO IF b.first.page = page
THEN BEGIN
IF b.first.correctable
THEN BEGIN
b.first.reported ← FALSE;
IF NOT correctable
THEN { b.first.correctable ← FALSE; soft ← soft-1; hard ← hard+1 };
END;
RESUME;
END;
ENDLOOP;
badPages ← CONS[first: [page: page, correctable: correctable], rest: badPages];
IF correctable THEN soft ← soft+1 ELSE hard ← hard+1;
IF hard > 100
THEN BEGIN
out.PutRope["\nThere have been more than 100 hard disk errors. Do you want me to continue? "];
IF NOT IagoOps.Confirm[in, out] THEN CONTINUE;
hard ← 0;
END;
RESUME
END;
END;
VM.Pin[pageSpace];
IF format
THEN BEGIN
init: BOOLTRUE;
Randomize: UNSAFE PROC = UNCHECKED
BEGIN
RandomizeOne[@initLabel, SIZE[Disk.Label]];
RandomizeOne[data, VM.PagesForWords[File.wordsPerPage]];
init ← NOT init;
END;
RandomizeOne: UNSAFE PROC[where: LONG POINTER, nwords: CARDINAL] = UNCHECKED
BEGIN
array: LONG POINTER TO ARRAY OF CARDINAL = LOOPHOLE[where];
FOR i: CARDINAL IN [0..nwords)
DO array[i] ← IF init
THEN Basics.LowHalf[BasicTime.GetClockPulses[]]
ELSE Basics.BITNOT[array[i]];
ENDLOOP;
END;
Randomize[];
SweepDisk[out, d, format];
SweepDisk[out, d, verify];
THROUGH [1..passes]
DO -- First, set label and data to random values
Randomize[];
SweepDisk[out, d, write];
SweepDisk[out, d, verify];
ENDLOOP;
END
ELSE THROUGH [1..passes]
DO SweepDisk[out, d, read] ENDLOOP;
Record any bad pages
BEGIN
p: PhysicalVolume.Physical;
FOR p ← PhysicalVolume.NextPhysical[NIL], PhysicalVolume.NextPhysical[p]
UNTIL p = NIL
DO IF PhysicalVolume.PhysicalInfo[p].channel = d THEN EXIT ENDLOOP;
IF p = NIL
THEN BEGIN
out.PutRope["\nCreating a physical volume to hold the bad page table ..."];
p ← FileExtra.CreatePhysicalVolume[
where: d, name: "JustForTheBadPageTable", id: IagoOps.NewID[]];
out.PutRope["done"];
END;
IF badPages # NIL
THEN BEGIN
rc: PhysicalVolume.PhysicalRC ← ok;
out.PutRope["\nRecording bad pages in physical bad page table ... "];
FOR b: BadList ← badPages, b.rest UNTIL b = NIL OR rc # ok
DO IF NOT b.first.correctable THEN rc ← FileExtra.PhysicalPageBad[p, b.first.page];
ENDLOOP;
out.PutRope[IF rc = ok THEN "done" ELSE IagoOps.FileError[rc]];
END;
END;
END;
VM.Free[pageSpace];
END;
FlushCache: PROC[in, out: IO.STREAM] =
BEGIN
count: INT ← 0;
DoName: FSBackdoor.NameProc =
BEGIN
count ← count+1;
out.PutF["\nFlushing %g ... ", [rope[fullGName]]];
BEGIN
ENABLE FS.Error => { out.PutRope[error.explanation]; CONTINUE };
FSBackdoor.Flush[fullGName: fullGName, volName: volName];
out.PutRope["done"];
END;
RETURN[TRUE];
END;
v: File.Volume = IagoOps.GetLogical[in, out, "On "];
volName: Rope.ROPE = File.GetVolumeName[v];
pattern: Rope.ROPE ← IagoOps.GetFile[in: in, out: out, pattern: TRUE];
out.PutRope[" ... "];
FSBackdoor.EnumerateCacheForNames[DoName, volName, pattern];
IF count = 0
THEN out.PutRope["not found"]
ELSE out.PutF["\n%g files", [integer[count]] ];
END;
InstallCredentials: PROC[in, out: IO.STREAM] =
BEGIN
out.PutRope["\nDo you want to password-protect this disk so that only you can access it? "];
[] ← UserCredentials.ChangeState[IF IagoOps.Confirm[in, out] THEN name ELSE nameHint];
END;
InstallInitial: PROC[in, out: IO.STREAM] =
BEGIN
d: Disk.Channel = IagoOps.GetDrive[in, out];
name: Rope.ROPE = IagoOps.GetFile[
in: in, out: out,
extension: ".eb",
default: IagoOps.InitialMicrocodeFileName[],
check: TRUE
];
out.PutRope[" ... installing"];
[] ← DoInstallation[out, d, name];
END;
DoInstallation: PROC[out: IO.STREAM, d: Disk.Channel, name: Rope.ROPE] RETURNS[done: BOOLTRUE] =
BEGIN
dest: Rope.ROPE = "InitialMicrocode.eb";
wDir: Rope.ROPENIL;
fsFile: FS.OpenFile;
pages: INT ← 0;
v: File.Volume;
FOR v ← File.NextVolume[NIL], File.NextVolume[v] UNTIL v = NIL
DO IF File.LogicalInfo[v].vamStatus = ok AND File.GetVolumePages[v].free >= 100
THEN EXIT;
REPEAT FINISHED =>
BEGIN
out.PutRope[" ... I can't find a suitable logical volume for buffering the file. Create one then try again"];
RETURN
END
ENDLOOP;
wDir ← Rope.Cat["[]<", File.GetVolumeName[v], ">"];
out.PutF[" as %g%g ... ", [rope[wDir]], [rope[dest]] ];
FS.Copy[from: name, to: dest, wDir: wDir];
fsFile ← FS.Open[name: dest, lock: read, wDir: wDir];
pages ← FS.GetInfo[fsFile].pages;
TRUSTED BEGIN
pos: INT ← 0;
Reader: UNSAFE PROC[ ptr: LONG POINTER]RETURNS [ok: BOOLTRUE] =
BEGIN
IF pages <= 0 THEN RETURN[FALSE];
FS.Read[file: fsFile, from: pos, nPages: 1, to: ptr];
pages ← pages-1; pos ← pos+1;
END;
FormatDisk.InstallInitialMicrocode[d, Reader ! FormatDisk.MicrocodeInstallFailure =>
BEGIN
out.PutRope[SELECT why FROM
emptyFile => "empty microcode file",
firstPageBad => "first page of microcode area of disk is bad",
flakeyPageFound => "bad page in microcode area of disk",
microcodeTooBig => "microcode too big for fixed area of disk",
other => "other error during installation",
ENDCASE => "unknown error during installation"];
done ← FALSE;
CONTINUE
END ];
END;
FS.Close[fsFile];
IF done THEN out.PutRope["done"];
END;
InstallLogicalFile: PROC[in, out: IO.STREAM, which: File.VolumeFile[checkpoint..bootFile]] =
BEGIN
v: File.Volume = IagoOps.GetLogical[in, out, "For "];
localVolume: Rope.ROPE = Rope.Cat["[]<", File.GetVolumeName[v], ">"];
name: Rope.ROPE = IagoOps.GetFile[
in: in,
out: out,
extension: IagoOps.ext[which],
default: IagoOps.RemoteRootFileName[which],
wDir: localVolume,
check: TRUE
];
fsFile: FS.OpenFile;
file: File.Handle;
PhysicalToo: PROC RETURNS[BOOL] =
BEGIN
IF which # bootFile
THEN { out.PutRope["\nInstalling on the physical volume"]; RETURN[TRUE] };
IF File.IsDebugger[v] THEN RETURN[FALSE];
out.PutRope["\nDo you want to use this file when you boot the physical volume? "];
RETURN[IagoOps.Confirm[in, out]]
END;
"name" is a canonicalized FS name.
out.PutRope[" ... "];
IF name.Length[] # 0
AND name.Fetch[0] = '[
AND Rope.Run[s1: name, s2: localVolume, case: FALSE] # localVolume.Length[]
AND ( v # File.SystemVolume[] OR Rope.Run[s1: name, s2: "[]<>"] # 4 )
THEN BEGIN
localName: Rope.ROPE ← IagoOps.LocalRootFileName[which];
Help: PROC =
{ out.PutRope["? Please type the name of an FS local file such as \"a.b\""] };
out.PutRope[IF name.Length[] >=1 AND name.Fetch[1] = ']
THEN "that file is on another volume ..."
ELSE "that file is on a server ..."];
See what local name the user would like
DO
localName ← IagoOps.GetArg[
in: in,
out: out,
prompt: "\nCopy to local file name (please confirm or alter): ",
default: localName,
help: Help];
IF localName.Length[] = 0 OR localName.Fetch[0] = '[ THEN Help[] ELSE EXIT;
ENDLOOP;
out.PutRope[" ... copying ... "];
FS.Copy[from: name, to: localName, wDir: localVolume];
fsFile ← FS.Open[name: localName, lock: write, wDir: localVolume];
END
ELSE fsFile ← FS.Open[name: name, lock: write, wDir: localVolume];
file ← FSBackdoor.GetFileHandle[fsFile];
IF File.Info[file].volume # v
THEN out.PutRope["I'm confused: the file is on the wrong volume"]
ELSE { out.PutRope["installing ... "]; File.SetRoot[which, file]; out.PutRope["done"] };
FS.Close[fsFile];
IF PhysicalToo[]
THEN { PhysicalVolume.SetPhysicalRoot[v, which]; out.PutRope[" ... done"] };
END;
ListCache: PROC[in, out: IO.STREAM] =
BEGIN
count: INT ← 0;
DoName: FSBackdoor.NameProc =
BEGIN
count ← count+1;
out.PutF["\n %g", [rope[fullGName]]];
RETURN[TRUE];
END;
v: File.Volume = IagoOps.GetLogical[in, out, "On "];
pattern: Rope.ROPE ← IagoOps.GetFile[in: in, out: out, pattern: TRUE];
out.PutRope[" ... "];
FSBackdoor.EnumerateCacheForNames[DoName, File.GetVolumeName[v], pattern];
IF count = 0
THEN out.PutRope["not found"]
ELSE out.PutF["\n%g files", [integer[count]] ];
END;
ListFileInfo: PROC[in, out: IO.STREAM] =
BEGIN
count: INT ← 0;
DoName: FS.InfoProc =
BEGIN
count ← count+1;
out.PutF["\n %g, keep: %g, bytes: %g, created: %g", [rope[fullFName]], [cardinal[keep]], [integer[bytes]], [time[created]] ];
IF attachedTo # NIL THEN out.PutF[", attached to %g", [rope[attachedTo]] ];
RETURN[TRUE];
END;
pattern: Rope.ROPE ← IagoOps.GetFile[in: in, out: out, pattern: TRUE];
out.PutRope[" ... "];
FS.EnumerateForInfo[pattern, DoName];
IF count = 0
THEN out.PutRope["not found"]
ELSE out.PutF["\n%g files", [integer[count]] ];
END;
ListNames: PROC[in, out: IO.STREAM] =
BEGIN
count: INT ← 0;
prevFile: Rope.ROPENIL;
DoName: FS.NameProc =
BEGIN
newFile, newPrefix: Rope.ROPE;
newPos: FS.ComponentPositions;
count ← count+1;
[fullFName: newFile, cp: newPos] ← FS.ExpandName[fullFName];
newPrefix ← fullFName.Substr[len: newPos.ver.start];
IF prevFile # NIL AND newPos.ver.length # 0 AND prevFile.Equal[newPrefix, FALSE]
THEN out.PutF[", %g",
[rope[fullFName.Substr[start: newPos.ver.start, len: newPos.ver.length]]] ]
ELSE BEGIN
prevFile ← newPrefix;
out.PutF["\n%g", [rope[fullFName]]];
END;
RETURN[TRUE];
END;
pattern: Rope.ROPE ← IagoOps.GetFile[in: in, out: out, pattern: TRUE];
out.PutRope[" ... "];
FS.EnumerateForNames[pattern, DoName];
IF count = 0
THEN out.PutRope["not found"]
ELSE out.PutF["\n%g files", [integer[count]] ];
END;
Login: PROC[options: UserCredentials.LoginOptions] =
BEGIN
TurnOnProc: PROC RETURNS [in, out: IO.STREAM] = {
[in: in, out: out] ← SimpleTerminal.TurnOn[];
out.PutRope["\nPlease login ...\n"];
};
TurnOffProc: PROC [in, out: IO.STREAM] = {
SimpleTerminal.TurnOff[]
};
UserCredentials.Login[startInteraction: TurnOnProc, endInteraction: TurnOffProc,
options: options];
END;
Rename: PROC[in, out: IO.STREAM] =
BEGIN
from: Rope.ROPE = IagoOps.GetFile[
in: in,
out: out,
prompt: "From ",
check: TRUE];
to: Rope.ROPE = IagoOps.GetFile[
in: in,
out: out,
prompt: "To "];
out.PutRope[" ... "];
FS.Rename[from: from, to: to];
out.PutRope["done"];
END;
Rollback: PROC[in, out: IO.STREAM] =
BEGIN
v: File.Volume = IagoOps.GetLogical[in, out];
reject: Rope.ROPE;
mySwitches: Booting.Switches ← ALL[FALSE];
mySwitches[r] ← TRUE;
reject ← Booting.Boot[[logical[v]], mySwitches];
IF reject # NIL THEN { out.PutRope[" ... couldn't: "]; out.PutRope[reject] };
END;
Scavenge: PROC[in, out: IO.STREAM] =
BEGIN
v: File.Volume = IagoOps.GetLogical[in, out];
out.PutRope[" ... "];
[] ← File.FindVM[];
FSBackdoor.ScavengeDirectoryAndCache[File.GetVolumeName[v]];
out.PutRope["done"];
END;
SetKeep: PROC[in, out: IO.STREAM] =
BEGIN
count: INT ← 0;
DoName: FS.NameProc =
BEGIN
count ← count+1;
fullFName ← fullFName.Substr[start: 0, len: fullFName.Index[0, "!"]];
out.PutF["\nSetting %g ... ", [rope[fullFName]]];
BEGIN
ENABLE FS.Error => { out.PutRope[error.explanation]; CONTINUE };
FS.SetKeep[fullFName, k];
out.PutRope["done"];
END;
RETURN[TRUE];
END;
pattern: Rope.ROPE ← IagoOps.GetFile[
in: in,
out: out,
pattern: TRUE,
prompt: "For "];
k: INT = IagoOps.GetNumber[
in,
out,
1,
LAST[CARDINAL]-1,
"\nNumber of versions to keep: ",
"? Type how many versions of each file should be kept (excess versions will be deleted)"];
IF Rope.Find[pattern, "!"] < 0 THEN pattern ← pattern.Cat["!H"];
out.PutRope[" ... "];
FS.EnumerateForNames[pattern, DoName];
IF count = 0
THEN out.PutRope["not found"]
ELSE out.PutF["\n%g files", [integer[count]] ];
END;
SetPhysicalFile: PROC[in, out: IO.STREAM, which: File.VolumeFile[checkpoint..bootFile]] =
BEGIN
v: File.Volume = IagoOps.GetLogical[in, out, "From "];
out.PutRope[" ... "];
PhysicalVolume.SetPhysicalRoot[v, which];
out.PutRope["done"];
END;
SetWDir: PROC[in, out: IO.STREAM] =
BEGIN
Help: PROC =
{ out.PutRope["? Type the prefix of a file name, such as \"[]<volume>\""] };
name: Rope.ROPE = IagoOps.GetArg[
in: in,
out: out,
prompt: "\nWorking directory: ",
default: IF IagoOps.clientVolName.Length[] # 0
THEN Rope.Cat["[]<", IagoOps.clientVolName, ">"]
ELSE NIL,
help: Help];
FS.SetDefaultWDir[name];
END;
******** Main program ********
DoCommand: PROC[in, out: IO.STREAM, c: IagoOps.Command] RETURNS[exit: BOOLFALSE, overwritten: BOOLFALSE] =
BEGIN
ENABLE
BEGIN
IagoOps.Rubout => { out.PutRope[" XXX"]; CONTINUE };
File.Error =>
BEGIN
out.PutRope[" ... "];
out.PutRope[IagoOps.FileError[why]];
CONTINUE
END;
FS.Error =>
BEGIN
out.PutRope[" ... "];
out.PutRope[
IF error.explanation.Length[] # 0
THEN error.explanation
ELSE "FS.Error without a message: consult an expert"];
CONTINUE
END;
BasicTime.TimeNotKnown =>
BEGIN
out.PutRope[" ... I can't do that without knowing the current time."];
CONTINUE
END;
END;
SELECT c FROM
attach =>
Attach[in, out];
bootLogical =>
BootLogical[in, out];
checkDrive =>
TRUSTED{ FormatOrScan[in: in, out: out, format: FALSE] };
copy =>
Copy[in, out];
createLogical =>
CreateLogical[in, out];
createPhysical =>
overwritten CreatePhysical[in, out];
createUserWorld =>
CreateUserWorld[in, out];
createVM =>
CreateVM[in, out];
delete =>
Delete[in, out];
describeDrives =>
DescribeDrives[in, out];
describeLV =>
DescribeLV[in, out];
describeMachine =>
DescribeMachine[in, out];
describePV =>
DescribePV[in, out];
eraseLogical =>
EraseLogical[in, out];
format =>
TRUSTED{ FormatOrScan[in: in, out: out, format: TRUE] };
flushCache =>
FlushCache[in, out];
installBoot =>
InstallLogicalFile[in, out, bootFile];
installCredentials =>
InstallCredentials[in, out];
installGerm =>
InstallLogicalFile[in, out, germ];
installInitial =>
InstallInitial[in, out];
installMicrocode =>
InstallLogicalFile[in, out, microcode];
listCache =>
ListCache[in, out];
listFileInfo =>
ListFileInfo[in, out];
listNames =>
ListNames[in, out];
login =>
Login[[alwaysInteract: TRUE, ignoreDiskEntirely: TRUE]];
quit =>
{ out.PutRope["\nAre you sure? "]; exit ← IagoOps.Confirm[in, out] };
rename =>
Rename[in, out];
rollbackLogical =>
Rollback[in, out];
scavenge =>
Scavenge[in, out];
setKeep =>
SetKeep[in, out];
setPhysicalBoot =>
SetPhysicalFile[in, out, bootFile];
setPhysicalGerm =>
SetPhysicalFile[in, out, germ];
setPhysicalMicrocode =>
SetPhysicalFile[in, out, microcode];
setWDir =>
SetWDir[in, out];
ENDCASE => ERROR;
END;
Main: PROC[in, out: IO.STREAM, mode: { normal, nSwitch, createWorld }] =
BEGIN
out.PutRope["\n\nType \"?\" at any time if you need help."];
IF Booting.switches[d]
THEN out.PutRope["\nBooted with the \"D\" switch: defaults come from PreCedar directory instead of Cedar."];
DO BEGIN
ENABLE
BEGIN
ABORTED => EXIT;
IagoOps.Rubout => { out.PutRope[" XXX"]; CONTINUE };
END;
c: IagoOps.Command = IagoOps.GetCommand[in: in, out: out, diskReadable: mode = normal];
exit, overwritten: BOOL;
[exit, overwritten] ← DoCommand[in, out, c];
IF overwritten THEN mode ← normal;
IF exit THEN EXIT;
END;
ENDLOOP;
out.PutRope["\nLeaving Iago ...\n"];
END;
DoIt: PROC =
BEGIN
in, out: IO.STREAM;
etherBoot: BOOL = GermSwap.bootedFrom.location.deviceType = ethernet;
Login[[ignoreDiskEntirely: Booting.switches[n]]];
IF NOT Booting.switches[n]
THEN DebuggerSwap.EnableSwatWatcher[TRUE] -- ELSE it may be enabled by the "I" switch--;
[in: in, out: out] ← SimpleTerminal.TurnOn[];
IF etherBoot
THEN BEGIN
DO out.PutRope["\nDo you want to initialize your disk from scratch for use by Cedar (selecting from the standard options I will offer you)? "];
IF IagoOps.Confirm[in, out ! IagoOps.Rubout => CONTINUE]
THEN [] ← DoCommand[in, out, createUserWorld]
ELSE EXIT;
ENDLOOP;
END;
SELECT TRUE FROM
Booting.switches[n] =>
BEGIN
out.PutRope["\nBooted with the \"N\" switch. Invoking Iago, with restricted command set"];
Main[in: in, out: out, mode: nSwitch];
END;
Booting.switches[l] OR etherBoot =>
BEGIN
out.PutRope["\nDo you want to use \"Iago\", the Cedar disk utility program? "];
IF IagoOps.Confirm[in, out ! IagoOps.Rubout => CONTINUE]
THEN Main[in: in, out: out, mode: normal];
END;
ENDCASE => NULL;
SimpleTerminal.TurnOff[];
END;
DoIt[];
END.