-- file CoreLeaf.Mesa (See also Core*.mesa.)
-- Last edited by Butterfield, July 27, 1979 3:11 PM.
-- Converted to Multiprocess Leaf by Brotz, February 11, 1981 11:12 PM
-- Edited by Brotz, March 18, 1981 9:45 AM

DIRECTORY
crD: FROM "CoreDefs",
exD: FROM "ExceptionDefs",
gsD: FROM "GlobalStorageDefs",
Inline,
intCommon,
Leaf,
NameInfoDefs,
NameInfoSpecialDefs,
ovD: FROM "OverviewDefs",
PupDefs,
PupTypes,
RetrieveDefs,
Sequin,
Storage,
String,
TimeDefs;

CoreLeaf: PROGRAM
IMPORTS exD, Inline, intC: intCommon, NameInfoDefs, NameInfoSpecialDefs,
PupDefs, RetrieveDefs, Sequin, Storage, String, TimeDefs
EXPORTS crD
SHARES crD =

BEGIN
OPEN crD;

leafOK: CARDINAL ← 0;

LeafOpenFile: PUBLIC PROCEDURE [user: DMSUser, filename: UFilename, mode: OpenMode]
RETURNS [erc: ovD.ErrorCode, uFH: UFileHandle] =
-- Opens the specified file in the specified mode, and returns a handle to the opened file.
-- Handles are usable until a CloseFile or DeleteFile. When OpenFile fails, i.e. ErrorCode
-- # ok, uFH = NIL, is returned.
-- The maximum size of a filename is 99 characters. --
-- Error Codes: diskError, diskFull, diskCorrupted, illegalFilename, fileInUse, fileTooBig. --
BEGIN
update: BOOLEAN = (mode = update);
eofPage: PageNumber;
eofByte: PageByte;
fileSSD, serverSSD: String.SubStringDescriptor;
server: STRING ← [50];
file: STRING ← [99];
i: CARDINAL;
sl: SequinListPtr;
openRequest: Leaf.OpenRequest;
resetRequest: Leaf.ResetRequest;
paramsRequest: Leaf.ParamsRequest;
answer: Leaf.Answer;
buffer: Sequin.Buffer;
madeTemp: BOOLEAN ← FALSE;

erc ← ovD.ok;
uFH ← NIL;

BEGIN -- for EXITS --
IF ~intC.leafOk THEN GO TO Illegal;
IF leafOK#12795 THEN
BEGIN
userName: STRING;
mbr: NameInfoDefs.Membership;
SELECT RetrieveDefs.MailboxState[intC.retrieveHandle] FROM
badName, badPwd => GO TO Illegal;
ENDCASE;
userName ← Storage.String[intC.user.name.length+intC.user.registry.length+1];
String.AppendString[userName, intC.user.name];
String.AppendChar[userName, ’.];
String.AppendString[userName, intC.user.registry];
mbr ← NameInfoDefs.IsMemberClosure["CoreLeafUsers.ms"L, userName];
NameInfoSpecialDefs.CleanUp[];
Storage.FreeString[userName];
SELECT mbr FROM
yes, allDown => leafOK ← 12795;
ENDCASE => GO TO Illegal;
END;
IF filename.length < 2 THEN GO TO Illegal;
IF filename[1] = ’] THEN
BEGIN
temp: STRING;
IF intC.remoteFilePath = NIL THEN GO TO Illegal;
temp ← Storage.String[filename.length + intC.remoteFilePath.length];
String.AppendString[temp, intC.remoteFilePath];
fileSSD ← String.SubStringDescriptor[filename, 2, filename.length - 2];
String.AppendSubString[temp, @fileSSD];
filename ← temp;
madeTemp ← TRUE;
END;

-- parse filename into server plus file and obtain the sequin for this server.
FOR i IN [1 .. filename.length) DO
IF filename[i] = ’] THEN EXIT;
REPEAT
FINISHED => GO TO Illegal;
ENDLOOP;
serverSSD ← String.SubStringDescriptor[filename, 1, i - 1];
String.AppendSubString[server, @serverSSD];
fileSSD ← String.SubStringDescriptor[filename, i + 1, filename.length - i - 1];
IF i = 1 OR fileSSD.length > 99 THEN GO TO Illegal;
String.AppendSubString[file, @fileSSD];

