-- Copyright (C) 1981, 1984, 1985 by Xerox Corporation. All rights reserved. -- MailboxAlloc.mesa, Transport Mechanism Mail Server - Mailbox management -- -- HGM, 15-Sep-85 12:57:00 -- Andrew Birrell September 14, 1982 2:14 pm -- Randy Gobbel 19-May-81 20:47:16 -- Mark Johnson 19-May-81 13:43:58 -- Hankins 10-Aug-84 16:22:13 Klamath update DIRECTORY Ascii USING [CR, TAB], BitMapDefs USING [Map, FindFree, Clear, Test, Set], BodyDefs USING [ ItemHeader, ItemLength, maxRNameLength, oldestTime, PackedTime, RName, RNameSize, Timestamp], BTreeDefs USING [BTreeHandle, Call, Delete, EnumerateFrom, Insert, Lookup], EnquiryDefs USING [Handle], Heap USING [systemZone], HeapDefs USING [ GetReaderOffset, HeapAbandonWrite, HeapEndRead, HeapEndWrite, HeapReadData, HeapReadRName, HeapStartRead, HeapStartWrite, HeapWriteData, HeapWriteRName, ObjectNumber, ReaderHandle, ReadItemHeader, SendObj, SetReaderOffset, WriterHandle], Inline USING [LongCOPY], LocalNameDefs USING [ReadMSName], LogDefs USING [DisplayNumber, ShowLine, ShowNumber, WriteChar, WriteLogEntry, WriteString], MailboxDefs, NameInfoDefs USING [Close, Enumerate, GetMembers, MemberInfo], ObjectDirDefs USING [noObject, FreeObject, UseObject], PolicyDefs USING [ArchiverPause, EndOperation, Wait, WaitOperation], Process USING [InitializeMonitor], ProtocolDefs USING [ AppendTimestamp, Handle, Remark, SendItemHeader, SendTimestamp], ReturnDefs USING [CopyItem], SendDefs USING [Handle], SLDefs USING [SLHeader, SLWrite], SpillDefs USING [ AddBody, ArchReader, CopyBody, CopyText, Delete, DeleteEntry, EndWrite, EndReading, OpenToRead, RecoverBody, ServerCrash, StartWrite, TestDeletion], String USING [AppendDecimal, AppendString, EquivalentStrings, LowerCase], Time USING [Append, Current, Unpack], VMDefs USING [ Page, ReadPage, UsePage, PageNumber, PageIndex, Release, FileHandle, MarkStartWait, LookAheadCount]; MailboxAlloc: MONITOR IMPORTS BitMapDefs, BodyDefs, BTreeDefs, Heap, HeapDefs, Inline, LocalNameDefs, LogDefs, NameInfoDefs, ObjectDirDefs, PolicyDefs, Process, ProtocolDefs, ReturnDefs, SLDefs, SpillDefs, String, Time, VMDefs EXPORTS EnquiryDefs, MailboxDefs SHARES MailboxDefs = BEGIN MBXName: TYPE = VMDefs.PageNumber; MBXHandle: PUBLIC TYPE = LONG POINTER TO MBXData; -- representation of state of mailbox reading -- MBXData: TYPE = MONITORED RECORD [ lock: MBXName, -- page number for mailbox interlock bit -- latest: MBXName, --address of latest page of mbx-- where: MBXName, --address of this page-- current: CARDINAL, --number of next page to be read-- messages: LONG DESCRIPTOR FOR ARRAY OF HeapDefs.ObjectNumber, --messages in this page-- nextMsg: CARDINAL, -- index into array of messages -- object: HeapDefs.ObjectNumber, -- current element of "messages" -- bodyObj: HeapDefs.ObjectNumber, --"object" without TOC indirection -- archObj: HeapDefs.ObjectNumber, -- current "archived" object -- archCount: CARDINAL, -- count relative to start of archive file -- archFile: SpillDefs.ArchReader, archOpen: BOOLEAN, remainder: CARDINAL, -- number of messages left in the mailbox -- next: MBXHandle, -- for free chain of 'MBXData's-- page: VMDefs.Page --current page buffer-- ]; -- header in disk representation of each mailbox page -- MBXHeader: TYPE = RECORD [ chain: MBXName, --previous MBX page of this MBX-- first: VMDefs.PageIndex, --first entry in this page-- free: VMDefs.PageIndex, number: CARDINAL --page number within this MBX-- ]; MBXEnd: MBXHandle = NIL; MBXChain: MBXHandle ← MBXEnd; handle: VMDefs.FileHandle; lookAhead: VMDefs.LookAheadCount = 0; pageMap: BitMapDefs.Map; mailboxes: CARDINAL ← 0; -- proc var's for MailboxRestart to use: Arch: TYPE = PROC; FindIn: TYPE = PROC [who: BodyDefs.RName] RETURNS [TreeRec]; GetBody: TYPE = PROC [given: HeapDefs.ObjectNumber] RETURNS [body: HeapDefs.ObjectNumber]; ReadP: TYPE = PROC [body: HeapDefs.ObjectNumber] RETURNS [postmark: BodyDefs.Timestamp]; ChangeIn: TYPE = PROC [ who: BodyDefs.RName, where, lock: MBXName, oldest: BodyDefs.Timestamp]; archiver: Arch ← Archiver; findInTree: FindIn ← FindInTree; getBodyObj: GetBody ← GetBodyObj; readPostmark: ReadP ← ReadPostmark; changeInTree: ChangeIn ← ChangeInTree; myName: BodyDefs.RName = [BodyDefs.maxRNameLength]; -- punning for R-Names -- RNameDesc: PROCEDURE [name: BodyDefs.RName] RETURNS [LONG DESCRIPTOR FOR ARRAY OF WORD] = BEGIN index: CARDINAL; FOR index IN [0..name.length) DO name[index] ← String.LowerCase[name[index]] ENDLOOP; IF name.length MOD 2 # 0 THEN name[name.length] ← '@; RETURN[DESCRIPTOR[@(name.text), (1 + name.length) / 2]] END; -- Management of Btree entries -- tree: BTreeDefs.BTreeHandle; TreeRec: TYPE = RECORD [ SELECT t: * FROM found => [where, lock: MBXName, oldest: BodyDefs.Timestamp], empty => [last: BodyDefs.Timestamp], ENDCASE]; FindInTree: INTERNAL PROCEDURE [who: BodyDefs.RName] RETURNS [TreeRec] = BEGIN value: TreeRec; length: CARDINAL = BTreeDefs.Lookup[ tree, RNameDesc[who], DESCRIPTOR[@value, SIZE[TreeRec]]]; IF length = 177777B THEN -- not in tree -- RETURN[[empty[BodyDefs.oldestTime]]] ELSE RETURN[value] END; ChangeInTree: INTERNAL PROCEDURE [ who: BodyDefs.RName, where, lock: MBXName, oldest: BodyDefs.Timestamp] = BEGIN value: TreeRec ← [found[where: where, lock: lock, oldest: oldest]]; BTreeDefs.Insert[tree, RNameDesc[who], DESCRIPTOR[@value, SIZE[TreeRec]]]; END; EmptyInTree: ENTRY PROCEDURE [who: BodyDefs.RName, last: BodyDefs.Timestamp] = BEGIN value: TreeRec ← [empty[last]]; BTreeDefs.Insert[tree, RNameDesc[who], DESCRIPTOR[@value, SIZE[TreeRec]]]; mailboxes ← mailboxes - 1; END; PurgeFromTree: ENTRY PROCEDURE [who: BodyDefs.RName] = BEGIN value: TreeRec = FindInTree[who]; WITH value SELECT FROM empty => BTreeDefs.Delete[tree, RNameDesc[who]]; ENDCASE => NULL -- someone else may have reinstated it -- ; END; EnumerateTree: ENTRY PROC [work: PROC [BodyDefs.RName, found TreeRec]] = BEGIN -- beware: calls "work" with the monitor locked -- Action: INTERNAL BTreeDefs.Call = BEGIN value: LONG POINTER TO TreeRec = LOOPHOLE[BASE[v]]; name: BodyDefs.RName = [BodyDefs.maxRNameLength]; more ← TRUE; dirty ← FALSE; -- results of "Action" -- IF LENGTH[k] = 0 THEN RETURN; name.length ← 2 * LENGTH[k]; Inline.LongCOPY[from: BASE[k], to: @(name.text), nwords: LENGTH[k]]; IF name.length > 0 AND name[name.length - 1] = '@ THEN name.length ← name.length - 1 -- undo padding kludge -- ; WITH res: value SELECT FROM found => work[name, res]; ENDCASE => NULL; END; BTreeDefs.EnumerateFrom[tree, DESCRIPTOR[NIL, 0], Action ! UNWIND => NULL]; END; -- Interlocks on mailboxes -- -- the lock map is used for single writer/multiple reader synchronization -- Any potential writer tests the lock; readers claim it. Both always -- have the monitor lock while doing this. lockMap: BitMapDefs.Map; conflictMap: BitMapDefs.Map; MBXFree: CONDITION; AcquireMBX: INTERNAL PROCEDURE [who: BodyDefs.RName] RETURNS [found: BOOLEAN, mbx, lock: MBXName] = BEGIN DO value: TreeRec = FindInTree[who]; WITH res: value SELECT FROM empty => {found ← FALSE; EXIT}; found => IF LockedMBX[res.lock] THEN WAIT MBXFree ELSE BEGIN found ← TRUE; mbx ← res.where; lock ← res.lock; BitMapDefs.Set[lockMap, lock]; EXIT END; ENDCASE => ERROR; ENDLOOP; END; LockedMBX: INTERNAL PROCEDURE [lock: MBXName] RETURNS [locked: BOOLEAN] = BEGIN locked ← BitMapDefs.Test[lockMap, lock]; IF locked THEN BitMapDefs.Set[conflictMap, lock]; END; FreeMBX: INTERNAL PROCEDURE [lock: MBXName] = BEGIN BitMapDefs.Clear[lockMap, lock]; IF BitMapDefs.Test[conflictMap, lock] THEN { BitMapDefs.Clear[conflictMap, lock]; NoteUnlocked[]}; BROADCAST MBXFree; END; unlockedMBX: BOOLEAN ← TRUE; unlockedMBXCond: CONDITION; WaitForUnlocked: PUBLIC ENTRY PROC = BEGIN UNTIL unlockedMBX DO WAIT unlockedMBXCond ENDLOOP; unlockedMBX ← FALSE; END; NoteUnlocked: INTERNAL PROC = INLINE { unlockedMBX ← TRUE; NOTIFY unlockedMBXCond}; -- Access to mailboxes -- nextVirginPage: VMDefs.PageNumber; -- size of mailbox file -- WrongNewMBXPage: ERROR = CODE; mailboxContents: LONG CARDINAL ← 0; MBXWrite: PUBLIC ENTRY PROCEDURE [ who: BodyDefs.RName, obj: HeapDefs.ObjectNumber, postmark: BodyDefs.Timestamp] RETURNS [done: BOOLEAN] = BEGIN ENABLE UNWIND => NULL; addPage: BOOLEAN ← FALSE; page: VMDefs.Page; header: LONG POINTER TO MBXHeader; number: CARDINAL; -- number of a new page within this mailbox -- mbx: TreeRec = FindInTree[who: who]; WITH tree: mbx SELECT FROM empty => BEGIN IF tree.last = postmark THEN { LogDefs.WriteChar['E]; LogDefs.WriteChar['!]; RETURN[TRUE] --duplicate-- }; number ← 0; addPage ← TRUE; -- no interlock to check -- END; found => BEGIN page ← VMDefs.ReadPage[[handle, tree.where], lookAhead]; header ← LOOPHOLE[page, LONG POINTER] + FIRST[VMDefs.PageIndex]; IF header.first # header.free THEN -- test for duplicate elimination -- BEGIN x: LONG POINTER TO HeapDefs.ObjectNumber = LOOPHOLE[page, LONG POINTER] + header.free - SIZE[HeapDefs.ObjectNumber]; IF x↑ = obj THEN BEGIN VMDefs.Release[page]; LogDefs.WriteChar['E]; RETURN[TRUE] --duplicate-- END; END; IF LockedMBX[tree.lock] THEN BEGIN VMDefs.Release[page]; RETURN[FALSE] END --ELSE we are protected by our monitor lock -- ; IF header.free + SIZE[HeapDefs.ObjectNumber] > LAST[VMDefs.PageIndex] THEN -- will need to expand to another page -- BEGIN number ← header.number + 1; VMDefs.Release[page]; addPage ← TRUE; END; IF tree.oldest = BodyDefs.oldestTime AND NOT addPage THEN ChangeInTree[ who: who, where: tree.where, lock: tree.lock, oldest: postmark]; END; ENDCASE => ERROR; IF addPage THEN BEGIN -- create and initialise new page of mailbox -- desc: LONG DESCRIPTOR FOR ARRAY OF WORD = DESCRIPTOR[ who, BodyDefs.RNameSize[who]]; new: MBXName = BitMapDefs.FindFree[pageMap]; IF new >= nextVirginPage THEN IF new > nextVirginPage THEN ERROR WrongNewMBXPage[] ELSE nextVirginPage ← nextVirginPage + 1; BitMapDefs.Set[pageMap, new]; WITH tree: mbx SELECT FROM empty => BEGIN ChangeInTree[who: who, where: new, lock: new, oldest: postmark]; mailboxes ← mailboxes + 1; END; found => ChangeInTree[ who: who, where: new, lock: tree.lock, oldest: IF tree.oldest = BodyDefs.oldestTime THEN postmark ELSE tree.oldest]; ENDCASE => ERROR; page ← VMDefs.UsePage[[handle, new]]; header ← LOOPHOLE[page, LONG POINTER] + FIRST[VMDefs.PageIndex]; header.free ← FIRST[VMDefs.PageIndex] + SIZE[MBXHeader]; Inline.LongCOPY[BASE[desc], LENGTH[desc], page + header.free]; header.first ← header.free ← header.free + LENGTH[desc]; header.chain ← WITH tree: mbx SELECT FROM found => tree.where, ENDCASE => new -- end-of-chain -- ; header.number ← number; END; BEGIN x: LONG POINTER TO HeapDefs.ObjectNumber = LOOPHOLE[page, LONG POINTER] + header.free; x↑ ← obj; END; header.free ← header.free + SIZE[HeapDefs.ObjectNumber]; ObjectDirDefs.UseObject[obj]; mailboxContents ← mailboxContents + 1; -- global count -- VMDefs.MarkStartWait[page]; VMDefs.Release[page]; RETURN[TRUE] END; FreeMBXPage: ENTRY PROC [page: MBXName] = {BitMapDefs.Clear[pageMap, page]}; Poll: PUBLIC ENTRY PROCEDURE [who: BodyDefs.RName] RETURNS [found: BOOLEAN] = BEGIN value: TreeRec = FindInTree[who]; found ← WITH value SELECT FROM found => TRUE, ENDCASE => FALSE; END; Open: PUBLIC ENTRY PROCEDURE [who: BodyDefs.RName] RETURNS [found: BOOLEAN, count: CARDINAL, mbx: MBXHandle] = BEGIN IF MBXChain = MBXEnd THEN mbx ← Heap.systemZone.NEW[MBXData] ELSE BEGIN mbx ← MBXChain; MBXChain ← MBXChain.next END; [found, mbx.latest, mbx.lock] ← AcquireMBX[who: who]; IF found THEN BEGIN Process.InitializeMonitor[@mbx.LOCK]; SetupMBX[mbx]; count ← mbx.remainder; END ELSE BEGIN LogAction[who, nothing]; mbx.next ← MBXChain; MBXChain ← mbx; count ← 0; mbx ← NIL; END; END; Destroy: ENTRY PROC [mbx: MBXHandle] = BEGIN VMDefs.Release[mbx.page]; FreeMBX[mbx.lock]; mbx.next ← MBXChain; MBXChain ← mbx; END; RestartMBX: PROC [mbx: MBXHandle] = BEGIN -- re-positions to start of mailbox without releasing lock -- CloseArch[mbx]; VMDefs.Release[mbx.page]; SetupMBX[mbx]; END; SetupMBX: PROC [mbx: MBXHandle] = BEGIN mbx.current ← 0; mbx.page ← VMDefs.ReadPage[[handle, mbx.where ← mbx.latest], lookAhead]; mbx.remainder ← GetPage[mbx]; mbx.archObj ← ObjectDirDefs.noObject; mbx.archOpen ← FALSE; END; GetPage: PROCEDURE [mbx: MBXHandle] RETURNS [count: CARDINAL] = BEGIN header: LONG POINTER TO MBXHeader; count ← 0; IF mbx.where # mbx.latest THEN BEGIN -- finish with previous page -- VMDefs.Release[mbx.page]; mbx.page ← VMDefs.ReadPage[[handle, mbx.where ← mbx.latest], lookAhead]; END; DO BEGIN header ← LOOPHOLE[mbx.page, LONG POINTER] + FIRST[VMDefs.PageIndex]; count ← count + (header.free - header.first) / SIZE[HeapDefs.ObjectNumber]; IF header.number = mbx.current THEN EXIT; BEGIN next: MBXName = header.chain; -- that store is about to go -- VMDefs.Release[mbx.page]; mbx.where ← next; END; mbx.page ← VMDefs.ReadPage[[handle, mbx.where], lookAhead]; END; ENDLOOP; mbx.messages ← DESCRIPTOR[ mbx.page + header.first, (header.free - header.first) / SIZE[HeapDefs.ObjectNumber]]; mbx.nextMsg ← 0; mbx.current ← mbx.current + 1; END; NextMessage: PUBLIC PROC [mbx: MBXHandle] RETURNS [msgExists, archived, deleted: BOOLEAN] = { [msgExists, archived, deleted] ← RealNextMessage[mbx, yes]}; RealNextMessage: PROC [mbx: MBXHandle, testArchDeletion: {yes, no}] RETURNS [msgExists, archived, deleted: BOOLEAN] = BEGIN IF mbx.remainder = 0 THEN RETURN[FALSE, FALSE, FALSE]; IF mbx.nextMsg = LENGTH[mbx.messages] THEN [] ← GetPage[mbx]; IF LENGTH[mbx.messages] = 0 THEN ERROR; mbx.nextMsg ← mbx.nextMsg + 1; mbx.remainder ← mbx.remainder - 1; mbx.object ← mbx.messages[mbx.nextMsg - 1]; mbx.bodyObj ← GetBodyObj[mbx.object]; archived ← (mbx.bodyObj.type = archived); IF archived THEN BEGIN ENABLE BEGIN SpillDefs.ServerCrash => ERROR InaccessibleArchive[]; UNWIND => mbx.archOpen ← FALSE; END; IF mbx.bodyObj = mbx.archObj THEN mbx.archCount ← mbx.archCount + 1 ELSE {CloseArch[mbx]; mbx.archObj ← mbx.bodyObj; mbx.archCount ← 0}; IF testArchDeletion = yes THEN BEGIN OpenArch[mbx]; -- for deletion test! -- deleted ← SpillDefs.TestDeletion[mbx.archFile, mbx.archCount]; END ELSE deleted ← FALSE --don't care-- END ELSE BEGIN deleted ← (mbx.bodyObj = ObjectDirDefs.noObject); IF NOT deleted -- deleted msgs were skipped when archiving -- THEN {CloseArch[mbx]; mbx.archObj ← ObjectDirDefs.noObject}; END; msgExists ← TRUE; END; ReadTOC: PUBLIC PROC [mbx: MBXHandle, text: ProtocolDefs.Remark] = BEGIN IF mbx.object.type # TOC THEN text.length ← 0 ELSE BEGIN reader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[mbx.object]; obj: HeapDefs.ObjectNumber; [] ← HeapDefs.HeapReadData[reader, [@obj, SIZE[HeapDefs.ObjectNumber]]]; --CHEAT-- [] ← HeapDefs.HeapReadRName[reader, text]; HeapDefs.HeapEndRead[reader]; END; END; GetBodyObj: PROC [given: HeapDefs.ObjectNumber] RETURNS [body: HeapDefs.ObjectNumber] = BEGIN IF given.type = TOC THEN BEGIN tocReader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[given]; [] ← HeapDefs.HeapReadData[tocReader, [@body, SIZE[HeapDefs.ObjectNumber]]]; HeapDefs.HeapEndRead[tocReader]; END ELSE body ← given; END; InaccessibleArchive: PUBLIC ERROR = CODE; SendBody: PUBLIC PROC [mbx: MBXHandle, str: ProtocolDefs.Handle] = BEGIN IF mbx.bodyObj.type = archived THEN BEGIN ENABLE BEGIN SpillDefs.ServerCrash => ERROR InaccessibleArchive[]; UNWIND => mbx.archOpen ← FALSE; END; OpenArch[mbx]; SpillDefs.CopyBody[mbx.archFile, mbx.archCount, str]; CountArchived[]; END ELSE BEGIN reader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[mbx.bodyObj]; header: BodyDefs.ItemHeader = HeapDefs.ReadItemHeader[reader]; postmark: BodyDefs.Timestamp; IF header.type # PostMark THEN ERROR; [] ← HeapDefs.HeapReadData[reader, [@postmark, SIZE[BodyDefs.Timestamp]]]; ConsiderDelay[postmark]; ProtocolDefs.SendItemHeader[str, header]; ProtocolDefs.SendTimestamp[str, postmark]; HeapDefs.SendObj[reader, str]; END; END; SendText: PUBLIC PROC [ mbx: MBXHandle, sendLength: PROC [BodyDefs.ItemLength], str: SendDefs.Handle, sendBytes: PROC [SendDefs.Handle, LONG POINTER, CARDINAL]] = BEGIN IF mbx.bodyObj.type = archived THEN BEGIN ENABLE BEGIN SpillDefs.ServerCrash => ERROR InaccessibleArchive[]; UNWIND => mbx.archOpen ← FALSE; END; OpenArch[mbx]; SpillDefs.CopyText[mbx.archFile, mbx.archCount, sendLength, str, sendBytes]; CountArchived[]; END ELSE BEGIN reader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[mbx.bodyObj]; DO ENABLE UNWIND => HeapDefs.HeapEndRead[reader]; header: BodyDefs.ItemHeader = HeapDefs.ReadItemHeader[reader]; SELECT header.type FROM PostMark => BEGIN postmark: BodyDefs.Timestamp; [] ← HeapDefs.HeapReadData[ reader, [@postmark, SIZE[BodyDefs.Timestamp]]]; ConsiderDelay[postmark]; END; Text => BEGIN sendLength[header.length]; ReturnDefs.CopyItem[reader, header.length, sendBytes, str]; EXIT END; LastItem => EXIT --no text-- ; ENDCASE => HeapDefs.SetReaderOffset[ reader, HeapDefs.GetReaderOffset[reader] + (header.length + 1) / 2]; ENDLOOP; HeapDefs.HeapEndRead[reader]; END; END; OpenArch: PROC [mbx: MBXHandle] = BEGIN IF NOT mbx.archOpen THEN BEGIN mbx.archFile ← SpillDefs.OpenToRead[mbx.archObj]; mbx.archOpen ← TRUE; END; END; CloseArch: PROC [mbx: MBXHandle] = BEGIN IF mbx.archOpen THEN SpillDefs.EndReading[mbx.archFile ! SpillDefs.ServerCrash => CONTINUE]; mbx.archOpen ← FALSE; END; WriteTOC: PUBLIC PROC [mbx: MBXHandle, text: ProtocolDefs.Remark] = BEGIN Accept: PROC [obj: HeapDefs.ObjectNumber] = BEGIN ObjectDirDefs.UseObject[obj]; mbx.messages[mbx.nextMsg - 1] ← obj; VMDefs.MarkStartWait[mbx.page]; IF mbx.object.type = TOC THEN ObjectDirDefs.FreeObject[mbx.object]; mbx.object ← obj; END; body: HeapDefs.ObjectNumber ← mbx.bodyObj; IF text.length = 0 THEN BEGIN -- no TOC required -- IF mbx.object.type = TOC THEN { Accept[body]; ObjectDirDefs.FreeObject[body]}; END ELSE BEGIN writer: HeapDefs.WriterHandle = HeapDefs.HeapStartWrite[TOC]; HeapDefs.HeapWriteData[writer, [@body, SIZE[HeapDefs.ObjectNumber]]]; --CHEAT-- HeapDefs.HeapWriteRName[writer, text]; HeapDefs.HeapEndWrite[writer, Accept]; END; END; DeleteMessage: PUBLIC PROC [mbx: MBXHandle] = BEGIN IF mbx.bodyObj.type = archived THEN BEGIN ENABLE BEGIN SpillDefs.ServerCrash => ERROR InaccessibleArchive[]; UNWIND => mbx.archOpen ← FALSE; END; IF mbx.object.type = TOC THEN BEGIN mbx.messages[mbx.nextMsg - 1] ← mbx.bodyObj; VMDefs.MarkStartWait[mbx.page]; END; OpenArch[mbx]; SpillDefs.DeleteEntry[mbx.archFile, mbx.archCount]; END ELSE BEGIN mbx.messages[mbx.nextMsg - 1] ← ObjectDirDefs.noObject; VMDefs.MarkStartWait[mbx.page]; ObjectDirDefs.FreeObject[mbx.bodyObj]; mbx.bodyObj ← ObjectDirDefs.noObject; END; IF mbx.object.type = TOC THEN BEGIN ObjectDirDefs.FreeObject[mbx.object]; mbx.object ← mbx.bodyObj; END; END; Close: PUBLIC PROCEDURE [mbx: MBXHandle] = BEGIN LogAction[ LOOPHOLE[mbx.page, LONG POINTER] + FIRST[VMDefs.PageIndex] + SIZE[MBXHeader], no]; InnerClose[mbx]; END; InnerClose: PROC [mbx: MBXHandle] = BEGIN CloseArch[mbx]; Destroy[mbx]; END; ReadPostmark: PROC [body: HeapDefs.ObjectNumber] RETURNS [postmark: BodyDefs.Timestamp] = BEGIN reader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[body]; header: BodyDefs.ItemHeader = HeapDefs.ReadItemHeader[reader]; IF header.type # PostMark THEN ERROR; [] ← HeapDefs.HeapReadData[reader, [@postmark, SIZE[BodyDefs.Timestamp]]]; HeapDefs.HeapEndRead[reader]; END; FlushAndClose: PUBLIC PROCEDURE [mbx: MBXHandle] = BEGIN -- Beware: this procedure works without the monitor lock, because there -- may be unbounded delays while we delete archive files. The order of -- events is quite important! We must also take care that everything -- will look ok if we crash in the middle of flushing a multi-page -- mailbox. buffer: LONG DESCRIPTOR FOR ARRAY OF HeapDefs.ObjectNumber; base: VMDefs.PageIndex = FIRST[VMDefs.PageIndex]; header: LONG POINTER TO MBXHeader ← LOOPHOLE[mbx.page, LONG POINTER] + base; msgCount: CARDINAL ← 0; archiveFailure: BOOLEAN ← FALSE; lastPost: BodyDefs.Timestamp ← BodyDefs.oldestTime; -- postmark of latest message -- DeleteArch: PROC = -- delete archive file -- BEGIN IF mbx.archObj # ObjectDirDefs.noObject THEN BEGIN SpillDefs.Delete[ mbx.archObj ! SpillDefs.ServerCrash => {archiveFailure ← TRUE; CONTINUE}]; ObjectDirDefs.FreeObject[mbx.archObj]; mbx.archObj ← ObjectDirDefs.noObject; END; END; IF mbx.where # mbx.latest THEN BEGIN -- find first page -- VMDefs.Release[mbx.page]; mbx.page ← VMDefs.ReadPage[[handle, mbx.where ← mbx.latest], lookAhead]; END; CloseArch[mbx]; mbx.archObj ← ObjectDirDefs.noObject; DO who: BodyDefs.RName = LOOPHOLE[mbx.page, LONG POINTER] + base + SIZE[MBXHeader]; header ← LOOPHOLE[mbx.page, LONG POINTER] + base; buffer ← DESCRIPTOR[ mbx.page + header.first, (header.free - header.first) / SIZE[HeapDefs.ObjectNumber]]; msgCount ← msgCount + LENGTH[buffer]; BEGIN -- write MBX to disk as empty, in case of crash -- header.free ← header.first; VMDefs.MarkStartWait[mbx.page]; END; IF mbx.where = mbx.latest THEN BEGIN -- this mbx page contains last mbx message -- lastObj: HeapDefs.ObjectNumber = buffer[LENGTH[buffer] - 1]; IF lastObj # ObjectDirDefs.noObject THEN BEGIN body: HeapDefs.ObjectNumber = GetBodyObj[lastObj]; IF body.type # archived THEN lastPost ← ReadPostmark[body] -- ELSE lastPost is irrelevant! -- ; END; END; -- decrement reference counts -- FOR index: CARDINAL DECREASING IN [0..LENGTH[buffer]) DO obj: HeapDefs.ObjectNumber = buffer[index]; IF obj # ObjectDirDefs.noObject THEN BEGIN body: HeapDefs.ObjectNumber = GetBodyObj[obj]; IF obj.type = TOC THEN ObjectDirDefs.FreeObject[obj]; IF body # mbx.archObj THEN BEGIN DeleteArch[]; IF body.type = archived -- record for later deletion -- THEN {mbx.archObj ← body; ObjectDirDefs.UseObject[body]}; END; ObjectDirDefs.FreeObject[body]; END; mailboxContents ← mailboxContents - 1; ENDLOOP; -- Clearing bit in pageMap allows page to be re-used -- IF header.number = 0 -- we're at the end of the earliest page -- THEN BEGIN EmptyInTree[who, lastPost]; FreeMBXPage[mbx.where]; -- must not access page after here -- EXIT END; BEGIN next: MBXName = header.chain; --that store is about to go-- FreeMBXPage[mbx.where]; -- must not access page after here -- VMDefs.Release[mbx.page]; mbx.where ← next; mbx.page ← VMDefs.ReadPage[[handle, mbx.where], lookAhead]; END; ENDLOOP; DeleteArch[]; IF archiveFailure THEN LogDefs.ShowLine["Failed to delete mailbox archive file"L]; LogAction[ LOOPHOLE[mbx.page, LONG POINTER] + base + SIZE[MBXHeader], yes, msgCount]; Destroy[mbx]; END; AdjustOldest: ENTRY PROC [who: BodyDefs.RName, time: BodyDefs.Timestamp] = BEGIN -- alter "oldest" stamp after archiving -- prev: TreeRec = FindInTree[who: who]; WITH tree: prev SELECT FROM empty => ERROR; -- silly: we have an interlock on it! -- found => ChangeInTree[who: who, where: tree.where, lock: tree.lock, oldest: time]; ENDCASE => ERROR; END; Archive: PUBLIC PROC [who: BodyDefs.RName, keep: CARDINAL] = BEGIN mbx: MBXHandle; contents: CARDINAL; found: BOOLEAN; [found, contents, mbx] ← Open[who]; IF found AND contents > keep THEN BEGIN ENABLE UNWIND => Close[mbx]; msgExists, archived, deleted: BOOLEAN; archStart: CARDINAL ← 0; WHILE archStart < contents - keep -- skip to non-archived message -- DO [msgExists, archived, deleted] ← RealNextMessage[mbx, no]; IF NOT msgExists THEN ERROR; IF NOT archived AND NOT deleted THEN EXIT; archStart ← archStart + 1; ENDLOOP; IF archStart < contents - keep THEN BEGIN spillCount: CARDINAL = (contents - keep) - archStart; archObj: HeapDefs.ObjectNumber; Backup: PROC = BEGIN RestartMBX[mbx]; THROUGH [0..archStart] DO [msgExists, archived, deleted] ← RealNextMessage[mbx, no]; ENDLOOP; END; BEGIN ENABLE SpillDefs.ServerCrash => { ObjectDirDefs.FreeObject[archObj]; Backup[]; RETRY}; archObj ← SpillDefs.StartWrite[ who, spillCount ! SpillDefs.ServerCrash => ERROR InaccessibleArchive[]]; THROUGH [0..spillCount) DO IF NOT msgExists OR archived THEN ERROR; IF NOT deleted THEN SpillDefs.AddBody[mbx.bodyObj]; [msgExists, archived, deleted] ← NextMessage[mbx]; ENDLOOP; SpillDefs.EndWrite[]; END; Backup[]; THROUGH [0..spillCount) DO IF NOT deleted THEN BEGIN mbx.messages[mbx.nextMsg - 1] ← archObj; ObjectDirDefs.UseObject[archObj]; VMDefs.MarkStartWait[mbx.page]; IF mbx.object.type = TOC THEN ObjectDirDefs.FreeObject[mbx.object]; ObjectDirDefs.FreeObject[mbx.bodyObj]; END; [msgExists, archived, deleted] ← NextMessage[mbx]; ENDLOOP; AdjustOldest[ who, IF msgExists THEN ReadPostmark[mbx.bodyObj] ELSE BodyDefs.oldestTime]; ObjectDirDefs.FreeObject[archObj]; LogAction[who, arch, spillCount]; END; END; IF found THEN InnerClose[mbx]; END; defaultDays: CARDINAL = 7; archiveDays: CARDINAL ← defaultDays; ReadArchiveDays: ENTRY PROC RETURNS [days: CARDINAL] = INLINE { days ← archiveDays; archiveDays ← defaultDays}; SetArchiveDays: PUBLIC ENTRY PROC [days: CARDINAL] = {archiveDays ← days}; Archiver: PROC = BEGIN DO PolicyDefs.ArchiverPause[]; PolicyDefs.WaitOperation[archiver]; IF ~ArchivingMakesSense[] THEN LOOP; BEGIN writer: HeapDefs.WriterHandle = HeapDefs.HeapStartWrite[temp]; archivePeriod: CARDINAL ← ReadArchiveDays[]; now: BodyDefs.PackedTime = Time.Current[]; old: BodyDefs.PackedTime = now - LONG[archivePeriod] * 24 * 60 * 60; count: CARDINAL ← 0; Work: PROC [who: BodyDefs.RName, tree: found TreeRec] = BEGIN IF tree.oldest # BodyDefs.oldestTime AND tree.oldest.time < old THEN { HeapDefs.HeapWriteRName[writer, who]; count ← count + 1}; END; EnumerateTree[Work]; IF count = 0 THEN HeapDefs.HeapAbandonWrite[writer] ELSE BEGIN reader: HeapDefs.ReaderHandle; GetReader: PROC [obj: HeapDefs.ObjectNumber] = { reader ← HeapDefs.HeapStartRead[obj]}; LogDefs.ShowNumber["Starting mailbox archiving. ("L, archivePeriod, " day cutoff)."L]; HeapDefs.HeapEndWrite[writer, GetReader]; THROUGH [0..count) DO once: BOOLEAN ← FALSE; who: BodyDefs.RName = [BodyDefs.maxRNameLength]; [] ← HeapDefs.HeapReadRName[reader, who]; BEGIN ENABLE InaccessibleArchive => { LogAction[who, archFail]; RETRY; }; IF once THEN { PolicyDefs.EndOperation[archiver]; PolicyDefs.Wait[mins: 15]; PolicyDefs.WaitOperation[archiver]; }; once ← TRUE; Archive[who, 0]; END; ENDLOOP; HeapDefs.HeapEndRead[reader]; LogDefs.ShowLine["Finished archiving mailboxes."L]; END; END; PolicyDefs.EndOperation[archiver]; ENDLOOP; END; ArchivingMakesSense: PROC RETURNS [ok: BOOL ← FALSE] = { LookAtServer: PROC [name: BodyDefs.RName] RETURNS [done: BOOLEAN] = { ok ← TRUE; RETURN[TRUE]; }; listInfo: NameInfoDefs.MemberInfo; serverListPrefix: STRING = "Archive-"L; serverListName: BodyDefs.RName = [BodyDefs.maxRNameLength]; String.AppendString[serverListName, serverListPrefix]; String.AppendString[serverListName, myName]; listInfo ← NameInfoDefs.GetMembers[serverListName]; WITH listInfo SELECT FROM group => { NameInfoDefs.Enumerate[members, LookAtServer]; NameInfoDefs.Close[members]; }; ENDCASE => NULL; }; Remail: PUBLIC PROC [who: BodyDefs.RName, valid: BOOLEAN] = BEGIN RemailMessage: PROC [body: HeapDefs.ObjectNumber] = BEGIN log: STRING = [64]; slHeader: SLDefs.SLHeader; slHeader.created ← ReadPostmark[body]; slHeader.received ← [0, 0, Time.Current[]]; String.AppendString[log, "Remailing "L]; ProtocolDefs.AppendTimestamp[log, slHeader.created]; String.AppendString[log, " as "L]; slHeader.created.time ← slHeader.received.time; ProtocolDefs.AppendTimestamp[log, slHeader.created]; LogDefs.WriteLogEntry[log]; slHeader.server ← NIL; BEGIN -- write steering list -- sl: HeapDefs.WriterHandle = HeapDefs.HeapStartWrite[SLinput]; HeapDefs.HeapWriteData[sl, [@slHeader, SIZE[SLDefs.SLHeader]]]; HeapDefs.HeapWriteRName[sl, who]; SLDefs.SLWrite[body: body, SL: sl, queue: input]; END; END; found: BOOLEAN; count: CARDINAL; mbx: MBXHandle; [found, count, mbx] ← Open[who]; IF found THEN BEGIN IF valid THEN BEGIN ENABLE UNWIND => Close[mbx]; LogAction[who, remail]; THROUGH [1..count] DO msgExists, archived, deleted: BOOLEAN; [msgExists, archived, deleted] ← NextMessage[mbx]; IF NOT msgExists THEN ERROR; IF deleted THEN LOOP; BEGIN ENABLE UNWIND => PolicyDefs.EndOperation[remailing]; PolicyDefs.WaitOperation[remailing]; IF mbx.bodyObj.type = archived THEN BEGIN ENABLE SpillDefs.ServerCrash => {mbx.archOpen ← FALSE; GOTO inaccessible}; OpenArch[mbx]; HeapDefs.HeapEndWrite[ SpillDefs.RecoverBody[mbx.archFile, mbx.archCount], RemailMessage]; EXITS inaccessible => {ERROR InaccessibleArchive[]}; END ELSE RemailMessage[mbx.bodyObj]; DeleteMessage[mbx]; PolicyDefs.EndOperation[remailing]; END; ENDLOOP; END ELSE LogAction[who, dead]; FlushAndClose[mbx]; END; PurgeFromTree[who]; END; s: STRING = "Birrell.pa"; --for use from the debugger-- LogAction: PROCEDURE [ who: BodyDefs.RName, action: {yes, no, nothing, arch, archFail, remail, dead}, data: CARDINAL ← 0] = BEGIN log: LONG STRING ← Heap.systemZone.NEW[StringBody[128]]; String.AppendString[log, "Mailbox "L]; String.AppendString[log, who]; String.AppendString[ log, SELECT action FROM nothing => ": already empty"L, no => ": abandoned reading"L, yes => ": emptied, "L, arch => ": archived "L, archFail => ": archive failed"L, remail => ": remailing"L, dead => ": dead"L, ENDCASE => ERROR]; SELECT action FROM yes, arch => BEGIN String.AppendDecimal[log, data]; String.AppendString[log, " messages"L]; END; ENDCASE => NULL; LogDefs.WriteLogEntry[log]; Heap.systemZone.FREE[@log]; IF action = remail THEN { LogDefs.WriteString[" Remail "L]; LogDefs.WriteString[who]}; END; MailboxCount: PUBLIC PROC [ str: EnquiryDefs.Handle, nameSought: BodyDefs.RName] = BEGIN OPEN str; allNames: BOOLEAN ← String.EquivalentStrings[nameSought, "*"L]; encountered: LONG CARDINAL ← 0; -- should be equivalent to mailboxContents freePageCount: CARDINAL ← 0; WriteChar[Ascii.CR]; FOR pageNumber: CARDINAL IN [0..nextVirginPage) DO page: VMDefs.Page = VMDefs.ReadPage[[handle, pageNumber], 0]; BEGIN ENABLE UNWIND => VMDefs.Release[page]; header: LONG POINTER TO MBXHeader = LOOPHOLE[page]; IF header.first = header.free THEN freePageCount ← freePageCount + 1 ELSE BEGIN name: BodyDefs.RName = LOOPHOLE[page, LONG POINTER] + SIZE[MBXHeader]; count: CARDINAL = (header.free - header.first) / SIZE[HeapDefs.ObjectNumber]; objects: LONG DESCRIPTOR FOR ARRAY OF HeapDefs.ObjectNumber = DESCRIPTOR[ LOOPHOLE[page, LONG POINTER] + header.first, count]; tocCount: CARDINAL ← 0; delCount: CARDINAL ← 0; archCount: CARDINAL ← 0; IF ~allNames AND ~String.EquivalentStrings[nameSought, name] THEN GOTO tryNextName; WriteString[name]; WriteChar['(]; WriteDecimal[header.number]; WriteChar[')]; WriteString[": "L]; WriteDecimal[count]; encountered ← encountered + count; WriteString[" messages"L]; FOR this: CARDINAL IN [0..count) DO SELECT objects[this].type FROM TOC => tocCount ← tocCount + 1; archived => archCount ← archCount + 1; gap => delCount ← delCount + 1; ENDCASE => NULL; ENDLOOP; IF tocCount # 0 THEN { WriteString["; "L]; WriteDecimal[tocCount]; WriteString[" TOC's"L]}; IF delCount # 0 THEN { WriteString["; "L]; WriteDecimal[delCount]; WriteString[" deleted"L]}; IF archCount # 0 THEN { WriteString["; "L]; WriteDecimal[archCount]; WriteString[" archived"L]}; IF header.number = 0 THEN BEGIN LookInTree: ENTRY PROC RETURNS [TreeRec] = INLINE { RETURN[FindInTree[name]]}; mbx: TreeRec = LookInTree[]; WriteString["; "L]; WITH tree: mbx SELECT FROM empty => WriteString[" *** not in tree!"L]; found => IF tree.oldest = BodyDefs.oldestTime THEN WriteString["no date"L] ELSE BEGIN t: STRING = [18]; -- 29-Feb-99 24:23:22 -- Time.Append[t, Time.Unpack[LOOPHOLE[tree.oldest.time]]]; WriteString[t]; END; ENDCASE => WriteString[" *** illegal tree entry"L]; END; WriteChar[Ascii.CR]; END; EXITS tryNextName => NULL; END; VMDefs.Release[page]; IF DelTyped[] THEN GOTO del; ENDLOOP; IF allNames THEN BEGIN WriteDecimal[freePageCount]; WriteString[" empty pages"L]; WriteChar[Ascii.CR]; WriteString["Number of mailboxes with content = "L]; WriteLongDecimal[mailboxes]; WriteChar[Ascii.CR]; WriteString["Total of messages in mailboxes (some are duplicates) = "L]; WriteLongDecimal[encountered]; WriteChar[Ascii.CR]; END; EXITS del => {str.Flush[]; str.WriteString[" XXX"L]}; END; HistBox: TYPE = {min17, min53, min160, hr8, day1, day3, day9, longer, archived}; histInterval: ARRAY HistBox OF CARDINAL = [ (8 * 60) / 27, (8 * 60) / 9, (8 * 60) / 3, 8 * 60, 3 * 8 * 60, 9 * 8 * 60, 27 * 8 * 60, LAST[CARDINAL], LAST[CARDINAL]]; histCount: ARRAY HistBox OF CARDINAL ← ALL[0]; histTotal: LONG CARDINAL ← 0; ConsiderDelay: ENTRY PROC [postmark: BodyDefs.Timestamp] = BEGIN now: LONG CARDINAL = Time.Current[]; FOR box: HistBox IN [FIRST[HistBox]..longer) DO limit: LONG CARDINAL = now - LONG[60] * histInterval[box]; IF postmark.time > limit THEN {histCount[box] ← histCount[box] + 1; EXIT}; REPEAT FINISHED => histCount[longer] ← histCount[longer] + 1 ENDLOOP; histTotal ← histTotal + 1; END; CountArchived: ENTRY PROC = INLINE { histCount[archived] ← histCount[archived] + 1; histTotal ← histTotal + 1}; Histograms: PUBLIC PROC [str: EnquiryDefs.Handle] = BEGIN OPEN str; histCaption: ARRAY HistBox OF STRING = [ "17 min: "L, "53 min: "L, "2.7 hr: "L, "8 hrs: "L, "24 hrs: "L, "3 days: "L, "9 days: "L, "longer: "L, "archiv: "L]; WriteChar[Ascii.CR]; WriteString["Mail retrieval delays"L]; WriteChar[Ascii.CR]; FOR box: HistBox IN HistBox DO WriteString[histCaption[box]]; WriteDecimal[histCount[box]]; WriteChar[Ascii.TAB]; IF histTotal = 0 THEN WriteChar['?] ELSE WriteLongDecimal[(histCount[box] * LONG[100]) / histTotal]; WriteChar['%]; WriteChar[Ascii.CR]; ENDLOOP; WriteString["total: "L]; WriteLongDecimal[histTotal]; END; LogDefs.DisplayNumber["Mailboxes"L, [short[@mailboxes]]]; String.AppendString[myName, LocalNameDefs.ReadMSName[].name]; END.