-- AccessControlMainImpl.mesa
-- Last edited by
-- Kolling on May 27, 1983 4:00 pm


DIRECTORY

AccessControl
USING[OperationFailed, StaticallyInvalid, Unknown],
AccessControlFile
USING[ComputeLastDataRecNumberFromFileLength, FirstDataRecNum,
GetNextDataRecNumber, LengthToSetOwnerFile, LockConflict,
OutOfRecNumRange, PagesPerRec, ReadDataRec, ReadDataRecViaRecNum,
ReadHeaderRec, Stopped, UnlockDataRecViaRecNum, WordsPerRec,
WriteDataRec, WriteHeaderRec],
AccessControlLight
USING[ApplyMinsDefaultsAndFilterRequestedWriteProperties],
AccessControlMain
USING[Caller, OpWeight],
AccessControlMainAux
USING[InitializeOwnerRecord, OverWriteOwnerSpaceInUseToRecord,
ReadOwnerAccessListFromRecord, ReadOwnerQuotaFromRecord,
ReadOwnerRootFileFromRecord, ReadOwnerSpaceInUseFromRecord,
WriteOwnerPropsToRecord],
AccessControlPrivate
USING[FileDataRec, FileHeaderRec, InternalAccessControlLogicError, nullRecNumber,
OwnerFileFormatVersion, PntrDataRec, PntrHeaderRec, RecNumber,
SpecialOwnerForAlpineAdmin],
AccessControlTransMap
USING[GetTransID, Handle, nullHandle],
AccessControlUtility
USING[Compare, MakeOwnerStringRepFromRName, MakeRNameFromOwnerStringRep],
AlpineEnvironment
USING[AccessList, AccessRights, CommitOrAbort, Conversation, FileID, LockMode,
nullOpenFileID, nullUniversalFile, nullVolumeGroupID, OpenFileID,
OwnerName, OwnerProperty, OwnerPropertySet, OwnerPropertyValuePair,
PageCount, PageNumber, UniversalFile, VolumeGroupID, VolumeID],
AlpineFile
USING[Create, Delete, GetSize, LockFailed, Open, OperationFailed,
ReadPages, SetSize, Unknown, WritePages],
AlpineIdentity
USING[myLocalConversation];


AccessControlMainImpl: MONITOR
IMPORTS AC: AccessControl, ACF: AccessControlFile, ACMA: AccessControlMainAux,
ACP: AccessControlPrivate, ACL: AccessControlLight, ACU: AccessControlUtility, AF:
AlpineFile, AID: AlpineIdentity, TM: AccessControlTransMap
EXPORTS AccessControl, AccessControlMain =


BEGIN OPEN ACM: AccessControlMain, AE: AlpineEnvironment;

-- this module should contain no checking for privileges, since we do not want to wait for Grapevine inside the monitor.



-- for Light operations:


-- For owner routines that need to get the header page.

CommonChangeOwnerAndHeader: PUBLIC ENTRY SAFE PROCEDURE[volGroupID:
AE.VolumeGroupID, caller: ACM.Caller, transHandle: TM.Handle, ownerName: AE.OwnerName,
overCommitQuotasIfNeeded: BOOLEAN, ownerProperties: LIST OF AE.OwnerPropertyValuePair,
newQuota: AE.PageCount] RETURNS[spaceLeftOnVolumeGroup: AE.PageCount] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.OperationFailed[duplicateOwner, ownerDatabaseFull, ownerRecordFull, ownerRecordInUse, spaceInUseByThisOwner, totalQuotaExceeded], AC.StaticallyInvalid(badLengthName), AC.Unknown[owner, transID, volumeGroupID].
ENABLE BEGIN
AC.Unknown => IF why = openFileID THEN GOTO transAborted;
UNWIND => NULL;
END;

volGroupKey: VolGroupKey;
ownerOpenFileID: AE.OpenFileID;
fileHeaderRec: ACP.FileHeaderRec;
fileDataRec: ACP.FileDataRec;
dataRecNum, lastDataRecNum: ACP.RecNumber;
oldQuota: AE.PageCount;
increaseInSpace: AE.PageCount;
reclaimedRec: BOOLEAN;
[volGroupKey, ownerOpenFileID, lastDataRecNum] ←
CheckRegStatGetVolGroupMaybeOpenFile[transHandle, @volGroupID, prevReg, light,
TRUE];
ACF.ReadHeaderRec[ownerOpenFileID, write, @fileHeaderRec];
[dataRecNum, reclaimedRec] ← ACF.ReadDataRec[ownerOpenFileID, ownerName,(IF caller = add
THEN wantEmpty ELSE wantOwner), write, @fileDataRec, lastDataRecNum
! AC.OperationFailed => IF ((why = ownerDatabaseFull) AND
(fileHeaderRec.numberOfOwners < fileHeaderRec.maxNumberOfOwnersAllowed))
THEN ERROR];
oldQuota ← (IF caller = add THEN 0 ELSE ACMA.ReadOwnerQuotaFromRecord[@fileDataRec]);
increaseInSpace ← newQuota - oldQuota;
IF NOT ((increaseInSpace <= 0) OR (overCommitQuotasIfNeeded) OR
(fileHeaderRec.quotaLeft >= increaseInSpace))
THEN ERROR AC.OperationFailed[totalQuotaExceeded];
SELECT caller FROM
add => BEGIN
IF ((NOT reclaimedRec) AND (fileHeaderRec.numberOfOwnerSlotsInUse =
fileHeaderRec.maxNumberOfOwnersAllowed)) THEN ERROR
AC.OperationFailed[ownerDatabaseFull];
ACMA.InitializeOwnerRecord[@fileDataRec];
ACU.MakeOwnerStringRepFromRName[ownerName, @fileDataRec.ownerName];
ACMA.WriteOwnerPropsToRecord[@fileDataRec, ownerProperties, ownerName];
fileDataRec.state ← valid;
fileHeaderRec.numberOfOwners ← fileHeaderRec.numberOfOwners + 1;
IF NOT reclaimedRec THEN fileHeaderRec.numberOfOwnerSlotsInUse ←
fileHeaderRec.numberOfOwnerSlotsInUse + 1;
END
;
-- remove goes to ACV because inuse is sum and also it will remove the
-- volatile entry for this owner.
remove => BEGIN RemoveTheOwner[volGroupKey, ownerName, transHandle,
@fileDataRec];
fileDataRec.state ← deleted;
fileHeaderRec.numberOfOwners ← fileHeaderRec.numberOfOwners - 1;
END;
-- writeProps goes to ACV because it will change the volatile entry's
-- quota.
writeProps => BEGIN ChangeQuotaOwner[volGroupKey, ownerName,
transHandle, newQuota, oldQuota];
ACMA.WriteOwnerPropsToRecord[@fileDataRec, ownerProperties, ownerName];
END;
ENDCASE;
fileHeaderRec.quotaLeft ← fileHeaderRec.quotaLeft - increaseInSpace;
ACF.WriteDataRec[ownerOpenFileID, dataRecNum, @fileDataRec];
ACF.WriteHeaderRec[ownerOpenFileID, @fileHeaderRec];
RETURN[fileHeaderRec.quotaLeft]
EXITS transAborted => ERROR AC.Unknown[transID];
END;



-- For WriteProperties, when it doesn't need to get the header page. I.e., createAccessList, modifyAccessList or rootFile.

CommonChangeOwner: PUBLIC ENTRY SAFE PROCEDURE[volGroupID: AE.VolumeGroupID,
transHandle: TM.Handle, ownerName: AE.OwnerName, ownerProperties: LIST OF
AE.OwnerPropertyValuePair] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.OperationFailed[ownerRecordFull], AC.StaticallyInvalid(badLengthName), AC.Unknown[owner, transID, volumeGroupID].
ENABLE UNWIND => NULL;
ownerOpenFileID: AE.OpenFileID;
dataRecNum: ACP.RecNumber;
fileDataRec: ACP.FileDataRec;
[, ownerOpenFileID, dataRecNum] ←
CheckRegStatGetVolGroupOpenFileAndDemandOwnerDataRec[
transHandle, @volGroupID, ownerName, write, @fileDataRec];
ACMA.WriteOwnerPropsToRecord[@fileDataRec, ownerProperties, ownerName];
ACF.WriteDataRec[ownerOpenFileID, dataRecNum, @fileDataRec
! AC.Unknown => IF why = openFileID THEN GOTO transAborted];
EXITS
transAborted => ERROR AC.Unknown[transID];
END;



