-- Transport Mechanism Registration Server - Access to entries.

-- [Juniper]<Grapevine>MS>RegAccess.mesa

-- Andrew Birrell  17-Jul-81 13:48:50 

DIRECTORY
BodyDefs	USING [maxRNameLength, RName, Timestamp],
HeapDefs	USING [HeapAbandonWrite,
		       HeapEndRead, ObjectOffset,
		       objectStart, ReaderHandle,
		       SetReaderOffset,
		       WriterHandle],
LocalNameDefs	USING[ ReadRSName ],
LogDefs		USING[ WriteLogEntry, WriteString ],
ProtocolDefs	USING[ ReturnCode, RNameType ],
RegAccessDefs	USING[ NameState ],
RegBTreeDefs	USING[ Insert, KnownRegistry, Lookup,
		       LookupReason, RegistryObject, RegState,
		       TestKnownReg, UpdateFailed ],
RegistryDefs	USING[ EnumerateRList ],
RegServerDefs	USING[ EnumeratedMembers, IsInList, Membership,
		       MembershipLevel ],
SendDefs	USING[ AddRecipient, AddToItem, Create, Destroy, Handle,
		       StartItem, StartSend, StartSendInfo, Send ],
String		USING [AppendString, EquivalentStrings,
		       EquivalentSubStrings, SubStringDescriptor];

RegAccess: MONITOR
   IMPORTS HeapDefs, LocalNameDefs, LogDefs, RegBTreeDefs, RegistryDefs,
           RegServerDefs, SendDefs, String
   EXPORTS RegAccessDefs =

BEGIN

EndsWith: PROC[s: STRING, b: STRING] RETURNS[ BOOLEAN ] =
   BEGIN
   pattern: String.SubStringDescriptor ← [b, 0, b.length];
   target: String.SubStringDescriptor ← [s,s.length-b.length,b.length];
   RETURN[ s.length >= b.length
           AND String.EquivalentSubStrings[@pattern,@target] ]
   END;



-- ================ Synchronization for Registry changes ================ --

-- This layer is concerned with the integrity of the "Registry" concept,
-- determining whether entries are in known registries and handling
-- changes in the set of known registries.
-- Under our monitor lock, Lookup determines whether this is the correct
-- R-Server for the lookup.  All updates also go through the monitor
-- lock, and retry if we have become (or ceased to be) the correct
-- server for that registry.  This includes the updates which alter our
-- set of registries.  The BTree changes provoked by the RegPurger are
-- exempt from this.

Lookup: PUBLIC PROC[name: BodyDefs.RName,
                    reason: RegBTreeDefs.LookupReason]
            RETURNS[info: RegAccessDefs.NameState] =
   BEGIN
   IF EndsWith[name, ".GV"L]
   THEN -- avoid deadlocking recursion on updates to GV names --
        BEGIN
        treeInfo: RegBTreeDefs.RegistryObject =
                                      RegBTreeDefs.Lookup[name, reason];
        info ← [yes --Beware: BTree may not have knownReg bits yet! --,
                treeInfo.type, treeInfo.stamp, treeInfo.reader];
        END
   ELSE info ← InnerLookup[name, reason];
   END;

InnerLookup: PUBLIC ENTRY PROC[name: BodyDefs.RName,
                          reason: RegBTreeDefs.LookupReason]
                RETURNS[info: RegAccessDefs.NameState] =
   BEGIN
   treeInfo: RegBTreeDefs.RegistryObject =
                                      RegBTreeDefs.Lookup[name, reason];
   info ← [yes, treeInfo.type, treeInfo.stamp, treeInfo.reader];
   IF info.type = notFound THEN info.regState ← CheckReg[name];
   END;


-- This module also contains a layer for producing MS internal mail.
-- For "Insert" where the old type was Individual, the old reader
-- is positioned to the mailbox site list.
-- To avoid problems at start-of-world, MS-mail is disabled in the early
-- stages of some restarts - see RegRestart.

OldReaderNeeded: ERROR = CODE;
CantCreateMSInternalMail: ERROR = CODE;
MSMailEnabled: BOOLEAN ← FALSE;

IsMSMailEnabled: ENTRY PROC RETURNS[ BOOLEAN ] = INLINE
   { RETURN[MSMailEnabled] };
   
EnableMSMail: ENTRY PROC = INLINE
   { MSMailEnabled ← TRUE };

