<> <> <> <> <<>> 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.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 => { <, the and following 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)"]] }; <> 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 \"[]a.b\" or \"[server]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]; <> firstPage _ origin; IF firstPage >= altoStart AND firstPage <= altoStart + altoCount THEN firstPage _ [altoStart + altoCount]; <= FormatDisk.hardUCodeStart>> <> <> <> pageCount _ diskCount-firstPage; IF firstPage < altoStart AND firstPage+pageCount > altoStart THEN pageCount _ altoStart-firstPage; <> < FormatDisk.hardUCodeStart>> <> 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]Top>" ELSE "[Indigo]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.