IF (sl ← GetSequinAddress[server]) = NIL THEN GO TO Broken;
IF sl.nFiles = 0 THEN
BEGIN -- set up a new sequin and start with reset and params.
sl.handle ← Sequin.Create[sl.address, Leaf.ptLeaf];
buffer ← Sequin.GetEmptyBuffer[];
resetRequest ← LOOPHOLE[buffer.data];
resetRequest↑ ← Leaf.RequestObject[op: Leaf.resetOp, opSpecific: reset[]];
buffer.nBytes ← Leaf.resetOp.length;
AddStringToBuffer[@buffer, user.name];
AddStringToBuffer[@buffer, user.password];
resetRequest.op.length ← buffer.nBytes;
Sequin.Put[sl.handle, buffer ! Sequin.Broken => GO TO Broken];
buffer ← Sequin.Get[sl.handle ! Sequin.Broken => GO TO Broken];
answer ← LOOPHOLE[buffer.data];
WITH ans: answer SELECT answer.op.type FROM
reset => NULL;
error => {erc ← LeafToUError[ans.error]; GO TO NotMyNight};
ENDCASE => {erc ← ovD.cantConnect; GO TO NotMyNight};
Sequin.ReleaseBuffer[buffer];
buffer ← Sequin.GetEmptyBuffer[];
paramsRequest ← LOOPHOLE[buffer.data];
paramsRequest↑ ← Leaf.RequestObject
[op: Leaf.paramsOp,
opSpecific: params
[packetDataBytes:
2 * MIN[PupDefs.DataWordsPerPupBuffer[], PupTypes.maxDataWordsPerGatewayPup],
fileLockTimeout: 60, -- 5 minutes --
connectionTimeout: 60]]; -- 5 minutes --
buffer.nBytes ← Leaf.paramsOp.length;
Sequin.Put[sl.handle, buffer ! Sequin.Broken => GO TO Broken];
buffer ← Sequin.Get[sl.handle ! Sequin.Broken => GO TO Broken];
answer ← LOOPHOLE[buffer.data];
WITH ans: answer SELECT answer.op.type FROM
params => NULL;
error => {erc ← LeafToUError[ans.error]; GO TO NotMyNight};
ENDCASE => {erc ← ovD.cantConnect; GO TO NotMyNight};
Sequin.ReleaseBuffer[buffer];
END;

-- we have a valid sequin set up; now try to open the file.
buffer ← Sequin.GetEmptyBuffer[];
openRequest ← LOOPHOLE[buffer.data];
openRequest↑ ← Leaf.RequestObject
[op: Leaf.openOp,
opSpecific: open[write: update, extend: update, create: update, vExplicit: no]];
buffer.nBytes ← Leaf.openOp.length;
AddStringToBuffer[@buffer, user.name];
AddStringToBuffer[@buffer, user.password];
AddStringToBuffer[@buffer, ""L]; -- connect name --
AddStringToBuffer[@buffer, ""L]; -- connect password --
AddStringToBuffer[@buffer, file];
openRequest.op.length ← buffer.nBytes;
Sequin.Put[sl.handle, buffer ! Sequin.Broken => GO TO Broken];
buffer ← Sequin.Get[sl.handle ! Sequin.Broken => GO TO Broken];
answer ← LOOPHOLE[buffer.data];
WITH ans: answer SELECT answer.op.type FROM
open =>
BEGIN
sl.nFiles ← sl.nFiles + 1;
[eofPage, eofByte] ← MapLeafToUAddress[ans.eofAddress];
uFH ← Storage.Node[SIZE[leaf UFileObject]];
uFH↑ ← UFileObject
[access: mode,
lastFilePage: eofPage,
byteFF: eofByte,
close: LeafCloseFile,
delete: LeafDeleteFile,
length: LeafFileLength,
getTimes: LeafGetTimes,
setTimes: LeafSetTimes,
truncate: LeafTruncateFile,
read: LeafReadPages,
write: LeafWritePages,
varpart: leaf[sequinList: sl, leafHandle: LOOPHOLE[ans.handle]]];
END;
error => {erc ← LeafToUError[ans.error]; GO TO NotMyNight};
ENDCASE => {erc ← ovD.cantConnect; GO TO NotMyNight};
Sequin.ReleaseBuffer[buffer];
EXITS
Illegal => erc ← ovD.illegalFilename;
Broken => erc ← ovD.cantConnect;
NotMyNight =>
BEGIN
Sequin.ReleaseBuffer[buffer];
IF sl.nFiles = 0 THEN
{Sequin.Destroy[sl.handle]; RemoveSequinList[sl]};
END;
END; -- for EXITS --
IF madeTemp THEN Storage.FreeString[filename];
END; -- of LeafOpenFile --


