-- VolumeInitImplA.mesa    modified April 11, 1983 5:59 pm by Taft

DIRECTORY
  Boot USING [BootFileType, LVBootFiles],
  CedarSnapshot USING [RollBack],
  Device,
  DeviceTypes USING [cdc9730, sa1000, sa4000, sa800],
  File,
  FileTypes USING [tUntypedFile],
  FTPDefs USING [FtpError],
  MicrocodeFile,
  OthelloDefs,
  OthelloDevice,
  OthelloOps,
  PhysicalVolume,
  PhysicalVolumeExtras USING [noProblems, RepairType, Scavenge,
    ScavengerStatus],
  PilotClient USING[],
  PilotSwitches USING [switches],
  Scavenger USING [Scavenge],
  SimpleTTYExtras,
  Space USING [Create, Delete, Handle, Map, mds, Pointer],
  SpecialVolume USING [GetLogicalVolumeBootFiles],
  Storage USING [CopyString],
  String USING [
    AppendChar, AppendLongNumber, AppendString, EquivalentStrings],
  System USING [GreenwichMeanTime, PowerOff],
  TemporaryBooting USING [
    BootButton, BootFromVolume, defaultSwitches, InvalidParameters, Switches],
  Time USING [Append, Unpack],
  UserCredentialsUnsafe,
  Volume,
  VolumeExtras USING [OpenVolume];

VolumeInitImplA: PROGRAM
  IMPORTS        
    CedarSnapshot, File, MicrocodeFile, OthelloDefs, OthelloDevice, OthelloOps,
    PhysicalVolume, PilotSwitches, PhysicalVolumeExtras, Scavenger, SimpleTTYExtras, Space,
    SpecialVolume, Storage, System, String, TemporaryBooting, Time, UserCredentialsUnsafe,
    Volume, VolumeExtras
  EXPORTS OthelloDefs, PilotClient =
BEGIN OPEN OthelloOps, OthelloDefs;

currentCommands: DESCRIPTOR FOR ARRAY OF CommandTableRecord;

-- Keep command tables alphabetical
normalCommandTable: ARRAY [0..40) OF CommandTableRecord ← [
  ["@", Indirect, "Run command file"],
  ["Boot", BootBoot, "Boot From Logical Volume"],
  ["Check Drive", CheckDrive, "Check drive for unreadable pages"],
  ["Connect", GetConnectNamePassword,
    "Set secondary name and password"],
  ["Close", CloseCmd, "FTP Close"],
  ["Create Physical Volume", OthelloDevice.CreateVolume,
    "Write new physical and logical volumes on drive (old contents lost)"],
  ["Delete Boot File", DeleteBootFile, "Delete old boot file from volume"],
  ["Delete Temporary Files", DeleteTempFilesUser,
    "Delete Temporary Files"],
  ["Describe Physical Volumes", DescribePhysicalVolumes,
    "Describe online physical volumes and installed microcode"],
  ["Diagnostic Microcode Fetch", FetchDiagnosticMicrocode,
    "Fetch and Install Diagnostic Microcode"],
  ["Directory", Directory, "Set Default FTP directory"],
  ["Erase", Erase, "Erase Logical Volume"],
  ["Fetch", FetchBoot, "Fetch Boot File"],
  ["Format", Format, "Format Physical Volume"],
  ["Germ Fetch", FetchGerm, "Fetch Germ"],
  ["Help", Help, "Type this message"],
  ["Initial Microcode Fetch", OthelloDevice.FetchInitialMicrocode,
    "Fetch and Install Initial Microcode"],
  ["Install Credentials", InstallUserCredentials,
    "Install your current user credentials on disk"],
  ["List Bad Pages", ListBadPages, "List known bad pages on Pilot volume"],
  ["List Drives", ListDrives, "List all known Drives"],
  ["List Logical Volumes", ListLogicalVolumes, "List logical volumes"],
  ["List Physical Volumes", ListPhysicalVolumes, "List physical volumes"],
  ["List Remote Files", ListRemoteFiles, "FTP list remote files"],
  ["Login", GetUserNamePassword, "Set user name and password"],
  ["Make Page Bad", MakeBad, "Enter page into bad page table"],
  ["Offline", Offline, "Bring physical volume offline"],
  ["Online", Online, "Bring drive online"],
  ["Open", OpenCmd, "FTP Open"],
  ["Partitions", OthelloDevice.PartitionCmd,
    "Specify disk partition(s) for Pilot volumes"],
  ["Physical Volume Scavenge", PVScavenge, "Scavenge physical volume"],
  ["Pilot Microcode Fetch", FetchPilotMicrocode,
    "Fetch and Install Pilot Microcode"],
  ["Power Off", PowerOff, "Execute System.PowerOff"],
  ["Pup Echo User", EchoUser, "Pup echo user"],
  ["Quit", Quit, "Push the boot button"],
  ["Reserve Alto Volume", OthelloDevice.IndicateAltoness,
    "Reserve space for alto-type volume"],
  ["RollBack", RollBackCmd, "Restore state saved in most recent snapshot"],
  ["Routing Tables", PrintLocalPupRoutingTable,
    "Show local network routing tables"],
  ["Scavenge", Scavenge, "Scavenge Logical Volume"],
  ["Set Debugger Pointers", SetDebuggerUser,
    "Set up pointers to debugger for volume"],
  ["Set Physical Boot Files", SetPvBoot,
    "Set physical boot files from Logical Volume"]];

restrictedCommandTable: ARRAY [0..19) OF CommandTableRecord ← [
  ["@", Indirect, "Run command file"],
  ["Check Drive", CheckDrive, "Check drive for unreadable pages"],
  ["Connect", GetConnectNamePassword,
    "Set secondary name and password"],
  ["Close", CloseCmd, "FTP Close"],
  ["Create Physical Volume", OthelloDevice.CreateVolume,
    "Write new physical and logical volumes on drive (old contents lost)"],
  ["Directory", Directory, "Set Default FTP directory"],
  ["Format", Format, "Format Physical Volume"],
  ["Help", Help, "Type this message"],
  ["Initial Microcode Fetch", OthelloDevice.FetchInitialMicrocode,
    "Fetch and Install Initial Microcode"],
  ["Login", GetUserNamePassword, "Set user name and password"],
  ["Make Page Bad", MakeBad, "Enter page into bad page table"],
  ["Open", OpenCmd, "FTP Open"],
  ["Partitions", OthelloDevice.PartitionCmd,
    "Specify disk partition(s) for Pilot volumes"],
  ["Physical Volume Scavenge", PVScavenge, "Scavenge physical volume"],
  ["Power Off", PowerOff, "Execute System.PowerOff"],
  ["Pup Echo User", EchoUser, "Pup echo user"],
  ["Quit", Quit, "Push the boot button"],
  ["Reserve Alto Volume", OthelloDevice.IndicateAltoness,
    "Reserve space for alto-type volume"],
  ["Routing Tables", PrintLocalPupRoutingTable,
    "Show local network routing tables"]];