ReadAllOwnerProperties: PUBLIC ENTRY SAFE PROCEDURE[volGroupID:
AE.VolumeGroupID, transHandle: TM.Handle, ownerName: AE.OwnerName,
desiredOwnerProperties: AE.OwnerPropertySet] RETURNS [ownerProperties: LIST OF
AE.OwnerPropertyValuePair] =
TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.StaticallyInvalid(badLengthName), AC.Unknown[owner, transID, volumeGroupID].
ENABLE UNWIND => NULL;
volGroupKey: VolGroupKey;
ownerOpenFileID: AE.OpenFileID;
fileDataRec: ACP.FileDataRec;
[volGroupKey, ownerOpenFileID, ] ←
CheckRegStatGetVolGroupOpenFileAndDemandOwnerDataRec[transHandle, @volGroupID,
ownerName, read, @fileDataRec];
RETURN[ReadAllOwnerPropsFromRec[transHandle, volGroupKey, ownerName,
desiredOwnerProperties, @fileDataRec]];
END;



GetOwnerRecordForReadAndUnlock: PUBLIC ENTRY SAFE PROCEDURE[volGroupID:
AE.VolumeGroupID, transHandle: TM.Handle, ownerName: AE.OwnerName,
pntrDataRec: ACP.PntrDataRec] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.StaticallyInvalid(badLengthName), AC.Unknown[owner, transID, volumeGroupID].
ENABLE UNWIND => NULL;
volGroupKey: VolGroupKey;
ownerOpenFileID: AE.OpenFileID;
dataRecNum, lastDataRecNum: ACP.RecNumber;
[volGroupKey, ownerOpenFileID, dataRecNum, lastDataRecNum] ←
CheckRegStatGetVolGroupOpenFileAndDemandOwnerDataRec[transHandle,
@volGroupID, ownerName, read, pntrDataRec];
ACF.UnlockDataRecViaRecNum[ownerOpenFileID, dataRecNum, lastDataRecNum
! ACF.OutOfRecNumRange => GOTO horrible;
AC.Unknown => IF why = openFileID THEN GOTO transAborted];
EXITS
horrible => ERROR ACP.InternalAccessControlLogicError;
transAborted => ERROR AC.Unknown[transID];
END;



ChangeSpaceViaOwnerName: PUBLIC ENTRY SAFE PROCEDURE[volGroupID:
AE.VolumeGroupID, transHandle: TM.Handle, ownerName: AE.OwnerName, nPages:
AE.PageCount] RETURNS [okay: BOOLEAN] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.StaticallyInvalid(badLengthName), AC.Unknown[owner, transID, volumeGroupID].
ENABLE UNWIND => NULL;
volGroupKey: VolGroupKey;
ownerOpenFileID: AE.OpenFileID;
fileDataRec: ACP.FileDataRec;
dataRecNum, lastDataRecNum: ACP.RecNumber;
[volGroupKey, ownerOpenFileID, dataRecNum, lastDataRecNum] ←
CheckRegStatGetVolGroupOpenFileAndDemandOwnerDataRec[
transHandle, @volGroupID, ownerName, read, @fileDataRec];
okay ← Allocate[volGroupKey, ownerName, transHandle, nPages, @fileDataRec,
dataRecNum];
ACF.UnlockDataRecViaRecNum[ownerOpenFileID, dataRecNum, lastDataRecNum
! ACF.OutOfRecNumRange => GOTO horrible;
AC.Unknown => IF why = openFileID THEN GOTO transAborted]
EXITS
horrible => ERROR ACP.InternalAccessControlLogicError;
transAborted => ERROR AC.Unknown[transID];
END;




-- for Heavy operations:


CreateAndInitOwnerFile: PUBLIC ENTRY SAFE PROCEDURE[volGroupID: AE.VolumeGroupID,
transHandle: TM.Handle, ownerFileVolID: AE.VolumeID, totalQuota, volumeGroupSize:
AE.PageCount, overCommitQuotaDuringInitializeIfNeeded: BOOLEAN,
maxNumberOfOwnersAllowed: NAT, nOwnerCacheEntries: NAT]
RETURNS[ownerFileID: AE.FileID] = TRUSTED
BEGIN -- non system-fatal errors: AC.StaticallyInvalid(ownerCacheEntriesOutOfRange, maxOwnerEntriesTooSmall, volGroupQuotaLTSpaceForOwnerFile), AC.OperationFailed[insufficientSpace], AC.Unknown[fileID, transID, volumeID, volumeGroupID].
ENABLE BEGIN
UNWIND
=> NULL;
AC.Unknown => IF why = openFileID THEN GOTO transAborted;
END;
ownerOpenFileID: AE.OpenFileID;
ownerUniversalFile: AE.UniversalFile;
fileSize: AE.PageCount;
lastDataRecNum: ACP.RecNumber;
fileHeaderRec: ACP.FileHeaderRec;
fileDataRec: ACP.FileDataRec;
dataRecNum: ACP.RecNumber;
quotaLeft: AE.PageCount;
IF maxNumberOfOwnersAllowed < 1 THEN ERROR AC.StaticallyInvalid;
[, , ] ← CheckRegStatGetVolGroupMaybeOpenFile[transHandle, @volGroupID, prevUnreg,
heavy, FALSE];
fileSize ← ACF.LengthToSetOwnerFile[MakeSparseOwnerFile[maxNumberOfOwnersAllowed]];
IF (((quotaLeft ← totalQuota - fileSize) < 0) AND
(NOT overCommitQuotaDuringInitializeIfNeeded)) THEN ERROR AC.StaticallyInvalid;
[ownerOpenFileID, ownerUniversalFile] ← AF.Create[AID.myLocalConversation,
TM.GetTransID[transHandle], volGroupID, ACP.SpecialOwnerForAlpineAdmin, fileSize,
log, sequential
! AF.OperationFailed => IF why = insufficientSpace THEN GOTO convertToACNoSpace];
ownerFileID ← ownerUniversalFile.fileID;
lastDataRecNum ← ACF.ComputeLastDataRecNumberFromFileLength[fileSize];
ACF.ReadHeaderRec[ownerOpenFileID, write, @fileHeaderRec];
--initialize the header record.
fileHeaderRec ← [version: ACP.OwnerFileFormatVersion, volGroupID: volGroupID, totalQuota:
totalQuota, quotaLeft: quotaLeft, volumeGroupSize: volumeGroupSize, numberOfOwners: 1,
numberOfOwnerSlotsInUse: 1, maxNumberOfOwnersAllowed: maxNumberOfOwnersAllowed,
spare: ALL[0]];
ACF.WriteHeaderRec[ownerOpenFileID, @fileHeaderRec];
-- write all data records as empty.
EmptyOutAnOwnerFile[ownerOpenFileID, lastDataRecNum];
-- write the weird owner record.
dataRecNum ← ACF.ReadDataRec[ownerOpenFileID, ACP.SpecialOwnerForAlpineAdmin,
wantEmpty, write, @fileDataRec, lastDataRecNum].dataRecNum;
ACU.MakeOwnerStringRepFromRName[ACP.SpecialOwnerForAlpineAdmin,
@fileDataRec.ownerName];
ACMA.WriteOwnerPropsToRecord[@fileDataRec, CONS[[spaceInUse[fileSize]],
ACL.ApplyMinsDefaultsAndFilterRequestedWriteProperties[CONS[[quota[fileSize]], NIL],
ACP.SpecialOwnerForAlpineAdmin, TRUE].newOwnerProperties],
ACP.SpecialOwnerForAlpineAdmin];
fileDataRec.state ← valid;
ACF.WriteDataRec[ownerOpenFileID, dataRecNum, @fileDataRec];
statsNInit statsNInit + 1;
EXITS

transAborted => ERROR AC.Unknown[transID];
convertToACNoSpace => ERROR AC.OperationFailed[insufficientSpace];
END
;



MakeSparseOwnerFile: INTERNAL SAFE PROCEDURE[maxNumberOfOwnersAllowed: NAT]
RETURNS[newMaxNumberOfOwnersAllowed: NAT] = CHECKED
BEGIN -- non system-fatal errors: none.
RETURN[(maxNumberOfOwnersAllowed*14)/10];
END
;



RegisterVolGroup
: PUBLIC ENTRY SAFE PROCEDURE[volGroupID: AE.VolumeGroupID,
transHandle: TM.Handle, ownerFileUniversalFile: AE.UniversalFile,
nOwnerCacheEntries: NAT] RETURNS[newOwnerFileID: AE.FileID] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.OperationFailed[duplicateVolumeGroup, ownerFileFormatOrVolGroupMismatch], AC.Unknown[fileID, transID, volumeID].
ENABLE UNWIND => NULL;
ownerOpenFileID: AE.OpenFileID;
fileHeaderRec: ACP.FileHeaderRec;
lastDataRecNum: ACP.RecNumber;
oldVolGroupKey: VolGroupKey;
[oldVolGroupKey, , ] ← CheckRegStatGetVolGroupMaybeOpenFile[transHandle, @volGroupID,
prevUnreg, heavy, FALSE];
[ownerOpenFileID, , lastDataRecNum, newOwnerFileID] ←
OpenFileGetSizeAndHeaderRec[transHandle, ownerFileUniversalFile, @fileHeaderRec];
IF NOT ((fileHeaderRec.version = ACP.OwnerFileFormatVersion) AND
(fileHeaderRec.volGroupID = volGroupID)) THEN ERROR
AC.OperationFailed[ownerFileFormatOrVolGroupMismatch];
ownerFileUniversalFile.fileID ← newOwnerFileID;
PresentDummyVolGroup[oldVolGroupKey, MakeDummyVolGroup[@volGroupID,
@ownerFileUniversalFile, lastDataRecNum, fileHeaderRec.totalQuota,
fileHeaderRec.quotaLeft, reg, transHandle]];
statsNRegister statsNRegister + 1;
END
;