-- Note: all of the following operations require a UFileHandle for a file which is in open state.
-- They have no way of checking the validity of the UFileHandle.
-- Terrible things will happen if they are given an invalid UFileHandle.


LeafCloseFile: PUBLIC PROCEDURE [uFH: UFileHandle] RETURNS [erc: ovD.ErrorCode] =
-- Closes the file associated with uFH. The handle is closed and should not be re-used
-- by the caller. If handle = NIL this is a no-op.
-- Error Codes: cantConnect, diskError, diskCorrupted.
BEGIN
sl: SequinListPtr;
leafHandle: Leaf.Handle;
buffer: Sequin.Buffer;
closeRequest: Leaf.CloseRequest;
answer: Leaf.Answer;

WITH fh: uFH SELECT FROM
leaf => {leafHandle ← LOOPHOLE[fh.leafHandle]; sl ← LOOPHOLE[fh.sequinList]};
ENDCASE => exD.SysBug[];

buffer ← Sequin.GetEmptyBuffer[];
closeRequest ← LOOPHOLE[buffer.data];
closeRequest↑ ← Leaf.RequestObject[op: Leaf.closeOp, opSpecific: close[handle: leafHandle]];
buffer.nBytes ← Leaf.closeOp.length;
BEGIN -- for EXITS --
Sequin.Put[sl.handle, buffer ! Sequin.Broken => GO TO Broken];
buffer ← Sequin.Get[sl.handle ! Sequin.Broken => GO TO Broken];
answer ← LOOPHOLE[buffer.data];
WITH ans: answer SELECT answer.op.type FROM
close => erc ← ovD.ok;
error => erc ← LeafToUError[ans.error];
ENDCASE => erc ← ovD.cantConnect;
Sequin.ReleaseBuffer[buffer];
EXITS
Broken => erc ← ovD.cantConnect;
END; -- for EXITS --
sl.nFiles ← sl.nFiles - 1;
IF sl.nFiles = 0 THEN Sequin.Destroy[sl.handle];
Storage.Free[uFH];
END; -- of LeafCloseFile --


LeafDeleteFile: PUBLIC PROCEDURE [uFH: UFileHandle] RETURNS [erc: ovD.ErrorCode] =
-- Deletes the file associated with uFH. The handle is closed and should not be re-used
-- by the caller. If handle = NIL this is a no-op.
-- Error Codes: cantConnect, diskError, diskCorrupted.
BEGIN
sl: SequinListPtr;
leafHandle: Leaf.Handle;
buffer: Sequin.Buffer;
deleteRequest: Leaf.DeleteRequest;
answer: Leaf.Answer;

WITH fh: uFH SELECT FROM
leaf => {leafHandle ← LOOPHOLE[fh.leafHandle]; sl ← LOOPHOLE[fh.sequinList]};
ENDCASE => exD.SysBug[];

