-- PromOpsImpl.mesa
-- edited by: Masinter.pa on:     23-Aug-84 11:36:52
-- edited by: Curbow on:      20-Jun-84 16:43:16


DIRECTORY
  AccessFloppy USING [
    Attributes, AttributesRecord, Close, Error, ErrorType, leaderLength, LookUp,
    maxDataSize, Open],
  Device USING [PilotDisk, nullType, Type],
  DeviceTypes USING [
    cdc9730, q2000, q2010, q2020, q2030, q2040, q2080, sa1000, sa1004, sa4000, sa4008,
    sa800, t300, t80],
  Environment USING [PageCount],
  File USING [
    Create, Delete, GetAttributes, File, MakePermanent, nullFile, PageCount,
    PageNumber, SetSize, Type, Unknown],
  Floppy USING [
    CopyToPilotFile, DataError, Error, ErrorType, FileHandle, GetAttributes,
    nullFileID, nullVolumeHandle, PageCount, PageNumber, Read, VolumeHandle],
  FloppyChannel USING [Drive, Error, ErrorType, GetHandle, Handle, Nop],
  FormatPilotDisk, FormatPilotDiskExtras,
  Heap USING [systemZone],
  NSString USING [String, StringFromMesaString],
  OthelloOps USING [
    BadSwitches, BootFileType, DecodeSwitches, DeleteTempFiles, GetDriveSize,
    GetPhysicalVolumeBootFile, GetSwitches, GetVolumeBootFile, MakeBootable,
    MakeUnbootable, SetDebugger, SetDebuggerSuccess, SetGetSwitchesSuccess,
    SetPhysicalVolumeBootFile, SetSwitches, SetVolumeBootFile,
    VoidPhysicalVolumeBootFile, VoidVolumeBootFile],
  OthelloDefs USING [LeaderPage, leaderPages, lpVersion, lpNoteLength],
  PhysicalVolume USING [
    AssertNotAPilotVolume, AssertPilotVolume, CreatePhysicalVolume, Error,
    ErrorType, FinishWithNonPilotVolume, GetAttributes,
    GetContainingPhysicalVolume, GetHandle, GetHints, GetNext, GetNextBadPage,
    GetNextDrive, GetNextLogicalVolume, Handle, ID, InterpretHandle,
    NeedsScavenging, MarkPageBad, maxNameLength, maxSubvolumesOnPhysicalVolume,
    noProblems, nullBadPage, nullDeviceIndex, nullID, Offline, PageNumber,
    RepairType, Scavenge, ScavengerStatus],
  Process USING [MsecToTicks, Pause, SecondsToTicks],
  PromCommand USING [
    AbortOption, CommandTable, CommandTableRecord, workingDriveIndex],
  PromIO USING [
    ttyHandle, Filter, FormatError, HardError, InputError, ReadLine, ReadName,
    ReadNumber, ReadShortNumber, ReadYes, SetIOFilter, WriteChar, WriteCR,
    WriteFixedWidthNumber, WriteLine, WriteLongNumber, WriteString],
  PromScript USING [AllowStateToBeSaved, GetRootFile, SaveState],
  PromTime USING [RestoreTimeZone],
  Scavenger USING [Scavenge, Error, ErrorType],
  Space USING [Interval, Map, ScratchMap, Unmap],
  String USING [
    AppendChar, AppendCharAndGrow, AppendLongNumber, AppendString, Equivalent,
    FreeString],
  System USING [defaultSwitches, PowerOff, Switches],
  TemporaryBooting USING [BootButton, BootFromVolume, InvalidParameters],
  TextInput USING [GetYesNo],
  Time USING [Append, Unpack],
  UserTerminal USING [Beep],
  Volume USING [
    Close, Create, Erase, GetAttributes, GetLabelString, GetStatus, GetType, ID,
    InsufficientSpace, nullID, Open, PageCount, Type];

