-- file VirtTOC.Mesa
-- Edited by Schroeder, October 17, 1979 3:38 PM.
-- Edited by Brotz, January 28, 1983 1:55 PM.
-- Edited by Levin, February 25, 1981 10:37 AM.
-- Edited by Taft, May 13, 1983 10:25 AM

DIRECTORY
AltoFile USING [DiskFull],
displayCommon USING [bitmapInMDS],
exD: FROM "ExceptionDefs" USING [SysBug],
FrameDefs USING [GetCaller],
Inline USING [BITAND, COPY],
Process USING [Yield],
Storage USING [Node],
String USING [WordsForString],
vmD: FROM "VirtualMgrDefs" USING [bugTrapValue, DisplayMessagePtr, DMList,
DMListBlk, GetBuffer, HardTOCAddress, MakeBufferEmpty, PageNumber, TOCFixedPart,
TOCFixedPartPtr, TOCHandle, TOCIndex, TOCMinStringSize, TOCOverflow,
TOCPageHeader, TOCPageHeaderBlk, TOCPageTableSize, TOCType, VoidCharCache],
VMDefs USING [Error, MarkStartWait, Page, Release];

VirtTOC: MONITOR
IMPORTS AltoFile, disC: displayCommon, exD, FrameDefs, Inline, Process, Storage,
String, vmD, VMDefs
EXPORTS vmD =

BEGIN
OPEN vmD;

-- The TOC Department is implemented here and in VirtSS.mesa.


nextKey: CARDINAL ← 1;


LockTOC: PUBLIC ENTRY PROCEDURE [toc: TOCHandle] RETURNS [key: CARDINAL] =
-- Returns either 0, meaning that the toc is already locked, or some key # 0. A caller
-- that receives a 0 must not call any other procedures requiring the TOC key. A non-0
-- key may be used by the caller in subsequent calls to VirtTOC procedures until the
-- toc is unlocked by explicit call to UnlockTOC.
BEGIN
IF toc.key # 0 THEN RETURN[0];
key ← toc.key ← nextKey;
nextKey ← nextKey + 1;
IF nextKey = 0 THEN nextKey ← 1;
toc.keyHolder ← FrameDefs.GetCaller[];
END; -- of LockTOC --


WaitForLock: PUBLIC PROCEDURE [toc: TOCHandle] RETURNS [key: CARDINAL] =
-- Busy waits until it can return a non-zero key for toc.
BEGIN
WHILE (key ← LockTOC[toc]) = 0 DO Process.Yield[]; ENDLOOP;
toc.keyHolder ← FrameDefs.GetCaller[];
END; -- of WaitForLock --


UnlockTOC: PUBLIC PROCEDURE [toc: TOCHandle, key: CARDINAL] =
-- Unlocks the toc.
BEGIN
IF key = 0 OR key # toc.key THEN exD.SysBug[];
IF disC.bitmapInMDS THEN MakeBufferEmpty[toc]; -- free up VM buffer
toc.keyHolder ← NIL;
toc.key ← 0;
END; -- of UnlockTOC --


ExtendTOC: PUBLIC
PROCEDURE [toc: TOCHandle, key: CARDINAL, fp: TOCFixedPartPtr, s: STRING] =
-- The TOC data and string are incorporated in the TOC structure at the end. After this
-- procedure is called, the information and string can be modified with
-- PutTOC(FixedPart/String) and accessed with GetTOC(FixedPart/String). Care is taken
-- to allocate a disk page for each new page of the TOC that is assigned.
-- May raise TOCOverflow.
BEGIN
numWords, remainingWordsOnPage: CARDINAL;
sLen: CARDINAL = s.length;
sMaxLen: CARDINAL = MAX[sLen, TOCMinStringSize];
sWords: CARDINAL = String.WordsForString[sMaxLen];
hta: HardTOCAddress;
pageHeader: TOCPageHeader;