buffer ← Sequin.GetEmptyBuffer[];
deleteRequest ← LOOPHOLE[buffer.data];
deleteRequest↑ ← Leaf.RequestObject[op: Leaf.deleteOp, opSpecific: delete[handle: leafHandle]];
buffer.nBytes ← Leaf.deleteOp.length;
BEGIN -- for EXITS --
Sequin.Put[sl.handle, buffer ! Sequin.Broken => GO TO Broken];
buffer ← Sequin.Get[sl.handle ! Sequin.Broken => GO TO Broken];
answer ← LOOPHOLE[buffer.data];
WITH ans: answer SELECT answer.op.type FROM
delete => erc ← ovD.ok;
error => erc ← LeafToUError[ans.error];
ENDCASE => erc ← ovD.cantConnect;
Sequin.ReleaseBuffer[buffer];
EXITS
Broken => erc ← ovD.cantConnect;
END; -- for EXITS --
sl.nFiles ← sl.nFiles - 1;
IF sl.nFiles = 0 THEN Sequin.Destroy[sl.handle];
Storage.Free[uFH];
END; -- of LeafDeleteFile --


LeafFileLength: PUBLIC PROCEDURE [uFH: UFileHandle]
RETURNS [erc: ovD.ErrorCode, lastPage: PageNumber, byteFF: PageByte] =
-- Returns the first free byte in the file, i.e. its length, via (lastPage * 512 + byteFF).
-- Any data that has been written since the file was opened is included. Empty files
-- return [0,0].
-- Note: The name of this routine may be misleading. The length returned is a DMS file
-- length; the page number component is a crD.PageNumber, not an AltoPageNumber.
-- Error Codes: none.
{RETURN[ovD.ok, uFH.lastFilePage, uFH.byteFF]};


LeafTruncateFile: PUBLIC PROCEDURE
[lastPage: PageNumber, byteFF: PageByte, uFH: UFileHandle]
RETURNS [erc: ovD.ErrorCode] =
-- Shortens a file which has been opened by LeafOpenFile. The position has the same
-- semantics as UFileLength. Do NOT use to LENGTHEN a file! File must have been
-- opened in "update" mode.
-- Error Codes: cantConnect, diskError, diskCorrupted.
BEGIN
sl: SequinListPtr;
leafHandle: Leaf.Handle;
buffer: Sequin.Buffer;
truncateRequest: Leaf.TruncateRequest;
answer: Leaf.Answer;

WITH fh: uFH SELECT FROM
leaf => {leafHandle ← LOOPHOLE[fh.leafHandle]; sl ← LOOPHOLE[fh.sequinList]};
ENDCASE => exD.SysBug[];

buffer ← Sequin.GetEmptyBuffer[];
truncateRequest ← LOOPHOLE[buffer.data];
truncateRequest↑ ← Leaf.RequestObject
[op: Leaf.truncateOp,
opSpecific: truncate
[handle: leafHandle,
eofAddress: MapUToLeafAddress[lastPage, byteFF]]];
buffer.nBytes ← Leaf.truncateOp.length;
BEGIN -- for EXITS --
Sequin.Put[sl.handle, buffer ! Sequin.Broken => GO TO Broken];
buffer ← Sequin.Get[sl.handle ! Sequin.Broken => GO TO Broken];
answer ← LOOPHOLE[buffer.data];
WITH ans: answer SELECT answer.op.type FROM
truncate => erc ← ovD.ok;
error => erc ← LeafToUError[ans.error];
ENDCASE => erc ← ovD.cantConnect;
Sequin.ReleaseBuffer[buffer];
uFH.lastFilePage ← lastPage + byteFF / 512;
uFH.byteFF ← byteFF MOD 512;
EXITS
Broken => erc ← ovD.cantConnect;
END; -- for EXITS --
END; -- of LeafTruncateFile --


LeafReadPages: PROCEDURE [coreBuffer: gsD.MemoryPagePtr, byteCount: CARDINAL,
pageNumber: PageNumber, uFH: UFileHandle]
RETURNS[erc: ovD.ErrorCode, bytesRead: CARDINAL] =
-- Reads up to "byteCount" bytes of data from the file associated with uFH beginning with
-- page "pageNumber" into the memory page(s) beginning at "coreBuffer". The byteCount
-- must be a multiple of 512 (an even page). The bytesRead returned is the count of bytes
-- actually read and will be equal to the requested byteCount except at EOF, when it may
-- be less. Note that if pageNumber is beyound the end-of-file then bytesRead = 0 is
-- returned.
-- Error Codes: diskError.
BEGIN
sl: SequinListPtr;
leafHandle: Leaf.Handle;
buffer: Sequin.Buffer;
readRequest: Leaf.ReadRequest;
answer: Leaf.Answer;
answerBytes: CARDINAL;
copyFrom, copyTo: POINTER;

