-- 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