-- file: ReplaceMailOp.mesa
-- edited by Brotz, March 4, 1983 9:55 AM
-- edited by Crowther, October 6, 1981 4:15 PM

DIRECTORY
Ascii USING [SP],
csD: FROM "CoreStreamDefs" USING [Close, MapPageByteToPosition,
MapPositionToPageByte, Open, Position, SetPosition, StreamCopy, StreamHandle, Write,
WriteBlock],
DiskKDDefs USING [CountFreeDiskPages],
dsD: FROM "DisplayDefs" USING [ClearRectangle],
Editor USING [RefreshSoThatFirstCharStartsLine],
exD: FROM "ExceptionDefs" USING [DisplayExceptionString, SysBug],
inD: FROM "InteractorDefs" USING [leftMargin, MessageTextNbrPtr, RefreshTOCChange,
rightMargin, TOCChangeAction, TOCTextNbrPtr],
Inline USING [COPY, LowHalf],
intCommon USING [dmTextNbr, tocTextNbr],
MailParseDefs USING [endOfInput],
mfD: FROM "MailFormatDefs" USING [CreateStamp, ParseHeaderForTOC],
opD: FROM "OperationsDefs" USING [maxTOCStringLength],
Process USING [Yield],
String USING [WordsForString],
vmD: FROM "VirtualMgrDefs" USING [bugTrapValue, CharIndex,
CheckpointDisplayMessage, DisplayMessage, DisplayMessagePtr, DMList,
FlushDisplayMessage, GetMessageChar, GetMessageSize, GetTOCFixedPart,
HardTOCAddress, LoadDisplayMessage, MakeBufferEmpty, PageNumber,
PutTOCFixedPart, SearchTOCTable, SetTOCValidity, TOCFixedPart, TOCFixedPartPtr,
TOCHandle, TOCIndex, TOCPageHeader, TOCPageHeaderBlk, TOCPageTableSize,
TOCType, VirtualMessagePtr],
VMDefs USING [Deactivate, FileHandle, FSAlto, GetFileLength, GetFileSystem, MarkStart,
Page, PageNumber, Position, ReadPage, Release, RemapPage, SetFileLength, StartFile,
StartReading, UsePage, WaitFile];

ReplaceMailOp: PROGRAM
IMPORTS csD, DiskKDDefs, dsD, Editor, exD, inD, Inline, intC: intCommon, mfD, Process,
String, vmD, VMDefs
EXPORTS opD =

BEGIN
OPEN vmD;

bytesPerStamp: CARDINAL = 24;


ReplaceMailOperation: PUBLIC PROCEDURE
[delete: BOOLEAN, index: TOCIndex, msg: VirtualMessagePtr, toc: TOCHandle,
key: CARDINAL] RETURNS [worked: BOOLEAN] =
BEGIN
messageLength: CARDINAL = IF msg = NIL THEN 0 ELSE GetMessageSize[msg];
insertionLength: CARDINAL = IF msg = NIL THEN 0 ELSE messageLength + bytesPerStamp;
deletionLength: CARDINAL ← 0;
tocString: STRING ← [opD.maxTOCStringLength];
eof: VMDefs.Position;
extraPages: CARDINAL;
mailFile: VMDefs.FileHandle = toc.mailFile;
fp, newTOCFixedPart: TOCFixedPart;
mailFileInsertAt, mailFileMoveFrom, mailFileMoveTo, mailFileEnd, newMailFileEnd:
csD.Position;
newEndPage: PageNumber;
newEndByte: CARDINAL;
dm: inD.MessageTextNbrPtr = intC.dmTextNbr;
displayMessage: DisplayMessagePtr = DisplayMessage[dm.message];
tnp: inD.TOCTextNbrPtr ← intC.tocTextNbr;
action: inD.TOCChangeAction;
charIndex: CharIndex ← 0;

