Cedar Nucleus (Files): access to physical and logical volumes
PhysicalVolumeImpl.mesa
Andrew Birrell July 8, 1983 2:55 pm
DIRECTORY
Disk USING[ DoIO, Channel, DriveAttributes, InspectDiskShape, Label, labelCheck, NextChannel, ok, PageCount, PageNumber, Request, Status, Valid ],
DiskFace USING[ wordsPerPage ],
File USING[ VolumeID ],
FileInternal USING[ ReadRootPage, TranslateStatus, VolumeObject ],
VolumeFormat,
PhysicalVolume USING[ Credentials, PhysicalRC, SubVolumeDetails, SubVolumeDetailsObject ],
VM USING[ AddressToPageNumber, Allocate, Free, PageNumberToAddress, Pin, Unpin, wordsPerPage ],
Rope USING[ ROPE, FromRefText ];
PhysicalVolumeImpl: CEDAR MONITOR
IMPORTS Disk, FileInternal, Rope, VM
EXPORTS DiskFace--AbsID--, File, PhysicalVolume =
BEGIN
This implementation currently doesn't really do the right things, particularly about volumes going offline. It will get rewritten sometime. (ADB May 2, 1983 11:48 am).
******** List of known physical volumes ********
PageData: TYPE = LONG POINTER;
pagePages: INT = (DiskFace.wordsPerPage+VM.wordsPerPage-1)/VM.wordsPerPage;
AccessPage: UNSAFE PROC[channel: Disk.Channel,
page: Disk.PageNumber,
label: LONG POINTER TO Disk.Label,
data: PageData,
direction: { read, write },
verifyLabel: BOOL --else read label--]
RETURNS[ status: Disk.Status ] = TRUSTED
BEGIN
ENABLE UNWIND => VM.Unpin[[page: VM.AddressToPageNumber[data], count: pagePages]];
countDone: Disk.PageCount;
req: Disk.Request ← [
diskPage: page,
data: data,
command: [header: verify,
label: IF verifyLabel THEN verify ELSE read,
data: IF direction=read THEN read ELSE write],
count: 1 ];
VM.Pin[[page: VM.AddressToPageNumber[data], count: pagePages]];
[status, countDone] ← Disk.DoIO[channel, label, @req];
VM.Unpin[[page: VM.AddressToPageNumber[data], count: pagePages]];
END;
FreePage: UNSAFE PROC[data: PageData] = TRUSTED
{ VM.Free[[page: VM.AddressToPageNumber[data], count: pagePages]] };
ReadPage: PROC[channel: Disk.Channel,
page: Disk.PageNumber,
label: LONG POINTER TO Disk.Label,
verifyLabel: BOOL]
RETURNS[ status: Disk.Status, data: PageData ] = TRUSTED
BEGIN
data ← VM.PageNumberToAddress[VM.Allocate[pagePages].page];
status ← AccessPage[channel, page, label, data, read, verifyLabel];
IF status # Disk.ok THEN { FreePage[data]; data ← NIL };
END;
PhysRoot: TYPE = LONG POINTER TO VolumeFormat.PhysicalRoot;
--DiskFace.--AbsID: PUBLIC TYPE = VolumeFormat.AbsID;
--DiskFace.--Attributes: PUBLIC TYPE = VolumeFormat.Attributes;
PhysRootLabel: PROC[id: File.VolumeID] RETURNS[Disk.Label] = TRUSTED
BEGIN
RETURN[ [
fileID: [abs[AbsID[id]]],
filePage: 0,
attributes: Attributes[physicalRoot],
dontCare: LOOPHOLE[LONG[0]]
] ]
END;
CredentialsLabel: PROC[id: File.VolumeID] RETURNS[Disk.Label] = TRUSTED
BEGIN
I'd prefer using the pvID and a separate file type. For now, compatibility wins (ADB)
cannedID: MACHINE DEPENDENT RECORD[a,b,c,d,e: CARDINAL] =
[a: 076543B, b: 021076B, c: 054321B, d: 076543B, e: 021076B]; -- ask Roy!
RETURN[ [
fileID: [abs[AbsID[LOOPHOLE[cannedID]]]],
filePage: 0,
attributes: Attributes[LOOPHOLE[0]],
dontCare: LOOPHOLE[LONG[0]]
] ]
END;
ReadPhysicalRoot: PROC[channel: Disk.Channel]
RETURNS[ status: Disk.Status, root: PhysRoot ] = TRUSTED
BEGIN
label: Disk.Label;
data: PageData;
[status, data] ← ReadPage[channel, VolumeFormat.rootPageNumber, @label, FALSE];
root ← LOOPHOLE[data];
IF status = Disk.ok AND label # PhysRootLabel[root.pvID]
THEN { status ← Disk.labelCheck; FreePage[data]; root ← NIL };
END;
--PhysicalVolume.--PhysicalObject: PUBLIC TYPE = RECORD[
channel: Disk.Channel,
rootStatus: PhysicalVolume.PhysicalRC ← ok,
root: PhysRoot ← NIL,
name: Rope.ROPENIL,
rest: REF PhysicalObject ← NIL ];
Physical: TYPE = REF PhysicalObject;
physicalList: REF PhysicalObject ← NIL; -- known physical volumes --
NextPhysical: PUBLIC PROC[prev: Physical, wait: BOOL]
RETURNS[next: Physical] = TRUSTED
BEGIN
DO lastCh: Disk.Channel;
[next, lastCh] ← EntryNextPhysical[prev];
IF next # NIL OR NOT wait THEN EXIT;
[] ← Disk.NextChannel[lastCh, TRUE];
ENDLOOP;
END;
lastChannel: Disk.Channel ← NIL;
EntryNextPhysical: ENTRY PROC[prev: Physical]
RETURNS[next: Physical, lastCh: Disk.Channel] =
{ [next, lastCh] ← InnerNextPhysical[prev] };
InnerNextPhysical: INTERNAL PROC[prev: Physical]
RETURNS[next: Physical, lastCh: Disk.Channel] = TRUSTED
BEGIN
next ← IF prev = NIL THEN physicalList ELSE prev.rest;
-- check whether found volume has changed state --
WHILE next # NIL AND NOT Disk.Valid[next.channel]
DO AssignPhysical[prev, next.rest]; next ← next.rest; ENDLOOP;
IF next = NIL
THEN BEGIN
-- Check for a new channel --
new: Disk.Channel = Disk.NextChannel[lastChannel];
IF new # NIL THEN { AssignPhysical[prev, NewPhysical[new]]; lastChannel ← new };
next ← IF prev = NIL THEN physicalList ELSE prev.rest;
END;
lastCh ← lastChannel;
END;
AssignPhysical: INTERNAL PROC[tail: Physical, new: Physical] =
{ IF tail = NIL THEN physicalList ← new ELSE tail.rest ← new };
NewPhysical: INTERNAL PROC[channel: Disk.Channel]
RETURNS[ new: Physical ] = TRUSTED
BEGIN
status: Disk.Status;
new ← NEW[PhysicalObject ← [channel: channel]];
IF NOT Disk.InspectDiskShape[channel, quickReadOnly] THEN ERROR;
[status, new.root] ← ReadPhysicalRoot[channel];
new.rootStatus ← FileInternal.TranslateStatus[status];
IF new.rootStatus # ok THEN RETURN;
IF new.root.seal # VolumeFormat.PRSeal
OR new.root.version # VolumeFormat.PRCurrentVersion
THEN { new.rootStatus ← inconsistent; RETURN };
TRUSTED{ new.name ← GetName[@new.root.label, new.root.labelLength] };
now enumerate its sub-volumes to see what logical volume they belong to.
FOR s: INT IN [0..new.root.subVolumeCount)
DO svd: LONG POINTER TO VolumeFormat.SubVolumeDesc = @new.root.subVolumes[s];
l: REF LogicalObject ← NIL;
FOR l ← logicalList, l.rest UNTIL l = NIL
DO IF l.id = svd.lvID THEN EXIT
REPEAT FINISHED =>
BEGIN
l ← NEW[LogicalObject ← [id: svd.lvID, size: svd.lvSize, rest: NIL]];
IF lastLogical = NIL THEN logicalList ← l ELSE lastLogical.rest ← l;
lastLogical ← l;
END;
ENDLOOP;
-- Now, "l" is appropriate LogicalObject --
BEGIN
this: PhysicalVolume.SubVolumeDetails = NEW[PhysicalVolume.SubVolumeDetailsObject ← [
id: svd.lvID,
start: svd.lvPage,
size: svd.nPages,
physical: new,
channel: channel,
address: svd.pvPage] ];
prev: LIST OF PhysicalVolume.SubVolumeDetails ← NIL;
FOR sv: LIST OF PhysicalVolume.SubVolumeDetails ← l.subVols, sv.rest UNTIL sv = NIL
DO IF NOT Disk.Valid[sv.first.channel]
THEN BEGIN -- remove from chain --
IF prev = NIL
THEN l.subVols ← sv.rest
ELSE prev.rest ← sv.rest;
LOOP
END;
IF sv.first.start > this.start
THEN BEGIN -- insert before "sv" --
IF this.start + this.size > sv.first.start
THEN { new.rootStatus ← inconsistent; RETURN };
IF prev = NIL
THEN l.subVols ← CONS[first: this, rest: sv]
ELSE prev.rest ← CONS[first: this, rest: sv];
EXIT
END;
IF sv.first.start + sv.first.size > this.start
THEN { new.rootStatus ← inconsistent; RETURN };
prev ← sv;
REPEAT FINISHED =>
IF prev = NIL
THEN l.subVols ← CONS[first: this, rest: NIL]
ELSE prev.rest ← CONS[first: this, rest: NIL]
ENDLOOP;
END;
ENDLOOP;
END;
GetName: PROC[
chars: LONG POINTER TO PACKED ARRAY [0..VolumeFormat.volumeLabelLength) OF CHAR,
length: INT]
RETURNS[Rope.ROPE] = TRUSTED
BEGIN
text: REF TEXT = NEW[TEXT[length]];
FOR i: INT IN [0..length) DO text[i] ← chars[i] ENDLOOP;
text.length ← length;
RETURN[Rope.FromRefText[text]]
END;
PhysicalInfo: PUBLIC PROC[physical: Physical] RETURNS[
channel: Disk.Channel,
rootStatus: PhysicalVolume.PhysicalRC,
id: File.VolumeID, -- unique id of this physical volume --
name: Rope.ROPE,
size: Disk.PageCount,
free: Disk.PageCount,
timeValid: BOOL,
time: VolumeFormat.TimeParameters] = TRUSTED
BEGIN
size ← Disk.DriveAttributes[physical.channel].nPages;
channel ← physical.channel;
rootStatus ← physical.rootStatus;
IF rootStatus = ok
THEN BEGIN
lastSV: VolumeFormat.SubVolumeDesc =
physical.root.subVolumes[physical.root.subVolumeCount-1];
id ← physical.root.pvID;
name ← physical.name;
free ← size - (lastSV.pvPage + lastSV.nPages);
timeValid ← physical.root.timeParametersValid;
time ← physical.root.timeParameters;
END;
END;
WriteTimeParameters: PUBLIC PROC[physical: Physical, timeValid: BOOL, time: VolumeFormat.TimeParameters] RETURNS[ PhysicalVolume.PhysicalRC ] = TRUSTED
BEGIN
label: Disk.Label ← PhysRootLabel[physical.root.pvID];
status: Disk.Status;
physical.root.timeParametersValid ← timeValid;
IF timeValid THEN physical.root.timeParameters ← time;
status ← AccessPage[physical.channel, VolumeFormat.rootPageNumber,
@label, physical.root, write, TRUE];
RETURN[ FileInternal.TranslateStatus[status] ]
END;
ReadCredentials: PUBLIC UNSAFE PROC[physical: Physical, credentials: PhysicalVolume.Credentials] RETURNS[ PhysicalVolume.PhysicalRC ] = UNCHECKED
BEGIN
label: Disk.Label ← CredentialsLabel[physical.root.pvID];
status: Disk.Status = AccessPage[physical.channel, VolumeFormat.credentialsPageNumber,
@label, credentials, read, TRUE];
RETURN[ FileInternal.TranslateStatus[status] ]
END;
WriteCredentials: PUBLIC PROC[physical: Physical, credentials: PhysicalVolume.Credentials] RETURNS[ PhysicalVolume.PhysicalRC ] = TRUSTED
BEGIN
label: Disk.Label ← CredentialsLabel[physical.root.pvID];
status: Disk.Status = AccessPage[physical.channel, VolumeFormat.credentialsPageNumber,
@label, credentials, write, TRUE];
RETURN[ FileInternal.TranslateStatus[status] ]
END;
******** Accumulation of sub-volumes into logical volumes ********
LogicalObject: TYPE = RECORD[
id: File.VolumeID,
size: VolumeFormat.LogicalPageCount,
subVols: LIST OF PhysicalVolume.SubVolumeDetails ← NIL, -- ordered list of fragments --
rest: REF LogicalObject ];
Logical: TYPE = REF LogicalObject;
logicalList: REF LogicalObject ← NIL; -- known logical volumes --
NextLogical: PROC[prev: Logical, wait: BOOL]
RETURNS[next: Logical] =
BEGIN
DO lastP: Physical;
[next, lastP] ← EntryNextLogical[prev];
IF next # NIL OR NOT wait THEN EXIT;
[] ← NextPhysical[lastP, TRUE];
ENDLOOP;
END;
lastPhysical: REF PhysicalObject ← NIL;
EntryNextLogical: ENTRY PROC[prev: Logical] RETURNS[next: Logical, lastP: Physical] =
{ [next, lastP] ← InnerNextLogical[prev] };
InnerNextLogical: INTERNAL PROC[prev: Logical] RETURNS[next: Logical, lastP: Physical] =
BEGIN
-- check for any more physical volumes --
FOR p: Physical ← InnerNextPhysical[lastPhysical].next, InnerNextPhysical[p].next
UNTIL p = NIL
DO lastPhysical ← p ENDLOOP;
next ← prev;
DO wanted: INT ← 0;
next ← IF next = NIL
THEN logicalList
ELSE next.rest;
IF next = NIL THEN RETURN[NIL, lastPhysical];
-- check entire volume is known --
FOR sv: LIST OF PhysicalVolume.SubVolumeDetails ← next.subVols, sv.rest UNTIL sv = NIL
DO IF wanted # sv.first.start OR NOT Disk.Valid[sv.first.channel] THEN EXIT;
wanted ← wanted + sv.first.size;
ENDLOOP;
IF wanted = next.size THEN EXIT;
ENDLOOP;
RETURN[next, lastPhysical]
END;
Valid: INTERNAL PROC[list: LIST OF PhysicalVolume.SubVolumeDetails] RETURNS[BOOL] = TRUSTED
BEGIN
FOR sv: LIST OF PhysicalVolume.SubVolumeDetails ← list, sv.rest UNTIL sv = NIL
DO IF NOT Disk.Valid[sv.first.channel] THEN RETURN[FALSE] ENDLOOP;
RETURN[TRUE]
END;
******** Top-level: list of File.Volume objects ********
Volume: TYPE = REF VolumeObject;
--File.--VolumeObject: PUBLIC TYPE = FileInternal.VolumeObject;
volumeList: Volume ← NIL; -- known volumes --
--File.--NextVolume: PUBLIC PROC[volume: Volume, wait: BOOLFALSE]
RETURNS[next: Volume] =
BEGIN
DO lastLog: Logical;
[next, lastLog] ← EntryNext[volume];
IF next # NIL OR NOT wait THEN EXIT;
[] ← NextLogical[lastLog, TRUE];
ENDLOOP;
If "next" is newly created, we're responsible for reading its root page. Note that if next.initialised = FALSE, we have exclusive access to next.root (others wait inside EntryNext until we're ready). If next.root # NIL, then it's immutable. So we can read next.root outside the monitor lock.
IF next # NIL AND NOT next.initialised
THEN { FileInternal.ReadRootPage[next]; NoteRootRead[next] };
END;
lastLogical: Logical ← NIL;
rootRead: CONDITION;
EntryNext: ENTRY PROC[prev: Volume]
RETURNS[next: Volume, lastLog: Logical] =
BEGIN
next ← IF prev = NIL THEN volumeList ELSE prev.rest;
-- check whether next volume has gone offline --
WHILE next # NIL AND NOT Valid[next.subVolumes]
DO Assign[prev, next.rest]; next ← next.rest; ENDLOOP;
IF next = NIL
THEN BEGIN
-- Check for a new volume --
new: Logical = InnerNextLogical[lastLogical].next;
IF new # NIL THEN { Assign[prev, NewVolume[new]]; lastLogical ← new };
next ← IF prev = NIL THEN volumeList ELSE prev.rest;
END
ELSE UNTIL next.initialised DO WAIT rootRead ENDLOOP;
lastLog ← lastLogical;
END;
Assign: INTERNAL PROC[tail: Volume, new: Volume] =
{ IF tail = NIL THEN volumeList ← new ELSE tail.rest ← new };
NoteRootRead: ENTRY PROC[volume: Volume] =
{ volume.initialised ← TRUE; BROADCAST rootRead };
NewVolume: INTERNAL PROC[logical: Logical]
RETURNS[volume: Volume] =
BEGIN
volume ←
NEW[VolumeObject ← [id: logical.id, subVolumes: logical.subVols, size: logical.size]];
END;
--PhysicalVolume.--GetPhysical: PUBLIC PROC[volume: Volume, page: VolumeFormat.LogicalPage ← [0]] RETURNS[Physical] =
BEGIN
FOR sv: LIST OF PhysicalVolume.SubVolumeDetails ← volume.subVolumes, sv.rest
UNTIL sv = NIL
DO IF sv.first.start <= page AND sv.first.start + sv.first.size > page
THEN RETURN[sv.first.physical];
ENDLOOP;
RETURN[NIL]
END;
END.