Insert: PUBLIC PROCEDURE [name:   BodyDefs.RName,
                          type:   ProtocolDefs.RNameType,
                          stamp:  POINTER TO BodyDefs.Timestamp,
                          writer: HeapDefs.WriterHandle,
                          oldInfo:   POINTER TO RegAccessDefs.NameState]
      RETURNS[done: BOOLEAN] =
   BEGIN
   -- compose any message to mail servers before destroying the old reader,
   -- but we must commit the update before sending the mail (to avoid
   -- remote possibility of M-Server reading mail and re-evaluating site
   -- with old entry before we commit the update!).
   IF oldInfo.type = individual AND IsMSMailEnabled[]
   THEN BEGIN
        humanHint: STRING = "MS Internal mail for R-Name "L;
        myName: BodyDefs.RName;
        myPassword: STRING;
        mail: SendDefs.Handle = SendDefs.Create[];
        sendInfo: SendDefs.StartSendInfo;
        [myName, myPassword,] ← LocalNameDefs.ReadRSName[];
        sendInfo ← SendDefs.StartSend[
           handle: mail,
           sender: myName, senderPwd: myPassword,
           returnTo: "DeadLetter.MS"L, validate: FALSE];
        IF sendInfo # ok
        THEN ERROR CantCreateMSInternalMail[];
        IF oldInfo.reader = NIL THEN ERROR OldReaderNeeded[];
        -- oldInfo.reader exists and is at his mailbox site list --
        CopyRList[mail, oldInfo.reader];
        SendDefs.StartItem[mail, reMail];
        SendDefs.AddToItem[mail, DESCRIPTOR[ @(name.text), name.length]];
        SendDefs.StartItem[mail, Text];-- in case it gets to DeadLetter! --
        SendDefs.AddToItem[mail, DESCRIPTOR[ @(humanHint.text),
                                             humanHint.length ] ];
        SendDefs.AddToItem[mail, DESCRIPTOR[ @(name.text), name.length]];
        done ← ActualInsert[name, type, stamp, writer, oldInfo];
        IF done THEN SendDefs.Send[mail];
        SendDefs.Destroy[mail];
        END
   ELSE done ← ActualInsert[name, type, stamp, writer, oldInfo];
   END;

CopyRList: PROCEDURE[ message: SendDefs.Handle,
                      reader: HeapDefs.ReaderHandle] =
   BEGIN
   Work: PROC[name: BodyDefs.RName] RETURNS[done:BOOLEAN] =
      BEGIN
      done ← FALSE;
      FOR i: CARDINAL DECREASING IN [0..name.length)
      DO IF name[i] = '. THEN EXIT; REPEAT
      FINISHED => GOTO notGV
      ENDLOOP;
      SendDefs.AddRecipient[message, name];
      EXITS notGV => -- foreign mail server site --
        NULL;
      END;
   RegistryDefs.EnumerateRList[reader, Work];
   END;


ActualInsert: ENTRY PROC[ name: BodyDefs.RName,
                 type: ProtocolDefs.RNameType,
                 stamp: POINTER TO BodyDefs.Timestamp,
                 writer: HeapDefs.WriterHandle,
                 oldInfo: POINTER TO RegAccessDefs.NameState ]
      RETURNS[done: BOOLEAN] =
   BEGIN
   -- If done=FALSE at return, then oldInfo↑ has been updated --
   ENABLE UNWIND => NULL;
   treeInfo: RegBTreeDefs.RegistryObject ←
         [oldInfo.type, oldInfo.stamp, oldInfo.reader];
   nameIsGVGV: BOOLEAN = String.EquivalentStrings[name, "GV.GV"L];
   -- "GV.GV" is special only during the InitializeWorld restart sequence--
   IF oldInfo.type = notFound AND NOT nameIsGVGV
   THEN BEGIN
	newRegState: RegBTreeDefs.RegState = CheckReg[name];
        IF newRegState # oldInfo.regState
        THEN BEGIN
             IF writer # NIL THEN HeapDefs.HeapAbandonWrite[writer];
             IF oldInfo.reader # NIL
             THEN HeapDefs.SetReaderOffset[oldInfo.reader, 
                                           HeapDefs.objectStart];
             oldInfo.regState ← newRegState;
             RETURN[FALSE]
             END;
	END;
   BEGIN
      CheckOneOfMine: INTERNAL PROC[type: ProtocolDefs.RNameType]
                             RETURNS[BOOLEAN] =
         BEGIN
         -- determines whether "name" is a registry known to this server --
         -- don't call ReadRSName[] when creating FirstRS.gv! --
         RETURN[ type = group
           AND EndsWith[name, ".GV"L]
           AND ( nameIsGVGV
               OR RegServerDefs.IsInList[name,
                                         LocalNameDefs.ReadRSName[].name,
                                         direct, self, members
                                         ].membership
                     = yes
               ) ]
         END;
      wasMine: BOOLEAN = CheckOneOfMine[oldInfo.type];
      isMine: BOOLEAN;
      RegBTreeDefs.Insert[name, type, stamp, writer, @treeInfo !
         RegBTreeDefs.UpdateFailed => {treeInfo ← info; GOTO failed}];
      -- BTree sets knownReg bit FALSE --
      done ← TRUE;
      isMine ← CheckOneOfMine[type];
      SELECT TRUE FROM
        isMine =>
          { RegBTreeDefs.KnownRegistry[name, TRUE];
            IF NOT wasMine THEN LogAddition[name] };
        wasMine AND NOT isMine =>
          { RegBTreeDefs.KnownRegistry[name, FALSE];
            LogRemoval[name];
            StartChanger[name, FALSE] -- may unlock monitor -- };
      ENDCASE => NULL;
      EXITS failed =>
         BEGIN
         oldInfo↑ ← [CheckReg[name],
                     treeInfo.type, treeInfo.stamp, treeInfo.reader];
         done ← FALSE;
         END;
   END;
   END;