NextCharForParseHeader: PROCEDURE RETURNS [char: CHARACTER] =
BEGIN
IF charIndex >= messageLength THEN RETURN[MailParseDefs.endOfInput];
char ← GetMessageChar[msg, charIndex];
charIndex ← charIndex + 1;
END; -- of NextCharForParseHeader --

MoveUpInMailFile: PROCEDURE =
-- Copy bytes of mailFile to higher addresses,
-- from [mailFileMoveFrom .. mailFileEnd) to [mailFileMoveTo .. newMailFileEnd)
BEGIN
out, in: VMDefs.Page;
inPage, outPage, fromPage: PageNumber;
outByte, inByte, fromByte: CARDINAL;

CopyBytes: PROCEDURE =
BEGIN
bytesToCopy: CARDINAL ← MIN[inByte, outByte];
IF inPage = fromPage THEN bytesToCopy ← MIN[bytesToCopy, inByte - fromByte];
inByte ← inByte - bytesToCopy;
outByte ← outByte - bytesToCopy;
FOR i: CARDINAL DECREASING IN [0 .. bytesToCopy) DO
out.chars[outByte + i] ← in.chars[inByte + i];
ENDLOOP;
END; -- of CopyBytes --

[inPage, inByte] ← csD.MapPositionToPageByte[mailFileEnd];
[outPage, outByte] ← csD.MapPositionToPageByte[newMailFileEnd];
[fromPage, fromByte] ← csD.MapPositionToPageByte[mailFileMoveFrom];
IF outByte = 0 THEN {outByte ← 512; outPage ← outPage - 1};
IF inByte = 0 THEN {inByte ← 512; inPage ← inPage - 1};
in ← VMDefs.ReadPage[[mailFile, inPage]];
out ← VMDefs.UsePage[[mailFile, outPage]];
DO
Process.Yield[];
CopyBytes[];
IF inPage = fromPage AND inByte = fromByte THEN {VMDefs.MarkStart[out]; EXIT};
IF inByte = 0 THEN
BEGIN
VMDefs.Release[in];
inPage ← inPage - 1;
inByte ← 512;
in ← VMDefs.ReadPage[[mailFile, inPage]];
END;
IF outByte = 0 THEN
BEGIN
VMDefs.MarkStart[out];
VMDefs.Release[out];
outPage ← outPage - 1;
outByte ← 512;
out ← VMDefs.UsePage[[mailFile, outPage]];
END;
ENDLOOP;
VMDefs.Release[out];
VMDefs.Release[in];
END; -- of MoveUpInMailFile --

MoveDownInMailFile: PROCEDURE =
-- Copy bytes of mailFile to lower addresses,
-- from [mailFileMoveFrom .. mailFileEnd) to [mailFileMoveTo .. newMailFileEnd)
BEGIN
out: csD.StreamHandle ← csD.Open[mailFile, byte, write];
in: csD.StreamHandle ← csD.Open[mailFile, byte, read];
csD.SetPosition[in, mailFileMoveFrom];
csD.SetPosition[out, mailFileMoveTo];
csD.StreamCopy[in, out, mailFileEnd - mailFileMoveFrom];
csD.Close[out];
csD.Close[in];
END; -- of MoveDownInMailFile --

InsertStampAndMessage: PROCEDURE =
BEGIN
out: csD.StreamHandle ← csD.Open[mailFile, byte, write];
messageIndex: CharIndex ← 0;

PutCharInStamp: PROCEDURE [char: CHARACTER] =
{csD.Write[out, char]};

csD.SetPosition[out, mailFileInsertAt];
mfD.CreateStamp[@newTOCFixedPart, PutCharInStamp];
UNTIL messageIndex >= messageLength DO
[] ← GetMessageChar[msg, messageIndex]; -- MAGIC: set up msg.get.
csD.WriteBlock[out, msg.buffer, msg.get.floor + (messageIndex - msg.get.first),
msg.get.free - messageIndex];
messageIndex ← msg.get.free;
ENDLOOP;
csD.Close[out];
END; -- of InsertStampAndMessage --