IF key = 0 OR key # toc.key THEN exD.SysBug[];
numWords ← SIZE[TOCFixedPart] + sWords;
IF numWords > 256 - SIZE[TOCPageHeaderBlk] THEN exD.SysBug[];
IF toc.indexFF = 0
THEN remainingWordsOnPage ← 0
ELSE BEGIN
GetBuffer[toc, toc.filePageFF - 1, FALSE];
pageHeader ← LOOPHOLE[toc.buffer];
hta ← LOOPHOLE[toc.buffer + SIZE[TOCPageHeaderBlk]];
THROUGH [toc.pageTable[toc.logicalPageNumber] .. toc.indexFF) DO
hta ← WordsInTOCEntry[hta] + hta;
ENDLOOP;
remainingWordsOnPage ← 256 - Inline.BITAND[255, LOOPHOLE[hta, CARDINAL]];
IF remainingWordsOnPage = 256 THEN remainingWordsOnPage ← 0;
END;

IF numWords > remainingWordsOnPage THEN
BEGIN -- Get a new page.
IF toc.filePageFF >= TOCPageTableSize THEN ERROR TOCOverflow;
GetBuffer[toc, toc.filePageFF, TRUE];
pageHeader ← LOOPHOLE[toc.buffer];
hta ← LOOPHOLE[toc.buffer + SIZE[TOCPageHeaderBlk]];
pageHeader.numberOfEntries ← 0;
pageHeader.garbageDetector ← TOCType; -- for LoadTOC.
--make sure there’s a place on the disk for the new page
VMDefs.MarkStartWait[toc.buffer
! VMDefs.Error =>
IF reason = resources THEN {VMDefs.Release[toc.buffer]; toc.bufferState ← empty};
AltoFile.DiskFull => {VMDefs.Release[toc.buffer]; toc.bufferState ← empty}];
toc.pageTable[toc.filePageFF].firstTOCIndex ← toc.indexFF;
toc.filePageFF ← toc.filePageFF+1;
END; -- We have gotten and inited a new page.

toc.indexFF ← toc.indexFF + 1;
pageHeader.numberOfEntries ← pageHeader.numberOfEntries + 1;
toc.bufferState ← dirty;
fp.changed ← FALSE;
PutTOCFixedPartInMemory[fp, hta];
PutTOCStringInMemory[s, sWords, sLen, sMaxLen, hta + SIZE[TOCFixedPart]];
END; -- of ExtendTOC --


GetTOCFixedPart: PUBLIC PROCEDURE
[toc: TOCHandle, key: CARDINAL, index: TOCIndex, fp: TOCFixedPartPtr] =
-- Obtains the index’th TOCEntry data portion and copies it into the structure pointed at by
-- fP.
BEGIN
hta: HardTOCAddress;
IF key = 0 OR key # toc.key THEN exD.SysBug[];
hta ← FindTOCAddress[toc, index];
fp↑ ← LOOPHOLE[hta, TOCFixedPartPtr]↑;
END; -- of GetTOCFixedPart --


GetTOCString: PUBLIC
PROCEDURE [toc: TOCHandle, key: CARDINAL, index: TOCIndex, s: STRING] =
-- Obtains the index’th TOCEntry string and copies as much as will fit into s.
BEGIN
hta: HardTOCAddress;
s2: STRING;
size: CARDINAL;
IF key = 0 OR key # toc.key THEN exD.SysBug[];
hta ← FindTOCAddress[toc, index];
s2 ← LOOPHOLE[hta + SIZE[TOCFixedPart], STRING];
size ← MIN[s.maxlength, s2.length];
Inline.COPY[@s2.text, String.WordsForString[size] - 2, @s.text];
s.length ← size;
END; -- of GetTOCString --


PutTOCFixedPart: PUBLIC PROCEDURE
[toc: TOCHandle, key: CARDINAL, index: TOCIndex, fp: TOCFixedPartPtr,
changed: BOOLEAN ← TRUE] =
-- Obtains the index’th TOCEntry and copies portions of the data structure pointed to by toc
-- into it. Also sets the changed flag of that TOCEntry to "changed" and marks the TOC
-- page as having been written.
BEGIN
hta: HardTOCAddress;
IF key = 0 OR key # toc.key THEN exD.SysBug[];
hta ← FindTOCAddress[toc, index];
PutTOCFixedPartInMemory[fp, hta];
MarkChange[toc, index, hta, changed];
END; -- of PutTOCFixedPart --


