-- 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.