-- main code for ReplaceMail.

IF ~toc.open
OR index ~IN [1 .. toc.indexFF]
OR (delete AND index = toc.indexFF)
OR (~delete AND msg = NIL)
OR (msg.vmoType = DM AND DisplayMessage[msg].toc = toc)
THEN exD.SysBug[];

action ← SELECT TRUE FROM
(delete AND msg = NIL) => delete,
delete => replace,
ENDCASE => insert;

-- Get mail file insertion point.
eof ← VMDefs.GetFileLength[mailFile];
mailFileEnd ← csD.MapPageByteToPosition[eof.page, eof.byte];
IF index = toc.indexFF THEN
BEGIN
mailFileMoveFrom ← mailFileMoveTo ← mailFileInsertAt ← mailFileEnd;
newMailFileEnd ← mailFileEnd + insertionLength;
END
ELSE BEGIN
GetTOCFixedPart[toc, key, index, @fp];
mailFileInsertAt ← fp.firstPage * LONG[512] + fp.firstByte;
IF delete THEN deletionLength ← fp.textLength + fp.offsetToHeader;
mailFileMoveFrom ← mailFileInsertAt + deletionLength;
mailFileMoveTo ← mailFileInsertAt + insertionLength;
newMailFileEnd ← mailFileEnd + (mailFileMoveTo - mailFileMoveFrom);
END;

-- Check to be sure an insertion will fit on disk and in TOC.
extraPages ← 1 + (IF insertionLength > deletionLength
THEN (insertionLength - deletionLength + 511) / 512 ELSE 0);
IF action # delete AND (toc.filePageFF + 1 >= TOCPageTableSize
OR (VMDefs.GetFileSystem[mailFile] = VMDefs.FSAlto
AND extraPages > DiskKDDefs.CountFreeDiskPages[]))
THEN RETURN[FALSE];

-- Flush buffers on the mail file held by all displayed messages.
FOR dmList: vmD.DMList ← toc.dmList, dmList.next UNTIL dmList = NIL DO
IF dmList.dm # NIL THEN vmD.CheckpointDisplayMessage[dmList.dm, key];
ENDLOOP;

SetTOCValidity[toc, FALSE];

-- Move in mail file.
[newEndPage, newEndByte] ← csD.MapPositionToPageByte[newMailFileEnd];
SELECT mailFileMoveTo FROM
> mailFileMoveFrom =>
BEGIN
VMDefs.SetFileLength[mailFile, [newEndPage, newEndByte]];
IF mailFileMoveFrom < mailFileEnd THEN MoveUpInMailFile[];
END;
< mailFileMoveFrom =>
BEGIN
IF mailFileMoveFrom < mailFileEnd THEN MoveDownInMailFile[];
VMDefs.SetFileLength[mailFile, [newEndPage, newEndByte]];
END;
ENDCASE => VMDefs.SetFileLength[mailFile, [newEndPage, newEndByte]];

-- Set up pieces of new TOC entry.
IF msg # NIL THEN
BEGIN
mfD.ParseHeaderForTOC[tocString, NextCharForParseHeader];
tocString↑ ← StringBody[length: tocString.length, maxlength: tocString.length, text: ];
newTOCFixedPart ← TOCFixedPart
[changed: FALSE,
deleted: FALSE,
seen: TRUE,
bogus: FALSE,
mark: Ascii.SP,
firstPage: Inline.LowHalf[mailFileInsertAt / 512],
firstByte: Inline.LowHalf[mailFileInsertAt] MOD 512,
bugTrap: bugTrapValue,
offsetToHeader: bytesPerStamp,
textLength: messageLength];
END;

-- Insert in mail file.
IF msg # NIL THEN InsertStampAndMessage[];

-- Fix TOC.
ResetTOCAddresses[toc, key, index, insertionLength, deletionLength, action];
FixUpTOC[toc, index, action, @newTOCFixedPart, tocString];