WITH fh: uFH SELECT FROM
leaf => {leafHandle ← LOOPHOLE[fh.leafHandle]; sl ← LOOPHOLE[fh.sequinList]};
ENDCASE => exD.SysBug[];

IF pageNumber > uFH.lastFilePage OR byteCount < 512 THEN RETURN[ovD.ok, 0];
IF LONG[pageNumber] * 512 + byteCount
> LONG[uFH.lastFilePage] * 512 + uFH.byteFF THEN
IF (byteCount ← (uFH.lastFilePage - pageNumber) * 512 + uFH.byteFF) = 0 THEN
RETURN[ovD.ok, 0];

buffer ← Sequin.GetEmptyBuffer[];
readRequest ← LOOPHOLE[buffer.data];
readRequest↑ ← Leaf.RequestObject
[op: Leaf.readOp,
opSpecific: read
[handle: leafHandle,
address: MapUToLeafAddress[pageNumber, 0],
length: byteCount]];
buffer.nBytes ← Leaf.readOp.length;

BEGIN -- for EXITS --
Sequin.Put[sl.handle, buffer ! Sequin.Broken => GO TO Broken];
bytesRead ← 0;
copyTo ← coreBuffer;
UNTIL byteCount = 0 DO
buffer ← Sequin.Get[sl.handle ! Sequin.Broken => GO TO Broken];
answer ← LOOPHOLE[buffer.data];
answerBytes ← buffer.nBytes - Leaf.readAns.length;
WITH ans: answer SELECT answer.op.type FROM
read => erc ← ovD.ok;
error => {erc ← LeafToUError[ans.error]; GO TO CantConnect};
ENDCASE => {erc ← ovD.cantConnect; GO TO CantConnect};
copyFrom ← buffer.data + Leaf.readAns.length / 2;
Inline.COPY[from: copyFrom, to: copyTo, nwords: (answerBytes + 1) / 2];
byteCount ← byteCount - answerBytes;
bytesRead ← bytesRead + answerBytes;
copyTo ← copyTo + answerBytes / 2;
Sequin.ReleaseBuffer[buffer];
IF answerBytes MOD 2 = 1 AND byteCount # 0 THEN exD.SysBug[];
ENDLOOP;
EXITS
Broken => erc ← ovD.cantConnect;
CantConnect => Sequin.ReleaseBuffer[buffer];
END; -- for EXITS --
END; -- of LeafReadPages --


LeafWritePages: PUBLIC PROCEDURE [coreBuffer: gsD.MemoryPagePtr,
byteCount: CARDINAL, pageNumber: PageNumber, uFH: UFileHandle]
RETURNS [erc: ovD.ErrorCode] =
-- Writes byteCount of data from memory, beginning at coreBuffer, to pageNumber of the
-- file uFH. The operation is legal only if the file was opened in update OpenMode. This
-- procedure can be used to extend the file’s size by writing beyond the current end.
-- Note, however, that the file is never shortened. byteCount must always be a
-- multiple of 512, an even page, except on the last page of the file. byteCount must not
-- be 0.
-- Regarding byteCount, let "remainder" be (byteCount MOD 512). If non-zero, remainder
-- denotes the number of bytes supplied by the client to be written on the last page of the
-- current transaction. The number of byte actually written will be the
-- MAX[current-bytes-on-that-file-page, remainder]; the value of all bytes beyond
-- remainder are indeterminate.
-- Side effects: Updates the file size in the uFH as needed.
-- Error Codes: diskFull, diskError, fileTooBig.
BEGIN
sl: SequinListPtr;
leafHandle: Leaf.Handle;
buffer: Sequin.Buffer;
writeRequest: Leaf.WriteRequest;
answer: Leaf.Answer;
lastAddress, lastWrittenAddress: Leaf.FileAddress;
writeBytes, maxWriteBytes, bytesWritten: CARDINAL;
copyFrom: POINTER ← coreBuffer;
packetsSent, packetsAcked: CARDINAL;
packetsSentLimit: CARDINAL
= (511 / (PupDefs.DataWordsPerPupBuffer[] * 2 - Leaf.writeOp.length)) + 1;

