-- PhysicalVolumeScavengerImplD0DLion.mesa modified September 8, 1982 10:59 am by Taft -- Version for D0/DLion (i.e., for Shugart disks) -- 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 [sa1000, 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], FormatSA1000andSA4000 USING [FirstSA1000PageForPilot, SA4000startOfModel44s], 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]; PhysicalVolumeScavengerImplD0DLion: 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 FSa: FormatSA1000andSA4000; found: BOOLEAN ← TRUE; page: PhysicalVolume.PageNumber ← SELECT PhysicalVolume.InterpretHandle[pvInstance].type FROM DeviceTypes.sa1000 => FSa.FirstSA1000PageForPilot, DeviceTypes.sa4000 => -- assume no Alto partitions FSa.SA4000startOfModel44s, 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. September 8, 1982 11:00 am Taft Renamed to PhysicalVolumeScavengerImplD0DLion, since it really is D0/DLion specific