logicalVolumeTypeString: ARRAY Volume.Type OF STRING ←
    ["normal", "debugger", "debuggerDebugger", "nonPilot"];

ftpOpen:               PUBLIC BOOLEAN ← FALSE;
inputDriveString:      STRING ← [10];
inputPhysString:       STRING ← [10+maxNameLength];
maxNameLength:         CARDINAL = PhysicalVolume.maxNameLength;

BootBoot: PROC =
  BEGIN
  lvID: Volume.ID ← GetLvIDFromUser[].lvID;
  switches: STRING ← [40];
  ts: TemporaryBooting.Switches;
  GetName["switches: "L, switches, , TRUE
  ! Question =>
      BEGIN
      WriteLine["Pilot switches (0-9, a-z) or CR."L];
      WriteLine["See latest HowToUsePilot.memo for current list of valid switches."L];
      RESUME;
      END];
  ts ← DecodeSwitches[switches ! BadSwitch => RESUME];
  IF ftpOpen THEN CloseCmd[];
  TemporaryBooting.BootFromVolume[lvID, ts];
  END;

CheckDrive: PROC =
  BEGIN
  badSpots: CARDINAL;
  badSpotArray: ARRAY [0..OthelloDevice.badTableSize) OF PhysicalVolume.PageNumber;
  couldDo: BOOLEAN;
  h: PhysicalVolume.Handle;
  p: PUBLIC PhysicalVolume.ID ← PhysicalVolume.nullID;
  wasOnLine: BOOLEAN ← FALSE;

  [couldDo, badSpots, h] ← OthelloDevice.FormatCheckDrive[@badSpotArray, check];
  IF ~couldDo THEN {ReportError["Othello can't check this device."L]; RETURN}
  ELSE IF badSpots=0 THEN {WriteLine["No bad pages found."L]; RETURN}
  ELSE IF ~Yes["\rShall I record these pages in the bad table? "L] THEN RETURN;
  -- See if was on line/put on line
  DO
    p ← PhysicalVolume.GetNext[p];
    IF p=PhysicalVolume.nullID THEN {p ← PhysicalVolume.AssertPilotVolume[h]; EXIT}
    ELSE IF h=PhysicalVolume.GetAttributes[p].instance THEN {wasOnLine ← TRUE; EXIT};
    ENDLOOP;
  -- this code depends upon Pilot's bad spot table being smaller than ours, 
  -- and an error being raised when Pilot's table is full.
  FOR i: CARDINAL IN [0..badSpots) DO
    PhysicalVolume.MarkPageBad[p, badSpotArray[i]
    ! PhysicalVolume.Error => IF error=badSpotTableFull THEN
        {WriteLine["Too many bad spots."L]; EXIT}]; 
    ENDLOOP;
  IF ~wasOnLine THEN PhysicalVolume.Offline[p];
  WriteLine["Consider scavenging some volumes."L];
  END;

CloseCmd: PROC =
  BEGIN
  msg: STRING ← [100];
  IF ftpOpen THEN WriteLine[IF Close[msg: msg] THEN "closed"L ELSE msg];
  ftpOpen ← FALSE;
  END;

DeleteBootFile: PROC =
  BEGIN
  lvID: Volume.ID= GetLvIDFromUser[].lvID;
  pvID: PhysicalVolume.ID = 
    PhysicalVolume.GetContainingPhysicalVolume[lvID];
  FOR t: BootFileType IN [hardMicrocode..pilot] DO
    cap: File.Capability = GetVolumeBootFile[lvID, t].cap;
    IF cap=File.nullCapability THEN LOOP;
    Volume.Open[lvID];
    BEGIN ENABLE File.Unknown => CONTINUE;
    IF File.GetAttributes[cap].immutable THEN
      File.DeleteImmutable[cap, lvID] ELSE File.Delete[cap];
    END;
    VoidVolumeBootFile[lvID, t];
    IF GetPhysicalVolumeBootFile[pvID, t].cap=cap THEN
      VoidPhysicalVolumeBootFile[pvID, t];
    Volume.Close[lvID];
    ENDLOOP;
  END;

DeleteTempFilesUser: PROC = {DeleteTempFiles[GetLvIDFromUser[].lvID]};

