-- OthelloOpsImpl.mesa     13-Jan-84 11:13:05 by Keith

-- Public procs are first, utility procedures in a later separate section.

DIRECTORY
  Boot USING [BootFileType, Location, LVBootFiles, PVBootFiles],
  BootFile USING [
    currentVersion, Header, MapEntry, maxEntriesPerHeader, maxEntriesPerTrailer,
    Trailer],
  Buffer USING [Buffer, NSBuffer],
  Device USING [Type],
  DiskChannel USING [
    Drive, GetDriveAttributes, GetNextDrive, GetPageNumber, nullDrive],
  Environment USING [
    LongNumber, PageFromLongPointer, PageCount, PageNumber, wordsPerPage],
  File USING [
    File, GetSize, ID, MissingPages, nullFile, nullID, PageCount, PageNumber,
    Unknown],
  Inline USING [LongDivMod],
  KernelFile USING [eofLink, GetBootLocation, Link, MakeBootable, MakeUnbootable],
  KernelPhysicalVolume USING [PVHandle],
  NSConstants USING [timeServerSocket],
  NSTypes USING [],
  OthelloOps USING [
    BootFileType, nullSubVolume, SetDebuggerSuccess, SetExpirationDateSuccess,
    SetGetSwitchesSuccess, SubVolume, TimeServerErrorType],
  PhysicalVolume USING [
    GetAttributes, GetContainingPhysicalVolume, Handle, ID, InterpretHandle,
    PageNumber],
  PilotDisk USING [Address, FileID, Handle],
  ProcessorFace USING [GreenwichMeanTime, SetGreenwichMeanTime],
  Runtime USING [IsBound],
  Socket USING [
    AssignNetworkAddress, BroadcastAddressFromSocket,
    BroadcastPacketToAllConnectedNets, ChannelHandle, Create, Delete, GetPacket,
    GetPacketBytes, GetSendBuffer, NetworkAddress, ReturnBuffer, SetDestination,
    SetPacketWords, SetWaitTime, TimeOut],
  Space USING [Access, Interval, Map, PagesFromWords, Unmap],
  SpecialVolume USING [
    GetLogicalVolumeBootFiles, GetNextSubVolume, GetPhysicalVolumeBootFiles,
    nullSubVolume, SetLogicalVolumeBootFiles, SetPhysicalVolumeBootFiles,
    SubVolume, SubVolumeUnknown],
  StartList USING [BootLocation, Header, Switches, VersionID],
  System USING [
    defaultSwitches, GetGreenwichMeanTime, gmtEpoch, GreenwichMeanTime,
    LocalTimeParameters, NetworkAddress, nullID, Switches, UpDown, WestEast],
  TemporaryBooting USING [MakeBootable, MakeUnbootable],
  TemporarySetGMT USING [GetNetworkGMT, TimeZoneDirection],
  Volume USING [
    Close, GetStatus, ID, NotOnline, NotOpen, nullID, Open,
    ReadOnly, Status, Unknown];