UnRegisterVolGroup: PUBLIC ENTRY SAFE PROCEDURE[volGroupID: AE.VolumeGroupID,
transHandle: TM.Handle] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.Unknown[transID, volumeGroupID].
ENABLE UNWIND => NULL;
oldVolGroupKey: VolGroupKey;
ownerOpenFileID: AE.OpenFileID;
[oldVolGroupKey, ownerOpenFileID] ←
CheckRegStatGetVolGroupMaybeOpenFile[transHandle, @volGroupID, prevReg, heavy,
TRUE];
FlushOutAVolumeGroup[ownerOpenFileID, oldVolGroupKey, transHandle]; -- dump out any pending updates in the volatile structure.
PresentDummyVolGroup[oldVolGroupKey, MakeDummyVolGroup[@volGroupID,
, , , , unreg, transHandle]];
statsNUnReg statsNUnReg + 1;
END
;



-- called when need to make room for more owner entries, clean up owner database, etc. Will change the size of the file based on maxNumberOfOwnersAllowed. This procedure can error back once it has started modifying the owner file, but I believe that all those cases reflect the disappearance of the transaction or a fatal system error.

ReorgOwnerFile: PUBLIC ENTRY SAFE PROCEDURE[volGroupID: AE.VolumeGroupID,
transHandle: TM.Handle, maxNumberOfOwnersAllowed: NAT] =
TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.OperationFailed[insufficientSpace, ownerDatabaseFull], AC.Unknown[transID, volumeGroupID].
ENABLE
BEGIN
AC.Unknown => IF why = openFileID THEN GOTO transAborted;
AF.Unknown => SELECT what FROM openFileID => GOTO transAborted; transID =>
GOTO convertToACUnkTrans; ENDCASE => NULL;
UNWIND => NULL;
END
;
oldVolGroupKey, newVolGroupKey: VolGroupKey;
ownerOpenFileID, tempOpenFileID: AE.OpenFileID;
tempPageNumber: AE.PageNumber ← 0;
tempDataRecNum: ACP.RecNumber;
newSizeOfOwnerFile, oldSizeOfOwnerFile, tempFileSize: AE.PageCount;
fileHeaderRec: ACP.FileHeaderRec;
fileDataRec: ACP.FileDataRec;
dataRecNum, oldLastDataRecNum, newLastDataRecNum, tempLastDataRecNum:
ACP.RecNumber;
currentEntries: NAT;
SaveOldOwnerRecordsInTempFile:
SAFE PROCEDURE = TRUSTED
BEGIN
tempOpenFileID ← AF.Create[AID.myLocalConversation, TM.GetTransID[transHandle],
volGroupID, ACP.SpecialOwnerForAlpineAdmin, tempFileSize, log, sequential
! AF.OperationFailed => IF why = insufficientSpace THEN GOTO
convertToACNoSpace].openFileID;
tempLastDataRecNum ← ACF.ComputeLastDataRecNumberFromFileLength[tempFileSize];
tempDataRecNum ← ACF.FirstDataRecNum;
DO
ACF.ReadDataRecViaRecNum[ownerOpenFileID, tempDataRecNum, read, @fileDataRec,
oldLastDataRecNum];
IF fileDataRec.state = valid
THEN BEGIN AF.WritePages[AID.myLocalConversation, tempOpenFileID, [firstPage:
tempPageNumber, count: ACF.PagesPerRec], DESCRIPTOR[@fileDataRec,
ACF.WordsPerRec], [write, fail]];
tempPageNumber ← tempPageNumber + ACF.PagesPerRec;
END;
tempDataRecNum ← ACF.GetNextDataRecNumber[tempDataRecNum, oldLastDataRecNum,
stopOnEof, ! ACF.Stopped => EXIT];
ENDLOOP;
EXITS convertToACNoSpace => ERROR AC.OperationFailed[insufficientSpace];
END;
CopyOldOwnerRecordsToNewFile: SAFE PROCEDURE = TRUSTED
BEGIN
FOR tempPageNumber ← 0, tempPageNumber + ACF.PagesPerRec
UNTIL tempPageNumber >= tempFileSize
DO
bitDumpDataRec: ACP.FileDataRec;
AF.ReadPages[AID.myLocalConversation, tempOpenFileID, [firstPage: tempPageNumber,
count: ACF.PagesPerRec], DESCRIPTOR[@fileDataRec, ACF.WordsPerRec], [write, fail]];
dataRecNum ← ACF.ReadDataRec[ownerOpenFileID,
ACU.MakeRNameFromOwnerStringRep[ @fileDataRec.ownerName], wantEmpty, write,
@bitDumpDataRec, newLastDataRecNum].dataRecNum;
ACF.WriteDataRec[ownerOpenFileID, dataRecNum, @fileDataRec];
ENDLOOP;
AF.Delete[AID.myLocalConversation, tempOpenFileID];
END;
-- some preliminary checking and catch some errors.

[oldVolGroupKey, ownerOpenFileID, oldLastDataRecNum] ←
CheckRegStatGetVolGroupMaybeOpenFile[transHandle, @volGroupID, prevReg, heavy,
TRUE];
ACF.ReadHeaderRec[ownerOpenFileID, write, @fileHeaderRec];
currentEntries ← fileHeaderRec.numberOfOwners;
IF ((maxNumberOfOwnersAllowed = 0) OR (currentEntries > maxNumberOfOwnersAllowed))
THEN ERROR AC.OperationFailed[ownerDatabaseFull];
newSizeOfOwnerFile ←
ACF.LengthToSetOwnerFile[MakeSparseOwnerFile[(maxNumberOfOwnersAllowed + 1)]];
tempFileSize ← currentEntries*ACF.PagesPerRec;
FlushOutAVolumeGroup[ownerOpenFileID, oldVolGroupKey, transHandle]; -- dump out any pending updates in the volatile structure.
SaveOldOwnerRecordsInTempFile[];
oldSizeOfOwnerFile ← AF.GetSize[AID.myLocalConversation, ownerOpenFileID, [read, fail]];
-- reinitialize the owner file.
AF.SetSize[AID.myLocalConversation, ownerOpenFileID, newSizeOfOwnerFile,
[write, fail]];
newLastDataRecNum ←
ACF.ComputeLastDataRecNumberFromFileLength[newSizeOfOwnerFile];
newVolGroupKey ← MakeDummyVolGroup[@volGroupID,
@oldVolGroupKey.ownerFileUniversalFile, newLastDataRecNum, fileHeaderRec.totalQuota,
fileHeaderRec.quotaLeft, reorg, transHandle];
EmptyOutAnOwnerFile[ownerOpenFileID, newLastDataRecNum];
CopyOldOwnerRecordsToNewFile[];
-- adjust the specialowner's quota.
dataRecNum ← ACF.ReadDataRec[ownerOpenFileID, ACP.SpecialOwnerForAlpineAdmin,
wantOwner, write, @fileDataRec, newLastDataRecNum].dataRecNum;
ACMA.WriteOwnerPropsToRecord[@fileDataRec, CONS[[spaceInUse[newSizeOfOwnerFile]],
CONS[[quota[newSizeOfOwnerFile]], NIL]], ACP.SpecialOwnerForAlpineAdmin];
ACF.WriteDataRec[ownerOpenFileID, dataRecNum, @fileDataRec];
fileHeaderRec.quotaLeft ← fileHeaderRec.quotaLeft - (newSizeOfOwnerFile -
oldSizeOfOwnerFile);
fileHeaderRec.maxNumberOfOwnersAllowed ← maxNumberOfOwnersAllowed;
fileHeaderRec.numberOfOwnerSlotsInUse ← currentEntries;
ACF.WriteHeaderRec[ownerOpenFileID, @fileHeaderRec];
PresentDummyVolGroup[oldVolGroupKey, newVolGroupKey];
statsNReorg statsNReorg + 1;
EXITS
convertToACUnkTrans => ERROR AC.Unknown[transID];
transAborted => ERROR AC.Unknown[transID];
END
;



-- called only by clients who have the volume group exclusively locked.

