-- HelloVIIA.mesa from VolumeInitImplA.mesa edited by:
-- Masinter 21-Aug-84 19:51:52 Make Describe ignore nonPilot volumes
-- Lichtenberg 14-Jul-84 16:16:45 some Lisp stuff
--   Johnsson	 2-Dec-83 18:21:20
--   Sweet	17-Oct-83 14:53:35

DIRECTORY
  Device USING [PilotDisk, Type],
  DeviceTypes USING [
    q2000, q2010, q2020, q2030, q2040, q2080, sa1000, sa1004, sa4000,
    sa4008, t300, t80],
  Environment USING [wordsPerPage],
  File USING [
    Delete, File, GetAttributes, ID, nullFile, PageNumber, Type, Unknown, GetSize, PageCount],
  Heap USING [systemZone],
  Inline USING [BITROTATE],
  OthelloDefs USING [
    AbortingCommand, CloseFetch, CommandProcessor, Confirm, GetName,
    IndexTooLarge, LeaderPage, leaderPages, lpVersion, MyNameIs, NewLine,
    PackedTimeFromString, Question, ReadNumber, RegisterCommandProc,
    SetCommandString, WriteChar, WriteFixedWidthNumber, WriteLine,
    WriteLongNumber, WriteOctal, WriteString, Yes],
  OthelloOps USING [
    BadSwitches, BootFileType, DecodeSwitches, DeleteTempFiles, GetDriveSize,
    GetNextSubVolume, GetPhysicalVolumeBootFile, GetSwitches, GetVolumeBootFile,
    nullSubVolume, SetDebugger, SetDebuggerSuccess, SetExpirationDate,
    SetExpirationDateSuccess, SetGetSwitchesSuccess, SetPhysicalVolumeBootFile,
    SetSwitches, SubVolume, VoidPhysicalVolumeBootFile, VoidVolumeBootFile],
  PhysicalVolume USING [
    AssertPilotVolume, DamageStatus, Error, GetAttributes, GetHandle, GetNext,
    GetNextBadPage, GetNextDrive, GetNextLogicalVolume, Handle, ID,
    InterpretHandle, MarkPageBad, maxNameLength, noProblems, nullBadPage,
    nullDeviceIndex, nullID, Offline, PageNumber, RepairType, Scavenge,
    ScavengerStatus],
  Process USING [MsecToTicks],
  Runtime USING [IsBound],
  Scavenger USING [
    BootFileType, Error, FileEntry, Header, Problem, RepairType, Scavenge],
  Space USING [CopyIn, Map, ScratchMap, Unmap],
  SpecialVolume USING [OpenVolume],
  String USING [
    AppendCharAndGrow, AppendLongNumber, AppendString, CopyToNewString, Equivalent,
    Length, Replace],
  System USING [
    defaultSwitches, GetLocalTimeParameters, gmtEpoch, GreenwichMeanTime,
    PowerOff, Switches],
  TemporaryBooting USING [BootButton, BootFromVolume],
  Volume USING [
    Close, Erase, GetAttributes, GetLabelString, GetType, ID,
    NeedsScavenging, NotOnline, nullID, Open, Type],
  VolumeVersion USING [Examine];

