-- ScavengeImpl.mesa (last edited by: Gobbel on: 20-Mar-81 11:25:27)
-- This is a cut at the interim Scavenger  

DIRECTORY
  DiskChannel USING [CompletionStatus, GetAttributes],
  Environment USING [bitsPerWord, wordsPerPage],
  File USING [
    Capability, Create, delete, Delete, DeleteImmutable, Error, GetAttributes, ID, lastPageNumber,
    MakeImmutable, MakePermanent, nullCapability, nullID, PageCount, PageNumber,
    read, SetSize, Type, Unknown, write],
  FileInternal USING [Descriptor, FilePtr, PageGroup],
  FMPrograms USING [],
  Inline USING [LongCOPY, LowHalf],
  KernelFile USING [GetNextFile, GetRootFile],
  LabelTransfer USING [ReadLabel, ReadLabelAndData, WriteLabelAndData, WriteLabels, VerifyLabels],
  LogicalVolume USING [
    BeginScavenging, EndScavenging, Free, FreeVolumePages, Handle, nullVolumePage, PageNumber,
    PutRootFile, ReadOnlyVolume, rootPageNumber, Vam, VolumeAccess, VolumeAccessProc,
    VolumeAccessStatus],
  MarkerPage USING [Find],
  PhysicalVolume USING [Error, GetContainingPhysicalVolume, MarkPageBad],
  PhysicalVolumeFormat USING [Handle, maxBadPages, PageNumber, rootPageNumber],
  PilotDisk USING [GetLabelFilePage, Label, nullLabel],
  PilotFileTypes USING [
    PilotVFileType, tFreePage, tScavengerLog, tScavengerLogOtherVolume, tUnassigned,
    tVolumeAllocationMap, tVolumeFileMap],
  PilotSwitches USING [switches],
  Runtime USING [CallDebugger],
  Scavenger USING [Error, ErrorType, FileEntry, Header, Problem],
  SimpleSpace USING [Create, Handle, LongPointer, Map, Page, Unmap],
  Space USING [defaultWindow, Handle, PageNumber, WindowOrigin],
  SubVolume USING [Find, Handle],
  System USING [GetGreenwichMeanTime],
  SystemInternal USING [Unimplemented],
  Utilities USING [LongPointerFromPage, PageFromLongPointer],
  VolAllocMap USING [AccessVAM, AllocPageGroup, Close],
  VolFileMap USING [Close, GetPageGroup, InitMap, InsertPageGroup],
  Volume USING [
    Close, GetNext, GetStatus, GetType, ID, InsufficientSpace, NotOpen, nullID, PageCount, systemID,
    Type, TypeSet, Unknown],
  VolumeInternal USING [PageNumber];

