-- Copyright (C) 1981, 1982, 1984, 1985 by Xerox Corporation. All rights reserved. -- Spiller.mesa, Transport Mechanism Mail Server: Mail spilling -- HGM, 10-Dec-85 22:26:14 -- Andrew Birrell 24-Dec-81 13:04:15 -- Randy Gobbel 19-May-81 18:56:08 -- Mark Johnson 14-Jan-82 10:31:50 -- Ted Wobber 1-Nov-82 13:33:29 -- Hankins 25-Jul-84 14:11:32 Klamath update (LONG[str] in CopyText ok?) 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], SendDefs USING [Handle], ServerDefs USING [ DownServer, GetLeafServer, 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: LONG STRING, from: LONG 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: LONG STRING ¬ NIL; -- Archive file titles are of the form: -- 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, 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: LONG 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: LONG 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: LONG 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 local 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 ENABLE VMDefs.CantWriteBackingStore => CONTINUE; -- Happened once ?? fs: VMDefs.FileSystem = VMDefs.GetFileSystem[file]; VMDefs.CloseFile[file]; VMDefs.Logout[fs]; END; AbandonAndLogout: PROC [file: VMDefs.FileHandle] = BEGIN ENABLE VMDefs.CantWriteBackingStore => CONTINUE; -- Happened once ?? 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 = LONG 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 ¬ NIL; 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.GetLeafServer[ [connect[trialServerName]]]; 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]" -- 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: LONG 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, LONG 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.GetLeafServer[[connect[serverName]]], 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; SendBytes: PROC [p: LONG POINTER, c: CARDINAL] = { ProtocolDefs.SendBytes[str, p, c]}; entry: DirEntry = ReadDirEntry[file, body]; CopyBytes[file, entry.bodyStart, 2 * entry.bodyLength, 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: SendDefs.Handle, sendBytes: PROC [SendDefs.Handle, LONG POINTER, CARDINAL]] = BEGIN ENABLE BEGIN VMDefs.Error, VMDefs.CantReadBackingStore => ERROR ServerCrash[]; UNWIND => AbandonAndLogout[file]; END; SendBytes: PROC [p: LONG POINTER, c: CARDINAL] = {sendBytes[str, p, c]; }; entry: DirEntry = ReadDirEntry[file, body]; sendTextLength[entry.textLength]; CopyBytes[file, entry.textStart, entry.textLength, 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 [ptr: LONG 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, 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, sendBytes: PROC [LONG 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[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.