-- Transport Mechanism - mailbox and R-Server site cache --
-- [Indigo]<Grapevine>MS>SiteCache.mesa
-- Andrew Birrell 4-Mar-82 15:24:41 --
-- Mike Schroeder 26-Jan-83 10:05:32 --
DIRECTORY
BodyDefs USING[ maxRNameLength, oldestTime, RName, Timestamp],
BTreeDefs USING[ BTreeHandle, Call, CreateAndInitializeBTree,
Delete, EnumerateFrom, Insert, KeyNotFound, Lookup,
TestKeys ],
HeapDefs USING[ HeapEndRead ],
NameInfoDefs USING[ CheckStamp, Close, Enumerate, Expand, ExpandInfo ],
ServerDefs USING[ ServerHandle, ServerIsLocal, GetServer, ServerUp ],
SiteCacheDefs USING[ ForeignDL, RecipientInfo, RemailInfo],
VMDefs USING[ OpenFile ];
SiteCache: MONITOR
IMPORTS BTreeDefs, HeapDefs, NameInfoDefs, ServerDefs, SiteCacheDefs,
VMDefs
EXPORTS SiteCacheDefs =
BEGIN
tree: BTreeDefs.BTreeHandle ← LOOPHOLE[NIL];
-- Management of Btree entries --
LowerCase: PROCEDURE[c: CHARACTER] RETURNS[CHARACTER] = INLINE
{ RETURN[IF c IN ['A..'Z] THEN c - 'A + 'a ELSE c] };
IsFirstGE: BTreeDefs.TestKeys =
BEGIN
-- parameters a,b: DESC FOR ARRAY OF WORD returns[ BOOLEAN]--
aC: POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[a]];
bC: POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[b]];
FOR i:CARDINAL IN [0..2*MIN[LENGTH[a],LENGTH[b]])
DO IF LowerCase[aC[i]] < LowerCase[bC[i]] THEN RETURN[FALSE];
IF LowerCase[aC[i]] > LowerCase[bC[i]] THEN RETURN[TRUE];
ENDLOOP;
RETURN[LENGTH[a] >= LENGTH[b]];
END;
AreTheyEq: BTreeDefs.TestKeys =
BEGIN
aC: POINTER TO PACKED ARRAY OF CHARACTER =LOOPHOLE[BASE[a]];
bC: POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[b]];
IF LENGTH[a] = LENGTH[b]
THEN FOR i:CARDINAL IN [0..2*LENGTH[a])
DO IF LowerCase[aC[i]] # LowerCase[bC[i]] THEN EXIT;
REPEAT FINISHED => RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
END;
RNameDesc: PROCEDURE[ name: BodyDefs.RName ]
RETURNS[ DESCRIPTOR FOR ARRAY OF WORD ] =
BEGIN
IF name.length MOD 2 # 0 THEN name[name.length] ← '@;
RETURN[ DESCRIPTOR[ @(name.text), (1+name.length)/2 ] ]
END;
TreeData: TYPE =
RECORD[ visibility: {visible, gone} --internal to BTree--,
primary: BOOLEAN,
var: SELECT type:* FROM
found => [ server: ServerDefs.ServerHandle ],
allDown => [ stamp: BodyDefs.Timestamp ],
valid => NULL --name ok, but we don't know details--,
notInTree => NULL --doesn't appear in BTree--,
ENDCASE ];
FindInTree: ENTRY PROCEDURE[ who: BodyDefs.RName ]
RETURNS[ data: TreeData ] =
BEGIN
desc: DESCRIPTOR FOR ARRAY OF WORD = RNameDesc[who];
length:CARDINAL = BTreeDefs.Lookup[tree, desc,
DESCRIPTOR[@data,SIZE[TreeData]] ];
IF length # BTreeDefs.KeyNotFound
THEN IF data.visibility = visible
THEN RETURN
ELSE BEGIN
BTreeDefs.Delete[tree, desc ];
RETURN[ [visible, TRUE, notInTree[]] ]
END
ELSE RETURN[ [visible, TRUE, notInTree[]] ]
END;
ChangeInTree: ENTRY PROCEDURE[ who: BodyDefs.RName, data: TreeData ] =
BEGIN
prev: TreeData;
desc: DESCRIPTOR FOR ARRAY OF WORD = RNameDesc[who];
IF BTreeDefs.Lookup[tree, desc, DESCRIPTOR[@prev,SIZE[TreeData]] ]
# BTreeDefs.KeyNotFound
THEN BTreeDefs.Delete[tree, desc ];
IF data.type # notInTree
THEN BEGIN
data.visibility ← visible;
BTreeDefs.Insert[tree, desc,
DESCRIPTOR[@data, SELECT data.type FROM
found => SIZE[found TreeData],
allDown => SIZE[allDown TreeData],
valid => SIZE[valid TreeData],
ENDCASE => ERROR] ];
END;
END;
SingleFlush: PUBLIC PROCEDURE[ who: BodyDefs.RName ] =
BEGIN
foreign: BodyDefs.RName = [BodyDefs.maxRNameLength];
ChangeInTree[who, [,,notInTree[]]];
[] ← MakeRNameBeForeign[who, foreign];
ChangeInTree[foreign, [,,notInTree[]]];
END;
SelectiveFlush: PUBLIC ENTRY PROCEDURE[ given: ServerDefs.ServerHandle ] =
BEGIN
-- Flushes from the site cache those entries which are non-primary and
-- indicate a server other than the given one. That is, the only entries
-- not flushed are those which are primary and those which are for the
-- given server. This is called by UpServer.
Action: BTreeDefs.Call =
BEGIN
record: POINTER TO TreeData = LOOPHOLE[ BASE[v] ];
more ← TRUE; dirty ← FALSE;
IF LENGTH[k] = 0 THEN RETURN;
WITH record SELECT FROM
found => IF server # given
AND NOT primary
AND record.visibility = visible
THEN { record.visibility ← gone; dirty ← TRUE };
allDown, valid => IF record.visibility = visible
THEN { record.visibility ← gone; dirty ← TRUE };
ENDCASE => ERROR;
END;
BTreeDefs.EnumerateFrom[tree, DESCRIPTOR[NIL,0], Action];
END;
-- R-Name lookup --
SubMBXSite: PROCEDURE[ who, really: BodyDefs.RName ]
RETURNS[ SiteCacheDefs.RecipientInfo ] =
BEGIN
-- Finds MBX site or distribution list contents, using cache if possible --
-- BEWARE: If "who" describes a distribution list, this procedure starts
-- a reader for it. In particular, to discriminate on the variant record
-- returned, do not write "WITH FindMBXSite[....] SELECT", since each
-- access to the opened fields of the record causes "FindMBXSite[....]" to
-- be re-evaluated, causing another reader to be started!
found: BOOLEAN ← FALSE;
isLocal: BOOLEAN ← FALSE;
primary: BOOLEAN ← TRUE;
server: ServerDefs.ServerHandle;
treeData: TreeData = FindInTree[who];
stamp: BodyDefs.Timestamp ← BodyDefs.oldestTime;
WITH t: treeData SELECT FROM
found =>
BEGIN
isLocal ← ServerDefs.ServerIsLocal[server ← t.server];
found ← isLocal OR ServerDefs.ServerUp[server];
END;
allDown =>
stamp ← t.stamp;
notInTree, valid =>
NULL;
ENDCASE => ERROR;
IF NOT found
THEN -- ask registration server --
BEGIN
info: NameInfoDefs.ExpandInfo =
-- should just call Expand, but at present that always causes a
-- reader to be opened in the R-Server, so use CheckStamp first.
IF treeData.type = allDown
AND NameInfoDefs.CheckStamp[who,stamp] = noChange
THEN [noChange[]]
ELSE NameInfoDefs.Expand[who, stamp];
WITH i: info SELECT FROM
notFound =>
BEGIN
IF treeData.type # notInTree
THEN ChangeInTree[who, [,,notInTree[]] ];
RETURN[ [notFound[]] ];
END;
allDown, noChange =>
NULL;
group =>
BEGIN
IF treeData.type # valid
THEN ChangeInTree[who, [,,valid[]] ];
RETURN[ [dl[i.members]] ];
END;
individual =>
BEGIN
--scan list of mailbox sites--
hasNoSites: BOOLEAN ← TRUE;
Work: PROC[site: BodyDefs.RName] RETURNS[done: BOOLEAN] =
BEGIN
hasNoSites ← FALSE;
server ← ServerDefs.GetServer[[rName[site]],mail];
isLocal ← ServerDefs.ServerIsLocal[server];
--If network is broken, ServerIsLocal may call DownServer--
--So we get more predictable results by calling it early--
IF ServerDefs.ServerUp[server]
THEN { found ← done ← TRUE; }
ELSE { primary ← FALSE; done ← FALSE };
END;
stamp ← i.stamp;
NameInfoDefs.Enumerate[i.sites, Work];
NameInfoDefs.Close[i.sites];
IF hasNoSites THEN RETURN[ [notFound[]] ];
IF found THEN ChangeInTree[who, [,primary,found[server]] ];
END;
ENDCASE => ERROR;
END;
IF found
THEN BEGIN
-- the server may, of course, go down after we find it --
IF who # really
THEN ChangeInTree[really, [,primary,found[server]] ];
RETURN[ IF isLocal THEN [local[]] ELSE [found[server]] ]
END
ELSE BEGIN
IF treeData.type # allDown
THEN ChangeInTree[who, [,,allDown[stamp]] ];
RETURN[ [allDown[]] ]
END
END;
NeedToRemail: PUBLIC PROCEDURE[ who: BodyDefs.RName ]
RETURNS[ SiteCacheDefs.RemailInfo ] =
BEGIN
info: NameInfoDefs.ExpandInfo ← NameInfoDefs.Expand[who];
WITH i: info SELECT FROM
notFound =>
BEGIN
-- Note that we always remail if foreign registry exists --
foreign: BodyDefs.RName = [BodyDefs.maxRNameLength];
[] ← MakeRNameBeForeign[who, foreign];
info ← NameInfoDefs.Expand[foreign];
WITH i: info SELECT FROM
notFound => RETURN [invalid];
allDown, noChange => NULL;
group => NameInfoDefs.Close[i.members];
individual => NameInfoDefs.Close[i.sites];
ENDCASE => ERROR;
RETURN[remail];
END;
allDown, noChange =>
RETURN[remail];
group =>
{NameInfoDefs.Close[i.members]; RETURN[remail]};
individual =>
BEGIN
--scan list of mailbox sites--
rI: SiteCacheDefs.RemailInfo ← invalid;
Work: PROC[site: BodyDefs.RName] RETURNS[done: BOOLEAN] =
BEGIN
server: ServerDefs.ServerHandle =
ServerDefs.GetServer[[rName[site]],mail];
IF ServerDefs.ServerIsLocal[server]
THEN {rI ← stillLocal; done ← TRUE}
ELSE {rI ← remail; done ← FALSE};
END;
NameInfoDefs.Enumerate[i.sites, Work];
NameInfoDefs.Close[i.sites];
RETURN[rI];
END;
ENDCASE => ERROR;
END;
FindMBXSite: PUBLIC PROCEDURE[ who: BodyDefs.RName ]
RETURNS[ info: SiteCacheDefs.RecipientInfo ] =
BEGIN
-- this layer is for the MTP recipient kludge --
info ← SubMBXSite[who, who];
IF info.type = notFound
THEN BEGIN
foreign: BodyDefs.RName = [BodyDefs.maxRNameLength];
upArrow: BOOLEAN = MakeRNameBeForeign[who, foreign];
info ← SubMBXSite[foreign, IF upArrow THEN foreign ELSE who];
IF upArrow AND info.type = found
THEN info ← SiteCacheDefs.ForeignDL[who, info];
END;
END;
MakeRNameBeForeign: PROC[sname, destination: BodyDefs.RName]
RETURNS[ upArrow: BOOLEAN ] =
{ RETURN[ MakeRNameHaveNA["Foreign"L, sname, destination] ] };
MakeRNameHaveNA: PROC[nname, sname, destination: BodyDefs.RName]
RETURNS[ upArrow: BOOLEAN ] =
BEGIN
-- nname is of the form "NA" or "NA.something"
-- sname is of form "something.SN" or "SN"
-- assumes that SN and NA do not contain '.
-- constructs "SN.NA", truncating NA if needed
SNstart: CARDINAL;
sep: CHARACTER = '.;
destination.length ← 0;
SNstart ← sname.length;
UNTIL SNstart = 0 OR sname[SNstart-1] = sep
DO SNstart ← SNstart - 1; ENDLOOP;
upArrow ← (SNstart > 1 AND sname[SNstart-2] = '↑);
FOR index: CARDINAL IN [SNstart..sname.length)
DO IF destination.length = destination.maxlength THEN ERROR;
destination[destination.length] ← sname[index];
destination.length ← destination.length + 1;
ENDLOOP;
IF destination.length = destination.maxlength THEN RETURN;
destination[destination.length] ← sep;
destination.length ← destination.length + 1;
FOR index: CARDINAL IN [0..nname.length)
WHILE nname[index] # sep
DO IF destination.length = destination.maxlength THEN EXIT;
destination[destination.length] ← nname[index];
destination.length ← destination.length + 1;
ENDLOOP;
END;
ValidateRName: PUBLIC PROCEDURE[ who: BodyDefs.RName ]
RETURNS[ ok: BOOLEAN ] =
BEGIN
treeData: TreeData = FindInTree[who];
IF treeData.type # notInTree
THEN ok ← TRUE
ELSE SELECT NameInfoDefs.CheckStamp[who] FROM
notFound =>
BEGIN
foreign: BodyDefs.RName = [BodyDefs.maxRNameLength];
upArrow: BOOLEAN = MakeRNameBeForeign[who, foreign];
fInfo: SiteCacheDefs.RecipientInfo =
SubMBXSite[foreign, IF upArrow THEN foreign ELSE who];
ok ← fInfo.type # notFound;
IF ok AND upArrow THEN ChangeInTree[who, [,,valid[]] ];
END;
group =>
BEGIN
ok ← TRUE; ChangeInTree[who, [,,valid[]] ];
END;
ENDCASE => -- includes "individual" and silly replies --
BEGIN
info: SiteCacheDefs.RecipientInfo = SubMBXSite[who, who];
WITH i:info SELECT FROM
allDown => ok ← TRUE;
notFound => ok ← FALSE;
dl => { ok ← TRUE; NameInfoDefs.Close[i.members] };
foreign => { ok ← TRUE; HeapDefs.HeapEndRead[i.members] };
local, found => ok ← TRUE;
ENDCASE => ERROR;
END;
END;
CheckPrimary: PUBLIC PROC[who: BodyDefs.RName,
server: ServerDefs.ServerHandle]
RETURNS[ BOOLEAN ] =
BEGIN
-- in case of doubt, it's safe to return FALSE --
SingleFlush[who];
IF ValidateRName[who]
THEN BEGIN
treeData: TreeData = FindInTree[who];
WITH t:treeData SELECT FROM
found => RETURN[t.primary AND t.server = server];
ENDCASE--may possibly include notInTree!-- => RETURN[FALSE]
END
ELSE RETURN[FALSE]
END;
Init: ENTRY PROCEDURE =
BEGIN
tree ← BTreeDefs.CreateAndInitializeBTree [
fileH: LOOPHOLE[VMDefs.OpenFile[options:oldOrNew,
name:"SiteCache.BTree", cacheFraction:10] ],
initializeFile: TRUE, useDefaultOrderingRoutines: FALSE,
isFirstGreaterOrEqual: IsFirstGE,
areTheyEqual: AreTheyEq ];
END;
Init[];
END.