WITH fh: uFH SELECT FROM
leaf => {leafHandle ← LOOPHOLE[fh.leafHandle]; sl ← LOOPHOLE[fh.sequinList]};
ENDCASE => exD.SysBug[];

erc ← ovD.ok;
BEGIN -- for EXITS --
bytesWritten ← 0;
UNTIL byteCount = 0 DO
packetsSent ← 0;
UNTIL byteCount = 0 OR packetsSent = packetsSentLimit DO
buffer ← Sequin.GetEmptyBuffer[];
maxWriteBytes ← buffer.maxBytes - Leaf.writeOp.length;
writeBytes ← MIN[byteCount, maxWriteBytes];
writeRequest ← LOOPHOLE[buffer.data];
writeRequest↑ ← Leaf.RequestObject
[op: Leaf.writeOp,
opSpecific: write
[handle: leafHandle,
address: MapUToLeafAddress[pageNumber, bytesWritten],
length: writeBytes,
writeBody: NULL]];
buffer.nBytes ← writeRequest.op.length ← writeBytes + Leaf.writeOp.length;
Inline.COPY[from: copyFrom, to: @writeRequest.writeBody, nwords: (writeBytes + 1) / 2];
Sequin.Put[sl.handle, buffer ! Sequin.Broken => GO TO Broken];
packetsSent ← packetsSent + 1;
copyFrom ← copyFrom + writeBytes / 2;
byteCount ← byteCount - writeBytes;
bytesWritten ← bytesWritten + writeBytes;
IF byteCount > 0 AND writeBytes MOD 2 = 1 THEN exD.SysBug[];
ENDLOOP;
FOR packetsAcked ← 0, packetsAcked + 1 UNTIL packetsAcked = packetsSent DO
buffer ← Sequin.Get[sl.handle ! Sequin.Broken => GO TO Broken];
answer ← LOOPHOLE[buffer.data];
WITH ans: answer SELECT answer.op.type FROM
write => erc ← ovD.ok;
error => {erc ← LeafToUError[ans.error]; GO TO CantConnect};
ENDCASE => {erc ← ovD.cantConnect; GO TO CantConnect};
IF buffer.nBytes # Leaf.writeAns.length THEN exD.SysBug[];
Sequin.ReleaseBuffer[buffer];
ENDLOOP;
ENDLOOP;
lastAddress ← MapUToLeafAddress[uFH.lastFilePage, uFH.byteFF];
lastWrittenAddress ← MapUToLeafAddress[pageNumber, bytesWritten];
IF lastWrittenAddress.high > lastAddress.high
OR lastWrittenAddress.high = lastAddress.high AND lastWrittenAddress.low > lastAddress.low
THEN [uFH.lastFilePage, uFH.byteFF] ← MapLeafToUAddress[lastWrittenAddress];
EXITS
Broken => erc ← ovD.cantConnect;
CantConnect => Sequin.ReleaseBuffer[buffer];
END; -- for EXITS --
END; -- of LeafWritePages --


LeafGetTimes: PROCEDURE [uFH: UFileHandle]
RETURNS [read, write, create: TimeDefs.PackedTime, ec: ovD.ErrorCode] =
BEGIN
read ← write ← create ← TimeDefs.CurrentDayTime[];
ec ← ovD.ok;
END; -- of LeafGetTimes --


LeafSetTimes: PROCEDURE [uFH: UFileHandle, read, write, create: TimeDefs.PackedTime]
RETURNS [ovD.ErrorCode] =
BEGIN
RETURN[ovD.ok];
END; -- of LeafSetTimes --