PromOpsImpl: PROGRAM
  IMPORTS
    AccessFloppy, File, Floppy, FloppyChannel, FormatPilotDisk, Heap, NSString,
    OthelloOps, PhysicalVolume, Process, PromCommand, PromIO, PromScript, PromTime,
    Scavenger, Space, String, System, TemporaryBooting, TextInput, Time,
    UserTerminal, Volume
  EXPORTS PromCommand
  SHARES File, FormatPilotDisk =
  BEGIN OPEN PromIO, OthelloOps;

  z: UNCOUNTED ZONE ← Heap.systemZone;

  -- Keep command table alphabetical
  commandTable: ARRAY [0..32) OF PromCommand.CommandTableRecord ← [
    ["--", CodeComment], ["Boot", BootBoot], ["Check Drive", CheckDrive], [
    "Close", CloseCmd], ["Comment", WriteComment], ["Confirm", Confirm], [
    "Create Physical Volume", CreateVolume], ["Debug", ShowDebugInfo], [
    "Delete Boot File", DeleteBootFile], [
    "Delete Temporary Files", DeleteTempFilesUser], [
    "Diagnostic Microcode Fetch", FetchDiagnosticMicrocode], ["Erase", Erase], [
    "Fetch", FetchBoot], ["Format", Format], ["Germ Fetch", FetchGerm], [
    "Initial Microcode Fetch", FetchInitialMicrocode], [
    "Logical Volume Scavenge", LVScavenge], ["Make Page Bad", MakeBad], [
    "Offline", Offline], ["Online", Online], ["Pause", Pause], [
    "Physical Volume Scavenge", PVScavenge], [
    "Pilot Microcode Fetch", FetchPilotMicrocode], ["Power Off", PowerOff], [
    "Quit", Quit], ["Request Floppy", RequestFloppy], [
    "Root Fetch", FetchRootFile], ["Save", Save], [
    "Set Boot File Default Switches", SetBootFileSwitches], [
    "Set Debugger Pointers", SetDebuggerUser], [
    "Set Physical Boot Files", SetPvBoot], [
    "Specify Default Volume Sizes", SpecifyDefaultVolumeSizes]];

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

  DefaultLVSizeData: TYPE = RECORD [
    volumeName: StringBody ← [maxlength: maxNameLength, length: 0, text:],
    txt: PACKED ARRAY [0..maxNameLength) OF CHARACTER ← NULL,
    sizes: ARRAY [0..nSupportedDiskTypes) OF TypeAndSize];
  DiskPageNumber: TYPE = PhysicalVolume.PageNumber;
  Handle: TYPE = PhysicalVolume.Handle;
  TypeAndSize: TYPE = RECORD [
    type: Device.Type ← Device.nullType, size: LONG CARDINAL];
  bufferPtr: LONG POINTER = Space.ScratchMap[1];
  badTableSize: CARDINAL = 200;
  carryVolumeOpen: BOOLEAN ← FALSE;
  defaultLVSizeData: DefaultLVSizeData;
  lastCarryName: STRING ← [10 + maxNameLength];
  lastDriveName: STRING ← [10];
  lastLvName: STRING ← [10 + maxNameLength];
  lastPvName: STRING ← [10 + maxNameLength];
  --  switches:              LONG STRING ← NIL;
  logicalVolumeOverhead: CARDINAL = 1;
  maxNameLength: CARDINAL = PhysicalVolume.maxNameLength;
  minLogicalVolumeSize: CARDINAL = 50;  -- fudge + 1+1+6;
  nSupportedDiskTypes: CARDINAL = 5;
  fileName: STRING ← [100];
  buffer: ARRAY [1..AccessFloppy.maxDataSize + SIZE[AccessFloppy.AttributesRecord]]
    OF WORD ← ALL[0];
  attributes: AccessFloppy.Attributes ← LOOPHOLE[LONG[@buffer]];

  -- BUG! The following statement is wrong!  physicalVolumeOverhead should
  -- be set based upon device type.  This will be necessary once Pilot
  -- knows how to create bad page tables based upon device types.  For
  -- the time being, we just KNOW that all bad page tables are one page
  -- long, and that as a result there are always two
  -- pages of physical volume overhead.
  physicalVolumeOverhead: CARDINAL = 2;


  LvRecord: TYPE = MACHINE DEPENDENT RECORD [
    size: Volume.PageCount ← NULL,
    type: Volume.Type ← NULL,
    fill: [0..16000] ← 0,
    name: StringBody ← [maxlength: maxNameLength, length: 0, text:],
    txt: PACKED ARRAY [0..maxNameLength) OF CHARACTER ← NULL];

  VolumeNotFound: ERROR = CODE;
  BadSeal: ERROR = CODE;
  WrongVersion: ERROR = CODE;
  WrongIdOnMarkerPage: ERROR = CODE;
  MarkerPageReadFailed: ERROR = CODE;
  MarkerPageWriteFailed: ERROR = CODE;


  GetCommandTable: PUBLIC PROC RETURNS [PromCommand.CommandTable] = {
    RETURN[DESCRIPTOR[commandTable]]};



  ---- ---- ---- ---- ---- ---- ---- ----  ----
  -- individual commands.

  BootBoot: PROC =
    BEGIN
    lvID: Volume.ID;
    ts: System.Switches;
    switches: LONG STRING ← [100];

    lvID ← ReadLvID[].lvID;
    GetSetBootFileSwitches[
      get, lvID ! File.Unknown => InputError["(can't get default switches)"L]];
    ReadLine["switches: "L, switches];
    ts ← DecodeSwitches[switches ! BadSwitches => {InputError["Bad Switches"L]}];
    PromScript.SaveState[];
    TemporaryBooting.BootFromVolume[lvID, ts];
    END;  -- of BootBoot

  CheckDrive: PROC =
    BEGIN

    << Othello version found in OthelloDeviceImplD0DLion.mesa >>
    badSpots: CARDINAL;
    badSpotArray: ARRAY [0..badTableSize) OF DiskPageNumber;
    couldDo: BOOLEAN;
    h: Handle;
    p: PhysicalVolume.ID ← PhysicalVolume.nullID;
    wasOnLine: BOOLEAN ← FALSE;

    [couldDo, badSpots, h] ← FormatCheckDrive[@badSpotArray, check];
    IF ~couldDo THEN {InputError["Can't check this device."L]; RETURN}
    ELSE IF badSpots = 0 THEN {WriteLine["No bad pages found."L]; 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 {
            HardError["Too many bad spots on the System Disk. Cannot proceed."L];
            EXIT}];
      ENDLOOP;
    IF ~wasOnLine THEN PhysicalVolume.Offline[p];
    WriteLine["Consider scavenging some volumes."L];
    --should scavenger be called automatically? What do we tell the user?
    END;  -- of CheckDrive

  CloseCmd: PROC =
    BEGIN
    AccessFloppy.Close[ ! AccessFloppy.Error, Floppy.Error => CONTINUE];
    WriteLine["closed"L];
    END;

  CodeComment: PROC = BEGIN PromIO.ReadLine[NIL, NIL]; END;

  Confirm: PROC =
    BEGIN
    prompt: LONG STRING ← [80];
    PromIO.ReadLine[NIL, prompt];
    InternalConfirm[prompt];
    END;  -- of Confirm

  InternalConfirm: PROC [prompt: LONG STRING] =
    BEGIN
    defaultPrompt: LONG STRING = "OK to proceed? (Y/N)"L;

    IF prompt = NIL THEN prompt ← defaultPrompt;
    IF TextInput.GetYesNo[PromIO.ttyHandle, NSString.StringFromMesaString[prompt]]
      # yes THEN ERROR PromCommand.AbortOption[];
    END;  -- of InternalConfirm

  CreateVolume: PROC =
    BEGIN

    << Othello version is found in OthelloDeviceImplD0DLion.mesa >>
    h: Handle = ReadDrive[];
    badTable: ARRAY [0..badTableSize) OF PhysicalVolume.PageNumber;
    bad: CARDINAL ← 0;
    broughtOnLine: BOOLEAN ← FALSE;
    driveSize: LONG CARDINAL;
    driveType: Device.Type;
    nSubVols: CARDINAL;
    lvID: Volume.ID;
    lvsize: Volume.PageCount;
    lvTable: ARRAY [0..10) OF LvRecord ← ALL[[]];
    pvID: PhysicalVolume.ID ← PhysicalVolume.nullID;
    pvName: STRING ← [maxNameLength];
    retryCount: CARDINAL ← 0;

    PagesRippedOff: PROC RETURNS [p: LONG CARDINAL] =
      --we don't really lose pg 0
      INLINE {p ← MinPilotPage[h]; IF p # 0 THEN p ← p - 1};

    volumeFound: BOOLEAN ← TRUE;
    DO
      pvID ← PhysicalVolume.GetNext[pvID];
      IF pvID = PhysicalVolume.nullID THEN
        BEGIN
        volumeFound ← ReadYes["Shall I try to find an old bad page Table? "L];

        << If the physical volume needs Scavenging, just ignore it. We will
	be creating a new volume on top, and so there's no reason to waste 
	time scavenging it. This is what Othello does. >>
        IF volumeFound THEN
          BEGIN
          broughtOnLine ← TRUE;
          pvID ← PhysicalVolume.AssertPilotVolume[
            h !
            PhysicalVolume.Error, PhysicalVolume.NeedsScavenging => {
              volumeFound ← broughtOnLine ← FALSE; CONTINUE}];
          END;
        EXIT;
        END;
      IF h = PhysicalVolume.GetAttributes[pvID].instance THEN EXIT;
      ENDLOOP;
    IF volumeFound THEN
      BEGIN OPEN PhysicalVolume;
      badTable[bad] ← GetNextBadPage[pvID, nullBadPage];
      WHILE badTable[bad] # nullBadPage DO
        badTable[bad + 1] ← GetNextBadPage[pvID, badTable[bad]];
        bad ← bad + 1;
        ENDLOOP;
      volumeFound ← PhysicalVolume.GetNextLogicalVolume[pvID, Volume.nullID] #
        Volume.nullID;
      END;
    ReadName["New physical volume name: "L, pvName];
    nSubVols ← ReadShortNumber[
      "Number of logical volumes: "L, 1,
      PhysicalVolume.maxSubvolumesOnPhysicalVolume, 3];

    driveType ←
      SELECT PhysicalVolume.InterpretHandle[h].type FROM
        DeviceTypes.sa1004, DeviceTypes.sa1000 => DeviceTypes.sa1004,
        DeviceTypes.sa4008, DeviceTypes.sa4000 => DeviceTypes.sa4008,
        DeviceTypes.q2040, DeviceTypes.q2000 => DeviceTypes.q2040,
        ENDCASE => PhysicalVolume.InterpretHandle[h].type;

    driveSize ←
      GetDriveSize[h] -
        (physicalVolumeOverhead + nSubVols * logicalVolumeOverhead +
           PagesRippedOff[] + LastCylinder[h]);
    FOR i: CARDINAL IN [0..nSubVols) DO
      OPEN lvTable[i];
      defaultVolumeSize: LONG CARDINAL ← 0;
      WriteString["Logical volume "L];
      WriteLongNumber[LONG[i]];
      WriteCR[];

      DO
        duplicate: BOOLEAN ← FALSE;
        ReadName["  Name: "L, @name];
        FOR j: CARDINAL IN [0..i) WHILE ~duplicate DO
          duplicate ← String.Equivalent[@name, @lvTable[j].name]; ENDLOOP;
        IF ~duplicate THEN EXIT;
        InputError["Name is already in use; please choose another."L];
        ENDLOOP;

      --determine default size for this volume.
      defaultVolumeSize ← driveSize / (nSubVols - i);
      IF String.Equivalent[@name, @defaultLVSizeData.volumeName] THEN
        FOR i: CARDINAL IN [0..nSupportedDiskTypes) DO
          IF driveType = defaultLVSizeData.sizes[i].type THEN
            defaultVolumeSize ← defaultLVSizeData.sizes[i].size;
          ENDLOOP;

      size ← ReadNumber[
        "  Pages: "L, minLogicalVolumeSize,
        driveSize - ((nSubVols - (i + 1)) * minLogicalVolumeSize),
        defaultVolumeSize];
      driveSize ← driveSize - size;
      type ← ReadLvType["  Type: "L];
      ENDLOOP;

    IF broughtOnLine THEN PhysicalVolume.Offline[pvID];
    PhysicalVolume.Offline[pvID ! ANY => CONTINUE];
    pvID ← PhysicalVolume.CreatePhysicalVolume[h, pvName];
    FOR i: CARDINAL IN [0..bad) DO
      PhysicalVolume.MarkPageBad[pvID, badTable[i]] ENDLOOP;
    FOR i: CARDINAL IN [0..nSubVols) DO
      OPEN lvTable[i];
      lvID ← Volume.Create[pvID, size, @name, type, MinPilotPage[h]];
      IF (lvsize ← Volume.GetAttributes[lvID].volumeSize) # size THEN
        BEGIN
        WriteString[@name];
        WriteString["'s size decreased (because of bad pages) to "L];
        WriteLongNumber[lvsize];
        WriteCR[];
        END;
      ENDLOOP;
    PromTime.RestoreTimeZone[pvID];
    END;  -- of CreateVolume    

  DeleteBootFile: PROC =
    BEGIN
    lvID: Volume.ID = ReadLvID[].lvID;
    pvID: PhysicalVolume.ID = PhysicalVolume.GetContainingPhysicalVolume[lvID];
    FOR t: BootFileType IN [hardMicrocode..pilot] DO
      bootFile: File.File;
      [bootFile] ← GetVolumeBootFile[lvID, t];

      IF bootFile = File.nullFile THEN LOOP;
      Volume.Open[lvID];
      BEGIN ENABLE File.Unknown => CONTINUE; File.Delete[bootFile]; END;
      VoidVolumeBootFile[lvID, t];
      IF GetPhysicalVolumeBootFile[pvID, t].file = bootFile THEN
        VoidPhysicalVolumeBootFile[pvID, t];
      Volume.Close[lvID];
      ENDLOOP;
    END;  -- of DeleteBootFile

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

  Erase: PROC =
    BEGIN
    lvID: Volume.ID ← ReadLvID[].lvID;
    Volume.Close[lvID];
    WriteString["Erase...."L];
    Volume.Erase[lvID];
    WriteLine["complete"L];
    END;  -- of Erase

  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]};

  Fetch: PROC [type: OthelloOps.BootFileType, prompt: STRING] =
    BEGIN
    attr: AccessFloppy.Attributes ← attributes;
    created: BOOLEAN;
    bootFile: File.File;
    fileHandle: Floppy.FileHandle;
    lvID: Volume.ID;
    firstPage: File.PageNumber;

    FetchInternal: PROC =
      BEGIN
      fileHandle ← AccessFloppy.LookUp[
        NSString.StringFromMesaString[fileName], attr];
      -- IF a boot file exists on this logical volume already, and 
      -- not of the same TYPE then delete it. 
      -- Also remove it as the physical volume boot file as necessary.
      IF bootFile # File.nullFile THEN
        BEGIN
        ENABLE File.Unknown => {bootFile ← File.nullFile; CONTINUE};
        oldType: File.Type;
        [oldType] ← File.GetAttributes[bootFile];
        IF oldType # attr.type THEN
          BEGIN
          pvID: PhysicalVolume.ID = PhysicalVolume.GetContainingPhysicalVolume[
            lvID];
          File.Delete[bootFile];
          OthelloOps.VoidVolumeBootFile[lvID, type];
          IF OthelloOps.GetPhysicalVolumeBootFile[pvID, type].file = bootFile THEN
            OthelloOps.VoidPhysicalVolumeBootFile[pvID, type];
          bootFile ← File.nullFile;
          END
        END;

      -- Retrieve the specified floppy file
      -- NOTE: Allow OthelloDefs.leaderPages at front of file to contain
      -- info about this file, i.e. filename, dates, size, etc. 
      IF (created ← (bootFile = File.nullFile)) THEN
        bootFile ← File.Create[
          lvID, attr.totalSize + OthelloDefs.leaderPages, attr.type]
      ELSE
        BEGIN
        OthelloOps.MakeUnbootable[
          bootFile, type, firstPage !
          TemporaryBooting.InvalidParameters => {
            PromIO.WriteLine["Warning: trouble making unbootable."L]; CONTINUE}];
        File.SetSize[
          bootFile, attr.totalSize + OthelloDefs.leaderPages !
          Volume.InsufficientSpace => {
            PromIO.WriteLine["Volume Full"L]; ERROR PromCommand.AbortOption[]}];
        END;
      PromIO.WriteString["Fetching..."L];
      Floppy.CopyToPilotFile[
        floppyFile: fileHandle, pilotFile: bootFile,
        firstFloppyPage: AccessFloppy.leaderLength,
        firstPilotPage: attr.offset + OthelloDefs.leaderPages, count: attr.size];
      END;  --of FetchInternal

    lvID ← ReadLvID[].lvID;
    Volume.Open[lvID];
    PromIO.ReadName[prompt, fileName];
    [bootFile, firstPage] ← OthelloOps.GetVolumeBootFile[lvID, type];
    fileHandle ← [Floppy.nullVolumeHandle, Floppy.nullFileID];
    FetchInternal[
      !
      AccessFloppy.Error => {
        SELECT type FROM
          volumeNotOpen => PromIO.InputError["Floppy not open."L];
          fileNotFound => PromIO.InputError["Floppy file not found."L];
          ENDCASE => PromIO.InputError["AccessFloppy Error"L]};

      Floppy.Error =>
        IF fileHandle.file = Floppy.nullFileID THEN {
          SELECT error FROM
            fileNotFound => PromIO.InputError["Floppy file not found."L];
            ENDCASE => PromIO.InputError["Floppy Lookup Error"];
          GO TO Return}
        ELSE
          SELECT error FROM
            fileNotFound, endOfFile => {GetNewFloppy[fileHandle.volume]; RETRY};
            ENDCASE; Floppy.DataError => {GetNewFloppy[fileHandle.volume]; RETRY}];
    PromIO.WriteString["Installing..."L];
    OthelloOps.SetVolumeBootFile[bootFile, type, OthelloDefs.leaderPages];
    File.MakePermanent[bootFile];
    IF attr.offset + attr.size = attr.totalSize THEN
      BEGIN  << at end of multiple piece file, or only single piece file>>
      lp: LONG POINTER TO OthelloDefs.LeaderPage ← Space.Map[
        [bootFile, 0, OthelloDefs.leaderPages]].pointer;
      note: LONG STRING ← [OthelloDefs.lpNoteLength];
      note.length ← 0;  -- just to be sure.
      String.AppendString[to: note, from: fileName];
      String.AppendString[to: note, from: "("L];
      Time.Append[
        s: note, unpacked: Time.Unpack[time: attr.createDate], zone: TRUE];
      String.AppendString[to: note, from: ")"L];
      lp.version ← OthelloDefs.lpVersion;
      lp.length ← MIN[note.length, OthelloDefs.lpNoteLength];
      FOR i: CARDINAL IN [0..lp.length) DO lp.note[i] ← note[i]; ENDLOOP;
      [] ← Space.Unmap[lp];
      OthelloOps.MakeBootable[
        bootFile, type, OthelloDefs.leaderPages !
        TemporaryBooting.InvalidParameters =>
          BEGIN
          Volume.Close[lvID];
          --what happens here if we are fetching the bootfile in pieces?
          PromIO.InputError[
            IF type # pilot THEN "Can't make file bootable."L
            ELSE "Warning: File not complete."L];
          GOTO Return;
          END];
      END;
    PromIO.WriteLine["done"L];
    IF type IN [hardMicrocode..germ]
      AND PromIO.ReadYes["Shall I also use this for the Physical Volume? "L] THEN
      OthelloOps.SetPhysicalVolumeBootFile[
        bootFile, type, OthelloDefs.leaderPages];
    Volume.Close[lvID];
    EXITS Return => NULL;
    END;  -- of Fetch

  FetchInitialMicrocode: PROC =
    BEGIN OPEN DeviceTypes;

    << Othello version found in OthelloDeviceImplD0DLion.mesa
    This version very different from Othello one >>
    h: Handle = ReadDrive[];
    t: Device.Type = GetDriveType[h];
    mapped: BOOLEAN ← FALSE;
    attr: AccessFloppy.Attributes ← attributes;
    fileHandle: Floppy.FileHandle;

    -- Read file name and verify that it exists on Floppy
    ReadName["File name: "L, fileName];
    fileHandle ← AccessFloppy.LookUp[
      NSString.StringFromMesaString[fileName], attr !
      AccessFloppy.Error => {
        SELECT type FROM
          volumeNotOpen => PromIO.InputError["Floppy not open."L];
          fileNotFound => PromIO.InputError["Floppy file not found."L];
          ENDCASE => PromIO.InputError["AccessFloppy Error"L];
        GO TO Return};
      Floppy.Error => {
        SELECT error FROM
          fileNotFound => InputError["Floppy file not found."L];
          ENDCASE => InputError["Floppy Lookup Error"];
        GO TO Return}];

    SELECT t FROM
      q2000, q2040, q2080, sa1000, sa1004, sa4000, sa4008, t80, t300 =>
        BEGIN OPEN FSa: FormatPilotDisk;
        currentPage: File.PageNumber ← AccessFloppy.leaderLength;

        GetPage: PROC RETURNS [LONG POINTER] =
          BEGIN
          IF currentPage > attr.size THEN RETURN[NIL];
          Floppy.Read[fileHandle, currentPage, 1, bufferPtr];
          currentPage ← currentPage + 1;
          RETURN[bufferPtr];
          END;  -- end of GetPage

        wasOnline: BOOLEAN = ForceOffline[h];
        PhysicalVolume.AssertNotAPilotVolume[h];

        << FormatBootMicrocodeArea may raise the NotAPilotDisk ERROR. >>
        {
        ENABLE UNWIND => PhysicalVolume.FinishWithNonPilotVolume[h];
        FSa.FormatBootMicrocodeArea[
          h: h, passes: 1, retries: 0 ! FSa.NotAPilotDisk => GOTO BadDevice;
          FSa.BadPage =>
            BEGIN
            WriteString["Warning: page "L];
            WriteLongNumber[p];
            WriteLine[" is bad (will be skipped). "L];
            RESUME
            ;
            END];

        WriteString["Fetching..."L];
        FSa.InstallBootMicrocode[h, GetPage]} << end ENABLE UNWIND>> ;
        PhysicalVolume.FinishWithNonPilotVolume[h];
        IF wasOnline THEN [] ← PhysicalVolume.AssertPilotVolume[h];
        WriteLine["Done"L];
        END;
      ENDCASE => GOTO BadDevice;
    EXITS
      BadDevice => InputError["Cannot install microcode on that device."L];
      Return => NULL;
    END;  -- of FetchInitialMicrocode

  FetchRootFile: PROC =
    BEGIN
    attr: AccessFloppy.Attributes ← attributes;
    fileHandle: Floppy.FileHandle;
    rootFile: File.File ← File.nullFile;
    rootFileVolumeID: Volume.ID;

    rootFileVolumeID ← ReadLvID[].lvID;
    ReadName["Rootfile Name: ", fileName];
    fileHandle ← AccessFloppy.LookUp[
      NSString.StringFromMesaString[fileName], attr !
      AccessFloppy.Error => {
        SELECT type FROM
          volumeNotOpen => PromIO.InputError["Floppy not open."L];
          fileNotFound => PromIO.InputError["Floppy file not found."L];
          ENDCASE => PromIO.InputError["AccessFloppy Error"L];
        GO TO Return};

      Floppy.Error => {
        SELECT error FROM
          fileNotFound => InputError["Floppy file not found."L];
          ENDCASE => InputError["Floppy Lookup Error"];
        GO TO Return}];

    Volume.Open[rootFileVolumeID];
    rootFile ← PromScript.GetRootFile[rootFileVolumeID, attr.type, attr.totalSize];
    Floppy.CopyToPilotFile[
      floppyFile: fileHandle, pilotFile: rootFile,
      firstFloppyPage: AccessFloppy.leaderLength, firstPilotPage: attr.offset,
      count: attr.size];
    Volume.Close[rootFileVolumeID];
    EXITS Return => NULL;
    END;  -- of FetchRootFile


  Format: PROC =
    BEGIN

    << Othello version found in OthelloDeviceImplD0DLion.mesa >>
    badSpots: CARDINAL;
    badSpotArray: ARRAY [0..badTableSize) OF DiskPageNumber;
    couldDo: BOOLEAN;
    h: Handle;
    p: PhysicalVolume.ID;

    [couldDo, badSpots, h] ← FormatCheckDrive[@badSpotArray, format];
    IF ~couldDo THEN {InputError["Cannot format this device."L]; RETURN};
    FOR i: CARDINAL IN [0..MIN[badSpots, LENGTH[badSpotArray]]) DO
      IF badSpotArray[i] = 0 THEN {
        HardError["Critical System Disk pages bad. Cannot proceed"L]; RETURN};
      ENDLOOP;
    WriteCR[];
    WriteLine["Creating 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;
    PromTime.RestoreTimeZone[p];
    PhysicalVolume.Offline[p];
    EXITS
      fullBadSpotTable =>
        HardError["Too many bad spots on the disk. Cannot proceed."L];
    END;  -- of Format

  ForceOffline: PROC [h: PhysicalVolume.Handle]
    RETURNS [wasOnline: BOOLEAN ← FALSE] = {
    p: PhysicalVolume.ID ← PhysicalVolume.nullID;
    DO
      p ← PhysicalVolume.GetNext[p];
      IF p = PhysicalVolume.nullID THEN EXIT;
      IF h = PhysicalVolume.GetAttributes[p].instance THEN {
        wasOnline ← TRUE; PhysicalVolume.Offline[p]; EXIT};
      ENDLOOP};

  FormatCheckDrive: PUBLIC PROC [
    bs: POINTER TO ARRAY [0..badTableSize) OF PhysicalVolume.PageNumber,
    op: {format, check}]
    RETURNS [couldDo: BOOLEAN, badSpots: CARDINAL, h: PhysicalVolume.Handle] = {
    -- leaves pv offline for op = format; restores previous state for op = check
    cbs: ARRAY [0..badTableSize) OF CARDINAL ← ALL[0];
    tooManyMsg: BOOLEAN ← FALSE;
    passes: CARDINAL ← 1;

    NoteBad: PROC [p: PhysicalVolume.PageNumber] = {
      FOR i: CARDINAL IN [0..badSpots) DO
        IF bs[i] = p THEN {cbs[i] ← cbs[i] + 1; WriteBadSpot[p, cbs[i]]; RETURN}
        ENDLOOP;
      IF badSpots < LENGTH[bs↑] THEN {
        bs[badSpots] ← p;
        cbs[badSpots] ← cbs[badSpots] + 1;
        badSpots ← badSpots + 1;
        WriteBadSpot[p, 1]}
      ELSE {
        IF ~tooManyMsg THEN {
          WriteLine["Too many bad pages"L]; tooManyMsg ← TRUE; column ← 0};
        WriteBadSpot[p, 1]}};  -- end of NoteBad

    FormatSummary: PROC = {
      IF badSpots > 0 THEN {
        column ← 0;
        WriteLine["\rSummary of bad pages: badPage(countTimesBad)"L];
        FOR i: CARDINAL IN [0..badSpots) DO WriteBadSpot[bs[i], cbs[i]] ENDLOOP;
        WriteCR[]}};

    column: CARDINAL ← 0;
    WriteBadSpot: PROC [p: PhysicalVolume.PageNumber, cnt: CARDINAL] = {
      WriteFixedWidthNumber[p, 8];
      WriteChar['(];
      WriteFixedWidthNumber[
        cnt, SELECT passes FROM IN [0..9] => 1, IN [10..99] => 2, ENDCASE => 3];
      WriteChar[')];
      IF (column MOD 5) = 4 THEN {column ← 0; WriteCR[]} ELSE column ← column + 1};

    badSpots ← 0;
    h ← ReadDrive[];
    SELECT TRUE FROM
      -- dam compiler won't coerce [CARDINAL] into CARDINAL
      LOOPHOLE[GetDriveType[h], CARDINAL] IN Device.PilotDisk => {
        OPEN FPD: FormatPilotDisk;
        pilotStart: PhysicalVolume.PageNumber = MinPilotPage[h];
        retries: FormatPilotDisk.RetryLimit ← 0;
        -- number of retries on bad page
        cylSize: CARDINAL = CylinderSize[h];
        IF op = format THEN {
          passes ← CARDINAL[ReadNumber["Number of passes: "L, 1, 200, 10]];
          retries ← CARDINAL[
            ReadNumber[
            "Number of retries: "L, FPD.noRetries, FPD.retryLimit, FPD.noRetries]];
          [] ← ForceOffline[h];
          -- format zero'th cylinder separately
          PhysicalVolume.AssertNotAPilotVolume[h];
          {
          ENABLE UNWIND => PhysicalVolume.FinishWithNonPilotVolume[h];
          FPD.Format[
            h, 0, cylSize, passes, retries ! FPD.BadPage => {NoteBad[p]; RESUME }];
          -- format rest of disk possibly allowing for alto-type volume
          -- or other device dependent dreck
          FPD.Format[
            h, pilotStart, GetDriveSize[h] - pilotStart, passes, retries !
            FPD.BadPage => {NoteBad[p]; RESUME }]};  -- ENABLE
          PhysicalVolume.FinishWithNonPilotVolume[h]}
        ELSE {  -- op=scan
          wasOnline: BOOLEAN;
          wasOnline ← ForceOffline[h];
          -- scan zero'th cylinder seperatly
          FPD.Scan[h, 0, cylSize ! FPD.BadPage => {NoteBad[p]; RESUME }];
          -- scan rest of disk possibly allowing for alto-type volume
          -- or other device dependent dreck
          FPD.Scan[
            h, pilotStart, GetDriveSize[h] - pilotStart !
            FPD.BadPage => {NoteBad[p]; RESUME }];
          IF wasOnline THEN [] ← PhysicalVolume.AssertPilotVolume[h]};
        FormatSummary[];
        RETURN[TRUE, badSpots, h]};
      ENDCASE => RETURN[FALSE, badSpots, h]};  -- end of FormatCheckDrive

  UnknownCylSize: ERROR = CODE;
  CylinderSize: PROC [h: PhysicalVolume.Handle] RETURNS [cylSize: CARDINAL] = {
    OPEN FPD: FormatPilotDisk, FPDx: FormatPilotDiskExtras;
    SELECT GetDriveType[h] FROM
      DeviceTypes.sa1000, DeviceTypes.sa1004 =>
        cylSize ← FPD.SA1004pagesPerCylinder;
      DeviceTypes.q2000 => cylSize ← FPD.Q2040pagesPerCylinder;
      DeviceTypes.q2010 => cylSize ← FPD.Q2010pagesPerCylinder;
      DeviceTypes.q2020 => cylSize ← FPD.Q2020pagesPerCylinder;
      DeviceTypes.q2030 => cylSize ← FPD.Q2030pagesPerCylinder;
      DeviceTypes.q2040 => cylSize ← FPD.Q2040pagesPerCylinder;
      DeviceTypes.q2080 => cylSize ← FPDx.Q2080pagesPerCylinder;

      DeviceTypes.sa4000, DeviceTypes.sa4008 =>
        cylSize ← FPD.SA4008pagesPerCylinder;
      DeviceTypes.t80 => cylSize ← FPD.t80pagesPerCylinder;
      DeviceTypes.t300 => cylSize ← FPD.t300pagesPerCylinder;
      ENDCASE => ERROR UnknownCylSize};

  GetNewFloppy: PROC [floppyHandle: Floppy.VolumeHandle] =
    BEGIN
    desiredFloppyName: STRING ← [maxNameLength];
    [] ← Floppy.GetAttributes[floppyHandle, desiredFloppyName];
    AccessFloppy.Close[ ! AccessFloppy.Error, Floppy.Error => CONTINUE];
    UserTerminal.Beep[];
    WriteCR[always];
    WriteLine["Trouble reading this Floppy"L, always];
    WriteString["Insert another Floppy Disk labeled """L, always];
    WriteString[desiredFloppyName, always];
    WriteLine[""" in Floppy Disk Drive."L, always];
    UNTIL TextInput.GetYesNo[
      PromIO.ttyHandle, NSString.StringFromMesaString[
      "Indicate when Floppy Disk is ready"L]] = yes DO ENDLOOP;
    RequestFloppyInternal[desiredFloppyName];
    END;

  LVScavenge: PROC =
    BEGIN
    lvID: Volume.ID ← ReadLvID[].lvID;
    scavIfInconsistent: BOOLEAN ← ReadYes["Only if inconsistent? "L];

    Volume.Close[lvID ! ANY => CONTINUE];
    IF scavIfInconsistent AND Volume.GetStatus[lvID] # closedAndInconsistent THEN
      RETURN;

    WriteString["Scavenging...."L];
    [] ← Scavenger.Scavenge[
      lvID, lvID, safeRepair, FALSE !
      Scavenger.Error =>
        SELECT error FROM
          needsConversion => {
            WriteLine[
              "Needs conversion to current format. Conversion may not be reversible."L];
            InternalConfirm[NIL];
            [] ← Scavenger.Scavenge[lvID, lvID, safeRepair, TRUE]};
          needsRiskyRepair => {
            WriteLine["Needs risky repair. Results are not guarenteed."L];
            InternalConfirm[NIL];
            [] ← Scavenger.Scavenge[lvID, lvID, riskyRepair, FALSE]};
          cannotWriteLog => WriteLine["cannotWriteLog"L];
          noSuchPage => WriteLine["noSuchPage"L];
          orphanNotFound => WriteLine["orphanNotFound"L];
          volumeOpen => WriteLine["volumeOpen"L];
          diskHardwareError => WriteLine["diskHardwareError"L];
          diskNotReady => WriteLine["diskNotReady"L];
          ENDCASE => WriteLine["UNKNOWN  ERROR"L]; ];

    WriteLine["complete"L];
    END;  -- of LVScavenge

  MakeBad: PROC =
    BEGIN
    handle: PhysicalVolume.Handle;
    id: PhysicalVolume.ID;
    page: PhysicalVolume.PageNumber;
    [id, handle] ← ReadPvID[];
    page ← ReadNumber["Decimal Page Number: "L, 0, GetDriveSize[handle] - 1];
    PhysicalVolume.MarkPageBad[id, page];
    --consider scavenging some volumes
    END;  -- of MakeBad

  Offline: PROC =
    BEGIN
    id: PhysicalVolume.ID;
    handle: Handle;
    [id, handle] ← ReadPvID[];
    PhysicalVolume.Offline[id];
    END;  -- of Offline

  Online: PROC =
    BEGIN
    handle: Handle = ReadDrive[];
    [] ← PhysicalVolume.AssertPilotVolume[
      handle !
      PhysicalVolume.Error =>
        SELECT error FROM
          alreadyAsserted => CONTINUE;
          diskReadError => {FormatError["not Pilot Volume"L]; CONTINUE};
          ENDCASE; ];
    END;  -- of Online

  Pause: PROC =
    BEGIN
    Process.Pause[
      Process.SecondsToTicks[ReadShortNumber["Seconds: "L, 1, 120, 5]]];
    END;  -- Pause

  PVScavenge: PROC =
    BEGIN OPEN PV: PhysicalVolume;
    s: PV.ScavengerStatus;
    h: PhysicalVolume.Handle;
    verbose: PromIO.Filter;
    repair: PV.RepairType;
    p: PhysicalVolume.ID ← PhysicalVolume.nullID;

    h ← ReadDrive[];
    verbose ← IF ReadYes["Verbose? "L] THEN always ELSE debug;
    repair ←
      IF ~ReadYes["Repair? "L] THEN checkOnly
      ELSE IF ReadYes["Risky repair? "L] THEN riskyRepair ELSE safeRepair;
    DO
      IF (p ← PhysicalVolume.GetNext[p]) = PhysicalVolume.nullID THEN EXIT;
      IF h = PhysicalVolume.GetAttributes[p].instance THEN {
        PhysicalVolume.Offline[p]; EXIT};
      ENDLOOP;
    WriteString["Scavenging...."L];
    s ← PV.Scavenge[h, repair, TRUE];  <<okayToConvert>>
    WriteLine["Complete"L];
    IF s = PV.noProblems THEN {WriteLine["No problems detected"L]; RETURN};
    SELECT s.internalStructures FROM
      damaged =>
        WriteLine["Critical disk data structures appear to be damaged"L, always];
      repaired => WriteLine["Critical disk data structures repaired"L, always];
      ENDCASE;
    SELECT s.badPageList FROM
      damaged => WriteLine["Bad disk page list appears to be damaged"L, always];
      lost => WriteLine["Bad disk page list was lost"L, always];
      ENDCASE;
    IF s.germ = damaged OR s.softMicrocode = damaged OR s.hardMicrocode = damaged
      OR s.bootFile = damaged THEN
      WriteLine["Installed software appears to be damaged"L, verbose]
    ELSE
      IF s.germ = lost OR s.softMicrocode = lost OR s.hardMicrocode = lost
        OR s.bootFile = lost THEN
        WriteLine["Installed software was lost"L, verbose];
    IF s.internalStructures = damaged OR s.badPageList # okay THEN {
      WriteLine["Cannot Proceed"L, always]; PromCommand.AbortOption[]; };

    PromTime.RestoreTimeZone[];
    END;  -- of PVScavenge

  PowerOff: PROC = {CloseCmd[]; System.PowerOff[]};

  Quit: PROC =
    BEGIN
    CloseCmd[];
    PromScript.SaveState[];
    TemporaryBooting.BootButton[];  --BootFromPhysicalVolume?
    END;  -- of Quit

  RequestFloppy: PROC =
    BEGIN
    desiredFloppyName: STRING ← [maxNameLength];
    ReadLine["Label: "L, desiredFloppyName];
    RequestFloppyInternal[desiredFloppyName];
    END;  -- of RequestFloppy

  RequestFloppyInternal: PROC [desiredFloppyName: STRING] =
    BEGIN
    actualFloppyName: STRING ← [maxNameLength];
    floppyHandle: Floppy.VolumeHandle;

    AwaitFloppyChange: PROC =
      BEGIN OPEN FloppyChannel;
      --if the floppy is ready, wait until it goes notReady first
      UNTIL Nop[
        GetHandle[0] ! Error => IF type = invalidHandle THEN RETRY].notReady DO
        Process.Pause[Process.MsecToTicks[3000]]; ENDLOOP;
      UNTIL ~Nop[
        FloppyChannel.GetHandle[0] !
        Error => IF type = invalidHandle THEN RETRY].notReady DO
        Process.Pause[Process.MsecToTicks[3000]]; ENDLOOP;
      END;  -- of AwaitFloppyChange

    CheckFloppy: PROC =
      BEGIN
      floppyHandle ← AccessFloppy.Open[];
      [] ← Floppy.GetAttributes[floppyHandle, actualFloppyName];
      RestoreBlanks[actualFloppyName];
      IF NOT String.Equivalent[desiredFloppyName, actualFloppyName] THEN
        BEGIN
        AccessFloppy.Close[ ! AccessFloppy.Error => CONTINUE];
        ERROR Floppy.Error[volumeNotOpen];
        END;
      END;  -- of CheckFloppy

    RestoreBlanks[desiredFloppyName];
    CheckFloppy[
      !
      Floppy.Error =>
        BEGIN
        SELECT error FROM
          invalidFormat =>
            WriteLine["This Floppy Disk has invalid format."L, always];
          needsScavenging =>
            WriteLine["This Floppy Disk is not well formed."L, always];
          noSuchDrive => WriteLine["No Floppy Disk drive is known."L, always];
          notReady =>
            WriteLine["Floppy Disk is not placed in drive properly."L, always];
          volumeNotOpen =>
            BEGIN
            WriteString["This Floppy Disk is labeled """L, always];
            WriteString[actualFloppyName, always];
            WriteLine[""".", always];
            END;
          ENDCASE => REJECT;
        UserTerminal.Beep[];
        WriteString["Insert Floppy Disk labeled """L, always];
        WriteString[desiredFloppyName, always];
        WriteLine[""" in Floppy Disk Drive."L, always];
        AwaitFloppyChange[];
        RETRY;
        END];
    END;  -- of RequestFloppyInternal

  RestoreBlanks: PROC [s: STRING] =
    --Replace arrows with blanks.
    --Arrows are there to make the name one word.
    BEGIN
    FOR i: CARDINAL IN [0..s.length) DO IF s[i] = '← THEN s[i] ← ' ; ENDLOOP;
    END;  -- of RestoreBlanks

  Save: PROC = {PromScript.AllowStateToBeSaved[ReadLvID[].lvID]; };

  SetBootFileSwitches: PROC =
    BEGIN
    ts: System.Switches;
    lvID: Volume.ID;
    switches: STRING ← [100];

    lvID ← ReadLvID[].lvID;
    GetSetBootFileSwitches[get, lvID];  -- volume.needsScav (caught higher up)
    DO
      ReadName["switches: "L, switches];
      ts ← DecodeSwitches[switches ! BadSwitches => {InputError["bad switches"L]}];
      EXIT;
      ENDLOOP;
    GetSetBootFileSwitches[set, lvID, ts];
    END;

  SetDebuggerUser: PROC =
    BEGIN
    bootFile: File.File;
    firstPage: File.PageNumber;
    lvID: Volume.ID = ReadLvID["for debuggee Logical Volume: "L].lvID;
    dLvID: Volume.ID;
    dPvID: PhysicalVolume.ID;
    dH: Handle;
    [bootFile, firstPage] ← GetVolumeBootFile[lvID, pilot];
    IF bootFile = File.nullFile THEN {InputError["No boot file found."]; RETURN};
    [dPvID, dLvID, dH] ← ReadLvID["for debugger Logical Volume: "L];
    Volume.Open[lvID];
    BEGIN
    ENABLE UNWIND => Volume.Close[lvID];
    SELECT
    (SetDebugger[
      debuggeeFile: bootFile, debuggeeFirstPage: firstPage, debugger: dLvID,
      debuggerType: GetDriveType[dH], debuggerOrdinal: GetDriveNumber[dH]]) FROM
      success => NULL;
      nullBootFile, cantWriteBootFile, notInitialBootFile =>
        InputError["Boot file broken."L];
      cantFindStartListHeader, startListHeaderHasBadVersion =>
        InputError["Boot file header broken."L];
      noDebugger => InputError["No debugger installed."L];
      ENDCASE;
    END;
    Volume.Close[lvID];
    END;

  SetPvBoot: PROC =
    BEGIN
    lvID: Volume.ID = ReadLvID["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].file = File.nullFile THEN RETURN;
      found ← TRUE;
      WriteString["Set physical volume "L];
      WriteString[s];
      IF (set[t] ← ReadYes[" from this logical volume? "L]) THEN changed ← TRUE;
      END;
    Smash["boot file"L, pilot];
    Smash["pilot microcode"L, softMicrocode];
    Smash["germ"L, germ];
    Smash["diagnostic microcode"L, hardMicrocode];
    IF ~found THEN {WriteLine["Logical volume has null boot files"L]; RETURN};
    IF ~changed THEN RETURN;
    Volume.Open[lvID];
    FOR t: BootFileType IN [hardMicrocode..pilot] DO
      IF set[t] THEN
        BEGIN
        bootFile: File.File;
        firstPage: File.PageNumber;
        [bootFile, firstPage] ← GetVolumeBootFile[lvID, t];
        SetPhysicalVolumeBootFile[bootFile, t, firstPage]
        END;
      ENDLOOP;
    Volume.Close[lvID];
    END;

  ShowDebugInfo: PROC = BEGIN PromIO.SetIOFilter[debug]; END;

  SpecifyDefaultVolumeSizes: PROC =
    BEGIN
    ReadName["  volume name: "L, @defaultLVSizeData.volumeName];
    defaultLVSizeData.sizes ← [
      [
      DeviceTypes.sa1004, ReadNumber[
      prompt: "  size if sa1004: "L, min: 0, max: 37777777777B, default: 0]], [
      DeviceTypes.sa4008, ReadNumber[
      prompt: "  size if sa4008: "L, min: 0, max: 37777777777B, default: 0]], [
      DeviceTypes.t300, ReadNumber[
      prompt: "  size if t300: "L, min: 0, max: 37777777777B, default: 0]], [
      DeviceTypes.t80, ReadNumber[
      prompt: "  size if t80: "L, min: 0, max: 37777777777B, default: 0]], [
      DeviceTypes.q2040, ReadNumber[
      prompt: "  size if q2040: "L, min: 0, max: 37777777777B, default: 0]]]
    END;

  WriteComment: PROC =
    BEGIN
    s: STRING ← [80];
    PromIO.ReadLine[NIL, s];
    PromIO.WriteLine[s, always];
    END;





  -- Volume Init Supporting Procedures

  unknown: STRING = "Unknown";

  GetSetBootFileSwitches: PROC [
    getSet: {get, set}, lvID: Volume.ID,
    ts: System.Switches ← System.defaultSwitches] =
    BEGIN
    outcome: SetGetSwitchesSuccess;
    bootFile: File.File;
    firstPage: File.PageNumber;
    switches: LONG STRING ← NIL;

    -- ***** Think this can be deleted.
    String.FreeString[z, switches];
    Volume.Open[lvID];
    [bootFile, firstPage] ← GetVolumeBootFile[lvID, pilot];
    IF bootFile = File.nullFile THEN InputError["No boot file found."L];
    IF getSet = get THEN [outcome, ts] ← GetSwitches[bootFile, firstPage]
    ELSE outcome ← SetSwitches[bootFile, 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, z]; LOOP};
        ENDCASE => NULL;
      String.AppendCharAndGrow[@switches, '\\, z];
      IF c > 77C THEN String.AppendCharAndGrow[@switches, (c - 0C) / 64 + '0, z];
      IF c > 7C THEN
        String.AppendCharAndGrow[@switches, ((c - 0C) / 8 MOD 8) + '0, z];
      String.AppendCharAndGrow[@switches, ((c - 0C) MOD 8) + '0, z];
      String.AppendCharAndGrow[@switches, '\\, z]
      ENDLOOP;
    END;  -- end of GetSetBootFileSwitches

  LastCylinder: PROC [h: Handle] RETURNS [LONG CARDINAL] =
    BEGIN OPEN FormatPilotDisk, FormatPilotDiskExtras, DeviceTypes;
    RETURN[
      SELECT PhysicalVolume.InterpretHandle[h].type FROM
      sa1000, sa1004 => SA1004pagesPerCylinder,
      q2000 => Q2040pagesPerCylinder,
      q2010 => Q2010pagesPerCylinder,
      q2020 => Q2020pagesPerCylinder,
      q2030 => Q2030pagesPerCylinder,
      q2040 => Q2040pagesPerCylinder,
      q2080 => Q2080pagesPerCylinder,
      sa4000, sa4008 => SA4008pagesPerCylinder,
      t80   => t80pagesPerCylinder,
      t300  => t300pagesPerCylinder,
        ENDCASE => 0];
    END;

  --The "ReadMumble" procedures acquire some kind of object from the character stream. Characters are read until the name of a Mumble object is found, and converted to an object with a more usable Mesa type than STRING.

  ReadDrive: PROC RETURNS [h: Handle] =
    BEGIN
    inString: STRING ← [20];
    DO
      index: CARDINAL ← PhysicalVolume.nullDeviceIndex;
      ReadName["Drive Name: "L, inString, lastDriveName];
      FOR i: CARDINAL IN [0..inString.length) DO
        lastDriveName[i] ← inString[i]; ENDLOOP;
      lastDriveName.length ← inString.length;
      IF inString[inString.length - 1] = ': THEN
        inString.length ← inString.length - 1;
      DO
        index ← PhysicalVolume.GetNextDrive[index];
        IF index = PhysicalVolume.nullDeviceIndex THEN EXIT;
        h ← PhysicalVolume.GetHandle[index];
        IF String.Equivalent[GetDriveStringName[h], inString] THEN RETURN;
        ENDLOOP;
      FormatError["Drive not found!"L];  --running on the wrong hardware!!
      ENDLOOP;
    END;

  ReadLvID: PROC [prompt: STRING ← NIL]
    RETURNS [pvID: PhysicalVolume.ID, lvID: Volume.ID, drive: Handle] =
    BEGIN
    inString: STRING ← [10 + maxNameLength];
    fullName: STRING ← [10 + maxNameLength];
    s: LONG STRING ← [maxNameLength];
    driveString: STRING;
    matches: CARDINAL;
    tempLvID: Volume.ID;
    tempPvID: PhysicalVolume.ID;

    IF prompt = NIL THEN prompt ← "Logical Volume Name: "L;
    DO
      ReadName[prompt, inString, lastLvName];
      FOR i: CARDINAL IN [0..inString.length) DO
        lastLvName[i] ← inString[i]; ENDLOOP;
      lastLvName.length ← inString.length;
      matches ← 0;
      tempPvID ← PhysicalVolume.nullID;
      DO
        tempPvID ← PhysicalVolume.GetNext[tempPvID];
        IF tempPvID = PhysicalVolume.nullID THEN EXIT;
        driveString ← GetDriveStringName[
          PhysicalVolume.GetAttributes[tempPvID].instance];
        -- Form string Drive:LogicalName
        FOR i: CARDINAL IN [0..driveString.length) DO
          fullName[i] ← driveString[i]; ENDLOOP;
        fullName[driveString.length] ← ':;
        tempLvID ← Volume.nullID;
        DO
          match: BOOLEAN;
          tempLvID ← PhysicalVolume.GetNextLogicalVolume[tempPvID, tempLvID];
          IF tempLvID = Volume.nullID THEN EXIT;
          GetLogicalVolumeName[tempLvID, 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.Equivalent[s, inString]
            OR String.Equivalent[fullName, inString];
          IF match THEN {matches ← matches + 1; lvID ← tempLvID; pvID ← tempPvID};
          ENDLOOP;
        ENDLOOP;
      SELECT matches FROM
        0 => FormatError["Not found"L];
        1 => {drive ← PhysicalVolume.GetAttributes[pvID].instance; RETURN};
        ENDCASE => FormatError["Ambigous; please specify Device:LogicalName"L];
      ENDLOOP;
    END;

  ReadLvType: PROC [prompt: STRING] RETURNS [t: Volume.Type] =
    BEGIN
    inString: STRING ← [30];
    DO
      ReadName[prompt, inString];
      FOR t IN [normal..nonPilot] DO
        IF String.Equivalent[logicalVolumeTypeString[t], inString] THEN RETURN;
        ENDLOOP;
      InputError["Illegal type"L];
      ENDLOOP;
    END;

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

    DO
      ReadName["Physical Volume Name: "L, inString, lastPvName];
      FOR i: CARDINAL IN [0..inString.length) DO
        lastPvName[i] ← inString[i]; ENDLOOP;
      lastPvName.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.Equivalent[s, inString]
          OR String.Equivalent[fullName, inString]
          OR String.Equivalent[driveString, inString];
        IF match THEN {matches ← matches + 1; id ← tmpID; drive ← h};
        ENDLOOP;
      SELECT matches FROM
        0 => FormatError["Not Found"L];
        1 => RETURN;
        ENDCASE => FormatError["Ambigous; please specify Device:PhysicalName"L];
      ENDLOOP;
    END;

  --The GetMumble routines take one kind of name for an object and convert it to another kind of name for that same object.

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

  GetDriveStringName: PROC [h: Handle] RETURNS [s: STRING] =
    BEGIN OPEN DeviceTypes;
    s ←
      SELECT GetDriveType[h] FROM
        sa800 => "Fp?",
        q2000, q2010, q2030, q2040, q2080, sa1000, sa4000, sa1004, sa4008, t80, t300 => "Rd?",
        cdc9730 => "Cd?",
        ENDCASE => "UnknownType?";
    s[s.length - 1] ← GetDriveNumber[h] + '0;
    END;

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

  GetLogicalVolumeName: PROC [vid: Volume.ID, s: LONG 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];
        s.length ← 0;
        String.AppendChar[s, '[];
        FOR i: CARDINAL 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;



  MinPilotPage: PROC [h: Handle] RETURNS [DiskPageNumber] =
    BEGIN OPEN FSa: FormatPilotDisk, DeviceTypes;
    dn: CARDINAL = GetDriveNumber[h];
    SELECT GetDriveType[h] FROM
      q2000, q2010, q2020, q2030, q2040, q2080 => RETURN[FSa.FirstQ2000PageForPilot];
      sa1000, sa1004 => RETURN[FSa.FirstSA1000PageForPilot];
      sa4000, sa4008 => RETURN[FSa.SA4000FirstPageForPilot[0]];
      t80 => RETURN[FSa.Firstt80PageForPilot];
      t300 => RETURN[FSa.Firstt300PageForPilot];
      ENDCASE => RETURN[0];
    END;

  WriteSetDebuggerSuccess: PROC [outcome: SetDebuggerSuccess] = {
    SELECT outcome FROM
      success => NULL;
      nullBootFile, cantWriteBootFile, notInitialBootFile =>
        InputError["Boot file broken."L];
      cantFindStartListHeader, startListHeaderHasBadVersion =>
        InputError["file built by incompatible version of StartPilot"L];
      noDebugger => InputError["No debugger installed."L];
      ENDCASE => ERROR};

  Init: PROC =
    BEGIN
    driveString: STRING;
    pvID: PhysicalVolume.ID;
    handle: PhysicalVolume.Handle ← PhysicalVolume.GetHandle[
      PromCommand.workingDriveIndex];
    lastDriveName ← GetDriveStringName[handle];
    pvID ← PhysicalVolume.GetHints[handle ! PhysicalVolume.Error => CONTINUE].pvID;


    << A Physical Volume Name may be 
    (1) drive name only, 
    (2) physical volume name only, or 
    (3) both 1 and 2 seperated by a colon. 
    Since the drive name is unique by itself, just use that. >>
    driveString ← GetDriveStringName[handle];

    FOR i: CARDINAL IN [0..driveString.length) DO
      lastPvName[i] ← driveString[i]; ENDLOOP;
    lastPvName.length ← driveString.length;

    lastLvName.length ← 0;
    END;  -- of Init


  <<Main>>
  Init[];


  END.

LOG [Time - Person - Action]


 6-Apr-83 16:47:36 - Thorup - Update to Sierra
 6-Sep-83 16:21:22 - Curbow - Converted to Klamath
 6-Sep-83 16:21:25 - Curbow - Deleted ChangeVolumeType
 27-Sep-83 10:16:41 - Curbow - Replaced FormatCheckDrive from Othello>OthelloDeviceImplD0DLion
 18-Oct-83 14:48:28 - Curbow - Cleanup Klamath conversion code
 1-Nov-83 10:46:27 - Curbow - Catch errors from PhysicalVolume and handle.
19-Dec-83 13:12:32 - Curbow - Convert to Services 8.0. Replace NSCommand with TextInput
13-Feb-84 16:51:45 - Curbow - Add catchphrase for AccessFloppy calls.
13-May-84 15:19:19 - Curbow - Fixed AccessFloppy.AttributesRecord being stomped (ar #6614).
12-Jun-84 12:31:55 - Curbow - Changed all Floppy.Close calls to AccessFloppy.Close so that CACHE in AccessFloppy would work correctly.
19-Jun-84 17:33:38 - Curbow - Fixed AR 7455 (Doesn't preserve create dates of installed files)
20-Jun-84 16:35:58 - Curbow - Fixed AR 8382 - Handle multiple drives correctly.
20-Jun-84 16:42:59 - Curbow - Purge log of Pre-1983 info.
28-Jun-84 14:25:45 - Curbow - Update CreateVolume to handle PhysicalVolume.NeedsScavenging the same way as Othello does.
28-Jun-84 14:25:49 - Curbow - Modify fix for AR 7455 to record the same info as Othello per Davirro.PA
28-Jun-84 17:19:52 - Curbow - Make sure that all AccessFloppy calls catch Floppy.Error in addition to AccessFloppy.Error AR #9217
23-Aug-84 11:36:28 - Masinter - add q2080