LogAddition: INTERNAL PROC[name: BodyDefs.RName] =
   BEGIN
   log: STRING = [84];
   String.AppendString[log, "New registry "L];
   String.AppendString[log, name];
   LogDefs.WriteLogEntry[log]; LogDefs.WriteString[log];
   END;

LogRemoval: INTERNAL PROC[name: BodyDefs.RName] =
   BEGIN
   log: STRING = [84];
   String.AppendString[log, "Remove registry "L];
   String.AppendString[log, name];
   LogDefs.WriteLogEntry[log]; LogDefs.WriteString[log];
   END;

BadRegName: ERROR = CODE;

regChangerIdle: BOOLEAN ← FALSE;
regChangerWanted: BOOLEAN ← FALSE;
regChangerCond: CONDITION;
regChangerName: BodyDefs.RName;
regChangerAdd: BOOLEAN;

StartChanger: INTERNAL PROC[name: BodyDefs.RName, add: BOOLEAN] =
   BEGIN
   UNTIL regChangerIdle DO WAIT regChangerCond ENDLOOP;
   regChangerIdle ← FALSE; -- prevent others attempting to call it --   
   regChangerName ← name;
   regChangerAdd ← add;
   regChangerWanted ← TRUE;-- ask it to listen --
   BROADCAST regChangerCond;
   WHILE regChangerWanted DO WAIT regChangerCond ENDLOOP;--let it take args--
   END;

GetChangerArg: ENTRY PROC[regName: BodyDefs.RName] RETURNS[add: BOOLEAN] =
   BEGIN
   regChangerIdle ← TRUE; BROADCAST regChangerCond; -- open for calls --
   UNTIL regChangerWanted DO WAIT regChangerCond ENDLOOP;
   regName.length ← 0;
   String.AppendString[regName, "All."L];
   FOR i: CARDINAL DECREASING IN [0..regChangerName.length)
   DO IF regChangerName[i] = '.
      THEN BEGIN
           realLength: CARDINAL = regChangerName.length;
           regChangerName.length ← i;
           String.AppendString[regName, regChangerName];
           regChangerName.length ← realLength;
           EXIT
           END;
   REPEAT
   FINISHED => ERROR BadRegName[];
   ENDLOOP;
   add ← regChangerAdd;
   regChangerWanted ← FALSE; BROADCAST regChangerCond;-- free caller --
   END;

RegChanger: PROC =
   BEGIN
   DO regName: BodyDefs.RName = [BodyDefs.maxRNameLength];
      IF GetChangerArg[regName]
      THEN ERROR --add registry --
      ELSE DoRemoval[regName];
   ENDLOOP;
   END;

DoRemoval: PROC[regName: BodyDefs.RName] =
   BEGIN
   -- regName is "All.reg" --
   LogDefs.WriteLogEntry["Starting removal"L];
   BEGIN
      reader: HeapDefs.ReaderHandle =
                RegServerDefs.EnumeratedMembers[regName, notFound--ugh!--];
      Action: PROC[entry: BodyDefs.RName] RETURNS[done: BOOLEAN] =
         BEGIN
         done ← FALSE;
         RegBTreeDefs.Insert[entry, notFound,NIL,NIL,NIL];
         END;
      RegistryDefs.EnumerateRList[reader, Action];
      HeapDefs.HeapEndRead[reader];
   END;
   LogDefs.WriteLogEntry["End of removal"L];
   END;

CheckReg: INTERNAL PROC[name: BodyDefs.RName]
                RETURNS[RegBTreeDefs.RegState] =
   BEGIN
   -- determines whether "name" is in a valid and/or local registry --
   RETURN[ RegBTreeDefs.TestKnownReg[name] ]
   END;

Abandon: PUBLIC PROCEDURE[name: BodyDefs.RName,
                   nameObj: POINTER TO RegAccessDefs.NameState]
           RETURNS[rc: ProtocolDefs.ReturnCode] =
  BEGIN
  IF nameObj.reader # NIL THEN HeapDefs.HeapEndRead[nameObj.reader];
  rc ← IF nameObj.type = notFound AND nameObj.regState = no
       THEN [WrongServer, nameObj.type]
       ELSE [BadRName, nameObj.type];
  END;


regChangerProcess: PROCESS = FORK RegChanger[];

STOP;

EnableMSMail[];

END.