-- 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; ownerName _ ACU.MakeRNameFromOwnerStringRep[@fileDataRec.ownerName]; ownerProperties _ ReadAllOwnerPropsFromRec[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 ownerName _ ACU.MakeRNameFromOwnerStringRep[ @fileDataRec.ownerName]; ownerProperties _ ReadAllOwnerPropsFromRec[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 dataRecNum _ ownerKey.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; prevOpenFileIDsKey _ openFileIDsKey; 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".