-- PhysicalVolumeScavengerImplDorado.mesa (last edited by:  Taft on:  24-Jan-82 14:25:42)

-- LIMITING ASSUMPTIONS:

--  For the physical volume scavenge:
--   Only one page out of the set of critical physical volume pages will be
--   self-inconsistent (including unreadable) per scavenge.  This scavenger
--   cannot deal with multiple-page information loss.  For these purposes,
--   the set of critical physical volume pages is defined to be the PV root
--   page and all the marker pages on the PV.  (See ForceMutualConsistency
--   for the one situation in which this scavenger is capable of fixing more
--   than one of the set of critical physical volume pages.)
--  Similarly for the logical volume scavenge:
--   Only one page out of the set of critical logical volume pages for a
--   particular logical volume will be self-inconsistent (including
--   unreadable) per scavenge.  This scavenger cannot deal with multiple-page
--   information loss.  For these purposes, the set of critical logical
--   volume pages for a particular logical volume is defined to be its LV
--   root page and the marker page at the end of that logical volume.

-- (The above assumptions were made to allow for quick implementation for
-- Rubicon.  They should be revisited for the Trinity version and beyond.)

DIRECTORY
  DeviceTypes USING [sa4000],
  DiskChannel USING [Command, CompletionHandle, CompletionStatus, Create,
    CreateCompletionObject, Delete, DiskPageCount, GetDriveAttributes,
    Handle, InitiateIO, IORequest, IORequestHandle, nullHandle, PLabel,
    PVHandle, WaitAny],
  Environment USING [wordsPerPage],
  File USING [ID],
  FormatTrident USING [pagesReservedInPartition1], -- **** Dorado-specific ****
  Inline USING [LowHalf],
  LogicalVolume USING [currentVersion, Descriptor, Handle, LSMCurrentVersion,
    LSMSeal, maxLogicalVolumeLabelLength, nullID, nullIntervals,
    nullRootFileIDs, rootPageNumber, Seal, TreeLevel],
  MarkerPage USING [SubVolumeMarkerPage],
  PhysicalVolume USING [CanNotScavenge, Error, ErrorType, GetAttributes,
    GetNext, Handle, ID, InterpretHandle, nullID, PageNumber],
  PhysicalVolumeExtras USING [noProblems, RepairType, ScavengerStatus],
  PhysicalVolumeFormat USING [currentVersion, descriptorSize, Handle,
    maxBadPages, maxSubVols, nullBadPage, PageCount,
    physicalVolumeLabelLength, PSMCurrentVersion, PSMSeal, rootPageNumber,
    Seal, SubVolumeDesc],
  PilotDisk USING [GetLabelFilePage, Label, nullLabel, SetLabelFilePage],
  PilotFileTypes USING [PilotRootFileType, tBadPage, tFreePage,
    tLogicalVolumeRootPage, tPhysicalVolumeRootPage, tSubVolumeMarkerPage,
    tTempFileList, tVolumeAllocationMap, tVolumeFileMap],
  Space USING [Create, Delete, Error, GetHandle, GetWindow, Handle,
    LongPointer, LongPointerFromPage, Map, nullHandle, PageFromLongPointer,
    PageNumber, virtualMemory, VMPageNumber],
  SpecialSpace USING [MakeResident, MakeSwappable],
  System USING [GetUniversalID, LocalTimeParameters, nullID],
  Volume USING [Type],
  VolumeImplInterface USING [BarePvID];