LeafToUError: PROCEDURE [error: Leaf.IfsError] RETURNS [erc: ovD.ErrorCode] =
BEGIN
SELECT error FROM
ok => erc ← ovD.ok;
userName, userPassword, filesOnly, connectName, connectPassword, createStreamFailed =>
erc ← ovD.cantConnect;
createStreamFailed => erc ← ovD.diskError;
allocExceeded, fileSystemFull => erc ← ovD.diskFull;
fileBusy => erc ← ovD.fileInUse;
fileNotFound, dirNotFound, accessDenied, fileUndeletable => erc ← ovD.fileNotFound;
nameMalformed, illegalChar, illegalStar, illegalVersion, nameTooLong, illegalDIFAccess,
fileAlreadyExists, fileUndeletable => erc ← ovD.illegalFilename;
ENDCASE => exD.SysBug[];
END; -- of LeafToUError --


AddStringToBuffer: PROCEDURE [buffer: POINTER TO Sequin.Buffer, string: STRING] =
BEGIN
startWord: CARDINAL = (buffer.nBytes + 1) / 2;
nWords: CARDINAL;
IF string = NIL THEN string ← ""L;
nWords ← (string.length + 1) / 2;
buffer.data.words[startWord] ← string.length;
Inline.COPY[from: @string.text, to: @buffer.data.words[startWord + 1], nwords: nWords];
buffer.nBytes ← buffer.nBytes + 2 * nWords + 2;
END; -- of AddStringToBuffer --


MapLeafToUAddress: PROCEDURE [leafAddress: Leaf.FileAddress]
RETURNS [page: PageNumber, byte: PageByte] =
BEGIN
page ← (leafAddress.high MOD 512) * 128 + leafAddress.low / 512;
byte ← leafAddress.low MOD 512;
END; -- of MapLeafToUAddress --


MapUToLeafAddress: PROCEDURE [page: PageNumber, byte: PageByte]
RETURNS [leafAddress: Leaf.FileAddress] =
BEGIN
page ← page + byte / 512;
byte ← byte MOD 512;
leafAddress ← Leaf.FileAddress[high: page / 128, low: (page MOD 128) * 512 + byte];
END; -- of MapUToLeafAddress --


SequinList: TYPE = RECORD
[next: SequinListPtr,
server: STRING,
address: PupDefs.PupAddress,
handle: Sequin.Handle,
nFiles: CARDINAL];
SequinListPtr: TYPE = POINTER TO SequinList;

sequinListHead: SequinListPtr ← NIL;


GetSequinList: PROCEDURE [name: STRING] RETURNS [sl: SequinListPtr] =
BEGIN
FOR sl ← sequinListHead, sl.next UNTIL sl = NIL OR String.EquivalentString[name, sl.server]
DO ENDLOOP;
END; -- of GetSequinList --


RemoveSequinList: PROCEDURE [sl: SequinListPtr] =
BEGIN
prevSl: SequinListPtr ← NIL;
nextSl: SequinListPtr ← sequinListHead;
UNTIL nextSl = NIL OR nextSl = sl DO
prevSl ← nextSl;
nextSl ← nextSl.next;
ENDLOOP;
IF nextSl = NIL THEN exD.SysBug[];
IF prevSl = NIL THEN sequinListHead ← nextSl.next
ELSE prevSl.next ← nextSl.next;
Storage.FreeString[sl.server];
Storage.Free[sl];
END; -- of RemoveSequinList --


GetSequinAddress: PROCEDURE [name: STRING] RETURNS [sl: SequinListPtr] =
BEGIN
pupAddress: PupDefs.PupAddress;
sl ← GetSequinList[name];
IF sl # NIL THEN RETURN;
PupDefs.GetPupAddress
[@pupAddress, name ! PupDefs.PupNameTrouble => GO TO NameTrouble];
pupAddress.socket ← Leaf.leafSocket;
sl ← Storage.Node[SIZE[SequinList]];
sl↑ ← SequinList
[next: sequinListHead,
server: Storage.String[name.length],
address: pupAddress,
handle: NULL,
nFiles: 0];
String.AppendString[sl.server, name];
sequinListHead ← sl;
EXITS
NameTrouble => NULL;
END; -- of GetSequinAddress --


END. -- of CoreLeaf --