SetTOCValidity[toc, TRUE];


-- Fix up selection and redisplay TOC.
inD.RefreshTOCChange[toc, key, index, action];

-- fix up all dm’s
FOR dmList: vmD.DMList ← toc.dmList, dmList.next UNTIL dmList = NIL DO
dispMess: DisplayMessagePtr = dmList.dm;
dmIndex: TOCIndex;

Reload: PROCEDURE [i: TOCIndex] =
{FlushDisplayMessage[dispMess, key]; LoadDisplayMessage[toc, key, i, dispMess]};

IF dispMess # NIL THEN
BEGIN
Process.Yield[];
SELECT dmIndex ← dispMess.index FROM
< index => NULL;
= index =>
SELECT action FROM
delete => BEGIN
FlushDisplayMessage[dispMess, key];
IF dm.haveMessage AND dispMess = displayMessage THEN
BEGIN
dm.haveMessage ← FALSE;
dsD.ClearRectangle[inD.leftMargin, inD.rightMargin, dm.topY, dm.bottomY];
exD.DisplayExceptionString["The displayed message has been deleted."L];
END
ELSE exD.SysBug[]; -- someone else is holding onto a deleted display message.
END;
insert => Reload[dmIndex + 1];
replace => BEGIN
IF insertionLength # deletionLength THEN Reload[dmIndex];
IF dm.haveMessage AND dispMess = displayMessage THEN
BEGIN
dsD.ClearRectangle[inD.leftMargin, inD.rightMargin, dm.topY, dm.bottomY];
Editor.RefreshSoThatFirstCharStartsLine
[firstChar: 0, firstLine: dm.lines, mnp: dm];
END;
END;
ENDCASE => ERROR;
ENDCASE => BEGIN -- > index --
SELECT action FROM
delete => Reload[dmIndex - 1];
insert => Reload[dmIndex + 1];
replace => IF insertionLength # deletionLength THEN Reload[dmIndex];
ENDCASE => ERROR;
END;
END;
ENDLOOP;

RETURN[TRUE];
END; -- of ReplaceMail --


FixUpTOC: PROCEDURE [toc: TOCHandle, index: TOCIndex, action: inD.TOCChangeAction,
fp: TOCFixedPartPtr, tocString: STRING] =
BEGIN
isFirstEntry: BOOLEAN;
spaceRemaining, sizeOfNewEntry, sizeOfOldEntry: CARDINAL;
hardTOCAddress: HardTOCAddress;

[isFirstEntry, spaceRemaining, hardTOCAddress] ← GetTOCPage[toc, index];
sizeOfNewEntry ← IF action = delete THEN 0
ELSE String.WordsForString[tocString.length] + SIZE[TOCFixedPart];
sizeOfOldEntry ← IF action = insert THEN 0 ELSE WordsInTOCEntry[hardTOCAddress];
DO
Process.Yield[];
SELECT sizeOfNewEntry FROM
= 0 => {RemoveTOCEntry[toc, hardTOCAddress, spaceRemaining]; EXIT};
<= sizeOfOldEntry =>
BEGIN -- cannot happen if inserting only.
IF sizeOfNewEntry < sizeOfOldEntry THEN
MoveDownInTOCPage[toc.buffer, hardTOCAddress + sizeOfOldEntry,
sizeOfOldEntry - sizeOfNewEntry, spaceRemaining];
IF action # delete THEN WriteEntryToTOC[toc, hardTOCAddress, fp, tocString];
EXIT;
END;
<= sizeOfOldEntry + spaceRemaining =>
BEGIN -- cannot occur on delete.
MoveUpInTOCPage[toc.buffer, hardTOCAddress + sizeOfOldEntry,
sizeOfNewEntry - sizeOfOldEntry, spaceRemaining];
WriteEntryToTOC[toc, hardTOCAddress, fp, tocString];
IF action = insert THEN IncrementTOCTable[toc, toc.logicalPageNumber];
EXIT;
END;
ENDCASE;
IF action # insert THEN
BEGIN
RemoveTOCEntry[toc, hardTOCAddress, spaceRemaining];
-- Cannot result in an empty page! Otherwise, one of above select arms would be used.
IF toc.bufferState = empty THEN exD.SysBug[];
spaceRemaining ← spaceRemaining - sizeOfOldEntry;
END;
action ← insert;
sizeOfOldEntry ← 0;
IF isFirstEntry THEN
BEGIN
[ , spaceRemaining, hardTOCAddress] ← GetTOCPage[toc, index - 1];
hardTOCAddress ← hardTOCAddress + WordsInTOCEntry[hardTOCAddress];
IF sizeOfNewEntry <= spaceRemaining THEN
BEGIN -- put toc entry on previous page.
WriteEntryToTOC[toc, hardTOCAddress, fp, tocString];
IncrementTOCTable[toc, toc.logicalPageNumber];
EXIT;
END
END;
[isFirstEntry, hardTOCAddress, spaceRemaining]
← SplitTOCPage[toc, index, hardTOCAddress, spaceRemaining];
ENDLOOP;
END; -- of FixUpTOC --