OthelloOpsImpl: PROGRAM
  IMPORTS
    DiskChannel, Environment, File, Inline, KernelFile, PhysicalVolume,
    ProcessorFace, Runtime, Socket, Space, SpecialVolume, System,
    TemporaryBooting, TemporarySetGMT, Volume
  EXPORTS OthelloOps, PhysicalVolume  --[Handle]--
  SHARES Buffer, File =
  BEGIN OPEN OthelloOps;

  -- Time Server Constants:    THESE SHOULD BE IN NSTypes, etc.!

  TimeHeader: TYPE = MACHINE DEPENDENT RECORD [
    id1, id2: CARDINAL, clientType: CARDINAL, version: CARDINAL, type: CARDINAL];
  timeRequest: CARDINAL = 1;  -- for TimeHeader.type
  timeResponse: CARDINAL = 2;  -- for TimeHeader.type
  timeProtocolVersion: CARDINAL = 2;  -- for TimeHeader.version
  packetExchangeClient: CARDINAL = 1;  -- for TimeHeader.clientType

  WireTimeFormat: TYPE = MACHINE DEPENDENT RECORD [
    timeH(0), timeL(1): CARDINAL,
    zoneS(2): System.WestEast,
    zoneH(3): [0..177B],
    zoneM(4): [0..377B],
    beginDST(5), endDST(6): WORD,
    errorAccurate(7): BOOLEAN,
    errorLow(8), errorHigh(9): CARDINAL];


  Handle: PUBLIC --PhysicalVolume.-- TYPE = KernelPhysicalVolume.PVHandle;

  BadSwitches: PUBLIC ERROR = CODE;
  SubVolumeUnknown: PUBLIC ERROR [sv: SubVolume] = CODE;
  TimeServerError: PUBLIC ERROR [error: TimeServerErrorType] = CODE;

  StartListHeadPtr: TYPE = LONG POINTER TO StartList.Header;

  Bug: ERROR [bugType: BugType] = CODE;
  BugType: TYPE = {
    impossibleBootFileType, startListOffEndOfBootFile, whereDidTheDriveGo,
    whereDidTheLogicalVolumeGo};


  --========================================
  -- Public procedures:
  --========================================

  DecodeSwitches: PUBLIC PROC [switchString: LONG STRING]
    RETURNS [switches: System.Switches ← System.defaultSwitches] = {
    escapeCount: CARDINAL ← 0;
    setUpDown: System.UpDown ← down;
    escapeChar: CHARACTER ← 0C;

    FOR i: CARDINAL IN [0..switchString.length) DO
      c: CHARACTER = switchString[i];
      SELECT TRUE FROM
        c = '-, c = '~ => {
          IF setUpDown = up THEN ERROR BadSwitches ELSE {setUpDown ← up; LOOP}};
        c = '\\ => {
          IF escapeCount # 0 THEN ERROR BadSwitches ELSE {escapeCount ← 1; LOOP}};
        escapeCount = 1 => {
          SELECT c FROM
            'n, 'N, 'r, 'R => {switches['\n] ← setUpDown; escapeCount ← 0};
            't, 'T => {switches['\t] ← setUpDown; escapeCount ← 0};
            'b, 'B => {switches['\b] ← setUpDown; escapeCount ← 0};
            'f, 'F => {switches['\f] ← setUpDown; escapeCount ← 0};
            'l, 'L => {switches['\l] ← setUpDown; escapeCount ← 0};
            '\\ => {switches['\\] ← setUpDown; escapeCount ← 0};
            IN ['0..'7] => {
              escapeChar ← c - ('0 - 0C); escapeCount ← escapeCount + 1};
            ENDCASE => ERROR BadSwitches;
          LOOP};
        escapeCount # 0 => {
          IF c NOT IN ['0..'7] THEN ERROR BadSwitches;
          escapeChar ← (c - '0) + (escapeChar - 0C) * 8 + 0C;
          IF escapeChar > 377C THEN ERROR BadSwitches;
          IF (escapeCount ← escapeCount + 1) = 4 THEN {
            switches[escapeChar] ← setUpDown; escapeCount ← 0};
          LOOP};
        ENDCASE => switches[c] ← setUpDown;
      -- fall through to here if normal set or escape set
      -- but not on seeing or while collecting escape
      setUpDown ← down;
      ENDLOOP;
    IF escapeCount # 0 THEN ERROR BadSwitches;
    RETURN};

  VolumeNotClosed: PUBLIC ERROR = CODE;

  DeleteTempFiles: PUBLIC PROC [lvID: Volume.ID] =
    BEGIN
    status: Volume.Status = Volume.GetStatus[lvID];
    IF status = openRead OR status = openReadWrite THEN ERROR VolumeNotClosed;
    Volume.Open[lvID];  -- Deletes any temporary files.
    Volume.Close[lvID];
    END;

  GetDriveSize: PUBLIC PROC [h: Handle] RETURNS [nPages: LONG CARDINAL] = {
    RETURN[DiskChannel.GetDriveAttributes[h.drive].nPages]};

  GetNextSubVolume: PUBLIC PROC [pvID: PhysicalVolume.ID, thisSv: SubVolume]
    RETURNS [SubVolume] =
    BEGIN
    sv: SpecialVolume.SubVolume = SpecialVolume.GetNextSubVolume[
      pvID,
      IF thisSv = nullSubVolume THEN SpecialVolume.nullSubVolume
      ELSE [
        lvID: thisSv.lvID, firstLVPageNumber: thisSv.firstLVPageNumber,
        firstPVPageNumber: thisSv.firstPVPageNumber,
        subVolumeSize: thisSv.subVolumeSize] !
      SpecialVolume.SubVolumeUnknown => GOTO error];
    RETURN[
      IF sv = SpecialVolume.nullSubVolume THEN nullSubVolume
      ELSE [
        lvID: sv.lvID, subVolumeSize: sv.subVolumeSize,
        firstLVPageNumber: sv.firstLVPageNumber,
        firstPVPageNumber: sv.firstPVPageNumber]];
    EXITS error => ERROR SubVolumeUnknown[thisSv];
    END;

  GetTimeFromTimeServer: PUBLIC PROC
    RETURNS [
      serverTime: System.GreenwichMeanTime,
      serverLTPs: System.LocalTimeParameters] =
    BEGIN
    pfGMT: ProcessorFace.GreenwichMeanTime;
    isValid: BOOLEAN;
    IF Runtime.IsBound[LOOPHOLE[Socket.Create]] THEN
      [isValid, serverTime, serverLTPs] ← GetOISCPTime[]
    ELSE {
      zoneDirection: TemporarySetGMT.TimeZoneDirection;
      [networkTimeFound: isValid, timeFromNetwork: pfGMT,
        zoneDirection: zoneDirection, zone: serverLTPs.zone,
        zoneMinutes: serverLTPs.zoneMinutes, beginDST: serverLTPs.beginDST,
        endDST: serverLTPs.endDST] ← TemporarySetGMT.GetNetworkGMT[];
      serverTime ← [pfGMT];
      serverLTPs.direction ← IF zoneDirection = east THEN east ELSE west};
    IF ~isValid THEN ERROR TimeServerError[noResponse];
    END;

  IsTimeValid: PUBLIC PROC RETURNS [valid: BOOLEAN] = {
    RETURN[System.GetGreenwichMeanTime[] # System.gmtEpoch]};

  MakeBootable: PUBLIC PROC [
    file: File.File, type: BootFileType, firstPage: File.PageNumber] =
    BEGIN
    SELECT type FROM
      pilot => TemporaryBooting.MakeBootable[file, firstPage];
      ENDCASE =>
        BEGIN
        lvID: Volume.ID = file.volumeID;
        l: KernelFile.Link = KernelFile.MakeBootable[
          file: file, firstPage: firstPage, count: File.GetSize[file] - firstPage,
          lastLink: KernelFile.eofLink];
        END;
    END;

  MakeUnbootable: PUBLIC PROC [
    file: File.File, type: BootFileType, firstPage: File.PageNumber] =
    BEGIN
    SELECT type FROM
      pilot => TemporaryBooting.MakeUnbootable[file, firstPage];
      ENDCASE =>
        KernelFile.MakeUnbootable[
          file: file, firstPage: firstPage,
          count: File.GetSize[file] - firstPage];
    END;

  SetProcessorTime: PUBLIC PROC [time: System.GreenwichMeanTime] = {
    ProcessorFace.SetGreenwichMeanTime[time]};

  SetVolumeBootFile: PUBLIC PROC [
    file: File.File, type: BootFileType, firstPage: File.PageNumber] =
    BEGIN
    bootFiles: Boot.LVBootFiles;
    lvID: Volume.ID = file.volumeID;
    pilotDiskFileID: PilotDisk.FileID ← [volumeRelative[file.fileID]];
    addr: PilotDisk.Address = KernelFile.GetBootLocation[
      file, firstPage].diskAddress;
    SpecialVolume.GetLogicalVolumeBootFiles[lvID, @bootFiles];
    bootFiles[ConvertBootFileType[type]] ← [pilotDiskFileID, firstPage, addr];
    SpecialVolume.SetLogicalVolumeBootFiles[lvID, @bootFiles];
    END;

  SetPhysicalVolumeBootFile: PUBLIC PROC [
    file: File.File, type: BootFileType, firstPage: File.PageNumber] =
    BEGIN
    pilotDiskFileID: PilotDisk.FileID ← [volumeRelative[file.fileID]];
    pvID: PhysicalVolume.ID = PhysicalVolume.GetContainingPhysicalVolume[
      file.volumeID];
    addr: PilotDisk.Address = KernelFile.GetBootLocation[
      file, firstPage].diskAddress;
    pBootFiles: Boot.PVBootFiles;
    SpecialVolume.GetPhysicalVolumeBootFiles[pvID, @pBootFiles];
    pBootFiles[ConvertBootFileType[type]] ← [pilotDiskFileID, firstPage, addr];
    SpecialVolume.SetPhysicalVolumeBootFiles[pvID, @pBootFiles];
    END;

  GetVolumeBootFile: PUBLIC PROC [lvID: Volume.ID, type: BootFileType]
    RETURNS [file: File.File, firstPage: File.PageNumber] =
    BEGIN
    bootFiles: Boot.LVBootFiles;
    cType: Boot.BootFileType = ConvertBootFileType[type];
    SpecialVolume.GetLogicalVolumeBootFiles[lvID, @bootFiles];
    IF bootFiles[cType].fID.fileID = File.nullID THEN RETURN[File.nullFile, 0]
    ELSE {
      file ← [bootFiles[cType].fID.fileID, lvID];
      RETURN[file, bootFiles[cType].firstPage]};
    END;

  GetPhysicalVolumeBootFile: PUBLIC PROC [
    pvID: PhysicalVolume.ID, type: BootFileType]
    RETURNS [file: File.File, firstPage: File.PageNumber] =
    BEGIN
    cType: Boot.BootFileType = ConvertBootFileType[type];
    bootFiles: Boot.PVBootFiles;
    lvID: Volume.ID;


    -- Begin main text of GetPhysicalVolumeBootFile:
    SpecialVolume.GetPhysicalVolumeBootFiles[pvID, @bootFiles];
    IF bootFiles[cType].fID.fileID = File.nullID THEN RETURN[File.nullFile, 0]
    ELSE
      BEGIN
      -- determine what logical volume we are on given pvID and bootFiles[cType].da
      bootFilePage: PhysicalVolume.PageNumber;
      drive: DiskChannel.Drive;
      subVolume: SpecialVolume.SubVolume;
      instance: PhysicalVolume.Handle = PhysicalVolume.GetAttributes[
        pvID].instance;
      index: CARDINAL ← PhysicalVolume.InterpretHandle[instance].index;

      -- Find drive that the index corresponds to:
      FOR drive ← DiskChannel.GetNextDrive[DiskChannel.nullDrive],
        DiskChannel.GetNextDrive[drive] WHILE drive # DiskChannel.nullDrive DO
        IF DiskChannel.GetDriveAttributes[drive].deviceOrdinal = index THEN EXIT;
        REPEAT FINISHED => Bug[whereDidTheDriveGo];
        ENDLOOP;

      -- Find volume the boot file page lives in:
      bootFilePage ← DiskChannel.GetPageNumber[drive, bootFiles[cType].da];
      FOR subVolume ← SpecialVolume.GetNextSubVolume[
        pvID, SpecialVolume.nullSubVolume], SpecialVolume.GetNextSubVolume[
        pvID, subVolume] UNTIL subVolume = SpecialVolume.nullSubVolume DO
        IF bootFilePage IN
          [subVolume.firstPVPageNumber..subVolume.firstPVPageNumber +
                                          subVolume.subVolumeSize) THEN {
          lvID ← subVolume.lvID; EXIT};
        REPEAT FINISHED => Bug[whereDidTheLogicalVolumeGo];
        ENDLOOP;
      file ← [bootFiles[cType].fID.fileID, lvID];
      RETURN[file, bootFiles[cType].firstPage];
      END;
    END;  --GetPhysicalVolumeBootFile--


  VoidVolumeBootFile: PUBLIC PROC [lvID: Volume.ID, type: BootFileType] =
    BEGIN
    pBootFiles: Boot.LVBootFiles;
    SpecialVolume.GetLogicalVolumeBootFiles[lvID, @pBootFiles];
    pBootFiles[ConvertBootFileType[type]].fID.fileID ← File.nullID;
    SpecialVolume.SetLogicalVolumeBootFiles[lvID, @pBootFiles];
    END;

  VoidPhysicalVolumeBootFile: PUBLIC PROC [
    pvID: PhysicalVolume.ID, type: BootFileType] =
    BEGIN
    pBootFiles: Boot.PVBootFiles;
    SpecialVolume.GetPhysicalVolumeBootFiles[pvID, @pBootFiles];
    pBootFiles[ConvertBootFileType[type]].fID.fileID ← File.nullID;
    SpecialVolume.SetPhysicalVolumeBootFiles[pvID, @pBootFiles];
    END;

  SetDebugger: PUBLIC PROC [
    debuggeeFile: File.File, debuggeeFirstPage: File.PageNumber,
    debugger: Volume.ID, debuggerType: Device.Type, debuggerOrdinal: CARDINAL]
    RETURNS [outcome: SetDebuggerSuccess] =
    BEGIN
    ENABLE NoStartListHeader --[code]-- => {outcome ← code; CONTINUE};
    nullID: PilotDisk.FileID ← [unique[System.nullID]];
    debuggerBootFiles: Boot.LVBootFiles;
    pStartListHeader: StartListHeadPtr;
    debuggerBootFiles ← ALL[[fID: nullID, firstPage:, da:]];

    IF debugger # Volume.nullID THEN {
      SpecialVolume.GetLogicalVolumeBootFiles[debugger, @debuggerBootFiles];
      IF debuggerBootFiles[pilot].fID = nullID
        OR debuggerBootFiles[debugger].fID = nullID
        OR debuggerBootFiles[debuggee].fID = nullID THEN RETURN[noDebugger]}
    ELSE {debuggerOrdinal ← 0; debuggerType ← LOOPHOLE[0]};
    [outcome, pStartListHeader] ← MapStartListHeader[
      debuggeeFile, debuggeeFirstPage];
    IF outcome = success THEN
      BEGIN
      pStartListHeader.locDebuggerMicrocode ← LOOPHOLE[Boot.Location[
        deviceType: debuggerType, deviceOrdinal: debuggerOrdinal,
        vp: disk[debuggerBootFiles[softMicrocode]]]];
      pStartListHeader.locDebuggerGerm ← LOOPHOLE[Boot.Location[
        deviceType: debuggerType, deviceOrdinal: debuggerOrdinal,
        vp: disk[debuggerBootFiles[germ]]]];
      pStartListHeader.locDebugger ← LOOPHOLE[Boot.Location[
        deviceType: debuggerType, deviceOrdinal: debuggerOrdinal,
        vp: disk[debuggerBootFiles[debugger]]]];
      pStartListHeader.locDebuggee ← LOOPHOLE[Boot.Location[
        deviceType: debuggerType, deviceOrdinal: debuggerOrdinal,
        vp: disk[debuggerBootFiles[debuggee]]]];
      END;
    [] ← Space.Unmap[pStartListHeader];
    END;  --SetDebugger--

  SetExpirationDate: PUBLIC PROC [
    file: File.File, firstPage: File.PageNumber,
    expirationDate: System.GreenwichMeanTime]
    RETURNS [outcome: SetExpirationDateSuccess] =
    BEGIN
    ENABLE NoStartListHeader --[code]-- => {outcome ← code; CONTINUE};
    pStartListHeader: StartListHeadPtr;
    [outcome, pStartListHeader] ← MapStartListHeader[file, firstPage];
    IF outcome = success THEN pStartListHeader.expirationDate ← expirationDate;
    [] ← Space.Unmap[pStartListHeader];
    END;

  SetSwitches: PUBLIC PROC [
    file: File.File, firstPage: File.PageNumber, switches: System.Switches]
    RETURNS [outcome: SetGetSwitchesSuccess] =
    BEGIN
    ENABLE NoStartListHeader --[code]-- => {outcome ← code; CONTINUE};
    pStartListHeader: StartListHeadPtr;
    [outcome, pStartListHeader] ← MapStartListHeader[file, firstPage];
    IF outcome = success THEN
      BEGIN
      pBootHeader: LONG POINTER TO BootFile.Header;
      pStartListHeader.switches ← LOOPHOLE[switches];
      -- Note that MapStartListHeader already mapped the header once, so this
      -- Map of the bootfile header will succeed again
      pBootHeader ← Space.Map[
        window: [file, firstPage, 1], swapUnits: [unitary[]]].pointer;
      pBootHeader.switches ← LOOPHOLE[switches];
      [] ← Space.Unmap[pBootHeader];
      END;
    [] ← Space.Unmap[pStartListHeader];
    END;

  GetSwitches: PUBLIC PROC [file: File.File, firstPage: File.PageNumber]
    RETURNS [outcome: SetGetSwitchesSuccess, switches: System.Switches] =
    BEGIN
    ENABLE NoStartListHeader --[code]-- => {outcome ← code; CONTINUE};
    pStartListHeader: StartListHeadPtr;
    [outcome, pStartListHeader] ← MapStartListHeader[file, firstPage, readOnly];
    IF outcome = success THEN switches ← LOOPHOLE[pStartListHeader.switches];
    [] ← Space.Unmap[pStartListHeader];
    END;


  --========================================
  -- Private, utility procedures:
  --========================================

  ConvertBootFileType: PROC [x: BootFileType] RETURNS [Boot.BootFileType] =
    BEGIN
    SELECT x FROM
      hardMicrocode => RETURN[hardMicrocode];
      softMicrocode => RETURN[softMicrocode];
      germ => RETURN[germ];
      pilot => RETURN[pilot];
      ENDCASE => ERROR Bug[impossibleBootFileType];
    END;

  GetOISCPTime: PROC
    RETURNS [
      valid: BOOLEAN ← FALSE, time: System.GreenwichMeanTime,
      ltp: System.LocalTimeParameters] =
    BEGIN
    cH: Socket.ChannelHandle;
    id1: CARDINAL = 12345;
    id2: CARDINAL = 6789;
    target: System.NetworkAddress = Socket.BroadcastAddressFromSocket[
      NSConstants.timeServerSocket];
    timeHeader: LONG POINTER TO TimeHeader;

    cH ← Socket.Create[local: Socket.AssignNetworkAddress[], receive: 1];
    Socket.SetWaitTime[cH, 700];  -- milliseconds
    THROUGH [0..3) DO
      sendBuf: Buffer.NSBuffer ← Socket.GetSendBuffer[cH];
      Socket.SetPacketWords[sendBuf, SIZE[TimeHeader]];
      sendBuf.ns.packetType ← packetExchange;
      Socket.SetDestination[sendBuf, target];
      timeHeader ← LOOPHOLE[@sendBuf.ns.nsWords];
      timeHeader↑ ← [
        id1, id2, packetExchangeClient, timeProtocolVersion, timeRequest];
      Socket.BroadcastPacketToAllConnectedNets[cH, sendBuf];
      DO
        recBuf: Buffer.NSBuffer;
        recBuf ← Socket.GetPacket[cH ! Socket.TimeOut => EXIT];
        timeHeader ← LOOPHOLE[@recBuf.ns.nsWords];
        SELECT TRUE FROM
          Socket.GetPacketBytes[recBuf] < 2 * (SIZE[WireTimeFormat] + 1)
            OR (timeHeader.id1 # id1) OR (timeHeader.id2 # id2)
            OR (timeHeader.type # timeResponse) => LOOP;
          ENDCASE =>
            BEGIN
            wt: LONG POINTER TO WireTimeFormat = LOOPHOLE[@recBuf.ns.nsWords[
              SIZE[TimeHeader]]];
            valid ← TRUE;
            time ← LOOPHOLE[Environment.LongNumber[
              num[lowbits: wt.timeL, highbits: wt.timeH]]];
            ltp ← [
              zone: wt.zoneH, direction: wt.zoneS, zoneMinutes: wt.zoneM,
              beginDST: wt.beginDST, endDST: wt.endDST];
            Socket.ReturnBuffer[recBuf];
            GOTO done;
            END;
        ENDLOOP;
      REPEAT done => NULL;
      ENDLOOP;
    Socket.Delete[cH];
    END;  --GetOISCPTime--

  NoStartListHeader: PRIVATE ERROR [code: SetGetSwitchesSuccess] = CODE;

  MapStartListHeader: PROC [
    file: File.File, firstPage: File.PageNumber, access: Space.Access ← readWrite]
    RETURNS [outcome: SetGetSwitchesSuccess, pStartListHeader: StartListHeadPtr] =
    << Maps space (readWrite or readOnly) to the page containing the start list header
    in the boot file starting at firstPage of file, and returns a pointer to it.
    If any trouble, NoStartListHeader is raised (and the space is not mapped).>>
    BEGIN
    shortBootFile: SetGetSwitchesSuccess = SetGetSwitchesSuccess[other];
    pBootHeader: LONG POINTER TO BootFile.Header ← NIL;  -- and space.
    startListPage: Environment.PageNumber;
    offsetStartListInPage: CARDINAL;
    remainingBootFilePages: Environment.PageCount;
    headerPage: File.PageNumber;
    nEntries: CARDINAL;
    entries: LONG POINTER TO ARRAY [0..0) OF BootFile.MapEntry;
    bootFileSize: File.PageCount;
    BEGIN
    ENABLE
      UNWIND => IF pBootHeader # NIL THEN pBootHeader ← Space.Unmap[pBootHeader];

    bootFileSize ← File.GetSize[
      file !
      File.Unknown, Volume.Unknown, Volume.NotOnline, Volume.NotOpen =>
        ERROR NoStartListHeader[nullBootFile]];

    -- Map space to boot file header, get location of start list out of it:
    IF firstPage > bootFileSize THEN ERROR NoStartListHeader[shortBootFile];
    pBootHeader ← Space.Map[  -- map to header page
      window: [file, firstPage, 1], swapUnits: [unitary[]], access: access !
      Volume.ReadOnly => ERROR NoStartListHeader[cantWriteBootFile];
      File.MissingPages => ERROR NoStartListHeader[other]].mapUnit.pointer;

    IF pBootHeader.version # BootFile.currentVersion THEN
      ERROR NoStartListHeader[other];
    startListPage ← Environment.PageFromLongPointer[pBootHeader.pStartListHeader];
    offsetStartListInPage ← Inline.LongDivMod[
      num: LOOPHOLE[pBootHeader.pStartListHeader],
      den: Environment.wordsPerPage].remainder;

    -- Set up loop variables:
    remainingBootFilePages ← pBootHeader.countData;
    entries ← @pBootHeader.entries;
    nEntries ← BootFile.maxEntriesPerHeader;
    headerPage ← firstPage;
    DO  --until get to header page which describes page containing start list--
      -- At this point we have either a boot file Header or Trailer page,
      -- and are looking at the map entries in it,
      -- searching for the vm page containing the start list.
      nEntries ← CARDINAL[MIN[remainingBootFilePages, nEntries]];
      FOR k: CARDINAL IN [0..nEntries) DO
        IF entries[k].virtual = startListPage THEN
          BEGIN  -- found file page containing start list.
          startListHeaderPages: Environment.PageCount = Space.PagesFromWords[
            StartList.Header.SIZE + Environment.wordsPerPage - 1];  -- can start anywhere in page.
          pBootHeader ← Space.Unmap[pBootHeader];  -- unmap the header/trailer page.
          -- Map the start list header:
          IF headerPage + 1 + k + startListHeaderPages > bootFileSize THEN
            ERROR NoStartListHeader[shortBootFile];
          pStartListHeader ←
            Space.Map[
              window: [file, headerPage + 1 + k, startListHeaderPages],
              swapUnits: [unitary[]], access: access !
              Volume.ReadOnly => ERROR NoStartListHeader[cantWriteBootFile];
              File.MissingPages => ERROR NoStartListHeader[other]].mapUnit.pointer
              + offsetStartListInPage;
          RETURN[
            outcome:
            IF pStartListHeader.version # StartList.VersionID THEN
            startListHeaderHasBadVersion ELSE success,
              pStartListHeader: pStartListHeader];
          END;
        ENDLOOP;
      -- It's not described in this Header or Trailer page; go for the next:
      headerPage ← headerPage + 1 + nEntries;  -- step past header and data pages.
      remainingBootFilePages ← remainingBootFilePages - nEntries;
      pBootHeader ← Space.Unmap[pBootHeader];
      IF remainingBootFilePages = 0 THEN ERROR Bug[startListOffEndOfBootFile];
      IF headerPage > bootFileSize THEN ERROR NoStartListHeader[shortBootFile];
      pBootHeader ← Space.Map[  -- map to trailer page
        window: [file, headerPage, 1], swapUnits: [unitary[]], access: access !
        Volume.ReadOnly => ERROR NoStartListHeader[cantWriteBootFile];
        File.MissingPages => ERROR NoStartListHeader[other]].mapUnit.pointer;
      entries ← @LOOPHOLE[pBootHeader, LONG POINTER TO BootFile.Trailer].entries;
      nEntries ← BootFile.maxEntriesPerTrailer;
      ENDLOOP;
    END;  --ENABLE UNWIND--
    END;  --GetStartListHeader--

  END.


LOG

May 31, 80 9:54 PM   Forrest	Re-created file from combines of old VolumeImplA & B.
Jul 13, 80 8:10 PM   Forrest	Another run at the old fence.
18-Aug-81 14:36:15   Forrest	new switches.
16-Nov-81 11:38:25   pasqua	fold in bootstrap changes made by Knutsen
17-Nov-81 17:33:25   Forrest
   change GetOISCP time to not reference SocketInternal; implement SetExpirationDate
26-Jan-83 18:18:31   Johnsson	only one \ for excapes.
15-Mar-83 14:11:25   Thiara
   Update To Klamath (space stuff). Added to GetPhysicalVolumeBootFile
   to find logical volume. Changed DeleteTempFiles. 
31-Mar-83 10:51:36   Luniewski	Fold in Sierra changes:
13-Apr-83 13:34:43   pasqua
   OIS => NS. Some things in SpecialFile moved to KernelFile.
14-Apr-83 11:20:09   pasqua	SetDebugger should be PUBLIC.
22-Apr-83 14:29:12   DKnutsen
   MapStartListHeader sometimes didn't map space; and clients wrongly always Deallocated it. Reorganized MapStartListHeader. It now checks for short boot file, version mismatches, etc. and handles own error cleanups; now handles case of Header overlapping page boundary. Use Map rather than MapAt. Rearranged to public first, private later.
12-May-83 10:22:10   DKnutsen   Allow GetSwitches on readOnly volume
13-May-83 12:49:47   DKnutsen   Prev edit didn't fix second Map.
13-Jul-83 13:49:40   Luniewski	Convert to 11.0b.
16-Aug-83 16:39:38   pasqua	DeleteTempFiles did too much work.
16-Sep-83 12:54:12   marzullo
   SetSwitches did not set the switches into the bootfile header.
29-Nov-83 12:13:55   DKnutsen
   FindLogicalVolume looped for multiple drives. Uninitialized variable in DeleteTempFiles. Shoot loopholes and uninitialized warnings.
13-Jan-84 10:50:34   marzullo
   GetOISCPTime changed to new protocol.