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