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