OpenFileGetSizeAndHeaderRec: INTERNAL SAFE PROCEDURE[transHandle: TM.Handle,
ownerFileUniversalFile: AE.UniversalFile, pntrHeaderRec: ACP.PntrHeaderRec] RETURNS
[ownerOpenFileID: AE.OpenFileID, fileSize: AE.PageCount, lastDataRecNum: ACP.RecNumber,
newOwnerFileID: AE.FileID] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.Unknown[fileID, transID, volumeID].
ENABLE
BEGIN
AF.Unknown => SELECT what FROM openFileID => GOTO unkOpenFileID; transID =>
GOTO unkTransID; ENDCASE => NULL;
AC.Unknown => IF why = openFileID THEN GOTO unkOpenFileID;
END
;
[ownerOpenFileID, newOwnerFileID] ← AF.Open[AID.myLocalConversation,
TM.GetTransID[transHandle], ownerFileUniversalFile, readWrite, [write, fail], log,
sequential
! AF.Unknown => SELECT what FROM volumeID => GOTO unkVolID; fileID => GOTO
unkFileID; ENDCASE;
AF.LockFailed => IF why = conflict THEN GOTO lockConflict];
fileSize ← AF.GetSize[AID.myLocalConversation, ownerOpenFileID, [read, fail]];
lastDataRecNum ← ACF.ComputeLastDataRecNumberFromFileLength[fileSize];
ACF.ReadHeaderRec[ownerOpenFileID, write, pntrHeaderRec];
EXITS
lockConflict => ERROR ACF.LockConflict[[write, file[TM.GetTransID[transHandle],
ownerFileUniversalFile, sequential]]];
unkFileID => ERROR AC.Unknown[fileID];
unkOpenFileID, unkTransID => ERROR AC.Unknown[transID];
unkVolID => ERROR AC.Unknown[volumeID];
END;



-- expects the volGroupKey to be in a valid state.

EmptyOutAnOwnerFile: INTERNAL SAFE PROCEDURE[ownerOpenFileID: AE.OpenFileID,
lastDataRecNum: ACP.RecNumber] = TRUSTED
BEGIN -- non system-fatal errors: AC.Unknown[transID].
dataRecNum: ACP.RecNumber ← ACF.FirstDataRecNum;
fileDataRec: ACP.FileDataRec;
ACMA.InitializeOwnerRecord[@fileDataRec];
DO

ACF.WriteDataRec[ownerOpenFileID, dataRecNum, @fileDataRec
! AC.Unknown => IF why = openFileID THEN GOTO transAborted];
dataRecNum ← ACF.GetNextDataRecNumber[dataRecNum, lastDataRecNum, stopOnEof,
! ACF.Stopped => EXIT];
ENDLOOP;
EXITS transAborted => ERROR AC.Unknown[transID];
END
;



-- dump out any pending updates before reorganize and unregister.

FlushOutAVolumeGroup: INTERNAL SAFE PROCEDURE[ownerOpenFileID: AE.OpenFileID,
oldVolGroupKey: VolGroupKey, transHandle: TM.Handle] = TRUSTED
BEGIN -- non system-fatal errors: AC.Unknown[transID].
ENABLE AC.Unknown => IF why = openFileID THEN GOTO transAborted;
ownerKey: OwnerKey;
dataRecNum: ACP.RecNumber;
lastDataRecNum: ACP.RecNumber ← FindLastDataRecNum[oldVolGroupKey];
fileDataRec: ACP.FileDataRec;
[ownerKey, dataRecNum] ←
FindFirstRecNeedingUpdateForThisVolGroup[oldVolGroupKey];
UNTIL ownerKey = NIL
DO
ACF.ReadDataRecViaRecNum[ownerOpenFileID, dataRecNum, write, @fileDataRec,
lastDataRecNum];
ACMA.OverWriteOwnerSpaceInUseToRecord[@fileDataRec,
(ACMA.ReadOwnerQuotaFromRecord[@fileDataRec] - ownerKey.quotaLeft) +
ownerKey.transKey.alloc + ownerKey.transKey.dealloc];
ACF.WriteDataRec[ownerOpenFileID, dataRecNum, @fileDataRec];
[ownerKey, dataRecNum] ←
RemoveRecAndGetNextRecForVolGroup[oldVolGroupKey, ownerKey];
ENDLOOP;
EXITS transAborted => ERROR AC.Unknown[transID];
END;



-- User supplied prevOwner = NIL means beginning of file. Returned ownerName = NIL means no more owners. Locks out all other transactions from the owner file while this transaction is in progress. If the same transaction tries something funny, like a reorganize between calls to EnumerateAllOwners, it deserves what it gets.

EnumAllOwners: PUBLIC ENTRY SAFE PROCEDURE[volGroupID: AE.VolumeGroupID,
transHandle: TM.Handle, prevOwnerName: AE.OwnerName, desiredOwnerProperties:
AE.OwnerPropertySet] RETURNS [ownerName: AE.OwnerName, ownerProperties: LIST OF
AE.OwnerPropertyValuePair] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.StaticallyInvalid(badLengthName), AC.Unknown[owner, transID, volumeGroupID].
ENABLE
BEGIN
UNWIND
=> NULL;
AC.Unknown => IF why = openFileID THEN GOTO transAborted;
END;
volGroupKey: VolGroupKey;
ownerOpenFileID: AE.OpenFileID;
fileDataRec: ACP.FileDataRec;
dataRecNum, prevDataRecNum, lastDataRecNum: ACP.RecNumber;
[volGroupKey, ownerOpenFileID, lastDataRecNum] ←
CheckRegStatGetVolGroupMaybeOpenFile[transHandle, @volGroupID, prevReg, heavy,
TRUE];
IF prevOwnerName = NIL
THEN dataRecNum ← ACF.FirstDataRecNum
ELSE BEGIN
valid: BOOLEAN;
[valid, prevDataRecNum] ← FindEnumRecNum[volGroupKey, prevOwnerName];
IF (NOT valid) THEN prevDataRecNum ←
ACF.ReadDataRec[ownerOpenFileID, prevOwnerName, wantOwner, read,
@fileDataRec, lastDataRecNum].dataRecNum;
dataRecNum ← ACF.GetNextDataRecNumber[prevDataRecNum, lastDataRecNum,
stopOnEof, ! ACF.Stopped => GOTO pastEof];
END;
DO
ACF.ReadDataRecViaRecNum[ownerOpenFileID, dataRecNum, read, @fileDataRec,
lastDataRecNum];
IF fileDataRec.state = valid THEN EXIT;
dataRecNum ← ACF.GetNextDataRecNumber[dataRecNum, lastDataRecNum, stopOnEof,
! ACF.Stopped => GOTO pastEof];
ENDLOOP;
ownerNameACU.MakeRNameFromOwnerStringRep[@fileDataRec.ownerName];
ownerPropertiesReadAllOwnerPropsFromRec[transHandle, volGroupKey, ownerName,
desiredOwnerProperties, @fileDataRec];
SetEnumRecNum[volGroupKey, ownerName, dataRecNum];
statsNEnumAll statsNEnumAll + 1;
EXITS

pastEof => RETURN[NIL, NIL];
transAborted => ERROR AC.Unknown[transID];
END;



-- For debugging. Locks up the entire owner file while this transaction is in progress. Very similar to EnumerateAllOwners, but tells about the empty and deleted records as well. Refing the contRecNumber is due to compiler limitation on long records.

SickOwnerFile
: ERROR = CODE;

EnumAllDataEntriesInOwnerFile: PUBLIC ENTRY SAFE PROCEDURE[volGroupID:
AE.VolumeGroupID, transHandle: TM.Handle, contRecNum: INT, desiredOwnerProperties:
AE.OwnerPropertySet] RETURNS [entryEmpty, entryValid: BOOLEAN, ownerName:
AE.OwnerName, ownerProperties: LIST OF AE.OwnerPropertyValuePair, nextContRecNum: INT] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.StaticallyInvalid(badRecordNumber), AC.Unknown[transID, volumeGroupID].
ENABLE UNWIND => NULL;
volGroupKey: VolGroupKey;
ownerOpenFileID: AE.OpenFileID;
fileDataRec: ACP.FileDataRec;
dataRecNum: ACP.RecNumber ← IF contRecNum = 0 THEN ACF.FirstDataRecNum
ELSE LOOPHOLE[contRecNum, ACP.RecNumber];
lastDataRecNum: ACP.RecNumber;
[volGroupKey, ownerOpenFileID, lastDataRecNum] ←
CheckRegStatGetVolGroupMaybeOpenFile[transHandle, @volGroupID, prevReg, heavy,
TRUE];
ACF.ReadDataRecViaRecNum[ownerOpenFileID, dataRecNum, read, @fileDataRec,
lastDataRecNum ! ACF.OutOfRecNumRange => GOTO badRecNumber;
AC.Unknown => IF why = openFileID THEN GOTO transAborted];
SELECT fileDataRec.state FROM
valid, deleted => BEGIN
ownerNameACU.MakeRNameFromOwnerStringRep[
@fileDataRec.ownerName];
ownerPropertiesReadAllOwnerPropsFromRec[transHandle, volGroupKey,
ownerName, desiredOwnerProperties, @fileDataRec];
entryEmpty ← FALSE;
entryValid ← (fileDataRec.state = valid);
END;
empty => entryEmpty ← TRUE;
ENDCASE => ERROR SickOwnerFile;
nextContRecNum ← LOOPHOLE[ACF.GetNextDataRecNumber[dataRecNum, lastDataRecNum,
stopOnEof, ! ACF.Stopped => GOTO done]];
statsNEnumAll statsNEnumAll + 1;
EXITS

