-- AccessControlFileImpl.mesa -- Last edited by -- Kolling on May 27, 1983 4:11 pm -- MBrown on January 30, 1984 2:51:22 pm PST DIRECTORY AccessControl USING[LockFailed, OperationFailed, StaticallyInvalid, Unknown], AccessControlFile USING[LockConflict, LockItem, RecNumber, StopOrWrap, Stopped, WantOwnerOrEmpty, Wrapped], AccessControlPrivate USING[InternalAccessControlLogicError, PntrHeaderRec, PntrDataRec], AccessControlUtility USING[Compare, MakeRNameFromOwnerStringRep], AlpineEnvironment USING[LockMode, OpenFileID, OwnerName, PageCount, PageNumber, PageRun, wordsPerPage], AlpineFile USING[LockFailed, LockPages, Open, ReadPages, UnlockPages, Unknown, WritePages], AlpineIdentity USING[myLocalConversation], Basics USING[BITAND, BITXOR, LongDivMod, LowHalf], Rope USING[Fetch, Size]; AccessControlFileImpl: PROGRAM IMPORTS AC: AccessControl, ACF: AccessControlFile, ACP: AccessControlPrivate, ACU: AccessControlUtility, AF: AlpineFile, AID: AlpineIdentity, Basics, Rope EXPORTS AccessControlFile, AccessControlPrivate SHARES AccessControlPrivate = -- shares so we can get RecNumber. BEGIN OPEN AE: AlpineEnvironment; LockConflict: PUBLIC ERROR [lockItem: ACF.LockItem] = CODE; -- signalled by various read routines. UnexpectedResultFromLock: ERROR = CODE; -- horrible. -- Reads are done in fail or wait mode. Writes are always done in fail mode, because we should always be guaranteed that they will work, either due to the volume group lock or due to previous reads acquiring the appropriate locks. ReadHeaderRec: PUBLIC SAFE PROCEDURE[ownerOpenFileID: AE.OpenFileID, recLockMode: AE.LockMode, pntrHeaderRec: ACP.PntrHeaderRec] = TRUSTED BEGIN -- non system-fatal errors: ACF.LockConflict, AC.Unknown[openFileID, transID]. pageRun: AE.PageRun _ [GetFirstPageNumFromRecNumber[HeaderRecNumber], PagesPerRec]; BEGIN AF.ReadPages[AID.myLocalConversation, ownerOpenFileID, pageRun, DESCRIPTOR[pntrHeaderRec, WordsPerRec], [recLockMode, fail] ! AF.Unknown => SELECT what FROM openFileID => GOTO unkOpenFileID; transID => GOTO unkTransID; ENDCASE => NULL; AF.LockFailed => IF why = conflict THEN GOTO lockConflict]; EXITS unkOpenFileID => ERROR AC.Unknown[openFileID]; unkTransID => ERROR AC.Unknown[transID]; lockConflict => ERROR ACF.LockConflict[[recLockMode, pageRun[ownerOpenFileID, pageRun]]]; END; END; ReadDataRec: PUBLIC SAFE PROCEDURE[ownerOpenFileID: AE.OpenFileID, ownerName: AE.OwnerName, desiredState: ACF.WantOwnerOrEmpty, recLockMode: AE.LockMode, pntrDataRec: ACP.PntrDataRec, lastDataRecNum: ACF.RecNumber] RETURNS [dataRecNum: ACF.RecNumber, reclaimedRec: BOOLEAN] = CHECKED BEGIN -- non system-fatal errors: ACF.LockConflict, AC.OperationFailed[duplicateOwner, ownerDatabaseFull], AC.StaticallyInvalid(badLengthName), AC.Unknown[openFileID, owner, transID]. initialRecNum: ACF.RecNumber; firstDeletedRecNum: ACF.RecNumber _ nullRecNumber; pageRun: AE.PageRun; reclaimedRec _ FALSE; dataRecNum _ initialRecNum _ GetDataRecNumFromOwnerName[lastDataRecNum, ownerName]; DO TRUSTED BEGIN pageRun _ [GetFirstPageNumFromRecNumber[dataRecNum], PagesPerRec]; AF.ReadPages[AID.myLocalConversation, ownerOpenFileID, pageRun, DESCRIPTOR[pntrDataRec, WordsPerRec], [recLockMode, fail] ! AF.Unknown => SELECT what FROM openFileID => GOTO unkOpenFileID; transID => GOTO unkTransID; ENDCASE => NULL; AF.LockFailed => IF why = conflict THEN GOTO lockConflict]; SELECT pntrDataRec.state FROM empty => IF (desiredState = wantEmpty) THEN BEGIN IF firstDeletedRecNum # nullRecNumber THEN BEGIN dataRecNum _ firstDeletedRecNum; AF.ReadPages[AID.myLocalConversation, ownerOpenFileID, [GetFirstPageNumFromRecNumber[dataRecNum], PagesPerRec], DESCRIPTOR[pntrDataRec, WordsPerRec], [recLockMode, fail] ! AF.Unknown => SELECT what FROM openFileID => GOTO unkOpenFileID; transID => GOTO unkTransID; ENDCASE => NULL;]; reclaimedRec _ TRUE; END; RETURN; END ELSE ERROR AC.Unknown[owner]; valid => IF ACU.Compare[ACU.MakeRNameFromOwnerStringRep[ @pntrDataRec.ownerName], ownerName] THEN BEGIN IF (desiredState = wantOwner) THEN RETURN ELSE ERROR AC.OperationFailed[duplicateOwner]; END; ENDCASE => IF firstDeletedRecNum = nullRecNumber THEN firstDeletedRecNum _ dataRecNum; END; dataRecNum _ GetNextDataRecNumberFromRecNum[dataRecNum, wrapOnEof, lastDataRecNum, initialRecNum ! Wrapped => GOTO wrapped]; REPEAT wrapped => IF (desiredState = wantOwner) THEN ERROR AC.Unknown[owner] ELSE ERROR AC.OperationFailed[ownerDatabaseFull]; unkOpenFileID => ERROR AC.Unknown[openFileID]; unkTransID => ERROR AC.Unknown[transID]; lockConflict => ERROR ACF.LockConflict[[recLockMode, pageRun[ownerOpenFileID, pageRun]]]; ENDLOOP; END; OutOfRecNumRange: PUBLIC ERROR = CODE; ReadDataRecViaRecNum: PUBLIC SAFE PROCEDURE[ownerOpenFileID: AE.OpenFileID, dataRecNum: ACF.RecNumber, recLockMode: AE.LockMode, pntrDataRec: ACP.PntrDataRec, lastDataRecNum: ACF.RecNumber] = CHECKED BEGIN -- non system-fatal errors: OutOfRecNumRange, ACF.LockConflict, AC.Unknown[openFileID, transID]. pageRun: AE.PageRun; IF dataRecNum NOT IN [FirstDataRecNum..lastDataRecNum) THEN ERROR OutOfRecNumRange; TRUSTED BEGIN pageRun _ [GetFirstPageNumFromRecNumber[dataRecNum], PagesPerRec]; AF.ReadPages[AID.myLocalConversation, ownerOpenFileID, pageRun, DESCRIPTOR[pntrDataRec, WordsPerRec], [recLockMode, fail] ! AF.Unknown => SELECT what FROM openFileID => GOTO unkOpenFileID; transID => GOTO unkTransID; ENDCASE => NULL; AF.LockFailed => IF why = conflict THEN GOTO lockConflict]; EXITS unkOpenFileID => ERROR AC.Unknown[openFileID]; unkTransID => ERROR AC.Unknown[transID]; lockConflict => ERROR ACF.LockConflict[[recLockMode, pageRun[ownerOpenFileID, pageRun]]]; END; END; UnlockDataRecViaRecNum: PUBLIC SAFE PROCEDURE[ownerOpenFileID: AE.OpenFileID, dataRecNum: ACF.RecNumber, lastDataRecNum: ACF.RecNumber] = CHECKED BEGIN -- non system-fatal errors: OutOfRecNumRange, AC.Unknown[openFileID, transID]. IF dataRecNum NOT IN [FirstDataRecNum..lastDataRecNum) THEN ERROR OutOfRecNumRange; TRUSTED BEGIN AF.UnlockPages[AID.myLocalConversation, ownerOpenFileID, [firstPage: GetFirstPageNumFromRecNumber[dataRecNum], count: PagesPerRec] ! AF.Unknown => SELECT what FROM openFileID => GOTO unkOpenFileID; transID => GOTO unkTransID; ENDCASE => NULL]; END; EXITS unkOpenFileID => ERROR AC.Unknown[openFileID]; unkTransID => ERROR AC.Unknown[transID]; END; LockFileOrPageRun: PUBLIC SAFE PROCEDURE[lockItem: ACF.LockItem] = TRUSTED BEGIN -- non system-fatal errors: AC.LockFailed[timeout], AC.Unknown[transID]. ENABLE BEGIN AF.LockFailed => IF why = timeout THEN GOTO lockTimeout; AF.Unknown => IF what = transID THEN GOTO unkTransID; END; WITH l: lockItem SELECT FROM file => [, ] _ AF.Open[AID.myLocalConversation, l.transID, l.universalFile, readWrite, [l.mode, wait], log, l.refPattern ! AF.Unknown => SELECT what FROM volumeID, fileID => GOTO worldChanged; ENDCASE;]; pageRun => AF.LockPages[AID.myLocalConversation, l.openFileID, l.pageRun, [l.mode, wait] ! AF.Unknown => IF what = openFileID THEN GOTO transAborted]; ENDCASE; EXITS lockTimeout => ERROR AC.LockFailed[timeout]; transAborted, unkTransID => ERROR AC.Unknown[transID]; worldChanged => NULL; -- let higher routines discover this. END; WriteHeaderRec: PUBLIC SAFE PROCEDURE[ownerOpenFileID: AlpineEnvironment.OpenFileID, pntrHeaderRec: AccessControlPrivate.PntrHeaderRec] = CHECKED BEGIN -- non system-fatal errors: AC.Unknown[openFileID, transID]. TRUSTED BEGIN AF.WritePages[AID.myLocalConversation, ownerOpenFileID, [firstPage: GetFirstPageNumFromRecNumber[HeaderRecNumber], count: PagesPerRec], DESCRIPTOR[pntrHeaderRec, WordsPerRec], [write, fail] ! AF.Unknown => SELECT what FROM openFileID => GOTO unkOpenFileID; transID => GOTO unkTransID; ENDCASE => NULL; AF.LockFailed => GOTO lockInconsistency]; END; EXITS unkOpenFileID => ERROR AC.Unknown[openFileID]; unkTransID => ERROR AC.Unknown[transID]; lockInconsistency => ERROR UnexpectedResultFromLock; END; WriteDataRec: PUBLIC SAFE PROCEDURE[ownerOpenFileID: AlpineEnvironment.OpenFileID, dataRecNum: ACF.RecNumber, pntrDataRec: AccessControlPrivate.PntrDataRec] = CHECKED BEGIN -- non system-fatal errors: AC.Unknown[openFileID, transID]. TRUSTED BEGIN AF.WritePages[AID.myLocalConversation, ownerOpenFileID, [firstPage: GetFirstPageNumFromRecNumber[dataRecNum], count: PagesPerRec], DESCRIPTOR[pntrDataRec, WordsPerRec], [write, fail] ! AF.Unknown => SELECT what FROM openFileID => GOTO unkOpenFileID; transID => GOTO unkTransID; ENDCASE => NULL; AF.LockFailed => GOTO lockInconsistency]; END; EXITS unkOpenFileID => ERROR AC.Unknown[openFileID]; unkTransID => ERROR AC.Unknown[transID]; lockInconsistency => ERROR UnexpectedResultFromLock; END; GetNextDataRecNumber: PUBLIC SAFE PROCEDURE[prevDataRecNum, lastDataRecNum: ACF.RecNumber, whatToDo: ACF.StopOrWrap, wrapDataRecNumber: ACF.RecNumber] RETURNS [nextDataRecNum: ACF.RecNumber] = CHECKED BEGIN -- non system-fatal errors: ACF.Stopped, ACF.Wrapped. RETURN[GetNextDataRecNumberFromRecNum[prevDataRecNum, whatToDo, lastDataRecNum, wrapDataRecNumber]]; END; ownerFileLengthLessThanMinAllowed: ERROR = CODE; ComputeLastDataRecNumberFromFileLength: PUBLIC SAFE PROCEDURE [length: AE.PageCount] RETURNS [lastDataRecNum: ACF.RecNumber] = CHECKED BEGIN -- non system-fatal errors: none. rem: CARDINAL; [lastDataRecNum.num, rem] _ Basics.LongDivMod[length, PagesPerRec]; IF (((lastDataRecNum.num _ lastDataRecNum.num - 1) < 1) OR (rem # 0)) THEN ERROR ownerFileLengthLessThanMinAllowed; RETURN[[FirstDataRecNum.num + lastDataRecNum.num - 1]]; END; OwnerFileEntriesZero: ERROR = CODE; -- should always be at least a weird owner. LengthToSetOwnerFile: PUBLIC SAFE PROCEDURE[totalEntries: CARDINAL] RETURNS [length: AE.PageCount] = CHECKED BEGIN -- non system-fatal errors: none. GetNextPrime: SAFE PROCEDURE[number: AE.PageCount] RETURNS [prime: AE.PageCount] = CHECKED BEGIN divisor, quotient, remainder: CARDINAL; DO divisor _ 2; DO [quotient, remainder] _ Basics.LongDivMod[number, divisor]; IF remainder = 0 THEN EXIT; -- this number is not prime. IF quotient < divisor THEN RETURN[number]; -- prime. divisor _ divisor + 1; ENDLOOP; number _ number + 1; ENDLOOP; END; IF totalEntries = 0 THEN ERROR OwnerFileEntriesZero; length _ totalEntries*PagesPerRec; length _ GetNextPrime[length] + 1; -- one for Header. END; GetDataRecNumFromOwnerName: SAFE PROCEDURE[lastDataRecNum: ACF.RecNumber, ownerName: AE.OwnerName] RETURNS [dataRecNum: ACF.RecNumber] = CHECKED BEGIN -- non system-fatal errors: AC.StaticallyInvalid(badLengthName). ThreeFields: TYPE = MACHINE DEPENDENT RECORD[signBit (0: 0..0): OneBit, first (0: 1..5): FiveBits, second (0: 6..10): FiveBits, third (0: 11..15): FiveBits]; OneBit: TYPE = [0..1); FiveBits: TYPE = [0..32); hash: ThreeFields _ [0, 0, 0, 0]; temp: FiveBits; length: INTEGER; size: INT _ Rope.Size[ownerName]; IF size = 0 THEN ERROR AC.StaticallyInvalid; length _ Basics.LowHalf[size]; FOR index: INTEGER IN [0..length) DO temp _ hash.first; hash.first _ hash.second; hash.second _ hash.third; hash.third _ temp; LOOPHOLE[hash, CARDINAL] _ Basics.BITXOR[LOOPHOLE[hash, CARDINAL], Basics.BITAND[LOOPHOLE[Rope.Fetch[ownerName, index], CARDINAL], 37B]]; -- include 5 bits of each char. ENDLOOP; RETURN[[(LOOPHOLE[hash, CARDINAL] MOD (lastDataRecNum - FirstDataRecNum)) + FirstDataRecNum]]; END; GetFirstPageNumFromRecNumber: SAFE PROCEDURE[dataRecNum: ACF.RecNumber] RETURNS [pageNum: AE.PageNumber] = TRUSTED INLINE BEGIN -- non system-fatal errors: none. RETURN[LOOPHOLE[dataRecNum, AE.PageNumber]]; -- here we know there is one page per record. END; Stopped: PUBLIC ERROR = CODE; Wrapped: PUBLIC ERROR = CODE; GetNextDataRecNumberFromRecNum: SAFE PROCEDURE[prevDataRecNum: ACF.RecNumber, whatToDo: ACF.StopOrWrap, lastDataRecNum, wrapDataRecNumber: ACF.RecNumber] RETURNS [nextDataRecNum: ACF.RecNumber] = CHECKED BEGIN -- non system-fatal errors: ACF.Stopped, ACF.Wrapped. IF prevDataRecNum NOT IN [FirstDataRecNum..lastDataRecNum) THEN ERROR ACP.InternalAccessControlLogicError; IF prevDataRecNum = lastDataRecNum - 1 THEN BEGIN IF whatToDo = stopOnEof THEN ERROR ACF.Stopped ELSE nextDataRecNum _ FirstDataRecNum; END ELSE nextDataRecNum _ [prevDataRecNum + 1]; IF ((whatToDo = wrapOnEof) AND (nextDataRecNum = wrapDataRecNumber)) THEN ERROR ACF.Wrapped; END; StartAccessControlFile: PUBLIC SAFE PROCEDURE = CHECKED BEGIN NULL; END; nullRecNumber: PUBLIC ACF.RecNumber _ [0]; HeaderRecNumber: ACF.RecNumber = [0]; FirstDataRecNum: PUBLIC ACF.RecNumber _ [HeaderRecNumber + 1]; PagesPerRec: PUBLIC CARDINAL _ 1; -- header and owner records are the same size to make things easy. WordsPerRec: PUBLIC CARDINAL _ PagesPerRec*AE.wordsPerPage; IF AE.wordsPerPage # 256 THEN ERROR; END. Edit Log Initial: Kolling: 10-Nov-81 16:37:08: module that knows about the physical format of the owner database files, for AccessControl.