RemoveTOCEntry: PROCEDURE
[toc: TOCHandle, hardTOCAddress: HardTOCAddress, spaceRemaining: CARDINAL] =
-- Squeezes out the toc entry at hardTOCAddress, updates page in toc file and toc page
-- table. If removing this toc entry results in a toc page with no entries, then the toc
-- file is shifted down to squeeze out the entire page.
BEGIN
entryLength: CARDINAL;
pn: PageNumber = toc.logicalPageNumber;
tph: TOCPageHeader ← LOOPHOLE[toc.buffer];
IF toc.bufferState = empty THEN exD.SysBug[];
tph.numberOfEntries ← tph.numberOfEntries - 1;
toc.indexFF ← toc.indexFF - 1;
IF tph.numberOfEntries = 0 THEN
BEGIN
MakeBufferEmpty[toc];
toc.filePageFF ← toc.filePageFF - 1;
FOR p: PageNumber IN [pn .. toc.filePageFF) DO
Process.Yield[];
toc.buffer ← VMDefs.ReadPage[[toc.file, p + 1], 2];
VMDefs.RemapPage[toc.buffer, [toc.file, p]];
VMDefs.MarkStart[toc.buffer];
VMDefs.Release[toc.buffer];
toc.pageTable[p] ← [toc.pageTable[p + 1] - 1];
ENDLOOP;
VMDefs.SetFileLength[toc.file, [toc.filePageFF, 0]];
RETURN;
END;
entryLength ← WordsInTOCEntry[hardTOCAddress];
MoveDownInTOCPage
[toc.buffer, hardTOCAddress + entryLength, entryLength, spaceRemaining];
toc.bufferState ← dirty;
FOR p: PageNumber IN (pn .. toc.filePageFF) DO
toc.pageTable[p] ← [toc.pageTable[p] - 1];
ENDLOOP;
END; -- of RemoveTOCEntry --


WriteEntryToTOC: PROCEDURE
[toc: TOCHandle, hardTOCAddress: HardTOCAddress, fp: TOCFixedPartPtr, s: STRING] =
BEGIN
Inline.COPY[from: fp, nwords: SIZE[TOCFixedPart], to: hardTOCAddress];
Inline.COPY[from: s, nwords: String.WordsForString[s.length],
to: hardTOCAddress + SIZE[TOCFixedPart]];
toc.bufferState ← dirty;
END; -- of WriteEntryToTOC --


