-- Transport Mechanism Mail Server: Mail spilling
-- [Indigo]<Grapevine>MS>Spiller.mesa
-- Andrew Birrell September 14, 1982 2:08 pm
-- Randy Gobbel 19-May-81 18:56:08
DIRECTORY
BodyDefs USING[ ItemHeader, ItemLength, maxRNameLength, RName ],
HeapDefs USING[ GetReaderOffset, HeapEndRead, HeapEndWrite,
HeapReadData, HeapReadRName, HeapReadString,
HeapStartRead, HeapStartWrite, HeapWriteData,
HeapWriteRName, objectStart, ReaderHandle,
ReadItemHeader, SetReaderOffset, WriterHandle ],
Inline USING[ LowHalf ],
LocalNameDefs USING[ ReadMSName ],
NameInfoDefs USING[ Close, Enumerate, GetMembers, MemberInfo],
ObjectDirDefs USING[ FreeObject, ObjectNumber, UseObject ],
ProtocolDefs USING[ endSST, Failed, Handle, SendBytes ],
PupDefs USING[ AppendPupAddress, GetHopsToNetwork, PupAddress ],
PupStream USING[ StreamClosing ],
ServerDefs USING[ DownServer, GetServer, NoSuchServer, ServerAddr,
ServerHandle, ServerNotUp, ServerUp ],
SpillDefs USING[ ],
Stream USING[ SetSST ],
String USING[ AppendChar, AppendString ],
Time USING[ Append, Current, Unpack ],
VMDefs USING[ AbandonFile, CantOpen, CantReadBackingStore,
CantWriteBackingStore, CloseFile, DestroyFile,
Error, FileHandle, FileSystem, GetFileSystem,
Login, Logout, MarkStartWait,
OpenFile, OpenOptions,
Page, PageNumber, pageSize, ReadPage, Release,
SetFileLength, UnableToLogin, UsePage ];
Spiller: MONITOR
IMPORTS HeapDefs, Inline, LocalNameDefs, NameInfoDefs, ObjectDirDefs,
ProtocolDefs,
PupDefs, PupStream, ServerDefs, Stream, String, Time,
VMDefs
EXPORTS SpillDefs
SHARES ObjectDirDefs =
BEGIN
AppendTruncated: PROC[to: STRING, from: STRING, reserve: CARDINAL ← 0] =
BEGIN
FOR index: CARDINAL IN [0..from.length)
WHILE to.length < to.maxlength-reserve
DO to[to.length] ← from[index]; to.length ← to.length + 1; ENDLOOP;
END;
localDisk: BOOLEAN ← FALSE; -- debugging: archive to local disk --
maxFileTitleLength: CARDINAL = 99; -- IFS --
myName: BodyDefs.RName = [BodyDefs.maxRNameLength];
myPassword: STRING ← NIL;
-- Archive file titles are of the form:
-- <pathName>serverName>userName-uniqueID!1
-- The (almost) uniqueID is the text form of the current time; uniqueness
-- is sufficiently guaranteed by the "alreadyExists" error raised if we
-- attempt to create a second file with the same title.
-- For example, <DMS>Cabernet.ms>Birrell.pa-19-Jan-81-23-30-59-PDT!1
-- They are restricted to "maxFileTitleLength" characters by truncating
-- the userName and/or serverName and/or pathName if necessary.
uniqueIDLength: CARDINAL = 25 -- length of ">24-Jul-81-15-37-05-GMT!1" --;
AppendID: PROC[to: STRING] =
BEGIN
start: CARDINAL = to.length;
Time.Append[to, Time.Unpack[Time.Current[]], TRUE];
FOR i: CARDINAL IN [start..to.length)
DO SELECT to[i] FROM
IN ['a..'z], IN ['A..'Z], IN ['0..'9] => NULL;
ENDCASE => to[i] ← '-; --stick to legal file title characters--
ENDLOOP;
END;
AppendTitle: INTERNAL PROC[to: STRING, user, path: BodyDefs.RName] =
BEGIN
sep: CHARACTER = IF localDisk THEN '- ELSE '>;
IF NOT localDisk THEN AppendTruncated[to, path, uniqueIDLength+1];
AppendTruncated[to, myName, uniqueIDLength+1];
String.AppendChar[to, sep];
AppendTruncated[to, user, uniqueIDLength];
String.AppendChar[to, '-];
AppendID[to];
IF NOT localDisk THEN String.AppendString[to, "!1"L];
END;
AlreadyExists: ERROR = CODE;
OpenFile: PROC[server: ServerDefs.ServerHandle, title: STRING,
options: VMDefs.OpenOptions]
RETURNS[ file: VMDefs.FileHandle ] =
BEGIN
ENABLE
BEGIN
VMDefs.CantOpen =>
IF reason = alreadyExists THEN ERROR AlreadyExists[]
ELSE ERROR ServerCrash[];
VMDefs.Error, VMDefs.UnableToLogin,
VMDefs.CantWriteBackingStore,
ServerDefs.ServerNotUp, ServerDefs.NoSuchServer
=> ERROR ServerCrash[];
END;
addr: PupDefs.PupAddress ← ServerDefs.ServerAddr[server];
addrString: STRING = [21] -- 377#377#177777|177777 --;
addr.socket ← [0,0];
PupDefs.AppendPupAddress[addrString, addr];
IF NOT ServerDefs.ServerUp[server] THEN ERROR ServerCrash[];
BEGIN
ENABLE UNWIND => ServerDefs.DownServer[server];
fs: VMDefs.FileSystem =
VMDefs.Login[IF localDisk THEN Alto ELSE IFS,
addrString, myName, myPassword];
file ← VMDefs.OpenFile[system: fs, name: title, options: options !
UNWIND => VMDefs.Logout[fs] ];
END;
END;
CloseAndLogout: PROC[ file: VMDefs.FileHandle ] =
BEGIN
fs: VMDefs.FileSystem = VMDefs.GetFileSystem[file];
VMDefs.CloseFile[file];
VMDefs.Logout[fs];
END;
AbandonAndLogout: PROC[ file: VMDefs.FileHandle ] =
BEGIN
fs: VMDefs.FileSystem = VMDefs.GetFileSystem[file];
VMDefs.AbandonFile[file];
VMDefs.Logout[fs];
END;
TryServer: INTERNAL PROC[user: BodyDefs.RName,
server: ServerDefs.ServerHandle,
serverName: BodyDefs.RName,
pathName: BodyDefs.RName]
RETURNS[obj: ObjectDirDefs.ObjectNumber,
file: VMDefs.FileHandle ] =
BEGIN
title: STRING = [maxFileTitleLength];
BEGIN
ENABLE AlreadyExists => { title.length←0; RETRY };
AppendTitle[title, user, pathName];
file ← OpenFile[server, title, new];
END;
BEGIN
writer: HeapDefs.WriterHandle = HeapDefs.HeapStartWrite[archived];
CatchObj: PROC[given: ObjectDirDefs.ObjectNumber] =
{ ObjectDirDefs.UseObject[obj ← given] };
HeapDefs.HeapWriteRName[writer, serverName];
HeapDefs.HeapWriteRName[writer, title];
HeapDefs.HeapWriteRName[writer, user];
HeapDefs.HeapWriteRName[writer, myName];
HeapDefs.HeapEndWrite[writer, CatchObj];
END;
END;
-- An archive file consists of a directory, followed by the message bodies
-- A directory is a sequence of pages, each containing an array of entries
-- A directory entry is as follows:
DirEntry: TYPE = MACHINE DEPENDENT RECORD[
bodyStart: LONG CARDINAL, -- words from start of file; 0 for deleted --
bodyLength: LONG CARDINAL, -- words --
textStart: LONG CARDINAL, -- words from start of file, for MTP --
textLength: BodyDefs.ItemLength -- bytes, for MTP -- ];
DirHeader: TYPE = MACHINE DEPENDENT RECORD[spare: CARDINAL];
entriesPerPage: CARDINAL = (VMDefs.pageSize-SIZE[DirHeader]) /
SIZE[DirEntry];
DirPageContents: TYPE = MACHINE DEPENDENT RECORD[
header: DirHeader, entries: ARRAY [0..entriesPerPage) OF DirEntry ];
DirPage: TYPE = POINTER TO DirPageContents;
-- Writer: one at a time, state in global frame --
file: VMDefs.FileHandle;
page: VMDefs.Page; -- current buffer for writing bodies --
pageNumber: CARDINAL ← 0; -- page number of body buffer --
pageUsed: CARDINAL ← 0; -- words already used in body buffer --
bodiesSeen: CARDINAL ← 0; -- bodies previously written --
bodiesTold: CARDINAL ← 0; -- bodies specified for this file --
dirPage: DirPage; -- current buffer for directory --
dirPageNumber: CARDINAL ← 0; -- page number of directory buffer --
-- only one writing client is allowed: others wait --
writerActive: BOOLEAN ← FALSE;
writerStopped: CONDITION;
NoteInactiveWriter: INTERNAL PROC =
{ writerActive ← FALSE; NOTIFY writerStopped };
StartWrite: PUBLIC ENTRY PROC[user: BodyDefs.RName, bodies: CARDINAL]
RETURNS[ obj: ObjectDirDefs.ObjectNumber] =
BEGIN
-- raises the error ServerCrash if no backing server is available --
ENABLE UNWIND => NULL;
serverListPrefix: STRING = "Archive-"L;
serverListName: BodyDefs.RName = [BodyDefs.maxRNameLength];
String.AppendString[serverListName,serverListPrefix];
IF myName.length + serverListName.length > serverListName.maxlength
THEN --obscure!-- ERROR ServerCrash[];
String.AppendString[serverListName,myName];
WHILE writerActive DO WAIT writerStopped ENDLOOP;
writerActive ← TRUE;
DO -- repeat if server appears to be up then crashes --
ENABLE UNWIND => NoteInactiveWriter[];
bestHops: CARDINAL ← LAST[CARDINAL];
serverName: BodyDefs.RName = [BodyDefs.maxRNameLength];
pathName: BodyDefs.RName = [BodyDefs.maxRNameLength];
server: ServerDefs.ServerHandle;
found: BOOLEAN ← FALSE;
LookAtServer: PROC[name: BodyDefs.RName] RETURNS[done: BOOLEAN] =
BEGIN
ENABLE ServerDefs.ServerNotUp, ServerDefs.NoSuchServer =>
{ done ← FALSE; CONTINUE };
trialServerName: BodyDefs.RName = [BodyDefs.maxRNameLength];
trialPathName: BodyDefs.RName = [BodyDefs.maxRNameLength];
IF GetHostPath[name, trialServerName, trialPathName]
THEN BEGIN
this: ServerDefs.ServerHandle =
ServerDefs.GetServer[[rName[trialServerName]],foreign];
thisAddr: PupDefs.PupAddress = ServerDefs.ServerAddr[this];
hops: CARDINAL = PupDefs.GetHopsToNetwork[thisAddr.net];
IF hops < bestHops AND ServerDefs.ServerUp[this]
THEN BEGIN
serverName.length ← pathName.length ← 0;
String.AppendString[serverName, trialServerName];
String.AppendString[pathName, trialPathName];
server ← this; bestHops ← hops;
found ← TRUE;
END;
END;
done ← FALSE;
END;
listInfo: NameInfoDefs.MemberInfo =
NameInfoDefs.GetMembers[serverListName];
WITH listInfo SELECT FROM
group =>
BEGIN
NameInfoDefs.Enumerate[members, LookAtServer !
UNWIND => NameInfoDefs.Close[members]];
NameInfoDefs.Close[members];
IF NOT found THEN ERROR ServerCrash[];
END;
ENDCASE => ERROR ServerCrash[];
-- we have a candidate: try it! --
[obj, file] ← TryServer[user, server, serverName, pathName !
ServerCrash => LOOP];
BEGIN
pageNumber ← (bodies+entriesPerPage-1)/entriesPerPage;
pageUsed ← 0;
VMDefs.SetFileLength[file, [pageNumber,0] !
VMDefs.Error, VMDefs.CantWriteBackingStore => GOTO crashed];
page ← VMDefs.UsePage[[file,pageNumber]];
bodiesTold ← bodies;
dirPageNumber ← 0; bodiesSeen ← 0;
dirPage ← LOOPHOLE[VMDefs.UsePage[[file,dirPageNumber]], DirPage];
EXIT
EXITS crashed =>
{ ServerDefs.DownServer[server]; AbandonAndLogout[file];
ObjectDirDefs.FreeObject[obj] };
END;
ENDLOOP;
END;
GetHostPath: PROC[name: BodyDefs.RName, host, path: BodyDefs.RName]
RETURNS[BOOLEAN] =
BEGIN
-- name should be "[Ivy]<DMS>" --
host.length ← path.length ← 0;
IF name[0] # '[ THEN GOTO cant;
FOR i: CARDINAL IN [1..name.length)
DO IF name[i] = '] THEN EXIT;
String.AppendChar[host, name[i]];
REPEAT FINISHED => GOTO cant
ENDLOOP;
FOR i: CARDINAL IN [host.length+2..name.length)
DO String.AppendChar[path, name[i]] ENDLOOP;
IF path.length < 2 OR path[0] # '< OR path[path.length-1] # '>
THEN GOTO cant;
RETURN[TRUE]
EXITS cant => RETURN[FALSE];
END;
ServerCrash: PUBLIC ERROR = CODE;
MoreBodiesThanAdvertised: ERROR = CODE;
AddBody: PUBLIC ENTRY PROC[body: ObjectDirDefs.ObjectNumber] =
BEGIN
ENABLE
BEGIN
VMDefs.Error, VMDefs.CantWriteBackingStore => ERROR ServerCrash[];
UNWIND => AbortWriting[];
END;
Here: PROC RETURNS[ LONG CARDINAL ] =
{ RETURN[LONG[pageNumber]*VMDefs.pageSize + pageUsed] };
reader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[body];
entry: POINTER TO DirEntry;
wantedDirPage: VMDefs.PageNumber = bodiesSeen/entriesPerPage;
IF bodiesSeen >= bodiesTold THEN ERROR MoreBodiesThanAdvertised[];
IF wantedDirPage # dirPageNumber
THEN BEGIN
VMDefs.MarkStartWait[LOOPHOLE[dirPage]];
VMDefs.Release[LOOPHOLE[dirPage]];
dirPageNumber ← wantedDirPage;
dirPage ← LOOPHOLE[VMDefs.UsePage[[file,dirPageNumber]], DirPage];
END;
entry ← @((dirPage.entries)[bodiesSeen MOD entriesPerPage]);
entry.bodyStart ← Here[];
-- find text start and length for MTP --
entry.textStart ← 0; entry.textLength ← 0; --defaults--
DO header: BodyDefs.ItemHeader = HeapDefs.ReadItemHeader[reader];
SELECT header.type FROM
Text =>
BEGIN
entry.textStart ← entry.bodyStart +
HeapDefs.GetReaderOffset[reader];
entry.textLength ← header.length;
EXIT
END;
LastItem => EXIT --no text--;
ENDCASE => HeapDefs.SetReaderOffset[reader,
HeapDefs.GetReaderOffset[reader] + (header.length+1)/2 ];
ENDLOOP;
HeapDefs.SetReaderOffset[reader, HeapDefs.objectStart];
-- copy body --
DO ENABLE UNWIND => HeapDefs.HeapEndRead[reader];
ended: BOOLEAN;
used: CARDINAL;
[ended,used] ← HeapDefs.HeapReadData[reader,
[LOOPHOLE[page,POINTER]+pageUsed, VMDefs.pageSize-pageUsed]];
pageUsed ← pageUsed + used;
IF pageUsed >= VMDefs.pageSize
THEN BEGIN
IF pageUsed > VMDefs.pageSize THEN ERROR;
VMDefs.MarkStartWait[page];
VMDefs.Release[page];
pageNumber ← pageNumber + 1; pageUsed ← 0;
page ← VMDefs.UsePage[[file,pageNumber]];
END;
IF ended THEN EXIT;
ENDLOOP;
entry.bodyLength ← Here[] - entry.bodyStart;
HeapDefs.HeapEndRead[reader];
bodiesSeen ← bodiesSeen + 1;
END;
EndWrite: PUBLIC ENTRY PROC =
BEGIN
ENABLE
BEGIN
VMDefs.Error, VMDefs.CantWriteBackingStore => ERROR ServerCrash[];
UNWIND => AbortWriting[];
END;
VMDefs.MarkStartWait[LOOPHOLE[dirPage]];
IF pageUsed > 0 THEN VMDefs.MarkStartWait[page];
VMDefs.Release[LOOPHOLE[dirPage]];
VMDefs.Release[page];
CloseAndLogout[file];
NoteInactiveWriter[];
END;
AbortWriting: INTERNAL PROC =
{ AbandonAndLogout[file]; NoteInactiveWriter[] };
-- Reading, modifying. deleting archive files --
ArchReader: PUBLIC TYPE = RECORD[silly:VMDefs.FileHandle];
OpenToRead: PUBLIC PROC[obj: ObjectDirDefs.ObjectNumber]
RETURNS[file: ArchReader] =
{ RETURN[ [silly:OpenFromObj[obj, old]] ] };
OpenFromObj: PROC[obj: ObjectDirDefs.ObjectNumber,
options: VMDefs.OpenOptions]
RETURNS[file: VMDefs.FileHandle] =
BEGIN
title: STRING = [maxFileTitleLength];
serverName: BodyDefs.RName = [BodyDefs.maxRNameLength];
reader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[obj];
[] ← HeapDefs.HeapReadRName[reader, serverName];
[] ← HeapDefs.HeapReadString[reader, title];
HeapDefs.HeapEndRead[reader];
file ← OpenFile[ServerDefs.GetServer[[rName[serverName]],foreign],
title, options];
END;
CopyBody: PUBLIC PROC[file: ArchReader, body: CARDINAL,
str: ProtocolDefs.Handle] =
BEGIN
ENABLE
BEGIN
VMDefs.Error, VMDefs.CantReadBackingStore => ERROR ServerCrash[];
UNWIND => AbandonAndLogout[file];
END;
entry: DirEntry = ReadDirEntry[file, body];
CopyBytes[file, entry.bodyStart, 2*entry.bodyLength,
str, ProtocolDefs.SendBytes];
Stream.SetSST[str, ProtocolDefs.endSST ! PupStream.StreamClosing =>
ERROR ProtocolDefs.Failed[communicationError] ];
END;
CopyText: PUBLIC PROC[file: ArchReader, body: CARDINAL,
sendTextLength: PROC[LONG CARDINAL],
str: UNSPECIFIED,
sendBytes: PROC[UNSPECIFIED,POINTER,CARDINAL] ] =
BEGIN
ENABLE
BEGIN
VMDefs.Error, VMDefs.CantReadBackingStore => ERROR ServerCrash[];
UNWIND => AbandonAndLogout[file];
END;
entry: DirEntry = ReadDirEntry[file, body];
sendTextLength[entry.textLength];
CopyBytes[file, entry.textStart, entry.textLength, str, sendBytes];
END;
RecoverBody: PUBLIC PROC[file: ArchReader, body: CARDINAL]
RETURNS[ writer: HeapDefs.WriterHandle ] =
BEGIN
ENABLE
BEGIN
VMDefs.Error, VMDefs.CantReadBackingStore => ERROR ServerCrash[];
UNWIND => AbandonAndLogout[file];
END;
entry: DirEntry = ReadDirEntry[file, body];
WriteBlock: PROC[writer: UNSPECIFIED, ptr: POINTER, amount: CARDINAL] =
BEGIN
-- amount is in bytes, but it's even --
IF amount MOD 2 # 0 THEN ERROR;
HeapDefs.HeapWriteData[writer, [ptr,amount/2]];
END;
writer ← HeapDefs.HeapStartWrite[body];
CopyBytes[file, entry.bodyStart, 2*entry.bodyLength,
writer, WriteBlock];
END;
DeleteEntry: PUBLIC PROC[file: ArchReader, body: CARDINAL] =
BEGIN
ENABLE
BEGIN
VMDefs.Error, VMDefs.CantReadBackingStore,
VMDefs.CantWriteBackingStore => ERROR ServerCrash[];
UNWIND => AbandonAndLogout[file];
END;
dir: DirPage = LOOPHOLE[VMDefs.ReadPage[[file,body/entriesPerPage],0]];
(dir.entries)[body MOD entriesPerPage].bodyStart ← 0;
VMDefs.MarkStartWait[LOOPHOLE[dir]];
VMDefs.Release[LOOPHOLE[dir]];
END;
TestDeletion: PUBLIC PROC[file: ArchReader, body: CARDINAL]
RETURNS[deleted: BOOLEAN] =
BEGIN
ENABLE
BEGIN
VMDefs.Error, VMDefs.CantReadBackingStore => ERROR ServerCrash[];
UNWIND => AbandonAndLogout[file];
END;
RETURN[ ReadDirEntry[file, body].bodyStart = 0 ];
END;
EndReading: PUBLIC PROC[file: ArchReader] =
BEGIN
CloseAndLogout[file !
VMDefs.Error => ERROR ServerCrash[];
UNWIND => AbandonAndLogout[file] ];
END;
Delete: PUBLIC PROC[obj: ObjectDirDefs.ObjectNumber] =
BEGIN
file: VMDefs.FileHandle = OpenFromObj[obj, old];
fs: VMDefs.FileSystem = VMDefs.GetFileSystem[file];
VMDefs.DestroyFile[file !
VMDefs.Error => ERROR ServerCrash[];
UNWIND => AbandonAndLogout[file] ];
VMDefs.Logout[fs];
END;
CopyBytes: PROC[file: VMDefs.FileHandle,
start: LONG CARDINAL, bytes: LONG CARDINAL,
str: UNSPECIFIED,
sendBytes: PROC[UNSPECIFIED,POINTER,CARDINAL] ] =
BEGIN
offset: CARDINAL ← Inline.LowHalf[start MOD VMDefs.pageSize];
pageNumber: CARDINAL ← Inline.LowHalf[start / VMDefs.pageSize];
WHILE bytes > 0
DO page: VMDefs.Page = VMDefs.ReadPage[[file,pageNumber], 3];
available: CARDINAL = 2*(VMDefs.pageSize-offset); --bytes--
amount: CARDINAL = IF bytes < available
THEN Inline.LowHalf[bytes]
ELSE available;
sendBytes[str, page+offset, amount ! UNWIND => VMDefs.Release[page] ];
pageNumber ← pageNumber + 1; offset ← 0; bytes ← bytes - amount;
VMDefs.Release[page];
ENDLOOP;
END;
ReadDirEntry: PROC[file: VMDefs.FileHandle, body: CARDINAL]
RETURNS[entry: DirEntry] =
BEGIN
dir: DirPage = LOOPHOLE[VMDefs.ReadPage[[file,body/entriesPerPage],0]];
entry ← (dir.entries)[body MOD entriesPerPage];
VMDefs.Release[LOOPHOLE[dir]];
END;
Init: ENTRY PROC =
BEGIN
String.AppendString[myName, LocalNameDefs.ReadMSName[].name];
myPassword ← LocalNameDefs.ReadMSName[].password;
END;
Init[];
END.