PhysicalVolumeScavengerImplDorado: MONITOR
  IMPORTS DiskChannel, Inline, PhysicalVolume, PilotDisk, Space,
    SpecialSpace, System
  EXPORTS PhysicalVolume, PhysicalVolumeExtras =
  BEGIN

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Data structures
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  Marker: TYPE = RECORD [
    lvSelfConsistent, pvSelfConsistent, includeInSubset, needsScavenging:
      BOOLEAN ← FALSE,
    status: DiskChannel.CompletionStatus,
    page: PhysicalVolume.PageNumber,
    markerLabel: PilotDisk.Label,
    markerData: LONG POINTER TO MarkerPage.SubVolumeMarkerPage ← NIL,
      -- markerData = NIL => this marker record is not in use
    space: Space.Handle ← Space.nullHandle];
  Markers: TYPE = ARRAY SubVolumeRange OF Marker;
  PVRoot: TYPE = RECORD [
    pvrStatus: DiskChannel.CompletionStatus,
    pvrPage: PhysicalVolume.PageNumber,
    pvrLabel: PilotDisk.Label];
  PVRoots: TYPE = RECORD [
    pvrSelfConsistent, pvrIncludeInSubset: BOOLEAN ← FALSE,
    -- above apply only to first page (i.e. root page, not bad page table).
    pvrData: PhysicalVolumeFormat.Handle ← NIL,
    pvrSpace: Space.Handle ← Space.nullHandle,
    pages: ARRAY [0..rootPagePages) OF PVRoot];
  LVRoot: TYPE = RECORD [
    lvrSelfConsistent: BOOLEAN ← FALSE,
    lvrStatus: DiskChannel.CompletionStatus,
    lvrPage: PhysicalVolume.PageNumber,
    lvrLabel: PilotDisk.Label,
    lvrData: LogicalVolume.Handle ← NIL,
    lvrSpace: Space.Handle ← Space.nullHandle];
  SubVolumeRange: TYPE = [0..PhysicalVolumeFormat.maxSubVols);
  PageAsArray: TYPE = ARRAY [0..Environment.wordsPerPage) OF WORD;

  -- Eventually, set rootPagePages based upon the device type of a volume.
  -- When this happens, we will probably have to change the PVRoots data
  -- structure.
  rootPagePages: CARDINAL = -- WE ASSUME that the root page isn't too big
    PhysicalVolumeFormat.descriptorSize/Environment.wordsPerPage;

  -- This scavenger adds another level of retries above the driver/head
  -- retries.  Therefore, the total number of retries on an incorrigible
  -- page is (maxRetries * the number driver/head retries).
  maxRetries: CARDINAL ← 10;  -- variable to allow setting from debugger
  
  -- DiskChannel variables
  channel: DiskChannel.Handle ← DiskChannel.nullHandle;
  completionHandle: DiskChannel.CompletionHandle ←
    DiskChannel.CreateCompletionObject[];
  pvInstance: Handle;

  -- Cache the return status here (set as side effect of various procedures)
  scavengerStatus: PhysicalVolumeExtras.ScavengerStatus;

  -- global space handles for use by various procedures (avoid creating and
  -- deleting buffer spaces over and over, thus avoiding overflowing the
  -- region cache.)  NOTE:  these space handles must NOT be used by more than
  -- one procedure per possible call stack (to avoid clobbering each other).
  scratchSpace: Space.Handle ← Space.nullHandle;
  verifySpace: Space.Handle ← Space.nullHandle;  -- used only by WritePage

  -- debugging stuff
  debug: BOOLEAN ← TRUE;  -- turn on redundant debugging checks
  BugType: TYPE = {CoerceLVMarkerPageFailed, CoerceLVRootPageFailed,
    CoercePVMarkerPageFailed, CoercePVRootPageFailed, diskChannelFunny,
    FindConsistentSubsetFailed, FixInconsistentPageFailed,
    ForceMutualConsistencyFailed, HandleAnyIncorrigiblePagesFailed,
    impossibleSelectError, MakeLVConsistentAndReadableFailed,
    MakePVConsistentAndReadableFailed, markerDisappeared};
  Bug: ERROR [BugType] = CODE;

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- PhysicalVolume implementation:
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  Handle: PUBLIC TYPE = DiskChannel.PVHandle;

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- PhysicalVolumeExtras implementation:
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  Scavenge: PUBLIC ENTRY PROCEDURE
    [instance: Handle, repair: PhysicalVolumeExtras.RepairType]
    RETURNS [status: PhysicalVolumeExtras.ScavengerStatus] =
    BEGIN
    theError: PhysicalVolume.ErrorType;
    pvRoots: PVRoots;
    lvRoot: LVRoot;
    markers: Markers;
    IF PVOnline[instance] THEN
      RETURN WITH ERROR PhysicalVolume.CanNotScavenge;
    BEGIN ENABLE
      PhysicalVolume.Error => {theError ← error; GOTO PVError};
    verifySpace ← Space.Create[1, Space.virtualMemory];
    scratchSpace ← Space.Create[1, Space.virtualMemory];
    pvInstance ← instance;
    channel ← DiskChannel.Create[instance.drive, completionHandle];
    scavengerStatus ← PhysicalVolumeExtras.noProblems;
    CheckPVRootAndMarkerPages[@pvRoots, @markers, repair];
    CheckBadPageTable[@pvRoots, @markers, repair];
    IF scavengerStatus.internalStructures # damaged THEN
      -- can find LV root pages only if PV root and markers are intact
      CheckLVRootAndMarkerPages[@pvRoots, @lvRoot, @markers, repair];
    EXITS
      PVError =>
        {TidyUp[@pvRoots, @lvRoot, @markers];
	RETURN WITH ERROR PhysicalVolume.Error[theError]};
    END;  -- ENABLE scope
    TidyUp[@pvRoots, @lvRoot, @markers];
    status ← scavengerStatus;
    END;  -- Scavenge
    
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- Private internal procedures
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  CheckBadPageTable: INTERNAL PROCEDURE [pvRoots: POINTER TO PVRoots,
    markers: POINTER TO Markers, repair: PhysicalVolumeExtras.RepairType] =
    -- If the bad page table is partially or completely unreadable, resets it
    -- to empty and tries to rewrite it.  Gives up if the rewrite fails,
    -- because we cannot relocate the bad page table.  (This procedure does
    -- not try to rediscover the bad pages, but instead leaves that up to the
    -- logical volume scavenger.  If and when bad pages are marked with file
    -- type tBadPage in their labels, this procedure could conceivably scan
    -- the disk and reconstruct the bad page table, including any pages that
    -- are either unreadable or are marked as bad.)
    BEGIN
    okay: BOOLEAN ← TRUE;
    rootMemoryPage: Space.PageNumber ←
      Space.PageFromLongPointer[pvRoots.pvrData];
    FOR i: CARDINAL IN [1..rootPagePages) WHILE okay DO
      okay ← okay AND pvRoots.pages[i].pvrStatus = goodCompletion;
      ENDLOOP;
    IF okay THEN RETURN;
    IF repair = checkOnly THEN
      {scavengerStatus.badPageList ← damaged; RETURN};
    -- ASSERT:  part or all of the bad page table is unreadable.
    -- reset the bad page table to empty and rewrite it
    pvRoots.pvrData.badPageList ← ALL[PhysicalVolumeFormat.nullBadPage];
    scavengerStatus.badPageList ← lost;
    pvRoots.pvrData.badPageCount ← 0;
    FOR i: CARDINAL IN [0..pvRoots.pvrData.subVolumeCount) DO
      markers[i].needsScavenging ← TRUE;  -- to find bad pages again
      ENDLOOP;
    okay ← TRUE;
    FOR i: CARDINAL IN [0..rootPagePages) DO
      OPEN pvRoots.pages[i];
      pvrStatus ← WritePage[pvrPage, @pvrLabel, rootMemoryPage + i];
      okay ← okay AND pvrStatus = goodCompletion;
      ENDLOOP;
    IF ~okay THEN ERROR PhysicalVolume.Error[badDisk];
    END;  -- CheckBadPageTable

  CheckLVRootAndMarkerPages: INTERNAL PROCEDURE [pvRoots: POINTER TO PVRoots,
    lvRoot: POINTER TO LVRoot, markers: POINTER TO Markers,
    repair: PhysicalVolumeExtras.RepairType] =
    BEGIN OPEN pv: pvRoots.pvrData, lvRoot;
    IF pv.subVolumeCount = 0 THEN RETURN;  -- no logical volumes
    lvrSpace ← Space.Create[1, Space.virtualMemory];
    lvrData ← Space.LongPointer[lvrSpace];
    Space.Map[lvrSpace];
    FOR i: CARDINAL IN [0..pv.subVolumeCount) DO
      IF pv.subVolumes[i].lvPage = LogicalVolume.rootPageNumber THEN
        -- this subvolume contains root page
	{CollectLVPages[repair, pvRoots, lvRoot, markers, i];
	MakeLVConsistentAndReadable[repair, pvRoots, lvRoot, markers, i]};
      ENDLOOP;
    END;  -- CheckLVRootAndMarkerPages

  CheckPVRootAndMarkerPages: INTERNAL PROCEDURE [pvRoots: POINTER TO PVRoots,
    markers: POINTER TO Markers, repair: PhysicalVolumeExtras.RepairType] =
    BEGIN
    CollectPVPages[repair, pvRoots, markers];
    IF repair = checkOnly AND
      scavengerStatus # PhysicalVolumeExtras.noProblems THEN RETURN;
    MakePVConsistentAndReadable[repair, pvRoots, markers];
    END;  -- CheckPVRootAndMarkerPages

  CoerceLVMarkerPage: INTERNAL PROCEDURE [pvRoots: POINTER TO PVRoots,
    lvRoot: POINTER TO LVRoot, marker: POINTER TO Marker] =
    -- Rewrites marker page and the matching subvolume descriptor in the PV
    -- root page from the LV root page.  Gives up if the rewrite fails,
    -- since we were supposed to guarantee in the PV phase that the marker
    -- pages are all good pages.  Uses the existing label contents, since the
    -- PV phase made sure they were okay.  ASSUMES that there is only one
    -- subvolume per logical volume.
    BEGIN OPEN lvd: lvRoot.lvrData, m: marker.markerData,
      pvr: pvRoots.pages[0], LV: LogicalVolume;
    -- force volume size in LV root page to agree with marker page physical
    -- portion, in case the marker page was incorrigible and had to be moved,
    -- thereby changing the size of the logical volume.  The LV root page
    -- is guaranteed to be written later by MakeLVConsistentAndReadable in
    -- order to set the changing flag.
    lvd.volumeSize ← m.physical.descriptor.lvSize;
    m.logical ← [labelLength: lvd.labelLength, type: lvd.type,
      label: lvd.label, bootingInfo: lvd.bootingInfo,
      clientRootFile: lvd.clientRootFile]; -- pick up defaults from interface
    pvRoots.pvrData.subVolumes[m.physical.svNumber] ← m.physical.descriptor ←
      [lvID: lvd.vID, lvSize: lvd.volumeSize, lvPage: LV.rootPageNumber,
      pvPage: lvRoot.lvrPage, nPages: lvd.volumeSize];
    m.fill1 ← ALL[0];
    -- make sure the above physical subvolume changes are consistent with the
    -- other subvolumes; if they aren't, this disk has more than one damaged
    -- root or marker page, so we have to give up.
    IF ~SubVolumesOkay[pvRoots.pvrData] THEN
      ERROR PhysicalVolume.Error[badDisk];
    -- rewrite the root page
    IF (pvr.pvrStatus ← WritePage[pvr.pvrPage, @pvr.pvrLabel,
	Space.PageFromLongPointer[pvRoots.pvrData]]) # goodCompletion THEN
      ERROR PhysicalVolume.Error[badDisk];
    -- rewrite the marker page
    IF (marker.status ← WritePage[marker.page, @marker.markerLabel,
        Space.PageFromLongPointer[marker.markerData]]) # goodCompletion THEN
      ERROR PhysicalVolume.Error[badDisk];
    marker.needsScavenging ← TRUE;
    IF debug AND marker.status = goodCompletion THEN
      {ValidateLVMarkerPage[marker];
      ValidatePVMarkerPage[marker];
      IF ~marker.lvSelfConsistent OR ~marker.pvSelfConsistent THEN
        ERROR Bug[CoerceLVMarkerPageFailed]}
    ELSE marker.lvSelfConsistent ← TRUE;
    END;  -- CoerceLVMarkerPage

  CoerceLVRootPage: INTERNAL PROCEDURE [lvRoot: POINTER TO LVRoot,
    marker: POINTER TO Marker] =
    -- Rewrites LV root page from the marker page.  If rewrite fails, saves
    -- the status in lvRoot.lvrStatus and returns normally.  (Incorrigible
    -- LV root pages are handled later.  Note that the in-memory copy of the
    -- LV root page is now fine regardless of whether the rewrite succeeds.)
    BEGIN OPEN mpd: marker.markerData.physical.descriptor,
      ml: marker.markerData.logical, LV: LogicalVolume;
    -- fix the data
    lvRoot.lvrData↑ ← [vID: mpd.lvID, labelLength: ml.labelLength,
      label: ml.label, type: ml.type, volumeSize: mpd.lvSize,
      bootingInfo: ml.bootingInfo, changing: TRUE,
      clientRootFile: ml.clientRootFile];  -- uses defaults
    FindRootFileIDs[firstPage: mpd.pvPage, lastPage:
      mpd.pvPage + mpd.nPages, lvrData: lvRoot.lvrData];
    -- fix the label
    lvRoot.lvrLabel ← PilotDisk.nullLabel;  -- just to zero boot links
    lvRoot.lvrLabel ← [fileID: LOOPHOLE[mpd.lvID], filePageLo:, filePageHi:,
      immutable: FALSE, temporary: FALSE, zeroSize: FALSE,
      type: PilotFileTypes.tLogicalVolumeRootPage, bootChainLink:];
    PilotDisk.SetLabelFilePage[@lvRoot.lvrLabel, LV.rootPageNumber];
    -- rewrite the LV root page and label
    lvRoot.lvrStatus ← WritePage[mpd.pvPage, @lvRoot.lvrLabel,
      Space.PageFromLongPointer[lvRoot.lvrData]];
    IF debug AND lvRoot.lvrStatus = goodCompletion THEN
      {ValidateLVRootPage[lvRoot];
      IF ~lvRoot.lvrSelfConsistent THEN ERROR Bug[CoerceLVRootPageFailed]}
    ELSE lvRoot.lvrSelfConsistent ← TRUE;
    END;  -- CoerceLVRootPage

  CoercePVMarkerPage: INTERNAL PROCEDURE [pvRoots: POINTER TO PVRoots,
    markers: POINTER TO Markers, i: SubVolumeRange] =
    -- Rewrites marker page from PV root page.  If rewrite fails, saves the
    -- status in markers[i].status and returns normally.  (Incorrigible
    -- marker pages are handled later.  Note that the in-memory copy of the
    -- marker page is now fine regardless of whether the rewrite succeeds.)
    -- NOTE:  marker page is damaged, so from single page damage limiting
    -- assumption, we can assume the root page is undamaged.  Thus we can
    -- use the root page to rebuild the marker page.  If this is not true,
    -- give up.
    BEGIN OPEN markers[i], PVF: PhysicalVolumeFormat;
    markerID: File.ID = SELECT TRUE FROM
      pvRoots.pvrData.subVolumeMarkerID # System.nullID =>
        -- not always true with Rubicon due to bug
        pvRoots.pvrData.subVolumeMarkerID,
      i # 0 => markers[0].markerLabel.fileID,
      i = 0 AND pvRoots.pvrData.subVolumeCount > 1 =>
        markers[1].markerLabel.fileID,
      ENDCASE => [System.GetUniversalID[]];
    IF pvRoots.pvrSelfConsistent THEN
      {OPEN pvRoots.pvrData;
      markerData.physical ← [pvID: pvID, label: label,
	bootingInfo: bootingInfo, maxBadPages: maxBadPages,
	labelLength: labelLength, svNumber: i,
	descriptor: subVolumes[i]]}
    ELSE  -- single critical page damage ASSUMED, so multiple implies punt
      ERROR PhysicalVolume.Error[badDisk];
    markerData.fill2 ← ALL[0];
    markerData.checksum ← 0;
    markerLabel ← PilotDisk.nullLabel;  -- just to zero boot links
    markerLabel ← [fileID: markerID, filePageLo:, filePageHi:,
      immutable: FALSE, temporary: FALSE, zeroSize: FALSE,
      type: PilotFileTypes.tSubVolumeMarkerPage, bootChainLink:];
    PilotDisk.SetLabelFilePage[@markerLabel, page];
    status ← WritePage[page, @markerLabel, Space.PageFromLongPointer[
      markerData]];
    needsScavenging ← TRUE;  -- mark the logical volume for scavenging
    IF debug AND status = goodCompletion THEN
      {ValidatePVMarkerPage[@markers[i]];
      IF ~markers[i].pvSelfConsistent THEN
        ERROR Bug[CoercePVMarkerPageFailed]}
    ELSE markers[i].pvSelfConsistent ← TRUE;
    END;  -- CoercePVMarkerPage

  CoercePVRootPage: INTERNAL PROCEDURE
    [pvRoots: POINTER TO PVRoots, markers: POINTER TO Markers] =
    -- Rewrites the PV root page from the marker pages.  Forces a LV scavenge
    -- on all the subvolumes on this PV to be safe, although this is not
    -- strictly necessary.  If rewrite fails, gives up and raises badDisk
    -- error since we cannot relocate the root page.
    -- NOTE:  the root page is damaged, so from single page damage limiting
    -- assumption, we can assume the marker pages are undamaged.  Thus we
    -- were able to find them all and can use them to rebuild the root page.
    -- If this is not true, give up and raise badDisk error.
    -- NOTE:  this procedure must NOT be called if no marker pages were
    -- found.
    BEGIN OPEN pvRoots, PVF: PhysicalVolumeFormat;
    barePvID: VolumeImplInterface.BarePvID;
    badPageListReadable: BOOLEAN ← TRUE;
    badPageCounter: CARDINAL ← 0;
    -- zero out the PV root page first to initialize subVolumes array
    -- (doesn't affect bad page table)
    p: LONG POINTER TO PageAsArray ← LOOPHOLE[pvRoots.pvrData];
    p↑ ← ALL[0];
    BEGIN OPEN markers[0].markerData.physical;
    -- use the first marker page to fix up the global PV fields
    pvrData↑ ← [labelLength: labelLength, pvID: pvID,
      bootingInfo: bootingInfo, label: label, subVolumeCount:,
      subVolumeMarkerID: markers[0].markerLabel.fileID,
      subVolumes:, badPageList:];  -- pick up defaults from interface
    END;  -- markers[0].markerData.physical scope
    -- count bad pages (if possible) and correct count in root page
    FOR i: CARDINAL IN [1..rootPagePages) DO
      badPageListReadable ← badPageListReadable AND
        pvRoots.pages[i].pvrStatus = goodCompletion;
      ENDLOOP;
    IF badPageListReadable THEN
      FOR i: CARDINAL IN [0..  -- ASSUME maxBadPages is <= LAST[CARDINAL]
        LOOPHOLE[Inline.LowHalf[pvrData.maxBadPages], CARDINAL])
        UNTIL pvrData.badPageList[i] = PVF.nullBadPage DO
        badPageCounter ← badPageCounter + 1;
        ENDLOOP;
    pvrData.badPageCount ← badPageCounter;
    -- collect subvolume descriptors from marker pages
    FOR i: CARDINAL IN SubVolumeRange WHILE markers[i].markerData # NIL DO
      IF ~markers[i].pvSelfConsistent THEN
        ERROR PhysicalVolume.Error[badDisk];
      markers[i].needsScavenging ← TRUE;  -- force LV scavenge to be safe
      pvrData.subVolumeCount ← i + 1;
      pvrData.subVolumes[markers[i].markerData.physical.svNumber] ←
        markers[i].markerData.physical.descriptor;
      -- note that a missing marker page will make it impossible to rebuild
      -- the PV root page correctly, since there will be an empty subvolume
      -- descriptor in the PV root page subvolume array
      ENDLOOP;
    -- reset the label
    pages[0].pvrLabel ← PilotDisk.nullLabel;  -- just to zero boot links
    barePvID ← markers[0].markerData.physical.pvID;  -- avoid LOOPHOLE below
    pages[0].pvrLabel ← [fileID: [barePvID], filePageLo:, filePageHi:,
      immutable: FALSE, temporary: FALSE, zeroSize: FALSE,
      type: PilotFileTypes.tPhysicalVolumeRootPage, bootChainLink:];
    PilotDisk.SetLabelFilePage[@pages[0].pvrLabel, pages[0].pvrPage];
    -- rewrite the root page and label
    pages[0].pvrStatus ← WritePage[pages[0].pvrPage, @pages[0].pvrLabel,
      Space.PageFromLongPointer[pvrData]];
    IF pages[0].pvrStatus # goodCompletion THEN
      ERROR PhysicalVolume.Error[badDisk];
    IF debug THEN
      IF ~ValidatePVRootPage[pvRoots] THEN ERROR Bug[CoercePVRootPageFailed]
    ELSE pvrSelfConsistent ← TRUE;
    END;  -- CoercePVRootPage

  CollectLVPages: INTERNAL PROCEDURE [
    repair: PhysicalVolumeExtras.RepairType, pvRoots: POINTER TO PVRoots,
    lvRoot: POINTER TO LVRoot, markers: POINTER TO Markers,
    svIndex: CARDINAL] =
    BEGIN
    m: CARDINAL = FindMatchingMarker[markers, svIndex];
    ReadAndCheckLVRootPage[pvRoots, lvRoot, svIndex, repair];
    ValidateLVMarkerPage[@markers[m]];  -- marker read earlier for PV checks
    END;  -- CollectLVPages

  CollectPVPages: INTERNAL PROCEDURE [
    repair: PhysicalVolumeExtras.RepairType, pvRoots: POINTER TO PVRoots,
    markers: POINTER TO Markers] =
    BEGIN
    IF PVRootPageOkay[pvRoots, repair] THEN
      {FOR i: CARDINAL IN [0..pvRoots.pvrData.subVolumeCount) DO
        OPEN pvRoots.pvrData.subVolumes[i];
        ReadAndCheckMarkerPage[pvPage + nPages, @markers[i], repair];
        ENDLOOP;
      RETURN}
    ELSE IF repair = checkOnly THEN
      {scavengerStatus.internalStructures ← damaged; RETURN}
    ELSE ScanForMarkers[markers, repair];
    END;  -- CollectPVPages

  FindConsistentSubset: INTERNAL PROCEDURE
    [pvRoots: POINTER TO PVRoots, markers: POINTER TO Markers]
    RETURNS [mutuallyConsistent: BOOLEAN] =
    -- Tries to find an n-1 mutually-consistent subset of the n root and
    -- marker pages.  At least one page is known to be mutually inconsistent
    -- with the others, but all the root and marker pages are known to be
    -- self-consistent.  If such a subset can be found, the one disagreeable
    -- page is updated to agree with the others and success is indicated.
    -- Otherwise, failure is indicated.
    -- NOTE:  If one root or marker page is NOT self-consistent, then either
    -- ForceMutualConsistency is called (if the self-consistent pages are
    -- mutually INconsistent), or FixInconsistentPage is called (if the
    -- self-consistent pages are mutually consistent) instead of this
    -- procedure.
    BEGIN
    mutuallyConsistent ← FALSE;
    -- Try throwing out one marker page at a time first.
    FOR i: CARDINAL IN SubVolumeRange WHILE ~mutuallyConsistent DO
      OPEN markers[i];
      IF markerData = NIL THEN LOOP;
      includeInSubset ← FALSE;
      IF (mutuallyConsistent ← MutualConsistencyCheck[pvRoots, markers]) THEN
        CoercePVMarkerPage[pvRoots, markers, i];
      includeInSubset ← TRUE;
      ENDLOOP;
    IF ~mutuallyConsistent THEN
    -- No one marker page is causing the inconsistency; try throwing out the
    -- root page.
      {pvRoots.pvrIncludeInSubset ← FALSE;
      IF (mutuallyConsistent ← MutualConsistencyCheck[pvRoots, markers]) THEN
        CoercePVRootPage[pvRoots, markers];
      pvRoots.pvrIncludeInSubset ← TRUE};
    IF debug AND mutuallyConsistent THEN
      IF ~MutualConsistencyCheck[pvRoots, markers] THEN
        ERROR Bug[FindConsistentSubsetFailed];
    END;  -- FindConsistentSubset

  FindMatchingMarker: INTERNAL PROCEDURE
    [markers: POINTER TO Markers, svIndex: CARDINAL]
    RETURNS [markerIndex: CARDINAL] =
    -- Returns the index into the marker array for the marker corresponding
    -- to the subvolume numbered svIndex.  This procedure is called only
    -- after the PV root page and marker pages have been found (or made)
    -- to be mutually consistent, so the corresponding marker should always
    -- be found.  Failure to find it indicates a bug.
    BEGIN
    markerIndex ← LAST[CARDINAL];
    FOR i: CARDINAL IN SubVolumeRange WHILE markerIndex = LAST[CARDINAL] DO
      IF markers[i].markerData # NIL AND
          markers[i].markerData.physical.svNumber = svIndex THEN
	markerIndex ← i;
      ENDLOOP;
    IF markerIndex = LAST[CARDINAL] THEN ERROR Bug[markerDisappeared];
    END;  -- FindMatchingMarker

  FindNextMarkerPage: INTERNAL PROCEDURE
    [firstPage, lastPage: PhysicalVolume.PageNumber,
    marker: POINTER TO Marker, repair: PhysicalVolumeExtras.RepairType]
    RETURNS [found: BOOLEAN, page: PhysicalVolume.PageNumber] =
    -- Scans the disk from firstPage to lastPage, stopping if it finds a
    -- marker page.  Sets up marker record for the marker page and checks
    -- the marker page for internal consistency.
    -- The return value 'page' is valid only if a marker page was found,
    -- (i.e. found = TRUE).  In that case, it is the page AFTER the marker
    -- page just found.
    -- NOTE:  this procedure takes advantage of a hack in the disk driver
    -- to speed up the disk scan.  The driver does not retry a label verify
    -- error if it occurs on any page other than the first in the run of
    -- pages specified in the IORequest.  This makes it possible to find the
    -- end of the next page group at disk speed without having a bunch of
    -- retries each time.  See also LabelTransfer.VerifyLabels and the
    -- logical volume scavenger.
    BEGIN
    status: DiskChannel.CompletionStatus;
    markerMemoryPage: Space.PageNumber = Space.VMPageNumber[scratchSpace];
    pageGroupSize: DiskChannel.DiskPageCount ← 1;
    [] ← Space.GetWindow[scratchSpace !  -- map if not already mapped
      Space.Error =>
        {IF type # noWindow THEN REJECT;  -- unexpected error type
	Space.Map[scratchSpace]; CONTINUE}];
    found ← FALSE;
    FOR page ← firstPage, page + pageGroupSize UNTIL found OR
      page > lastPage DO
      status ← ReadPage[page, @marker.markerLabel, markerMemoryPage, repair,
        FALSE];
      SELECT status FROM
        goodCompletion, dataError =>
	  IF marker.markerLabel.type = PilotFileTypes.tSubVolumeMarkerPage
	    THEN
	    {found ← TRUE;
	    marker.status ← status;
	    marker.page ← page;
	    pageGroupSize ← 0}
	  ELSE pageGroupSize ← IF status = dataError THEN
	    1  -- skip over bad page
	    ELSE TransferPageRun[page, vvr, @marker.markerLabel,
	      markerMemoryPage, lastPage - page + 1, TRUE].countValid;
	ENDCASE => pageGroupSize ← 1;
      ENDLOOP;
    IF found THEN
      {page ← marker.page + 1;
      marker.space ← scratchSpace;
      scratchSpace ← Space.Create[1, Space.virtualMemory];
      ValidatePVMarkerPage[marker]};
    END;  -- FindNextMarkerPage

  FindRootFileIDs: INTERNAL PROCEDURE
    [firstPage, lastPage: PhysicalVolume.PageNumber,
    lvrData: LogicalVolume.Handle] =
    -- Scans the logical volume from firstPage to LastPage, checking the file
    -- type of each page group.  The file ID of the FIRST page group
    -- encountered of each PilotRootFileType is entered in the LV root page
    -- rootFileID array.  Generates new IDs for the free page, VAM, and VFM
    -- volume root files if any of them is not found on the scan, or is
    -- non-unique.  This procedure should not be called if repair =
    -- checkOnly.
    -- NOTE:  this procedure takes advantage of a hack in the disk driver
    -- to speed up the disk scan.  The driver does not retry a label verify
    -- error if it occurs on any page other than the first in the run of
    -- pages specified in the IORequest.  This makes it possible to find the
    -- end of the next page group at disk speed without having a bunch of
    -- retries each time.  See also LabelTransfer.VerifyLabels and the
    -- logical volume scavenger.
    BEGIN
    page: PhysicalVolume.PageNumber;
    scratchLabel: PilotDisk.Label;
    status: DiskChannel.CompletionStatus;
    memoryPage: Space.PageNumber = Space.VMPageNumber[scratchSpace];
    pageGroupSize: DiskChannel.DiskPageCount ← 1;
    [] ← Space.GetWindow[scratchSpace !  -- map if not already mapped
      Space.Error =>
        {IF type # noWindow THEN REJECT;  -- unexpected error type
	Space.Map[scratchSpace]; CONTINUE}];
    FOR page ← firstPage, page + pageGroupSize UNTIL page > lastPage DO
      status ← ReadPage[page, @scratchLabel, memoryPage, safeRepair, FALSE];
      SELECT status FROM
        goodCompletion, dataError =>
	  {IF scratchLabel.type IN PilotFileTypes.PilotRootFileType AND
	      lvrData.rootFileID[scratchLabel.type] = LogicalVolume.nullID
	      THEN
	    lvrData.rootFileID[scratchLabel.type] ← scratchLabel.fileID;
	  pageGroupSize ← IF status = dataError THEN 1  -- skip over bad page
	    ELSE TransferPageRun[page, vvr, @scratchLabel, memoryPage,
	      lastPage - page + 1, TRUE].countValid};
	ENDCASE => pageGroupSize ← 1;
      ENDLOOP;
    IF ~ValidVolumeRootFiles[lvrData] THEN
      {OPEN lvrData, PFT: PilotFileTypes;
      rootFileID[PFT.tFreePage] ← [System.GetUniversalID[]];
      rootFileID[PFT.tVolumeAllocationMap] ← [System.GetUniversalID[]];
      rootFileID[PFT.tVolumeFileMap] ← [System.GetUniversalID[]]};
    END;  -- FindRootFileIDs

  FixInconsistentPage: INTERNAL PROCEDURE
    [pvRoots: POINTER TO PVRoots, markers: POINTER TO Markers] =
    -- coerce the self-INconsistent page to agree with the others and try to
    -- write it out to the disk.  This procedure assumes (and checks to make
    -- sure) that there is only ONE self-inconsistent page out of the
    -- collection of marker pages and the PV root page.
    BEGIN
    damagedPageAlreadyFound: BOOLEAN;
    IF (damagedPageAlreadyFound ← ~pvRoots.pvrSelfConsistent) THEN
      CoercePVRootPage[pvRoots, markers];
    FOR i: CARDINAL IN [0..pvRoots.pvrData.subVolumeCount) DO
      IF damagedPageAlreadyFound AND ~markers[i].pvSelfConsistent THEN
	ERROR PhysicalVolume.Error[badDisk];
      IF (damagedPageAlreadyFound ← ~markers[i].pvSelfConsistent) THEN
	CoercePVMarkerPage[pvRoots, markers, i];
      ENDLOOP;
    IF debug THEN
      IF ~MutualConsistencyCheck[pvRoots, markers] THEN
        ERROR Bug[FixInconsistentPageFailed];
    END;  -- FixInconsistentPage

  ForceMutualConsistency: INTERNAL PROCEDURE
    [pvRoots: POINTER TO PVRoots, markers: POINTER TO Markers] =
    -- Uses the root page to restore and rewrite all the marker pages.
    -- If the root page is unreadable or inconsistent itself, gives up and
    -- raises PhysicalVolume.Error[badDisk].  (Note that this guy is not
    -- called unless multiple pages have problems.  If only the root page is
    -- damaged, for instance, FindConsistentSubset will have fixed it up from
    -- the good marker pages.  The assumption is that we crashed in the
    -- middle of updating the bootingInfo in the marker pages from the root 
    -- page.  This could leave at most one marker page unreadable and two
    -- consistent subsets of self-consistent root/marker pages.  One subset
    -- contains the old (out of date) bootingInfo, and the other subset
    -- (including the root page) contains the new bootingInfo.)
    BEGIN OPEN pvRoots.pvrData;
    IF ~pvRoots.pvrSelfConsistent THEN ERROR PhysicalVolume.Error[badDisk];
    IF subVolumeMarkerID = System.nullID THEN
      subVolumeMarkerID ← [System.GetUniversalID[]];
    FOR i: CARDINAL IN [0..subVolumeCount) DO
      markers[i].page ← subVolumes[i].pvPage + subVolumes[i].nPages;
      CoercePVMarkerPage[pvRoots, markers, i];
      ENDLOOP;
    IF debug THEN
      IF ~MutualConsistencyCheck[pvRoots, markers] THEN
        ERROR Bug[ForceMutualConsistencyFailed];
    END;  -- ForceMutualConsistency

  FullLVConsistencyCheck: INTERNAL PROCEDURE
    [lvRoot: POINTER TO LVRoot, marker: POINTER TO Marker]
    RETURNS [mutuallyConsistent: BOOLEAN] =
    -- Checks for mutual consistency between a logical volume root page and
    -- the subvolume marker page at the end of that logical volume.  ASSUMES
    -- there is only one subvolume per logical volume.  (This will be true
    -- until logical volumes spanning physical volumes are implemented, or
    -- until a means of extending logical volumes is implemented, if ever.)
    BEGIN
    BEGIN OPEN ml: marker.markerData.logical, lv: lvRoot.lvrData;
    -- make sure both the LV root and marker page are self-consistent, and
    -- check stuff in logical volume portion of marker page
    mutuallyConsistent ← marker.lvSelfConsistent AND
      lvRoot.lvrSelfConsistent AND ml.labelLength = lv.labelLength AND
      ml.type = lv.type AND ml.label = lv.label AND
      ml.bootingInfo = lv.bootingInfo AND
      ml.clientRootFile = lv.clientRootFile;
    END;  -- ml and lv scope
    BEGIN OPEN mpd: marker.markerData.physical.descriptor,
      lv: lvRoot.lvrData;
    -- also check stuff in physical volume portion of marker page
    mutuallyConsistent ← mutuallyConsistent AND
      mpd.lvID = lv.vID AND mpd.lvSize = lv.volumeSize AND
      mpd.nPages = lv.volumeSize AND
      mpd.pvPage = lvRoot.lvrPage;
    END;  -- mpd and lv scope
    END;  -- FullLVConsistencyCheck

  FullPVConsistencyCheck: INTERNAL PROCEDURE
    [pvRoots: POINTER TO PVRoots, markers: POINTER TO Markers]
    RETURNS [countSelfInconsistent: CARDINAL, mutuallyConsistent: BOOLEAN] =
    -- Returns in countSelfInconsistent the count of PV root and marker pages
    -- that are self-INconsistent (including unreadable).  Checks mutual
    -- consistency of ONLY the self-consistent pages and returns the result
    -- in mutuallyConsistent.
    BEGIN
    countSelfInconsistent ← IF pvRoots.pvrSelfConsistent THEN 0 ELSE 1;
    FOR i: CARDINAL IN SubVolumeRange UNTIL markers[i].markerData = NIL DO
      countSelfInconsistent ← countSelfInconsistent +
        (IF markers[i].pvSelfConsistent THEN 0 ELSE 1);
      ENDLOOP;
    mutuallyConsistent ← MutualConsistencyCheck[pvRoots, markers];
    END;  -- FullPVConsistencyCheck

  HandleAnyIncorrigiblePages: INTERNAL PROCEDURE
    [pvRoots: POINTER TO PVRoots, markers: POINTER TO Markers,
    repair: PhysicalVolumeExtras.RepairType] =
    BEGIN
    IF pvRoots.pages[0].pvrStatus # goodCompletion THEN
      ERROR PhysicalVolume.Error[badDisk];  -- cannot replace PV root page
    FOR i: CARDINAL IN [0..pvRoots.pvrData.subVolumeCount) DO
      OPEN markers[i];
      IF status # goodCompletion THEN
        IF repair = riskyRepair THEN
          {ReplaceIncorrigibleMarker[pvRoots, @markers[i]];
	  -- note for MakeLVConsistentAndReadable that this logical volume
	  -- will need scavenging
	  needsScavenging ← TRUE}
	ELSE scavengerStatus.internalStructures ← damaged;
      ENDLOOP;
    IF debug THEN
      IF scavengerStatus.internalStructures # damaged AND
          ~MutualConsistencyCheck[pvRoots, markers] THEN
        ERROR Bug[HandleAnyIncorrigiblePagesFailed];
    END;  -- HandleAnyIncorrigiblePages

  MakeLVConsistentAndReadable: INTERNAL PROCEDURE [
    repair: PhysicalVolumeExtras.RepairType, pvRoots: POINTER TO PVRoots,
    lvRoot: POINTER TO LVRoot, markers: POINTER TO Markers,
    svIndex: CARDINAL] =
    -- Makes the LV root page readable and consistent with its marker page.
    -- Also sets the changing flag if this scavenger has decided that the LV
    -- needs scavenging.  Moves the LV root page and sets the changing flag
    -- if the original is incorrigible.  Nulls out the tTempFileList entry in
    -- the rootFileID array so that the LV scavenger won't trip over a
    -- missing or unreadable page in the temp file list file when it creates
    -- its log file.  (This forces the old kind of temp file deletion (the
    -- scan-the-volume-for-temp-files kind) the next time each logical volume
    -- is opened.  The added cost is not as important as the added benefit of
    -- being able to recover from a damaged temp file list file.)
    BEGIN
    m: CARDINAL = FindMatchingMarker[markers, svIndex];
    IF ~FullLVConsistencyCheck[lvRoot, @markers[m]] THEN
      {IF repair = checkOnly THEN
        {scavengerStatus.internalStructures ← damaged; RETURN};
      IF markers[m].lvSelfConsistent THEN
        -- the marker page is less volatile than the LV root page, so use it
	-- to fix the LV root page if it is self-consistent
        CoerceLVRootPage[lvRoot, @markers[m]]
      ELSE IF lvRoot.lvrSelfConsistent THEN
        -- otherwise fix the marker page from the LV root page if it is self-
        -- consistent
        CoerceLVMarkerPage[pvRoots, lvRoot, @markers[m]]
      ELSE ERROR PhysicalVolume.Error[badDisk]};
    IF repair = checkOnly THEN RETURN;
    IF lvRoot.lvrStatus # goodCompletion OR markers[m].needsScavenging THEN
      lvRoot.lvrData.changing ← TRUE;  -- force a logical volume scavenge
    lvRoot.lvrData.rootFileID[PilotFileTypes.tTempFileList] ←
        LogicalVolume.nullID;  -- just in case this file is damaged
    IF lvRoot.lvrStatus # goodCompletion THEN
      IF repair = riskyRepair THEN
        ReplaceIncorrigibleLVRoot[lvRoot, pvRoots, markers, svIndex]
      ELSE scavengerStatus.internalStructures ← damaged
    ELSE IF WritePage[lvRoot.lvrPage, @lvRoot.lvrLabel,
        Space.PageFromLongPointer[lvRoot.lvrData]] #
      goodCompletion THEN ERROR PhysicalVolume.Error[badDisk];
    IF debug THEN
      IF scavengerStatus.internalStructures # damaged AND
          ~FullLVConsistencyCheck[lvRoot, @markers[m]] THEN
        ERROR Bug[MakeLVConsistentAndReadableFailed];
    END;  -- MakeLVConsistentAndReadable

  MakePVConsistentAndReadable: INTERNAL PROCEDURE [
    repair: PhysicalVolumeExtras.RepairType, pvRoots: POINTER TO PVRoots,
    markers: POINTER TO Markers] =
    BEGIN
    countSelfInconsistent: CARDINAL;
    mutuallyConsistent: BOOLEAN;
    [countSelfInconsistent: countSelfInconsistent, mutuallyConsistent:
      mutuallyConsistent] ← FullPVConsistencyCheck[pvRoots, markers];
    IF countSelfInconsistent = 0 AND mutuallyConsistent THEN RETURN;
    IF repair = checkOnly THEN
      {scavengerStatus.internalStructures ← damaged; RETURN};
    IF countSelfInconsistent > 1 THEN ERROR PhysicalVolume.Error[badDisk];
    -- ASSERT:  countSelfInconsistent <= 1.
    SELECT TRUE FROM
      mutuallyConsistent AND countSelfInconsistent = 1 =>
        FixInconsistentPage[pvRoots, markers];
      ~mutuallyConsistent AND countSelfInconsistent = 1 =>
        ForceMutualConsistency[pvRoots, markers];
      ~mutuallyConsistent AND countSelfInconsistent = 0 =>
        IF ~FindConsistentSubset[pvRoots, markers] THEN
          ForceMutualConsistency[pvRoots, markers];
      ENDCASE => ERROR Bug[MakePVConsistentAndReadableFailed];
    -- ASSERT:  all self-consistent PV root and marker pages are now mutually
    -- consistent in memory, although one or more may have been unwritable
    -- on the disk.
    HandleAnyIncorrigiblePages[pvRoots, markers, repair];
    IF debug THEN
      {IF scavengerStatus.internalStructures = damaged THEN RETURN;
      [countSelfInconsistent: countSelfInconsistent, mutuallyConsistent:
        mutuallyConsistent] ← FullPVConsistencyCheck[pvRoots, markers];
      IF countSelfInconsistent # 0 OR ~mutuallyConsistent THEN
        ERROR Bug[MakePVConsistentAndReadableFailed]};
    END;  -- MakePVConsistentAndReadable

  MutualConsistencyCheck: INTERNAL PROCEDURE
    [pvRoots: POINTER TO PVRoots, markers: POINTER TO Markers]
    RETURNS [mutuallyConsistent: BOOLEAN] =
    -- Checks for mutual consistency between a subset of the PV root page and
    -- the physical portion of the marker pages.
    BEGIN
    alreadyFound: ARRAY SubVolumeRange OF BOOLEAN ← ALL[FALSE];
    iSubVolEnd, jSubVolEnd: PhysicalVolume.PageNumber;
    mutuallyConsistent ← TRUE;
    IF pvRoots.pvrIncludeInSubset THEN
      FOR i: CARDINAL IN SubVolumeRange WHILE mutuallyConsistent DO
        OPEN iSV: markers[i].markerData.physical,
	  iSVD: markers[i].markerData.physical.descriptor,
	  r: pvRoots.pvrData;
	IF ~markers[i].includeInSubset THEN LOOP;
	mutuallyConsistent ← iSV.pvID = r.pvID AND
	  iSV.label = r.label AND
	  iSV.bootingInfo = r.bootingInfo AND
	  iSV.maxBadPages = r.maxBadPages AND
	  iSV.labelLength = r.labelLength AND
	  iSVD = r.subVolumes[iSV.svNumber] AND
	  ~alreadyFound[iSV.svNumber];
	alreadyFound[iSV.svNumber] ← TRUE;
	-- no need to check explicitly for disjointness of subvolumes since
	-- the entries in the root page subvolume array have already been
	-- checked for that.
	ENDLOOP
    ELSE -- root page not included in subset being tested
      {baseMarker: CARDINAL;
      baseMarkerFound: BOOLEAN ← FALSE;
      FOR i: CARDINAL IN SubVolumeRange WHILE mutuallyConsistent DO
        OPEN iSV: markers[i].markerData.physical,
	  iSVD: markers[i].markerData.physical.descriptor,
	  baseSV: markers[baseMarker].markerData.physical;
	IF ~markers[i].includeInSubset THEN LOOP;
	IF ~baseMarkerFound THEN
	  {baseMarkerFound ← TRUE; baseMarker ← i; LOOP};
	mutuallyConsistent ← iSV.pvID = baseSV.pvID AND
	  iSV.label = baseSV.label AND
	  iSV.bootingInfo = baseSV.bootingInfo AND
	  iSV.maxBadPages = baseSV.maxBadPages AND
	  iSV.labelLength = baseSV.labelLength;
	iSubVolEnd ← iSVD.pvPage + iSVD.nPages;
	FOR j: CARDINAL IN [baseMarker..i) WHILE mutuallyConsistent DO
	  OPEN jSV: markers[j].markerData.physical,
	    jSVD: markers[j].markerData.physical.descriptor;
          IF ~markers[j].includeInSubset THEN LOOP;
	  jSubVolEnd ← jSVD.pvPage + jSVD.nPages;
	  mutuallyConsistent ← -- check for disjointness of subvolumes
	    ~(jSVD.pvPage >= iSVD.pvPage AND jSVD.pvPage <= iSubVolEnd) AND
	    ~(jSubVolEnd >= iSVD.pvPage AND jSubVolEnd <= iSubVolEnd) AND
	    ~(iSV.svNumber = jSV.svNumber);
	  ENDLOOP;
	ENDLOOP};
    END;  -- MutualConsistencyCheck

  PVOnline: INTERNAL PROCEDURE [instance: Handle]
    RETURNS [online: BOOLEAN] =
    BEGIN
    pvID: PhysicalVolume.ID ← PhysicalVolume.nullID;
    online ← FALSE;
    FOR pvID ← PhysicalVolume.GetNext[pvID], PhysicalVolume.GetNext[pvID]
	WHILE pvID # PhysicalVolume.nullID AND ~online DO
      IF instance = PhysicalVolume.GetAttributes[pvID].instance THEN
        online ← TRUE;
      ENDLOOP;
    END;  -- PVOnline

  PVRootPageOkay: INTERNAL PROCEDURE [pvRoots: POINTER TO PVRoots,
    repair: PhysicalVolumeExtras.RepairType] 
    RETURNS [okay: BOOLEAN] =
    -- Reads PV root pages into memory and then validates the first one, the
    -- root page itself.  (The other page is the bad page list which is
    -- checked later.  Note that if the bad page list itself is unreadable,
    -- ReadPage is not allowed to rewrite it, because we want
    -- CheckBadPageTable to write out an empty bad page table in that case.)
    BEGIN
    rootMemoryPage: Space.PageNumber;
    pvRoots.pvrSpace ← Space.Create[rootPagePages, Space.virtualMemory];
    Space.Map[pvRoots.pvrSpace];
    pvRoots.pvrData ← Space.LongPointer[pvRoots.pvrSpace];
    rootMemoryPage ← Space.PageFromLongPointer[pvRoots.pvrData];
    FOR i: CARDINAL IN [0..rootPagePages) DO
      -- must read one page at a time in order to collect all the labels
      OPEN r: pvRoots.pages[i];
      r.pvrPage ← PhysicalVolumeFormat.rootPageNumber + i;
      r.pvrStatus ← ReadPage[r.pvrPage, @r.pvrLabel, rootMemoryPage + i,
        repair, r.pvrPage = PhysicalVolumeFormat.rootPageNumber];
      ENDLOOP; 
    okay ← ValidatePVRootPage[pvRoots];
    END;  -- PVRootPageOkay

  ReadAndCheckLVRootPage: INTERNAL PROCEDURE [pvRoots: POINTER TO PVRoots,
    lvRoot: POINTER TO LVRoot, svIndex: CARDINAL,
    repair: PhysicalVolumeExtras.RepairType] =
    BEGIN OPEN lvRoot;
    lvrMemoryPage: Space.PageNumber = Space.PageFromLongPointer[lvrData];
    lvrPage ← pvRoots.pvrData.subVolumes[svIndex].pvPage;
    lvrStatus ← ReadPage[lvrPage, @lvrLabel, lvrMemoryPage, repair, TRUE];
    ValidateLVRootPage[lvRoot];
    END;  -- ReadAndCheckLVRootPage

  ReadAndCheckMarkerPage: INTERNAL PROCEDURE
    [page: PhysicalVolume.PageNumber, marker: POINTER TO Marker,
    repair: PhysicalVolumeExtras.RepairType] =
    BEGIN
    markerMemoryPage: Space.PageNumber;
    marker.space ← Space.Create[1, Space.virtualMemory];
    markerMemoryPage ← Space.VMPageNumber[marker.space];
    Space.Map[marker.space];
    marker.page ← page;
    marker.status ← ReadPage[page, @marker.markerLabel, markerMemoryPage,
      repair, TRUE];
    ValidatePVMarkerPage[marker];
    END;  -- ReadAndCheckMarkerPage

  ReadPage: INTERNAL PROCEDURE [page: PhysicalVolume.PageNumber,
    pLabel: DiskChannel.PLabel, memoryPage: Space.PageNumber,
    repair: PhysicalVolumeExtras.RepairType, rewriteDesired: BOOLEAN]
    RETURNS [status: DiskChannel.CompletionStatus] =
    BEGIN
    IF (status ← TransferPageRun[page, vrr, pLabel, memoryPage, 1].status) #
        goodCompletion THEN
      status ← TryHardToRead[page, pLabel, memoryPage, repair,
        rewriteDesired];
    END;  -- ReadPage

  ReplaceIncorrigibleLVRoot: INTERNAL PROCEDURE [lvRoot: POINTER TO LVRoot,
    pvRoots: POINTER TO PVRoots, markers: POINTER TO Markers,
    svIndex: CARDINAL] =
    -- Replaces an incorrigible LV root page with the first following good
    -- page.  Adjusts the PV root page and marker page subvolume descriptors
    -- accordingly.  Also adjusts the volume size appropriately in the LV
    -- root page.  (If multiple-subvolume logical volumes are ever
    -- implemented, we will need to update lvSize and lvPage in more than one
    -- subvolume marker page.)  Does not enter any pages into the bad page
    -- table, since these pages just disappear anyway (they are not in any
    -- subvolume).
    -- NOTE:  because this is a risky operation (i.e., if it fails, it will
    -- make things worse than they were), it should be called only when the
    -- client has requested a riskyRepair.
    BEGIN
    m: CARDINAL = FindMatchingMarker[markers, svIndex];
    BEGIN OPEN sVD: pvRoots.pvrData.subVolumes[svIndex], lvRoot,
        mSVD: markers[m].markerData.physical.descriptor;
    lvrMemoryPage: Space.PageNumber = Space.PageFromLongPointer[lvrData];
    newPage: PhysicalVolume.PageNumber;
    scratchLabel: PilotDisk.Label;
    scratchMemoryPage: Space.PageNumber = Space.VMPageNumber[scratchSpace];
    status: DiskChannel.CompletionStatus ← checkError; -- loop initialization
    [] ← Space.GetWindow[scratchSpace !  -- map if not already mapped
      Space.Error =>
        {IF type # noWindow THEN REJECT;  -- unexpected error type
	Space.Map[scratchSpace]; CONTINUE}];
    -- find a replacement page
    FOR p: PhysicalVolume.PageNumber ← sVD.pvPage + 1, p + 1
	WHILE p < sVD.pvPage + sVD.nPages AND status # goodCompletion DO
      status ← TransferPageRun[p, vrr, @scratchLabel, scratchMemoryPage,
          1].status;
      newPage ← p;
      ENDLOOP;
    IF status # goodCompletion THEN  -- no readable replacement page found
      ERROR PhysicalVolume.Error[badDisk];
    -- zap the old copy so it won't confuse us in the future (ignore errors)
    scratchLabel ← PilotDisk.nullLabel;
    scratchLabel.type ← PilotFileTypes.tBadPage;
    [] ← WritePage[sVD.pvPage, @scratchLabel, lvrMemoryPage];
    lvRoot.lvrPage ← newPage;
    -- adjust volume size in LV root page
    lvrData.volumeSize ← lvrData.volumeSize - (newPage - sVD.pvPage);
    -- try to write the new LV root page
    IF (lvrStatus ← WritePage[newPage, @lvrLabel, lvrMemoryPage]) #
        goodCompletion THEN
      ERROR PhysicalVolume.Error[badDisk];
    -- update the subvolume descriptor in the PV root page and the
    -- appropriate marker page
    sVD.nPages ← mSVD.nPages ← sVD.nPages - (newPage - sVD.pvPage);
    sVD.lvSize ← mSVD.lvSize ← sVD.lvSize - (newPage - sVD.pvPage);
    sVD.pvPage ← mSVD.pvPage ← newPage;
    -- rewrite the PV root page and appropriate marker page
    IF (pvRoots.pages[0].pvrStatus ← WritePage[pvRoots.pages[0].pvrPage,
        @pvRoots.pages[0].pvrLabel,
	Space.PageFromLongPointer[pvRoots.pvrData]]) # goodCompletion THEN
      ERROR PhysicalVolume.Error[badDisk];
    IF (markers[m].status ← WritePage[markers[m].page,
        @markers[m].markerLabel,
	Space.PageFromLongPointer[markers[m].markerData]]) # goodCompletion
      THEN ERROR PhysicalVolume.Error[badDisk];
    END;  -- sVD and mSVD scope
    END;  -- ReplaceIncorrigibleLVRoot

  ReplaceIncorrigibleMarker: INTERNAL PROCEDURE [pvRoots: POINTER TO PVRoots,
    marker: POINTER TO Marker] =
    -- Replaces an incorrigible marker page with the first preceding good
    -- page.  Adjusts the PV root page and marker page subvolume descriptors
    -- accordingly.  ASSUMES that MakeLVConsistentAndReadable will adjust the
    -- volume size appropriately in the LV root page.  (If multiple-subvolume
    -- logical volumes are ever implemented, we will need to update lvSize
    -- and lvPage in more than one subvolume marker page.)  Does not enter
    -- any pages into the bad page table, since these pages just disappear
    -- anyway (they are not in any subvolume).
    -- NOTE:  because this is a risky operation (i.e., if it fails, it will
    -- make things worse than they were), it should be called only when the
    -- client has requested a riskyRepair.
    BEGIN OPEN
        sVD: pvRoots.pvrData.subVolumes[marker.markerData.physical.svNumber],
        mSVD: marker.markerData.physical.descriptor;
    markerMemoryPage: Space.PageNumber =
        Space.PageFromLongPointer[marker.markerData];
    newPage: PhysicalVolume.PageNumber;
    scratchLabel: PilotDisk.Label;
    scratchMemoryPage: Space.PageNumber = Space.VMPageNumber[scratchSpace];
    status: DiskChannel.CompletionStatus ← checkError; -- loop initialization
    [] ← Space.GetWindow[scratchSpace !  -- map if not already mapped
      Space.Error =>
        {IF type # noWindow THEN REJECT;  -- unexpected error type
	Space.Map[scratchSpace]; CONTINUE}];
    -- find a replacement page
    FOR p: PhysicalVolume.PageNumber ← sVD.pvPage + sVD.nPages - 1, p - 1
	WHILE p > sVD.pvPage AND status # goodCompletion DO
      status ← TransferPageRun[p, vrr, @scratchLabel, scratchMemoryPage,
          1].status;
      newPage ← p;
      ENDLOOP;
    IF status # goodCompletion THEN  -- no readable replacement page found
      ERROR PhysicalVolume.Error[badDisk];
    -- zap the old copy so it won't confuse us in the future (ignore errors)
    scratchLabel ← PilotDisk.nullLabel;
    scratchLabel.type ← PilotFileTypes.tBadPage;
    [] ← WritePage[sVD.pvPage + sVD.nPages, @scratchLabel, markerMemoryPage];
    -- adjust subvolume descriptor and other location-dependent values in the
    -- PV root page and the marker page
    marker.page ← newPage;
    PilotDisk.SetLabelFilePage[@marker.markerLabel, newPage];
    mSVD.nPages ← sVD.nPages - (sVD.pvPage + sVD.nPages - newPage);
    sVD.lvSize ← mSVD.lvSize ← sVD.lvSize - (sVD.pvPage + sVD.nPages -
        newPage);
    sVD.nPages ← mSVD.nPages;
    -- rewrite the PV root page and write the new marker page
    IF (pvRoots.pages[0].pvrStatus ← WritePage[pvRoots.pages[0].pvrPage,
        @pvRoots.pages[0].pvrLabel,
	Space.PageFromLongPointer[pvRoots.pvrData]]) # goodCompletion THEN
      ERROR PhysicalVolume.Error[badDisk];
    IF (marker.status ← WritePage[marker.page, @marker.markerLabel,
	markerMemoryPage]) # goodCompletion
      THEN ERROR PhysicalVolume.Error[badDisk];
    END;  -- ReplaceIncorrigibleMarker

  ScanForMarkers: INTERNAL PROCEDURE
    [markers: POINTER TO Markers, repair: PhysicalVolumeExtras.RepairType] =
    BEGIN OPEN FT: FormatTrident; -- **** Dorado specific ****
    found: BOOLEAN ← TRUE;
    page: PhysicalVolume.PageNumber ←
      SELECT PhysicalVolume.InterpretHandle[pvInstance].type FROM
	DeviceTypes.sa4000 => -- assume no Alto partitions
	  FT.pagesReservedInPartition1,
	ENDCASE => 0;
    lastPage: PhysicalVolume.PageNumber =
      DiskChannel.GetDriveAttributes[pvInstance.drive].nPages - 1;
    FOR i: CARDINAL IN SubVolumeRange UNTIL ~found DO
      [found, page] ←
        FindNextMarkerPage[firstPage: page, lastPage: lastPage,
	  marker: @markers[i], repair: repair];
      ENDLOOP;
    END;  -- ScanForMarkers

  SubVolumeDescOkay: INTERNAL PROCEDURE
    [descriptor: LONG POINTER TO PhysicalVolumeFormat.SubVolumeDesc]
    RETURNS [okay: BOOLEAN] =
    BEGIN OPEN descriptor;
    pvSize: PhysicalVolumeFormat.PageCount =
      DiskChannel.GetDriveAttributes[pvInstance.drive].nPages;
    okay ← lvPage < lvSize AND pvPage < pvSize AND nPages < pvSize AND
      pvPage + nPages < pvSize AND
      -- until logical volumes spanning physical volumes are implemented
      lvSize = nPages AND lvPage = 0;
    END;  -- SubVolumeDescOkay

  SubVolumesOkay: INTERNAL PROCEDURE
    [pvHandle: PhysicalVolumeFormat.Handle] RETURNS [okay: BOOLEAN] =
    BEGIN OPEN pvHandle;
    iSubVolEnd, jSubVolEnd: PhysicalVolume.PageNumber;
    okay ← TRUE;
    FOR i: CARDINAL IN [0..subVolumeCount) WHILE okay DO
      okay ← SubVolumeDescOkay[@subVolumes[i]];
      iSubVolEnd ← subVolumes[i].pvPage + subVolumes[i].nPages;
      FOR j: CARDINAL IN [0..i) WHILE okay DO
        OPEN iSVD: subVolumes[i], jSVD: subVolumes[j];
	jSubVolEnd ← jSVD.pvPage + jSVD.nPages;
	okay ←  -- check for disjointness of subvolumes
	  ~(jSVD.pvPage >= iSVD.pvPage AND jSVD.pvPage <= iSubVolEnd) AND
	  ~(jSubVolEnd >= iSVD.pvPage AND jSubVolEnd <= iSubVolEnd);
	ENDLOOP;
      ENDLOOP;
    END;  -- SubVolumesOkay

  TidyUp: INTERNAL PROCEDURE [pvRoots: POINTER TO PVRoots,
    lvRoot: POINTER TO LVRoot, markers: POINTER TO Markers] =
    BEGIN
    IF verifySpace # Space.nullHandle THEN
      {Space.Delete[verifySpace]; verifySpace ← Space.nullHandle};
    IF scratchSpace # Space.nullHandle THEN
      {Space.Delete[scratchSpace]; scratchSpace ← Space.nullHandle};
    IF channel # DiskChannel.nullHandle THEN
      {DiskChannel.Delete[channel];
      channel ← DiskChannel.nullHandle};
    IF pvRoots.pvrSpace # Space.nullHandle THEN
      Space.Delete[pvRoots.pvrSpace];
    IF lvRoot.lvrSpace # Space.nullHandle THEN Space.Delete[lvRoot.lvrSpace];
    FOR i: CARDINAL IN SubVolumeRange DO
      IF markers[i].space # Space.nullHandle THEN
        Space.Delete[markers[i].space];
      ENDLOOP;
    END;  -- TidyUp

  TransferPageRun: INTERNAL PROCEDURE
    [page: PhysicalVolume.PageNumber, command: DiskChannel.Command,
    labelBuffer: DiskChannel.PLabel, dataBuffer: Space.PageNumber,
    count: DiskChannel.DiskPageCount, verifyLabelsOnly: BOOLEAN ← FALSE]
    RETURNS [countValid: DiskChannel.DiskPageCount,
    status: DiskChannel.CompletionStatus] =
    -- Either transfers one or more pages if verifyLabelsOnly is FALSE, or
    -- verifies the labels on a run of pages if verifyLabelsOnly is TRUE.
    -- The labelBuffer parameter must point to a RESIDENT label buffer, for
    -- instance in a local frame.
    -- (This procedure cannot do MakeResident/MakeSwappable on the label
    -- buffer because MakeSwappable would have the effect of making local
    -- frames swappable, a no-no.)
    -- NOTE:  the microcode/head automatically increments the file page
    -- number in the memory copy of the label on label write or label verify
    -- operations (but not on label read operations, thankfully).  Hence, on
    -- a label write or label verify operation, this procedure makes its own
    -- memory copy of the label to protect the client's memory copy.
    BEGIN
    req: DiskChannel.IORequest;
    reqH: DiskChannel.IORequestHandle;
    dataSpaceHandle: Space.Handle = Space.GetHandle[dataBuffer];
    pLabel: DiskChannel.PLabel;
    scratchLabelBuffer: PilotDisk.Label;
    SELECT command FROM
      vww, vvw, vvr =>
        {scratchLabelBuffer ← labelBuffer↑;
	pLabel ← @scratchLabelBuffer};
      vrr => pLabel ← labelBuffer;
      ENDCASE => ERROR Bug[impossibleSelectError];
    -- can't use constructor for IORequest because of private fields
    req.command ← command;
    req.channel ← channel;
    req.diskPage ← page;
    req.memoryPage ← dataBuffer;
    req.dontIncrement ← verifyLabelsOnly;
    req.count ← count;
    req.countDone ← 0;
    req.label ← pLabel;
    req.tag ← 0;
    req.status ← goodCompletion;
    req.next ← NIL;
    SpecialSpace.MakeResident[dataSpaceHandle];
    -- ASSUME the label buffer is already resident in a local frame.
    DiskChannel.InitiateIO[@req];
    reqH ← DiskChannel.WaitAny[completionHandle];
    SpecialSpace.MakeSwappable[dataSpaceHandle];
    -- we should only get back our own request, but check anyway
    IF reqH ~= @req THEN ERROR Bug[diskChannelFunny];
    countValid ← req.countDone;
    status ← req.status;
    END;  -- TransferPageRun

  TryHardToRead: INTERNAL PROCEDURE [page: PhysicalVolume.PageNumber,
    pLabel: DiskChannel.PLabel, memoryPage: Space.PageNumber,
    repair: PhysicalVolumeExtras.RepairType, rewriteDesired: BOOLEAN]
    RETURNS [status: DiskChannel.CompletionStatus] =
    -- Tries very hard to read the page.  This procedure is called only after
    -- at least one read attempt has failed.  If repair = safeRepair or
    -- riskyRepair, and the caller has indicated a rewrite is desired, tries
    -- to rewrite the page (whether the read succeeded or not) to clear the
    -- flakiness.  If repair = checkOnly, just indicates problem with
    -- internal structures to alert the client that repair (at least
    -- rewriting) is needed and doesn't waste time actually retrying the
    -- read.
    BEGIN
    IF repair = checkOnly THEN
      {scavengerStatus.internalStructures ← damaged; RETURN};
    -- ASSERT:  repair = safeRepair or riskyRepair from here on.
    status ← dataError;  -- just for loop initialization
    THROUGH [0..maxRetries) WHILE status # goodCompletion DO
      status ← TransferPageRun[page, vrr, pLabel, memoryPage, 1].status;
      ENDLOOP;
    IF rewriteDesired THEN
      -- try to clear transient error by overwriting
      {status ← WritePage[page, pLabel, memoryPage];
      -- read one more time to see if the write cleared the error and to
      -- return a read, not write, status
      status ← TransferPageRun[page, vrr, pLabel, memoryPage, 1].status};
    END;  -- TryHardToRead

  ValidateLVMarkerPage: INTERNAL PROCEDURE [marker: POINTER TO Marker] =
    BEGIN OPEN marker;
    lvSelfConsistent ←
      -- these are redundant checks (already done in ValidatePVMarkerPage)
      status = goodCompletion AND
      markerLabel.type = PilotFileTypes.tSubVolumeMarkerPage AND
      PilotDisk.GetLabelFilePage[@markerLabel] = page;
    BEGIN OPEN markerData.logical;  -- validate the logical volume data
    -- the following are NOT redundant since they apply only to logical
    -- volumes
    lvSelfConsistent ← lvSelfConsistent AND
      seal = LogicalVolume.LSMSeal AND
      version = LogicalVolume.LSMCurrentVersion AND
      labelLength IN [0..LogicalVolume.maxLogicalVolumeLabelLength) AND
      type IN Volume.Type AND pad = 0;
    END;  -- markerData.logical scope
    END;  -- ValidateLVMarkerPage

  ValidateLVRootPage: INTERNAL PROCEDURE [lvRoot: POINTER TO LVRoot] =
    BEGIN OPEN lvRoot;
    lvrSelfConsistent ← lvrStatus = goodCompletion AND
      lvrLabel.type = PilotFileTypes.tLogicalVolumeRootPage AND
      PilotDisk.GetLabelFilePage[@lvrLabel] = LogicalVolume.rootPageNumber
      AND lvrLabel.fileID = lvrData.vID;
    BEGIN OPEN lvrData;
    lvrSelfConsistent ← lvrSelfConsistent AND
      seal = LogicalVolume.Seal AND
      version = LogicalVolume.currentVersion AND
      labelLength IN [0..LogicalVolume.maxLogicalVolumeLabelLength) AND
      type IN Volume.Type AND
      treeLevel IN LogicalVolume.TreeLevel AND
      freePageCount < volumeSize AND vamStart > 0 AND
      vamStart < volumeSize AND vfmStart > 0 AND
      vfmStart < volumeSize AND lowerBound > 0 AND
      lowerBound < volumeSize AND fill = ALL[0] AND checksum = 0 AND
      ValidVolumeRootFiles[lvrData];
    END;  -- lvrData scope
    END;  -- ValidateLVRootPage

  ValidatePVMarkerPage: INTERNAL PROCEDURE [marker: POINTER TO Marker] =
    BEGIN OPEN marker;
    markerData ← Space.LongPointer[space];
    pvSelfConsistent ← status = goodCompletion AND
      markerLabel.type = PilotFileTypes.tSubVolumeMarkerPage AND
      PilotDisk.GetLabelFilePage[@markerLabel] = page;
    BEGIN OPEN markerData.physical;  -- validate the physical volume data
    includeInSubset ← pvSelfConsistent ← pvSelfConsistent AND
      seal = PhysicalVolumeFormat.PSMSeal AND
      version = PhysicalVolumeFormat.PSMCurrentVersion AND
      maxBadPages = PhysicalVolumeFormat.maxBadPages AND
      labelLength IN [0..PhysicalVolumeFormat.physicalVolumeLabelLength) AND
      fill = 0 AND
      svNumber IN SubVolumeRange AND
      descriptor.pvPage + descriptor.nPages = page AND
      SubVolumeDescOkay[@descriptor] AND
      markerData.checksum = 0;
    END;  -- markerData.physical scope
    END;  -- ValidatePVMarkerPage

  ValidatePVRootPage: INTERNAL PROCEDURE [pvRoots: POINTER TO PVRoots]
    RETURNS [okay: BOOLEAN] =
    BEGIN OPEN pvRoots;
    pvrSelfConsistent ← pages[0].pvrStatus = goodCompletion AND
      pages[0].pvrLabel.type = PilotFileTypes.tPhysicalVolumeRootPage AND
      PilotDisk.GetLabelFilePage[@pages[0].pvrLabel] = pages[0].pvrPage AND
      PhysicalVolumeFormat.rootPageNumber = pages[0].pvrPage AND
      pages[0].pvrLabel.fileID = pvrData.pvID;
    BEGIN OPEN pvrData;  -- validate the physical volume data
    okay ← pvrIncludeInSubset ← pvrSelfConsistent ← pvrSelfConsistent AND
      seal = PhysicalVolumeFormat.Seal AND
      version = PhysicalVolumeFormat.currentVersion AND
      labelLength IN [0..PhysicalVolumeFormat.physicalVolumeLabelLength) AND
      subVolumeCount IN SubVolumeRange AND
      badPageCount IN [0..PhysicalVolumeFormat.maxBadPages] AND
      maxBadPages = PhysicalVolumeFormat.maxBadPages AND
      onLineCount = 0 AND
      (~localTimeParametersValid OR ValidTimeParms[@localTimeParameters]) AND
      SubVolumesOkay[pvrData] AND
      fill1 = ALL[0] AND
      checksum = 0;
    END;  -- pvrData scope
    END;  -- ValidatePVRootPage

  ValidTimeParms: INTERNAL PROCEDURE
    [parms: LONG POINTER TO System.LocalTimeParameters]
    RETURNS [okay: BOOLEAN] =
    BEGIN OPEN parms;
    okay ← zone IN [0..12] AND zoneMinutes IN [0..59] AND
      beginDST IN [0..366] AND endDST IN [0..366];
    END;  -- ValidTimeParms

  ValidVolumeRootFiles: INTERNAL PROCEDURE [lvH: LogicalVolume.Handle]
    RETURNS [okay: BOOLEAN] =
    BEGIN OPEN lvH, PFT: PilotFileTypes;
    okay ← rootFileID[PFT.tFreePage] # LogicalVolume.nullID AND
      rootFileID[PFT.tVolumeAllocationMap] # LogicalVolume.nullID AND
      rootFileID[PFT.tVolumeFileMap] # LogicalVolume.nullID AND
      rootFileID[PFT.tFreePage] # rootFileID[PFT.tVolumeAllocationMap] AND
      rootFileID[PFT.tFreePage] # rootFileID[PFT.tVolumeFileMap] AND
      rootFileID[PFT.tVolumeAllocationMap] # rootFileID[PFT.tVolumeFileMap];
    END;  -- ValidVolumeRootFiles

  WritePage: INTERNAL PROCEDURE
    [page: PhysicalVolume.PageNumber, pLabel: DiskChannel.PLabel,
    memoryPage: Space.PageNumber]
    RETURNS [status: DiskChannel.CompletionStatus] =
    BEGIN
    verifyData: LONG POINTER TO PageAsArray = Space.LongPointer[verifySpace];
    verifyPage: Space.PageNumber = Space.PageFromLongPointer[verifyData];
    suppliedData: LONG POINTER TO PageAsArray =
      Space.LongPointerFromPage[memoryPage];
    [] ← Space.GetWindow[verifySpace !  -- map if not already mapped
      Space.Error =>
        {IF type # noWindow THEN REJECT;  -- unexpected error type
	Space.Map[verifySpace]; CONTINUE}];
    status ← checkError;  -- set to error just for loop initialization
    THROUGH [0..maxRetries) UNTIL status = goodCompletion DO
      -- write the page
      IF (status ← TransferPageRun[
           page, vww, pLabel, memoryPage, 1].status) = goodCompletion AND
	-- read it back into a separate buffer
	(status ← TransferPageRun[
	    page, vvr, pLabel, verifyPage, 1].status) = goodCompletion AND
	-- compare to make sure the write was correct
	verifyData↑ # suppliedData↑ THEN status ← checkError;
      ENDLOOP;
    IF scavengerStatus.internalStructures # damaged THEN
      scavengerStatus.internalStructures ← repaired;
    -- reporting unrepaired damage takes priority over reporting repairs
    END;  -- WritePage

  END.
  

LOG
12-Oct-81 15:27:21	Fay
	Created file.
 5-Nov-81  9:54:05	Fay
	Split repair mode into safeRepair and riskyRepair; renamed okay to
	noProblems.
10-Dec-81 16:49:46	Fay
	Fixed bug in Coerce*Page procs in debugging check at end so that riskRepair
	works.
24-Jan-82 14:25:57	Taft
	Had to replace FormatSA1000andSA4000 constants with equivalent ones from
	FormatTrident.  Renamed to be PhysicalVolumeScavengerImplDorado.
	(Original file should have been named PhysicalVolumeScavengerImplD0DLion!)