badRecNumber => ERROR AC.StaticallyInvalid;
done => nextContRecNum ← 0;
transAborted => ERROR AC.Unknown[transID];
END;



-- For debugging. Locks up the entire owner database file while this transaction is in progress. Note that ReadOwnerDatabaseHeader and EnumerateAllDataEntriesInOwnerFile called in the same transaction will be consistent provided the transaction doesn't alter the owner database file itself.

ReadOwnerFileHead: PUBLIC ENTRY SAFE PROCEDURE[volGroupID: AE.VolumeGroupID,
transHandle: TM.Handle, opWeight: ACM.OpWeight] RETURNS [version: NAT,
recordedVolGroupID: AE.VolumeGroupID, totalQuota, quotaLeft, volumeGroupSize:
AE.PageCount, numberOfOwners, numberOfOwnerSlotsInUse, maxNumberOfOwnersAllowed:
NAT] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.Unknown[transID, volumeGroupID].
ENABLE UNWIND => NULL;
volGroupKey: VolGroupKey;
ownerOpenFileID: AE.OpenFileID;
fileHeaderRec: ACP.FileHeaderRec;
[volGroupKey, ownerOpenFileID, ] ← CheckRegStatGetVolGroupMaybeOpenFile[transHandle,
@volGroupID, prevReg, opWeight, TRUE];
ACF.ReadHeaderRec[ownerOpenFileID, read, @fileHeaderRec
! AC.Unknown => IF why = openFileID THEN GOTO transAborted];
RETURN[version: fileHeaderRec.version, recordedVolGroupID: fileHeaderRec.volGroupID,
totalQuota: fileHeaderRec.totalQuota, quotaLeft: fileHeaderRec.quotaLeft, volumeGroupSize:
fileHeaderRec.volumeGroupSize, numberOfOwners:
fileHeaderRec.numberOfOwners, numberOfOwnerSlotsInUse:
fileHeaderRec.numberOfOwnerSlotsInUse, maxNumberOfOwnersAllowed:
fileHeaderRec.maxNumberOfOwnersAllowed];
EXITS
transAborted => ERROR AC.Unknown[transID];
END;




-- misc.:



VolGroupRegStatus: TYPE = {prevReg, prevUnreg};

-- if the file is not opened, the lastDataRecNum returned is not valid.

CheckRegStatGetVolGroupMaybeOpenFile: INTERNAL SAFE PROCEDURE[transHandle:
TM.Handle, pntrVolGroupID: LONG POINTER TO AE.VolumeGroupID, regStatus:
VolGroupRegStatus, opWeight: ACM.OpWeight, openFile: BOOLEAN] RETURNS[volGroupKey:
VolGroupKey, ownerOpenFileID: AE.OpenFileID, lastDataRecNum: ACP.RecNumber] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.OperationFailed[duplicateVolumeGroup], AC.Unknown[transID, volumeGroupID].
IF ((openFile) AND (regStatus = prevUnreg))
THEN ERROR ACP.InternalAccessControlLogicError;
[volGroupKey, ownerOpenFileID] ←
FindVolGroupCheckRegGetLightOpenFileID[pntrVolGroupID, regStatus, transHandle,
((opWeight = light) AND (openFile))];
IF openFile
THEN BEGIN
IF ownerOpenFileID = AE.nullOpenFileID
THEN
BEGIN
ownerOpenFileID ← AF.Open[AID.myLocalConversation,
TM.GetTransID[transHandle], volGroupKey.ownerFileUniversalFile,
readWrite, [(IF opWeight = light THEN
intendWrite ELSE write), fail], log, (IF opWeight = light THEN random
ELSE sequential)
! AF.Unknown => IF what = transID THEN GOTO unkTransID;
AF.LockFailed => IF why = conflict THEN GOTO lockConflict].openFileID;
IF opWeight = light
THEN
SetLightOpenFileID[volGroupKey, transHandle, ownerOpenFileID];
END;
lastDataRecNum ← FindLastDataRecNum[volGroupKey];
END;
EXITS
unkTransID => ERROR AC.Unknown[transID];
lockConflict => ERROR ACF.LockConflict[[(IF opWeight = light THEN intendWrite ELSE
write), file[TM.GetTransID[transHandle], volGroupKey.ownerFileUniversalFile,
(IF opWeight = light THEN random ELSE sequential)]]];
END;



CheckRegStatGetVolGroupOpenFileAndDemandOwnerDataRec: INTERNAL SAFE
PROCEDURE[transHandle: TM.Handle, pntrVolGroupID: LONG POINTER TO
AE.VolumeGroupID, ownerName: AE.OwnerName, recLockMode: AE.LockMode, pntrDataRec:
ACP.PntrDataRec] RETURNS[volGroupKey: VolGroupKey, ownerOpenFileID: AE.OpenFileID,
dataRecNum, lastDataRecNum: ACP.RecNumber] = TRUSTED
BEGIN -- non system-fatal errors: ACF.LockConflict, AC.StaticallyInvalid(badLengthName), AC.Unknown[owner, transID, volumeGroupID].
[volGroupKey, ownerOpenFileID, lastDataRecNum] ←
CheckRegStatGetVolGroupMaybeOpenFile[transHandle, pntrVolGroupID, prevReg, light,
TRUE
];
dataRecNum ← ACF.ReadDataRec[ownerOpenFileID, ownerName, wantOwner,
recLockMode, pntrDataRec, lastDataRecNum
! AC.Unknown => IF why = openFileID THEN GOTO transAborted].dataRecNum;
EXITS transAborted => ERROR AC.Unknown[transID];
END;



ReadAllOwnerPropsFromRec: INTERNAL SAFE PROCEDURE[transHandle: TM.Handle,
volGroupKey: VolGroupKey, ownerName: AE.OwnerName, desiredOwnerProperties: AE.OwnerPropertySet, pntrFileDataRec:
ACP.PntrDataRec] RETURNS [ownerProperties: LIST OF AE.OwnerPropertyValuePair] = CHECKED
BEGIN -- non system-fatal errors: none.
ownerProperty: AE.OwnerPropertyValuePair ← [quota[0]]; -- initialize so GC won't think it has a REF.
ownerProperties ← NIL;
FOR ownerPropType: AE.OwnerProperty IN AE.OwnerProperty
DO
IF desiredOwnerProperties[ownerPropType]
THEN ownerProperties ←
SELECT ownerPropType FROM
rootFile => CONS[[rootFile[ACMA.ReadOwnerRootFileFromRecord[pntrFileDataRec]]],
ownerProperties],
spaceInUse => CONS[[spaceInUse[ACMA.ReadOwnerSpaceInUseFromRecord[
pntrFileDataRec] +
EnumOwnerSpaceChange[volGroupKey, ownerName, transHandle]]],
ownerProperties],
quota => CONS[[quota[ACMA.ReadOwnerQuotaFromRecord[pntrFileDataRec]]],
ownerProperties],
modifyAccessList =>
CONS[[modifyAccessList[ACMA.ReadOwnerAccessListFromRecord[
pntrFileDataRec, modify, ownerName]]], ownerProperties],
createAccessList =>
CONS[[createAccessList[ACMA.ReadOwnerAccessListFromRecord[
pntrFileDataRec, create, ownerName]]], ownerProperties],
ENDCASE => ERROR ACP.InternalAccessControlLogicError;
ENDLOOP;
END;




-- Processing at end of a transaction:



PhaseOneSpaceOwnerChanges: PUBLIC ENTRY SAFE PROCEDURE[transHandle:
TM.Handle] = TRUSTED
BEGIN -- non system-fatal errors: AFC.LockConflict, AC.Unknown[transID].
ENABLE UNWIND => NULL;
volGroupKey: VolGroupKey;
ownerKey: OwnerKey;
transKey: TransKey;
ownerOpenFileID: AE.OpenFileID;
dataRecNum: ACP.RecNumber;
fileDataRec: ACP.FileDataRec;
done: BOOLEAN;
[done, volGroupKey, ownerKey, transKey, ownerOpenFileID, dataRecNum] ←
RemoveOldRecAndGetNextRecForTrans[transHandle, NIL];
UNTIL done
DO
ACF.ReadDataRecViaRecNum[ownerOpenFileID, dataRecNum, write,
@fileDataRec, FindLastDataRecNum[volGroupKey]
! ACF.OutOfRecNumRange => GOTO horrible;
AC.Unknown => IF why = openFileID THEN GOTO transAborted];
ACMA.OverWriteOwnerSpaceInUseToRecord[@fileDataRec,
(ACMA.ReadOwnerQuotaFromRecord[@fileDataRec] - ownerKey.quotaLeft) +
transKey.alloc + transKey.dealloc];
ACF.WriteDataRec[ownerOpenFileID, dataRecNum, @fileDataRec
! AC.Unknown => IF why = openFileID THEN GOTO transAborted;];
[done, volGroupKey, ownerKey, transKey, ownerOpenFileID, dataRecNum] ←
RemoveOldRecAndGetNextRecForTrans[transKey.transHandle, transKey];
REPEAT
horrible => ERROR ACP.InternalAccessControlLogicError;
transAborted => ERROR AC.Unknown[transID];
ENDLOOP;
END;