ResetTOCAddresses: PROCEDURE [toc: TOCHandle, key: CARDINAL, index: TOCIndex,
insertionLength, deletionLength: CARDINAL, action: inD.TOCChangeAction] =
BEGIN
fp: TOCFixedPart;
startIndex: TOCIndex;
changedIndex: TOCIndex ← 0;
pos: LONG CARDINAL;
IF insertionLength = deletionLength THEN RETURN;
startIndex ← IF action = insert THEN index ELSE index + 1;
FOR i: TOCIndex IN [startIndex .. toc.indexFF) DO
Process.Yield[];
GetTOCFixedPart[toc, key, i, @fp];
pos ← fp.firstPage * LONG[512] + fp.firstByte + insertionLength - deletionLength;
fp.firstPage ← Inline.LowHalf[pos / 512];
fp.firstByte ← Inline.LowHalf[pos] MOD 512;
PutTOCFixedPart[toc, key, i, @fp];
IF fp.changed AND changedIndex = 0 THEN changedIndex ← i;
ENDLOOP;
IF toc.firstChange >= index THEN
toc.firstChange ← SELECT TRUE FROM
changedIndex = 0 => 0, -- only changed item was just deleted.
action = replace => changedIndex, -- indices don’t change.
action = delete => changedIndex - 1, -- indices will be decremented.
ENDCASE => changedIndex + 1; -- insert, indices will be incremented.
END; -- of ResetTOCAddresses --


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


ReadTOCPage: PROCEDURE [toc: TOCHandle, pn: PageNumber] =
BEGIN
IF toc.bufferState # empty AND toc.logicalPageNumber = pn THEN RETURN;
MakeBufferEmpty[toc];
toc.buffer ← VMDefs.ReadPage[[toc.file, pn]];
toc.logicalPageNumber ← pn;
toc.bufferState ← clean;
END; -- of ReadTOCPage --