ScavengeImpl: MONITOR
  IMPORTS
    DiskChannel, File, Inline, KernelFile, LabelTransfer, LogicalVolume,
    MarkerPage, PhysicalVolume, PilotDisk, PilotSwitches, Runtime, Scavenger, SimpleSpace,
    System, SystemInternal, SubVolume, Utilities, VolAllocMap, VolFileMap, Volume
  EXPORTS LogicalVolume, FMPrograms, Scavenger
  SHARES File =
  BEGIN

  -- NOTE !!!!
  -- The following constant definitions of dataError and labelError should be deleted once the real
  --   definitions are added to CompletionStatus in DiskChannel.mesa in the next build.  (21-Jan-81)
  -- Deleted (27-Feb-81)

  -- NOTE !!!!
  -- The following constant definitions of diskHardwareError and diskNotReady should be deleted once the
  --   real definitions are added to ErrorType in Scavenger.mesa in the next build.  (21-Jan-81)
  -- Deleted (27-Feb-81)

  bitsPerPage: CARDINAL = Environment.bitsPerWord*Environment.wordsPerPage;
  BadPageList: TYPE = RECORD [
    p: POINTER TO PageNumber,
    a: ARRAY [0..PhysicalVolumeFormat.maxBadPages] OF PageNumber];
  OrphanHandle: PUBLIC TYPE = LogicalVolume.PageNumber;
  PageNumber: TYPE = LogicalVolume.PageNumber;

  FileProblem: TYPE = RECORD [
    fileID: File.ID,
    problem: Scavenger.Problem];
  problemSpacePages: CARDINAL = 1;
  problemArraySize: CARDINAL = (problemSpacePages * Environment.wordsPerPage)/SIZE[FileProblem];
  ProblemArray: TYPE = ARRAY [0..problemArraySize) OF FileProblem;
  PageInterval: TYPE = RECORD [
    startingPage, nextStartingPage: File.PageNumber];

  IndexOfContexts: TYPE = [0..1);
  Contexts: ARRAY IndexOfContexts OF ScavengeRecord;
  freeContext: CONDITION;

  ImpossibleScavengerError: PRIVATE ERROR [
    {cantFindSubvolume, noRoomForVam, pageAlreadyBusy, labelChanged}] = CODE;


  -- The following implement the Scavenger interface. . .

  Error: PUBLIC ERROR [Scavenger.ErrorType] = CODE;

  DeleteLog: PUBLIC PROC [volume: Volume.ID] =
    BEGIN
    logFile: File.Capability ← GetLog[volume];
    IF logFile=File.nullCapability THEN RETURN;
    DeleteLogFile[logFile];
    logFile ← File.nullCapability;
    LogicalVolume.PutRootFile[@volume, PilotFileTypes.tScavengerLog, @logFile];
    END;

  DeleteOrphanPage: PUBLIC PROC [volume: Volume.ID, id: OrphanHandle] =
    BEGIN
    MakeFreePageProc: --PROC [volume: LogicalVolume.Handle] RETURNS [updateMarkers: BOOLEAN] --
      LogicalVolume.VolumeAccessProc =
      BEGIN
      updateMarkers ← FALSE;
      IF FixFreePage[id, volume] THEN [] ← VolAllocMap.AccessVAM[volume, id, FALSE, TRUE]
      ELSE ReallyMarkPageBad[id, svH, volume];
      END;

    found: BOOLEAN;
    svH: SubVolume.Handle;
    SELECT Volume.GetStatus[volume] FROM
      open => NULL;
      unknown => ERROR Volume.Unknown[volume];
      ENDCASE => ERROR Volume.NotOpen[volume];
    [found, svH] ← SubVolume.Find[volume, id];
    IF ~found THEN ERROR Scavenger.Error[orphanNotFound];
    SELECT LogicalVolume.VolumeAccess[@volume, MakeFreePageProc, TRUE] FROM
      ok => NULL;
      volumeReadOnly => ERROR LogicalVolume.ReadOnlyVolume;
      ENDCASE => ERROR;
    END;

  GetLog: PUBLIC PROC [volume: Volume.ID] RETURNS [logFile: File.Capability] =
    {RETURN[KernelFile.GetRootFile[PilotFileTypes.tScavengerLog, volume]]};

  ReadBadPage: PUBLIC PROC [file: File.ID, page: File.PageNumber, destination: Space.PageNumber] =
    BEGIN
    fileD: FileInternal.Descriptor ← [file, Volume.nullID, local[, , , ]];
    fileP: FileInternal.FilePtr = @fileD;
    found: BOOLEAN;
    group: FileInternal.PageGroup;
    volumePage: LogicalVolume.PageNumber;
    IF ~SpecialGetFileDescriptor[fileP] THEN ERROR File.Unknown[[file, File.read]];
    [found, group] ← FindContainingPageGroup[fileP, page];
    IF ~found THEN ERROR File.Unknown[[file, File.read]];
    IF (group.filePage = group.nextFilePage)  --page beyond end of file
        OR (group.volumePage = LogicalVolume.nullVolumePage) --page doesn't exist-- THEN
      ERROR Scavenger.Error[noSuchPage];
    volumePage ← group.volumePage + (page - group.filePage);
    [] ← VerifyPageLabel[fileD, page, volumePage];
    IF ReadPage[fileD, page, volumePage, destination, FALSE].status = labelError THEN
      DO Runtime.CallDebugger["Unrecoverable disk error:  labelError"L]; ENDLOOP;
    END;

  ReadOrphanPage: PUBLIC PROC [volume: Volume.ID, id: OrphanHandle, destination: Space.PageNumber]
      RETURNS [file: File.ID, type: File.Type, pageNumber: File.PageNumber,readErrors: BOOLEAN] =
    BEGIN
    fileD: FileInternal.Descriptor ← [File.nullID, volume, local[, , , ]];
    label: PilotDisk.Label;
    status: DiskChannel.CompletionStatus;
    SELECT Volume.GetStatus[volume] FROM
      open => NULL;
      unknown => ERROR Volume.Unknown[volume];
      ENDCASE => ERROR Volume.NotOpen[volume];
    IF ~SubVolume.Find[volume, id].success THEN ERROR Scavenger.Error[orphanNotFound];
    [label, status] ← ReadPage[fileD, 0, id, destination, FALSE];
    SELECT status FROM
      labelError => -- expect this status since orphan pages are pages with label checksum errors
        RETURN[File.nullID, PilotFileTypes.tUnassigned, 0, TRUE];
      goodCompletion, dataError => -- label should be valid in this case
        RETURN[label.fileID, label.type, PilotDisk.GetLabelFilePage[@label], status # goodCompletion];
      ENDCASE => -- should never get this case -- ERROR;
    END;

  RewritePage: PUBLIC PROC [file: File.ID, page: File.PageNumber, source: Space.PageNumber] =
    BEGIN
    fileD: FileInternal.Descriptor ← [file, Volume.nullID, local[, , , ]];
    fileP: FileInternal.FilePtr = @fileD;
    found: BOOLEAN;
    group: FileInternal.PageGroup;
    missingPage: BOOLEAN ← FALSE;
    IF ~SpecialGetFileDescriptor[fileP] THEN ERROR File.Unknown[[file, File.read + File.write]];
    WITH fileD SELECT FROM
      local => IF immutable THEN ERROR File.Error[immutable];
      ENDCASE => ERROR;  -- can't handle remote files.
    [found, group] ← FindContainingPageGroup[fileP, page];
    IF ~found THEN ERROR File.Unknown[[file, File.read + File.write]];
    IF (group.filePage = group.nextFilePage)  --page beyond end of file-- THEN
      ERROR Scavenger.Error[noSuchPage];
    IF group.volumePage = LogicalVolume.nullVolumePage THEN missingPage ← TRUE
    ELSE missingPage ← RewriteBadPage[fileD, page, group, source];
    IF missingPage THEN ReplaceMissingPage[fileP, page, source];
    END;

  Scavenge: PUBLIC PROC [volume, logDestination: Volume.ID, repair: BOOLEAN]
    RETURNS [logFile: File.Capability] =
    BEGIN
    context: ScavengeContext;
    theError: Scavenger.ErrorType;
    BEGIN
    IF ~repair THEN ERROR SystemInternal.Unimplemented;
    context ← OpenScavengeContext[volume];
    LogicalVolume.BeginScavenging[pVID: @volume, context: context !
      Scavenger.Error => {theError ← error; GOTO AnError}];
    DeleteLog[volume];
    IF ~CreateLogFile[logDestination, context] THEN {theError ← cannotWriteLog; GOTO AnError};
    context.logHeader.repaired ← TRUE;
    OpenLogFile[context];
    [] ← PutWords[context, @context.logHeader, SIZE[Scavenger.Header]];
    EnumerateFiles[context];
    PutHeader[context];
    CloseLogFile[context];
    logFile ← [context.logFile.fID, File.delete + File.read];
    LogicalVolume.PutRootFile[@volume, PilotFileTypes.tScavengerLog, @logFile];
    IF volume=logDestination THEN
      {File.MakePermanent[logFile]; File.MakeImmutable[logFile]};
    CloseScavengeContext[context];
    -- The following two statements are a hack to get around the following problem:
    -- When we created the log file above and deleted the old log file, we caused
    -- FileImpl to fill its tmpsFile cache.  The closes are necessary to clear those
    -- caches.  It is known to be safe to do because VolumeImpl 1) permits a closed
    -- volume to be closed again without error, and 2) is only called after FileImpl
    -- has cleared its caches (i.e., error checking is done after FileImpl has
    -- done its thing).
    Volume.Close[volume ! Volume.Unknown => CONTINUE];
    LogicalVolume.EndScavenging[@volume];
    EXITS
      AnError =>
        {LogicalVolume.EndScavenging[@volume];
        CloseScavengeContext[context];
        ERROR Scavenger.Error[theError]};
    END;
    END;


  -- The following implement (part of) the LogicalVolume interface. . .

  -- ScavengeContext/ScavengeRecord are here so that multiple scavenges can be going on at once
  --   someday.  This will occur when VolumeImpl has been modified and IndexOfContexts is changed
  --   appropriately.
  ScavengeContext: TYPE = POINTER TO ScavengeRecord;
  ScavengeRecord: PUBLIC TYPE = RECORD [
    occupied: BOOLEAN,
    logHeader: Scavenger.Header,
    volume: Volume.ID,
    buffer: Space.Handle,             -- Initialize only
    bufferPointer: LONG POINTER,      -- Initialize only
    problemCount: CARDINAL,
    problemSpace: Space.Handle,             -- Initialize only
    problem: LONG POINTER TO ProblemArray,      -- Initialize only
    problemIncomplete: BOOLEAN,
    logFile: File.Capability,
    nextWord: CARDINAL,      -- words
    fileLengthPages: File.PageCount];

  EraseVolume: PUBLIC PROC [
      vol: LogicalVolume.Handle, space: SimpleSpace.Handle] =
    BEGIN
    context: ScavengeContext ← OpenScavengeContext[vol.vID];
    ScavengeVolumeInternal[vol, space, TRUE, context];
    CloseScavengeContext[context];
    END;

  ScavengeVolume: PUBLIC PROC [
      vol: LogicalVolume.Handle, space: SimpleSpace.Handle, context: ScavengeContext] =
    BEGIN
    ScavengeVolumeInternal[vol, space, FALSE, context];
    END;


  -- Internal procs

  AdvanceBadPagePointer: PROC [l: POINTER TO BadPageList] = INLINE
    {l.p ← l.p + SIZE[PageNumber]};

  CloseLogFile: PROC [context: ScavengeContext] =
    {SimpleSpace.Unmap[context.buffer]};

  CloseScavengeContext: ENTRY PROC [context: ScavengeContext] =
    BEGIN
    IF context.problemCount # 0 THEN SimpleSpace.Unmap[context.problemSpace];
    context.occupied ← FALSE;
    NOTIFY freeContext;
    END;

  CountProblems: PROC [fileID: File.ID, context: ScavengeContext] RETURNS [count: CARDINAL] =
    BEGIN
    hole: PageInterval ← [0, 0];
    count ← 0;
    -- Count the unreadable and orphan Problems
    FOR i: CARDINAL IN [0..context.problemCount) DO
      IF context.problem[i].fileID = fileID THEN count ← count + 1;
      ENDLOOP;
    -- Count the missing page group Problems (i.e., holes in files)
    --  (Only real files have holes; File.nullID is the ID given for orphan pages.)
    IF fileID # File.nullID THEN
      DO
        hole ← FindNextHole[fileID, context, hole];
        IF hole.nextStartingPage = 0 THEN EXIT;
        count ← count + 1;
        ENDLOOP;
    END;

  CreateLogFile: PROC [logDestination: Volume.ID, context: ScavengeContext]
    RETURNS [success: BOOLEAN] =
    BEGIN
    context.logFile ← File.Create[
      logDestination, 1,
      IF logDestination=context.volume THEN
        PilotFileTypes.tScavengerLog ELSE PilotFileTypes.tScavengerLogOtherVolume
      ! ANY => GO TO Failed]; -- well, any does suggest we can't create the log.....
    context.fileLengthPages ← 1;
    RETURN[TRUE]
    EXITS Failed => RETURN[FALSE]
    END;

  CurrentBadPage: PROC [l: POINTER TO BadPageList] RETURNS [PageNumber] =
    INLINE {RETURN[l.p↑]};

  DeleteLogFile: PROC [logFile: File.Capability] =
    BEGIN
    immutable: BOOLEAN;
    volume: Volume.ID ← Volume.nullID;
    volumeTypeSet: Volume.TypeSet ← ALL[FALSE];
    [immutable: immutable] ← File.GetAttributes[logFile ! File.Unknown => GOTO return];
    IF ~immutable THEN File.Delete[logFile]
    ELSE
      BEGIN
      IF IsUtilityPilot[] THEN volumeTypeSet ← [normal: TRUE, debugger: TRUE, debuggerDebugger: TRUE]
      ELSE
        FOR type: Volume.Type IN [normal..Volume.GetType[Volume.systemID]] DO
          volumeTypeSet[type] ← TRUE; ENDLOOP;
      WHILE (volume ← Volume.GetNext[volume, volumeTypeSet]) # Volume.nullID DO
        File.DeleteImmutable[logFile, volume
          ! Volume.Unknown, Volume.NotOpen, File.Unknown, File.Error => LOOP];
        ENDLOOP;
      END;
    EXITS
    return => NULL
    END;

  EnumerateFiles: PROC [context: ScavengeContext] =
    BEGIN
    file: File.Capability ← File.nullCapability;  -- all orphan pages are associated with File.nullID
    context.logHeader.incomplete ← TRUE;
    DO
      fileEntry: Scavenger.FileEntry;
      fileEntry.file ← file.fID;
      IF ((fileEntry.numberOfProblems ← CountProblems[file.fID, context]) # 0) OR
        (file.fID # File.nullID) THEN
        BEGIN
        IF ~PutWords[context, @fileEntry, SIZE[Scavenger.FileEntry]] THEN RETURN;
        IF fileEntry.numberOfProblems # 0 THEN
          IF ~ReportProblems[file.fID, context] THEN RETURN;
        context.logHeader.numberOfFiles ← context.logHeader.numberOfFiles + 1;
        END;
      file ← KernelFile.GetNextFile[context.volume, file];
      IF file.fID = File.nullID THEN EXIT;
      ENDLOOP;
    context.logHeader.incomplete ← context.problemIncomplete;
    END;

  FindContainingPageGroup: PROC [fileP: FileInternal.FilePtr, page: File.PageNumber]
      RETURNS [found: BOOLEAN, group: FileInternal.PageGroup] =
    BEGIN
    GetPageGroupProc: --PROC [volume: LogicalVolume.Handle] RETURNS [updateMarkers: BOOLEAN] --
      LogicalVolume.VolumeAccessProc =
      BEGIN
      updateMarkers ← FALSE;
      [found, group] ← VolFileMap.GetPageGroup[volume, fileP, page];
      IF ~found THEN  -- check for hole at beginning of file
        {[success: found] ← VolFileMap.GetPageGroup[volume, fileP, File.lastPageNumber];
        IF found THEN group ← [0, LogicalVolume.nullVolumePage, group.nextFilePage]};
      END;

    getAll: Volume.TypeSet = [normal: TRUE, debugger: TRUE, debuggerDebugger: TRUE];
    volumeID: Volume.ID;  -- this variable necessary because VolumeAccess requires short pointer
    found ← FALSE;
    IF (fileP.volumeID # Volume.nullID) AND (Volume.GetStatus[fileP.volumeID] = open) THEN
      {volumeID ← fileP.volumeID;
      SELECT LogicalVolume.VolumeAccess[@volumeID, GetPageGroupProc] FROM
        ok => NULL;
        ENDCASE => ERROR;
      RETURN};
    WHILE ~found AND (fileP.volumeID ← Volume.GetNext[fileP.volumeID, getAll]) # Volume.nullID DO
      IF Volume.GetStatus[fileP.volumeID] # open THEN LOOP;
      volumeID ← fileP.volumeID;
      SELECT LogicalVolume.VolumeAccess[@volumeID, GetPageGroupProc] FROM
        ok => NULL;
        ENDCASE => ERROR;
      ENDLOOP;
    END;

  FindNextHole: PROC [fileID: File.ID, context: ScavengeContext, hole: PageInterval]
      RETURNS [nextHole: PageInterval] =
    BEGIN
    FindNextHoleProc: -- PROC [volume: LogicalVolume.Handle] RETURNS [updateMarkers: BOOLEAN] --
      LogicalVolume.VolumeAccessProc =
      BEGIN
      found: BOOLEAN;
      pageGroup: FileInternal.PageGroup;
      fileD: FileInternal.Descriptor ← [fileID, context.volume, local[, , , ]];
      filePage: File.PageNumber ← hole.nextStartingPage;
      updateMarkers ← FALSE;
      DO
        [found, pageGroup] ← VolFileMap.GetPageGroup[volume, @fileD, filePage];
        IF ~found THEN  -- this is a hole at the very beginning of the file
          {nextHole ← [0, pageGroup.nextFilePage]; RETURN; };
        IF pageGroup.filePage = pageGroup.nextFilePage THEN  -- normal end of file
          {nextHole ← [0, 0]; RETURN; };
        IF pageGroup.volumePage = LogicalVolume.nullVolumePage THEN
          -- this is a hole in the middle of the file
          {nextHole ← [pageGroup.filePage, pageGroup.nextFilePage]; RETURN; };
        filePage ← pageGroup.nextFilePage;
        ENDLOOP;
      END;

    SELECT LogicalVolume.VolumeAccess[@context.volume, FindNextHoleProc] FROM
      ok => NULL;
      ENDCASE => ERROR;
    END;

  FixFreePage: PROC [page: PageNumber, vol: LogicalVolume.Handle] RETURNS [success: BOOLEAN] =
    BEGIN
    countValid: File.PageCount;
    freeFileD: FileInternal.Descriptor = MakeFreeDescriptor[vol];
    retryCount: CARDINAL ← 8;
    status: DiskChannel.CompletionStatus;
    WHILE retryCount > 0 DO
      SELECT LabelTransfer.WriteLabels[freeFileD, [page, page, page + 1], FALSE] FROM
        goodCompletion =>
          {[countValid, status] ← LabelTransfer.VerifyLabels[
              file: freeFileD, pageGroup: [page, page, page + 1],
              expectErrorAfterFirstPage: FALSE, handleErrors: FALSE];
          IF countValid = 1 AND status = goodCompletion THEN RETURN[TRUE]};
        ENDCASE => RETURN[FALSE];  -- write error implies we've already done write retries, so give up
      retryCount ← retryCount - 1;  -- bad read after "good" write implies we should retry the write
      ENDLOOP;
    RETURN[FALSE];
    END;

  GetBadList: PROC [
    svH: SubVolume.Handle, space: SimpleSpace.Handle, l: POINTER TO BadPageList] =
    BEGIN
    RootWindow: PROC RETURNS [Space.WindowOrigin] = INLINE
      BEGIN
      RETURN[[
        [LOOPHOLE[MarkerPage.Find[
           [drive[DiskChannel.GetAttributes[svH.channel]]]].physicalID↑],
         File.read],
        PhysicalVolumeFormat.rootPageNumber]];
      END;
    lvPage: PageNumber;
    p: POINTER TO PageNumber;
    pv: PhysicalVolumeFormat.Handle
      = Utilities.LongPointerFromPage[SimpleSpace.Page[space]];
    l.p ← @l.a[0];
    SimpleSpace.Map[space, RootWindow[], FALSE];
    FOR i: CARDINAL IN [0..Inline.LowHalf[pv.badPageCount]) DO
      -- ASSUMEs no more than LAST[CARDINAL] bad pages
      IF pv.badPageList[i] NOT IN [svH.pvPage..svH.pvPage + svH.nPages) THEN LOOP;
      lvPage ← (pv.badPageList[i] - svH.pvPage) + svH.lvPage;
      p ← l.p; l.p ← l.p + SIZE[PageNumber];
      -- Pages are supposed to be ordered; we'll make sure
      WHILE p # @l.a[0] AND (p - SIZE[PageNumber])↑ > lvPage DO
        p↑ ← (p - SIZE[PageNumber])↑; p ← p - SIZE[PageNumber]; ENDLOOP;
      p↑ ← lvPage;
      ENDLOOP;
    SimpleSpace.Unmap[space];
    l.p↑ ← LAST[LONG CARDINAL];
    l.p ← @l.a[0];
    END;

  IsUtilityPilot: PROC RETURNS [BOOLEAN] = INLINE
    {RETURN[PilotSwitches.switches.u = down]};

  MakeFreeDescriptor: PROC [vol: LogicalVolume.Handle]
    RETURNS [FileInternal.Descriptor] = INLINE
    BEGIN
    RETURN[
      [LogicalVolume.Free[vol], vol.vID, local[
        FALSE, FALSE, vol.volumeSize, PilotFileTypes.tFreePage]]];
    END;

  OpenLogFile: PROC [context: ScavengeContext] =
    BEGIN
    SimpleSpace.Map[context.buffer, [context.logFile, 0], FALSE];
    context.nextWord ← 0
    END;

  OpenScavengeContext: ENTRY PROC [volume: Volume.ID]
    RETURNS [context: ScavengeContext] =
    BEGIN
    DO
      -- Until a free context is found
      FOR i: IndexOfContexts IN IndexOfContexts DO
        IF ~Contexts[i].occupied THEN
          BEGIN
          context ← @Contexts[i];
          context.occupied ← TRUE;
          context.logHeader ← [
            volume: volume, date: System.GetGreenwichMeanTime[],
            incomplete: TRUE, repaired: FALSE, numberOfFiles: 0];
          context.volume ← volume;
          context.problemCount ← 0;
          context.problemIncomplete ← FALSE;
          RETURN;
          END;
        ENDLOOP;
      WAIT freeContext;
      ENDLOOP
    END;

  OrphanPage: PROC [page: PageNumber, vol: LogicalVolume.Handle, context: ScavengeContext] =
    BEGIN
    IF context.problemCount = problemArraySize THEN context.problemIncomplete ← TRUE
    ELSE
      {IF context.problemCount = 0 THEN SimpleSpace.Map[
          context.problemSpace, Space.defaultWindow, TRUE];
      {OPEN context.problem[context.problemCount];
      fileID ← File.nullID;
      problem ← [orphan[id: page]];
      context.problemCount ← context.problemCount + 1}};
    VamPieceMarkBusy[vol, page, 1];
    END;

  PutHeader: PROC [context: ScavengeContext] =
    BEGIN
    SimpleSpace.Unmap[context.buffer];
    --(Re)--OpenLogFile[context];
    LOOPHOLE[context.bufferPointer,
      LONG POINTER TO Scavenger.Header]↑ ← context.logHeader;
    END;

  PutWords: PROC [context: ScavengeContext, words: LONG POINTER, count: CARDINAL]
      RETURNS [success: BOOLEAN] =
    -- stream interface to ScavengeLog
    BEGIN
    THROUGH [0..count) DO
      IF context.nextWord=Environment.wordsPerPage THEN
        BEGIN
        File.SetSize[context.logFile, context.fileLengthPages+1
          ! Volume.InsufficientSpace => GOTO noRoom];
        SimpleSpace.Unmap[context.buffer];
        SimpleSpace.Map[context.buffer, [context.logFile, context.fileLengthPages], FALSE];
        context.nextWord ← 0;
        context.fileLengthPages ← context.fileLengthPages + 1;
        END;
      (context.bufferPointer+context.nextWord)↑ ← words↑;
      context.nextWord ← context.nextWord+1;
      words ← words+1;
      ENDLOOP;
    RETURN[TRUE]
    EXITS
    noRoom => RETURN[FALSE]
    END;

  ReadPage: PROC [fileD: FileInternal.Descriptor, page: File.PageNumber, volumePage: PageNumber,
      destination: Space.PageNumber, handleErrors: BOOLEAN]
      RETURNS [label: PilotDisk.Label, status: DiskChannel.CompletionStatus] =
    BEGIN
    ReadPageProc: --PROC [volume: LogicalVolume.Handle] RETURNS [updateMarkers: BOOLEAN] --
      LogicalVolume.VolumeAccessProc =
      BEGIN
      updateMarkers ← FALSE;
      [label, status] ← LabelTransfer.ReadLabelAndData[fileD, page, volumePage,
          Utilities.PageFromLongPointer[context.bufferPointer], handleErrors];
      END;

    context: ScavengeContext;
    volStatus: LogicalVolume.VolumeAccessStatus;
    context ← OpenScavengeContext[fileD.volumeID];
    SimpleSpace.Map[context.buffer, Space.defaultWindow, TRUE];
    volStatus ← LogicalVolume.VolumeAccess[@fileD.volumeID, ReadPageProc, FALSE];
    Inline.LongCOPY[from: context.bufferPointer, nwords: Environment.wordsPerPage,
        to: Utilities.LongPointerFromPage[destination]];
    SimpleSpace.Unmap[context.buffer];
    CloseScavengeContext[context];
    SELECT volStatus FROM
      ok => NULL;
      ENDCASE => ERROR;
    SELECT status FROM
      notReady => ERROR Scavenger.Error[diskNotReady];
      hardwareError, noSuchPage, seekFailed => ERROR Scavenger.Error[diskHardwareError];
      ENDCASE;
    END;

  ReallyMarkPageBad: PROC [page: PageNumber, svH: SubVolume.Handle, vol: LogicalVolume.Handle] =
    BEGIN
    PhysicalVolume.MarkPageBad[
        pvID: PhysicalVolume.GetContainingPhysicalVolume[svH.lvID],
        badPage: LOOPHOLE[page - svH.lvPage + svH.pvPage] !
      PhysicalVolume.Error => IF error = badSpotTableFull THEN CONTINUE];
    VamMarkBadPage[vol, page];
    END;

  ReplaceMissingPage: PROC [fileP: FileInternal.FilePtr, page: File.PageNumber,
      source: Space.PageNumber] =
    BEGIN
    FillHoleProc: --PROC [volume: LogicalVolume.Handle] RETURNS [updateMarkers: BOOLEAN] --
      LogicalVolume.VolumeAccessProc =
      BEGIN
      updateMarkers ← FALSE;
      IF LogicalVolume.FreeVolumePages[volume] <= 1 THEN insufficientSpace ← TRUE
      ELSE
        {group ← [page, LogicalVolume.nullVolumePage, page + 1];  -- probably should set volumePage
            -- to last good page of the file before the hole, but punt that for now.
        VolAllocMap.AllocPageGroup[volume, fileP, @group, FALSE];
        VolFileMap.InsertPageGroup[volume, fileP, @group]};
      END;

    group: FileInternal.PageGroup;
    insufficientSpace: BOOLEAN ← FALSE;
    volumeID: Volume.ID ← fileP.volumeID;  -- this variable necessary because VolumeAccess requires a
        -- short pointer
    SELECT LogicalVolume.VolumeAccess[@volumeID, FillHoleProc, TRUE] FROM
      ok => NULL;
      volumeReadOnly => ERROR LogicalVolume.ReadOnlyVolume;
      ENDCASE => ERROR;
    IF insufficientSpace THEN ERROR Volume.InsufficientSpace;
    [] ← WritePage[fileP↑, page, group.volumePage, source, TRUE];  -- let Pilot handle any disk errors,
        -- since this is an ordinary, healthy page being written.
    END;

  ReportProblems: PROC [fileID: File.ID, context: ScavengeContext]
      RETURNS [sufficientSpace: BOOLEAN] =
    BEGIN
    hole: PageInterval ← [0, 0];
    sufficientSpace ← FALSE;
    -- Report the unreadable and orphan Problems
    FOR i: CARDINAL IN [0..context.problemCount) DO
      IF context.problem[i].fileID = fileID THEN
        IF ~PutWords[context, @context.problem[i].problem, SIZE[Scavenger.Problem]] THEN RETURN;
      ENDLOOP;
    -- Report the missing page group Problems (i.e., holes in files)
    --  (Only real files have holes; File.nullID is the ID given for orphan pages.)
    IF fileID # File.nullID THEN
      DO
        problem: Scavenger.Problem;
        hole ← FindNextHole[fileID, context, hole];
        IF hole.nextStartingPage = 0 THEN EXIT;
        problem ← [missing[first: hole.startingPage, count: hole.nextStartingPage - hole.startingPage]];
        IF ~PutWords[context, @problem, SIZE[Scavenger.Problem]] THEN RETURN;
        ENDLOOP;
    sufficientSpace ← TRUE;
    END;

  RewriteBadPage: PROC [fileD: FileInternal.Descriptor, page: File.PageNumber,
      group: FileInternal.PageGroup, source: Space.PageNumber] RETURNS [missingPage: BOOLEAN] =
    BEGIN
    DeleteBadPageProc: --PROC [volume: LogicalVolume.Handle] RETURNS [updateMarkers: BOOLEAN] --
      LogicalVolume.VolumeAccessProc =
      BEGIN
      updateMarkers ← FALSE;
      ReallyMarkPageBad[volumePage, svH, volume];
      -- Should alter the VFM at this point so that the bad page becomes a hole in the file instead, and
      -- then fill the hole with a new page, pretending that the page was missing all along.
      -- Can't do it very easily because of peculiarities with VolFileMap.DeletePageGroup (only works
      -- correctly for page group at end of file).  For the moment, just punt the issue.  The next time
      -- the scavenger is run, it will report the page as missing rather than unreadable because we have
      -- added it to the bad page list, so next time RewritePage should work as well.
      DO
        Runtime.CallDebugger[
            "Unreadable page marked permanently bad; suggest running scavenger again."L];
        ENDLOOP;
      END;

    found: BOOLEAN;
    retryCount: CARDINAL ← 8;
    svH: SubVolume.Handle;
    volumePage: LogicalVolume.PageNumber ← group.volumePage + (page - group.filePage);
    missingPage ← FALSE;
    [] ← VerifyPageLabel[fileD, page, volumePage];
    DO
      SELECT WritePage[fileD, page, volumePage, source, FALSE] FROM
        goodCompletion =>
          IF VerifyPageLabel[fileD, page, volumePage] = goodCompletion THEN EXIT
          ELSE  -- bad read after "good" write implies we should retry the write
            IF ((retryCount ← retryCount - 1) <= 0) THEN {missingPage ← TRUE; EXIT};
        ENDCASE => {missingPage ← TRUE; EXIT};  -- write error implies we've already retried, give up
      ENDLOOP;
    IF missingPage THEN  -- rewrite of bad page failed, so get rid of it here and let the missing page
        -- code in RewritePage substitute a new page for it.  (Doesn't work this way yet (see above).)
      {[found, svH] ← SubVolume.Find[fileD.volumeID, volumePage];
      IF ~found THEN ERROR Scavenger.Error[noSuchPage];
      SELECT LogicalVolume.VolumeAccess[@fileD.volumeID, DeleteBadPageProc, TRUE] FROM
        ok => NULL;
        volumeReadOnly => ERROR LogicalVolume.ReadOnlyVolume;
        ENDCASE => ERROR};
    END;

  ScavengeVolumeInternal: PROC [
      vol: LogicalVolume.Handle, space: SimpleSpace.Handle, erase: BOOLEAN,
      context: ScavengeContext] =
    BEGIN
    IF vol.type = nonPilot THEN RETURN; -- Scavenging non-Pilot Volumes is Easy
    vol.changing ← TRUE;
    IF erase THEN VamFind[vol, space]; -- FInd n unblemished pages
    Vam[vol, space, erase, context];
    Vfm[vol, space, erase];
    vol.changing ← FALSE;
    END;

  SpecialGetFileDescriptor: PROC [fileP: FileInternal.FilePtr] RETURNS [found: BOOLEAN] =
    -- This version of GetFileDescriptor is Special because:
    -- 1. it can find a file which is missing page zero;
    -- 2. it knows that a file missing page zero is assumed to be immutable and permanent; and
    -- 3. it doesn't fail on a file which has a dataError in page zero.
    BEGIN
    diskLabelCheck: STRING = "Disk label check"L;
    group: FileInternal.PageGroup;
    label: PilotDisk.Label;
    pageZeroMissing: BOOLEAN;
    status: DiskChannel.CompletionStatus;
    unrecoverableLabelError: STRING = "Unrecoverable disk error:  labelError"L;
    fileP.volumeID ← Volume.nullID;
    [found, group] ← FindContainingPageGroup[fileP, 0];
    IF ~found THEN RETURN;
    IF group.volumePage = LogicalVolume.nullVolumePage THEN
      {fileP.body ← local[immutable: TRUE, temporary: FALSE, size: , type: ];
      pageZeroMissing ← TRUE}
      --A file with page zero missing is assumed to be immutable and permanent.
    ELSE
      {[label, status] ← LabelTransfer.ReadLabel[fileP↑, 0, group.volumePage, FALSE];
      SELECT status FROM
        goodCompletion, dataError =>
          {IF (label.fileID # fileP.fileID) OR (PilotDisk.GetLabelFilePage[@label] # 0) THEN
            DO Runtime.CallDebugger[diskLabelCheck]; ENDLOOP;
          fileP.body ← local[immutable: label.immutable, temporary: label.temporary, size: ,
              type: label.type]};
        labelError => DO Runtime.CallDebugger[unrecoverableLabelError]; ENDLOOP;
        notReady => ERROR Scavenger.Error[diskNotReady];
        ENDCASE => ERROR Scavenger.Error[diskHardwareError]};
    [found, group] ← FindContainingPageGroup[fileP, File.lastPageNumber];
    IF ~found THEN RETURN;
    WITH fileP↑ SELECT FROM
      local => size ← group.filePage;
      ENDCASE => ERROR;
    IF pageZeroMissing THEN  -- get the type from the last page group of the file
      {[found, group] ← FindContainingPageGroup[fileP, group.filePage - 1];
      IF ~found THEN RETURN;
      [label, status] ← LabelTransfer.ReadLabel[fileP↑, group.filePage, group.volumePage, FALSE];
      SELECT status FROM
        goodCompletion, dataError =>
          {IF (label.fileID # fileP.fileID) OR (PilotDisk.GetLabelFilePage[@label] # group.filePage) THEN
            DO Runtime.CallDebugger[diskLabelCheck]; ENDLOOP;
          WITH fileP↑ SELECT FROM
            local => type ← label.type;
            ENDCASE => ERROR};
        labelError => DO Runtime.CallDebugger[unrecoverableLabelError]; ENDLOOP;
        notReady => ERROR Scavenger.Error[diskNotReady];
        ENDCASE => ERROR Scavenger.Error[diskHardwareError]};
    END;

  UnreadablePage: PROC [label: POINTER TO PilotDisk.Label, page: PageNumber,
      vol: LogicalVolume.Handle, context: ScavengeContext] =
    BEGIN
    filePage: PageNumber = PilotDisk.GetLabelFilePage[label];
    SELECT label.type FROM
      PilotFileTypes.tFreePage,
      PilotFileTypes.tVolumeFileMap =>
        {IF ~FixFreePage[page, vol] THEN VamMarkBadPage[vol, page]};
      ENDCASE =>
        {FOR i: CARDINAL IN [0..context.problemCount) DO
          IF context.problem[i].fileID = label.fileID THEN WITH context.problem[i].problem SELECT FROM
            unreadable => SELECT TRUE FROM
              (first # 0) AND (filePage = (first - 1)) =>
                {first ← filePage; count ← count + 1; EXIT};  -- merge with higher group
              filePage = (first + count) => {count ← count + 1; EXIT};  -- merge with lower group
              ENDCASE;
            ENDCASE;
          REPEAT
          FINISHED =>
            IF context.problemCount = problemArraySize THEN
              context.problemIncomplete ← TRUE
            ELSE
              {IF context.problemCount = 0 THEN SimpleSpace.Map[
                  context.problemSpace, Space.defaultWindow, TRUE];
              {OPEN context.problem[context.problemCount];
              fileID ← label.fileID;
              problem ← [unreadable[first: filePage, count: 1]];
              context.problemCount ← context.problemCount + 1}};
          ENDLOOP;
        VamPieceMarkBusy[vol, page, 1]};
    END;

  Vam: PROC [vol: LogicalVolume.Handle, space: SimpleSpace.Handle, erase: BOOLEAN,
      context: ScavengeContext] =
    BEGIN
    found: BOOLEAN;
    list: BadPageList;
    next: PageNumber;
    page: PageNumber ← 1;
    svEnd: Volume.PageCount;
    svH: SubVolume.Handle;
    vamEnd: Volume.PageCount ← vol.vamStart + VamSize[vol];
    VolAllocMap.Close[TRUE]; -- Flush any context VamImpl has
    VamInit[vol];
    WHILE page < vol.volumeSize DO
      [found, svH] ← SubVolume.Find[vol.vID, page];
      IF ~found THEN ERROR ImpossibleScavengerError[cantFindSubvolume];
      GetBadList[svH, space, @list];
      svEnd ← svH.lvPage + svH.nPages;
      WHILE page < svEnd DO
        IF page = CurrentBadPage[@list] THEN
          BEGIN
          VamMarkBadPage[vol, page];
          AdvanceBadPagePointer[@list];
          page ← page + 1;
          LOOP;
          END;
        IF page IN [vol.vamStart..vamEnd) THEN BEGIN page ← vamEnd; LOOP; END;
        next ← MIN[svEnd, CurrentBadPage[@list]];
        IF page < vol.vamStart AND next > vol.vamStart THEN next ← vol.vamStart;
        IF erase THEN VamPieceErase[vol, page, next]
        ELSE VamPieceRebuild[vol, page, next, context];
        page ← next;
        ENDLOOP;
      ENDLOOP;
    END;

  -- Find VamSize pages of good contiguous disk (for now we don't cross subvolume boundaries)
  VamFind: PROC [vol: LogicalVolume.Handle, space: SimpleSpace.Handle] =
    BEGIN
    found: BOOLEAN;
    list: BadPageList;
    page: PageNumber ← 1;
    next: PageNumber;
    svEnd: Volume.PageCount;
    svH: SubVolume.Handle;
    vamSize: Volume.PageCount ← VamSize[vol];
    WHILE page < vol.volumeSize DO
      [found, svH] ← SubVolume.Find[vol.vID, page];
      IF ~found THEN ERROR ImpossibleScavengerError[cantFindSubvolume];
      GetBadList[svH, space, @list];
      svEnd ← svH.lvPage + svH.nPages;
      WHILE page < svEnd DO
        IF page = CurrentBadPage[@list] THEN
          {AdvanceBadPagePointer[@list]; page ← page + 1; LOOP};
        next ← MIN[svEnd, CurrentBadPage[@list]];
        IF (next - page) > vamSize THEN {vol.vamStart ← page; RETURN};
        page ← next;
        ENDLOOP;
      ENDLOOP;
    ERROR ImpossibleScavengerError[noRoomForVam];
    END;

  VamInit: PROC [vol: LogicalVolume.Handle] =
    BEGIN
    vamEnd: PageNumber ← vol.vamStart + VamSize[vol];
    page: PageNumber;
    [] ← LabelTransfer.WriteLabels[
      FileInternal.Descriptor[
      LogicalVolume.Vam[vol], vol.vID, local[
      FALSE, FALSE, vol.volumeSize, PilotFileTypes.tVolumeAllocationMap]],
      FileInternal.PageGroup[vol.vamStart, vol.vamStart, vamEnd]];
    vol.freePageCount ← vol.volumeSize;
    -- Decremented every time a page is marked busy
    vol.lowerBound ← vol.volumeSize;
    -- we will set this when free pages are found.
    [] ← VolAllocMap.AccessVAM[vol, LogicalVolume.rootPageNumber, TRUE, FALSE];
    FOR page ← vol.vamStart, page + 1 WHILE page < vamEnd DO
      [] ← VolAllocMap.AccessVAM[vol, page, TRUE, FALSE];
      ENDLOOP;
    END;

  VamMarkBadPage: PROC [vol: LogicalVolume.Handle, page: PageNumber] =
    {[] ← VolAllocMap.AccessVAM[vol, page, TRUE, FALSE]};

  VamPageGroup: PROC [label: POINTER TO PilotDisk.Label, page, next: PageNumber,
      vol: LogicalVolume.Handle] RETURNS [pageCount: Volume.PageCount] =
    BEGIN
    filePage: PageNumber = PilotDisk.GetLabelFilePage[label];
    freePageGroup: BOOLEAN ← FALSE;
    IF filePage = 0 AND label.zeroSize THEN pageCount ← 1
    ELSE [countValid: pageCount] ← LabelTransfer.VerifyLabels[
        file: [label.fileID, vol.vID,
          local[label.immutable, label.temporary, filePage + (next - page), label.type]],
        pageGroup: [filePage, page, filePage + (next - page)],
        expectErrorAfterFirstPage: TRUE,
        handleErrors: FALSE];
    IF pageCount = 0 THEN ImpossibleScavengerError[labelChanged];
    IF (label.type = PilotFileTypes.tFreePage AND (filePage # page OR
        label.fileID # LogicalVolume.Free[vol])) OR label.type = PilotFileTypes.tVolumeFileMap THEN
      {freePageGroup ← TRUE;
      [] ← LabelTransfer.WriteLabels[MakeFreeDescriptor[vol], [page, page, page + pageCount]]};
    IF (label.type = PilotFileTypes.tFreePage) OR freePageGroup THEN
      {IF vol.lowerBound > page THEN vol.lowerBound ← page}
    ELSE
      VamPieceMarkBusy[vol, page, pageCount];
    END;

  VamPieceErase: PROC [vol: LogicalVolume.Handle, this, next: PageNumber] =
    BEGIN
    [] ← LabelTransfer.WriteLabels[MakeFreeDescriptor[vol], [this, this, next]];
    -- Pages are marked free in vam; no action needed
    IF vol.lowerBound > this THEN vol.lowerBound ← this;
    END;

  VamPieceMarkBusy: PROC [vol: LogicalVolume.Handle, page: PageNumber,
      pageCount: Volume.PageCount] =
    BEGIN
    WHILE pageCount > 0 --no, a subrange won't work-- DO
      IF VolAllocMap.AccessVAM[vol, page, TRUE, FALSE] THEN
        ERROR ImpossibleScavengerError[pageAlreadyBusy];
      -- can't be already set
      page ← page + 1;
      pageCount ← pageCount - 1;
      ENDLOOP;
    END;

  VamPieceRebuild: PROC [vol: LogicalVolume.Handle, page, next: PageNumber,
      context: ScavengeContext] =
    BEGIN
    label: PilotDisk.Label;
    pageCount: Volume.PageCount;
    status: DiskChannel.CompletionStatus;
    WHILE page < next DO
      [label, status] ← LabelTransfer.ReadLabel[[, vol.vID, local[,,,]], 0, page, FALSE];
      SELECT status FROM
        goodCompletion =>
          {pageCount ← VamPageGroup[@label, page, next, vol]; page ← page + pageCount};
        dataError => {UnreadablePage[@label, page, vol, context]; page ← page + 1};
        labelError => {OrphanPage[page, vol, context]; page ← page + 1};
        notReady => ERROR Scavenger.Error[diskNotReady];
        ENDCASE => ERROR Scavenger.Error[diskHardwareError];
      ENDLOOP;
    END;

  VamSize: PROC [vol: LogicalVolume.Handle] RETURNS [Volume.PageCount] =
    {RETURN[(vol.volumeSize + bitsPerPage)/bitsPerPage]};

  VerifyPageLabel: PROC [fileD: FileInternal.Descriptor, page: File.PageNumber,
        volumePage: LogicalVolume.PageNumber] RETURNS [status: DiskChannel.CompletionStatus] =
    BEGIN
    [status: status] ← LabelTransfer.VerifyLabels[fileD, [page, volumePage, page + 1], FALSE, FALSE];
    SELECT status FROM
      goodCompletion, dataError => NULL;  -- the label matched
      labelDoesNotMatch => DO Runtime.CallDebugger["Disk label check"L]; ENDLOOP;
      labelError => DO Runtime.CallDebugger["Unrecoverable disk error:  labelError"L]; ENDLOOP;
      notReady => ERROR Scavenger.Error[diskNotReady];
      ENDCASE => ERROR Scavenger.Error[diskHardwareError];
    END;

  Vfm: PROC [vol: LogicalVolume.Handle, space: SimpleSpace.Handle, erase: BOOLEAN] =
    BEGIN
    found: BOOLEAN;
    list: BadPageList;
    next: PageNumber;
    page: PageNumber ← 1;
    svEnd: Volume.PageCount;
    svH: SubVolume.Handle;
    VolFileMap.Close[TRUE];
    VolFileMap.InitMap[vol];
    IF ~erase THEN
      WHILE page < vol.volumeSize DO
        [found, svH] ← SubVolume.Find[vol.vID, page];
        IF ~found THEN ERROR ImpossibleScavengerError[cantFindSubvolume];
        GetBadList[svH, space, @list];
        svEnd ← svH.lvPage + svH.nPages;
        WHILE page < svEnd DO
          IF page = CurrentBadPage[@list] THEN
            {AdvanceBadPagePointer[@list]; page ← page + 1; LOOP};
          next ← MIN[svEnd, CurrentBadPage[@list]];
          VfmPieceRebuild[vol, page, next];
          page ← next;
          ENDLOOP;
        ENDLOOP;
    END;

  VfmPageGroup: PROC [label: POINTER TO PilotDisk.Label, page, next: PageNumber,
      vol: LogicalVolume.Handle, status: DiskChannel.CompletionStatus]
      RETURNS [pageCount: Volume.PageCount] =
    BEGIN
    fileD: FileInternal.Descriptor ← [, vol.vID, local[, , , ]];
    filePage: File.PageNumber;
    group: FileInternal.PageGroup;
    IF label.type IN PilotFileTypes.PilotVFileType THEN RETURN[pageCount: 1];  -- "volume" files are not
        -- listed in the VFM.
    filePage ← PilotDisk.GetLabelFilePage[label];
    fileD.fileID ← label.fileID;
    SELECT TRUE FROM
      filePage = 0 AND label.zeroSize => {group ← [0, page, 0]; pageCount ← 1};
      status = dataError => {group ← [filePage, page, filePage + 1]; pageCount ← 1};
        -- handle a dataError page as a one-page group.
      ENDCASE =>
        {[countValid: pageCount] ← LabelTransfer.VerifyLabels[
            file: [label.fileID, vol.vID,
              local[label.immutable, label.temporary, filePage + (next - page), label.type]],
            pageGroup: [filePage, page, filePage + (next - page)],
            expectErrorAfterFirstPage: TRUE,
            handleErrors: FALSE];
        IF pageCount = 0 THEN ImpossibleScavengerError[labelChanged];
        group ← [filePage, page, filePage + pageCount]};
    VolFileMap.InsertPageGroup[vol, @fileD, @group];
    END;

  VfmPieceRebuild: PROC [vol: LogicalVolume.Handle, page, next: PageNumber] =
    BEGIN
    fileD: FileInternal.Descriptor ← [, vol.vID, local[, , , ]];
    label: PilotDisk.Label;
    pageCount: Volume.PageCount;
    status: DiskChannel.CompletionStatus;
    WHILE page < next DO
      IF ~VolAllocMap.AccessVAM[vol, page, FALSE, FALSE] THEN {page ← page + 1; LOOP};
      [label, status] ← LabelTransfer.ReadLabel[fileD, 0, page, FALSE];
      SELECT status FROM
        goodCompletion, dataError =>
          {pageCount ← VfmPageGroup[@label, page, next, vol, status]; page ← page + pageCount};
            -- a page with a dataError has a readable label; it must be inserted in the VFM so that it can be
            -- found by RewritePage via file ID and file page number; it should have been logged already by
            -- VamPieceRebuild.
        labelError => {page ← page + 1};  -- orphan page; should have been logged already by
            -- VamPieceRebuild.
        notReady => ERROR Scavenger.Error[diskNotReady];
        ENDCASE => ERROR Scavenger.Error[diskHardwareError];
      ENDLOOP;
    END;

  WritePage: PROC [fileD: FileInternal.Descriptor, page: File.PageNumber, volumePage: PageNumber,
      source: Space.PageNumber, handleErrors: BOOLEAN]
      RETURNS [status: DiskChannel.CompletionStatus] =
    BEGIN
    WritePageProc: --PROC [volume: LogicalVolume.Handle] RETURNS [updateMarkers: BOOLEAN] --
      LogicalVolume.VolumeAccessProc =
      BEGIN
      updateMarkers ← FALSE;
      status ← LabelTransfer.WriteLabelAndData[
        file: fileD,
        filePage: page,
        volumePage: volumePage,
        memoryPage: Utilities.PageFromLongPointer[context.bufferPointer],
        bootChainLink: LOOPHOLE[PilotDisk.nullLabel.bootChainLink],
        handleErrors: handleErrors];
      END;

    context: ScavengeContext;
    volStatus: LogicalVolume.VolumeAccessStatus;
    context ← OpenScavengeContext[fileD.volumeID];
    SimpleSpace.Map[context.buffer, Space.defaultWindow, TRUE];
    Inline.LongCOPY[from: Utilities.LongPointerFromPage[source], nwords: Environment.wordsPerPage,
        to: context.bufferPointer];
    volStatus ← LogicalVolume.VolumeAccess[@fileD.volumeID, WritePageProc, TRUE];
    SimpleSpace.Unmap[context.buffer];
    CloseScavengeContext[context];
    SELECT volStatus FROM
      ok => NULL;
      volumeReadOnly => ERROR LogicalVolume.ReadOnlyVolume;
      ENDCASE => ERROR;
    SELECT status FROM
      notReady => ERROR Scavenger.Error[diskNotReady];
      hardwareError, noSuchPage, seekFailed => ERROR Scavenger.Error[diskHardwareError];
      ENDCASE;
    END;


  -- Initialization

  Initialize: PROC =
    BEGIN
    FOR i: IndexOfContexts IN IndexOfContexts DO
      context: ScavengeContext;
      context ← @Contexts[i];
      context.occupied ← FALSE;
      context.buffer ← SimpleSpace.Create[1, hyperspace];
      context.bufferPointer ← SimpleSpace.LongPointer[context.buffer];
      context.problemSpace ← SimpleSpace.Create[problemSpacePages, hyperspace];
      context.problem ← LOOPHOLE[SimpleSpace.LongPointer[context.problemSpace]];
      ENDLOOP;
    END;

  Initialize[];
  END.....


LOG
Time: October 2, 1979  11:51 AM	By: Forrest	Action: Created file from Vol*MapImpl.mesa
Time: October 16, 1979  1:35 AM	By: Forrest	Action: Called Vol*map.close after scavange
Time: November 6, 1979  11:34 AM	By: Forrest	Action: Fixed VFM scavenge (as examining bogus pages)
Time: November 16, 1979  4:13 PM	By: Forrest	Action: Added check for non-pilot volumes
Time: November 19, 1979  11:50 AM	By: Forrest	Action: Added missing ~ to erase in VFM
Time: January 10, 1980  6:29 PM	By: Forrest	Action: Take advantage of new LabelTransfer interface
Time: March 7, 1980  9:16 PM	By: Forrest	Action: Change calls to Vol*mapClose
Time: May 29, 1980  10:22 AM	By: Luniewski	Action: PhysicalVolume => PhysicalVolumeFormat.  Exports PhysicalVolume.Error due to compiler bug vis a vis PhysicalVolumeImpl.
Time: June 23, 1980  2:11 PM	By: Luniewski	Action: Interim implementation of the Scavenger interface.  This just enumerates files after a scavenge completes.
Time: July 16, 1980  5:57 PM	By: McJones	Action: Rewrite FOR loop in EnumerateFiles as plain DO loop to avoid Mesa AR 4956; FilePageLabel=>PilotDisk
Time: August 4, 1980  4:33 PM	By: Luniewski	Action: Delete old log files whenever possible.
Time: September 17, 1980  2:39 PM	By: Luniewski	Action: Use SimpleSpace.LongPointer instead of Space.LongPointer.
Time: October 9, 1980  4:09 PM	By: Forrest	Action: Convert log file access to a stream-type interface.
Time: October 11, 1980  10:10 PM	By: Forrest	Action: Add ExpectErrorAfterFirstPage parameter to VerifyLabels.
Time: January 12, 1981  1:31 PM	By: Luniewski	Action: New LabelTransfer interface
Time: February 1, 1981  5:12 PM	By: Fay	Action: Implement Problem entries and associated
procedures for missing, orphan, and unreadable pages.
Time: February 27, 1981  10:00 AM	By: Yokota	Action: Temporary LOOPHOLEs are removed
Time: 20-Mar-81 11:21:55	By: Gobbel	Action: Fixed bug in Scavenger.RewritePage