PhaseTwoOrAbortSpaceAndOwnerChanges: PUBLIC ENTRY SAFE PROCEDURE[transHandle:
TM.Handle, outcome: AE.CommitOrAbort] = CHECKED
BEGIN -- non system-fatal errors: none.
PhaseTwoOrAbort[transHandle, outcome];
END;




-- herewith the old "volatile structure" module:



-- registered volume groups. We can't use LISTs, because there's a problem with LISTs and opaque types.

regVolGroups: VolGroupKey ← NEW[RegVolGroup ← [AE.nullVolumeGroupID,
AE.nullUniversalFile, ACP.nullRecNumber, 0, 0, [NIL,
ACP.nullRecNumber], header, TM.nullHandle, NIL, NIL, NIL, NIL]];
VolGroupKey: TYPE = REF RegVolGroup;
RegVolGroup: TYPE = RECORD[
volGroupID: AE.VolumeGroupID,
ownerFileUniversalFile: AE.UniversalFile,
lastDataRecNum: ACP.RecNumber,
totalQuota: AE.PageCount,
quotaLeft: AE.PageCount,
enumCache: EnumerationCache,
currentState: HeavyOpType, -- reg, unreg, reorg, normal, header.
heavyTrans: TM.Handle,
openFileIDsKey: OpenFileIDsKey,
initialVolGroup: REF RegVolGroup, -- so we can undo heavies.
ownerKey: OwnerKey,
next, prev: VolGroupKey];
EnumerationCache: TYPE = RECORD[ownerName: AE.OwnerName, dataRecNum:
ACP.RecNumber];


OwnerKey: TYPE = REF OwnerEntry;
OwnerEntry: TYPE = RECORD[
ownerName: AE.OwnerName, dataRecNum: ACP.RecNumber, quotaLeft: AE.PageCount,
transKey: TransKey, next, prev: OwnerKey];

TransKey: TYPE = REF TransEntry;
TransEntry: TYPE = RECORD[
transHandle: TM.Handle,
alloc: AE.PageCount,
dealloc: AE.PageCount,
phaseOneSeen: BOOLEAN,
next, prev: TransKey];

OpenFileIDsKey
: TYPE = REF OpenFileIDsEntry; -- for light ops only.
OpenFileIDsEntry: TYPE = RECORD[
transHandle: TM.Handle,
openFileID: AE.OpenFileID,
next: OpenFileIDsKey];





-- for init and reorganize volume groups. also used by register and unregister since it's easier.

HeavyOpType: TYPE = {reg, unreg, reorg, normal, header};

MakeDummyVolGroup: INTERNAL SAFE PROCEDURE[pntrVolGroupID: LONG POINTER TO
AE.VolumeGroupID, pntrOwnerFileUniversalFile: LONG POINTER TO AE.UniversalFile,
lastDataRecNum: ACP.RecNumber, totalQuota, quotaLeft: AE.PageCount, opType: HeavyOpType,
transHandle: TM.Handle] RETURNS [newVolGroupKey: VolGroupKey] = TRUSTED
BEGIN -- non system-fatal errors: none.
newVolGroupKey ← NEW[RegVolGroup ← [pntrVolGroupID^, pntrOwnerFileUniversalFile^,
lastDataRecNum, totalQuota, quotaLeft, [NIL, ACP.nullRecNumber],
opType, transHandle, NIL, NIL, NIL, NIL, NIL]];
END;



-- replace the old volGroup with the new one.

PresentDummyVolGroup: INTERNAL SAFE PROCEDURE[oldVolGroupKey, newVolGroupKey:
VolGroupKey] = CHECKED
BEGIN -- non system-fatal errors: none.
IF oldVolGroupKey = NIL
THEN BEGIN IF newVolGroupKey.currentState # reg
THEN ERROR ACP.InternalAccessControlLogicError;
newVolGroupKey.next ← regVolGroups.next;
newVolGroupKey.prev ← regVolGroups;
regVolGroups.next.prev ← newVolGroupKey;
regVolGroups.next ← newVolGroupKey;
END
ELSE BEGIN newVolGroupKey.initialVolGroup ← (IF oldVolGroupKey.currentState
= normal THEN oldVolGroupKey ELSE oldVolGroupKey.initialVolGroup);
newVolGroupKey.next ← oldVolGroupKey.next;
newVolGroupKey.prev ← oldVolGroupKey.prev;
oldVolGroupKey.prev.next ← newVolGroupKey;
oldVolGroupKey.next.prev ← newVolGroupKey;
END;
END;



-- handle (de)allocation.


-- don't allow allocations over quota, and the owner doesn't get credit for deallocations until the end of the transaction. A request for 0 pages is not expected.