PutTOCString: PUBLIC PROCEDURE
[toc: TOCHandle, key: CARDINAL, index: TOCIndex, s: STRING] =
-- Obtains the index’th TOCEntry and copies as much of s as will fit into the string portion
-- of this entry. Marks the TOC page as having been written.
BEGIN
hta: HardTOCAddress;
old: STRING;
sMaxLen, sLen: CARDINAL;
IF key = 0 OR key # toc.key THEN exD.SysBug[];
hta ← FindTOCAddress[toc, index];
old ← LOOPHOLE[hta + SIZE[TOCFixedPart], STRING];
sMaxLen ← old.maxlength;
sLen ← MIN[s.length, sMaxLen];
PutTOCStringInMemory[s, String.WordsForString[sMaxLen], sLen, sMaxLen, old];
MarkChange[toc, index, hta];
END; -- of PutTOCString --


MarkChange: PROCEDURE [toc: TOCHandle, index: TOCIndex, hta: HardTOCAddress,
changed: BOOLEAN ← TRUE] =
-- House-keep changed records.
BEGIN
LOOPHOLE[hta, TOCFixedPartPtr].changed ← changed;
IF changed THEN
toc.firstChange ← (IF toc.firstChange = 0 THEN index ELSE MIN[toc.firstChange, index]);
toc.bufferState ← dirty;
END; -- of MarkChange --


FirstFreeTOCIndex: PUBLIC PROCEDURE [toc: TOCHandle, key: CARDINAL]
RETURNS [index: TOCIndex] =
-- This procedure returns the TOC Index value of the next entry available after the current
-- TOC, which is equal to the number of entries in the TOC and one greater than the real
-- last TOC Index. If the toc is not open then zero is returned.
BEGIN
IF key = 0 OR key # toc.key THEN exD.SysBug[];
RETURN[IF ~toc.open THEN 0 ELSE toc.indexFF];
END; -- of FirstFreeTOCIndex --


FirstChangedTOCIndex: PUBLIC
PROCEDURE [toc: TOCHandle] RETURNS [TOCIndex] =
-- Returns the TOC Index value of the 1st (i.e. the numerically lowest) TOC entry which
-- has been changed via PutTOCFixedPart. This is the first TOC entry which has the
-- "changed" BOOLEAN as true. ExtendTOC does not normally set this true (as does
-- PutTOCFixedPart), but will respect that setting if the user does so. If no TOC entries
-- have changed=TRUE, then the first free (ref. GetFirstFreeTOCIndex) index is returned.
-- Non-recoverable errors: "No TOC open".
BEGIN
IF ~toc.open THEN exD.SysBug [];
RETURN[IF toc.firstChange = 0 THEN toc.indexFF ELSE toc.firstChange];
END; -- of FirstChangedTOCIndex --


PutTOCFixedPartInMemory: PROCEDURE [fp: TOCFixedPartPtr, hta: HardTOCAddress] =
-- Internal proc to stash FixedPart into memory.
BEGIN
fp.bugTrap ← bugTrapValue;
Inline.COPY[fp, SIZE[TOCFixedPart], hta];
END; -- of PutTOCFixedPartInMemory --


PutTOCStringInMemory: PROCEDURE
[s: STRING, sWords, sLen, sMaxLen: CARDINAL, hta: HardTOCAddress] =
-- Internal proc to stash String into memory. (This is made complicated by my avoiding
-- knowing the structure of a STRING, which may change!)
BEGIN
Inline.COPY[s, sWords, hta];
LOOPHOLE[hta, STRING]↑ ← StringBody[length: sLen, maxlength: sMaxLen, text: ];
END; -- of PutTOCFixedPartInMemory --


TOCAddressOnPage: PUBLIC PROCEDURE
[toc: TOCHandle, index: TOCIndex, page: VMDefs.Page, pn: PageNumber]
RETURNS [TOCFixedPartPtr] =
BEGIN
fp: TOCFixedPartPtr ← LOOPHOLE[page + SIZE[TOCPageHeaderBlk]];
first: TOCIndex = toc.pageTable[pn];
FOR i: CARDINAL IN [first .. index) DO
fp ← fp + WordsInTOCEntry[fp];
ENDLOOP;
IF fp.bugTrap # bugTrapValue THEN exD.SysBug[];
RETURN[fp];
END; -- of TOCAddressOnPage --


SearchTOCTable: PUBLIC PROCEDURE [toc: TOCHandle, index: TOCIndex]
RETURNS [pn: PageNumber] =
-- Maps a TOCIndex to a TOC file page number.
BEGIN
IF index >= toc.indexFF THEN exD.SysBug[];
FOR pn IN [1 .. toc.filePageFF) DO
IF toc.pageTable[pn] > index THEN EXIT;
REPEAT
FINISHED => pn ← toc.filePageFF;
ENDLOOP;
pn ← pn - 1;
END; -- of SearchTOCTable --


