IagoCommandsImpl.mesa - Iago commands implementation
Copyright © 1984 by Xerox Corporation. All rights reserved.
Andrew Birrell December 7, 1983 4:07 pm
Levin, September 22, 1983 1:32 pm
Russ Atkinson, June 18, 1984 6:20:08 pm PDT
Willie-Sue, March 8, 1984 5:53:21 pm PST
DIRECTORY
Basics USING[ BITNOT, DIVMOD, LongDivMod, LowHalf ],
BasicTime USING[ FromPupTime, GetClockPulses, GMT, OutOfRange ],
BootFileChanges USING [MemorySizeToFileSize],
Booting USING[ Boot, Switches, switches ],
DebuggerSwap USING [EnableSwatWatcher],
DefaultRemoteNames USING [Get],
Disk USING [Channel, DriveAttributes, Label, NextChannel, PageCount, PageNumber],
DiskExtras USING [GetDeviceFromChannel],
DiskFace USING [DeviceHandle, GetDeviceAttributes],
DiskFaceExtras USING [GetTrueDeviceAttributes],
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, Delete, EnumerateForInfo, EnumerateForNames, Error, ExpandName, FileInfo, GetInfo, InfoProc, NameProc, Open, OpenFile, Read, Rename, SetByteCountAndCreatedTime, SetKeep, SetDefaultWDir ],
FSBackdoor USING
[ CloseVolume, EnumerateCacheForNames, Flush, FNameFromHandle, GetFileHandle, NameProc, ScavengeDirectoryAndCache ],
FSExtrasForIago USING [CreateVMBacking],
GermSwap USING[ Switch ],
IagoCommands,
IagoOps --USING everything--,
IO USING[ PutChar, PutF, PutRope, STREAM ],
Loader USING[ Error, IRItem, Instantiate, Start],
PhysicalVolume USING
[ GetSubVolumes, NextPhysical, Physical, PhysicalInfo, PhysicalRC, SetPhysicalRoot, SubVolumes ],
PrincOps USING[ ControlModule, NullControl, wordsPerPage],
PrincOpsUtils USING[ LongCopy],
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
[CantAllocate, AddressForPageNumber, Allocate, Free, Interval, PagesForWords, Pin, SwapIn, Unpin],
VMSideDoor USING [rmPages];
IagoCommandsImpl: CEDAR PROGRAM
IMPORTS
Basics, BasicTime, BootFileChanges, Booting, DebuggerSwap, DefaultRemoteNames, Disk, DiskExtras, DiskFace, DiskFaceExtras, File, FileExtra, FormatDisk, FS, FSBackdoor, FSExtrasForIago, IagoOps, IO, Loader, PhysicalVolume, PrincOpsUtils, ProcessorFace, PupDefs, Rope, SimpleTerminal, SystemVersion, UserCredentials, VM, VMSideDoor
EXPORTS IagoCommands = {
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
******** The command subroutines (alphabetic order) ********
Attach: PUBLIC PROC [in, out: STREAM] = {
from: ROPE = IagoOps.GetFile[
in: in,
out: out,
prompt: "From ",
check: TRUE];
to: ROPE = IagoOps.GetFile[
in: in,
out: out,
prompt: "To "];
IO.PutRope[out, " ... "];
FS.Copy[from: from, to: to, remoteCheck: FALSE, attach: TRUE];
IO.PutRope[out, "done"];
};
BootLogical: PUBLIC PROC [in, out: STREAM] = {
v: File.Volume = IagoOps.GetLogical[in, out];
reject: ROPE = Booting.Boot[[logical[v]], IagoOps.GetSwitches[in, out]];
IF reject # NIL THEN { IO.PutRope[out, " ... couldn't: "]; IO.PutRope[out, reject] };
};
Copy: PUBLIC PROC [in, out: STREAM] = {
from: ROPE = IagoOps.GetFile[
in: in,
out: out,
prompt: "From ",
check: TRUE];
to: ROPE = IagoOps.GetFile[
in: in,
out: out,
prompt: "To "];
IO.PutRope[out, " ... "];
FS.Copy[from: from, to: to];
IO.PutRope[out, "done"];
};
CreateLogical: PUBLIC PROC [in, out: STREAM] = {
name: ROPEIF File.FindVolumeFromName[IagoOps.clientVolName] = NIL
THEN IagoOps.clientVolName
ELSE NIL;
size: INT;
p: PhysicalVolume.Physical;
free: INT;
HelpCreation: PROC = {
IO.PutRope[out,
"? type a string which is not the same as the name of any existing volume"];
};
DO
name ← IagoOps.GetArg[
in: in,
out: out,
prompt: Rope.Cat["\nNew volume name: "],
default: name,
help: HelpCreation];
IF File.FindVolumeFromName[name] # NIL
THEN IO.PutRope[out, " ... 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];
IO.PutRope[out, "\nAre you sure? "];
IF IagoOps.Confirm[in, out] THEN {
new: File.Volume ← NIL;
IO.PutRope[out, " ... "];
new ← FileExtra.CreateLogicalVolume[
id: IagoOps.NewID[], where: LIST[p], size: size, name: name];
IO.PutRope[out, " done"];
IagoOps.clientVolName ← name;
IO.PutRope[out,
"\nDo you want to use this as the debugger volume? "];
IF IagoOps.Confirm[in, out] THEN File.MarkDebugger[new, TRUE];
};
};
CloseFSVolumes: PROC = {
FOR v: File.Volume ← File.NextVolume[NIL], File.NextVolume[v] UNTIL v = NIL DO
FSBackdoor.CloseVolume[v];
ENDLOOP;
};
CreatePhysical: PUBLIC PROC [in, out: STREAM] RETURNS[done: BOOLFALSE] = {
HelpCreation: PROC = {
IO.PutRope[out, "? type a name not the same as that of any existing volume"];
};
d: Disk.Channel = IagoOps.GetDrive[in, out];
alto: BOOL = Disk.DriveAttributes[d].ordinal = 0 AND IagoOps.CheckAltoRegions[in, out];
name: 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 {
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];
};
};
CreateUserWorld: PUBLIC PROC [in, out: STREAM] = {
Install: PROC [where: File.Volume, which: File.VolumeFile[microcode..bootFile]] = {
wDir: ROPE = Rope.Cat["[]<", File.GetVolumeName[where], ">"];
remote: ROPE = IagoOps.RemoteRootFileName[which];
local: ROPE = IagoOps.LocalRootFileName[which];
fsFile: FS.OpenFile;
file: File.Handle;
createTime: BasicTime.GMT;
IO.PutF[out, "\nInstalling %g as %g%g ... ",
[rope[remote]], [rope[wDir]], [rope[local]] ];
FS.Copy[from: remote, to: local, wDir: wDir];
createTime ← FS.FileInfo[name: local, wDir: wDir].created;
fsFile ← FS.Open[name: local, lock: write, wDir: wDir];
file ← FSBackdoor.GetFileHandle[fsFile];
File.SetRoot[which, file];
FS.SetByteCountAndCreatedTime[file: fsFile, created: createTime];
FS.Close[fsFile];
IO.PutRope[out, "done"];
};
format, coCedar, protect: BOOL;
altoText: ROPENIL;
myName: ROPE = PupDefs.GetMyName[];
systemHost: ROPE = DefaultRemoteNames.Get[].systemHost;
userHost: ROPE = DefaultRemoteNames.Get[].userHost;
d: Disk.Channel = Disk.NextChannel[NIL];
newP: PhysicalVolume.Physical;
newC, newD: File.Volume;
memImagePages: INT ← MemImagePages[];
Room for a physical memory image plus possible junk
cedarVMPages: INTMAX[memImagePages, 20000];
debuggerVMPages: INTMAX[16000, memImagePages];
debuggerVolumePages: INT ← debuggerVMPages+8000+2*memImagePages;
room for debugger, debuggee, VM, the boot file, and junk
IO.PutChar[out, '\n];
IagoOps.ReserveAltoRegions[in, out];
IF IagoOps.CheckAltoRegions[in, out]
THEN altoText ← " (excluding the Alto disk regions)";
IO.PutRope[out, "\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];
IO.PutRope[out, "\n\nDo you want to password-protect this disk? "];
protect ← IagoOps.Confirm[in, out];
IO.PutRope[out, "\n\nDo you want to have a debugger volume on this disk? "];
coCedar ← IagoOps.Confirm[in, out];
IO.PutRope[out, "\n\nI intend to perform the following operations:"];
IF format THEN {
IO.PutRope[out, "\n - format the entire disk"];
IO.PutRope[out, altoText];
IO.PutChar[out, ';];
};
IO.PutF[out, "\n - create a new physical volume named \"%g\";", [rope[myName]] ];
IF coCedar THEN
IO.PutF[out,
"\n - create a debugger volume named \"Debugger\" with a %g page VM;",
[integer[debuggerVMPages]]];
IO.PutF[out,
"\n - create a client volume named \"Cedar\" with a %g page VM;\n - install microcode, germ and boot files(s) from the release directory;\n - make the \"Cedar\" volume be the physical boot volume;\n - boot the volume(s) to initialize the software.\n", [integer[cedarVMPages]] ];
IF NOT IagoOps.ConfirmDestruction[
in, out, Rope.Cat["the entire disk on drive RD0", altoText]]
THEN RETURN;
CloseFSVolumes[];
IO.PutChar[out, '\n];
IF format THEN TRUSTED {
DoFormatOrScan[in: in, out: out, d: d, format: TRUE, passes: 1] };
IO.PutRope[out, "\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];
IO.PutRope[out, "done"];
IF coCedar THEN {
IO.PutRope[out, "\nCreating logical volume \"Debugger\" ... "];
newD ← FileExtra.CreateLogicalVolume[id: IagoOps.NewID[], where: LIST[newP],
size: debuggerVolumePages,
name: "Debugger"];
File.MarkDebugger[newD, TRUE];
IO.PutRope[out, "done. Erasing it ... "];
File.EraseVolume[newD];
IO.PutRope[out, "done"];
CreateVMFile[out, newD, debuggerVMPages];
Install[where: newD, which: bootFile];
};
IO.PutRope[out, "\nCreating logical volume \"Cedar\" ... "];
newC ← FileExtra.CreateLogicalVolume[
id: IagoOps.NewID[],
where: LIST[newP],
size: PhysicalVolume.PhysicalInfo[newP].free,
name: "Cedar"];
IO.PutRope[out, "done. Erasing it ... "];
File.EraseVolume[newC];
IO.PutRope[out, "done"];
CreateVMFile[out, newC, cedarVMPages];
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];
{
init: ROPE = IagoOps.InitialMicrocodeFileName[];
IO.PutRope[out, "\nInstalling "];
IO.PutRope[out, init];
IF DoInitialMicrocodeInstallation[out, d, init, "[]<Cedar>Temp>Initial.Microcode"] THEN
[] ← Booting.Boot[
[logical[IF coCedar THEN newD ELSE newC]],
[d: Booting.switches[d]] ];
};
};
CreateVM: PUBLIC PROC [in, out: STREAM] = {
v: File.Volume = IagoOps.GetLogical[in, out, "On "];
size: INT = IagoOps.GetSize[
in: in, out: out, default: MAX[MemImagePages[], 20000], 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 {
IO.PutF[out,
"\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 {
IO.PutRope[out, " ... "]; File.SetSize[oldFile, size]; IO.PutRope[out, "done"] };
}
ELSE CreateVMFile[out, v, size]; -- Create the file ourselves
};
CreateVMFile: PROC [out: STREAM, v: File.Volume, size: INT] = {
newFile: FS.OpenFile;
volName: ROPE = File.GetVolumeName[v];
vmName: ROPE = Rope.Cat["[]<", volName, ">", IagoOps.LocalRootFileName[VM]];
IO.PutF[out, "\nCreating %g ... ", [rope[vmName]] ];
newFile ← FSExtrasForIago.CreateVMBacking[name: vmName, pages: size];
File.SetRoot[VM, FSBackdoor.GetFileHandle[newFile]];
FS.Close[newFile];
IO.PutRope[out, "done"];
[] ← IagoOps.WriteRemoteNames[volName, NIL, NIL, out];
};
Delete: PUBLIC PROC [in, out: STREAM] = {
count: INT ← 0;
DoName: FS.NameProc = {
count ← count+1;
IO.PutF[out, "\nDeleting %g ... ", [rope[fullFName]]];
{
ENABLE FS.Error => { IO.PutRope[out, error.explanation]; CONTINUE };
FS.Delete[fullFName];
IO.PutRope[out, "done"];
};
RETURN[TRUE];
};
pattern: ROPE ← IagoOps.GetFile[in: in, out: out, pattern: TRUE];
IF Rope.Find[pattern, "!"] < 0 THEN pattern ← pattern.Cat["!L"];
IO.PutRope[out, " ... "];
FS.EnumerateForNames[pattern, DoName];
IF count = 0
THEN IO.PutRope[out, "not found"]
ELSE IO.PutF[out, "\n%g files", [integer[count]] ];
};
DescribeDrives: PUBLIC PROC [in, out: STREAM] = {
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];
IO.PutF[out, "\nRD%g, size: %g pages", [integer[ordinal]], [integer[nPages]] ];
ENDLOOP;
};
DescribeLV: PUBLIC PROC [in, out: STREAM] = {
FOR v: File.Volume ← File.NextVolume[NIL], File.NextVolume[v] UNTIL v = NIL DO {
ENABLE File.Error => IO.PutRope[out, "\nUnexpected File.Error"]--and let it propagate--;
id: File.VolumeID;
rootStatus, vamStatus: File.RC;
name: ROPE;
size, free, freeboard: INT;
[id: id, size: size, rootStatus: rootStatus, name: name, vamStatus: vamStatus, free: free, freeboard: freeboard] ← File.LogicalInfo[v];
IO.PutRope[out, "\n\nID: "]; IagoOps.PutID[out, File.GetVolumeID[v]];
IF v = File.SystemVolume[] THEN IO.PutRope[out, " (the system volume)"];
SELECT rootStatus FROM
ok, nonCedarVolume => NULL;
wentOffline => {
IO.PutRope[out, "\nVolume went offline"];
LOOP;
};
inconsistent => {
IO.PutRope[out, "\nRoot page is inconsistent (wrong version?)"];
LOOP;
};
software => {
IO.PutRope[out, "\nCan't find the volume root page (label-check)"];
LOOP;
};
hardware => {
IO.PutRope[out, "\nHard disk error reading the volume root page"];
LOOP;
};
ENDCASE => ERROR File.Error[rootStatus];
IO.PutF[out, "\nName: %g", [rope[name]] ];
IF File.IsDebugger[v] THEN IO.PutRope[out, "\nThis is a debugger volume"];
IO.PutF[out, "\nSize: %g", [integer[size]] ];
IF rootStatus = nonCedarVolume THEN { IO.PutRope[out, "\nNot a Cedar volume"]; LOOP };
SELECT vamStatus FROM
ok => IO.PutF[out, ", free pages: %g", [integer[free]] ];
wentOffline => IO.PutRope[out, "\nVolume went offline during VAM operation"];
inconsistent => IO.PutRope[out, "\nDon't understand the VAM"];
software => IO.PutRope[out, "\nCan't find the VAM (label-check)"];
hardware => IO.PutRope[out, "\nHard disk error reading the VAM"];
unknownFile => IO.PutRope[out, "\nCan't find the VAM (unknown-file)"];
unknownPage => IO.PutRope[out, "\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;
};
ENDLOOP;
};
PutVolumeFile: PROC [out: STREAM, v: File.Volume, vf: File.VolumeFile] = {
fp: File.FP ← File.GetRoot[v, vf].fp;
file: File.Handle;
name: ROPE;
IF fp = File.nullFP THEN RETURN;
IO.PutF[out, "\n%g fp: [id:%bB,da:%bB]",
[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 => {
IO.PutRope[out, " ... "];
IO.PutRope[out, IagoOps.FileError[why]];
file ← NIL;
CONTINUE } ];
IF file # NIL THEN {
ENABLE FS.Error => {
IO.PutRope[out, " ... "];
IF error.code = $invalidPropertyPage
THEN IO.PutRope[out, "not an FS file"]
ELSE IO.PutRope[out, error.explanation];
CONTINUE
};
IO.PutF[out, ", size: %g", [integer[File.Info[file].size]] ];
name ← FSBackdoor.FNameFromHandle[file];
IO.PutRope[out, ", name: "]; IO.PutRope[out, name];
};
};
DescribeMachine: PUBLIC PROC [in, out: STREAM] = {
id: RECORD[a,b,c: CARDINAL] = LOOPHOLE[ProcessorFace.processorID];
IO.PutF[out,
"\nThis processor is a %g, id = [%xH,%xH,%xH], 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]] ];
IO.PutF[out, "\nThe microcode is version %g of ", [integer[SystemVersion.uCodeVersion]] ];
{
ENABLE BasicTime.OutOfRange => {
IO.PutRope[out, "unknown date."];
CONTINUE;
};
IO.PutF[out, "%g.", [time[BasicTime.FromPupTime[SystemVersion.uCodeDate]]] ];
};
IF NOT SystemVersion.uCodeCedar
THEN IO.PutRope[out, " No Cedar microcode."];
IF NOT SystemVersion.uCodeFloatingPoint
THEN IO.PutRope[out, " No floating-point microcode."];
};
DescribePV: PUBLIC PROC [in, out: STREAM] = {
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;
size: Disk.PageCount;
free: Disk.PageCount;
[channel: channel, rootStatus: rootStatus, id: id, name: name, size: size, free: free] ←
PhysicalVolume.PhysicalInfo[p];
IO.PutF[out, "\n\nID: "]; IagoOps.PutID[out, id];
IO.PutF[out, "\nOn drive RD%g", [integer[Disk.DriveAttributes[channel].ordinal]] ];
SELECT rootStatus FROM
ok => NULL--drop through to after ENDCASE--;
wentOffline => { IO.PutRope[out, "\nVolume went offline"]; LOOP };
inconsistent => { IO.PutRope[out, "\nRoot page is inconsistent (wrong version?)"]; LOOP };
software => { IO.PutRope[out, "\nCan't find the volume root page (label-check)"]; LOOP };
hardware => { IO.PutRope[out, "\nHard disk error reading the volume root page"]; LOOP };
ENDCASE => ERROR File.Error[rootStatus];
TRUSTED {
microcodeInstalled: BOOL;
time: BasicTime.GMT;
name: ROPE;
[microcodeInstalled, time, name] ← FormatDisk.IdentifyInitialMicrocode[channel];
IF microcodeInstalled
THEN {
IO.PutF[out, "\nContains initial microcode %g of %g",
[rope[name]], [time[time]] ];
};
};
IO.PutF[out, "\nName: %g\nSize: %g, free pages: %g\nLogical sub-volumes:",
[rope[name]],
[integer[size]],
[integer[free]]
];
{
sv: PhysicalVolume.SubVolumes = PhysicalVolume.GetSubVolumes[p];
FOR i: CARDINAL IN [0..sv.count)
DO IO.PutF[out, "\n At physical page %g, logical id: ", [integer[sv[i].address]] ];
IagoOps.PutID[out, sv[i].id];
IO.PutF[out, ", pages [%g..%g)",
[integer[sv[i].start]],
[integer[sv[i].start+sv[i].size]]
];
ENDLOOP;
};
ENDLOOP;
};
EraseLogical: PUBLIC PROC [in, out: STREAM] = {
v: File.Volume = IagoOps.GetLogical[in, out];
IF IagoOps.ConfirmDestruction[in, out, "the entire logical volume"] THEN {
IO.PutRope[out, "\nErasing ... "];
File.EraseVolume[v];
IO.PutRope[out, "done"];
FSBackdoor.CloseVolume[v];
};
};
FormatOrScan: PUBLIC PROC [in, out: STREAM, format: BOOL] = TRUSTED {
UnsafeFormatOrScan[in, out, format];
};
UnsafeFormatOrScan: UNSAFE PROC [in, out: STREAM, format: BOOL] = UNCHECKED {
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];
};
DoFormatOrScan: UNSAFE PROC
[in, out: STREAM, d: Disk.Channel, format: BOOL, passes: INT] = UNCHECKED {
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: BOOL, dontReport: BOOLFALSE];
badPages: BadList ← NIL;
errorsThisPass: BOOL;
SweepDisk: UNSAFE PROC
[out: STREAM, d: Disk.Channel, operation: FormatDisk.Operation] = UNCHECKED {
origin: Disk.PageNumber ← [0];
label: Disk.Label ← initLabel;
IO.PutRope[out, SELECT operation FROM
format => "\nFormatting ... ",
write => "\nWriting ... ",
read => "\nReading ... ",
verify => "\nVerifying ... "
ENDCASE => ERROR];
errorsThisPass ← FALSE;
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;
IF NOT errorsThisPass THEN IO.PutRope[out, "no errors"];
};
IF format THEN CloseFSVolumes[];
{
ENABLE {
UNWIND => VM.Free[pageSpace];
FormatDisk.BadPage => {
[page: Disk.PageNumber, correctable: BOOL]
b: BadList;
FOR b ← badPages, b.rest UNTIL b = NIL DO
IF b.first.page = page THEN {
IF b.first.correctable
THEN {
IF NOT correctable
THEN { b.first.correctable ← FALSE; soft ← soft-1; hard ← hard+1 };
};
EXIT
};
REPEAT FINISHED => {
badPages ← b ←
CONS[first: [page: page, correctable: correctable], rest: badPages];
IF correctable THEN soft ← soft+1 ELSE hard ← hard+1;
}
ENDLOOP;
IF hard > 100 THEN {
IO.PutRope[out, "\nThere are over 100 hard disk errors. Continue? "];
IF NOT IagoOps.Confirm[in, out] THEN CONTINUE;
hard ← 0;
};
IF NOT b.first.dontReport THEN {
logicalDiskCyl: CARDINAL;
physicalDiskCyl, physicalDiskTrack, physicalDiskSector: CARDINAL;
cylinder, track, sector, temp: CARDINAL;
device: DiskFace.DeviceHandle ← DiskExtras.GetDeviceFromChannel[d];
IO.PutRope[out, IF errorsThisPass THEN ", " ELSE "\nDisk error(s): "];
errorsThisPass ← TRUE;
[cylinders: physicalDiskCyl, movingHeads: physicalDiskTrack, sectorsPerTrack: physicalDiskSector] ← DiskFaceExtras.GetTrueDeviceAttributes[device];
[cylinders: logicalDiskCyl] ← DiskFace.GetDeviceAttributes[device];
IF logicalDiskCyl # physicalDiskCyl
THEN {
Abnormal cyl/track/sect numbering on a Dorado for Alto compatability
[quotient: temp, remainder: sector] ← Basics.LongDivMod[
num: page, den: physicalDiskSector ];
[quotient: track, remainder: cylinder] ← Basics.DIVMOD[
num: temp, den: physicalDiskCyl];
}
ELSE {
Normal cyl/track/sect numbering
[quotient: cylinder, remainder: temp] ← Basics.LongDivMod[
num: page, den: physicalDiskSector * physicalDiskTrack];
[quotient: track, remainder: sector] ← Basics.DIVMOD[
num: temp, den: physicalDiskSector];
};
IO.PutF[out, "%bB[c:%g, t:%g, s:%g](%g)",
[integer[page]], [cardinal[cylinder]], [cardinal[track]], [cardinal[sector]],
[rope[IF correctable THEN "soft" ELSE "hard"]] ];
b.first.dontReport ← NOT correctable; -- suppress messages on subsequent passes
};
RESUME
};
};
VM.Pin[pageSpace];
IF format
THEN {
init: BOOLTRUE;
Randomize: UNSAFE PROC = UNCHECKED {
RandomizeOne[@initLabel, SIZE[Disk.Label]];
RandomizeOne[data, VM.PagesForWords[File.wordsPerPage]];
init ← NOT init;
};
RandomizeOne: UNSAFE PROC
[where: LONG POINTER, nwords: CARDINAL] = UNCHECKED {
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;
};
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;
}
ELSE THROUGH [1..passes]
DO SweepDisk[out, d, read] ENDLOOP;
Record any bad pages
{
p: PhysicalVolume.Physical ← NIL;
IF NOT format THEN
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 {
IO.PutRope[out, "\nCreating a physical volume to hold the bad page table ..."];
p ← FileExtra.CreatePhysicalVolume[
where: d, name: "JustForTheBadPageTable", id: IagoOps.NewID[]];
IO.PutRope[out, "done"];
};
IF badPages # NIL THEN {
rc: PhysicalVolume.PhysicalRC ← ok;
IO.PutRope[out, "\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;
IF rc = ok THEN IO.PutRope[out, "done" ] ELSE ERROR File.Error[rc];
};
};
};
VM.Free[pageSpace];
};
FlushCache: PUBLIC PROC [in, out: STREAM] = {
count: INT ← 0;
DoName: FSBackdoor.NameProc = {
count ← count+1;
IO.PutF[out, "\nFlushing %g ... ", [rope[fullGName]]];
{
ENABLE FS.Error => { IO.PutRope[out, error.explanation]; CONTINUE };
FSBackdoor.Flush[fullGName: fullGName, volName: volName];
IO.PutRope[out, "done"];
};
RETURN[TRUE];
};
v: File.Volume = IagoOps.GetLogical[in, out, "On "];
volName: ROPE = File.GetVolumeName[v];
pattern: ROPE ← IagoOps.GetFile[in: in, out: out, pattern: TRUE];
IO.PutRope[out, " ... "];
FSBackdoor.EnumerateCacheForNames[DoName, volName, pattern];
IF count = 0
THEN IO.PutRope[out, "not found"]
ELSE IO.PutF[out, "\n%g files", [integer[count]] ];
};
InstallCredentials: PUBLIC PROC [in, out: STREAM] = {
IO.PutRope[out,
"\nDo you want to password-protect this disk? "];
[] ← UserCredentials.ChangeState[IF IagoOps.Confirm[in, out] THEN name ELSE nameHint];
};
InstallInitial: PUBLIC PROC [in, out: STREAM] = {
d: Disk.Channel = IagoOps.GetDrive[in, out];
name: ROPE = IagoOps.GetFile[
in: in, out: out,
extension: ".eb",
default: IagoOps.InitialMicrocodeFileName[],
check: TRUE
];
IO.PutRope[out, " ... installing"];
[] ← DoInitialMicrocodeInstallation[out, d, name];
};
DoInitialMicrocodeInstallation: PROC
[out: STREAM, d: Disk.Channel, name: ROPE, temp: ROPENIL]
RETURNS [done: BOOLFALSE] = {
fsFile: FS.OpenFile;
pages: INT ← 0;
vmInterval: VM.Interval;
lp: LONG POINTER;
{
{ENABLE FS.Error => {IO.PutRope[out, error.explanation]; GOTO noFile};
It may be necessary to copy initial microcode into a named file instead of relying on the file system to stage it to the system volume, because there may be no system volume!
IF temp = NIL
THEN temp ← name
ELSE FS.Copy[from: name, to: temp, remoteCheck: TRUE];
fsFile ← FS.Open[name: temp, lock: read];
};
pages ← FS.GetInfo[fsFile].pages;
vmInterval← VM.Allocate[pages
! VM.CantAllocate => {
IO.PutRope[out, " Can't allocate!"];
FS.Close[fsFile];
GO TO noFile}];
TRUSTED {
VM.SwapIn[interval: vmInterval, kill: TRUE, pin: TRUE];
lp ← VM.AddressForPageNumber[vmInterval.page];
FS.Read[file: fsFile, from: 0, nPages: pages, to: lp
! File.Error => {
IO.PutRope[out, " Can't read the file at all!"];
GO TO finish}];
};
TRUSTED {
VMReader: UNSAFE PROC [ ptr: LONG POINTER]RETURNS [ok: BOOLTRUE] = {
IF pages <= 0 THEN RETURN[FALSE];
PrincOpsUtils.LongCopy[from: lp, nwords: 256, to: ptr];
pages ← pages-1; lp ← lp+PrincOps.wordsPerPage;
};
FormatDisk.InstallInitialMicrocode[d, VMReader
! FormatDisk.MicrocodeInstallFailure => {
IO.PutRope[out, 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"];
GO TO finish;
} ];
};
done ← TRUE;
IO.PutRope[out, " done."];
GO TO finish;
EXITS
noFile => {};
finish => {
VM.Unpin[vmInterval];
TRUSTED {VM.Free[vmInterval]};
FS.Close[fsFile];
};
};
};
InstallLogicalFile: PUBLIC PROC
[in, out: STREAM, which: File.VolumeFile[checkpoint..bootFile]] = {
v: File.Volume = IagoOps.GetLogical[in, out, "For "];
localVolume: ROPE = Rope.Cat["[]<", File.GetVolumeName[v], ">"];
createTime: BasicTime.GMT;
name: ROPE = IagoOps.GetFile[
in: in, out: out, extension: IagoOps.ext[which],
default: IagoOps.RemoteRootFileName[which],
wDir: localVolume, check: TRUE
];
localName: ROPE ← name;
fsFile: FS.OpenFile;
file: File.Handle;
PhysicalToo: PROC RETURNS[BOOL] = {
IF which # bootFile
THEN { IO.PutRope[out, "\nInstalling on the physical volume"]; RETURN[TRUE] };
IF File.IsDebugger[v] THEN RETURN[FALSE];
IO.PutRope[out, "\nDo you want to use this file when you boot the physical volume? "];
RETURN[IagoOps.Confirm[in, out]]
};
"name" is a canonicalized FS name.
IO.PutRope[out, " ... "];
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 {
Help: PROC = {
IO.PutRope[out, "? Please type the name of an FS local file such as \"a.b\""];
};
localName ← IagoOps.LocalRootFileName[which];
IO.PutRope[out, 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;
IO.PutRope[out, " ... copying ... "];
FS.Copy[from: name, to: localName, wDir: localVolume];
};
createTime ← FS.FileInfo[name: localName, wDir: localVolume].created;
fsFile ← FS.Open[name: localName, lock: write, wDir: localVolume];
file ← FSBackdoor.GetFileHandle[fsFile];
IF File.Info[file].volume # v
THEN IO.PutRope[out, "I'm confused: the file is on the wrong volume"]
ELSE {
IO.PutRope[out, "installing ... "];
File.SetRoot[which, file];
FS.SetByteCountAndCreatedTime[file: fsFile, created: createTime];
IO.PutRope[out, "done"];
};
FS.Close[fsFile];
IF PhysicalToo[] THEN {
PhysicalVolume.SetPhysicalRoot[v, which];
IO.PutRope[out, " ... done"] };
};
ListCache: PUBLIC PROC [in, out: STREAM] = {
count: INT ← 0;
DoName: FSBackdoor.NameProc = {
count ← count+1;
IO.PutF[out, "\n %g", [rope[fullGName]]];
RETURN[TRUE];
};
v: File.Volume = IagoOps.GetLogical[in, out, "On "];
pattern: ROPE ← IagoOps.GetFile[in: in, out: out, pattern: TRUE];
IO.PutRope[out, " ... "];
FSBackdoor.EnumerateCacheForNames[DoName, File.GetVolumeName[v], pattern];
IF count = 0
THEN IO.PutRope[out, "not found"]
ELSE IO.PutF[out, "\n%g files", [integer[count]] ];
};
ListFileInfo: PUBLIC PROC [in, out: STREAM] = {
count: INT ← 0;
DoName: FS.InfoProc = {
count ← count+1;
IO.PutF[out, "\n %g, keep: %g, bytes: %g, created: %g", [rope[fullFName]], [cardinal[keep]], [integer[bytes]], [time[created]] ];
IF attachedTo # NIL THEN IO.PutF[out, ", attached to %g", [rope[attachedTo]] ];
RETURN[TRUE];
};
pattern: ROPE ← IagoOps.GetFile[in: in, out: out, pattern: TRUE];
IO.PutRope[out, " ... "];
FS.EnumerateForInfo[pattern, DoName];
IF count = 0
THEN IO.PutRope[out, "not found"]
ELSE IO.PutF[out, "\n%g files", [integer[count]] ];
};
ListNames: PUBLIC PROC [in, out: STREAM] = {
count: INT ← 0;
prevFile: ROPENIL;
DoName: FS.NameProc = {
newFile, newPrefix: 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 IO.PutF[out, ", %g",
[rope[fullFName.Substr[start: newPos.ver.start, len: newPos.ver.length]]] ]
ELSE {
prevFile ← newPrefix;
IO.PutF[out, "\n%g", [rope[fullFName]]];
};
RETURN[TRUE];
};
pattern: ROPE ← IagoOps.GetFile[in: in, out: out, pattern: TRUE];
IO.PutRope[out, " ... "];
FS.EnumerateForNames[pattern, DoName];
IF count = 0
THEN IO.PutRope[out, "not found"]
ELSE IO.PutF[out, "\n%g files", [integer[count]] ];
};
Login: PUBLIC PROC [options: UserCredentials.LoginOptions] = {
TurnOnProc: PROC RETURNS [in, out: STREAM] = {
[in: in, out: out] ← SimpleTerminal.TurnOn[];
IO.PutRope[out, "\nPlease login ...\n"];
};
TurnOffProc: PROC [in, out: STREAM] = {
SimpleTerminal.TurnOff[]
};
UserCredentials.Login[startInteraction: TurnOnProc, endInteraction: TurnOffProc,
options: options];
};
Rename: PUBLIC PROC [in, out: STREAM] = {
from: ROPE = IagoOps.GetFile[in: in, out: out, prompt: "From ", check: TRUE];
to: ROPE = IagoOps.GetFile[in: in, out: out, prompt: "To "];
IO.PutRope[out, " ... "];
FS.Rename[from: from, to: to];
IO.PutRope[out, "done"];
};
Rollback: PUBLIC PROC [in, out: STREAM] = {
v: File.Volume = IagoOps.GetLogical[in, out];
reject: ROPE;
mySwitches: Booting.Switches ← ALL[FALSE];
mySwitches[r] ← TRUE;
reject ← Booting.Boot[[logical[v]], mySwitches];
IF reject # NIL THEN { IO.PutRope[out, " ... couldn't: "]; IO.PutRope[out, reject] };
};
RunDiagnosticBCD: PUBLIC PROC [in, out: STREAM] = {
fName: ROPE;
fsFile: FS.OpenFile;
cm: PrincOps.ControlModule;
unboundImports: LIST OF Loader.IRItem;
fName← IagoOps.GetFile[in: in, out: out, prompt: "FileName ", check: TRUE];
fsFile← FS.Open[fName ! FS.Error => {
IO.PutRope[out, error.explanation]; GOTO noFile}];
TRUSTED {
[cm, unboundImports]← Loader.Instantiate[fsFile ! Loader.Error => {
IO.PutRope[out, message]; cm← PrincOps.NullControl; CONTINUE}];
};
IF unboundImports#NIL THEN {
IO.PutRope[out, "\nThere are unbound imports; confirm to start the code anyway"];
IF ~IagoOps.Confirm[in, out] THEN RETURN;
};
TRUSTED {IF cm # PrincOps.NullControl THEN Loader.Start[cm ! ABORTED => CONTINUE]};
FS.Close[fsFile ! FS.Error => CONTINUE];
EXITS
noFile => NULL;
};
Scavenge: PUBLIC PROC [in, out: STREAM] = {
v: File.Volume = IagoOps.GetLogical[in, out];
IO.PutRope[out, " ... "];
[] ← File.FindVM[];
FSBackdoor.ScavengeDirectoryAndCache[File.GetVolumeName[v]];
IO.PutRope[out, "done"];
};
SetKeep: PUBLIC PROC [in, out: STREAM] = {
count: INT ← 0;
DoName: FS.NameProc = {
count ← count+1;
fullFName ← fullFName.Substr[start: 0, len: fullFName.Index[0, "!"]];
IO.PutF[out, "\nSetting %g ... ", [rope[fullFName]]];
{
ENABLE FS.Error => { IO.PutRope[out, error.explanation]; CONTINUE };
FS.SetKeep[fullFName, k];
IO.PutRope[out, "done"];
};
RETURN[TRUE];
};
pattern: 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"];
IO.PutRope[out, " ... "];
FS.EnumerateForNames[pattern, DoName];
IF count = 0
THEN IO.PutRope[out, "not found"]
ELSE IO.PutF[out, "\n%g files", [integer[count]] ];
};
SetPhysicalFile: PUBLIC PROC
[in, out: STREAM, which: File.VolumeFile[checkpoint..bootFile]] = {
v: File.Volume = IagoOps.GetLogical[in, out, "From "];
IO.PutRope[out, " ... "];
PhysicalVolume.SetPhysicalRoot[v, which];
IO.PutRope[out, "done"];
};
SetWDir: PUBLIC PROC [in, out: STREAM] = {
Help: PROC = {
IO.PutRope[out, "? Type the prefix of a file name, such as \"[]<volume>\""] };
name: 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];
};
MemImagePages: PROC RETURNS [INT] = TRUSTED {
RETURN [BootFileChanges.MemorySizeToFileSize[VMSideDoor.rmPages]+24];
};
}.