GetTOCPage: PROCEDURE [toc: TOCHandle, index: TOCIndex]
RETURNS [isFirstEntry: BOOLEAN, spaceRemaining: CARDINAL,
hardTOCAddress: HardTOCAddress] =
-- Returns buffer with the page from the table of contents file that contains index. Special
-- case for index = indexFF: always returns last good page of the TOC file.
BEGIN
hta: HardTOCAddress;
nEntries, entryOnPage: CARDINAL;
tph: TOCPageHeader;
pn: PageNumber ← SearchTOCTable[toc, index - (IF index # toc.indexFF THEN 0 ELSE 1)];
ReadTOCPage[toc, pn];
tph ← LOOPHOLE[toc.buffer];
hardTOCAddress ← hta ← tph + SIZE[TOCPageHeaderBlk];
nEntries ← tph.numberOfEntries;
entryOnPage ← index - toc.pageTable[pn];
FOR i: CARDINAL IN [0 .. nEntries) DO
hta ← hta + WordsInTOCEntry[hta];
IF i + 1 = entryOnPage THEN hardTOCAddress ← hta;
ENDLOOP;
isFirstEntry ← toc.pageTable[pn] = index;
spaceRemaining ← 256 - (hta - tph);
END; -- of GetTOCPage --


SplitTOCPage: PROCEDURE [toc: TOCHandle, index: TOCIndex,
hardTOCAddress: HardTOCAddress, spaceRemaining: CARDINAL]
RETURNS [isFirstEntry: BOOLEAN, newHardTOCAddress: HardTOCAddress,
newSpaceRemaining: CARDINAL] =
-- toc.buffer contains a TOC page. SplitTOCPage causes all entries at index and beyond to
-- be moved to a new page. If index is the index of the first entry not on this page,
-- then a blank TOC page (containing only the TOCPageHeader) is created. toc.buffer
-- will contain the new page upon return. This procedure will cause the TOC file to
-- increase in length by one page. All TOC data structures will be updated to reflect the
-- entries in each page after the split is performed.
BEGIN
nEntries, entryOnPage, delta, hardTOCAddressOffset: CARDINAL;
pn, startPn, basePn: PageNumber;
chunk: CARDINAL = 6;
page: VMDefs.Page;
tph: TOCPageHeader ← LOOPHOLE[toc.buffer];
IF toc.bufferState = empty THEN exD.SysBug[];
nEntries ← tph.numberOfEntries;
startPn ← toc.logicalPageNumber;
hardTOCAddressOffset ← hardTOCAddress - toc.buffer;
entryOnPage ← index - toc.pageTable[startPn];
VMDefs.SetFileLength[toc.file, [toc.filePageFF + 1, 0]];
pn ← toc.filePageFF;
toc.filePageFF ← toc.filePageFF + 1;
MakeBufferEmpty[toc];
VMDefs.StartFile[toc.file]; -- to not lose previous write to startPn when it is Remap’ed.
VMDefs.WaitFile[toc.file];
UNTIL pn = startPn DO -- must be FALSE first time.
basePn ← IF pn < chunk THEN startPn ELSE MAX[startPn, pn - chunk];
VMDefs.StartReading[[toc.file, basePn], pn - basePn - 1];
UNTIL pn = basePn DO
Process.Yield[];
pn ← pn - 1;
page ← VMDefs.ReadPage[[toc.file, pn]];
VMDefs.RemapPage[page, [toc.file, pn + 1]];
VMDefs.MarkStart[page];
VMDefs.Deactivate[page];
toc.pageTable[pn + 1] ← toc.pageTable[pn];
ENDLOOP;
ENDLOOP;
IF entryOnPage # nEntries THEN
BEGIN -- must move some entries from page to new page.
page ← VMDefs.ReadPage[[toc.file, startPn], 1];
tph ← LOOPHOLE[page];
tph.numberOfEntries ← entryOnPage;
VMDefs.MarkStart[page];
VMDefs.Release[page];
END;
ReadTOCPage[toc, startPn + 1];
tph ← LOOPHOLE[toc.buffer];
tph.garbageDetector ← TOCType;
newHardTOCAddress ← tph + SIZE[TOCPageHeaderBlk];
isFirstEntry ← TRUE;
tph.numberOfEntries ← nEntries ← nEntries - entryOnPage;
delta ← hardTOCAddressOffset - SIZE[TOCPageHeaderBlk];
IF nEntries > 0 THEN MoveDownInTOCPage
[toc.buffer, toc.buffer + hardTOCAddressOffset, delta, spaceRemaining];
toc.bufferState ← dirty;
toc.pageTable[startPn + 1] ← [toc.pageTable[startPn] + entryOnPage];
newSpaceRemaining ← spaceRemaining + delta;
END; -- of SplitTOCPage --


MoveDownInTOCPage: PROC [base, start: POINTER, delta, spaceRemaining: CARDINAL] =
BEGIN
Inline.COPY[from: start, nwords: 256 - spaceRemaining - (start - base), to: start - delta];
END; -- of MoveDownInTOCPage --


MoveUpInTOCPage: PROC [base, start: POINTER, delta, spaceRemaining: CARDINAL] =
BEGIN
from: POINTER TO ARRAY OF WORD ← start;
to: POINTER TO ARRAY OF WORD ← start + delta;
nWords: CARDINAL ← 256 - spaceRemaining - (start - base);
FOR i: CARDINAL DECREASING IN [0 .. nWords) DO
to[i] ← from[i];
ENDLOOP;
END; -- of MoveUpInTOCPage --


IncrementTOCTable: PROCEDURE [toc: TOCHandle, pn: PageNumber] =
-- Rewrites page to the toc file to indicate that it contains an additional entry. Increments
-- the first index for each toc page after page in the toc page table.
BEGIN
tph: TOCPageHeader;
ReadTOCPage[toc, pn];
tph ← LOOPHOLE[toc.buffer];
tph.numberOfEntries ← tph.numberOfEntries + 1;
toc.bufferState ← dirty;
toc.indexFF ← toc.indexFF + 1;
FOR p: PageNumber IN (pn .. toc.filePageFF) DO
toc.pageTable[p] ← [toc.pageTable[p] + 1];
ENDLOOP;
END; -- of IncrementTOCTable --


END. -- of ReplaceMailOp --