-- 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. Ê“˜Jš±œÏcƒœÏk œžœ\žœ…žœcžœIžœwžœjžœ-žœžœžœ,žœÐblœžœžœžœžœžœžœžœžœ!žœ,žœ#œžœžœžœ$žœžœ žœ žœ'œžœžœ œèœÏn œžœžœž œžœžœžœžœžœœ3œ žœMžœžœ žœ7ž œ;žœ žœžœžœ%žœ žœžœ žœžœžœžœžœžœžœ+žœžœ*žœžœcžœžœ  œžœžœž œžœžœžœ žœžœžœ žœžœžœžœžœœ”œžœ$žœ)žœžœežœžœžœMžœ žœ7ž œ9žœ žœžœžœ%žœ žœžœ žœžœžœžœžœžœžœ1žœžœ$žœJžœžœjžœ žœ°ž œ…žœ žœžœžœHžœ žœ1žœAžœ0žœ%žœ%žœžœžœžœ!žœžœ žœkžœžœžœžœžœžœžœžœ"žœžœžœ:žœ&žœ{žœžœžœ1žœžœžœ%žœžœžœ=žœžœ+žœžœ*žœžœcžœžœžœžœžœ œžœžœž œžœžœžœžœžœžœžœ`œžœžœ žœžœ*žœžœžœžœMžœ žœ7ž œ9žœ žœžœžœ%žœ žœžœ žœžœžœžœžœžœžœ+žœžœ*žœžœcžœžœ œžœžœž œžœžœžœžœžœNœžœ žœžœ*žœžœžœžœžœ žœžœ žœžœžœ%žœ žœžœžœžœžœžœ+žœžœžœ œžœžœž œ žœ žœžœHœžœ žœ žœžœžœžœžœ žœžœžœžœžœ žœžœžœžœŒžœ žœžœžœ(žœžœ žœkžœ žœžœžœžœžœžœžœ9žœžœ*žœ%œžœ œžœžœž œfžœžœ<œžœžœžœ žœ…ž œ5žœ žœžœžœ%žœ žœžœ žœžœžœžœžœžœ+žœžœ/žœžœ  œžœžœž œ=žœ=žœžœœœžœžœžœ žœ€ž œ3žœ žœžœžœ%žœ žœžœ žœžœžœžœžœžœ+žœžœ/žœžœ œžœžœž œ!žœžœ žœ žœžœžœžœœœžœjžœ'žœžœ &œžœžœž œ žœ žœžœžœžœ!œ žœNžœ6žœžœžœ(žœ6žœžœžœ,œ œžœžœž œžœžœ žœžœžœ"œ  œžœž œ žœ žœ žœžœžœ%žœžœžœMžœžœžœœ žœžœžœ œ(žœ#žœžœžœžœžœ=œ'œžœ œžœž œžœžœ žœžœžœžœAœžœžœž œžœŒžœžœRžœ žœžœ žœžœžœ;žœžœžœžœ~žœžœ žœžœžœžœžœžœ *œžœžœžœžœžœEžœ œžœž œžœžœžœžœ"œžœžœ-œžœ œžœžœžœ œžœžœžœ œžœž œmžœ$žœžœ5œžœžœžœ#žœžœ1žœ-žœžœžœžœžœ0žœ žœ,žœžœ.žœžœžœ œžœžœžœžœžœ  œžœ œ œžœ(  œžœžœBœ  œžœžœ&žœžœžœžœœ˜óu—…—:öC