AccessControlFileImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Last edited by
Kolling on May 27, 1983 4:11 pm
MBrown on January 30, 1984 2:51:22 pm PST
Last Edited by: Kupfer, August 6, 1984 2:32:05 pm PDT
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.
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 {
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
The AC.LockFailed[] is only a propagation of the AF.LockFailed[], so we log it at a lower level.
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.
Edited on July 24, 1984 2:16:57 pm PDT, by Kupfer
Add SkiPatrolLog probes.
changes to: DIRECTORY, ReadDataRec, LockFileOrPageRun
Edited on August 6, 1984 2:06:25 pm PDT, by Kupfer
Remove the possible race condition in SkiPatrolLog probes by assigning the PROC to a temporary variable.
changes to: ReadDataRec