Allocate: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey, ownerName:
AE.OwnerName, transHandle: TM.Handle, nPages: AE.PageCount, pntrDataRec:
ACP.PntrDataRec, dataRecNum: ACP.RecNumber] RETURNS [okay: BOOLEAN] = CHECKED
BEGIN -- non system-fatal errors: none.
ownerKey: OwnerKey ← FindOwnerEntry[volGroupKey, ownerName];
transKey: TransKey;
IF nPages = 0 THEN ERROR ACP.InternalAccessControlLogicError;
okay ← ((nPages < 0)) OR
((ownerKey = NIL) AND
(ACMA.ReadOwnerQuotaFromRecord[pntrDataRec] -
ACMA.ReadOwnerSpaceInUseFromRecord[pntrDataRec] - nPages >= 0)) OR
((ownerKey # NIL) AND (RoomForAlloc[ownerKey, nPages]));
IF NOT okay
THEN BEGIN statsNAllocReqsLose ← statsNAllocReqsLose + 1; RETURN; END;
IF ownerKey = NIL
THEN BEGIN
ownerKey ← CreateOwnerEntry[volGroupKey, ownerName, dataRecNum,
pntrDataRec];
transKey ← CreateTransEntry[ownerKey, transHandle];
END
ELSE transKey ← FindOrCreateTransEntry[ownerKey, transHandle];
IF nPages <= 0
THEN BEGIN transKey.dealloc ← transKey.dealloc + nPages;
statsNDeallocReqs ← statsNDeallocReqs + 1;
END
ELSE BEGIN transKey.alloc ← transKey.alloc + nPages;
statsNAllocReqsWin ← statsNAllocReqsWin + 1;
END;
END;


-- ownerKey is guaranteed non-NIL by caller.

RoomForAlloc: INTERNAL SAFE PROCEDURE[ownerKey: OwnerKey, nPages: AE.PageCount]
RETURNS[okay: BOOLEAN] = CHECKED INLINE
BEGIN -- non system-fatal errors: none.
quotaLeft: AE.PageCount ← ownerKey.quotaLeft;
FOR transKey: TransKey ← ownerKey.transKey, transKey.next
UNTIL transKey = NIL
DO
quotaLeft ← quotaLeft - transKey.alloc;
ENDLOOP;
RETURN[(quotaLeft - nPages) >= 0];
END;



-- handle owner ops.


-- ChangeQuota doesn't care if the owner isn't in the volatile structure. Also, it is okay to change the quota even if the current allocation will then be over quota.

ChangeQuotaOwner: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey,
ownerName: AE.OwnerName, transHandle: TM.Handle, newQuota, oldQuota: AE.PageCount] =
CHECKED
BEGIN -- non system-fatal errors: AC.OperationFailed[ownerRecordInUse].
ownerKey: OwnerKey ← FindOwnerEntry[volGroupKey, ownerName];
IF ownerKey # NIL
THEN BEGIN
IF ((ownerKey.transKey.transHandle # transHandle) OR
(ownerKey.transKey.next # NIL))
THEN ERROR AC.OperationFailed[ownerRecordInUse]; -- thorn.
ownerKey.quotaLeft ← ownerKey.quotaLeft + (newQuota - oldQuota);
END;
END;



RemoveTheOwner: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey, ownerName:
AE.OwnerName, transHandle: TM.Handle, pntrDataRec: ACP.PntrDataRec] = CHECKED
BEGIN -- non system-fatal errors: AC.OperationFailed[ownerRecordInUse, spaceInUseByThisOwner].
ownerKey: OwnerKey ← FindOwnerEntry[volGroupKey, ownerName];
IF ownerKey # NIL
THEN BEGIN
IF ((ownerKey.transKey.transHandle # transHandle) OR
(ownerKey.transKey.next # NIL))
THEN ERROR AC.OperationFailed[ownerRecordInUse];
IF ACMA.ReadOwnerQuotaFromRecord[pntrDataRec] #
(ownerKey.quotaLeft - ownerKey.transKey.alloc - ownerKey.transKey.dealloc)
THEN ERROR AC.OperationFailed[spaceInUseByThisOwner];
RemoveOwnerEntry[volGroupKey, ownerKey];
END
ELSE BEGIN IF (ACMA.ReadOwnerSpaceInUseFromRecord[pntrDataRec] # 0)
THEN ERROR AC.OperationFailed[spaceInUseByThisOwner];
END;
statsNRemoveOwner ← statsNRemoveOwner + 1;
END;



EnumOwnerSpaceChange: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey,
ownerName: AE.OwnerName, transHandle: TM.Handle] RETURNS [inUseChange:
AE.PageCount] = CHECKED
BEGIN -- non system-fatal errors: none.
ownerKey: OwnerKey ← FindOwnerEntry[volGroupKey, ownerName];
IF ownerKey = NIL THEN RETURN[0];
FOR transKey: TransKey ← ownerKey.transKey, transKey.next
UNTIL transKey = NIL
DO
IF transKey.transHandle = transHandle THEN GOTO found;
REPEAT
found => RETURN[transKey.alloc + transKey.dealloc];
FINISHED => RETURN[0];
ENDLOOP;
END;



-- handle enumeration "cache".


FindEnumRecNum: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey,
prevOwnerName: AE.OwnerName] RETURNS [valid: BOOLEAN, prevDataRecNum:
ACP.RecNumber] = CHECKED
BEGIN -- non system-fatal errors: none.
valid ← ((volGroupKey.enumCache.ownerName # NIL) AND
(ACU.Compare[volGroupKey.enumCache.ownerName, prevOwnerName]));
IF valid
THEN statsNEnumFindWin ← statsNEnumFindWin + 1
ELSE statsNEnumFindLose ← statsNEnumFindLose + 1;
RETURN[valid, volGroupKey.enumCache.dataRecNum];
END;



SetEnumRecNum: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey, ownerName:
AE.OwnerName, dataRecNum: ACP.RecNumber] = CHECKED
BEGIN -- non system-fatal errors: none.
volGroupKey.enumCache ← [ownerName, dataRecNum];
statsNSetEnums ← statsNSetEnums + 1;
END;




-- misc.


FindLastDataRecNum: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey] RETURNS
[lastDataRecNum: ACP.RecNumber] = CHECKED INLINE
BEGIN -- non system-fatal errors: none.
RETURN[volGroupKey.lastDataRecNum];
END;



-- routine called at the beginning of an action.


FindVolGroupCheckRegGetLightOpenFileID: INTERNAL SAFE PROCEDURE[pntrVolGroupID:
LONG POINTER TO AE.VolumeGroupID, regStatus: VolGroupRegStatus, transHandle: TM.Handle,
findLightOpenFileID: BOOLEAN] RETURNS[volGroupKey: VolGroupKey, ownerOpenFileID:
AE.OpenFileID] = CHECKED
BEGIN -- non system-fatal errors: AC.OperationFailed[duplicateVolumeGroup], AC.Unknown[VolumeGroupID].
ownerOpenFileID ← AE.nullOpenFileID;
volGroupKey ← regVolGroups.next;
DO
IF volGroupKey = regVolGroups THEN BEGIN volGroupKey ← NIL; EXIT; END;
TRUSTED BEGIN IF volGroupKey.volGroupID = pntrVolGroupID^ THEN EXIT; END;
volGroupKey ← volGroupKey.next;
ENDLOOP;
IF (regStatus = prevReg) =
((volGroupKey = NIL) OR (volGroupKey.currentState = unreg))
THEN BEGIN
IF regStatus = prevReg
THEN ERROR
AC.Unknown[volumeGroupID]
ELSE ERROR AC.OperationFailed[duplicateVolumeGroup];
END;
IF findLightOpenFileID
THEN
ownerOpenFileID ← FindLightOpenFileID[volGroupKey, transHandle];
statsNActions ← statsNActions + 1;
END;



-- routines called to flush out the volatile structure before reorganize and unregister.


FindFirstRecNeedingUpdateForThisVolGroup: INTERNAL SAFE PROCEDURE[volGroupKey:
VolGroupKey] RETURNS [ownerKey: OwnerKey, dataRecNum: ACP.RecNumber] = CHECKED
BEGIN -- non system-fatal errors: none.
IF (ownerKey ← volGroupKey.ownerKey) # NIL THEN dataRecNumownerKey.dataRecNum;
END;


-- update the file record, and get the next volatile record.

RemoveRecAndGetNextRecForVolGroup: INTERNAL SAFE PROCEDURE[volGroupKey:
VolGroupKey, ownerKey: OwnerKey] RETURNS [nextOwnerKey: OwnerKey,
nextDataRecNum: ACP.RecNumber] = CHECKED
BEGIN -- non system-fatal errors: none.
nextOwnerKey ← (volGroupKey.ownerKey ← ownerKey.next);
IF nextOwnerKey # NIL THEN nextDataRecNum ← nextOwnerKey.dataRecNum;
END;



-- routine called to write out the transaction's information at Phase One.


RemoveOldRecAndGetNextRecForTrans: INTERNAL SAFE PROCEDURE[transHandle:
TM.Handle, transKey: TransKey] RETURNS[done: BOOLEAN, nextVolGroupKey: VolGroupKey,
nextOwnerKey: OwnerKey, nextTransKey: TransKey, nextOwnerOpenFileID:
AE.OpenFileID, nextDataRecNum: ACP.RecNumber] = TRUSTED
BEGIN -- non system-fatal errors: none.
[done, nextVolGroupKey, nextOwnerKey, nextTransKey, nextDataRecNum,
nextOwnerOpenFileID] ← SearchForTransRec[transHandle, phaseOne, transKey];
IF ((NOT done) AND (nextOwnerOpenFileID = AE.nullOpenFileID))
THEN ERROR
ACP.InternalAccessControlLogicError;
END;



-- At Phase One this routine sets phaseOneSeen for the prev record (if any) for this transaction, finds the "next" record for this transaction (it ignores ones with phaseOneSeen set), and reports it to the caller. At PhaseTwo or Abort, it finds all the records for the specified transaction, (iff PhaseTwo updates their volatile owners), then deletes them.

SearchForTransRec: INTERNAL SAFE PROCEDURE[transHandle: TM.Handle, phase:
{phaseOne, phaseTwo, abort}, prevTransKey: TransKey] RETURNS[done: BOOLEAN,
nextVolGroupKey: VolGroupKey, nextOwnerKey: OwnerKey, nextTransKey: TransKey,
nextDataRecNum: ACP.RecNumber, nextOwnerOpenFileID: AE.OpenFileID] =
CHECKED
BEGIN -- non system-fatal errors: none.
IF ((phase = phaseOne) AND (prevTransKey # NIL))
THEN prevTransKey.phaseOneSeen ← TRUE;
FOR nextVolGroupKey ← regVolGroups.next, nextVolGroupKey.next
UNTIL nextVolGroupKey = regVolGroups
DO

FOR nextOwnerKey ← nextVolGroupKey.ownerKey, nextOwnerKey.next
UNTIL nextOwnerKey = NIL
DO
FOR nextTransKey ← nextOwnerKey.transKey, nextTransKey.next
UNTIL nextTransKey = NIL
DO
IF (nextTransKey.transHandle = transHandle) AND
((phase # phaseOne) OR (NOT nextTransKey.phaseOneSeen))
THEN
SELECT phase FROM
phaseOne => RETURN[FALSE, nextVolGroupKey, nextOwnerKey,
nextTransKey, nextOwnerKey.dataRecNum,
FindLightOpenFileID[nextVolGroupKey, transHandle]];
ENDCASE =>BEGIN
IF phase = phaseTwo THEN nextOwnerKey.quotaLeft ←
nextOwnerKey.quotaLeft - nextTransKey.alloc -
nextTransKey.dealloc;
RemoveTransEntry[nextOwnerKey, nextTransKey];
IF nextOwnerKey.transKey = NIL
THEN RemoveOwnerEntry[nextVolGroupKey, nextOwnerKey];
EXIT;
END
;
ENDLOOP; -- end of trans records.
ENDLOOP; -- end of owner records.
IF phase # phaseOne THEN RemoveLightOpenFileID[nextVolGroupKey, transHandle];
ENDLOOP
; -- end of volGroup records.
done ← TRUE;
END;



-- At Phase Two this routine makes the volatile owner records reflect this transaction's being committed, deletes the trans records, and handles heavy op cleanup. At Abort, it deletes the trans records and does heavy op abort.

PhaseTwoOrAbort: INTERNAL SAFE PROCEDURE[transHandle: TM.Handle, outcome:
AE.CommitOrAbort] = CHECKED
BEGIN -- non system-fatal errors: none.
volGroupKey: VolGroupKey;
IF outcome = abort
THEN statsNAborts ← statsNAborts + 1
ELSE statsNPhaseTwos ← statsNPhaseTwos + 1;
IF (NOT SearchForTransRec[transHandle, (IF outcome = commit THEN phaseTwo
ELSE abort), NIL].done) THEN ERROR ACP.InternalAccessControlLogicError;
volGroupKey ← regVolGroups.next;
DO
IF volGroupKey = regVolGroups THEN EXIT;
IF volGroupKey.heavyTrans = transHandle
THEN BEGIN
oldVolGroupKey: VolGroupKey ← volGroupKey.initialVolGroup;
SELECT TRUE FROM
((outcome = abort) AND (oldVolGroupKey = NIL)) OR
((outcome = commit) AND (volGroupKey.currentState = unreg)) =>
BEGIN
-- remove this vol group from list.
volGroupKey.prev.next ← volGroupKey.next;
volGroupKey.next.prev ← volGroupKey.prev;
END;
(outcome = abort) => -- here the oldVolGroupKey # NIL
BEGIN
-- put old vol group back.
volGroupKey.prev.next ← oldVolGroupKey;
volGroupKey.next.prev ← oldVolGroupKey;
oldVolGroupKey.prev ← volGroupKey.prev;
oldVolGroupKey.next ← volGroupKey.next;
END;
ENDCASE => -- here ((outcome = commit) AND (volGroupKey.currentState # unreg))
BEGIN
-- make new vol group real.
volGroupKey.initialVolGroup ← NIL;
volGroupKey.currentState ← normal;
volGroupKey.heavyTrans ← TM.nullHandle;
END;
END
;
volGroupKey ← volGroupKey.next;
ENDLOOP;
END;



-- utility routines for the volatile structure:


CreateOwnerEntry: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey, ownerName:
AE.OwnerName, dataRecNum: ACP.RecNumber, pntrDataRec: ACP.PntrDataRec]
RETURNS[ownerKey: OwnerKey] = CHECKED
BEGIN -- non system-fatal errors: none.
ownerKey ← NEW[OwnerEntry ←
[ownerName, dataRecNum, ACMA.ReadOwnerQuotaFromRecord[pntrDataRec] -
ACMA.ReadOwnerSpaceInUseFromRecord[pntrDataRec], NIL, volGroupKey.ownerKey,
NIL]];
IF volGroupKey.ownerKey # NIL THEN volGroupKey.ownerKey.prev ← ownerKey;
volGroupKey.ownerKey ← ownerKey;
END;


RemoveOwnerEntry: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey, ownerKey:
OwnerKey] = CHECKED
BEGIN -- non system-fatal errors: none.
IF ownerKey.prev # NIL
THEN ownerKey.prev.next ← ownerKey.next
ELSE volGroupKey.ownerKey ← ownerKey.next;
IF ownerKey.next # NIL THEN ownerKey.next.prev ← ownerKey.prev;
END;


-- returns NIL if not found.

FindOwnerEntry: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey, ownerName:
AE.OwnerName] RETURNS[ownerKey: OwnerKey] = CHECKED
BEGIN -- non system-fatal errors: none.
ownerKey ← volGroupKey.ownerKey;
UNTIL ownerKey = NIL
DO IF ACU.Compare[ownerKey.ownerName, ownerName] THEN RETURN;
ownerKey ← ownerKey.next;
ENDLOOP;
RETURN[NIL];
END;


CreateTransEntry: INTERNAL SAFE PROCEDURE[ownerKey: OwnerKey, transHandle:
TM.Handle] RETURNS[transKey: TransKey] = CHECKED
BEGIN -- non system-fatal errors: none.
transKey ← NEW[TransEntry ← [transHandle, 0, 0, FALSE, ownerKey.transKey,
NIL]];
IF ownerKey.transKey # NIL THEN ownerKey.transKey.prev ← transKey;
ownerKey.transKey ← transKey;
END;


FindOrCreateTransEntry: INTERNAL SAFE PROCEDURE[ownerKey: OwnerKey, transHandle:
TM.Handle] RETURNS[transKey: TransKey] = CHECKED
BEGIN -- non system-fatal errors: none.
transKey ← ownerKey.transKey;
UNTIL transKey = NIL
DO IF transKey.transHandle = transHandle THEN RETURN;
transKey ← transKey.next;
ENDLOOP;
RETURN[CreateTransEntry[ownerKey, transHandle]];
END;


RemoveTransEntry: INTERNAL SAFE PROCEDURE[ownerKey: OwnerKey, transKey: TransKey] =
CHECKED INLINE
BEGIN -- non system-fatal errors: none.
IF transKey.prev # NIL
THEN transKey.prev.next ← transKey.next
ELSE ownerKey.transKey ← transKey.next;
IF transKey.next # NIL THEN transKey.next.prev ← transKey.prev;
END;


FindLightOpenFileID: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey, transHandle:
TM.Handle] RETURNS[ownerOpenFileID: AE.OpenFileID] = CHECKED
BEGIN -- non system-fatal errors: none.
FOR openFileIDsKey: OpenFileIDsKey ← volGroupKey.openFileIDsKey, openFileIDsKey.next
UNTIL
openFileIDsKey = NIL
DO
IF openFileIDsKey.transHandle = transHandle THEN RETURN[openFileIDsKey.openFileID];
ENDLOOP;
RETURN[AE.nullOpenFileID];
END
;


SetLightOpenFileID: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey, transHandle: TM.Handle, ownerOpenFileID: AE.OpenFileID] =
CHECKED
BEGIN -- non system-fatal errors: none.
openFileIDsKey: OpenFileIDsKey ← NEW[OpenFileIDsEntry ← [transHandle, ownerOpenFileID,
volGroupKey.openFileIDsKey]];
volGroupKey.openFileIDsKey ← openFileIDsKey;
END;


RemoveLightOpenFileID: INTERNAL SAFE PROCEDURE[volGroupKey: VolGroupKey,
transHandle: TM.Handle] = CHECKED
BEGIN -- non system-fatal errors: none.
prevOpenFileIDsKey: OpenFileIDsKey ← NIL;
FOR
openFileIDsKey: OpenFileIDsKey ← volGroupKey.openFileIDsKey, openFileIDsKey.next
UNTIL
openFileIDsKey = NIL
DO
IF openFileIDsKey.transHandle = transHandle
THEN BEGIN
IF
prevOpenFileIDsKey = NIL
THEN volGroupKey.openFileIDsKey ← openFileIDsKey.next
ELSE prevOpenFileIDsKey.next ← openFileIDsKey.next;
RETURN;
END;
prevOpenFileIDsKeyopenFileIDsKey;
ENDLOOP
;
END;


ReadOwnerUniversalFile: PUBLIC ENTRY SAFE PROCEDURE[volumeGroupID:
AE.VolumeGroupID] RETURNS[dBID: AE.UniversalFile] = TRUSTED
BEGIN -- non system-fatal errors: AC.Unknown[VolumeGroupID].
volGroupKey: VolGroupKey ← FindVolGroupCheckRegGetLightOpenFileID[@volumeGroupID,
prevReg, NIL, FALSE].volGroupKey;
RETURN[volGroupKey.ownerFileUniversalFile];
END;


-- statsNActions, statsNEnumFindWin, and statsNEnumFindLose can be too large due to the locking cycling.

statsNRegister, statsNInit, statsNUnReg, statsNReorg, statsNEnumAll, statsNAllocReqsWin,
statsNAllocReqsLose, statsNDeallocReqs, statsNRemoveOwner, statsNEnumFindWin,
statsNEnumFindLose, statsNSetEnums, statsNActions, statsNPhaseOnes, statsNPhaseTwos,
statsNAborts: INT ← 0;

ReportVolatileStats: PUBLIC ENTRY SAFE PROCEDURE RETURNS[nRegs, nInits,
nUnRegs, nReorganizes, nEnumAlls, nAllocReqsWin, nAllocReqsLose, nDeallocReqs,
nRemoveOwner, nEnumFindWin, nEnumFindLose, nSetEnums, nActions,
nPhaseOnes, nPhaseTwos, nAborts: INT] = CHECKED
BEGIN -- non system-fatal errors: none.
RETURN[statsNRegister, statsNInit, statsNUnReg, statsNReorg, statsNEnumAll,
statsNAllocReqsWin, statsNAllocReqsLose, statsNDeallocReqs,
statsNRemoveOwner, statsNEnumFindWin, statsNEnumFindLose, statsNSetEnums,
statsNActions, statsNPhaseOnes, statsNPhaseTwos, statsNAborts];
END;





regVolGroups.next ← regVolGroups;
regVolGroups.prev ← regVolGroups;



END.


Edit Log

Initial: Kolling: January 11, 1983 5:52 pm: upper level module which protects delicate operations within a "monster monitor".