<> <> <<>> <> <> <> <> 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[Cat, Fetch, Size], SkiPatrolHooks USING[FileInfoToTransID], SkiPatrolLog USING[notice, OpFailureInfo]; AccessControlFileImpl: PROGRAM IMPORTS AC: AccessControl, ACF: AccessControlFile, ACP: AccessControlPrivate, ACU: AccessControlUtility, AF: AlpineFile, AID: AlpineIdentity, Basics, Rope, SkiPatrolHooks, SkiPatrolLog 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. <> 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 { IF firstDeletedRecNum # nullRecNumber THEN { 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; }; RETURN; } ELSE ERROR AC.Unknown[owner]; valid => { ownerNameFromFile: AE.OwnerName _ ACU.MakeRNameFromOwnerStringRep[@pntrDataRec.ownerName]; IF ACU.Compare[ownerNameFromFile, ownerName] THEN { IF (desiredState = wantOwner) THEN RETURN ELSE { logProc: PROC [SkiPatrolLog.OpFailureInfo]; IF (logProc _ SkiPatrolLog.notice.operationFailed) # NIL THEN logProc[[ what: duplicateOwner, where: "AccessControlFileImpl.ReadDataRec", transID: SkiPatrolHooks.FileInfoToTransID[AID.myLocalConversation, ownerOpenFileID], message: Rope.Cat["Owners = ", ownerName, ", ", ownerNameFromFile] ]]; ERROR AC.OperationFailed[duplicateOwner]; }; }; }; 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 { logProc: SAFE PROC [SkiPatrolLog.OpFailureInfo]; IF (logProc _ SkiPatrolLog.notice.operationFailed) # NIL THEN logProc[[ what: ownerDatabaseFull, where: "AccessControlFileImpl.ReadDataRec", transID: SkiPatrolHooks.FileInfoToTransID[AID.myLocalConversation, ownerOpenFileID], message: "" ]]; 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]]; <> 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. <> <> <> <> <> <> <<>>