WordsInTOCEntry: PROCEDURE [hta: HardTOCAddress] RETURNS [CARDINAL] = INLINE
BEGIN
fWords: CARDINAL = SIZE[TOCFixedPart];
RETURN[fWords + String.WordsForString[LOOPHOLE[hta + fWords, STRING].maxlength]];
END; -- of WordsInTOCEntry --


FindTOCAddress: PUBLIC PROCEDURE [toc: TOCHandle, index: TOCIndex]
RETURNS [hta: HardTOCAddress] =
-- You’d better be careful, cache activity may cause the pointers returned to become
-- dangling references.
-- Leaves the toc page containing index in toc.buffer.
BEGIN
pn: PageNumber = SearchTOCTable[toc, index]; -- index is on "page".
IF toc.key = 0 THEN exD.SysBug[];
GetBuffer[toc, pn, FALSE];
RETURN [TOCAddressOnPage[toc, index, toc.buffer, pn]];
END; -- of FindTOCAddress --


LoadDisplayMessage: PUBLIC PROCEDURE
[toc: TOCHandle, key: CARDINAL, index: TOCIndex, msg: DisplayMessagePtr] =
-- Calls ClearMessage[msg] and then initializes msg to be the messsage string pointed to by
-- the index TOC entry. Any previous contents of msg are lost.
BEGIN
hta: HardTOCAddress;
fp: TOCFixedPartPtr;
dml, nilMsgDml: DMList ← NIL;
IF key = 0 OR key # toc.key OR ~toc.open THEN exD.SysBug[];
hta ← FindTOCAddress[toc, index];
fp ← LOOPHOLE[hta];
FOR dml ← toc.dmList, dml.next UNTIL dml = NIL DO
IF dml.dm = msg THEN exD.SysBug[]; -- someone forgot to unvirtualize the last msg.
IF nilMsgDml = NIL AND dml.dm = NIL THEN nilMsgDml ← dml;
ENDLOOP;
IF nilMsgDml = NIL THEN
BEGIN
nilMsgDml ← Storage.Node[SIZE[DMListBlk]];
nilMsgDml↑ ← DMListBlk[next: toc.dmList, dm: msg];
toc.dmList ← nilMsgDml;
END
ELSE nilMsgDml.dm ← msg;
VoidCharCache[@msg.get];
msg.toc ← toc;
msg.file ← toc.mailFile;
msg.open ← TRUE;
msg.index ← index;
msg.firstPage ← fp.firstPage;
msg.firstByte ← fp.firstByte + fp.offsetToHeader;
msg.textLength ← fp.textLength; -- in bytes,
END; -- of LoadDisplayMessage --


FlushDisplayMessage: PUBLIC PROCEDURE [msg: DisplayMessagePtr, key: CARDINAL] =
-- Notifies the virtual manager that a displayed message is no longer in use.
BEGIN
dml, nilMsgDml: DMList ← NIL;
CheckpointDisplayMessage[msg, key];
FOR dml ← msg.toc.dmList, dml.next UNTIL dml = NIL DO
IF dml.dm = msg THEN {dml.dm ← NIL; EXIT};
REPEAT
FINISHED => exD.SysBug[];
ENDLOOP;
msg.toc ← NIL;
msg.file ← NIL;
msg.index ← 0;
msg.open ← FALSE;
msg.firstPage ← 0;
msg.firstByte ← 0;
msg.textLength ← 0;
END; -- of FlushDisplayMessage --


CheckpointDisplayMessage: PUBLIC PROC [msg: DisplayMessagePtr, key: CARDINAL] =
-- In cases where the mail file is to be written (as when appending to the file), this
-- procedure updates the mail file to contain all changes in msg. msg is manipulateds so
-- that subsequent accesses to its contents will require initial reading from the mail file
-- once again.
BEGIN
IF msg.toc = NIL OR key = 0 OR key # msg.toc.key OR ~msg.toc.open THEN exD.SysBug[];
MakeBufferEmpty[msg];
VoidCharCache[@msg.get];
END; -- of CheckpointDisplayMessage --


END. -- of VirtTOC