DescribePhysicalVolumes: PROC =
  BEGIN
  pvID: PhysicalVolume.ID ← PhysicalVolume.nullID;
  pvFound: BOOLEAN ← FALSE;

  DO
    h: PhysicalVolume.Handle;
    s: STRING  ← [maxNameLength];
    sV: SubVolume ← nullSubVolume;
    sVFound: BOOLEAN ← FALSE;
    microcodeInstalled: BOOLEAN;
    time: System.GreenwichMeanTime;
    hasPVBootFile: PACKED ARRAY BootFileType OF BOOLEAN;
    bootFileCount: CARDINAL;
    pvID  ← PhysicalVolume.GetNext[pvID];
    IF pvID=PhysicalVolume.nullID THEN EXIT ELSE pvFound ← TRUE;
    h ← PhysicalVolume.GetAttributes[pvID, s].instance;
    WriteString["Physical Volume "L]; WriteString[s];
    WriteString[" on drive "L]; WriteString[GetDriveStringName[h]];
    WriteString[" (which is a "L];
    WriteString[SELECT GetDriveType[h] FROM
      DeviceTypes.sa800=> "Shugart 800"L,
      DeviceTypes.sa1000 => "Shugart 1000"L,
      DeviceTypes.sa4000 => OthelloDevice.printNameSA4000,
      DeviceTypes.cdc9730 => "CDC 9730"L,
      ENDCASE => "unknown type"L];
    WriteString[")\r"];
    [microcodeInstalled, time] ← OthelloDevice.IdentifyInitialMicrocode[h, s];
    IF microcodeInstalled THEN
      BEGIN
      WriteString["  Initial microcode is "L];
      WriteString[s];
      WriteString[" of "];
      s.length ← 0;
      Time.Append[s, Time.Unpack[time]];
      WriteLine[s];
      END;
    DO
      sV ← GetNextSubVolume[pvID, sV];
      IF sV=nullSubVolume THEN EXIT;
      sVFound ← TRUE;
      WriteString["  Logical Volume "L];
      IF Volume.GetAttributes[sV.lvID].volumeSize#sV.subVolumeSize THEN
        WriteString["piece "L];
      GetLogicalVolumeName[sV.lvID, s];
      WriteString[s]; WriteString[" (type =  "L];
      WriteString[GetLogicalVolumeTypeName[sV.lvID]];
      WriteString[")\r    Occupies "L]; WriteLongNumber[sV.subVolumeSize];
      WriteString[" pages starting at physical address "L];
      WriteLongNumber[sV.firstPVPageNumber];
      NewLine[];
      bootFileCount ← 0;
      FOR type: BootFileType IN BootFileType DO
        file: File.Capability;
	firstPage: File.PageNumber;
	[file, firstPage] ← GetVolumeBootFile[sV.lvID, type];
        IF (hasPVBootFile[type] ← file#File.nullCapability AND
	  file=GetPhysicalVolumeBootFile[pvID, type].cap) THEN
	  bootFileCount ← bootFileCount+1;
	ENDLOOP;
      IF bootFileCount#0 THEN
        BEGIN
	bootFileOrdinal: CARDINAL ← 0;
        WriteString["    Contains the physical volume "L];
	FOR type: BootFileType IN BootFileType DO
	  IF hasPVBootFile[type] THEN
	    BEGIN
	    WriteString[GetBootFileTypeString[type]];
	    bootFileOrdinal ← bootFileOrdinal+1;
	    IF bootFileOrdinal<bootFileCount AND bootFileCount#2 THEN WriteChar[',];
	    WriteChar[' ];
	    IF bootFileOrdinal=bootFileCount-1 THEN WriteString["and "L];
	    END;
	  ENDLOOP;
	WriteLine[IF bootFileCount=1 THEN "file"L ELSE "files"L];
	END;
      [microcodeInstalled, time] ← IdentifyPilotMicrocode[sV, s];
      IF microcodeInstalled THEN
        BEGIN
        WriteString["    Contains Pilot microcode "L];
        WriteString[s];
        WriteString[" of "];
        s.length ← 0;
        Time.Append[s, Time.Unpack[time]];
        WriteLine[s];
        END;
      ENDLOOP;
    IF ~sVFound THEN WriteLine["  No logical volumes"L];
    ENDLOOP;
  IF ~pvFound THEN WriteLine["No physical Volumes found"L];
  END;

Directory: PROC= {GetName["Directory: "L, directory]};

Erase: PROC =
  BEGIN
  lvID: Volume.ID ← GetLvIDFromUser[].lvID;
  Confirm[];
  Volume.Close[lvID];
  WriteString["Erase...."L];
  PhysicalVolume.EraseLogicalVolume[lvID];
  WriteLine["complete"L];
  END;

FetchBoot: PROC = {Fetch[pilot, "Boot file name: "L]};

FetchGerm: PROC = {Fetch[germ, "Germ file name: "L]};

FetchPilotMicrocode: PROC = {Fetch[softMicrocode, "Pilot microcode file name: "L]};

FetchDiagnosticMicrocode: PROC = 
  {Fetch[hardMicrocode, "Diagnostic microcode file name: "L]};

file: PUBLIC STRING ← [100];

Fetch: PROC [type: BootFileType, prompt: STRING] =
  BEGIN
  created: BOOLEAN ← FALSE;
  cap: File.Capability;
  firstPage: File.PageNumber;
  lvID: Volume.ID;
  msg: STRING ← [100];

  msg.length ← 0;
  IF ~ftpOpen THEN {ReportError["Please open a connection first"L]; RETURN};
  lvID ← GetLvIDFromUser[].lvID;
  GetName[prompt, file];
  Volume.Open[lvID];
  [cap, firstPage] ← GetVolumeBootFile[lvID, type];
  IF cap#File.nullCapability THEN
    BEGIN ENABLE File.Unknown => {cap ← File.nullCapability; CONTINUE};
    IF File.GetAttributes[cap].immutable THEN 
      BEGIN
      pvID: PhysicalVolume.ID =
        PhysicalVolume.GetContainingPhysicalVolume[lvID];
      File.DeleteImmutable[cap, lvID];
      VoidVolumeBootFile[lvID, type];
      IF GetPhysicalVolumeBootFile[pvID, type].cap=cap THEN
        VoidPhysicalVolumeBootFile[pvID, type];
      cap ← File.nullCapability;
      END;
    END;
  IF (created ← cap=File.nullCapability) THEN
    cap ← File.Create[lvID, 0, FileTypes.tUntypedFile]
  ELSE
    MakeUnbootable[cap, type, firstPage
    ! TemporaryBooting.InvalidParameters =>
        {WriteLine["Warning, trouble making unbootable"L]; CONTINUE}];
  WriteString["Fetching..."L];
  IF Retrieve[
    remoteFile: file, msg: msg, destination: [pilotFileSystemWrite[cap]] !
      CredentialError => { PromptForCredentials[ftpError, message]; RESUME }] THEN
    BEGIN
    WriteString["Installing..."L];
    SetVolumeBootFile[cap, type, 0];
    File.MakePermanent[cap];
    MakeBootable[cap, type, 0
    ! TemporaryBooting.InvalidParameters =>
        {WriteLine["Warning, trouble making bootable"L]; CONTINUE}];
    WriteLine["done"L];
    IF type IN [hardMicrocode..germ] AND
      Yes["Shall I also use this for the Physical Volume? "L] THEN
	SetPhysicalVolumeBootFile[cap, type, 0];
    END
  ELSE {IF created THEN File.Delete[cap]; WriteLine[msg]};
  Volume.Close[lvID];
  END;

Format: PROC =
  BEGIN
  badSpots: CARDINAL;
  badSpotArray: ARRAY [0..OthelloDevice.badTableSize) OF PhysicalVolume.PageNumber;
  couldDo: BOOLEAN;
  h: PhysicalVolume.Handle;
  p: PhysicalVolume.ID;

  [couldDo, badSpots, h] ← OthelloDevice.FormatCheckDrive[@badSpotArray, format];
  IF ~couldDo THEN {ReportError["Othello can't format this device."L]; RETURN};
  FOR i: CARDINAL IN [0..MIN[badSpots, LENGTH[badSpotArray]]) DO
    IF badSpotArray[i]=0 THEN {ReportError["Physical page zero bad"L]; RETURN};
    ENDLOOP;
  WriteLine["\rCreating Pilot volume named ""Empty"" to hold bad spot table"L];
  p ← PhysicalVolume.CreatePhysicalVolume[h, "Empty"L];
  FOR i: CARDINAL IN [0..badSpots) DO
    PhysicalVolume.MarkPageBad[p, badSpotArray[i]
    ! PhysicalVolume.Error => IF error=badSpotTableFull THEN GOTO fullBadSpotTable]; 
    ENDLOOP;
  PhysicalVolume.Offline[p];
  EXITS
  fullBadSpotTable => WriteLine["Too many bad spots."L];
  END;

GetConnectNamePassword: PROC=
  BEGIN
  GetName["Directory: "L, connectName];
  GetName["Password: "L, connectPassword, noEcho];
  END;

GetUserNamePassword: PROC=
  BEGIN
  GetName["User: "L, userName];
  GetName["Password: "L, userPassword, noEcho];
  UserCredentialsUnsafe.SetUserCredentials[name: userName, password: userPassword];
  END;

Help: PROC=
  BEGIN
  tabWidth: CARDINAL  ← 0;
  FOR i: CARDINAL IN [0..LENGTH[currentCommands]) DO
    tabWidth ← MAX[tabWidth, currentCommands[i].name.length] ENDLOOP;
  tabWidth ← tabWidth+4;
  FOR i: CARDINAL IN [0..LENGTH[currentCommands]) DO
    WriteString[currentCommands[i].name];
    THROUGH [currentCommands[i].name.length..tabWidth) DO WriteChar[' ] ENDLOOP;
    WriteLine[currentCommands[i].detail]
    ENDLOOP;
  WriteLine["In General, Del will abort current command, ? will explain options"L]
  END;

Indirect: PROC =
  BEGIN
  msg: STRING ← [100];
  cmFile: STRING ← [60];
  s: STRING ← NIL;
  GetString: PROC [command: STRING] = {s← Storage.CopyString[command]};
  GetName["Command file: "L, file
      ! Question => {WriteLine["[Host]<Dir>Filename"L]; RESUME}];
  directory.length ← host.length ← 0;
  ParseCmFileName[host, cmFile];
  SELECT TRUE FROM
    host.length = 0 => {ReportError["No host specified!"L]; RETURN};
    cmFile.length = 0 => {ReportError["No file specified!"L]; RETURN};
    ENDCASE;
  IF ftpOpen THEN CloseCmd[];
  ftpOpen ← Open[server: host, remoteMsg: msg];
  WriteLine[msg]; msg.length ← 0;
  WriteString["Fetching..."L];
  IF Retrieve[cmFile, msg, [string[GetString]] !
    CredentialError => { PromptForCredentials[ftpError, message]; RESUME }] THEN
    SetCommandString[s]
  ELSE ReportError[msg];
  WriteLine["done"L];
  END;

ListBadPages: PROC =
  BEGIN
  id: PhysicalVolume.ID ← GetPvIDFromUser[].id;
  page: PhysicalVolume.PageNumber ← PhysicalVolume.nullBadPage;
  badSpots: BOOLEAN ← FALSE;
  col: CARDINAL ← 0;
  WHILE (page ← PhysicalVolume.GetNextBadPage[id, page])
      # PhysicalVolume.nullBadPage DO
    IF col=6 THEN BEGIN NewLine[]; col← 0; END;
    WriteFixedWidthNumber[page, 11];
    col ← col+1;
    badSpots ← TRUE;
    ENDLOOP;
  WriteLine[IF badSpots THEN NIL ELSE "No known bad spots"L];
  END;

ListDrives: PROC =
  BEGIN
  t: Device.Type ← Device.nullType;
  index: CARDINAL ← PhysicalVolume.nullDeviceIndex;
  first: BOOLEAN ← TRUE;
  DO
    [t, index] ← PhysicalVolume.GetNextDrive[t, index];
    IF t=Device.nullType AND index=PhysicalVolume.nullDeviceIndex THEN EXIT;
    IF ~first THEN WriteString[", "L];
    first ← FALSE;
    WriteString[GetDriveStringName[PhysicalVolume.GetHandle[t, index]]];
    ENDLOOP;
  NewLine[];
  END;

ListLogicalVolumes: PROC =
  BEGIN
  first: BOOLEAN ← TRUE;
  pID: PhysicalVolume.ID ← PhysicalVolume.nullID;
  DO
    sV: SubVolume ← nullSubVolume;
    pID ← PhysicalVolume.GetNext[pID];
    IF pID=PhysicalVolume.nullID THEN EXIT;
    DO
    s: STRING ← [maxNameLength];
      sV ← GetNextSubVolume[pID, sV];
      IF sV=nullSubVolume THEN EXIT;
      IF sV.firstLVPageNumber#0 THEN LOOP;
      IF ~first THEN WriteString[", "L];
      WriteString[GetDriveStringName[
        PhysicalVolume.GetAttributes[pID].instance]];
      WriteChar[':]; GetLogicalVolumeName[sV.lvID, s]; WriteString[s];
      first ← FALSE;
      ENDLOOP;
    ENDLOOP;
  WriteLine[IF first THEN "No logical volumes found"L ELSE NIL];
  END;

ListPhysicalVolumes: PROC =
  BEGIN
  s: STRING ← [maxNameLength];
  driveString: STRING;
  first: BOOLEAN ← TRUE;
  pID: PhysicalVolume.ID ← PhysicalVolume.nullID;
  DO
    pID ← PhysicalVolume.GetNext[pID];
    IF pID=PhysicalVolume.nullID THEN EXIT;
    driveString ← GetDriveStringName[PhysicalVolume.GetAttributes[pID, s].instance];
    IF ~first THEN WriteString[", "L];
    WriteString[driveString];
    WriteChar[':];
    WriteString[s];
    first ← FALSE;
    ENDLOOP;
  WriteLine[IF ~first THEN NIL ELSE "No physical volumes found"L];
  END;

ListRemoteFiles: PROC =
  BEGIN
  msg: STRING ← [100];
  ListOne: PROC [filename, creationDate, author: STRING] =
    BEGIN
    WriteString[filename];
    THROUGH [filename.length..40] DO WriteChar[' ] ENDLOOP;
    WriteString["  "L];
    IF creationDate#NIL THEN WriteString[creationDate];
    WriteString["  "L];
    IF author#NIL THEN WriteString[author];
    WriteChar['\r];
    END;
  msg.length ← 0;
  IF ~ftpOpen THEN {ReportError["Please open a connection first"L]; RETURN};
  GetName["Remote file name or pattern: "L, file];
  IF Enumerate[remoteFile: file, msg: msg, proc: ListOne !
      CredentialError => { PromptForCredentials[ftpError, message]; RESUME }] THEN NULL
  ELSE WriteLine[msg];
  END;

MakeBad: PROC =
  BEGIN
  h: PhysicalVolume.Handle;
  id: PhysicalVolume.ID;
  page: PhysicalVolume.PageNumber;
  [id, h] ← GetPvIDFromUser[];
  page ← ReadNumber["Decimal Page Number: "L, 0, GetDriveSize[h]-1];
  PhysicalVolume.MarkPageBad[id, page];
  WriteLine["Consider scavenging some logical volumes."L];
  END;

Offline: PROC = {PhysicalVolume.Offline[GetPvIDFromUser[].id]};

Online: PROC =
  BEGIN
  [] ← PhysicalVolume.AssertPilotVolume[GetDriveFromUser[]
  ! PhysicalVolume.Error => IF error = alreadyAsserted THEN CONTINUE];
  END;

host: STRING ← [40];
OpenCmd: PROC =
  BEGIN
  msg: STRING ← [100];
  IF ftpOpen THEN CloseCmd[];
  GetName["Open connection to "L, host];
  ftpOpen ← Open[server: host, remoteMsg: msg];
  WriteLine[msg];
  END;

PowerOff: PROC= {Confirm[]; IF ftpOpen THEN CloseCmd[]; System.PowerOff[]};

PVScavenge: PROC =
  BEGIN OPEN PVE: PhysicalVolumeExtras;
  badPageListStr: STRING = "Bad page list "L;
  bootFileStr: STRING = "Boot file "L;
  germStr: STRING = "Germ "L;
  pilotMicrocodeStr: STRING = "Pilot microcode "L;
  diagMicrocodeStr: STRING = "Diagnostic microcode "L;
  internalStructuresStr: STRING = "Internal structures "L;
  damagedStr: STRING = "damaged"L;
  lostStr: STRING = "lost"L;
  repairedStr: STRING = "have been repaired!"L;
  s: PVE.ScavengerStatus;
  h: PhysicalVolume.Handle ← GetDriveFromUser[];
  repair: PVE.RepairType ←
    IF ~Yes["Repair? "L] THEN checkOnly
    ELSE IF Yes["Risky repair? "L] THEN riskyRepair
    ELSE safeRepair;
  WriteString["Scavenging..."L];
  s ← PVE.Scavenge[
    h, repair !
    PhysicalVolume.Error =>
      {WriteString["PhysicalVolume.Error..."L];
      SELECT error FROM
        badDisk => WriteLine["badDisk"L];
	invalidHandle => WriteLine["drive went away"L];
	ENDCASE => WriteLine["(unknown error type)"L];
      GOTO Error};
    PhysicalVolume.CanNotScavenge =>
      {WriteLine["Cannot scavenge an online physical volume"L]; GOTO Error}];
  WriteLine["complete"L];
  IF s = PVE.noProblems THEN
    WriteLine["You have a beautiful physical volume!"L]
  ELSE
    {WriteString["Damage report:  "L];
    IF s.internalStructures = repaired THEN
      {WriteString[internalStructuresStr]; WriteLine[repairedStr]};
    IF s.internalStructures = damaged THEN
      {WriteString[internalStructuresStr]; WriteLine[damagedStr];
      IF repair = safeRepair THEN
        WriteLine["Consider trying a risky repair."L]};
    IF s.badPageList = damaged THEN
      {WriteString[badPageListStr]; WriteLine[damagedStr]};
    IF s.badPageList = lost THEN
      {WriteString[badPageListStr]; WriteLine[lostStr]};
    IF s.bootFile = damaged THEN
      {WriteString[bootFileStr]; WriteLine[damagedStr]};
    IF s.bootFile = lost THEN
      {WriteString[bootFileStr]; WriteLine[lostStr]};
    IF s.germ = damaged THEN
      {WriteString[germStr]; WriteLine[damagedStr]};
    IF s.germ = lost THEN
      {WriteString[germStr]; WriteLine[lostStr]};
    IF s.softMicrocode = damaged THEN
      {WriteString[pilotMicrocodeStr]; WriteLine[damagedStr]};
    IF s.softMicrocode = lost THEN
      {WriteString[pilotMicrocodeStr]; WriteLine[lostStr]};
    IF s.hardMicrocode = damaged THEN
      {WriteString[diagMicrocodeStr]; WriteLine[damagedStr]};
    IF s.hardMicrocode = lost THEN
      {WriteString[diagMicrocodeStr]; WriteLine[lostStr]}};
  EXITS
    Error => AbortCommandFileIfAny[];
  END;

Quit: PROC=
  BEGIN
  Confirm[];
  IF ftpOpen THEN CloseCmd[];
  TemporaryBooting.BootButton[TemporaryBooting.defaultSwitches];
  END;

RollBackCmd: PROC =
  BEGIN
  volume: Volume.ID ← Volume.nullID;
  allVolumes: Volume.TypeSet = ALL[TRUE];
  default: STRING ← [maxNameLength];
  snapshotSlot: Boot.BootFileType = hardMicrocode;
  -- Figure out a default volume name, which is the first volume of type normal which
  -- has an installed checkpoint.
  -- Guess what, sports fans?  TypeSets don't work in UtilityPilot, due to an
  -- ugly crock in VolumeImpl.GetNext that essentially ignores them.  So, we have
  -- to do it ourselves
  UNTIL (volume ← Volume.GetNext[volume, allVolumes]) = Volume.nullID DO
    IF Volume.GetType[volume] = normal THEN
      BEGIN
      bootFiles: Boot.LVBootFiles;
      SpecialVolume.GetLogicalVolumeBootFiles[volume, @bootFiles];
      IF bootFiles[snapshotSlot].fID # File.nullID THEN {
        GetLogicalVolumeName[volume, default]; EXIT};
      END
    REPEAT
      FINISHED => default ← NIL;
    ENDLOOP;
  volume ← GetLvIDFromUser[default: default].lvID;
  -- We only do the following Open to check if scavenging is needed, since
  -- Rollback doesn't require that the volume be open.
  -- Volume.NeedsScavenging will be caught by caller and generate the right message.
  VolumeExtras.OpenVolume[volume: volume, readOnly: TRUE];
  CedarSnapshot.RollBack[volume];  -- doesn't return if successful
  ReportError["Can't: there's no saved checkpoint on that volume."L];
  END;

Scavenge: PROC =
  BEGIN
  lvID: Volume.ID ← GetLvIDFromUser[].lvID;
  Confirm[];
  WriteString["Scavenging...."L];
  Volume.Close[lvID ! ANY => CONTINUE];
  [] ← Scavenger.Scavenge[lvID, lvID, TRUE];
  WriteLine["complete"L];
  END;

SetDebuggerUser: PROC =
  BEGIN
  cap: File.Capability;
  firstPage: File.PageNumber;
  lvID: Volume.ID = GetLvIDFromUser["for debuggee Logical Volume: "L].lvID;
  dLvID: Volume.ID;
  dPvID: PhysicalVolume.ID;
  dH: PhysicalVolume.Handle;
  [cap, firstPage] ← GetVolumeBootFile[lvID, pilot];
  IF cap=File.nullCapability THEN {ReportError["No boot file found."]; RETURN};
  [dPvID, dLvID, dH] ← GetLvIDFromUser["for debugger Logical Volume: "L];
  Confirm[];
  Volume.Open[lvID];
  SELECT SetDebugger[
    debuggeeCap: cap, debuggeeFirstPage: firstPage,
    debugger: dLvID, debuggerType: GetDriveType[dH],
    debuggerOrdinal: GetDriveNumber[dH]] FROM
      success => NULL;
      nullBootFile, cantWriteBootFile, notInitialBootFile
        => ReportError["Boot file broken."L];
      cantFindStartListHeader, startListHeaderHasBadVersion
        => ReportError["Error: Debuggee built by incompatible version of StartPilot"L];
      noDebugger => ReportError["No debugger installed."L];
      ENDCASE;
  Volume.Close[lvID];
  END;

SetPvBoot: PROC =
  BEGIN
  lvID: Volume.ID = GetLvIDFromUser["Logical Volume Name: "L].lvID;
  pvID: PhysicalVolume.ID = PhysicalVolume.GetContainingPhysicalVolume[lvID];
  set: ARRAY BootFileType OF BOOLEAN ← ALL[FALSE];
  found, changed: BOOLEAN ← FALSE;
  Smash: PROC [s: STRING, t: BootFileType] =
    BEGIN
    IF GetVolumeBootFile[lvID, t].cap = File.nullCapability THEN RETURN;
    found ← TRUE;
    WriteString["Set physical volume "L]; WriteString[s];
    IF (set[t] ← Yes[" from this logical volume? "L]) THEN changed ← TRUE;
    END;
  Smash["boot file"L, pilot];
  Smash["pilot microcode"L, softMicrocode];
  Smash["germ from"L, germ];
  Smash["diagnostic microcode"L, hardMicrocode];
  IF ~found THEN {WriteLine["Logical volume has null boot files"L]; RETURN};
  IF ~changed THEN RETURN;
  Confirm[];
  Volume.Open[lvID];
  FOR t: BootFileType IN [hardMicrocode..pilot] DO
    IF set[t] THEN
      BEGIN
      cap: File.Capability;
      firstPage: File.PageNumber;
      [cap, firstPage] ← GetVolumeBootFile[lvID, t];
      SetPhysicalVolumeBootFile[cap, t, firstPage]
      END;
    ENDLOOP;
  Volume.Close[lvID];
  END;

-- Volume Init Supporting Procedures

unknown: STRING = "Unknown";

GetBootFileTypeString: PROC [type: BootFileType] RETURNS [s: STRING] =
  BEGIN
  s ← SELECT type FROM
    hardMicrocode => "diagnostic microcode",
    softMicrocode => "Pilot microcode",
    germ => "germ",
    pilot => "boot",
    ENDCASE => "?";
  END;

GetDriveFromUser: PUBLIC PROC RETURNS [h: PhysicalVolume.Handle] =
  BEGIN
  inString: STRING  ← [20];
  DO
    t: Device.Type ← Device.nullType;
    index: CARDINAL ← PhysicalVolume.nullDeviceIndex;
    GetName["Drive Name: "L, inString, echo, TRUE, inputDriveString
      ! Question => {ListDrives[]; RESUME}];
    FOR i: CARDINAL IN [0..inString.length) DO inputDriveString[i] ← inString[i] ENDLOOP;
    inputDriveString.length ← inString.length;
    IF inString[inString.length-1]=': THEN inString.length ← inString.length-1;
    DO
      [t, index] ← PhysicalVolume.GetNextDrive[t, index];
      IF t=Device.nullType AND index=PhysicalVolume.nullDeviceIndex THEN EXIT;
      h ← PhysicalVolume.GetHandle[t, index];
      IF String.EquivalentStrings[GetDriveStringName[h], inString] THEN RETURN;
      ENDLOOP;
    ReportError["Drive not found!"L];
    ENDLOOP;
  END;

GetDriveNumber: PUBLIC PROC [h: PhysicalVolume.Handle] RETURNS [CARDINAL]=
  {RETURN[PhysicalVolume.InterpretHandle[h].index]};

GetDriveStringName: PROC [h: PhysicalVolume.Handle] RETURNS [s: STRING] =
  BEGIN
  s ← SELECT GetDriveType[h] FROM
    DeviceTypes.sa800 => "Fp?",
    DeviceTypes.sa1000,
    DeviceTypes.sa4000 => "Rd?",
    DeviceTypes.cdc9730 => "Cd?",
    ENDCASE => "UnknownType?";
  s[s.length-1] ← GetDriveNumber[h]+'0;
  END;

GetDriveType: PUBLIC PROC [h: PhysicalVolume.Handle] RETURNS [Device.Type]=
  {RETURN[PhysicalVolume.InterpretHandle[h].type]};

GetLogicalVolumeName: PROC[vid: Volume.ID, s: STRING] =
  BEGIN
  s.length ← 0;
  Volume.GetLabelString[vid, s ! ANY => GOTO signal];
  EXITS signal =>
    BEGIN
    f: POINTER TO ARRAY [0..4) OF WORD = LOOPHOLE[@vid];
    i: CARDINAL;
    s.length ← 0;
    String.AppendChar[s, '[];
    FOR i IN [0..4) DO
      String.AppendLongNumber[s, LONG[f[i]], 8];
      String.AppendChar[s, ',]; String.AppendChar[s, ' ];
      ENDLOOP;
    String.AppendChar[s, ']];
    END
  END;

GetLogicalVolumeTypeName: PROC [vid: Volume.ID] RETURNS[STRING]=
  BEGIN
  t: Volume.Type;
  t ← Volume.GetType[vid ! ANY => GOTO  signal];
  RETURN[logicalVolumeTypeString[t]];
  EXITS signal => RETURN[unknown];
  END;

-- Accept string of Form LogicalVolumeName OR
--    Drive:LogicalVolumeName

inputLogicalString: STRING ← [10+maxNameLength];

GetLvIDFromUser: PROC [prompt, default: STRING ← NIL]
  RETURNS [pvID: PhysicalVolume.ID, lvID: Volume.ID, drive: PhysicalVolume.Handle] =
  BEGIN
  inString: STRING  ← [10+maxNameLength];
  fullName: STRING ← [10+maxNameLength];
  s: STRING ← [maxNameLength];
  driveString: STRING;
  matches: CARDINAL;
  ltmpID: Volume.ID;
  ptmpID: PhysicalVolume.ID;

  IF prompt=NIL THEN prompt ← "Logical Volume Name: "L;
  IF default#NIL THEN String.AppendString[inString, default];
  DO
    GetName[prompt, inString, echo, TRUE, inputLogicalString
    ! Question => {ListLogicalVolumes[]; RESUME}];
    FOR i: CARDINAL IN [0..inString.length) DO
      inputLogicalString[i] ← inString[i]; ENDLOOP;
    inputLogicalString.length ← inString.length;
    matches ← 0;
    ptmpID ← PhysicalVolume.nullID;
    DO
      ptmpID ← PhysicalVolume.GetNext[ptmpID];
      IF ptmpID=PhysicalVolume.nullID THEN EXIT;
      driveString ← GetDriveStringName[
        PhysicalVolume.GetAttributes[ptmpID].instance];
      -- Form string Drive:LogicalName
      FOR i: CARDINAL IN [0..driveString.length) DO
        fullName[i] ← driveString[i] ENDLOOP;
      fullName[driveString.length] ← ':;
      ltmpID ← Volume.nullID;
      DO
        match: BOOLEAN;
        ltmpID ← PhysicalVolume.GetNextLogicalVolume[ptmpID, ltmpID];
        IF ltmpID=Volume.nullID THEN EXIT;
        GetLogicalVolumeName[ltmpID, s];
        FOR i: CARDINAL IN [0..s.length) DO
          fullName[i+driveString.length+1] ← s[i]; ENDLOOP;
        fullName.length ← driveString.length+s.length+1;
        match ← String.EquivalentStrings[s, inString] OR
          String.EquivalentStrings[fullName, inString];
        IF match THEN {matches ← matches + 1; lvID ← ltmpID; pvID ← ptmpID}; 
        ENDLOOP;
      ENDLOOP;
    SELECT matches FROM
      0 => ReportError["Not found\r"L];
      1 => {drive ← PhysicalVolume.GetAttributes[pvID].instance; RETURN};
      ENDCASE => ReportError["Ambigous; please specify Device:LogicalName"L];
    ENDLOOP;
  END;

GetLvTypeFromUser: PUBLIC PROC [prompt: STRING] RETURNS [t: Volume.Type] =
  BEGIN
  inString: STRING  ← [30];
  ListTypes: PROC =
    BEGIN
    FOR t IN [normal..nonPilot] DO
      WriteString[logicalVolumeTypeString[t]];
      WriteString[IF t=nonPilot THEN "\r"L ELSE ", "L];
      ENDLOOP;
    END;
  DO
    GetName[prompt, inString, echo, TRUE ! Question => {ListTypes[]; RESUME}];
    FOR t IN [normal..nonPilot] DO
      IF String.EquivalentStrings[logicalVolumeTypeString[t], inString] THEN RETURN
      ENDLOOP;
    ReportError["Illegal type"L];
    ENDLOOP;
  END;

-- Accept string of Form PhysicalVolumeName OR
--   Drive:PhysicalVolumeName OR Drive
GetPvIDFromUser: PROC RETURNS [id: PhysicalVolume.ID, drive: PhysicalVolume.Handle] =
  BEGIN
  h: PhysicalVolume.Handle;
  inString: STRING  ← [10+maxNameLength];
  fullName: STRING ← [10+maxNameLength];
  s: STRING ← [maxNameLength];
  driveString: STRING;
  matches: CARDINAL;
  tmpID: PhysicalVolume.ID;

  DO
    GetName[
      "Physical Volume Name: "L, inString, echo, TRUE, inputPhysString
      ! Question => BEGIN ListPhysicalVolumes[]; RESUME; END];
    FOR i: CARDINAL IN [0..inString.length) DO inputPhysString[i] ← inString[i] ENDLOOP;
    inputPhysString.length ← inString.length;
    matches ← 0;
    tmpID ← PhysicalVolume.nullID;
    DO
      match: BOOLEAN;
      tmpID ← PhysicalVolume.GetNext[tmpID];
      IF tmpID=PhysicalVolume.nullID THEN EXIT;
      h ← PhysicalVolume.GetAttributes[tmpID, s].instance;
      driveString ← GetDriveStringName[h];
      -- Form string Drive:PhysicalName
      FOR i: CARDINAL IN [0..driveString.length) DO
        fullName[i] ← driveString[i]; ENDLOOP;
      fullName[driveString.length] ← ':;
      FOR i: CARDINAL IN [0..s.length) DO
        fullName[i+driveString.length+1] ← s[i]; ENDLOOP;
      fullName.length ← driveString.length+s.length+1;
      match ← String.EquivalentStrings[s, inString]
        OR String.EquivalentStrings[fullName, inString]
        OR String.EquivalentStrings[driveString, inString];
      IF match THEN {matches ← matches + 1; id ← tmpID; drive ← h}; 
      ENDLOOP;
    SELECT matches FROM
      0 => ReportError["Not Found"L];
      1 => RETURN;
      ENDCASE => ReportError["Ambigous; please specify Device:PhysicalName"L];
    ENDLOOP;
  END;
  
  IdentifyPilotMicrocode: PROCEDURE [sV: SubVolume, s: STRING]
    RETURNS [microcodeInstalled: BOOLEAN ← FALSE, time: System.GreenwichMeanTime] =
    BEGIN
    cap: File.Capability;
    page: File.PageNumber;
    space: Space.Handle;
    header: POINTER TO MicrocodeFile.Header;
    s.length ← 0;
    [cap, page] ← GetVolumeBootFile[sV.lvID, softMicrocode];
    IF cap=File.nullCapability THEN RETURN;
    Volume.Open[sV.lvID];
    space ← Space.Create[parent: Space.mds, size: 1];
      BEGIN ENABLE ANY => CONTINUE;
      Space.Map[space: space, window: [file: cap, base: page]];
      header ← Space.Pointer[space];
      IF header.name.length IN [1..header.name.maxlength] AND
        header.name.maxlength<=MicrocodeFile.maxNameLength AND
	header.name.length<=s.maxlength THEN
	BEGIN
	time ← MicrocodeFile.GMTFromBCPLTime[header.createDate];
	String.AppendString[s, @header.name];
	microcodeInstalled ← TRUE;
	END;
      END;
    Space.Delete[space];
    Volume.Close[sV.lvID];
    END;

  InstallUserCredentials: PUBLIC PROC =
    BEGIN
    [] ← UserCredentialsUnsafe.ChangeCredentialsState[
      IF Yes["Do you wish to prevent others from accessing your disk?"L]
      THEN name ELSE nameHint];
    currentCommands ← DESCRIPTOR [normalCommandTable];
    END;

  ParseCmFileName: PROC [host, cmFile: STRING] = {
    hostIndex: CARDINAL;
    c: CHARACTER;
    IF file.length = 0 THEN RETURN;
    FOR hostIndex ← 0, hostIndex + 1 UNTIL hostIndex = file.length DO
      SELECT c ← file[hostIndex] FROM
        '[ => LOOP;
        '] => {hostIndex ← hostIndex + 1; EXIT};
        ENDCASE => String.AppendChar[host, c];
      ENDLOOP;
    IF hostIndex = file.length THEN {host.length ← hostIndex ← 0};
    FOR i: CARDINAL IN [hostIndex..file.length) DO
      String.AppendChar[cmFile, file[i]];
      ENDLOOP};

  PromptForCredentials: PUBLIC PROC [ftpError: FTPDefs.FtpError, message: STRING] =
    BEGIN
    string: STRING;
    index: CARDINAL;
    WriteLine[message];
    [string, index] ← GetCommandString[];
    SELECT ftpError FROM
      noSuchSecondaryUser, incorrectSecondaryPassword => GetConnectNamePassword[];
      ENDCASE => GetUserNamePassword[];
    SetCommandString[string, index];
    END;
  
-- Ok, Let's do It
Run: PUBLIC PROC =
  BEGIN
  StartInteraction: PROC
    RETURNS [UserCredentialsUnsafe.GetProc, UserCredentialsUnsafe.PutProc] =
    {RETURN [ReadChar, WriteChar]};
  EndInteraction: PROC = {};
  START OthelloFTP;
  inputPhysString.length ← inputDriveString.length ←  0;
  SimpleTTYExtras.SetDebuggerEnabled[FALSE];
  Herald[];
  PrintBcdTime[];
  IF PilotSwitches.switches.n = up THEN
    BEGIN
    h: PhysicalVolume.Handle;
    t: Device.Type ← Device.nullType;
    index: CARDINAL ← PhysicalVolume.nullDeviceIndex;
    currentCommands ← DESCRIPTOR [normalCommandTable];
    WriteString["Online"L];
    [t, index] ← PhysicalVolume.GetNextDrive[t, index];
    IF t#Device.nullType AND index#PhysicalVolume.nullDeviceIndex THEN
      BEGIN
      h ← PhysicalVolume.GetHandle[t, index];
      WriteChar[' ];
      WriteString[GetDriveStringName[h]];
      [] ← PhysicalVolume.AssertPilotVolume[h !
        PhysicalVolume.Error => IF error = alreadyAsserted THEN CONTINUE;
        ANY => {WriteString[" failed!"L]; CONTINUE}];
      END;
    END
  ELSE BEGIN
    currentCommands ← DESCRIPTOR [restrictedCommandTable];
    WriteString["Booted with 'n' switch -- disk not put online, and command set restricted."L];
    END;
  NewLine[];
  UserCredentialsUnsafe.Login[
    startInteraction: StartInteraction, endInteraction: EndInteraction,
    options: [ignoreDiskEntirely: PilotSwitches.switches.n = down]];
  UserCredentialsUnsafe.GetUserCredentials[name: userName, password: userPassword];
  SimpleTTYExtras.SetDebuggerEnabled[TRUE];
  DO RunCommand[currentCommands]; ENDLOOP;
  END;

Herald: PROC = {WriteString["Cedar Othello 7.0"L]};

END.

March 19, 1980  3:47 PM	Forrest	Delete newly created temporary files when fetch fails; ome indentation changing
April 16, 1980  12:16 PM	Gobbel	Addd diagnostic microcode fetch
May 31, 1980  11:49 PM	Forrest	Shuffle around VolumeInitImplA and B
July 30, 1980  4:33 PM	Luniewski	Permit Online'ing an already online volume
September 18, 1980  12:04 PM	McJones	Don't bother to open volume to boot from
September 19, 1980  11:24 AM	Luniewski	physicalVolumeOverhead ← 2 for new physical volume format.
September 29, 1980  2:07 PM	Jose	Add SA800 format and scan, USING clauses.
October 10, 1980  3:17 PM	Forrest	Version 5.0.
January 5, 1981  10:14 PM	Forrest	Made use String for appendChar, equivilantString, appendLongNumber.  Add TemporaryBooting.invalid paramater catch.
January 31, 1981  9:19 PM	Jose	Fix format prompt.
March 1, 1981  12:59 PM	Luniewski	Version => 6.0b.
March 13, 1981  7:22 PM	Yokota	Version => 6.0c, trouple => trouble (correction), "Boot file header broken" => "Error: Debuggee built by incompatible version of StartPilot".
March 25, 1981  8:28 PM	Fay	Version => 6.0.
April 14, 1981  11:38 AM	Bruce	@ added.
11-Jun-81 10:53:01	Taft	Remove all machine- and device-dependent code to separate module OthelloDeviceImplD*.mesa
24-Jan-82 13:57:32	Taft	Add physical volume scavenger (per Chuck Fay); catch CredentialError from Retrieve and prompt for new credentials.
22-Feb-82 14:02:44	Taft	Add RollBack command.
 4-Jun-82  8:21:21	Taft	Remove Guest credentials; export PromptForCredentials; PromptForCredentials saves and restores command string
 4-Jun-82 17:41:51	Taft	Add List Remote Files
20-Jun-82 14:39:41	Taft	Describe command displays names and dates of microcode files
September 9, 1982 5:46 pm  Taft  Mods for new Cedar Login
November 10, 1982 5:45 pm  Taft  Allow Initial Microcode Fetch in restricted command set; Rollback finds first volume of type normal with an installed checkpoint