VolumeInitImplA: PROGRAM
  IMPORTS
    File, Heap, Inline, OthelloDefs, OthelloOps, PhysicalVolume, Process, Runtime,
    Scavenger, Space, SpecialVolume, System, String, TemporaryBooting, Volume,
    VolumeVersion
  EXPORTS OthelloDefs 
  SHARES File =
  BEGIN OPEN OthelloOps, OthelloDefs;

  commandProcessor: CommandProcessor ← [CommonCommands];

  CommonCommands: PROC [index: CARDINAL] = {
    SELECT index FROM
      0 => BootBoot[];
      1 => DeleteBootFiles[];
      2 => DeleteTempFilesUser[];
      3 => DescribePhysicalVolumes[];
      4 => Erase[];
      5 => ListBadPages[];
      6 => ListBootFiles[];
      7 => ListDrives[];
      8 => ListLogicalVolumes[];
      9 => ListPhysicalVolumes[];
     10 => MakeBad[];
     11 => Offline[];
     12 => Online[];
     13 => PowerOff[];
     14 => PVScavenge[];
     15 => Quit[];
     16 => Scavenge[];
     17 => SetBootFileSwitches[];
     18 => SetDebuggerUser[];
     19 => SetExpirationDateUser[];
     20 => SetPvBoot[];
     21 => WizardMode[];
    ENDCASE => IndexTooLarge};

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

  inputDriveString:      LONG STRING ← NIL;
  inputLogicalString:    LONG STRING ← NIL;
  debuggerLogicalString: LONG STRING ← NIL;
  inputPhysString:       LONG STRING ← NIL;
  switches:              LONG STRING ← NIL;
  lvTypeString:          LONG STRING ← NIL;
  expirationString:      LONG STRING ← NIL;

  maxNameLength: CARDINAL = PhysicalVolume.maxNameLength;

  BootBoot: PROC =
    BEGIN
    lvID: Volume.ID;
    ts:   System.Switches;
    lispFile: File.File;
    size: File.PageCount;

    MyNameIs[
      myNameIs: "Boot"L, myHelpIs: "Boot From Logical Volume"L];
    lvID ← GetLvIDFromUser[].lvID;
    lispFile ← GetVolumeBootFile[lvID, BootFileType.hardMicrocode].file;
         IF lispFile # File.nullFile THEN {
	  SpecialVolume.OpenVolume[lispFile.volumeID,read];
	  size ← File.GetSize[lispFile];
	  Volume.Close[lispFile.volumeID];
          IF size > 1000 THEN {
	    IF Yes["Boot Lisp from this volume? "L] THEN {
		Volume.Open[lispFile.volumeID];
		SetPhysicalVolumeBootFile[lispFile,
			BootFileType.hardMicrocode,leaderPages];
            IF Runtime.IsBound[LOOPHOLE[CloseFetch]] THEN CloseFetch[];
	    Volume.Close[lispFile.volumeID];
	    TemporaryBooting.BootButton[]; 
   	    }
	  };
	  Volume.Close[lispFile.volumeID];
	};
    GetSetBootFileSwitches[get, lvID
    ! Volume.NeedsScavenging, File.Unknown => {
        WriteLine["(can't get default switches)"L];
        CONTINUE};
      AbortingCommand => {
        WriteLine[reason];
	WriteLine["(can't get default switches)"L];
        CONTINUE}];
    DO
      GetName["switches: "L, @switches, echo, TRUE
      ! Question => {
        WriteLine[
          "See Pilot Users Handbook for list of valid switches."L];
        RESUME}];
      ts ← DecodeSwitches[switches
      ! BadSwitches => {WriteLine["bad switches"L]; LOOP}];
      EXIT;
      ENDLOOP;
    IF Runtime.IsBound[LOOPHOLE[CloseFetch]] THEN CloseFetch[];
    TemporaryBooting.BootFromVolume[lvID, ts];
    END;

  DeleteBootFiles: PROC =
    BEGIN
    lvID: Volume.ID;
    pvID: PhysicalVolume.ID;
    IF ~CanOthello[] THEN RETURN;
    MyNameIs[
      myNameIs: "Delete Boot Files"L,
      myHelpIs: "Delete all boot files from volume"L];
    [pvID: pvID, lvID: lvID] ← GetLvIDFromUser[];
    FOR t: BootFileType IN [hardMicrocode..pilot] DO
      file: File.File = GetVolumeBootFile[lvID, t].file;
      IF file = File.nullFile THEN LOOP;
      Volume.Open[file.volumeID];
      BEGIN ENABLE File.Unknown => CONTINUE;
      File.Delete[file];
      END;
      VoidVolumeBootFile[lvID, t];
      IF GetPhysicalVolumeBootFile[pvID, t].file = file THEN
        VoidPhysicalVolumeBootFile[pvID, t];
      Volume.Close[lvID];
      ENDLOOP;
    END;

  DeleteTempFilesUser: PROC = {
    IF ~CanOthello[] THEN RETURN;
    MyNameIs[
      myNameIs: "Delete Temporary Files"L,
      myHelpIs: "Delete Temporary Files"L];
    DeleteTempFiles[GetLvIDFromUser[].lvID]};

  DescribePhysicalVolumes: PROC =
    BEGIN
    pvID:    PhysicalVolume.ID ← PhysicalVolume.nullID;
    pvFound: BOOLEAN           ← FALSE;
    nonPID:  Volume.Type	← nonPilot;

    MyNameIs[
      myNameIs: "Describe Physical Volumes"L,
      myHelpIs: "Describe OnLine disk(s) and Volumes"L];
    DO
      h:       PhysicalVolume.Handle;
      s:       STRING    ← [maxNameLength];
      sV:      SubVolume ← nullSubVolume;
      sVFound: BOOLEAN   ← FALSE;
      IF (pvID ← PhysicalVolume.GetNext[pvID]) = PhysicalVolume.nullID THEN EXIT;
      pvFound ← TRUE;
      h ← PhysicalVolume.GetAttributes[pvID, s].instance;
      WriteString["Physical Volume "L];
      WriteString[s]; WriteString[" on drive "L];
      WriteString[GetDriveStringName[h]];
      WriteString[" ("L];
      WriteString[
        SELECT GetDriveType[h] FROM
          DeviceTypes.sa1004, DeviceTypes.sa1000  => "Shugart 1000"L,
          DeviceTypes.sa4000, DeviceTypes.sa4008  => "Shugart 4000"L,
          DeviceTypes.q2000,  DeviceTypes.q2010, DeviceTypes.q2020,
          DeviceTypes.q2030,  DeviceTypes.q2040,  DeviceTypes.q2080    => "Quantum 2000"L,
          DeviceTypes.t80  =>   "T80"L,
          DeviceTypes.t300 =>   "T300"L,
          ENDCASE =>            "unknown type"L];
      DO
        needsScavenging: BOOLEAN ← FALSE;
	nonP: BOOLEAN ← FALSE;
	freePages, volumeSize: LONG CARDINAL;
	sV ← GetNextSubVolume[pvID, sV];
        IF sV = nullSubVolume THEN EXIT;
        IF ~sVFound THEN WriteLine[") contains:"L];
        sVFound ← TRUE;
        WriteString["Volume "L];
        [volumeSize: volumeSize, freePageCount: freePages] ← Volume.GetAttributes[sV.lvID
	  ! Volume.NeedsScavenging => {
	    needsScavenging ← TRUE; 
	    volumeSize ← 0; -- don't really know
	    CONTINUE }];
	IF volumeSize # sV.subVolumeSize AND volumeSize # 0 THEN
	  WriteString["piece "L];
        GetLogicalVolumeName[sV.lvID, s];
        WriteString[s]; WriteString[" (type =  "L];
        WriteString[GetLogicalVolumeTypeName[sV.lvID]];  WriteString[") "L];
        nonP ← GetLogicalVolumeType[sV.lvID]=nonPID;
	IF ~nonP AND volumeSize = sV.subVolumeSize THEN {
	  WriteLongNumber[freePages]; WriteString[" of "L];
	  WriteLongNumber[volumeSize]; WriteString[" pages free"L]}
        ELSE {WriteLongNumber[sV.subVolumeSize]; WriteString[" pages"L]};
	IF needsScavenging THEN WriteString["    *** Needs Scavenging ***"L];
	NewLine[];
	WriteString["   starting at physical address "L];
        WriteLongNumber[sV.firstPVPageNumber];
        NewLine[];
	IF ~nonP AND ~needsScavenging THEN ShowBootFiles[pvID, sV.lvID];
        ENDLOOP;
      IF ~sVFound THEN WriteLine[") no subvolumes"L];
      ENDLOOP;
    IF ~pvFound THEN WriteLine["No physical Volumes found"L];
    END;

  Erase: PROC = {
    lvID: Volume.ID;
    pvID: PhysicalVolume.ID;
    MyNameIs[
      myNameIs: "Erase"L, myHelpIs: "Erase logical volume"L];
    [pvID: pvID, lvID: lvID] ← GetLvIDFromUser[];
    Confirm[];
    Volume.Close[lvID];
    SELECT VolumeVersion.Examine[lvID] FROM
      otherVersion =>
        IF Yes["That volume is not in the current format. Do you want to convert it? "L]
          THEN Confirm[twice]
        ELSE RETURN;
      ENDCASE;
    WriteString["Erasing..."L];
    Volume.Erase[lvID];
    FOR t: BootFileType IN [hardMicrocode..pilot] DO
      IF GetPhysicalVolumeBootFile[pvID, t].file.volumeID = lvID THEN
        VoidPhysicalVolumeBootFile[pvID, t];
      ENDLOOP;
    WriteLine["complete"L]};

  ListBadPages: PROC =
    BEGIN
    id: PhysicalVolume.ID;
    page: PhysicalVolume.PageNumber ← PhysicalVolume.nullBadPage;
    badSpots: BOOLEAN ← FALSE;
    col: CARDINAL ← 0;
    MyNameIs[
      myNameIs: "List Bad Pages"L,
      myHelpIs: "List known bad pages on volume"L];
    id ← GetPvIDFromUser[].id;
    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;

  ListBootFiles: PROC =
    BEGIN
    lvID: Volume.ID;
    pvID: PhysicalVolume.ID;
    MyNameIs[
      myNameIs: "List Boot Files"L,
      myHelpIs: "List boot files currently on logical volume"L];
    [pvID: pvID, lvID: lvID] ← GetLvIDFromUser[];
    ShowBootFiles[pvID, lvID];
    END;

  ListDrives: PROC = {
    index: CARDINAL ← PhysicalVolume.nullDeviceIndex;
    first: BOOLEAN  ← TRUE;
    MyNameIs[myNameIs: "List Drives"L, myHelpIs: "List disk drives"L];
    DO
      index ← PhysicalVolume.GetNextDrive[index];
      IF index = PhysicalVolume.nullDeviceIndex THEN EXIT;
      IF ~first THEN WriteString[", "L];
      first ← FALSE;
      WriteString[GetDriveStringName[PhysicalVolume.GetHandle[index]]];
      ENDLOOP;
    NewLine[]};

  ListLogicalVolumes: PROC =
    BEGIN
    first: BOOLEAN ← TRUE;
    pID: PhysicalVolume.ID ← PhysicalVolume.nullID;
    MyNameIs[
      myNameIs: "List Logical Volumes"L, myHelpIs: "List logical volumes"L];
    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;
    MyNameIs[
      myNameIs: "List Physical Volumes"L,
      myHelpIs: "List physical volumes"L];
    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;

  MakeBad: PROC =
    BEGIN
    h:    PhysicalVolume.Handle;
    id:   PhysicalVolume.ID;
    page: PhysicalVolume.PageNumber;
    IF ~CanOthello[] THEN RETURN;
    MyNameIs[
      myNameIs: "Make Page Bad"L,
      myHelpIs: "Enter page into bad page table"L];
    [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 = {
    MyNameIs[
      myNameIs: "Offline"L, myHelpIs: "Bring physical volume offline"L];
    PhysicalVolume.Offline[GetPvIDFromUser[].id]};

  Online: PROC = {
    pvID: PhysicalVolume.ID;
    MyNameIs[
      myNameIs: "Online"L, myHelpIs: "Bring drive online"L];
    pvID ← PhysicalVolume.AssertPilotVolume[GetDriveFromUser[] !
      PhysicalVolume.Error => IF error = alreadyAsserted THEN CONTINUE];
    -- (maybe) update time parameters on disk
    [] ← System.GetLocalTimeParameters[pvID]};

  PowerOff: PROC = {
    MyNameIs[
      myNameIs: "Power Off", myHelpIs: "Ready system for power off"L];
    Confirm[]; CloseFetch[]; System.PowerOff[]};

  PVScavenge: PROC =
    BEGIN OPEN PV: PhysicalVolume;
    convert: BOOLEAN ← FALSE;
    s:      PV.ScavengerStatus;
    h:      PV.Handle;
    repair: PV.RepairType;
    p:      PV.ID ← PV.nullID;
    PrintDamageStatus: PROC [s: STRING, d: PV.DamageStatus] = {
      IF d=okay THEN RETURN;
      WriteString[s];
      WriteLine[IF d=damaged THEN " damaged"L ELSE " lost"L]};
 
    IF ~CanOthello[] THEN RETURN;
    MyNameIs[
      myNameIs: "Physical Volume Scavenge"L,
      myHelpIs: "Attempt to scavenge physical volume"L];
    h ← GetDriveFromUser[];
    repair ←
      IF ~Yes["Repair? "L] THEN checkOnly
      ELSE IF Yes["Risky repair? "L] THEN riskyRepair ELSE safeRepair;
    Confirm[];
    DO
      IF (p←PhysicalVolume.GetNext[p]) = PhysicalVolume.nullID THEN EXIT;
      IF h = PhysicalVolume.GetAttributes[p].instance THEN {
        PhysicalVolume.Offline[p]; EXIT};
      ENDLOOP;
    BEGIN ENABLE PhysicalVolume.Error =>
      IF error = needsConversion AND ~convert THEN
        IF (convert ← Yes["That volume is not in the current format. Do you want to convert it? "L])
	  THEN {Confirm[]; RETRY}
	ELSE AbortingCommand["Volume cannot be scavenged"L];
      WriteString["Scavenging..."L];
      s ← PV.Scavenge[h, repair, convert];
      WriteLine["Complete"L];
      END; -- ENABLE
    IF s = PV.noProblems THEN {WriteLine["No problems detected"L]; RETURN};
    WriteString["Damage detected:  "L];
    IF s.internalStructures # okay THEN {
      WriteString["Internal structures "L];
      WriteLine[
        IF s.internalStructures=damaged THEN
          IF repair=safeRepair THEN "damaged -- contact hardware support for risky repair"L
	  ELSE "damaged"L
	ELSE "repaired"L]};
    PrintDamageStatus["Bad page list"L,        s.badPageList];
    PrintDamageStatus["Boot file"L,            s.bootFile];
    PrintDamageStatus["Germ"L,                 s.germ];
    PrintDamageStatus["Pilot microcode"L,      s.softMicrocode];
    PrintDamageStatus["Sysout or diagnostic microcode"L,		 s.hardMicrocode];
    END;

  Quit: PROC = {	
    IF ~CanOthello[] THEN RETURN;
    MyNameIs[
      myNameIs: "Quit"L, myHelpIs: "Push the boot button"L];
    Confirm[]; CloseFetch[]; TemporaryBooting.BootButton[]};

  Scavenge: PROC = {
    convert: BOOLEAN ← FALSE;
    lvID:    Volume.ID;
    logFile: File.File;
    logPage: File.PageNumber ← 0;
    logWd:   CARDINAL ← Environment.wordsPerPage;
    buffer: LONG POINTER TO ARRAY [0..Environment.wordsPerPage) OF UNSPECIFIED ← NIL;
    
    GetWds: PROC [p: POINTER, c: CARDINAL] = {
      WHILE c#0 DO
        IF logWd=Environment.wordsPerPage THEN {
	  [] ← Space.CopyIn[buffer, [logFile, logPage, 1]];
	  logPage ← logPage + 1; logWd ← 0};
	p↑ ← buffer[logWd];
	p ← p+1; c ← c-1; logWd ← logWd+1;
	ENDLOOP};
    DisplayScavLog: PROC = {
      fileCount: LONG CARDINAL; problems: BOOLEAN ← FALSE;
      BEGIN
      hd: Scavenger.Header;
      GetWds[@hd, SIZE[Scavenger.Header]];
      WriteString["volume"L]; IF ~hd.repaired THEN WriteString[" not"L];
      WriteString[" repaired, log file"L];
      IF hd.incomplete THEN WriteString[" not"L]; WriteLine[" complete "L];
      WriteLongNumber[fileCount ← hd.numberOfFiles];
      WriteLine[" files on volume"L];
      END;
      WHILE fileCount#0 DO
	OpenID: TYPE = ARRAY [0..SIZE[File.ID]) OF CARDINAL;
	fe:     Scavenger.FileEntry;
	GetWds[@fe, SIZE[Scavenger.FileEntry]];
	THROUGH [0..fe.numberOfProblems) DO
	  fp: Scavenger.Problem;
	  GetWds[@fp, SIZE[Scavenger.Problem]];
	  WriteChar['[];
	  FOR i: CARDINAL IN [0..SIZE[File.ID]-1) DO 
	    WriteOctal[LOOPHOLE[fe.file, OpenID][i]]; WriteString[", "L] ENDLOOP;
	  WriteOctal[LOOPHOLE[fe.file, OpenID][SIZE[File.ID]-1]];
	  WriteString["] type = "L];
	  BEGIN ENABLE File.Unknown => GOTO noType;
	  f: File.Type = File.GetAttributes[[fe.file, lvID]].type;
	  WriteLongNumber[LONG[LOOPHOLE[f, CARDINAL]]];
	  EXITS noType => WriteString["unknown"L];
	  END;
	  WriteString["; "L];
	  WITH fp SELECT FROM
	    unreadable => {
	      WriteString["unreadable"L];
	      WriteString[" pages ["L]; WriteLongNumber[first];
	      WriteString[".."L]; WriteLongNumber[first+count]; WriteLine[")"L]};
	    missing => {
	      WriteString["missing"L];
	      WriteString[" pages ["L]; WriteLongNumber[first];
	      WriteString[".."L]; WriteLongNumber[first+count]; WriteLine[")"L]};
	    duplicate => { 
	      WriteString["duplicate"L]; WriteLine[" page found"L]};
	    orphan => { 
	      WriteString["orphan"L]; WriteLine[" page found"L]};
	    ENDCASE => WriteLine["unknown problem"L];
	  problems ← TRUE;
	  ENDLOOP;
	fileCount ← fileCount-1;
	ENDLOOP;
      WriteLine[IF ~problems THEN "No problems found"L ELSE NIL]};
    IF ~CanOthello[] THEN RETURN;
    MyNameIs[
      myNameIs: "Scavenge"L, myHelpIs: "Attempt to scavenge logical volume"L];
    lvID ← GetLvIDFromUser[].lvID;
    Confirm[];
    Volume.Close[lvID ! ANY => CONTINUE];
    BEGIN ENABLE Scavenger.Error =>
      IF error = needsConversion AND ~convert THEN
        IF (convert ← Yes["That volume is not in the current format. Do you want to convert it? "L])
	  THEN {Confirm[twice]; RETRY}
	ELSE AbortingCommand["Volume cannot be scavenged"L];
      WriteString["Scavenging..."L];
      logFile ← Scavenger.Scavenge[lvID, lvID, safeRepair, convert];
      WriteLine["Complete"L];
      END; -- ENABLE
    SpecialVolume.OpenVolume[lvID, read];
    buffer ← Space.ScratchMap[1];
    DisplayScavLog[
      ! UNWIND => {[] ← Space.Unmap[buffer]; Volume.Close[lvID]}];
    [] ← Space.Unmap[buffer]; Volume.Close[lvID]};

    SetBootFileSwitches: PROC =
    BEGIN
    ts:   System.Switches;
    lvID: Volume.ID;

    IF ~CanOthello[] THEN RETURN;
    MyNameIs[
      myNameIs: "Set Boot File Default Switches"L,
      myHelpIs: "Set default switches for boot file on volume"L];
    lvID ← GetLvIDFromUser[].lvID;
    GetSetBootFileSwitches[get, lvID];  -- volume.needsScav (caught higher up)
    DO
      GetName["switches: "L, @switches, echo, TRUE
      ! Question => {WriteLine[
        "See Pilot Users Handbook for list of valid switches."L];
        RESUME}];
      ts ← DecodeSwitches[switches
      ! BadSwitches => {WriteLine["bad switches"L]; LOOP}];
      EXIT;
      ENDLOOP;
    Confirm[];
    GetSetBootFileSwitches[set, lvID, ts];
    END;

  SetDebuggerUser: PROC =
    BEGIN
    file:       File.File;
    firstPage: File.PageNumber;
    lvID:      Volume.ID;
    dLvID:     Volume.ID;
    dH:        PhysicalVolume.Handle;
    dT:        Device.Type;
    dO:        CARDINAL;
    outcome:   SetDebuggerSuccess;
    
    IF ~CanOthello[] THEN RETURN;
    MyNameIs[
      myNameIs: "Set Debugger Pointers"L,
      myHelpIs: "Set up pointers to debugger for volume"L];
    lvID ← GetLvIDFromUser["for debuggee Logical Volume: "L].lvID;
    [file, firstPage] ← GetVolumeBootFile[lvID, pilot];
    IF file = File.nullFile THEN
      AbortingCommand["No boot file found."L];
    [, dLvID, dH] ← GetLvIDFromUser["for debugger Logical Volume: "L, TRUE];
    IF dLvID=Volume.nullID THEN WriteLine["(Clear existing pointers)"L]
    ELSE {dT ← GetDriveType[dH]; dO ← GetDriveNumber[dH]};
    Confirm[];
    Volume.Open[lvID];
    outcome ← SetDebugger[
      debuggeeFile: file, debuggeeFirstPage: firstPage, debugger: dLvID,
      debuggerType: dT, debuggerOrdinal: dO];
    Volume.Close[lvID];
    WriteSetDebuggerSuccess[outcome];
    END;

  SetExpirationDateUser: PROC =
    BEGIN
    file:       File.File;
    firstPage: File.PageNumber;
    time:      System.GreenwichMeanTime;
    lvID:      Volume.ID;
    outcome:   SetExpirationDateSuccess;

    IF ~CanOthello[] THEN RETURN;
    MyNameIs[
      myNameIs: "Set Hardware Clock Upper Limit"L,
      myHelpIs: "Set last believable hardware clock date for boot file on logical volume"L];
    lvID ← GetLvIDFromUser[].lvID;
    [file, firstPage] ← GetVolumeBootFile[lvID, pilot];
    IF file = File.nullFile THEN
      AbortingCommand["No boot file found."L];
    DO
      GetName["Date (DD-MMM-YY): "L, @expirationString];
      IF expirationString.length=0 THEN {
        WriteLine["(setting no upper limit on hardware clock)"L];
	time ← System.gmtEpoch;
	EXIT}
      ELSE {
        time ← PackedTimeFromString[s: expirationString, justDate: TRUE];
        IF time=System.gmtEpoch THEN WriteLine["invalid date"L]
	ELSE EXIT};
      ENDLOOP;
    Confirm[];
    Volume.Open[lvID];
    outcome ← SetExpirationDate[file, firstPage, time]; 
    Volume.Close[lvID];
    WriteSetDebuggerSuccess[outcome];
    END;

  SetPvBoot: PROC =
    BEGIN
    lvID: Volume.ID;
    set: ARRAY BootFileType[hardMicrocode..pilot] OF BOOLEAN ← ALL[FALSE];
    found, changed: BOOLEAN ← FALSE;
    size: File.PageCount;
    file: File.File;
    Smash: PROC [s: STRING, t: BootFileType] = {
      file ← GetVolumeBootFile[lvID, t].file;
      IF file = File.nullFile THEN RETURN;
      SpecialVolume.OpenVolume[lvID, read];
      size ← File.GetSize[file];
      Volume.Close[lvID];
      found ← TRUE;
      WriteString["Set physical volume "L];
      IF size > 1000 AND t = BootFileType.hardMicrocode THEN WriteString["Lisp sysout"L]
      ELSE WriteString[s];
      IF (set[t] ← Yes[" from this logical volume? "L]) THEN changed ← TRUE};

    MyNameIs[
      myNameIs: "Set Physical Volume Boot Files"L,
      myHelpIs: "Set Physical Volume Boot Files"L];
    lvID ← GetLvIDFromUser[].lvID;
    Smash["boot file"L, pilot];
    Smash["pilot microcode"L, softMicrocode];
    Smash["germ file"L, germ];
    Smash["diagnostic microcode"L, hardMicrocode];
    IF ~found THEN AbortingCommand["Logical volume has null boot files"L];
    IF ~changed THEN RETURN;
    Confirm[];
    SpecialVolume.OpenVolume[lvID, read];
    FOR t: BootFileType IN [hardMicrocode..pilot] DO
      IF set[t] THEN {
        file: File.File;
        firstPage: File.PageNumber;
        [file, firstPage] ← GetVolumeBootFile[lvID, t];
        SetPhysicalVolumeBootFile[file, t, firstPage]};
      ENDLOOP;
    Volume.Close[lvID];
    END;


  ShowBootFiles: PROC [pv: PhysicalVolume.ID, lv: Volume.ID] = {
    bootNames: ARRAY BootFileType[hardMicrocode..pilot] OF STRING ← [
      hardMicrocode: "Diagnostic microcode"L,
      softMicrocode: "Pilot microcode"L,
      germ: "Germ"L,
      pilot: "Pilot bootfile"L];
    SpecialVolume.OpenVolume[lv, read ! Volume.NeedsScavenging => GOTO scavenge];
    FOR t: BootFileType IN BootFileType[hardMicrocode..pilot] DO
      ENABLE UNWIND => Volume.Close[lv];
      file: File.File;
      firstPage: File.PageNumber;
      [file: file, firstPage: firstPage] ← GetVolumeBootFile[lv, t];
      IF file = File.nullFile THEN LOOP;
      WriteString["   "L];
      IF GetPhysicalVolumeBootFile[pv, t].file = file THEN
        WriteString["(PV) "L];
      IF File.GetSize[file] > 1000 AND t = hardMicrocode THEN 
      	   WriteString["Lisp sysout"L]  
      ELSE WriteString[bootNames[t]];
      WriteString[": "L];
      IF firstPage = OthelloDefs.leaderPages THEN ShowLeaderNote[file]
      ELSE WriteLine["(no information available)"L];
      ENDLOOP;
    Volume.Close[lv];
    EXITS
    scavenge => NULL};

  ShowLeaderNote: PROC [file: File.File] = {
    lp: LONG POINTER TO OthelloDefs.LeaderPage;
    lp ← Space.Map[window:[file, 0, OthelloDefs.leaderPages], access: readOnly].pointer;
    IF lp.version = OthelloDefs.lpVersion THEN
      FOR i: CARDINAL IN [0..lp.length) DO WriteChar[lp.note[i]] ENDLOOP
    ELSE WriteString["(no information available)"L];
    NewLine[];
    [] ← Space.Unmap[lp]};

  WizardMode: PROC = {
    password: LONG STRING ← NIL;
    IF wizardMode THEN RETURN;
    MyNameIs[
      myNameIs: "Wizard Mode"L, myHelpIs: "Enable special commands"L];
    GetName["Password: "L, @password, stars];
    IF Hash[password] = wizardPassword THEN {wizardMode ← TRUE; WriteLine[" ok"L]}
    ELSE WriteLine[" incorrect password"L]};

  -- Wizard Supporting Procedures
  wizardMode: BOOLEAN ← Process.MsecToTicks[2000] # 39; --FALSE for DLion only.
  wizardPassword: CARDINAL = 17777; --  was 18939

  Hash: PROCEDURE [s: LONG STRING] RETURNS [h: CARDINAL] =
    BEGIN
    h ← 17777;
    FOR i: CARDINAL IN [0..String.Length[s]) DO
      c: CHARACTER;
      IF (c ← s[i]) IN ['A..'Z] THEN c ← c + ('a-'A);
      h ← Inline.BITROTATE[h, 1] + (c-0C);
      ENDLOOP;
    END;
  
  Wizard: PUBLIC PROC RETURNS [BOOLEAN] = {RETURN[wizardMode]};
  CanOthello: PUBLIC PROC RETURNS [BOOLEAN] = {RETURN[TRUE]}; -- may want to turn off Othello/non-Hello commands at some time

  -- Volume Init Supporting Procedures
  unknown: STRING = "Unknown";

  GetSetBootFileSwitches: PROC [
    getSet: {get, set}, lvID: Volume.ID,
    ts: System.Switches ← System.defaultSwitches] = {
    outcome:   SetGetSwitchesSuccess;
    file:       File.File;
    firstPage: File.PageNumber;

    Heap.systemZone.FREE[@switches];
    IF getSet=get THEN SpecialVolume.OpenVolume[lvID, read]
    ELSE Volume.Open[lvID];
    [file, firstPage] ← GetVolumeBootFile[lvID, pilot];
    IF file = File.nullFile THEN AbortingCommand["No boot file found."L];
    IF getSet=get THEN [outcome, ts] ← GetSwitches[file, firstPage]
    ELSE outcome ← SetSwitches[file, firstPage, ts];
    Volume.Close[lvID];
    WriteSetDebuggerSuccess[outcome];
    IF getSet=set THEN RETURN;
    FOR c: CHARACTER IN [0C..377C] DO 
      IF ts[c]=up THEN LOOP;
      SELECT c FROM 
        '~, '-, '\\, '', '" => NULL;
	IN ['a..'z], IN ['A..'Z], IN (' ..'?] => {
	  String.AppendCharAndGrow[@switches, c, Heap.systemZone]; LOOP};
	ENDCASE => NULL;
      String.AppendCharAndGrow[@switches, '\\, Heap.systemZone];
      String.AppendCharAndGrow[@switches, (c-0C)/64 + '0, Heap.systemZone];
      String.AppendCharAndGrow[@switches, ((c-0C)/8 MOD 8) + '0, Heap.systemZone];
      String.AppendCharAndGrow[@switches, ((c-0C) MOD 8) + '0, Heap.systemZone];
      ENDLOOP};
      
  WriteSetDebuggerSuccess: PROC [outcome: SetDebuggerSuccess] = {
    SELECT outcome FROM
      success => NULL;
      nullBootFile, cantWriteBootFile, notInitialBootFile =>
        AbortingCommand["Boot file broken."L];
      cantFindStartListHeader, startListHeaderHasBadVersion =>
        AbortingCommand["file built by incompatible version of StartPilot"L];
      noDebugger => AbortingCommand["No debugger installed."L];
      ENDCASE => ERROR};

  GetDriveFromUser: PUBLIC PROC RETURNS [h: PhysicalVolume.Handle] = {
    DO
      index: CARDINAL ← PhysicalVolume.nullDeviceIndex;
      GetName[
        "Drive Name: "L, @inputDriveString, echo, TRUE
        ! Question => {ListDrives[]; RESUME}];
      IF inputDriveString[inputDriveString.length - 1] = ': THEN
        inputDriveString.length ← inputDriveString.length - 1;
      DO
        index ← PhysicalVolume.GetNextDrive[index];
        IF index = PhysicalVolume.nullDeviceIndex THEN EXIT;
        h ← PhysicalVolume.GetHandle[index];
        IF String.Equivalent[GetDriveStringName[h], inputDriveString] THEN RETURN;
        ENDLOOP;
      WriteLine["Drive not found!"L]
      ENDLOOP};

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

  GetDriveStringName: PROC [h: PhysicalVolume.Handle] RETURNS [s: STRING] =
    BEGIN
    s ← SELECT TRUE FROM
      -- damn compiler won't allow t IN Device.PilotDisk
      LOOPHOLE[GetDriveType[h], CARDINAL] IN Device.PilotDisk
              => "Rd?",
      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] = {
    s.length ← 0;
    Volume.GetLabelString[vid, s ! Volume.NeedsScavenging => GOTO bad];
    EXITS bad => {
      IDRep: TYPE = RECORD [p: ARRAY [0..3) OF CARDINAL, n: LONG CARDINAL];
      String.AppendString[s, "NeedsScavenging"L];
      String.AppendLongNumber[s, LOOPHOLE[vid, IDRep].n, 8]}};

  GetLogicalVolumeTypeName: PROC [vid: Volume.ID] RETURNS [STRING] = {
    RETURN[logicalVolumeTypeString[Volume.GetType[vid ! ANY => GOTO signal]]];
    EXITS signal => RETURN[unknown]};
    
  GetLogicalVolumeType: PROC [vid: Volume.ID] RETURNS [Volume.Type] = {
    RETURN[Volume.GetType[vid ! ANY => GOTO signal]];
    EXITS signal => RETURN[nonPilot]};

  -- Accept string of Form LogicalVolumeName OR
  --    Drive:LogicalVolumeName
  GetLvIDFromUser: PUBLIC PROC [
    prompt: STRING ← NIL,
    calledFromSetDebuggerPtrs: BOOLEAN ← FALSE]
    RETURNS [
      pvID: PhysicalVolume.ID, lvID: Volume.ID,
      drive: PhysicalVolume.Handle] =
    BEGIN
    IF prompt = NIL THEN prompt ← "Logical Volume Name: "L;
    DO
     ptmpID:      PhysicalVolume.ID ← PhysicalVolume.nullID;
     inputString: LONG STRING;
     matches:     CARDINAL ← 0;
     GetName[
        prompt: prompt, how: echo, signalQuestion: TRUE,
	dest: IF calledFromSetDebuggerPtrs THEN @debuggerLogicalString
	      ELSE @inputLogicalString
	! Question => {ListLogicalVolumes[]; RESUME}];
      IF calledFromSetDebuggerPtrs THEN {
        IF debuggerLogicalString.length=0 THEN {lvID ← Volume.nullID; RETURN}
	ELSE inputString ← debuggerLogicalString}
      ELSE {inputString ← inputLogicalString};
      DO
        driveTemp: PhysicalVolume.Handle;
	ltmpID:    Volume.ID ← Volume.nullID;
	IF (ptmpID ← PhysicalVolume.GetNext[ptmpID]) = PhysicalVolume.nullID THEN EXIT;
        driveTemp ← PhysicalVolume.GetAttributes[ptmpID].instance;
        DO
          s: STRING = [maxNameLength];
	  IF (ltmpID ← PhysicalVolume.GetNextLogicalVolume[ptmpID, ltmpID])
	     = Volume.nullID THEN EXIT;
          GetLogicalVolumeName[ltmpID, s ! Volume.NotOnline => LOOP];
          IF FunnyEqual[driveTemp, s, inputString] THEN {
	    matches ← matches + 1; lvID ← ltmpID; pvID ← ptmpID; drive ← driveTemp};
          ENDLOOP;
        ENDLOOP;
      SELECT matches FROM
        0 => WriteString["Not found\r"L];
        1 => RETURN;
        ENDCASE => WriteLine["Ambigous; please specify Device:LogicalName"L];
      ENDLOOP;
    END;

  FunnyEqual: PROC [
    h: PhysicalVolume.Handle, name: STRING, userName: LONG STRING,
    mode: {checkNakedPName, dontCheckNakedPName} ← dontCheckNakedPName]
    RETURNS[BOOLEAN] = {
    driveName: STRING;
    SameChar: PROC [a, b: CHARACTER]
      RETURNS [BOOLEAN] = {
      IF a=b THEN RETURN[TRUE]
      ELSE IF a IN ['a..'z] AND b IN ['A..'Z] AND (a-'a+'A)=b THEN RETURN[TRUE]
      ELSE IF a IN ['A..'Z] AND b IN ['a..'z] AND (a-'A+'a)=b THEN RETURN[TRUE]
      ELSE RETURN[FALSE]};
    IF String.Equivalent[name, userName] THEN RETURN[TRUE];
    driveName ← GetDriveStringName[h];
    IF userName.length < driveName.length THEN RETURN [FALSE];
    FOR i: CARDINAL IN [0..driveName.length) DO
      IF ~SameChar[driveName[i], userName[i]] THEN RETURN[FALSE] ENDLOOP;
    IF mode=checkNakedPName THEN {
      IF (userName.length=driveName.length)
         OR (userName.length=driveName.length+1
             AND userName[driveName.length] = ':) THEN RETURN[TRUE]};
    IF driveName.length+name.length+1 # userName.length THEN RETURN[FALSE];
    IF userName[driveName.length] # ': THEN RETURN[FALSE];
    FOR i: CARDINAL IN [0..name.length) DO
      IF ~SameChar[name[i], userName[driveName.length+1+i]] THEN RETURN[FALSE]
      ENDLOOP;
    RETURN[TRUE]};
    
  GetLvTypeFromUser: PUBLIC PROC [
    prompt: STRING, defaultType: Volume.Type] RETURNS [Volume.Type] =
    BEGIN
    ListTypes: PROC = {
      FOR t: Volume.Type IN [normal..nonPilot] DO
        WriteString[logicalVolumeTypeString[t]];
        WriteString[IF t = nonPilot THEN "\r"L ELSE ", "L];
        ENDLOOP};
    String.Replace[@lvTypeString, logicalVolumeTypeString[defaultType], Heap.systemZone];
    DO
      GetName[prompt, @lvTypeString, echo, TRUE
      ! Question => {ListTypes[]; RESUME}];
      FOR t: Volume.Type IN [normal..nonPilot] DO
        IF String.Equivalent[logicalVolumeTypeString[t], lvTypeString] THEN
          RETURN[t]
        ENDLOOP;
      WriteLine["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
    DO
      tmpID:   PhysicalVolume.ID ← PhysicalVolume.nullID;
      matches: CARDINAL ← 0;
      GetName["Physical Volume Name: "L, @inputPhysString, , TRUE
      ! Question => {ListPhysicalVolumes[]; RESUME}];
      DO
        s:         STRING = [maxNameLength];
        match:     BOOLEAN;
	driveTemp: PhysicalVolume.Handle;
	IF (tmpID ← PhysicalVolume.GetNext[tmpID]) = PhysicalVolume.nullID THEN
	  EXIT;
        driveTemp ← PhysicalVolume.GetAttributes[tmpID, s].instance;
        match ← FunnyEqual[driveTemp, s, inputPhysString, checkNakedPName];
        IF match THEN {matches ← matches + 1; id ← tmpID; drive ← driveTemp};
        ENDLOOP;
      SELECT matches FROM
        0 => WriteLine["Not Found"L];
        1 => RETURN;
        ENDCASE => WriteLine["Ambigous; please specify Device:PhysicalName"L];
      ENDLOOP;
    END;

  StringInit: PROC = {
    SetCommandString[String.CopyToNewString["Online RD0"L, Heap.systemZone]]};
    debuggerLogicalString ← String.CopyToNewString["CoPilot"L, Heap.systemZone];

  RegisterCommandProc[@commandProcessor];

  StringInit[];
  
  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
17-Jul-81 15:34:33	Glassman	Merged OthelloDevice into OthelloDefs
12-Aug-81 12:33:54	Yokota	Added a catch phrase for Volume.GetAttributes and commented it out for Volume.GetLabelString
 5-Dec-81 17:30:28	Fay	Converted from PhysicalVolumeExtras to PhysicalVolume for PV scavenger.
11-Dec-82 15:10:21	Johnsson	Removed Storage.
13-Apr-83 12:27:04	Johnsson	Klamath conversion
15-Aug-84  Lichtenberg/Masinter Convert to Hello, Lisp specific modifications